diff --git a/app/src/main/java/com/tangem/tap/common/url/CustomTabsUrlOpener.kt b/app/src/main/java/com/tangem/tap/common/url/CustomTabsUrlOpener.kt index 965d6caacd..de65ad7aa7 100644 --- a/app/src/main/java/com/tangem/tap/common/url/CustomTabsUrlOpener.kt +++ b/app/src/main/java/com/tangem/tap/common/url/CustomTabsUrlOpener.kt @@ -3,12 +3,12 @@ package com.tangem.tap.common.url import android.content.Context import android.content.Intent import android.content.pm.PackageManager -import android.net.Uri import androidx.browser.customtabs.CustomTabColorSchemeParams import androidx.browser.customtabs.CustomTabsClient import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_DARK import androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_LIGHT +import androidx.core.net.toUri import com.tangem.core.navigation.url.UrlOpener import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder import com.tangem.tap.common.extensions.getColorCompat @@ -25,30 +25,37 @@ internal class CustomTabsUrlOpener : UrlOpener { } } + override fun openUrlExternalBrowser(url: String) { + foregroundActivityObserver.withForegroundActivity { context -> + if (url.isEmpty()) return@withForegroundActivity + val browserIntent = Intent(Intent.ACTION_VIEW, url.toUri()) + context.startActivity(browserIntent) + } + } + private fun openUrl(url: String, context: Context) { if (url.isEmpty()) return - val customTabsIntent = CustomTabsIntent.Builder() - .setDefaultColorSchemeParams( - CustomTabColorSchemeParams.Builder() - .setNavigationBarColor(context.getColorCompat(R.color.toolbarColor)) - .build(), - ) - .setColorScheme( - if (MutableAppThemeModeHolder.isDarkThemeActive) COLOR_SCHEME_DARK else COLOR_SCHEME_LIGHT, - ) - .build() - - customTabsIntent.intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP - - val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + val browserIntent = Intent(Intent.ACTION_VIEW, url.toUri()) runCatching { if (checkCustomTabsAvailability(context, browserIntent)) { context.startActivity(browserIntent) } else { - customTabsIntent.launchUrl(context, Uri.parse(url)) + val customTabsIntent = CustomTabsIntent.Builder() + .setDefaultColorSchemeParams( + CustomTabColorSchemeParams.Builder() + .setNavigationBarColor(context.getColorCompat(R.color.toolbarColor)) + .build(), + ) + .setColorScheme( + if (MutableAppThemeModeHolder.isDarkThemeActive) COLOR_SCHEME_DARK else COLOR_SCHEME_LIGHT, + ) + .build() + + customTabsIntent.intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP + customTabsIntent.launchUrl(context, url.toUri()) } }.onFailure { - Timber.e(it.message) + Timber.e(it) } } diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index d33f1e1360..6ecb2f5ac6 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -21,7 +21,7 @@ }, { "name": "WALLET_CONNECT_REDESIGN_ENABLED", - "version": "undefined" + "version": "5.27.0" }, { "name": "PUSH_NOTIFICATIONS_ENABLED", diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/AssetDiff.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/AssetDiff.kt index 51e5374ae8..be70cd4a52 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/AssetDiff.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/AssetDiff.kt @@ -15,8 +15,8 @@ data class AssetDiff( data class Asset( @Json(name = "chain_id") val chainId: Int? = null, @Json(name = "logo_url") val logoUrl: String? = null, - @Json(name = "symbol") val symbol: String, - @Json(name = "decimals") val decimals: Int, + @Json(name = "symbol") val symbol: String? = null, + @Json(name = "decimals") val decimals: Int? = null, ) @JsonClass(generateAdapter = true) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/Exposure.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/Exposure.kt index 0ed36e6a25..e776056d4c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/Exposure.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/Exposure.kt @@ -18,6 +18,8 @@ data class SpenderDetails( @JsonClass(generateAdapter = true) data class ExposureDetail( - @Json(name = "value") val value: String, - @Json(name = "raw_value") val rawValue: String, + @Json(name = "value") val value: String? = null, + @Json(name = "raw_value") val rawValue: String? = null, + @Json(name = "token_id") val tokenId: String? = null, + @Json(name = "logo_url") val logoUrl: String? = null, ) \ No newline at end of file diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/url/DummyUrlOpener.kt b/core/navigation/src/main/java/com/tangem/core/navigation/url/DummyUrlOpener.kt index cff4946b40..d47667b064 100644 --- a/core/navigation/src/main/java/com/tangem/core/navigation/url/DummyUrlOpener.kt +++ b/core/navigation/src/main/java/com/tangem/core/navigation/url/DummyUrlOpener.kt @@ -5,4 +5,8 @@ class DummyUrlOpener : UrlOpener { override fun openUrl(url: String) { /* no-op */ } + + override fun openUrlExternalBrowser(url: String) { + /* no-op */ + } } \ No newline at end of file diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/url/UrlOpener.kt b/core/navigation/src/main/java/com/tangem/core/navigation/url/UrlOpener.kt index d78ba1b1eb..df94cdfacf 100644 --- a/core/navigation/src/main/java/com/tangem/core/navigation/url/UrlOpener.kt +++ b/core/navigation/src/main/java/com/tangem/core/navigation/url/UrlOpener.kt @@ -3,4 +3,6 @@ package com.tangem.core.navigation.url interface UrlOpener { fun openUrl(url: String) + + fun openUrlExternalBrowser(url: String) } \ No newline at end of file diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 075f05f465..b28e3174c2 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -66,6 +66,10 @@ %d carte %d cartes + + %s token + %s tokens + Désactivez cette option si vous ne voulez pas que cette carte soit utilisée pour réinitialiser les codes d\'accès sur d\'autres cartes de ce portefeuille. Veuillez noter que cela vous empêchera également de réinitialiser le code d\'accès sur cette carte. Vous permet d\'utiliser cette carte pour réinitialiser le code d\'accès sur d\'autres cartes de ce portefeuille Récupération du code d\'accès @@ -111,6 +115,7 @@ Annuler Changez Choisissez une action + Choisissez le réseau Choisir le jeton Réclamer Réclamez des récompenses @@ -152,6 +157,7 @@ Synchroniser les adresses Aller au fournisseur Aller au jeton + Compris Cacher heure Importez @@ -196,6 +202,7 @@ Le serveur n\'est pas disponible, veuillez réessayer plus tard Partager Partager le lien + Afficher moins Afficher plus Signez Signez et envoyez @@ -205,6 +212,7 @@ Soumettre Avec succès Support + Réseaux pris en charge Échanger termes et conditions Conditions d\'utilisation @@ -265,6 +273,7 @@ %s hashes ID de l\'appareil Contactez l\'équipe de support + Ouvrir le chat d\'assistance Lier plus de cartes Monnaie de l\'application Retourner pour masquer les soldes @@ -281,6 +290,7 @@ Ne peut pas être échangé contre %s Fourni par Statut + Choisir un fournisseur Tangem propose des échanges de jetons via des fournisseurs tiers selon les conditions de chaque fournisseur Fournisseur Une erreur s\'est produite. Code : %s @@ -340,6 +350,9 @@ Masquer cette transaction Une fois masqué, le statut de la transaction ne peut plus être consulté. Vous pouvez simplement faire glisser votre doigt pour le fermer. Masquer le statut de la transaction ? + Ce jeton n\'est pas pris en charge. Veuillez choisir un autre jeton à échanger. + %s n\'est pas pris en charge + Échanger avec Aucun jeton trouvé. Veuillez essayer une autre demande ID : %s ID de transaction copié @@ -367,6 +380,8 @@ Pour continuer, accordez aux smart contracts de %1s l\'autorisation d\'utiliser votre %2s Donner l\'autorisation Illimité + Ajouter un Portefeuille existant + Créer un nouveau Portefeuille Commandez Scannez à %s @@ -423,6 +438,7 @@ Cet actif n\'est pas pris en charge dans le portefeuille Cet actif n\'est pas disponible pour ce portefeuille Ajoutez un jeton + APY %s Réseaux disponibles Mon portfolio Marché @@ -709,6 +725,7 @@ Paramètres Activer les notifications Recevez des alertes pour les transactions entrantes sur les réseaux pris en charge. + Notifications de transaction Sélectionnez dans la galerie Paramètres Vous n\'avez pas donné accès à votre caméra @@ -818,6 +835,9 @@ Mémo : %s Mémo invalide Couverture des frais de réseau + Nonce + Numéro unique pour chaque transaction. Utilisez-le pour renvoyer ou annuler une transaction en attente. + Entrez le nonce… Fonds insuffisants pour le transfert, car le total des frais et du montant du transfert dépasse le solde existant Le total dépasse le solde Un solde d\'au moins %s est requis pour conserver votre compte sur la blockchain afin d\'éviter les risques de sécurité. Ce montant restera sur votre solde et ne pourra pas être retiré. @@ -847,6 +867,7 @@ Adresse non valide Assurez-vous que l\'adresse du portefeuille de réception est sur le réseau %s pour éviter de perdre vos jetons envoyer à %s + Assurez-vous de %s une adresse réseau, car des erreurs peuvent entraîner des transferts perdus Envoyer à Un Memo/Destination Tag est un identifiant unique permettant de différencier les transactions envoyées au même destinataire sur le même réseau. Attention : L\'omission d\'un mémo peut entraîner des fonds mal placés Le Memo / Destination Tag est un code qui distingue les transactions envoyées à un destinataire partagé sur un réseau crypto. @@ -873,7 +894,11 @@ Les frais de commissions dépassent le solde Le total dépasse le solde Échanger et envoyer + Envoyez n\'importe quel jeton et nous le convertirons en cours de route. Votre destinataire reçoit exactement ce dont il a besoin, en toute simplicité. + Le destinataire recevra + Un destinataire sera envoyé Montant à recevoir + Envoyer avec swap Transaction envoyée Scannez la carte/ bague que vous souhaitez configurer. Oublier le portefeuille @@ -1014,6 +1039,7 @@ Compatible avec Web 3.0 Une transaction entrante d\'au moins de %1$s est requise pour continuer Fonds insuffisants + Taux fixe Le réseau facturera des frais d\'approbation de jeton pour vérifier que vous autorisez l\'utilisation de votre jeton pour l\'échange. Échangez plus de jetons à de meilleurs taux directement dans votre portefeuille. Nouveau fournisseur d\'échange disponible ! @@ -1091,6 +1117,8 @@ Notifications de transaction Minimum %s Le montant minimum pour effectuer cette transaction est %1$s. + Les frais de réseau Tron pour les jetons populaires peuvent être plus élevés. Le staking de TRX peut contribuer à réduire les coûts de transaction. + Économisez sur les frais de réseau Tron Réessayez Vous avez scanné la même carte. Pour créer un portefeuille jumeau, vous devez scanner la carte portant le numéro %d Vous avez scanné une mauvaise carte jumelle. S\'il vous plaît, essayez-en un autre @@ -1103,6 +1131,8 @@ Tangem Twin Cette action est irréversible. Vous n\'aurez plus accès à l\'ancien portefeuille. Appuyez sur la carte jumelle avec le numéro %s et ne la retirez pas jusqu\'à la fin de l\'opération + Veuillez réessayer ultérieurement. Si le problème persiste, veuillez contacter le support. + Une erreur s\'est produite ! Nous avons rencontré une erreur. Code d\'erreur : %s. Veuillez contacter notre équipe de support. Utilisez %s ou scannez une carte/bague pour avoir accès à votre portefeuille Échec de la connexion : Cette dApp utilise Wallet Connect version1.0, qui n\'est pas prise en charge. Veuillez vous assurer que la dApp prend en charge Wallet Connect version2.0 pour réussir la connexion. @@ -1253,6 +1283,7 @@ Votre avis nous motive à améliorer encore le Portefeuille Tangem Vous appréciez Tangem ? Vous devez associer votre jeton avant de recevoir des jetons + Vous devez ouvrir la ligne de confiance pour votre jeton avant de le recevoir Frais de location de réseau requis Action requise Avez-vous contacté l\'assistance via l\'application ou par e-mail dans les 7 jours suivant la création d\'un portefeuille ? Si vous l\'avez fait ou si vous n\'êtes pas sûr, suivez et complétez les instructions. @@ -1273,17 +1304,38 @@ À des fins de test uniquement Le solde peut être obsolète. Rafraîchissez la page. Pas assez de %1$s. Rechargez votre compte %2$s pour associer ce jeton. + Activer Trustline + Une Trustline doit être activée pour recevoir ce jeton. Le réseau requiert une réserve de %1$s %2$s + Trustline requise Domaine malveillant + Signer quand même Si le problème persiste, n’hésitez pas à contacter notre support. Le portefeuille Tangem ne prend actuellement pas en charge %ss dApp non prise en charge + Code d\'erreur : 8 005. Si le problème persiste, n\'hésitez pas à contacter notre support. + Nous avons rencontré une erreur inconnue + Autoriser à dépenser + Adresse + Chargement + Illimité Connexions + Contenu + Copier les données Déconnecter tout Texte sur la déconnexion de toutes les dApps Déconnecter toutes les dApps + Modifications estimées du portefeuille + La transaction n\'a pas pu être simulée. Veuillez procéder avec prudence. + Transaction malveillante Nouvelle connexion Connectez votre portefeuille à différentes dApps Aucune séance + Demande de + Type de signature + À + Demande de transaction + Demande de transaction + Montant illimité Ignorer Vous avez une sauvegarde interrompue. Voulez-vous la reprendre ? Oui, reprendre diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index a8961f599c..704c48f319 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -12,6 +12,7 @@ Set a %s-digit Access Code to unlock your wallet. Create Access Code Access code + Account name 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 @@ -1500,6 +1501,7 @@ New connection Connect your wallet to a different dApps No sessions + No wallet changes detected This domain is flagged as unsafe by multiple security providers. Leave immediately to protect your assets Known security risk Request from @@ -1511,6 +1513,7 @@ Ensure that each pairing attempt uses a fresh and unique URI URI already used Wallet connect + Suspicious transaction Discard You have an interrupted backup. Do you want to resume? Yes, resume diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt index 1e2279b8c2..7b1baa0109 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt @@ -214,12 +214,15 @@ internal fun TextsBlock( SpacerH(height = TangemTheme.dimens.spacing2) } - Text( - text = subtitle.resolveReference(), - color = subtitleColor, - style = TangemTheme.typography.caption2, - modifier = Modifier.testTag(NotificationTestTags.TEXT), - ) + val subtitleText = subtitle.resolveReference() + if (subtitleText.isNotEmpty()) { + Text( + text = subtitleText, + color = subtitleColor, + style = TangemTheme.typography.caption2, + modifier = Modifier.testTag(NotificationTestTags.TEXT), + ) + } } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/tooltip/TangemTooltip.kt b/core/ui/src/main/java/com/tangem/core/ui/components/tooltip/TangemTooltip.kt index b436ca3287..395b09aed0 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/tooltip/TangemTooltip.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/tooltip/TangemTooltip.kt @@ -22,9 +22,15 @@ import com.tangem.core.ui.res.TangemThemePreview import kotlinx.coroutines.launch @Composable -fun TangemTooltip(text: String, content: @Composable (Modifier) -> Unit, modifier: Modifier = Modifier) { +fun TangemTooltip( + text: String, + content: @Composable (Modifier) -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, +) { InternalTangemTooltip( modifier = modifier, + enabled = enabled, tooltipContent = { Text( modifier = Modifier.background(TangemTheme.colors.icon.secondary), @@ -38,9 +44,15 @@ fun TangemTooltip(text: String, content: @Composable (Modifier) -> Unit, modifie } @Composable -fun TangemTooltip(text: AnnotatedString, content: @Composable (Modifier) -> Unit, modifier: Modifier = Modifier) { +fun TangemTooltip( + text: AnnotatedString, + content: @Composable (Modifier) -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, +) { InternalTangemTooltip( modifier = modifier, + enabled = enabled, tooltipContent = { Text( modifier = Modifier.background(TangemTheme.colors.icon.secondary), @@ -59,6 +71,7 @@ private fun InternalTangemTooltip( tooltipContent: @Composable () -> Unit, content: @Composable (Modifier) -> Unit, modifier: Modifier = Modifier, + enabled: Boolean = true, ) { val tooltipState = rememberTooltipState(isPersistent = true) val coroutineScope = rememberCoroutineScope() @@ -78,11 +91,14 @@ private fun InternalTangemTooltip( ) }, content = { - content( + val contentModifier = if (enabled) { Modifier.clickableSingle( onClick = { coroutineScope.launch { tooltipState.show() } }, - ), - ) + ) + } else { + Modifier + } + content(contentModifier) }, ) } diff --git a/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidMapper.kt b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidMapper.kt index e245687538..128d1cfd56 100644 --- a/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidMapper.kt +++ b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidMapper.kt @@ -16,6 +16,7 @@ import org.json.JSONArray private const val SUCCESS_STATUS = "Success" private const val DOMAIN_CHECKED_STATUS = "hit" private const val VALIDATION_SAFE_STATUS = "Benign" +private const val VALIDATION_WARNING_STATUS = "Warning" internal object BlockAidMapper { @@ -32,6 +33,7 @@ internal object BlockAidMapper { validation = when { from.validation.status != SUCCESS_STATUS -> ValidationResult.FAILED_TO_VALIDATE from.validation.resultType == VALIDATION_SAFE_STATUS -> ValidationResult.SAFE + from.validation.resultType == VALIDATION_WARNING_STATUS -> ValidationResult.WARNING else -> ValidationResult.UNSAFE }, simulation = if (from.simulation.status != SUCCESS_STATUS) { @@ -83,7 +85,7 @@ internal object BlockAidMapper { from.exposures, ) !from.traces.isNullOrEmpty() -> mapNftSendReceiveTransaction(from.traces) - else -> SimulationResult.FailedToSimulate + else -> SimulationResult.Success(data = SimulationData.NoWalletChangesDetected) } } @@ -92,15 +94,15 @@ internal object BlockAidMapper { val tokenInfo = TokenInfo( chainId = exposure.asset.chainId, logoUrl = exposure.asset.logoUrl, - symbol = exposure.asset.symbol, - decimals = exposure.asset.decimals, + symbol = exposure.asset.symbol ?: "", + decimals = exposure.asset.decimals ?: 0, ) exposure.spenders.flatMap { (_, spender) -> val isUnlimited = spender.isApprovedForAll == true val approval = spender.approval?.hexToBigDecimal() - spender.exposure.mapNotNull { detail -> + spender.exposure.map { detail -> ApprovedAmount( - approvedAmount = detail.value.toBigDecimalOrNull() ?: approval ?: return@mapNotNull null, + approvedAmount = detail.value?.toBigDecimalOrNull() ?: approval ?: 1.toBigDecimal(), isUnlimited = isUnlimited, tokenInfo = tokenInfo, ) @@ -110,7 +112,7 @@ internal object BlockAidMapper { return if (!amounts.isNullOrEmpty()) { SimulationResult.Success(SimulationData.Approve(amounts)) } else { - SimulationResult.FailedToSimulate + SimulationResult.Success(SimulationData.NoWalletChangesDetected) } } @@ -122,8 +124,8 @@ internal object BlockAidMapper { val token = TokenInfo( chainId = diff.asset.chainId, logoUrl = diff.asset.logoUrl, - symbol = diff.asset.symbol, - decimals = diff.asset.decimals, + symbol = diff.asset.symbol ?: "", + decimals = diff.asset.decimals ?: 0, ) diff.outTransfer.orEmpty().forEach { transfer -> transfer.value?.toBigDecimalOrNull()?.let { amount -> @@ -140,7 +142,7 @@ internal object BlockAidMapper { return if (sendInfo.isNotEmpty() || receiveInfo.isNotEmpty()) { SimulationResult.Success(SimulationData.SendAndReceive(send = sendInfo, receive = receiveInfo)) } else { - SimulationResult.FailedToSimulate + SimulationResult.Success(SimulationData.NoWalletChangesDetected) } } @@ -154,7 +156,7 @@ internal object BlockAidMapper { return if (!sendInfo.isNullOrEmpty()) { SimulationResult.Success(SimulationData.SendAndReceive(send = sendInfo, receive = listOf())) } else { - SimulationResult.FailedToSimulate + SimulationResult.Success(SimulationData.NoWalletChangesDetected) } } } \ No newline at end of file diff --git a/data/blockaid/src/test/kotlin/com/tangem/data/blockaid/BlockAidMapperTest.kt b/data/blockaid/src/test/kotlin/com/tangem/data/blockaid/BlockAidMapperTest.kt index 1980d63605..00dd54e198 100644 --- a/data/blockaid/src/test/kotlin/com/tangem/data/blockaid/BlockAidMapperTest.kt +++ b/data/blockaid/src/test/kotlin/com/tangem/data/blockaid/BlockAidMapperTest.kt @@ -114,7 +114,7 @@ class BlockAidMapperTest { val result = mapper.mapToDomain(response) Truth.assertThat(result.validation).isEqualTo(ValidationResult.FAILED_TO_VALIDATE) - Truth.assertThat(result.simulation is SimulationResult.FailedToSimulate).isTrue() + Truth.assertThat(result.simulation is SimulationResult.Success).isTrue() } @Test @@ -160,6 +160,6 @@ class BlockAidMapperTest { ) val result = mapper.mapToDomain(txResponse) - Truth.assertThat(result.simulation is SimulationResult.FailedToSimulate).isTrue() + Truth.assertThat(result.simulation is SimulationResult.Success).isTrue() } } \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt index 40593c27e5..0ee7dab28e 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt @@ -225,8 +225,12 @@ internal object WalletConnectDataModule { @Provides @Singleton - fun wcRequestUseCaseFactory(diHelperBox: DiHelperBox): WcRequestUseCaseFactory { - return DefaultWcRequestUseCaseFactory(diHelperBox.handlers) + fun wcRequestUseCaseFactory( + diHelperBox: DiHelperBox, + namespaceConverters: Set<@JvmSuppressWildcards WcNamespaceConverter>, + analytics: AnalyticsEventHandler, + ): WcRequestUseCaseFactory { + return DefaultWcRequestUseCaseFactory(diHelperBox.handlers, namespaceConverters, analytics) } @Provides diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt index 7b7823fd7f..0f9e21543f 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt @@ -7,7 +7,6 @@ import arrow.core.right import com.squareup.moshi.Moshi import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.ExcludedBlockchains -import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.data.walletconnect.model.CAIP2 import com.tangem.data.walletconnect.model.NamespaceKey import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter @@ -15,7 +14,6 @@ import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter.Compani import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext import com.tangem.data.walletconnect.utils.WcNamespaceConverter import com.tangem.data.walletconnect.utils.WcNetworksConverter -import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.walletconnect.model.* import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest @@ -156,16 +154,6 @@ internal class WcEthNetwork( val ethChainId = chainId.reference.toIntOrNull() ?: return null return Blockchain.fromChainId(ethChainId) } - - override fun toCAIP2(network: Network): CAIP2? { - val blockchain = network.toBlockchain() - if (!blockchain.isEvm()) return null - val chainId = blockchain.getChainId() ?: return null - return CAIP2( - namespace = namespaceKey.key, - reference = chainId.toString(), - ) - } } internal class Factories @Inject constructor( diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSendTransactionUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSendTransactionUseCase.kt index 0621f51855..5358c3a317 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSendTransactionUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSendTransactionUseCase.kt @@ -41,6 +41,9 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor( WcMutableFee { private var approvalAmount: WcApprovedAmount? = null + // in case of change TransactionExtras + // change approvalAmount for example + private var isIgnoreDAppFee: Boolean = false private var dAppFee: Fee? = null override val securityStatus: LceFlow = @@ -102,7 +105,7 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor( }, ) approvalAmount = action.amount - dAppFee = null + isIgnoreDAppFee = true uncompiled.copy(extras = extras.copy(callData = callData)) } is WcEthTxAction.UpdateFee -> uncompiled.copy(fee = action.fee) @@ -111,8 +114,11 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor( emit(newState) } - override fun dAppFee(): Fee? { - return dAppFee + override suspend fun dAppFee(): Fee? { + if (isIgnoreDAppFee) return null + if (dAppFee != null) return dAppFee + return ethTxHelper.getDAppFee(method.transaction, wallet, network) + .also { dAppFee = it } } override fun updateFee(fee: Fee) { @@ -120,9 +126,8 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor( } override fun invoke(): Flow> = flow { - dAppFee = ethTxHelper.getDAppFee(method.transaction, wallet, network) val transactionData = ethTxHelper.createTransactionData( - dAppFee = dAppFee, + dAppFee = dAppFee(), network = context.network, txParams = method.transaction, ) ?: return@flow diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSignTransactionUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSignTransactionUseCase.kt index eda9598750..b6795bf29b 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSignTransactionUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSignTransactionUseCase.kt @@ -41,6 +41,9 @@ internal class WcEthSignTransactionUseCase @AssistedInject constructor( WcMutableFee { private var approvalAmount: WcApprovedAmount? = null + // in case of change TransactionExtras + // change approvalAmount for example + private var isIgnoreDAppFee: Boolean = false private var dAppFee: Fee? = null override val securityStatus = blockAidDelegate.getSecurityStatus( @@ -101,7 +104,7 @@ internal class WcEthSignTransactionUseCase @AssistedInject constructor( }, ) approvalAmount = action.amount - dAppFee = null + isIgnoreDAppFee = true uncompiled.copy(extras = extras.copy(callData = callData)) } is WcEthTxAction.UpdateFee -> uncompiled.copy(fee = action.fee) @@ -115,17 +118,19 @@ internal class WcEthSignTransactionUseCase @AssistedInject constructor( } override fun invoke(): Flow> = flow { - dAppFee = ethTxHelper.getDAppFee(method.transaction, wallet, network) val transactionData = ethTxHelper.createTransactionData( - dAppFee = dAppFee, + dAppFee = dAppFee(), network = context.network, txParams = method.transaction, ) ?: return@flow emitAll(delegate.invoke(transactionData)) } - override fun dAppFee(): Fee? { - return dAppFee + override suspend fun dAppFee(): Fee? { + if (isIgnoreDAppFee) return null + if (dAppFee != null) return dAppFee + return ethTxHelper.getDAppFee(method.transaction, wallet, network) + .also { dAppFee = it } } override fun getAmount(): WcApprovedAmount? { diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthTxHelper.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthTxHelper.kt index ebf796f991..057afaa2e9 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthTxHelper.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthTxHelper.kt @@ -76,7 +76,7 @@ internal class WcEthTxHelper @Inject constructor( ?: return null val approves = (simulation.data as? SimulationData.Approve)?.approvedAmounts ?: return null - if (approves.size != 1) return null + if (approves.isEmpty()) return null val amount = approves.first() return amount } diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt index 8fa3aa5469..0b47155291 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt @@ -8,7 +8,6 @@ import com.squareup.moshi.Moshi import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.extensions.decodeBase58 import com.tangem.blockchainsdk.utils.ExcludedBlockchains -import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.common.extensions.toHexString import com.tangem.data.walletconnect.model.CAIP2 import com.tangem.data.walletconnect.model.NamespaceKey @@ -17,7 +16,6 @@ import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter.Compani import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext import com.tangem.data.walletconnect.utils.WcNamespaceConverter import com.tangem.data.walletconnect.utils.WcNetworksConverter -import com.tangem.domain.models.network.Network import com.tangem.domain.walletconnect.model.HandleMethodError import com.tangem.domain.walletconnect.model.WcSolanaMethod import com.tangem.domain.walletconnect.model.WcSolanaMethodName @@ -88,27 +86,14 @@ internal class WcSolanaNetwork( override val namespaceKey: NamespaceKey = NamespaceKey("solana") override fun toBlockchain(chainId: CAIP2): Blockchain? { + val isMainNet = MAINNET_CHAIN_ID.any { it.lowercase() == chainId.reference.lowercase() } if (chainId.namespace != namespaceKey.key) return null - return when (chainId.reference) { - MAINNET_CHAIN_ID -> Blockchain.Solana - TESTNET_CHAIN_ID -> Blockchain.SolanaTestnet + return when { + isMainNet -> Blockchain.Solana + chainId.reference.lowercase() == TESTNET_CHAIN_ID -> Blockchain.SolanaTestnet else -> null } } - - override fun toCAIP2(network: Network): CAIP2? { - val blockchain = network.toBlockchain() - val chainId = when (blockchain) { - Blockchain.Solana -> MAINNET_CHAIN_ID - Blockchain.SolanaTestnet -> TESTNET_CHAIN_ID - else -> null - } - chainId ?: return null - return CAIP2( - namespace = namespaceKey.key, - reference = chainId, - ) - } } private fun WcSolanaMethodName.toMethod(request: WcSdkSessionRequest): Either { @@ -140,7 +125,7 @@ internal class WcSolanaNetwork( ) companion object { - private const val MAINNET_CHAIN_ID = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" + private val MAINNET_CHAIN_ID = listOf("5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ") private const val TESTNET_CHAIN_ID = "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z" } } \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/AssociateNetworksDelegate.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/AssociateNetworksDelegate.kt index 7ecdf54de9..d0f06c74b2 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/AssociateNetworksDelegate.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/AssociateNetworksDelegate.kt @@ -110,8 +110,10 @@ internal class AssociateNetworksDelegate( .distinctBy { it.rawId } } - private fun Map.setOfChainId(): Set = - this.values.flatMap { proposal -> proposal.chains ?: listOf() }.toSet() - private fun missingNetworkName(chainId: String): String = chainId.replaceFirstChar(Char::titlecase) + + companion object { + internal fun Map.setOfChainId(): Set = + this.values.flatMap { proposal -> proposal.chains ?: listOf() }.toSet() + } } \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/CaipNamespaceDelegate.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/CaipNamespaceDelegate.kt index 82c921611a..4b5d2c336a 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/CaipNamespaceDelegate.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/CaipNamespaceDelegate.kt @@ -2,6 +2,8 @@ package com.tangem.data.walletconnect.pair import com.reown.walletkit.client.Wallet import com.tangem.data.walletconnect.model.CAIP10 +import com.tangem.data.walletconnect.model.CAIP2 +import com.tangem.data.walletconnect.pair.AssociateNetworksDelegate.Companion.setOfChainId import com.tangem.data.walletconnect.utils.WcNamespaceConverter import com.tangem.data.walletconnect.utils.WcNetworksConverter import com.tangem.domain.models.network.Network @@ -22,6 +24,26 @@ internal class CaipNamespaceDelegate( val userWallet = sessionForApprove.wallet val result = mutableMapOf() + val requiredNamespaces = sessionProposal.requiredNamespaces.setOfChainId() + val optionalNamespaces = sessionProposal.optionalNamespaces.setOfChainId() + val allWcNetworks = (requiredNamespaces + optionalNamespaces) + .mapNotNull { chainId -> + val network = namespaceConverters + .firstNotNullOfOrNull { it.toNetwork(chainId, userWallet) } + ?: return@mapNotNull null + val caip2 = CAIP2.fromRaw(chainId) ?: return@mapNotNull null + network to caip2 + } + + suspend fun createCAIP10(userWalletId: UserWalletId, network: Network): CAIP10? { + val address = walletManagersFacade.getDefaultAddress(userWalletId, network) + val chainId = allWcNetworks + .find { (wcNetwork, _) -> network.rawId == wcNetwork.rawId } + ?.second + if (chainId == null || address == null) return null + return CAIP10(chainId = chainId, accountAddress = address) + } + wcNetworksConverter.convertNetworksForApprove(sessionForApprove) .mapNotNull { createCAIP10(userWallet.walletId, it) } .forEach { account -> @@ -52,13 +74,6 @@ internal class CaipNamespaceDelegate( } } - private suspend fun createCAIP10(userWalletId: UserWalletId, network: Network): CAIP10? { - val address = walletManagersFacade.getDefaultAddress(userWalletId, network) - val chainId = namespaceConverters.firstNotNullOfOrNull { it.toCAIP2(network) } - if (chainId == null || address == null) return null - return CAIP10(chainId = chainId, accountAddress = address) - } - private data class Session( val chains: MutableSet = mutableSetOf(), val accounts: MutableSet = mutableSetOf(), diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt index 91951d8f48..bfd1192118 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt @@ -49,7 +49,7 @@ internal class DefaultWcPairUseCase @AssistedInject constructor( val pairResult = sdkDelegate.pair(uri) .onLeft { Timber.tag(WC_TAG).e(it, "Failed to call pair $pairRequest") - analytics.send(WcAnalyticEvents.PairFailed) + analytics.send(WcAnalyticEvents.PairFailed(it.code)) emit(WcPairState.Error(it)) } .getOrNull() ?: return@flow @@ -65,7 +65,7 @@ internal class DefaultWcPairUseCase @AssistedInject constructor( val proposalState = buildProposalState(sdkSessionProposal, sdkVerifyContext) .onLeft { - analytics.send(WcAnalyticEvents.PairFailed) + analytics.send(WcAnalyticEvents.PairFailed(it.code)) emit(WcPairState.Error(it)) } .getOrNull() ?: return@flow @@ -115,6 +115,7 @@ internal class DefaultWcPairUseCase @AssistedInject constructor( }.onCompletion { if (it != null) { Timber.tag(WC_TAG).e(it, "Completed with error $pairRequest") + emit(WcPairState.Error(WcPairError.Unknown(it.message.orEmpty()))) } else { Timber.tag(WC_TAG).i("Completed successfully $pairRequest") } @@ -134,7 +135,7 @@ internal class DefaultWcPairUseCase @AssistedInject constructor( private suspend fun walletKitApproveSession( sessionForApprove: WcSessionApprove, sdkSessionProposal: Wallet.Model.SessionProposal, - ): Either { + ): Either = try { val namespaces = caipNamespaceDelegate.associate( sdkSessionProposal, sessionForApprove, @@ -143,7 +144,10 @@ internal class DefaultWcPairUseCase @AssistedInject constructor( proposerPublicKey = sdkSessionProposal.proposerPublicKey, namespaces = namespaces, ) - return sdkDelegate.approve(sessionApprove) + sdkDelegate.approve(sessionApprove) + } catch (e: Throwable) { + Timber.tag(WC_TAG).e(e, "Failed to sdk approve session $pairRequest") + WcPairError.ApprovalFailed(e.message.orEmpty()).left() } private suspend fun buildProposalState( diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/WcPairSdkDelegate.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/WcPairSdkDelegate.kt index e115bb7807..e9665c0673 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/WcPairSdkDelegate.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/WcPairSdkDelegate.kt @@ -150,7 +150,7 @@ internal class WcPairSdkDelegate : WcSdkObserver { private fun Throwable.toApproveError() = WcPairError.ApprovalFailed(this.localizedMessage.orEmpty()).left() companion object { - private const val CALLBACK_TIMEOUT = 30 + private const val CALLBACK_TIMEOUT = 60 // com.reown.android.pairing.engine.domain.PairingEngine.pair private val pairingExpiredMessages = listOf( "Pairing URI expired", diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestUseCaseFactory.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestUseCaseFactory.kt index 53ad5c917a..31791fd91c 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestUseCaseFactory.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestUseCaseFactory.kt @@ -3,10 +3,14 @@ package com.tangem.data.walletconnect.request import arrow.core.Either import arrow.core.left import arrow.core.right +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.data.walletconnect.utils.WC_TAG +import com.tangem.data.walletconnect.utils.WcNamespaceConverter +import com.tangem.domain.walletconnect.WcAnalyticEvents import com.tangem.domain.walletconnect.WcRequestUseCaseFactory import com.tangem.domain.walletconnect.model.HandleMethodError import com.tangem.domain.walletconnect.model.WcMethod +import com.tangem.domain.walletconnect.model.WcRequestError.Companion.code import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest import com.tangem.domain.walletconnect.usecase.method.WcMethodUseCase import timber.log.Timber @@ -14,6 +18,8 @@ import javax.inject.Inject internal class DefaultWcRequestUseCaseFactory @Inject constructor( private val requestConverters: Set, + private val namespaceConverters: Set, + private val analytics: AnalyticsEventHandler, ) : WcRequestUseCaseFactory { @Suppress("UNCHECKED_CAST") @@ -25,12 +31,26 @@ internal class DefaultWcRequestUseCaseFactory @Inject constructor( ?.toUseCase(request) ?: HandleMethodError.UnknownError("Failed to create WcUseCase").left() - return useCase.fold( + val result = useCase.fold( ifLeft = { Timber.tag(WC_TAG).e("$it") it.left() }, ifRight = { (it as? T)?.right() ?: HandleMethodError.Unsupported(WcMethod.Unsupported(request)).left() }, ) + result.onLeft { logError(it, request) } + return result + } + + private fun logError(error: HandleMethodError, request: WcSdkSessionRequest) { + val blockchainName = namespaceConverters + .firstNotNullOfOrNull { it.toBlockchain(request.chainId.orEmpty()) } + ?.getCoinName().orEmpty() + val event = WcAnalyticEvents.SignatureRequestReceivedFailed( + rawRequest = request, + errorCode = error.code().orEmpty(), + blockchain = blockchainName, + ) + analytics.send(event) } } \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt index 614d12dc9c..38ed978e2d 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt @@ -52,7 +52,7 @@ internal class DefaultWcSessionsManager( if (someMigrated) return@transform // ignore emit, wait next one } val associatedSessions: List = associate(inSdk, inStore, wallets) - val someRemove = removeUnknownSessions(inStore, associatedSessions) + val someRemove = removeUnknownSessions(inStore, inSdk, associatedSessions) if (someRemove) return@transform // ignore emit, wait next one emit(associatedSessions.groupBy { it.wallet }) } @@ -79,7 +79,7 @@ internal class DefaultWcSessionsManager( override suspend fun removeSession(session: WcSession): Either { val topic = session.sdkModel.topic val sdkCall = sdkDisconnectSession(topic) - .onRight { onSessionDelete.trySend(Wallet.Model.SessionDelete.Success(topic = topic, reason = "")) } + onSessionDelete.trySend(Wallet.Model.SessionDelete.Success(topic = topic, reason = "")) analytics.send(WcAnalyticEvents.SessionDisconnected(session.sdkModel.appMetaData)) return sdkCall } @@ -142,10 +142,17 @@ internal class DefaultWcSessionsManager( return wcSessions } - private suspend fun removeUnknownSessions(storeSessions: Set, wcSessions: List): Boolean { + private suspend fun removeUnknownSessions( + storeSessions: Set, + inSdkSessions: List, + wcSessions: List, + ): Boolean { val unknownStoredSessions = storeSessions .filterNot { dto -> wcSessions.any { it.sdkModel.topic == dto.topic } } val haveSomeUnknown = unknownStoredSessions.isNotEmpty() + val unknownSdkSessions = inSdkSessions + .filterNot { sdkSession -> wcSessions.any { it.sdkModel.topic == sdkSession.topic } } + val haveSomeUnknownSdkSessions = unknownSdkSessions.isNotEmpty() if (haveSomeUnknown) { Timber.tag(WC_TAG).i("removeUnknownSessions $unknownStoredSessions") @@ -164,7 +171,10 @@ internal class DefaultWcSessionsManager( store.removeSessions(emptyNetworksDto) } if (haveEmptySessions) { - emptyNetworkSessions.map { scope.launch { sdkDisconnectSession(it.sdkModel.topic) } } + emptyNetworkSessions.forEach { scope.launch { sdkDisconnectSession(it.sdkModel.topic) } } + } + if (haveSomeUnknownSdkSessions) { + unknownSdkSessions.forEach { scope.launch { sdkDisconnectSession(it.topic) } } } return haveSomeUnknown || haveEmptyDto } diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/WcSignUseCaseDelegate.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/WcSignUseCaseDelegate.kt index 2dea8ba415..af4f00d088 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/WcSignUseCaseDelegate.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/WcSignUseCaseDelegate.kt @@ -43,7 +43,6 @@ internal class WcSignUseCaseDelegate( val state = MutableStateFlow(WcSignState(initModel, WcSignStep.PreSign)) analytics.send( WcAnalyticEvents.SignatureRequestReceived( - session = context.session, rawRequest = context.rawSdkRequest, network = context.network, ), @@ -73,7 +72,6 @@ internal class WcSignUseCaseDelegate( val event = step.result.fold( ifLeft = { error -> WcAnalyticEvents.SignatureRequestFailed( - session = context.session, rawRequest = context.rawSdkRequest, network = context.network, errorCode = error.code() ?: error::class.simpleName.orEmpty(), @@ -81,7 +79,6 @@ internal class WcSignUseCaseDelegate( }, ifRight = { WcAnalyticEvents.SignatureRequestHandled( - session = context.session, rawRequest = context.rawSdkRequest, network = context.network, ) diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNamespaceConverter.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNamespaceConverter.kt index 5d12294eed..a65c47d3df 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNamespaceConverter.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNamespaceConverter.kt @@ -16,7 +16,6 @@ internal interface WcNamespaceConverter { fun toBlockchain(chainId: CAIP2): Blockchain? fun toBlockchain(chainId: String): Blockchain? = toCAIP2(chainId)?.let { caip2 -> toBlockchain(caip2) } - fun toCAIP2(network: Network): CAIP2? fun toCAIP2(chainId: String): CAIP2? = CAIP2.fromRaw(chainId) fun toNetwork(chainId: String, wallet: UserWallet): Network? { diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcSdkSessionRequestConverter.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcSdkSessionRequestConverter.kt index 603699848e..3c0fd55daa 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcSdkSessionRequestConverter.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcSdkSessionRequestConverter.kt @@ -10,6 +10,8 @@ internal object WcSdkSessionRequestConverter : Converter, ) : SimulationData() + + /** + * Simulation was successfully performed and no changes detected + */ + data object NoWalletChangesDetected : SimulationData() } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/BaseActionsFactory.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/BaseActionsFactory.kt index 5863373ebf..ae077ffd24 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/BaseActionsFactory.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/BaseActionsFactory.kt @@ -173,7 +173,7 @@ internal open class BaseActionsFactory( } } - private fun getReceiveScenario(requirements: AssetRequirementsCondition?): ScenarioUnavailabilityReason { + protected fun getReceiveScenario(requirements: AssetRequirementsCondition?): ScenarioUnavailabilityReason { return when (requirements) { AssetRequirementsCondition.PaidTransaction, is AssetRequirementsCondition.PaidTransactionWithFee, 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 522690bc10..05b1e1969e 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 @@ -8,6 +8,7 @@ import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.model.TokenActionsState.ActionState +import com.tangem.domain.transaction.models.AssetRequirementsCondition import com.tangem.domain.walletmanager.WalletManagersFacade import kotlinx.coroutines.Deferred import kotlinx.coroutines.async @@ -65,6 +66,7 @@ internal class CommonActionsFactory( getSwapUnavailabilityReason( userWalletId = userWallet.walletId, currency = cryptoCurrencyStatus.currency, + requirementsDeferred = requirementsDeferred, ) } } else { @@ -170,7 +172,16 @@ internal class CommonActionsFactory( private suspend fun getSwapUnavailabilityReason( userWalletId: UserWalletId, currency: CryptoCurrency, + requirementsDeferred: Deferred?, ): ScenarioUnavailabilityReason { - return rampStateManager.availableForSwap(userWalletId = userWalletId, cryptoCurrency = currency) + val swapUnavailabilityReason = rampStateManager + .availableForSwap(userWalletId = userWalletId, cryptoCurrency = currency) + val shouldCheckAssetRequirements = + swapUnavailabilityReason == ScenarioUnavailabilityReason.None && requirementsDeferred != null + return if (shouldCheckAssetRequirements) { + getReceiveScenario(requirementsDeferred.await()) + } else { + swapUnavailabilityReason + } } } \ No newline at end of file diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/sdkcopy/WcAppMetaData.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/sdkcopy/WcAppMetaData.kt index f8de996d34..4b5d5c4475 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/sdkcopy/WcAppMetaData.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/sdkcopy/WcAppMetaData.kt @@ -1,8 +1,11 @@ package com.tangem.domain.walletconnect.model.sdkcopy +import kotlinx.serialization.Serializable + /** * copy of [com.reown.android.Core.Model.AppMetaData] */ +@Serializable data class WcAppMetaData( val name: String, val description: String, diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/sdkcopy/WcSdkSessionRequest.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/sdkcopy/WcSdkSessionRequest.kt index 02240181d0..7892491554 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/sdkcopy/WcSdkSessionRequest.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/sdkcopy/WcSdkSessionRequest.kt @@ -9,6 +9,7 @@ import kotlinx.serialization.Serializable data class WcSdkSessionRequest( val topic: String, val chainId: String?, + val dAppMetaData: WcAppMetaData, val request: JSONRPCRequest, ) { diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt index cb8f983b1f..fa4b050106 100644 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt @@ -49,8 +49,13 @@ sealed class WcAnalyticEvents( ), ) - data object PairFailed : WcAnalyticEvents( + class PairFailed( + errorCode: String, + ) : WcAnalyticEvents( event = "Session Failed", + params = mapOf( + AnalyticsParam.Key.ERROR_CODE to errorCode, + ), ) class DAppConnected( @@ -83,49 +88,61 @@ sealed class WcAnalyticEvents( ) class SignatureRequestReceived( - session: WcSession, rawRequest: WcSdkSessionRequest, network: Network, ) : WcAnalyticEvents( event = "Signature Request Received", params = mapOf( - AnalyticsParam.Key.DAPP_NAME to session.sdkModel.appMetaData.name, - AnalyticsParam.Key.DAPP_URL to session.sdkModel.appMetaData.url, + AnalyticsParam.Key.DAPP_NAME to rawRequest.dAppMetaData.name, + AnalyticsParam.Key.DAPP_URL to rawRequest.dAppMetaData.url, AnalyticsParam.Key.METHOD_NAME to rawRequest.request.method, AnalyticsParam.Key.BLOCKCHAIN to network.name, ), ) class SignatureRequestHandled( - session: WcSession, rawRequest: WcSdkSessionRequest, network: Network, ) : WcAnalyticEvents( event = "Signature Request Handled", params = mapOf( - AnalyticsParam.Key.DAPP_NAME to session.sdkModel.appMetaData.name, - AnalyticsParam.Key.DAPP_URL to session.sdkModel.appMetaData.url, + AnalyticsParam.Key.DAPP_NAME to rawRequest.dAppMetaData.name, + AnalyticsParam.Key.DAPP_URL to rawRequest.dAppMetaData.url, AnalyticsParam.Key.METHOD_NAME to rawRequest.request.method, AnalyticsParam.Key.BLOCKCHAIN to network.name, ), ) class SignatureRequestFailed( - session: WcSession, rawRequest: WcSdkSessionRequest, network: Network, errorCode: String, ) : WcAnalyticEvents( event = "Signature Request Failed", params = mapOf( - AnalyticsParam.Key.DAPP_NAME to session.sdkModel.appMetaData.name, - AnalyticsParam.Key.DAPP_URL to session.sdkModel.appMetaData.url, + AnalyticsParam.Key.DAPP_NAME to rawRequest.dAppMetaData.name, + AnalyticsParam.Key.DAPP_URL to rawRequest.dAppMetaData.url, AnalyticsParam.Key.METHOD_NAME to rawRequest.request.method, AnalyticsParam.Key.BLOCKCHAIN to network.name, AnalyticsParam.Key.ERROR_CODE to errorCode, ), ) + class SignatureRequestReceivedFailed( + rawRequest: WcSdkSessionRequest, + blockchain: String, + errorCode: String, + ) : WcAnalyticEvents( + event = "Signature Request Received with Failed", + params = mapOf( + AnalyticsParam.Key.DAPP_NAME to rawRequest.dAppMetaData.name, + AnalyticsParam.Key.DAPP_URL to rawRequest.dAppMetaData.url, + AnalyticsParam.Key.METHOD_NAME to rawRequest.request.method, + AnalyticsParam.Key.BLOCKCHAIN to blockchain, + AnalyticsParam.Key.ERROR_CODE to errorCode, + ), + ) + class ButtonSign( rawRequest: WcSdkSessionRequest, ) : WcAnalyticEvents( diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcTransactionUseCase.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcTransactionUseCase.kt index acc4686c4d..af7e9c585f 100644 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcTransactionUseCase.kt +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcTransactionUseCase.kt @@ -34,7 +34,7 @@ interface WcListTransactionUseCase : * [updateFee] triggered a new [TransactionData] emit */ interface WcMutableFee { - fun dAppFee(): Fee? + suspend fun dAppFee(): Fee? fun updateFee(fee: Fee) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt index d19b0fc95b..01c26e0e76 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt @@ -85,7 +85,13 @@ internal class OnrampRedirectModel @Inject constructor( .onLeft(::handleError) .onRight { latestOnrampTransaction = it - urlOpener.openUrl(it.redirectUrl) + + // Workaround to open Unlimit provider in external browser instead of chrome custom tabs + if (params.onrampProviderWithQuote.provider.id.equals(UNLIMIT_PROVIDER_ID, ignoreCase = true)) { + urlOpener.openUrlExternalBrowser(it.redirectUrl) + } else { + urlOpener.openUrl(it.redirectUrl) + } } } } @@ -126,4 +132,8 @@ internal class OnrampRedirectModel @Inject constructor( messageSender.send(message) } + + private companion object { + const val UNLIMIT_PROVIDER_ID = "unlimit" + } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/AlertsComponentV2.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/AlertsComponentV2.kt index 78ea021b35..db7a6f0f00 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/AlertsComponentV2.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/AlertsComponentV2.kt @@ -12,6 +12,7 @@ internal class AlertsComponentV2( ) : AppComponentContext by appComponentContext, ComposableBottomSheetComponent { override fun dismiss() { + messageUM.onDismissRequest() router.pop() } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcPairComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcPairComponent.kt index 675c063840..a171a0f38a 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcPairComponent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcPairComponent.kt @@ -115,8 +115,11 @@ internal class WcPairComponent( is Alert.Type.Verified -> WcAlertsFactory.createVerifiedDomainAlert(alertType.appName) is Alert.Type.UnknownDomain -> WcAlertsFactory.createUnknownDomainAlert(model::connectFromAlert) is Alert.Type.UnsafeDomain -> WcAlertsFactory.createUnsafeDomainAlert(model::connectFromAlert) - is Alert.Type.UnsupportedDApp -> WcAlertsFactory.createUnsupportedDomainAlert(alertType.appName) - is Alert.Type.UnsupportedNetwork -> WcAlertsFactory.createUnsupportedChainAlert(alertType.appName) + is Alert.Type.UnsupportedDApp -> + WcAlertsFactory.createUnsupportedDomainAlert(alertType.appName, model::errorAlertOnDismiss) + is Alert.Type.UnsupportedNetwork -> + WcAlertsFactory.createUnsupportedChainAlert(alertType.appName, model::errorAlertOnDismiss) + is Alert.Type.UriAlreadyUsed -> WcAlertsFactory.createUriAlreadyUsedAlert(model::errorAlertOnDismiss) } } 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 f92ac32008..c745515dc4 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 @@ -149,6 +149,10 @@ internal class WcPairModel @Inject constructor( connect() } + fun errorAlertOnDismiss() { + router.pop() + } + private fun onConnect(securityStatus: CheckDAppResult) { when (securityStatus) { CheckDAppResult.SAFE -> connect() @@ -193,16 +197,23 @@ internal class WcPairModel @Inject constructor( } private fun processError(error: WcPairError) { - when (error) { + val alert = when (error) { is WcPairError.UnsupportedDApp -> { WcAppInfoRoutes.Alert.Type.UnsupportedDApp(error.appName) } is WcPairError.UnsupportedBlockchains -> { WcAppInfoRoutes.Alert.Type.UnsupportedNetwork(error.appName) } - else -> null - }?.let { stackNavigation.pushNew(WcAppInfoRoutes.Alert(it)) } - ?: run { messageSender.send(ToastMessage(message = stringReference(error.message))) } + is WcPairError.UriAlreadyUsed -> { + WcAppInfoRoutes.Alert.Type.UriAlreadyUsed + } + else -> { + messageSender.send(ToastMessage(message = stringReference(error.message))) + router.pop() + null + } + } + alert?.let { stackNavigation.pushNew(WcAppInfoRoutes.Alert(it)) } } override fun onWalletSelected(userWalletId: UserWalletId) { diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcAppInfoRoutes.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcAppInfoRoutes.kt index 56215acac5..cf5b226353 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcAppInfoRoutes.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcAppInfoRoutes.kt @@ -34,6 +34,7 @@ internal sealed class WcAppInfoRoutes : TangemBottomSheetConfigContent, Route { data object UnsafeDomain : Type() data class UnsupportedDApp(val appName: String) : Type() data class UnsupportedNetwork(val appName: String) : Type() + data object UriAlreadyUsed : Type() } } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/WcAppInfoBS.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/WcAppInfoBS.kt index 9804255452..af9a95cdf2 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/WcAppInfoBS.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/WcAppInfoBS.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -311,23 +312,28 @@ private fun WalletRowItem(walletName: String, showEndIcon: Boolean, modifier: Mo contentDescription = null, tint = TangemTheme.colors.icon.accent, ) - Text( - modifier = Modifier - .padding(start = TangemTheme.dimens.spacing4) - .weight(1f), - text = stringResourceSafe(R.string.manage_tokens_network_selector_wallet), - style = TangemTheme.typography.body1, - color = TangemTheme.colors.text.primary1, - ) - Text( - modifier = Modifier - .padding(start = TangemTheme.dimens.spacing16) - .weight(1f), - text = walletName, - textAlign = TextAlign.End, - style = TangemTheme.typography.body1, - color = TangemTheme.colors.text.tertiary, - ) + Row( + modifier = Modifier.weight(1f), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + modifier = Modifier.padding(start = TangemTheme.dimens.spacing4), + text = stringResourceSafe(R.string.manage_tokens_network_selector_wallet), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.primary1, + maxLines = 1, + ) + Text( + modifier = Modifier.padding(start = TangemTheme.dimens.spacing16), + text = walletName, + textAlign = TextAlign.End, + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.tertiary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } if (showEndIcon) { Icon( modifier = Modifier @@ -611,7 +617,7 @@ private class WcAppInfoStateProvider : CollectionPreviewParameterProvider createUnsafeDomainAlert() - is WcTransactionRoutes.Alert.Type.MaliciousInfo -> - createMaliciousDAppAlert(alertType.description, alertType.onClick) + is WcTransactionRoutes.Alert.Type.BlockAidErrorInfo -> + createMaliciousDAppAlert(alertType.description, alertType.onClick, alertType.iconType, alertType.iconBgType) is WcTransactionRoutes.Alert.Type.UnknownError -> - createUnknownErrorAlert(alertType.errorMessage, alertType.onDismiss) + createUnknownErrorAlert(alertType.errorMessage, alertType.onDismiss, alertType.onRetry) } fun createUnknownDomainAlert(activeButtonOnClick: (() -> Unit)? = null): MessageBottomSheetUMV2 { @@ -74,11 +74,11 @@ internal object WcAlertsFactory { } } - fun createUnsupportedDomainAlert(appName: String): MessageBottomSheetUMV2 { + fun createUnsupportedDomainAlert(appName: String, onDismiss: () -> Unit): MessageBottomSheetUMV2 { return messageBottomSheetUM { infoBlock { icon(R.drawable.ic_wallet_connect_24) { - type = MessageBottomSheetUMV2.Icon.Type.Informative + type = Type.Informative backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint } title = resourceReference(R.string.wc_alert_unsupported_dapps_title) @@ -86,16 +86,34 @@ internal object WcAlertsFactory { } primaryButton { text = resourceReference(R.string.common_got_it) - onClick { closeBs() } + onClick { onDismiss() } + } + onDismissRequest = onDismiss + } + } + + fun createUriAlreadyUsedAlert(onDismiss: () -> Unit): MessageBottomSheetUMV2 { + return messageBottomSheetUM { + infoBlock { + icon(R.drawable.ic_wallet_connect_24) { + type = Type.Informative + backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint + } + title = resourceReference(R.string.wc_uri_already_used_title) + body = resourceReference(R.string.wc_uri_already_used_description) + } + primaryButton { + text = resourceReference(R.string.common_got_it) + onClick { onDismiss() } } } } - fun createUnsupportedChainAlert(appName: String): MessageBottomSheetUMV2 { + fun createUnsupportedChainAlert(appName: String, onDismiss: () -> Unit): MessageBottomSheetUMV2 { return messageBottomSheetUM { infoBlock { icon(R.drawable.ic_network_new_24) { - type = MessageBottomSheetUMV2.Icon.Type.Informative + type = Type.Informative backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint } title = resourceReference(R.string.wc_alert_unsupported_networks_title) @@ -103,12 +121,17 @@ internal object WcAlertsFactory { } primaryButton { text = resourceReference(R.string.common_got_it) - onClick { closeBs() } + onClick { onDismiss() } } + onDismissRequest = onDismiss } } - private fun createUnknownErrorAlert(errorMessage: String?, onDismiss: () -> Unit): MessageBottomSheetUMV2 { + private fun createUnknownErrorAlert( + errorMessage: String?, + onDismiss: () -> Unit, + onRetry: () -> Unit, + ): MessageBottomSheetUMV2 { return messageBottomSheetUM { infoBlock { icon(R.drawable.img_attention_20) { @@ -124,21 +147,29 @@ internal object WcAlertsFactory { ) } } + primaryButton { + text = resourceReference(R.string.alert_button_try_again) + onClick { onRetry() } + } secondaryButton { - text = resourceReference(R.string.balance_hidden_got_it_button) + text = resourceReference(R.string.common_cancel) onClick { onDismiss() } } + onDismissRequest = onDismiss } } private fun createMaliciousDAppAlert( description: String?, activeButtonOnClick: (() -> Unit), + iconType: Type, + iconBgType: MessageBottomSheetUMV2.Icon.BackgroundType, ): MessageBottomSheetUMV2 { return messageBottomSheetUM { infoBlock { icon(R.drawable.img_knight_shield_32) { - backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.Attention + type = iconType + backgroundType = iconBgType } title = resourceReference(R.string.security_alert_title) if (!description.isNullOrEmpty()) { diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/send/WcSendTransactionComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/send/WcSendTransactionComponent.kt index eb105f2216..62f838c9b7 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/send/WcSendTransactionComponent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/send/WcSendTransactionComponent.kt @@ -7,6 +7,7 @@ import com.arkivanov.essenty.lifecycle.doOnResume import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.ui.decompose.ComposableBottomSheetComponent import com.tangem.features.send.v2.api.FeeSelectorBlockComponent +import com.tangem.features.send.v2.api.entity.FeeSelectorUM import com.tangem.features.send.v2.api.params.FeeSelectorParams import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionFeeState import com.tangem.features.walletconnect.transaction.model.WcSendTransactionModel @@ -58,6 +59,7 @@ internal class WcSendTransactionComponent( WcSendTransactionModalBottomSheet( state = state, feeSelectorBlockComponent = feeSelectorBlock, + feeSelectorUM = content?.feeSelectorUM ?: FeeSelectorUM.Loading, onClickTransactionRequest = model::showTransactionRequest, onBack = router::pop, onDismiss = ::dismiss, diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcAddressConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcAddressConverter.kt index cbcc897ce4..4e9838e99c 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcAddressConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcAddressConverter.kt @@ -1,24 +1,14 @@ package com.tangem.features.walletconnect.transaction.converter import com.tangem.domain.walletconnect.usecase.method.WcNetworkDerivationState -import com.tangem.features.walletconnect.transaction.entity.common.WcAddressUM import com.tangem.utils.converter.Converter -private const val ADDRESS_FIRST_PART_LENGTH = 7 -private const val ADDRESS_SECOND_PART_LENGTH = 4 +internal object WcAddressConverter : Converter { -internal object WcAddressConverter : Converter { - - override fun convert(value: WcNetworkDerivationState): WcAddressUM? { + override fun convert(value: WcNetworkDerivationState): String? { return when (value) { is WcNetworkDerivationState.Single -> null - is WcNetworkDerivationState.Multiple -> WcAddressUM( - fullAddress = value.walletAddress, - shortAddress = value.walletAddress.toShortAddressText(), - ) + is WcNetworkDerivationState.Multiple -> value.walletAddress } } - - private fun String.toShortAddressText() = - "${take(ADDRESS_FIRST_PART_LENGTH)}...${takeLast(ADDRESS_SECOND_PART_LENGTH)}" } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcCommonTransactionUMConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcCommonTransactionUMConverter.kt deleted file mode 100644 index 84d4f29ebc..0000000000 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcCommonTransactionUMConverter.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.tangem.features.walletconnect.transaction.converter - -import com.tangem.domain.walletconnect.model.WcEthMethod -import com.tangem.domain.walletconnect.model.WcSolanaMethod -import com.tangem.domain.walletconnect.usecase.method.WcMessageSignUseCase -import com.tangem.domain.walletconnect.usecase.method.WcSignState -import com.tangem.domain.walletconnect.usecase.method.WcSignUseCase -import com.tangem.domain.walletconnect.usecase.method.WcTransactionUseCase -import com.tangem.features.send.v2.api.entity.FeeSelectorUM -import com.tangem.features.walletconnect.transaction.entity.common.WcCommonTransactionUM -import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionActionsUM -import com.tangem.utils.converter.Converter -import javax.inject.Inject - -internal class WcCommonTransactionUMConverter @Inject constructor( - private val signTypedDataUMConverter: WcSignTypedDataUMConverter, - private val signTransactionUMConverter: WcSignTransactionUMConverter, - private val sendTransactionUMConverter: WcSendTransactionUMConverter, -) : Converter { - - override fun convert(value: Input): WcCommonTransactionUM? { - return when (value.useCase) { - is WcMessageSignUseCase -> { - when (value.useCase.method) { - is WcEthMethod.SignTypedData -> signTypedDataUMConverter.convert( - WcSignTypedDataUMConverter.Input( - useCase = value.useCase, - signState = value.signState, - signModel = value.signState.signModel as WcMessageSignUseCase.SignModel, - actions = value.actions, - ), - ) - is WcEthMethod.MessageSign, is WcSolanaMethod.SignMessage -> signTransactionUMConverter.convert( - WcSignTransactionUMConverter.Input( - useCase = value.useCase, - signState = value.signState, - signModel = value.signState.signModel as WcMessageSignUseCase.SignModel, - actions = value.actions, - ), - ) - else -> null - } - } - is WcTransactionUseCase -> sendTransactionUMConverter.convert( - WcSendTransactionUMConverter.Input( - useCase = value.useCase, - signState = value.signState, - actions = value.actions, - feeSelectorUM = value.feeSelectorUM, - ), - ) - else -> null - } - } - - data class Input( - val useCase: WcSignUseCase<*>, - val signState: WcSignState<*>, - val actions: WcTransactionActionsUM, - val feeSelectorUM: FeeSelectorUM? = null, - ) -} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt index 9a2275f2d4..348a69439e 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt @@ -1,8 +1,11 @@ package com.tangem.features.walletconnect.transaction.converter +import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.walletconnect.model.WcEthMethod import com.tangem.domain.walletconnect.model.WcSolanaMethod -import com.tangem.domain.walletconnect.usecase.method.* +import com.tangem.domain.walletconnect.usecase.method.WcMethodContext +import com.tangem.domain.walletconnect.usecase.method.WcSignState +import com.tangem.domain.walletconnect.usecase.method.WcSignStep import com.tangem.features.send.v2.api.entity.FeeSelectorUM import com.tangem.features.walletconnect.transaction.entity.blockaid.WcSendReceiveTransactionCheckResultsUM import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionActionsUM @@ -10,6 +13,7 @@ import com.tangem.features.walletconnect.transaction.entity.common.WcTransaction import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestInfoUM import com.tangem.features.walletconnect.transaction.entity.send.WcSendTransactionItemUM import com.tangem.features.walletconnect.transaction.entity.send.WcSendTransactionUM +import com.tangem.features.walletconnect.utils.WcNotificationsFactory import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.toImmutableList import javax.inject.Inject @@ -18,58 +22,62 @@ internal class WcSendTransactionUMConverter @Inject constructor( private val appInfoContentUMConverter: WcTransactionAppInfoContentUMConverter, private val networkInfoUMConverter: WcNetworkInfoUMConverter, private val requestBlockUMConverter: WcTransactionRequestBlockUMConverter, + private val notificationsFactory: WcNotificationsFactory, ) : Converter { - override fun convert(value: Input): WcSendTransactionUM? = when (value.useCase.method) { - is WcEthMethod.SendTransaction, - is WcEthMethod.SignTransaction, - is WcSolanaMethod.SignAllTransaction, - is WcSolanaMethod.SignTransaction, - -> WcSendTransactionUM( - transaction = WcSendTransactionItemUM( - onDismiss = value.actions.onDismiss, - onSend = value.actions.onSign, - appInfo = appInfoContentUMConverter.convert( - WcTransactionAppInfoContentUMConverter.Input( - session = value.useCase.session, - onShowVerifiedAlert = value.actions.onShowVerifiedAlert, - ), - ), - feeState = constructFeeState(useCase = value.useCase, actions = value.actions), - walletName = value.useCase.session.wallet.name.takeIf { value.useCase.session.showWalletInfo }, - networkInfo = networkInfoUMConverter.convert(value.useCase.network), - estimatedWalletChanges = WcSendReceiveTransactionCheckResultsUM(), - isLoading = value.signState.domainStep == WcSignStep.Signing, - address = WcAddressConverter.convert(value.useCase.derivationState), - ), - feeSelectorUM = value.feeSelectorUM ?: FeeSelectorUM.Loading, - transactionRequestInfo = WcTransactionRequestInfoUM( - blocks = buildList { - addAll( - requestBlockUMConverter.convert( - WcTransactionRequestBlockUMConverter.Input(value.useCase.rawSdkRequest), - ), - ) - }.toImmutableList(), - onCopy = value.actions.onCopy, - ), + override fun convert(value: Input): WcSendTransactionUM? { + val feeErrorNotification = notificationsFactory.createFeeNotifications( + cryptoCurrencyStatus = value.cryptoCurrencyStatus, + feeSelectorUM = value.feeSelectorUM, + onFeeReload = value.onFeeReload, ) - else -> null - } - - private fun constructFeeState( - useCase: WcTransactionUseCase, - actions: WcTransactionActionsUM, - ): WcTransactionFeeState { - val mutableFee = useCase as? WcMutableFee ?: return WcTransactionFeeState.None - val dAppFee = mutableFee.dAppFee() - return WcTransactionFeeState.Success(dAppFee = dAppFee, onClick = actions.onShowFeeBottomSheet) + return when (value.context.method) { + is WcEthMethod.SendTransaction, + is WcEthMethod.SignTransaction, + is WcSolanaMethod.SignAllTransaction, + is WcSolanaMethod.SignTransaction, + -> WcSendTransactionUM( + transaction = WcSendTransactionItemUM( + onDismiss = value.actions.onDismiss, + onSend = value.actions.onSign, + appInfo = appInfoContentUMConverter.convert( + WcTransactionAppInfoContentUMConverter.Input( + session = value.context.session, + onShowVerifiedAlert = value.actions.onShowVerifiedAlert, + ), + ), + feeState = value.feeState, + walletName = value.context.session.wallet.name.takeIf { value.context.session.showWalletInfo }, + networkInfo = networkInfoUMConverter.convert(value.context.network), + estimatedWalletChanges = WcSendReceiveTransactionCheckResultsUM(), + isLoading = value.signState.domainStep == WcSignStep.Signing, + address = WcAddressConverter.convert(value.context.derivationState), + sendEnabled = value.feeSelectorUM is FeeSelectorUM.Content && feeErrorNotification == null, + feeErrorNotification = feeErrorNotification, + ), + feeSelectorUM = value.feeSelectorUM ?: FeeSelectorUM.Loading, + transactionRequestInfo = WcTransactionRequestInfoUM( + blocks = buildList { + addAll( + requestBlockUMConverter.convert( + WcTransactionRequestBlockUMConverter.Input(value.context.rawSdkRequest), + ), + ) + }.toImmutableList(), + onCopy = value.actions.onCopy, + ), + ) + else -> null + } } data class Input( - val useCase: WcTransactionUseCase, + val context: WcMethodContext, + val feeState: WcTransactionFeeState, val signState: WcSignState<*>, val actions: WcTransactionActionsUM, val feeSelectorUM: FeeSelectorUM?, + val cryptoCurrencyStatus: CryptoCurrencyStatus, + val onFeeReload: () -> Unit, ) } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTransactionUMConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTransactionUMConverter.kt index a1e30fdfad..28368845c9 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTransactionUMConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTransactionUMConverter.kt @@ -1,6 +1,7 @@ package com.tangem.features.walletconnect.transaction.converter import com.tangem.domain.walletconnect.usecase.method.WcMessageSignUseCase +import com.tangem.domain.walletconnect.usecase.method.WcMethodContext import com.tangem.domain.walletconnect.usecase.method.WcSignState import com.tangem.domain.walletconnect.usecase.method.WcSignStep import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionActionsUM @@ -23,25 +24,25 @@ internal class WcSignTransactionUMConverter @Inject constructor( onSign = value.actions.onSign, appInfo = appInfoContentUMConverter.convert( WcTransactionAppInfoContentUMConverter.Input( - session = value.useCase.session, + session = value.context.session, onShowVerifiedAlert = value.actions.onShowVerifiedAlert, ), ), - walletName = value.useCase.session.wallet.name.takeIf { value.useCase.session.showWalletInfo }, - networkInfo = networkInfoUMConverter.convert(value.useCase.network), + walletName = value.context.session.wallet.name.takeIf { value.context.session.showWalletInfo }, + networkInfo = networkInfoUMConverter.convert(value.context.network), isLoading = value.signState.domainStep == WcSignStep.Signing, - address = WcAddressConverter.convert(value.useCase.derivationState), + address = WcAddressConverter.convert(value.context.derivationState), ), transactionRequestInfo = WcTransactionRequestInfoUM( requestBlockUMConverter.convert( - WcTransactionRequestBlockUMConverter.Input(value.useCase.rawSdkRequest, value.signModel), + WcTransactionRequestBlockUMConverter.Input(value.context.rawSdkRequest, value.signModel), ).toImmutableList(), onCopy = value.actions.onCopy, ), ) data class Input( - val useCase: WcMessageSignUseCase, + val context: WcMethodContext, val signState: WcSignState<*>, val signModel: WcMessageSignUseCase.SignModel, val actions: WcTransactionActionsUM, diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTypedDataUMConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTypedDataUMConverter.kt index df8661fc77..faef1ed164 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTypedDataUMConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTypedDataUMConverter.kt @@ -1,6 +1,7 @@ package com.tangem.features.walletconnect.transaction.converter import com.tangem.domain.walletconnect.usecase.method.WcMessageSignUseCase +import com.tangem.domain.walletconnect.usecase.method.WcMethodContext import com.tangem.domain.walletconnect.usecase.method.WcSignState import com.tangem.domain.walletconnect.usecase.method.WcSignStep import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionActionsUM @@ -23,20 +24,20 @@ internal class WcSignTypedDataUMConverter @Inject constructor( onSign = value.actions.onSign, appInfo = appInfoContentUMConverter.convert( WcTransactionAppInfoContentUMConverter.Input( - session = value.useCase.session, + session = value.context.session, onShowVerifiedAlert = value.actions.onShowVerifiedAlert, ), ), - walletName = value.useCase.session.wallet.name.takeIf { value.useCase.session.showWalletInfo }, - networkInfo = networkInfoUMConverter.convert(value.useCase.network), - address = WcAddressConverter.convert(value.useCase.derivationState), + walletName = value.context.session.wallet.name.takeIf { value.context.session.showWalletInfo }, + networkInfo = networkInfoUMConverter.convert(value.context.network), + address = WcAddressConverter.convert(value.context.derivationState), isLoading = value.signState.domainStep == WcSignStep.Signing, ), transactionRequestInfo = WcTransactionRequestInfoUM( blocks = buildList { addAll( requestBlockUMConverter.convert( - WcTransactionRequestBlockUMConverter.Input(value.useCase.rawSdkRequest, value.signModel), + WcTransactionRequestBlockUMConverter.Input(value.context.rawSdkRequest, value.signModel), ), ) }.toImmutableList(), @@ -45,7 +46,7 @@ internal class WcSignTypedDataUMConverter @Inject constructor( ) data class Input( - val useCase: WcMessageSignUseCase, + val context: WcMethodContext, val signState: WcSignState<*>, val signModel: WcMessageSignUseCase.SignModel, val actions: WcTransactionActionsUM, diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/blockaid/WcSendReceiveTransactionCheckResultsUM.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/blockaid/WcSendReceiveTransactionCheckResultsUM.kt index 096af30724..850cfb201e 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/blockaid/WcSendReceiveTransactionCheckResultsUM.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/blockaid/WcSendReceiveTransactionCheckResultsUM.kt @@ -6,6 +6,17 @@ import com.tangem.features.walletconnect.transaction.entity.approve.WcSpendAllow internal data class WcSendReceiveTransactionCheckResultsUM( val estimatedWalletChanges: WcEstimatedWalletChangesUM? = null, val spendAllowance: WcSpendAllowanceUM? = null, - val notificationText: TextReference? = null, + val notification: BlockAidNotificationUM? = null, + val additionalNotification: TextReference? = null, val isLoading: Boolean = true, -) \ No newline at end of file +) + +internal data class BlockAidNotificationUM( + val type: Type, + val title: TextReference, + val text: TextReference? = null, +) { + internal enum class Type { + ERROR, WARNING + } +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/common/WcAddressUM.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/common/WcAddressUM.kt deleted file mode 100644 index a43b29458a..0000000000 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/common/WcAddressUM.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.tangem.features.walletconnect.transaction.entity.common - -data class WcAddressUM(val fullAddress: String, val shortAddress: String) \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/send/WcSendTransactionUM.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/send/WcSendTransactionUM.kt index b1461cbfd5..86ef260951 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/send/WcSendTransactionUM.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/send/WcSendTransactionUM.kt @@ -1,10 +1,10 @@ package com.tangem.features.walletconnect.transaction.entity.send +import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.features.send.v2.api.entity.FeeSelectorUM import com.tangem.features.walletconnect.transaction.entity.approve.WcSpendAllowanceUM import com.tangem.features.walletconnect.transaction.entity.blockaid.WcSendReceiveTransactionCheckResultsUM -import com.tangem.features.walletconnect.transaction.entity.common.WcAddressUM import com.tangem.features.walletconnect.transaction.entity.common.WcCommonTransactionUM import com.tangem.features.walletconnect.transaction.entity.common.WcNetworkInfoUM import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionAppInfoContentUM @@ -26,6 +26,8 @@ internal data class WcSendTransactionItemUM( val estimatedWalletChanges: WcSendReceiveTransactionCheckResultsUM?, val walletName: String?, val networkInfo: WcNetworkInfoUM, - val address: WcAddressUM?, + val address: String?, + val sendEnabled: Boolean, + val feeErrorNotification: NotificationUM.Info?, val isLoading: Boolean = false, ) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/sign/WcSignTransactionUM.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/sign/WcSignTransactionUM.kt index fd69bf9cb7..54c08cf874 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/sign/WcSignTransactionUM.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/sign/WcSignTransactionUM.kt @@ -1,7 +1,6 @@ package com.tangem.features.walletconnect.transaction.entity.sign import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent -import com.tangem.features.walletconnect.transaction.entity.common.WcAddressUM import com.tangem.features.walletconnect.transaction.entity.common.WcCommonTransactionUM import com.tangem.features.walletconnect.transaction.entity.common.WcNetworkInfoUM import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionAppInfoContentUM @@ -18,6 +17,6 @@ internal data class WcSignTransactionItemUM( val appInfo: WcTransactionAppInfoContentUM, val walletName: String?, val networkInfo: WcNetworkInfoUM, - val address: WcAddressUM?, + val address: String?, val isLoading: Boolean = false, ) : TangemBottomSheetConfigContent \ No newline at end of file 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 fc2dac78e4..419551a54d 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 @@ -15,6 +15,8 @@ import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.clipboard.ClipboardManager +import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2 +import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2.Icon.Type import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.core.lce.Lce import com.tangem.domain.models.currency.CryptoCurrencyStatus @@ -36,14 +38,16 @@ import com.tangem.features.send.v2.api.subcomponents.feeSelector.FeeSelectorRelo import com.tangem.features.send.v2.api.subcomponents.feeSelector.entity.FeeSelectorData import com.tangem.features.walletconnect.connections.routing.WcInnerRoute import com.tangem.features.walletconnect.transaction.components.common.WcTransactionModelParams -import com.tangem.features.walletconnect.transaction.converter.WcCommonTransactionUMConverter import com.tangem.features.walletconnect.transaction.converter.WcHandleMethodErrorConverter +import com.tangem.features.walletconnect.transaction.converter.WcSendTransactionUMConverter import com.tangem.features.walletconnect.transaction.entity.blockaid.WcSendReceiveTransactionCheckResultsUM import com.tangem.features.walletconnect.transaction.entity.common.WcCommonTransactionModel import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionActionsUM +import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionFeeState import com.tangem.features.walletconnect.transaction.entity.send.WcSendTransactionUM import com.tangem.features.walletconnect.transaction.routes.WcTransactionRoutes import com.tangem.features.walletconnect.transaction.ui.blockaid.WcSendAndReceiveBlockAidUiConverter +import com.tangem.features.walletconnect.utils.WcNotificationsFactory import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch @@ -62,10 +66,11 @@ internal class WcSendTransactionModel @Inject constructor( private val router: Router, private val clipboardManager: ClipboardManager, private val useCaseFactory: WcRequestUseCaseFactory, - private val converter: WcCommonTransactionUMConverter, + private val converter: WcSendTransactionUMConverter, private val blockAidUiConverter: WcSendAndReceiveBlockAidUiConverter, private val getFeeUseCase: GetFeeUseCase, private val getNetworkCoinUseCase: GetNetworkCoinStatusUseCase, + private val notificationsFactory: WcNotificationsFactory, private val analytics: AnalyticsEventHandler, ) : Model(), WcCommonTransactionModel, FeeSelectorModelCallback { @@ -137,7 +142,7 @@ internal class WcSendTransactionModel @Inject constructor( feeReloadState.value = false modelScope.launch { feeSelectorReloadTrigger.triggerUpdate( - feeData = FeeSelectorData(removeSuggestedFee = true), + FeeSelectorData(removeSuggestedFee = feeStateConfiguration !is FeeStateConfiguration.Suggestion), ) } } @@ -156,7 +161,20 @@ internal class WcSendTransactionModel @Inject constructor( * Also handles fee results from FeeSelectorBlockComponent */ fun updateFee(feeSelectorUM: FeeSelectorUM) { - _uiState.update { it?.copy(feeSelectorUM = feeSelectorUM) } + val feeErrorNotification = notificationsFactory.createFeeNotifications( + cryptoCurrencyStatus = cryptoCurrencyStatus, + feeSelectorUM = feeSelectorUM, + onFeeReload = ::triggerFeeReload, + ) + _uiState.update { + it?.copy( + feeSelectorUM = feeSelectorUM, + transaction = it.transaction.copy( + sendEnabled = feeSelectorUM is FeeSelectorUM.Content && feeErrorNotification == null, + feeErrorNotification = feeErrorNotification, + ), + ) + } val fee = (feeSelectorUM as? FeeSelectorUM.Content)?.selectedFeeItem?.fee ?: return (useCase as? WcMutableFee)?.updateFee(fee) } @@ -198,20 +216,31 @@ internal class WcSendTransactionModel @Inject constructor( is Lce.Error -> WcSendReceiveTransactionCheckResultsUM(isLoading = false) is Lce.Loading -> WcSendReceiveTransactionCheckResultsUM(isLoading = true) } + + val feeState = when { + useCase is WcMutableFee -> WcTransactionFeeState.Success( + dAppFee = useCase.dAppFee(), + onClick = ::onShowFeeBottomSheet, + ) + else -> WcTransactionFeeState.None + } + val actions = WcTransactionActionsUM( + onShowVerifiedAlert = ::showVerifiedAlert, + onDismiss = { cancel(useCase) }, + onSign = { onSign(securityCheck.getOrNull()) }, + onCopy = { copyData(useCase.rawSdkRequest.request.params) }, + ) var transactionUM = converter.convert( - WcCommonTransactionUMConverter.Input( - useCase = useCase, + WcSendTransactionUMConverter.Input( + context = useCase, + feeState = feeState, signState = signState, - actions = WcTransactionActionsUM( - onShowVerifiedAlert = ::showVerifiedAlert, - onDismiss = { cancel(useCase) }, - onSign = { onSign(securityCheck.getOrNull()) }, - onCopy = { copyData(useCase.rawSdkRequest.request.params) }, - onShowFeeBottomSheet = ::onShowFeeBottomSheet, - ), + actions = actions, feeSelectorUM = uiState.value?.feeSelectorUM, + cryptoCurrencyStatus = cryptoCurrencyStatus, + onFeeReload = ::triggerFeeReload, ), - ) as? WcSendTransactionUM + ) transactionUM = transactionUM?.copy( transaction = transactionUM.transaction.copy(estimatedWalletChanges = blockAidState), spendAllowance = blockAidState.spendAllowance, @@ -236,10 +265,10 @@ internal class WcSendTransactionModel @Inject constructor( } private fun onSign(securityCheck: BlockAidTransactionCheck.Result?) { - if (securityCheck?.result?.validation == ValidationResult.UNSAFE) { - showMaliciousAlert(securityCheck.result.description) - } else { - sign() + when (securityCheck?.result?.validation) { + ValidationResult.UNSAFE -> showMaliciousAlert(securityCheck.result.description) + ValidationResult.WARNING -> showWarningAlert(securityCheck.result.description) + else -> sign() } securityCheck?.result?.validation?.let { securityStatus -> val event = WcAnalyticEvents.NoticeSecurityAlert( @@ -250,6 +279,7 @@ internal class WcSendTransactionModel @Inject constructor( when (securityStatus) { ValidationResult.SAFE -> Unit ValidationResult.UNSAFE, + ValidationResult.WARNING, ValidationResult.FAILED_TO_VALIDATE, -> analytics.send(event) } @@ -273,7 +303,22 @@ internal class WcSendTransactionModel @Inject constructor( } private fun showMaliciousAlert(description: String?) { - val type = WcTransactionRoutes.Alert.Type.MaliciousInfo(description = description, onClick = ::signFromAlert) + val type = WcTransactionRoutes.Alert.Type.BlockAidErrorInfo( + description = description, + onClick = ::signFromAlert, + iconType = Type.Warning, + iconBgType = MessageBottomSheetUMV2.Icon.BackgroundType.Warning, + ) + stackNavigation.pushNew(WcTransactionRoutes.Alert(type)) + } + + private fun showWarningAlert(description: String?) { + val type = WcTransactionRoutes.Alert.Type.BlockAidErrorInfo( + description = description, + onClick = ::signFromAlert, + iconType = Type.Attention, + iconBgType = MessageBottomSheetUMV2.Icon.BackgroundType.Attention, + ) stackNavigation.pushNew(WcTransactionRoutes.Alert(type)) } @@ -284,22 +329,24 @@ internal class WcSendTransactionModel @Inject constructor( private fun signingIsDone(signState: WcSignState<*>, useCase: WcSignUseCase<*>): Boolean { (signState.domainStep as? WcSignStep.Result)?.result?.let { - handleSigningError(it, useCase) - return true + return handleSigningError(it, useCase) } return false } - private fun handleSigningError(result: Either, useCase: WcSignUseCase<*>) { - if (result.isLeft()) { + private fun handleSigningError(result: Either, useCase: WcSignUseCase<*>): Boolean { + return if (result.isLeft()) { val error = WcTransactionRoutes.Alert.Type.UnknownError( errorMessage = result.leftOrNull()?.message(), onDismiss = { cancel(useCase) }, + onRetry = { signFromAlert() }, ) stackNavigation.pushNew(WcTransactionRoutes.Alert(error)) + false } else { showSuccessSignMessage() router.pop() + true } } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt index 4738a1abaa..cdcb80f9f4 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt @@ -11,13 +11,16 @@ import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.clipboard.ClipboardManager import com.tangem.domain.walletconnect.WcRequestUseCaseFactory +import com.tangem.domain.walletconnect.model.WcEthMethod +import com.tangem.domain.walletconnect.model.WcSolanaMethod import com.tangem.domain.walletconnect.usecase.method.WcMessageSignUseCase import com.tangem.domain.walletconnect.usecase.method.WcSignState import com.tangem.domain.walletconnect.usecase.method.WcSignStep import com.tangem.domain.walletconnect.usecase.method.WcSignUseCase import com.tangem.features.walletconnect.transaction.components.common.WcTransactionModelParams -import com.tangem.features.walletconnect.transaction.converter.WcCommonTransactionUMConverter import com.tangem.features.walletconnect.transaction.converter.WcHandleMethodErrorConverter +import com.tangem.features.walletconnect.transaction.converter.WcSignTransactionUMConverter +import com.tangem.features.walletconnect.transaction.converter.WcSignTypedDataUMConverter import com.tangem.features.walletconnect.transaction.entity.common.WcCommonTransactionModel import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionActionsUM import com.tangem.features.walletconnect.transaction.entity.sign.WcSignTransactionUM @@ -40,7 +43,8 @@ internal class WcSignTransactionModel @Inject constructor( private val router: Router, private val clipboardManager: ClipboardManager, private val useCaseFactory: WcRequestUseCaseFactory, - private val converter: WcCommonTransactionUMConverter, + private val signTypedDataUMConverter: WcSignTypedDataUMConverter, + private val signTransactionUMConverter: WcSignTransactionUMConverter, ) : Model(), WcCommonTransactionModel { private val params = paramsContainer.require() @@ -58,24 +62,44 @@ internal class WcSignTransactionModel @Inject constructor( useCase.invoke() .onEach { signState -> if (signingIsDone(signState)) return@onEach - val signTransactionUM = converter.convert( - WcCommonTransactionUMConverter.Input( - useCase = useCase, - signState = signState, - actions = WcTransactionActionsUM( - onShowVerifiedAlert = ::showVerifiedAlert, - onDismiss = { cancel(useCase) }, - onSign = useCase::sign, - onCopy = { copyData(useCase.rawSdkRequest.request.params) }, - ), - ), - ) as? WcSignTransactionUM + val signTransactionUM = convertToUI(useCase, signState) _uiState.emit(signTransactionUM) } .launchIn(this) } } + private fun convertToUI( + useCase: WcMessageSignUseCase, + signState: WcSignState, + ): WcSignTransactionUM? { + val actions = WcTransactionActionsUM( + onShowVerifiedAlert = ::showVerifiedAlert, + onDismiss = { cancel(useCase) }, + onSign = useCase::sign, + onCopy = { copyData(useCase.rawSdkRequest.request.params) }, + ) + return when (useCase.method) { + is WcEthMethod.SignTypedData -> signTypedDataUMConverter.convert( + WcSignTypedDataUMConverter.Input( + context = useCase, + signState = signState, + signModel = signState.signModel, + actions = actions, + ), + ) + is WcEthMethod.MessageSign, is WcSolanaMethod.SignMessage -> signTransactionUMConverter.convert( + WcSignTransactionUMConverter.Input( + context = useCase, + signState = signState, + signModel = signState.signModel, + actions = actions, + ), + ) + else -> null + } + } + override fun dismiss() { _uiState.value?.transaction?.onDismiss?.invoke() ?: router.pop() } 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 4c23abfde5..843e0e291d 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 @@ -3,6 +3,7 @@ package com.tangem.features.walletconnect.transaction.routes import androidx.compose.runtime.Immutable import com.tangem.core.decompose.navigation.Route import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2 import kotlinx.serialization.Serializable @Serializable @@ -27,8 +28,17 @@ internal sealed class WcTransactionRoutes : TangemBottomSheetConfigContent, Rout data class Verified(val appName: String) : Type() data object UnknownDomain : Type() data object UnsafeDomain : Type() - data class MaliciousInfo(val description: String?, val onClick: () -> Unit) : Type() - data class UnknownError(val errorMessage: String?, val onDismiss: () -> Unit) : Type() + data class BlockAidErrorInfo( + val description: String?, + val onClick: () -> Unit, + val iconType: MessageBottomSheetUMV2.Icon.Type, + val iconBgType: MessageBottomSheetUMV2.Icon.BackgroundType, + ) : Type() + data class UnknownError( + val errorMessage: String?, + val onDismiss: () -> Unit, + val onRetry: () -> Unit, + ) : Type() } } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/TransactionCheckResultsItem.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/TransactionCheckResultsItem.kt index 0ff92d4802..2b7aca4340 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/TransactionCheckResultsItem.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/TransactionCheckResultsItem.kt @@ -11,11 +11,12 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.isNullOrEmpty import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.walletconnect.impl.R +import com.tangem.features.walletconnect.transaction.entity.blockaid.BlockAidNotificationUM import com.tangem.features.walletconnect.transaction.entity.blockaid.WcEstimatedWalletChangeUM import com.tangem.features.walletconnect.transaction.entity.blockaid.WcEstimatedWalletChangesUM import com.tangem.features.walletconnect.transaction.entity.blockaid.WcSendReceiveTransactionCheckResultsUM @@ -36,15 +37,17 @@ internal fun TransactionCheckResultsItem( if (item.isLoading) { WcEstimatedWalletChangesLoadingItem() } else { - if (item.notificationText != null) { - WcTransactionCheckErrorItem(item.notificationText.resolveReference()) + if (item.notification != null) { + WcTransactionCheckErrorItem(item.notification) } if (item.estimatedWalletChanges != null) { WcEstimatedWalletChangesItem(item.estimatedWalletChanges) } else if (item.spendAllowance != null) { WcSpendAllowanceItem(item.spendAllowance, onClickAllowToSpend) + } else if (!item.additionalNotification.isNullOrEmpty()) { + WcEstimatedWalletChangesNotificationItem(description = item.additionalNotification) } else { - WcEstimatedWalletChangesNotLoadedItem() + WcEstimatedWalletChangesNotificationItem() } } } @@ -70,7 +73,11 @@ private class TransactionCheckResultsItemProvider : PreviewParameterProvider { override fun convert(value: Input): WcSendReceiveTransactionCheckResultsUM { - val description = value.result.description + val description = value.result.description?.let { if (it.isNotEmpty()) TextReference.Str(it) else null } + val simulation = value.result.simulation return WcSendReceiveTransactionCheckResultsUM( isLoading = false, - notificationText = when (value.result.validation) { + notification = when (value.result.validation) { ValidationResult.SAFE, ValidationResult.FAILED_TO_VALIDATE -> null - ValidationResult.UNSAFE -> if (!description.isNullOrEmpty()) TextReference.Str(description) else null + ValidationResult.UNSAFE -> BlockAidNotificationUM( + type = BlockAidNotificationUM.Type.ERROR, + title = TextReference.Res(R.string.wc_malicious_transaction), + text = description, + ) + ValidationResult.WARNING -> BlockAidNotificationUM( + type = BlockAidNotificationUM.Type.WARNING, + title = TextReference.Res(R.string.wc_warning_transaction), + text = description, + ) }, - estimatedWalletChanges = (value.result.simulation as? SimulationResult.Success)?.data?.let { data -> + additionalNotification = (simulation as? SimulationResult.Success)?.data?.let { data -> + if (data is SimulationData.NoWalletChangesDetected) { + TextReference.Res(R.string.wc_no_wallet_changes_detected) + } else { + null + } + }, + estimatedWalletChanges = (simulation as? SimulationResult.Success)?.data?.let { data -> when (data) { - is SimulationData.Approve -> null + is SimulationData.Approve, SimulationData.NoWalletChangesDetected -> null is SimulationData.SendAndReceive -> { val items: ImmutableList = ( data.send.map { @@ -84,7 +102,7 @@ internal class WcSendAndReceiveBlockAidUiConverter @Inject constructor( }, spendAllowance = (value.result.simulation as? SimulationResult.Success)?.data?.let { data -> when (data) { - is SimulationData.SendAndReceive -> null + is SimulationData.SendAndReceive, SimulationData.NoWalletChangesDetected -> null is SimulationData.Approve -> value.approvedAmount?.let { spendAllowanceUMConverter.convert(it) } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcTransactionCheckErrorItem.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcTransactionCheckErrorItem.kt index a8431340b3..a5d01a0246 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcTransactionCheckErrorItem.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcTransactionCheckErrorItem.kt @@ -11,26 +11,42 @@ import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.walletconnect.impl.R +import com.tangem.features.walletconnect.transaction.entity.blockaid.BlockAidNotificationUM @Composable -internal fun WcTransactionCheckErrorItem(notificationText: String, modifier: Modifier = Modifier) { +internal fun WcTransactionCheckErrorItem(notification: BlockAidNotificationUM, modifier: Modifier = Modifier) { Notification( modifier = modifier .fillMaxWidth(), config = NotificationConfig( - title = resourceReference(R.string.wc_malicious_transaction), - subtitle = TextReference.Str(notificationText), - iconResId = R.drawable.ic_alert_circle_24, + title = notification.title, + subtitle = TextReference.Str(notification.text?.resolveReference() ?: ""), + iconResId = when (notification.type) { + BlockAidNotificationUM.Type.ERROR -> R.drawable.ic_alert_circle_24 + BlockAidNotificationUM.Type.WARNING -> R.drawable.ic_alert_triangle_20 + }, ), - containerColor = TangemColorPalette.Amaranth.copy(alpha = 0.1f), - titleColor = TangemTheme.colors.text.warning, - subtitleColor = TangemTheme.colors.text.primary1, - iconTint = TangemTheme.colors.icon.warning, + containerColor = when (notification.type) { + BlockAidNotificationUM.Type.ERROR -> TangemColorPalette.Amaranth.copy(alpha = 0.1f) + BlockAidNotificationUM.Type.WARNING -> TangemColorPalette.Dark1.copy(alpha = 0.1f) + }, + titleColor = when (notification.type) { + BlockAidNotificationUM.Type.ERROR -> TangemTheme.colors.text.warning + BlockAidNotificationUM.Type.WARNING -> TangemTheme.colors.text.primary1 + }, + subtitleColor = when (notification.type) { + BlockAidNotificationUM.Type.ERROR -> TangemTheme.colors.text.primary1 + BlockAidNotificationUM.Type.WARNING -> TangemTheme.colors.text.tertiary + }, + iconTint = when (notification.type) { + BlockAidNotificationUM.Type.ERROR -> TangemTheme.colors.icon.warning + BlockAidNotificationUM.Type.WARNING -> TangemTheme.colors.icon.attention + }, ) } @@ -43,7 +59,13 @@ private fun WcTransactionCheckErrorItemPreview() { modifier = Modifier .background(TangemTheme.colors.background.tertiary), ) { - WcTransactionCheckErrorItem("The transaction approves erc20 tokens to a known malicious address") + WcTransactionCheckErrorItem( + BlockAidNotificationUM( + type = BlockAidNotificationUM.Type.ERROR, + title = TextReference.Res(R.string.wc_malicious_transaction), + text = TextReference.Str("The transaction approves erc20 tokens to a known malicious address"), + ), + ) } } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcAddressItem.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcAddressItem.kt index f14eb7984b..35e9a28c76 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcAddressItem.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcAddressItem.kt @@ -5,20 +5,23 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.Icon import androidx.compose.material3.Text -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign +import com.tangem.core.ui.components.SpacerWMax +import com.tangem.core.ui.components.atoms.text.EllipsisText +import com.tangem.core.ui.components.atoms.text.TextEllipsis import com.tangem.core.ui.components.tooltip.TangemTooltip import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.features.walletconnect.impl.R -import com.tangem.features.walletconnect.transaction.entity.common.WcAddressUM @Composable -internal fun WcAddressItem(address: WcAddressUM, modifier: Modifier = Modifier) { +internal fun WcAddressItem(address: String, modifier: Modifier = Modifier) { Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) { + var isTooltipEnabled by remember { mutableStateOf(false) } Icon( modifier = Modifier.size(TangemTheme.dimens.size24), painter = painterResource(R.drawable.ic_user_square_24), @@ -26,25 +29,26 @@ internal fun WcAddressItem(address: WcAddressUM, modifier: Modifier = Modifier) tint = TangemTheme.colors.icon.accent, ) Text( - modifier = Modifier - .padding(start = TangemTheme.dimens.spacing8) - .weight(1f), + modifier = Modifier.padding(start = TangemTheme.dimens.spacing8), text = stringResourceSafe(R.string.wc_common_address), style = TangemTheme.typography.body1, color = TangemTheme.colors.text.primary1, + maxLines = 1, ) + SpacerWMax() TangemTooltip( - modifier = Modifier - .padding(start = TangemTheme.dimens.spacing16) - .weight(1f), - text = address.fullAddress, + modifier = Modifier.padding(start = TangemTheme.dimens.spacing16), + text = address, + enabled = isTooltipEnabled, content = { contentModifier -> - Text( + EllipsisText( + onTextLayout = { isTooltipEnabled = !it.hasVisualOverflow }, modifier = contentModifier, - text = address.shortAddress, + text = address, textAlign = TextAlign.End, style = TangemTheme.typography.body1, color = TangemTheme.colors.text.tertiary, + ellipsis = TextEllipsis.Middle, ) }, ) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcNetworkItem.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcNetworkItem.kt index fe95a18f69..bc1a75f3fa 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcNetworkItem.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcNetworkItem.kt @@ -1,16 +1,21 @@ package com.tangem.features.walletconnect.transaction.ui.common import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Icon import androidx.compose.material3.Text -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow +import com.tangem.core.ui.components.tooltip.TangemTooltip import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.features.walletconnect.impl.R @@ -22,39 +27,46 @@ internal fun WcNetworkItem(networkInfo: WcNetworkInfoUM, modifier: Modifier = Mo modifier = modifier, verticalAlignment = Alignment.CenterVertically, ) { + var isTooltipEnabled by remember { mutableStateOf(false) } Icon( modifier = Modifier.size(TangemTheme.dimens.size24), painter = painterResource(R.drawable.ic_network_new_24), contentDescription = null, tint = TangemTheme.colors.icon.accent, ) - Text( - modifier = Modifier - .padding(start = TangemTheme.dimens.spacing8) - .weight(1f), - text = stringResourceSafe(R.string.wc_common_network), - style = TangemTheme.typography.body1, - color = TangemTheme.colors.text.primary1, - ) - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.End, - modifier = Modifier.weight(1f), - ) { + Row(modifier = Modifier.weight(1f), horizontalArrangement = Arrangement.SpaceBetween) { Text( - text = networkInfo.name, + modifier = Modifier.padding(start = TangemTheme.dimens.spacing8), + text = stringResourceSafe(R.string.wc_common_network), style = TangemTheme.typography.body1, - color = TangemTheme.colors.text.tertiary, + color = TangemTheme.colors.text.primary1, + maxLines = 1, ) - Spacer(modifier = Modifier.width(TangemTheme.dimens.spacing6)) - Image( - painter = painterResource(id = networkInfo.iconRes), - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = Modifier - .clip(CircleShape) - .size(TangemTheme.dimens.size20), + TangemTooltip( + modifier = Modifier.padding(start = TangemTheme.dimens.spacing16), + text = networkInfo.name, + enabled = isTooltipEnabled, + content = { contentModifier -> + Text( + modifier = contentModifier, + text = networkInfo.name, + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.tertiary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + onTextLayout = { isTooltipEnabled = it.hasVisualOverflow }, + ) + }, ) } + Image( + painter = painterResource(id = networkInfo.iconRes), + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier + .padding(start = TangemTheme.dimens.spacing6) + .clip(CircleShape) + .size(TangemTheme.dimens.size20), + ) } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcSendTransactionItems.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcSendTransactionItems.kt index 6b703bf94d..fe0f4cfc06 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcSendTransactionItems.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcSendTransactionItems.kt @@ -15,23 +15,26 @@ import com.tangem.core.ui.components.divider.DividerWithPadding import com.tangem.core.ui.extensions.clickableSingle import com.tangem.core.ui.res.TangemTheme import com.tangem.features.send.v2.api.FeeSelectorBlockComponent -import com.tangem.features.walletconnect.transaction.entity.common.WcAddressUM +import com.tangem.features.send.v2.api.entity.FeeSelectorUM import com.tangem.features.walletconnect.transaction.entity.common.WcNetworkInfoUM import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionFeeState +@Suppress("LongParameterList") @Composable internal fun WcSendTransactionItems( walletName: String?, networkInfo: WcNetworkInfoUM, feeState: WcTransactionFeeState, feeSelectorBlockComponent: FeeSelectorBlockComponent?, - address: WcAddressUM?, + feeSelectorUM: FeeSelectorUM, + address: String?, modifier: Modifier = Modifier, ) { - val onFeeBlockClicked = remember(feeState) { - when (feeState) { - WcTransactionFeeState.None -> null - is WcTransactionFeeState.Success -> feeState.onClick + val onFeeBlockClicked = remember(feeState, feeSelectorUM) { + if (feeState is WcTransactionFeeState.Success && feeSelectorUM is FeeSelectorUM.Content) { + feeState.onClick + } else { + null } } Column( diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcTransactionRequestButtons.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcTransactionRequestButtons.kt index 9d8ec8d0fb..ed0217aee9 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcTransactionRequestButtons.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcTransactionRequestButtons.kt @@ -20,6 +20,7 @@ internal fun WcTransactionRequestButtons( onDismiss: () -> Unit, onClickActiveButton: () -> Unit, modifier: Modifier = Modifier, + enabled: Boolean = true, ) { Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) { SecondaryButton( @@ -37,6 +38,7 @@ internal fun WcTransactionRequestButtons( onClick = onClickActiveButton, iconResId = R.drawable.ic_tangem_24, showProgress = isLoading, + enabled = enabled, ) } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcWalletItem.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcWalletItem.kt index 18babacc67..3a416412ee 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcWalletItem.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcWalletItem.kt @@ -5,11 +5,13 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.Icon import androidx.compose.material3.Text -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import com.tangem.core.ui.components.SpacerWMax +import com.tangem.core.ui.components.tooltip.TangemTooltip import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.features.walletconnect.impl.R @@ -17,6 +19,7 @@ import com.tangem.features.walletconnect.impl.R @Composable internal fun WcWalletItem(walletName: String, modifier: Modifier = Modifier) { Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) { + var isTooltipEnabled by remember { mutableStateOf(false) } Icon( modifier = Modifier.size(TangemTheme.dimens.size24), painter = painterResource(R.drawable.ic_wallet_new_24), @@ -24,21 +27,28 @@ internal fun WcWalletItem(walletName: String, modifier: Modifier = Modifier) { tint = TangemTheme.colors.icon.accent, ) Text( - modifier = Modifier - .padding(start = TangemTheme.dimens.spacing8) - .weight(1f), + modifier = Modifier.padding(start = TangemTheme.dimens.spacing8), text = stringResourceSafe(R.string.manage_tokens_network_selector_wallet), style = TangemTheme.typography.body1, color = TangemTheme.colors.text.primary1, + maxLines = 1, ) - Text( - modifier = Modifier - .padding(start = TangemTheme.dimens.spacing16) - .weight(1f), + SpacerWMax() + TangemTooltip( + modifier = Modifier.padding(start = TangemTheme.dimens.spacing16), text = walletName, - textAlign = TextAlign.End, - style = TangemTheme.typography.body1, - color = TangemTheme.colors.text.tertiary, + enabled = isTooltipEnabled, + content = { contentModifier -> + Text( + modifier = contentModifier, + text = walletName, + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.tertiary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + onTextLayout = { isTooltipEnabled = it.hasVisualOverflow }, + ) + }, ) } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt index 08a5ea45b0..0a0a1f1eac 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt @@ -4,7 +4,9 @@ import android.content.res.Configuration import androidx.compose.animation.animateContentSize import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -14,25 +16,29 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp +import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetWithFooter import com.tangem.core.ui.components.divider.DividerWithPadding +import com.tangem.core.ui.components.notifications.Notification 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.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.send.v2.api.FeeSelectorBlockComponent +import com.tangem.features.send.v2.api.entity.FeeSelectorUM import com.tangem.features.walletconnect.connections.entity.VerifiedDAppState import com.tangem.features.walletconnect.connections.ui.WcAppInfoItem import com.tangem.features.walletconnect.impl.R import com.tangem.features.walletconnect.transaction.components.PreviewFeeSelectorBlockComponent +import com.tangem.features.walletconnect.transaction.entity.blockaid.BlockAidNotificationUM import com.tangem.features.walletconnect.transaction.entity.blockaid.WcEstimatedWalletChangeUM import com.tangem.features.walletconnect.transaction.entity.blockaid.WcEstimatedWalletChangesUM import com.tangem.features.walletconnect.transaction.entity.blockaid.WcSendReceiveTransactionCheckResultsUM -import com.tangem.features.walletconnect.transaction.entity.common.WcAddressUM import com.tangem.features.walletconnect.transaction.entity.common.WcNetworkInfoUM import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionAppInfoContentUM import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionFeeState @@ -49,6 +55,7 @@ import kotlinx.collections.immutable.persistentListOf internal fun WcSendTransactionModalBottomSheet( state: WcSendTransactionItemUM, feeSelectorBlockComponent: FeeSelectorBlockComponent?, + feeSelectorUM: FeeSelectorUM, onClickTransactionRequest: () -> Unit, onBack: () -> Unit, onDismiss: () -> Unit, @@ -101,14 +108,23 @@ internal fun WcSendTransactionModalBottomSheet( if (state.estimatedWalletChanges != null) { TransactionCheckResultsItem(state.estimatedWalletChanges, onClickAllowToSpend) } - Spacer(Modifier.height(16.dp)) WcSendTransactionItems( + modifier = Modifier.padding(top = 16.dp), walletName = state.walletName, networkInfo = state.networkInfo, feeState = state.feeState, feeSelectorBlockComponent = feeSelectorBlockComponent, + feeSelectorUM = feeSelectorUM, address = state.address, ) + if (state.feeErrorNotification != null) { + Notification( + modifier = Modifier.padding(top = 14.dp), + config = state.feeErrorNotification.config, + iconTint = TangemTheme.colors.icon.warning, + containerColor = TangemTheme.colors.button.disabled, + ) + } } } }, @@ -119,6 +135,7 @@ internal fun WcSendTransactionModalBottomSheet( onClickActiveButton = state.onSend, activeButtonText = resourceReference(R.string.common_send), isLoading = state.isLoading, + enabled = state.sendEnabled, ) }, ) @@ -155,6 +172,7 @@ private fun WcSendTransactionBottomSheetPreview( onBack = {}, onDismiss = {}, onClickAllowToSpend = {}, + feeSelectorUM = FeeSelectorUM.Loading, ) }, ) @@ -173,8 +191,10 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide appSubtitle = "react-app.walletconnect.com", ), estimatedWalletChanges = WcSendReceiveTransactionCheckResultsUM( - notificationText = TextReference.Str( - "The transaction approves erc20 tokens to a known malicious address", + notification = BlockAidNotificationUM( + type = BlockAidNotificationUM.Type.ERROR, + title = TextReference.Res(R.string.wc_malicious_transaction), + text = TextReference.Str("The transaction approves erc20 tokens to a known malicious address"), ), estimatedWalletChanges = WcEstimatedWalletChangesUM( items = persistentListOf( @@ -194,10 +214,12 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide ), isLoading = false, ), - walletName = "Tangem 2.0", - networkInfo = WcNetworkInfoUM(name = "Ethereum", iconRes = R.drawable.img_eth_22), - feeState = WcTransactionFeeState.Success(null, {}), + walletName = "Tangem 2.0 Tangem 2.0 Tangem 2", + networkInfo = WcNetworkInfoUM(name = "Optimistic Ethereum Network", iconRes = R.drawable.img_eth_22), + feeState = WcTransactionFeeState.Success(dAppFee = null, onClick = {}), address = null, + sendEnabled = true, + feeErrorNotification = null, ), WcSendTransactionItemUM( onDismiss = {}, @@ -209,8 +231,10 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide appSubtitle = "react-app.walletconnect.com", ), estimatedWalletChanges = WcSendReceiveTransactionCheckResultsUM( - notificationText = TextReference.Str( - "The transaction approves erc20 tokens to a known malicious address", + notification = BlockAidNotificationUM( + type = BlockAidNotificationUM.Type.ERROR, + title = TextReference.Res(R.string.wc_malicious_transaction), + text = TextReference.Str("The transaction approves erc20 tokens to a known malicious address"), ), estimatedWalletChanges = WcEstimatedWalletChangesUM( items = persistentListOf( @@ -231,8 +255,13 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide ), walletName = "Tangem 2.0", networkInfo = WcNetworkInfoUM(name = "Ethereum", iconRes = R.drawable.img_eth_22), - feeState = WcTransactionFeeState.Success(null, {}), - address = WcAddressUM("0xdac17f958d2ee523a2206206994597c13d831ec7", "0x345FF...34FA"), + feeState = WcTransactionFeeState.Success(dAppFee = null, onClick = {}), + address = "0xdac17f958d2ee523a2206206994597c13d831ec7", + sendEnabled = true, + feeErrorNotification = NotificationUM.Info( + title = stringReference("Insufficient Ethereum"), + subtitle = stringReference("Top up your balance to cover the network fee"), + ), ), WcSendTransactionItemUM( onDismiss = {}, @@ -244,8 +273,10 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide appSubtitle = "react-app.walletconnect.com", ), estimatedWalletChanges = WcSendReceiveTransactionCheckResultsUM( - notificationText = TextReference.Str( - "The transaction approves erc20 tokens to a known malicious address", + notification = BlockAidNotificationUM( + type = BlockAidNotificationUM.Type.ERROR, + title = TextReference.Res(R.string.wc_malicious_transaction), + text = TextReference.Str("The transaction approves erc20 tokens to a known malicious address"), ), estimatedWalletChanges = WcEstimatedWalletChangesUM( items = persistentListOf( @@ -268,6 +299,11 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide networkInfo = WcNetworkInfoUM(name = "Ethereum", iconRes = R.drawable.img_eth_22), feeState = WcTransactionFeeState.None, address = null, + sendEnabled = false, + feeErrorNotification = NotificationUM.Info( + title = stringReference("Insufficient Ethereum"), + subtitle = stringReference("Top up your balance to cover the network fee"), + ), ), ), ) \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheetContent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheetContent.kt index b6aba3ab28..86e7fad3f4 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheetContent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheetContent.kt @@ -28,7 +28,6 @@ import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.walletconnect.connections.entity.VerifiedDAppState import com.tangem.features.walletconnect.connections.ui.WcAppInfoItem import com.tangem.features.walletconnect.impl.R -import com.tangem.features.walletconnect.transaction.entity.common.WcAddressUM import com.tangem.features.walletconnect.transaction.entity.common.WcNetworkInfoUM import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionAppInfoContentUM import com.tangem.features.walletconnect.transaction.entity.sign.WcSignTransactionItemUM @@ -196,7 +195,7 @@ private class WcSignTransactionStateProvider : CollectionPreviewParameterProvide ), walletName = null, networkInfo = WcNetworkInfoUM(name = "Ethereum", iconRes = R.drawable.img_eth_22), - address = WcAddressUM("0xdac17f958d2ee523a2206206994597c13d831ec7", "0x345FF...34FA"), + address = "0xdac17f958d2ee523a2206206994597c13d831ec7", ), ), ) \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/utils/WcNotificationsFactory.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/utils/WcNotificationsFactory.kt new file mode 100644 index 0000000000..f36b09dc1c --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/utils/WcNotificationsFactory.kt @@ -0,0 +1,65 @@ +package com.tangem.features.walletconnect.utils + +import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.ui.components.notifications.NotificationConfig +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.features.send.v2.api.entity.FeeSelectorUM +import com.tangem.features.send.v2.api.subcomponents.feeSelector.utils.FeeCalculationUtils +import com.tangem.features.walletconnect.impl.R +import javax.inject.Inject + +internal class WcNotificationsFactory @Inject constructor() { + + fun createFeeNotifications( + cryptoCurrencyStatus: CryptoCurrencyStatus, + feeSelectorUM: FeeSelectorUM?, + onFeeReload: () -> Unit, + ): NotificationUM.Info? { + return when (feeSelectorUM) { + is FeeSelectorUM.Content -> createFeeExceedsBalance(cryptoCurrencyStatus, feeSelectorUM) + is FeeSelectorUM.Error -> createFeeErrorNotification(onFeeReload) + FeeSelectorUM.Loading, null -> null + } + } + + private fun createFeeExceedsBalance( + cryptoCurrencyStatus: CryptoCurrencyStatus, + feeSelectorUM: FeeSelectorUM?, + ): NotificationUM.Info? { + // TODO: [REDACTED_TASK_KEY] localization + return NotificationUM.Info( + title = stringReference("Insufficient ${cryptoCurrencyStatus.currency.name}"), + subtitle = stringReference("Top up your balance to cover the network fee"), + ).takeIf { isFeeExceedsBalance(cryptoCurrencyStatus = cryptoCurrencyStatus, feeSelectorUM = feeSelectorUM) } + } + + private fun createFeeErrorNotification(onFeeReload: () -> Unit): NotificationUM.Info { + return NotificationUM.Info( + title = resourceReference(R.string.send_fee_unreachable_error_title), + subtitle = resourceReference(R.string.send_fee_unreachable_error_text), + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = resourceReference(R.string.warning_button_refresh), + onClick = onFeeReload, + ), + ) + } + + private fun isFeeExceedsBalance( + cryptoCurrencyStatus: CryptoCurrencyStatus, + feeSelectorUM: FeeSelectorUM?, + ): Boolean { + val feeSelectorContent = feeSelectorUM as? FeeSelectorUM.Content ?: return false + val lowestFee = when (val fees = feeSelectorContent.fees) { + is TransactionFee.Choosable -> fees.minimum + is TransactionFee.Single -> fees.normal + } + + return FeeCalculationUtils.checkExceedBalance( + feeBalance = cryptoCurrencyStatus.value.amount, + feeAmount = lowestFee.amount.value, + ) + } +} \ No newline at end of file