Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-16 13:47:30 +05:00
commit 8541957ee3
20 changed files with 866 additions and 16 deletions

View file

@ -1316,7 +1316,7 @@
<string name="warning_express_approval_in_progress_message">Die Genehmigung des Swaps ist im Gange und wird in Kürze abgeschlossen sein.</string>
<string name="warning_express_approval_in_progress_title">Genehmigung in Arbeit</string>
<string name="warning_express_dust_message">Der Mindestbetrag für den Tausch beträgt %1$s. Bitte stelle sicher, dass der Restsaldo nach dem Swap nicht unter %2$s liegt.</string>
<string name="warning_express_no_exchangeable_coins_description">Du hast keine %s austauschbaren Coins in Deiner Liste</string>
<string name="warning_express_no_exchangeable_coins_description">Sie haben keine Token in Ihrem Portfolio, gegen die %s getauscht werden kann. Bitte fügen Sie einen anderen Token hinzu, um den Tausch durchzuführen.</string>
<string name="warning_express_no_exchangeable_coins_title">Keine Token zum Tauschen verfügbar</string>
<string name="warning_express_not_enough_fee_for_token_tx_description">Um eine Transaktion durchzuführen, du etwas etwas einzahlen %1$s %2$s</string>
<string name="warning_express_not_enough_fee_for_token_tx_title">Die Gebühr %s kann nicht gedeckt werden</string>

View file

@ -1236,7 +1236,7 @@
<string name="warning_express_approval_in_progress_message">La aprobación del intercambio está en progreso y se completará en breve</string>
<string name="warning_express_approval_in_progress_title">Aprobación en proceso</string>
<string name="warning_express_dust_message">El monto mínimo de transacción es %1$s. Asegúrese de que el saldo restante después del canje no sea inferior a %2$s.</string>
<string name="warning_express_no_exchangeable_coins_description">No tiene %s monedas negociables en su lista</string>
<string name="warning_express_no_exchangeable_coins_description">No tienes tokens en tu portafolio a los que puedas intercambiar %s. Por favor, añade otro token para realizar el intercambio.</string>
<string name="warning_express_no_exchangeable_coins_title">No hay tokens disponibles para intercambiar</string>
<string name="warning_express_not_enough_fee_for_token_tx_description">Para realizar una transacción necesita depositar %1$s %2$s</string>
<string name="warning_express_not_enough_fee_for_token_tx_title">No se pueden cubrir %s tarifa</string>

View file

@ -49,6 +49,8 @@
<string name="app_settings_theme_mode_system">Par défaut du système</string>
<string name="app_settings_theme_selector_title">Thème</string>
<string name="app_settings_title">Paramètres de l\'application</string>
<string name="backup_complete_seed_description">Ces mots ne peuvent pas être récupérés en cas de perte. Assurez-vous de les conserver en lieu sûr.</string>
<string name="backup_seed_caution">Ne partagez jamais ces mots. Quiconque les apprend peut voler toutes vos cryptomonnaies. Tangem ne vous les demandera jamais. Les %s mots ci-dessous constituent la phrase de récupération de votre portefeuille. Cette phrase vous permet de récupérer votre portefeuille en cas de perte de votre appareil.</string>
<string name="balance_hidden_description">Pour masquer ou afficher vos soldes, il suffit de retourner l\'écran de votre appareil vers le bas ou de le désactiver dans les paramètres</string>
<string name="balance_hidden_do_not_show_button">Ne plus afficher</string>
<string name="balance_hidden_got_it_button">Compris</string>
@ -216,6 +218,7 @@
<string name="common_unreachable">Inaccessible</string>
<string name="common_unstake">Unstakez</string>
<string name="common_utxo_validate_withdrawal_message_warning">En raison d\'une limitations sur les %1$s, seuls les %2$d UTXO peuvent s\'intégrer dans une seule transaction. Ce qui signifie vous ne pouvez envoyer que %3$s ou moins. Réduisez le montant.</string>
<string name="common_value_copied">Valeur copiée</string>
<string name="common_week">semaine</string>
<string name="common_with">avec</string>
<string name="common_yes">Oui</string>
@ -699,6 +702,7 @@
<string name="organize_tokens_sort_by_balance">Par solde</string>
<string name="organize_tokens_title">Organiser les jetons</string>
<string name="organize_tokens_ungroup">Dégrouper</string>
<string name="push_transactions_notifications_description">Recevez des alertes pour les transactions entrantes sur les réseaux pris en charge.</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>
@ -855,6 +859,7 @@
<string name="send_validation_invalid_amount">Montant invalide</string>
<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="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>
@ -1085,6 +1090,7 @@
<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>
<string name="user_push_notification_agreement_argument_one">Restez à jour avec les dernières fonctionnalités et actualités</string>
<string name="user_push_notification_agreement_argument_three">Recevez des notifications des transactions entrantes</string>
<string name="user_push_notification_agreement_argument_two">Soyez le premier informé des nouvelles promotions</string>
<string name="user_push_notification_agreement_header">Souhaitez-vous utiliser les\nnotifications push?</string>
<string name="user_wallet_list_add_button">Ajouter un nouveau portefeuille</string>
@ -1157,6 +1163,7 @@
<string name="wallet_promo_banner_button_title">Obtenez-le maintenant avec 10% de réduction</string>
<string name="wallet_promo_banner_description">Accédez à plus de 13 000 cryptomonnaies. Achetez, vendez, échangez et stakez en un seul clic.\nAssociez jusqu\'à trois cartes pour une sauvegarde.</string>
<string name="wallet_promo_banner_title">Découvrez le Portefeuille Tangem</string>
<string name="wallet_settings_push_notifications_description">Recevez des notifications sur les transactions entrantes du portefeuille et les mises à jour de Tangem.</string>
<string name="wallet_settings_title">Paramètres du portefeuille</string>
<string name="wallet_title">Tangem</string>
<string name="warning_access_denied_message">Utilisez %s ou scannez une carte/bague pour déverrouiller l\'accès à votre portefeuille</string>
@ -1184,7 +1191,7 @@
<string name="warning_express_approval_in_progress_message">L\'approbation de l\'échange est en cours et sera achevée sous peu</string>
<string name="warning_express_approval_in_progress_title">Approbation en cours</string>
<string name="warning_express_dust_message">Le montant minimum d\'échange est de %1$s. Veuillez vous assurer que le solde restant après l\'échange ne sera pas inférieur à %2$s.</string>
<string name="warning_express_no_exchangeable_coins_description">Vous n\'avez pas de pièces échangeables %s dans votre liste</string>
<string name="warning_express_no_exchangeable_coins_description">Vous navez aucun jeton dans votre portefeuille pouvant recevoir %s en échange. Veuillez ajouter un autre jeton pour effectuer léchange.</string>
<string name="warning_express_no_exchangeable_coins_title">Aucun jeton disponible à échanger</string>
<string name="warning_express_not_enough_fee_for_token_tx_description">Pour effectuer une transaction, vous devez déposer %1$s %2$s</string>
<string name="warning_express_not_enough_fee_for_token_tx_title">Impossible de couvrir %s frais</string>
@ -1243,9 +1250,13 @@
<string name="warning_some_networks_unreachable_message">Certains réseaux sont actuellement inaccessibles. Veuillez réessayer plus tard.</string>
<string name="warning_some_networks_unreachable_title">Certains réseaux sont inaccessibles</string>
<string name="warning_some_token_balances_not_updated">Certains soldes de jetons n\'ont pas pu être mis à jour</string>
<string name="warning_stellar_token_trustline_not_enough_xlm">Pas assez de %s. Rechargez votre compte XLM pour associer ce jeton</string>
<string name="warning_testnet_card_message">Il s\'agit d\'une carte Testnet. Elle ne peut pas traiter les transactions et ne doit être utilisée qu\'à des fins de test et de développement.</string>
<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="wc_alert_audit_malicious_domain">Domaine malveillant</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_connections">Connexions</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>

