diff --git a/app/build.gradle.kts b/app/build.gradle.kts index dd1295ea13..2c48609883 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -99,6 +99,7 @@ dependencies { implementation(projects.domain.balanceHiding) implementation(projects.domain.balanceHiding.models) implementation(projects.domain.transaction) + implementation(projects.domain.transaction.models) implementation(projects.domain.analytics) implementation(projects.domain.visa) implementation(projects.domain.onboarding) diff --git a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt index ffd34ca773..d7c8721bff 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt @@ -18,6 +18,7 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton +@Suppress("TooManyFunctions") @Module @InstallIn(SingletonComponent::class) internal object TransactionDomainModule { @@ -189,6 +190,15 @@ internal object TransactionDomainModule { ) } + @Provides + @Singleton + fun providePrepareAndSignUseCase( + transactionRepository: TransactionRepository, + cardSdkConfigRepository: CardSdkConfigRepository, + ): PrepareAndSignUseCase { + return PrepareAndSignUseCase(transactionRepository, cardSdkConfigRepository) + } + @Provides @Singleton fun provideSignUseCase( diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index ffb1dbb2b0..9df63ef08a 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -13,9 +13,6 @@ import com.tangem.common.CompletionResult import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toDecompressedPublicKey import com.tangem.common.extensions.toHexString -import com.tangem.core.analytics.Analytics -import com.tangem.core.analytics.models.Basic -import com.tangem.core.analytics.models.Basic.TransactionSent.MemoType import com.tangem.data.walletconnect.network.ethereum.LegacySdkHelper import com.tangem.domain.models.wallet.UserWallet import com.tangem.operations.sign.SignHashCommand @@ -42,7 +39,6 @@ import org.json.JSONArray import org.json.JSONObject import timber.log.Timber import java.math.BigDecimal -import com.tangem.core.analytics.models.AnalyticsParam as CoreAnalyticsParam @Suppress("LargeClass") class WalletConnectSdkHelper { @@ -227,8 +223,6 @@ class WalletConnectSdkHelper { ) return when (result) { is Result.Success -> { - val sentFrom = CoreAnalyticsParam.TxSentFrom.WalletConnect - Analytics.send(Basic.TransactionSent(sentFrom = sentFrom, memoType = MemoType.Null)) val hash = result.data.hash if (hash.startsWith(HEX_PREFIX)) { hash @@ -394,7 +388,8 @@ class WalletConnectSdkHelper { signature = signedHash, hash = hashToSign, publicKey = wallet.publicKey.blockchainKey.toDecompressedPublicKey(), - ).asRSVLegacyEVM().toHexString().formatHex().lowercase() // use lowercase because some dapps cant handle UPPERCASE + ).asRSVLegacyEVM().toHexString().formatHex() + .lowercase() // use lowercase because some dapps cant handle UPPERCASE } } is CompletionResult.Failure -> { diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt index bfeee05b09..6c4c20e0cc 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt @@ -18,6 +18,7 @@ import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.transaction.models.AssetRequirementsCondition import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.runCatching @@ -130,6 +131,18 @@ internal class DefaultRampManager( } } + override fun checkAssetRequirements(requirements: AssetRequirementsCondition?): Boolean { + return when (requirements) { + AssetRequirementsCondition.PaidTransaction, + is AssetRequirementsCondition.PaidTransactionWithFee, + is AssetRequirementsCondition.RequiredTrustline, + -> false + is AssetRequirementsCondition.IncompleteTransaction, + null, + -> true + } + } + private suspend fun getExchangeableState( userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 40e0686f77..615b1c3778 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -411,6 +411,7 @@ internal class ChildFactory @Inject constructor( context = context, params = PushNotificationsParams( modelCallbacks = PushNotificationsModelCallbacksStub(), + source = route.source, nextRoute = AppRoute.Home(), ), componentFactory = pushNotificationsComponentFactory, diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 6d4e985501..d0ffcca403 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -1,6 +1,7 @@ package com.tangem.common.routing import android.os.Bundle +import com.tangem.common.routing.AppRoute.ManageTokens.Source import com.tangem.common.routing.bundle.RouteBundleParams import com.tangem.common.routing.bundle.bundle import com.tangem.common.routing.entity.InitScreenLaunchMode @@ -195,7 +196,15 @@ sealed class AppRoute(val path: String) : Route { ) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrencyId.value}/$yieldId") @Serializable - data object PushNotification : AppRoute(path = "/push_notification") + data class PushNotification( + val source: Source, + ) : AppRoute(path = "/push_notification") { + enum class Source { + Stories, + Main, + Onboarding, + } + } @Serializable data class WalletSettings( diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index 9257e90ce8..d04509676c 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -112,7 +112,12 @@ sealed class AnalyticsParam { val permissionType: String, ) : TxSentFrom("Approve"), TxData - data object WalletConnect : TxSentFrom("WalletConnect") + data class WalletConnect( + override val blockchain: String, + override val token: String, + override val feeType: FeeType?, + ) : TxSentFrom("WalletConnect"), TxData + data object Sell : TxSentFrom("Sell") data class NFT( @@ -131,7 +136,7 @@ sealed class AnalyticsParam { sealed interface TxData { val blockchain: String val token: String - val feeType: FeeType + val feeType: FeeType? } sealed class FeeType(val value: String) { diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt index 110391778a..875cf24182 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt @@ -55,7 +55,9 @@ sealed class Basic( if (sentFrom is AnalyticsParam.TxData) { this[AnalyticsParam.BLOCKCHAIN] = sentFrom.blockchain this[AnalyticsParam.TOKEN_PARAM] = sentFrom.token - this[AnalyticsParam.FEE_TYPE] = sentFrom.feeType.value + sentFrom.feeType?.value?.let { + this[AnalyticsParam.FEE_TYPE] = it + } } if (sentFrom is AnalyticsParam.TxSentFrom.Approve) { this[AnalyticsParam.PERMISSION_TYPE] = sentFrom.permissionType diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/BlockAidApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/BlockAidApi.kt index 605bc8a391..cdb6ec04e3 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/BlockAidApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/BlockAidApi.kt @@ -4,6 +4,7 @@ import com.tangem.datasource.api.common.blockaid.models.request.DomainScanReques import com.tangem.datasource.api.common.blockaid.models.request.EvmTransactionScanRequest import com.tangem.datasource.api.common.blockaid.models.request.SolanaTransactionScanRequest import com.tangem.datasource.api.common.blockaid.models.response.DomainScanResponse +import com.tangem.datasource.api.common.blockaid.models.response.SolanaTransactionResponse import com.tangem.datasource.api.common.blockaid.models.response.TransactionScanResponse import retrofit2.http.Body import retrofit2.http.POST @@ -17,5 +18,5 @@ interface BlockAidApi { suspend fun scanJsonRpc(@Body request: EvmTransactionScanRequest): TransactionScanResponse @POST("solana/message/scan") - suspend fun scanSolanaMessage(@Body request: SolanaTransactionScanRequest): TransactionScanResponse + suspend fun scanSolanaMessage(@Body request: SolanaTransactionScanRequest): SolanaTransactionResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/request/SolanaTransactionScanRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/request/SolanaTransactionScanRequest.kt index 232343cf53..e89428b6a6 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/request/SolanaTransactionScanRequest.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/request/SolanaTransactionScanRequest.kt @@ -7,7 +7,7 @@ import com.tangem.datasource.api.common.blockaid.models.response.TransactionMeta @JsonClass(generateAdapter = true) data class SolanaTransactionScanRequest( @Json(name = "encoding") val encoding: String = "base64", - @Json(name = "chain") val chain: String, + @Json(name = "blockchain") val blockchain: String, @Json(name = "method") val method: String, @Json(name = "options") val options: List = listOf("simulation", "validation"), @Json(name = "metadata") val metadata: TransactionMetadata, 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 be70cd4a52..a3c5d878a6 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 @@ -16,6 +16,7 @@ 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? = null, + @Json(name = "name") val name: String? = null, @Json(name = "decimals") val decimals: Int? = null, ) 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 e776056d4c..cfab51b716 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 @@ -5,6 +5,7 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class Exposure( + @Json(name = "asset_type") val assetType: String, @Json(name = "asset") val asset: Asset, @Json(name = "spenders") val spenders: Map, ) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/SolanaTransactionResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/SolanaTransactionResponse.kt new file mode 100644 index 0000000000..d98f56ee5d --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/SolanaTransactionResponse.kt @@ -0,0 +1,58 @@ +package com.tangem.datasource.api.common.blockaid.models.response + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class SolanaTransactionResponse( + @Json(name = "result") val result: SolanaTransactionResult, +) + +@JsonClass(generateAdapter = true) +data class SolanaTransactionResult( + @Json(name = "validation") val validation: SolanaTransactionValidation, + @Json(name = "simulation") val simulation: SolanaTransactionSimulation? = null, +) + +@JsonClass(generateAdapter = true) +data class SolanaTransactionValidation( + @Json(name = "result_type") val resultType: String, + @Json(name = "description") val description: String?, +) + +@JsonClass(generateAdapter = true) +data class SolanaTransactionSimulation( + @Json(name = "account_summary") val accountSummary: SolanaTransactionAccountSummary, + @Json(name = "error") val error: String? = null, + @Json(name = "error_details") val errorDetails: String? = null, +) + +@JsonClass(generateAdapter = true) +data class SolanaTransactionAccountSummary( + @Json(name = "account_assets_diff") + val accountAssetsDiff: List, +) + +@JsonClass(generateAdapter = true) +data class SolanaTransactionAssetDiff( + @Json(name = "asset_type") val assetType: String, + @Json(name = "asset") val asset: SolanaTransactionAsset, + @Json(name = "in") val inTransfer: SolanaTransferDetail? = null, + @Json(name = "out") val outTransfer: SolanaTransferDetail? = null, +) + +@JsonClass(generateAdapter = true) +data class SolanaTransactionAsset( + @Json(name = "address") val address: String? = null, + @Json(name = "symbol") val symbol: String? = null, + @Json(name = "name") val name: String? = null, + @Json(name = "decimals") val decimals: Int? = null, + @Json(name = "type") val type: String? = null, + @Json(name = "logo") val logoUrl: String? = null, +) + +@JsonClass(generateAdapter = true) +data class SolanaTransferDetail( + @Json(name = "value") val amount: String? = null, + @Json(name = "summary") val summary: String? = null, +) \ No newline at end of file diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index c9acb51a42..ddcad5ed92 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -116,6 +116,7 @@ Nicht genug ADA Akzeptieren Zugang verweigert + Hinzufügen Zum Portfolio hinzufügen Token hinzufügen Vertragsadresse @@ -1396,12 +1397,18 @@ Trustline aktivieren Um dieses Token zu erhalten, muss eine Trustline aktiviert sein. Das Netzwerk benötigt eine Reserve von %1$s %2$s. Trustline erforderlich + Das erforderliche Netzwerk %s ist nicht in Ihrem Portfolio hinzugefügt. Fügen Sie es zuerst hinzu und fahren Sie dann mit der Verbindung fort. + Netzwerk zum Portfolio hinzufügen Bösartige/ verdächtige Domäne Unbekannte Domäne Trotzdem verbinden Zeitüberschreitungsfehler. Bitte versuche es später erneut. WalletConnect konnte nicht hergestellt werden Diese Domäne kann nicht verifiziert werden. Überprüfe die Anfrage sorgfältig und bestätige diese dann. + Um fortzufahren, verbinden Sie bitte Ihre dApp-Sitzung erneut mit dem erforderlichen Netzwerk %s. + Netzwerk nicht verbunden + Überprüfen Sie Ihre Netzwerkverbindung + Anforderungs-Zeitüberschreitung Bitte kehre zu Deinem Browser zurück und stellen die Verbindung über WalletConnect erneut her. WalletConnect-Sitzung wurde getrennt Trotzdem unterschreiben @@ -1411,9 +1418,11 @@ Tangem Wallet unterstützt derzeit nicht %s Fehlercode: 8 005. Wenn das Problem weiterhin besteht, wende Dich bitte an unseren Support. Wir haben einen unbekannten Fehler festgestellt. + Dieses Netzwerk %s wird von Tangem Wallet nicht unterstützt und kann nicht verbunden werden. + Nicht unterstütztes Netzwerk Tangem unterstützt derzeit das erforderliches Netzwerk von %s nicht. Nicht unterstützte Netzwerke - Diese Domain hat die Überprüfungen bestanden und gilt als sicher, vertrauenswürdig und frei von bekannten Bedrohungen oder verdächtigen Aktivitäten. %s + Diese Domain hat die Überprüfungen bestanden und gilt als sicher, vertrauenswürdig und frei von bekannten Bedrohungen oder verdächtigen Aktivitäten. Verifizierte Domain Falsche Karte oder falscher Ring in der App ausgewählt Wir haben eine Art Problem diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 3810328a13..fa2411cdf5 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -113,6 +113,7 @@ ADA insuficiente Aceptar Acceso denegado + Agregar Añadir al portafolio Agregar token Dirección @@ -1336,12 +1337,18 @@ Habilitar línea de confianza Una línea de confianza debe estar habilitada para recibir este token. La red requiere un %1$s %2$s reserva. Se requiere línea de confianza + La red requerida %s no está añadida a su portafolio. Añádala primero y luego continúe con la conexión. + Agregar red al portafolio Dominio malicioso Dominio desconocido Conectarse de todas formas Error de tiempo de espera. Por favor, inténtalo de nuevo más tarde. Error al establecer WalletConnect Este dominio no puede ser verificado. Compruebe cuidadosamente la solicitud de aprobación. + Para continuar, vuelva a conectar su sesión de dApp con la red requerida %s. + Red no conectada + Verifique su conexión de red + Tiempo de espera de la solicitud agotado Vuelva a su navegador y vuelva a conectarse a través de WalletConnect. La sesión de Wallet Connect se desconectó Firmar de todos modos @@ -1352,9 +1359,11 @@ dApp no compatible Código de error: 8 005. Si el problema persiste, no dudes en contactar con nuestro soporte. Hemos encontrado un error desconocido + Esta red %s no es compatible con Tangem Wallet y no puede conectarse. + Red no compatible Actualmente, Tangem no es compatible con una red requerida por %s. Redes no compatibles - Este dominio ha superado las verificaciones y se considera seguro, confiable y libre de amenazas conocidas o actividades sospechosas. %s + Este dominio ha superado las verificaciones y se considera seguro, confiable y libre de amenazas conocidas o actividades sospechosas. Dominio verificado Se seleccionó una tarjeta o un anillo incorrectos en la app Tenemos algún tipo de problema diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index a24b04d401..e907d8df01 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -95,6 +95,7 @@ ADA insuffisant Accepter Accès refusé + Ajouter Ajouter au portfolio Ajouter un jeton Adresse @@ -195,6 +196,7 @@ Rejeter Recharger Renommer + Obligatoire Enregistrez Sauvegarder les modifications Rechercher @@ -1317,22 +1319,58 @@ 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 + Le réseau requis %s n’est pas ajouté à votre portefeuille. Ajoutez-le d’abord, puis poursuivez la connexion. + Ajouter le réseau au portefeuille Domaine malveillant + Domaine inconnu + Se connecter quand même + Erreur de délai d\'attente. Veuillez réessayer plus tard. + Échec de la connexion à WalletConnect + Ce domaine ne peut pas être vérifié. Vérifiez attentivement la demande avant de l\'approuver. + Pour continuer, veuillez reconnecter votre session dApp avec le réseau requis %s. + Réseau non connecté + Vérifiez votre connexion réseau + Délai d’attente de la requête dépassé + Veuillez retourner à votre navigateur et vous reconnecter via WalletConnect. + La session Wallet Connect a été déconnectée. Signer quand même + Code d\'erreur : %s. Si le problème persiste, n\'hésitez pas à contacter notre service d\'assistance. Si le problème persiste, n’hésitez pas à contacter notre support. + Nous avons rencontré une erreur inconnue. 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 - Ce domaine a passé les contrôles de vérification et est considéré comme sûr, fiable et exempt de menaces connues ou d’activités suspectes.%s + Le réseau %s n’est pas pris en charge par Tangem Wallet et ne peut pas être connecté. + Réseau non pris en charge + Tangem ne prend actuellement pas en charge le réseau requis par %s. + Réseaux non pris en charge + Ce domaine a passé les contrôles de vérification et est considéré comme sûr, fiable et exempt de menaces connues ou d’activités suspectes. + Domaine vérifié + Carte ou bague incorrecte sélectionnée dans l\'application + Nous avons un problème. + Toutes les dApps sont déconnectées Autoriser à dépenser Adresse + Connecter Chargement + Réseau + Réseaux Illimité + Wallet + Application connectée Réseaux connectés + Connecté à %1$s + Consultez le solde et l\'activité de votre portefeuille + Signer des transactions sans vous en informer + Demander l\'accord pour les transactions + Ne pourra pas + Souhaite + Demande de connexion Connexions Contenu Copier les données + dApp déconnectée Déconnecter tout Toutes les sessions dApp seront déconnectées. Votre portefeuille ne sera plus lié à aucune dApp. Déconnecter toutes les dApps @@ -1344,13 +1382,22 @@ La proposition de connexion a expiré Modifications estimées du portefeuille La transaction n\'a pas pu être simulée. Veuillez procéder avec prudence. + Rechargez votre solde pour couvrir les frais de réseau. + %1$s Insuffisant Transaction malveillante + Ajoutez le réseau %s à votre portefeuille pour ce wallet + Le wallet ne nécessite aucun réseaux Nouvelle connexion Connectez votre portefeuille à différentes dApps Aucune séance Des risques potentiels ou un comportement malveillant ont été détectés. Se connecter ou signer des transactions peut entraîner une perte de fonds. + Risque de sécurité connu + Ouvrez l\'application Web3 et sélectionnez l\'option WalletConnect. Demande de Type de signature + Au moins un réseau est requis pour la connexion à une dApp. + Spécifier les réseaux sélectionnés + Signé avec succès À Demande de transaction Demande de transaction diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index a66d2ba966..2e061c0bce 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -71,8 +71,10 @@ 設定に移動して、Tangemアプリで生体認証を有効にします。 生体認証を有効にする %1$sが無効になると、アプリのロックを解除してウォレットを操作するために、パスコードを入力する必要があります。 + 後でウォレットのアクセスコードを入力してもらいます。それを安全に保存し、今後の利用に備えるためです。 これにより、保存されているウォレットアクセスコードがすべて削除されます。ウォレットでの今後の操作には、アクセスコードの送信が必要になります。 保存したデバイスを削除すると、保存されているすべてのウォレットとそのアクセスコードがアプリから削除されます。 + これにより、保存されているウォレットアクセスコードがすべて削除されます。今後ウォレットを操作するには、アクセスコードの送信が必要になります。 アクセスコードを要求する このオプションを選択すると、機密性の高い操作における生体認証が無効になります。取引の署名時などには、毎回アクセスコードの入力が必要になります。 アクセスコードを保存 @@ -147,6 +149,7 @@ ADAが不足しています。 受け入れる アクセスが拒否されました + 追加 ポートフォリオに追加 トークンを追加 アドレス @@ -475,6 +478,8 @@ 実行すると、最初からやり直す必要があります。 Googleドライブのバックアップに保存されている既存のウォレットを復元する Googleドライブのバックアップ + Tangemの業界最高水準のハードウェアウォレットで、今すぐセキュリティをアップグレードしましょう。 + ハードウェアウォレット バックアップへ移動 アクセスコードを使用してウォレットを保護するには、まずバックアップを完了してください。 まずバックアップを完了する @@ -486,7 +491,21 @@ 最新の機能とニュースをお届けします シードフレーズのバックアップ モバイルウォレットを作成する + このリカバリーフレーズはすでにインポートされています。 モバイルウォレット + 手続き中も資金は安全に保管され、完全にアクセス可能です + 資金へのアクセス + すべてのプライベートウォレットデータはモバイルアプリから削除され、Tangemデバイスにのみ安全に保存されます。 + セキュリティ全般 + 秘密鍵は、アプリからTangemカード・リングに移動します + 鍵の移行 + デバイスをスキャン + アップグレードを開始 + ウォレットをTangemウォレットにアップグレードします。これにより、コールドストレージで資産を安全に保管できます。 + Tangemウォレット + ハードウェアウォレットにアップグレード + Tangemの業界最高水準のハードウェアウォレットで、暗号資産を安全に保管しましょう。 + ハードウェアバックアップでウォレットをアップグレード この情報はAIで生成されました。 \nエラーが見つかった場合は、ここをタップしてください。 アクセスコードを変更するには、上図のようにカードまたはリングをタップし、操作が終了するまで取り外さないでください。 パスコードを変更するには、上記のようにカードをタップし、操作が終了するまで取り外さないでください。 @@ -1437,12 +1456,18 @@ トラストラインを有効にする このトークンを受け取るには、トラストラインを有効にする必要があります。ネットワークには%1$s %2$s予備金が必要です。 トラストラインが必要 + 必要なネットワーク %s はポートフォリオに追加されていません。まず追加してから接続を続行してください。 + ネットワークをポートフォリオに追加 悪意のあるドメイン 不明なドメイン とにかく接続する タイムアウトエラーが発生しました。しばらくしてからもう一度お試しください。 WalletConnectを確立できませんでした このドメインは検証できません。承認前にリクエスト内容をよく確認してください。 + 続行するには、必要なネットワーク %s でdAppセッションを再接続してください。 + ネットワークが接続されていません + ネットワーク接続を確認してください + リクエストがタイムアウトしました ブラウザに戻り、WalletConnect経由で再接続してください。 Wallet Connectセッションが接続解除されました とにかくサインする @@ -1453,9 +1478,11 @@ サポートされていないdApp エラーコード: 8 005。問題が解決しない場合は、お気軽にサポートまでお問い合わせください。 不明なエラーが発生しました + このネットワーク %s はTangem Walletでサポートされておらず、接続できません。 + サポートされていないネットワーク Tangemは現在%sで必要なネットワークをサポートしていません。 未対応のネットワーク - このドメインは検証チェックに合格しており、安全で信頼でき、既知の脅威や不審な活動がないと判断されています。%s + このドメインは検証チェックに合格しており、安全で信頼でき、既知の脅威や不審な活動がないと判断されています。 検証済みドメイン アプリで間違ったカードまたはリングが選択されました 問題が起きています @@ -1483,7 +1510,7 @@ 使用可能量の設定 dAppが接続解除されました すべての接続を解除する - すべてのdAppセッションが切断されます。ウォレットはどのdAppにも接続されなくなります。 + すべてのdAppセッションの接続が解除されます。ウォレットはどのdAppにもリンクされなくなります。 すべてのdAppを接続解除する 新しいURIで、再度ペアリングを試してください 無効なdAppドメイン diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 6bef7c8991..befe0fd2d7 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -92,6 +92,7 @@ Недостаточно ADA Принять Доступ запрещен + Добавить Добавить в портфель Добавить токен Адрес @@ -1306,12 +1307,18 @@ Открыть Trustline Чтобы получить этот токен, необходимо включить Trustline. Сеть требует резерв %1$s %2$s. Требуется трастлайн + Требуемая сеть %s не добавлена в ваш портфель. Добавьте её, а затем выполните подключение. + Добавьте сеть в ваш портфель Вредоносный домен Неизвестный домен Всё равно подключиться Ошибка тайм-аута. Пожалуйста, попробуйте позже. Не удалось подключиться через Wallet Connect Этот домен не может быть верифицирован. Внимательно проверьте запрос перед одобрением. + Чтобы продолжить, переподключите сессию dApp с требуемой сетью %s. + Сеть не подключена + Проверьте подключение к сети + Время ожидания запроса истекло Пожалуйста, вернитесь в браузер и выполните повторное подключение через WalletConnect. Сессия Wallet Connect была завершена Подписать всё равно @@ -1321,9 +1328,11 @@ Кошелек Tangem в настоящий момент не поддерживает %s Неподдерживаемый dApp Мы обнаружили неизвестную ошибку + Эта сеть %s не поддерживается Tangem Wallet и не может быть подключена. + Неподдерживаемая сеть Tangem в настоящее время не поддерживает необходимую сеть для %s Неподдерживаемые сети - Этот домен прошёл проверку и считается безопасным, надёжным и свободным от известных угроз или подозрительной активности. %s + Этот домен прошёл проверку и считается безопасным, надёжным и свободным от известных угроз или подозрительной активности. Верифицированный домен Выбрана не верная карта или кольцо Похоже, возникла проблема diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index e8b9b18fbc..5c7d9f4ef4 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -92,6 +92,7 @@ Недостатньо ADA Прийняти Доступ заборонено + Додати Додати у портфель Додати токен Адреса @@ -1293,19 +1294,27 @@ Відкрити Trustline Щоб отримати цей токен, потрібно увімкнути Trustline. Мережа вимагає резерв %1$s %2$s. Відкрийте Trustline + Потрібна мережа %s не додана до вашого портфеля. Спочатку додайте її, а потім виконайте підключення. + Додати мережу до портфеля Невідомий домен Все одно підключитися Помилка тайм-ауту. Будь ласка, спробуйте пізніше. Не вдалося зʼєднатися через Wallet Connect Цей домен не може бути підтверджений. Уважно перевірте запит перед схваленням. + Щоб продовжити, перепідключіть сесію dApp із потрібною мережею %s. + Мережа не підключена + Перевірте підключення до мережі + Час очікування запиту вичерпано Будь ласка, поверніться до браузеру і повторно підключіться через WalletConnect. Сеанс Wallet Connect було завершено Код помилки: %s. Якщо проблема зберігається, зверніться до нашої служби підтримки. Ми зіткнулися з невідомою помилкою Tangem Wallet наразі не підтримує %s + Ця мережа %s не підтримується Tangem Wallet і не може бути підключена. + Непідтримувана мережа Tangem наразі не підтримує необхідну мережу для %s. Непідтримувані мережі - Цей домен пройшов перевірку та вважається безпечним, надійним і вільним від відомих загроз чи підозрілої активності. %s + Цей домен пройшов перевірку та вважається безпечним, надійним і вільним від відомих загроз чи підозрілої активності. Верифікований домен Обрана не вірна картка або кільце Схоже, виникла проблема @@ -1337,6 +1346,7 @@ Немає мереж Будь ласка, згенеруйте новий URI та спробуйте ще раз Термін для з’єднання минув + Прогнозовані зміни Поповніть баланс, щоб покрити комісію мережі Недостатньо %1$s Додайте %s мережі до вашого портфелю для цього гаманця diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index f9238788d1..dc9ced8f5c 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -152,6 +152,7 @@ Not enough ADA Accept Access denied + Add Add to portfolio Add token Address @@ -484,6 +485,8 @@ If you do, you\'ll need to start over. Recover an existing wallet stored in your Google Drive backup Google Drive Backup + Upgrade your security right away with a best in class hardware wallet from Tangem. + Hardware Wallet Go to backup To secure your wallet with a Access Code, complete the backup first. Finish Backup First @@ -497,6 +500,19 @@ Create Mobile Wallet This recovery phrase has already been imported Mobile Wallet + Your funds stay safe and fully accessible during the process + Funds access + All private wallet data will be removed from the mobile app and stored securely on your Tangem device only + General Security + Private keys will be moved from the app to your Tangem card or ring + Key Migration + Scan device + Start upgrade + You’re about to upgrade your wallet to Tangem Wallet. This will keep your assets safe with cold storage. + Tangem Wallet + Upgrade to Hardware Wallet + Keep your crypto safe with Tangem’s best-in-class hardware wallet. + Upgrade wallet with a hardware
backup This information was generated with AI.\nTap here, if you find any errors. To change the access code tap the card or ring as shown above and do not remove until the end of the operation To change the passcode tap the card as shown above and do not remove until the end of the operation @@ -1531,7 +1547,7 @@ Unsupported network Tangem does not currently support a required network by %s. Unsupported networks - This domain has passed verification checks and is considered safe, reputable, and free from known threats or suspicious activity. %s + This domain has passed verification checks and is considered safe, reputable, and free from known threats or suspicious activity. Verified domain Wrong card or ring selected in the App We\'ve got some kind of problem diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/PinTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/PinTextField.kt index 0a593a7a8d..214da97109 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/PinTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/PinTextField.kt @@ -59,7 +59,7 @@ fun PinTextField( } .focusRequester(focusRequester), keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Number, + keyboardType = KeyboardType.NumberPassword, imeAction = ImeAction.Done, ), singleLine = true, diff --git a/core/ui/src/main/res/drawable/img_approvale_new_24.xml b/core/ui/src/main/res/drawable/img_approvale_new_24.xml new file mode 100644 index 0000000000..bc1f814f54 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_approvale_new_24.xml @@ -0,0 +1,17 @@ + + + + + + + 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 128d1cfd56..5334f1e066 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 @@ -3,7 +3,7 @@ package com.tangem.data.blockaid import com.domain.blockaid.models.dapp.CheckDAppResult import com.domain.blockaid.models.transaction.* import com.domain.blockaid.models.transaction.simultation.AmountInfo -import com.domain.blockaid.models.transaction.simultation.ApprovedAmount +import com.domain.blockaid.models.transaction.simultation.ApproveInfo import com.domain.blockaid.models.transaction.simultation.SimulationData import com.domain.blockaid.models.transaction.simultation.TokenInfo import com.tangem.blockchain.extensions.hexToBigDecimal @@ -17,6 +17,9 @@ 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" +private const val VALIDATION_MALICIOUS_STATUS = "Malicious" + +private const val SOL_ASSET_SYMBOL = "SOL" internal object BlockAidMapper { @@ -28,6 +31,26 @@ internal object BlockAidMapper { } } + fun mapToDomain(from: SolanaTransactionResponse): CheckTransactionResult { + val validation = when (from.result.validation.resultType) { + VALIDATION_SAFE_STATUS -> ValidationResult.SAFE + VALIDATION_WARNING_STATUS -> ValidationResult.WARNING + VALIDATION_MALICIOUS_STATUS -> ValidationResult.UNSAFE + else -> ValidationResult.FAILED_TO_VALIDATE + } + val simulationResponse = from.result.simulation + val simulation = if (simulationResponse == null) { + SimulationResult.FailedToSimulate + } else { + mapToSolanaAssetsDiffs(simulationResponse.accountSummary.accountAssetsDiff) + } + return CheckTransactionResult( + validation = validation, + description = from.result.validation.description, + simulation = simulation, + ) + } + fun mapToDomain(from: TransactionScanResponse): CheckTransactionResult { return CheckTransactionResult( validation = when { @@ -68,7 +91,7 @@ internal object BlockAidMapper { fun mapToSolanaRequest(from: TransactionData): SolanaTransactionScanRequest { return SolanaTransactionScanRequest( - chain = from.chain.lowercase(), + blockchain = from.chain.lowercase(), accountAddress = from.accountAddress, metadata = TransactionMetadata(from.domainUrl), method = from.method, @@ -78,35 +101,61 @@ internal object BlockAidMapper { private fun mapSimulationSuccessResult(from: AccountSummaryResponse): SimulationResult { return when { - !from.assetsDiffs.isNullOrEmpty() -> mapSendReceiveTransaction( - from.assetsDiffs, - ) - !from.exposures.isNullOrEmpty() -> mapApproveTransaction( - from.exposures, - ) - !from.traces.isNullOrEmpty() -> mapNftSendReceiveTransaction(from.traces) + !from.exposures.isNullOrEmpty() -> mapApproveTransaction(from.exposures) + !from.assetsDiffs.isNullOrEmpty() -> mapSendReceiveTransaction(from.assetsDiffs) else -> SimulationResult.Success(data = SimulationData.NoWalletChangesDetected) } } - private fun mapApproveTransaction(exposures: List?): SimulationResult { - val amounts = exposures?.flatMap { exposure -> - val tokenInfo = TokenInfo( - chainId = exposure.asset.chainId, - logoUrl = exposure.asset.logoUrl, - symbol = exposure.asset.symbol ?: "", - decimals = exposure.asset.decimals ?: 0, + private fun mapToSolanaAssetsDiffs(assetsDiffs: List): SimulationResult { + val sendInfo = assetsDiffs.mapNotNull { assetDiff -> + val outTransfer = assetDiff.outTransfer ?: return@mapNotNull null + val amount = outTransfer.amount?.toBigDecimalOrNull() ?: return@mapNotNull null + AmountInfo.FungibleTokens( + amount = amount, + token = TokenInfo( + chainId = null, + logoUrl = assetDiff.asset.logoUrl, + symbol = assetDiff.asset.assetSymbol(), + decimals = assetDiff.asset.decimals ?: 0, + ), ) - exposure.spenders.flatMap { (_, spender) -> - val isUnlimited = spender.isApprovedForAll == true - val approval = spender.approval?.hexToBigDecimal() - spender.exposure.map { detail -> - ApprovedAmount( - approvedAmount = detail.value?.toBigDecimalOrNull() ?: approval ?: 1.toBigDecimal(), - isUnlimited = isUnlimited, - tokenInfo = tokenInfo, - ) - } + } + val receiveInfo = assetsDiffs.mapNotNull { assetDiff -> + val inTransfer = assetDiff.inTransfer ?: return@mapNotNull null + val amount = inTransfer.amount?.toBigDecimalOrNull() ?: return@mapNotNull null + AmountInfo.FungibleTokens( + amount = amount, + token = TokenInfo( + chainId = null, + logoUrl = assetDiff.asset.logoUrl, + symbol = assetDiff.asset.assetSymbol(), + decimals = assetDiff.asset.decimals ?: 0, + ), + ) + } + + return if (sendInfo.isNotEmpty() || receiveInfo.isNotEmpty()) { + SimulationResult.Success(SimulationData.SendAndReceive(send = sendInfo, receive = receiveInfo)) + } else { + SimulationResult.Success(SimulationData.NoWalletChangesDetected) + } + } + + private fun SolanaTransactionAsset.assetSymbol(): String { + return if (type?.lowercase().equals(SOL_ASSET_SYMBOL, ignoreCase = true)) { + symbol ?: SOL_ASSET_SYMBOL + } else { + symbol.orEmpty() + } + } + + private fun mapApproveTransaction(exposures: List?): SimulationResult { + val amounts: List? = exposures?.flatMap { exposure -> + if (exposure.assetType.isNFT()) { + listOf(mapApproveNftTransaction(exposure)) + } else { + mapTransaction(exposure) } } return if (!amounts.isNullOrEmpty()) { @@ -116,6 +165,34 @@ internal object BlockAidMapper { } } + private fun mapTransaction(exposure: Exposure): List { + val tokenInfo = TokenInfo( + chainId = exposure.asset.chainId, + logoUrl = exposure.asset.logoUrl, + symbol = exposure.asset.symbol ?: "", + decimals = exposure.asset.decimals ?: 0, + ) + return exposure.spenders.flatMap { (_, spender) -> + val isUnlimited = spender.isApprovedForAll == true + val approval = spender.approval?.hexToBigDecimal() + spender.exposure.map { detail -> + ApproveInfo.Amount( + approvedAmount = detail.value?.toBigDecimalOrNull() ?: approval ?: 1.toBigDecimal(), + isUnlimited = isUnlimited, + tokenInfo = tokenInfo, + ) + } + } + } + + private fun mapApproveNftTransaction(exposure: Exposure): ApproveInfo.NonFungibleToken { + return ApproveInfo.NonFungibleToken( + name = exposure.asset.name.orEmpty(), + logoUrl = exposure.spenders.values.firstOrNull()?.exposure?.firstOrNull()?.logoUrl + ?: exposure.asset.logoUrl, + ) + } + private fun mapSendReceiveTransaction(assetDiffs: List?): SimulationResult { val sendInfo = arrayListOf() val receiveInfo = arrayListOf() @@ -128,13 +205,31 @@ internal object BlockAidMapper { decimals = diff.asset.decimals ?: 0, ) diff.outTransfer.orEmpty().forEach { transfer -> - transfer.value?.toBigDecimalOrNull()?.let { amount -> - sendInfo.add(AmountInfo.FungibleTokens(amount = amount, token = token)) + if (diff.assetType.isNFT()) { + sendInfo.add( + AmountInfo.NonFungibleTokens( + name = diff.asset.name.orEmpty(), + logoUrl = token.logoUrl, + ), + ) + } else { + transfer.value?.toBigDecimalOrNull()?.let { amount -> + sendInfo.add(AmountInfo.FungibleTokens(amount = amount, token = token)) + } } } diff.inTransfer.orEmpty().forEach { transfer -> - transfer.value?.toBigDecimalOrNull()?.let { amount -> - receiveInfo.add(AmountInfo.FungibleTokens(amount = amount, token = token)) + if (diff.assetType.isNFT()) { + receiveInfo.add( + AmountInfo.NonFungibleTokens( + name = diff.asset.name.orEmpty(), + logoUrl = token.logoUrl, + ), + ) + } else { + transfer.value?.toBigDecimalOrNull()?.let { amount -> + receiveInfo.add(AmountInfo.FungibleTokens(amount = amount, token = token)) + } } } } @@ -146,17 +241,7 @@ internal object BlockAidMapper { } } - private fun mapNftSendReceiveTransaction(traces: List?): SimulationResult { - val sendInfo = traces?.mapNotNull { - it.exposed?.let { exposed -> - AmountInfo.NonFungibleTokens(name = "${it.asset.name} #${exposed.tokenId}", logoUrl = exposed.logoUrl) - } - } - - return if (!sendInfo.isNullOrEmpty()) { - SimulationResult.Success(SimulationData.SendAndReceive(send = sendInfo, receive = listOf())) - } else { - SimulationResult.Success(SimulationData.NoWalletChangesDetected) - } + private fun String.isNFT(): Boolean { + return this.lowercase() == "erc721" || this.lowercase() == "erc1155" || this.lowercase() == "nft" } } \ No newline at end of file diff --git a/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/DefaultBlockAidRepository.kt b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/DefaultBlockAidRepository.kt index 66483ed2df..985e8d7e2e 100644 --- a/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/DefaultBlockAidRepository.kt +++ b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/DefaultBlockAidRepository.kt @@ -24,16 +24,21 @@ internal class DefaultBlockAidRepository( } override suspend fun verifyTransaction(data: TransactionData): CheckTransactionResult { - val response = withContext(dispatchers.io) { - when (data.params) { - is TransactionParams.Evm -> { - api.scanJsonRpc(mapper.mapToEvmRequest(data)) - } - is TransactionParams.Solana -> { - api.scanSolanaMessage(mapper.mapToSolanaRequest(data)) - } - } + return when (data.params) { + is TransactionParams.Evm -> scanEvmTransaction(data = data) + is TransactionParams.Solana -> scanSolanaTransaction(data = data) } - return mapper.mapToDomain(response) } + + private suspend fun scanEvmTransaction(data: TransactionData): CheckTransactionResult = + withContext(dispatchers.io) { + val response = api.scanJsonRpc(mapper.mapToEvmRequest(data)) + mapper.mapToDomain(response) + } + + private suspend fun scanSolanaTransaction(data: TransactionData): CheckTransactionResult = + withContext(dispatchers.io) { + val response = api.scanSolanaMessage(mapper.mapToSolanaRequest(data)) + mapper.mapToDomain(response) + } } \ 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 00dd54e198..3b24f6e0a4 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 @@ -4,6 +4,7 @@ import com.domain.blockaid.models.dapp.CheckDAppResult import com.domain.blockaid.models.transaction.SimulationResult import com.domain.blockaid.models.transaction.ValidationResult import com.domain.blockaid.models.transaction.simultation.AmountInfo +import com.domain.blockaid.models.transaction.simultation.ApproveInfo import com.domain.blockaid.models.transaction.simultation.SimulationData import com.google.common.truth.Truth import com.tangem.datasource.api.common.blockaid.models.response.* @@ -44,6 +45,7 @@ class BlockAidMapperTest { val exposure = Exposure( asset = Asset(chainId = 1, logoUrl = "logo", symbol = "PEPE", decimals = 8), spenders = mapOf("spender" to spenderDetails), + assetType = "native", ) val response = TransactionScanResponse( validation = ValidationResponse(status = "Success", resultType = "Benign", description = ""), @@ -65,9 +67,10 @@ class BlockAidMapperTest { val approve = simulation?.data as? SimulationData.Approve Truth.assertThat(approve).isNotNull() - Truth.assertThat(approve?.approvedAmounts?.size).isEqualTo(1) - Truth.assertThat(approve?.approvedAmounts?.first()?.approvedAmount).isEqualTo(BigDecimal("1000.0")) - Truth.assertThat(approve?.approvedAmounts?.first()?.isUnlimited).isTrue() + Truth.assertThat(approve?.items?.size).isEqualTo(1) + Truth.assertThat((approve?.items?.first() as? ApproveInfo.Amount)?.approvedAmount) + .isEqualTo(BigDecimal("1000.0")) + Truth.assertThat((approve?.items?.first() as? ApproveInfo.Amount)?.isUnlimited).isTrue() } @Test diff --git a/data/blockaid/src/test/kotlin/com/tangem/data/blockaid/DefaultBlockAidRepositoryTest.kt b/data/blockaid/src/test/kotlin/com/tangem/data/blockaid/DefaultBlockAidRepositoryTest.kt index d125246bce..54d8331f1f 100644 --- a/data/blockaid/src/test/kotlin/com/tangem/data/blockaid/DefaultBlockAidRepositoryTest.kt +++ b/data/blockaid/src/test/kotlin/com/tangem/data/blockaid/DefaultBlockAidRepositoryTest.kt @@ -11,6 +11,7 @@ import com.tangem.datasource.api.common.blockaid.models.request.DomainScanReques import com.tangem.datasource.api.common.blockaid.models.request.EvmTransactionScanRequest import com.tangem.datasource.api.common.blockaid.models.request.SolanaTransactionScanRequest import com.tangem.datasource.api.common.blockaid.models.response.DomainScanResponse +import com.tangem.datasource.api.common.blockaid.models.response.SolanaTransactionResponse import com.tangem.datasource.api.common.blockaid.models.response.TransactionScanResponse import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider import io.mockk.* @@ -91,7 +92,7 @@ class DefaultBlockAidRepositoryTest { ) val request = mockk() - val response = mockk() + val response = mockk() val expectedResult = mockk() every { mapper.mapToSolanaRequest(data) } returns request diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt index 18929af1a4..eca48639fe 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt @@ -9,6 +9,7 @@ import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.data.common.api.safeApiCall import com.tangem.data.common.currency.CardCryptoCurrencyFactory import com.tangem.data.common.currency.UserTokensResponseFactory +import com.tangem.data.common.currency.UserTokensSaver import com.tangem.data.common.network.NetworkFactory import com.tangem.data.common.utils.retryOnError import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher @@ -42,6 +43,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider internal class DefaultManageTokensRepository( private val tangemTechApi: TangemTechApi, private val userWalletsStore: UserWalletsStore, + private val userTokenSaver: UserTokensSaver, private val manageTokensUpdateFetcher: ManageTokensUpdateFetcher, private val userTokensResponseStore: UserTokensResponseStore, private val testnetTokensStorage: TestnetTokensStorage, @@ -127,7 +129,8 @@ internal class DefaultManageTokensRepository( val tokensResponse = request.params.userWalletId?.let { userWalletId -> if (loadUserTokensFromRemote && userWallet != null) { safeApiCall({ tangemTechApi.getUserTokens(userWalletId.stringValue).bind() }) { - createDefaultUserTokensResponse(userWallet) + // save tokens response only if loadUserTokensFromRemote is true and it means onboarding call + createAndSaveDefaultUserTokensResponse(userWallet = userWallet) } } else { getSavedUserTokensResponseSync(userWalletId) @@ -158,6 +161,12 @@ internal class DefaultManageTokensRepository( ) } + private suspend fun createAndSaveDefaultUserTokensResponse(userWallet: UserWallet): UserTokensResponse { + val userTokensResponse = createDefaultUserTokensResponse(userWallet) + userTokenSaver.store(userWallet.walletId, userTokensResponse, useEnricher = false) + return userTokensResponse + } + private suspend fun fetchTestnetCurrencies( userWallet: UserWallet, request: Request, diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt index 87943d1e88..412155dcda 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt @@ -32,6 +32,7 @@ internal object ManageTokensDataModule { userWalletsStore: UserWalletsStore, manageTokensUpdateFetcher: ManageTokensUpdateFetcher, userTokensResponseStore: UserTokensResponseStore, + userTokensSaver: UserTokensSaver, testnetTokensStorage: TestnetTokensStorage, dispatchers: CoroutineDispatcherProvider, excludedBlockchains: ExcludedBlockchains, @@ -43,6 +44,7 @@ internal object ManageTokensDataModule { userWalletsStore = userWalletsStore, manageTokensUpdateFetcher = manageTokensUpdateFetcher, userTokensResponseStore = userTokensResponseStore, + userTokenSaver = userTokensSaver, testnetTokensStorage = testnetTokensStorage, excludedBlockchains = excludedBlockchains, cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt index a9d3b05d9b..7179221efb 100644 --- a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt +++ b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt @@ -381,6 +381,26 @@ internal class DefaultTransactionRepository( preparer.prepareForSendMultiple(transactionData, signer) } + override suspend fun prepareAndSign( + transactionData: TransactionData, + signer: TransactionSigner, + userWalletId: UserWalletId, + network: Network, + ) = withContext(dispatchers.io) { + val preparer = getPreparer(network, userWalletId) + preparer.prepareAndSign(transactionData, signer) + } + + override suspend fun prepareAndSignMultiple( + transactionData: List, + signer: TransactionSigner, + userWalletId: UserWalletId, + network: Network, + ) = withContext(dispatchers.io) { + val preparer = getPreparer(network, userWalletId) + preparer.prepareAndSignMultiple(transactionData, signer) + } + private suspend fun getPreparer(network: Network, userWalletId: UserWalletId): TransactionPreparer { val blockchain = network.toBlockchain() val walletManager = walletManagersFacade.getOrCreateWalletManager( diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthAddNetworkUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthAddNetworkUseCase.kt index 0bc201fd7a..bf9f6674f9 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthAddNetworkUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthAddNetworkUseCase.kt @@ -1,9 +1,21 @@ package com.tangem.data.walletconnect.network.ethereum import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.reown.walletkit.client.Wallet +import com.reown.walletkit.client.Wallet.Model +import com.reown.walletkit.client.WalletKit +import com.tangem.blockchain.extensions.hexToInt +import com.tangem.data.walletconnect.model.CAIP10 +import com.tangem.data.walletconnect.model.CAIP2 +import com.tangem.data.walletconnect.network.ethereum.WcEthNetwork.NamespaceConverter.Companion.ETH_NAMESPACE_KEY import com.tangem.data.walletconnect.respond.WcRespondService import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext +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.HandleMethodError import com.tangem.domain.walletconnect.model.WcEthMethod import com.tangem.domain.walletconnect.model.WcRequestError import com.tangem.domain.walletconnect.model.WcSession @@ -13,9 +25,13 @@ import com.tangem.domain.walletconnect.usecase.method.WcNetworkDerivationState import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume internal class WcEthAddNetworkUseCase @AssistedInject constructor( private val respondService: WcRespondService, + private val networksConverter: WcNetworksConverter, + addSwitchCommonDelegateFactory: WcEthAddSwitchCommonDelegate.Factory, @Assisted val context: WcMethodUseCaseContext, @Assisted override val method: WcEthMethod.AddEthereumChain, ) : WcAddNetworkUseCase { @@ -31,10 +47,63 @@ internal class WcEthAddNetworkUseCase @AssistedInject constructor( else -> WcNetworkDerivationState.Single } + private val addSwitchCommonDelegate = addSwitchCommonDelegateFactory.create(context) + + override suspend fun invoke(): Either { + return addSwitchCommonDelegate + .commonChecks(method.rawChain.chainId) + .map { addedNetwork -> + WcAddNetworkUseCase.AddNetwork( + network = addedNetwork, + isExistInWcSession = addSwitchCommonDelegate.existInWcSession(addedNetwork), + ) + } + } + override suspend fun approve(): Either { + fun illegalState() = WcRequestError.UnknownError(IllegalStateException("IllegalStateException")).left() + val requestedNetworkCAIP2 = CAIP2.fromRaw(rawSdkRequest.chainId.orEmpty()) ?: return illegalState() + val networkToAddCAIP2 = addSwitchCommonDelegate.hexChainIdToCAIP2(method.rawChain.chainId) + ?: return illegalState() + val namespaces = session.sdkModel.namespaces[requestedNetworkCAIP2.namespace] + ?: return illegalState() + // find and add all derivation + val networkToAddCAIP10 = networksConverter + .allAddressForChain(networkToAddCAIP2.raw, wallet) + .map { address -> CAIP10(networkToAddCAIP2, address).raw } + val newNamespaces = namespaces.copy( + chains = namespaces.chains.plus(networkToAddCAIP2.raw), + accounts = namespaces.accounts.plus(networkToAddCAIP10), + ) + val sdkNewNamespaces = session.sdkModel.namespaces + .plus(requestedNetworkCAIP2.namespace to newNamespaces) + .mapValues { (_, session) -> + Model.Namespace.Session( + chains = session.chains, + accounts = session.accounts, + methods = session.methods, + events = session.events, + ) + } + + val sessionUpdate = Wallet.Params.SessionUpdate( + sessionTopic = context.session.sdkModel.topic, + namespaces = sdkNewNamespaces, + ) + sdkUpdateSession(sessionUpdate) // ignore result for now return respondService.respond(rawSdkRequest, "") } + private suspend fun sdkUpdateSession(sessionUpdate: Wallet.Params.SessionUpdate): Either { + return suspendCancellableCoroutine { continuation -> + WalletKit.updateSession( + params = sessionUpdate, + onSuccess = { if (continuation.isActive) continuation.resume(Unit.right()) }, + onError = { if (continuation.isActive) continuation.resume(it.throwable.left()) }, + ) + } + } + override fun reject() { respondService.rejectRequestNonBlock(rawSdkRequest) } @@ -43,4 +112,37 @@ internal class WcEthAddNetworkUseCase @AssistedInject constructor( interface Factory { fun create(context: WcMethodUseCaseContext, method: WcEthMethod.AddEthereumChain): WcEthAddNetworkUseCase } +} + +internal class WcEthAddSwitchCommonDelegate @AssistedInject constructor( + private val networksConverter: WcNetworksConverter, + @Assisted val context: WcMethodUseCaseContext, +) { + + private val wallet: UserWallet get() = context.session.wallet + + fun hexChainIdToCAIP2(hexChainId: String): CAIP2? = CAIP2.fromRaw("$ETH_NAMESPACE_KEY:${hexChainId.hexToInt()}") + + fun existInWcSession(network: Network): Boolean { + return context.session.networks.any { it.rawId == network.rawId } + } + + suspend fun commonChecks(hexChainId: String): Either { + val caip2 = hexChainIdToCAIP2(hexChainId) + ?: return HandleMethodError.UnknownError("Failed to parse CAIP2").left() + val generalNetwork = networksConverter.createNetwork(caip2.raw, wallet) + if (generalNetwork == null) { + return HandleMethodError.TangemUnsupportedNetwork(caip2.raw).left() + } + val addedNetwork = networksConverter.mainOrAnyWalletNetworkForRequest(caip2.raw, wallet) + if (addedNetwork == null) { + return HandleMethodError.NotAddedNetwork(generalNetwork.name).left() + } + return addedNetwork.right() + } + + @AssistedFactory + interface Factory { + fun create(context: WcMethodUseCaseContext): WcEthAddSwitchCommonDelegate + } } \ No newline at end of file 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 0f9e21543f..a14460a566 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 @@ -14,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.wallet.UserWallet import com.tangem.domain.walletconnect.model.* import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest import com.tangem.domain.walletconnect.repository.WcSessionsManager @@ -44,7 +43,7 @@ internal class WcEthNetwork( ?: return HandleMethodError.UnknownSession.left() val wallet = session.wallet val chainId = request.chainId.orEmpty() - val method: WcEthMethod = name.toMethod(request, wallet) + val method: WcEthMethod = name.toMethod(request) .getOrElse { return error(it.message.orEmpty()) } ?: return error("Failed to parse $name") suspend fun anyExistNetwork() = networksConverter.mainOrAnyWalletNetworkForRequest(chainId, wallet) @@ -54,7 +53,9 @@ internal class WcEthNetwork( is WcEthMethod.SendTransaction -> method.transaction.from is WcEthMethod.SignTransaction -> method.transaction.from is WcEthMethod.SignTypedData -> method.account - is WcEthMethod.AddEthereumChain -> + is WcEthMethod.AddEthereumChain, + is WcEthMethod.SwitchEthereumChain, + -> anyExistNetwork() ?.let { network -> walletManagersFacade.getDefaultAddress(wallet.walletId, network).orEmpty() } .orEmpty() @@ -65,7 +66,9 @@ internal class WcEthNetwork( is WcEthMethod.SendTransaction, is WcEthMethod.SignTransaction, -> networksConverter.findWalletNetworkForRequest(request, session, accountAddress) - is WcEthMethod.AddEthereumChain -> anyExistNetwork() + is WcEthMethod.AddEthereumChain, + is WcEthMethod.SwitchEthereumChain, + -> anyExistNetwork() } ?: return error("Failed to find walletNetwork for accountAddress $accountAddress") val context = WcMethodUseCaseContext( @@ -81,13 +84,11 @@ internal class WcEthNetwork( is WcEthMethod.SignTransaction -> factories.signTransaction.create(context, method) is WcEthMethod.SignTypedData -> factories.signTypedData.create(context, method) is WcEthMethod.AddEthereumChain -> factories.addNetwork.create(context, method) + is WcEthMethod.SwitchEthereumChain -> factories.switchNetwork.create(context, method) }.right() } - private suspend fun WcEthMethodName.toMethod( - request: WcSdkSessionRequest, - wallet: UserWallet, - ): Either { + private fun WcEthMethodName.toMethod(request: WcSdkSessionRequest): Either { val rawParams = request.request.params return when (this) { WcEthMethodName.EthSign, @@ -109,14 +110,17 @@ internal class WcEthNetwork( } } ?: return null.right() - WcEthMethodName.AddEthereumChain -> moshi.fromJson>(rawParams) + WcEthMethodName.AddEthereumChain, + WcEthMethodName.SwitchEthereumChain, + -> moshi.fromJson>(rawParams) .getOrElse { return it.left() } ?.firstOrNull() ?.let { - val newNetwork = networksConverter - .mainOrAnyWalletNetworkForRequest(it.chainId, wallet) - ?: return null.right() - WcEthMethod.AddEthereumChain(rawChain = it, network = newNetwork).right() + if (this == WcEthMethodName.AddEthereumChain) { + WcEthMethod.AddEthereumChain(rawChain = it).right() + } else { + WcEthMethod.SwitchEthereumChain(rawChain = it).right() + } } ?: null.right() } @@ -147,13 +151,17 @@ internal class WcEthNetwork( override val excludedBlockchains: ExcludedBlockchains, ) : WcNamespaceConverter { - override val namespaceKey: NamespaceKey = NamespaceKey("eip155") + override val namespaceKey: NamespaceKey = NamespaceKey(ETH_NAMESPACE_KEY) override fun toBlockchain(chainId: CAIP2): Blockchain? { if (chainId.namespace != namespaceKey.key) return null val ethChainId = chainId.reference.toIntOrNull() ?: return null return Blockchain.fromChainId(ethChainId) } + + companion object { + const val ETH_NAMESPACE_KEY = "eip155" + } } internal class Factories @Inject constructor( @@ -162,5 +170,6 @@ internal class WcEthNetwork( val sendTransaction: WcEthSendTransactionUseCase.Factory, val signTransaction: WcEthSignTransactionUseCase.Factory, val addNetwork: WcEthAddNetworkUseCase.Factory, + val switchNetwork: WcEthSwitchNetworkUseCase.Factory, ) } \ No newline at end of file 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 5358c3a317..e82c497072 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 @@ -7,6 +7,9 @@ import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.extensions.formatHex import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.models.AnalyticsParam.TxSentFrom +import com.tangem.core.analytics.models.Basic +import com.tangem.core.analytics.models.Basic.TransactionSent.MemoType import com.tangem.data.walletconnect.respond.WcRespondService import com.tangem.data.walletconnect.sign.BaseWcSignUseCase import com.tangem.data.walletconnect.sign.SignCollector @@ -86,6 +89,16 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor( emit(state.toResult(parseSendError(error).left())) } .getOrNull() ?: return + analytics.send( + Basic.TransactionSent( + sentFrom = TxSentFrom.WalletConnect( + blockchain = network.name, + token = network.currencySymbol, + feeType = null, + ), + memoType = MemoType.Null, + ), + ) val respondResult = respondService.respond(rawSdkRequest, hash.formatHex()) emit(state.toResult(respondResult)) } 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 b6795bf29b..8cfe678df0 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 @@ -30,7 +30,7 @@ import com.tangem.blockchain.common.Amount as BlockchainAmount internal class WcEthSignTransactionUseCase @AssistedInject constructor( override val respondService: WcRespondService, override val analytics: AnalyticsEventHandler, - private val prepareForSend: PrepareForSendUseCase, + private val prepareForSend: PrepareForSendUseCase, // TODO: TODO("[REDACTED_JIRA]") private val ethTxHelper: WcEthTxHelper, @Assisted override val context: WcMethodUseCaseContext, @Assisted override val method: WcEthMethod.SignTransaction, diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSwitchNetworkUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSwitchNetworkUseCase.kt new file mode 100644 index 0000000000..5b94908fa8 --- /dev/null +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSwitchNetworkUseCase.kt @@ -0,0 +1,56 @@ +package com.tangem.data.walletconnect.network.ethereum + +import arrow.core.Either +import com.tangem.data.walletconnect.respond.WcRespondService +import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext +import com.tangem.domain.models.network.Network +import com.tangem.domain.walletconnect.model.HandleMethodError +import com.tangem.domain.walletconnect.model.WcEthMethod +import com.tangem.domain.walletconnect.model.WcSession +import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest +import com.tangem.domain.walletconnect.usecase.method.WcNetworkDerivationState +import com.tangem.domain.walletconnect.usecase.method.WcSwitchNetworkUseCase +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class WcEthSwitchNetworkUseCase @AssistedInject constructor( + private val respondService: WcRespondService, + @Assisted val context: WcMethodUseCaseContext, + @Assisted override val method: WcEthMethod.SwitchEthereumChain, + addSwitchCommonDelegateFactory: WcEthAddSwitchCommonDelegate.Factory, +) : WcSwitchNetworkUseCase { + + override val session: WcSession + get() = context.session + override val rawSdkRequest: WcSdkSessionRequest + get() = context.rawSdkRequest + override val network: Network + get() = context.network + override val derivationState: WcNetworkDerivationState = when { + context.networkDerivationsCount > 1 -> WcNetworkDerivationState.Multiple(walletAddress = context.accountAddress) + else -> WcNetworkDerivationState.Single + } + + private val addSwitchCommonDelegate = addSwitchCommonDelegateFactory.create(context) + + override suspend fun invoke(): Either { + return addSwitchCommonDelegate + .commonChecks(method.rawChain.chainId) + .map { addedNetwork -> + WcSwitchNetworkUseCase.SwitchNetwork( + network = addedNetwork, + isExistInWcSession = addSwitchCommonDelegate.existInWcSession(addedNetwork), + ) + } + } + + override fun reject() { + respondService.rejectRequestNonBlock(rawSdkRequest) + } + + @AssistedFactory + interface Factory { + fun create(context: WcMethodUseCaseContext, method: WcEthMethod.SwitchEthereumChain): WcEthSwitchNetworkUseCase + } +} \ No newline at end of file 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 057afaa2e9..3526fb2d8c 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 @@ -2,7 +2,7 @@ package com.tangem.data.walletconnect.network.ethereum import com.domain.blockaid.models.transaction.CheckTransactionResult import com.domain.blockaid.models.transaction.SimulationResult -import com.domain.blockaid.models.transaction.simultation.ApprovedAmount +import com.domain.blockaid.models.transaction.simultation.ApproveInfo import com.domain.blockaid.models.transaction.simultation.SimulationData import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras import com.tangem.blockchain.blockchains.ethereum.tokenmethods.ApprovalERC20TokenCallData @@ -68,13 +68,15 @@ internal class WcEthTxHelper @Inject constructor( ) } - fun getApprovedAmount(txData: String?, result: CheckTransactionResult): ApprovedAmount? { + fun getApprovedAmount(txData: String?, result: CheckTransactionResult): ApproveInfo.Amount? { val approvalMethodId = ApprovalERC20TokenCallData("", null).methodId val isApprovalWcMethod = txData?.startsWith(approvalMethodId) if (isApprovalWcMethod != true) return null val simulation = result.simulation as? SimulationResult.Success ?: return null - val approves = (simulation.data as? SimulationData.Approve)?.approvedAmounts + val approves = (simulation.data as? SimulationData.Approve) + ?.items + ?.filterIsInstance() ?: return null if (approves.isEmpty()) return null val amount = approves.first() diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignAllTransactionUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignAllTransactionUseCase.kt index 8e91c2c8dd..d8b9e39468 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignAllTransactionUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignAllTransactionUseCase.kt @@ -10,7 +10,7 @@ import com.tangem.data.walletconnect.sign.SignCollector import com.tangem.data.walletconnect.sign.SignStateConverter.toResult import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate -import com.tangem.domain.transaction.usecase.PrepareForSendUseCase +import com.tangem.domain.transaction.usecase.PrepareAndSignUseCase import com.tangem.domain.walletconnect.error.parseSendError import com.tangem.domain.walletconnect.model.WcSolanaMethod import com.tangem.domain.walletconnect.usecase.method.BlockAidTransactionCheck @@ -29,7 +29,7 @@ import org.json.JSONObject internal class WcSolanaSignAllTransactionUseCase @AssistedInject constructor( override val respondService: WcRespondService, override val analytics: AnalyticsEventHandler, - private val prepareForSend: PrepareForSendUseCase, + private val prepareAndSign: PrepareAndSignUseCase, @Assisted override val context: WcMethodUseCaseContext, @Assisted override val method: WcSolanaMethod.SignAllTransaction, blockAidDelegate: BlockAidVerificationDelegate, @@ -46,7 +46,7 @@ internal class WcSolanaSignAllTransactionUseCase @AssistedInject constructor( ).map { lce -> lce.map { result -> BlockAidTransactionCheck.Result.Plain(result) } } override suspend fun SignCollector>.onSign(state: WcSignState>) { - val hash = prepareForSend.invoke(transactionData = state.signModel, userWallet = wallet, network = network) + val hash = prepareAndSign.invoke(transactionData = state.signModel, userWallet = wallet, network = network) .onLeft { error -> emit(state.toResult(parseSendError(error).left())) } diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignTransactionUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignTransactionUseCase.kt index 282125a721..c1d301135a 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignTransactionUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignTransactionUseCase.kt @@ -10,7 +10,7 @@ import com.tangem.data.walletconnect.sign.SignCollector import com.tangem.data.walletconnect.sign.SignStateConverter.toResult import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate -import com.tangem.domain.transaction.usecase.PrepareForSendUseCase +import com.tangem.domain.transaction.usecase.PrepareAndSignUseCase import com.tangem.domain.walletconnect.error.parseSendError import com.tangem.domain.walletconnect.model.WcSolanaMethod import com.tangem.domain.walletconnect.usecase.method.BlockAidTransactionCheck @@ -27,7 +27,7 @@ import okio.ByteString.Companion.decodeBase64 internal class WcSolanaSignTransactionUseCase @AssistedInject constructor( override val respondService: WcRespondService, override val analytics: AnalyticsEventHandler, - private val prepareForSend: PrepareForSendUseCase, + private val prepareAndSign: PrepareAndSignUseCase, @Assisted override val context: WcMethodUseCaseContext, @Assisted override val method: WcSolanaMethod.SignTransaction, blockAidDelegate: BlockAidVerificationDelegate, @@ -44,7 +44,7 @@ internal class WcSolanaSignTransactionUseCase @AssistedInject constructor( ).map { lce -> lce.map { result -> BlockAidTransactionCheck.Result.Plain(result) } } override suspend fun SignCollector.onSign(state: WcSignState) { - val hash = prepareForSend.invoke(transactionData = state.signModel, userWallet = wallet, network = network) + val hash = prepareAndSign.invoke(transactionData = state.signModel, userWallet = wallet, network = network) .onLeft { error -> emit(state.toResult(parseSendError(error).left())) } 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 bfd1192118..ad67584bed 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 @@ -20,10 +20,12 @@ import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.* import org.joda.time.DateTime import timber.log.Timber +import java.net.URI @Suppress("LongParameterList") internal class DefaultWcPairUseCase @AssistedInject constructor( @@ -63,6 +65,12 @@ internal class DefaultWcPairUseCase @AssistedInject constructor( return@flow } + val dAppUri = URI(sdkSessionProposal.url) + if (dAppUri.host.isNullOrEmpty()) { + emit(WcPairState.Error(WcPairError.InvalidDomainURL)) + return@flow + } + val proposalState = buildProposalState(sdkSessionProposal, sdkVerifyContext) .onLeft { analytics.send(WcAnalyticEvents.PairFailed(it.code)) @@ -112,14 +120,21 @@ internal class DefaultWcPairUseCase @AssistedInject constructor( Timber.tag(WC_TAG).e(it, "Failed to approve session ${sdkSessionProposal.name}") } emit(WcPairState.Approving.Result(sessionForApprove, either)) - }.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") - } } + .catch { + val pairError: WcPairError = when (it) { + is TimeoutCancellationException -> WcPairError.TimeoutException(it.message.orEmpty()) + else -> WcPairError.Unknown(it.message.orEmpty()) + } + emit(WcPairState.Error(pairError)) + } + .onCompletion { + if (it != null) { + Timber.tag(WC_TAG).e(it, "Completed with error $pairRequest") + } else { + Timber.tag(WC_TAG).i("Completed successfully $pairRequest") + } + } } override fun approve(sessionForApprove: WcSessionApprove) { 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 e9665c0673..35902fb41f 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 = 60 + private const val CALLBACK_TIMEOUT = 15 // 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/DefaultWcRequestService.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt index 4c50b189fd..db8788c05a 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt @@ -33,6 +33,7 @@ internal class DefaultWcRequestService( Timber.tag(WC_TAG).i("handle request name $name") if (name is WcMethodName.Unsupported) { respondService.rejectRequestNonBlock(sr) + if (name.raw.startsWith("wallet_")) return } _wcRequest.trySend(name to sr) } diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/BlockAidChainNameConverter.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/BlockAidChainNameConverter.kt index 3421317947..c8491f5be6 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/BlockAidChainNameConverter.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/BlockAidChainNameConverter.kt @@ -4,31 +4,32 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.domain.models.network.Network import com.tangem.utils.converter.Converter -import javax.inject.Inject -internal class BlockAidChainNameConverter @Inject constructor() : Converter { +internal object BlockAidChainNameConverter : Converter { @Suppress("CyclomaticComplexMethod") - override fun convert(value: Network): String { + override fun convert(value: Network): String? { return when (Blockchain.fromNetworkId(value.backendId)) { Blockchain.Arbitrum -> "arbitrum" Blockchain.Avalanche -> "avalanche" Blockchain.AvalancheTestnet -> "avalanche-fuji" - Blockchain.Binance, Blockchain.BSC -> "bsc" - Blockchain.Ethereum -> "ethereum" - Blockchain.EthereumTestnet -> "ethereum-sepolia" - Blockchain.Polygon -> "polygon" - Blockchain.Solana -> "mainnet" - Blockchain.Gnosis -> "gnosis" - Blockchain.Optimism -> "optimism" - Blockchain.ZkSyncEra -> "zksync" - Blockchain.ZkSyncEraTestnet -> "zksync-sepolia" Blockchain.Base -> "base" Blockchain.BaseTestnet -> "base-sepolia" + Blockchain.Binance, Blockchain.BSC -> "bsc" + Blockchain.Ethereum -> "ethereum" + Blockchain.Optimism -> "optimism" + Blockchain.Polygon -> "polygon" + Blockchain.ZkSyncEra -> "zksync" + Blockchain.ZkSyncEraTestnet -> "zksync-sepolia" Blockchain.Blast, Blockchain.BlastTestnet -> "blast" - Blockchain.ApeChain, Blockchain.ApeChainTestnet -> "apechain" Blockchain.Scroll -> "scroll" - else -> value.name + Blockchain.EthereumTestnet -> "ethereum-sepolia" + Blockchain.Gnosis -> "gnosis" + Blockchain.ApeChain, Blockchain.ApeChainTestnet -> "apechain" + + Blockchain.Solana -> "mainnet" + + else -> null } } } \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/BlockAidVerificationDelegate.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/BlockAidVerificationDelegate.kt index 4a774ac505..8bc9a0c492 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/BlockAidVerificationDelegate.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/BlockAidVerificationDelegate.kt @@ -17,7 +17,6 @@ import javax.inject.Inject internal class BlockAidVerificationDelegate @Inject constructor( private val blockAidVerifier: BlockAidVerifier, - private val blockAidChainNameConverter: BlockAidChainNameConverter, ) { fun getSecurityStatus( @@ -27,7 +26,6 @@ internal class BlockAidVerificationDelegate @Inject constructor( session: WcSession, accountAddress: String?, ): LceFlow = flow { - emit(Lce.Loading(partialContent = null)) val failedResult = CheckTransactionResult( validation = ValidationResult.FAILED_TO_VALIDATE, simulation = SimulationResult.FailedToSimulate, @@ -36,7 +34,21 @@ internal class BlockAidVerificationDelegate @Inject constructor( emit(Lce.Content(failedResult)) return@flow } - when (method) { + val chain = BlockAidChainNameConverter.convert(network) + if (chain == null) { + emit(Lce.Content(failedResult)) + return@flow + } + emit(Lce.Loading(partialContent = null)) + val methodName = when (method) { + is WcEthMethod -> rawSdkRequest.request.method + is WcSolanaMethod -> method.trimmedPrefixMethodName + is WcMethod.Unsupported -> { + emit(Lce.Content(failedResult)) + return@flow + } + } + val params = when (method) { is WcEthMethod -> TransactionParams.Evm(rawSdkRequest.request.params) is WcSolanaMethod.SignAllTransaction -> TransactionParams.Solana(method.transaction) is WcSolanaMethod.SignTransaction -> TransactionParams.Solana(listOf(method.transaction)) @@ -45,25 +57,28 @@ internal class BlockAidVerificationDelegate @Inject constructor( emit(Lce.Content(failedResult)) return@flow } - else -> null - }?.let { params -> - blockAidVerifier.verifyTransaction( - TransactionData( - chain = blockAidChainNameConverter.convert(network), - accountAddress = accountAddress, - method = rawSdkRequest.request.method, - domainUrl = session.sdkModel.appMetaData.url, - params = params, - ), - ).fold( - ifLeft = { - Timber.e("Failed to verify transaction: ${it.localizedMessage}") - emit(Lce.Error(it)) - }, - ifRight = { - emit(Lce.Content(it)) - }, - ) - } ?: emit(Lce.Content(failedResult)) + else -> { + emit(Lce.Content(failedResult)) + return@flow + } + } + + blockAidVerifier.verifyTransaction( + data = TransactionData( + chain = chain, + accountAddress = accountAddress, + method = methodName, + domainUrl = session.sdkModel.appMetaData.url, + params = params, + ), + ).fold( + ifLeft = { + Timber.e("Failed to verify transaction: ${it.localizedMessage}") + emit(Lce.Error(it)) + }, + ifRight = { + emit(Lce.Content(it)) + }, + ) } } \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt index 6a80763cc9..a7d1d285fb 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt @@ -25,6 +25,11 @@ internal class WcNetworksConverter @Inject constructor( private val tokensFeatureToggles: TokensFeatureToggles, ) { + fun createNetwork(chainId: String, wallet: UserWallet): Network? { + return namespaceConverters + .firstNotNullOfOrNull { it.toNetwork(chainId, wallet) } + } + suspend fun findWalletNetworkForRequest( request: WcSdkSessionRequest, session: WcSession, @@ -48,6 +53,11 @@ internal class WcNetworksConverter @Inject constructor( return networks.firstOrNull { !isCustomCoin(it) } ?: networks.firstOrNull() } + suspend fun allAddressForChain(rawChainId: String, wallet: UserWallet): List { + return filterWalletNetworkForRequest(rawChainId, wallet) + .mapNotNull { walletManagersFacade.getDefaultAddress(wallet.walletId, it)?.lowercase() } + } + /** * return all exist derivation networks */ diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcSdkSessionConverter.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcSdkSessionConverter.kt index 0f2e570acd..f588c4f171 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcSdkSessionConverter.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcSdkSessionConverter.kt @@ -10,6 +10,14 @@ internal object WcSdkSessionConverter : Converter + WcSdkSession.Session( + chains = session.chains ?: listOf(), + accounts = session.accounts, + methods = session.methods, + events = session.events, + ) + }, ) } } \ No newline at end of file diff --git a/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/DefaultWcPairUseCaseTest.kt b/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/DefaultWcPairUseCaseTest.kt index df3b560b15..573d2949bc 100644 --- a/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/DefaultWcPairUseCaseTest.kt +++ b/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/DefaultWcPairUseCaseTest.kt @@ -47,7 +47,7 @@ internal class DefaultWcPairUseCaseTest { pairingTopic = "", name = "", description = "", - url = "", + url = "https://react-app.walletconnect.com/", icons = listOf(), redirect = "", requiredNamespaces = mapOf(), diff --git a/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/WcSignUseCaseDelegateTest.kt b/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/WcSignUseCaseDelegateTest.kt index e68555c7c1..15049820a7 100644 --- a/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/WcSignUseCaseDelegateTest.kt +++ b/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/WcSignUseCaseDelegateTest.kt @@ -62,6 +62,7 @@ internal class WcSignUseCaseDelegateTest { connectingTime = 0L, sdkModel = WcSdkSession( topic = "", + namespaces = mapOf(), appMetaData = WcAppMetaData( name = "", description = "", diff --git a/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/simultation/ApproveInfo.kt b/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/simultation/ApproveInfo.kt new file mode 100644 index 0000000000..2325c38b32 --- /dev/null +++ b/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/simultation/ApproveInfo.kt @@ -0,0 +1,13 @@ +package com.domain.blockaid.models.transaction.simultation + +import java.math.BigDecimal + +sealed class ApproveInfo { + data class Amount( + val approvedAmount: BigDecimal, + val isUnlimited: Boolean, + val tokenInfo: TokenInfo, + ) : ApproveInfo() + + data class NonFungibleToken(val name: String, val logoUrl: String?) : ApproveInfo() +} \ No newline at end of file diff --git a/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/simultation/ApprovedAmount.kt b/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/simultation/ApprovedAmount.kt deleted file mode 100644 index 6fdad58ec2..0000000000 --- a/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/simultation/ApprovedAmount.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.domain.blockaid.models.transaction.simultation - -import java.math.BigDecimal - -data class ApprovedAmount( - val approvedAmount: BigDecimal, - val isUnlimited: Boolean, - val tokenInfo: TokenInfo, -) \ No newline at end of file diff --git a/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/simultation/SimulationData.kt b/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/simultation/SimulationData.kt index 13c28ebba4..13937ad93e 100644 --- a/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/simultation/SimulationData.kt +++ b/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/simultation/SimulationData.kt @@ -16,9 +16,7 @@ sealed class SimulationData { /** * Represents an approve operation with the specified amount (can be multiple amounts for NFT) */ - data class Approve( - val approvedAmounts: List, - ) : SimulationData() + data class Approve(val items: List) : SimulationData() /** * Simulation was successfully performed and no changes detected diff --git a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt index 3ac8a33c7f..087b4312ed 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt @@ -7,11 +7,13 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason +import com.tangem.domain.transaction.models.AssetRequirementsCondition import kotlinx.coroutines.flow.Flow /** * Manager that holds info about available actions as Sell and Buy */ +@Deprecated("Move to express domain layer") interface RampStateManager { suspend fun availableForBuy(userWallet: UserWallet, cryptoCurrency: CryptoCurrency): ScenarioUnavailabilityReason @@ -50,4 +52,9 @@ interface RampStateManager { userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus, ): ScenarioUnavailabilityReason + + /** + * Returns whether asset requirements are full filled to be able use express services + */ + fun checkAssetRequirements(requirements: AssetRequirementsCondition?): Boolean } \ No newline at end of file diff --git a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/GetManagedTokensUseCase.kt b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/GetManagedTokensUseCase.kt index e8a4b8d205..6a9c3a47ed 100644 --- a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/GetManagedTokensUseCase.kt +++ b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/GetManagedTokensUseCase.kt @@ -10,6 +10,7 @@ class GetManagedTokensUseCase( operator fun invoke( context: ManageTokensListBatchingContext, + // only for onboarding case, change carefully and check repository implementation loadUserTokensFromRemote: Boolean, batchSize: Int = 40, ): ManageTokensListBatchFlow { diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt index d8d52708eb..3a258c4cf6 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt @@ -386,8 +386,9 @@ abstract class BaseCurrencyStatusOperations( multiWalletCryptoCurrenciesSupplier.getSyncOrNull( params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId), ) - ?.firstOrNull { it.network.id == networkId && it.network.derivationPath == derivationPath } - ?: error("Unable to create network coin with ID: $networkId and derivation path: $derivationPath") + ?.filterIsInstance() + ?.firstOrNull { it.network.id == networkId } + ?: error("Unable to create network coin with ID: $networkId") } else { currenciesRepository.getNetworkCoin(userWalletId, networkId, derivationPath) } diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt index 62feb67e90..f6171137cd 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt @@ -109,4 +109,18 @@ interface TransactionRepository { userWalletId: UserWalletId, network: Network, ): com.tangem.blockchain.extensions.Result> + + suspend fun prepareAndSign( + transactionData: TransactionData, + signer: TransactionSigner, + userWalletId: UserWalletId, + network: Network, + ): com.tangem.blockchain.extensions.Result + + suspend fun prepareAndSignMultiple( + transactionData: List, + signer: TransactionSigner, + userWalletId: UserWalletId, + network: Network, + ): com.tangem.blockchain.extensions.Result> } \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/PrepareAndSignUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/PrepareAndSignUseCase.kt new file mode 100644 index 0000000000..4fecd3147f --- /dev/null +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/PrepareAndSignUseCase.kt @@ -0,0 +1,70 @@ +package com.tangem.domain.transaction.usecase + +import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.blockchain.common.TransactionData +import com.tangem.blockchain.common.TransactionSigner +import com.tangem.blockchain.extensions.Result +import com.tangem.domain.card.common.TapWorkarounds.isTangemTwins +import com.tangem.domain.card.models.TwinKey +import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.domain.models.network.Network +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.requireColdWallet +import com.tangem.domain.transaction.TransactionRepository +import com.tangem.domain.transaction.error.SendTransactionError + +class PrepareAndSignUseCase( + private val transactionRepository: TransactionRepository, + private val cardSdkConfigRepository: CardSdkConfigRepository, +) { + + suspend operator fun invoke( + transactionData: TransactionData, + userWallet: UserWallet, + network: Network, + ): Either { + val signer = createSigner(userWallet) + val result = transactionRepository.prepareAndSign( + transactionData = transactionData, + userWalletId = userWallet.walletId, + network = network, + signer = signer, + ) + return when (result) { + is Result.Failure -> SendTransactionUseCase.handleError(result).left() + is Result.Success -> result.data.right() + } + } + + suspend operator fun invoke( + transactionData: List, + userWallet: UserWallet, + network: Network, + ): Either> { + val signer = createSigner(userWallet) + val result = transactionRepository.prepareAndSignMultiple( + transactionData = transactionData, + userWalletId = userWallet.walletId, + network = network, + signer = signer, + ) + return when (result) { + is Result.Failure -> SendTransactionUseCase.handleError(result).left() + is Result.Success -> result.data.right() + } + } + + private fun createSigner(userWallet: UserWallet): TransactionSigner { + userWallet.requireColdWallet() // TODO [REDACTED_TASK_KEY] + val card = userWallet.scanResponse.card + val isCardNotBackedUp = card.backupStatus?.isActive != true && !card.isTangemTwins + + val signer = cardSdkConfigRepository.getCommonSigner( + cardId = card.cardId.takeIf { isCardNotBackedUp }, + twinKey = TwinKey.getOrNull(scanResponse = userWallet.scanResponse), + ) + return signer + } +} \ No newline at end of file diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthAddChain.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthAddChain.kt index 9daf8154a1..33633558c4 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthAddChain.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthAddChain.kt @@ -5,6 +5,11 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class WcEthAddChain( + /** + * chainId are identified by EIP-155 integers expressed in hexadecimal notation, + * with 0x prefix and no leading zeroes for the chainId value. + * For more information https://eips.ethereum.org/EIPS/eip-5792#atomicbatch-capability + */ @Json(name = "chainId") val chainId: String, ) \ No newline at end of file diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthMethod.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthMethod.kt index 24a4878ec4..34b43e11bb 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthMethod.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthMethod.kt @@ -1,7 +1,5 @@ package com.tangem.domain.walletconnect.model -import com.tangem.domain.models.network.Network - sealed interface WcEthMethod : WcMethod { data class MessageSign( @@ -15,7 +13,7 @@ sealed interface WcEthMethod : WcMethod { val account: String, val dataForSign: String, ) : WcEthMethod { - val humanMsg: String = params.message.contents.orEmpty() + val humanMsg: String = params.message?.contents.orEmpty() } data class SendTransaction( @@ -28,6 +26,9 @@ sealed interface WcEthMethod : WcMethod { data class AddEthereumChain( val rawChain: WcEthAddChain, - val network: Network, + ) : WcEthMethod + + data class SwitchEthereumChain( + val rawChain: WcEthAddChain, ) : WcEthMethod } \ No newline at end of file diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthSignTypedDataParams.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthSignTypedDataParams.kt index 0054a9c68c..b14e6af6df 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthSignTypedDataParams.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthSignTypedDataParams.kt @@ -6,24 +6,24 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class WcEthSignTypedDataParams( @Json(name = "domain") - val domain: Domain, + val domain: Domain?, @Json(name = "message") - val message: Message, + val message: Message?, @Json(name = "primaryType") - val primaryType: String, + val primaryType: String?, @Json(name = "types") val types: Map>, ) { @JsonClass(generateAdapter = true) data class Domain( @Json(name = "chainId") - val chainId: Int, + val chainId: Int?, @Json(name = "name") - val name: String, + val name: String?, @Json(name = "verifyingContract") - val verifyingContract: String, + val verifyingContract: String?, @Json(name = "version") - val version: String, + val version: String?, ) @JsonClass(generateAdapter = true) diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcMethodName.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcMethodName.kt index 6ae4b431ac..982c1cacf1 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcMethodName.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcMethodName.kt @@ -14,6 +14,7 @@ enum class WcEthMethodName(override val raw: String) : WcMethodName { SignTransaction("eth_signTransaction"), SendTransaction("eth_sendTransaction"), AddEthereumChain("wallet_addEthereumChain"), + SwitchEthereumChain("wallet_switchEthereumChain"), } enum class WcSolanaMethodName(override val raw: String) : WcMethodName { diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcPairError.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcPairError.kt index f04de6251a..2ac1b68531 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcPairError.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcPairError.kt @@ -16,4 +16,5 @@ sealed class WcPairError( data class ApprovalFailed(override val message: String) : WcPairError("107 002 003") data object RejectionFailed : WcPairError("107 002 004") data class Unknown(override val message: String) : WcPairError(message) + data class TimeoutException(override val message: String) : WcPairError(message) } \ No newline at end of file diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcRequestError.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcRequestError.kt index 3c1cfe610a..e0ad853f77 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcRequestError.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcRequestError.kt @@ -69,4 +69,9 @@ sealed class HandleMethodError( data object UnknownSession : HandleMethodError(message = "WalletConnect session was disconnected") data class UnknownError(override val message: String) : HandleMethodError(message) + data class TangemUnsupportedNetwork(val unsupportedNetwork: String) : + HandleMethodError("TangemUnsupportedNetwork $unsupportedNetwork") + + data class NotAddedNetwork(val networkName: String) : HandleMethodError("NotAddedNetwork $networkName") + data class RequiredNetwork(val networkName: String) : HandleMethodError("RequiredNetwork $networkName") } \ No newline at end of file diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSolanaMethod.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSolanaMethod.kt index 0042025dfb..d44d98c88f 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSolanaMethod.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSolanaMethod.kt @@ -2,18 +2,27 @@ package com.tangem.domain.walletconnect.model sealed interface WcSolanaMethod : WcMethod { + val methodName: String + val trimmedPrefixMethodName: String get() = methodName.substringAfter("_") + data class SignMessage( val pubKey: String, val rawMessage: String, val humanMsg: String, - ) : WcSolanaMethod + ) : WcSolanaMethod { + override val methodName: String = WcSolanaMethodName.SignMessage.raw + } data class SignTransaction( val transaction: String, val address: String?, - ) : WcSolanaMethod + ) : WcSolanaMethod { + override val methodName: String = WcSolanaMethodName.SignTransaction.raw + } data class SignAllTransaction( val transaction: List, - ) : WcSolanaMethod + ) : WcSolanaMethod { + override val methodName: String = WcSolanaMethodName.SendAllTransaction.raw + } } \ No newline at end of file diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/sdkcopy/WcSdkSession.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/sdkcopy/WcSdkSession.kt index 9b2d6c45b7..63ea82098f 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/sdkcopy/WcSdkSession.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/sdkcopy/WcSdkSession.kt @@ -6,4 +6,12 @@ package com.tangem.domain.walletconnect.model.sdkcopy data class WcSdkSession( val topic: String, val appMetaData: WcAppMetaData, -) \ No newline at end of file + val namespaces: Map, +) { + data class Session( + val chains: List, + val accounts: List, + val methods: List, + val events: List, + ) +} \ No newline at end of file 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 0d3291dca3..8b6bd34eae 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 @@ -11,6 +11,7 @@ import com.tangem.domain.walletconnect.model.WcSessionApprove import com.tangem.domain.walletconnect.model.WcSessionProposal import com.tangem.domain.walletconnect.model.sdkcopy.WcAppMetaData import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest +import com.tangem.utils.extensions.mapNotNullValues sealed class WcAnalyticEvents( event: String, @@ -103,7 +104,7 @@ sealed class WcAnalyticEvents( class SignatureRequestReceived( rawRequest: WcSdkSessionRequest, network: Network, - emulationStatus: EmulationStatus, + emulationStatus: EmulationStatus?, ) : WcAnalyticEvents( event = "Signature Request Received", params = mapOf( @@ -111,8 +112,8 @@ sealed class WcAnalyticEvents( AnalyticsParam.Key.DAPP_URL to rawRequest.dAppMetaData.url, AnalyticsParam.Key.METHOD_NAME to rawRequest.request.method, AnalyticsParam.Key.BLOCKCHAIN to network.name, - AnalyticsParam.Key.EMULATION_STATUS to emulationStatus.status, - ), + AnalyticsParam.Key.EMULATION_STATUS to emulationStatus?.status, + ).mapNotNullValues { it.value }, ) { enum class EmulationStatus(val status: String) { Emulated("Emulated"), diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcAddNetworkUseCase.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcAddNetworkUseCase.kt index 787bea6e93..7573141b6d 100644 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcAddNetworkUseCase.kt +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcAddNetworkUseCase.kt @@ -1,12 +1,20 @@ package com.tangem.domain.walletconnect.usecase.method import arrow.core.Either +import com.tangem.domain.models.network.Network +import com.tangem.domain.walletconnect.model.HandleMethodError import com.tangem.domain.walletconnect.model.WcRequestError interface WcAddNetworkUseCase : WcMethodUseCase, WcMethodContext { + suspend operator fun invoke(): Either suspend fun approve(): Either fun reject() + + data class AddNetwork( + val network: Network, + val isExistInWcSession: Boolean, + ) } \ No newline at end of file diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcSwitchNetworkUseCase.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcSwitchNetworkUseCase.kt new file mode 100644 index 0000000000..b7980f5500 --- /dev/null +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcSwitchNetworkUseCase.kt @@ -0,0 +1,18 @@ +package com.tangem.domain.walletconnect.usecase.method + +import arrow.core.Either +import com.tangem.domain.models.network.Network +import com.tangem.domain.walletconnect.model.HandleMethodError + +interface WcSwitchNetworkUseCase : + WcMethodUseCase, + WcMethodContext { + + suspend operator fun invoke(): Either + fun reject() + + data class SwitchNetwork( + val network: Network, + val isExistInWcSession: Boolean, + ) +} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetIsBiometricsEnabledUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetIsBiometricsEnabledUseCase.kt deleted file mode 100644 index 2c3f0deaba..0000000000 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetIsBiometricsEnabledUseCase.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.domain.wallets.usecase - -import com.tangem.sdk.api.TangemSdkManager -import javax.inject.Inject - -class GetIsBiometricsEnabledUseCase @Inject constructor( - private val tangemSdkManager: TangemSdkManager, -) { - - operator fun invoke(): Boolean = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() ?: false - - fun canUseBiometry(): Boolean { - return tangemSdkManager.canUseBiometry - } -} \ No newline at end of file diff --git a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt index a1d162c8a3..50fe65280b 100644 --- a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt +++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt @@ -53,7 +53,7 @@ internal class DisclaimerModel @Inject constructor( val shouldAskPushPermission = notificationsRepository.shouldShowSubscribeOnNotificationsAfterUpdate() val isHuaweiDevice = getIsHuaweiDeviceWithoutGoogleServicesUseCase() if (shouldAskPushPermission && !isHuaweiDevice) { - router.push(AppRoute.PushNotification) + router.push(AppRoute.PushNotification(AppRoute.PushNotification.Source.Stories)) } else { neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION) neverRequestPermissionUseCase(PUSH_PERMISSION) diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/entry/routing/AddExistingWalletChildFactory.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/entry/routing/AddExistingWalletChildFactory.kt index 1e0fd88f21..de7e7a76f2 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/entry/routing/AddExistingWalletChildFactory.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/entry/routing/AddExistingWalletChildFactory.kt @@ -1,5 +1,6 @@ package com.tangem.features.hotwallet.addexistingwallet.entry.routing +import com.tangem.common.routing.AppRoute import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.features.hotwallet.addexistingwallet.entry.AddExistingWalletModel @@ -63,6 +64,7 @@ internal class AddExistingWalletChildFactory @Inject constructor( context = childContext, params = PushNotificationsParams( modelCallbacks = model.pushNotificationsCallbacks, + source = AppRoute.PushNotification.Source.Onboarding, ), ) is AddExistingWalletRoute.SetupFinished -> MobileWalletSetupFinishedComponent( diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/routing/WalletActivationChildFactory.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/routing/WalletActivationChildFactory.kt index 59762e8fe3..e6c13bdbd9 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/routing/WalletActivationChildFactory.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/routing/WalletActivationChildFactory.kt @@ -1,12 +1,13 @@ package com.tangem.features.hotwallet.walletactivation.entry.routing +import com.tangem.common.routing.AppRoute import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.features.hotwallet.accesscode.AccessCodeComponent import com.tangem.features.hotwallet.manualbackup.check.ManualBackupCheckComponent import com.tangem.features.hotwallet.manualbackup.completed.ManualBackupCompletedComponent import com.tangem.features.hotwallet.manualbackup.phrase.ManualBackupPhraseComponent import com.tangem.features.hotwallet.manualbackup.start.ManualBackupStartComponent -import com.tangem.features.hotwallet.accesscode.AccessCodeComponent import com.tangem.features.hotwallet.setupfinished.MobileWalletSetupFinishedComponent import com.tangem.features.hotwallet.walletactivation.entry.WalletActivationModel import com.tangem.features.pushnotifications.api.PushNotificationsComponent @@ -72,6 +73,7 @@ internal class WalletActivationChildFactory @Inject constructor( context = childContext, params = PushNotificationsParams( modelCallbacks = model.pushNotificationsCallbacks, + source = AppRoute.PushNotification.Source.Onboarding, ), ) is WalletActivationRoute.SetupFinished -> MobileWalletSetupFinishedComponent( diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt index b486703bd8..7664a6ce6e 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt @@ -93,6 +93,7 @@ internal class ManageTokensListManager @AssistedInject constructor( actionsFlow = actionsFlow, coroutineScope = this, ), + // only for onboarding case, change carefully and check repository implementation loadUserTokensFromRemote = userWalletId != null && source == ManageTokensSource.ONBOARDING, ) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeEnter.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeEnter.kt index 6c8cb1fc1b..d4c055fcda 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeEnter.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeEnter.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -12,6 +13,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -73,6 +75,7 @@ internal fun MultiWalletAccessCodeEnter( label = stringResourceSafe(id = R.string.onboarding_wallet_info_title_third), isError = state.codesNotMatchError, visualTransformation = PasswordVisualTransformation(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), caption = when { state.codesNotMatchError && reEnterAccessCodeState -> stringResourceSafe(R.string.onboarding_access_codes_doesnt_match) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/ui/OnboardingVisaPinCode.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/ui/OnboardingVisaPinCode.kt index cb2d5993c8..094a37d884 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/ui/OnboardingVisaPinCode.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/ui/OnboardingVisaPinCode.kt @@ -153,7 +153,7 @@ private fun PinCode( } }, keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Number, + keyboardType = KeyboardType.NumberPassword, imeAction = ImeAction.Done, ), keyboardActions = KeyboardActions( diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt index 6259c9cacc..ae9b3f2e32 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt @@ -21,7 +21,6 @@ import com.tangem.domain.tokens.GetAssetRequirementsUseCase import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason -import com.tangem.domain.transaction.models.AssetRequirementsCondition import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent @@ -210,21 +209,22 @@ internal class OnrampTokenListModel @Inject constructor( val isOperationAvailable = checkAvailabilityByOperation(status = status) val isNotMissedDerivation = status.value !is CryptoCurrencyStatus.MissedDerivation val isNotLoading = status.value !is CryptoCurrencyStatus.Loading + val requirements = getAssetRequirementsUseCase( userWalletId = userWallet.walletId, currency = status.currency, ).getOrNull() - val isNotTrustlineRequired = requirements !is AssetRequirementsCondition.RequiredTrustline + val isAvailableForBuy = rampStateManager.checkAssetRequirements(requirements) val isNotUnreachable = status.value !is CryptoCurrencyStatus.Unreachable val isAvailable = when (params.filterOperation) { OnrampOperation.BUY -> { - isNotTrustlineRequired + isAvailableForBuy } // unreachable state is available for Buy operation OnrampOperation.SELL -> isNotUnreachable OnrampOperation.SWAP -> { - isNotUnreachable && isNotTrustlineRequired + isNotUnreachable && isAvailableForBuy } } diff --git a/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsParams.kt b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsParams.kt index 4e152b9c09..2ab5d7832f 100644 --- a/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsParams.kt +++ b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsParams.kt @@ -6,4 +6,5 @@ data class PushNotificationsParams( val isBottomSheet: Boolean = false, val nextRoute: AppRoute? = null, val modelCallbacks: PushNotificationsModelCallbacks, + val source: AppRoute.PushNotification.Source, ) \ No newline at end of file diff --git a/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/analytics/PushNotificationAnalyticEvents.kt b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/analytics/PushNotificationAnalyticEvents.kt index 7977e16e18..8ffec6c23c 100644 --- a/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/analytics/PushNotificationAnalyticEvents.kt +++ b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/analytics/PushNotificationAnalyticEvents.kt @@ -53,6 +53,15 @@ sealed class PushNotificationAnalyticEvents( ), ) + data class NotificationsScreenOpened( + val source: AnalyticsParam.ScreensSources, + ) : PushNotificationAnalyticEvents( + event = "Push Notification Screen Opened", + params = mapOf( + AnalyticsParam.SOURCE to source.value, + ), + ) + data class NotificationsEnabled( val isEnabled: Boolean, ) : PushNotificationAnalyticEvents( diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt index 9a6115aa48..c76c250f14 100644 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt @@ -1,6 +1,7 @@ package com.tangem.features.pushnotifications.impl.model import androidx.compose.runtime.Stable +import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam @@ -36,6 +37,11 @@ internal class PushNotificationsModel @Inject constructor( ) : Model(), PushNotificationsClickIntents { val params: PushNotificationsParams = paramsContainer.require() + val source = when (params.source) { + AppRoute.PushNotification.Source.Stories -> AnalyticsParam.ScreensSources.Stories + AppRoute.PushNotification.Source.Main -> AnalyticsParam.ScreensSources.Main + AppRoute.PushNotification.Source.Onboarding -> AnalyticsParam.ScreensSources.Onboarding + } private val _state = MutableStateFlow( PushNotificationsUM( @@ -43,6 +49,10 @@ internal class PushNotificationsModel @Inject constructor( ), ) + init { + analyticHandler.send(PushNotificationAnalyticEvents.NotificationsScreenOpened(source)) + } + val state = _state.asStateFlow() override fun onAllowClick() { @@ -51,9 +61,7 @@ internal class PushNotificationsModel @Inject constructor( notificationsRepository.setUserAllowToSubscribeOnPushNotifications(true) } } - analyticHandler.send( - PushNotificationAnalyticEvents.ButtonAllow(AnalyticsParam.ScreensSources.Stories), - ) + analyticHandler.send(PushNotificationAnalyticEvents.ButtonAllow(source)) } override fun onLaterClick() { @@ -62,9 +70,7 @@ internal class PushNotificationsModel @Inject constructor( notificationsRepository.setUserAllowToSubscribeOnPushNotifications(false) } } - analyticHandler.send( - PushNotificationAnalyticEvents.ButtonLater(AnalyticsParam.ScreensSources.Stories), - ) + analyticHandler.send(PushNotificationAnalyticEvents.ButtonLater(source)) modelScope.launch { neverRequestPermissionUseCase(PUSH_PERMISSION) neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION) diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorModalBottomSheet.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorModalBottomSheet.kt index 36ae4fc32f..c99b647b80 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorModalBottomSheet.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorModalBottomSheet.kt @@ -408,7 +408,10 @@ private class FeeSelectorUMContentProvider : CollectionPreviewParameterProvider< isPrimaryButtonEnabled = false, feeItems = persistentListOf( FeeItem.Suggested( - title = stringReference("Suggested by Tangem"), + title = resourceReference( + id = R.string.wc_fee_suggested, + formatArgs = wrappedList("Tangem"), + ), fee = Fee.Common(Amount(value = BigDecimal("0.1"), blockchain = Blockchain.Ethereum)), ), FeeItem.Slow(fee = Fee.Common(Amount(value = BigDecimal("0.01"), blockchain = Blockchain.Ethereum))), diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/analytics/NFTSendAnalyticHelper.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/analytics/NFTSendAnalyticHelper.kt index b9e53dda49..b57e306719 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/analytics/NFTSendAnalyticHelper.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/analytics/NFTSendAnalyticHelper.kt @@ -5,9 +5,10 @@ import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.features.send.v2.sendnft.ui.state.NFTSendUM +import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents.Companion.NFT_SEND_CATEGORY import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationTextFieldUM import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM +import com.tangem.features.send.v2.sendnft.ui.state.NFTSendUM import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM import javax.inject.Inject @@ -34,7 +35,7 @@ internal class NFTSendAnalyticHelper @Inject constructor( Basic.TransactionSent( sentFrom = AnalyticsParam.TxSentFrom.NFT( blockchain = cryptoCurrency.network.name, - token = cryptoCurrency.symbol, + token = NFT_SEND_CATEGORY, // should send "NFT" in token param feeType = feeType, ), memoType = getSendTransactionMemoType(destinationUM?.memoTextField), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt index 36a4a319f5..4be332b317 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt @@ -38,6 +38,7 @@ internal data class BalanceState( val validator: Yield.Validator?, val pendingActions: ImmutableList, val isPending: Boolean, + val validatorAddress: String?, ) @Immutable diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt index 15cc4b24ae..5d548f7ab4 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt @@ -68,6 +68,7 @@ internal class BalanceItemConverter( pendingActions = value.pendingActions.toPersistentList(), isClickable = value.isClickable(), isPending = value.isPending, + validatorAddress = value.validatorAddress, ) } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt index 4ab5ef020a..4c47f26565 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt @@ -92,6 +92,7 @@ internal class RewardsValidatorStateConverter( isClickable = true, type = balance.type, isPending = balance.isPending, + validatorAddress = balance.validatorAddress, ) } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt index c54feee187..d4f2c4da10 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt @@ -57,8 +57,10 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor( val state = stateController.value val confirmationState = state.confirmationState as? StakingStates.ConfirmationState.Data ?: error("Illegal state") - val validatorState = state.validatorState as? StakingStates.ValidatorState.Data - ?: error("No validator provided") + + val validatorAddress = (state.validatorState as? StakingStates.ValidatorState.Data)?.chosenValidator?.address + ?: state.balanceState?.validatorAddress + ?: error("No validator address provided") val amount = (state.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value ?: error("No amount provided") @@ -66,8 +68,6 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor( val pendingAction = confirmationState.pendingAction val pendingActions = confirmationState.pendingActions - val validatorAddress = validatorState.chosenValidator.address - val isEnter = state.actionType is StakingActionCommonType.Enter val isApprovalNeeded = confirmationState.isApprovalNeeded val isAllowanceNotEnough = confirmationState.allowance < amount diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt index da70fc8533..4deacdff09 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt @@ -100,6 +100,7 @@ internal object InitialStakingStatePreview { type = BalanceType.STAKED, subtitle = null, isPending = false, + validatorAddress = "", ), ), ), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt index 9a319caa8a..47475d3b74 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt @@ -29,6 +29,7 @@ import com.tangem.features.staking.impl.presentation.state.converters.RewardsVal import com.tangem.features.staking.impl.presentation.state.converters.YieldBalancesConverter import com.tangem.features.staking.impl.presentation.state.utils.getRewardScheduleText import com.tangem.utils.Provider +import com.tangem.utils.StringsSigns.DASH_SIGN import com.tangem.utils.isNullOrZero import com.tangem.utils.transformer.Transformer import kotlinx.collections.immutable.PersistentList @@ -227,6 +228,9 @@ internal class SetInitialDataStateTransformer( } private fun getAprRange(validators: List): TextReference { + if (validators.isEmpty()) { + return stringReference(DASH_SIGN) + } val aprValues = validators .filter { it.preferred } .takeIf { it.isNotEmpty() } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt index d9948ed3b8..c940c12e8a 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt @@ -33,6 +33,10 @@ internal class ValidatorSelectChangeTransformer( selectedValidator } + if (selectedValidator == null && yield.preferredValidators.isEmpty()) { + return prevState + } + return prevState.copy( validatorState = StakingStates.ValidatorState.Data( chosenValidator = selectedValidator ?: yield.preferredValidators.first(), diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt index 743c26e5a2..96a200e419 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt @@ -26,11 +26,11 @@ import com.tangem.datasource.crypto.DataSignatureVerifier import com.tangem.datasource.exchangeservice.swap.ExpressUtils import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.transaction.models.AssetRequirementsCondition import com.tangem.feature.swap.converters.* import com.tangem.feature.swap.domain.api.SwapRepository import com.tangem.feature.swap.domain.models.ExpressDataError @@ -55,6 +55,7 @@ internal class DefaultSwapRepository( private val errorsDataConverter: ErrorsDataConverter, private val dataSignatureVerifier: DataSignatureVerifier, private val appPreferencesStore: AppPreferencesStore, + private val rampStateManager: RampStateManager, moshi: Moshi, excludedBlockchains: ExcludedBlockchains, ) : SwapRepository { @@ -138,7 +139,8 @@ internal class DefaultSwapRepository( val currenciesList = currencyList .filter { val requirements = walletManagersFacade.getAssetRequirements(userWallet.walletId, it) - requirements !is AssetRequirementsCondition.RequiredTrustline + val isAvailableForSwap = rampStateManager.checkAssetRequirements(requirements) + isAvailableForSwap } .map { leastTokenInfoConverter.convert(it) } diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt index 118602742e..4aada993a6 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt @@ -9,6 +9,7 @@ import com.tangem.datasource.crypto.DataSignatureVerifier import com.tangem.datasource.di.NetworkMoshi import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.feature.swap.DefaultSwapRepository import com.tangem.feature.swap.DefaultSwapTransactionRepository @@ -38,6 +39,7 @@ internal class SwapDataModule { @NetworkMoshi moshi: Moshi, excludedBlockchains: ExcludedBlockchains, appPreferencesStore: AppPreferencesStore, + rampStateManager: RampStateManager, ): SwapRepository { return DefaultSwapRepository( tangemExpressApi = tangemExpressApi, @@ -49,6 +51,7 @@ internal class SwapDataModule { moshi = moshi, excludedBlockchains = excludedBlockchains, appPreferencesStore = appPreferencesStore, + rampStateManager = rampStateManager, ) } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index abfa93bd3a..de44a22010 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -17,6 +17,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.extenstions.unwrap import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.demo.IsDemoCardUseCase +import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.network.Network @@ -31,7 +32,6 @@ import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.transaction.error.GetFeeError -import com.tangem.domain.transaction.models.AssetRequirementsCondition import com.tangem.domain.transaction.usecase.* import com.tangem.domain.utils.convertToSdkAmount import com.tangem.domain.wallets.usecase.GetUserWalletUseCase @@ -82,6 +82,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase, private val getAssetRequirementsUseCase: GetAssetRequirementsUseCase, private val amountFormatter: AmountFormatter, + private val rampStateManager: RampStateManager, @Assisted private val userWalletId: UserWalletId, ) : SwapInteractor { @@ -179,12 +180,13 @@ internal class SwapInteractorImpl @AssistedInject constructor( tokenInfoForAvailable: (SwapPairLeast) -> LeastTokenInfo, ): List? { val requirements = getAssetRequirementsUseCase.invoke(userWalletId, cryptoCurrencyStatuses.currency).getOrNull() + val isAvailableForSwap = rampStateManager.checkAssetRequirements(requirements) return swapPairsLeastList.firstNotNullOfOrNull { val listTokenInfo = tokenInfoForAvailable(it) if (cryptoCurrencyStatuses.currency.network.backendId == listTokenInfo.network && cryptoCurrencyStatuses.currency.getContractAddress() == listTokenInfo.contractAddress && - requirements !is AssetRequirementsCondition.RequiredTrustline + isAvailableForSwap ) { it.providers } else { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt index 07a629a880..8ee7b2c6c2 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.slot.childSlot import com.arkivanov.decompose.router.slot.dismiss +import com.tangem.common.routing.AppRoute import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.child import com.tangem.core.decompose.context.childByContext @@ -74,6 +75,7 @@ internal class WalletComponent @AssistedInject constructor( params = PushNotificationsParams( isBottomSheet = true, modelCallbacks = model.askForPushNotificationsModelCallbacks, + source = AppRoute.PushNotification.Source.Main, ), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt index e62305cd29..14324845f1 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt @@ -13,6 +13,7 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.card.common.util.cardTypesResolver import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.models.wallet.isLocked import com.tangem.domain.models.wallet.isMultiCurrency import com.tangem.domain.nft.ObserveAndClearNFTCacheIfNeedUseCase import com.tangem.domain.notifications.GetIsHuaweiDeviceWithoutGoogleServicesUseCase @@ -84,7 +85,7 @@ internal class WalletModel @Inject constructor( private val getWalletsListForEnablingUseCase: GetWalletsForAutomaticallyPushEnablingUseCase, private val setNotificationsEnabledUseCase: SetNotificationsEnabledUseCase, private val notificationsFeatureToggles: NotificationsFeatureToggles, - private val getIsBiometryIsEnabledUseCase: GetIsBiometricsEnabledUseCase, + private val shouldSaveUserWalletsSyncUseCase: ShouldSaveUserWalletsSyncUseCase, private val getIsHuaweiDeviceWithoutGoogleServicesUseCase: GetIsHuaweiDeviceWithoutGoogleServicesUseCase, val screenLifecycleProvider: ScreenLifecycleProvider, val innerWalletRouter: InnerWalletRouter, @@ -212,9 +213,15 @@ internal class WalletModel @Inject constructor( modelScope.launch { val shouldAskPermission = shouldAskPermissionUseCase(PUSH_PERMISSION) val afterUpdate = notificationsRepository.shouldShowSubscribeOnNotificationsAfterUpdate() - val isBiometricsEnabled = getIsBiometryIsEnabledUseCase() + val isBiometricsEnabled = shouldSaveUserWalletsSyncUseCase() val isHuaweiDevice = getIsHuaweiDeviceWithoutGoogleServicesUseCase() val shouldShowBottomSheet = shouldAskPermission || afterUpdate + Timber.d( + "push BS afterUpdate: $afterUpdate," + + "shouldAskPermission $shouldAskPermission," + + "isBiometricsEnabled $isBiometricsEnabled," + + "isHuaweiDevice $isHuaweiDevice", + ) if (!isBiometricsEnabled) return@launch if (isHuaweiDevice) return@launch if (!shouldShowBottomSheet) return@launch @@ -383,9 +390,11 @@ internal class WalletModel @Inject constructor( val otherWallets = action.wallets.minus(action.selectedWallet) if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) { - otherWallets.onEach { userWallet -> - modelScope.launch { walletContentFetcher(userWalletId = userWallet.walletId) } - } + otherWallets + .filterNot(UserWallet::isLocked) + .onEach { userWallet -> + modelScope.launch { walletContentFetcher(userWalletId = userWallet.walletId) } + } } if (action.wallets.size > 1 && isWalletsScrollPreviewEnabled()) { @@ -500,6 +509,10 @@ internal class WalletModel @Inject constructor( clickIntents = clickIntents, coroutineScope = modelScope, ) + + action.unlockedWallets.onEach { userWallet -> + modelScope.launch { fetchWalletContent(userWallet = userWallet) } + } } private fun demonstrateWalletsScrollPreview(direction: Direction) { @@ -541,6 +554,8 @@ internal class WalletModel @Inject constructor( private suspend fun fetchWalletContent(userWallet: UserWallet) { if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) { + if (userWallet.isLocked) return + /* * Updating the balance of the current wallet is an essential part of InitializationWallets, * so the coroutine is launched in the current context diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletClickIntents.kt index a7454d94db..55fd1893e3 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletClickIntents.kt @@ -5,13 +5,14 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.extenstions.unwrap import com.tangem.domain.card.common.util.cardTypesResolver import com.tangem.domain.exchange.RampStateManager +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.isLocked import com.tangem.domain.onramp.FetchHotCryptoUseCase import com.tangem.domain.settings.NeverToShowWalletsScrollPreview import com.tangem.domain.tokens.FetchCardTokenListUseCase import com.tangem.domain.tokens.FetchCurrencyStatusUseCase import com.tangem.domain.tokens.FetchTokenListUseCase import com.tangem.domain.tokens.TokensFeatureToggles -import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.domain.wallets.usecase.SelectWalletUseCase import com.tangem.feature.wallet.presentation.router.InnerWalletRouter @@ -87,7 +88,7 @@ internal class WalletClickIntents @Inject constructor( stateHolder.update { it.copy(selectedWalletIndex = index) } maybeUserWallet.onRight { - if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) { + if (tokensFeatureToggles.isWalletBalanceFetcherEnabled && !it.isLocked) { launch { walletContentFetcher(userWalletId = it.walletId) } } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/AlertsComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/AlertsComponent.kt index 6fd6fde73b..fd682083d3 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/AlertsComponent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/AlertsComponent.kt @@ -38,5 +38,23 @@ internal class AlertsComponent( @Serializable data class WcDisconnected(override val onDismiss: () -> Unit) : AlertType() + + @Serializable + data class TangemUnsupportedNetwork( + val network: String, + override val onDismiss: () -> Unit, + ) : AlertType() + + @Serializable + data class RequiredAddNetwork( + val network: String, + override val onDismiss: () -> Unit, + ) : AlertType() + + @Serializable + data class RequiredReconnectWithNetwork( + val network: String, + override val onDismiss: () -> Unit, + ) : AlertType() } } \ No newline at end of file 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 a171a0f38a..afa23ea711 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 @@ -57,7 +57,7 @@ internal class WcPairComponent( private fun onChildBack() { when (val config = contentStack.value.active.configuration) { is WcAppInfoRoutes.AppInfo -> dismiss() - is Alert -> when (config.type) { + is Alert -> when (config.alertType) { is Alert.Type.UnsupportedDApp, is Alert.Type.UnsupportedNetwork, -> dismiss() @@ -85,7 +85,7 @@ internal class WcPairComponent( ) is Alert -> AlertsComponentV2( appComponentContext = appComponentContext, - messageUM = createBottomSheetMessageUM(config.type), + messageUM = createBottomSheetMessageUM(config.alertType), ) is WcAppInfoRoutes.SelectNetworks -> WcSelectNetworksComponent( appComponentContext = appComponentContext, @@ -114,12 +114,14 @@ internal class WcPairComponent( return when (alertType) { is Alert.Type.Verified -> WcAlertsFactory.createVerifiedDomainAlert(alertType.appName) is Alert.Type.UnknownDomain -> WcAlertsFactory.createUnknownDomainAlert(model::connectFromAlert) + is Alert.Type.InvalidDomain -> WcAlertsFactory.createInvalidDomainAlert(model::errorAlertOnDismiss) is Alert.Type.UnsafeDomain -> WcAlertsFactory.createUnsafeDomainAlert(model::connectFromAlert) 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) + is Alert.Type.TimeoutException -> WcAlertsFactory.createTimeoutExceptionAlert(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 9a63c9baad..fe64931b39 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 @@ -17,6 +17,7 @@ import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.message.SnackbarMessage import com.tangem.core.ui.message.ToastMessage import com.tangem.domain.models.network.Network +import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.isLocked import com.tangem.domain.models.wallet.isMultiCurrency @@ -69,8 +70,9 @@ internal class WcPairModel @Inject constructor( val stackNavigation = StackNavigation() - private val selectedUserWalletFlow = + private val selectedUserWalletFlow: MutableStateFlow by lazy { MutableStateFlow(getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }) + } private var proposalNetwork by Delegates.notNull() private var sessionProposal by Delegates.notNull() private var additionallyEnabledNetworks = setOf() @@ -214,15 +216,11 @@ internal class WcPairModel @Inject constructor( private fun processError(error: WcPairError) { val alert = when (error) { - is WcPairError.UnsupportedDApp -> { - WcAppInfoRoutes.Alert.Type.UnsupportedDApp(error.appName) - } - is WcPairError.UnsupportedBlockchains -> { - WcAppInfoRoutes.Alert.Type.UnsupportedNetwork(error.appName) - } - is WcPairError.UriAlreadyUsed -> { - WcAppInfoRoutes.Alert.Type.UriAlreadyUsed - } + is WcPairError.InvalidDomainURL -> WcAppInfoRoutes.Alert.Type.InvalidDomain + is WcPairError.UnsupportedDApp -> WcAppInfoRoutes.Alert.Type.UnsupportedDApp(error.appName) + is WcPairError.UnsupportedBlockchains -> WcAppInfoRoutes.Alert.Type.UnsupportedNetwork(error.appName) + is WcPairError.UriAlreadyUsed -> WcAppInfoRoutes.Alert.Type.UriAlreadyUsed + is WcPairError.TimeoutException -> WcAppInfoRoutes.Alert.Type.TimeoutException else -> { messageSender.send(ToastMessage(message = stringReference(error.message))) router.pop() 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 cf5b226353..784e0457ed 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 @@ -26,15 +26,17 @@ internal sealed class WcAppInfoRoutes : TangemBottomSheetConfigContent, Route { ) : WcAppInfoRoutes() @Serializable - data class Alert(val type: Type) : WcAppInfoRoutes() { + data class Alert(val alertType: Type) : WcAppInfoRoutes() { @Serializable sealed class Type { data class Verified(val appName: String) : Type() data object UnknownDomain : Type() data object UnsafeDomain : Type() + data object InvalidDomain : Type() data class UnsupportedDApp(val appName: String) : Type() data class UnsupportedNetwork(val appName: String) : Type() data object UriAlreadyUsed : Type() + data object TimeoutException : Type() } } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/DefaultWcRoutingComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/DefaultWcRoutingComponent.kt index 42b336932f..30c6d10c14 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/DefaultWcRoutingComponent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/DefaultWcRoutingComponent.kt @@ -18,8 +18,10 @@ import com.tangem.features.send.v2.api.FeeSelectorBlockComponent import com.tangem.features.send.v2.api.FeeSelectorComponent import com.tangem.features.walletconnect.components.WcRoutingComponent import com.tangem.features.walletconnect.connections.components.AlertsComponent +import com.tangem.features.walletconnect.connections.components.AlertsComponent.AlertType.* import com.tangem.features.walletconnect.connections.components.WcPairComponent import com.tangem.features.walletconnect.transaction.components.chain.WcAddNetworkContainerComponent +import com.tangem.features.walletconnect.transaction.components.chain.WcSwitchNetworkComponent import com.tangem.features.walletconnect.transaction.components.common.WcTransactionModelParams import com.tangem.features.walletconnect.transaction.components.send.WcSendTransactionContainerComponent import com.tangem.features.walletconnect.transaction.components.sign.WcSignTransactionContainerComponent @@ -71,6 +73,10 @@ internal class DefaultWcRoutingComponent @AssistedInject constructor( appComponentContext = childContext, params = WcTransactionModelParams(config.rawRequest), ) + is WcInnerRoute.SwitchNetwork -> WcSwitchNetworkComponent( + appComponentContext = childContext, + params = WcTransactionModelParams(config.rawRequest), + ) is WcInnerRoute.Send -> WcSendTransactionContainerComponent( appComponentContext = childContext, params = WcTransactionModelParams(config.rawRequest), @@ -88,13 +94,31 @@ internal class DefaultWcRoutingComponent @AssistedInject constructor( is WcInnerRoute.UnsupportedMethodAlert -> AlertsComponent( childContext, AlertsComponent.Params( - alertType = AlertsComponent.AlertType.UnsupportedMethod { model.innerRouter.pop() }, + alertType = UnsupportedMethod { model.innerRouter.pop() }, ), ) is WcInnerRoute.WcDappDisconnected -> AlertsComponent( childContext, AlertsComponent.Params( - alertType = AlertsComponent.AlertType.WcDisconnected { model.innerRouter.pop() }, + alertType = WcDisconnected { model.innerRouter.pop() }, + ), + ) + is WcInnerRoute.TangemUnsupportedNetwork -> AlertsComponent( + childContext, + AlertsComponent.Params( + alertType = TangemUnsupportedNetwork(config.networkName) { model.innerRouter.pop() }, + ), + ) + is WcInnerRoute.RequiredAddNetwork -> AlertsComponent( + childContext, + AlertsComponent.Params( + alertType = RequiredAddNetwork(config.networkName) { model.innerRouter.pop() }, + ), + ) + is WcInnerRoute.RequiredReconnectWithNetwork -> AlertsComponent( + childContext, + AlertsComponent.Params( + alertType = RequiredReconnectWithNetwork(config.networkName) { model.innerRouter.pop() }, ), ) } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcInnerRoute.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcInnerRoute.kt index 4cabfc0547..bc9f386e88 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcInnerRoute.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcInnerRoute.kt @@ -22,6 +22,9 @@ internal sealed interface WcInnerRoute : Route { @Serializable data class AddNetwork(override val rawRequest: WcSdkSessionRequest) : Method + @Serializable + data class SwitchNetwork(override val rawRequest: WcSdkSessionRequest) : Method + @Serializable data class Pair(val request: WcPairRequest) : WcInnerRoute @@ -30,4 +33,13 @@ internal sealed interface WcInnerRoute : Route { @Serializable data object WcDappDisconnected : WcInnerRoute + + @Serializable + data class TangemUnsupportedNetwork(val networkName: String) : WcInnerRoute + + @Serializable + data class RequiredAddNetwork(val networkName: String) : WcInnerRoute + + @Serializable + data class RequiredReconnectWithNetwork(val networkName: String) : WcInnerRoute } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcRoutingModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcRoutingModel.kt index 3c320faab7..5524f954e3 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcRoutingModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcRoutingModel.kt @@ -51,6 +51,8 @@ internal class WcRoutingModel @Inject constructor( -> WcInnerRoute.SignMessage(rawRequest) WcEthMethodName.AddEthereumChain, -> WcInnerRoute.AddNetwork(rawRequest) + WcEthMethodName.SwitchEthereumChain, + -> WcInnerRoute.SwitchNetwork(rawRequest) WcEthMethodName.SignTransaction, WcEthMethodName.SendTransaction, WcSolanaMethodName.SignTransaction, diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/AlertsModalBottomSheet.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/AlertsModalBottomSheet.kt index 7c93a184f5..30370ab112 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/AlertsModalBottomSheet.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/AlertsModalBottomSheet.kt @@ -87,7 +87,11 @@ private fun ButtonsContainer(alert: AlertsComponent.AlertType, modifier: Modifie ) { val buttonsModifier = Modifier.fillMaxWidth() when (alert) { - is AlertsComponent.AlertType.UnsupportedMethod -> SecondaryButton( + is AlertsComponent.AlertType.UnsupportedMethod, + is AlertsComponent.AlertType.RequiredAddNetwork, + is AlertsComponent.AlertType.RequiredReconnectWithNetwork, + is AlertsComponent.AlertType.TangemUnsupportedNetwork, + -> SecondaryButton( modifier = buttonsModifier, onClick = alert.onDismiss, text = stringResourceSafe(R.string.balance_hidden_got_it_button), @@ -105,16 +109,28 @@ private fun ButtonsContainer(alert: AlertsComponent.AlertType, modifier: Modifie private fun AlertIcon(alert: AlertsComponent.AlertType, modifier: Modifier = Modifier) { val color = when (alert) { is AlertsComponent.AlertType.WcDisconnected -> TangemTheme.colors.icon.informative - is AlertsComponent.AlertType.UnsupportedMethod -> TangemTheme.colors.icon.attention + is AlertsComponent.AlertType.UnsupportedMethod, + is AlertsComponent.AlertType.RequiredAddNetwork, + is AlertsComponent.AlertType.RequiredReconnectWithNetwork, + is AlertsComponent.AlertType.TangemUnsupportedNetwork, + -> TangemTheme.colors.icon.attention } @DrawableRes val drawableId = when (alert) { is AlertsComponent.AlertType.WcDisconnected -> R.drawable.ic_wallet_connect_24 - is AlertsComponent.AlertType.UnsupportedMethod -> R.drawable.img_attention_20 + is AlertsComponent.AlertType.UnsupportedMethod, + is AlertsComponent.AlertType.RequiredAddNetwork, + is AlertsComponent.AlertType.RequiredReconnectWithNetwork, + is AlertsComponent.AlertType.TangemUnsupportedNetwork, + -> R.drawable.img_attention_20 } val iconTint = when (alert) { is AlertsComponent.AlertType.WcDisconnected -> color - is AlertsComponent.AlertType.UnsupportedMethod -> Color.Unspecified + is AlertsComponent.AlertType.UnsupportedMethod, + is AlertsComponent.AlertType.RequiredAddNetwork, + is AlertsComponent.AlertType.RequiredReconnectWithNetwork, + is AlertsComponent.AlertType.TangemUnsupportedNetwork, + -> Color.Unspecified } Box( modifier = modifier @@ -138,6 +154,9 @@ private fun AlertContentTitle(alert: AlertsComponent.AlertType, modifier: Modifi @StringRes val titleRes: Int = when (alert) { is AlertsComponent.AlertType.WcDisconnected -> R.string.wc_alert_session_disconnected_title is AlertsComponent.AlertType.UnsupportedMethod -> R.string.wc_alert_unsupported_method_title + is AlertsComponent.AlertType.RequiredAddNetwork -> R.string.wc_alert_add_network_to_portfolio_title + is AlertsComponent.AlertType.RequiredReconnectWithNetwork -> R.string.wc_alert_network_not_connected_title + is AlertsComponent.AlertType.TangemUnsupportedNetwork -> R.string.wc_alert_unsupported_network_title } Text( modifier = modifier, @@ -157,6 +176,18 @@ private fun AlertContentDescription(alert: AlertsComponent.AlertType, modifier: is AlertsComponent.AlertType.UnsupportedMethod -> stringResourceSafe( R.string.wc_alert_unsupported_method_description, ) + is AlertsComponent.AlertType.RequiredAddNetwork -> stringResourceSafe( + R.string.wc_alert_add_network_to_portfolio_description, + alert.network, + ) + is AlertsComponent.AlertType.RequiredReconnectWithNetwork -> stringResourceSafe( + R.string.wc_alert_network_not_connected_description, + alert.network, + ) + is AlertsComponent.AlertType.TangemUnsupportedNetwork -> stringResourceSafe( + R.string.wc_alert_unsupported_network_description, + alert.network, + ) } Text( modifier = modifier, @@ -188,5 +219,8 @@ private class AlertTypesProvider : CollectionPreviewParameterProvider 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_errors_invalid_domain_title) + body = resourceReference(R.string.wc_errors_invalid_domain_subtitle) + } + primaryButton { + text = resourceReference(R.string.common_got_it) + onClick { onDismiss() } + } + onDismissRequest = onDismiss + } + } + fun createUnsafeDomainAlert(activeButtonOnClick: (() -> Unit)? = null): MessageBottomSheetUMV2 { return messageBottomSheetUM { infoBlock { @@ -109,6 +127,23 @@ internal object WcAlertsFactory { } } + fun createTimeoutExceptionAlert(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_alert_request_timeout_title) + body = resourceReference(R.string.wc_alert_request_timeout_description) + } + primaryButton { + text = resourceReference(R.string.common_got_it) + onClick { onDismiss() } + } + } + } + fun createUnsupportedChainAlert(appName: String, onDismiss: () -> Unit): MessageBottomSheetUMV2 { return messageBottomSheetUM { infoBlock { diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/di/WalletConnectModelModule.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/di/WalletConnectModelModule.kt index 623136bf30..f40742eb25 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/di/WalletConnectModelModule.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/di/WalletConnectModelModule.kt @@ -7,6 +7,7 @@ import com.tangem.features.walletconnect.connections.routing.WcRoutingModel import com.tangem.features.walletconnect.transaction.model.WcAddNetworkModel import com.tangem.features.walletconnect.transaction.model.WcSendTransactionModel import com.tangem.features.walletconnect.transaction.model.WcSignTransactionModel +import com.tangem.features.walletconnect.transaction.model.WcSwitchNetworkModel import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -57,6 +58,11 @@ internal interface WalletConnectModelModule { @ClassKey(WcAddNetworkModel::class) fun bindWcAddNetworkModel(model: WcAddNetworkModel): Model + @Binds + @IntoMap + @ClassKey(WcSwitchNetworkModel::class) + fun bindWcSwitchNetworkModel(model: WcSwitchNetworkModel): Model + @Binds @IntoMap @ClassKey(WcSendTransactionModel::class) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/chain/WcSwitchNetworkComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/chain/WcSwitchNetworkComponent.kt new file mode 100644 index 0000000000..9bf945bd89 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/chain/WcSwitchNetworkComponent.kt @@ -0,0 +1,21 @@ +package com.tangem.features.walletconnect.transaction.components.chain + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.features.walletconnect.transaction.components.common.WcTransactionModelParams +import com.tangem.features.walletconnect.transaction.model.WcSwitchNetworkModel + +internal class WcSwitchNetworkComponent( + appComponentContext: AppComponentContext, + params: WcTransactionModelParams, +) : AppComponentContext by appComponentContext, ComposableContentComponent { + + private val model: WcSwitchNetworkModel = getOrCreateModel(params = params) + + @Composable + override fun Content(modifier: Modifier) { + } +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcHandleMethodErrorConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcHandleMethodErrorConverter.kt index 6c6d317053..93ffa18380 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcHandleMethodErrorConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcHandleMethodErrorConverter.kt @@ -12,5 +12,9 @@ internal object WcHandleMethodErrorConverter : is HandleMethodError.UnknownError, -> WcInnerRoute.UnsupportedMethodAlert HandleMethodError.UnknownSession -> WcInnerRoute.WcDappDisconnected + + is HandleMethodError.NotAddedNetwork -> WcInnerRoute.RequiredAddNetwork(value.networkName) + is HandleMethodError.RequiredNetwork -> WcInnerRoute.RequiredReconnectWithNetwork(value.networkName) + is HandleMethodError.TangemUnsupportedNetwork -> WcInnerRoute.TangemUnsupportedNetwork(value.unsupportedNetwork) } } \ 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 348a69439e..c8f65f4c23 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 @@ -3,6 +3,7 @@ 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.BlockAidTransactionCheck import com.tangem.domain.walletconnect.usecase.method.WcMethodContext import com.tangem.domain.walletconnect.usecase.method.WcSignState import com.tangem.domain.walletconnect.usecase.method.WcSignStep @@ -52,10 +53,19 @@ internal class WcSendTransactionUMConverter @Inject constructor( estimatedWalletChanges = WcSendReceiveTransactionCheckResultsUM(), isLoading = value.signState.domainStep == WcSignStep.Signing, address = WcAddressConverter.convert(value.context.derivationState), - sendEnabled = value.feeSelectorUM is FeeSelectorUM.Content && feeErrorNotification == null, + transactionValidationResult = value.securityCheck?.result?.validation, + sendEnabled = when (value.feeState) { + WcTransactionFeeState.None -> feeErrorNotification == null + is WcTransactionFeeState.Success -> { + value.feeSelectorUM is FeeSelectorUM.Content && feeErrorNotification == null + } + }, feeErrorNotification = feeErrorNotification, ), - feeSelectorUM = value.feeSelectorUM ?: FeeSelectorUM.Loading, + feeSelectorUM = when (value.feeState) { + WcTransactionFeeState.None -> FeeSelectorUM.Loading + is WcTransactionFeeState.Success -> value.feeSelectorUM ?: FeeSelectorUM.Loading + }, transactionRequestInfo = WcTransactionRequestInfoUM( blocks = buildList { addAll( @@ -78,6 +88,7 @@ internal class WcSendTransactionUMConverter @Inject constructor( val actions: WcTransactionActionsUM, val feeSelectorUM: FeeSelectorUM?, val cryptoCurrencyStatus: CryptoCurrencyStatus, + val securityCheck: BlockAidTransactionCheck.Result?, val onFeeReload: () -> Unit, ) } \ 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 86ef260951..e97fc79509 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,5 +1,6 @@ package com.tangem.features.walletconnect.transaction.entity.send +import com.domain.blockaid.models.transaction.ValidationResult import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.features.send.v2.api.entity.FeeSelectorUM @@ -27,6 +28,7 @@ internal data class WcSendTransactionItemUM( val walletName: String?, val networkInfo: WcNetworkInfoUM, val address: String?, + val transactionValidationResult: ValidationResult?, val sendEnabled: Boolean, val feeErrorNotification: NotificationUM.Info?, val isLoading: Boolean = false, diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt index 1684d836c3..ab284df37a 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt @@ -49,28 +49,37 @@ internal class WcAddNetworkModel @Inject constructor( private val params = paramsContainer.require() private var useCase by Delegates.notNull() + private val signatureReceivedAnalyticsSendState = MutableStateFlow(false) init { modelScope.launch { useCase = useCaseFactory.createUseCase(params.rawRequest) .onLeft { router.push(WcHandleMethodErrorConverter.convert(it)) } .getOrNull() ?: return@launch - _uiState.emit( - wcAddEthereumChainUMConverter.convert( - WcAddEthereumChainUMConverter.Input( - useCase = useCase, - actions = WcTransactionActionsUM( - onShowVerifiedAlert = ::showVerifiedAlert, - onDismiss = { cancel(useCase) }, - onSign = { sign(useCase) }, - onCopy = { copyData(useCase.rawSdkRequest.request.params) }, - ), - ), - ), - ) + sendSignatureReceivedAnalytics(useCase) + val either = useCase.invoke() + either + .onLeft { router.push(WcHandleMethodErrorConverter.convert(it)) } + .map { + if (it.isExistInWcSession) cancel(useCase) else showUI() + } } } + private fun showUI() { + _uiState.value = wcAddEthereumChainUMConverter.convert( + WcAddEthereumChainUMConverter.Input( + useCase = useCase, + actions = WcTransactionActionsUM( + onShowVerifiedAlert = ::showVerifiedAlert, + onDismiss = { cancel(useCase) }, + onSign = { sign(useCase) }, + onCopy = { copyData(useCase.rawSdkRequest.request.params) }, + ), + ), + ) + } + override fun dismiss() { _uiState.value?.transaction?.onDismiss?.invoke() ?: router.pop() } @@ -114,4 +123,18 @@ internal class WcAddNetworkModel @Inject constructor( private fun copyData(text: String) { clipboardManager.setText(text = text, isSensitive = true) } + + private fun sendSignatureReceivedAnalytics(useCase: WcAddNetworkUseCase) { + if (signatureReceivedAnalyticsSendState.value) return + + analytics.send( + WcAnalyticEvents.SignatureRequestReceived( + rawRequest = useCase.rawSdkRequest, + network = useCase.network, + emulationStatus = null, + ), + ) + + signatureReceivedAnalyticsSendState.value = true + } } \ 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 0e9db77189..d3f10b517e 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 @@ -18,7 +18,8 @@ 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.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList import com.tangem.domain.core.lce.Lce import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.network.Network @@ -26,6 +27,7 @@ import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.tokens.GetNetworkCoinStatusUseCase import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.transaction.error.GetFeeError +import com.tangem.domain.transaction.error.SendTransactionError.UserCancelledError import com.tangem.domain.transaction.usecase.GetFeeUseCase import com.tangem.domain.walletconnect.WcAnalyticEvents import com.tangem.domain.walletconnect.WcAnalyticEvents.SignatureRequestReceived.EmulationStatus @@ -39,6 +41,7 @@ import com.tangem.features.send.v2.api.params.FeeSelectorParams.FeeStateConfigur import com.tangem.features.send.v2.api.subcomponents.feeSelector.FeeSelectorReloadTrigger import com.tangem.features.send.v2.api.subcomponents.feeSelector.entity.FeeSelectorData import com.tangem.features.walletconnect.connections.routing.WcInnerRoute +import com.tangem.features.walletconnect.impl.R import com.tangem.features.walletconnect.transaction.components.common.WcTransactionModelParams import com.tangem.features.walletconnect.transaction.converter.WcHandleMethodErrorConverter import com.tangem.features.walletconnect.transaction.converter.WcSendTransactionUMConverter @@ -57,7 +60,7 @@ import java.math.BigDecimal import javax.inject.Inject import kotlin.properties.Delegates -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") @Stable @ModelScoped internal class WcSendTransactionModel @Inject constructor( @@ -112,7 +115,10 @@ internal class WcSendTransactionModel @Inject constructor( ?.dAppFee() ?.let { dAppFee -> feeStateConfiguration = FeeStateConfiguration.Suggestion( - title = stringReference(useCase.session.sdkModel.appMetaData.name), + title = resourceReference( + id = R.string.wc_fee_suggested, + formatArgs = wrappedList(useCase.session.sdkModel.appMetaData.name), + ), fee = dAppFee, ) } @@ -244,6 +250,7 @@ internal class WcSendTransactionModel @Inject constructor( feeSelectorUM = uiState.value?.feeSelectorUM, cryptoCurrencyStatus = cryptoCurrencyStatus, onFeeReload = ::triggerFeeReload, + securityCheck = securityCheck.getOrNull(), ), ) transactionUM = transactionUM?.copy( @@ -275,6 +282,7 @@ internal class WcSendTransactionModel @Inject constructor( stackNavigation.pushNew(WcTransactionRoutes.SelectFee) } + // Before change, make sure you are align with WcTransactionRequestButtons private fun onSign(securityCheck: BlockAidTransactionCheck.Result?) { when (securityCheck?.result?.validation) { ValidationResult.UNSAFE -> showMaliciousAlert(securityCheck.result.description) @@ -339,25 +347,35 @@ internal class WcSendTransactionModel @Inject constructor( } private fun signingIsDone(signState: WcSignState<*>, useCase: WcSignUseCase<*>): Boolean { - (signState.domainStep as? WcSignStep.Result)?.result?.let { - return handleSigningError(it, useCase) + return when (val step = signState.domainStep) { + is WcSignStep.Result -> processResultStep(result = step.result, useCase = useCase) + WcSignStep.PreSign, + WcSignStep.Signing, + -> false } - return false } - 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 + private fun processResultStep(result: Either, useCase: WcSignUseCase<*>): Boolean { + return when (result) { + is Either.Left -> { + val error = result.value + if (error is WcRequestError.WrappedSendError && error.sendTransactionError is UserCancelledError) { + return false + } + + val alertError = WcTransactionRoutes.Alert.Type.UnknownError( + errorMessage = result.value.message(), + onDismiss = { cancel(useCase) }, + onRetry = { signFromAlert() }, + ) + stackNavigation.pushNew(WcTransactionRoutes.Alert(alertError)) + false + } + is Either.Right -> { + 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 1283f3e84f..9f3a686357 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 @@ -59,12 +59,14 @@ internal class WcSignTransactionModel @Inject constructor( val stackNavigation = StackNavigation() private var useCase by Delegates.notNull() + private val signatureReceivedAnalyticsSendState = MutableStateFlow(false) init { modelScope.launch { useCase = useCaseFactory.createUseCase(params.rawRequest) .onLeft { router.push(WcHandleMethodErrorConverter.convert(it)) } .getOrNull() ?: return@launch + sendSignatureReceivedAnalytics(useCase) useCase.invoke() .onEach { signState -> if (signingIsDone(signState)) return@onEach @@ -129,8 +131,10 @@ internal class WcSignTransactionModel @Inject constructor( } private fun signingIsDone(signState: WcSignState<*>): Boolean { - (signState.domainStep as? WcSignStep.Result)?.result?.let { - showSuccessSignMessage() + (signState.domainStep as? WcSignStep.Result)?.result?.let { result -> + if (result.isRight()) { + showSuccessSignMessage() + } router.pop() return true } @@ -145,4 +149,18 @@ internal class WcSignTransactionModel @Inject constructor( private fun copyData(text: String) { clipboardManager.setText(text = text, isSensitive = true) } + + private fun sendSignatureReceivedAnalytics(useCase: WcMessageSignUseCase) { + if (signatureReceivedAnalyticsSendState.value) return + + analytics.send( + WcAnalyticEvents.SignatureRequestReceived( + rawRequest = useCase.rawSdkRequest, + network = useCase.network, + emulationStatus = null, + ), + ) + + signatureReceivedAnalyticsSendState.value = true + } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSwitchNetworkModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSwitchNetworkModel.kt new file mode 100644 index 0000000000..d8c49a7508 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSwitchNetworkModel.kt @@ -0,0 +1,51 @@ +package com.tangem.features.walletconnect.transaction.model + +import androidx.compose.runtime.Stable +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.decompose.navigation.Router +import com.tangem.domain.walletconnect.WcRequestUseCaseFactory +import com.tangem.domain.walletconnect.model.HandleMethodError +import com.tangem.domain.walletconnect.usecase.method.WcSwitchNetworkUseCase +import com.tangem.features.walletconnect.transaction.components.common.WcTransactionModelParams +import com.tangem.features.walletconnect.transaction.converter.WcHandleMethodErrorConverter +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.launch +import javax.inject.Inject + +@Suppress("LongParameterList") +@Stable +@ModelScoped +internal class WcSwitchNetworkModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, + private val router: Router, + private val useCaseFactory: WcRequestUseCaseFactory, +) : Model() { + + private val params = paramsContainer.require() + + init { + modelScope.launch { + val useCase = useCaseFactory.createUseCase(params.rawRequest) + .onLeft { showErrorDialog(it) } + .getOrNull() ?: return@launch + val either = useCase.invoke() + useCase.reject() + either + .onLeft { showErrorDialog(it) } + .map { + if (it.isExistInWcSession) { + router.pop() + } else { + showErrorDialog(HandleMethodError.RequiredNetwork(it.network.name)) + } + } + } + } + + private fun showErrorDialog(error: HandleMethodError) { + router.push(WcHandleMethodErrorConverter.convert(error)) + } +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcEstimatedWalletChangeUMConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcEstimatedWalletChangeUMConverter.kt index c51cc78fbb..91ab33bb5b 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcEstimatedWalletChangeUMConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcEstimatedWalletChangeUMConverter.kt @@ -6,7 +6,6 @@ import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.format import com.tangem.features.walletconnect.transaction.entity.blockaid.WcEstimatedWalletChangeUM import com.tangem.utils.converter.Converter -import java.math.BigDecimal import javax.inject.Inject internal class WcEstimatedWalletChangeUMConverter @Inject constructor() : @@ -17,7 +16,7 @@ internal class WcEstimatedWalletChangeUMConverter @Inject constructor() : is AmountInfo.FungibleTokens -> WcEstimatedWalletChangeUM( iconRes = value.iconRes, title = resourceReference(value.titleRes), - description = "${value.sign} ${amountInfo.amount.amountText()} ${amountInfo.token.symbol}", + description = "${value.sign} ${amountInfo.amountText()} ${amountInfo.token.symbol}", tokenIconUrl = amountInfo.token.logoUrl, ) is AmountInfo.NonFungibleTokens -> WcEstimatedWalletChangeUM( @@ -29,7 +28,7 @@ internal class WcEstimatedWalletChangeUMConverter @Inject constructor() : } } - private fun BigDecimal.amountText() = format { crypto("", DECIMALS_AMOUNT) } + private fun AmountInfo.FungibleTokens.amountText() = amount.format { crypto("", token.decimals) } data class Input( val amountInfo: AmountInfo, diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcEstimatedWalletChangesItem.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcEstimatedWalletChangesItem.kt index 25e8b53eba..590b49c95e 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcEstimatedWalletChangesItem.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcEstimatedWalletChangesItem.kt @@ -132,8 +132,7 @@ private fun EstimatedWalletChangesPreviewMoreThanFour( } } -private class EstimatedWalletChangesPreviewProviderTwoItems : - PreviewParameterProvider { +private class EstimatedWalletChangesPreviewProviderTwoItems : PreviewParameterProvider { override val values = sequenceOf( WcEstimatedWalletChangesUM( items = persistentListOf( @@ -149,6 +148,13 @@ private class EstimatedWalletChangesPreviewProviderTwoItems : description = "+ 1,131.46 MATIC", tokenIconUrl = "https://tangem.com", ), + WcEstimatedWalletChangeUM( + iconRes = R.drawable.img_approvale_new_24, + title = resourceReference(R.string.common_approve), + description = "10 Collection", + tokenIconUrl = "https://cdn.blockaid.io/nft/0x09851531816f78cF4841f1DeF22fbaB78aDD02c5/29805/" + + "polygon?r=ed117da0-6065-4ff8-ba81-cb7395e1ec3d", + ), ), ), ) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcSendAndReceiveBlockAidUiConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcSendAndReceiveBlockAidUiConverter.kt index 1eda478462..3ef6afbf4c 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcSendAndReceiveBlockAidUiConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcSendAndReceiveBlockAidUiConverter.kt @@ -4,6 +4,7 @@ import com.domain.blockaid.models.transaction.CheckTransactionResult import com.domain.blockaid.models.transaction.SimulationResult import com.domain.blockaid.models.transaction.ValidationResult import com.domain.blockaid.models.transaction.simultation.AmountInfo +import com.domain.blockaid.models.transaction.simultation.ApproveInfo import com.domain.blockaid.models.transaction.simultation.SimulationData import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.format.bigdecimal.crypto @@ -21,7 +22,7 @@ import kotlinx.collections.immutable.toImmutableList import java.math.BigDecimal import javax.inject.Inject -internal const val DECIMALS_AMOUNT = 2 +private const val DECIMALS_AMOUNT = 2 @Suppress("CyclomaticComplexMethod", "LongMethod") internal class WcSendAndReceiveBlockAidUiConverter @Inject constructor( @@ -55,7 +56,20 @@ internal class WcSendAndReceiveBlockAidUiConverter @Inject constructor( }, estimatedWalletChanges = (simulation as? SimulationResult.Success)?.data?.let { data -> when (data) { - is SimulationData.Approve, SimulationData.NoWalletChangesDetected -> null + is SimulationData.NoWalletChangesDetected -> null + is SimulationData.Approve -> { + val nftItems = data.items.mapNotNull { item -> + if (item !is ApproveInfo.NonFungibleToken) return@mapNotNull null + + WcEstimatedWalletChangeUM( + iconRes = R.drawable.img_approvale_new_24, + title = TextReference.Res(R.string.common_approve), + description = item.name, + tokenIconUrl = item.logoUrl, + ) + } + WcEstimatedWalletChangesUM(nftItems.toImmutableList()).takeIf { nftItems.isNotEmpty() } + } is SimulationData.SendAndReceive -> { val items: ImmutableList = ( data.send.map { diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/chain/WcAddEthereumChainModalBottomSheetContent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/chain/WcAddEthereumChainModalBottomSheetContent.kt index e835f5ad87..73d7ef80e2 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/chain/WcAddEthereumChainModalBottomSheetContent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/chain/WcAddEthereumChainModalBottomSheetContent.kt @@ -30,8 +30,8 @@ import com.tangem.features.walletconnect.connections.ui.WcAppInfoItem import com.tangem.features.walletconnect.impl.R import com.tangem.features.walletconnect.transaction.entity.chain.WcAddEthereumChainItemUM import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionAppInfoContentUM +import com.tangem.features.walletconnect.transaction.ui.common.WcSimpleConfirmButtons import com.tangem.features.walletconnect.transaction.ui.common.WcSmallTitleItem -import com.tangem.features.walletconnect.transaction.ui.common.WcTransactionRequestButtons import com.tangem.features.walletconnect.transaction.ui.common.WcTransactionRequestItem import com.tangem.features.walletconnect.transaction.ui.common.WcWalletItem @@ -52,7 +52,7 @@ internal fun WcAddEthereumChainModalBottomSheetContent( onBack = onBack, title = { TangemModalBottomSheetTitle( - title = resourceReference(R.string.wc_wallet_connect), + title = resourceReference(R.string.wc_transaction_flow_title), endIconRes = R.drawable.ic_close_24, onEndClick = onDismiss, ) @@ -91,11 +91,11 @@ internal fun WcAddEthereumChainModalBottomSheetContent( } }, footer = { - WcTransactionRequestButtons( + WcSimpleConfirmButtons( modifier = Modifier.padding(16.dp), onDismiss = state.onDismiss, onClickActiveButton = state.onSign, - activeButtonText = resourceReference(R.string.common_sign), + activeButtonText = resourceReference(R.string.common_add), isLoading = state.isLoading, ) }, 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 ed0217aee9..61f1cf95e7 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 @@ -2,9 +2,12 @@ package com.tangem.features.walletconnect.transaction.ui.common import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.domain.blockaid.models.transaction.ValidationResult +import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.PrimaryButtonIconEnd import com.tangem.core.ui.components.SecondaryButton import com.tangem.core.ui.extensions.TextReference @@ -17,10 +20,78 @@ import com.tangem.features.walletconnect.impl.R internal fun WcTransactionRequestButtons( activeButtonText: TextReference, isLoading: Boolean, + validationResult: ValidationResult?, onDismiss: () -> Unit, onClickActiveButton: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, +) { + WcCommonButtons( + onDismiss = onDismiss, + modifier = modifier, + primaryButton = { + // Before change, make sure you are align with WcSendTransactionModel::onSign + when (validationResult) { + ValidationResult.UNSAFE, + ValidationResult.WARNING, + -> PrimaryButton( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + text = stringResourceSafe(R.string.common_continue), + onClick = onClickActiveButton, + showProgress = isLoading, + enabled = enabled, + ) + ValidationResult.SAFE, + ValidationResult.FAILED_TO_VALIDATE, + null, + -> PrimaryButtonIconEnd( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + text = activeButtonText.resolveReference(), + onClick = onClickActiveButton, + iconResId = R.drawable.ic_tangem_24, + showProgress = isLoading, + enabled = enabled, + ) + } + }, + ) +} + +@Composable +internal fun WcSimpleConfirmButtons( + activeButtonText: TextReference, + isLoading: Boolean, + onDismiss: () -> Unit, + onClickActiveButton: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, +) { + WcCommonButtons( + onDismiss = onDismiss, + modifier = modifier, + primaryButton = { + PrimaryButton( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + text = activeButtonText.resolveReference(), + onClick = onClickActiveButton, + showProgress = isLoading, + enabled = enabled, + ) + }, + ) +} + +@Composable +internal fun WcCommonButtons( + onDismiss: () -> Unit, + modifier: Modifier = Modifier, + primaryButton: @Composable RowScope.() -> Unit, ) { Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) { SecondaryButton( @@ -30,15 +101,6 @@ internal fun WcTransactionRequestButtons( text = stringResourceSafe(R.string.common_cancel), onClick = onDismiss, ) - PrimaryButtonIconEnd( - modifier = Modifier - .fillMaxWidth() - .weight(1f), - text = activeButtonText.resolveReference(), - onClick = onClickActiveButton, - iconResId = R.drawable.ic_tangem_24, - showProgress = isLoading, - enabled = enabled, - ) + primaryButton() } } \ 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 cf5f3fee9f..b647ef5abd 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 @@ -16,6 +16,7 @@ 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.domain.blockaid.models.transaction.ValidationResult import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent @@ -24,6 +25,7 @@ import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTi 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.components.notifications.NotificationConfig import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference @@ -71,7 +73,7 @@ internal fun WcSendTransactionModalBottomSheet( containerColor = TangemTheme.colors.background.tertiary, title = { config -> TangemModalBottomSheetTitle( - title = resourceReference(R.string.wc_wallet_connect), + title = resourceReference(R.string.wc_transaction_flow_title), endIconRes = R.drawable.ic_close_24, onEndClick = onDismiss, ) @@ -122,7 +124,7 @@ internal fun WcSendTransactionModalBottomSheet( modifier = Modifier.padding(top = 14.dp), config = state.feeErrorNotification.config, iconTint = TangemTheme.colors.icon.warning, - containerColor = TangemTheme.colors.button.disabled, + containerColor = TangemTheme.colors.background.action, ) } } @@ -136,6 +138,7 @@ internal fun WcSendTransactionModalBottomSheet( activeButtonText = resourceReference(R.string.common_send), isLoading = state.isLoading, enabled = state.sendEnabled, + validationResult = state.transactionValidationResult, ) }, ) @@ -220,6 +223,7 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide address = null, sendEnabled = true, feeErrorNotification = null, + transactionValidationResult = null, ), WcSendTransactionItemUM( onDismiss = {}, @@ -262,6 +266,7 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide title = stringReference("Insufficient Ethereum"), subtitle = stringReference("Top up your balance to cover the network fee"), ), + transactionValidationResult = ValidationResult.WARNING, ), WcSendTransactionItemUM( onDismiss = {}, @@ -301,9 +306,14 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide address = null, sendEnabled = false, feeErrorNotification = NotificationUM.Info( - title = stringReference("Insufficient Ethereum"), - subtitle = stringReference("Top up your balance to cover the network fee"), + 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 = {}, + ), ), + transactionValidationResult = ValidationResult.SAFE, ), ), ) \ 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 86e7fad3f4..5b0b50fe2e 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 @@ -50,7 +50,7 @@ internal fun WcSignTransactionModalBottomSheetContent( onBack = onBack, title = { TangemModalBottomSheetTitle( - title = resourceReference(R.string.wc_wallet_connect), + title = resourceReference(R.string.wc_transaction_flow_title), endIconRes = R.drawable.ic_close_24, onEndClick = onDismiss, ) @@ -95,6 +95,7 @@ internal fun WcSignTransactionModalBottomSheetContent( onClickActiveButton = state.onSign, activeButtonText = resourceReference(R.string.common_sign), isLoading = state.isLoading, + validationResult = null, ) }, ) 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 index 8867a92b5d..09b3659efd 100644 --- 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 @@ -1,6 +1,5 @@ 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 @@ -54,14 +53,11 @@ internal class WcNotificationsFactory @Inject constructor() { 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 - } + val selectedFee = feeSelectorContent.selectedFeeItem.fee return FeeCalculationUtils.checkExceedBalance( feeBalance = cryptoCurrencyStatus.value.amount, - feeAmount = lowestFee.amount.value, + feeAmount = selectedFee.amount.value, ) } } \ No newline at end of file diff --git a/features/welcome/impl/build.gradle.kts b/features/welcome/impl/build.gradle.kts index 78977b9216..7557118256 100644 --- a/features/welcome/impl/build.gradle.kts +++ b/features/welcome/impl/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { implementation(projects.domain.appCurrency) implementation(projects.domain.wallets) implementation(projects.domain.card) + implementation(projects.domain.settings) /** DI */ implementation(deps.hilt.android) diff --git a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt index 6a432d7e17..3baa86b20e 100644 --- a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt +++ b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt @@ -13,14 +13,14 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.message.SnackbarMessage -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.models.wallet.isLocked import com.tangem.domain.core.wallets.UserWalletsListRepository import com.tangem.domain.core.wallets.error.UnlockWalletError +import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.models.wallet.isLocked +import com.tangem.domain.settings.CanUseBiometryUseCase import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase -import com.tangem.domain.wallets.usecase.GetIsBiometricsEnabledUseCase import com.tangem.features.wallet.utils.UserWalletsFetcher import com.tangem.features.welcome.impl.R import com.tangem.features.welcome.impl.ui.state.WelcomeUM @@ -30,11 +30,7 @@ import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.update +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -45,12 +41,12 @@ internal class WelcomeModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val router: Router, private val uiMessageSender: UiMessageSender, - private val userWalletsFetcherFactory: UserWalletsFetcher.Factory, private val userWalletsListRepository: UserWalletsListRepository, - private val getIsBiometricsEnabledUseCase: GetIsBiometricsEnabledUseCase, + private val canUseBiometryUseCase: CanUseBiometryUseCase, private val walletsRepository: WalletsRepository, private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase, private val urlOpener: UrlOpener, + userWalletsFetcherFactory: UserWalletsFetcher.Factory, ) : Model() { val uiState: StateFlow @@ -224,7 +220,7 @@ internal class WelcomeModel @Inject constructor( } private suspend fun canUnlockWithBiometrics(): Boolean { - return getIsBiometricsEnabledUseCase.canUseBiometry() && walletsRepository.useBiometricAuthentication() + return canUseBiometryUseCase() && walletsRepository.useBiometricAuthentication() } suspend fun unlockWallet(userWalletId: UserWalletId, unlockMethod: UserWalletsListRepository.UnlockMethod) { diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index fb79857d96..f430a4dd17 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,9 +5,9 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "develop-1140" +tangemBlockchainSdk = "develop-1205" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-518" +tangemCardSdk = "develop-557" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem12" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildConfigField.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildConfigField.kt index 17f61ecdd2..058a4ccb06 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildConfigField.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildConfigField.kt @@ -15,13 +15,6 @@ internal sealed class BuildConfigField(val type: String, val name: String, val v value = "\"$value\"", ) - // TODO remove - class TestActionEnabled(isEnabled: Boolean) : BuildConfigField( - type = "Boolean", - name = "TEST_ACTION_ENABLED", - value = isEnabled.toString(), - ) - class LogEnabled(isEnabled: Boolean) : BuildConfigField( type = "Boolean", name = "LOG_ENABLED", diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildType.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildType.kt index 91ee0ee9d8..896952a0e8 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildType.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildType.kt @@ -25,7 +25,6 @@ internal enum class BuildType( appIdSuffix = "debug", configFields = listOf( BuildConfigField.Environment(value = "dev"), - BuildConfigField.TestActionEnabled(isEnabled = true), BuildConfigField.LogEnabled(isEnabled = true), BuildConfigField.TesterMenuAvailability(isEnabled = true), BuildConfigField.MockDataSource(isEnabled = false), @@ -47,7 +46,6 @@ internal enum class BuildType( versionSuffix = "mocked", configFields = listOf( BuildConfigField.Environment(value = "dev"), - BuildConfigField.TestActionEnabled(isEnabled = false), BuildConfigField.LogEnabled(isEnabled = true), BuildConfigField.TesterMenuAvailability(isEnabled = true), BuildConfigField.MockDataSource(isEnabled = true), @@ -71,7 +69,6 @@ internal enum class BuildType( versionSuffix = "internal", configFields = listOf( BuildConfigField.Environment(value = "prod"), - BuildConfigField.TestActionEnabled(isEnabled = true), BuildConfigField.LogEnabled(isEnabled = true), BuildConfigField.TesterMenuAvailability(isEnabled = true), BuildConfigField.MockDataSource(isEnabled = false), @@ -92,7 +89,6 @@ internal enum class BuildType( versionSuffix = "external", configFields = listOf( BuildConfigField.Environment(value = "prod"), - BuildConfigField.TestActionEnabled(isEnabled = false), BuildConfigField.LogEnabled(isEnabled = false), BuildConfigField.TesterMenuAvailability(isEnabled = false), BuildConfigField.MockDataSource(isEnabled = false), @@ -111,7 +107,6 @@ internal enum class BuildType( id = "release", configFields = listOf( BuildConfigField.Environment(value = "prod"), - BuildConfigField.TestActionEnabled(isEnabled = false), BuildConfigField.LogEnabled(isEnabled = false), BuildConfigField.TesterMenuAvailability(isEnabled = false), BuildConfigField.MockDataSource(isEnabled = false),