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