View file

@ -4,6 +4,9 @@
<string name="access_code_alert_skip_ok">とにかくスキップ</string>
<string name="access_code_alert_skip_title">アクセスコードが設定されていません</string>
<string name="access_code_check_title">アクセスコードを入力</string>
<string name="access_code_check_warining_delete">アクセスコードが間違っています。あと%s回間違えるとホットウォレットが削除されます。</string>
<string name="access_code_check_warining_lock">アクセスコードが間違っています。あと%s回入力エラーが発生するとアプリがロックされます。</string>
<string name="access_code_check_warining_wait">アクセスコードが間違っています。 \n %s秒待ってから再試行してください。</string>
<string name="access_code_confirm_description">続行するには、以前に入力したコードを確認してください</string>
<string name="access_code_confirm_title">アクセスコードを再入力</string>
<string name="access_code_create_description">ウォレットのロックを解除するには、 %s桁のアクセスコードを設定します。</string>
@ -655,7 +658,7 @@
<string name="onboarding_button_continue_wallet">ウォレットへ進む</string>
<string name="onboarding_button_finalize_backup">バックアップを完了する</string>
<string name="onboarding_button_receive_crypto">暗号資産を受け取る</string>
<string name="onboarding_button_scan_origin_card">プライマリカードをスキャン</string>
<string name="onboarding_button_scan_origin_card">メインカードまたはリングをスキャン</string>
<string name="onboarding_button_skip_backup">スキップする</string>
<string name="onboarding_button_what_does_it_mean">どのように機能しますか?</string>
<string name="onboarding_create_wallet_body">すべての秘密鍵をカードまたはリング内で生成し、安全なウォレットを作成しましょう</string>
@ -706,7 +709,7 @@
<string name="onboarding_title_no_backup_cards">バックアップデバイスなし</string>
<string name="onboarding_title_notifications">通知</string>
<string name="onboarding_title_one_backup_card">バックアップデバイスが1つ追加されました</string>
<string name="onboarding_title_scan_origin_card">カードを準備してください</string>
<string name="onboarding_title_scan_origin_card">カードまたはリングを用意してください</string>
<string name="onboarding_title_two_backup_cards">バックアップデバイス2つが追加されました</string>
<string name="onboarding_top_up_body">始めるには、ウォレットに任意の金額を入金するだけです</string>
<string name="onboarding_top_up_body_no_account_error">始めるには、ウォレットに%1$s %2$s以上入金するだけです</string>
@ -924,6 +927,7 @@
<string name="send_validation_invalid_amount">無効な金額</string>
<string name="send_validation_invalid_fee">手数料が残高を超えています</string>
<string name="send_validation_invalid_total">合計金額が残高を超えています</string>
<string name="send_with_swap_confirm_title">スワップして送信</string>
<string name="send_with_swap_notification_text">トークンを送信すれば、送信中に変換されます。受信者は必要なものをシームレスに受け取ります。</string>
<string name="send_with_swap_recipient_amount_text">受信者に送信されます</string>
<string name="send_with_swap_recipient_amount_title">受取金額</string>
@ -1296,8 +1300,8 @@
<string name="warning_express_approval_in_progress_message">スワップ承認は現在進行中で、まもなく完了する予定です。</string>
<string name="warning_express_approval_in_progress_title">承認が進行中</string>
<string name="warning_express_dust_message">最低のスワップ金額は%1$s です。スワップ後の残金が%2$s を下回らないようにしてください。</string>
<string name="warning_express_no_exchangeable_coins_description">あなたのリストには、交換可能な %s トークンがありません</string>
<string name="warning_express_no_exchangeable_coins_title">スワップ可能なトークンがありません</string>
<string name="warning_express_no_exchangeable_coins_description">ポートフォリオ内に%sとスワップ可能なトークンがありません。交換を有効にするには、別のトークンを追加してください</string>
<string name="warning_express_no_exchangeable_coins_title">互換性のあるトークンが追加されていません</string>
<string name="warning_express_not_enough_fee_for_token_tx_description">取引を行うには、 %1$s %2$sを入金する必要があります。</string>
<string name="warning_express_not_enough_fee_for_token_tx_title">%s 手数料を支払えません</string>
<string name="warning_express_notification_invalid_reserve_amount_title">受け取る金額は、 %s 以上である必要があります。</string>
@ -1375,6 +1379,8 @@
<string name="wc_alert_unknown_error_description">エラーコード: %s 。問題が解決しない場合は、お気軽にサポートまでお問い合わせください。</string>
<string name="wc_alert_unknown_error_description_no_error_code">問題が解決しない場合は、お気軽にサポートまでお問い合わせください。</string>
<string name="wc_alert_unknown_error_title">不明なエラーが発生しました</string>
<string name="wc_alert_unsupported_dapps_description">Tangemウォレットは、現在%sをサポートしていません。</string>
<string name="wc_alert_unsupported_dapps_title">サポートされていないdApp</string>
<string name="wc_alert_unsupported_method_description">エラーコード: 8 005。問題が解決しない場合は、お気軽にサポートまでお問い合わせください。</string>
<string name="wc_alert_unsupported_method_title">不明なエラーが発生しました</string>
<string name="wc_alert_unsupported_networks_description">Tangemは現在%sで必要なネットワークをサポートしていません。</string>

View file

@ -620,7 +620,7 @@
<string name="onboarding_button_continue_wallet">Перейти к моему кошельку</string>
<string name="onboarding_button_finalize_backup">Завершение бэкапа</string>
<string name="onboarding_button_receive_crypto">Получить криптовалюту</string>
<string name="onboarding_button_scan_origin_card">Сканировать основную карту</string>
<string name="onboarding_button_scan_origin_card">Сканировать основную карту или кольцо</string>
<string name="onboarding_button_skip_backup">Пропустить</string>
<string name="onboarding_button_what_does_it_mean">Как это работает?</string>
<string name="onboarding_create_wallet_body">Давайте сгенерируем все ключи на вашей карте или кольце и создадим безопасный кошелек</string>
@ -676,7 +676,7 @@
<string name="onboarding_title_no_backup_cards">Нет резервных устройств</string>
<string name="onboarding_title_notifications">Уведомления</string>
<string name="onboarding_title_one_backup_card">Добавлено одно резервное устройство</string>
<string name="onboarding_title_scan_origin_card">Подготовьте свою карту</string>
<string name="onboarding_title_scan_origin_card">Подготовьте свою карту или кольцо</string>
<string name="onboarding_title_two_backup_cards">Добавлены два резервных девайса</string>
<string name="onboarding_top_up_body">Пополните кошелек на любую сумму, чтобы начать пользоваться картой</string>
<string name="onboarding_top_up_body_no_account_error">Пополните кошелек более чем на %1$s %2$s, чтобы начать пользоваться картой</string>
@ -723,7 +723,12 @@
<string name="organize_tokens_title">Упорядочить токены</string>
<string name="organize_tokens_ungroup">Список</string>
<string name="push_notifications_more_info">Подробнее</string>
<string name="push_notifications_permission_alert_description">Вы можете включить нотификации в настройках</string>
<string name="push_notifications_permission_alert_negative_button">Включить позже</string>
<string name="push_notifications_permission_alert_positive_button">Настройки</string>
<string name="push_notifications_permission_alert_title">Подключить нотификации</string>
<string name="push_transactions_notifications_description">Получайте уведомления о входящих транзакциях в поддерживаемых сетях.</string>
<string name="push_transactions_notifications_title">Уведомления о транзакциях</string>
<string name="qr_scanner_camera_denied_gallery_button">Выбрать из галереи</string>
<string name="qr_scanner_camera_denied_settings_button">Настройки</string>
<string name="qr_scanner_camera_denied_text">Вы не предоставили доступ к вашей камере</string>
@ -1119,6 +1124,7 @@
<string name="unlock_wallet_description_full">Используйте %s или отсканируйте карту/кольцо, чтобы получить доступ к своему кошельку</string>
<string name="unsupported_wc_version">Соединение не удалось: это dApp использует Wallet Connect версии 1.0, которая не поддерживается. Убедитесь, что dApp поддерживает Wallet Connect версии 2.0 для успешного подключения.</string>
<string name="user_push_notification_agreement_argument_one">Будьте в курсе новых функций и новостей</string>
<string name="user_push_notification_agreement_argument_three">Получайте уведомления о входящих транзакциях</string>
<string name="user_push_notification_agreement_argument_two">Узнавайте первым о новых акциях</string>
<string name="user_push_notification_agreement_header">Хотите использовать Push-уведомления?</string>
<string name="user_wallet_list_add_button">Добавить новый кошелек</string>
@ -1174,6 +1180,7 @@
<string name="wallet_promo_banner_button_title">Получить с 10% скидкой</string>
<string name="wallet_promo_banner_description">Получите доступ к более чем 13 000 криптовалют. Покупайте, продавайте, обменивайте и стейкайте в один клик. Свяжите до трех карт для резервного копирования. </string>
<string name="wallet_promo_banner_title">Откройте Tangem Wallet</string>
<string name="wallet_settings_push_notifications_description">Получайте уведомления о входящих транзакциях в кошельке и обновлениях Tangem.</string>
<string name="wallet_settings_push_notifications_title">Уведомления о транзакциях</string>
<string name="wallet_settings_title">Настройки кошелька</string>
<string name="wallet_title">Tangem</string>
@ -1202,7 +1209,7 @@
<string name="warning_express_approval_in_progress_message">Разрешение обмена в процессе и будет скоро завершено</string>
<string name="warning_express_approval_in_progress_title">Разрешение в процессе</string>
<string name="warning_express_dust_message">Минимальная сумма обмена - %1$s. Пожалуйста, убедитесь, что остаток после обмена также не будет меньше %2$s.</string>
<string name="warning_express_no_exchangeable_coins_description">У вас в списке нет монет доступных для обмена с %s</string>
<string name="warning_express_no_exchangeable_coins_description">У вас нет токенов в портфеле, на которые можно обменять %s. Пожалуйста, добавьте другой токен, чтобы выполнить обмен.</string>
<string name="warning_express_no_exchangeable_coins_title">Нет доступных для обмена токенов</string>
<string name="warning_express_not_enough_fee_for_token_tx_description">Чтобы совершить транзакцию, вам необходимо внести немного %1$s %2$s</string>
<string name="warning_express_not_enough_fee_for_token_tx_title">Невозможно покрыть комиссию %s</string>
@ -1268,6 +1275,7 @@
<string name="warning_token_trustline_subtitle">Согласно правилам сети, чтобы пополнить этот токен, необходимо сначала открыть Trustline — это позволит вашему кошельку принимать и хранить этот актив.</string>
<string name="warning_token_trustline_title">Откройте Trustline</string>
<string name="wc_alert_session_disconnected_description">Пожалуйста, вернитесь в браузер и выполните повторное подключение через WalletConnect.</string>
<string name="wc_alert_unsupported_dapps_title">Неподдерживаемый dApp</string>
<string name="wc_alert_wrong_card_description">Выбрана не верная карта или кольцо</string>
<string name="wc_common_address">Адрес</string>
<string name="wc_common_connect">Подключение</string>

View file

@ -316,6 +316,7 @@
<string name="express_exchange_status_receiving">Очікуємо на депозит</string>
<string name="express_exchange_status_receiving_active">Очікуємо на депозит...</string>
<string name="express_exchange_status_refunded">Повернено</string>
<string name="express_exchange_status_refunding">Повернення коштів</string>
<string name="express_exchange_status_sending">Надсилаємо вам</string>
<string name="express_exchange_status_sending_active">Надсилаємо вам...</string>
<string name="express_exchange_status_sent">Надіслано</string>
@ -370,6 +371,7 @@
<string name="give_permission_swap_subtitle" formatted="false">Щоб продовжити, вам потрібно дозволити смарт-контракту %1s використовувати ваш %2s</string>
<string name="give_permission_title">Надати дозвіл</string>
<string name="give_permission_unlimited">Необмежено</string>
<string name="home_button_create_new_wallet">Створити новий гаманець</string>
<string name="home_button_order">Купити</string>
<string name="home_button_scan">Сканувати</string>
<string name="hot_crypto_add_token_subtitle">в %s</string>
@ -572,10 +574,11 @@
<string name="nft_receive_unsupported_types_description">cNFT і pNFT наразі не підтримуються. Будь ласка, не надсилайте їх на свій гаманець.</string>
<string name="nft_send">Надіслати NFT</string>
<string name="nft_traits_title">Риси</string>
<string name="nft_untitled_collection">Безіменна колекція</string>
<string name="nft_wallet_count">NFTs в %2$d колекціях%1$d</string>
<plurals name="nft_wallet_count_android">
<item quantity="one">%1$d NFT в %2$d колекціi</item>
<item quantity="few"></item>
<item quantity="few">%1$d NFTs в %2$d колекціях</item>
<item quantity="many"></item>
<item quantity="other">%1$d NFTs в %2$d колекціях</item>
</plurals>
@ -720,6 +723,7 @@
<string name="organize_tokens_ungroup">Список</string>
<string name="push_notifications_more_info">Більше</string>
<string name="push_notifications_permission_alert_title">Увімкнути сповіщення</string>
<string name="push_transactions_notifications_description">Отримуйте сповіщення про вхідні транзакції в підтримуваних мережах.</string>
<string name="qr_scanner_camera_denied_gallery_button">Виберіть з галереї</string>
<string name="qr_scanner_camera_denied_settings_button">Налаштування</string>
<string name="qr_scanner_camera_denied_text">Ви не надали доступ до своєї камери</string>
@ -829,6 +833,9 @@
<string name="send_max_fee">Комісія може сягати до</string>
<string name="send_memo_destination_tag_error">Недопустимий Memo</string>
<string name="send_network_fee_warning_title">Покриття мережевої комісії</string>
<string name="send_nonce">Nonce</string>
<string name="send_nonce_footer">Унікальний номер транзакції. Змініть його, щоб пришвидшити відправку або скасувати завислу операцію.</string>
<string name="send_nonce_hint">Введіть nonce…</string>
<string name="send_notification_exceed_balance_text">Недостатньо коштів для здійснення переказу, оскільки загальна сума комісії та переказу перевищує наявний баланс</string>
<string name="send_notification_exceed_balance_title">Сума перевищує баланс</string>
<string name="send_notification_existential_deposit_text">Для збереження вашого акаунту у блокчейні та захисту від можливих ризиків необхідний баланс не менше %s. Ця сума залишиться на вашому рахунку та не може бути знята.</string>
@ -837,6 +844,7 @@
<string name="send_notification_fee_too_high_title">Встановлена комісія завелика</string>
<string name="send_notification_high_fee_text">Через особливості мережі %1$s комісія за переказ всього балансу вища. Щоб зменшити комісію, Ви можете залишити %2$s.</string>
<string name="send_notification_high_fee_title">Підвищена комісія</string>
<string name="send_notification_invalid_amount_rent_destination">Рахунок одержувача не активований. Мінімальна сума переказу повинна бути не менша балансу, необхідного для покриття арендної плати: %1$s.</string>
<string name="send_notification_invalid_amount_rent_fee">Баланс вашого рахунку не може бути меншим за орендну плату. Будь ласка, залиште на рахунку не менше %1$s або виведіть всі кошти.</string>
<string name="send_notification_invalid_amount_text">Включена комісія перевищує суму переказу, що призводить до від’ємного значення</string>
<string name="send_notification_invalid_amount_title">Недопустима сума</string>
@ -1095,6 +1103,8 @@
<string name="transaction_history_transaction_validator">валідатор: %s</string>
<string name="transfer_min_amount_error">Мінімум %s</string>
<string name="transfer_notification_invalid_minimum_transaction_amount_text">Мінімальна сума транзакції становить %1$s.</string>
<string name="tron_will_be_send_token_fee_description">Комісії мережі Tron для популярних токенів можуть бути вищими. Стейкінг TRX може допомогти знизити транзакційні витрати.</string>
<string name="tron_will_be_send_token_fee_title">Заощаджуйте на Tron комісіях</string>
<string name="try_to_load_data_again_button_title">Спробуйте знову</string>
<string name="twin_error_same_card">Ви відсканували одну й ту саму картку. Для створення twin-гаманця вам потрібно відсканувати картку з номером %d</string>
<string name="twin_error_wrong_twin">Ви відсканували не ту twin-картку. Будь ласка, спробуйте відсканувати іншу</string>
@ -1111,6 +1121,7 @@
<string name="unlock_wallet_description_full">Використовуйте %s або відскануйте картку/кільце, щоб отримати доступ до свого гаманця</string>
<string name="unsupported_wc_version">Не вдалося встановити з\'єднання: Цей dApp використовує Wallet Connect версії 1.0, яка не підтримується. Будь ласка, переконайтеся, що dApp підтримує Wallet Connect версії 2.0 для успішного підключення.</string>
<string name="user_push_notification_agreement_argument_one">Будьте в курсі останніх функцій та новин</string>
<string name="user_push_notification_agreement_argument_three">Отримуйте сповіщення про вхідні транзакції</string>
<string name="user_push_notification_agreement_argument_two">Дізнавайтеся першими про нові акції</string>
<string name="user_push_notification_agreement_header">Бажаєте використовувати Push-повідомлення?</string>
<string name="user_wallet_list_add_button">Додати новий гаманець</string>
@ -1122,6 +1133,7 @@
<string name="user_wallet_list_rename_popup_title">Перейменування гаманця</string>
<string name="user_wallet_list_unlock_all">Розблокувати все</string>
<string name="user_wallet_list_unlock_all_with">Розблокувати все з %s</string>
<string name="visa_onboarding_pin_not_accepted">PIN-код не прийнятий. Спробуйте ще раз або введіть інший код.</string>
<string name="wallet_balance_blockchain_unreachable_try_later">Блокчейн недоступний. Спробуйте пізніше</string>
<string name="wallet_balance_missing_derivation">Відскануйте картку або кільце</string>
<string name="wallet_been_activated_message">Цей гаманець вже був активований раніше.\nЯкщо це було зроблено не вами, зверніться до служби підтримки.\nTangem ніколи не продає гаманці разом з попередньо згенерованим кодом доступу.</string>
@ -1165,6 +1177,7 @@
<string name="wallet_promo_banner_button_title">Отримати з 10% знижкою</string>
<string name="wallet_promo_banner_description">Доступ до 13,000+ криптовалют. Купуйте, продавайте, обмінюйте та стейкайте одним дотиком.\nЗєднайте до трьох карток для бекапу.</string>
<string name="wallet_promo_banner_title">Відкрийте Tangem Wallet</string>
<string name="wallet_settings_push_notifications_description">Отримуйте сповіщення про вхідні транзакції в гаманці та оновлення Tangem.</string>
<string name="wallet_settings_push_notifications_title">Сповіщення про транзакції</string>
<string name="wallet_settings_title">Налаштування гаманця</string>
<string name="wallet_title">Tangem</string>
@ -1193,7 +1206,7 @@
<string name="warning_express_approval_in_progress_message">Дозвіл обміну триває і незабаром буде завершено</string>
<string name="warning_express_approval_in_progress_title">Затвердження в процесі</string>
<string name="warning_express_dust_message">Мінімальна сума обміну становить - %1$s. Будь ласка, переконайтеся, що залишок на рахунку після обміну буде не менше за %2$s.</string>
<string name="warning_express_no_exchangeable_coins_description">У вашому списку немає доступних монет для обміну %s</string>
<string name="warning_express_no_exchangeable_coins_description">У вашому портфелі немає токенів, на які можна обміняти %s. Будь ласка, додайте інший токен, щоб виконати обмін.</string>
<string name="warning_express_no_exchangeable_coins_title">Немає доступних токенів для обміну</string>
<string name="warning_express_not_enough_fee_for_token_tx_description">Щоб здійснити транзакцію, вам потрібно внести трохи %1$s %2$s</string>
<string name="warning_express_not_enough_fee_for_token_tx_title">Неможливо покрити комісію %s</string>
@ -1257,8 +1270,23 @@
<string name="warning_testnet_card_message">Це картка Testnet. Вона не може обробляти транзакції і повинна використовуватися лише для тестування та розробки.</string>
<string name="warning_testnet_card_title">Лише для цілей тестування</string>
<string name="warning_token_balance_not_updated">Баланс може бути застарілим. Оновіть сторінку.</string>
<string name="warning_token_trustline_button_title">Відкрити Trustline</string>
<string name="warning_token_trustline_subtitle">Відповідно до правил мережі, щоб поповнити цей токен, спочатку потрібно відкрити Trustline — це дозволить вашому гаманцю приймати та зберігати цей актив.</string>
<string name="warning_token_trustline_title">Відкрийте Trustline</string>
<string name="wc_alert_session_disconnected_description">Будь ласка, поверніться до браузеру і повторно підключіться через WalletConnect.</string>
<string name="wc_alert_wrong_card_description">Обрана не вірна картка або кільце</string>
<string name="wc_common_address">Адреса</string>
<string name="wc_common_connect">Підключення</string>
<string name="wc_connection_reqeust_can_view_balance">Переглянути баланс гаманця та активність</string>
<string name="wc_connection_request">Запит на підключення</string>
<string name="wc_contents">Вміст</string>
<string name="wc_copy_data_button_text">Копіювати дані</string>
<string name="wc_request_from">Запит від</string>
<string name="wc_signature_type">Тип підпису</string>
<string name="wc_transaction_info_to_title">До</string>
<string name="wc_transaction_request">Запит транзакції</string>
<string name="wc_transaction_request_title">Запит транзакції</string>
<string name="wc_wallet_connect">Підключення гаманця</string>
<string name="welcome_interrupted_backup_alert_discard">Відмовитися</string>
<string name="welcome_interrupted_backup_alert_message">Ви не завершили резервне копіювання. Бажаєте продовжити?</string>
<string name="welcome_interrupted_backup_alert_resume">Так, поновити</string>

View file

@ -4,6 +4,9 @@
<string name="access_code_alert_skip_ok">Skip anyway</string>
<string name="access_code_alert_skip_title">Access Code not set</string>
<string name="access_code_check_title">Enter Access Code</string>
<string name="access_code_check_warining_delete">Wrong access code. Your hot wallet will be deleted after %s more incorrect attempts.</string>
<string name="access_code_check_warining_lock">Wrong access code. App will be locked with %s more input errors</string>
<string name="access_code_check_warining_wait">Wrong access code.\nPlease wait %s seconds and try again.</string>
<string name="access_code_confirm_description">Confirm your previously entered code to continue</string>
<string name="access_code_confirm_title">Re-enter Access Code</string>
<string name="access_code_create_description">Set a %s-digit Access Code to unlock your wallet.</string>
@ -58,6 +61,9 @@
<string name="app_settings_theme_mode_system">System default</string>
<string name="app_settings_theme_selector_title">Theme</string>
<string name="app_settings_title">App settings</string>
<string name="auth_info_add_wallet_title">Add Wallet</string>
<string name="auth_info_subtitle">Select a wallet to log in</string>
<string name="auth_info_title">Welcome back!</string>
<string name="backup_complete_description">You successfully backed up your wallet.</string>
<string name="backup_complete_seed_description">These words cant be recovered if lost. Make sure to keep it somewhere secure.</string>
<string name="backup_complete_title">Backup Completed</string>
@ -432,6 +438,7 @@
<string name="hw_create_seed_description">Stay up to date with the latest features and news</string>
<string name="hw_create_seed_title">Seed phrase backup</string>
<string name="hw_create_title">Create Mobile Wallet</string>
<string name="hw_mobile_wallet">Mobile Wallet</string>
<string name="information_generated_with_ai">This information was generated with AI.\nTap here, if you find any errors.</string>
<string name="initial_message_change_access_code_body">To change the access code tap the card or ring as shown above and do not remove until the end of the operation</string>
<string name="initial_message_change_passcode_body">To change the passcode tap the card as shown above and do not remove until the end of the operation</string>
@ -666,7 +673,7 @@
<string name="onboarding_button_continue_wallet">Continue to my wallet</string>
<string name="onboarding_button_finalize_backup">Finalize backup</string>
<string name="onboarding_button_receive_crypto">Receive crypto</string>
<string name="onboarding_button_scan_origin_card">Scan primary card</string>
<string name="onboarding_button_scan_origin_card">Scan primary card or ring</string>
<string name="onboarding_button_skip_backup">Skip for later</string>
<string name="onboarding_button_what_does_it_mean">How does it work?</string>
<string name="onboarding_create_wallet_body">Let\'s generate all the keys on your card or ring and create a secure wallet</string>
@ -719,7 +726,7 @@
<string name="onboarding_title_no_backup_cards">No backup devices</string>
<string name="onboarding_title_notifications">Notifications</string>
<string name="onboarding_title_one_backup_card">One backup device added</string>
<string name="onboarding_title_scan_origin_card">Prepare your card</string>
<string name="onboarding_title_scan_origin_card">Prepare your card or ring</string>
<string name="onboarding_title_two_backup_cards">Two backup devices added</string>
<string name="onboarding_top_up_body">To get started, simply top up the wallet with any amount</string>
<string name="onboarding_top_up_body_no_account_error">To get started, simply top up the wallet with more than %1$s %2$s</string>
@ -1360,8 +1367,8 @@
<string name="warning_express_approval_in_progress_message">Swap approval is underway and will be completed shortly</string>
<string name="warning_express_approval_in_progress_title">Approval in progress</string>
<string name="warning_express_dust_message">The minimum swapping amount is %1$s. Please ensure that the remaining balance after the swap will not be less than %2$s.</string>
<string name="warning_express_no_exchangeable_coins_description">You do not have any %s exchangeable coins in your list</string>
<string name="warning_express_no_exchangeable_coins_title">No available tokens to swap</string>
<string name="warning_express_no_exchangeable_coins_description">You dont have any tokens in your portfolio that %s can be swapped to. Please add another token to enable the exchange.</string>
<string name="warning_express_no_exchangeable_coins_title">No compatible tokens added</string>
<string name="warning_express_not_enough_fee_for_token_tx_description">To make a transaction you need to deposit some %1$s %2$s</string>
<string name="warning_express_not_enough_fee_for_token_tx_title">Unable to cover %s fee</string>
<string name="warning_express_notification_invalid_reserve_amount_title">The amount to receive must be at least %s</string>

View file

@ -13,4 +13,12 @@ import com.tangem.domain.swap.models.SwapDirection.Reverse
enum class SwapDirection {
Direct,
Reverse,
;
companion object {
inline fun <T> SwapDirection.withSwapDirection(onDirect: () -> T, onReverse: () -> T): T = when (this) {
Direct -> onDirect()
Reverse -> onReverse()
}
}
}

View file

@ -34,6 +34,9 @@ dependencies {
implementation(projects.common.ui)
implementation(projects.common.routing)
/** Libs */
implementation(projects.libs.crypto)
implementation(tangemDeps.blockchain) {
exclude(module = "joda-time")
}
@ -54,6 +57,9 @@ dependencies {
implementation(projects.domain.transaction.models)
implementation(projects.domain.transaction)
implementation(projects.domain.legacy)
implementation(projects.domain.balanceHiding.models)
implementation(projects.domain.balanceHiding)
implementation(projects.domain.settings)
/** Compose */
implementation(deps.compose.foundation)

View file

@ -0,0 +1,6 @@
package com.tangem.features.swap.v2.impl.common
internal object SwapUtils {
const val INCREASE_GAS_LIMIT_FOR_DEX = 112 // 12%
const val INCREASE_GAS_LIMIT_FOR_CEX = 105 // 5%
}

View file

@ -0,0 +1,24 @@
package com.tangem.features.swap.v2.impl.common.entity
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableList
@Immutable
internal sealed class ConfirmUM {
abstract val isPrimaryButtonEnabled: Boolean
data class Content(
override val isPrimaryButtonEnabled: Boolean = false,
val isTransactionInProcess: Boolean,
val showTapHelp: Boolean,
val sendingFooter: TextReference,
val notifications: ImmutableList<NotificationUM>,
) : ConfirmUM()
data object Empty : ConfirmUM() {
override val isPrimaryButtonEnabled: Boolean = false
}
}

View file

@ -0,0 +1,31 @@
package com.tangem.features.swap.v2.impl.sendviaswap
import com.tangem.core.decompose.navigation.Route
import com.tangem.features.send.v2.api.subcomponents.destination.DestinationRoute
import com.tangem.features.swap.v2.impl.amount.SwapAmountRoute
import kotlinx.serialization.Serializable
internal sealed class SendWithSwapRoute : Route {
abstract val isEditMode: Boolean
@Serializable
data class Amount(
override val isEditMode: Boolean,
) : SendWithSwapRoute(), SwapAmountRoute
@Serializable
data class Destination(
override val isEditMode: Boolean,
) : SendWithSwapRoute(), DestinationRoute
@Serializable
data object Confirm : SendWithSwapRoute() {
override val isEditMode: Boolean = false
}
@Serializable
data object Success : SendWithSwapRoute() {
override val isEditMode: Boolean = false
}
}

View file

@ -0,0 +1,162 @@
package com.tangem.features.swap.v2.impl.sendviaswap.confirm
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.swap.models.SwapDirection
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.send.v2.api.SendNotificationsComponent
import com.tangem.features.send.v2.api.entity.PredefinedValues
import com.tangem.features.send.v2.api.params.FeeSelectorParams
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationBlockComponent
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponentParams
import com.tangem.features.swap.v2.impl.amount.SwapAmountBlockComponent
import com.tangem.features.swap.v2.impl.amount.SwapAmountComponentParams
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
import com.tangem.features.swap.v2.impl.sendviaswap.SendWithSwapRoute
import com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.SendWithSwapConfirmModel
import com.tangem.features.swap.v2.impl.sendviaswap.confirm.ui.SendWithSwapConfirmContent
import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.*
import java.math.BigDecimal
internal class SendWithSwapConfirmComponent @AssistedInject constructor(
@Assisted private val appComponentContext: AppComponentContext,
@Assisted private val params: Params,
sendDestinationBlockComponent: SendDestinationBlockComponent.Factory,
feeSelectorBlockComponentFactory: FeeSelectorBlockComponent.Factory,
sendNotificationsComponentFactory: SendNotificationsComponent.Factory,
) : ComposableContentComponent, AppComponentContext by appComponentContext {
private val model: SendWithSwapConfirmModel = getOrCreateModel(params = params)
private val blockClickEnableFlow = MutableStateFlow(false)
private val amountBlockComponent = SwapAmountBlockComponent(
appComponentContext = child("sendWithSwapConfirmAmountBlock"),
params = SwapAmountComponentParams.AmountBlockParams(
amountUM = model.uiState.value.amountUM,
analyticsCategoryName = params.analyticsCategoryName,
userWallet = params.userWallet,
blockClickEnableFlow = blockClickEnableFlow.asStateFlow(),
primaryCryptoCurrencyStatusFlow = params.primaryCryptoCurrencyStatusFlow,
secondaryCryptoCurrency = model.secondaryCurrency,
isBalanceHidingFlow = params.isBalanceHidingFlow,
swapDirection = params.swapDirection,
),
onResult = model::onAmountResult,
onClick = model::showEditAmount,
)
private val sendDestinationBlockComponent = sendDestinationBlockComponent.create(
context = child("sendWithSwapConfirmDestinationBlock"),
params = SendDestinationComponentParams.DestinationBlockParams(
state = model.uiState.value.destinationUM,
analyticsCategoryName = params.analyticsCategoryName,
userWalletId = params.userWallet.walletId,
blockClickEnableFlow = blockClickEnableFlow.asStateFlow(),
cryptoCurrency = model.secondaryCurrency,
predefinedValues = PredefinedValues.Empty,
),
onResult = model::onDestinationResult,
onClick = model::showEditDestination,
)
private val feeSelectorBlockComponent = feeSelectorBlockComponentFactory.create(
context = child("sendWithSwapConfirmFeeBlock"),
params = FeeSelectorParams.FeeSelectorBlockParams(
state = model.uiState.value.feeSelectorUM,
onLoadFee = model::loadFee,
feeCryptoCurrencyStatus = model.primaryFeePaidCurrencyStatus,
cryptoCurrencyStatus = model.primaryCurrencyStatus,
suggestedFeeState = FeeSelectorParams.SuggestedFeeState.None,
feeDisplaySource = FeeSelectorParams.FeeDisplaySource.Screen,
),
onResult = model::onFeeResult,
)
private val sendNotificationsComponent = sendNotificationsComponentFactory.create(
context = appComponentContext.childByContext(child("sendWithSwapConfirmNotifications")),
params = SendNotificationsComponent.Params(
analyticsCategoryName = params.analyticsCategoryName,
userWalletId = params.userWallet.walletId,
cryptoCurrencyStatus = model.primaryCurrencyStatus,
feeCryptoCurrencyStatus = model.primaryFeePaidCurrencyStatus,
appCurrency = params.appCurrency,
notificationData = SendNotificationsComponent.Params.NotificationData(
// todo fill with data
destinationAddress = "",
memo = "",
amountValue = BigDecimal.ZERO,
reduceAmountBy = BigDecimal.ZERO,
isIgnoreReduce = false,
fee = null,
feeError = null,
),
),
)
init {
model.uiState.onEach { state ->
val confirmUM = state.confirmUM as? ConfirmUM.Content
blockClickEnableFlow.value = confirmUM?.isTransactionInProcess == false
}.launchIn(componentScope)
}
fun updateState(sendWithSwapUM: SendWithSwapUM) {
amountBlockComponent.updateState(sendWithSwapUM.amountUM)
sendDestinationBlockComponent.updateState(sendWithSwapUM.destinationUM)
feeSelectorBlockComponent.updateState(sendWithSwapUM.feeSelectorUM)
model.updateState(sendWithSwapUM)
}
@Composable
override fun Content(modifier: Modifier) {
val sendWithSwapUM by model.uiState.collectAsStateWithLifecycle()
val sendNotificationsUM by sendNotificationsComponent.state.collectAsStateWithLifecycle()
SendWithSwapConfirmContent(
sendWithSwapUM = sendWithSwapUM,
amountBlockComponent = amountBlockComponent,
sendDestinationBlockComponent = sendDestinationBlockComponent,
feeSelectorBlockComponent = feeSelectorBlockComponent,
sendNotificationsComponent = sendNotificationsComponent,
sendNotificationsUM = sendNotificationsUM,
modifier = modifier,
)
}
data class Params(
val sendWithSwapUM: SendWithSwapUM,
val analyticsCategoryName: String,
val userWallet: UserWallet,
val appCurrency: AppCurrency,
val currentRoute: Flow<SendWithSwapRoute>,
val swapDirection: SwapDirection,
val isBalanceHidingFlow: StateFlow<Boolean>,
val primaryCryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
val primaryFeePaidCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
val callback: ModelCallback,
)
@AssistedFactory
interface Factory {
fun create(appComponentContext: AppComponentContext, params: Params): SendWithSwapConfirmComponent
}
interface ModelCallback {
fun onResult(sendWithSwapUM: SendWithSwapUM)
}
}

View file

@ -0,0 +1,20 @@
package com.tangem.features.swap.v2.impl.sendviaswap.confirm.di
import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.SendWithSwapConfirmModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(ModelComponent::class)
internal interface SendWithSwapConfirmModule {
@Binds
@IntoMap
@ClassKey(SendWithSwapConfirmModel::class)
fun bindsSendWithSwapConfirmModel(impl: SendWithSwapConfirmModel): Model
}

View file

@ -0,0 +1,239 @@
package com.tangem.features.swap.v2.impl.sendviaswap.confirm.model
import arrow.core.Either
import arrow.core.getOrElse
import arrow.core.left
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.navigationButtons.NavigationButton
import com.tangem.common.ui.navigationButtons.NavigationUM
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.express.models.ExpressProviderType
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.settings.IsSendTapHelpEnabledUseCase
import com.tangem.domain.swap.models.SwapDirection.Companion.withSwapDirection
import com.tangem.domain.tokens.IsAmountSubtractAvailableUseCase
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.usecase.EstimateFeeUseCase
import com.tangem.features.send.v2.api.SendNotificationsComponent.Params.NotificationData
import com.tangem.features.send.v2.api.callbacks.FeeSelectorModelCallback
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
import com.tangem.features.send.v2.api.subcomponents.notifications.SendNotificationsUpdateTrigger
import com.tangem.features.swap.v2.impl.R
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.common.SwapUtils.INCREASE_GAS_LIMIT_FOR_CEX
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.features.swap.v2.impl.sendviaswap.SendWithSwapRoute
import com.tangem.features.swap.v2.impl.sendviaswap.confirm.SendWithSwapConfirmComponent
import com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers.SendWithSwapConfirmInitialStateTransformer
import com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers.SendWithSwapConfirmationNotificationsTransformer
import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM
import com.tangem.lib.crypto.BlockchainFeeUtils.patchTransactionFeeForSwap
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.orZero
import jakarta.inject.Inject
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import com.tangem.utils.transformer.update as transformerUpdate
@Suppress("LongParameterList")
@ModelScoped
internal class SendWithSwapConfirmModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val router: Router,
private val isSendTapHelpEnabledUseCase: IsSendTapHelpEnabledUseCase,
private val estimateFeeUseCase: EstimateFeeUseCase,
private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase,
private val sendNotificationsUpdateTrigger: SendNotificationsUpdateTrigger,
paramsContainer: ParamsContainer,
) : Model(), FeeSelectorModelCallback {
private val params: SendWithSwapConfirmComponent.Params = paramsContainer.require()
val uiState: StateFlow<SendWithSwapUM>
field = MutableStateFlow(params.sendWithSwapUM)
val primaryCurrencyStatus: CryptoCurrencyStatus = params.primaryCryptoCurrencyStatusFlow.value
val primaryFeePaidCurrencyStatus: CryptoCurrencyStatus = params.primaryFeePaidCurrencyStatusFlow.value
private val amountUM = uiState.value.amountUM as? SwapAmountUM.Content
val secondaryCurrency: CryptoCurrency = requireNotNull(amountUM?.secondaryCryptoCurrencyStatus?.currency) {
"Crypto currency must not be null"
}
private var isAmountSubtractAvailable = false
init {
initAmountSubtractAvailability()
configConfirmNavigation()
initialState()
}
override fun onFeeResult(feeSelectorUM: FeeSelectorUM) {
uiState.update { it.copy(feeSelectorUM = feeSelectorUM) }
updateConfirmNotifications()
}
fun onAmountResult(amountUM: SwapAmountUM) {
uiState.update { it.copy(amountUM = amountUM) }
updateConfirmNotifications()
}
fun onDestinationResult(destinationUM: DestinationUM) {
uiState.update { it.copy(destinationUM = destinationUM) }
updateConfirmNotifications()
}
fun updateState(sendWithSwapUM: SendWithSwapUM) {
uiState.value = sendWithSwapUM
}
fun showEditAmount() {
router.push(SendWithSwapRoute.Amount(isEditMode = true))
}
fun showEditDestination() {
router.push(SendWithSwapRoute.Destination(isEditMode = true))
}
suspend fun loadFee(): Either<GetFeeError, TransactionFee> {
val defaultError = GetFeeError.UnknownError.left()
val quote = amountUM?.selectedQuote as? SwapQuoteUM.Content ?: return defaultError
val amountUM = uiState.value.amountUM as? SwapAmountUM.Content ?: return defaultError
val amountField = amountUM.swapDirection.withSwapDirection(
onDirect = { amountUM.primaryAmount.amountField },
onReverse = { amountUM.secondaryAmount.amountField },
) as? AmountState.Data ?: return defaultError
val amountValue = amountField.amountTextField.cryptoAmount.value ?: return defaultError
return when (val providerType = quote.provider.type) {
ExpressProviderType.CEX -> {
estimateFeeUseCase(
amount = amountValue,
userWallet = params.userWallet,
cryptoCurrency = primaryCurrencyStatus.currency,
).map {
it.patchTransactionFeeForSwap(INCREASE_GAS_LIMIT_FOR_CEX)
}
}
ExpressProviderType.DEX,
ExpressProviderType.DEX_BRIDGE,
-> {
// todo send with swap
GetFeeError.UnknownError.left()
}
ExpressProviderType.ONRAMP,
-> GetFeeError.DataError(
cause = IllegalStateException("Provider $providerType is not supported in Send With Swap"),
).left()
}
}
private fun onSendClick() {
// todo swap send tx
}
private fun initAmountSubtractAvailability() {
modelScope.launch {
isAmountSubtractAvailable =
isAmountSubtractAvailableUseCase(
params.userWallet.walletId,
primaryCurrencyStatus.currency,
).getOrElse { false }
}
}
private fun initialState() {
val confirmUM = uiState.value.confirmUM
modelScope.launch {
val isShowTapHelp = isSendTapHelpEnabledUseCase().getOrElse { false }
if (confirmUM is ConfirmUM.Empty) {
uiState.update {
it.copy(
confirmUM = SendWithSwapConfirmInitialStateTransformer(
isShowTapHelp = isShowTapHelp,
).transform(uiState.value.confirmUM),
)
}
updateConfirmNotifications()
}
}
}
private fun updateConfirmNotifications() {
val amountUM = uiState.value.amountUM as? SwapAmountUM.Content ?: return
val destinationUM = uiState.value.destinationUM as? DestinationUM.Content ?: return
val feeSelectorUMContent = uiState.value.feeSelectorUM as? FeeSelectorUM.Content
val feeSelectorUMError = uiState.value.feeSelectorUM as? FeeSelectorUM.Error
val amountField = amountUM.swapDirection.withSwapDirection(
onDirect = { amountUM.primaryAmount.amountField },
onReverse = { amountUM.secondaryAmount.amountField },
) as? AmountState.Data ?: return
val enteredDestination = destinationUM.addressTextField.actualAddress
modelScope.launch {
sendNotificationsUpdateTrigger.triggerUpdate(
data = NotificationData(
destinationAddress = enteredDestination,
memo = null,
amountValue = amountField.amountTextField.cryptoAmount.value.orZero(),
reduceAmountBy = amountField.reduceAmountBy.orZero(),
isIgnoreReduce = amountField.isIgnoreReduce,
fee = feeSelectorUMContent?.selectedFeeItem?.fee,
feeError = feeSelectorUMError?.error,
),
)
uiState.transformerUpdate(
SendWithSwapConfirmationNotificationsTransformer(),
)
}
}
private fun configConfirmNavigation() {
combine(
flow = uiState,
flow2 = params.currentRoute,
transform = { state, route -> state to route },
).filter {
it.second is SendWithSwapRoute.Confirm
}.onEach { (state, _) ->
val confirmUM = state.confirmUM
params.callback.onResult(
state.copy(
navigationUM = NavigationUM.Content(
title = resourceReference(id = R.string.send_with_swap_confirm_title),
subtitle = null,
backIconRes = R.drawable.ic_back_24,
backIconClick = router::pop,
primaryButton = NavigationButton(
textReference = resourceReference(R.string.common_send),
iconRes = R.drawable.ic_tangem_24,
isEnabled = confirmUM.isPrimaryButtonEnabled,
onClick = {
when (confirmUM) {
is ConfirmUM.Content -> if (confirmUM.isTransactionInProcess) {
return@NavigationButton
} else {
onSendClick()
}
else -> return@NavigationButton
}
},
),
),
),
)
}.launchIn(modelScope)
}
}

View file

@ -0,0 +1,20 @@
package com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers
import com.tangem.core.ui.extensions.TextReference
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.persistentListOf
internal class SendWithSwapConfirmInitialStateTransformer(
private val isShowTapHelp: Boolean,
) : Transformer<ConfirmUM> {
override fun transform(prevState: ConfirmUM): ConfirmUM {
return ConfirmUM.Content(
isPrimaryButtonEnabled = false,
isTransactionInProcess = false,
showTapHelp = isShowTapHelp,
sendingFooter = TextReference.EMPTY,
notifications = persistentListOf(),
)
}
}

View file

@ -0,0 +1,106 @@
package com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.swap.models.SwapDirection.Companion.withSwapDirection
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.send.v2.api.subcomponents.feeSelector.utils.FeeCalculationUtils.checkIfCustomFeeTooHigh
import com.tangem.features.send.v2.api.subcomponents.feeSelector.utils.FeeCalculationUtils.checkIfCustomFeeTooLow
import com.tangem.features.swap.v2.impl.R
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.toPersistentList
internal class SendWithSwapConfirmationNotificationsTransformer : Transformer<SendWithSwapUM> {
override fun transform(prevState: SendWithSwapUM): SendWithSwapUM {
val confirmUM = prevState.confirmUM as? ConfirmUM.Content ?: return prevState
val feeSelectorUM = prevState.feeSelectorUM as? FeeSelectorUM.Content ?: return prevState
return prevState.copy(
confirmUM = confirmUM.copy(
sendingFooter = getSendingFooterText(feeSelectorUM, prevState.amountUM),
notifications = buildList {
addTooHighNotification(feeSelectorUM = feeSelectorUM)
addTooLowNotification(feeSelectorUM = feeSelectorUM)
}.toPersistentList(),
),
)
}
private fun MutableList<NotificationUM>.addTooLowNotification(feeSelectorUM: FeeSelectorUM.Content) {
if (checkIfCustomFeeTooLow(feeSelectorUM = feeSelectorUM)) {
add(NotificationUM.Warning.FeeTooLow)
}
}
private fun MutableList<NotificationUM>.addTooHighNotification(feeSelectorUM: FeeSelectorUM.Content) {
val (isFeeTooHigh, diff) = checkIfCustomFeeTooHigh(feeSelectorUM = feeSelectorUM)
if (isFeeTooHigh) {
add(NotificationUM.Warning.TooHigh(diff))
}
}
private fun getSendingFooterText(feeSelectorUM: FeeSelectorUM.Content, swapAmountUM: SwapAmountUM): TextReference {
val amountUM = swapAmountUM.swapDirection.withSwapDirection(
onDirect = { swapAmountUM.primaryAmount.amountField },
onReverse = { swapAmountUM.secondaryAmount.amountField },
) as? AmountState.Data
val feeItem = feeSelectorUM.selectedFeeItem
val feeFiatRateUM = feeSelectorUM.feeFiatRateUM
val appCurrency = feeFiatRateUM?.appCurrency
if (amountUM == null || appCurrency == null) return TextReference.EMPTY
val fiatAmountValue = amountUM.amountTextField.fiatAmount.value
val fiatFeeValue = feeItem.fee.amount.value?.multiply(feeFiatRateUM.rate)
val fiatSendingValue = if (feeSelectorUM.feeExtraInfo.isFeeConvertibleToFiat) {
fiatFeeValue?.let { fiatAmountValue?.plus(it) }
} else {
fiatAmountValue
}
val fiatSending = fiatSendingValue.format {
fiat(
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
)
}
val fiatFee = TextReference.EMPTY
// todo send with swap footer
// formatFooterFiatFee(
// amount = feeItem.fee.amount.copy(value = fiatFeeValue),
// isFeeConvertibleToFiat = feeSelectorUM.feeExtraInfo.isFeeConvertibleToFiat,
// isFeeApproximate = feeSelectorUM.feeExtraInfo.isFeeApproximate,
// appCurrency = appCurrency,
// )
return if (feeSelectorUM.feeExtraInfo.isTronToken && feeItem.fee is Fee.Tron) {
// todo send with swap footer
// getTronTokenFeeSendingText(
// fee = feeItem.fee,
// fiatFee = fiatFee,
// fiatSending = stringReference(fiatSending),
// )
TextReference.EMPTY
} else {
resourceReference(
id = if (feeSelectorUM.feeExtraInfo.isFeeConvertibleToFiat) {
R.string.send_summary_transaction_description
} else {
R.string.send_summary_transaction_description_no_fiat_fee
},
formatArgs = wrappedList(fiatSending, fiatFee),
)
}
}
}

View file

@ -0,0 +1,72 @@
package com.tangem.features.swap.v2.impl.sendviaswap.confirm.ui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.components.SpacerHMax
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.send.v2.api.SendNotificationsComponent
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationBlockComponent
import com.tangem.features.swap.v2.impl.amount.SwapAmountBlockComponent
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM
import kotlinx.collections.immutable.ImmutableList
@Suppress("LongParameterList")
@Composable
internal fun SendWithSwapConfirmContent(
sendWithSwapUM: SendWithSwapUM,
amountBlockComponent: SwapAmountBlockComponent,
sendDestinationBlockComponent: SendDestinationBlockComponent,
feeSelectorBlockComponent: FeeSelectorBlockComponent,
sendNotificationsComponent: SendNotificationsComponent,
sendNotificationsUM: ImmutableList<NotificationUM>,
modifier: Modifier = Modifier,
) {
val confirmUM = sendWithSwapUM.confirmUM as? ConfirmUM.Content
Column(modifier = modifier) {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.padding(horizontal = 12.dp),
) {
item(key = "SendWithSwapAmountBlock") {
amountBlockComponent.Content(Modifier)
}
item(key = "SendWithSwapDestinationBlock") {
sendDestinationBlockComponent.Content(Modifier)
}
item(key = "SendWithSwapFeeBLock") {
Box(
modifier = Modifier.clip(RoundedCornerShape(16.dp)),
) {
feeSelectorBlockComponent.Content(Modifier)
}
}
if (confirmUM != null) {
// tapHelp(isDisplay = confirmUM.showTapHelp) // todo
with(sendNotificationsComponent) {
content(
state = sendNotificationsUM,
isClickDisabled = confirmUM.isTransactionInProcess,
)
}
// notifications(
// notifications = confirmUM.notifications,
// isClickDisabled = confirmUM.isTransactionInProcess,
// )
}
}
SpacerHMax()
// todo
// SendingText(footerText = confirmUM?.sendingFooter ?: TextReference.EMPTY)
}
}

View file

@ -0,0 +1,15 @@
package com.tangem.features.swap.v2.impl.sendviaswap.entity
import com.tangem.common.ui.navigationButtons.NavigationUM
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
internal data class SendWithSwapUM(
val amountUM: SwapAmountUM,
val destinationUM: DestinationUM,
val feeSelectorUM: FeeSelectorUM,
val confirmUM: ConfirmUM,
val navigationUM: NavigationUM,
)

View file

@ -0,0 +1,81 @@
package com.tangem.lib.crypto
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import java.math.BigInteger
import java.math.RoundingMode
/**
* !!!IMPORTANT!!!
* Methods for tuning transaction fee of different blockchains
*
* Temporary solution for domain specific logic for Blockchain.
* Instead of creating repositories and unnecessary and overkill use cases
*/
object BlockchainFeeUtils {
private val HUNDRED_PERCENT = BigInteger("100")
/**
* We need to increase gasLimit for Ethereum fees for 2 cases
*
* DEX: for dex calculated gasLimit for given data might be changed when transaction processing
* for that case dex providers recommend to increase gasLimit for few percents to ensure transaction completes
*
* CEX: for that case we calculate fee for random generated address and gasLimit might be different for it
* and result address to send. That's why we should increase gasLimit a little
*
*/
fun TransactionFee.patchTransactionFeeForSwap(increaseBy: Int): TransactionFee {
return when (this) {
is TransactionFee.Choosable -> {
this.copy(
minimum = this.minimum.increaseEthGasLimitInNeeded(increaseBy),
normal = this.normal.increaseEthGasLimitInNeeded(increaseBy),
priority = this.priority.increaseEthGasLimitInNeeded(increaseBy),
)
}
is TransactionFee.Single -> this.copy(normal = this.normal.increaseEthGasLimitInNeeded(increaseBy))
}
}
private fun Fee.increaseEthGasLimitInNeeded(increaseBy: Int): Fee {
return when (this) {
is Fee.Ethereum.EIP1559,
is Fee.Ethereum.Legacy,
-> this.increaseGasLimitBy(increaseBy)
is Fee.Alephium,
is Fee.Aptos,
is Fee.Bitcoin,
is Fee.CardanoToken,
is Fee.Common,
is Fee.Filecoin,
is Fee.Hedera,
is Fee.Kaspa,
is Fee.Sui,
is Fee.Tron,
is Fee.VeChain,
-> this
}
}
/**
* Increase gasLimit for Fee.Ethereum
*/
private fun Fee.increaseGasLimitBy(percentage: Int): Fee {
if (this !is Fee.Ethereum) return this
val gasLimit = this.gasLimit
val increasedGasPrice = this.amount.value?.movePointRight(this.amount.decimals)
?.divide(gasLimit.toBigDecimal(), RoundingMode.HALF_UP)
val increasedGasLimit = gasLimit
.multiply(percentage.toBigInteger())
.divide(HUNDRED_PERCENT)
val increasedAmount = this.amount.copy(
value = increasedGasLimit.toBigDecimal().multiply(increasedGasPrice).movePointLeft(this.amount.decimals),
)
return when (this) {
is Fee.Ethereum.EIP1559 -> copy(amount = increasedAmount, gasLimit = increasedGasLimit)
is Fee.Ethereum.Legacy -> copy(amount = increasedAmount, gasLimit = increasedGasLimit)
}
}
}