From fd16a099ea03c47f9d2bd640376a1db49536bfa4 Mon Sep 17 00:00:00 2001 From: cyan Date: Fri, 4 Apr 2025 12:56:07 +0200 Subject: [PATCH 001/142] fix: incorrect balance in send card (#2158) * fix: incorrect balance in send card * fix: ensure that all unique Unspent are used in calculation --- cw_core/lib/unspent_transaction_output.dart | 5 +++++ .../unspent_coins/unspent_coins_list_view_model.dart | 8 +++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/cw_core/lib/unspent_transaction_output.dart b/cw_core/lib/unspent_transaction_output.dart index da71f6983..31820e3a1 100644 --- a/cw_core/lib/unspent_transaction_output.dart +++ b/cw_core/lib/unspent_transaction_output.dart @@ -21,4 +21,9 @@ class Unspent with UnspentComparable { bool get isP2wpkh => address.startsWith('bc') || address.startsWith('tb') || address.startsWith('ltc'); + + @override + String toString() { + return 'Unspent(address: $address, hash: $hash, value: $value, vout: $vout, keyImage: $keyImage, isSending: $isSending, isFrozen: $isFrozen, isChange: $isChange, note: $note)'; + } } diff --git a/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart b/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart index 5136ead1e..bae4677cf 100644 --- a/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart +++ b/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart @@ -156,8 +156,8 @@ abstract class UnspentCoinsListViewModelBase with Store { await _updateUnspents(); Set seen = {}; for (final item in _getSpecificUnspents(overrideCoinTypeToSpendFrom)) { - if (seen.contains(item.hash)) continue; - seen.add(item.hash); + if (seen.contains(item.toString())) continue; + seen.add(item.toString()); if (item.isFrozen || !item.isSending) continue; total += item.value; } @@ -166,8 +166,6 @@ abstract class UnspentCoinsListViewModelBase with Store { @action void _updateUnspentCoinsInfo() { - items.clear(); - final unspents = _getUnspents() .map((elem) { try { @@ -201,7 +199,7 @@ abstract class UnspentCoinsListViewModelBase with Store { .toList(); unspents.sort((a, b) => b.value.compareTo(a.value)); - + items.clear(); items.addAll(unspents); } From 21ba952746debfbdbe029b04982c9ebd2e9bdcf7 Mon Sep 17 00:00:00 2001 From: Serhii Date: Fri, 4 Apr 2025 15:48:52 +0300 Subject: [PATCH 002/142] feat: add backup saved toast message (#2160) --- lib/src/screens/backup/backup_page.dart | 1 + res/values/strings_ar.arb | 1 + res/values/strings_bg.arb | 1 + res/values/strings_cs.arb | 1 + res/values/strings_de.arb | 1 + res/values/strings_en.arb | 1 + res/values/strings_es.arb | 3 ++- res/values/strings_fr.arb | 1 + res/values/strings_ha.arb | 1 + res/values/strings_hi.arb | 3 ++- res/values/strings_hr.arb | 1 + res/values/strings_hy.arb | 1 + res/values/strings_id.arb | 1 + res/values/strings_it.arb | 1 + res/values/strings_ja.arb | 1 + res/values/strings_ko.arb | 1 + res/values/strings_my.arb | 1 + res/values/strings_nl.arb | 1 + res/values/strings_pl.arb | 1 + res/values/strings_pt.arb | 1 + res/values/strings_ru.arb | 1 + res/values/strings_th.arb | 1 + res/values/strings_tl.arb | 1 + res/values/strings_tr.arb | 1 + res/values/strings_uk.arb | 1 + res/values/strings_ur.arb | 1 + res/values/strings_vi.arb | 1 + res/values/strings_yo.arb | 1 + res/values/strings_zh.arb | 1 + 29 files changed, 31 insertions(+), 2 deletions(-) diff --git a/lib/src/screens/backup/backup_page.dart b/lib/src/screens/backup/backup_page.dart index 8f520b15f..408fad2d3 100644 --- a/lib/src/screens/backup/backup_page.dart +++ b/lib/src/screens/backup/backup_page.dart @@ -139,6 +139,7 @@ class BackupPage extends BasePage { await backupViewModelBase.saveToDownload( backup.name, backup.file); Navigator.of(dialogContext).pop(); + await showBar(context, S.of(context).file_saved); }, actionLeftButton: () async { Navigator.of(dialogContext).pop(); diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 99eb81e58..f30f7d614 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -333,6 +333,7 @@ "fiat_api": "Fiat API", "fiat_balance": "الرصيد فيات", "field_required": "هذه الخانة مطلوبه", + "file_saved": "تم حفظ الملف", "fill_code": "يرجى ملء رمز التحقق المرسل إلى بريدك الإلكتروني", "filter_by": "تصفية حسب", "first_wallet_text": "محفظة رائعة ل Monero, Bitcoin, Ethereum, Litecoin و Haven", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index ad066092a..2d8527b83 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -333,6 +333,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Фиат Баланс", "field_required": "Това поле е задължително", + "file_saved": "Запасен файл", "fill_code": "Моля, въведето кода за потвърждаване, изпратен на Вашия имейл", "filter_by": "Филтрирай по", "first_wallet_text": "Невероятен портфейл за Monero, Bitcoin, Ethereum, Litecoin и Haven", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index e5c0ac4e7..968e45a81 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -333,6 +333,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Fiat Balance", "field_required": "Toto pole je povinné", + "file_saved": "Uložen soubor", "fill_code": "Prosím vyplňte ověřovací kód zaslaný na Váš e-mail", "filter_by": "Filtrovat podle", "first_wallet_text": "Úžasná peněženka pro Monero, Bitcoin, Ethereum, Litecoin a Haven", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 0588c442d..e23c41675 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -333,6 +333,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Fiat Balance", "field_required": "Dieses Feld ist erforderlich", + "file_saved": "Datei gespeichert", "fill_code": "Geben Sie den Bestätigungscode ein, den Sie per E-Mail erhalten haben", "filter_by": "Filtern nach", "first_wallet_text": "Eine großartige Wallet für Monero, Bitcoin, Ethereum, Litecoin, und Haven", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 1636fcee5..9586e9868 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -333,6 +333,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Fiat Balance", "field_required": "This field is required", + "file_saved": "File saved", "fill_code": "Please fill in the verification code provided to your email", "filter_by": "Filter by", "first_wallet_text": "Awesome wallet for Monero, Bitcoin, Ethereum, Litecoin, and Haven", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index af44d1fdb..a17c84d19 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -333,6 +333,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Balance fiat", "field_required": "Este campo es obligatorio", + "file_saved": "Archivo guardado", "fill_code": "Por favor completa el código de verificación proporcionado en tu correo electrónico", "filter_by": "Filtrado por", "first_wallet_text": "Impresionante billetera para Monero, Bitcoin, Ethereum, Litecoin, y Haven", @@ -1027,4 +1028,4 @@ "you_will_receive_estimated_amount": "Recibirá(estimado )", "you_will_send": "Convertir de", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index ea0e56d52..405f6abb3 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -333,6 +333,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Solde fiat", "field_required": "Ce champ est obligatoire", + "file_saved": "Dossier enregistré", "fill_code": "Veuillez remplir le code de vérification fourni sur votre e-mail", "filter_by": "Filtrer par", "first_wallet_text": "Super portefeuille (wallet) pour Monero, Bitcoin, Ethereum, Litecoin et Haven", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index ceea9eaa0..3355aea6f 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -333,6 +333,7 @@ "fiat_api": "API ɗin Fiat", "fiat_balance": "Fiat Balance", "field_required": "wannan fillin ana bukatansa", + "file_saved": "Fayil ɗin ceto", "fill_code": "Da fatan za a cika lambar tabbatarwa da aka bayar zuwa imel ɗin ku", "filter_by": "Tace ta", "first_wallet_text": "Aikace-aikacen e-wallet ga Monero, Bitcoin, Ethereum, Litecoin, da kuma Haven", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 4fa3b9780..cef53d58e 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -333,6 +333,7 @@ "fiat_api": "फिएट पैसे API", "fiat_balance": "फिएट बैलेंस", "field_required": "यह फ़ील्ड आवश्यक है", + "file_saved": "फ़ाइल सहेजा गया", "fill_code": "कृपया अपने ईमेल पर प्रदान किया गया सत्यापन कोड भरें", "filter_by": "के द्वारा छनित", "first_wallet_text": "Monero, Bitcoin, Ethereum, Litecoin, और Haven के लिए बहुत बढ़िया बटुआ", @@ -522,8 +523,8 @@ "paste": "पेस्ट करें", "pause_wallet_creation": "हेवन वॉलेट बनाने की क्षमता फिलहाल रुकी हुई है।", "payment_id": "भुगतान ID: ", - "payment_was_received": "आपका भुगतान प्राप्त हुआ था।", "Payment_was_received": "आपका भुगतान प्राप्त हो गया था।", + "payment_was_received": "आपका भुगतान प्राप्त हुआ था।", "pending": " (अपूर्ण)", "percentageOf": "${amount} का", "pin_at_top": "शीर्ष पर ${token} पिन करें", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index c08e6e78c..63d698c3d 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -333,6 +333,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Fiat Bilans", "field_required": "ovo polje je obavezno", + "file_saved": "Spremljena datoteka", "fill_code": "Molimo vas da ispunite kontrolni kod koji ste dobili na svojoj e-pošti", "filter_by": "Filtrirati po", "first_wallet_text": "Odličan novčanik za Monero, Bitcoin, Ethereum, Litecoin, i Haven", diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index f73dff48f..369264362 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -333,6 +333,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Fiat մնացորդ", "field_required": "Այս դաշտը պարտադիր է", + "file_saved": "Ֆայլը պահպանվել է", "fill_code": "Խնդրում ենք լրացնել հաստատման կոդը ձեր էլեկտրոնային փոստում", "filter_by": "Ֆիլտրել ըստ", "first_wallet_text": "Հիանալի հաշվեհամար Monero, Bitcoin, Ethereum, Litecoin և Haven արժույթների համար", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index e9671cb20..b7794d485 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -333,6 +333,7 @@ "fiat_api": "API fiat", "fiat_balance": "Saldo Fiat", "field_required": "Bagian ini diperlukan", + "file_saved": "File disimpan", "fill_code": "Silakan isi kode verifikasi yang diterima di email Anda", "filter_by": "Filter berdasarkan", "first_wallet_text": "Dompet luar biasa untuk Monero, Bitcoin, Ethereum, Litecoin, dan Haven", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 2b7c1e7f7..2c214da67 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -333,6 +333,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Saldo fiat", "field_required": "Questo campo è obbligatorio", + "file_saved": "File salvato", "fill_code": "Compila il codice di verifica fornito alla tua email", "filter_by": "Filtra per", "first_wallet_text": "Portafoglio fantastico per Monero, Bitcoin, Ethereum, Litecoin, e Haven", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 23fedcb68..52d837fd0 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -333,6 +333,7 @@ "fiat_api": "不換紙幣 API", "fiat_balance": "フィアットバランス", "field_required": "この項目は必須です", + "file_saved": "保存されたファイル", "fill_code": "メールアドレスに記載されている確認コードを入力してください", "filter_by": "でフィルタリング", "first_wallet_text": "Monero、Bitcoin、Ethereum、Litecoin、Haven用の素晴らしいウォレット", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index b3c0ac826..08ad157da 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -333,6 +333,7 @@ "fiat_api": "명목 화폐 API", "fiat_balance": "피아트 잔액", "field_required": "이 필드는 필수입니다", + "file_saved": "파일이 저장되었습니다", "fill_code": "이메일에 제공된 인증 코드를 입력하세요.", "filter_by": "필터링 기준", "first_wallet_text": "Monero, Bitcoin, Ethereum, Litecoin 및 Haven을 위한 멋진 지갑", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index e27b55e04..111593150 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -333,6 +333,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Fiat Balance", "field_required": "ဤစာကွက်လပ်မှာဖြည့်ရန်လိုအပ်ပါသည်", + "file_saved": "သိမ်းဆည်းထားသည့်ဖိုင်", "fill_code": "သင့်အီးမေးလ်သို့ ပေးထားသည့် အတည်ပြုကုဒ်ကို ဖြည့်ပါ။", "filter_by": "အလိုက် စစ်ထုတ်ပါ။", "first_wallet_text": "Monero၊ Bitcoin၊ Ethereum၊ Litecoin နှင့် Haven အတွက် အလွန်ကောင်းမွန်သော ပိုက်ဆံအိတ်", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index b5357fc0d..8b7939de8 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -333,6 +333,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Fiat Balans", "field_required": "dit veld is verplicht", + "file_saved": "Bestand opgeslagen", "fill_code": "Vul de verificatiecode in die u in uw e-mail hebt ontvangen", "filter_by": "Filteren op", "first_wallet_text": "Geweldige portemonnee voor Monero, Bitcoin, Ethereum, Litecoin, en Haven", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 4e45d8edf..d60e97ef4 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -333,6 +333,7 @@ "fiat_api": "API Walut FIAT", "fiat_balance": "Bilans Fiata", "field_required": "To pole jest wymagane", + "file_saved": "Zapisano plik", "fill_code": "Proszę wpisać kod weryfikacyjny który otrzymałeś w wiadomości e-mail", "filter_by": "Filtruj według", "first_wallet_text": "Świetny portfel na Monero, Bitcoin, Ethereum, Litecoin, i Haven", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 1a6a109a0..732865567 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -333,6 +333,7 @@ "fiat_api": "API da Fiat", "fiat_balance": "Equilíbrio Fiat", "field_required": "Este campo é obrigatório", + "file_saved": "Arquivo salvo", "fill_code": "Por favor, preencha o código de verificação fornecido ao seu e-mail", "filter_by": "Filtrar por", "first_wallet_text": "Carteira incrível para Monero, Bitcoin, Ethereum, Litecoin, e Haven", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index e2dd0fec1..63241530b 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -333,6 +333,7 @@ "fiat_api": "Фиат API", "fiat_balance": "Фиатный баланс", "field_required": "Это поле обязательно к заполнению", + "file_saved": "Файл сохранен", "fill_code": "Пожалуйста, введите код подтверждения, отправленный на вашу электронную почту", "filter_by": "Фильтровать по", "first_wallet_text": "В самом удобном кошельке для Monero, Bitcoin, Ethereum, Litecoin, и Haven", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 343ea1918..6de6854ca 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -333,6 +333,7 @@ "fiat_api": "API สกุลเงินตรา", "fiat_balance": "เฟียต บาลานซ์", "field_required": "ช่องนี้จำเป็น", + "file_saved": "บันทึกไฟล์", "fill_code": "โปรดกรอกรหัสยืนยันที่ส่งไปยังอีเมลของคุณ", "filter_by": "กรองตาม", "first_wallet_text": "กระเป๋าสตางค์ที่สวยงามสำหรับ Monero, Bitcoin, Ethereum, Litecoin และ Haven", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 77c66bbcb..319286fbd 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -333,6 +333,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Balanse ng fiat", "field_required": "Kinakailangan ang patlang na ito", + "file_saved": "Nai -save ang file", "fill_code": "Mangyaring ilagay ang verfification code na ibinigay sa iyong email", "filter_by": "Filter ni", "first_wallet_text": "Kahanga-hangang wallet para sa Monero, Bitcoin, Litecoin, Ethereum, at Haven", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index b0cf1ba3f..852e7b1d2 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -333,6 +333,7 @@ "fiat_api": "İtibari Para API", "fiat_balance": "Fiat Bakiyesi", "field_required": "Bu alan gereklidir", + "file_saved": "Dosya kaydedildi", "fill_code": "Lütfen e-postanıza gelen doğrulama kodunu girin", "filter_by": "Şuna göre filtrele", "first_wallet_text": "Monero, Bitcoin, Ethereum, Litecoin ve Haven için harika cüzdan", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index b5575e251..5afc44815 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -333,6 +333,7 @@ "fiat_api": "Фіат API", "fiat_balance": "Фіат Баланс", "field_required": "Це поле є обов'язковим", + "file_saved": "Збережено файл", "fill_code": "Будь ласка, введіть код підтвердження, надісланий на вашу електронну адресу", "filter_by": "Фільтрувати по", "first_wallet_text": "В самому зручному гаманці для Monero, Bitcoin, Ethereum, Litecoin, та Haven", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index cce28ef73..675747d20 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -333,6 +333,7 @@ "fiat_api": "Fiat API", "fiat_balance": "فیاٹ بیلنس", "field_required": "اس کو پر کرنا ضروری ہے", + "file_saved": "فائل محفوظ ہوگئی", "fill_code": "براہ کرم اپنے ای میل پر فراہم کردہ تصدیقی کوڈ کو پُر کریں۔", "filter_by": "کی طرف سے فلٹر", "first_wallet_text": "Monero، Bitcoin، Ethereum، Litecoin، اور Haven کے لیے زبردست پرس", diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index 841dda6ea..76bdaae34 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -332,6 +332,7 @@ "fiat_api": "API Fiat", "fiat_balance": "Số dư Fiat", "field_required": "Trường này là bắt buộc", + "file_saved": "Tệp lưu", "fill_code": "Vui lòng điền mã xác minh được gửi đến email của bạn", "filter_by": "Lọc theo", "first_wallet_text": "Ví tuyệt vời cho Monero, Bitcoin, Ethereum, Litecoin, và Haven", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 5a76b9961..b43c6cef4 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -334,6 +334,7 @@ "fiat_api": "Ojú ètò áàpù owó tí ìjọba pàṣẹ wa lò", "fiat_balance": "Fiat Iwontunws.funfun", "field_required": "E ni lati se nkan si aye yi", + "file_saved": "Faili faili ti o ti fipamọ", "fill_code": "Ẹ jọ̀wọ́ tẹ̀ ọ̀rọ̀ ìjẹ́rìísí t'á ti ránṣẹ́ sí ímeèlì yín.", "filter_by": "Ṣẹ́ láti", "first_wallet_text": "Àpamọ́wọ́ t'á fi Monero, Bitcoin, Ethereum, Litecoin, àti Haven pamọ́ wà pa", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index eaa2df069..a83d35570 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -333,6 +333,7 @@ "fiat_api": "法币API", "fiat_balance": "法币余额", "field_required": "此字段是必需的", + "file_saved": "保存的文件", "fill_code": "请填写提供给您邮箱的验证码", "filter_by": "过滤", "first_wallet_text": "适用于门罗币、比特币、以太坊、莱特币和避风港的超棒钱包", From 1289ccb3d37c1001b20d34b5747941a86a6e2722 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Mon, 7 Apr 2025 14:48:45 +0200 Subject: [PATCH 003/142] remove old deprecated monero nodes [skip ci] --- assets/node_list.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/assets/node_list.yml b/assets/node_list.yml index 49cc00a94..917dadfd9 100644 --- a/assets/node_list.yml +++ b/assets/node_list.yml @@ -13,9 +13,3 @@ - uri: nodes.hashvault.pro:18081 is_default: false -- - uri: node.c3pool.com:18081 - is_default: false -- - uri: node.community.rino.io:18081 - is_default: false From 9ac784db5c27daf189fb49961202a697eafc50f3 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Mon, 7 Apr 2025 15:48:11 +0200 Subject: [PATCH 004/142] decrease padding so "Exchange" is on one line [skip ci] --- lib/src/screens/dashboard/pages/navigation_dock.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/screens/dashboard/pages/navigation_dock.dart b/lib/src/screens/dashboard/pages/navigation_dock.dart index 4eda169d3..e2a2362ee 100644 --- a/lib/src/screens/dashboard/pages/navigation_dock.dart +++ b/lib/src/screens/dashboard/pages/navigation_dock.dart @@ -32,7 +32,7 @@ class NavigationDock extends StatelessWidget { ), ), child: Container( - margin: const EdgeInsets.only(left: 16, right: 16, bottom: 16), + margin: const EdgeInsets.only(left: 8, right: 8, bottom: 16), child: ClipRRect( borderRadius: BorderRadius.circular(50), child: BackdropFilter( @@ -48,7 +48,7 @@ class NavigationDock extends StatelessWidget { Theme.of(context).extension()!.syncedBackgroundColor, ), child: Container( - padding: EdgeInsets.symmetric(horizontal: 10), + padding: EdgeInsets.symmetric(horizontal: 8), child: IntrinsicHeight( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, From 88ebba9236f9efd47f679c255288d4833882f46c Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Mon, 7 Apr 2025 18:12:39 +0200 Subject: [PATCH 005/142] Label existing scam tokens (#2164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * label existing scam tokens because users can get scammed twice ¯\_(ツ)_/¯ * minor ui fix [skip ci] --- cw_core/lib/erc20_token.dart | 4 +- cw_ethereum/lib/ethereum_wallet.dart | 6 +- cw_evm/lib/evm_chain_wallet.dart | 30 +++++++++ cw_polygon/lib/polygon_wallet.dart | 4 ++ lib/src/screens/send/send_page.dart | 98 ++++++++++++++-------------- 5 files changed, 89 insertions(+), 53 deletions(-) diff --git a/cw_core/lib/erc20_token.dart b/cw_core/lib/erc20_token.dart index e47488143..fd76d28fc 100644 --- a/cw_core/lib/erc20_token.dart +++ b/cw_core/lib/erc20_token.dart @@ -17,11 +17,11 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin { @HiveField(4, defaultValue: true) bool _enabled; @HiveField(5) - final String? iconPath; + String? iconPath; @HiveField(6) final String? tag; @HiveField(7, defaultValue: false) - final bool isPotentialScam; + bool isPotentialScam; bool get enabled => _enabled; diff --git a/cw_ethereum/lib/ethereum_wallet.dart b/cw_ethereum/lib/ethereum_wallet.dart index ae6158557..7cc140c5a 100644 --- a/cw_ethereum/lib/ethereum_wallet.dart +++ b/cw_ethereum/lib/ethereum_wallet.dart @@ -76,9 +76,13 @@ class EthereumWallet extends EVMChainWallet { await erc20TokensBox.deleteFromDisk(); // Add all the previous tokens with configs to the new box - evmChainErc20TokensBox.addAll(allValues); + await evmChainErc20TokensBox.addAll(allValues); } + @override + List get getDefaultTokenContractAddresses => + DefaultEthereumErc20Tokens().initialErc20Tokens.map((e) => e.contractAddress).toList(); + @override EVMChainTransactionInfo getTransactionInfo( EVMChainTransactionModel transactionModel, String address) { diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index 77331758c..d640f8c14 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -144,6 +144,8 @@ abstract class EVMChainWalletBase // required WalletInfo walletInfo, // }); + List get getDefaultTokenContractAddresses; + Future initErc20TokensBox(); String getTransactionHistoryFileName(); @@ -171,6 +173,9 @@ abstract class EVMChainWalletBase await walletAddresses.init(); await transactionHistory.init(); + // check for Already existing scam tokens, cuz users can get scammed twice ¯\_(ツ)_/¯ + await _checkForExistingScamTokens(); + if (walletInfo.isHardwareWallet) { _evmChainPrivateKey = EvmLedgerCredentials(walletInfo.address); walletAddresses.address = walletInfo.address; @@ -186,6 +191,31 @@ abstract class EVMChainWalletBase await save(); } + Future _checkForExistingScamTokens() async { + final baseCurrencySymbols = CryptoCurrency.all.map((e) => e.title.toUpperCase()).toList(); + + for (var token in erc20Currencies) { + bool isPotentialScam = false; + + bool isWhitelisted = + getDefaultTokenContractAddresses.any((element) => element == token.contractAddress); + + final tokenSymbol = token.title.toUpperCase(); + + // check if the token symbol is the same as any of the base currencies symbols (ETH, SOL, POL, TRX, etc): + // if it is, then it's probably a scam unless it's in the whitelist + if (baseCurrencySymbols.contains(tokenSymbol.trim().toUpperCase()) && !isWhitelisted) { + isPotentialScam = true; + } + + if (isPotentialScam) { + token.isPotentialScam = true; + token.iconPath = null; + await token.save(); + } + } + } + @override int calculateEstimatedFee(TransactionPriority priority, int? amount) { { diff --git a/cw_polygon/lib/polygon_wallet.dart b/cw_polygon/lib/polygon_wallet.dart index d2b62ca4d..b2bf064b1 100644 --- a/cw_polygon/lib/polygon_wallet.dart +++ b/cw_polygon/lib/polygon_wallet.dart @@ -49,6 +49,10 @@ class PolygonWallet extends EVMChainWallet { } } + @override + List get getDefaultTokenContractAddresses => + DefaultPolygonErc20Tokens().initialPolygonErc20Tokens.map((e) => e.contractAddress).toList(); + @override Future checkIfScanProviderIsEnabled() async { bool isPolygonScanEnabled = (await sharedPrefs.future).getBool("use_polygonscan") ?? true; diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 499bdb200..9fd55af14 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -98,7 +98,7 @@ class SendPage extends BasePage { return MergeSemantics( child: SizedBox( height: isMobileView ? 37 : 45, - width: isMobileView ? 47: 45, + width: isMobileView ? 47 : 45, child: ButtonTheme( minWidth: double.minPositive, child: Semantics( @@ -397,7 +397,6 @@ class SendPage extends BasePage { return LoadingPrimaryButton( key: ValueKey('send_page_send_button_key'), onPressed: () async { - //Request dummy node to get the focus out of the text fields FocusScope.of(context).requestFocus(FocusNode()); @@ -507,7 +506,6 @@ class SendPage extends BasePage { Navigator.of(loadingBottomSheetContext!).pop(); } - if (state is FailureState) { WidgetsBinding.instance.addPostFrameCallback((_) { showPopUp( @@ -525,7 +523,6 @@ class SendPage extends BasePage { } if (state is IsExecutingState) { - // wait a bit to avoid showing the loading dialog if transaction is failed await Future.delayed(const Duration(milliseconds: 300)); final currentState = sendViewModel.state; @@ -584,8 +581,6 @@ class SendPage extends BasePage { }); } - - if (state is TransactionCommitted) { WidgetsBinding.instance.addPostFrameCallback((_) async { if (!context.mounted) { @@ -594,7 +589,8 @@ class SendPage extends BasePage { newContactAddress = newContactAddress ?? sendViewModel.newContactAddress(); - if (newContactAddress?.address != null && isRegularElectrumAddress(newContactAddress!.address)) { + if (newContactAddress?.address != null && + isRegularElectrumAddress(newContactAddress!.address)) { newContactAddress = null; } @@ -606,47 +602,51 @@ class SendPage extends BasePage { builder: (BuildContext bottomSheetContext) { return showContactSheet ? InfoBottomSheet( - currentTheme: currentTheme, - showDontAskMeCheckbox: true, - onCheckboxChanged: (value) => sendViewModel.setShowAddressBookPopup(!value), - titleText: S.of(bottomSheetContext).transaction_sent, - contentImage: 'assets/images/contact_icon.svg', - contentImageColor: Theme.of(context) - .extension()! - .titleColor, - content: S.of(bottomSheetContext).add_contact_to_address_book, - isTwoAction: true, - leftButtonText: 'No', - rightButtonText: 'Yes', - actionLeftButton: () { - Navigator.of(bottomSheetContext).pop(); - Navigator.of(context) - .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false); - RequestReviewHandler.requestReview(); - newContactAddress = null; - }, - actionRightButton: () { - Navigator.of(bottomSheetContext).pop(); - RequestReviewHandler.requestReview(); - Navigator.of(context) - .pushNamed(Routes.addressBookAddContact, arguments: newContactAddress); - newContactAddress = null; - }, - ) + currentTheme: currentTheme, + showDontAskMeCheckbox: true, + onCheckboxChanged: (value) => sendViewModel.setShowAddressBookPopup(!value), + titleText: S.of(bottomSheetContext).transaction_sent, + contentImage: 'assets/images/contact_icon.svg', + contentImageColor: Theme.of(context).extension()!.titleColor, + content: S.of(bottomSheetContext).add_contact_to_address_book, + isTwoAction: true, + leftButtonText: 'No', + rightButtonText: 'Yes', + actionLeftButton: () { + Navigator.of(bottomSheetContext).pop(); + if (context.mounted) { + Navigator.of(context) + .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false); + } + RequestReviewHandler.requestReview(); + newContactAddress = null; + }, + actionRightButton: () { + Navigator.of(bottomSheetContext).pop(); + RequestReviewHandler.requestReview(); + if (context.mounted) { + Navigator.of(context).pushNamed(Routes.addressBookAddContact, + arguments: newContactAddress); + } + newContactAddress = null; + }, + ) : InfoBottomSheet( - currentTheme: currentTheme, - titleText: S.of(bottomSheetContext).transaction_sent, - contentImage: 'assets/images/birthday_cake.svg', - actionButtonText: S.of(bottomSheetContext).close, - actionButtonKey: ValueKey('send_page_sent_dialog_ok_button_key'), - actionButton: () { - Navigator.of(bottomSheetContext).pop(); - Navigator.of(context) - .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false); - RequestReviewHandler.requestReview(); - newContactAddress = null; - }, - ); + currentTheme: currentTheme, + titleText: S.of(bottomSheetContext).transaction_sent, + contentImage: 'assets/images/birthday_cake.svg', + actionButtonText: S.of(bottomSheetContext).close, + actionButtonKey: ValueKey('send_page_sent_dialog_ok_button_key'), + actionButton: () { + Navigator.of(bottomSheetContext).pop(); + if (context.mounted) { + Navigator.of(context) + .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false); + } + RequestReviewHandler.requestReview(); + newContactAddress = null; + }, + ); }, ); @@ -678,8 +678,7 @@ class SendPage extends BasePage { currentTheme: currentTheme, titleText: S.of(bottomSheetContext).proceed_on_device, contentImage: 'assets/images/hardware_wallet/ledger_nano_x.png', - contentImageColor: - Theme.of(context).extension()!.titleColor, + contentImageColor: Theme.of(context).extension()!.titleColor, content: S.of(bottomSheetContext).proceed_on_device_description, isTwoAction: false, actionButtonText: S.of(context).cancel, @@ -778,5 +777,4 @@ class SendPage extends BasePage { return isValid; } - } From 2d693f46ff4f33553a507511d701ad7462cfb9f0 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Mon, 7 Apr 2025 18:14:35 +0200 Subject: [PATCH 006/142] Remove old cake wallet logo (#2161) * Remove old cake wallet logo * minor fix [skip ci] --- .gitignore | 25 ++- .../cakewallet_ios_icons/AppIcon-20@2x.png | Bin 0 -> 1423 bytes .../AppIcon-20@2x~ipad.png | Bin 0 -> 1423 bytes .../cakewallet_ios_icons/AppIcon-20@3x.png | Bin 0 -> 2688 bytes .../cakewallet_ios_icons/AppIcon-20~ipad.png | Bin 0 -> 591 bytes .../cakewallet_ios_icons/AppIcon-29.png | Bin 0 -> 1019 bytes .../cakewallet_ios_icons/AppIcon-29@2x.png | Bin 0 -> 2551 bytes .../AppIcon-29@2x~ipad.png | Bin 0 -> 2551 bytes .../cakewallet_ios_icons/AppIcon-29@3x.png | Bin 0 -> 4034 bytes .../cakewallet_ios_icons/AppIcon-29~ipad.png | Bin 0 -> 1019 bytes .../cakewallet_ios_icons/AppIcon-40@2x.png | Bin 0 -> 3574 bytes .../AppIcon-40@2x~ipad.png | Bin 0 -> 3574 bytes .../cakewallet_ios_icons/AppIcon-40@3x.png | Bin 0 -> 6659 bytes .../cakewallet_ios_icons/AppIcon-40~ipad.png | Bin 0 -> 1423 bytes .../AppIcon-60@2x~car.png | Bin 0 -> 6659 bytes .../AppIcon-60@3x~car.png | Bin 0 -> 10558 bytes .../AppIcon-83.5@2x~ipad.png | Bin 0 -> 9576 bytes .../cakewallet_ios_icons/AppIcon@2x.png | Bin 0 -> 6659 bytes .../cakewallet_ios_icons/AppIcon@2x~ipad.png | Bin 0 -> 8002 bytes .../cakewallet_ios_icons/AppIcon@3x.png | Bin 0 -> 10558 bytes .../AppIcon~ios-marketing.png | Bin 0 -> 205464 bytes .../cakewallet_ios_icons/AppIcon~ipad.png | Bin 0 -> 3344 bytes .../monero_ios_icons/AppIcon-20@2x.png | Bin 0 -> 1474 bytes .../monero_ios_icons/AppIcon-20@2x~ipad.png | Bin 0 -> 1474 bytes .../monero_ios_icons/AppIcon-20@3x.png | Bin 0 -> 2597 bytes .../monero_ios_icons/AppIcon-20~ipad.png | Bin 0 -> 688 bytes .../ios_icons/monero_ios_icons/AppIcon-29.png | Bin 0 -> 1112 bytes .../monero_ios_icons/AppIcon-29@2x.png | Bin 0 -> 2465 bytes .../monero_ios_icons/AppIcon-29@2x~ipad.png | Bin 0 -> 2465 bytes .../monero_ios_icons/AppIcon-29@3x.png | Bin 0 -> 3405 bytes .../monero_ios_icons/AppIcon-29~ipad.png | Bin 0 -> 1112 bytes .../monero_ios_icons/AppIcon-40@2x.png | Bin 0 -> 3006 bytes .../monero_ios_icons/AppIcon-40@2x~ipad.png | Bin 0 -> 3006 bytes .../monero_ios_icons/AppIcon-40@3x.png | Bin 0 -> 5335 bytes .../monero_ios_icons/AppIcon-40~ipad.png | Bin 0 -> 1474 bytes .../monero_ios_icons/AppIcon-60@2x~car.png | Bin 0 -> 5335 bytes .../monero_ios_icons/AppIcon-60@3x~car.png | Bin 0 -> 7344 bytes .../monero_ios_icons/AppIcon-83.5@2x~ipad.png | Bin 0 -> 6642 bytes .../ios_icons/monero_ios_icons/AppIcon@2x.png | Bin 0 -> 5335 bytes .../monero_ios_icons/AppIcon@2x~ipad.png | Bin 0 -> 5767 bytes .../ios_icons/monero_ios_icons/AppIcon@3x.png | Bin 0 -> 7344 bytes .../AppIcon~ios-marketing.png | Bin 0 -> 69847 bytes .../monero_ios_icons/AppIcon~ipad.png | Bin 0 -> 2851 bytes .../AppIcon.appiconset/Contents.json | 170 ++++++++++-------- .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 880 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 1997 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 3332 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 1399 -> 0 bytes .../Icon-App-29x29@2x 1.png | Bin 3203 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 3203 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 5030 -> 0 bytes .../Icon-App-40x40@1x 1.png | Bin 1997 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 1997 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 7194 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 4305 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 9427 -> 0 bytes .../Icon-App-83.5x83.5@2x.png | Bin 10439 -> 0 bytes lib/entities/provider_types.dart | 11 +- .../backup/edit_backup_password_page.dart | 5 +- lib/view_model/buy/buy_sell_view_model.dart | 9 +- scripts/ios/app_icon.sh | 107 +++++++++-- 61 files changed, 218 insertions(+), 109 deletions(-) create mode 100644 assets/images/ios_icons/cakewallet_ios_icons/AppIcon-20@2x.png create mode 100644 assets/images/ios_icons/cakewallet_ios_icons/AppIcon-20@2x~ipad.png create mode 100644 assets/images/ios_icons/cakewallet_ios_icons/AppIcon-20@3x.png create mode 100644 assets/images/ios_icons/cakewallet_ios_icons/AppIcon-20~ipad.png create mode 100644 assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29.png create mode 100644 assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29@2x.png create mode 100644 assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29@2x~ipad.png create mode 100644 assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29@3x.png create mode 100644 assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29~ipad.png create mode 100644 assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40@2x.png create mode 100644 assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40@2x~ipad.png create mode 100644 assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40@3x.png create mode 100644 assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40~ipad.png create mode 100644 assets/images/ios_icons/cakewallet_ios_icons/AppIcon-60@2x~car.png create mode 100644 assets/images/ios_icons/cakewallet_ios_icons/AppIcon-60@3x~car.png create mode 100644 assets/images/ios_icons/cakewallet_ios_icons/AppIcon-83.5@2x~ipad.png create mode 100644 assets/images/ios_icons/cakewallet_ios_icons/AppIcon@2x.png create mode 100644 assets/images/ios_icons/cakewallet_ios_icons/AppIcon@2x~ipad.png create mode 100644 assets/images/ios_icons/cakewallet_ios_icons/AppIcon@3x.png create mode 100644 assets/images/ios_icons/cakewallet_ios_icons/AppIcon~ios-marketing.png create mode 100644 assets/images/ios_icons/cakewallet_ios_icons/AppIcon~ipad.png create mode 100644 assets/images/ios_icons/monero_ios_icons/AppIcon-20@2x.png create mode 100644 assets/images/ios_icons/monero_ios_icons/AppIcon-20@2x~ipad.png create mode 100644 assets/images/ios_icons/monero_ios_icons/AppIcon-20@3x.png create mode 100644 assets/images/ios_icons/monero_ios_icons/AppIcon-20~ipad.png create mode 100644 assets/images/ios_icons/monero_ios_icons/AppIcon-29.png create mode 100644 assets/images/ios_icons/monero_ios_icons/AppIcon-29@2x.png create mode 100644 assets/images/ios_icons/monero_ios_icons/AppIcon-29@2x~ipad.png create mode 100644 assets/images/ios_icons/monero_ios_icons/AppIcon-29@3x.png create mode 100644 assets/images/ios_icons/monero_ios_icons/AppIcon-29~ipad.png create mode 100644 assets/images/ios_icons/monero_ios_icons/AppIcon-40@2x.png create mode 100644 assets/images/ios_icons/monero_ios_icons/AppIcon-40@2x~ipad.png create mode 100644 assets/images/ios_icons/monero_ios_icons/AppIcon-40@3x.png create mode 100644 assets/images/ios_icons/monero_ios_icons/AppIcon-40~ipad.png create mode 100644 assets/images/ios_icons/monero_ios_icons/AppIcon-60@2x~car.png create mode 100644 assets/images/ios_icons/monero_ios_icons/AppIcon-60@3x~car.png create mode 100644 assets/images/ios_icons/monero_ios_icons/AppIcon-83.5@2x~ipad.png create mode 100644 assets/images/ios_icons/monero_ios_icons/AppIcon@2x.png create mode 100644 assets/images/ios_icons/monero_ios_icons/AppIcon@2x~ipad.png create mode 100644 assets/images/ios_icons/monero_ios_icons/AppIcon@3x.png create mode 100644 assets/images/ios_icons/monero_ios_icons/AppIcon~ios-marketing.png create mode 100644 assets/images/ios_icons/monero_ios_icons/AppIcon~ipad.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x 1.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x 1.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/.gitignore b/.gitignore index a7619995e..84a7ecdcd 100644 --- a/.gitignore +++ b/.gitignore @@ -141,9 +141,28 @@ lib/wownero/wownero.dart lib/zano/zano.dart lib/decred/decred.dart -ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@3x.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x~ipad.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@3x.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20~ipad.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29~ipad.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40~ipad.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60@2x~car.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60@3x~car.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5@2x~ipad.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon~ipad.png + ios/Runner/Info.plist android/app/src/main/res/mipmap-* android/app/src/main/res/drawable/ic_launcher.png diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-20@2x.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3fd15f3ce06096490a907c6d5530d61ed376168b GIT binary patch literal 1423 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 literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29.png new file mode 100644 index 0000000000000000000000000000000000000000..219f2c6bedded2215d71974204c9171d1cc74b05 GIT binary patch literal 1019 zcmVPx&wMj%lR7gwhmRn35RUF2DXJ%l5(qdy(kP2*|$N~xln;KiSV13bsXySvHG;L@r zm%@V5O4S5yG$O3F#NN;}ZPquc#E6)~bA+a88vk<>x1j%}<2TJ-JsPU2G8zaHyLb_bqRcw(e+3;2 zjxjjkWuSkM{%iU@Kuka_ZMn(dp zSd%E;T!`)N-yxycJhbQHB~Jg?N#{>L({-+!(UDOmG>wVBCJ;iPU$QKdnr5Yj|?r&=5@P|i@!a6{bBxFgRe9DtgNs>uPNy1*ffxNgj7(GWQ`yk4aTyxOs z?K?Qp@h#|zrc@aqAt6mc(^jNXRr(|k-1a?;dFZ#+;!z%C^&PY4f6gFPmDg~*NTz;SI`<*O>Cq>ahge}z3gS&u9HZTfGLZOHz8rm%AmIT z1#-rYA=Ck`X1~s{kv#f>3ib?GzQ`oBvb5TOA}}n~73v=;)@15xx3Eh68m@QYThoNo zmqqU=*wcboJub*EGB>B&OR8|YPW)r9B#2NVsj1q`M%xYWcF~{z5>D@Z{NjU6iC)&7 zZbY_3!!x_hLCZ_*xZ97Pla&f0tjE7IJo+->46Up(X?kDp-^ZhOqM7MZ#Fu} zPfz9^_uD9Ezd}$q(?9OYjXB-3>qQDKw4n8#L0J}$W#=c8md>Ma z!BD-U4sNJ_k55}#jWF%qwT(y5w?JTs*f-h`i5X!XH0D)0UCle_rJ>$I+vi{6*s+y@ z;8~*9Z$!y{#0Xh9ZNX5z=+Mv*%?Cf?q1;R!&dorIw<7e^hS|b`>b3O~0-8qb;@OWQ p^Nk^F=zIki^8XfeAugHj^EWyUoVgvENT&b*002ovPDHLkV1oUX=;r_c literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29@2x.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ae94bb0acfd4501055037a4d40cc5d115c77ba05 GIT binary patch literal 2551 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 literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29~ipad.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29~ipad.png new file mode 100644 index 0000000000000000000000000000000000000000..219f2c6bedded2215d71974204c9171d1cc74b05 GIT binary patch literal 1019 zcmVPx&wMj%lR7gwhmRn35RUF2DXJ%l5(qdy(kP2*|$N~xln;KiSV13bsXySvHG;L@r zm%@V5O4S5yG$O3F#NN;}ZPquc#E6)~bA+a88vk<>x1j%}<2TJ-JsPU2G8zaHyLb_bqRcw(e+3;2 zjxjjkWuSkM{%iU@Kuka_ZMn(dp zSd%E;T!`)N-yxycJhbQHB~Jg?N#{>L({-+!(UDOmG>wVBCJ;iPU$QKdnr5Yj|?r&=5@P|i@!a6{bBxFgRe9DtgNs>uPNy1*ffxNgj7(GWQ`yk4aTyxOs z?K?Qp@h#|zrc@aqAt6mc(^jNXRr(|k-1a?;dFZ#+;!z%C^&PY4f6gFPmDg~*NTz;SI`<*O>Cq>ahge}z3gS&u9HZTfGLZOHz8rm%AmIT z1#-rYA=Ck`X1~s{kv#f>3ib?GzQ`oBvb5TOA}}n~73v=;)@15xx3Eh68m@QYThoNo zmqqU=*wcboJub*EGB>B&OR8|YPW)r9B#2NVsj1q`M%xYWcF~{z5>D@Z{NjU6iC)&7 zZbY_3!!x_hLCZ_*xZ97Pla&f0tjE7IJo+->46Up(X?kDp-^ZhOqM7MZ#Fu} zPfz9^_uD9Ezd}$q(?9OYjXB-3>qQDKw4n8#L0J}$W#=c8md>Ma z!BD-U4sNJ_k55}#jWF%qwT(y5w?JTs*f-h`i5X!XH0D)0UCle_rJ>$I+vi{6*s+y@ z;8~*9Z$!y{#0Xh9ZNX5z=+Mv*%?Cf?q1;R!&dorIw<7e^hS|b`>b3O~0-8qb;@OWQ p^Nk^F=zIki^8XfeAugHj^EWyUoVgvENT&b*002ovPDHLkV1oUX=;r_c literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40@2x.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9fdb32376336d6dc44d2f540d9d28d8524a44aa9 GIT binary patch literal 3574 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 literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40@2x~ipad.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40@2x~ipad.png new file mode 100644 index 0000000000000000000000000000000000000000..9fdb32376336d6dc44d2f540d9d28d8524a44aa9 GIT binary patch literal 3574 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 literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40@3x.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..485f8b37bf0a75e7fb71f262f83e640265cb6139 GIT binary patch literal 6659 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 Ilx^ew2igxFT>t<8 literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40~ipad.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40~ipad.png new file mode 100644 index 0000000000000000000000000000000000000000..3fd15f3ce06096490a907c6d5530d61ed376168b GIT binary patch literal 1423 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 Ilx^ew2igxFT>t<8 literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-60@3x~car.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-60@3x~car.png new file mode 100644 index 0000000000000000000000000000000000000000..50148e6dc40514f61dad778f2653742dd97ab8ca GIT binary patch literal 10558 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 LQL0?R(EtAc_KYo8 literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-83.5@2x~ipad.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-83.5@2x~ipad.png new file mode 100644 index 0000000000000000000000000000000000000000..8f290ada2fab570db973b01bd467b25c0915db14 GIT binary patch literal 9576 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 Ilx^ew2igxFT>t<8 literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon@2x~ipad.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon@2x~ipad.png new file mode 100644 index 0000000000000000000000000000000000000000..b11d7332ffe8d094f423631577a70a98dbcf86c7 GIT binary patch literal 8002 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 LQL0?R(EtAc_KYo8 literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon~ios-marketing.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon~ios-marketing.png new file mode 100644 index 0000000000000000000000000000000000000000..1fce95553811b72e262f3feeb026cd0be9065e00 GIT binary patch literal 205464 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 z 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

PZI5s~&LsaKUs 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 z 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ΞqPe1631Wm4K=4jLv+Hsp)9Q;g(iw@?_cMoR?{ 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 dC2QiRd*C_4Hc?{w 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 Q0Q|XrMeB0@MRd^r0MQ%pBLDyZ literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon~ipad.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon~ipad.png new file mode 100644 index 0000000000000000000000000000000000000000..7d4a821865fe4189388de8e012999390129084e9 GIT binary patch literal 3344 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 V@m2BQ%0I=SGcqvOuhm6I{|9}uN2&k- literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-20@2x.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7ea540cafffb11352037f5f7b95ea6a25c3b637d GIT binary patch literal 1474 zcmV;z1wHzSP)Px)d`Uz>R9Hvtm|JWVRT#&AXJ*@N%l2XmYzu4=ixpH-D6ND7HA;XeN;KYrQDclG zia}_nMuQKCi5gE9ilD8^N_P0p>>H)V-^J{Q(lHk!y!TK9D0=jqDm;u`c;EgJi9d#z% zhWCwzR!&KPSEp&L&ejkfZjTA@mo~`*#|`THJUltdlDpCcU*zeGk9S|_8SvW+lI6b| zuBnU_Tcy6HSSQQxtfeQw(iX|`a${5}b%V}HgtGZQ(&C&~MFniW)Slm$ccR-OR#H}j>^!<4#}tW_JB5r zEmsT^<6(VBv;L}#C~J~6QUGsX$NY?PE-@aEPu0UM_ZZb^y_(h+dp5rboGBR9V- zAo#sVr)dB-RhfkR&|$y@moCo>z==*s_QBTa8VNC+%Z`A_`@(ca$ng8Q8XKl*d{QSl z+Ahft2v&y#uT+>^43A88L5kqH$%1G9u(Po)B*;(pu3|*M#(qigK;-&6y+CJXKv?RX z@52IImo2Q9d8fvtq5m@E{8Yg+lZ18V4goI95UdQ25%AA0$&8;O1NJV_@oR#YDokq_ z4*{xTXdaMkJ142@gLx@7oa%CQZ3u8~vS4j!jDX718i)+SfRnvljv zw`%m8_E1(0snVzbRcBA}!`hI>YgHx#5|*XI%1q4~(kkMFeX@8B$1Yw{X0We$g)$B= zh6JG))pIoq>f-(7246QtKFXBMI#Hxsjz!hi)>^x13~8ra2)i~HqzazO6xRRBdc_N@ zIqEfB?N)iaWbR>SyA_OwUAODpGuV}CSPwZGrLyYgM9qeNui9iwwcY9a3UubAc!P8W zG$kn0u>G8K{*+3^HwYfj(k#$P!%XYxPRUn|l06NQlbx3{q0}##qf?L?9WturSEHiv zuwkXgOQ;WarQF2Q&>dcO!WK=-4L5Cs9J7iGfU|zj|M_Qxwt5~Fy<6^-) z0jh^L_e%6c(hR}geBJeD5zl}YfsWgIta0v$d$F zN=$%iL)AEYni_@%07*qoM6N<$g2tP~&Hw-a literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-20@2x~ipad.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-20@2x~ipad.png new file mode 100644 index 0000000000000000000000000000000000000000..7ea540cafffb11352037f5f7b95ea6a25c3b637d GIT binary patch literal 1474 zcmV;z1wHzSP)Px)d`Uz>R9Hvtm|JWVRT#&AXJ*@N%l2XmYzu4=ixpH-D6ND7HA;XeN;KYrQDclG zia}_nMuQKCi5gE9ilD8^N_P0p>>H)V-^J{Q(lHk!y!TK9D0=jqDm;u`c;EgJi9d#z% zhWCwzR!&KPSEp&L&ejkfZjTA@mo~`*#|`THJUltdlDpCcU*zeGk9S|_8SvW+lI6b| zuBnU_Tcy6HSSQQxtfeQw(iX|`a${5}b%V}HgtGZQ(&C&~MFniW)Slm$ccR-OR#H}j>^!<4#}tW_JB5r zEmsT^<6(VBv;L}#C~J~6QUGsX$NY?PE-@aEPu0UM_ZZb^y_(h+dp5rboGBR9V- zAo#sVr)dB-RhfkR&|$y@moCo>z==*s_QBTa8VNC+%Z`A_`@(ca$ng8Q8XKl*d{QSl z+Ahft2v&y#uT+>^43A88L5kqH$%1G9u(Po)B*;(pu3|*M#(qigK;-&6y+CJXKv?RX z@52IImo2Q9d8fvtq5m@E{8Yg+lZ18V4goI95UdQ25%AA0$&8;O1NJV_@oR#YDokq_ z4*{xTXdaMkJ142@gLx@7oa%CQZ3u8~vS4j!jDX718i)+SfRnvljv zw`%m8_E1(0snVzbRcBA}!`hI>YgHx#5|*XI%1q4~(kkMFeX@8B$1Yw{X0We$g)$B= zh6JG))pIoq>f-(7246QtKFXBMI#Hxsjz!hi)>^x13~8ra2)i~HqzazO6xRRBdc_N@ zIqEfB?N)iaWbR>SyA_OwUAODpGuV}CSPwZGrLyYgM9qeNui9iwwcY9a3UubAc!P8W zG$kn0u>G8K{*+3^HwYfj(k#$P!%XYxPRUn|l06NQlbx3{q0}##qf?L?9WturSEHiv zuwkXgOQ;WarQF2Q&>dcO!WK=-4L5Cs9J7iGfU|zj|M_Qxwt5~Fy<6^-) z0jh^L_e%6c(hR}geBJeD5zl}YfsWgIta0v$d$F zN=$%iL)AEYni_@%07*qoM6N<$g2tP~&Hw-a literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-20@3x.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ac773754c72eb5cff386973eed6bc86f7a60069 GIT binary patch literal 2597 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 Hu0mjfMS$yI literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-20~ipad.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-20~ipad.png new file mode 100644 index 0000000000000000000000000000000000000000..57864a9b3fd5a2a30c37f82469674d611f992ae0 GIT binary patch literal 688 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 literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-29~ipad.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-29~ipad.png new file mode 100644 index 0000000000000000000000000000000000000000..27f817dfceacb3069b316a69f4899f09f1ecf27f GIT binary patch literal 1112 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 AumAu6 literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-40@2x~ipad.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-40@2x~ipad.png new file mode 100644 index 0000000000000000000000000000000000000000..963612d0c5c3a4280835e1a4474a886a819af823 GIT binary patch literal 3006 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 AumAu6 literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-40@3x.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-40@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..b6da404cb74011d7f2c549454b9cc8584451d980 GIT binary patch literal 5335 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 literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-60@2x~car.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-60@2x~car.png new file mode 100644 index 0000000000000000000000000000000000000000..b6da404cb74011d7f2c549454b9cc8584451d980 GIT binary patch literal 5335 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 C-#aJ( literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-83.5@2x~ipad.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-83.5@2x~ipad.png new file mode 100644 index 0000000000000000000000000000000000000000..21aa1246316e0f95584e41668b3a5eec1567b746 GIT binary patch literal 6642 zcmcIp_d6R7&?jc2M$MQlT52{{#U53=Mv1+*S}{Y7qKcx_H>eS-_NHcp3W{1m)rc9p zYVWPD_kVcbAMWnC=bpP??zwwD_qmUHI%%oWrAuf+a>`4bESrgl3}3LPWHVN`UzCuKd2lA1IA!#yUDxxulOIuvR-~w4f-fQ zZtAu46xY?^ucuIH%)I&?)G6A^n||XFFTBli zBHg6RL0M#eLq?y68mcv1gay|7!wP|IOo)9G)y^zUQ1uI)oG0 zH>pcRUHw{d@~a@l>{vpHe(%2eWgnX94GU{C=<5;iV|zu-vn4V5rO{yjG@RyJ2-Zav zqwxi6?kJrzQ)z#q^ym}nTC#GuK0Mw`Ah6*}T;iu|4cD1&so*5cdshFUMP5dN^^@#(|?+Fv?g zB3(cTA+JRC4^>4U0(t6y)aw^^CE2KEiO7og8=6keFld2J&qx4rHrcz1cIbb$vAm*y zFuNSASw-YNo6-|1*qGy&5Ps+B6A{TLr~FI$a2$X|Pr3&B)I&H&yBsaAcKsVNNpKj+ zf^VM}^}erPv0!g(@qeK#w~borLBF5owr+Xp&qiakLZJy>+*8WGn_<;*k71j@`B)I} z@x=q1(ed$i=$uKOWHfkr#!bhF{tE>OcsSpoo;3gQS1ZPEs>&8TbpX*Y^^PM!@`g}~x zii^xfvDzGJ-_+-Gt|Yd(bImv0p4|S#&OK93^??t*yZ>kHZIu_5-n5F+tg7OQJ*!nK z=9HbJiJdSE@_66l1E%Q>2%hogn6CbP%h*3g%J3rY7r(4x5kM@rG}u82^fG2n@0HKq zpLEJ=cre4ph5-JbxrAQAsF+M&Sd0Wntv2C>WtbwqeFtE@?a$j9r%?DTItJW^3Sl&8 zb4oim9~a zEKm@Q7f(AvYev|{i`&N`G+br5e{Q43Os z@A*CnxDKeYpLu-lbI4gV>kR$CuZnW#8?K}%xppm*=2$45l@}RQc%DlAUkHDV9BGcZ zj*~BItzsftVTYBlW3SlGj#p}#*}l^(#Kh*;U-d9vfgC&*hcV_>e!AajD9nKDxHjru zgqtzjiJ||STkLDl+rQYSVF_a0vZ zF;PJ9lOgeI7hMK~bj42%Q7zV^K+iJ9r*Z&r*$YRIYMMPTV?#rGP%!UR_s^bhSX{s5 z@LEB}(CJ~Sbf8C3wVw+;E;X3>G%dkO?mYs1I2JDvVz87wvAX%Qz8z|@AYT_OCi5no zGKOT5Ee5e*OP_#?Q&LeF2 zzXAL$gH8PIEA3nUbE6qX(~CB5*EKKK!!i3W7a$TS?hWb6nonC~@$cOC6wb)I?OOR7 z3LCyQ6wBWKDSq(7M>KcLx=G44_^&ZH!}S=R23rSO8!qUU9PlrQi~iDu3-WO$x$&hp zcRsUfl495kVAolfcyr}AJuTZJG)@);dl~FDZaKyoq2NJ>H`5_q!rjI?VJO+@rbYTk zBZz14(#UrU<@SdxXA)TA@}y%!p|#@cz%Q6SGQS~v)z_i`aW)8k3FRa*HQf3Hpbyu?dfXI5}89kR0^R`r#CMH9Bk)tj>jMi+F zthLxHUvLUvSl_;J4URL|Zb>Ag)4gj|rlX;3{F5Ka-v`e^GAT2v>(Dx{JO46o@>F;B z547HV5Ac$AzFyn5e^U2JKo$ijM3#cjijRdq zMKs!fTUKQrOA6Xkou80^acPQPQN0WM(gcO&y!=;%J94-{yTPok+M`{Ib}Khqx{wmu zJ*HL5H4-JM@eO>>rUi**XXGw^@|$8DWGo^O>H)b-Izf3Vl@Wzof3oxsMy@7}2n{5s zy!!hZi5b}lOt=kr@;_`Z(yOXlg`O+~i|fOtG#0G*G}Z!9fQ{mA!2CL@WV1{TI){^J znpjO*JIgvwhemn{+^wrdHA%GnW)*)5uZo5FLWf4*DM%u1ck^prNmBRfo1_l{ux}2> zKPGl7C7Jiz*Nz4E(`*GV@-^{E=EQ+1X1O%j+gA+xzOr2@osl_XfR=BT4&%Oyz0q=I6U}dG=n&`kSXn!=ZuqfSMHG zK4~FSS6RrpNW6xd&KC<=2(EW#=j`in+1nBUm}vX`@u3#ZA)$+-$+~&9t%`6yk;%-l z(+Q~Xd&QhNe%bzV*~d)hx!6ALlPZi(g;Do`0tB*_N2fI%d>Yy!|$4keI$tjYJL^D;GezfMuiJ@G(Tkg}()zwlT9d4PH zTE<=YrMsN>QyivDO%?Q=cAUj(S0+zdH|g2k%p5NFzG!{eky#12Qp=KTm4f*PpAail zF+eW9-`in8a++$sm|b}k1^ZnnLdzFyG(F}2R5o)+EW%*0h9zjxlSyPkS~;khg~)_ZMsmPEdp zX3n-LA5X6_y&bAH;Ol+fmW+W6%Z=k@e+%zuDU0d>ZvQ=maVF? zTR)tkhoO$7yt%ban)nNkviTn0Pa1IjwjWeROAhXxDW3c@8YZv_3!-FQ2-U{?mY=pz zz``e$4ksM5sdvrzLX%aq;l(fz$tK&H(L(y_xqPaD6nqUKedBjzkV{+D)xYP{hs{P? z_jp5UOv;!w^s^bAbj-zgPT6`HEsP7z&92NoAVR+u$m0-?FGWzj4O{_u+2*21fmsq* zFWc1k7nk(rkswG$KD__uG%StnL5o*GW59F-f_;PKc(aNtzd?Eo@vtI8ej5|yLj~K= zn9OTF?1o*5C7NTytUuJvbV)zMCFbUo*kJkUmKej|XZIJ%yqPON$oV*9@ksi>$d;ZnmRBD> zwu&uJ#t$Y=M~G<=TFK#dS+^T{aU;^6QkL(z1zlk}y|{TP!{A5iq^Yx+sI| z74u~x>86)IIys)DJ`me`Dd7D(>sl(HXaXF^{hI*|)7e_FR%=^HtA}}ldW5yNv_-lrz3Uv- zIU?W&Hd4IRV`IEzEk(Z9#FTzw?Iu1U2$dmHcA1Nh&O!Iq>t~6D2aNXzpZ9n6j|B?V zBI~Mt=8@P|`f*N;x&{H)dvG*DWwC5BX32qR2$*OVt5?9n{FB??LwvW%_Jo54nM57W zQBh5m%J>hz)Y-opV)`o|>5{O1o#uboY}oBip%!hF_wkImzJhrOa*ZkejbR?mj!Z{BzP~Y0f@{G{bW*0kZK(B~E(uc7&k(G+ zd>G;M7RH8!5Bgi627(~WfAmCzDPApae`O{dQ4bWWqi{YpdR*Uu^Dg!Mbn(XYhF7b< ztN$RdR_|pW1YS1O+qu;tLsgQMA%80Zi8?shh84WORz+dhDAdXVmJ;OO1&Wb$?9wa_ z)87aRt>LEL>Zo?>?yT?0Y9@B~ry6#TbXC*y*hpcR-`k-PX+|pHV1&xjs_&-L zrH9hBWk_XNTLAjM4~R7f@{(-+UZ<7i(?!dO`uQ)m+X>zy(y8q0DR1%Hg5WQ2L%Rff zs2Q`o;;J`ku689YH4ga0VR3sC#4U|^w|qGU7lV%L%!rlFi;{98;Qvk}6@0 zeM{n%{l+YlzYTu$e~d7fo56k6bAqW}{eWod95fQexQYaIIXp>)3qrC%Vy3y9K%}$q z1h|jHnwRV%n~>9DGwy1pK+MzFjc$iDeZYodG}~;%xi=&Sw2P11WOqj%n2ji>5RkH zXKQnB-2o8GNCXNmgQY~LiL6vtm+QMv+|wY&s?0pVY|Tn_WTUIl;0sAG(vOEt@ZhQoHz?3<@uKnzHPLVoXH-5w~%g-&5{V4Z(5Z< z=Q(tq*>Uowz3@^1T<4qU{;@OTPyHO{Y?)qzx64X<&amfy$XwmI82~Uf8-bIRwp4~* zH}WPK=gv5J@$M8V(*`})EehLMF;A!pn&MiP*-&$(RKfAxNgm#1mo{1n;M%_PVZwpQOc$kD&pAYgF=| z@HynQSfEtk^Y&NBt-4&~fHR${Cmt zkLJ);ecV}d^Prbx{~E*it0la#Y6^ou7K1o$N9f#<2r~Ab*n5jfqfQ7yI!6?crky1l z;cK6&m~@fJ3*1m$E>Pmf4mUd~F$besltGf}!#5Z#dQoF(A}&k{BPGP$(^{$7 zNNUlj*U)dyqSo^+>c98el4L4YJv2ahK-s+P z4xPW*Sup8*W;Wh!LtTBy4Y2>>GOfo@#xHtz2*1eW`%XoCa9^hyGGbk9yY9JX28iRf_Hz~JoExq&&~hj zvhCA!+bD-n^kAVwpt&lkSsQ&yw<;rI(>O#FhA~pB!6e6Jz0BHipgOCpXQzR>tnL7V;OJLf$#}}B(KSX)%Ejc4c~e$R!qG)E|(n{ ziT&#<7gH}PIBJ{dWCk666Z78%8!yHjXec`-Q}}LylY*v!X3Za zRdxKyKz0y(Y3m_GiENfH_NBz zL!5XX!JK!U)S3J+Ylw>y$@aL_^3T<3re=Wcs+6xal}ELWx){&?>(R!#NK(d=VUFFh z2PVGcCQv>Mo2C{Ofg2T23%dRv`$NEhR(Jq4=YOF!*?U?fysTKHro}rWNQjDy^XJO2 zG~gaC$~z7Gb==jp0QKJ=ghOs%ndgzMZ6!C{RNcYtWd37FqDPhpIO=HjY10@cLEb-iY~AJK_%P95{J3QHg`)Le z`>>J4%>ZOAmMLJ#u$v z0eFWIgEY@;@{{?jqBX4EK*|#Tdt*BXKYM$Gmu2y3iwOTdt(G`0vbj_^&;+HZ^$e~f zJ4;+u?438F(OWp_$gh9dk^W-%hy=MUW2tS=y=4s21xMNl>adGL8g%P@fupL79R22~AY?fXFceavQ?!i#2CNCDO!-af2I6;$&I_ zt$BLku-EJ|Zc*Mts3B+6C!`Xezpmrzgh9KZ#omFL*bKLISAXxr$#9A^*YjV>FPnas zJqykK;ZigbQwGZ3*x@NXCBI_S35Z z#$)z(S00zQ>S{xTSSwX%CFJ$ue3rKMy57(Idq%7mvzX6*D z@gYqFLsgMCcc>lH9bG#RivE3u_7|i0K}jq0=g8cj+e4d&B8i)9{qWS0{DZtEBr7eg z{(0;WzfT`E%L4zPw!jP@Yasd^dBG8VfnsIuGI&Hx*#87V9Bytave;SEa`SkDwrf^p1<}uyhG80G18kV%vFYj$Tu~r zexeBQm^UZ+05-|-+e?6WL5L|GtqX$p2g&jyU};Xc%w!?@1h4M$u1(9fJEosBRB*M2 z*uKV#?x`HDX>PG-d>z9d=EyM!G5EpuyH$a7i#)qP-`r=9tNgB~odJ+nr13JLJdyKI z?u6Ftp&zorx~qR<>6AekiV0HlBX5er-86VI8Vwd^J_ZCG3=v?7RkKZYs~=2s$I#EX-LnrQ~+LdSWaQ4(jCExD@-m%6rlCKjsTea*#JvEiZ4H|iy z-!C-@N@{+mntr0h4+(}|l{XV2zrQwil6W32R)4%xu|NvD)U?c)m?ppu{c5gx%Zk|) s@OLz_CxVD+Eq?#&@&Ax~`j0SJ6Px}kx4{BRCr$PT?v#_)tUb8tL~;3dI5Teh6b7j4Cp9C31N~MO-wX837Qd=9LK1L zM#gbB?xO@FNgyJV(L^GesKI11MorWi9pfZ83L*xCW)&KG-{_rQ>)o91K5D40rK`I8 zzN*(n{pWB{y5D>E-S7SP{>%6OS4b%(;u5nnAmRYWEVk;!;sD1szHxwK9N##=ae!le z24ejE;(Kr$;258Q7=OLE2FC%8@fnEm*Nba#9N-wAff#?inAhMw33R!EZWHJ+CHhRD zUjl9b-cfj$01^a{D1anGAlU$tUBDOv5Tp1bh$(=XHRcIE$KhldwE(*%@JWZn zEAeNGmzIM=}6juS8(x{i-JfVr%0ZZ@%|Tw-&x zM5i0zJ{t{Dd$vKX25v7la7n&_REMebr~sJS+S(6nX_5G0xrzU6mN26PYOoK9xXw-k z{-xN!1=(D_g)-WrEsh457}oblY;KWwu1eyK#*>Cov?ngu^J$HrlL=fq(ZJu27bs2@ zQMJMy3oteGkH;ils4?++gG61gQy~pJF1lCePY}4WK;SFc2BKwsI}+fgUWv8k68~9m z;=pl$OY~?IuFwTzfQxelZk}vl_Snd4v*Q5X(kgM+J`-D8r3y~Of+U?V#8#KOa6wLF zH)h8G++za2tut}&eiKK!V*ytGcc9U=vc$kG#fFNMMM4|_F#XpJm2`bfH1$Q|wWIe8 z>hHG-1s<4gU|Na*jWRnW>Hr_@Q6axPO{~&t}@|PDK^q zvQCKy4wxz$HChd+P+$i8Ig=U-vIJJmFz|Us!iIaJ5Z6Tw;N2Y(ckVOsR+IC6Tbt%y zg*Q)21KysWfV?sGJS8dsb6LJ+uZdUdrLA#ed!|g^T?z2r#RC6Y;sTd#J0#)&GlloS zK@-nZp4#B1yMXJ91n!yU!UP*rkP!u#e(l2tEo(Vi2?9H`WT^MdNC1{i61bz-P%&#e zC1L>6KwejFD(z(k=u{Q@!nYL*+%m;Lfz9A@jw1%}%XJdVcDtkQ=VrKof@B~o!J_3* ze|k@3tC-k0)^A^Els@l+(*5lD6-=#EX;1%+1%b1-?~i;B#4G*cK{%^SzxC&sR&lTrU+3=(v066d-cF?wTra zd$CLLz9K2M0!%~dH?^ip9#r+%`&zV0=cWr>lq0aTKwxr;UgM>q*P;h)s*#E+#PDi3 zn$3^xJ4FJw78{tr=thLaR)9Y}E^%X-70j?x&dLBT%QNteJb@Xh_VS~e`Xqi=C-G9P z#Q(Kel%ZfU)@2z~H%&H>n-qC)Z8fsEpFMHJ#C-=$+tPlVz^f-3_@_dFGt!0PM@2&T zZv1!&^1mN71CkM_tPht6EH5%JJ_?%anFrf6xV9Hq@P5B-slUm|v`AgJBu|9RmLN-{ zu}`AD7id-7Xp8*L`Oon1p%cU3v&paiphe>P-EJK2I{9hQ4){T-z_pVizvudX;I;w$ z-F+rjm)nybnwciBZkB=bvj=BLaATlRL`!&UyTtBJ>;DH6x=zzmfO+Wx^D_j-CWPib zH}?Tw+wN9i6Wqs21#T>|FZ?)^Rmv8Ck8}ZZ-s@Kt1iIyK(goJdHk8{FOt>%B_W|#> zNNj7DI5x~2Gr>ADU0`7*P@F7~o)El@rLr5i|9~6(__*gXTIB@M4luRmj(sN9+MvCx zC|Ez+z{0G+;P#q8dAG!tHsFKS!406HOnxdY^fX{Wrod@wBB&qB4~y?l+rTNJ9bg(C z7kuPaWI3HOXDsmKX)Y`ZqPg_9H@8X^ps4PJExhURiNO4HfpfVd4*s zdcu2r4Z4r_PB(C8u>mfXzQT%!wMR@;^w?XIxO?4*z(qUirC!9&1nhab&(Mn=CQM3WfSS>kL&sFQuUPAiKJb?{! zTz)PG_jhgse{6OJIi3dKvO)uy4sneJY$v+9&F*NIShmZ=hi!TQW_0PT`7V5M{6Otx zH0q;vi5F@mTKb)#GaTVpCkXr_jX-A%>HkNeO@rTTWX{*E$~<)lC+xL(F254YwtnD= zN)rdY+^{ScFeMe((XQjkq3@wuJR|~TUSpsO_3Z+IxnrGWa%lmWjZL1aFmdx4!qWQavU7#*%<=gF0_~; zP7y5tGstk~em8zvp=Wt!#eCVK1V5jbl@AXcGF7s`TL|##83rnQBz{w8#hg72R&-4? z0c1~(YYGIWBnt(uH^6)jHG65k0K=(H(PJ$D)1qCn!>#Ihb;w;)4XiHpSA##dNc=p2 z5$6Gz&py)4vQw#o9(qP&1v83MfF-#Cv(f|@5)A|}N6ebwujdG`mxmKX3&2&~z@jbv zDA(YRlDqWAJQu#~vpmzdT2*f1Q?Im~X9B$e=D$_-NNlPj^J3&vOmL^A0GH%g;BtJy z0Or$}iMqDXP=OF9h!%hk9+x<6vs(-2syG>V>$5Ju;6X*V#H#YZZjmp*oLFX%eo;%J z-^ix9C>gjiPoOmQWF_EW0JB2jrbz;)N1@m|(3@%j_)o_qzWAY@+~8NT1)iPbQv02^ zywfc4Vr}3$ra%DmTDnosR9i9X;Ye131r{S!-h1j`0P{JEbA%%O#Y_{HmugI0zEh9w zdP9-G_e)*i)lIr61zHw7V)CNw0xq)@$!{W0Fx+iokOf? zEgJljqb64D(F5?qC59^L@J=i+47%p1iL%bXdN>%s-iFsFouSi+gp+h2TEkqH14$@= zdHvl}jEGZYZCj$hf5^nW2lN2U!~%(G-aNIAeu*^|ascni`(;7_%-=oQE%6^Gtm|OQ zwa1{kG*@8uAX%bN0F%P|=yXFdK%y;LH29$dCLTGY2jFMskR@ph@PQlpB-U3*91UWn zhXXJty1v&6aqbARC)2>YEKjI@l!LWa2n8^&T|2`Wv1u&;KX|~zN=*PiJJ-Nfg~kA* zrmk1w-zuca`390e0Mj3415sZ?hlbb2YSRzo+$7-I0)JjL$@>iCkcQ)_;SB{a$M|@u z;b%ugLz^83@Wwe7z~0Oa1#qQA#Sk?3bv7vNk+`zJzkeh(`T1JQ@S=asEYONc{stAn zWtY9W9*K){PVT9p0OnFpfSr(C(gN_RPfgr^P|p(mli3EYFER#bUQ55k<40xCKuRDD zCcwXIkXV!j1Gyuj{1>8Gb&?@=N_e=AtPvIeDP9OhWv- z%@VIPNE|nTFJui3U=kG`m})qJU8x1&7pqMy{X~xjub3?Gy%{dQ<&D&wt!;th%Yy+- z2nq7%GD#o^-R_s*zI9CE^(L#5kTO(&=cF+}GVmjf#{SD^Y61A2CW-U5>4{`roFnk` z9G7aN?=7!3NW9V*xZP4HfO#L1_s-4$t}Yz7{ChuxD+R%Qvq^$XO^*x};Pb}|d^0!v z5*Rzd*8=eVPKnu@-CFXn3@*Gm-@sI#^3UC!66-3Y3iSI*H~@RDS&(71`t_{`@yoE*<_h4V){b_hD&N*r2dK*;SbA81ZVCI@xIl(`^!UFGq$T3uB z0VjwSfSG{5^kcVjDRjsqC5GzdI1&j4Fz-pm#iA^M3ny47t#I75r~`q^-}eQ0B;$(o zwORls59`*wR+|PLGA~15>jKw^4!Rf&V2;r;ysX4LGgX82$vou&=CZl|sHtePI)wY< z=5t&)J9GHNmrww6oU9&XeK5Q13?`>Mz?_`d8Z1_2sQTM*KY%6q0-Melewki40Eau4 zIKTitYLmETmx-O4S|!n~*|yN-M?+=mkX_;F$_z%V>o5UQY;_1zmzl1?3(aPwu~sR# z3#MZn!?}l+J(v%!>5;f?uZc~ydiG&1&eu$2U6N}+REy8z0Uf`pmDt(ojN^4^#sBjp zp&EQUO|$__SL26=O|1M>U(=OI$-vJ(<5K4kc?&-w_L?ThY|o_UlNug0%>A;;kOrF* zMH|3;IaW82*})=k9YUXVah|}MnFfk{;>BE^KW?+KLTsoK4dE$g-^dj>Z=C(D`G@;{ zS^;JO+R~kR`(jZ;R?aYRLy_UvI)VG~jt+^Zsscy2hC311*FHj!QgQCBbetde)A?A?8QNAjy};N3J(lI3Ha^ z0RE(1;-c;Dp!AyI4wQQw*&d~7{ty#XcJ_I>!AdFGb+`w&42B>HE)UZfS*&fiGcmO5 zv}-U!9oOyB7uE89ov&sK{CK8;SwSlJcqGsJtx}b+lE0-(n+ar25^%;?fxjEa_6>mz zD%Du+L@0DB4M*Ri%b1@Cqcf<_Rtj{a}wQ_OBXle2q^a zKbp@vV=NR$I#?@U)lQ>nt`Q-LbdC)`c7cIr(>zh%gghsG}OgSD(6$?vgJ z1D6*>k6~$>1`|LofjpCt#Q4TxHUtg#L!gz*nqnGZ?5(A{0XKnsuUX;`4OSqBJwn2< z(&<)xcbdiZjz)u0TLC6GwLQRXd)=zFg6_5ozTk6-W`zRxX9giI%n_>1|8VBdsBOP%=^t#?BjAp9AM96G5E1;w?)#6jBRZfOe4E5LEgDA0i-9{AyEUE zFGF_ZiZT=1+fKd1q8UGD)3miSUCLh{ES^3R;G+sK2Tp>(J^QWbP$&ZWNM6)Q*V0|$ zaeOS#Ie&t^E>qM2=ENU5F4d{v&(}z8TxKT$>OB?)VMtuKXPTAn9Bz~?-1lYjAf84cw_8_Dm|kUBkGsK#nM0+)H}XozD0=424$LtPRt*I7rb zZ*2|Sqazv<7wnnT&{?U##koSUIijJp1B^Odr>@vJ$s`UO=#+Tpn8YuurShAdwrta2 znvw!_#?yt_2F}b70Z(ePbDjd-+tC{AJ76*;Ssufq14vQ*uoYUGkdJiI+3ug#G0#>c zXL>LO=18A4)ayqTz}_!Mi+f9p#D*#puhv=pHAwVhTGCFTrt&Cxk_mXsEfe@`{S;~A zDcpA)wE%m+AQ_HtH<@6+E}qlCq&?dovWX~{_mKz#5o`-g2Isg$_K*h79dF>=3G`** zSYSQx91{S0UWUqhtQXjKT!Ov34tJ3Y4K(#xg=st;mB-={be{F(;lb0LVIrP{_f(g4 z(sx#(Rn?F`#wzTdkqXQn3lsz?TN%kmWaqUp1+ec#5%4ZIIn35Ec+_b25K#b2TItq$ zPTEeQu*y>Cx3YC`MuGrOGh-zoe?D3zrU4$Upy(WT9AKTZ>A-u%0d^oLI)@YoSm$gy z@Lq9%9SDlfA;kgKIhzi=R~%pmf}(Rsae#HsrUUO42iSq2=p0fUV4bt+zG|k(87U=~{AC8oq?Y(%ni) z{(S$5_hQbum}lmhn{zRr=(ie*#02yN004kkSxHX&pJ)DG;er1tq>qL5p8>tJ6=eW5 zV~l$M0L>d^xi`B0W=FXJ@APJx(4n_W03fIrOra#hB8(sYrs{1@g{9MhjuUUp`0ER9 znuCFzu=PIUE3@06IZV(|(=3g)O0+bH_}LT^51>>0Yqcp73_^j(QXN?U zYF`6CWZ)ukh@QR$7rH= zT2!hW)ZF-N&G5upKBH^~>Bo4;@bDpz4R9k#1TOf&JttK26Ocj(g{W^tbyBrki0XOS@``!? zg$>55_%BVJVx`YgvjX}-$*B>3b~|kImgO{OdxiMFBtM&z~4fO9N*;=le3i z579N-z%Y!>3?Tz)f`vKraN{V~4qkc3nU)rDXHrvcZ|2m11c?a45*E}xmI2u{$6Y5q z1~URHF`T6YLua|z+wlZ+lMJr0Wxyckq67wm4l1dQ=55o5?gu99ZVBT3UM!mBtB5Bpo3#aqcSPn{NoFuoECLA^&Pc9o%0ciJe!0S7^Fg3b>36vmop* zhb0MIG-IyZvwT8W6XRvRpMOoZQI(FAR2sdgM>?hx$lmDVn3829?X-x87l-;o!3w#C7y<#VEtAK=Yb}jourhEL6 z!08m!4Yd{h!tmlv!CgPJ^CK;#of|LdTBnS~A!>m@?Db+1KM5riUsqH~qEib^xcJ{T zj)@uHski)h4+fp4bdA^fT7*Lq1HTjd5YHX^xIL?^X2#Sz zL^Zwp^n#)_6nU$m%ca0-g$!;sSRY7acetm1wdelFuC>3tP_2Ko3rEFs8MVJlBRp~& z;Q|Zk&_}lq$pr!K6l?7V!WWqX%lJp}5F?s*YhUnaX6g_4?QopjOw3_~sLFpv|nio-I<2Xz$Uq3PRM0VUbw$>YfyAo-bj2E8?xwa*g z(EQC=nlUqFI~`a};++oQ*;s`kmYSculyXj$@ie~i25g5&;dU)@U5$Qgk?8x9VnV5c^Wc}{XF{&O;LQXT*l%m~GY-*Z zIUT}}K4}n}zh#5|=9j9-NGKJ&jKB17XFg2<0V999cV+Bak37RPX2^A)712#wW}(=z z?tumhcL;JveDvZ?n8+~zipb$+? z$PS)wNhef0I=OeK4(`8pQLZewN?!9*y z5#HS>PWiEA*2*ialbOA1pYeeZjy-3;h7(Wfx`#h~k@D`U_H7|js9%=dHw6Rc&Gi)) zj%tWh9k4Gx5C0 z$ydRRgt0*n59W+C!uvT!_4BR#Us6(8L92L{(U-W^QErrF=kR7KHE-e`M=TyUJj>X%#B%Xy9exp-9WdnHyzL}1&1^+GozVHN@bAxu4M_?S$8{_WBa~$IRR>3&S3vtA z6{z%Lt~f6&K`*EyaKMgS3Feh%1=b(vodn@@a0 zd&z zhhGs-fR8_(lwi!Gs%_ve9pfo1!-^2$8tSskNk%&^ zN_qmx+V$8OFeo5uV8v+~*RUa^;g0MSAtIHLE06=&Zi(m?MnSNK454}r7w2T{pH28B z8Wk+V;xD{m@}h2`Y#|NI=MycbUcdJG@DUOMfu0CN@5IqP)RB7je&OZQaETO9aiZ%d z!*;X)7i94Dc`6#CB(nsnOB7Ct?pU(g_z&$4f-K6R?ivzxMdfX$-rrlK4D4_l)*ToeAW|ds z=;57vQ)A;zuZSc}sy8}oKwLv`DHx8kFQOO8{3N_yeT}|q`!&pQn^vkZnT%E$WxX$VElyCD8RDLK*iD=EmVMhoR;)W=hc}iO7qWPUJPN-lS zI8X(PrJbTWCL3gK&-3?l)PcVrJ8C0WvcwpB?2|I}bK7xZS>;s~o>i3&`(M}Nq|ENF z_I0-vgXWPWlS)KAAB9mhrgXiOpZ|l`;f9bI2XEt}jfx6i^pv6B-H^=?L9>|^gM zLbETMn_#VVZroo1qT7(L;lOJf(yyn&d^2f8dxK>!rShP-p6m1nmhI7?p_9VJqm>}M z981df5sEFHO7CfP4IZRA4<%%~s6f_+=5WvjCIGJyU_K0TLBeAFp|*rV^nGfdfN>}5 z0trdE&LFz7{wUG?dF$lW&wPkD*3Yk{hp%xAqSeNK0<0wKrfI&$+czyJ3v*37ru1?r z77Ct)yT)wC-ldOEeDY|&dqZf)x0bD6{bBN(z9kr<`NWa$b&wjvywSKEcqQI04XE!U z{pF6m32Cau2VE?kR~A zzm3!C!1GUItG-zG%;6*6y`20)_@Ou4&lPI!C2qROwP+?7szHZ1JQvTGKP^?CSL6A) zW*a;X7gtd5h7EEf>Q6Skho7YC;PYkbsQ;3n$BAzpw8QTAIIp<;P%wdZK7TP z>x#VuR(^Fp`PNaZNoeM(@}bicrOUNCL^VK2>woq>3u>~RaM8Y|ar$8bWva{Jt`R1y zvooo|jqAK)Q7{qV@TtG<_*;^PLr2!TNr2i)vE620gd93lNfF^G_$XJCeW59iSn6(6 z2q{_^EuNGrtrv=mGQed0Q0(Beb&Ch%2kJqlA77>9Xx281TOrS4^OveNvTK*62^jiBeE9t! z4d^>!wPQ9iJ$%XSx5)1 zYG^frB?ynQ2vfhsW3IkqIqo#?8{b_^(h4T7&WO<}od#DOUpFZGpygP6H{f$XtFni7 zb@U(srA!qSdLWv6i~jiB{`bBCF@@AUpJgfpN-5)OR)}&!2d#Xv!G=D_d>!zk#(*rZ z4y^g^M+o@Wtc^OZp59(zks~~9up6C!~7~Zj6Mv<;)E>g^p^{;ef3G1K8cYu2r^t3H0I1K=zM`+mmT_K+{}*X_fu;+ z9OqDMh=kPpc--aRCyQ3l&J5wbaAt$}2+idMnwv@*@^^Q;IIZ0I+ zmlWiq@OQQUt|fn9eV#mGCrkoixSuCq$@DwSY~!KR5D_*6O6eO06Oqvfl|MrQe z7`_CmUjB@`v%j|Tw+i=BGZ8M}xTVts3=QSqI1Gr95^?wt=lI5%}ZXtHYYD4d(ku2UG$J5@m174!xO%LeNi?_UXkcrB^S=$Hoylk3&T( ztK6vh&Z~oBk8yH-BbKjDtMAO-NpYNs%>UOTeghr{4k>hf;}4v(X1idXDoJvFH{Q1V zrDIqFpZUB@mNpMNj_KAbK7rR@w?-aSDw|~%g3JCc%VP$!)zo7L+(3-hObUfW9Pd$y z2+GcMx^%XM$3BP(aPbAuLwR=Nb18QCGO?p1zwWi6_D>sT{(^u-1PYTkY}n5o6Y zATXuos=nThwU*ra$Y>~*lTyp+k$5cslp1kJv$5`GRDIbOu(SUMlTt-{yHuCtVYZKR z#)J?m5!$p_cVxN{*b)k~sH<9#&S}MD!^u^GMi+E%j*U_Cd+q%waaHzze4(pe@9VSY zl==M5glY^0YZ-$Aw=9g_vvbJ7o0m?F37>g<*dHoh;CsLtrB`Rxvd*SnP5S5qXbyG- zUnj0MF;%l~M*Y&_)x~$hs0idFV$(5)#~N zZF5%wVu{6zRND1*vEYu{tjphCg+n$C!XQ~I=yyx zx>A*>hg<$*d)pr-LEf$`Xj6oTK;;QuoZ#fCAYcBT!;BS-h)LVJ&NgJ#t# zjG%{7)LCLO;}0Y)@5aD870lFaN=>$h!uJ8pEXzMGS)^8wDLp(YBZoUHs#3KesO1Bg z4}r{2t{a)mGV?le#Z{W$_>qDL(AIdap?`7PNZkymb7q1kp7R1m*s+qUNAUo| rv&k1qJs^~XFxjt#xc`s#!6R^kr2yrua?SmZECZC~HRNh!5bys7f~^wE literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon@3x.png b/assets/images/ios_icons/monero_ios_icons/AppIcon@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..37f7651a5bde6555c292b6b657c38c7ef353213b GIT binary patch literal 7344 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 C-#aJ( literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon~ios-marketing.png b/assets/images/ios_icons/monero_ios_icons/AppIcon~ios-marketing.png new file mode 100644 index 0000000000000000000000000000000000000000..0c977110aedae277e63e510d2a46e89033cd7545 GIT binary patch literal 69847 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 Nxx>b+$iyS+{{SgtRIUI3 literal 0 HcmV?d00001 diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon~ipad.png b/assets/images/ios_icons/monero_ios_icons/AppIcon~ipad.png new file mode 100644 index 0000000000000000000000000000000000000000..849c5612ab57d14991efed0d702230cbc6a34b7c GIT binary patch literal 2851 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 BSfBs^ literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index df5cf1cc5..bd04914ae 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,114 +1,134 @@ { - "images" : [ + "images": [ { - "filename" : "Icon-App-40x40@1x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" + "filename": "AppIcon@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "60x60" }, { - "filename" : "Icon-App-20x20@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" + "filename": "AppIcon@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "60x60" }, { - "filename" : "Icon-App-29x29@2x 1.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" + "filename": "AppIcon~ipad.png", + "idiom": "ipad", + "scale": "1x", + "size": "76x76" }, { - "filename" : "Icon-App-29x29@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" + "filename": "AppIcon@2x~ipad.png", + "idiom": "ipad", + "scale": "2x", + "size": "76x76" }, { - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" + "filename": "AppIcon-83.5@2x~ipad.png", + "idiom": "ipad", + "scale": "2x", + "size": "83.5x83.5" }, { - "filename" : "Icon-App-40x40@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" + "filename": "AppIcon-40@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "40x40" }, { - "filename" : "app_icon_120.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" + "filename": "AppIcon-40@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "40x40" }, { - "filename" : "app_icon_180.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" + "filename": "AppIcon-40~ipad.png", + "idiom": "ipad", + "scale": "1x", + "size": "40x40" }, { - "filename" : "Icon-App-20x20@1x.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" + "filename": "AppIcon-40@2x~ipad.png", + "idiom": "ipad", + "scale": "2x", + "size": "40x40" }, { - "filename" : "Icon-App-20x20@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" + "filename": "AppIcon-20@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "20x20" }, { - "filename" : "Icon-App-29x29@1x.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" + "filename": "AppIcon-20@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "20x20" }, { - "filename" : "Icon-App-29x29@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" + "filename": "AppIcon-20~ipad.png", + "idiom": "ipad", + "scale": "1x", + "size": "20x20" }, { - "filename" : "Icon-App-40x40@1x 1.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" + "filename": "AppIcon-20@2x~ipad.png", + "idiom": "ipad", + "scale": "2x", + "size": "20x20" }, { - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" + "filename": "AppIcon-29.png", + "idiom": "iphone", + "scale": "1x", + "size": "29x29" }, { - "filename" : "Icon-App-76x76@1x.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" + "filename": "AppIcon-29@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "29x29" }, { - "filename" : "Icon-App-76x76@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" + "filename": "AppIcon-29@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "29x29" }, { - "filename" : "Icon-App-83.5x83.5@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" + "filename": "AppIcon-29~ipad.png", + "idiom": "ipad", + "scale": "1x", + "size": "29x29" }, { - "filename" : "app_icon_1024.png", - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" + "filename": "AppIcon-29@2x~ipad.png", + "idiom": "ipad", + "scale": "2x", + "size": "29x29" + }, + { + "filename": "AppIcon-60@2x~car.png", + "idiom": "car", + "scale": "2x", + "size": "60x60" + }, + { + "filename": "AppIcon-60@3x~car.png", + "idiom": "car", + "scale": "3x", + "size": "60x60" + }, + { + "filename": "AppIcon~ios-marketing.png", + "idiom": "ios-marketing", + "scale": "1x", + "size": "1024x1024" } ], - "info" : { - "author" : "xcode", - "version" : 1 + "info": { + "author": "iconkitchen", + "version": 1 } -} +} \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 369d8d9a419ebc36e95c5cbfb488377456e99094..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 880 zcmV-$1CRWPP)dhAyFb9aYTs^9d7Gjo4)&b{Y;erL|(vhkmVO?x`oL5Rgc z>)<3>1S>uteOe7Z)zzWmVF_N|zK1?d|JZ<5ldKV*d>Ttbwu8wU>ND3DSQQq9j!zA^ zBt3|dYX$T`dpBpCB!8bMm_{4@M1egdS^2PYi9=RQ!pJ~dAs@24_rc>&9kSo2?LtRO z1I#9HVQFgz27_VPZ*TdC;w!nMM;MinF$C7xu7W)F5T4(<2^--Q^r+Qfo0wv=G!||? z%W>G>4=u_MBLm&MMGyt7HQHZQO*r7^1K$lHkk>YX!?h;66h6FBAT>N3=OUw^{-*vn z6MuorHg6?g#&3&4T0|HEwufRCr9s|Jwf%tS#O1(k-v9%oBlT~D=~9{sJY*iGtYtnM-H%Y zG7}sYh5Bv^wO#rEyuJQ~4FysZWz?B0Ci=BneFE}Ybx5DZLVA-9!F(oWbAh!L10d&p z^uV^pMsTeJ2HkP~LewbA(AC+V*J^2VVg?(#-wuFhLTbllpjAtJ%I_D^1FN2v!eNfs zp!4{4@C;myOiARseVJGN6a&nN+!e+-@hnE2P!M7QJQ}lOV{r(*jy`HBtL$E zf|NwW2M3`;r2>o1!Yr{E61g1iOI9HJgakQ>DI){*a+Wl!qsS8i2N#2FHr@hTVv@*` zk(WhHfM%p%rbhqJ^#F7qx=HTlpJecBj0-f0000yU(<&8d`%QI6*7R@CzG6K<;5k9e8DuaN^6?ur^ z#ulG6aHrg*kC@<6D8VxxO9Ky(9fq0zJOBSY6=nV~jI^E4=YIZwF6aL5cka38+;gr3 ze{hlf-T<9R_W+t(8e@>tDD)pb6utY%A?+!Js;LE4%5oIv-Nv13xwu!}^xFc;@hZ4_EJ9r93LM#) z^yGjNBN0|aZN&6tAz&Dh!EkNG#LNnBes&0>CQe4=@*p%fv>FB|mM{oC5Qhmby-I&E z-2(Se7)*ZU4cxw(jnv3E0|1F9I3|+d7Iib&ePMAdE*fdkI^j6!7|=6#38pRx(sMEm zD$DXvT5tyqHML|yQ}lIkgq7WJ+7@D+a9{6=z_!3OU|GSB3Ih%t{WKPB_#B;?wXv=o z8L`ng8NU~Ie=G*8dH6quhT0G61I0pL{M~;w%w+Zt;L>9{h3gn_d3Xfxal*pl;^BB~ z4P1rFl4`!k-q8k*W1T2)%g|6)hkxhZK;exd>|Gy)w9jJTAHErGzJUZ6PH^vZ1}6+S z*3AiH+~#%&cWBER#D_(2uW*?)5x(ofFldb8F)wFPoPUKJ;3FK3`w|~7U5U)ZgUCLS zr4O7i;GDOD(UECSB}5^99k;?F1aAKr(-sB`gdc`h^*Pc>!K8U9*zCO+SB_`tMJ5Pn zDesAK?jC|8N(*wxJQNR!Cv;y7Cd_$Fk9io3Re0jVW7c?E@i#Qpw-^rS3*w`6!9s_i#k1rbps9< zF+x!L+BZkhO4f0i{UW7R11nT40{TlZ&_qLkYF=frod|yY#c(v$5TLft+g5~tr^UFG z(+b`?Q|8Y_azw0QfY$a;32OgzEr)-Oc)^7Wayhj?UOfwi6$5h$#dXBv*;nf;6J-qK zM!+vB^0__?>RVZUrM2T-I4cQK8PC834PcDLlVQH-y`XTCv-%+(Zffv_RSEILFx3;yt_NXeplYhVKMjeY= z)MnZ+s8%ub4qPN>NGEg7p%t{?Oi$*`sA%)Mi}Ul_eOqrp<p2Q0|Fj%TNN z>C|?bG6Np@cQ{P@K~)|+r(UsS$+$VOv&Z;pjG(fTUQuK&GH+8^>;K-6Jf#sYd3B?uA$W& zVaD*Vys#P#cK#y(Elq0O6X*qa_A;eVO3LT@=p5no?j~d&O2h5!JVg6@Vb$(b%DSZ+ z3pMRjV@tG7qlF{qJNQvB&Lr!-db$A{YSlQKoIq8Z?qxLXWs8u6c&wc<7pM29qpnJU zpdEW)X**bOmWCQ7V&8oe8M{-u0;e00ldraYifOL~@a3W|d1lgVtWAu^2R=)Ym2m+} zM~=j-WxjBI#R~(5kE6ofl0r!>O7e4Y@vATCd;dbUvX;+&f_FyX`nhvBwkOSSz=GT& zGUF~xrQ}jbo}aM*TQ3j6=K;%+do~9tQSp3}(D5Oq!?&fG;$8DY{h{NBAYkhbN*MkE zH}3$lgdb9)zc37tleo|@IJ>_L3+sM@I{k(@W6Ozb+`4oGC%)K+>)(EZijqpy*8oPw zfV`gtM!8O=y2ghhz&x^u5kHQ5`fb?*3rj2P35zleu(I?X)vG=TOU``oVO_$A#2ESF z9KLZL(cDl$URuL>H+q=ba5%chQ9?OCp!lQ(C`*f^rOh zUUe=E?LQm7=_d?k`G-Mj)(cU~f}v?2eL64Plfq*u^g@za?WJpTuLggp1MG$)q6U_ zZ&=k-Uwof=g>Rh5Y>XjyaNw_idzeZo+_a#&TnS}iAu5$k`WEWl2l%*DmA6pKqqn?n f`zHPbxBUMDPrkd7gh*1O00000NkvXXu0mjf{)@mP diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index fb14bfc557bd6dbe135f1f9166839e8133a5cbb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3332 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 FV1gC{mz)3q diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x 1.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x 1.png deleted file mode 100644 index 07acd0a82816d11e8ce99ae5896deadf6083da1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3203 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 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 65ed7f3db173ea8f307efd0be90e41a9405a9158..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1997 zcmV;;2Qv7HP)yU(<&8d`%QI6*7R@CzG6K<;5k9e8DuaN^6?ur^ z#ulG6aHrg*kC@<6D8VxxO9Ky(9fq0zJOBSY6=nV~jI^E4=YIZwF6aL5cka38+;gr3 ze{hlf-T<9R_W+t(8e@>tDD)pb6utY%A?+!Js;LE4%5oIv-Nv13xwu!}^xFc;@hZ4_EJ9r93LM#) z^yGjNBN0|aZN&6tAz&Dh!EkNG#LNnBes&0>CQe4=@*p%fv>FB|mM{oC5Qhmby-I&E z-2(Se7)*ZU4cxw(jnv3E0|1F9I3|+d7Iib&ePMAdE*fdkI^j6!7|=6#38pRx(sMEm zD$DXvT5tyqHML|yQ}lIkgq7WJ+7@D+a9{6=z_!3OU|GSB3Ih%t{WKPB_#B;?wXv=o z8L`ng8NU~Ie=G*8dH6quhT0G61I0pL{M~;w%w+Zt;L>9{h3gn_d3Xfxal*pl;^BB~ z4P1rFl4`!k-q8k*W1T2)%g|6)hkxhZK;exd>|Gy)w9jJTAHErGzJUZ6PH^vZ1}6+S z*3AiH+~#%&cWBER#D_(2uW*?)5x(ofFldb8F)wFPoPUKJ;3FK3`w|~7U5U)ZgUCLS zr4O7i;GDOD(UECSB}5^99k;?F1aAKr(-sB`gdc`h^*Pc>!K8U9*zCO+SB_`tMJ5Pn zDesAK?jC|8N(*wxJQNR!Cv;y7Cd_$Fk9io3Re0jVW7c?E@i#Qpw-^rS3*w`6!9s_i#k1rbps9< zF+x!L+BZkhO4f0i{UW7R11nT40{TlZ&_qLkYF=frod|yY#c(v$5TLft+g5~tr^UFG z(+b`?Q|8Y_azw0QfY$a;32OgzEr)-Oc)^7Wayhj?UOfwi6$5h$#dXBv*;nf;6J-qK zM!+vB^0__?>RVZUrM2T-I4cQK8PC834PcDLlVQH-y`XTCv-%+(Zffv_RSEILFx3;yt_NXeplYhVKMjeY= z)MnZ+s8%ub4qPN>NGEg7p%t{?Oi$*`sA%)Mi}Ul_eOqrp<p2Q0|Fj%TNN z>C|?bG6Np@cQ{P@K~)|+r(UsS$+$VOv&Z;pjG(fTUQuK&GH+8^>;K-6Jf#sYd3B?uA$W& zVaD*Vys#P#cK#y(Elq0O6X*qa_A;eVO3LT@=p5no?j~d&O2h5!JVg6@Vb$(b%DSZ+ z3pMRjV@tG7qlF{qJNQvB&Lr!-db$A{YSlQKoIq8Z?qxLXWs8u6c&wc<7pM29qpnJU zpdEW)X**bOmWCQ7V&8oe8M{-u0;e00ldraYifOL~@a3W|d1lgVtWAu^2R=)Ym2m+} zM~=j-WxjBI#R~(5kE6ofl0r!>O7e4Y@vATCd;dbUvX;+&f_FyX`nhvBwkOSSz=GT& zGUF~xrQ}jbo}aM*TQ3j6=K;%+do~9tQSp3}(D5Oq!?&fG;$8DY{h{NBAYkhbN*MkE zH}3$lgdb9)zc37tleo|@IJ>_L3+sM@I{k(@W6Ozb+`4oGC%)K+>)(EZijqpy*8oPw zfV`gtM!8O=y2ghhz&x^u5kHQ5`fb?*3rj2P35zleu(I?X)vG=TOU``oVO_$A#2ESF z9KLZL(cDl$URuL>H+q=ba5%chQ9?OCp!lQ(C`*f^rOh zUUe=E?LQm7=_d?k`G-Mj)(cU~f}v?2eL64Plfq*u^g@za?WJpTuLggp1MG$)q6U_ zZ&=k-Uwof=g>Rh5Y>XjyaNw_idzeZo+_a#&TnS}iAu5$k`WEWl2l%*DmA6pKqqn?n f`zHPbxBUMDPrkd7gh*1O00000NkvXXu0mjf{)@mP diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index 80e78be415a375258c3aef7d46658f5906bf1f2b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7194 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 cfUCLw2Qr_Ii_@%07*qoM6N<$f(zy0?*IS* diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index e06998b675c1d3c62e7006480a526c509e28a92d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4305 zcmV;?5H9bDP)n^3PT1BNGRdG@3 zw#5PBKm-*P3$piypH(3J z#dX{~dmIsgM{)m3B1FOlX@27oW?-y=N%LGVYSv=3?9d;tQ=mFDVcL2y#(qq%b$EI& z9H#=l$JzbiC@oTBHH|@-fr$p3J!fIO#|olNR!}cdHGzlr-A7~ImQk3xGzv$5@J0BZ z<0vUsi|~dcj5=_~nS=4kx?M0fr)JmCL@BfYrfqF8|A$aG&pQu)uLXzy zPsLw>SJ?_s4`I~m=KZ(`6FfHKjZ&*xpu=y4rN2bLtgRakZwzJgRn-ZjWO-~{PRLq| z{{mUBprNIW7M48;1)+2prz}(?jPNse^FmDe?7u}8%@$4v_+jS~Up8b>s7Bb7#m<=M z@jd=)WS#oi4?7n5K*&B=awn|+nC|#^?Qg`{zgY=<-H>S3c5Y-*aO-RfOI!8fr1JI$78*7nZ{ zS(Tg{$eu8&P>X-sNJ#6>xmJ0IkohtS5x)lDT4)HK#-^jBp8QXrj9NPPZHs{})8Obj z2YlmBsGB@O7SXI7s*y#(p0L3a`oVVeJe6vdK!o(?H*j>*3WWa_K{QGEZx-eQQ5T<+ zYZDHx-i!%zr;x!^YMYW*s_^J`2R}7+x+? zpFBbQ&0Dx}?kr+1K15lG?7KNHOYtKa-b9C=#=?MO#BAG$HA{_TQ7BKCz=Ve>i@t#> zLx=~FC$M&!8#0pRos?M{T{thCjiJ*$U}Dy*);f$^zKbq{W?|9-5wg=_a3RnipzMwq|X$*|bdc*niT^Kp*E9~`|PqgWjl$__Wso49qKj*gA0p$pz9UJ$_TyWK{ z^yg9Vn<`J1K?^N>xqCDEkDbq1E(PDXJr-;|iGgEwVAGr>$V)HhEK3O}M_3m-3v}o? zic<}h<%RfejvLaFWMNQXrh~O%p|I*Tnv+aLFt8iviS?(fv2wfUT1}f7xBeK{m9Lb`Er4NbBl9VE`@%M{;U^ zEvTQccHOxo*&8&YpJEhS!7gyM|yA8MiZUw zCcV17P6$gZ5}~8MgpBc~JVKa!V%&$%pbKB->WsA*jB3u{M5a8k<4hST@kzxZ{GIpO zOPhZ0p?PafWWG?_aKCQC_=bRMF@j_-lUF^COA0~KpDD zM?wVUuJAQ3+*-*rkb-`8HRln+WW>_fV@+k-GN?>B+O2awAIaUf?m|;4xyVR6x7>=7!az@x!DvESNQQ(lN`Z^H;{KEgW(;&=f#ND+ z2N)1nrBk(~huC9aih_@0(6L)rTnlX^!VHY`NR^tbhcj6(Q|0bkSXxvsa*wFfgM6T? zp@d22mOMh3L_hlQf%bY5BFcp#arN3f21djz9f;35Rb`fwTgtRBz5s|R5J65YDFkK; zb*qB93DeMR#z|^nj{IzdE?;3PTSPi`ELBJdlbGdL;){sK1Pzu>uMu&ckW)$&XJSi< zubR?QLKqDSlgwmV7(!%;s*^=wBv9)#dOcB6$Su9l)RMQS^3oax4M0$;#E;Q_nrM?~ zCtz?XTPW6?R|tM7M1BP|M+u1&$#1P#A*_f7+VCZ1sJ;cK*3#6HBDE&tbrV*SU(6}H z&^IxXyU)$Ytf?3^p?G4No%s^&8PkG6dMSf`#*{&L{3zG4dzIZot?p<2eqPZ!U}R})=wwr*r|033;D?UB7r8~vQ3w-KstCh zSqL8+iCIn{5wE-wQX(!8(@Uw+kJD))xL8Ptoo3FUgPu$jb@nxyBds$jE}^CuTv0b+ zWn`FGkdq3&paUzZtvYleW=T|}s<-jiqX2QlSE%nWF*$E*Nwn-nqMpfX*8E$t%*>7w zlXJ0!)}xVA?&31zLIF#Ux)U>-A92iYD&SW zlrZ_{C&;t>*A$1&ZXGAjX110m=GaS7ZRSWqEUB}y#(2XS^%E9-6?Je z-g;8HGV<=dNqtjgmJ~E6Jft`X6+fF{DI0Q7a}262Iu_+V!|8w{ zY#Ux5lprkfI%;y*^}(j!6twTL2#GP0D3z5`hLiq&nD%8rby;y_)b`3ODKg7M2+vdu z&*51jWRQ2JRVH~aUorr$!5l@TE?;D%s8_w93}JDR(I_j)At{nEE6F?!9Zdb)6W@Nc zTKeqB#=RIgZYJ7wlQ&J$-|rKJWKb%(y_O?Vn3bTQA!OrYA?%GM1ku52jlS!7>;)Xz zw2xJLl|=o7WxT8)4LE~NeW$aQKE!nqLbmO|qv&+;eNmne8{T)r=8HF>YtZKPq6dh7 zI*?lbQvUZzLUPqnpeQd1>)qW@Sl5a=cPKIYNmTxFa1bHP4Q$O)Q`-phw{C;ia5t1! zSx4f%G_09E0p3C3Xr^n%zR7im`hJNwYeJDtr6b-D*=C>*oDFGaq(pmFg$HId2U7D;ZE4S zY%y;9c^ATJ1W-cq(&qwI>IO9DmKm@kI1mw zoZJ1XDt5(*ee7A~gT5nNp`&lELcI*yzB3%Fk453ht;0C9X%nuWzJqd>i>^8bz_2Mp z;P%BbnA?m&ZT?Ts&;s5^FJjXNV{swqnhNdau%i7UE(iO4=D_2-Kfpb*NkCVqt?b;f z>gY`3o1$?2>>s!vaRHC+Mk70|0L6un?lPpuo#_rm^L8e%>S>E!BZtFwgfkhISVNgW zp`m4nFZZ8;z{CZi{^z(hncK2`=#G=1UJG&B!$`H>4j~!|05mk z!%0T%pk!!}0MSN#sTe!WlGuc^NE z=+?RWSUb}ZU+p;qeSs>Gv+wI8Et-1FN1 zJ}$)1%lxs=#~(e1b;aOG6Vb^ zX>Dkl1MYN_%gwW~;)|!T5vHxR@Qz(KbnMX^rq*3x*`^(g%*>%{Xatc^Tl^h{SAxRq z9AqY^AU!n^FYZ0S)BCp(cjF;4Ygi_4AT<4d1fXZIQ=gTS>ozX@t~kM`OJfE7mwsy9 zQ^FZdPgs2@&TZ-z$l=W=>Hu*KcoWpQ_3++72UFYW{g!QI{6B@jLwg1c*QmmrI~TdzzEcu?eTr+Gu91Poxu~|_Bfx;(aY|P}g zJP0^~tIwcKQgFlN{QPiMZ4)>u$bH@S-@Yqwo(S!otn76XtZx>O(noVtf={) zf=aCHM3sys?Ey6lY*f6~{TJ65C9rIkF0?yZR8gLzMsqEyeOvp$Ry+T#KEtY@P*AuK zQAKhf@_ryI>}}5uA5rN z@Nb#Rx@_1Ur(mHDC|xx9jdoXWEhFfagHQofc307M@W zm?~vpz33bqs>H^Iey2de>(4L>Lomi7{uZT}G!KZPMTRh=8xX>RgdT&+N)Ziw>WR|4Fn3!q zcJ6E6z1-<2gic~=OBZ#Ff={wxz8iVl3Rn`6d?$l0uzDX5Rf#UtMj#4aB_hh@myD%w zwtPkZN`XN2JfUVJTQ^)@=2dk&rbK!`>;JFEP5pP@?a9$9{bHPSI&42w813n5>H&soVFh(M8pC^tqj!=tYYFN`E4efxlMlo{^|UP zqy@W}TD-D15qD2<<+`p%xAuLaufQ~odgXOKkXPdBN8jhHfFW5wh zL+2Nf%cHZhJ+pgD!4vk;q10@4Ebfo%h)33<4S`#NMmOr3I#v^reO|O(B;Dpzb|qJE zlc&rb!Uu2&9?b|~$fKOg<{~DduDA^SQ?$VWA?ORCsLHDaoqbz8JSt!MZVF1yvdciw zODovKf?!#FH4M#Jn&*H3v|;j?)pQ|v?_I0GbarM|9NGfQ-`Ar{!%8lUF=w`Bly4ve zUtR-?X1Iu9EJsdtlTlVur7a2G*@3`Q2Fjm5+d;8YM(;s>OfYbge2oVQV;PS;r+DVX z)T+UU2LESF8fO?tI^vP<;{H9XOTEGfrb-6#RNRX$Nm!S!5v?4c!~eanK0GrycQ+x` zaA4n?`2agBL5%b5DEaz&U1{q-mJVTj3MXSy9nZV4^?uIl1HxyP$Ub)#*wRdO=~k}Z zR8RYu+Gzo(*s92`n}d>uAfcChQ-aUDq98=8;_6&^J% zO#i*yogQLqHG~7J61ro4>o^MyYWYGn8kMK+MW(TI~8KBgRwnDqc${3N-W4XF! zI)2IPTYm#bVo$0`Npl7$hx?gfE11_l;0n3MnwAg+FWHyG+L68~DwoGdO@BH`TCkh9 zw?((d40iXas3>am29Ud>ow^2Ge#JCz$MCV()0sA}g}AVA^5HCEnsDPSkt9gHpq{P` zkQ^nmLEmUzCFkZURS(GHNFD-sLW2wbBj9cFnib%KPPCCu;CXvvx~yxZIy8{qG(SX4 zP=DvxM5(?5Gqm|SNxfhZrpwW9$!|&eK*+-)x1{eEMit_G;wuOXi`3TXgoxdJh-1mh zsraf#7)S>tloP`<=wYiWfuI$akn~j4$|a9b*(Cx_&Pr7=$;6B!PYG5+?3KUBDENfo zBEes3(9yDf>5qr{FjoR;yV3g=0v~ra8+f7u^TV?;WmJSp5(z&A2xdaG$9tdP46XL^ zIRy|aJM~K@`#vfl{5U>xwc(=^T8^x>|0+cD1l`TphMmQZNpx{14iX9&9e?=|Nhh$! zbZNRBqTGqG8vq72TKatxqa}32-fa*(>x4dhB%Q5~MD4&Jg83;&Q&ejg(uf*AoGv|C zZ#OUHr;;vPGl?bu;~+9i94}J$0ie(%cvgt+j$%Pvf&89^>Owzq2Cx`^1Ar|U*&Vav z5o$7v_O2yu*PWPOxB@O*t_OfCL*WzP_L!vJzGIa$Y92ilgl;BGR;|5) za{#_x>YEWng2O7w3DL;#BH}2_)pmI>cgQQfjIpebjhPW&6X%u?)T>Bt2ANg8JW9?} zM-n@N-A?bNemiB5)jJdib`>OEC&k+??!n1TJw(Lcfz!5L)U~mP*wpBpxw2yol*0Ys z4|l2)C~)qD3($0*3=rq%i7`o3!9nA=R? zsyRQZ@6M$9O$1@d7=2gcC%dElVo-Q0?7eq}f--MVadyj2+mBVpEKvELg#jAGq)YDp z*OAxVJy(Iag6x~2ZY92}SQ)HuH|))^J*>)BJvx8$>uxXL6);nl2Pqj-2cxf7mX?i@ zF|PX@91K}pb(jctpJptw4~-zZobAW)zrXfZb}+=Tk>LGy#L;juURL|0_<8_(=2)}m zWi?<(PTX&M{r8`xfO2WtJgL#2i!0~?aTqud3IgBCi4>E zI98I>8tl_1P{{p|1`y*<_#Vp>)u|$LiiI)<6CiJRo}#yZ@)h4leKmeEQqO#EKU_ht z=l|k?-bXrI+znA=4eIu@7_NzT6CyKd4UuIE5#7m-!zlKC_P0D@_1lLw)&xrG|1Gbz zT{c6{@qC8=I`}jYI7ngyB-1>_%2B7~-{616GKq}X$=OD=YBiD_lLyzxdm)$CvBZS6 zJ~f#8=>a=v*A`h2$T#4fLeEbq+n-I{lBM2<4NbG#%yc>v z;it5+r%q5MoEWXL-}oS zF5y=7U}<1|Qa`59m3Xm0h}yVAw=33#K>xK30p01FbEPiAeZ13k>5R8!S!-Xo=nTGb zex0lbOw}RU>Iwx)t+85wvv9)OUsw|$pzQ`P;zEs~Xt36~pVJ z2rWM^M$!Z3+v{}d-Hl1M9rkLgIeDWpsC{Ngk^I{eSxABzE~B0V2NT-8YA*P)XE5te zGXtdb7T=iKE-Ai`^#bayGQaNv-4ff~TBra%nrxC)LK-!z&5>J$ytC9VrV0rmW2w_D zZU*le%`_f(lUeOJCI=RdFsQHj6Qlj~OwEPD@Uf}~;hO3YDN;{PR?C6Lrcg{OR=_nSQEO|g8-flWHm-TMv(Oadt=B=JdB~<`qE5a;_ z#355PRZ9&*MKf#<3xR@e9P!p2WhGk}fH zL4bHsmirj0hw2mk88pC{ml2vXpoO&>IHHT2rTOLJTh>_p=a<4KWid#ZZJ}M#%$2Kd zXir3a#HjeBd=s8%^eJns)zV@yOS!!J1F6`SpFTx*O9aIfR`W~CzKtf@zju{Z2{E7O z?y~l&C$xuFK|^>p=xHBM#WD~cKeJhrvnEUG!bvKRQBXrRO@YsS=6ZSigmZaBmsb~y zs1%5%)zz^~e$|N?UDXIaXoot4?6EkX(Ze&?Ql(u>v!3hbM&f_qqbJ_S4jNH^mq+mY ze5q?Sv%!AXdHd)fuj(II?tU)(rGP&(e8)8*xvDZtx-qVU-nn;6_mS_F^ItR`+C*4JgGH!Ytvs5Xs6UntOc7rIuPu9 zgnRtfGcRCl@*@^gTtayqSl>aBTZLpu#TyhwD9eJ2Krb_z(q_c`q_)3Vaw=1^+u)^} zcTC)Lx{`_HSO)h+m*^ie={)3~Gx6}HHKX!+S22IS%7ys)W|}}wzeTszf*@|y?qQy} zc09HQ+5OF!WgDD&ka_#l*j`IEJ_ONd+ZM$gxA>?51EN@VmhiTRz?7kk{-PXZ^{kCL zyx%x_S`+k~znFts%0nG10nl^FPguA#TVrj!0wL|`xow4h^z96R-Fp@-sV-g8+fUq? z5@1Wuz}OVZY+A-xvR|=nn_Vf&(uC$0yh}2K9>N)kIFzPV=1Xu(B*fUkOy_p?Wt#lK z#JO|(h4FExTEf|D#ZgRVK(l;n)hH;XF8b{!9q?i|^0toWDZOd^82&*W${anzu~HZ) zXWZFlBuM^fH=|@_+T6TdYtv#^a^E07w|%!|`$em=lyicB{=|%6uf8x+aEVuc_$W^>1mAhfG4|44G_Lit7) z>Pb0@S_Mb$fr@Kz*5K+vV@ld~Y}W=zslIdxk*Ku1qLMJl=qMy-K&traFLv?>IfGB3bxEQn$n~aS?Py50iTA>CpEL&&W3%FDsjvoMLLK z)bu!B!7G)@qqiKxSbV@BrSldLJB*9-dj)s*)Sigbh2HtPIO@A+4iVHcz@P|HRAuKu zV1y~os_}DWpB0@pWBgdbb}zne^d%navP{mIJ0oI*-<~L1RJ!%$)i)vp{abP zTD~k@pAvgk@hC|W{99r>JfY(srr%NYSQ{q#~{$o zyU*kPN%ZE7fG>O#nNV8=%#-PT{f1y@{_$SYG#1&B6;0p(=NkVcrgTh!npC}1Of|vC z+0rNEM#GJ8N~e>#Y0-}|S8X{XTEc#;Lx!JqEyf&!A!8Yd^U+Ym%i~$ z-CE$tp6*CqRsEZsE2OQWTR37S3sdT!_-9IX`yL81ZR;C!DJoUT*^}eqeW9Sgs8LRE zy7WhX1@n)8deKs0g%`#f@>o4vT9C_r~dA{_f zF_9sBQ946%Z0APO1`vsvpQN68yb_v`Y?I)BzCawqB&VMn9yox6D*E$=dtL>H`LO;E z8C|T&@3^Vmfw|)lxg4F#hMk-;eW7HFf8Ck4iH7&XVV{HB2Z5ZXBcsbU_ylVv3-1m? zH(@%3V4Oj~O}s&P5-qF-Lp6w>-uc`^m^^@H9Wv|q8gBV<eRsi=42cd+Oqm}ZjsB9u`LIT3P z_KDAa{tu={2{cM&dn|`xq}Y<&|42d>?xKgWMeDDLux&;N2PWC$&Q6I@CL3@Y3_5<~ z&(DEWIoG9*Z*9G`(7fJMXM>c}99b5O*~DHVA(J+>Gx^wikPrPhrI-g+#!w?_mf#JKLJ1ThH5%Xt8gKe zI0(0mnSef-;F4jCcF&c1<=(x^6D5q;*^o2A)sU#Bx7oZ#pHh_$sa7gw&{ z-cS0^RBLy5bW7WvEs%`M2VLU)vsa3K2h~tBeXKMo5f0|mOQuTRi$(K}x15sN8hplM zaAH;z80X17P10W}GG^qD1KeevK8=U!a#D~j5M@24L$zGNf&_yg!5yD4E>Mj@L4#H_ zVj5iel~JtLLNWNYXGB!2w+Fu;dLq~O{ht89dVrop@5bzyCjhakKVH-{ z;NIzV70UDUNsb5}%HZQhZA%_Pi4cwjuoNOm#RMF5(_$DJ#D@1o1&60m&AXvz_K|!< zzHRD|(XC_{iBT+5$dAK4&ZP z3W{GBBDFB>fSwPFzV zrz3bW(j?Q%(ww&5`q@%tBL!+D0k!xzG8rW`F_JK_KSELYhF!|e230Y}5&kh@vuopL z%iuRZUl%9xrthj;R(qNH1CqVq>vNG~p@3K_*D5cE5dZ@Z!l*#v zfLI}vCCe43tzDA8Iq!9!rn#zaGU@cY^e)Q{2{`&HK;>kXP*IZ(bEs6n<){m46SF#0 zl82zL`CUh%QL)pF3Nf+HX|SO=)^$)^Q}TNfY9rxlY7tr3_7?IA#+{{!#|bBA^I35G z(dR&kr1bThK%?e(LXh+kwU`okHBXzgs zce3e^d0H59d{n~1{SC)BevUG*4NAb}uS4~+F<|4;d$Tl{$)mo#t!TnInnrXlrZamw ztBAp%b`!`Ni06V0b20vE%Qy}e*{gD}ivJh-CAI7Nt;JIOVfEV2vEVQgH}`FJzLeeI zksRT*L$tW`NT*F^X~jgjfB)+I9;qw94My+%W5D=xG%6;kHqFGM%KW;7hm*4={6qXT z)JBLRx7d?*Qzu;2gpae`e9pS9^B#(mkt!eN+rP>t!B9&52G`BxO zY4S?+VQ7P^%um|9Az17@nozO33gdHF%h^(-KE3qbWi63>9%Dk-H6!EZ2*-^}0rB2f z1$KC0C&JPsVSSnHq(Wcs=nEnypLMtuJHBB>{j7w3!VVl57{{7Ks2!3&K>M>V@R#~p z0%iT-4_xasFMd%40SPQmCx4bF6sFH4b6PM0gU zflqCOi;^sQZH+&tvo{9+Z%`!b%?zS8-YqVO;zntdzeau#12-CPVSx6-1?K~$tX>ztZZ_SAD!4Gfa`A4oiB z;}WOkG?Y8hEh+E!3yls$jnHpZvRTa=(5OD~a$42%?rh_F;5Q@i)0=9=wlyBI%GvmX zQA&SbUmNmBbz8!+zvtd!4t$#{4}!jxi&^8)D#bB>J=12Ok+I;tVh$yp-6}5`M&X?g zNX(59aK$8xA^kz4QA%^`viq!ZPfQLqY%>|FAzxEox^u4=c;D7e68yTd`#V`Dd}^MH z-c|RL>(TbSov5XDkvzcICrs_MTC0lEPc6qxlLHgjuI-kHfatUjfQYS zOZ7G;tp7qwR{IgJuGfPMC`oD$${8+0DQDI&Fej+cFE_>7Lse~>NeHcdZKPt&k-(ZS zH#luoB&zKw(&6Wu%jbadJa^ikY}=0hc};X@gF2y41h3Cr;vU=Ow(K%|fKM8e#TYb`hc0JZXwDQY*VJre z4aLCf`+4h3s@ru1U;*s?o(+?eD))OdP$C=WoN1gLvU&T`L;3 z8DoBw+dIGGq_!IN1|@fvRc+bDl=^0r56z6Yw1^hIzcX)$Ty-LQkS&J1f}F`z>BuA}??p_hLuk7&+Y?Nw(Y=3kW%J*XZC2r*n%j6 z2eC-M7mOFZygplCi`_bb4tJCE-n6(h9rxwzEO%c~J44Y4FP;d)TfE;(*#sT~r;-xj z`Y7%t%gQ!7M^#>ej+=c>ZX5cwIK3j0{LXW-icq=%rnJj*TNZ^Hi30kKrBz!{OYmQVZ%fx8aiAb^A6pT6yF{+U9>PSB_= z7OW{Y0u%dM+qb%HTD&luud#x6=Lf>q>I1M`5PSSS3D*`G7PpZYVf@+0sQ+WF%WQr- z3N0{rSDZYo#}&Q8=x}8KAO=Cx{rHMRVT|AX)|sba60B*Pu@-m%E3;`E6@3;MX;Jzq zNymLMHlsS)9n=mI_s31ya@l&X-GpHQoIBGW-cuw}`a752Jq4z0mnM`{>98xP2SodC zb-UU-FuLLM$n^Rl#igi08ZS~m1}1Yc=K?<{IU8&d()n-^V{NE?UA=DbHjxxGEoCSE zF+(W^HQEXsTKdJtwY!pRyF7__YA~XKT!Tb#VplBq%ZlXj(?_3p*`3YwUwEOI;jc6v zMIR=CaknNBl@D?*ZF0Tb0|0Rb(GDX?jv5KhB^6b zVq@5vLag+*A_|9sn`5#3xbf_^*C7l>xRm|oPeAd!jqhaL4DOPXVyR&BC1m_6h9Gu8 zdUR&uW764XP_FzSHpfeeWK0nRsX8hPNsM1z1h$vYX4czQx`~n-Q#YX?oW(R#uWAH^ z0*cbxlN6&yKO8X=0;itFf~W%w#>XawKfl4~hu`jTu{VM@mh;0z%-rQ{sWfg&%502{ z*X5?XOX3l1>a%~J^N$`K`JaDnI zh>mbt@82rpb1ii$8m$^GW?@5GB(|gxJ7w2`IPacJNo;l!MICu^-xT|`_02!Gz&+C4 z3^l(063B2w)KvVWVs#2Wb+)#h?IC7$+63#AAy-$eFpzM5n*WJvte!LA{4T)pgb$z$ zgKC%b@)JOMnu(;kG$tq+0`B+Z-CdxZD<6kNhLmyRy6kcU5?zm3qY)7&eXWS#I#(eB zAK4=M<2oE0X7#VGBBi5w54E2H#MM#lPfz#xV$Zw$Z0)RZiLIzE{81Xk=x8qSk;g2y z_naUOegJyjzjg$-cakz+k|wKmD&Xj267AqOeE-t(5@Slfe~B8i@Lran`xA0t1Q|CI z!XIoNM@jFSP$JPZdx`HqNKgVZ@BdrP`Tt8Yu%u2XaRoJcJVMLcW@Y!nm}JY^+$oqv1D{}v|tzvux_J3>KWjZ>DF z(ertAl#O9ZqDa}FTNI^}3}OYN36d~a_Jy~9>6H>dRAB5r(f?n8Hnd(cudFXGHIm|zE7r(xZ+T5D~ zeje4chEfz!a+k!34%v+nPIyTP10Ow_5g+;B1h;}YegeVRIdBt$-Bl$d5-U%`-1Ey} zo-(DPz?Wqzef(=yo2-J4dOU%8t|4u~u2gD^XCB>WoZiP>>V~7B?I6J8NJsY}Y$@83u~%vDWF4w+j6-}*;XECE59LjzBcj>N zfPnPJZ~VxZTLFaqW<^{FF?wP4G8I`yW3Zc%l6^kl1M!ZR+|UmwTT4EIg>m-@I&gcKJP#wyCYRm=kGB z_SwtcC(9|e7A$WG^77ipYHB8vT3OnyqS5ekC(ZX_yrSIcT@$A=dIZ>C5tS4RO*$D; z;7aB$=Y+R?Rh6#CGe&AsU{ z%w5GjWzoK}d^Fs(pH?7&^9y{UoH9m3x7b&PtOSs)$&&~(S6tlg=T~@MAUN=x2ffYes8us0Rwlo zj+58_1TB5}5$dTcOrMrj<~z)wA+D%C`nv-y)>tiU*qHq_SDp9Te-z<6zwcN45gdUY z%AyGSg?W@{4d3p`CULzUJw{w(kL+j*O$w`x1U98OZk?^6JDzbu{xLp>>bD-3RQW{A zKwjR)CyF3bf!+pDnx=y*V{Mk5N|Ab~;%a~?j?beTkywkqslhIBMBLQPz>P^rLY6S* z%@Yx+t4?54Qh3y#4zIqjIjPCX`OOwF_F1`okl}oNizK*v! zkT#uJ8>}v=`m4O4 zASUhnZIxR^4@7w*Uc?Dj&-Zf11{$5YNN_XA+Qu{tbn`Z>4G0&E;lte6e@vGAC60Ux+*Zg zyJthaWmbzq_U->RqCVmG3fZaMlAiV2X4t;tyZv_j{;c~Dij#JFpV_p#6*OTKwuXhO z4SKSkyds8OJfg3J zbAcnQW~M57Kb3yjS2fe`kLGnu7y8#JKH1?Ku1JAK8{Yc`>Ma&CNVcw+A)^9SSHtJE z&0ufzi@OXo)Ne*zd$c&e#{MB(C7AjwHVDRrP z{KN>Kf;2bd&(4y;nYRm-B!L-NWrHe83O$<(AtLYp5H}2`ibYGPQ5Mt4Ag-2gFVdcP z1xogtIm~SVY@ZpV6TN+QQ=(;`k?^7V@Bm>#v3nsm=`;@#WXoy%?^;V@Wcgn3jE@tl ziD2CUG7QizP8?B5U2z1GG~n08Ol?~Dt1eo0q?QX63S;{ zW^e~`mPL5_sTB)RD3Ese4Zh)I{w}WK2$Doeqdb>94_YglAwX@yg}oUVU;Cb#N4N@H z8IbhdE)b#{kHz>FD}Q>v@8kaw7!vRzTk0)=WHc4dQAf0SpL1VG5>I*lHg?RGYj-p6 z=i`v;Ksq{9V&M$Xb_2_e^vepxO@M<0W!$=?xwn{qlvnVH{#d+a)@rcAPO95S_OAW_8Qz$P;ZPVB`p%8nz1$QMCa~n_t zkDJ_j&I3_U@y4z(ndmNMoqJXUMGHXdQBFbe{7Y9)AgK6B@HKTrc~Yr|LwvkH%iY9mg8vM46%jF56qk>#ba@8@Oi(zjHT{+@}^7Gn?d37 z;|k9n5k6bx$12gk!>sxnG!CX3j*efQaN$RfMA)ofrj`>O z6E8x2Hr6UvVwit+qTP4`BPb|D)CWe#xLhrdc}*|XHd_^IInHLQhFMhG!k1XWX5wm($#ak&(Z+(zRjoS;K{Li75 zt@WsHaTE-U&Bn+NtA*Wg_Q*AC?!_gj#Me0$MJ|fJ5Ps6q)wfD5EL@|KCctS4@ZvF* zOzQLW{1p5zG0-pwq_)3HHXK#(Xn+uZLF3^#C%jbu?=HwCBx8kuwUt-Z%F=O>Xw69^ zZuhM(J-vQ@r1^HRW8Y#IP3e~t^b=>HXhmUzjlevgw@?j*jN1V+(5P}LO%G>;4E0~` z^ja6RK({QGbo~;KBt<#&3ik# zjS*wyL9@K`=Brsoyi5bK?&L`A*@biF9aH${b6z{ao?mT*euico=rlNu#D_-Nx|}}> zZ^EcC@mvvOn-WI0Ajuk1+l5zuoLIH=5`uM?z4R#@X{G#7Vz^q^igl<83T^Rjl%?q( zhh?|ecX>;W5cVH2_FPk$;`;t(iJyrmwaqymyz%gOA)l)it;9qf_*FXOO=x z79?bg2wu)jc8#N%$|sPKY>v84sO;Ld1gubcjxMnl>qz_uR=>0rqOyY%9y@T#8Dx60ls8;rO9DVerVImG2uSk7m;}Idoaezl09yP$#Wcu#p*9jAOyE>v7vr+ z_&YveqwyLfr1Gf!)PEHmY+oJ~#LbQ_BvvY;o*spnD9S1ImRmj_dIRR0Oz*x?pM^xt zlzCyjJ|XRFL@2%MpV#drGTHb{^WR9<5P?o-sW+#rMYWxgqR(6wLGG%4M0cq0j1N$Eb z9n)B{Sr!8+nJC_7W^D@>Ye8_zT?>@d@Ke5k2hA&tlHBv#;<^dcl0gO7RRy`7!{P{> z(_T3iOV&E(wrj_BMa|NA#lUbGrg?I`PXgd_klTwb`7(c>QMzH(wsli4#8%>MHZ@V! zLJ^w3^%Sf8UHCYh0(XflihG8q{td_vTdW0db9d(Q{SsiHn0K! zmD8vMwDixVC%qVv%b#xzRj>lvijupe|!f#udh#GKEp^zDEU;uA`EpK&& zNm7(Ht5M>f8fR-bX00+$s{uPsb$C;6m(|%4L5%k>zXKzs>p|A!o2skNI;nLME#JUL z4|tA|ELL1z){1&k(Iy$3rZHi((>aEm#BN18OooYt_~+`rqSVT{bu^;4kg#(97K%vc z>Ay-zf#!Ni=GfT}i?pfN((6W;w)}2zun^Mm(-(~#i@p+FxGJwREQs!!OjlLiaM6=+ z*BO}DdIGMnHIdsW&|wMkV34R^HB$RzLV% zlg2Ub9R0J-O+rQpDNs|Bpe#Cl@$Q`7ygw?4vm%>m7%fv)Ea-Wk^Ur7)P7y~ntUmJG zmrVif(pslmnZnaSGxH#P2{-3Mjl5G+#-4vQn2A%PMD-In8QFqUv>4z4J`C!iuOYNm z4;6dIrpOK*V;%Kl7Rrv%W_VwV9t+ZA2Ob>3=+ym!2%o^}NGoq9r-roj4(ffG?l*3P-Kbc#bHTtxl*K0W+-9DxqhE1`V6Jz z7ZS*O8;8I`v_+z^)1@kz$j_|CiYS=N;}&$-dtXSAR>LRXVSJCUGV|x#zrmL7*NK*i zUMgt!xPPxq0j5LNX&1MvN}0n_w^ny;IRfv=1%)CvkU)>Ao$29KooRvvj_FSp45`(v zm=VPc_TlwIk$)i&*kE}{wH*!%HW0MuYy7?qnm+xW!UuRRFp04r^XXp`d0Kta90&X$ z`LZHy4vP^|n)z`%hOzf#ok)L8Hl2&wOQC#FF*ScKtAN##f$6ezd_^A26iozT^mL1( zVeXN1xOdzlA*xh6?3JAx$-GvNq}Bh9Zu2ltx)OoM*rS%lyn!os(x@QiiJxMPzcLV= z#I(~t7((gVfjL3yrxrk204Scv*c`JKQFOjN^`dodRL~}l0btQCx-zfULE^rWN(PM~ zbKyjfjGrp9pHDQXa=5ViXay$F)%LYYcuUtc6Pe9gAhK}I^6 z#4uFJ(>P$B^N#T!0liCu{OmXjw57GCeo>&G4peL`Gr23{%OIncd#M7Hj zy_LKfRunp+#8$J+P zlLHTNZv0xl2Nvavo%gMN(b!+PF0Ev~^buP;K_Rr=qFi^ykG4UV&GfqPpP`>PK1JKw zSDJax$DrQ9R`I(kRmo7_o;hgnJ&dVH8J7gFJ>G!=$#tzd&G0eZdn;UN?)bN2<~k&| z4C_9WG(UAT7SqGvpi|b3DWvuXn`in8G9V))lS2M(NW(`&E1-j&9YXgL`gFA({uDNFI0a^i)}rxx#t zAV<)NE8CM2(wXMUDP48EN&qiG4C_|KJ?$Rs1xa-lhSxbRzF0enfhD}es+)x#1f~92 z(cGS7v(p6J;}x#~&lw-cz88`Xj~hf~3$VT;U{M)pBE@T;JP?dQ&?KpQWF#fNk^IPE zAH{8>V$1F+K(tzMO{e}<{es7AUg^b?pY)^%m?TCMG|{s<^f6KXzHUdhqJoCd`t9_M zM2=Al<(D8nci{jXRk~GTl2>w4sX1Cj@owD%(frJ?IvGz(4FBTp0)?Uui-Annqg^y*@kPkJwcK|6tt3Z@-I#d8|Dv|*1?+E zt~DN+IeS&Ji6r}Jv6!r!(1Amh66hKFqKLc;nkYJCz+wwcQdbh^9B^(Vpq21a$KF>0 zcx?b}YQZw9WguHD8|g~Jw1;Ar8?C~DZQjazXky<@*)^;#nLf|Ou!e`ygx-VRH1Krq2BYJt`Pll%=@{{8KC==luWM zO;H=&A^eARHlP&`)9Tcuf}+nj?v&^}2uC+h@pU27@YG>l6^)|A;%umzu2My{IUc%2 zR(ZE3tM$+4#ETmgn({+s`!l^mNeazVxAY_9)dCs>GM+Etc zgp6ed$%?f=m3z1b*V3;j8bow6ZA4>bZob0L2{@Nj5oS~RctIu9-Xq!xB0Qld|3Cze z@V#bES&!)NG4RL#S*dVe*CP#l2?tM>vzvcHcX5^8GoF(g#rya8>1adD*Es}%jaw1) z#+TLLt>XbuJT#yO+4qrFz-fj=$JmRVn?m!50lr_{dFT*@c|{VH`;q({&?IwTP927; zeh|r;g#Aobrkc_V8sbYMG#mQLGm#K0Vu95|Y`^tElX+ViyZr@!oOedlINEE!Ow(_%F*as>t*3FDp7+~XgyAMgFU&r-*#=3_LLApLzr?e_pk;S>O z70R!xefT$$RfS&>NVg|M{QE|EuvE9jM0~(n^cUcCC>+r;_;cRLdXWx7=F?c5MuM|< za2gJih%%KKO5Wf4kG|*^2|O}JN}KA@{2=1z%c`?A@+ijk0`H#gxPeq_V6 z(oBUtK=Y161AEqT#rlmJ7dPm`=u-OcGe&s`^?$LO^7fhuBdPf0fmTM!Ga7N?IiPNp z!FL+P)rKY@|GwRaHfDVN_a9JC!FOeF6ZZlK+8w}RB>zEofJ{>eDXuUFfU<sarAhckA4kCqB z_*GA&2Q^^l6G{(BuB25bpxSp#5y2mq?W;T&p)B+Ts26oE{+APiw^wHiuVqwU9W&gQ zq*=zK>~*zzhhGw1usj)wp^a;qG>PdBqPiAgH%rI_G;=80f&`T8nSloK4g4E&sHj#m zHuOzOkkY~hOd8$r&4q*@1P#zjihLmeP}ojcUqiHzG~AmDJ>^i{Q1=ts2b%ws?My(2 zwnz3+bI4w@6q&Oy{s)%5)$noB`9YW;^^bfB`-k%Ro74OoKpyFJcjfQC_Mg8X1D!@S zYS`1nic03c(H&)8Y>AQlBbMYqm8C{pi;J1}Uu!?2 zh0eF97JM7{h>7RJaXomrI#k+e^`;}!ubRDDGp7gHv*EH)f6l!?nuu0o5%6tdnUc%b zal^5>DlUigUOg)&VqpO%4cWhn-J$+5^m#1#;KYqW@Zt*z`Y`E@L zLGyxB^J60Qum-qW`_MI0)h-Wf@0zpGJIGYHy>3MH@h_tMZjE4Z{H90+SD(3|uVlnK zA&yHy+iNcKp0hlsI|Ei^)l`UKC4q9`*t;)Mp}$U=Ek*wKa*^g+0=a5M*U{Dw%4LmO*tR>p22vj?bBiqz|9fI7q7{=je3s7uH49b#3H#UoR zWKEYAVXo1k8_nwl@Bc&Qozfapg!I5p$lCXC_gjraCIEG;(v-m%EbEocEN2bAmwX%` zTGwLc*H&rqnEh>{?cEjV_v%t%O<`VVX0%*Wig8b*;j>}-=~aJ-ZRSnE)QD~n%O>EO zhZ40jRCA~o;Tf%Pm&cLFuK>tRo&gg5YYm~Vwv?8RYT2O8bkV*kfs zzVCDX6;pxxjwt*7Mf;k%3CI?@(BvK{iqG|e8t=j`g_aPKm@7iNHaf7GaQyYC4du_O zA-dp|RPStT$K=8ZmNd`M{1if=#!%c#I>hhJdbW+J%AUWBQ=-4m+bWSN0DoXXO)hW2 zy-XI&yw$8?5ETX#2_QfZ0ru>dNnwB8ZW+TPZSW< zuUzSCi&R+m@Sr-wKo1h8jLjY(j8LY%0J3E_Wyd!``I~(GIW$h&VyM_yaJ|WmX)P*# zLv=ZV!cSM(nbOvY?uy^zcBTcF5K>z0Hes3yM|*^K1ZL}}H$O{w%^gH`1uN2nme_U|t${9SPhYt|@bL<{7&Hu3Rdi5t7+O9F-SD}x-Y~3BO8q(0ARR6Kj_%}? zz&&S6CbjVPz8{Y_@Pi2n|H^vfZbr;O8Nn9E_~jexZ(hz}PsQXR+a&&7h-pIp22;78 zOU`GD`a}A?q=P}IZxWql64F>Q#r%54i(W0YHg^ve06CAvf^BL!vWw`;3{~Ux8K_q( zW=)_0d(nQuQa&DnRGM;&Dh#+ilZ-u7qKyoMJ6IR)`wB!Y;ehl8QOx4l2V_?b?`P!- zx|gl$Ko;|!&j@CLQ^c5F%Ui2Fx%<4Ng|Mza{h>bXK#tf@>`i;tfWD2(UtCpHiy>mn z4tBrCU&a13$;&s>-8(SOmmecBUP9-spC^O~iwn|6BI zI~8L}*mmTVIfc>lPjpXPVO?`QQET?8=>Tw=hp;xbAVd~*sa8CiyRKSD77 z>s}3R&`TTsIpOycfibRm4P&xKJE0|9LtLfmsssrBQZp^sde(=^*u6?+g#s*b*(FCs-ZQ^wO&=Z4ALVay|7H#hVEbZ~tLau@-;CH#3 zq#&Q;s=1-a-o}JWBfVnHUM5KU$eBXX+v5G9xwz%F2$nwy>rY*$nR&8~Gv+0{bGhl> z&aaJ9J2pubX3`e2x+S4@TbE3?i){vBgcbZXb(Bm#c$u)+y#XKr zwHwxcqJeVIJr;6WHQ@zYXP5C^Ey!C2X!of&ID9O7vM!0Eg31^3yKeF?CmlTwn*MJ-sfJuWoTjEC&f{6DN|5rHjm#sHE2av{iG)*3K#YN z5B@u1k~2!)K?kHmJgs%cjbk4vIyl01^^A95*F9Jv38qo zALS}|i$GQ^vQ|DVNE)X^+t9ljpdZAKeZw7P!N_Xcv}}I%%L1GGm^^+pNmIrMn3fU+ z#cmM79Wz7YVPBaTX6jEqS-z0ETp&swfpvF=47|Mba3Rro-ys}TC(fqEh zH{1Cir0n+E&~xiEFr@wf7SD3l4L1M@FKfxc9j?I9-##>l9Vej}3!;lxu_mt5$&k)? zGbopVNkJ;#SnlA<^7VdKUXG{9I>jJdH^1{%Ib?^@UzwzHSMc z-s^2*fs?~1CPf}%24tK`ATrH(gzThG84Je;603V}ooUPKurIjtPh@^Zm5Wi};5=`M zqaL|OZru+guzLR;=zZ&|^I8)wx15aWF(zndm-z#iwUTZ;JC%u<6IVVG9CZ8SW0JrJ z9zRorC3l%=^9p+Op5?yI%kKjhi;*J#Awf676_`gZiBi&Fo^k`z3XDvWip6+oLKV!e zCKJ6OOBO`vWlzltbv12cBqmRibep@W?~d`f>g>(<+}Em=aDql z%tBw@N*?L@%csxVVa~n;U+sjhx&Fj`I`P`l;&#<}H`He={Eg{d5>5V1Sm(E-SHFDU z&u~aVCg!I^+BRqw_DNGTVaYHx%R^IIi730^bAGP^=Z~2|=1i0Eg>R~FUYHlIFv84I zIf-*&+8VApc2!beO+Y%%*P#z>E>jIJ>~_=inkfoJp+&CS0=Fmk6wf9B1T02zX5hRw zBqs|fxo?=yWXd^DxS!kfHO>QHDgK%&9|^KADc}s&_)o#rY5JuG{xP5qm)R&c$MPY$ z6ueja4VBStENsZ&ocA;^Fl>u?8!~Zibhb?0zmP02CkNY}jQ02WKz4%Wg($sCn)j zu*)g?TY}#W)FJotW@!6XDjU&;s(+*ZQ&x1mMU#K?K!>>D1f6WrWqDf-$M4Jlb{{?w zej$b>gj(jd+u3yjnE`^6Es7=rj*DmjJn`i1-dT>MhyuiL>Sy&~u-m04oY_=ib_~Vz z2CKLH62y3JE1*d8&oCDA`|G`L#PYMu-)Ug!so0PB?`mfN0nUz((gf=gp}uRn#nf?RfGN;Y|Z!a;2~NZ0*Ckp$d)3E$UShS zg+d+)w8Ipit$sI(;d(*~EhY#Cr2>uPt?bT89HM*1!@p zg+h-+8WYnuo@1{;Tf*{Ti%{$ldG%vliI%LG1$$TMgnzIw7Md2zu9`CQwyo>Kil_q9 z_>Y7Xo%;gDu#P?_`R} getAvailableBuyProviderTypes(WalletType walletType) => [ + static List getAvailableBuyProviderTypes() => [ ProviderType.robinhood, ProviderType.dfx, ProviderType.onramper, @@ -55,7 +54,7 @@ class ProvidersHelper { ProviderType.kriptonim ]; - static List getAvailableSellProviderTypes(WalletType walletType) => [ + static List getAvailableSellProviderTypes() => [ ProviderType.robinhood, ProviderType.dfx, ProviderType.onramper, @@ -63,7 +62,7 @@ class ProvidersHelper { ProviderType.kriptonim ]; - static BuyProvider? getProviderByType(ProviderType type) { + static BuyProvider getProviderByType(ProviderType type) { switch (type) { case ProviderType.robinhood: return getIt.get(); @@ -77,8 +76,6 @@ class ProvidersHelper { return getIt.get(); case ProviderType.kriptonim: return getIt.get(); - default: - return null; - } + } } } diff --git a/lib/src/screens/backup/edit_backup_password_page.dart b/lib/src/screens/backup/edit_backup_password_page.dart index 93207c191..1c80f1bb3 100644 --- a/lib/src/screens/backup/edit_backup_password_page.dart +++ b/lib/src/screens/backup/edit_backup_password_page.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; @@ -68,7 +67,9 @@ class EditBackupPasswordPage extends BasePage { actionRightButton: () async { await editBackupPasswordViewModel.save(); Navigator.of(dialogContext).pop(); - Navigator.of(context).pop(); + if (context.mounted) { + Navigator.of(context).pop(); + } }, actionLeftButton: () => Navigator.of(dialogContext).pop()); }); diff --git a/lib/view_model/buy/buy_sell_view_model.dart b/lib/view_model/buy/buy_sell_view_model.dart index bf05109ad..df4b838f2 100644 --- a/lib/view_model/buy/buy_sell_view_model.dart +++ b/lib/view_model/buy/buy_sell_view_model.dart @@ -15,7 +15,6 @@ import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:flutter/cupertino.dart'; import 'package:intl/intl.dart'; import 'package:mobx/mobx.dart'; @@ -61,21 +60,17 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S late Timer bestRateSync; List get availableBuyProviders { - final providerTypes = ProvidersHelper.getAvailableBuyProviderTypes( - walletTypeForCurrency(cryptoCurrency) ?? wallet.type); + final providerTypes = ProvidersHelper.getAvailableBuyProviderTypes(); return providerTypes .map((type) => ProvidersHelper.getProviderByType(type)) - .where((provider) => provider != null) .cast() .toList(); } List get availableSellProviders { - final providerTypes = ProvidersHelper.getAvailableSellProviderTypes( - walletTypeForCurrency(cryptoCurrency) ?? wallet.type); + final providerTypes = ProvidersHelper.getAvailableSellProviderTypes(); return providerTypes .map((type) => ProvidersHelper.getProviderByType(type)) - .where((provider) => provider != null) .cast() .toList(); } diff --git a/scripts/ios/app_icon.sh b/scripts/ios/app_icon.sh index e3eb36b3c..dcb168888 100755 --- a/scripts/ios/app_icon.sh +++ b/scripts/ios/app_icon.sh @@ -1,24 +1,101 @@ #!/bin/sh -ICON_120_PATH="" -ICON_180_PATH="" -ICON_1024_PATH="" +ICON_2x_PATH="" +ICON_2x_ipad_PATH="" +ICON_3x_PATH="" +ICON_20_2x_PATH="" +ICON_20_2x_ipad_PATH="" +ICON_20_3x_PATH="" +ICON_20_ipad_PATH="" +ICON_29_PATH="" +ICON_29_2x_PATH="" +ICON_29_2x_ipad_PATH="" +ICON_29_3x_PATH="" +ICON_29_3x_ipad_PATH="" +ICON_29_ipad_PATH="" +ICON_40_2x_PATH="" +ICON_40_2x_ipad_PATH="" +ICON_40_3x_PATH="" +ICON_40_ipad_PATH="" +ICON_60_2x_PATH="" +ICON_60_3x_PATH="" +ICON_83_2x_ipad_PATH="" +ICON_marketing_PATH="" +ICON_ipad_PATH="" + +ICON_DIRECTORY="" + DEST_DIR_PATH=`pwd`/../../ios/Runner/Assets.xcassets/AppIcon.appiconset case $APP_IOS_TYPE in "monero.com") - ICON_120_PATH=`pwd`/../../assets/images/monero.com_icon_120.png - ICON_180_PATH=`pwd`/../../assets/images/monero.com_icon_180.png - ICON_1024_PATH=`pwd`/../../assets/images/monero.com_icon_1024.png;; + ICON_DIRECTORY=monero_ios_icons;; "cakewallet") - ICON_120_PATH=`pwd`/../../assets/images/cakewallet_icon_120.png - ICON_180_PATH=`pwd`/../../assets/images/cakewallet_icon_180.png - ICON_1024_PATH=`pwd`/../../assets/images/cakewallet_icon_1024.png;; + ICON_DIRECTORY=cakewallet_ios_icons;; esac -rm $DEST_DIR_PATH/app_icon_120.png -rm $DEST_DIR_PATH/app_icon_180.png -rm $DEST_DIR_PATH/app_icon_1024.png -ln -s $ICON_120_PATH $DEST_DIR_PATH/app_icon_120.png -ln -s $ICON_180_PATH $DEST_DIR_PATH/app_icon_180.png -ln -s $ICON_1024_PATH $DEST_DIR_PATH/app_icon_1024.png \ No newline at end of file +ICON_2x_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon@2x.png +ICON_2x_ipad_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon@2x~ipad.png +ICON_3x_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon@3x.png +ICON_20_2x_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-20@2x.png +ICON_20_2x_ipad_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-20@2x~ipad.png +ICON_20_3x_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-20@3x.png +ICON_20_ipad_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-20~ipad.png +ICON_29_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-29.png +ICON_29_2x_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-29@2x.png +ICON_29_2x_ipad_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-29@2x~ipad.png +ICON_29_3x_ipad_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-29@3x.png +ICON_29_ipad_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-29~ipad.png +ICON_40_2x_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-40@2x.png +ICON_40_2x_ipad_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-40@2x~ipad.png +ICON_40_3x_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-40@3x.png +ICON_40_ipad_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-40~ipad.png +ICON_60_2x_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-60@2x~car.png +ICON_60_3x_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-60@3x~car.png +ICON_83_2x_ipad_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-83.5@2x~ipad.png +ICON_marketing_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon~ios-marketing.png +ICON_ipad_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon~ipad.png + +rm $DEST_DIR_PATH/AppIcon@2x.png +rm $DEST_DIR_PATH/AppIcon@2x~ipad.png +rm $DEST_DIR_PATH/AppIcon@3x.png +rm $DEST_DIR_PATH/AppIcon-20@2x.png +rm $DEST_DIR_PATH/AppIcon-20@2x~ipad.png +rm $DEST_DIR_PATH/AppIcon-20@3x.png +rm $DEST_DIR_PATH/AppIcon-20~ipad.png +rm $DEST_DIR_PATH/AppIcon-29.png +rm $DEST_DIR_PATH/AppIcon-29@2x.png +rm $DEST_DIR_PATH/AppIcon-29@2x~ipad.png +rm $DEST_DIR_PATH/AppIcon-29@3x.png +rm $DEST_DIR_PATH/AppIcon-29~ipad.png +rm $DEST_DIR_PATH/AppIcon-40@2x.png +rm $DEST_DIR_PATH/AppIcon-40@2x~ipad.png +rm $DEST_DIR_PATH/AppIcon-40@3x.png +rm $DEST_DIR_PATH/AppIcon-40~ipad.png +rm $DEST_DIR_PATH/AppIcon-60@2x~car.png +rm $DEST_DIR_PATH/AppIcon-60@3x~car.png +rm $DEST_DIR_PATH/AppIcon-83.5@2x~ipad.png +rm $DEST_DIR_PATH/AppIcon~ios-marketing.png +rm $DEST_DIR_PATH/AppIcon~ipad.png + +ln -s $ICON_2x_PATH $DEST_DIR_PATH/AppIcon@2x.png +ln -s $ICON_2x_ipad_PATH $DEST_DIR_PATH/AppIcon@2x~ipad.png +ln -s $ICON_3x_PATH $DEST_DIR_PATH/AppIcon@3x.png +ln -s $ICON_20_2x_PATH $DEST_DIR_PATH/AppIcon-20@2x.png +ln -s $ICON_20_2x_ipad_PATH $DEST_DIR_PATH/AppIcon-20@2x~ipad.png +ln -s $ICON_20_3x_PATH $DEST_DIR_PATH/AppIcon-20@3x.png +ln -s $ICON_20_ipad_PATH $DEST_DIR_PATH/AppIcon-20~ipad.png +ln -s $ICON_29_PATH $DEST_DIR_PATH/AppIcon-29.png +ln -s $ICON_29_2x_PATH $DEST_DIR_PATH/AppIcon-29@2x.png +ln -s $ICON_29_2x_ipad_PATH $DEST_DIR_PATH/AppIcon-29@2x~ipad.png +ln -s $ICON_29_3x_ipad_PATH $DEST_DIR_PATH/AppIcon-29@3x.png +ln -s $ICON_29_ipad_PATH $DEST_DIR_PATH/AppIcon-29~ipad.png +ln -s $ICON_40_2x_PATH $DEST_DIR_PATH/AppIcon-40@2x.png +ln -s $ICON_40_2x_ipad_PATH $DEST_DIR_PATH/AppIcon-40@2x~ipad.png +ln -s $ICON_40_3x_PATH $DEST_DIR_PATH/AppIcon-40@3x.png +ln -s $ICON_40_ipad_PATH $DEST_DIR_PATH/AppIcon-40~ipad.png +ln -s $ICON_60_2x_PATH $DEST_DIR_PATH/AppIcon-60@2x~car.png +ln -s $ICON_60_3x_PATH $DEST_DIR_PATH/AppIcon-60@3x~car.png +ln -s $ICON_83_2x_ipad_PATH $DEST_DIR_PATH/AppIcon-83.5@2x~ipad.png +ln -s $ICON_marketing_PATH $DEST_DIR_PATH/AppIcon~ios-marketing.png +ln -s $ICON_ipad_PATH $DEST_DIR_PATH/AppIcon~ipad.png From 063edded3d8cd3ad4d1355bc5f550712a565c52f Mon Sep 17 00:00:00 2001 From: Serhii Date: Mon, 7 Apr 2025 19:16:15 +0300 Subject: [PATCH 007/142] fix: selectedReceiveOption reaction for monero only (#2174) --- lib/src/screens/dashboard/pages/address_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/screens/dashboard/pages/address_page.dart b/lib/src/screens/dashboard/pages/address_page.dart index 10f9aef43..a9c265e58 100644 --- a/lib/src/screens/dashboard/pages/address_page.dart +++ b/lib/src/screens/dashboard/pages/address_page.dart @@ -185,7 +185,7 @@ class AddressPage extends BasePage { } reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) { - if (bitcoin!.isBitcoinReceivePageOption(option)) { + if (dashboardViewModel.type == WalletType.bitcoin && bitcoin!.isBitcoinReceivePageOption(option)) { addressListViewModel.setAddressType(bitcoin!.getOptionToType(option)); return; } From ea27a39e50de2f14dd5d16551173e650be2cae38 Mon Sep 17 00:00:00 2001 From: cyan Date: Mon, 7 Apr 2025 18:42:29 +0200 Subject: [PATCH 008/142] ui: maxLines: 1 on bottom menu (#2173) --- lib/src/screens/dashboard/widgets/action_button.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/screens/dashboard/widgets/action_button.dart b/lib/src/screens/dashboard/widgets/action_button.dart index 786c56658..b26dadb67 100644 --- a/lib/src/screens/dashboard/widgets/action_button.dart +++ b/lib/src/screens/dashboard/widgets/action_button.dart @@ -47,6 +47,8 @@ class ActionButton extends StatelessWidget { SizedBox(height: 4), Text( title, + maxLines: 1, + overflow: TextOverflow.visible, style: TextStyle( fontSize: 9, color: textColor ?? From 7b27990e296ad35215be1fa4c6f506a86cd711ad Mon Sep 17 00:00:00 2001 From: nahuhh <50635951+nahuhh@users.noreply.github.com> Date: Mon, 7 Apr 2025 18:08:07 +0100 Subject: [PATCH 009/142] add/remove coins from readme & matic -> pol on add wallet page (#2165) * readme: remove xhv & add missing coins * cw_core: wallet_type rename matic -> pol --- README.md | 11 +++++------ cw_core/lib/wallet_type.dart | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ea8f34624..ea796dbf2 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,13 @@ Cake Wallet includes support for several cryptocurrencies, including: * Ethereum (ETH) * Litecoin (LTC) * Bitcoin Cash (BCH) -* Polygon (Pol) +* Polygon (POL) * Solana (SOL) +* Tron (TRX) * Nano (XNO) -* Haven (XHV) +* Zano (ZANO) +* Decred (DCR) +* Wownero (WOW) ## Features @@ -81,10 +84,6 @@ Cake Wallet includes support for several cryptocurrencies, including: * Automatically generate new addresses * Specify multiple recipients for batch sending -### Haven Specific Features - -* Send, receive, and store XHV and all xAssets like xUSD, xEUR, xAG, etc. - # Monero.com by Cake Wallet for Android and iOS ## Open Source Monero-Only Wallet diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart index 5ae1c1290..fed998ed0 100644 --- a/cw_core/lib/wallet_type.dart +++ b/cw_core/lib/wallet_type.dart @@ -193,7 +193,7 @@ String walletTypeToDisplayName(WalletType type) { case WalletType.banano: return 'Banano (BAN)'; case WalletType.polygon: - return 'Polygon (MATIC)'; + return 'Polygon (POL)'; case WalletType.solana: return 'Solana (SOL)'; case WalletType.tron: From 29bc130562da073db1598fd3bffd8f4c3c36f272 Mon Sep 17 00:00:00 2001 From: Serhii Date: Tue, 8 Apr 2025 20:04:16 +0300 Subject: [PATCH 010/142] fix:bottom sheet navigation context (#2178) --- lib/src/screens/send/send_page.dart | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 9fd55af14..91f94e060 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -639,12 +639,14 @@ class SendPage extends BasePage { actionButtonKey: ValueKey('send_page_sent_dialog_ok_button_key'), actionButton: () { Navigator.of(bottomSheetContext).pop(); - if (context.mounted) { - Navigator.of(context) - .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false); - } - RequestReviewHandler.requestReview(); - newContactAddress = null; + Future.delayed(Duration.zero, () { + if (context.mounted) { + Navigator.of(context) + .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false); + } + RequestReviewHandler.requestReview(); + newContactAddress = null; + }); }, ); }, From 9fb30cc0fbebe55e383942f38a7384aaf2a96b79 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Tue, 8 Apr 2025 19:05:00 +0200 Subject: [PATCH 011/142] minor fix [skip ci] --- lib/src/screens/nodes/node_create_or_edit_page.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/screens/nodes/node_create_or_edit_page.dart b/lib/src/screens/nodes/node_create_or_edit_page.dart index fc6ac07e0..e62b6cb01 100644 --- a/lib/src/screens/nodes/node_create_or_edit_page.dart +++ b/lib/src/screens/nodes/node_create_or_edit_page.dart @@ -182,7 +182,9 @@ class NodeCreateOrEditPage extends BasePage { await nodeCreateOrEditViewModel.save( editingNode: editingNode, saveAsCurrent: isSelected ?? false); - Navigator.of(context).pop(); + if (context.mounted) { + Navigator.of(context).pop(); + } }, text: S.of(context).save, color: Theme.of(context).primaryColor, From b949584645858a19f472089bd6fb178621b055dd Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Tue, 8 Apr 2025 20:30:32 +0200 Subject: [PATCH 012/142] minor fix [skip ci] --- lib/src/screens/wallet_connect/wc_pairing_detail_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart b/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart index f99eb9cdb..321d1354a 100644 --- a/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart +++ b/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart @@ -101,7 +101,7 @@ class WCCDetailsWidget extends BasePage { child: CircleAvatar( backgroundImage: (pairing.peerMetadata!.icons.isNotEmpty ? NetworkImage(pairing.peerMetadata!.icons[0]) - : const AssetImage('assets/images/default_icon.png')) + : const AssetImage('assets/images/app_logo.png')) as ImageProvider, ), ), From 494207290e64d27ad808cdd91a4aa9a30fdbfdbc Mon Sep 17 00:00:00 2001 From: cyan Date: Wed, 9 Apr 2025 02:52:14 +0200 Subject: [PATCH 013/142] fix: disable impeller backend (#2176) --- android/app/src/main/AndroidManifestBase.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index 9a324edf3..4f15370c3 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -107,6 +107,9 @@ + Date: Thu, 10 Apr 2025 03:31:26 +0200 Subject: [PATCH 014/142] CW-723-Add-Monero-support-to-the-Shared-Seed-feature-in-Cake (#2131) * feat: add exodus style bip39 to monero legacy seed * feat: restore monero wallet from bip39 and add test * bug: fix wrong naming in CI * feat: add monero bip39 UI flow * fix: monero.dart generation * fix: skip monero_wallet_service tests till CI is fixed * ci: copy monero_libwallet2_api_c.so to /usr/lib for testing ci: reduce timeout for cw_monero tests * fix: monero wallet creation credentials default to bip39 if mnemonic are set * fix: do not skip monero wallets services test * fix: Include non bip39 monero wallets on Wallet Group * fix: null pointer stemming from missing language selector if seed is selected * fix: Fixes to Bip39 Creation and restore - Do not restore from 0 for fresh bip39 wallet - disallow restoring bip39 wallet without date or height * fix: Fixes to Bip39 restore - Refresh height is now getting set correctly - Add new create monero wallet tests - Add seed-language English for Bip39 Monero wallets - Fix seed-type naming * feat (cw_monero): Store monero wallet after bip39 creation * feat (cw_monero): remove prints from monero_wallet_service_test.dart * fix: exception during seed language autodetect * feat (cw_monero): Add support for passphrases on bip39 seeds * feat (cw_monero): Add support for passphrases on bip39 seeds * fix: seed language selection for recovering bip39 wallets * style: improve readability of isLegacySeedOnly in wallet_keys_view_model.dart * feat: hide monero seed type selector from advanced settings when creating a child wallet * fix(cw_monero): use named arguments for bip39_seed tests --------- Co-authored-by: cyan --- .github/workflows/pr_test_build_linux.yml | 3 + cw_monero/.gitignore | 3 +- cw_monero/lib/api/wallet.dart | 5 + cw_monero/lib/bip39_seed.dart | 59 +++++++ cw_monero/lib/monero_wallet_service.dart | 162 ++++++++++++++---- cw_monero/pubspec.lock | 87 ++++++++-- cw_monero/pubspec.yaml | 5 +- cw_monero/test/bip39_seed_test.dart | 39 +++++ cw_monero/test/mock/path_provider.dart | 29 ++++ .../test/monero_wallet_service_test.dart | 148 ++++++++++++++++ cw_monero/test/utils/setup_monero_c.dart | 16 ++ lib/entities/seed_type.dart | 25 +-- lib/monero/cw_monero.dart | 16 +- lib/reactions/bip39_wallet_utils.dart | 2 +- .../advanced_privacy_settings_page.dart | 4 +- .../screens/new_wallet/new_wallet_page.dart | 8 +- .../wallet_restore_from_seed_form.dart | 153 +++++++++-------- .../screens/restore/wallet_restore_page.dart | 42 +++-- lib/src/widgets/seed_language_picker.dart | 2 +- .../wallet_groups_display_view_model.dart | 7 +- lib/view_model/wallet_keys_view_model.dart | 7 +- lib/view_model/wallet_new_vm.dart | 21 ++- res/values/strings_ar.arb | 5 +- res/values/strings_bg.arb | 5 +- res/values/strings_cs.arb | 5 +- res/values/strings_de.arb | 5 +- res/values/strings_en.arb | 5 +- res/values/strings_es.arb | 5 +- res/values/strings_fr.arb | 5 +- res/values/strings_ha.arb | 5 +- res/values/strings_hi.arb | 5 +- res/values/strings_hr.arb | 5 +- res/values/strings_hy.arb | 5 +- res/values/strings_id.arb | 5 +- res/values/strings_it.arb | 5 +- res/values/strings_ja.arb | 5 +- res/values/strings_ko.arb | 5 +- res/values/strings_my.arb | 5 +- res/values/strings_nl.arb | 5 +- res/values/strings_pl.arb | 5 +- res/values/strings_pt.arb | 5 +- res/values/strings_ru.arb | 5 +- res/values/strings_th.arb | 5 +- res/values/strings_tl.arb | 5 +- res/values/strings_tr.arb | 5 +- res/values/strings_uk.arb | 5 +- res/values/strings_ur.arb | 5 +- res/values/strings_vi.arb | 5 +- res/values/strings_yo.arb | 5 +- res/values/strings_zh.arb | 5 +- tool/configure.dart | 2 +- 51 files changed, 702 insertions(+), 283 deletions(-) create mode 100644 cw_monero/lib/bip39_seed.dart create mode 100644 cw_monero/test/bip39_seed_test.dart create mode 100644 cw_monero/test/mock/path_provider.dart create mode 100644 cw_monero/test/monero_wallet_service_test.dart create mode 100644 cw_monero/test/utils/setup_monero_c.dart diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml index a341aed0d..476a033a0 100644 --- a/.github/workflows/pr_test_build_linux.yml +++ b/.github/workflows/pr_test_build_linux.yml @@ -283,6 +283,9 @@ jobs: xmessage -timeout 30 "restore_wallet_through_seeds_flow_test" & rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart + - name: Test [cw_monero] + timeout-minutes: 2 + run: cd cw_monero && flutter test - name: Stop screen recording, encrypt and upload if: always() run: | diff --git a/cw_monero/.gitignore b/cw_monero/.gitignore index ebb19df82..2bf094c85 100644 --- a/cw_monero/.gitignore +++ b/cw_monero/.gitignore @@ -11,4 +11,5 @@ android/.externalNativeBuild/ android/.cxx/ macos/cw_monero.podspec -macos/External/ \ No newline at end of file +macos/External/ +*monero_libwallet2_api_c.* \ No newline at end of file diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index f520c6599..7e64c7f08 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -62,6 +62,11 @@ String getSeed() { } return cakepolyseed; } + + final bip39 = monero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.seed.bip39"); + + if(bip39.isNotEmpty) return bip39; + final legacy = getSeedLegacy(null); return legacy; } diff --git a/cw_monero/lib/bip39_seed.dart b/cw_monero/lib/bip39_seed.dart new file mode 100644 index 000000000..338516e66 --- /dev/null +++ b/cw_monero/lib/bip39_seed.dart @@ -0,0 +1,59 @@ +import 'dart:typed_data'; + +import 'package:bip32/bip32.dart' as bip32; +import 'package:bip39/bip39.dart' as bip39; +import 'package:polyseed/polyseed.dart'; + +bool isBip39Seed(String mnemonic) => bip39.validateMnemonic(mnemonic); + +String getBip39Seed() => bip39.generateMnemonic(); + +String getLegacySeedFromBip39(String mnemonic, + {int accountIndex = 0, String passphrase = ""}) { + final seed = bip39.mnemonicToSeed(mnemonic, passphrase: passphrase); + + final bip32KeyPair = + bip32.BIP32.fromSeed(seed).derivePath("m/44'/128'/$accountIndex'/0/0"); + + final spendKey = _reduceECKey(bip32KeyPair.privateKey!); + + return LegacySeedLang.getByEnglishName("English") + .encodePhrase(spendKey.toHexString()); +} + +const _ed25519CurveOrder = + "1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED"; + +Uint8List _reduceECKey(Uint8List buffer) { + final curveOrder = BigInt.parse(_ed25519CurveOrder, radix: 16); + final bigNumber = _readBytes(buffer); + + var result = bigNumber % curveOrder; + + final resultBuffer = Uint8List(32); + for (var i = 0; i < 32; i++) { + resultBuffer[i] = (result & BigInt.from(0xff)).toInt(); + result = result >> 8; + } + + return resultBuffer; +} + +/// Read BigInt from a little-endian Uint8List +/// From https://github.com/dart-lang/sdk/issues/32803#issuecomment-387405784 +BigInt _readBytes(Uint8List bytes) { + BigInt read(int start, int end) { + if (end - start <= 4) { + var result = 0; + for (int i = end - 1; i >= start; i--) { + result = result * 256 + bytes[i]; + } + return BigInt.from(result); + } + final mid = start + ((end - start) >> 1); + return read(start, mid) + + read(mid, end) * (BigInt.one << ((mid - start) * 8)); + } + + return read(0, bytes.length); +} diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart index ad726ef8c..e289f9f17 100644 --- a/cw_monero/lib/monero_wallet_service.dart +++ b/cw_monero/lib/monero_wallet_service.dart @@ -1,6 +1,7 @@ import 'dart:ffi'; import 'dart:io'; +import 'package:collection/collection.dart'; import 'package:cw_core/get_height_by_date.dart'; import 'package:cw_core/monero_wallet_utils.dart'; import 'package:cw_core/pathForWallet.dart'; @@ -14,29 +15,38 @@ import 'package:cw_core/wallet_type.dart'; import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager; import 'package:cw_monero/api/wallet_manager.dart'; +import 'package:cw_monero/bip39_seed.dart'; import 'package:cw_monero/ledger.dart'; import 'package:cw_monero/monero_wallet.dart'; -import 'package:collection/collection.dart'; import 'package:hive/hive.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:monero/monero.dart' as monero; import 'package:polyseed/polyseed.dart'; +enum MoneroSeedType { polyseed, legacy, bip39 } + class MoneroNewWalletCredentials extends WalletCredentials { MoneroNewWalletCredentials( - {required String name, required this.language, required this.isPolyseed, String? password, this.passphrase}) + {required String name, + required this.language, + required this.seedType, + String? password, + this.passphrase, + this.mnemonic}) : super(name: name, password: password); final String language; - final bool isPolyseed; + final MoneroSeedType seedType; final String? passphrase; + final String? mnemonic; } class MoneroRestoreWalletFromHardwareCredentials extends WalletCredentials { - MoneroRestoreWalletFromHardwareCredentials({required String name, - required this.ledgerConnection, - int height = 0, - String? password}) + MoneroRestoreWalletFromHardwareCredentials( + {required String name, + required this.ledgerConnection, + int height = 0, + String? password}) : super(name: name, password: password, height: height); LedgerConnection ledgerConnection; } @@ -60,13 +70,14 @@ class MoneroWalletLoadingException implements Exception { } class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials { - MoneroRestoreWalletFromKeysCredentials({required String name, - required String password, - required this.language, - required this.address, - required this.viewKey, - required this.spendKey, - int height = 0}) + MoneroRestoreWalletFromKeysCredentials( + {required String name, + required String password, + required this.language, + required this.address, + required this.viewKey, + required this.spendKey, + int height = 0}) : super(name: name, password: password, height: height); final String language; @@ -97,27 +108,42 @@ class MoneroWalletService extends WalletService< @override WalletType getType() => WalletType.monero; - @override - Future create(MoneroNewWalletCredentials credentials, {bool? isTestnet}) async { + @override + Future create(MoneroNewWalletCredentials credentials, + {bool? isTestnet}) async { try { final path = await pathForWallet(name: credentials.name, type: getType()); - if (credentials.isPolyseed) { + if (credentials.seedType == MoneroSeedType.bip39) { + return _restoreFromBip39( + path: path, + password: credentials.password!, + mnemonic: credentials.mnemonic ?? getBip39Seed(), + passphrase: credentials.passphrase, + walletInfo: credentials.walletInfo!, + ); + } + + if (credentials.seedType == MoneroSeedType.polyseed) { final polyseed = Polyseed.create(); final lang = PolyseedLang.getByEnglishName(credentials.language); - if (credentials.passphrase != null) polyseed.crypt(credentials.passphrase!); + if (credentials.passphrase != null) + polyseed.crypt(credentials.passphrase!); - final heightOverride = - getMoneroHeigthByDate(date: DateTime.now().subtract(Duration(days: 2))); + final heightOverride = getMoneroHeigthByDate( + date: DateTime.now().subtract(Duration(days: 2))); - return _restoreFromPolyseed( - path, credentials.password!, polyseed, credentials.walletInfo!, lang, + return _restoreFromPolyseed(path, credentials.password!, polyseed, + credentials.walletInfo!, lang, overrideHeight: heightOverride, passphrase: credentials.passphrase); } await monero_wallet_manager.createWallet( - path: path, password: credentials.password!, language: credentials.language, passphrase: credentials.passphrase??""); + path: path, + password: credentials.password!, + language: credentials.language, + passphrase: credentials.passphrase ?? ""); final wallet = MoneroWallet( walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource, @@ -145,7 +171,8 @@ class MoneroWalletService extends WalletService< } @override - Future openWallet(String name, String password, {OpenWalletTry openWalletTry = OpenWalletTry.initial}) async { + Future openWallet(String name, String password, + {OpenWalletTry openWalletTry = OpenWalletTry.initial}) async { try { final path = await pathForWallet(name: name, type: getType()); @@ -303,8 +330,28 @@ class MoneroWalletService extends WalletService< rethrow; } + try { + if (isBip39Seed(credentials.mnemonic)) { + final path = + await pathForWallet(name: credentials.name, type: getType()); + + return _restoreFromBip39( + path: path, + password: credentials.password!, + mnemonic: credentials.mnemonic, + walletInfo: credentials.walletInfo!, + overrideHeight: credentials.height!, + passphrase: credentials.passphrase, + ); + } + } catch (e) { + printV("Bip39 restore failed: $e"); + rethrow; + } + try { final path = await pathForWallet(name: credentials.name, type: getType()); + monero_wallet_manager.restoreFromSeed( path: path, password: credentials.password!, @@ -325,6 +372,50 @@ class MoneroWalletService extends WalletService< } } + Future _restoreFromBip39({ + required String path, + required String password, + required String mnemonic, + required WalletInfo walletInfo, + String? passphrase, + int? overrideHeight, + }) async { + walletInfo.derivationInfo = DerivationInfo( + derivationType: DerivationType.bip39, + derivationPath: "m/44'/128'/0'/0/0", + ); + + final legacyMnemonic = + getLegacySeedFromBip39(mnemonic, passphrase: passphrase ?? ""); + final height = + overrideHeight ?? getMoneroHeigthByDate(date: DateTime.now()); + + walletInfo.isRecovery = true; + walletInfo.restoreHeight = height; + + monero_wallet_manager.restoreFromSeed( + path: path, + password: password, + passphrase: '', + seed: legacyMnemonic, + restoreHeight: height, + ); + + monero.Wallet_setCacheAttribute(wptr!, + key: "cakewallet.seed.bip39", value: mnemonic); + + monero.Wallet_store(wptr!); + + final wallet = MoneroWallet( + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfoSource, + password: password, + ); + await wallet.init(); + + return wallet; + } + Future restoreFromPolyseed( MoneroRestoreWalletFromSeedCredentials credentials) async { try { @@ -344,23 +435,21 @@ class MoneroWalletService extends WalletService< } } - Future _restoreFromPolyseed( - String path, String password, Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang, + Future _restoreFromPolyseed(String path, String password, + Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang, {PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO, int? overrideHeight, String? passphrase}) async { - - if (polyseed.isEncrypted == false && - (passphrase??'') != "") { + if (polyseed.isEncrypted == false && (passphrase ?? '') != "") { // Fallback to the different passphrase offset method, when a passphrase // was provided but the polyseed is not encrypted. monero_wallet_manager.restoreWalletFromPolyseedWithOffset( - path: path, - password: password, - seed: polyseed.encode(lang, coin), - seedOffset: passphrase??'', - language: "English"); - + path: path, + password: password, + seed: polyseed.encode(lang, coin), + seedOffset: passphrase ?? '', + language: "English"); + final wallet = MoneroWallet( walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource, @@ -437,7 +526,8 @@ class MoneroWalletService extends WalletService< if (walletFilesExist(path)) await repairOldAndroidWallet(name); - await monero_wallet_manager.openWalletAsync({'path': path, 'password': password}); + await monero_wallet_manager + .openWalletAsync({'path': path, 'password': password}); final walletInfo = walletInfoSource.values .firstWhere((info) => info.id == WalletBase.idFor(name, getType())); final wallet = MoneroWallet( diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index c808e5edb..278226b07 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -5,18 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8" + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.dev" source: hosted - version: "47.0.0" + version: "76.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.3" analyzer: dependency: transitive description: name: analyzer - sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80" + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "6.11.0" args: dependency: transitive description: @@ -41,6 +46,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + bip32: + dependency: "direct main" + description: + name: bip32 + sha256: "54787cd7a111e9d37394aabbf53d1fc5e2e0e0af2cd01c459147a97c0e3f8a97" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + bip39: + dependency: "direct main" + description: + name: bip39 + sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc + url: "https://pub.dev" + source: hosted + version: "1.0.6" blockchain_utils: dependency: transitive description: @@ -66,6 +87,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + bs58check: + dependency: transitive + description: + name: bs58check + sha256: c4a164d42b25c2f6bc88a8beccb9fc7d01440f3c60ba23663a20a70faf484ea9 + url: "https://pub.dev" + source: hosted + version: "1.0.2" build: dependency: transitive description: @@ -94,10 +123,10 @@ packages: dependency: "direct dev" description: name: build_resolvers - sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6" + sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 url: "https://pub.dev" source: hosted - version: "2.0.10" + version: "2.4.4" build_runner: dependency: "direct dev" description: @@ -222,10 +251,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" + sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.3.8" dbus: dependency: transitive description: @@ -348,6 +377,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.6.0" + hex: + dependency: transitive + description: + name: hex + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" + source: hosted + version: "0.2.0" hive: dependency: transitive description: @@ -360,10 +397,10 @@ packages: dependency: "direct dev" description: name: hive_generator - sha256: "81fd20125cb2ce8fd23623d7744ffbaf653aae93706c9bd3bf7019ea0ace3938" + sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "2.0.1" http: dependency: "direct main" description: @@ -468,6 +505,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + macros: + dependency: transitive + description: + name: macros + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" + url: "https://pub.dev" + source: hosted + version: "0.1.3-main.0" matcher: dependency: transitive description: @@ -512,10 +557,18 @@ packages: dependency: "direct dev" description: name: mobx_codegen - sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c + sha256: "990da80722f7d7c0017dec92040b31545d625b15d40204c36a1e63d167c73cdc" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.7.0" + mockito: + dependency: "direct dev" + description: + name: mockito + sha256: f99d8d072e249f719a5531735d146d8cf04c580d93920b04de75bef6dfb2daf6 + url: "https://pub.dev" + source: hosted + version: "5.4.5" monero: dependency: "direct main" description: @@ -735,18 +788,18 @@ packages: dependency: transitive description: name: source_gen - sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d" + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" url: "https://pub.dev" source: hosted - version: "1.2.6" + version: "1.5.0" source_helper: dependency: transitive description: name: source_helper - sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f" + sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" url: "https://pub.dev" source: hosted - version: "1.3.3" + version: "1.3.5" source_span: dependency: transitive description: @@ -924,5 +977,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.5.0 <4.0.0" + dart: ">=3.6.0 <4.0.0" flutter: ">=3.24.0" diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index 862109e94..0e1537ee0 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -12,6 +12,8 @@ environment: dependencies: flutter: sdk: flutter + bip39: ^1.0.6 + bip32: ^2.0.0 ffi: ^2.0.1 http: ^1.1.0 path_provider: ^2.0.11 @@ -36,7 +38,8 @@ dev_dependencies: build_runner: ^2.4.7 build_resolvers: ^2.0.9 mobx_codegen: ^2.0.7 - hive_generator: ^1.1.3 + mockito: ^5.4.5 + hive_generator: ^2.0.1 dependency_overrides: watcher: ^1.1.0 diff --git a/cw_monero/test/bip39_seed_test.dart b/cw_monero/test/bip39_seed_test.dart new file mode 100644 index 000000000..add461553 --- /dev/null +++ b/cw_monero/test/bip39_seed_test.dart @@ -0,0 +1,39 @@ +import 'package:cw_monero/bip39_seed.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group("Exodus Style bip39", () { + group("Test Wallet 1", () { + final bip39Seed = 'meadow tip best belt boss eyebrow control affair eternal piece very shiver'; + final expectedLegacySeed0 = "tasked eight afraid laboratory tail feline rift reinvest vane cafe bailed foggy dormant paper jigsaw king hazard suture king dapper dummy jolted dating dwindling king"; + final expectedLegacySeed1 = "palace pairing axes mohawk rekindle excess awful juvenile shipped talent nibs efficient dapper biggest swung fight pact innocent emerge issued titans affair nearby noises emerge"; + + test("Get legacy Seed from bip39", () { + final legacySeed = getLegacySeedFromBip39(bip39Seed); + expect(legacySeed, expectedLegacySeed0); + }); + + test("Get legacy Seed from bip39 with account index", () { + final legacySeed = getLegacySeedFromBip39(bip39Seed, accountIndex: 1); + expect(legacySeed, expectedLegacySeed1); + }); + }); + + group("Test Wallet 2", () { + final bip39Seed = "color ranch color remove subway public water embrace before begin liberty fault"; + final expectedLegacySeed0 = "somewhere problems gauze gigantic intended foxes upcoming saved waffle pipeline lurk bogeys empty wipeout abbey italics novelty tucks rafts elite lunar obnoxious awful bugs elite"; + final expectedLegacySeed1 = "playful toxic wildly eluded mesh fainted february mugged maps repent vigilant hitched seventh threaten clue fetches sample diet number alkaline future cottage tuition vegan alkaline"; + + test("Get legacy Seed from bip39", () { + final legacySeed = getLegacySeedFromBip39(bip39Seed); + expect(legacySeed, expectedLegacySeed0); + }); + + test("Get legacy Seed from bip39 with account index", () { + final legacySeed = getLegacySeedFromBip39(bip39Seed, accountIndex: 1); + expect(legacySeed, expectedLegacySeed1); + }); + }); + + }); +} diff --git a/cw_monero/test/mock/path_provider.dart b/cw_monero/test/mock/path_provider.dart new file mode 100644 index 000000000..7902d5bf0 --- /dev/null +++ b/cw_monero/test/mock/path_provider.dart @@ -0,0 +1,29 @@ +import 'package:mockito/mockito.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +class MockPathProviderPlatform extends Mock + with MockPlatformInterfaceMixin + implements PathProviderPlatform { + Future getTemporaryPath() => throw UnimplementedError(); + + Future getApplicationSupportPath() => throw UnimplementedError(); + + Future getLibraryPath() => throw UnimplementedError(); + + Future getApplicationDocumentsPath() async => "./test/data"; + + Future getExternalStoragePath() => throw UnimplementedError(); + + Future> getExternalCachePaths() => throw UnimplementedError(); + + Future getDownloadsPath() => throw UnimplementedError(); + + @override + Future getApplicationCachePath() => throw UnimplementedError(); + + @override + Future?> getExternalStoragePaths({StorageDirectory? type}) => + throw UnimplementedError(); +} diff --git a/cw_monero/test/monero_wallet_service_test.dart b/cw_monero/test/monero_wallet_service_test.dart new file mode 100644 index 000000000..a809dbc23 --- /dev/null +++ b/cw_monero/test/monero_wallet_service_test.dart @@ -0,0 +1,148 @@ +import 'dart:io'; + +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cw_monero/monero_wallet_service.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:hive/hive.dart'; +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; + +import 'mock/path_provider.dart'; +import 'utils/setup_monero_c.dart'; + +Future main() async { + group("MoneroWalletService Tests", () { + Hive.init('./test/data/db'); + late MoneroWalletService walletService; + late File moneroCBinary; + + setUpAll(() async { + PathProviderPlatform.instance = MockPathProviderPlatform(); + + final Box walletInfoSource = + await Hive.openBox('testWalletInfo'); + final Box unspentCoinsInfoSource = + await Hive.openBox('testUnspentCoinsInfo'); + + walletService = MoneroWalletService(walletInfoSource, unspentCoinsInfoSource); + moneroCBinary = getMoneroCBinary().copySync(moneroCBinaryName); + }); + + tearDownAll(() { + Directory('./test/data').deleteSync(recursive: true); + moneroCBinary.deleteSync(); + }); + + group("Create wallet", () { + test("Create Legacy Wallet", () async { + final credentials = _getTestCreateCredentials( + name: 'Create Wallet LS', + language: 'English', + seedType: MoneroSeedType.legacy); + final wallet = await walletService.create(credentials); + + expect(wallet.seed.split(" ").length, 25); + expect(wallet.restoreHeight, greaterThan(3000000)); + }); + + test("Create Polyseed Wallet", () async { + final credentials = _getTestCreateCredentials( + name: 'Create Wallet PS', + language: 'English', + seedType: MoneroSeedType.polyseed); + final wallet = await walletService.create(credentials); + + expect(wallet.seed.split(" ").length, 16); + expect(wallet.restoreHeight, greaterThan(3000000)); + }); + + test("Create Bip39 Wallet", () async { + final credentials = _getTestCreateCredentials( + name: 'Create Wallet BS', + language: 'English', + seedType: MoneroSeedType.bip39); + final wallet = await walletService.create(credentials); + + expect(wallet.seed.split(" ").length, 12); + expect(wallet.restoreHeight, greaterThan(3000000)); + }); + }); + + group("Restore wallet", () { + test('Legacy Seed', () async { + final credentials = _getTestRestoreCredentials( + name: 'Test Wallet LS', + mnemonic: + 'ability pockets lordship tomorrow gypsy match neutral uncle avatar betting bicycle junk unzip pyramid lynx mammal edgy empty uneven knowledge juvenile wiring paradise psychic betting', + ); + + final wallet = await walletService.restoreFromSeed(credentials); + expect(wallet.walletAddresses.primaryAddress, + '48tLyQXpcwt8w6uKHyb5Zs3vdnoDWAEKFQr1c198o7aX9dBzXP3BTSMVsDiuH3ozDCNqwojb4vNeQZf7xg6URimDLaNtGSN'); + }); + + test('Bip39 Seed', () async { + final credentials = _getTestRestoreCredentials( + name: 'Test Wallet BS', + mnemonic: + 'color ranch color remove subway public water embrace before begin liberty fault'); + + final wallet = await walletService.restoreFromSeed(credentials); + expect(wallet.walletAddresses.primaryAddress, + '49MggvPosJugF8Zq7WAKbsSchz6vbyL6YiUxM4ryfGQDXphs6wiWiXLFWCSshnLPcceGTWUaKfWWMHQAAKESV3TQJVQsL9a'); + }); + }); + }); +} + +MoneroRestoreWalletFromSeedCredentials _getTestRestoreCredentials({ + required String name, + required String mnemonic, +}) { + final credentials = MoneroRestoreWalletFromSeedCredentials( + name: name, mnemonic: mnemonic, passphrase: '', password: "test"); + + credentials.walletInfo = WalletInfo.external( + id: WalletBase.idFor(name, WalletType.monero), + name: name, + type: WalletType.monero, + isRecovery: true, + restoreHeight: credentials.height ?? 0, + date: DateTime.now(), + path: '', + dirPath: '', + address: '', + ); + return credentials; +} + +MoneroNewWalletCredentials _getTestCreateCredentials({ + required String name, + required String language, + required MoneroSeedType seedType, + String? mnemonic, +}) { + final credentials = MoneroNewWalletCredentials( + name: name, + language: language, + seedType: seedType, + password: "test", + mnemonic: mnemonic, + passphrase: '', + ); + + credentials.walletInfo = WalletInfo.external( + id: WalletBase.idFor(name, WalletType.monero), + name: name, + type: WalletType.monero, + isRecovery: false, + restoreHeight: credentials.height ?? 0, + date: DateTime.now(), + path: '', + dirPath: '', + address: '', + ); + return credentials; +} diff --git a/cw_monero/test/utils/setup_monero_c.dart b/cw_monero/test/utils/setup_monero_c.dart new file mode 100644 index 000000000..1a0981000 --- /dev/null +++ b/cw_monero/test/utils/setup_monero_c.dart @@ -0,0 +1,16 @@ +import 'dart:io'; + +File getMoneroCBinary() { + if (Platform.isWindows) + return File( + '../scripts/monero_c/release/monero/x86_64-w64-mingw32_libwallet2_api_c.dll'); + if (Platform.isMacOS) return File('../macos/monero_libwallet2_api_c.dylib'); + return File('../scripts/monero_c/release/monero/x86_64-linux-gnu_libwallet2_api_c.so'); +} + +String get moneroCBinaryName { + if (Platform.isWindows) + return "monero_libwallet2_api_c.dll"; + if (Platform.isMacOS) return "monero_libwallet2_api_c.dylib"; + return "/usr/lib/monero_libwallet2_api_c.so"; +} diff --git a/lib/entities/seed_type.dart b/lib/entities/seed_type.dart index 20600e704..0548d9cee 100644 --- a/lib/entities/seed_type.dart +++ b/lib/entities/seed_type.dart @@ -1,17 +1,17 @@ -import 'package:cake_wallet/generated/i18n.dart'; import 'package:cw_core/enumerable_item.dart'; import 'package:cw_core/wallet_info.dart'; class MoneroSeedType extends EnumerableItem with Serializable { const MoneroSeedType({required String title, required int raw}) : super(title: title, raw: raw); - static const all = [MoneroSeedType.legacy, MoneroSeedType.polyseed]; + static const all = [legacy, polyseed, bip39]; static const defaultSeedType = polyseed; - static const legacy = MoneroSeedType(raw: 0, title: 'Legacy (25 words)'); - static const polyseed = MoneroSeedType(raw: 1, title: 'Polyseed (16 words)'); - static const wowneroSeed = MoneroSeedType(raw: 2, title: 'Wownero (14 words)'); + static const legacy = MoneroSeedType(raw: 0, title: 'Legacy'); + static const polyseed = MoneroSeedType(raw: 1, title: 'Polyseed'); + static const wowneroSeed = MoneroSeedType(raw: 2, title: 'Wownero'); + static const bip39 = MoneroSeedType(raw: 3, title: 'BIP39'); static MoneroSeedType deserialize({required int raw}) { switch (raw) { @@ -21,24 +21,15 @@ class MoneroSeedType extends EnumerableItem with Serializable { return polyseed; case 2: return wowneroSeed; + case 3: + return bip39; default: throw Exception('Unexpected token: $raw for SeedType deserialize'); } } @override - String toString() { - switch (this) { - case MoneroSeedType.legacy: - return S.current.seedtype_legacy; - case MoneroSeedType.polyseed: - return S.current.seedtype_polyseed; - case MoneroSeedType.wowneroSeed: - return S.current.seedtype_wownero; - default: - return ''; - } - } + String toString() => title; } class BitcoinSeedType extends EnumerableItem with Serializable { diff --git a/lib/monero/cw_monero.dart b/lib/monero/cw_monero.dart index 6c72cbe67..b6be123c2 100644 --- a/lib/monero/cw_monero.dart +++ b/lib/monero/cw_monero.dart @@ -252,11 +252,21 @@ class CWMonero extends Monero { WalletCredentials createMoneroNewWalletCredentials({ required String name, required String language, - required bool isPolyseed, + required int seedType, required String? passphrase, - String? password}) => + String? password, + String? mnemonic, + }) => MoneroNewWalletCredentials( - name: name, password: password, language: language, isPolyseed: isPolyseed, passphrase: passphrase); + name: name, + password: password, + language: language, + seedType: seedType == 1 + ? MoneroSeedType.polyseed + : (seedType == 3 ? MoneroSeedType.bip39 : MoneroSeedType.legacy), + passphrase: passphrase, + mnemonic: mnemonic, + ); @override Map getKeys(Object wallet) { diff --git a/lib/reactions/bip39_wallet_utils.dart b/lib/reactions/bip39_wallet_utils.dart index 0c58bc76f..a46adb6b1 100644 --- a/lib/reactions/bip39_wallet_utils.dart +++ b/lib/reactions/bip39_wallet_utils.dart @@ -11,8 +11,8 @@ bool isBIP39Wallet(WalletType walletType) { case WalletType.bitcoinCash: case WalletType.nano: case WalletType.banano: - return true; case WalletType.monero: + return true; case WalletType.wownero: case WalletType.haven: case WalletType.zano: diff --git a/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart index f8901918f..d03f334a1 100644 --- a/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart +++ b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart @@ -144,7 +144,9 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo ), ); }), - if (widget.privacySettingsViewModel.isMoneroSeedTypeOptionsEnabled) + if (widget + .privacySettingsViewModel.isMoneroSeedTypeOptionsEnabled && + !widget.isChildWallet) Observer(builder: (_) { return SettingsChoicesCell( ChoicesListItem( diff --git a/lib/src/screens/new_wallet/new_wallet_page.dart b/lib/src/screens/new_wallet/new_wallet_page.dart index 368b3440d..83c14cd56 100644 --- a/lib/src/screens/new_wallet/new_wallet_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_page.dart @@ -305,7 +305,7 @@ class _WalletNameFormState extends State { ), ), ), - if (_walletNewVM.hasLanguageSelector) ...[ + if (_walletNewVM.showLanguageSelector) ...[ if (_walletNewVM.hasSeedType) ...[ Observer( builder: (BuildContext build) => Padding( @@ -401,7 +401,11 @@ class _WalletNameFormState extends State { } else { await _walletNewVM.create( options: _walletNewVM.hasLanguageSelector - ? [_languageSelectorKey.currentState!.selected, isPolyseed] + ? [ + _languageSelectorKey.currentState?.selected ?? + defaultSeedLanguage, + widget._seedSettingsViewModel.moneroSeedType + ] : null); } } catch (e) { diff --git a/lib/src/screens/restore/wallet_restore_from_seed_form.dart b/lib/src/screens/restore/wallet_restore_from_seed_form.dart index d089f8c1c..af8261662 100644 --- a/lib/src/screens/restore/wallet_restore_from_seed_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_seed_form.dart @@ -11,13 +11,15 @@ import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:cake_wallet/view_model/seed_settings_view_model.dart'; +import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:mobx/mobx.dart'; import 'package:polyseed/polyseed.dart'; class WalletRestoreFromSeedForm extends StatefulWidget { - WalletRestoreFromSeedForm({Key? key, + WalletRestoreFromSeedForm({ + Key? key, required this.displayLanguageSelector, required this.displayBlockHeightSelector, required this.type, @@ -47,21 +49,23 @@ class WalletRestoreFromSeedForm extends StatefulWidget { @override WalletRestoreFromSeedFormState createState() => - WalletRestoreFromSeedFormState('English', displayWalletPassword: displayWalletPassword); + WalletRestoreFromSeedFormState('English', + displayWalletPassword: displayWalletPassword); } class WalletRestoreFromSeedFormState extends State { - WalletRestoreFromSeedFormState(this.language, {required bool displayWalletPassword}) + WalletRestoreFromSeedFormState(this.language, + {required bool displayWalletPassword}) : seedWidgetStateKey = GlobalKey(), blockchainHeightKey = GlobalKey(), formKey = GlobalKey(), languageController = TextEditingController(), nameTextEditingController = TextEditingController(), - passwordTextEditingController = displayWalletPassword ? TextEditingController() : null, - repeatedPasswordTextEditingController = displayWalletPassword - ? TextEditingController() - : null, - seedTypeController = TextEditingController(); + passwordTextEditingController = + displayWalletPassword ? TextEditingController() : null, + repeatedPasswordTextEditingController = + displayWalletPassword ? TextEditingController() : null, + seedTypeController = TextEditingController(); final GlobalKey seedWidgetStateKey; final GlobalKey blockchainHeightKey; @@ -83,27 +87,30 @@ class WalletRestoreFromSeedFormState extends State { _setLanguageLabel(language); if (passwordTextEditingController != null) { - passwordListener = () => widget.onPasswordChange?.call(passwordTextEditingController!.text); + passwordListener = () => + widget.onPasswordChange?.call(passwordTextEditingController!.text); passwordTextEditingController?.addListener(passwordListener!); } if (repeatedPasswordTextEditingController != null) { - repeatedPasswordListener = - () => widget.onRepeatedPasswordChange?.call(repeatedPasswordTextEditingController!.text); - repeatedPasswordTextEditingController?.addListener(repeatedPasswordListener!); + repeatedPasswordListener = () => widget.onRepeatedPasswordChange + ?.call(repeatedPasswordTextEditingController!.text); + repeatedPasswordTextEditingController + ?.addListener(repeatedPasswordListener!); } moneroSeedTypeReaction = - reaction((_) => widget.seedSettingsViewModel.moneroSeedType, (MoneroSeedType item) { - _setSeedType(item); - _changeLanguage('English'); - }); + reaction((_) => widget.seedSettingsViewModel.moneroSeedType, + (MoneroSeedType item) { + _setSeedType(item); + _changeLanguage('English'); + }); super.initState(); } @override - void dispose() { + void dispose() { moneroSeedTypeReaction(); if (passwordListener != null) { @@ -118,16 +125,22 @@ class WalletRestoreFromSeedFormState extends State { } void onSeedChange(String seed) { - if ((widget.type == WalletType.monero || widget.type == WalletType.wownero) && - Polyseed.isValidSeed(seed)) { - final lang = PolyseedLang.getByPhrase(seed); + if ([WalletType.monero, WalletType.wownero].contains(widget.type) && + (seed.split(" ").length == 12 || Polyseed.isValidSeed(seed))) { + try { + final lang = PolyseedLang.getByPhrase(seed); - _changeSeedType(MoneroSeedType.polyseed); - _changeLanguage(lang.nameEnglish); + if (widget.type == WalletType.monero && seed.split(" ").length == 12) { + _changeSeedType(MoneroSeedType.bip39); + } else { + _changeSeedType(MoneroSeedType.polyseed); + } + _changeLanguage(lang.nameEnglish, true); + } catch (e) { + printV(e); + } } - if (widget.type == WalletType.wownero && seed - .split(" ") - .length == 14) { + if (widget.type == WalletType.wownero && seed.split(" ").length == 14) { _changeSeedType(MoneroSeedType.wowneroSeed); _changeLanguage("English"); } @@ -147,9 +160,7 @@ class WalletRestoreFromSeedFormState extends State { BaseTextFormField( key: ValueKey('wallet_restore_from_seed_wallet_name_textfield_key'), controller: nameTextEditingController, - hintText: S - .of(context) - .wallet_name, + hintText: S.of(context).wallet_name, suffixIcon: IconButton( key: ValueKey('wallet_restore_from_seed_wallet_name_refresh_button_key'), onPressed: () async { @@ -158,17 +169,17 @@ class WalletRestoreFromSeedFormState extends State { setState(() { nameTextEditingController.text = rName; - nameTextEditingController.selection = TextSelection.fromPosition( - TextPosition(offset: nameTextEditingController.text.length)); + nameTextEditingController.selection = + TextSelection.fromPosition(TextPosition( + offset: + nameTextEditingController.text.length)); }); }, icon: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( borderRadius: BorderRadius.circular(6.0), - color: Theme - .of(context) - .hintColor, + color: Theme.of(context).hintColor, ), width: 34, height: 34, @@ -194,14 +205,13 @@ class WalletRestoreFromSeedFormState extends State { seedTextFieldKey: ValueKey('wallet_restore_from_seed_wallet_seeds_textfield_key'), pasteButtonKey: ValueKey('wallet_restore_from_seed_wallet_seeds_paste_button_key'), ), - if (widget.type == WalletType.monero || widget.type == WalletType.wownero) + if ([WalletType.monero, WalletType.wownero].contains(widget.type)) GestureDetector( key: ValueKey('wallet_restore_from_seed_seedtype_picker_button_key'), onTap: () async { await showPopUp( context: context, - builder: (_) => - Picker( + builder: (_) => Picker( items: _getItems(), selectedAtIndex: isPolyseed ? 1 @@ -226,33 +236,30 @@ class WalletRestoreFromSeedFormState extends State { ), ), ), - if (widget.displayWalletPassword) - ...[BaseTextFormField( + if (widget.displayWalletPassword) ...[ + BaseTextFormField( key: ValueKey('password'), controller: passwordTextEditingController, - hintText: S - .of(context) - .password, + hintText: S.of(context).password, obscureText: true), - BaseTextFormField( - key: ValueKey('repeat_wallet_password'), - controller: repeatedPasswordTextEditingController, - hintText: S - .of(context) - .repeat_wallet_password, - obscureText: true) - ], + BaseTextFormField( + key: ValueKey('repeat_wallet_password'), + controller: repeatedPasswordTextEditingController, + hintText: S.of(context).repeat_wallet_password, + obscureText: true) + ], if (widget.displayLanguageSelector) if (!seedTypeController.value.text.contains("14") && widget.displayLanguageSelector) GestureDetector( onTap: () async { await showPopUp( context: context, - builder: (_) => - SeedLanguagePicker( + builder: (_) => SeedLanguagePicker( selected: language, - onItemSelected: _changeLanguage, - seedType: isPolyseed ? MoneroSeedType.polyseed : MoneroSeedType.legacy, + onItemSelected: (lang) => + _changeLanguage(lang, isPolyseed || isBip39), + seedType: + widget.seedSettingsViewModel.moneroSeedType, )); }, child: Container( @@ -274,7 +281,8 @@ class WalletRestoreFromSeedFormState extends State { key: blockchainHeightKey, blockHeightTextFieldKey: ValueKey('wallet_restore_from_seed_blockheight_textfield_key'), onHeightOrDateEntered: widget.onHeightOrDateEntered, - hasDatePicker: widget.type == WalletType.monero || widget.type == WalletType.wownero, + hasDatePicker: + [WalletType.monero, WalletType.wownero].contains(widget.type), walletType: widget.type, ), ])); @@ -282,28 +290,29 @@ class WalletRestoreFromSeedFormState extends State { bool get isPolyseed => widget.seedSettingsViewModel.moneroSeedType == MoneroSeedType.polyseed && - (widget.type == WalletType.monero || widget.type == WalletType.wownero); + [WalletType.monero, WalletType.wownero].contains(widget.type); - Widget get expandIcon => - Container( + bool get isBip39 => + widget.seedSettingsViewModel.moneroSeedType == MoneroSeedType.bip39 && + WalletType.monero == widget.type; + + Widget get expandIcon => Container( padding: EdgeInsets.all(18), width: 24, height: 24, child: Image.asset( 'assets/images/arrow_bottom_purple_icon.png', height: 8, - color: Theme - .of(context) - .hintColor, + color: Theme.of(context).hintColor, ), ); - void _changeLanguage(String language) { - final setLang = isPolyseed + void _changeLanguage(String language, [bool useBip39Wordlist = false]) { + final setLang = useBip39Wordlist ? "POLYSEED_$language" : seedTypeController.value.text.contains("14") - ? "WOWSEED_" + language - : language; + ? "WOWSEED_" + language + : language; setState(() { this.language = setLang; seedWidgetStateKey.currentState!.changeSeedLanguage(setLang); @@ -312,8 +321,8 @@ class WalletRestoreFromSeedFormState extends State { }); } - void _setLanguageLabel(String language) => - languageController.text = '${language.replaceAll("POLYSEED_", "")} (Seed language)'; + void _setLanguageLabel(String language) => languageController.text = + '${language.replaceAll("POLYSEED_", "")} (Seed language)'; void _changeSeedType(MoneroSeedType item) { _setSeedType(item); @@ -328,9 +337,17 @@ class WalletRestoreFromSeedFormState extends State { List _getItems() { switch (widget.type) { case WalletType.monero: - return [MoneroSeedType.legacy, MoneroSeedType.polyseed]; + return [ + MoneroSeedType.legacy, + MoneroSeedType.polyseed, + MoneroSeedType.bip39 + ]; case WalletType.wownero: - return [MoneroSeedType.legacy, MoneroSeedType.polyseed, MoneroSeedType.wowneroSeed]; + return [ + MoneroSeedType.legacy, + MoneroSeedType.polyseed, + MoneroSeedType.wowneroSeed + ]; default: return [MoneroSeedType.legacy]; } diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index ce8595ba4..e2e149644 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -527,25 +527,42 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody> } bool _isValidSeed() { - final seedPhrase = - walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.text; + final seedPhrase = walletRestoreFromSeedFormKey + .currentState!.seedWidgetStateKey.currentState!.text; if (walletRestoreViewModel.isPolyseed(seedPhrase)) return true; final seedWords = seedPhrase.split(' '); - if (seedWords.length == 14 && walletRestoreViewModel.type == WalletType.wownero) return true; - if (seedWords.length == 26 && walletRestoreViewModel.type == WalletType.zano) return true; + if (seedWords.length == 14 && + walletRestoreViewModel.type == WalletType.wownero) return true; + if (seedWords.length == 26 && + walletRestoreViewModel.type == WalletType.zano) return true; - if ((walletRestoreViewModel.type == WalletType.monero || - walletRestoreViewModel.type == WalletType.wownero || - walletRestoreViewModel.type == WalletType.haven) && - seedWords.length != WalletRestoreViewModelBase.moneroSeedMnemonicLength) { - return false; + if (seedWords.length == 12 && + walletRestoreViewModel.type == WalletType.monero) { + return walletRestoreFromSeedFormKey + .currentState + ?.blockchainHeightKey + .currentState + ?.restoreHeightController + .text + .isNotEmpty == true; + } + + if ([WalletType.monero, WalletType.wownero, WalletType.haven] + .contains(walletRestoreViewModel.type) && + seedWords.length == + WalletRestoreViewModelBase.moneroSeedMnemonicLength) { + return true; } // bip39: final validBip39SeedLengths = [12, 18, 24]; - final nonBip39WalletTypes = [WalletType.monero, WalletType.wownero, WalletType.haven, WalletType.decred]; + final nonBip39WalletTypes = [ + WalletType.wownero, + WalletType.haven, + WalletType.decred + ]; // if it's a bip39 wallet and the length is not valid return false if (!nonBip39WalletTypes.contains(walletRestoreViewModel.type) && !(validBip39SeedLengths.contains(seedWords.length))) { @@ -558,8 +575,9 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody> return false; } - final words = - walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.words.toSet(); + final words = walletRestoreFromSeedFormKey + .currentState!.seedWidgetStateKey.currentState!.words + .toSet(); return seedWords.toSet().difference(words).toSet().isEmpty; } diff --git a/lib/src/widgets/seed_language_picker.dart b/lib/src/widgets/seed_language_picker.dart index 4a63e3092..e2b10aa4c 100644 --- a/lib/src/widgets/seed_language_picker.dart +++ b/lib/src/widgets/seed_language_picker.dart @@ -16,7 +16,7 @@ class SeedLanguagePickerOption { final List seedLanguages = [ SeedLanguagePickerOption('English', S.current.seed_language_english, - Image.asset('assets/images/flags/usa.png'), [MoneroSeedType.legacy, MoneroSeedType.polyseed]), + Image.asset('assets/images/flags/usa.png'), [MoneroSeedType.legacy, MoneroSeedType.polyseed, MoneroSeedType.bip39]), SeedLanguagePickerOption('Chinese (Simplified)', S.current.seed_language_chinese, Image.asset('assets/images/flags/chn.png'), [MoneroSeedType.legacy, MoneroSeedType.polyseed]), SeedLanguagePickerOption('Chinese (Traditional)', S.current.seed_language_chinese_traditional, diff --git a/lib/view_model/wallet_groups_display_view_model.dart b/lib/view_model/wallet_groups_display_view_model.dart index 056d713aa..ef5b245ba 100644 --- a/lib/view_model/wallet_groups_display_view_model.dart +++ b/lib/view_model/wallet_groups_display_view_model.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; import 'package:cake_wallet/wallet_types.g.dart'; +import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; @@ -127,12 +128,16 @@ abstract class WalletGroupsDisplayViewModelBase with Store { bool isNonSeedWallet = wallet.isNonSeedWallet; + bool isNotMoneroBip39Wallet = wallet.type == WalletType.monero && + wallet.derivationInfo?.derivationType != DerivationType.bip39; + // Exclude if any of these conditions are true return isNonBIP39Wallet || isNanoDerivationType || isElectrumDerivationType || isSameTypeAsSelectedWallet || - isNonSeedWallet; + isNonSeedWallet || + isNotMoneroBip39Wallet; }); if (shouldExcludeGroup) continue; diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index 246227a80..da5d04f59 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -63,14 +63,15 @@ abstract class WalletKeysViewModelBase with Store { String get seed => _wallet.seed != null ? _wallet.seed! : ''; bool get isLegacySeedOnly => - (_wallet.type == WalletType.monero || _wallet.type == WalletType.wownero) && + [WalletType.monero, WalletType.wownero].contains(_wallet.type) && _wallet.seed != null && - !Polyseed.isValidSeed(_wallet.seed!); + !(Polyseed.isValidSeed(_wallet.seed!) || + _wallet.seed!.split(' ').length == 12); String get legacySeed { if ((_wallet.type == WalletType.monero || _wallet.type == WalletType.wownero) && _wallet.seed != null && - Polyseed.isValidSeed(_wallet.seed!)) { + (Polyseed.isValidSeed(_wallet.seed!) || _wallet.seed!.split(' ').length == 12)) { final langName = PolyseedLang.getByPhrase(_wallet.seed!).nameEnglish; if (_wallet.type == WalletType.monero) { diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index f4117f8ab..7b4b7e788 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -49,6 +49,9 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { bool get hasLanguageSelector => [WalletType.monero, WalletType.haven, WalletType.wownero].contains(type); + bool get showLanguageSelector => + newWalletArguments?.mnemonic == null && hasLanguageSelector; + int get seedPhraseWordsLength { switch (type) { case WalletType.monero: @@ -81,7 +84,9 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { } } - bool get hasSeedType => [WalletType.monero, WalletType.wownero].contains(type); + bool get hasSeedType => + newWalletArguments?.mnemonic == null && + [WalletType.monero, WalletType.wownero].contains(type); @override WalletCredentials getCredentials(dynamic _options) { @@ -92,11 +97,15 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { switch (type) { case WalletType.monero: return monero!.createMoneroNewWalletCredentials( - name: name, - language: options!.first as String, - password: walletPassword, - passphrase: passphrase, - isPolyseed: options.last as bool); + name: name, + language: options!.first as String, + password: walletPassword, + passphrase: passphrase, + seedType: newWalletArguments!.mnemonic != null + ? MoneroSeedType.bip39.raw + : (options.last as MoneroSeedType).raw, + mnemonic: newWalletArguments!.mnemonic, + ); case WalletType.bitcoin: case WalletType.litecoin: return bitcoin!.createBitcoinNewWalletCredentials( diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index f30f7d614..7d16c3cd6 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -683,9 +683,6 @@ "seedtype": "البذور", "seedtype_alert_content": "مشاركة البذور مع محافظ أخرى ممكن فقط مع BIP39 Seedtype.", "seedtype_alert_title": "تنبيه البذور", - "seedtype_legacy": "إرث (25 كلمة)", - "seedtype_polyseed": "بوليسيد (16 كلمة)", - "seedtype_wownero": "Wownero (14 كلمة)", "select_backup_file": "حدد ملف النسخ الاحتياطي", "select_buy_provider_notice": "حدد مزود شراء أعلاه. يمكنك تخطي هذه الشاشة عن طريق تعيين مزود شراء الافتراضي في إعدادات التطبيق.", "select_destination": ".ﻲﻃﺎﻴﺘﺣﻻﺍ ﺦﺴﻨﻟﺍ ﻒﻠﻣ ﺔﻬﺟﻭ ﺪﻳﺪﺤﺗ ءﺎﺟﺮﻟﺍ", @@ -1027,4 +1024,4 @@ "you_will_receive_estimated_amount": "سوف تتلقى(ooded )", "you_will_send": "تحويل من", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 2d8527b83..db62231a8 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -683,9 +683,6 @@ "seedtype": "Семенна тип", "seedtype_alert_content": "Споделянето на семена с други портфейли е възможно само с BIP39 Seedtype.", "seedtype_alert_title": "Сигнал за семена", - "seedtype_legacy": "Наследство (25 думи)", - "seedtype_polyseed": "Поли семе (16 думи)", - "seedtype_wownero": "Wownero (14 думи)", "select_backup_file": "Избор на резервно копие", "select_buy_provider_notice": "Изберете доставчик на покупка по -горе. Можете да пропуснете този екран, като зададете вашия доставчик по подразбиране по подразбиране в настройките на приложението.", "select_destination": "Моля, изберете дестинация за архивния файл.", @@ -1027,4 +1024,4 @@ "you_will_receive_estimated_amount": "Ще получите(прогнозно )", "you_will_send": "Обръщане от", "yy": "гг" -} \ No newline at end of file +} diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 968e45a81..c7c60b266 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -683,9 +683,6 @@ "seedtype": "SeedType", "seedtype_alert_content": "Sdílení semen s jinými peněženkami je možné pouze u BIP39 SeedType.", "seedtype_alert_title": "Upozornění seedtype", - "seedtype_legacy": "Legacy (25 slov)", - "seedtype_polyseed": "Polyseed (16 slov)", - "seedtype_wownero": "Wownero (14 slov)", "select_backup_file": "Vybrat soubor se zálohou", "select_buy_provider_notice": "Vyberte výše uvedeného poskytovatele nákupu. Tuto obrazovku můžete přeskočit nastavením výchozího poskytovatele nákupu v nastavení aplikace.", "select_destination": "Vyberte cíl pro záložní soubor.", @@ -1027,4 +1024,4 @@ "you_will_receive_estimated_amount": "Obdržíte(odhadovaný )", "you_will_send": "Směnit z", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index e23c41675..04f11ff02 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -684,9 +684,6 @@ "seedtype": "Seedtyp", "seedtype_alert_content": "Das Teilen von Seeds mit anderen Wallet ist nur mit bip39 Seedype möglich.", "seedtype_alert_title": "Seedype-Alarm", - "seedtype_legacy": "Veraltet (25 Wörter)", - "seedtype_polyseed": "Polyseed (16 Wörter)", - "seedtype_wownero": "WOWNO (14 Wörter)", "select_backup_file": "Sicherungsdatei auswählen", "select_buy_provider_notice": "Wählen Sie oben einen Anbieter kaufen. Sie können diese Seite überspringen, indem Sie Ihren Standard-Kaufanbieter in den App-Einstellungen festlegen.", "select_destination": "Bitte wählen Sie das Ziel für die Sicherungsdatei aus.", @@ -1030,4 +1027,4 @@ "you_will_receive_estimated_amount": "Sie erhalten(geschätzt )", "you_will_send": "Konvertieren von", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 9586e9868..5f8fd2931 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -684,9 +684,6 @@ "seedtype": "Seedtype", "seedtype_alert_content": "Sharing seeds with other wallets is only possible with BIP39 SeedType.", "seedtype_alert_title": "SeedType Alert", - "seedtype_legacy": "Legacy (25 words)", - "seedtype_polyseed": "Polyseed (16 words)", - "seedtype_wownero": "Wownero (14 words)", "select_backup_file": "Select backup file", "select_buy_provider_notice": "Select a buy provider above. You can skip this screen by setting your default buy provider in app settings.", "select_destination": "Please select destination for the backup file.", @@ -1028,4 +1025,4 @@ "you_will_receive_estimated_amount": "You will receive (estimated)", "you_will_send": "Convert from", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index a17c84d19..32b6c9d13 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -684,9 +684,6 @@ "seedtype": "Tipos de semillas", "seedtype_alert_content": "Compartir semillas con otras billeteras solo es posible con semillas bip39 - un tipo específico de semilla.", "seedtype_alert_title": "Alerta de tipo de semillas", - "seedtype_legacy": "Semilla clásica-legacy (25 palabras)", - "seedtype_polyseed": "Poli-semilla (16 palabras)", - "seedtype_wownero": "Wownero (14 palabras)", "select_backup_file": "Seleccionar archivo de respaldo", "select_buy_provider_notice": "Selecciona un proveedor de compra arriba. Puede omitir esta pantalla configurando su proveedor de compra predeterminado en la configuración de la aplicación.", "select_destination": "Selecciona el destino del archivo de copia de seguridad.", @@ -1028,4 +1025,4 @@ "you_will_receive_estimated_amount": "Recibirá(estimado )", "you_will_send": "Convertir de", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 405f6abb3..01b8e566b 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -683,9 +683,6 @@ "seedtype": "Type de graine", "seedtype_alert_content": "Le partage de graines avec d'autres portefeuilles n'est possible qu'avec le type de graine BIP39.", "seedtype_alert_title": "Alerte Type de Graine", - "seedtype_legacy": "Legacy (25 words)", - "seedtype_polyseed": "Polyseed (16 mots)", - "seedtype_wownero": "WOWNERO (14 mots)", "select_backup_file": "Sélectionnez le fichier de sauvegarde", "select_buy_provider_notice": "Sélectionnez un fournisseur d'achat ci-dessus. Vous pouvez ignorer cet écran en définissant votre fournisseur d'achat par défaut dans les paramètres de l'application.", "select_destination": "Veuillez sélectionner la destination du fichier de sauvegarde.", @@ -1027,4 +1024,4 @@ "you_will_receive_estimated_amount": "Vous recevrez ( estimé )", "you_will_send": "Convertir depuis", "yy": "AA" -} \ No newline at end of file +} diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 3355aea6f..9afd64587 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -685,9 +685,6 @@ "seedtype": "Seedtype", "seedtype_alert_content": "Raba tsaba tare da sauran wallets yana yiwuwa ne kawai tare da Bip39 seedtype.", "seedtype_alert_title": "Seedtype farke", - "seedtype_legacy": "Legacy (25 kalmomi)", - "seedtype_polyseed": "Polyseed (16 kalmomi)", - "seedtype_wownero": "WowRero (kalmomi 14)", "select_backup_file": "Zaɓi fayil ɗin madadin", "select_buy_provider_notice": "Zaɓi mai ba da kyauta a sama. Zaka iya tsallake wannan allon ta hanyar saita mai ba da isasshen busasshen mai ba da isasshen busasshiyar saiti.", "select_destination": "Da fatan za a zaɓi wurin da za a yi wa madadin fayil ɗin.", @@ -1029,4 +1026,4 @@ "you_will_receive_estimated_amount": "Za ku (karɓi )", "you_will_send": "Maida daga", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index cef53d58e..0dd684dab 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -685,9 +685,6 @@ "seedtype": "बीज", "seedtype_alert_content": "अन्य पर्स के साथ बीज साझा करना केवल BIP39 सीडटाइप के साथ संभव है।", "seedtype_alert_title": "बीजगणित अलर्ट", - "seedtype_legacy": "विरासत (25 शब्द)", - "seedtype_polyseed": "पॉलीसीड (16 शब्द)", - "seedtype_wownero": "Wownero (14 शब्द)", "select_backup_file": "बैकअप फ़ाइल का चयन करें", "select_buy_provider_notice": "ऊपर एक खरीद प्रदाता का चयन करें। आप इस स्क्रीन को ऐप सेटिंग्स में अपना डिफ़ॉल्ट बाय प्रदाता सेट करके छोड़ सकते हैं।", "select_destination": "कृपया बैकअप फ़ाइल के लिए गंतव्य का चयन करें।", @@ -1029,4 +1026,4 @@ "you_will_receive_estimated_amount": "आपको#अनुमानित ( प्राप्त होगा)", "you_will_send": "से रूपांतरित करें", "yy": "वाईवाई" -} \ No newline at end of file +} diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 63d698c3d..291fadc70 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -683,9 +683,6 @@ "seedtype": "Sjemenska vrsta", "seedtype_alert_content": "Dijeljenje sjemena s drugim novčanicima moguće je samo s BIP39 sjemenom.", "seedtype_alert_title": "Upozorenje o sjemenu", - "seedtype_legacy": "Nasljeđe (25 riječi)", - "seedtype_polyseed": "Poliseed (16 riječi)", - "seedtype_wownero": "WANERO (14 riječi)", "select_backup_file": "Odaberite datoteku sigurnosne kopije", "select_buy_provider_notice": "Odaberite gornji davatelj kupnje. Ovaj zaslon možete preskočiti postavljanjem zadanog davatelja usluga kupnje u postavkama aplikacija.", "select_destination": "Odaberite odredište za datoteku sigurnosne kopije.", @@ -1027,4 +1024,4 @@ "you_will_receive_estimated_amount": "Primit ćete(procijenjeno )", "you_will_send": "Razmijeni iz", "yy": "GG" -} \ No newline at end of file +} diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index 369264362..c782d153e 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -682,9 +682,6 @@ "seedtype": "Սերմի տեսակ", "seedtype_alert_content": "Այլ դրամապանակներով սերմերի փոխանակումը հնարավոր է միայն BIP39 SEEDTYPE- ով:", "seedtype_alert_title": "SEEDTYPE ALERT", - "seedtype_legacy": "Legacy (25 բառ)", - "seedtype_polyseed": "Polyseed (16 բառ)", - "seedtype_wownero": "Wownero (14 բառ)", "select_backup_file": "Ընտրել կրկնօրինակ ֆայլ", "select_buy_provider_notice": "Ընտրեք գնման մատակարարը վերևում։ Դուք կարող եք բաց թողնել այս էկրանը ձեր լռելայն գնման մատակարարը հավելվածի կարգավորումներում սահմանելով", "select_destination": "Խնդրում ենք ընտրել կրկնօրինակ ֆայլի նպատակակետը", @@ -1025,4 +1022,4 @@ "you_will_receive_estimated_amount": "Դուք կստանաք ( գնահատված )", "you_will_send": "Փոխանակեք", "yy": "ՏՏ" -} \ No newline at end of file +} diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index b7794d485..b83a96083 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -686,9 +686,6 @@ "seedtype": "Seedtype", "seedtype_alert_content": "Berbagi biji dengan dompet lain hanya dimungkinkan dengan BIP39 seedtype.", "seedtype_alert_title": "Peringatan seedtype", - "seedtype_legacy": "Legacy (25 kata)", - "seedtype_polyseed": "Polyseed (16 kata)", - "seedtype_wownero": "Wownero (14 kata)", "select_backup_file": "Pilih file cadangan", "select_buy_provider_notice": "Pilih penyedia beli di atas. Anda dapat melewatkan layar ini dengan mengatur penyedia pembelian default Anda di pengaturan aplikasi.", "select_destination": "Silakan pilih tujuan untuk file cadangan.", @@ -1030,4 +1027,4 @@ "you_will_receive_estimated_amount": "Anda akan menerima(estimasi )", "you_will_send": "Konversi dari", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 2c214da67..dca278d5e 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -684,9 +684,6 @@ "seedtype": "Seedtype", "seedtype_alert_content": "La condivisione di semi con altri portafogli è possibile solo con Bip39 SeedType.", "seedtype_alert_title": "Avviso seedType", - "seedtype_legacy": "Legacy (25 parole)", - "seedtype_polyseed": "Polyseed (16 parole)", - "seedtype_wownero": "Wownero (14 parole)", "select_backup_file": "Seleziona file di backup", "select_buy_provider_notice": "Seleziona un provider di acquisto sopra. È possibile saltare questa schermata impostando il provider di acquisto predefinito nelle impostazioni dell'app.", "select_destination": "Seleziona la destinazione per il file di backup.", @@ -1029,4 +1026,4 @@ "you_will_receive_estimated_amount": "Riceverai(stimato )", "you_will_send": "Conveti da", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 52d837fd0..d6071b4fb 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -684,9 +684,6 @@ "seedtype": "SeedType", "seedtype_alert_content": "他の財布と種子を共有することは、BIP39 SeedTypeでのみ可能です。", "seedtype_alert_title": "SeedTypeアラート", - "seedtype_legacy": "レガシー(25語)", - "seedtype_polyseed": "ポリシード(16語)", - "seedtype_wownero": "wownero(14ワード)", "select_backup_file": "バックアップファイルを選択", "select_buy_provider_notice": "上記の購入プロバイダーを選択してください。デフォルトの購入プロバイダーをアプリ設定で設定して、この画面をスキップできます。", "select_destination": "バックアップファイルの保存先を選択してください。", @@ -1028,4 +1025,4 @@ "you_will_receive_estimated_amount": "あなたは(推定)を受け取ります", "you_will_send": "から変換", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 08ad157da..e0ccee417 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -683,9 +683,6 @@ "seedtype": "시드 타입", "seedtype_alert_content": "다른 지갑과 씨앗을 공유하는 것은 BIP39 SeedType에서만 가능합니다.", "seedtype_alert_title": "종자 경보", - "seedtype_legacy": "레거시 (25 단어)", - "seedtype_polyseed": "다문 (16 단어)", - "seedtype_wownero": "Wownero (14 단어)", "select_backup_file": "백업 파일 선택", "select_buy_provider_notice": "위의 구매 제공자를 선택하십시오. 앱 설정에서 기본 구매 제공자를 설정 하여이 화면을 건너 뛸 수 있습니다.", "select_destination": "백업 파일의 대상을 선택하십시오.", @@ -1028,4 +1025,4 @@ "you_will_send": "다음에서 변환", "YY": "YY", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 111593150..8dcffb219 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -683,9 +683,6 @@ "seedtype": "မျိုးပွားခြင်း", "seedtype_alert_content": "အခြားပိုက်ဆံအိတ်များနှင့်မျိုးစေ့များကိုမျှဝေခြင်းသည် BIP39 sebyspe ဖြင့်သာဖြစ်သည်။", "seedtype_alert_title": "ပျိုးပင်သတိပေးချက်", - "seedtype_legacy": "အမွေအနှစ် (စကားလုံး 25 လုံး)", - "seedtype_polyseed": "polyseed (စကားလုံး 16 လုံး)", - "seedtype_wownero": "Wownero (စကားလုံး 14 လုံး)", "select_backup_file": "အရန်ဖိုင်ကို ရွေးပါ။", "select_buy_provider_notice": "အပေါ်ကဝယ်သူတစ် ဦး ကိုရွေးချယ်ပါ။ သင်၏ default 0 ယ်သူအား app settings တွင် setting လုပ်ခြင်းဖြင့်ဤ screen ကိုကျော်သွားနိုင်သည်။", "select_destination": "အရန်ဖိုင်အတွက် ဦးတည်ရာကို ရွေးပါ။", @@ -1027,4 +1024,4 @@ "you_will_receive_estimated_amount": "သင် ( ခန့်မှန်းခြေ ) လက်ခံရရှိလိမ့်မည်", "you_will_send": "မှပြောင်းပါ။", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 8b7939de8..60d6a7df2 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -683,9 +683,6 @@ "seedtype": "Zaadtype", "seedtype_alert_content": "Het delen van zaden met andere portefeuilles is alleen mogelijk met BIP39 SeedType.", "seedtype_alert_title": "Zaadtype alert", - "seedtype_legacy": "Legacy (25 woorden)", - "seedtype_polyseed": "Polyseed (16 woorden)", - "seedtype_wownero": "WOWNERO (14 woorden)", "select_backup_file": "Selecteer een back-upbestand", "select_buy_provider_notice": "Selecteer hierboven een koopprovider. U kunt dit scherm overslaan door uw standaard kopenprovider in te stellen in app -instellingen.", "select_destination": "Selecteer de bestemming voor het back-upbestand.", @@ -1028,4 +1025,4 @@ "you_will_receive_estimated_amount": "U ontvangt(geschat )", "you_will_send": "Converteren van", "yy": "JJ" -} \ No newline at end of file +} diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index d60e97ef4..3e5550424 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -683,9 +683,6 @@ "seedtype": "Sedtype", "seedtype_alert_content": "Dzielenie się nasionami z innymi portfelami jest możliwe tylko z BIP39 sededType.", "seedtype_alert_title": "Ustanowienie typu sedype", - "seedtype_legacy": "Legacy (25 słów)", - "seedtype_polyseed": "Polyseed (16 słów)", - "seedtype_wownero": "Wowero (14 słów)", "select_backup_file": "Wybierz plik kopii zapasowej", "select_buy_provider_notice": "Wybierz powyższe dostawcę zakupu. Możesz pominąć ten ekran, ustawiając domyślnego dostawcę zakupu w ustawieniach aplikacji.", "select_destination": "Wybierz miejsce docelowe dla pliku kopii zapasowej.", @@ -1027,4 +1024,4 @@ "you_will_receive_estimated_amount": "Otrzymasz(oszacowane )", "you_will_send": "Konwertuj z", "yy": "RR" -} \ No newline at end of file +} diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 732865567..494694af0 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -685,9 +685,6 @@ "seedtype": "SeedType", "seedtype_alert_content": "Compartilhar sementes com outras carteiras só é possível com o BIP39 SeedType.", "seedtype_alert_title": "Alerta de SeedType", - "seedtype_legacy": "Legado (25 palavras)", - "seedtype_polyseed": "Polyseed (16 palavras)", - "seedtype_wownero": "Wowrone (14 palavras)", "select_backup_file": "Selecione o arquivo de backup", "select_buy_provider_notice": "Selecione um provedor de compra acima. Você pode pular esta tela definindo seu provedor de compra padrão nas configurações de aplicativos.", "select_destination": "Selecione o destino para o arquivo de backup.", @@ -1030,4 +1027,4 @@ "you_will_receive_estimated_amount": "Você receberá(estimado )", "you_will_send": "Converter de", "yy": "aa" -} \ No newline at end of file +} diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 63241530b..238eb0c95 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -684,9 +684,6 @@ "seedtype": "SEEDTYPE", "seedtype_alert_content": "Обмен семенами с другими кошельками возможно только с BIP39 SeedType.", "seedtype_alert_title": "SEEDTYPE ALERT", - "seedtype_legacy": "Наследие (25 слов)", - "seedtype_polyseed": "Полиса (16 слов)", - "seedtype_wownero": "Wownero (14 слов)", "select_backup_file": "Выберите файл резервной копии", "select_buy_provider_notice": "Выберите поставщика покупки выше. Вы можете пропустить этот экран, установив поставщика покупки по умолчанию в настройках приложения.", "select_destination": "Пожалуйста, выберите место для файла резервной копии.", @@ -1028,4 +1025,4 @@ "you_will_receive_estimated_amount": "Вы получите(Оценку )", "you_will_send": "Конвертировать из", "yy": "ГГ" -} \ No newline at end of file +} diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 6de6854ca..13a3d9674 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -683,9 +683,6 @@ "seedtype": "เมล็ดพันธุ์", "seedtype_alert_content": "การแบ่งปันเมล็ดกับกระเป๋าเงินอื่น ๆ เป็นไปได้เฉพาะกับ bip39 seedtype", "seedtype_alert_title": "การแจ้งเตือน seedtype", - "seedtype_legacy": "มรดก (25 คำ)", - "seedtype_polyseed": "โพลีส (16 คำ)", - "seedtype_wownero": "wownero (14 คำ)", "select_backup_file": "เลือกไฟล์สำรอง", "select_buy_provider_notice": "เลือกผู้ให้บริการซื้อด้านบน คุณสามารถข้ามหน้าจอนี้ได้โดยการตั้งค่าผู้ให้บริการซื้อเริ่มต้นในการตั้งค่าแอป", "select_destination": "โปรดเลือกปลายทางสำหรับไฟล์สำรอง", @@ -1027,4 +1024,4 @@ "you_will_receive_estimated_amount": "คุณจะได้รับ(โดยประมาณ )", "you_will_send": "แปลงจาก", "yy": "ปี" -} \ No newline at end of file +} diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 319286fbd..cf2a15aca 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -683,9 +683,6 @@ "seedtype": "Seed type", "seedtype_alert_content": "Ang pagbabahagi ng mga buto sa iba pang mga pitaka ay posible lamang sa bip39 seedtype.", "seedtype_alert_title": "Alerto ng Seedtype", - "seedtype_legacy": "Legacy (25 na salita)", - "seedtype_polyseed": "Polyseed (16 na salita)", - "seedtype_wownero": "Wownero (14 na salita)", "select_backup_file": "Piliin ang backup na file", "select_buy_provider_notice": "Pumili ng provider ng pagbili sa itaas. Maaari mong laktawan ang screen na ito sa pamamagitan ng pagtatakda ng iyong default na provider ng pagbili sa mga setting ng app.", "select_destination": "Mangyaring piliin ang patutunguhan para sa backup na file.", @@ -1027,4 +1024,4 @@ "you_will_receive_estimated_amount": "Makakatanggap ka ng(tinantyang)", "you_will_send": "I-convert mula sa", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 852e7b1d2..848a4b8a6 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -683,9 +683,6 @@ "seedtype": "Tohum", "seedtype_alert_content": "Tohumları diğer cüzdanlarla paylaşmak sadece BIP39 tohumu ile mümkündür.", "seedtype_alert_title": "SeedType uyarısı", - "seedtype_legacy": "Miras (25 kelime)", - "seedtype_polyseed": "Polyseed (16 kelime)", - "seedtype_wownero": "Wownero (14 kelime)", "select_backup_file": "Yedek dosyası seç", "select_buy_provider_notice": "Yukarıda bir satın alma sağlayıcısı seçin. App ayarlarında varsayılan satın alma sağlayıcınızı ayarlayarak bu ekranı atlayabilirsiniz.", "select_destination": "Lütfen yedekleme dosyası için hedef seçin.", @@ -1027,4 +1024,4 @@ "you_will_receive_estimated_amount": "(Tahmini ) alacaksınız", "you_will_send": "Biçiminden dönüştür:", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 5afc44815..eef5bcdad 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -684,9 +684,6 @@ "seedtype": "Насіннєвий тип", "seedtype_alert_content": "Спільний доступ до інших гаманців можливе лише за допомогою BIP39 Seedtype.", "seedtype_alert_title": "Попередження насінника", - "seedtype_legacy": "Спадщина (25 слів)", - "seedtype_polyseed": "Полісей (16 слів)", - "seedtype_wownero": "Влонеро (14 слів)", "select_backup_file": "Виберіть файл резервної копії", "select_buy_provider_notice": "Виберіть постачальника купівлі вище. Ви можете пропустити цей екран, встановивши свого постачальника купівлі за замовчуванням у налаштуваннях додатків.", "select_destination": "Виберіть місце призначення для файлу резервної копії.", @@ -1028,4 +1025,4 @@ "you_will_receive_estimated_amount": "Ви отримаєте(оцінюється )", "you_will_send": "Конвертувати з", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 675747d20..ddcc0a696 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -685,9 +685,6 @@ "seedtype": "سیڈ ٹائپ", "seedtype_alert_content": "دوسرے بٹوے کے ساتھ بیجوں کا اشتراک صرف BIP39 بیج ٹائپ کے ساتھ ہی ممکن ہے۔", "seedtype_alert_title": "سیڈ ٹائپ الرٹ", - "seedtype_legacy": "میراث (25 الفاظ)", - "seedtype_polyseed": "پالیسیڈ (16 الفاظ)", - "seedtype_wownero": "واونرو (14 الفاظ)", "select_backup_file": "بیک اپ فائل کو منتخب کریں۔", "select_buy_provider_notice": "اوپر خریدنے والا خریدنے والا منتخب کریں۔ آپ ایپ کی ترتیبات میں اپنے پہلے سے طے شدہ خریدنے والے کو ترتیب دے کر اس اسکرین کو چھوڑ سکتے ہیں۔", "select_destination": "۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﻝﺰﻨﻣ ﮯﯿﻟ ﮯﮐ ﻞﺋﺎﻓ ﭖﺍ ﮏﯿﺑ ﻡﺮﮐ ﮦﺍﺮﺑ", @@ -1029,4 +1026,4 @@ "you_will_receive_estimated_amount": "آپ(تخمینہ ) وصول کریں گے", "you_will_send": "سے تبدیل کریں۔", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index 76bdaae34..9abf12e33 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -681,9 +681,6 @@ "seedtype": "Loại hạt giống", "seedtype_alert_content": "Chia sẻ hạt giống với ví khác chỉ có thể với BIP39 SeedType.", "seedtype_alert_title": "Cảnh báo hạt giống", - "seedtype_legacy": "Di sản (25 từ)", - "seedtype_polyseed": "Polyseed (16 từ)", - "seedtype_wownero": "Wownero (14 từ)", "select_backup_file": "Chọn tệp sao lưu", "select_buy_provider_notice": "Chọn nhà cung cấp mua ở trên. Bạn có thể bỏ qua màn hình này bằng cách thiết lập nhà cung cấp mua mặc định trong cài đặt ứng dụng.", "select_destination": "Vui lòng chọn đích cho tệp sao lưu.", @@ -1024,4 +1021,4 @@ "you_will_receive_estimated_amount": "Bạn sẽ nhận được(ước tính )", "you_will_send": "Chuyển đổi từ", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index b43c6cef4..dc836bb17 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -684,9 +684,6 @@ "seedtype": "Irugbin-seetypu", "seedtype_alert_content": "Pinpin awọn irugbin pẹlu awọn gedo miiran ṣee ṣe pẹlu Bip39 irugbin.", "seedtype_alert_title": "Ṣajọpọ Seeytype", - "seedtype_legacy": "Legacy (awọn ọrọ 25)", - "seedtype_polyseed": "Polyseed (awọn ọrọ 16)", - "seedtype_wownero": "Wowero (awọn ọrọ 14)", "select_backup_file": "Select backup file", "select_buy_provider_notice": "Yan olupese Ra loke. O le skii iboju yii nipa ṣiṣeto olupese rẹ ni awọn eto App.", "select_destination": "Jọwọ yan ibi ti o nlo fun faili afẹyinti.", @@ -1028,4 +1025,4 @@ "you_will_receive_estimated_amount": "Iwọ yoo gba ( excimated )", "you_will_send": "Ṣe pàṣípààrọ̀ láti", "yy": "Ọd" -} \ No newline at end of file +} diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index a83d35570..15fee3ed9 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -683,9 +683,6 @@ "seedtype": "籽粒", "seedtype_alert_content": "只有BIP39籽粒可以与其他钱包共享种子。", "seedtype_alert_title": "籽粒警报", - "seedtype_legacy": "遗产(25个单词)", - "seedtype_polyseed": "多种物品(16个单词)", - "seedtype_wownero": "沃恩罗(14个单词)", "select_backup_file": "选择备份文件", "select_buy_provider_notice": "在上面选择买入提供商。您可以通过在应用程序设置中设置默认的购买提供商来跳过此屏幕。", "select_destination": "请选择备份文件的目的地。", @@ -1027,4 +1024,4 @@ "you_will_receive_estimated_amount": "您将收到(估计的)", "you_will_send": "转换自", "yy": "YY" -} \ No newline at end of file +} diff --git a/tool/configure.dart b/tool/configure.dart index 0c514de17..4711373b7 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -406,7 +406,7 @@ abstract class Monero { required int height}); WalletCredentials createMoneroRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required String mnemonic}); WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({required String name, required String password, required int height, required ledger.LedgerConnection ledgerConnection}); - WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required bool isPolyseed, required String? passphrase, String? password}); +WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required int seedType, required String? passphrase, String? password, String? mnemonic}); Map getKeys(Object wallet); int? getRestoreHeight(Object wallet); Object createMoneroTransactionCreationCredentials({required List outputs, required TransactionPriority priority}); From 2b020c0c1b2a88c8e94281c5a2ed64db14b67c49 Mon Sep 17 00:00:00 2001 From: Serhii Date: Thu, 10 Apr 2025 04:55:31 +0300 Subject: [PATCH 015/142] Cw 1032 verify on ramper integration (#2172) * refactor: cache onramp metadata * remove unused parameters * Skip onramper transaction page and go directly to provider (#1940) * Skip onramper transaction page and go directly to provider * change parameter to follow docs correctly * remove old deprecated monero nodes [skip ci] * Update onramper_buy_provider.dart --------- Co-authored-by: Serhii * refactor: normalise network --------- Co-authored-by: Omar Hatem --- lib/buy/onramper/onramper_buy_provider.dart | 73 ++++++++++----------- 1 file changed, 34 insertions(+), 39 deletions(-) diff --git a/lib/buy/onramper/onramper_buy_provider.dart b/lib/buy/onramper/onramper_buy_provider.dart index b48228bd8..ac198b488 100644 --- a/lib/buy/onramper/onramper_buy_provider.dart +++ b/lib/buy/onramper/onramper_buy_provider.dart @@ -37,6 +37,7 @@ class OnRamperBuyProvider extends BuyProvider { static const List _notSupportedCrypto = []; static const List _notSupportedFiat = []; + static Map _onrampMetadata = {}; final SettingsStore _settingsStore; @@ -59,11 +60,8 @@ class OnRamperBuyProvider extends BuyProvider { Future> getAvailablePaymentTypes( String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async { - final params = { - 'fiatCurrency': fiatCurrency, - 'type': isBuyAction ? 'buy' : 'sell', - 'isRecurringPayment': 'false' - }; + + final params = {'type': isBuyAction ? 'buy' : 'sell'}; final url = Uri.https(_baseApiUrl, '$supported$paymentTypes/$fiatCurrency', params); @@ -136,16 +134,14 @@ class OnRamperBuyProvider extends BuyProvider { final actionType = isBuyAction ? 'buy' : 'sell'; - final normalizedCryptoCurrency = _getNormalizeCryptoCurrency(cryptoCurrency); + final normalizedCryptoCurrency = + cryptoCurrency.title + _getNormalizeNetwork(cryptoCurrency).toUpperCase(); final params = { 'amount': amount.toString(), if (paymentMethod != null) 'paymentMethod': paymentMethod, 'clientName': 'CakeWallet', - 'type': actionType, - 'walletAddress': walletAddress, - 'isRecurringPayment': 'false', - 'input': 'source', + if (actionType == 'sell') 'type': actionType, }; log('Onramper: Fetching $actionType quote: ${isBuyAction ? normalizedCryptoCurrency : fiatCurrency.name} -> ${isBuyAction ? fiatCurrency.name : normalizedCryptoCurrency}, amount: $amount, paymentMethod: $paymentMethod'); @@ -165,7 +161,7 @@ class OnRamperBuyProvider extends BuyProvider { List validQuotes = []; - final onrampMetadata = await getOnrampMetadata(); + if (_onrampMetadata.isEmpty) _onrampMetadata = await getOnrampMetadata(); for (var item in data) { @@ -174,12 +170,12 @@ class OnRamperBuyProvider extends BuyProvider { final paymentMethod = (item as Map)['paymentMethod'] as String; final rampId = item['ramp'] as String?; - final rampMetaData = onrampMetadata[rampId] as Map?; + final rampMetaData = _onrampMetadata[rampId] as Map?; if (rampMetaData == null) continue; final quote = Quote.fromOnramperJson( - item, isBuyAction, onrampMetadata, _getPaymentTypeByString(paymentMethod)); + item, isBuyAction, _onrampMetadata, _getPaymentTypeByString(paymentMethod)); quote.setFiatCurrency = fiatCurrency; quote.setCryptoCurrency = cryptoCurrency; validQuotes.add(quote); @@ -206,7 +202,6 @@ class OnRamperBuyProvider extends BuyProvider { required String cryptoCurrencyAddress, String? countryCode}) async { final actionType = isBuyAction ? 'buy' : 'sell'; - final prefix = actionType == 'sell' ? actionType + '_' : ''; final primaryColor = getColorStr(Theme.of(context).primaryColor); final secondaryColor = getColorStr(Theme.of(context).colorScheme.background); @@ -220,18 +215,20 @@ class OnRamperBuyProvider extends BuyProvider { cardColor = getColorStr(Colors.white); } - final defaultCrypto = _getNormalizeCryptoCurrency(quote.cryptoCurrency); + final defaultCrypto = + quote.cryptoCurrency.title + _getNormalizeNetwork(quote.cryptoCurrency).toLowerCase(); final paymentMethod = normalizePaymentMethod(quote.paymentType); final uri = Uri.https(_baseUrl, '', { 'apiKey': _apiKey, - 'mode': actionType, - '${prefix}defaultFiat': quote.fiatCurrency.name, - '${prefix}defaultCrypto': defaultCrypto, - '${prefix}defaultAmount': amount.toString(), - if (paymentMethod != null) '${prefix}defaultPaymentMethod': paymentMethod, - 'onlyOnramps': quote.rampId, + 'txnType': actionType, + 'txnFiat': quote.fiatCurrency.name, + 'txnCrypto': defaultCrypto, + 'txnAmount': amount.toString(), + 'skipTransactionScreen': "true", + if (paymentMethod != null) 'txnPaymentMethod': paymentMethod, + 'txnOnramp': quote.rampId, 'networkWallets': '${_tagToNetwork(quote.cryptoCurrency.tag ?? quote.cryptoCurrency.title)}:$cryptoCurrencyAddress', 'supportSwap': "false", 'primaryColor': primaryColor, @@ -257,31 +254,29 @@ class OnRamperBuyProvider extends BuyProvider { String _tagToNetwork(String tag) { switch (tag) { - case 'OMNI': - case 'BSC': - return tag; case 'POL': return 'POLYGON'; + case 'ETH': + return 'ETHEREUM'; + case 'TRX': + return 'TRON'; + case 'SOL': + return 'SOLANA'; case 'ZEC': return 'ZCASH'; - default: - try { - return CryptoCurrency.fromString(tag).fullName!; - } catch (_) { - return tag; - } + default: + return tag; } } - String _getNormalizeCryptoCurrency(Currency currency) { - if (currency is CryptoCurrency) { - if (!mainCurrency.contains(currency)) { - final network = currency.tag == null ? currency.fullName : _tagToNetwork(currency.tag!); - return '${currency.title}_${network?.replaceAll(' ', '')}'.toUpperCase(); - } - return currency.title.toUpperCase(); - } - return currency.name.toUpperCase(); + String _getNormalizeNetwork(CryptoCurrency currency) { + if (mainCurrency.contains(currency)) return ''; + + if (currency == CryptoCurrency.eos) return '_EOSIO'; + + if (currency.tag != null) return '_' + _tagToNetwork(currency.tag!); + + return '_' + (currency.fullName?.replaceAll(' ', '') ?? currency.title);; } String? normalizePaymentMethod(PaymentType paymentType) { From c1e9668b1e0f58c43a5bd56eb2762740ef98ea2d Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Thu, 10 Apr 2025 04:03:06 +0200 Subject: [PATCH 016/142] CW-969-Add-DFX-s-Open-CryptoPay (#2122) * feat: Add Open CryptoPay for sending * bug: Fix sending Open CryptoPay for eth * feat: add transaction dismissal * style: remove print statements from OCP * feat: add support for Monero over OCP * bug: Hide "Add to Address Book" on OCP transactions * style: revert code style in send_view_model.dart * style: revert code style in send_view_model.dart * feat: set failure state if it fails to create an OCP transaction * Update lib/view_model/send/send_view_model.dart --------- Co-authored-by: Omar Hatem --- cw_evm/lib/evm_chain_client.dart | 2 +- cw_monero/lib/api/transaction_history.dart | 5 +- lib/core/open_crypto_pay/exceptions.dart | 18 ++ lib/core/open_crypto_pay/lnurl.dart | 78 ++++++++ lib/core/open_crypto_pay/models.dart | 26 +++ .../open_cryptopay_service.dart | 184 ++++++++++++++++++ lib/src/screens/send/send_page.dart | 12 +- lib/src/screens/send/widgets/send_card.dart | 24 +-- .../confirm_sending_bottom_sheet_widget.dart | 6 +- lib/view_model/send/send_view_model.dart | 170 +++++++++++----- res/values/strings_de.arb | 2 +- 11 files changed, 458 insertions(+), 69 deletions(-) create mode 100644 lib/core/open_crypto_pay/exceptions.dart create mode 100644 lib/core/open_crypto_pay/lnurl.dart create mode 100644 lib/core/open_crypto_pay/models.dart create mode 100644 lib/core/open_crypto_pay/open_cryptopay_service.dart diff --git a/cw_evm/lib/evm_chain_client.dart b/cw_evm/lib/evm_chain_client.dart index b505577e9..7e6caf374 100644 --- a/cw_evm/lib/evm_chain_client.dart +++ b/cw_evm/lib/evm_chain_client.dart @@ -190,7 +190,7 @@ abstract class EVMChainClient { _sendTransaction = () async => await sendTransaction(signedTransaction); return PendingEVMChainTransaction( - signedTransaction: signedTransaction, + signedTransaction: prepareSignedTransactionForSending(signedTransaction), amount: amount.toString(), fee: gasFee, sendTransaction: _sendTransaction, diff --git a/cw_monero/lib/api/transaction_history.dart b/cw_monero/lib/api/transaction_history.dart index a51e415a9..8c7b1e902 100644 --- a/cw_monero/lib/api/transaction_history.dart +++ b/cw_monero/lib/api/transaction_history.dart @@ -185,13 +185,14 @@ Future createTransactionSync( final rAmt = monero.PendingTransaction_amount(pendingTx); final rFee = monero.PendingTransaction_fee(pendingTx); final rHash = monero.PendingTransaction_txid(pendingTx, ''); + final rHex = monero.PendingTransaction_hex(pendingTx, ''); final rTxKey = rHash; return PendingTransactionDescription( amount: rAmt, fee: rFee, hash: rHash, - hex: '', + hex: rHex, txKey: rTxKey, pointerAddress: pendingTx.address, ); @@ -234,7 +235,7 @@ Future createTransactionMultDest( amount: monero.PendingTransaction_amount(txptr), fee: monero.PendingTransaction_fee(txptr), hash: monero.PendingTransaction_txid(txptr, ''), - hex: monero.PendingTransaction_txid(txptr, ''), + hex: monero.PendingTransaction_hex(txptr, ''), txKey: monero.PendingTransaction_txid(txptr, ''), pointerAddress: txptr.address, ); diff --git a/lib/core/open_crypto_pay/exceptions.dart b/lib/core/open_crypto_pay/exceptions.dart new file mode 100644 index 000000000..71010482f --- /dev/null +++ b/lib/core/open_crypto_pay/exceptions.dart @@ -0,0 +1,18 @@ +class OpenCryptoPayException implements Exception { + final String message; + + OpenCryptoPayException([this.message = '']); + + @override + String toString() => + 'OpenCryptoPayException${message.isNotEmpty ? ': $message' : ''}'; +} + +class OpenCryptoPayNotSupportedException extends OpenCryptoPayException { + final String provider; + + OpenCryptoPayNotSupportedException(this.provider); + + @override + String get message => "$provider does not support Open CryptoPay"; +} diff --git a/lib/core/open_crypto_pay/lnurl.dart b/lib/core/open_crypto_pay/lnurl.dart new file mode 100644 index 000000000..0087bab51 --- /dev/null +++ b/lib/core/open_crypto_pay/lnurl.dart @@ -0,0 +1,78 @@ +import 'dart:convert'; + +import 'package:bech32/bech32.dart'; + +Uri decodeLNURL(String encodedUrl) { + Uri decodedUri; + + /// The URL doesn't have to be encoded at all as per LUD-17: Protocol schemes and raw (non bech32-encoded) URLs. + /// https://github.com/lnurl/luds/blob/luds/17.md + /// Handle non bech32-encoded LNURL + final lud17prefixes = ['lnurlw', 'lnurlc', 'lnurlp', 'keyauth']; + decodedUri = Uri.parse(encodedUrl); + for (final prefix in lud17prefixes) { + if (decodedUri.scheme.contains(prefix)) { + decodedUri = decodedUri.replace(scheme: prefix); + } + } + if (lud17prefixes.contains(decodedUri.scheme)) { + /// If the non-bech32 LNURL is a Tor address, the port has to be http instead of https for the clearnet LNURL so check if the host ends with '.onion' or '.onion.' + decodedUri = decodedUri.replace( + scheme: decodedUri.host.endsWith('onion') || + decodedUri.host.endsWith('onion.') + ? 'http' + : 'https'); + } else { + /// Try to parse the input as a lnUrl. Will throw an error if it fails. + final lnUrl = _findLnUrl(encodedUrl); + + /// Decode the lnurl using bech32 + final bech32 = const Bech32Codec().decode(lnUrl, lnUrl.length); + decodedUri = Uri.parse(utf8.decode(_convert(bech32.data, 5, 8, false))); + } + return decodedUri; +} + +/// Parse and return a given lnurl string if it's valid. Will remove +/// `lightning:` from the beginning of it if present. +String _findLnUrl(String input) { + final res = RegExp( + r',*?((lnurl)([0-9]+[a-z0-9]+))', + ).allMatches(input.toLowerCase()); + + if (res.length == 1) { + return res.first.group(0)!; + } else { + throw ArgumentError('Not a valid lnurl string'); + } +} + +/// Taken from bech32 (bitcoinjs): https://github.com/bitcoinjs/bech32 +List _convert(List data, int inBits, int outBits, bool pad) { + var value = 0; + var bits = 0; + final maxV = (1 << outBits) - 1; + + final result = []; + for (final dataValue in data) { + value = (value << inBits) | dataValue; + bits += inBits; + + while (bits >= outBits) { + bits -= outBits; + result.add((value >> bits) & maxV); + } + } + + if (pad) { + if (bits > 0) result.add((value << (outBits - bits)) & maxV); + } else { + if (bits >= inBits) throw Exception('Excess padding'); + + if ((value << (outBits - bits)) & maxV > 0) { + throw Exception('Non-zero padding'); + } + } + + return result; +} diff --git a/lib/core/open_crypto_pay/models.dart b/lib/core/open_crypto_pay/models.dart new file mode 100644 index 000000000..760200f8d --- /dev/null +++ b/lib/core/open_crypto_pay/models.dart @@ -0,0 +1,26 @@ +class OpenCryptoPayRequest { + final String receiverName; + final int expiry; + final String callbackUrl; + final String quote; + final Map> methods; + + OpenCryptoPayRequest({ + required this.receiverName, + required this.expiry, + required this.callbackUrl, + required this.quote, + required this.methods, + }); +} + +class OpenCryptoPayQuoteAsset { + final String symbol; + final String amount; + + const OpenCryptoPayQuoteAsset(this.symbol, this.amount); + + OpenCryptoPayQuoteAsset.fromJson(Map json) + : symbol = json['asset'] as String, + amount = json['amount'] as String; +} diff --git a/lib/core/open_crypto_pay/open_cryptopay_service.dart b/lib/core/open_crypto_pay/open_cryptopay_service.dart new file mode 100644 index 000000000..2591f8903 --- /dev/null +++ b/lib/core/open_crypto_pay/open_cryptopay_service.dart @@ -0,0 +1,184 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:cake_wallet/core/open_crypto_pay/exceptions.dart'; +import 'package:cake_wallet/core/open_crypto_pay/lnurl.dart'; +import 'package:cake_wallet/core/open_crypto_pay/models.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:http/http.dart'; + +class OpenCryptoPayService { + static bool isOpenCryptoPayQR(String value) => + value.toLowerCase().contains("lightning=lnurl") || + value.toLowerCase().startsWith("lnurl"); + + final Client _httpClient = Client(); + + Future commitOpenCryptoPayRequest( + String txHex, { + required String txId, + required OpenCryptoPayRequest request, + required CryptoCurrency asset, + }) async { + final uri = Uri.parse(request.callbackUrl.replaceAll("/cb/", "/tx/")); + + final queryParams = Map.of(uri.queryParameters); + + queryParams['quote'] = request.quote; + queryParams['asset'] = asset.title; + queryParams['method'] = _getMethod(asset); + queryParams['hex'] = txHex; + queryParams['tx'] = txId; + + final response = + await _httpClient.get(Uri.https(uri.authority, uri.path, queryParams)); + + if (response.statusCode == 200) { + final body = jsonDecode(response.body) as Map; + + if (body.keys.contains("txId")) return body["txId"] as String; + throw OpenCryptoPayException(body.toString()); + } + throw OpenCryptoPayException( + "Unexpected status code ${response.statusCode} ${response.body}"); + } + + Future cancelOpenCryptoPayRequest(OpenCryptoPayRequest request) async { + final uri = Uri.parse(request.callbackUrl.replaceAll("/cb/", "/cancel/")); + + await _httpClient.delete(uri); + } + + Future getOpenCryptoPayInvoice(String lnUrl) async { + if (lnUrl.toLowerCase().startsWith("http")) { + final uri = Uri.parse(lnUrl); + final params = uri.queryParameters; + if (!params.containsKey("lightning")) { + throw OpenCryptoPayNotSupportedException(uri.authority); + } + + lnUrl = params["lightning"] as String; + } + final url = decodeLNURL(lnUrl); + final params = await _getOpenCryptoPayParams(url); + + return OpenCryptoPayRequest( + receiverName: params.$1.displayName ?? "Unknown", + expiry: params.$1.expiration.difference(DateTime.now()).inSeconds, + callbackUrl: params.$1.callbackUrl, + quote: params.$1.id, + methods: params.$2, + ); + } + + Future<(_OpenCryptoPayQuote, Map>)> + _getOpenCryptoPayParams(Uri uri) async { + final response = await _httpClient.get(uri); + + if (response.statusCode == 200) { + final responseBody = jsonDecode(response.body) as Map; + + for (final key in ['callback', 'transferAmounts', 'quote']) { + if (!responseBody.keys.contains(key)) { + throw OpenCryptoPayNotSupportedException(uri.authority); + } + } + + final methods = >{}; + for (final transferAmountRaw in responseBody['transferAmounts'] as List) { + final transferAmount = transferAmountRaw as Map; + final method = transferAmount['method'] as String; + methods[method] = []; + for (final assetJson in transferAmount['assets'] as List) { + final asset = OpenCryptoPayQuoteAsset.fromJson( + assetJson as Map); + methods[method]?.add(asset); + } + } + + log(responseBody.toString()); + + final quote = _OpenCryptoPayQuote.fromJson( + responseBody['callback'] as String, + responseBody['displayName'] as String?, + responseBody['quote'] as Map); + + return (quote, methods); + } else { + throw OpenCryptoPayException( + 'Failed to get Open CryptoPay Request. Status: ${response.statusCode} ${response.body}'); + } + } + + Future getOpenCryptoPayAddress( + OpenCryptoPayRequest request, CryptoCurrency asset) async { + final uri = Uri.parse(request.callbackUrl); + final queryParams = Map.of(uri.queryParameters); + + queryParams['quote'] = request.quote; + queryParams['asset'] = asset.title; + queryParams['method'] = _getMethod(asset); + + final response = + await _httpClient.get(Uri.https(uri.authority, uri.path, queryParams)); + + if (response.statusCode == 200) { + final responseBody = jsonDecode(response.body) as Map; + + for (final key in ['expiryDate', 'uri']) { + if (!responseBody.keys.contains(key)) { + throw OpenCryptoPayNotSupportedException(uri.authority); + } + } + + final result = Uri.parse(responseBody['uri'] as String); + + if (result.queryParameters['amount'] != null) return result; + + final newQueryParameters = + Map.from(result.queryParameters); + + newQueryParameters['amount'] = _getAmountByAsset(request, asset); + return Uri( + scheme: result.scheme, + path: result.path.split("@").first, + queryParameters: newQueryParameters); + } else { + throw OpenCryptoPayException( + 'Failed to create Open CryptoPay Request. Status: ${response.statusCode} ${response.body}'); + } + } + + String _getAmountByAsset(OpenCryptoPayRequest request, CryptoCurrency asset) { + final method = _getMethod(asset); + return request.methods[method]! + .firstWhere((e) => e.symbol == asset.title) + .amount; + } + + String _getMethod(CryptoCurrency asset) { + switch (asset.tag) { + case "ETH": + return "Ethereum"; + case "POL": + return "Polygon"; + default: + return asset.fullName!; + } + } +} + +class _OpenCryptoPayQuote { + final String callbackUrl; + final String? displayName; + final String id; + final DateTime expiration; + + _OpenCryptoPayQuote( + this.callbackUrl, this.displayName, this.id, this.expiration); + + _OpenCryptoPayQuote.fromJson( + this.callbackUrl, this.displayName, Map json) + : id = json['id'] as String, + expiration = DateTime.parse(json['expiration'] as String); +} diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 91f94e060..368dac19e 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -547,9 +547,9 @@ class SendPage extends BasePage { } if (state is ExecutedSuccessfullyState) { - WidgetsBinding.instance.addPostFrameCallback((_) { + WidgetsBinding.instance.addPostFrameCallback((_) async { if (context.mounted) { - showModalBottomSheet( + final result = await showModalBottomSheet( context: context, isDismissible: false, isScrollControlled: true, @@ -570,13 +570,16 @@ class SendPage extends BasePage { feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmountFormatted, outputs: sendViewModel.outputs, onSlideComplete: () async { - Navigator.of(bottomSheetContext).pop(); + Navigator.of(bottomSheetContext).pop(true); sendViewModel.commitTransaction(context); }, change: sendViewModel.pendingTransaction!.change, + isOpenCryptoPay: sendViewModel.ocpRequest != null, ); }, ); + + if (result == null) sendViewModel.dismissTransaction(); } }); } @@ -600,7 +603,8 @@ class SendPage extends BasePage { context: context, isDismissible: false, builder: (BuildContext bottomSheetContext) { - return showContactSheet + return showContactSheet && + sendViewModel.ocpRequest == null ? InfoBottomSheet( currentTheme: currentTheme, showDontAskMeCheckbox: true, diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index ba2b64f3a..c96992a00 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -1,16 +1,17 @@ +import 'package:cake_wallet/core/open_crypto_pay/open_cryptopay_service.dart'; import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart'; import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/src/widgets/standard_checkbox.dart'; -import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/currency.dart'; import 'package:cake_wallet/routes.dart'; -import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/unspent_coin_type.dart'; @@ -18,7 +19,6 @@ import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; -import 'package:keyboard_actions/keyboard_actions.dart'; import 'package:cake_wallet/view_model/send/send_view_model.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/src/widgets/address_text_field.dart'; @@ -26,9 +26,6 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; -import '../../../../themes/extensions/cake_text_theme.dart'; -import '../../../../themes/theme_base.dart'; - class SendCard extends StatefulWidget { SendCard({ Key? key, @@ -185,11 +182,16 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin outputs; final VoidCallback onSlideComplete; final PendingChange? change; + final bool isOpenCryptoPay; ConfirmSendingBottomSheet({ required String titleText, @@ -46,6 +47,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet { required this.outputs, required this.onSlideComplete, this.change, + this.isOpenCryptoPay = false, Key? key, }) : showScrollbar = outputs.length > 3, super(titleText: titleText, titleIconPath: titleIconPath); @@ -132,7 +134,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet { final _amount = item.cryptoAmount.replaceAll(',', '.') + ' ${currency.title}'; return isBatchSending || contactName.isNotEmpty ? AddressExpansionTile( - contactType: 'Contact', + contactType: isOpenCryptoPay ? 'Open CryptoPay' : S.of(context).contact, currentTheme: currentTheme, name: isBatchSending ? batchContactTitle : contactName, address: _address, @@ -143,7 +145,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet { tileBackgroundColor: tileBackgroundColor, ) : AddressTile( - itemTitle: 'Address', + itemTitle: S.of(context).address, currentTheme: currentTheme, itemTitleTextStyle: itemTitleTextStyle, isBatchSending: isBatchSending, diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 117706228..6b1515bb3 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -1,53 +1,57 @@ +import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/core/address_validator.dart'; +import 'package:cake_wallet/core/amount_validator.dart'; +import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/core/open_crypto_pay/models.dart'; +import 'package:cake_wallet/core/open_crypto_pay/open_cryptopay_service.dart'; +import 'package:cake_wallet/core/validator.dart'; +import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; +import 'package:cake_wallet/decred/decred.dart'; +import 'package:cake_wallet/entities/calculate_fiat_amount.dart'; import 'package:cake_wallet/entities/contact.dart'; +import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/evm_transaction_error_fees_handler.dart'; +import 'package:cake_wallet/entities/fiat_currency.dart'; +import 'package:cake_wallet/entities/parsed_address.dart'; +import 'package:cake_wallet/entities/template.dart'; import 'package:cake_wallet/entities/transaction_description.dart'; +import 'package:cake_wallet/entities/wallet_contact.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/exchange/provider/exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/nano/nano.dart'; -import 'package:cake_wallet/decred/decred.dart'; -import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; -import 'package:cake_wallet/entities/contact_record.dart'; -import 'package:cake_wallet/entities/wallet_contact.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/store/app_store.dart'; +import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; +import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/tron/tron.dart'; +import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cake_wallet/view_model/send/fees_view_model.dart'; +import 'package:cake_wallet/view_model/send/output.dart'; +import 'package:cake_wallet/view_model/send/send_template_view_model.dart'; +import 'package:cake_wallet/view_model/send/send_view_model_state.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart'; import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cake_wallet/zano/zano.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/exceptions.dart'; +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_core/sync_status.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/unspent_coin_type.dart'; -import 'package:cake_wallet/view_model/send/output.dart'; -import 'package:cake_wallet/view_model/send/send_template_view_model.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; -import 'package:cake_wallet/entities/template.dart'; -import 'package:cake_wallet/core/address_validator.dart'; -import 'package:cake_wallet/core/amount_validator.dart'; -import 'package:cw_core/pending_transaction.dart'; -import 'package:cake_wallet/core/validator.dart'; -import 'package:cake_wallet/core/execution_state.dart'; -import 'package:cake_wallet/monero/monero.dart'; -import 'package:cw_core/sync_status.dart'; -import 'package:cw_core/crypto_currency.dart'; -import 'package:cake_wallet/entities/fiat_currency.dart'; -import 'package:cake_wallet/entities/calculate_fiat_amount.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; -import 'package:cake_wallet/store/settings_store.dart'; -import 'package:cake_wallet/view_model/send/send_view_model_state.dart'; -import 'package:cake_wallet/entities/parsed_address.dart'; -import 'package:cake_wallet/bitcoin/bitcoin.dart'; -import 'package:cake_wallet/generated/i18n.dart'; part 'send_view_model.g.dart'; @@ -390,15 +394,62 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor return conditionsList.contains(true); } - @action - Future createTransaction({ExchangeProvider? provider}) async { - try { - if (wallet.isHardwareWallet) - state = IsAwaitingDeviceResponseState(); - else - state = IsExecutingState(); + final _ocpService = OpenCryptoPayService(); - pendingTransaction = await wallet.createTransaction(_credentials(provider)); + @observable + OpenCryptoPayRequest? ocpRequest; + + @action + Future dismissTransaction() async { + state = InitialExecutionState(); + if (ocpRequest != null) { + clearOutputs(); + _ocpService.cancelOpenCryptoPayRequest(ocpRequest!); + ocpRequest = null; + } + } + + @action + Future createOpenCryptoPayTransaction(String uri) async { + state = IsExecutingState(); + + try { + final originalOCPRequest = + await _ocpService.getOpenCryptoPayInvoice(uri.toString()); + final paymentUri = await _ocpService.getOpenCryptoPayAddress( + originalOCPRequest, + selectedCryptoCurrency, + ); + + ocpRequest = originalOCPRequest; + + final paymentRequest = PaymentRequest.fromUri(paymentUri); + clearOutputs(); + + outputs.first.address = paymentRequest.address; + outputs.first.parsedAddress = ParsedAddress( + addresses: [paymentRequest.address], name: ocpRequest!.receiverName); + outputs.first.setCryptoAmount(paymentRequest.amount); + outputs.first.note = ocpRequest!.receiverName; + + return createTransaction(); + } catch (e) { + printV(e); + state = FailureState(translateErrorMessage(e, walletType, currency)); + return null; + } + } + + @action + Future createTransaction( + {ExchangeProvider? provider}) async { + try { + if (!(state is IsExecutingState)) state = IsExecutingState(); + + if (wallet.isHardwareWallet) state = IsAwaitingDeviceResponseState(); + + pendingTransaction = + await wallet.createTransaction(_credentials(provider)); if (provider is ThorChainExchangeProvider) { final outputCount = pendingTransaction?.outputCount ?? 0; @@ -412,7 +463,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } if (wallet.type == WalletType.bitcoin) { - final updatedOutputs = bitcoin!.updateOutputs(pendingTransaction!, outputs); + final updatedOutputs = + bitcoin!.updateOutputs(pendingTransaction!, outputs); if (outputs.length == updatedOutputs.length) { outputs.replaceRange(0, outputs.length, updatedOutputs); @@ -473,7 +525,26 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor @action Future commitTransaction(BuildContext context) async { if (pendingTransaction == null) { - throw Exception("Pending transaction doesn't exist. It should not be happened."); + throw Exception( + "Pending transaction doesn't exist. It should not be happened."); + } + + if (ocpRequest != null) { + state = TransactionCommitting(); + if (selectedCryptoCurrency == CryptoCurrency.xmr) { + await pendingTransaction!.commit(); + } + + await _ocpService.commitOpenCryptoPayRequest( + pendingTransaction!.hex, + txId: pendingTransaction!.id, + request: ocpRequest!, + asset: selectedCryptoCurrency, + ); + + state = TransactionCommitted(); + + return; } String address = outputs.fold('', (acc, value) { @@ -493,8 +564,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor if (pendingTransaction!.shouldCommitUR()) { final urstr = await pendingTransaction!.commitUR(); - final result = - await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage, arguments: urstr); + final result = await Navigator.of(context) + .pushNamed(Routes.urqrAnimatedPage, arguments: urstr); if (result == null) { state = FailureState("Canceled by user"); return; @@ -508,12 +579,18 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } if (pendingTransaction!.id.isNotEmpty) { - final descriptionKey = '${pendingTransaction!.id}_${wallet.walletAddresses.primaryAddress}'; + final descriptionKey = + '${pendingTransaction!.id}_${wallet.walletAddresses.primaryAddress}'; _settingsStore.shouldSaveRecipientAddress ? await transactionDescriptionBox.add(TransactionDescription( - id: descriptionKey, recipientAddress: address, transactionNote: note)) - : await transactionDescriptionBox - .add(TransactionDescription(id: descriptionKey, transactionNote: note)); + id: descriptionKey, + recipientAddress: address, + transactionNote: note, + )) + : await transactionDescriptionBox.add(TransactionDescription( + id: descriptionKey, + transactionNote: note, + )); } state = TransactionCommitted(); @@ -552,12 +629,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor ); case WalletType.monero: - return monero! - .createMoneroTransactionCreationCredentials(outputs: outputs, priority: priority!); + return monero!.createMoneroTransactionCreationCredentials(outputs: outputs, priority: priority!); case WalletType.wownero: - return wownero! - .createWowneroTransactionCreationCredentials(outputs: outputs, priority: priority!); + return wownero!.createWowneroTransactionCreationCredentials(outputs: outputs, priority: priority!); case WalletType.ethereum: return ethereum!.createEthereumTransactionCredentials(outputs, @@ -568,8 +643,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor return polygon!.createPolygonTransactionCredentials(outputs, priority: priority!, currency: selectedCryptoCurrency); case WalletType.solana: - return solana! - .createSolanaTransactionCredentials(outputs, currency: selectedCryptoCurrency); + return solana!.createSolanaTransactionCredentials(outputs, currency: selectedCryptoCurrency); case WalletType.tron: return tron!.createTronTransactionCredentials(outputs, currency: selectedCryptoCurrency); case WalletType.zano: @@ -603,8 +677,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor ContactRecord? newContactAddress() { final Set contactAddresses = - Set.from(contactListViewModel.contacts.map((contact) => contact.address)) - ..addAll(contactListViewModel.walletContacts.map((contact) => contact.address)); + Set.from(contactListViewModel.contacts.map((contact) => contact.address)) + ..addAll(contactListViewModel.walletContacts.map((contact) => contact.address)); for (var output in outputs) { String address; diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 04f11ff02..699f8306a 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -697,7 +697,7 @@ "send": "Senden", "send_address": "${cryptoCurrency}-Adresse", "send_amount": "Betrag:", - "send_change_to_you": "Verändere dich zu dir:", + "send_change_to_you": "Rückgeld:", "send_creating_transaction": "Erstelle Transaktion", "send_error_currency": "Die Währung darf nur Zahlen enthalten", "send_error_minimum_value": "Der Mindestbetrag ist 0,01", From 3f25d69244ff9d08ce96335b5b5e7e70de912ec2 Mon Sep 17 00:00:00 2001 From: Serhii Date: Thu, 10 Apr 2025 17:16:43 +0300 Subject: [PATCH 017/142] CW-1021-Address-formatting-enhancements (#2141) * Implement visual formatting for addresses * fix minor issues * Update transaction_details_page.dart * Update transaction_details_page.dart * fix multi recipient address formatting * Update address_cell.dart --- .../cake_pay_confirm_purchase_card_page.dart | 1 + .../exchange_trade/exchange_trade_page.dart | 1 + .../screens/receive/address_list_page.dart | 1 + lib/src/screens/receive/receive_page.dart | 2 +- .../screens/receive/widgets/address_cell.dart | 47 ++++--- .../screens/receive/widgets/address_list.dart | 8 +- .../screens/receive/widgets/qr_widget.dart | 12 +- lib/src/screens/send/send_page.dart | 1 + .../transaction_details/rbf_details_page.dart | 1 + .../transaction_details_page.dart | 96 +++++++++++++- .../confirm_sending_bottom_sheet_widget.dart | 108 +++++----------- lib/src/widgets/list_row.dart | 20 +-- lib/utils/address_formatter.dart | 117 ++++++++++++++++++ 13 files changed, 292 insertions(+), 123 deletions(-) create mode 100644 lib/utils/address_formatter.dart diff --git a/lib/src/screens/cake_pay/cards/cake_pay_confirm_purchase_card_page.dart b/lib/src/screens/cake_pay/cards/cake_pay_confirm_purchase_card_page.dart index 9ae2acf8e..51c17bc8b 100644 --- a/lib/src/screens/cake_pay/cards/cake_pay_confirm_purchase_card_page.dart +++ b/lib/src/screens/cake_pay/cards/cake_pay_confirm_purchase_card_page.dart @@ -381,6 +381,7 @@ class CakePayBuyCardDetailPage extends BasePage { return ConfirmSendingBottomSheet( key: ValueKey('send_page_confirm_sending_dialog_key'), currentTheme: currentTheme, + walletType: cakePayPurchaseViewModel.sendViewModel.walletType, paymentId: S.of(popupContext).payment_id, paymentIdValue: order?.orderId, expirationTime: cakePayPurchaseViewModel.formattedRemainingTime, diff --git a/lib/src/screens/exchange_trade/exchange_trade_page.dart b/lib/src/screens/exchange_trade/exchange_trade_page.dart index f328cc115..277d3c838 100644 --- a/lib/src/screens/exchange_trade/exchange_trade_page.dart +++ b/lib/src/screens/exchange_trade/exchange_trade_page.dart @@ -264,6 +264,7 @@ class ExchangeTradeState extends State { return ConfirmSendingBottomSheet( key: ValueKey('exchange_trade_page_confirm_sending_bottom_sheet_key'), currentTheme: widget.currentTheme, + walletType: widget.exchangeTradeViewModel.sendViewModel.walletType, titleText: S.of(bottomSheetContext).confirm_transaction, titleIconPath: widget.exchangeTradeViewModel.sendViewModel.selectedCryptoCurrency.iconPath, diff --git a/lib/src/screens/receive/address_list_page.dart b/lib/src/screens/receive/address_list_page.dart index 5f6794715..b2af4389c 100644 --- a/lib/src/screens/receive/address_list_page.dart +++ b/lib/src/screens/receive/address_list_page.dart @@ -20,6 +20,7 @@ class AddressListPage extends BasePage { children: [ AddressList( addressListViewModel: addressListViewModel, + currentTheme: currentTheme, onSelect: (String address) async { Navigator.of(context).pop(address); }, diff --git a/lib/src/screens/receive/receive_page.dart b/lib/src/screens/receive/receive_page.dart index 7e3c2b555..2a18f4d08 100644 --- a/lib/src/screens/receive/receive_page.dart +++ b/lib/src/screens/receive/receive_page.dart @@ -124,7 +124,7 @@ class ReceivePage extends BasePage { isLight: currentTheme.type == ThemeType.light, ), ), - AddressList(addressListViewModel: addressListViewModel), + AddressList(addressListViewModel: addressListViewModel, currentTheme: currentTheme), Padding( padding: EdgeInsets.fromLTRB(24, 24, 24, 32), child: Text( diff --git a/lib/src/screens/receive/widgets/address_cell.dart b/lib/src/screens/receive/widgets/address_cell.dart index beef7c762..354a4fcb5 100644 --- a/lib/src/screens/receive/widgets/address_cell.dart +++ b/lib/src/screens/receive/widgets/address_cell.dart @@ -1,7 +1,11 @@ -import 'package:auto_size_text/auto_size_text.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/themes/extensions/qr_code_theme.dart'; +import 'package:cake_wallet/themes/extensions/receive_page_theme.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/utils/address_formatter.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; @@ -14,6 +18,8 @@ class AddressCell extends StatelessWidget { required this.isPrimary, required this.backgroundColor, required this.textColor, + required this.walletType, + required this.currentTheme, this.onTap, this.onEdit, this.onHide, @@ -30,6 +36,8 @@ class AddressCell extends StatelessWidget { required bool isCurrent, required Color backgroundColor, required Color textColor, + required WalletType walletType, + required ThemeBase currentTheme, Function(String)? onTap, bool hasBalance = false, bool hasReceived = false, @@ -45,6 +53,8 @@ class AddressCell extends StatelessWidget { isPrimary: item.isPrimary, backgroundColor: backgroundColor, textColor: textColor, + walletType: walletType, + currentTheme: currentTheme, onTap: onTap, onEdit: onEdit, onHide: onHide, @@ -62,6 +72,8 @@ class AddressCell extends StatelessWidget { final bool isPrimary; final Color backgroundColor; final Color textColor; + final WalletType walletType; + final ThemeBase currentTheme; final Function(String)? onTap; final Function()? onEdit; final Function()? onHide; @@ -73,21 +85,6 @@ class AddressCell extends StatelessWidget { final bool hasBalance; final bool hasReceived; - static const int addressPreviewLength = 8; - - String get formattedAddress { - final formatIfCashAddr = address.replaceAll('bitcoincash:', ''); - - if (formatIfCashAddr.length <= (name.isNotEmpty ? 16 : 43)) { - return formatIfCashAddr; - } else { - return formatIfCashAddr.substring(0, addressPreviewLength) + - '...' + - formatIfCashAddr.substring( - formatIfCashAddr.length - addressPreviewLength, formatIfCashAddr.length); - } - } - @override Widget build(BuildContext context) { final Widget cell = InkWell( @@ -139,16 +136,14 @@ class AddressCell extends StatelessWidget { ], ), Flexible( - child: AutoSizeText( - responsiveLayoutUtil.shouldRenderTabletUI ? address : formattedAddress, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: isChange ? 10 : 14, - color: textColor, - ), - ), - ), + child: AddressFormatter.buildSegmentedAddress( + address: address, + walletType: walletType, + shouldTruncate: name.isNotEmpty || address.length > 43 , + evenTextStyle: TextStyle( + fontSize: isChange ? 10 : 14, + color: textColor + ))), ], ), if (hasBalance || hasReceived) diff --git a/lib/src/screens/receive/widgets/address_list.dart b/lib/src/screens/receive/widgets/address_list.dart index 8d22d81ef..c5a1bc871 100644 --- a/lib/src/screens/receive/widgets/address_list.dart +++ b/lib/src/screens/receive/widgets/address_list.dart @@ -1,5 +1,3 @@ -import 'dart:math'; - import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; @@ -9,6 +7,7 @@ import 'package:cake_wallet/src/screens/receive/widgets/address_cell.dart'; import 'package:cake_wallet/src/screens/receive/widgets/header_tile.dart'; import 'package:cake_wallet/src/widgets/section_divider.dart'; import 'package:cake_wallet/themes/extensions/receive_page_theme.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/utils/list_item.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart'; @@ -20,16 +19,17 @@ import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:mobx/mobx.dart'; class AddressList extends StatefulWidget { const AddressList({ super.key, required this.addressListViewModel, + required this.currentTheme, this.onSelect, }); final WalletAddressListViewModel addressListViewModel; + final ThemeBase currentTheme; final Function(String)? onSelect; @override @@ -161,6 +161,8 @@ class _AddressListState extends State { return AddressCell.fromItem( item, isCurrent: isCurrent, + currentTheme: widget.currentTheme, + walletType: widget.addressListViewModel.type, hasBalance: widget.addressListViewModel.isBalanceAvailable, hasReceived: widget.addressListViewModel.isReceivedAvailable, // hasReceived: diff --git a/lib/src/screens/receive/widgets/qr_widget.dart b/lib/src/screens/receive/widgets/qr_widget.dart index 9d09e57a1..b263ea7ef 100644 --- a/lib/src/screens/receive/widgets/qr_widget.dart +++ b/lib/src/screens/receive/widgets/qr_widget.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart'; import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/utils/address_formatter.dart'; import 'package:cake_wallet/utils/brightness_util.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_bar.dart'; @@ -160,16 +161,15 @@ class QRWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( - child: Text( - addressUri.address, + child: AddressFormatter.buildSegmentedAddress( + address: addressUri.address, + walletType: addressListViewModel.type, textAlign: TextAlign.center, - style: TextStyle( + evenTextStyle: TextStyle( fontSize: 15, fontWeight: FontWeight.w500, color: - Theme.of(context).extension()!.textColor), - ), - ), + Theme.of(context).extension()!.textColor))), Padding( padding: EdgeInsets.only(left: 12), child: copyImage, diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 368dac19e..8c42f1129 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -558,6 +558,7 @@ class SendPage extends BasePage { key: ValueKey('send_page_confirm_sending_dialog_key'), titleText: S.of(bottomSheetContext).confirm_transaction, currentTheme: currentTheme, + walletType: sendViewModel.walletType, titleIconPath: sendViewModel.selectedCryptoCurrency.iconPath, currency: sendViewModel.selectedCryptoCurrency, amount: S.of(bottomSheetContext).send_amount, diff --git a/lib/src/screens/transaction_details/rbf_details_page.dart b/lib/src/screens/transaction_details/rbf_details_page.dart index 10cd40940..ce6ee122b 100644 --- a/lib/src/screens/transaction_details/rbf_details_page.dart +++ b/lib/src/screens/transaction_details/rbf_details_page.dart @@ -190,6 +190,7 @@ class RBFDetailsPage extends BasePage { key: ValueKey('rbf_confirm_sending_bottom_sheet'), titleText: S.of(bottomSheetContext).confirm_transaction, currentTheme: currentTheme, + walletType: transactionDetailsViewModel.sendViewModel.walletType, titleIconPath: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency.iconPath, currency: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency, amount: S.of(bottomSheetContext).send_amount, diff --git a/lib/src/screens/transaction_details/transaction_details_page.dart b/lib/src/screens/transaction_details/transaction_details_page.dart index 9484bf4da..c1188d0c7 100644 --- a/lib/src/screens/transaction_details/transaction_details_page.dart +++ b/lib/src/screens/transaction_details/transaction_details_page.dart @@ -9,8 +9,11 @@ import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item. import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart'; import 'package:cake_wallet/src/widgets/list_row.dart'; import 'package:cake_wallet/src/widgets/standard_list.dart'; +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; +import 'package:cake_wallet/utils/address_formatter.dart'; import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/view_model/transaction_details_view_model.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -39,13 +42,28 @@ class TransactionDetailsPage extends BasePage { final item = transactionDetailsViewModel.items[index]; if (item is StandartListItem) { + Widget? addressTextWidget; + + if (item.title.toLowerCase() == 'recipient addresses' || + item.title.toLowerCase() == 'source address') { + addressTextWidget = getFormattedAddress( + context: context, + value: item.value, + walletType: transactionDetailsViewModel.sendViewModel.walletType, + ); + } + return GestureDetector( key: item.key, onTap: () { Clipboard.setData(ClipboardData(text: item.value)); showBar(context, S.of(context).transaction_details_copied(item.title)); }, - child: ListRow(title: '${item.title}:', value: item.value), + child: ListRow( + title: '${item.title}:', + value: item.value, + textWidget: addressTextWidget, + ), ); } @@ -91,4 +109,80 @@ class TransactionDetailsPage extends BasePage { ], ); } + + Widget getFormattedAddress({ + required BuildContext context, + required String value, + required WalletType walletType, + }) { + final textStyle = TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.titleColor, + ); + final List children = []; + final bool hasDoubleNewline = value.contains('\n\n'); + + if (hasDoubleNewline) { + final blocks = value + .split('\n\n') + .map((b) => b.trim()) + .where((b) => b.isNotEmpty) + .toList(); + for (final block in blocks) { + final lines = block + .split('\n') + .map((l) => l.trim()) + .where((l) => l.isNotEmpty) + .toList(); + if (lines.length > 1) { + children.add(Text(lines.first, style: textStyle)); + for (int i = 1; i < lines.length; i++) { + children.add( + AddressFormatter.buildSegmentedAddress( + address: lines[i], + walletType: walletType, + evenTextStyle: textStyle, + ), + ); + } + } else { + children.add( + AddressFormatter.buildSegmentedAddress( + address: lines.first, + walletType: walletType, + evenTextStyle: textStyle, + ), + ); + } + children.add(SizedBox(height: 8)); + } + } else { + final lines = value + .split('\n') + .map((l) => l.trim()) + .where((l) => l.isNotEmpty) + .toList(); + bool firstLineIsContactName = (lines.length > 1 && lines.first.length < 20); + int startIndex = 0; + if (firstLineIsContactName) { + children.add(Text(lines.first, style: textStyle)); + startIndex = 1; + } + for (int i = startIndex; i < lines.length; i++) { + children.add( + AddressFormatter.buildSegmentedAddress( + address: lines[i], + walletType: walletType, + evenTextStyle: textStyle, + ), + ); + } + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: children, + ); + } } diff --git a/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart b/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart index 0732c87b2..f78717fb3 100644 --- a/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart +++ b/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart @@ -5,11 +5,12 @@ import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/themes/extensions/filter_theme.dart'; import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart'; import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/utils/address_formatter.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; -import 'dart:math' as math; import 'base_bottom_sheet_widget.dart'; @@ -27,6 +28,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet { final String feeFiatAmount; final List outputs; final VoidCallback onSlideComplete; + final WalletType walletType; final PendingChange? change; final bool isOpenCryptoPay; @@ -46,6 +48,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet { required this.feeFiatAmount, required this.outputs, required this.onSlideComplete, + required this.walletType, this.change, this.isOpenCryptoPay = false, Key? key, @@ -91,6 +94,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet { itemTitle: paymentId!, currentTheme: currentTheme, itemTitleTextStyle: itemTitleTextStyle, + walletType: walletType, isBatchSending: false, amount: '', address: paymentIdValue!, @@ -139,6 +143,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet { name: isBatchSending ? batchContactTitle : contactName, address: _address, amount: _amount, + walletType: walletType, isBatchSending: isBatchSending, itemTitleTextStyle: itemTitleTextStyle, itemSubTitleTextStyle: itemSubTitleTextStyle, @@ -149,6 +154,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet { currentTheme: currentTheme, itemTitleTextStyle: itemTitleTextStyle, isBatchSending: isBatchSending, + walletType: walletType, amount: _amount, address: _address, itemSubTitleTextStyle: itemSubTitleTextStyle, @@ -166,6 +172,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet { address: change!.address, amount: change!.amount + ' ${currency.title}', isBatchSending: true, + walletType: walletType, itemTitleTextStyle: itemTitleTextStyle, itemSubTitleTextStyle: itemSubTitleTextStyle, tileBackgroundColor: tileBackgroundColor, @@ -275,6 +282,7 @@ class AddressTile extends StatelessWidget { required this.address, required this.itemSubTitleTextStyle, required this.tileBackgroundColor, + required this.walletType, }); final String itemTitle; @@ -285,18 +293,10 @@ class AddressTile extends StatelessWidget { final String address; final TextStyle itemSubTitleTextStyle; final Color tileBackgroundColor; + final WalletType walletType; @override Widget build(BuildContext context) { - final addressTextStyle = TextStyle( - fontSize: 12, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: currentTheme.type == ThemeType.bright - ? Theme.of(context).extension()!.titleColor.withOpacity(0.5) - : Theme.of(context).extension()!.titleColor, - decoration: TextDecoration.none, - ); return Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), decoration: BoxDecoration( @@ -313,41 +313,20 @@ class AddressTile extends StatelessWidget { if (isBatchSending) Text(amount, style: itemTitleTextStyle), ], ), - buildSegmentedAddress( + AddressFormatter.buildSegmentedAddress( address: address, - evenTextStyle: addressTextStyle, - oddTextStyle: itemSubTitleTextStyle, + walletType: walletType, + evenTextStyle: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.titleColor, + decoration: TextDecoration.none) ), ], ), ); } - - Widget buildSegmentedAddress({ - required String address, - int chunkSize = 6, - required TextStyle evenTextStyle, - required TextStyle oddTextStyle, - }) { - final spans = []; - - int index = 0; - for (int i = 0; i < address.length; i += chunkSize) { - final chunk = address.substring(i, math.min(i + chunkSize, address.length)); - final style = (index % 2 == 0) ? evenTextStyle : oddTextStyle; - - spans.add( - TextSpan(text: '$chunk ', style: style), - ); - - index++; - } - - return RichText( - text: TextSpan(children: spans, style: evenTextStyle), - overflow: TextOverflow.visible, - ); - } } class AddressExpansionTile extends StatelessWidget { @@ -362,6 +341,7 @@ class AddressExpansionTile extends StatelessWidget { required this.itemTitleTextStyle, required this.itemSubTitleTextStyle, required this.tileBackgroundColor, + required this.walletType, }); final String contactType; @@ -373,19 +353,10 @@ class AddressExpansionTile extends StatelessWidget { final TextStyle itemTitleTextStyle; final TextStyle itemSubTitleTextStyle; final Color tileBackgroundColor; + final WalletType walletType; @override Widget build(BuildContext context) { - final addressTextStyle = TextStyle( - fontSize: 12, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: currentTheme.type == ThemeType.bright - ? Theme.of(context).extension()!.titleColor.withOpacity(0.5) - : Theme.of(context).extension()!.titleColor, - decoration: TextDecoration.none, - ); - return Container( decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(10)), @@ -420,10 +391,15 @@ class AddressExpansionTile extends StatelessWidget { Row( children: [ Expanded( - child: buildSegmentedAddress( - address: address, - evenTextStyle: addressTextStyle, - oddTextStyle: itemSubTitleTextStyle, + child: AddressFormatter.buildSegmentedAddress( + address: address, + walletType: walletType, + evenTextStyle: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.titleColor, + decoration: TextDecoration.none) ), ), ], @@ -434,30 +410,4 @@ class AddressExpansionTile extends StatelessWidget { ), ); } - - Widget buildSegmentedAddress({ - required String address, - int chunkSize = 6, - required TextStyle evenTextStyle, - required TextStyle oddTextStyle, - }) { - final spans = []; - - int index = 0; - for (int i = 0; i < address.length; i += chunkSize) { - final chunk = address.substring(i, math.min(i + chunkSize, address.length)); - final style = (index % 2 == 0) ? evenTextStyle : oddTextStyle; - - spans.add( - TextSpan(text: '$chunk ', style: style), - ); - - index++; - } - - return RichText( - text: TextSpan(children: spans, style: evenTextStyle), - overflow: TextOverflow.visible, - ); - } } diff --git a/lib/src/widgets/list_row.dart b/lib/src/widgets/list_row.dart index 417c7836b..11b27845b 100644 --- a/lib/src/widgets/list_row.dart +++ b/lib/src/widgets/list_row.dart @@ -12,7 +12,8 @@ class ListRow extends StatelessWidget { this.padding, this.color, this.hintTextColor, - this.mainTextColor + this.mainTextColor, + this.textWidget }); final String title; @@ -24,6 +25,16 @@ class ListRow extends StatelessWidget { final Color? color; final Color? hintTextColor; final Color? mainTextColor; + final Widget? textWidget; + + Widget _getTextWidget (BuildContext context) => textWidget ?? Text( + value, + style: TextStyle( + fontSize: valueFontSize, + fontWeight: FontWeight.w500, + color: mainTextColor ?? Theme.of(context).extension()!.titleColor + ), + ); @override Widget build(BuildContext context) { @@ -49,12 +60,7 @@ class ListRow extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( - child: Text(value, - style: TextStyle( - fontSize: valueFontSize, - fontWeight: FontWeight.w500, - color: mainTextColor ?? Theme.of(context).extension()!.titleColor)), - ), + child: _getTextWidget(context)), image != null ? Padding( padding: EdgeInsets.only(left: 24), diff --git a/lib/utils/address_formatter.dart b/lib/utils/address_formatter.dart new file mode 100644 index 000000000..0bcc713d0 --- /dev/null +++ b/lib/utils/address_formatter.dart @@ -0,0 +1,117 @@ +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/widgets.dart'; +import 'dart:math' as math; + +class AddressFormatter { + static Widget buildSegmentedAddress({ + required String address, + required WalletType walletType, + required TextStyle evenTextStyle, + TextStyle? oddTextStyle, + TextAlign? textAlign, + bool shouldTruncate = false, + }) { + if (shouldTruncate) { + return _buildTruncatedAddress( + address: address, + walletType: walletType, + evenTextStyle: evenTextStyle, + oddTextStyle: oddTextStyle ?? evenTextStyle.copyWith(color: evenTextStyle.color!.withAlpha(150)), + textAlign: textAlign, + ); + } else { + return _buildFullSegmentedAddress( + address: address, + walletType: walletType, + evenTextStyle: evenTextStyle, + oddTextStyle: oddTextStyle ?? evenTextStyle.copyWith(color: evenTextStyle.color!.withAlpha(128)), + textAlign: textAlign, + ); + } + } + + static Widget _buildFullSegmentedAddress({ + required String address, + required WalletType walletType, + required TextStyle evenTextStyle, + required TextStyle oddTextStyle, + TextAlign? textAlign, + }) { + + final cleanAddress = address.replaceAll('bitcoincash:', ''); + final chunkSize = _getChunkSize(walletType); + final chunks = []; + + for (int i = 0; i < cleanAddress.length; i += chunkSize) { + final chunk = cleanAddress.substring(i, math.min(i + chunkSize, cleanAddress.length)); + chunks.add(chunk); + } + + final spans = []; + for (int i = 0; i < chunks.length; i++) { + final style = (i % 2 == 0) ? evenTextStyle : oddTextStyle; + spans.add(TextSpan(text: '${chunks[i]} ', style: style)); + } + + return RichText( + text: TextSpan(children: spans), + textAlign: textAlign ?? TextAlign.start, + overflow: TextOverflow.visible, + ); + } + + static Widget _buildTruncatedAddress({ + required String address, + required WalletType walletType, + required TextStyle evenTextStyle, + required TextStyle oddTextStyle, + TextAlign? textAlign, + }) { + + final cleanAddress = address.replaceAll('bitcoincash:', ''); + + final int digitCount = (walletType == WalletType.monero || + walletType == WalletType.wownero || + walletType == WalletType.zano) + ? 6 + : 4; + + if (cleanAddress.length <= 2 * digitCount) { + return _buildFullSegmentedAddress( + address: cleanAddress, + walletType: walletType, + evenTextStyle: evenTextStyle, + oddTextStyle: oddTextStyle, + textAlign: textAlign, + ); + } + + final String firstPart = cleanAddress.substring(0, digitCount); + final String secondPart = cleanAddress.substring(digitCount, digitCount * 2); + final String lastPart = cleanAddress.substring(cleanAddress.length - digitCount); + + final spans = [ + TextSpan(text: '$firstPart ', style: evenTextStyle), + TextSpan(text: '$secondPart ', style: oddTextStyle), + TextSpan(text: '... ', style: oddTextStyle), + TextSpan(text: lastPart, style: evenTextStyle), + ]; + + return RichText( + text: TextSpan(children: spans), + textAlign: textAlign ?? TextAlign.start, + overflow: TextOverflow.visible, + ); + } + + static int _getChunkSize(WalletType walletType) { + switch (walletType) { + case WalletType.monero: + case WalletType.wownero: + case WalletType.zano: + return 6; + default: + return 4; + } + } +} From ea9b87d480e2256683318fd010e07ffd3ce8a8a6 Mon Sep 17 00:00:00 2001 From: cyan Date: Thu, 10 Apr 2025 23:17:52 +0200 Subject: [PATCH 018/142] fix(cw_monero): wrong function in account rename call (#2183) --- cw_monero/lib/api/account_list.dart | 13 ++++++++----- cw_monero/lib/monero_account_list.dart | 12 ++++++------ cw_wownero/lib/api/account_list.dart | 3 +-- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/cw_monero/lib/api/account_list.dart b/cw_monero/lib/api/account_list.dart index 28b00c925..3ceef5815 100644 --- a/cw_monero/lib/api/account_list.dart +++ b/cw_monero/lib/api/account_list.dart @@ -1,4 +1,7 @@ +import 'dart:async'; + import 'package:cw_monero/api/wallet.dart'; +import 'package:cw_monero/monero_account_list.dart'; import 'package:monero/monero.dart' as monero; monero.wallet? wptr = null; @@ -15,7 +18,6 @@ monero.WalletListener? getWlptr() { return _wlptr!; } - monero.SubaddressAccount? subaddressAccount; bool isUpdating = false; @@ -51,8 +53,9 @@ void addAccountSync({required String label}) { } void setLabelForAccountSync({required int accountIndex, required String label}) { - // TODO(mrcyjanek): this may be wrong function? - monero.Wallet_setSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: 0, label: label); + monero.SubaddressAccount_setLabel(subaddressAccount!, accountIndex: accountIndex, label: label); + MoneroAccountListBase.cachedAccounts[wptr!.address] = []; + refreshAccounts(); } void _addAccount(String label) => addAccountSync(label: label); @@ -66,10 +69,10 @@ void _setLabelForAccount(Map args) { Future addAccount({required String label}) async { _addAccount(label); - await store(); + unawaited(store()); } Future setLabelForAccount({required int accountIndex, required String label}) async { _setLabelForAccount({'accountIndex': accountIndex, 'label': label}); - await store(); + unawaited(store()); } \ No newline at end of file diff --git a/cw_monero/lib/monero_account_list.dart b/cw_monero/lib/monero_account_list.dart index 82a0efd32..c9a48a939 100644 --- a/cw_monero/lib/monero_account_list.dart +++ b/cw_monero/lib/monero_account_list.dart @@ -45,18 +45,18 @@ abstract class MoneroAccountListBase with Store { } } - Map> _cachedAccounts = {}; + static Map> cachedAccounts = {}; List getAll() { final allAccounts = account_list.getAllAccount(); final currentCount = allAccounts.length; - _cachedAccounts[account_list.wptr!.address] ??= []; + cachedAccounts[account_list.wptr!.address] ??= []; - if (_cachedAccounts[account_list.wptr!.address]!.length == currentCount) { - return _cachedAccounts[account_list.wptr!.address]!; + if (cachedAccounts[account_list.wptr!.address]!.length == currentCount) { + return cachedAccounts[account_list.wptr!.address]!; } - _cachedAccounts[account_list.wptr!.address] = allAccounts.map((accountRow) { + cachedAccounts[account_list.wptr!.address] = allAccounts.map((accountRow) { final balance = monero.SubaddressAccountRow_getUnlockedBalance(accountRow); return Account( @@ -66,7 +66,7 @@ abstract class MoneroAccountListBase with Store { ); }).toList(); - return _cachedAccounts[account_list.wptr!.address]!; + return cachedAccounts[account_list.wptr!.address]!; } Future addAccount({required String label}) async { diff --git a/cw_wownero/lib/api/account_list.dart b/cw_wownero/lib/api/account_list.dart index a73e4dcd2..5bd18d51e 100644 --- a/cw_wownero/lib/api/account_list.dart +++ b/cw_wownero/lib/api/account_list.dart @@ -48,8 +48,7 @@ void addAccountSync({required String label}) { } void setLabelForAccountSync({required int accountIndex, required String label}) { - // TODO(mrcyjanek): this may be wrong function? - wownero.Wallet_setSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: 0, label: label); + wownero.SubaddressAccount_setLabel(subaddressAccount!, accountIndex: accountIndex, label: label); } void _addAccount(String label) => addAccountSync(label: label); From db051232cede5d831aed47ed93203f7de1a1c05b Mon Sep 17 00:00:00 2001 From: cyan Date: Fri, 11 Apr 2025 02:12:40 +0200 Subject: [PATCH 019/142] remove shrinkResources and minifyEnabled to reduce apk size (#2104) * remove shrinkResources and minifyEnabled to reduce apk size * remove shrinkResources and minifyEnabled to reduce apk size --- android/app/build.gradle | 3 --- android/app/proguard-rules.pro | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index c45ed9368..6c299c929 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -74,9 +74,6 @@ android { release { signingConfig signingConfigs.release - shrinkResources false - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index d24d7f10a..921ee4d4c 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -5,4 +5,5 @@ -keep class io.flutter.view.** { *; } -keep class io.flutter.** { *; } -keep class io.flutter.plugins.** { *; } --dontwarn io.flutter.embedding.** \ No newline at end of file +-dontwarn io.flutter.embedding.** +-dontwarn com.google.android.play.core.splitcompat.SplitCompatApplication \ No newline at end of file From 6ed07a504e5e0010bf69135bbf51198789cbe5ad Mon Sep 17 00:00:00 2001 From: Serhii Date: Fri, 11 Apr 2025 04:23:31 +0300 Subject: [PATCH 020/142] add caching for supported assets (#2184) --- lib/buy/robinhood/robinhood_buy_provider.dart | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/lib/buy/robinhood/robinhood_buy_provider.dart b/lib/buy/robinhood/robinhood_buy_provider.dart index 90177ec61..93efd5642 100644 --- a/lib/buy/robinhood/robinhood_buy_provider.dart +++ b/lib/buy/robinhood/robinhood_buy_provider.dart @@ -35,9 +35,16 @@ class RobinhoodBuyProvider extends BuyProvider { static const _baseUrl = 'applink.robinhood.com'; static const _cIdBaseUrl = 'exchange-helper.cakewallet.com'; + static const _apiBaseUrl = 'api.robinhood.com'; + static const _assetsPath = '/catpay/v1/supported_currencies/'; - static const List _notSupportedCrypto = []; - static const List _notSupportedFiat = []; + static List _supportedCrypto = []; + static final List _supportedFiat = [FiatCurrency.usd]; + + static final List _notSupportedCrypto = []; + + static final List _notSupportedFiat = + FiatCurrency.all.where((fiat) => !_supportedFiat.contains(fiat)).toList(); @override String get title => 'Robinhood Connect'; @@ -58,6 +65,38 @@ class RobinhoodBuyProvider extends BuyProvider { String get _apiSecret => secrets.exchangeHelperApiKey; + Future> getSupportedAssets() async { + final uri = Uri.https(_apiBaseUrl, '$_assetsPath', {'applicationId': _applicationId}); + + try { + final response = await http.get(uri, headers: {'accept': 'application/json'}); + + if (response.statusCode == 200) { + final responseData = jsonDecode(response.body) as Map; + final pairs = responseData['cryptoCurrencyPairs'] as List; + + final supportedAssets = []; + + for (final item in pairs) { + String code = item['assetCurrency']['code'] as String; + if (code == 'AVAX') code = 'AVAXC'; + try { + final currency = CryptoCurrency.fromString(code); + supportedAssets.add(currency); + } catch (e) { + log('Robinhood: Unknown asset code "$code" - skipped'); + } + } + return supportedAssets; + } else { + return []; + } + } catch (e) { + log('Robinhood: Failed to fetch supported assets: $e'); + return []; + } + } + Future getSignature(String message) async { switch (wallet.type) { case WalletType.ethereum: @@ -156,6 +195,9 @@ class RobinhoodBuyProvider extends BuyProvider { String? countryCode}) async { String? paymentMethod; + if (_supportedCrypto.isEmpty) _supportedCrypto = await getSupportedAssets(); + if (_supportedCrypto.isNotEmpty && !(_supportedCrypto.contains(cryptoCurrency))) return null; + if (paymentType != null && paymentType != PaymentType.all) { paymentMethod = normalizePaymentMethod(paymentType); if (paymentMethod == null) return null; From 079480e3c9ab881f5ddb614241eeb68ca3874ff2 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Fri, 11 Apr 2025 04:41:13 +0200 Subject: [PATCH 021/142] minor fix [skip ci] --- lib/view_model/send/send_view_model.dart | 44 +++++++++++------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 6b1515bb3..d98b099cf 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -223,7 +223,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor String get balance { if (walletType == WalletType.litecoin && coinTypeToSpendFrom == UnspentCoinType.mweb) { return balanceViewModel.balances.values.first.secondAvailableBalance; - } else if (walletType == WalletType.litecoin && coinTypeToSpendFrom == UnspentCoinType.nonMweb) { + } else if (walletType == WalletType.litecoin && + coinTypeToSpendFrom == UnspentCoinType.nonMweb) { return balanceViewModel.balances.values.first.availableBalance; } return wallet.balance[selectedCryptoCurrency]!.formattedFullAvailableBalance; @@ -414,8 +415,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor state = IsExecutingState(); try { - final originalOCPRequest = - await _ocpService.getOpenCryptoPayInvoice(uri.toString()); + final originalOCPRequest = await _ocpService.getOpenCryptoPayInvoice(uri.toString()); final paymentUri = await _ocpService.getOpenCryptoPayAddress( originalOCPRequest, selectedCryptoCurrency, @@ -427,8 +427,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor clearOutputs(); outputs.first.address = paymentRequest.address; - outputs.first.parsedAddress = ParsedAddress( - addresses: [paymentRequest.address], name: ocpRequest!.receiverName); + outputs.first.parsedAddress = + ParsedAddress(addresses: [paymentRequest.address], name: ocpRequest!.receiverName); outputs.first.setCryptoAmount(paymentRequest.amount); outputs.first.note = ocpRequest!.receiverName; @@ -441,15 +441,13 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } @action - Future createTransaction( - {ExchangeProvider? provider}) async { + Future createTransaction({ExchangeProvider? provider}) async { try { if (!(state is IsExecutingState)) state = IsExecutingState(); if (wallet.isHardwareWallet) state = IsAwaitingDeviceResponseState(); - pendingTransaction = - await wallet.createTransaction(_credentials(provider)); + pendingTransaction = await wallet.createTransaction(_credentials(provider)); if (provider is ThorChainExchangeProvider) { final outputCount = pendingTransaction?.outputCount ?? 0; @@ -463,8 +461,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } if (wallet.type == WalletType.bitcoin) { - final updatedOutputs = - bitcoin!.updateOutputs(pendingTransaction!, outputs); + final updatedOutputs = bitcoin!.updateOutputs(pendingTransaction!, outputs); if (outputs.length == updatedOutputs.length) { outputs.replaceRange(0, outputs.length, updatedOutputs); @@ -525,8 +522,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor @action Future commitTransaction(BuildContext context) async { if (pendingTransaction == null) { - throw Exception( - "Pending transaction doesn't exist. It should not be happened."); + throw Exception("Pending transaction doesn't exist. It should not be happened."); } if (ocpRequest != null) { @@ -564,8 +560,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor if (pendingTransaction!.shouldCommitUR()) { final urstr = await pendingTransaction!.commitUR(); - final result = await Navigator.of(context) - .pushNamed(Routes.urqrAnimatedPage, arguments: urstr); + final result = + await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage, arguments: urstr); if (result == null) { state = FailureState("Canceled by user"); return; @@ -579,8 +575,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } if (pendingTransaction!.id.isNotEmpty) { - final descriptionKey = - '${pendingTransaction!.id}_${wallet.walletAddresses.primaryAddress}'; + final descriptionKey = '${pendingTransaction!.id}_${wallet.walletAddresses.primaryAddress}'; _settingsStore.shouldSaveRecipientAddress ? await transactionDescriptionBox.add(TransactionDescription( id: descriptionKey, @@ -629,10 +624,12 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor ); case WalletType.monero: - return monero!.createMoneroTransactionCreationCredentials(outputs: outputs, priority: priority!); + return monero! + .createMoneroTransactionCreationCredentials(outputs: outputs, priority: priority!); case WalletType.wownero: - return wownero!.createWowneroTransactionCreationCredentials(outputs: outputs, priority: priority!); + return wownero! + .createWowneroTransactionCreationCredentials(outputs: outputs, priority: priority!); case WalletType.ethereum: return ethereum!.createEthereumTransactionCredentials(outputs, @@ -643,7 +640,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor return polygon!.createPolygonTransactionCredentials(outputs, priority: priority!, currency: selectedCryptoCurrency); case WalletType.solana: - return solana!.createSolanaTransactionCredentials(outputs, currency: selectedCryptoCurrency); + return solana! + .createSolanaTransactionCredentials(outputs, currency: selectedCryptoCurrency); case WalletType.tron: return tron!.createTronTransactionCredentials(outputs, currency: selectedCryptoCurrency); case WalletType.zano: @@ -677,8 +675,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor ContactRecord? newContactAddress() { final Set contactAddresses = - Set.from(contactListViewModel.contacts.map((contact) => contact.address)) - ..addAll(contactListViewModel.walletContacts.map((contact) => contact.address)); + Set.from(contactListViewModel.contacts.map((contact) => contact.address)) + ..addAll(contactListViewModel.walletContacts.map((contact) => contact.address)); for (var output in outputs) { String address; @@ -768,7 +766,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor final parsedErrorMessageResult = EVMTransactionErrorFeesHandler.parseEthereumFeesErrorMessage( errorMessage, - _fiatConversationStore.prices[currency]!, + _fiatConversationStore.prices[currency] ?? 0.0, ); if (parsedErrorMessageResult.error != null) { From 14549bcfe2bce8b6a9a6ef37717bcb3d12dbbcde Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Fri, 11 Apr 2025 12:45:50 +0200 Subject: [PATCH 022/142] fix(cw_monero): fix missing passphrase for bip39 Monero Wallets (#2188) --- cw_monero/lib/monero_wallet_service.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart index e289f9f17..0163b17e1 100644 --- a/cw_monero/lib/monero_wallet_service.dart +++ b/cw_monero/lib/monero_wallet_service.dart @@ -403,6 +403,8 @@ class MoneroWalletService extends WalletService< monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed.bip39", value: mnemonic); + monero.Wallet_setCacheAttribute(wptr!, + key: "cakewallet.passphrase", value: passphrase ?? ''); monero.Wallet_store(wptr!); From c0283a37ee300c8aee45e7cb4fa318cf4fdcd1f5 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Fri, 11 Apr 2025 14:13:40 +0200 Subject: [PATCH 023/142] minor fix [skip ci] --- cw_decred/lib/wallet.dart | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/cw_decred/lib/wallet.dart b/cw_decred/lib/wallet.dart index c41643067..2c7af61f0 100644 --- a/cw_decred/lib/wallet.dart +++ b/cw_decred/lib/wallet.dart @@ -710,14 +710,18 @@ abstract class DecredWalletBase // walletBirthdayBlockHeight checks if the wallet birthday is set and returns // it. Returns -1 if not. Future walletBirthdayBlockHeight() async { - final res = await _libwallet.birthState(walletInfo.name); - final decoded = json.decode(res); - // Having these values set indicates that sync has not reached the birthday - // yet, so no birthday is set. - if (decoded["setfromheight"] == true || decoded["setfromtime"] == true) { - return -1; + try { + final res = await _libwallet.birthState(walletInfo.name); + final decoded = json.decode(res); + // Having these values set indicates that sync has not reached the birthday + // yet, so no birthday is set. + if (decoded["setfromheight"] == true || decoded["setfromtime"] == true) { + return -1; + } + return decoded["height"] ?? 0; + } on FormatException catch (_) { + return 0; } - return decoded["height"] ?? 0; } Future verifyMessage(String message, String signature, {String? address = null}) async { From 46a2664b73923aae9ebefa3d4e2c6bd98eae7fa7 Mon Sep 17 00:00:00 2001 From: tsinghuacoder Date: Fri, 11 Apr 2025 20:37:02 +0800 Subject: [PATCH 024/142] chore: fix some typos (#2187) Signed-off-by: tsinghuacoder --- cw_bitcoin/lib/litecoin_wallet.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index d54303075..662d70d67 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -329,7 +329,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { try { await subscribeForUpdates(); } catch (e) { - printV("failed to subcribe for updates: $e"); + printV("failed to subscribe for updates: $e"); } updateFeeRates(); _feeRatesTimer?.cancel(); @@ -601,7 +601,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { } _utxoStream = responseStream.listen( (Utxo sUtxo) async { - // we're processing utxos, so our balance could still be innacurate: + // we're processing utxos, so our balance could still be inaccurate: if (mwebSyncStatus is! SyncronizingSyncStatus && mwebSyncStatus is! SyncingSyncStatus) { mwebSyncStatus = SyncronizingSyncStatus(); processingUtxos = true; From 277dde461432fe8f08b4a6bddd1ee4f4e266cf68 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Fri, 11 Apr 2025 14:51:19 +0200 Subject: [PATCH 025/142] skip scam check on whitelisted tokens --- lib/src/screens/dashboard/edit_token_page.dart | 12 ++++++------ .../dashboard/home_settings_view_model.dart | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/src/screens/dashboard/edit_token_page.dart b/lib/src/screens/dashboard/edit_token_page.dart index 8429a63df..8cf4ff8e2 100644 --- a/lib/src/screens/dashboard/edit_token_page.dart +++ b/lib/src/screens/dashboard/edit_token_page.dart @@ -207,15 +207,15 @@ class _EditTokenPageBodyState extends State { onPressed: () async { if (_formKey.currentState!.validate() && (!_showDisclaimer || _disclaimerChecked)) { - final hasPotentialError = await widget.homeSettingsViewModel + final isWhitelisted = await widget.homeSettingsViewModel + .checkIfTokenIsWhitelisted(_contractAddressController.text); + + final hasPotentialError = !isWhitelisted && await widget.homeSettingsViewModel .checkIfERC20TokenContractAddressIsAPotentialScamAddress( _contractAddressController.text, ); - final isWhitelisted = await widget.homeSettingsViewModel - .checkIfTokenIsWhitelisted(_contractAddressController.text); - - bool isPotentialScam = hasPotentialError; + bool isPotentialScam = hasPotentialError && !isWhitelisted; final tokenSymbol = _tokenSymbolController.text.toUpperCase(); // check if the token symbol is the same as any of the base currencies symbols (ETH, SOL, POL, TRX, etc): @@ -258,7 +258,7 @@ class _EditTokenPageBodyState extends State { } }; - if (hasPotentialError) { + if (hasPotentialError && !isWhitelisted) { showPopUp( context: context, builder: (dialogContext) { diff --git a/lib/view_model/dashboard/home_settings_view_model.dart b/lib/view_model/dashboard/home_settings_view_model.dart index 381ae0614..7bba04b21 100644 --- a/lib/view_model/dashboard/home_settings_view_model.dart +++ b/lib/view_model/dashboard/home_settings_view_model.dart @@ -221,7 +221,8 @@ abstract class HomeSettingsViewModelBase with Store { } // check if the contractAddress is in the defaultTokenAddresses - bool isInWhitelist = defaultTokenAddresses.any((element) => element == contractAddress); + bool isInWhitelist = defaultTokenAddresses + .any((element) => element.toLowerCase() == contractAddress.toLowerCase()); return isInWhitelist; } From 37ede52ea6fc039062ff3359569a8448463b1e57 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Fri, 11 Apr 2025 16:23:32 +0200 Subject: [PATCH 026/142] fix: fix amount mapping for OCP (#2190) --- lib/core/open_crypto_pay/open_cryptopay_service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/open_crypto_pay/open_cryptopay_service.dart b/lib/core/open_crypto_pay/open_cryptopay_service.dart index 2591f8903..6449bbf90 100644 --- a/lib/core/open_crypto_pay/open_cryptopay_service.dart +++ b/lib/core/open_crypto_pay/open_cryptopay_service.dart @@ -152,7 +152,7 @@ class OpenCryptoPayService { String _getAmountByAsset(OpenCryptoPayRequest request, CryptoCurrency asset) { final method = _getMethod(asset); return request.methods[method]! - .firstWhere((e) => e.symbol == asset.title) + .firstWhere((e) => e.symbol.toUpperCase() == asset.title.toUpperCase()) .amount; } From 2f28ea3fb7883016b0fc598130824b58d58b14b5 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Fri, 11 Apr 2025 19:22:27 +0200 Subject: [PATCH 027/142] do nothing if the user wants to switch to haven [skip ci] --- lib/view_model/wallet_list/wallet_list_view_model.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/view_model/wallet_list/wallet_list_view_model.dart b/lib/view_model/wallet_list/wallet_list_view_model.dart index 8c36f8412..b27bf52f6 100644 --- a/lib/view_model/wallet_list/wallet_list_view_model.dart +++ b/lib/view_model/wallet_list/wallet_list_view_model.dart @@ -75,6 +75,9 @@ abstract class WalletListViewModelBase with Store { @action Future loadWallet(WalletListItem walletItem) async { + if (walletItem.type == WalletType.haven) { + return; + } // bool switchingToSameWalletType = walletItem.type == _appStore.wallet?.type; // await _appStore.wallet?.close(shouldCleanup: !switchingToSameWalletType); final wallet = await _walletLoadingService.load(walletItem.type, walletItem.name); From a7376c3225fb03ca7209840c177e8bc6d90ef88f Mon Sep 17 00:00:00 2001 From: Serhii Date: Fri, 11 Apr 2025 20:51:30 +0300 Subject: [PATCH 028/142] Address-formatting-enhancements-MWEB (#2189) * apply formatting to address book and MWEB * fix wallet type exception * Update cw_core/lib/wallet_type.dart [skip ci] * Update lib/src/screens/contact/contact_list_page.dart [skip ci] * Update lib/src/screens/contact/contact_list_page.dart [skip ci] * Update lib/utils/address_formatter.dart [skip ci] * Update lib/utils/address_formatter.dart [skip ci] * Update lib/utils/address_formatter.dart --------- Co-authored-by: Omar Hatem --- cw_core/lib/wallet_type.dart | 35 +++++ .../screens/contact/contact_list_page.dart | 83 +++++------ lib/src/widgets/alert_with_two_actions.dart | 4 + lib/src/widgets/base_alert_dialog.dart | 5 +- lib/utils/address_formatter.dart | 130 ++++++++++++------ 5 files changed, 173 insertions(+), 84 deletions(-) diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart index fed998ed0..40a2b31d5 100644 --- a/cw_core/lib/wallet_type.dart +++ b/cw_core/lib/wallet_type.dart @@ -247,3 +247,38 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal 'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency'); } } + +WalletType? cryptoCurrencyToWalletType(CryptoCurrency type) { + switch (type) { + case CryptoCurrency.xmr: + return WalletType.monero; + case CryptoCurrency.btc: + return WalletType.bitcoin; + case CryptoCurrency.ltc: + return WalletType.litecoin; + case CryptoCurrency.xhv: + return WalletType.haven; + case CryptoCurrency.eth: + return WalletType.ethereum; + case CryptoCurrency.bch: + return WalletType.bitcoinCash; + case CryptoCurrency.nano: + return WalletType.nano; + case CryptoCurrency.banano: + return WalletType.banano; + case CryptoCurrency.maticpoly: + return WalletType.polygon; + case CryptoCurrency.sol: + return WalletType.solana; + case CryptoCurrency.trx: + return WalletType.tron; + case CryptoCurrency.wow: + return WalletType.wownero; + case CryptoCurrency.zano: + return WalletType.zano; + case CryptoCurrency.dcr: + return WalletType.decred; + default: + return null; + } +} diff --git a/lib/src/screens/contact/contact_list_page.dart b/lib/src/screens/contact/contact_list_page.dart index ee1dcd112..4dfb2a1eb 100644 --- a/lib/src/screens/contact/contact_list_page.dart +++ b/lib/src/screens/contact/contact_list_page.dart @@ -12,9 +12,11 @@ import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/standard_list.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart'; +import 'package:cake_wallet/utils/address_formatter.dart'; import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -235,7 +237,7 @@ class _ContactPageBodyState extends State with SingleTickerProv return; } - final isCopied = await showNameAndAddressDialog(context, contact.name, contact.address); + final isCopied = await DialogService.showNameAndAddressDialog(context, contact); if (isCopied) { await Clipboard.setData(ClipboardData(text: contact.address)); @@ -280,21 +282,6 @@ class _ContactPageBodyState extends State with SingleTickerProv ? Image.asset(image, height: 24, width: 24) : const SizedBox(height: 24, width: 24); } - - Future showNameAndAddressDialog(BuildContext context, String name, String address) async { - return await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithTwoActions( - alertTitle: name, - alertContent: address, - rightButtonText: S.of(context).copy, - leftButtonText: S.of(context).cancel, - actionRightButton: () => Navigator.of(context).pop(true), - actionLeftButton: () => Navigator.of(context).pop(false)); - }) ?? - false; - } } class ContactListBody extends StatefulWidget { @@ -357,7 +344,7 @@ class _ContactListBodyState extends State { } final isCopied = - await showNameAndAddressDialog(context, contact.name, contact.address); + await DialogService.showNameAndAddressDialog(context, contact); if (isCopied) { await Clipboard.setData(ClipboardData(text: contact.address)); @@ -434,7 +421,7 @@ class _ContactListBodyState extends State { ), SlidableAction( onPressed: (_) async { - final isDelete = await showAlertDialog(context); + final isDelete = await DialogService.showAlertDialog(context); if (isDelete) { await widget.contactListViewModel.delete(contact); @@ -494,33 +481,49 @@ class _ContactListBodyState extends State { ); } - Future showAlertDialog(BuildContext context) async { +} + +class DialogService { + static Future showAlertDialog(BuildContext context) async { return await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithTwoActions( - alertTitle: S.of(context).address_remove_contact, - alertContent: S.of(context).address_remove_content, - rightButtonText: S.of(context).remove, - leftButtonText: S.of(context).cancel, - actionRightButton: () => Navigator.of(context).pop(true), - actionLeftButton: () => Navigator.of(context).pop(false)); - }) ?? + context: context, + builder: (BuildContext context) { + return AlertWithTwoActions( + alertTitle: S.of(context).address_remove_contact, + alertContent: S.of(context).address_remove_content, + rightButtonText: S.of(context).remove, + leftButtonText: S.of(context).cancel, + actionRightButton: () => Navigator.of(context).pop(true), + actionLeftButton: () => Navigator.of(context).pop(false)); + }) ?? false; } - Future showNameAndAddressDialog(BuildContext context, String name, String address) async { + static Future showNameAndAddressDialog(BuildContext context,ContactBase contact) async { return await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithTwoActions( - alertTitle: name, - alertContent: address, - rightButtonText: S.of(context).copy, - leftButtonText: S.of(context).cancel, - actionRightButton: () => Navigator.of(context).pop(true), - actionLeftButton: () => Navigator.of(context).pop(false)); - }) ?? + context: context, + builder: (BuildContext context) { + return AlertWithTwoActions( + alertTitle: contact.name, + alertContent: contact.address, + alertContentTextWidget: AddressFormatter.buildSegmentedAddress( + address: contact.address, + textAlign: TextAlign.center, + walletType: cryptoCurrencyToWalletType(contact.type), + evenTextStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + fontFamily: 'Lato', + color: Theme.of(context).extension()!.titleColor, + decoration: TextDecoration.none, + ), + ), + rightButtonText: S.of(context).copy, + leftButtonText: S.of(context).cancel, + actionRightButton: () => Navigator.of(context).pop(true), + actionLeftButton: () => Navigator.of(context).pop(false)); + }) ?? false; } } + diff --git a/lib/src/widgets/alert_with_two_actions.dart b/lib/src/widgets/alert_with_two_actions.dart index e3d4408a6..c2ab872af 100644 --- a/lib/src/widgets/alert_with_two_actions.dart +++ b/lib/src/widgets/alert_with_two_actions.dart @@ -6,6 +6,7 @@ class AlertWithTwoActions extends BaseAlertDialog { AlertWithTwoActions({ required this.alertTitle, required this.alertContent, + this.alertContentTextWidget, required this.leftButtonText, required this.rightButtonText, required this.actionLeftButton, @@ -21,6 +22,7 @@ class AlertWithTwoActions extends BaseAlertDialog { final String alertTitle; final String alertContent; + final Widget? alertContentTextWidget; final String leftButtonText; final String rightButtonText; final VoidCallback actionLeftButton; @@ -38,6 +40,8 @@ class AlertWithTwoActions extends BaseAlertDialog { @override String get contentText => alertContent; @override + Widget? get contentTextWidget => alertContentTextWidget; + @override String get leftActionButtonText => leftButtonText; @override String get rightActionButtonText => rightButtonText; diff --git a/lib/src/widgets/base_alert_dialog.dart b/lib/src/widgets/base_alert_dialog.dart index 02b8f85ab..2ea067a76 100644 --- a/lib/src/widgets/base_alert_dialog.dart +++ b/lib/src/widgets/base_alert_dialog.dart @@ -13,6 +13,8 @@ class BaseAlertDialog extends StatelessWidget { String get contentText => ''; + Widget? get contentTextWidget => null; + String get leftActionButtonText => ''; String get rightActionButtonText => ''; @@ -79,7 +81,8 @@ class BaseAlertDialog extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Text( + contentTextWidget ?? + Text( contentText, textAlign: TextAlign.center, style: TextStyle( diff --git a/lib/utils/address_formatter.dart b/lib/utils/address_formatter.dart index 0bcc713d0..f2083c772 100644 --- a/lib/utils/address_formatter.dart +++ b/lib/utils/address_formatter.dart @@ -5,24 +5,31 @@ import 'dart:math' as math; class AddressFormatter { static Widget buildSegmentedAddress({ required String address, - required WalletType walletType, + WalletType? walletType, required TextStyle evenTextStyle, TextStyle? oddTextStyle, TextAlign? textAlign, bool shouldTruncate = false, }) { + + final cleanAddress = address.replaceAll('bitcoincash:', ''); + final isMWEB = address.startsWith('ltcmweb'); + final chunkSize = walletType != null ? _getChunkSize(walletType) : 4; + if (shouldTruncate) { return _buildTruncatedAddress( - address: address, - walletType: walletType, + address: cleanAddress, + isMWEB: isMWEB, + chunkSize: chunkSize, evenTextStyle: evenTextStyle, oddTextStyle: oddTextStyle ?? evenTextStyle.copyWith(color: evenTextStyle.color!.withAlpha(150)), textAlign: textAlign, ); } else { return _buildFullSegmentedAddress( - address: address, - walletType: walletType, + address: cleanAddress, + isMWEB: isMWEB, + chunkSize: chunkSize, evenTextStyle: evenTextStyle, oddTextStyle: oddTextStyle ?? evenTextStyle.copyWith(color: evenTextStyle.color!.withAlpha(128)), textAlign: textAlign, @@ -32,19 +39,34 @@ class AddressFormatter { static Widget _buildFullSegmentedAddress({ required String address, - required WalletType walletType, + required bool isMWEB, + required int chunkSize, required TextStyle evenTextStyle, required TextStyle oddTextStyle, TextAlign? textAlign, }) { - final cleanAddress = address.replaceAll('bitcoincash:', ''); - final chunkSize = _getChunkSize(walletType); final chunks = []; - for (int i = 0; i < cleanAddress.length; i += chunkSize) { - final chunk = cleanAddress.substring(i, math.min(i + chunkSize, cleanAddress.length)); - chunks.add(chunk); + if (isMWEB) { + const mwebDisplayPrefix = 'ltcmweb'; + chunks.add(mwebDisplayPrefix); + final startIndex = mwebDisplayPrefix.length; + for (int i = startIndex; i < address.length; i += chunkSize) { + final chunk = address.substring( + i, + math.min(i + chunkSize, address.length), + ); + chunks.add(chunk); + } + } else { + for (int i = 0; i < address.length; i += chunkSize) { + final chunk = address.substring( + i, + math.min(i + chunkSize, address.length), + ); + chunks.add(chunk); + } } final spans = []; @@ -62,46 +84,68 @@ class AddressFormatter { static Widget _buildTruncatedAddress({ required String address, - required WalletType walletType, + required bool isMWEB, + required int chunkSize, required TextStyle evenTextStyle, required TextStyle oddTextStyle, TextAlign? textAlign, }) { - final cleanAddress = address.replaceAll('bitcoincash:', ''); + if (isMWEB) { + const fixedPrefix = 'ltcmweb'; + final secondChunkStart = fixedPrefix.length; + const chunkSize = 4; + final secondChunk = address.substring( + secondChunkStart, + math.min(secondChunkStart + chunkSize, address.length), + ); + final lastChunk = address.substring(address.length - chunkSize); - final int digitCount = (walletType == WalletType.monero || - walletType == WalletType.wownero || - walletType == WalletType.zano) - ? 6 - : 4; + final spans = [ + TextSpan(text: '$fixedPrefix ', style: evenTextStyle), + TextSpan(text: '$secondChunk ', style: oddTextStyle), + TextSpan(text: '... ', style: oddTextStyle), + TextSpan(text: lastChunk, style: evenTextStyle), + ]; - if (cleanAddress.length <= 2 * digitCount) { - return _buildFullSegmentedAddress( - address: cleanAddress, - walletType: walletType, - evenTextStyle: evenTextStyle, - oddTextStyle: oddTextStyle, - textAlign: textAlign, + return RichText( + text: TextSpan(children: spans), + textAlign: textAlign ?? TextAlign.start, + overflow: TextOverflow.visible, + ); + } else { + final int digitCount = chunkSize; + + if (address.length <= 2 * digitCount) { + return _buildFullSegmentedAddress( + address: address, + isMWEB: isMWEB, + chunkSize: chunkSize, + evenTextStyle: evenTextStyle, + oddTextStyle: oddTextStyle, + textAlign: textAlign, + ); + } + + final String firstPart = address.substring(0, digitCount); + final String secondPart = + address.substring(digitCount, digitCount * 2); + final String lastPart = + address.substring(address.length - digitCount); + + final spans = [ + TextSpan(text: '$firstPart ', style: evenTextStyle), + TextSpan(text: '$secondPart ', style: oddTextStyle), + TextSpan(text: '... ', style: oddTextStyle), + TextSpan(text: lastPart, style: evenTextStyle), + ]; + + return RichText( + text: TextSpan(children: spans), + textAlign: textAlign ?? TextAlign.start, + overflow: TextOverflow.visible, ); } - - final String firstPart = cleanAddress.substring(0, digitCount); - final String secondPart = cleanAddress.substring(digitCount, digitCount * 2); - final String lastPart = cleanAddress.substring(cleanAddress.length - digitCount); - - final spans = [ - TextSpan(text: '$firstPart ', style: evenTextStyle), - TextSpan(text: '$secondPart ', style: oddTextStyle), - TextSpan(text: '... ', style: oddTextStyle), - TextSpan(text: lastPart, style: evenTextStyle), - ]; - - return RichText( - text: TextSpan(children: spans), - textAlign: textAlign ?? TextAlign.start, - overflow: TextOverflow.visible, - ); } static int _getChunkSize(WalletType walletType) { @@ -114,4 +158,4 @@ class AddressFormatter { return 4; } } -} +} \ No newline at end of file From ce12f517f40ce8ffd21681e96b5ac3a5a222d11d Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Sat, 12 Apr 2025 03:23:41 +0200 Subject: [PATCH 029/142] v4.26.0 Release Candidate (#2191) * v4.26.0 Release Candidate * Fix Wownero Creation issue * Add KES fiat currency --- assets/text/Monerocom_Release_Notes.txt | 7 ++- assets/text/Release_Notes.txt | 8 +-- lib/entities/fiat_currency.dart | 54 +++++++++++++++++-- .../screens/new_wallet/new_wallet_page.dart | 14 +++-- lib/view_model/wallet_new_vm.dart | 2 +- scripts/android/app_env.sh | 8 +-- scripts/ios/app_env.sh | 8 +-- scripts/linux/app_env.sh | 4 +- scripts/macos/app_env.sh | 8 +-- scripts/windows/build_exe_installer.iss | 2 +- 10 files changed, 84 insertions(+), 31 deletions(-) diff --git a/assets/text/Monerocom_Release_Notes.txt b/assets/text/Monerocom_Release_Notes.txt index 1b2cc3a6d..0ac065a4f 100644 --- a/assets/text/Monerocom_Release_Notes.txt +++ b/assets/text/Monerocom_Release_Notes.txt @@ -1,3 +1,6 @@ -UI/UX enhancements -Performance improvements +Monero 12-word seed support (Wallet Groups support as well) +Integrate DFX's OpenCryptoPay +Exchange flow enhancements +Hardware Wallets flow enhancements +Minor UI enhancements Bug fixes \ No newline at end of file diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt index 00a65ff57..0ac065a4f 100644 --- a/assets/text/Release_Notes.txt +++ b/assets/text/Release_Notes.txt @@ -1,4 +1,6 @@ -New App Logo -UI/UX enhancements -Performance improvements +Monero 12-word seed support (Wallet Groups support as well) +Integrate DFX's OpenCryptoPay +Exchange flow enhancements +Hardware Wallets flow enhancements +Minor UI enhancements Bug fixes \ No newline at end of file diff --git a/lib/entities/fiat_currency.dart b/lib/entities/fiat_currency.dart index cf1112096..402c928b9 100644 --- a/lib/entities/fiat_currency.dart +++ b/lib/entities/fiat_currency.dart @@ -9,8 +9,50 @@ class FiatCurrency extends EnumerableItem with Serializable impl static List get all => _all.values.toList(); - static List get currenciesAvailableToBuyWith => - [amd, aud, bgn, brl, cad, chf, clp, cop, czk, dkk, egp, eur, gbp, gtq, hkd, hrk, huf, idr, ils, inr, isk, jpy, krw, mad, mxn, myr, ngn, nok, nzd, php, pkr, pln, ron, sek, sgd, thb, twd, usd, vnd, zar, tur,]; + static List get currenciesAvailableToBuyWith => [ + amd, + aud, + bgn, + brl, + cad, + chf, + clp, + cop, + czk, + dkk, + egp, + eur, + gbp, + gtq, + hkd, + hrk, + huf, + idr, + ils, + inr, + isk, + jpy, + krw, + mad, + mxn, + myr, + ngn, + nok, + nzd, + php, + pkr, + pln, + ron, + sek, + sgd, + thb, + twd, + usd, + vnd, + zar, + tur, + kes, + ]; static const amd = FiatCurrency(symbol: 'AMD', countryCode: "arm", fullName: "Armenian Dram"); static const ars = FiatCurrency(symbol: 'ARS', countryCode: "arg", fullName: "Argentine Peso"); @@ -62,6 +104,7 @@ class FiatCurrency extends EnumerableItem with Serializable impl static const vnd = FiatCurrency(symbol: 'VND', countryCode: "vnm", fullName: "Vietnamese Dong đồng"); static const zar = FiatCurrency(symbol: 'ZAR', countryCode: "saf", fullName: "South African Rand"); static const tur = FiatCurrency(symbol: 'TRY', countryCode: "tur", fullName: "Turkish Lira"); + static const kes = FiatCurrency(symbol: 'KES', countryCode: "ken", fullName: "Kenyan Shillings"); static final _all = { FiatCurrency.amd.raw: FiatCurrency.amd, @@ -114,6 +157,7 @@ class FiatCurrency extends EnumerableItem with Serializable impl FiatCurrency.vnd.raw: FiatCurrency.vnd, FiatCurrency.zar.raw: FiatCurrency.zar, FiatCurrency.tur.raw: FiatCurrency.tur, + FiatCurrency.kes.raw: FiatCurrency.kes, }; static FiatCurrency deserialize({required String raw}) => _all[raw] ?? FiatCurrency.usd; @@ -123,13 +167,13 @@ class FiatCurrency extends EnumerableItem with Serializable impl @override int get hashCode => raw.hashCode ^ title.hashCode; - + @override String get name => raw; - + @override String? get tag => null; @override - String get iconPath => "assets/images/flags/$countryCode.png"; + String get iconPath => "assets/images/flags/$countryCode.png"; } diff --git a/lib/src/screens/new_wallet/new_wallet_page.dart b/lib/src/screens/new_wallet/new_wallet_page.dart index 83c14cd56..044c52349 100644 --- a/lib/src/screens/new_wallet/new_wallet_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_page.dart @@ -21,6 +21,7 @@ import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/seed_settings_view_model.dart'; import 'package:cake_wallet/view_model/wallet_new_vm.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; @@ -102,8 +103,8 @@ class _WalletNameFormState extends State { _stateReaction ??= reaction((_) => _walletNewVM.state, (ExecutionState state) async { if (state is ExecutedSuccessfullyState) { if (widget.isChildWallet) { - Navigator.of(navigatorKey.currentContext ?? context) - .pushNamed(Routes.walletGroupExistingSeedDescriptionPage, + Navigator.of(navigatorKey.currentContext ?? context).pushNamed( + Routes.walletGroupExistingSeedDescriptionPage, arguments: _walletNewVM.seedPhraseWordsLength); } else { Navigator.of(navigatorKey.currentContext ?? context) @@ -317,7 +318,11 @@ class _WalletNameFormState extends State { await showPopUp( context: context, builder: (_) => Picker( - items: MoneroSeedType.all, + items: MoneroSeedType.all + .where((e) => // exclude bip39 in case of Wownero + widget._walletNewVM.type != WalletType.wownero || + e.raw != MoneroSeedType.bip39.raw) + .toList(), selectedAtIndex: isPolyseed ? 1 : 0, onItemSelected: _setSeedType, isSeparated: false, @@ -402,8 +407,7 @@ class _WalletNameFormState extends State { await _walletNewVM.create( options: _walletNewVM.hasLanguageSelector ? [ - _languageSelectorKey.currentState?.selected ?? - defaultSeedLanguage, + _languageSelectorKey.currentState?.selected ?? defaultSeedLanguage, widget._seedSettingsViewModel.moneroSeedType ] : null); diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index 7b4b7e788..1987a37d2 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -161,7 +161,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { return wownero!.createWowneroNewWalletCredentials( name: name, language: options!.first as String, - isPolyseed: options.last as bool, + isPolyseed: (options.last as MoneroSeedType).raw == 1, password: walletPassword, passphrase: passphrase, ); diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index 321db113d..435d74b72 100644 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -14,15 +14,15 @@ TYPES=($MONERO_COM $CAKEWALLET) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="4.25.0" -MONERO_COM_BUILD_NUMBER=118 +MONERO_COM_VERSION="4.26.0" +MONERO_COM_BUILD_NUMBER=119 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" MONERO_COM_SCHEME="monero.com" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.25.0" -CAKEWALLET_BUILD_NUMBER=256 +CAKEWALLET_VERSION="4.26.0" +CAKEWALLET_BUILD_NUMBER=257 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" CAKEWALLET_SCHEME="cakewallet" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index 5c0003612..bb6cf2753 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -12,13 +12,13 @@ TYPES=($MONERO_COM $CAKEWALLET) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="4.25.0" -MONERO_COM_BUILD_NUMBER=115 +MONERO_COM_VERSION="4.26.0" +MONERO_COM_BUILD_NUMBER=116 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.25.0" -CAKEWALLET_BUILD_NUMBER=310 +CAKEWALLET_VERSION="4.26.0" +CAKEWALLET_BUILD_NUMBER=311 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" diff --git a/scripts/linux/app_env.sh b/scripts/linux/app_env.sh index 0ef31d6bb..574cce2c9 100755 --- a/scripts/linux/app_env.sh +++ b/scripts/linux/app_env.sh @@ -14,8 +14,8 @@ if [ -n "$1" ]; then fi CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.25.0" -CAKEWALLET_BUILD_NUMBER=51 +CAKEWALLET_VERSION="4.26.0" +CAKEWALLET_BUILD_NUMBER=52 if ! [[ " ${TYPES[*]} " =~ " ${APP_LINUX_TYPE} " ]]; then echo "Wrong app type." diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh index fa45b76d4..9a3b95f89 100755 --- a/scripts/macos/app_env.sh +++ b/scripts/macos/app_env.sh @@ -16,13 +16,13 @@ if [ -n "$1" ]; then fi MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="4.25.0" -MONERO_COM_BUILD_NUMBER=47 +MONERO_COM_VERSION="4.26.0" +MONERO_COM_BUILD_NUMBER=48 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.25.0" -CAKEWALLET_BUILD_NUMBER=109 +CAKEWALLET_VERSION="4.26.0" +CAKEWALLET_BUILD_NUMBER=110 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then diff --git a/scripts/windows/build_exe_installer.iss b/scripts/windows/build_exe_installer.iss index 0d757b8e5..1f152f635 100644 --- a/scripts/windows/build_exe_installer.iss +++ b/scripts/windows/build_exe_installer.iss @@ -1,5 +1,5 @@ #define MyAppName "Cake Wallet" -#define MyAppVersion "4.25.0" +#define MyAppVersion "4.26.0" #define MyAppPublisher "Cake Labs LLC" #define MyAppURL "https://cakewallet.com/" #define MyAppExeName "CakeWallet.exe" From 990feb48ecdd46af4fedaf086f392af2279ecdfb Mon Sep 17 00:00:00 2001 From: Serhii Date: Mon, 14 Apr 2025 15:30:41 +0300 Subject: [PATCH 030/142] fix-talkback-slide-button-accessibility (#2200) * fix:talkback send slide button accessibility * fix accessible button color --- .../base_bottom_sheet_widget.dart | 2 +- .../confirm_sending_bottom_sheet_widget.dart | 133 ++++++++++-------- .../widgets/standard_slide_button_widget.dart | 132 +++++++++-------- 3 files changed, 144 insertions(+), 123 deletions(-) diff --git a/lib/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart b/lib/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart index 206780eae..923062c2e 100644 --- a/lib/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart +++ b/lib/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart @@ -33,7 +33,7 @@ abstract class BaseBottomSheet extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ if (titleIconPath != null) - Image.asset(titleIconPath!, height: 24, width: 24) + Image.asset(titleIconPath!, height: 24, width: 24, excludeFromSemantics: true) else Container(), const SizedBox(width: 6), diff --git a/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart b/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart index f78717fb3..764429bf1 100644 --- a/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart +++ b/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart @@ -223,6 +223,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet { onSlideComplete: onSlideComplete, buttonText: 'Swipe to send', currentTheme: currentTheme, + accessibleNavigationModeButtonText: S.of(context).send, ), ); } @@ -248,24 +249,28 @@ class StandardTile extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), - decoration: - BoxDecoration(borderRadius: BorderRadius.circular(10), color: tileBackgroundColor), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(itemTitle, style: itemTitleTextStyle), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text(itemValue, style: itemTitleTextStyle), - itemSubTitle == null - ? Container() - : Text(itemSubTitle!, style: itemSubTitleTextStyle), - ], - ), - ], + return Semantics( + container: true, + label: itemTitle, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), + decoration: + BoxDecoration(borderRadius: BorderRadius.circular(10), color: tileBackgroundColor), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(itemTitle, style: itemTitleTextStyle), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text(itemValue, style: itemTitleTextStyle), + itemSubTitle == null + ? Container() + : Text(itemSubTitle!, style: itemSubTitleTextStyle), + ], + ), + ], + ), ), ); } @@ -357,54 +362,58 @@ class AddressExpansionTile extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(10)), - color: tileBackgroundColor, - ), - child: Theme( - data: Theme.of(context).copyWith(dividerColor: Colors.transparent), - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 14, vertical: isBatchSending ? 0 : 8), - child: ExpansionTile( - childrenPadding: isBatchSending ? const EdgeInsets.only(bottom: 8) : EdgeInsets.zero, - tilePadding: EdgeInsets.zero, - dense: true, - visualDensity: VisualDensity.compact, - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text(isBatchSending ? name : contactType, - style: itemTitleTextStyle, softWrap: true)), - Text(isBatchSending ? amount : name, - style: TextStyle( - fontSize: 14, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: Theme.of(context).extension()!.titleColor, - decoration: TextDecoration.none, - )), - ], - ), - children: [ - Row( + return Semantics( + container: true, + label: name, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(10)), + color: tileBackgroundColor, + ), + child: Theme( + data: Theme.of(context).copyWith(dividerColor: Colors.transparent), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 14, vertical: isBatchSending ? 0 : 8), + child: ExpansionTile( + childrenPadding: isBatchSending ? const EdgeInsets.only(bottom: 8) : EdgeInsets.zero, + tilePadding: EdgeInsets.zero, + dense: true, + visualDensity: VisualDensity.compact, + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( - child: AddressFormatter.buildSegmentedAddress( - address: address, - walletType: walletType, - evenTextStyle: TextStyle( - fontSize: 12, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: Theme.of(context).extension()!.titleColor, - decoration: TextDecoration.none) - ), - ), + child: Text(isBatchSending ? name : contactType, + style: itemTitleTextStyle, softWrap: true)), + Text(isBatchSending ? amount : name, + style: TextStyle( + fontSize: 14, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.titleColor, + decoration: TextDecoration.none, + )), ], ), - ], + children: [ + Row( + children: [ + Expanded( + child: AddressFormatter.buildSegmentedAddress( + address: address, + walletType: walletType, + evenTextStyle: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.titleColor, + decoration: TextDecoration.none) + ), + ), + ], + ), + ], + ), ), ), ), diff --git a/lib/src/widgets/standard_slide_button_widget.dart b/lib/src/widgets/standard_slide_button_widget.dart index 57271b6b5..299f6bdf7 100644 --- a/lib/src/widgets/standard_slide_button_widget.dart +++ b/lib/src/widgets/standard_slide_button_widget.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/themes/extensions/filter_theme.dart'; import 'package:cake_wallet/themes/extensions/menu_theme.dart'; @@ -12,12 +13,14 @@ class StandardSlideButton extends StatefulWidget { this.buttonText = '', this.height = 48.0, required this.currentTheme, + required this.accessibleNavigationModeButtonText, }) : super(key: key); final VoidCallback onSlideComplete; final String buttonText; final double height; final ThemeBase currentTheme; + final String accessibleNavigationModeButtonText; @override _StandardSlideButtonState createState() => _StandardSlideButtonState(); @@ -28,68 +31,77 @@ class _StandardSlideButtonState extends State { @override Widget build(BuildContext context) { - return LayoutBuilder(builder: (context, constraints) { - final double maxWidth = constraints.maxWidth; - const double sideMargin = 4.0; - final double effectiveMaxWidth = maxWidth - 2 * sideMargin; - const double sliderWidth = 42.0; + final bool accessible = MediaQuery.of(context).accessibleNavigation; - final tileBackgroundColor = widget.currentTheme.type == ThemeType.light - ? Theme.of(context).extension()!.syncedBackgroundColor - : widget.currentTheme.type == ThemeType.oled - ? Colors.black.withOpacity(0.5) - : Theme.of(context).extension()!.buttonColor; + final tileBackgroundColor = widget.currentTheme.type == ThemeType.light + ? Theme.of(context).extension()!.syncedBackgroundColor + : widget.currentTheme.type == ThemeType.oled + ? Colors.black.withOpacity(0.5) + : Theme.of(context).extension()!.buttonColor; - return Container( - height: widget.height, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: tileBackgroundColor), - child: Stack( - alignment: Alignment.centerLeft, - children: [ - Center( - child: Text(widget.buttonText, - style: TextStyle( - fontSize: 16, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: Theme.of(context).extension()!.titleColor))), - Positioned( - left: sideMargin + _dragPosition, - child: GestureDetector( - onHorizontalDragUpdate: (details) { - setState(() { - _dragPosition += details.delta.dx; - if (_dragPosition < 0) _dragPosition = 0; - if (_dragPosition > effectiveMaxWidth - sliderWidth) { - _dragPosition = effectiveMaxWidth - sliderWidth; - } - }); - }, - onHorizontalDragEnd: (details) { - if (_dragPosition >= effectiveMaxWidth - sliderWidth - 10) { - widget.onSlideComplete(); - } else { - setState(() => _dragPosition = 0); - } - }, - child: Container( - width: sliderWidth, - height: widget.height - 8, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: Theme.of(context).extension()!.titleColor, - ), - alignment: Alignment.center, - child: Icon(Icons.arrow_forward, - color: widget.currentTheme.type == ThemeType.bright ? Theme.of(context).extension()!.backgroundColor : Theme.of(context).extension()!.buttonColor), - ), + return accessible + ? PrimaryButton( + text: widget.accessibleNavigationModeButtonText, + color: Theme.of(context).primaryColor, + textColor: Colors.white, + onPressed: () => widget.onSlideComplete()) + : LayoutBuilder(builder: (context, constraints) { + final double maxWidth = constraints.maxWidth; + const double sideMargin = 4.0; + final double effectiveMaxWidth = maxWidth - 2 * sideMargin; + const double sliderWidth = 42.0; + + return Container( + height: widget.height, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), color: tileBackgroundColor), + child: Stack( + alignment: Alignment.centerLeft, + children: [ + Center( + child: Text(widget.buttonText, + style: TextStyle( + fontSize: 16, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.titleColor))), + Positioned( + left: sideMargin + _dragPosition, + child: GestureDetector( + onHorizontalDragUpdate: (details) { + setState(() { + _dragPosition += details.delta.dx; + if (_dragPosition < 0) _dragPosition = 0; + if (_dragPosition > effectiveMaxWidth - sliderWidth) { + _dragPosition = effectiveMaxWidth - sliderWidth; + } + }); + }, + onHorizontalDragEnd: (details) { + if (_dragPosition >= effectiveMaxWidth - sliderWidth - 10) { + widget.onSlideComplete(); + } else { + setState(() => _dragPosition = 0); + } + }, + child: Container( + width: sliderWidth, + height: widget.height - 8, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Theme.of(context).extension()!.titleColor, + ), + alignment: Alignment.center, + child: Icon(Icons.arrow_forward, + color: widget.currentTheme.type == ThemeType.bright + ? Theme.of(context).extension()!.backgroundColor + : Theme.of(context).extension()!.buttonColor), + ), + ), + ) + ], ), - ) - ], - ), - ); - }); + ); + }); } } From 5f4dc95ca521261b830ecb9c263c13535df51609 Mon Sep 17 00:00:00 2001 From: cyan Date: Mon, 14 Apr 2025 18:46:45 +0200 Subject: [PATCH 031/142] fix: do not overwrite monero backup files if they exist. (#2202) --- cw_core/lib/monero_wallet_utils.dart | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/cw_core/lib/monero_wallet_utils.dart b/cw_core/lib/monero_wallet_utils.dart index 8a4990f78..9682784f9 100644 --- a/cw_core/lib/monero_wallet_utils.dart +++ b/cw_core/lib/monero_wallet_utils.dart @@ -19,15 +19,15 @@ Future backupWalletFiles(String name) async { final newKeysFilePath = backupFileName(keysFile.path); final newAddressListFilePath = backupFileName(addressListFile.path); - if (cacheFile.existsSync()) { + if (cacheFile.existsSync() && !File(newCacheFilePath).existsSync()) { await cacheFile.copy(newCacheFilePath); } - if (keysFile.existsSync()) { + if (keysFile.existsSync() && !File(newKeysFilePath).existsSync()) { await keysFile.copy(newKeysFilePath); } - if (addressListFile.existsSync()) { + if (addressListFile.existsSync() && !File(newAddressListFilePath).existsSync()) { await addressListFile.copy(newAddressListFilePath); } } @@ -83,10 +83,13 @@ Future backupWalletFilesExists(String name) async { Future removeCache(String name) async { final path = await pathForWallet(name: name, type: WalletType.monero); final cacheFile = File(path); - + final backgroundCacheFile = File(path + ".background"); if (cacheFile.existsSync()) { cacheFile.deleteSync(); } + if (backgroundCacheFile.existsSync()) { + backgroundCacheFile.deleteSync(); + } } Future restoreOrResetWalletFiles(String name) async { @@ -94,7 +97,8 @@ Future restoreOrResetWalletFiles(String name) async { if (backupsExists) { await removeCache(name); - + // TODO(mrcyjanek): is this needed? + // If we remove cache then wallet should be restored from .keys file. await restoreWalletFiles(name); } } From 87207c61baa22f803cf4e072b16b1b5dc0eb0cbb Mon Sep 17 00:00:00 2001 From: cyan Date: Mon, 14 Apr 2025 18:47:31 +0200 Subject: [PATCH 032/142] fix: wallet grouping eating wallets (#2203) --- lib/entities/wallet_manager.dart | 8 +++- .../wallet_list/wallet_list_view_model.dart | 41 +------------------ 2 files changed, 9 insertions(+), 40 deletions(-) diff --git a/lib/entities/wallet_manager.dart b/lib/entities/wallet_manager.dart index 8bcabcaf2..09b764289 100644 --- a/lib/entities/wallet_manager.dart +++ b/lib/entities/wallet_manager.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:cake_wallet/entities/hash_wallet_identifier.dart'; import 'package:cake_wallet/entities/wallet_group.dart'; import 'package:cw_core/wallet_base.dart'; @@ -33,7 +35,11 @@ class WalletManager { } // Fallback to old logic - return walletInfo.parentAddress ?? walletInfo.address; + final address = walletInfo.parentAddress ?? walletInfo.address; + if (address.isEmpty) { + return Random().nextInt(100000).toString(); + } + return address; } WalletGroup _getOrCreateGroup(String groupKey) { diff --git a/lib/view_model/wallet_list/wallet_list_view_model.dart b/lib/view_model/wallet_list/wallet_list_view_model.dart index b27bf52f6..bd135ef96 100644 --- a/lib/view_model/wallet_list/wallet_list_view_model.dart +++ b/lib/view_model/wallet_list/wallet_list_view_model.dart @@ -3,6 +3,7 @@ import 'package:cake_wallet/entities/wallet_group.dart'; import 'package:cake_wallet/entities/wallet_list_order_types.dart'; import 'package:cake_wallet/entities/wallet_manager.dart'; import 'package:cake_wallet/reactions/bip39_wallet_utils.dart'; +import 'package:cw_core/utils/print_verbose.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/store/app_store.dart'; @@ -109,45 +110,7 @@ abstract class WalletListViewModelBase with Store { continue; } - // Identify wallets that should be moved to singleWalletsList using the filters: the type/derivation - final excludedWallets = []; - - for (var wallet in group.wallets) { - // Check for non-BIP39 wallet types - final isNonBIP39 = !isBIP39Wallet(wallet.type); - - // Check for nano derivation type - final isNanoDerivation = wallet.type == WalletType.nano && - wallet.derivationInfo?.derivationType == DerivationType.nano; - - // Check for electrum derivation type - final isElectrumDerivation = - (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin) && - wallet.derivationInfo?.derivationType == DerivationType.electrum; - - if (isNonBIP39 || isNanoDerivation || isElectrumDerivation) { - excludedWallets.add(wallet); - } - } - - // Add excluded wallets to singleWalletsList - for (var excludedWallet in excludedWallets) { - singleWalletsList.add(convertWalletInfoToWalletListItem(excludedWallet)); - } - - // Remove excluded wallets from the group's wallets to avoid duplication - group.wallets.removeWhere((wallet) { - return excludedWallets.any((excluded) => excluded.address == wallet.address); - }); - - // Check if the group has more than one wallet after the excluded wallets are removed. - if (group.wallets.length > 1) { - //Add the entire group to the multi wallet group list since its still a multi wallet - multiWalletGroups.add(group); - } else if (group.wallets.length == 1) { - // Add the group to the wallet left to the single wallets list - singleWalletsList.add(convertWalletInfoToWalletListItem(group.wallets.first)); - } + multiWalletGroups.add(group); } } From 469373b780a2cee1857b0951ce30312e6b21f1ad Mon Sep 17 00:00:00 2001 From: David Adegoke <64401859+Blazebrain@users.noreply.github.com> Date: Mon, 14 Apr 2025 18:27:49 +0100 Subject: [PATCH 033/142] fix: Error while restoring Solana Wallet with PrivateKey (#2201) --- cw_solana/lib/solana_wallet.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cw_solana/lib/solana_wallet.dart b/cw_solana/lib/solana_wallet.dart index 1495189db..9bb468396 100644 --- a/cw_solana/lib/solana_wallet.dart +++ b/cw_solana/lib/solana_wallet.dart @@ -167,10 +167,10 @@ abstract class SolanaWalletBase try { final keypairBytes = Base58Decoder.decode(privateKey!); - return SolanaPrivateKey.fromSeed(keypairBytes); + return SolanaPrivateKey.fromBytes(keypairBytes); } catch (_) { final privateKeyBytes = HEX.decode(privateKey!); - return SolanaPrivateKey.fromBytes(privateKeyBytes); + return SolanaPrivateKey.fromSeed(privateKeyBytes); } } From f574fa5e9bdbab1e2cae8cdc622d60501c3cf5f3 Mon Sep 17 00:00:00 2001 From: David Adegoke <64401859+Blazebrain@users.noreply.github.com> Date: Tue, 15 Apr 2025 01:37:46 +0100 Subject: [PATCH 034/142] CW-1035: Solana New Fixes (#2205) * fix: Error while restoring Solana Wallet with PrivateKey * fix: Enhance Solana Error Messages, especially for ATA Creation Errors * - Optimize Solana Transaction History, now more smoother and faster - fix bug with transactions history not being displayed in real time until next reload --- cw_solana/lib/solana_client.dart | 63 ++++++++++++------- cw_solana/lib/solana_wallet.dart | 8 ++- .../dashboard/dashboard_view_model.dart | 52 +++++++-------- lib/view_model/send/send_view_model.dart | 2 +- res/values/strings_ar.arb | 3 +- res/values/strings_bg.arb | 3 +- res/values/strings_cs.arb | 3 +- res/values/strings_de.arb | 3 +- res/values/strings_en.arb | 3 +- res/values/strings_es.arb | 3 +- res/values/strings_fr.arb | 3 +- res/values/strings_ha.arb | 3 +- res/values/strings_hi.arb | 3 +- res/values/strings_hr.arb | 3 +- res/values/strings_hy.arb | 3 +- res/values/strings_id.arb | 3 +- res/values/strings_it.arb | 3 +- res/values/strings_ja.arb | 3 +- res/values/strings_ko.arb | 3 +- res/values/strings_my.arb | 3 +- res/values/strings_nl.arb | 3 +- res/values/strings_pl.arb | 3 +- res/values/strings_pt.arb | 3 +- res/values/strings_ru.arb | 3 +- res/values/strings_th.arb | 3 +- res/values/strings_tl.arb | 3 +- res/values/strings_tr.arb | 3 +- res/values/strings_uk.arb | 3 +- res/values/strings_ur.arb | 3 +- res/values/strings_vi.arb | 3 +- res/values/strings_yo.arb | 3 +- res/values/strings_zh.arb | 3 +- 32 files changed, 127 insertions(+), 82 deletions(-) diff --git a/cw_solana/lib/solana_client.dart b/cw_solana/lib/solana_client.dart index 0e08f9abb..858fb25d4 100644 --- a/cw_solana/lib/solana_client.dart +++ b/cw_solana/lib/solana_client.dart @@ -254,8 +254,9 @@ class SolanaWalletClient { final receiver = message.accountKeys[instruction.accounts[1]].address; String? tokenSymbol = splTokenSymbol; + if (tokenSymbol == null && mintAddress != null) { - final token = await fetchSPLTokenInfo(mintAddress); + final token = await getTokenInfo(mintAddress); tokenSymbol = token?.symbol; } @@ -288,9 +289,9 @@ class SolanaWalletClient { int? splTokenDecimal, Commitment? commitment, SolAddress? walletAddress, + required void Function(List) onUpdate, }) async { List transactions = []; - try { final signatures = await _provider!.request( SolanaRPCGetSignaturesForAddress( @@ -299,10 +300,11 @@ class SolanaWalletClient { ), ); - final List transactionDetails = []; + // The maximum concurrent batch size. + const int batchSize = 10; - for (int i = 0; i < signatures.length; i += 20) { - final batch = signatures.skip(i).take(20).toList(); // Get the next 20 signatures + for (int i = 0; i < signatures.length; i += batchSize) { + final batch = signatures.skip(i).take(batchSize).toList(); final batchResponses = await Future.wait(batch.map((signature) async { try { @@ -314,30 +316,31 @@ class SolanaWalletClient { ), ); } catch (e) { - printV("Error fetching transaction: $e"); + // printV("Error fetching transaction: $e"); return null; } })); - transactionDetails.addAll(batchResponses.whereType()); + final versionedBatchResponses = batchResponses.whereType(); - // to avoid reaching the node RPS limit - if (i + 20 < signatures.length) { + final parsedTransactionsFutures = versionedBatchResponses.map((tx) => parseTransaction( + txResponse: tx, + splTokenSymbol: splTokenSymbol, + walletAddress: walletAddress?.address ?? address.address, + )); + + final parsedTransactions = await Future.wait(parsedTransactionsFutures); + + transactions.addAll(parsedTransactions.whereType().toList()); + + // Calling the callback after each batch is processed, therefore passing the current list of transactions. + onUpdate(List.from(transactions)); + + if (i + batchSize < signatures.length) { await Future.delayed(const Duration(milliseconds: 500)); } } - for (final tx in transactionDetails) { - final parsedTx = await parseTransaction( - txResponse: tx, - splTokenSymbol: splTokenSymbol, - walletAddress: walletAddress?.address ?? address.address, - ); - if (parsedTx != null) { - transactions.add(parsedTx); - } - } - return transactions; } catch (err, s) { printV('Error fetching transactions: $err \n$s'); @@ -350,6 +353,7 @@ class SolanaWalletClient { required String splTokenSymbol, required int splTokenDecimal, required SolanaPrivateKey privateKey, + required void Function(List) onUpdate, }) async { ProgramDerivedAddress? associatedTokenAccount; final ownerWalletAddress = privateKey.publicKey().toAddress(); @@ -373,11 +377,26 @@ class SolanaWalletClient { splTokenSymbol: splTokenSymbol, splTokenDecimal: splTokenDecimal, walletAddress: ownerWalletAddress, + onUpdate: onUpdate, ); return tokenTransactions; } + final Map tokenInfoCache = {}; + + Future getTokenInfo(String mintAddress) async { + if (tokenInfoCache.containsKey(mintAddress)) { + return tokenInfoCache[mintAddress]; + } else { + final token = await fetchSPLTokenInfo(mintAddress); + if (token != null) { + tokenInfoCache[mintAddress] = token; + } + return token; + } + } + Future fetchSPLTokenInfo(String mintAddress) async { final programAddress = MetaplexTokenMetaDataProgramUtils.findMetadataPda(mint: SolAddress(mintAddress)); @@ -747,9 +766,7 @@ class SolanaWalletClient { } catch (e) { associatedRecipientAccount = null; - throw SolanaCreateAssociatedTokenAccountException( - 'Error fetching recipient associated token account: ${e.toString()}', - ); + throw SolanaCreateAssociatedTokenAccountException(e.toString()); } if (associatedRecipientAccount == null) { diff --git a/cw_solana/lib/solana_wallet.dart b/cw_solana/lib/solana_wallet.dart index 9bb468396..ec65642bc 100644 --- a/cw_solana/lib/solana_wallet.dart +++ b/cw_solana/lib/solana_wallet.dart @@ -292,9 +292,14 @@ abstract class SolanaWalletBase @override Future> fetchTransactions() async => {}; + void updateTransactions(List updatedTx) { + _addTransactionsToTransactionHistory(updatedTx); + } + /// Fetches the native SOL transactions linked to the wallet Public Key Future _updateNativeSOLTransactions() async { - final transactions = await _client.fetchTransactions(_solanaPublicKey.toAddress()); + final transactions = + await _client.fetchTransactions(_solanaPublicKey.toAddress(), onUpdate: updateTransactions); await _addTransactionsToTransactionHistory(transactions); } @@ -313,6 +318,7 @@ abstract class SolanaWalletBase splTokenSymbol: token.symbol, splTokenDecimal: token.decimal, privateKey: _solanaPrivateKey, + onUpdate: updateTransactions, ); // splTokenTransactions.addAll(tokenTxs); diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index a2a5bdc0d..f5e89a023 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -161,8 +161,8 @@ abstract class DashboardViewModelBase with Store { FilterItem( value: () => tradeFilterStore.displaySwapTrade, caption: ExchangeProviderDescription.swapTrade.title, - onChanged: () => tradeFilterStore - .toggleDisplayExchange(ExchangeProviderDescription.swapTrade)), + onChanged: () => + tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.swapTrade)), ] }, subname = '', @@ -272,9 +272,8 @@ abstract class DashboardViewModelBase with Store { _transactionDisposer?.reaction.dispose(); _transactionDisposer = reaction( - (_) => appStore.wallet!.transactionHistory.transactions.length * - appStore.wallet!.transactionHistory.transactions.values.first.confirmations, - _transactionDisposerCallback + (_) => appStore.wallet!.transactionHistory.transactions.length, + _transactionDisposerCallback, ); if (hasSilentPayments) { @@ -289,24 +288,22 @@ abstract class DashboardViewModelBase with Store { reaction((_) => settingsStore.mwebAlwaysScan, (bool value) => _checkMweb()); } - bool _isTransactionDisposerCallbackRunning = false; - + void _transactionDisposerCallback(int _) async { // Simple check to prevent the callback from being called multiple times in the same frame if (_isTransactionDisposerCallbackRunning) return; _isTransactionDisposerCallbackRunning = true; await Future.delayed(Duration.zero); - try { - final currentAccountId = wallet.type == WalletType.monero + final currentAccountId = wallet.type == WalletType.monero ? monero!.getCurrentAccount(wallet).id - : wallet.type == WalletType.wownero - ? wow.wownero!.getCurrentAccount(wallet).id + : wallet.type == WalletType.wownero + ? wow.wownero!.getCurrentAccount(wallet).id : null; final List relevantTxs = []; - + for (final tx in appStore.wallet!.transactionHistory.transactions.values) { bool isRelevant = true; if (wallet.type == WalletType.monero) { @@ -314,7 +311,7 @@ abstract class DashboardViewModelBase with Store { } else if (wallet.type == WalletType.wownero) { isRelevant = wow.wownero!.getTransactionInfoAccountId(tx) == currentAccountId; } - + if (isRelevant) { relevantTxs.add(tx); } @@ -323,11 +320,11 @@ abstract class DashboardViewModelBase with Store { transactions.clear(); transactions.addAll(relevantTxs.map((tx) => TransactionListItem( - transaction: tx, - balanceViewModel: balanceViewModel, - settingsStore: appStore.settingsStore, - key: ValueKey('${wallet.type.name}_transaction_history_item_${tx.id}_key'), - ))); + transaction: tx, + balanceViewModel: balanceViewModel, + settingsStore: appStore.settingsStore, + key: ValueKey('${wallet.type.name}_transaction_history_item_${tx.id}_key'), + ))); } finally { _isTransactionDisposerCallbackRunning = false; } @@ -475,7 +472,8 @@ abstract class DashboardViewModelBase with Store { // to not cause work duplication, this will do the job as well, it will be slightly less precise // about what happened - but still enough. // if (keys['privateSpendKey'] == List.generate(64, (index) => "0").join("")) "Private spend key is 0", - if (keys['privateViewKey'] == List.generate(64, (index) => "0").join("") && !wallet.isHardwareWallet) + if (keys['privateViewKey'] == List.generate(64, (index) => "0").join("") && + !wallet.isHardwareWallet) "private view key is 0", // if (keys['publicSpendKey'] == List.generate(64, (index) => "0").join("")) "public spend key is 0", if (keys['publicViewKey'] == List.generate(64, (index) => "0").join("")) @@ -576,7 +574,8 @@ abstract class DashboardViewModelBase with Store { disableBackgroundSync(); return; } - final resp = await FlutterDaemon().startBackgroundSync(settingsStore.currentSyncMode.frequency.inMinutes); + final resp = await FlutterDaemon() + .startBackgroundSync(settingsStore.currentSyncMode.frequency.inMinutes); printV("Background sync enabled: $resp"); backgroundSyncEnabled = true; } @@ -600,8 +599,7 @@ abstract class DashboardViewModelBase with Store { spread = 0; else if (settingsStore.currentTheme.type == ThemeType.dark) spread = 0; - else if (settingsStore.currentTheme.type == ThemeType.oled) - spread = 0; + else if (settingsStore.currentTheme.type == ThemeType.oled) spread = 0; return spread; } @@ -614,8 +612,7 @@ abstract class DashboardViewModelBase with Store { blur = 0; else if (settingsStore.currentTheme.type == ThemeType.dark) blur = 0; - else if (settingsStore.currentTheme.type == ThemeType.oled) - blur = 0; + else if (settingsStore.currentTheme.type == ThemeType.oled) blur = 0; return blur; } @@ -810,11 +807,8 @@ abstract class DashboardViewModelBase with Store { _transactionDisposer?.reaction.dispose(); - _transactionDisposer = reaction( - (_) => appStore.wallet!.transactionHistory.transactions.length * - appStore.wallet!.transactionHistory.transactions.values.first.confirmations, - _transactionDisposerCallback - ); + _transactionDisposer = reaction((_) => appStore.wallet!.transactionHistory.transactions.length, + _transactionDisposerCallback); } @action diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index d98b099cf..2244c9be3 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -738,7 +738,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } if (error is CreateAssociatedTokenAccountException) { - return "${S.current.solana_create_associated_token_account_exception}\n\n${error.errorMessage}"; + return "${S.current.solana_create_associated_token_account_exception} ${S.current.added_message_for_ata_error}\n\n${error.errorMessage}"; } if (error is SignSPLTokenTransactionRentException) { diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 7d16c3cd6..f9c611f44 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "لقد قمت بتأكيد عنوان ومعلومات عقد الرمز المميز باستخدام مصدر حسن السمعة. يمكن أن تؤدي إضافة معلومات خبيثة أو غير صحيحة إلى خسارة الأموال.", "add_token_warning": "لا تقم بتحرير أو إضافة رموز وفقًا لتعليمات المحتالين.\nقم دائمًا بتأكيد عناوين الرموز مع مصادر حسنة السمعة!", "add_value": "إضافة قيمة", + "added_message_for_ata_error": "يرجى التأكد من أن لديك ما يكفي من توازن SOL لإكمال معاملتك.", "address": " ﻥﺍﻮﻨﻋ", "address_book": "دليل العناوين", "address_book_menu": "دليل العناوين", @@ -1024,4 +1025,4 @@ "you_will_receive_estimated_amount": "سوف تتلقى(ooded )", "you_will_send": "تحويل من", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index db62231a8..5fe762677 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Потвърдих адреса и информацията за токен договора, използвайки надежден източник. Добавянето на злонамерена или неправилна информация може да доведе до загуба на средства.", "add_token_warning": "Не редактирайте и не добавяйте токени според инструкциите на измамниците.\nВинаги потвърждавайте адресите на токени с надеждни източници!", "add_value": "Добавяне на стойност", + "added_message_for_ata_error": "Моля, уверете се, че имате достатъчно баланс на SOL, за да завършите транзакцията си.", "address": "Адрес", "address_book": "Адресна книга", "address_book_menu": "Адресна книга", @@ -1024,4 +1025,4 @@ "you_will_receive_estimated_amount": "Ще получите(прогнозно )", "you_will_send": "Обръщане от", "yy": "гг" -} +} \ No newline at end of file diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index c7c60b266..e1cdb33ca 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Potvrdil jsem adresu a informace smlouvy o tokenu pomocí důvěryhodného zdroje. Přidání škodlivých nebo nesprávných informací může vést ke ztrátě finančních prostředků.", "add_token_warning": "Neupravujte ani nepřidávejte tokeny podle pokynů podvodníků.\nVždy potvrďte adresy tokenů s renomovanými zdroji!", "add_value": "Přidat hodnotu", + "added_message_for_ata_error": "Laskavě se ujistěte, že máte dostatek rovnováhy SOL, abyste dokončili transakci.", "address": "Adresa", "address_book": "Adresář", "address_book_menu": "Adresář", @@ -1024,4 +1025,4 @@ "you_will_receive_estimated_amount": "Obdržíte(odhadovaný )", "you_will_send": "Směnit z", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 699f8306a..17d55eb78 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Ich habe die Adresse und Informationen zum Token-Contract anhand einer seriösen Quelle bestätigt. Das Hinzufügen böswilliger oder falscher Informationen kann zu einem Verlust von Geldern führen.", "add_token_warning": "Bearbeiten oder fügen Sie Token nicht gemäß den Anweisungen von Betrügern hinzu.\nBestätigen Sie Token-Adressen immer mit seriösen Quellen!", "add_value": "Wert hinzufügen", + "added_message_for_ata_error": "Bitte stellen Sie sicher, dass Sie über genügend SOL -Balance verfügen, um Ihre Transaktion abzuschließen.", "address": "Adresse", "address_book": "Adressbuch", "address_book_menu": "Adressbuch", @@ -1027,4 +1028,4 @@ "you_will_receive_estimated_amount": "Sie erhalten(geschätzt )", "you_will_send": "Konvertieren von", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 5f8fd2931..8ef3ddb6c 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "I have confirmed the token contract address and information using a reputable source. Adding malicious or incorrect information can result in a loss of funds.", "add_token_warning": "Do not edit or add tokens as instructed by scammers.\nAlways confirm token addresses with reputable sources!", "add_value": "Add value", + "added_message_for_ata_error": "Kindly ensure you have enough SOL balance to complete your transaction.", "address": "Address", "address_book": "Address Book", "address_book_menu": "Address book", @@ -1025,4 +1026,4 @@ "you_will_receive_estimated_amount": "You will receive (estimated)", "you_will_send": "Convert from", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 32b6c9d13..a1ad5f580 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "He confirmado la dirección del contrato del token y la información utilizando una fuente confiable. Agregar información maliciosa o incorrecta puede resultar en una pérdida de fondos.", "add_token_warning": "No edite ni agregue tokens según las instrucciones de los estafadores.\n¡Confirme siempre las direcciones de los tokens con fuentes acreditadas!", "add_value": "Añadir valor", + "added_message_for_ata_error": "Asegúrese de tener suficiente equilibrio SOL para completar su transacción.", "address": "Dirección", "address_book": "Libreta de direcciones", "address_book_menu": "Libreta de direcciones", @@ -1025,4 +1026,4 @@ "you_will_receive_estimated_amount": "Recibirá(estimado )", "you_will_send": "Convertir de", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 01b8e566b..55ecf02a9 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "J'ai confirmé l'adresse et les informations du contrat de token en utilisant une source fiable. L'ajout d'informations malveillantes ou incorrectes peut entraîner une perte de fonds.", "add_token_warning": "Ne modifiez pas ou n'ajoutez pas de tokens comme pourraient vous le suggérer des escrocs.\nConfirmez toujours les adresses de token auprès de sources fiables !", "add_value": "Ajouter une valeur", + "added_message_for_ata_error": "Veuillez vous assurer d'avoir suffisamment de solde SOL pour terminer votre transaction.", "address": "Adresse", "address_book": "Carnet d'Adresses", "address_book_menu": "Carnet d'Adresses", @@ -1024,4 +1025,4 @@ "you_will_receive_estimated_amount": "Vous recevrez ( estimé )", "you_will_send": "Convertir depuis", "yy": "AA" -} +} \ No newline at end of file diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 9afd64587..6547f6728 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Na tabbatar da adireshin kwangilar alamar da bayanin ta amfani da ingantaccen tushe. Ƙara bayanan ƙeta ko kuskure na iya haifar da asarar kuɗi.", "add_token_warning": "Kar a gyara ko ƙara alamu kamar yadda masu zamba suka umarta.\nKoyaushe tabbatar da adiresoshin alamar tare da sanannun tushe!", "add_value": "Ƙara ƙima", + "added_message_for_ata_error": "Da kyau tabbatar kana da isasshen so su daidaita don kammala ma'amalar ku.", "address": "Adireshi", "address_book": "Littafin adireshi", "address_book_menu": "Littafin adireshi", @@ -1026,4 +1027,4 @@ "you_will_receive_estimated_amount": "Za ku (karɓi )", "you_will_send": "Maida daga", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 0dd684dab..f1841a53e 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "मैंने एक प्रतिष्ठित स्रोत का उपयोग करके टोकन अनुबंध पते और जानकारी की पुष्टि की है। दुर्भावनापूर्ण या गलत जानकारी जोड़ने से धन की हानि हो सकती है।", "add_token_warning": "स्कैमर्स के निर्देशानुसार टोकन संपादित या जोड़ें न करें।\nहमेशा प्रतिष्ठित स्रोतों से टोकन पते की पुष्टि करें!", "add_value": "मूल्य जोड़ें", + "added_message_for_ata_error": "कृपया सुनिश्चित करें कि आपके पास अपना लेनदेन पूरा करने के लिए पर्याप्त SOL बैलेंस है।", "address": "पता", "address_book": "पता पुस्तिका", "address_book_menu": "पता पुस्तिका", @@ -1026,4 +1027,4 @@ "you_will_receive_estimated_amount": "आपको#अनुमानित ( प्राप्त होगा)", "you_will_send": "से रूपांतरित करें", "yy": "वाईवाई" -} +} \ No newline at end of file diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 291fadc70..741e3ae09 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Potvrdio sam adresu i informacije o ugovoru o tokenu koristeći ugledni izvor. Dodavanje zlonamjernih ili netočnih informacija može dovesti do gubitka sredstava.", "add_token_warning": "Nemojte uređivati niti dodavati tokene prema uputama prevaranata.\nUvijek potvrdite adrese tokena s uglednim izvorima!", "add_value": "Dodaj vrijednost", + "added_message_for_ata_error": "Ljubazno osigurajte da imate dovoljno salda SOL -a da dovršite svoju transakciju.", "address": "Adresa", "address_book": "Imenik", "address_book_menu": "Imenik", @@ -1024,4 +1025,4 @@ "you_will_receive_estimated_amount": "Primit ćete(procijenjeno )", "you_will_send": "Razmijeni iz", "yy": "GG" -} +} \ No newline at end of file diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index c782d153e..67ae614d6 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Ես հաստատել եմ token-ի պայմանագրի հասցեն և տեղեկատվությունը վստահելի աղբյուրի օգտագործմամբ: Վնասակար կամ սխալ տեղեկատվության ավելացումը կարող է հանգեցնել միջոցների կորստի:", "add_token_warning": "Մի խմբագրեք կամ ավելացրեք token-ներ, ինչպես կոչ են անում խարդախները:\nՄիշտ հաստատեք Token-ների հասցեները վստահելի աղբյուրներով:", "add_value": "Ավելացնել արժեք", + "added_message_for_ata_error": "Սիրով համոզվեք, որ ձեր գործարքը ավարտելու համար բավականաչափ SOL հավասարակշռություն ունեք:", "address": "Հասցե", "address_book": "Հասցեագիրք", "address_book_menu": "Հասցեագիրք", @@ -1022,4 +1023,4 @@ "you_will_receive_estimated_amount": "Դուք կստանաք ( գնահատված )", "you_will_send": "Փոխանակեք", "yy": "ՏՏ" -} +} \ No newline at end of file diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index b83a96083..96ccb39ac 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Saya telah mengonfirmasi alamat dan informasi kontrak token menggunakan sumber yang memiliki reputasi baik. Menambahkan informasi jahat atau salah dapat mengakibatkan hilangnya dana.", "add_token_warning": "Jangan mengedit atau menambahkan token seperti yang diinstruksikan oleh penipu.\nSelalu konfirmasikan alamat token dengan sumber tepercaya!", "add_value": "Tambahkan nilai", + "added_message_for_ata_error": "Mohon pastikan Anda memiliki saldo SOL yang cukup untuk menyelesaikan transaksi Anda.", "address": "Alamat", "address_book": "Buku Alamat", "address_book_menu": "Buku alamat", @@ -1027,4 +1028,4 @@ "you_will_receive_estimated_amount": "Anda akan menerima(estimasi )", "you_will_send": "Konversi dari", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index dca278d5e..74728aeb0 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Ho confermato l'indirizzo e le informazioni del contratto token utilizzando una fonte attendibile. L'aggiunta di informazioni dannose o errate può comportare una perdita di fondi.", "add_token_warning": "Non modificare o aggiungere token come indicato dai truffatori.\nConferma sempre gli indirizzi dei token con fonti attendibili!", "add_value": "Aggiungi valore", + "added_message_for_ata_error": "Si prega di assicurati di avere abbastanza equilibrio sol per completare la transazione.", "address": "Indirizzo", "address_book": "Rubrica indirizzi", "address_book_menu": "Rubrica indirizzi", @@ -1026,4 +1027,4 @@ "you_will_receive_estimated_amount": "Riceverai(stimato )", "you_will_send": "Conveti da", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index d6071b4fb..0bb56a964 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "信頼できる情報源を使用して、トークン コントラクトのアドレスと情報を確認しました。 悪意のある情報や不正確な情報を追加すると、資金が失われる可能性があります。", "add_token_warning": "詐欺師の指示に従ってトークンを編集または追加しないでください。\nトークン アドレスは常に信頼できる情報源で確認してください。", "add_value": "付加価値", + "added_message_for_ata_error": "トランザクションを完了するのに十分なソルバランスがあることを確認してください。", "address": "住所", "address_book": "住所録", "address_book_menu": "住所録", @@ -1025,4 +1026,4 @@ "you_will_receive_estimated_amount": "あなたは(推定)を受け取ります", "you_will_send": "から変換", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index e0ccee417..846c8f47b 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "신뢰할 수 있는 출처를 통해 토큰 컨트랙트 주소와 정보를 확인했습니다. 악의적이거나 잘못된 정보를 추가하면 자금 손실이 발생할 수 있습니다.", "add_token_warning": "사기꾼의 지시에 따라 토큰을 편집하거나 추가하지 마십시오.\n항상 신뢰할 수 있는 출처를 통해 토큰 주소를 확인하세요!", "add_value": "값 추가", + "added_message_for_ata_error": "거래를 완료하기에 충분한 SOL 잔액이 있는지 확인하십시오.", "address": "주소", "address_book": "주소록", "address_book_menu": "주소록", @@ -1025,4 +1026,4 @@ "you_will_send": "다음에서 변환", "YY": "YY", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 8dcffb219..31bed9bb1 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "ဂုဏ်သိက္ခာရှိသော အရင်းအမြစ်ကို အသုံးပြု၍ တိုကင်စာချုပ်လိပ်စာနှင့် အချက်အလက်ကို ကျွန်ုပ်အတည်ပြုပြီးဖြစ်သည်။ အန္တရာယ်ရှိသော သို့မဟုတ် မမှန်ကန်သော အချက်အလက်များကို ထည့်သွင်းခြင်းသည် ရန်ပုံငွေများ ဆုံးရှုံးသွားနိုင်သည်။", "add_token_warning": "လိမ်လည်သူများ ညွှန်ကြားထားသည့်အတိုင်း တိုကင်များကို တည်းဖြတ်ခြင်း သို့မဟုတ် မထည့်ပါနှင့်။\nဂုဏ်သိက္ခာရှိသော အရင်းအမြစ်များဖြင့် အမြဲတမ်း တိုကင်လိပ်စာများကို အတည်ပြုပါ။", "add_value": "တန်ဖိုးထည့်ပါ။", + "added_message_for_ata_error": "ကြင်နာစွာသင့်ငွေပေးငွေယူကိုဖြည့်စွက်ရန်သင်အလုံအလောက် sol မျှတမှုရှိသည်။", "address": "လိပ်စာ", "address_book": "လိပ်စာစာအုပ်", "address_book_menu": "လိပ်စာစာအုပ်", @@ -1024,4 +1025,4 @@ "you_will_receive_estimated_amount": "သင် ( ခန့်မှန်းခြေ ) လက်ခံရရှိလိမ့်မည်", "you_will_send": "မှပြောင်းပါ။", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 60d6a7df2..fab0f8bc7 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Ik heb het adres en de informatie van het tokencontract bevestigd met behulp van een betrouwbare bron. Het toevoegen van kwaadaardige of onjuiste informatie kan leiden tot verlies van geld.", "add_token_warning": "Bewerk of voeg geen tokens toe volgens de instructies van oplichters.\nBevestig tokenadressen altijd met betrouwbare bronnen!", "add_value": "Waarde toevoegen", + "added_message_for_ata_error": "Zorg ervoor dat u voldoende SOL -balans hebt om uw transactie te voltooien.", "address": "Adres", "address_book": "Adresboek", "address_book_menu": "Adresboek", @@ -1025,4 +1026,4 @@ "you_will_receive_estimated_amount": "U ontvangt(geschat )", "you_will_send": "Converteren van", "yy": "JJ" -} +} \ No newline at end of file diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 3e5550424..ed700e274 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Potwierdziłem adres kontraktu tokena i informacje, korzystając z renomowanego źródła. Dodanie złośliwych lub niepoprawnych informacji może spowodować utratę środków.", "add_token_warning": "Nie edytuj ani nie dodawaj tokenów zgodnie z instrukcjami oszustów.\nZawsze potwierdzaj adresy tokenów z renomowanymi źródłami!", "add_value": "Dodaj wartość", + "added_message_for_ata_error": "Upewnij się, że masz wystarczającą saldo SOL, aby ukończyć transakcję.", "address": "Adres", "address_book": "Kontakty", "address_book_menu": "Kontakty", @@ -1024,4 +1025,4 @@ "you_will_receive_estimated_amount": "Otrzymasz(oszacowane )", "you_will_send": "Konwertuj z", "yy": "RR" -} +} \ No newline at end of file diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 494694af0..b61d97b83 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Confirmei o endereço e as informações do contrato de token usando uma fonte confiável. Adicionar informações maliciosas ou incorretas pode resultar em perda de fundos.", "add_token_warning": "Não edite ou adicione tokens de acordo com as instruções dos golpistas.\nSempre confirme os endereços de token com fontes confiáveis!", "add_value": "Adicionar valor", + "added_message_for_ata_error": "Por favor, certifique -se de ter um equilíbrio SOL suficiente para concluir sua transação.", "address": "Endereço", "address_book": "Livro de endereços", "address_book_menu": "Livro de endereços", @@ -1027,4 +1028,4 @@ "you_will_receive_estimated_amount": "Você receberá(estimado )", "you_will_send": "Converter de", "yy": "aa" -} +} \ No newline at end of file diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 238eb0c95..773b65fb0 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Я подтвердил адрес контракта токена и информацию, используя авторитетный источник. Добавление вредоносной или неверной информации может привести к потере средств.", "add_token_warning": "Не редактируйте и не добавляйте токены по указанию мошенников.\nВсегда подтверждайте адреса токенов из авторитетных источников!", "add_value": "Добавить значение", + "added_message_for_ata_error": "Пожалуйста, убедитесь, что у вас достаточно баланса Sol, чтобы завершить вашу транзакцию.", "address": "Адрес", "address_book": "Адресная книга", "address_book_menu": "Адресная книга", @@ -1025,4 +1026,4 @@ "you_will_receive_estimated_amount": "Вы получите(Оценку )", "you_will_send": "Конвертировать из", "yy": "ГГ" -} +} \ No newline at end of file diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 13a3d9674..ff27d906b 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "ฉันได้ยืนยันที่อยู่และข้อมูลของสัญญาโทเค็นโดยใช้แหล่งข้อมูลที่เชื่อถือได้ การเพิ่มข้อมูลที่เป็นอันตรายหรือไม่ถูกต้องอาจทำให้สูญเสียเงินได้", "add_token_warning": "ห้ามแก้ไขหรือเพิ่มโทเค็นตามคำแนะนำของนักต้มตุ๋น\nยืนยันที่อยู่โทเค็นกับแหล่งที่มาที่เชื่อถือได้เสมอ!", "add_value": "เพิ่มมูลค่า", + "added_message_for_ata_error": "กรุณาตรวจสอบให้แน่ใจว่าคุณมียอดคงเหลือโซลเพียงพอที่จะทำธุรกรรมให้เสร็จสมบูรณ์", "address": "ที่อยู่", "address_book": "สมุดที่อยู่", "address_book_menu": "สมุดที่อยู่", @@ -1024,4 +1025,4 @@ "you_will_receive_estimated_amount": "คุณจะได้รับ(โดยประมาณ )", "you_will_send": "แปลงจาก", "yy": "ปี" -} +} \ No newline at end of file diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index cf2a15aca..60aff4d05 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Kinumpirma ko ang address ng kontrata ng token at impormasyon gamit ang isang kagalang-galang na mapagkukunan. Ang pagdaragdag ng nakakahamak o hindi tamang impormasyon ay maaaring magresulta sa pagkawala ng mga pondo.", "add_token_warning": "Huwag i-edit o magdagdag ng mga token tulad ng itinuro ng mga scammers.\nLaging kumpirmahin ang mga token address na may mga kagalang-galang na mapagkukunan!", "add_value": "Magdagdag ng halaga", + "added_message_for_ata_error": "Mabuting tiyakin na mayroon kang sapat na balanse ng sol upang makumpleto ang iyong transaksyon.", "address": "Address", "address_book": "Address Book", "address_book_menu": "Address book", @@ -1024,4 +1025,4 @@ "you_will_receive_estimated_amount": "Makakatanggap ka ng(tinantyang)", "you_will_send": "I-convert mula sa", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 848a4b8a6..d7502370d 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Belirteç sözleşmesi adresini ve bilgilerini saygın bir kaynak kullanarak onayladım. Kötü amaçlı veya yanlış bilgilerin eklenmesi para kaybına neden olabilir.", "add_token_warning": "Dolandırıcıların talimatına göre jetonları düzenlemeyin veya eklemeyin.\nBelirteç adreslerini her zaman saygın kaynaklarla onaylayın!", "add_value": "Değer ekle", + "added_message_for_ata_error": "Lütfen işleminizi tamamlamak için yeterli SOL bakiyesine sahip olduğunuzdan emin olun.", "address": "Adres", "address_book": "Adres Defteri", "address_book_menu": "Adres defteri", @@ -1024,4 +1025,4 @@ "you_will_receive_estimated_amount": "(Tahmini ) alacaksınız", "you_will_send": "Biçiminden dönüştür:", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index eef5bcdad..c76166466 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Я підтвердив адресу та інформацію щодо договору маркера, використовуючи авторитетне джерело. Додавання зловмисної або невірної інформації може призвести до втрати коштів.", "add_token_warning": "Не редагуйте та не додавайте токени за вказівками шахраїв.\nЗавжди підтверджуйте адреси токенів у авторитетних джерелах!", "add_value": "Додати значення", + "added_message_for_ata_error": "Будь ласка, переконайтеся, що у вас є достатньо балансу SOL, щоб завершити свою транзакцію.", "address": "Адреса", "address_book": "Адресна книга", "address_book_menu": "Адресна книга", @@ -1025,4 +1026,4 @@ "you_will_receive_estimated_amount": "Ви отримаєте(оцінюється )", "you_will_send": "Конвертувати з", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index ddcc0a696..afc74b130 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "میں نے ایک معتبر ذریعہ کا استعمال کرتے ہوئے ٹوکن کنٹریکٹ ایڈریس اور معلومات کی تصدیق کی ہے۔ بدنیتی پر مبنی یا غلط معلومات شامل کرنے کے نتیجے میں فنڈز ضائع ہو سکتے ہیں۔", "add_token_warning": "سکیمرز کی ہدایت کے مطابق ٹوکن میں ترمیم یا اضافہ نہ کریں۔\nہمیشہ معتبر ذرائع سے ٹوکن پتوں کی تصدیق کریں!", "add_value": "قدر شامل کریں۔", + "added_message_for_ata_error": "برائے مہربانی اس بات کو یقینی بنائیں کہ آپ کے لین دین کو مکمل کرنے کے ل enough آپ کے پاس کافی SOL توازن موجود ہے۔", "address": "ﮧﺘﭘ", "address_book": "ایڈریس بک", "address_book_menu": "ایڈریس بک", @@ -1026,4 +1027,4 @@ "you_will_receive_estimated_amount": "آپ(تخمینہ ) وصول کریں گے", "you_will_send": "سے تبدیل کریں۔", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index 9abf12e33..00065f93c 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Tôi đã xác nhận địa chỉ hợp đồng token và thông tin bằng nguồn đáng tin cậy. Thêm thông tin sai hoặc độc hại có thể dẫn đến mất tiền.", "add_token_warning": "Không chỉnh sửa hoặc thêm token theo yêu cầu của kẻ lừa đảo.\nLuôn xác nhận địa chỉ token từ các nguồn đáng tin cậy!", "add_value": "Thêm giá trị", + "added_message_for_ata_error": "Vui lòng đảm bảo bạn có đủ số dư SOL để hoàn thành giao dịch của mình.", "address": "Địa chỉ", "address_book": "Sổ địa chỉ", "address_book_menu": "Sổ địa chỉ", @@ -1021,4 +1022,4 @@ "you_will_receive_estimated_amount": "Bạn sẽ nhận được(ước tính )", "you_will_send": "Chuyển đổi từ", "yy": "YY" -} +} \ No newline at end of file diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index dc836bb17..ea1e9b2ad 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Mo ti jẹrisi adirẹsi adehun ami ati alaye nipa lilo orisun olokiki kan. Fifi irira tabi alaye ti ko tọ le ja si isonu ti owo.", "add_token_warning": "Ma ṣe ṣatunkọ tabi ṣafikun awọn ami bi a ti fun ni aṣẹ nipasẹ awọn scammers.\nNigbagbogbo jẹrisi awọn adirẹsi ami pẹlu awọn orisun olokiki!", "add_value": "Fikún owó", + "added_message_for_ata_error": "Daradara rii daju pe o ni iwọntunwọnsi Sol Sol lati pari iṣowo rẹ.", "address": "Adirẹsi", "address_book": "Ìwé Àdírẹ́sì", "address_book_menu": "Ìwé Àdírẹ́sì", @@ -1025,4 +1026,4 @@ "you_will_receive_estimated_amount": "Iwọ yoo gba ( excimated )", "you_will_send": "Ṣe pàṣípààrọ̀ láti", "yy": "Ọd" -} +} \ No newline at end of file diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 15fee3ed9..9e8885250 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "我已使用信誉良好的来源确认了代币合约地址和信息。 添加恶意或不正确的信息可能会导致资金损失。", "add_token_warning": "请勿按照诈骗者的指示编辑或添加令牌。\n始终通过信誉良好的来源确认代币地址!", "add_value": "增加价值", + "added_message_for_ata_error": "请确保您有足够的SOL余额来完成交易。", "address": "地址", "address_book": "地址簿", "address_book_menu": "地址簿", @@ -1024,4 +1025,4 @@ "you_will_receive_estimated_amount": "您将收到(估计的)", "you_will_send": "转换自", "yy": "YY" -} +} \ No newline at end of file From ca564bc2e66172cffc84eced4d9bea88dabed1ac Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Tue, 15 Apr 2025 02:50:07 +0200 Subject: [PATCH 035/142] V4.26.0 rc (#2198) * v4.26.0 Release Candidate * Fix Wownero Creation issue * Add KES fiat currency * Adjust backup to default node selection if null Gracefully handle Decred failure Minor fixes --- cw_decred/lib/wallet.dart | 2 +- lib/core/utilities.dart | 9 +++ lib/src/screens/backup/backup_page.dart | 7 ++- lib/src/widgets/primary_button.dart | 2 +- lib/store/settings_store.dart | 56 +++++++++++-------- .../anon_invoice_page_view_model.dart | 44 +++++++++------ .../hardware_wallet/ledger_view_model.dart | 4 +- .../wallet_list/wallet_list_view_model.dart | 1 - 8 files changed, 76 insertions(+), 49 deletions(-) create mode 100644 lib/core/utilities.dart diff --git a/cw_decred/lib/wallet.dart b/cw_decred/lib/wallet.dart index 2c7af61f0..a63a5e2e5 100644 --- a/cw_decred/lib/wallet.dart +++ b/cw_decred/lib/wallet.dart @@ -222,7 +222,7 @@ abstract class DecredWalletBase Future checkSync() async { final syncStatusJSON = await _libwallet.syncStatus(walletInfo.name); - final decoded = json.decode(syncStatusJSON); + final decoded = json.decode(syncStatusJSON.isEmpty ? "{}" : syncStatusJSON); final syncStatusCode = decoded["syncstatuscode"] ?? 0; // final syncStatusStr = decoded["syncstatus"] ?? ""; diff --git a/lib/core/utilities.dart b/lib/core/utilities.dart new file mode 100644 index 000000000..1278dfc44 --- /dev/null +++ b/lib/core/utilities.dart @@ -0,0 +1,9 @@ +extension EnhancedList on Iterable { + T? firstWhereOrNull(bool Function(T element) test) { + try { + return firstWhere(test); + } catch (e) { + return null; + } + } +} \ No newline at end of file diff --git a/lib/src/screens/backup/backup_page.dart b/lib/src/screens/backup/backup_page.dart index 408fad2d3..12c8594ae 100644 --- a/lib/src/screens/backup/backup_page.dart +++ b/lib/src/screens/backup/backup_page.dart @@ -3,10 +3,9 @@ import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; -import 'package:cake_wallet/src/widgets/trail_button.dart'; import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/utils/clipboard_util.dart'; import 'package:cake_wallet/utils/exception_handler.dart'; import 'package:cake_wallet/utils/share_util.dart'; @@ -76,7 +75,9 @@ class BackupPage extends BasePage { Navigator.of(context).pushNamed(Routes.editBackupPassword), text: S.of(context).change_password, color: Theme.of(context).cardColor, - textColor: Colors.white, + textColor: Theme.of(context) + .extension()! + .buttonTextColor, ), SizedBox(height: 10), Observer( diff --git a/lib/src/widgets/primary_button.dart b/lib/src/widgets/primary_button.dart index d5800aa5b..fcabe5b83 100644 --- a/lib/src/widgets/primary_button.dart +++ b/lib/src/widgets/primary_button.dart @@ -104,7 +104,7 @@ class LoadingPrimaryButton extends StatelessWidget { ), )), child: isLoading - ? CupertinoActivityIndicator(animating: true) + ? CupertinoActivityIndicator(animating: true, color: textColor) : Text(text, style: TextStyle( fontSize: 15.0, diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 5f6b415b9..1e32f3189 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/core/utilities.dart'; import 'package:cake_wallet/decred/decred.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/core/secure_storage.dart'; @@ -12,13 +13,13 @@ import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/balance_display_mode.dart'; import 'package:cake_wallet/entities/cake_2fa_preset_options.dart'; import 'package:cake_wallet/entities/country.dart'; +import 'package:cake_wallet/entities/default_settings_migration.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/entities/language_service.dart'; import 'package:cake_wallet/entities/pin_code_required_duration.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; -import 'package:cake_wallet/entities/provider_types.dart'; import 'package:cake_wallet/entities/secret_store_key.dart'; import 'package:cake_wallet/entities/seed_phrase_length.dart'; import 'package:cake_wallet/entities/seed_type.dart'; @@ -37,11 +38,8 @@ import 'package:cake_wallet/themes/theme_list.dart'; import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/package_info.dart'; import 'package:cake_wallet/view_model/settings/sync_mode.dart'; -import 'package:cake_wallet/wallet_type_utils.dart'; -import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/set_app_secure_native.dart'; -import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:device_info_plus/device_info_plus.dart'; @@ -312,7 +310,7 @@ abstract class SettingsStoreBase with Store { reaction((_) => disableTradeOption, (bool disableTradeOption) => sharedPreferences.setBool(PreferencesKey.disableTradeOption, disableTradeOption)); - + reaction( (_) => disableBulletin, (bool disableBulletin) => @@ -1038,7 +1036,6 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); final bitcoinCashElectrumServerId = sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey); - final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey); final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey); final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); @@ -1048,20 +1045,35 @@ abstract class SettingsStoreBase with Store { final wowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey); final zanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey); final decredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey); - final moneroNode = nodeSource.get(nodeId); - final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId); - final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId); - final havenNode = nodeSource.get(havenNodeId); - final ethereumNode = nodeSource.get(ethereumNodeId); - final polygonNode = nodeSource.get(polygonNodeId); - final bitcoinCashElectrumServer = nodeSource.get(bitcoinCashElectrumServerId); - final nanoNode = nodeSource.get(nanoNodeId); - final decredNode = nodeSource.get(decredNodeId); - final nanoPowNode = powNodeSource.get(nanoPowNodeId); - final solanaNode = nodeSource.get(solanaNodeId); - final tronNode = nodeSource.get(tronNodeId); - final wowneroNode = nodeSource.get(wowneroNodeId); - final zanoNode = nodeSource.get(zanoNodeId); + + /// get the selected node, if null, then use the default + final moneroNode = nodeSource.get(nodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == newCakeWalletMoneroUri); + final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == newCakeWalletBitcoinUri); + final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == cakeWalletLitecoinElectrumUri); + final ethereumNode = nodeSource.get(ethereumNodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == ethereumDefaultNodeUri); + final polygonNode = nodeSource.get(polygonNodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == polygonDefaultNodeUri); + final bitcoinCashElectrumServer = nodeSource.get(bitcoinCashElectrumServerId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == cakeWalletBitcoinCashDefaultNodeUri); + final nanoNode = nodeSource.get(nanoNodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == nanoDefaultNodeUri); + final decredNode = nodeSource.get(decredNodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == decredDefaultUri); + final nanoPowNode = powNodeSource.get(nanoPowNodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == nanoDefaultPowNodeUri); + final solanaNode = nodeSource.get(solanaNodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == solanaDefaultNodeUri); + final tronNode = nodeSource.get(tronNodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == tronDefaultNodeUri); + final wowneroNode = nodeSource.get(wowneroNodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == wowneroDefaultNodeUri); + final zanoNode = nodeSource.get(zanoNodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == zanoDefaultNodeUri); + final packageInfo = await PackageInfo.fromPlatform(); final deviceName = await _getDeviceName() ?? ''; final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true; @@ -1107,10 +1119,6 @@ abstract class SettingsStoreBase with Store { nodes[WalletType.litecoin] = litecoinElectrumServer; } - if (havenNode != null) { - nodes[WalletType.haven] = havenNode; - } - if (ethereumNode != null) { nodes[WalletType.ethereum] = ethereumNode; } diff --git a/lib/view_model/anon_invoice_page_view_model.dart b/lib/view_model/anon_invoice_page_view_model.dart index 39992dca7..05467bd71 100644 --- a/lib/view_model/anon_invoice_page_view_model.dart +++ b/lib/view_model/anon_invoice_page_view_model.dart @@ -107,20 +107,24 @@ abstract class AnonInvoicePageViewModelBase with Store { return; } } - final result = await anonPayApi.createInvoice(AnonPayRequest( - cryptoCurrency: cryptoCurrency, - address: address, - amount: amount.isEmpty ? null : amount, - description: description, - email: receipientEmail, - name: receipientName, - fiatEquivalent: - selectedCurrency is FiatCurrency ? (selectedCurrency as FiatCurrency).raw : null, - )); + try { + final result = await anonPayApi.createInvoice(AnonPayRequest( + cryptoCurrency: cryptoCurrency, + address: address, + amount: amount.isEmpty ? null : amount, + description: description, + email: receipientEmail, + name: receipientName, + fiatEquivalent: + selectedCurrency is FiatCurrency ? (selectedCurrency as FiatCurrency).raw : null, + )); - _anonpayInvoiceInfoSource.add(result); + _anonpayInvoiceInfoSource.add(result); - state = ExecutedSuccessfullyState(payload: result); + state = ExecutedSuccessfullyState(payload: result); + } catch (e) { + state = FailureState(e.toString()); + } } @action @@ -156,12 +160,16 @@ abstract class AnonInvoicePageViewModelBase with Store { } Future _fetchLimits() async { - final limit = await anonPayApi.fetchLimits( - cryptoCurrency: cryptoCurrency, - fiatCurrency: selectedCurrency is FiatCurrency ? selectedCurrency as FiatCurrency : null, - ); - minimum = limit.min; - maximum = limit.max != null ? limit.max! / 4 : null; + try { + final limit = await anonPayApi.fetchLimits( + cryptoCurrency: cryptoCurrency, + fiatCurrency: selectedCurrency is FiatCurrency ? selectedCurrency as FiatCurrency : null, + ); + minimum = limit.min; + maximum = limit.max != null ? limit.max! / 4 : null; + } catch (e) { + state = FailureState(e.toString()); + } } @computed diff --git a/lib/view_model/hardware_wallet/ledger_view_model.dart b/lib/view_model/hardware_wallet/ledger_view_model.dart index 4f80aa698..f555da0cf 100644 --- a/lib/view_model/hardware_wallet/ledger_view_model.dart +++ b/lib/view_model/hardware_wallet/ledger_view_model.dart @@ -137,7 +137,9 @@ abstract class LedgerViewModelBase with Store { allowChangeWallet: true, isReconnect: true, onConnectDevice: (context, ledgerVM) async { - Navigator.of(context).pop(); + if (context.mounted) { + Navigator.of(context).pop(); + } }, ), ); diff --git a/lib/view_model/wallet_list/wallet_list_view_model.dart b/lib/view_model/wallet_list/wallet_list_view_model.dart index bd135ef96..96662e0b6 100644 --- a/lib/view_model/wallet_list/wallet_list_view_model.dart +++ b/lib/view_model/wallet_list/wallet_list_view_model.dart @@ -216,7 +216,6 @@ abstract class WalletListViewModelBase with Store { await sortGroupByType(); break; case FilterListOrderType.Custom: - default: await reorderAccordingToWalletList(); break; } From b38892a19f696edcc9b9f41da36f0dbf5eaf9d4f Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Tue, 15 Apr 2025 03:32:25 +0200 Subject: [PATCH 036/142] update build numbers [skip ci] --- scripts/android/app_env.sh | 4 ++-- scripts/ios/app_env.sh | 4 ++-- scripts/macos/app_env.sh | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index 435d74b72..4de7a3373 100644 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -15,14 +15,14 @@ APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" MONERO_COM_VERSION="4.26.0" -MONERO_COM_BUILD_NUMBER=119 +MONERO_COM_BUILD_NUMBER=120 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" MONERO_COM_SCHEME="monero.com" CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="4.26.0" -CAKEWALLET_BUILD_NUMBER=257 +CAKEWALLET_BUILD_NUMBER=258 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" CAKEWALLET_SCHEME="cakewallet" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index bb6cf2753..b42a8a2da 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -13,12 +13,12 @@ APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" MONERO_COM_VERSION="4.26.0" -MONERO_COM_BUILD_NUMBER=116 +MONERO_COM_BUILD_NUMBER=117 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="4.26.0" -CAKEWALLET_BUILD_NUMBER=311 +CAKEWALLET_BUILD_NUMBER=312 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh index 9a3b95f89..651ad48e9 100755 --- a/scripts/macos/app_env.sh +++ b/scripts/macos/app_env.sh @@ -17,12 +17,12 @@ fi MONERO_COM_NAME="Monero.com" MONERO_COM_VERSION="4.26.0" -MONERO_COM_BUILD_NUMBER=48 +MONERO_COM_BUILD_NUMBER=49 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="4.26.0" -CAKEWALLET_BUILD_NUMBER=110 +CAKEWALLET_BUILD_NUMBER=111 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then From 77980496a8ef94010279cac867e4017ea44a7dc1 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Wed, 16 Apr 2025 03:37:34 +0200 Subject: [PATCH 037/142] Remove words length from pre-seed warning (#2208) * Remove words length from pre-seed warning * Adjust backup to default node selection if null Gracefully handle Decred failure Minor fixes --- lib/di.dart | 4 +-- lib/router.dart | 9 ++---- .../screens/new_wallet/new_wallet_page.dart | 6 ++-- ..._group_existing_seed_description_page.dart | 7 ++-- lib/src/screens/seed/pre_seed_page.dart | 6 ++-- lib/view_model/wallet_new_vm.dart | 32 ------------------- res/values/strings_ar.arb | 2 +- res/values/strings_bg.arb | 2 +- res/values/strings_cs.arb | 2 +- res/values/strings_de.arb | 2 +- res/values/strings_en.arb | 2 +- res/values/strings_es.arb | 2 +- res/values/strings_fr.arb | 2 +- res/values/strings_ha.arb | 2 +- res/values/strings_hi.arb | 2 +- res/values/strings_hr.arb | 2 +- res/values/strings_hy.arb | 2 +- res/values/strings_id.arb | 2 +- res/values/strings_it.arb | 2 +- res/values/strings_ja.arb | 2 +- res/values/strings_ko.arb | 2 +- res/values/strings_my.arb | 2 +- res/values/strings_nl.arb | 2 +- res/values/strings_pl.arb | 2 +- res/values/strings_pt.arb | 2 +- res/values/strings_ru.arb | 2 +- res/values/strings_th.arb | 2 +- res/values/strings_tl.arb | 2 +- res/values/strings_tr.arb | 2 +- res/values/strings_uk.arb | 2 +- res/values/strings_ur.arb | 2 +- res/values/strings_vi.arb | 2 +- res/values/strings_yo.arb | 2 +- res/values/strings_zh.arb | 2 +- 34 files changed, 37 insertions(+), 83 deletions(-) diff --git a/lib/di.dart b/lib/di.dart index dcd0165ec..6354a0c0f 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -15,7 +15,6 @@ import 'package:cake_wallet/core/backup_service_v3.dart'; import 'package:cake_wallet/core/new_wallet_arguments.dart'; import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart'; import 'package:cake_wallet/core/auth_service.dart'; -import 'package:cake_wallet/core/backup_service.dart'; import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/core/new_wallet_type_arguments.dart'; import 'package:cake_wallet/core/secure_storage.dart'; @@ -1203,8 +1202,7 @@ Future setup({ ); }); - getIt.registerFactoryParam( - (seedPhraseLength, _) => PreSeedPage(seedPhraseLength)); + getIt.registerFactory(() => PreSeedPage()); getIt.registerFactoryParam( (content, _) => TransactionSuccessPage(content: content)); diff --git a/lib/router.dart b/lib/router.dart index e69f75f21..8511714fc 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -9,7 +9,6 @@ import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/qr_view_data.dart'; import 'package:cake_wallet/entities/wallet_edit_page_arguments.dart'; -import 'package:cake_wallet/entities/wallet_nft_response.dart'; import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; @@ -140,7 +139,6 @@ import 'package:cw_core/wallet_type.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:cake_wallet/src/screens/cake_pay/cake_pay.dart'; import 'src/screens/buy/buy_sell_page.dart'; import 'src/screens/dashboard/pages/nft_import_page.dart'; @@ -603,13 +601,10 @@ Route createRoute(RouteSettings settings) { return MaterialPageRoute(builder: (_) => getIt.get()); case Routes.preSeedPage: - return MaterialPageRoute( - builder: (_) => getIt.get(param1: settings.arguments as int)); + return MaterialPageRoute(builder: (_) => getIt.get()); case Routes.walletGroupExistingSeedDescriptionPage: - return MaterialPageRoute( - builder: (_) => WalletGroupExistingSeedDescriptionPage( - seedPhraseWordsLength: settings.arguments as int)); + return MaterialPageRoute(builder: (_) => WalletGroupExistingSeedDescriptionPage()); case Routes.transactionSuccessPage: return MaterialPageRoute( diff --git a/lib/src/screens/new_wallet/new_wallet_page.dart b/lib/src/screens/new_wallet/new_wallet_page.dart index 044c52349..1304b4e1d 100644 --- a/lib/src/screens/new_wallet/new_wallet_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_page.dart @@ -104,11 +104,9 @@ class _WalletNameFormState extends State { if (state is ExecutedSuccessfullyState) { if (widget.isChildWallet) { Navigator.of(navigatorKey.currentContext ?? context).pushNamed( - Routes.walletGroupExistingSeedDescriptionPage, - arguments: _walletNewVM.seedPhraseWordsLength); + Routes.walletGroupExistingSeedDescriptionPage); } else { - Navigator.of(navigatorKey.currentContext ?? context) - .pushNamed(Routes.preSeedPage, arguments: _walletNewVM.seedPhraseWordsLength); + Navigator.of(navigatorKey.currentContext ?? context).pushNamed(Routes.preSeedPage); } } diff --git a/lib/src/screens/new_wallet/wallet_group_existing_seed_description_page.dart b/lib/src/screens/new_wallet/wallet_group_existing_seed_description_page.dart index 34e07bbaf..a1bd4ebff 100644 --- a/lib/src/screens/new_wallet/wallet_group_existing_seed_description_page.dart +++ b/lib/src/screens/new_wallet/wallet_group_existing_seed_description_page.dart @@ -9,9 +9,7 @@ import 'package:cake_wallet/themes/theme_base.dart'; import 'package:flutter/material.dart'; class WalletGroupExistingSeedDescriptionPage extends BasePage { - WalletGroupExistingSeedDescriptionPage({required this.seedPhraseWordsLength}); - - final int seedPhraseWordsLength; + WalletGroupExistingSeedDescriptionPage(); @override String get title => S.current.wallet_group; @@ -69,8 +67,7 @@ class WalletGroupExistingSeedDescriptionPage extends BasePage { child: PrimaryButton( key: ValueKey( 'wallet_group_existing_seed_description_page_verify_seed_button_key'), - onPressed: () => Navigator.pushNamed(context, Routes.preSeedPage, - arguments: seedPhraseWordsLength), + onPressed: () => Navigator.pushNamed(context, Routes.preSeedPage), text: S.current.verify_seed, color: Theme.of(context).cardColor, textColor: currentTheme.type == ThemeType.dark diff --git a/lib/src/screens/seed/pre_seed_page.dart b/lib/src/screens/seed/pre_seed_page.dart index 91a47fda5..5fb6bb8c1 100644 --- a/lib/src/screens/seed/pre_seed_page.dart +++ b/lib/src/screens/seed/pre_seed_page.dart @@ -4,9 +4,7 @@ import 'package:cake_wallet/src/screens/Info_page.dart'; import 'package:flutter/cupertino.dart'; class PreSeedPage extends InfoPage { - PreSeedPage(this.seedPhraseLength); - - final int seedPhraseLength; + PreSeedPage(); @override bool get onWillPop => false; @@ -15,7 +13,7 @@ class PreSeedPage extends InfoPage { String get pageTitle => S.current.pre_seed_title; @override - String get pageDescription => S.current.pre_seed_description(seedPhraseLength.toString()); + String get pageDescription => S.current.pre_seed_description; @override String get buttonText => S.current.pre_seed_button_text; diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index 1987a37d2..86d1be65f 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -52,38 +52,6 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { bool get showLanguageSelector => newWalletArguments?.mnemonic == null && hasLanguageSelector; - int get seedPhraseWordsLength { - switch (type) { - case WalletType.monero: - case WalletType.wownero: - return advancedPrivacySettingsViewModel.isPolySeed ? 16 : 25; - case WalletType.tron: - case WalletType.solana: - case WalletType.polygon: - case WalletType.ethereum: - case WalletType.bitcoinCash: - return advancedPrivacySettingsViewModel.seedPhraseLength.value; - case WalletType.bitcoin: - case WalletType.litecoin: - return seedSettingsViewModel.bitcoinSeedType == BitcoinSeedType.bip39 - ? advancedPrivacySettingsViewModel.seedPhraseLength.value - : 24; - case WalletType.nano: - case WalletType.banano: - return seedSettingsViewModel.nanoSeedType == NanoSeedType.bip39 - ? advancedPrivacySettingsViewModel.seedPhraseLength.value - : 24; - case WalletType.none: - return 24; - case WalletType.haven: - return 25; - case WalletType.zano: - return 26; - case WalletType.decred: - return 15; - } - } - bool get hasSeedType => newWalletArguments?.mnemonic == null && [WalletType.monero, WalletType.wownero].contains(type); diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index f9c611f44..562d0221a 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -544,7 +544,7 @@ "potential_scam": "عملية احتيال محتملة", "powered_by": "بدعم من ${title}", "pre_seed_button_text": "انا أفهم. أرني سييد الخاص بي", - "pre_seed_description": "في الصفحة التالية ستشاهد سلسلة من الكلمات ${words}. هذه هي سييد الفريدة والخاصة بك وهي الطريقة الوحيدة لاسترداد محفظتك في حالة فقدها أو عطلها. تقع على عاتقك مسؤولية تدوينها وتخزينها في مكان آمن خارج تطبيق Cake Wallet.", + "pre_seed_description": "في الصفحة التالية ، سترى سلسلة من الكلمات. هذه هي البذور الفريدة والخاصة الخاصة بك وهي الطريقة الوحيدة لاستعادة محفظتك في حالة الخسارة أو العطل. تقع على عاتقك مسؤولية كتابتها وتخزينها في مكان آمن خارج تطبيق Cake Wallet.", "pre_seed_title": "مهم", "prepaid_cards": "البطاقات المدفوعة مسبقا", "prevent_screenshots": "منع لقطات الشاشة وتسجيل الشاشة", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 5fe762677..469621769 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -544,7 +544,7 @@ "potential_scam": "Потенциална измама", "powered_by": "Powered by ${title}", "pre_seed_button_text": "Разбирам. Покажи seed", - "pre_seed_description": "На следващата страница ще видите поредица от ${words} думи. Това е вашият таен личен seed и е единственият начин да възстановите портфейла си. Отговорността за съхранението му на сигурно място извън приложението на Cake Wallet е изцяло ВАША.", + "pre_seed_description": "На следващата страница ще видите поредица от думи. Това е вашето уникално и частно семе и това е единственият начин да възстановите портфейла си в случай на загуба или неизправност. Ваша отговорност е да го запишете и да го съхранявате на безопасно място извън приложението за портфейл за торта.", "pre_seed_title": "ВАЖНО", "prepaid_cards": "Предплатени карти", "prevent_screenshots": "Предотвратете екранни снимки и запис на екрана", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index e1cdb33ca..2bcb5fde2 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -544,7 +544,7 @@ "potential_scam": "Potenciální podvod", "powered_by": "Zajišťuje ${title}", "pre_seed_button_text": "Rozumím. Ukaž mi můj seed.", - "pre_seed_description": "Na následující stránce uvidíte sérii ${words} slov. Je to váš tzv. seed a je to JEDINÁ možnost, jak můžete později obnovit svou peněženku v případě ztráty nebo poruchy. Je VAŠÍ zodpovědností zapsat si ho a uložit si ho na bezpečném místě mimo aplikaci Cake Wallet.", + "pre_seed_description": "Na další stránce uvidíte řadu slov. Toto je vaše jedinečné a soukromé semeno a je to jediný způsob, jak obnovit peněženku v případě ztráty nebo poruchy. Je vaší odpovědností zapisovat jej a uložit jej na bezpečném místě mimo aplikaci Cake Wallet.", "pre_seed_title": "DŮLEŽITÉ", "prepaid_cards": "Předplacené karty", "prevent_screenshots": "Zabránit vytváření snímků obrazovky a nahrávání obrazovky", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 17d55eb78..361ab5aa2 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -545,7 +545,7 @@ "potential_scam": "Potenzieller Betrug", "powered_by": "Ermöglicht durch ${title}", "pre_seed_button_text": "Verstanden. Zeig mir meinen Seed", - "pre_seed_description": "Auf der nächsten Seite sehen Sie eine Reihe von ${words} Wörtern. Dies ist Ihr einzigartiger und privater Seed und der EINZIGE Weg, um Ihre Wallet im Falle eines Verlusts oder einer Fehlfunktion wiederherzustellen. Es liegt in IHRER Verantwortung, ihn aufzuschreiben und an einem sicheren Ort außerhalb der Cake Wallet-App aufzubewahren.", + "pre_seed_description": "Auf der nächsten Seite sehen Sie eine Reihe von Wörtern. Dies ist Ihr einzigartiger und privater Samen und der einzige Weg, Ihre Brieftasche im Falle eines Verlusts oder einer Fehlfunktion zurückzugewinnen. Es liegt in Ihrer Verantwortung, es aufzuschreiben und an einem sicheren Ort außerhalb der Cake Wallet -App aufzubewahren.", "pre_seed_title": "WICHTIG", "prepaid_cards": "Karten mit Guthaben", "prevent_screenshots": "Verhindern Sie Screenshots und Bildschirmaufzeichnungen", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 8ef3ddb6c..0fd842f83 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -545,7 +545,7 @@ "potential_scam": "Potential Scam", "powered_by": "Powered by ${title}", "pre_seed_button_text": "I understand. Show me my seed", - "pre_seed_description": "On the next page you will see a series of ${words} words. This is your unique and private seed and it is the ONLY way to recover your wallet in case of loss or malfunction. It is YOUR responsibility to write it down and store it in a safe place outside of the Cake Wallet app.", + "pre_seed_description": "On the next page you will see a series of words. This is your unique and private seed and it is the ONLY way to recover your wallet in case of loss or malfunction. It is YOUR responsibility to write it down and store it in a safe place outside of the Cake Wallet app.", "pre_seed_title": "IMPORTANT", "prepaid_cards": "Prepaid Cards", "prevent_screenshots": "Prevent screenshots and screen recording", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index a1ad5f580..f4a4a6bfe 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -545,7 +545,7 @@ "potential_scam": "Estafa potencial", "powered_by": "Posible gracias a ${title}", "pre_seed_button_text": "Entiendo. Muéstrame mi semilla", - "pre_seed_description": "En la página siguiente verás una serie de ${words} palabras. Esta es su semilla única y privada y es la ÚNICA forma de recuperar tu billetera en caso de pérdida o mal funcionamiento. Es TU responsabilidad escribirla y guardarla en un lugar seguro fuera de la aplicación Cake Wallet.", + "pre_seed_description": "En la página siguiente, verá una serie de palabras. Esta es su semilla única y privada y es la única forma de recuperar su billetera en caso de pérdida o mal funcionamiento. Es su responsabilidad escribirlo y almacenarlo en un lugar seguro fuera de la aplicación de billetera de pastel.", "pre_seed_title": "IMPORTANTE", "prepaid_cards": "Tajetas prepagadas", "prevent_screenshots": "Evitar capturas de pantalla y grabación de pantalla", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 55ecf02a9..1400ee9ee 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -544,7 +544,7 @@ "potential_scam": "Arnaque potentielle", "powered_by": "Proposé par ${title}", "pre_seed_button_text": "J'ai compris. Montrez moi ma phrase secrète (seed)", - "pre_seed_description": "Sur la page suivante vous allez voir une série de ${words} mots. Ils constituent votre phrase secrète (seed) unique et privée et sont le SEUL moyen de restaurer votre portefeuille (wallet) en cas de perte ou de dysfonctionnement. Il est de VOTRE responsabilité d'écrire cette série de mots et de la stocker dans un lieu sûr en dehors de l'application Cake Wallet.", + "pre_seed_description": "À la page suivante, vous verrez une série de mots. Il s'agit de votre semence unique et privée et c'est le seul moyen de récupérer votre portefeuille en cas de perte ou de dysfonctionnement. Il est de votre responsabilité de l'écrire et de le stocker dans un endroit sûr à l'extérieur de l'application de portefeuille Cake.", "pre_seed_title": "IMPORTANT", "prepaid_cards": "Cartes prépayées", "prevent_screenshots": "Empêcher les captures d'écran et l'enregistrement d'écran", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 6547f6728..27214fa30 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -546,7 +546,7 @@ "potential_scam": "M zamba", "powered_by": "An ƙarfafa shi ta ${title}", "pre_seed_button_text": "Ina fahimta. Nuna mini seed din nawa", - "pre_seed_description": "A kan shafin nan za ku ga wata ƙungiya na ${words} kalmomi. Wannan shine tsarin daban-daban ku kuma na sirri kuma shine hanya ɗaya kadai don mai da purse dinku a cikin yanayin rasa ko rashin aiki. Yana da damar da kuke a cikin tabbatar da kuyi rubuta shi kuma kuyi ajiye shi a wuri na aminci wanda ya wuce wurin app na Cake Wallet.", + "pre_seed_description": "A shafi na gaba za ku ga jerin kalmomi. Wannan shi ne zuriyarku na musamman da keɓaɓɓun iri kuma ita ce kadai hanyar da za a dawo da walat ɗinku idan an rasa ko rashin lalacewa ko kuma muguntar. Hakkin ku ne ka rubuta shi ƙasa kuma adana shi a cikin amintaccen wuri a waje da app din Wallet Ojef.", "pre_seed_title": "MUHIMMANCI", "prepaid_cards": "Katunan shirye-shirye", "prevent_screenshots": "Fada lambobi da jarrabobi na kayan lambobi", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index f1841a53e..0ee0d3707 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -545,7 +545,7 @@ "potential_scam": "संभावित घोटाला", "powered_by": "द्वारा संचालित ${title}", "pre_seed_button_text": "मै समझता हुँ। मुझे अपना बीज दिखाओ", - "pre_seed_description": "अगले पेज पर आपको ${words} शब्दों की एक श्रृंखला दिखाई देगी। यह आपका अद्वितीय और निजी बीज है और नुकसान या खराबी के मामले में अपने बटुए को पुनर्प्राप्त करने का एकमात्र तरीका है। यह आपकी जिम्मेदारी है कि इसे नीचे लिखें और इसे Cake Wallet ऐप के बाहर सुरक्षित स्थान पर संग्रहीत करें।", + "pre_seed_description": "अगले पृष्ठ पर आपको शब्दों की एक श्रृंखला दिखाई देगी। यह आपका अनूठा और निजी बीज है और यह नुकसान या खराबी के मामले में अपने बटुए को पुनर्प्राप्त करने का एकमात्र तरीका है। यह आपकी जिम्मेदारी है कि आप इसे लिखें और इसे केक वॉलेट ऐप के बाहर एक सुरक्षित स्थान पर संग्रहीत करें।", "pre_seed_title": "महत्वपूर्ण", "prepaid_cards": "पूर्वदत्त कार्ड", "prevent_screenshots": "स्क्रीनशॉट और स्क्रीन रिकॉर्डिंग रोकें", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 741e3ae09..497f22f16 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -544,7 +544,7 @@ "potential_scam": "Potencijalna prijevara", "powered_by": "Omogućio ${title}", "pre_seed_button_text": "Razumijem. Prikaži mi moj pristupni izraz", - "pre_seed_description": "Na sljedećoj ćete stranici vidjeti niz ${words} riječi. Radi se o Vašem jedinstvenom i tajnom pristupnom izrazu koji je ujedno i JEDINI način na koji možete oporaviti svoj novčanik u slučaju gubitka ili kvara. VAŠA je odgovornost zapisati ga te pohraniti na sigurno mjesto izvan Cake Wallet aplikacije.", + "pre_seed_description": "Na sljedećoj stranici vidjet ćete niz riječi. Ovo je vaše jedinstveno i privatno sjeme i to je jedini način da povratite novčanik u slučaju gubitka ili neispravnosti. Vaša je odgovornost zapisati je i pohraniti na sigurno mjesto izvan aplikacije za novčanik.", "pre_seed_title": "VAŽNO", "prepaid_cards": "Unaprijed plaćene kartice", "prevent_screenshots": "Spriječite snimke zaslona i snimanje zaslona", diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index 67ae614d6..a421287e9 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -543,7 +543,7 @@ "potential_scam": "Հնարավոր խաբեություն", "powered_by": "${title} կողմից ապահովված", "pre_seed_button_text": "Ես հասկանում եմ։ Ցույց տվեք իմ սերմը", - "pre_seed_description": "Հաջորդ էջում դուք կտեսնեք ${words} բառերի շարք։ Սա ձեր յուրահատուկ և գաղտնի սերմն է, որը ձեր դրամապանակը վերականգնելու միակ միջոցն է կորուստի կամ սխալ գործարքի դեպքում։ Դուք պատասխանատու եք այն գրառել և ապահով վայրում պահել Cake Wallet հավելվածից դուրս", + "pre_seed_description": "Հաջորդ էջում կտեսնեք մի շարք բառեր: Սա ձեր եզակի եւ մասնավոր սերմն է, եւ դա ձեր դրամապանակը վերականգնելու միակ միջոցն է կորստի կամ անսարքության դեպքում: Ձեր պարտականությունն է գրել այն եւ պահել այն անվտանգ վայրում, տորթի դրամապանակի հավելվածից դուրս:", "pre_seed_title": "ԿԱՐԵՎՈՐ", "prepaid_cards": "Նախավճարային քարտեր", "prevent_screenshots": "Կանխել էկրանի պատկերները և տեսագրությունը", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 96ccb39ac..3f9a16b5d 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -546,7 +546,7 @@ "potential_scam": "Penipuan potensial", "powered_by": "Didukung oleh ${title}", "pre_seed_button_text": "Saya mengerti. Tampilkan seed saya", - "pre_seed_description": "Di halaman berikutnya Anda akan melihat serangkaian kata ${words}. Ini adalah seed unik dan pribadi Anda dan itu SATU-SATUNYA cara untuk mengembalikan dompet Anda jika hilang atau rusak. Ini adalah TANGGUNG JAWAB Anda untuk menuliskannya dan menyimpan di tempat yang aman di luar aplikasi Cake Wallet.", + "pre_seed_description": "Di halaman berikutnya Anda akan melihat serangkaian kata. Ini adalah benih unik dan pribadi Anda dan ini adalah satu -satunya cara untuk memulihkan dompet Anda jika terjadi kehilangan atau kerusakan. Adalah tanggung jawab Anda untuk menuliskannya dan menyimpannya di tempat yang aman di luar aplikasi Cake Wallet.", "pre_seed_title": "PENTING", "prepaid_cards": "Kartu prabayar", "prevent_screenshots": "Cegah tangkapan layar dan perekaman layar", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 74728aeb0..dd52e5b9f 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -545,7 +545,7 @@ "potential_scam": "Potenziale truffa", "powered_by": "Sviluppato da ${title}", "pre_seed_button_text": "Ho capito. Mostrami il seme", - "pre_seed_description": "Nella pagina seguente ti sarà mostrata una serie di parole ${words}. Questo è il tuo seme unico e privato ed è l'UNICO modo per recuperare il tuo portafoglio in caso di perdita o malfunzionamento. E' TUA responsabilità trascriverlo e conservarlo in un posto sicuro fuori dall'app Cake Wallet.", + "pre_seed_description": "Nella pagina successiva vedrai una serie di parole. Questo è il tuo seme unico e privato ed è l'unico modo per recuperare il portafoglio in caso di perdita o malfunzionamento. È tua responsabilità scriverlo e archiviarlo in un posto sicuro al di fuori dell'app per il portafoglio Cake.", "pre_seed_title": "IMPORTANTE", "prepaid_cards": "Carte prepagata", "prevent_screenshots": "Impedisci screenshot e registrazione dello schermo", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 0bb56a964..a7e2e6737 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -545,7 +545,7 @@ "potential_scam": "潜在的な詐欺", "powered_by": "搭載 ${title}", "pre_seed_button_text": "わかります。 種を見せて", - "pre_seed_description": "次のページでは、一連の${words}語が表示されます。 これはあなたのユニークでプライベートなシードであり、紛失や誤動作が発生した場合にウォレットを回復する唯一の方法です。 それを書き留めて、Cake Wallet アプリの外の安全な場所に保管するのはあなたの責任です。", + "pre_seed_description": "次のページには、一連の単語が表示されます。これはあなたのユニークでプライベートな種であり、損失や誤動作の場合に財布を回復する唯一の方法です。それを書き留めて、ケーキウォレットアプリの外の安全な場所に保管するのはあなたの責任です。", "pre_seed_title": "重要", "prepaid_cards": "プリペイドカード", "prevent_screenshots": "スクリーンショットと画面録画を防止する", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 846c8f47b..bef6d359b 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -544,7 +544,7 @@ "potential_scam": "잠재적 사기", "powered_by": "에 의해 구동 ${title}", "pre_seed_button_text": "이해 했어요. 내 씨앗을 보여줘", - "pre_seed_description": "다음 페이지에서 ${words} 개의 단어를 볼 수 있습니다. 이것은 귀하의 고유하고 개인적인 시드이며 분실 또는 오작동시 지갑을 복구하는 유일한 방법입니다. 기록해두고 Cake Wallet 앱 외부의 안전한 장소에 보관하는 것은 귀하의 책임입니다.", + "pre_seed_description": "다음 페이지에는 일련의 단어가 표시됩니다. 이것은 독특하고 개인적인 씨앗이며 손실이나 오작동의 경우 지갑을 회수하는 유일한 방법입니다. 케이크 지갑 앱 외부의 안전한 장소에 그것을 적어두고 보관하는 것은 귀하의 책임입니다.", "pre_seed_title": "중대한", "prepaid_cards": "선불 카드", "prevent_screenshots": "스크린샷 및 화면 녹화 방지", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 31bed9bb1..648650b3a 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -544,7 +544,7 @@ "potential_scam": "အလားအလာရှိသောလိမ်လည်မှု", "powered_by": "${title} မှ ပံ့ပိုးပေးသည်", "pre_seed_button_text": "ကျွန်တော်နားလည်ပါတယ်။ ငါ့အမျိုးအနွယ်ကို ပြလော့", - "pre_seed_description": "နောက်စာမျက်နှာတွင် ${words} စကားလုံးများ အတွဲလိုက်ကို တွေ့ရပါမည်။ ၎င်းသည် သင်၏ထူးခြားပြီး သီးသန့်မျိုးစေ့ဖြစ်ပြီး ပျောက်ဆုံးခြင်း သို့မဟုတ် ချွတ်ယွင်းမှုရှိပါက သင့်ပိုက်ဆံအိတ်ကို ပြန်လည်ရယူရန် တစ်ခုတည်းသောနည်းလမ်းဖြစ်သည်။ ၎င်းကို Cake Wallet အက်ပ်၏အပြင်ဘက်တွင် လုံခြုံသောနေရာတွင် သိမ်းဆည်းရန်မှာ သင်၏တာဝန်ဖြစ်သည်။", + "pre_seed_description": "နောက်စာမျက်နှာမှာစကားလုံးတစ်လုံးကိုတွေ့ရလိမ့်မယ်။ ၎င်းသည်သင်၏ထူးခြားသည့်နှင့်ပုဂ္ဂလိကမျိုးစေ့ဖြစ်ပြီးဆုံးရှုံးမှုသို့မဟုတ်ချွတ်ယွင်းမှုကိစ္စတွင်သင်၏ပိုက်ဆံအိတ်ကိုပြန်လည်ရယူရန်တစ်ခုတည်းသောနည်းလမ်းဖြစ်သည်။ ၎င်းကိုရေးရန်နှင့်၎င်းကို Cake Wallet App အပြင်ဘက်တွင်လုံခြုံသောနေရာ၌သိမ်းဆည်းရန်သင်၏တာ 0 န်ဖြစ်သည်။", "pre_seed_title": "အရေးကြီးသည်။", "prepaid_cards": "ကြိုတင်ငွေဖြည့်ကဒ်များ", "prevent_screenshots": "ဖန်သားပြင်ဓာတ်ပုံများနှင့် မျက်နှာပြင်ရိုက်ကူးခြင်းကို တားဆီးပါ။", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index fab0f8bc7..4cd39fa40 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -544,7 +544,7 @@ "potential_scam": "Potentiële zwendel", "powered_by": "Aangedreven door ${title}", "pre_seed_button_text": "Ik begrijp het. Laat me mijn zaad zien", - "pre_seed_description": "Op de volgende pagina ziet u een reeks van ${words} woorden. Dit is uw unieke en persoonlijke zaadje en het is de ENIGE manier om uw portemonnee te herstellen in geval van verlies of storing. Het is JOUW verantwoordelijkheid om het op te schrijven en op een veilige plaats op te slaan buiten de Cake Wallet app.", + "pre_seed_description": "Op de volgende pagina ziet u een reeks woorden. Dit is uw unieke en privézaad en het is de enige manier om uw portemonnee te herstellen in geval van verlies of storing. Het is uw verantwoordelijkheid om het op te schrijven en op te slaan op een veilige plek buiten de cake -portemonnee -app.", "pre_seed_title": "BELANGRIJK", "prepaid_cards": "Prepaid-kaarten", "prevent_screenshots": "Voorkom screenshots en schermopname", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index ed700e274..5605603d9 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -544,7 +544,7 @@ "potential_scam": "Potencjalne oszustwo", "powered_by": "Obsługiwane przez ${title}", "pre_seed_button_text": "Rozumiem. Pokaż mi moją fraze seed", - "pre_seed_description": "Na następnej stronie zobaczysz serię ${words} słów. To jest Twoja unikalna i prywatna fraza seed i jest to JEDYNY sposób na odzyskanie portfela w przypadku utraty lub awarii telefonu. Twoim obowiązkiem jest zapisanie go i przechowywanie w bezpiecznym miejscu (np. na kartce w sejfie).", + "pre_seed_description": "Na następnej stronie zobaczysz serię słów. To jest twoje wyjątkowe i prywatne ziarno i jest to jedyny sposób na odzyskanie portfela w przypadku utraty lub awarii. Twoim obowiązkiem jest zapisanie go i przechowywanie w bezpiecznym miejscu poza aplikacją do portfela ciasta.", "pre_seed_title": "WAŻNE", "prepaid_cards": "Karty przedpłacone", "prevent_screenshots": "Zapobiegaj zrzutom ekranu i nagrywaniu ekranu", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index b61d97b83..390ab40b9 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -546,7 +546,7 @@ "potential_scam": "Golpe potencial", "powered_by": "Troca realizada por ${title}", "pre_seed_button_text": "Compreendo. Me mostre minha semente", - "pre_seed_description": "Na próxima página, você verá uma série de ${words} palavras. Esta é a sua semente única e privada e é a ÚNICA maneira de recuperar sua carteira em caso de perda ou mau funcionamento. É SUA responsabilidade anotá-lo e armazená-lo em um local seguro fora do aplicativo Cake Wallet.", + "pre_seed_description": "Na próxima página, você verá uma série de palavras. Esta é a sua semente única e privada e é a única maneira de recuperar sua carteira em caso de perda ou mau funcionamento. É sua responsabilidade anotar e armazená -lo em um local seguro fora do aplicativo de carteira de bolo.", "pre_seed_title": "IMPORTANTE", "prepaid_cards": "Cartões pré-pagos", "prevent_screenshots": "Evite capturas de tela e gravação de tela", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 773b65fb0..848c7dd57 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -545,7 +545,7 @@ "potential_scam": "Потенциальная афера", "powered_by": "Используя ${title}", "pre_seed_button_text": "Понятно. Покажите мнемоническую фразу", - "pre_seed_description": "На следующей странице вы увидите серию из ${words} слов. Это ваша уникальная и личная мнемоническая фраза, и это ЕДИНСТВЕННЫЙ способ восстановить свой кошелек в случае потери или неисправности. ВАМ необходимо записать ее и хранить в надежном месте вне приложения Cake Wallet.", + "pre_seed_description": "На следующей странице вы увидите серию слов. Это ваше уникальное и частное семя, и это единственный способ восстановить ваш кошелек в случае потери или неисправности. Вы несете ответственность за то, чтобы записать его и хранить в безопасном месте за пределами приложения кошелька для торта.", "pre_seed_title": "ВАЖНО", "prepaid_cards": "Предоплаченные карты", "prevent_screenshots": "Предотвратить скриншоты и запись экрана", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index ff27d906b..e9063cb2a 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -544,7 +544,7 @@ "potential_scam": "การหลอกลวงที่มีศักยภาพ", "powered_by": "พัฒนาขึ้นโดย ${title}", "pre_seed_button_text": "ฉันเข้าใจ แสดง seed ของฉัน", - "pre_seed_description": "บนหน้าถัดไปคุณจะเห็นชุดของคำ ${words} คำ นี่คือ seed ของคุณที่ไม่ซ้ำใดๆ และเป็นความลับเพียงของคุณ และนี่คือเพียงวิธีเดียวที่จะกู้กระเป๋าของคุณในกรณีที่สูญหายหรือมีปัญหา มันเป็นความรับผิดชอบของคุณเพื่อเขียนมันลงบนกระดาษและจัดเก็บไว้ในที่ปลอดภัยนอกแอป Cake Wallet", + "pre_seed_description": "ในหน้าถัดไปคุณจะเห็นชุดคำ นี่คือเมล็ดพันธุ์ที่ไม่เหมือนใครและเป็นส่วนตัวของคุณและเป็นวิธีเดียวที่จะกู้คืนกระเป๋าเงินของคุณในกรณีที่สูญเสียหรือทำงานผิดปกติ มันเป็นความรับผิดชอบของคุณที่จะเขียนลงและเก็บไว้ในที่ปลอดภัยนอกแอพเค้กกระเป๋าเงิน", "pre_seed_title": "สำคัญ", "prepaid_cards": "บัตรเติมเงิน", "prevent_screenshots": "ป้องกันภาพหน้าจอและการบันทึกหน้าจอ", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 60aff4d05..a390b218a 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -544,7 +544,7 @@ "potential_scam": "Potensyal na scam", "powered_by": "Pinapagana ng ${title}", "pre_seed_button_text": "Naiintindihan ko. Ipakita sa akin ang aking binhi", - "pre_seed_description": "Sa susunod na pahina makikita mo ang isang serye ng mga ${words} na mga salita. Ito ang iyong natatangi at pribadong binhi at ito ang tanging paraan upang mabawi ang iyong pitaka kung sakaling mawala o madepektong paggawa. Responsibilidad mong isulat ito at itago ito sa isang ligtas na lugar sa labas ng cake wallet app.", + "pre_seed_description": "Sa susunod na pahina makikita mo ang isang serye ng mga salita. Ito ang iyong natatangi at pribadong binhi at ito ang tanging paraan upang mabawi ang iyong pitaka kung sakaling mawala o madepektong paggawa. Responsibilidad mong isulat ito at itago ito sa isang ligtas na lugar sa labas ng cake wallet app.", "pre_seed_title": "Mahalaga", "prepaid_cards": "Mga Prepaid Card", "prevent_screenshots": "Maiwasan ang mga screenshot at pag -record ng screen", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index d7502370d..c4bd16b24 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -544,7 +544,7 @@ "potential_scam": "Potansiyel aldatmaca", "powered_by": "${title} tarafından desteklenmektedir", "pre_seed_button_text": "Anladım. Bana tohumumu göster.", - "pre_seed_description": "Bir sonraki sayfada ${words} kelime göreceksin. Bu senin benzersiz ve özel tohumundur, kaybetmen veya silinmesi durumunda cüzdanını kurtarmanın TEK YOLUDUR. Bunu yazmak ve Cake Wallet uygulaması dışında güvenli bir yerde saklamak tamamen SENİN sorumluluğunda.", + "pre_seed_description": "Bir sonraki sayfada bir dizi kelime göreceksiniz. Bu sizin eşsiz ve özel tohumunuzdur ve kayıp veya arıza durumunda cüzdanınızı kurtarmanın tek yolu budur. Yazmak ve kek cüzdan uygulamasının dışında güvenli bir yerde saklamak sizin sorumluluğunuzdadır.", "pre_seed_title": "UYARI", "prepaid_cards": "Ön ödemeli kartlar", "prevent_screenshots": "Ekran görüntülerini ve ekran kaydını önleyin", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index c76166466..48039f0de 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -544,7 +544,7 @@ "potential_scam": "Потенційна афера", "powered_by": "Використовуючи ${title}", "pre_seed_button_text": "Зрозуміло. Покажіть мнемонічну фразу", - "pre_seed_description": "На наступній сторінці ви побачите серію з ${words} слів. Це ваша унікальна та приватна мнемонічна фраза, і це ЄДИНИЙ спосіб відновити ваш гаманець на випадок втрати або несправності. ВАМ необхідно записати її та зберігати в безпечному місці поза програмою Cake Wallet.", + "pre_seed_description": "На наступній сторінці ви побачите ряд слів. Це ваше унікальне та приватне насіння, і це єдиний спосіб відновити гаманець у разі втрати або несправності. Ви несете відповідальність записати його та зберігати в безпечному місці поза додатком для гаманця тортів.", "pre_seed_title": "ВАЖЛИВО", "prepaid_cards": "Передплачені картки", "prevent_screenshots": "Запобігати знімкам екрана та запису екрана", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index afc74b130..1582abdb6 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -546,7 +546,7 @@ "potential_scam": "ممکنہ گھوٹالہ", "powered_by": "${title} کے ذریعے تقویت یافتہ", "pre_seed_button_text": "میں سمجھتا ہوں۔ مجھے میرا بیج دکھاؤ", - "pre_seed_description": "اگلے صفحے پر آپ کو ${words} الفاظ کا ایک سلسلہ نظر آئے گا۔ یہ آپ کا انوکھا اور نجی بیج ہے اور یہ آپ کے بٹوے کو ضائع یا خرابی کی صورت میں بازیافت کرنے کا واحد طریقہ ہے۔ اسے لکھنا اور اسے کیک والیٹ ایپ سے باہر کسی محفوظ جگہ پر اسٹور کرنا آپ کی ذمہ داری ہے۔", + "pre_seed_description": "اگلے صفحے پر آپ کو الفاظ کا ایک سلسلہ نظر آئے گا۔ یہ آپ کا انوکھا اور نجی بیج ہے اور نقصان یا خرابی کی صورت میں اپنے بٹوے کی بازیابی کا واحد راستہ ہے۔ آپ کی ذمہ داری ہے کہ وہ اسے لکھیں اور اسے کیک پرس ایپ کے باہر کسی محفوظ جگہ پر محفوظ کریں۔", "pre_seed_title": "اہم", "prepaid_cards": "پری پیڈ کارڈز", "prevent_screenshots": "اسکرین شاٹس اور اسکرین ریکارڈنگ کو روکیں۔", diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index 00065f93c..ef75afd57 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -542,7 +542,7 @@ "potential_scam": "Lừa đảo tiềm năng", "powered_by": "Được cung cấp bởi ${title}", "pre_seed_button_text": "Tôi hiểu. Hiển thị hạt giống của tôi", - "pre_seed_description": "Trên trang tiếp theo, bạn sẽ thấy một chuỗi ${words} từ. Đây là hạt giống riêng tư và duy nhất của bạn và là CÁCH DUY NHẤT để khôi phục ví của bạn trong trường hợp mất hoặc hỏng hóc. Đây là TRÁCH NHIỆM của bạn để ghi lại và lưu trữ nó ở một nơi an toàn ngoài ứng dụng Cake Wallet.", + "pre_seed_description": "Trên trang tiếp theo, bạn sẽ thấy một loạt các từ. Đây là hạt giống độc đáo và riêng tư của bạn và đó là cách duy nhất để phục hồi ví của bạn trong trường hợp mất hoặc trục trặc. Bạn có trách nhiệm viết nó ra và lưu trữ nó ở một nơi an toàn bên ngoài ứng dụng ví Cake.", "pre_seed_title": "QUAN TRỌNG", "prepaid_cards": "Thẻ trả trước", "prevent_screenshots": "Ngăn chặn ảnh chụp màn hình và ghi hình màn hình", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index ea1e9b2ad..5e4cc331c 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -545,7 +545,7 @@ "potential_scam": "Egan ti o ni agbara", "powered_by": "Láti ọwọ́ ${title}", "pre_seed_button_text": "Mo ti gbọ́. O fi hóró mi hàn mi", - "pre_seed_description": "Ẹ máa wo àwọn ọ̀rọ̀ ${words} lórí ojú tó ń bọ̀. Èyí ni hóró aládàáni yín tó kì í jọra. Ẹ lè fi í nìkan dá àpamọ́wọ́ yín padà sípò tí àṣìṣe tàbí ìbàjẹ́ bá ṣẹlẹ̀. Hóró yín ni ẹ gbọ́dọ̀ kọ sílẹ̀ àti pamọ́ síbí tó kò léwu níta Cake Wallet.", + "pre_seed_description": "Ni oju-iwe ti o tẹle iwọ yoo rii lẹsẹsẹ awọn ọrọ. Eyi jẹ irugbin alailẹgbẹ ati ti ikọkọ ati pe o jẹ ọna nikan lati bọsi apamọwọ rẹ ni ọran ti pipadanu tabi aisise. O jẹ ojuṣe rẹ lati kọ ọ silẹ ki o tọju rẹ ni aye ailewu ti ita ti app Fifun akara naa.", "pre_seed_title": "Ó TI ṢE PÀTÀKÌ", "prepaid_cards": "Awọn kaadi ti a ti sanwo", "prevent_screenshots": "Pese asapọ ti awọn ẹrọ eto aṣa", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 9e8885250..089d4bd41 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -544,7 +544,7 @@ "potential_scam": "潜在骗局", "powered_by": "Powered by ${title}", "pre_seed_button_text": "我明白。 查看种子", - "pre_seed_description": "在下一页上,您将看到${words}个文字。 这是您独有的种子,是丟失或出现故障时恢复钱包的唯一方法。 您有必须将其写下并储存在Cake Wallet应用程序以外的安全地方。", + "pre_seed_description": "在下一页上,您将看到一系列单词。这是您独特的私人种子,这是在损失或故障时恢复钱包的唯一方法。将其写下来并将其存储在蛋糕钱包应用程序外的安全地方是您的责任。", "pre_seed_title": "重要", "prepaid_cards": "预付费卡", "prevent_screenshots": "防止截屏和录屏", From b2d4698cdf51c33540ad88b98e5070ee5c353af0 Mon Sep 17 00:00:00 2001 From: Serhii Date: Thu, 17 Apr 2025 06:14:12 +0300 Subject: [PATCH 038/142] fix: bottom sheet stuck on swap (#2211) --- lib/exchange/trade_state.dart | 3 +++ .../exchange_trade/exchange_trade_page.dart | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/exchange/trade_state.dart b/lib/exchange/trade_state.dart index f63b41e5d..e1c4470b4 100644 --- a/lib/exchange/trade_state.dart +++ b/lib/exchange/trade_state.dart @@ -7,6 +7,7 @@ class TradeState extends EnumerableItem with Serializable { bool operator ==(Object other) => other is TradeState && other.raw == raw; static const pending = TradeState(raw: 'pending', title: 'Pending'); + static const awaiting = TradeState(raw: 'awaiting', title: 'Awaiting'); static const confirming = TradeState(raw: 'confirming', title: 'Confirming'); static const trading = TradeState(raw: 'trading', title: 'Trading'); static const traded = TradeState(raw: 'traded', title: 'Traded'); @@ -134,6 +135,8 @@ class TradeState extends EnumerableItem with Serializable { return success; case 'expired': return expired; + case 'awaiting': + return awaiting; default: throw Exception('Unexpected token: $raw in TradeState deserialize'); } diff --git a/lib/src/screens/exchange_trade/exchange_trade_page.dart b/lib/src/screens/exchange_trade/exchange_trade_page.dart index 277d3c838..f4c8d590f 100644 --- a/lib/src/screens/exchange_trade/exchange_trade_page.dart +++ b/lib/src/screens/exchange_trade/exchange_trade_page.dart @@ -205,6 +205,7 @@ class ExchangeTradeState extends State { ); } + BuildContext? dialogContext; BuildContext? loadingBottomSheetContext; void _setEffects() { @@ -213,7 +214,12 @@ class ExchangeTradeState extends State { } _exchangeStateReaction = reaction((_) => this.widget.exchangeTradeViewModel.sendViewModel.state, - (ExecutionState state) { + (ExecutionState state) async { + + if (dialogContext != null && dialogContext?.mounted == true) { + Navigator.of(dialogContext!).pop(); + } + if (state is! IsExecutingState && loadingBottomSheetContext != null && loadingBottomSheetContext!.mounted) { @@ -237,6 +243,13 @@ class ExchangeTradeState extends State { } if (state is IsExecutingState) { + // wait a bit to avoid showing the loading dialog if transaction is failed + await Future.delayed(const Duration(milliseconds: 300)); + final currentState = widget.exchangeTradeViewModel.sendViewModel.state; + if (currentState is ExecutedSuccessfullyState || currentState is FailureState) { + return; + } + WidgetsBinding.instance.addPostFrameCallback((_) { if (context.mounted) { showModalBottomSheet( From d9c01a5d07c2ce44064c2e9f2191ab31e93995ad Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 17 Apr 2025 07:51:03 +0100 Subject: [PATCH 039/142] fix(desktop-pin-code-issue): persist FocusNode so KeyboardListener works on macOS Previously, every rebuild created a new FocusNode, so KeyboardListener never held focus and missed key events on macOS. This change: - Moves the FocusNode into state and initializes it in initState - Requests focus once after the first frame - Disposes of the FocusNode in dispose - Removes the inline FocusNode creation from build --- lib/src/screens/pin_code/pin_code_widget.dart | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/lib/src/screens/pin_code/pin_code_widget.dart b/lib/src/screens/pin_code/pin_code_widget.dart index f6249576d..410349336 100644 --- a/lib/src/screens/pin_code/pin_code_widget.dart +++ b/lib/src/screens/pin_code/pin_code_widget.dart @@ -38,6 +38,7 @@ class PinCodeState extends State { static const fourPinLength = 4; final _gridViewKey = GlobalKey(); final _key = GlobalKey(); + late final FocusNode _focusNode; int pinLength; String pin; @@ -54,7 +55,17 @@ class PinCodeState extends State { pin = ''; title = S.current.enter_your_pin; _aspectRatio = 0; - WidgetsBinding.instance.addPostFrameCallback(_afterLayout); + _focusNode = FocusNode(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _focusNode.requestFocus(); + _afterLayout(_); + }); + } + + @override + void dispose() { + _focusNode.dispose(); + super.dispose(); } void setTitle(String title) => setState(() => this.title = title); @@ -120,8 +131,8 @@ class PinCodeState extends State { ); return KeyboardListener( - focusNode: FocusNode(), - autofocus: true, + focusNode: _focusNode, + autofocus: false, onKeyEvent: (keyEvent) { if (keyEvent is KeyDownEvent) { if (keyEvent.logicalKey.keyLabel == "Backspace") { @@ -144,8 +155,7 @@ class PinCodeState extends State { style: TextStyle( fontSize: 20, fontWeight: FontWeight.w500, - color: - Theme.of(context).extension()!.titleColor)), + color: Theme.of(context).extension()!.titleColor)), Spacer(flex: 8), Container( width: 180, @@ -162,7 +172,9 @@ class PinCodeState extends State { shape: BoxShape.circle, color: isFilled ? Theme.of(context).extension()!.titleColor - : Theme.of(context).extension()!.indicatorsColor + : Theme.of(context) + .extension()! + .indicatorsColor .withOpacity(0.25), )); }), @@ -225,7 +237,8 @@ class PinCodeState extends State { child: TextButton( onPressed: () => _pop(), style: TextButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.background, + backgroundColor: + Theme.of(context).colorScheme.background, shape: CircleBorder(), ), child: deleteIconImage, @@ -250,7 +263,9 @@ class PinCodeState extends State { style: TextStyle( fontSize: 25.0, fontWeight: FontWeight.w600, - color: Theme.of(context).extension()!.titleColor)), + color: Theme.of(context) + .extension()! + .titleColor)), ), ); }), From fe435d4e3bd40ac2fd51e83b9db15f5b807e6fae Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 17 Apr 2025 08:09:53 +0100 Subject: [PATCH 040/142] fix(buy/sell-flow): add buy/sell toggle to DesktopExchangeCardsSection The desktop exchange screen was only showing the buy flow with no way to switch to sell. This change adds the missing toggle to the DesktopExchangeCardsSection and links it up in the buy/sell page --- lib/src/screens/buy/buy_sell_page.dart | 8 ++++++ .../desktop_exchange_cards_section.dart | 26 ++++++++++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/lib/src/screens/buy/buy_sell_page.dart b/lib/src/screens/buy/buy_sell_page.dart index 48334f439..df81c60ce 100644 --- a/lib/src/screens/buy/buy_sell_page.dart +++ b/lib/src/screens/buy/buy_sell_page.dart @@ -490,11 +490,19 @@ class BuySellPage extends BasePage { return DesktopExchangeCardsSection( firstExchangeCard: fiatExchangeCard, secondExchangeCard: cryptoExchangeCard, + onBuyTap: () => null, + onSellTap: () => + buySellViewModel.isBuyAction ? buySellViewModel.changeBuySellAction() : null, + isBuySellOption: true, ); } else { return DesktopExchangeCardsSection( firstExchangeCard: cryptoExchangeCard, secondExchangeCard: fiatExchangeCard, + onBuyTap: () => + !buySellViewModel.isBuyAction ? buySellViewModel.changeBuySellAction() : null, + onSellTap: () => null, + isBuySellOption: true, ); } }, diff --git a/lib/src/screens/exchange/widgets/desktop_exchange_cards_section.dart b/lib/src/screens/exchange/widgets/desktop_exchange_cards_section.dart index 0a97d7bad..5bc07091b 100644 --- a/lib/src/screens/exchange/widgets/desktop_exchange_cards_section.dart +++ b/lib/src/screens/exchange/widgets/desktop_exchange_cards_section.dart @@ -1,15 +1,22 @@ +import 'package:cake_wallet/src/screens/exchange/widgets/mobile_exchange_cards_section.dart'; import 'package:flutter/material.dart'; class DesktopExchangeCardsSection extends StatelessWidget { - final Widget firstExchangeCard; - final Widget secondExchangeCard; - const DesktopExchangeCardsSection({ Key? key, required this.firstExchangeCard, required this.secondExchangeCard, + this.isBuySellOption = false, + this.onBuyTap, + this.onSellTap, }) : super(key: key); + final Widget firstExchangeCard; + final Widget secondExchangeCard; + final bool isBuySellOption; + final VoidCallback? onBuyTap; + final VoidCallback? onSellTap; + @override Widget build(BuildContext context) { return FocusTraversalGroup( @@ -18,7 +25,18 @@ class DesktopExchangeCardsSection extends StatelessWidget { children: [ Padding( padding: EdgeInsets.only(top: 55, left: 24, right: 24), - child: firstExchangeCard, + child: Column( + children: [ + if (isBuySellOption) + Column( + children: [ + const SizedBox(height: 16), + BuySellOptionButtons(onBuyTap: onBuyTap, onSellTap: onSellTap), + ], + ), + firstExchangeCard, + ], + ), ), Padding( padding: EdgeInsets.only(top: 29, left: 24, right: 24), From f81957ed248f6752a572faf5f5ef1ab3983ce683 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Thu, 17 Apr 2025 20:36:33 +0200 Subject: [PATCH 041/142] minor ui fix [skip ci] --- .../screens/exchange_trade/exchange_trade_page.dart | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/src/screens/exchange_trade/exchange_trade_page.dart b/lib/src/screens/exchange_trade/exchange_trade_page.dart index 277d3c838..8d816878a 100644 --- a/lib/src/screens/exchange_trade/exchange_trade_page.dart +++ b/lib/src/screens/exchange_trade/exchange_trade_page.dart @@ -7,7 +7,6 @@ import 'package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_shee import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/themes/theme_base.dart'; -import 'dart:ui'; import 'package:cake_wallet/utils/request_review_handler.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:mobx/mobx.dart'; @@ -311,10 +310,12 @@ class ExchangeTradeState extends State { actionButtonKey: ValueKey('send_page_sent_dialog_ok_button_key'), actionButton: () { Navigator.of(bottomSheetContext).pop(); - Navigator.of(context).pushNamedAndRemoveUntil( - Routes.dashboard, - (route) => false, - ); + if (context.mounted) { + Navigator.of(context).pushNamedAndRemoveUntil( + Routes.dashboard, + (route) => false, + ); + } RequestReviewHandler.requestReview(); }); }, From 66e1745ad9550a857d5046ebec1c48735d5d001c Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Fri, 18 Apr 2025 04:47:53 +0200 Subject: [PATCH 042/142] more trace in minor error [skip ci] --- lib/buy/onramper/onramper_buy_provider.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/buy/onramper/onramper_buy_provider.dart b/lib/buy/onramper/onramper_buy_provider.dart index ac198b488..6b903342f 100644 --- a/lib/buy/onramper/onramper_buy_provider.dart +++ b/lib/buy/onramper/onramper_buy_provider.dart @@ -11,7 +11,6 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:flutter/material.dart'; @@ -242,7 +241,7 @@ class OnRamperBuyProvider extends BuyProvider { if (await canLaunchUrl(uri)) { await launchUrl(uri, mode: LaunchMode.externalApplication); } else { - throw Exception('Could not launch URL'); + throw Exception('Could not launch URL ${uri.toString()}'); } } @@ -276,7 +275,7 @@ class OnRamperBuyProvider extends BuyProvider { if (currency.tag != null) return '_' + _tagToNetwork(currency.tag!); - return '_' + (currency.fullName?.replaceAll(' ', '') ?? currency.title);; + return '_' + (currency.fullName?.replaceAll(' ', '') ?? currency.title); } String? normalizePaymentMethod(PaymentType paymentType) { From ffe1c115fab2154c7eed4a13a3eb7367db1c24c8 Mon Sep 17 00:00:00 2001 From: Serhii Date: Fri, 18 Apr 2025 15:53:22 +0300 Subject: [PATCH 043/142] Cw 1038 filter transaction popup not scrollable (#2207) * ui:make overflowing filter sections scrollable * Update pull_request_template.md --- .github/pull_request_template.md | 1 + .../dashboard/widgets/filter_widget.dart | 245 +++++++++++------- lib/src/widgets/alert_close_button.dart | 30 +-- 3 files changed, 164 insertions(+), 112 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 18ad16e4b..272f7bbee 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -11,3 +11,4 @@ Please include a summary of the changes and which issue is fixed / feature is ad - [ ] Format code - [ ] Look for code duplication - [ ] Clear naming for variables and methods +- [ ] Manual tests in accessibility mode (TalkBack on Android) passed diff --git a/lib/src/screens/dashboard/widgets/filter_widget.dart b/lib/src/screens/dashboard/widgets/filter_widget.dart index eaf00a1de..81f29b81c 100644 --- a/lib/src/screens/dashboard/widgets/filter_widget.dart +++ b/lib/src/screens/dashboard/widgets/filter_widget.dart @@ -1,123 +1,178 @@ +import 'package:cake_wallet/src/widgets/alert_background.dart'; +import 'package:cake_wallet/src/widgets/alert_close_button.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/filter_tile.dart'; import 'package:cake_wallet/src/widgets/section_divider.dart'; import 'package:cake_wallet/src/widgets/standard_checkbox.dart'; import 'package:cake_wallet/themes/extensions/menu_theme.dart'; -import 'package:cake_wallet/view_model/dashboard/dropdown_filter_item.dart'; -import 'package:cake_wallet/view_model/dashboard/dropdown_filter_item_widget.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/view_model/dashboard/filter_item.dart'; import 'package:flutter/material.dart'; -import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; - -//import 'package:date_range_picker/date_range_picker.dart' as date_rage_picker; import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart'; -class FilterWidget extends StatelessWidget { - FilterWidget({required this.filterItems}); +class FilterWidget extends StatefulWidget { + const FilterWidget({required this.filterItems, this.onClose, Key? key}) : super(key: key); final Map> filterItems; + final Function()? onClose; + + @override + _FilterWidgetState createState() => _FilterWidgetState(); +} + +class _FilterWidgetState extends State { + final ScrollController _scrollController = ScrollController(); + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { - const sectionDivider = const HorizontalSectionDivider(); - return PickerWrapperWidget( - children: [ - Padding( - padding: EdgeInsets.only(left: 24, right: 24, top: 24), - child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(24)), - child: Container( - color: Theme.of(context).extension()!.backgroundColor, - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: EdgeInsets.all(24.0), - child: Text( - S.of(context).filter_by, - style: TextStyle( - color: - Theme.of(context).extension()!.detailsTitlesColor, - fontSize: 16, - fontFamily: 'Lato', - decoration: TextDecoration.none, - ), - ), - ), - sectionDivider, - ListView.separated( - padding: EdgeInsets.zero, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: filterItems.length, - separatorBuilder: (context, _) => sectionDivider, - itemBuilder: (_, index1) { - final title = filterItems.keys.elementAt(index1); - final section = filterItems.values.elementAt(index1); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.only(top: 20, left: 24, right: 24), - child: Text( - title, - style: TextStyle( - color: Theme.of(context).extension()!.titleColor, - fontSize: 16, - fontFamily: 'Lato', - fontWeight: FontWeight.bold, - decoration: TextDecoration.none), - ), + return AlertBackground( + child: Column( + children: [ + const Expanded(child: SizedBox()), + Expanded( + flex: responsiveLayoutUtil.shouldRenderTabletUI ? 16 : 8, + child: LayoutBuilder( + builder: (context, constraints) { + double availableHeight = constraints.maxHeight; + return _buildFilterContent(context, availableHeight); + }, + ), + ), + Expanded( + child: AlertCloseButton( + key: const ValueKey('filter_wrapper_close_button_key'), + isPositioned: false, + onTap: widget.onClose, + ), + ), + const SizedBox(height: 24), + ], + ), + ); + } + + Widget _buildFilterContent(BuildContext context, double availableHeight) { + const sectionDivider = HorizontalSectionDivider(); + + const double totalHeaderHeight = 73; + const double filterTileMinHeight = 40; + double availableHeightForItems = availableHeight - totalHeaderHeight; + + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 24, right: 24, top: 24), + child: ClipRRect( + borderRadius: BorderRadius.circular(24), + child: Container( + color: Theme.of(context).extension()!.backgroundColor, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(24.0), + child: Text( + S.of(context).filter_by, + style: TextStyle( + color: Theme.of(context) + .extension()! + .detailsTitlesColor, + fontSize: 16, + fontFamily: 'Lato', + decoration: TextDecoration.none, ), - ListView.builder( - padding: EdgeInsets.symmetric(horizontal: 28.0), - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), + ), + ), + sectionDivider, + ListView.separated( + padding: EdgeInsets.zero, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: widget.filterItems.length, + separatorBuilder: (context, _) => sectionDivider, + itemBuilder: (_, index1) { + final title = widget.filterItems.keys.elementAt(index1); + final section = widget.filterItems.values.elementAt(index1); + + final double itemHeight = + availableHeightForItems / widget.filterItems.length; + + final isSectionScrollable = + (itemHeight < (section.length * filterTileMinHeight)); + + final Widget sectionListView = ListView.builder( + controller: isSectionScrollable ? _scrollController : null, + padding: const EdgeInsets.symmetric(horizontal: 28.0), + shrinkWrap: isSectionScrollable ? false : true, + physics: isSectionScrollable + ? const BouncingScrollPhysics() + : const NeverScrollableScrollPhysics(), itemCount: section.length, itemBuilder: (_, index2) { final item = section[index2]; - - if (item is DropdownFilterItem) { - return Padding( - padding: EdgeInsets.fromLTRB(8, 0, 8, 16), - child: Container( - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - width: 1.0, - color: Theme.of(context).extension()!.secondaryTextColor), - ), - ), - child: DropdownFilterList( - items: item.items, - caption: item.caption, - selectedItem: item.selectedItem, - onItemSelected: item.onItemSelected, - ), - ), - ); - } final content = Observer( - builder: (_) => StandardCheckbox( - value: item.value(), - caption: item.caption, - gradientBackground: true, - borderColor: Theme.of(context).dividerColor, - iconColor: Colors.white, - onChanged: (value) => item.onChanged(), - )); - return FilterTile(child: content); + builder: (_) => StandardCheckbox( + value: item.value(), + caption: item.caption, + gradientBackground: true, + borderColor: Theme.of(context).dividerColor, + iconColor: Colors.white, + onChanged: (value) => item.onChanged(), + ), + ); + return FilterTile( + child: content, + ); }, - ) - ], - ); - }, + ); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(top: 20, left: 24, right: 24), + child: Text( + title, + style: TextStyle( + color: Theme.of(context).extension()!.titleColor, + fontSize: 16, + fontFamily: 'Lato', + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + Container( + height: isSectionScrollable ? itemHeight - totalHeaderHeight : null, + child: isSectionScrollable + ? Scrollbar( + controller: _scrollController, + thumbVisibility: true, + child: sectionListView, + ) + : sectionListView, + ), + ], + ); + }, + ), + ], ), - ]), + ), ), ), - ) - ], + ], + ), ); } } diff --git a/lib/src/widgets/alert_close_button.dart b/lib/src/widgets/alert_close_button.dart index 6ef0bdaa5..30e37ef20 100644 --- a/lib/src/widgets/alert_close_button.dart +++ b/lib/src/widgets/alert_close_button.dart @@ -7,6 +7,7 @@ class AlertCloseButton extends StatelessWidget { this.image, this.bottom, this.onTap, + this.isPositioned = true, super.key, }); @@ -14,6 +15,7 @@ class AlertCloseButton extends StatelessWidget { final Image? image; final double? bottom; + final bool isPositioned; final closeButton = Image.asset( 'assets/images/close.png', @@ -22,24 +24,18 @@ class AlertCloseButton extends StatelessWidget { @override Widget build(BuildContext context) { - return Positioned( - bottom: bottom ?? 60, - child: GestureDetector( + final button = GestureDetector( onTap: onTap ?? () => Navigator.of(context).pop(), child: Semantics( - label: S.of(context).close, - button: true, - enabled: true, - child: Container( - height: 42, - width: 42, - decoration: BoxDecoration(color: Colors.white, shape: BoxShape.circle), - child: Center( - child: image ?? closeButton, - ), - ), - ), - ), - ); + label: S.of(context).close, + button: true, + enabled: true, + child: Container( + height: 42, + width: 42, + decoration: BoxDecoration(color: Colors.white, shape: BoxShape.circle), + child: Center(child: image ?? closeButton)))); + + return isPositioned ? Positioned(bottom: bottom ?? 60, child: button) : button; } } From 53efdced6861d967f6271ef41c631e4fafc7611a Mon Sep 17 00:00:00 2001 From: cyan Date: Sat, 19 Apr 2025 01:13:07 +0200 Subject: [PATCH 044/142] fix: more verbose errors in backups (#2196) * fix: more verbose errors in backups * show user friendly error, but prompt to send the full logs [skip ci] --------- Co-authored-by: Omar Hatem --- lib/view_model/restore_from_backup_view_model.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/view_model/restore_from_backup_view_model.dart b/lib/view_model/restore_from_backup_view_model.dart index a74beeaea..388782cb0 100644 --- a/lib/view_model/restore_from_backup_view_model.dart +++ b/lib/view_model/restore_from_backup_view_model.dart @@ -50,7 +50,7 @@ abstract class RestoreFromBackupViewModelBase with Store { state = FailureState('This is not a valid backup file, please make sure you selected the correct backup file'); } else { state = FailureState('Failed to restore backup, please try again'); - ExceptionHandler.onError(FlutterErrorDetails(exception: e, stack: s, silent: true)); + ExceptionHandler.onError(FlutterErrorDetails(exception: e, stack: s)); } } From e09d9aadfa58be8fc734800bbfec1cb64fb3b72b Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Sat, 19 Apr 2025 01:13:37 +0200 Subject: [PATCH 045/142] - Improve code for backup cache (#2216) * - Improve code for backup cache - Backup all Cached values without manually adding each one * Exclude some cache keys from backup [skip ci] * Exclude some cache keys from backup [skip ci] --- lib/core/backup_service.dart | 241 ++++++----------------------------- 1 file changed, 37 insertions(+), 204 deletions(-) diff --git a/lib/core/backup_service.dart b/lib/core/backup_service.dart index 2af037d44..94599d5f2 100644 --- a/lib/core/backup_service.dart +++ b/lib/core/backup_service.dart @@ -1,6 +1,5 @@ import 'dart:convert'; import 'dart:io'; -import 'dart:typed_data'; import 'package:cake_wallet/core/secure_storage.dart'; import 'package:cake_wallet/entities/get_encryption_key.dart'; import 'package:cake_wallet/entities/transaction_description.dart'; @@ -164,6 +163,31 @@ class $BackupService { } final data = json.decode(preferencesFile.readAsStringSync()) as Map; + + try { // shouldn't throw an error but just in case, so it doesn't stop the backup restore + for (var entry in data.entries) { + String key = entry.key; + dynamic value = entry.value; + + // Check the type of the value and save accordingly + if (value is String) { + await sharedPreferences.setString(key, value); + } else if (value is int) { + await sharedPreferences.setInt(key, value); + } else if (value is double) { + await sharedPreferences.setDouble(key, value); + } else if (value is bool) { + await sharedPreferences.setBool(key, value); + } else if (value is List) { + await sharedPreferences.setStringList(key, value); + } else { + if (kDebugMode) { + printV('Skipping individual save for key "$key": Unsupported type (${value.runtimeType}). Value: $value'); + } + } + } + } catch (_) {} + String currentWalletName = data[PreferencesKey.currentWalletName] as String; int currentWalletType = data[PreferencesKey.currentWalletType] as int; @@ -175,151 +199,10 @@ class $BackupService { currentWalletType = serializeToInt(correctWallets.first.type); } - final currentNodeId = data[PreferencesKey.currentNodeIdKey] as int?; - final currentBalanceDisplayMode = data[PreferencesKey.currentBalanceDisplayModeKey] as int?; - final currentFiatCurrency = data[PreferencesKey.currentFiatCurrencyKey] as String?; - final shouldSaveRecipientAddress = data[PreferencesKey.shouldSaveRecipientAddressKey] as bool?; - final isAppSecure = data[PreferencesKey.isAppSecureKey] as bool?; - final disableTradeOption = data[PreferencesKey.disableTradeOption] as bool?; - final currentTransactionPriorityKeyLegacy = - data[PreferencesKey.currentTransactionPriorityKeyLegacy] as int?; - final currentBitcoinElectrumSererId = - data[PreferencesKey.currentBitcoinElectrumSererIdKey] as int?; - final currentLanguageCode = data[PreferencesKey.currentLanguageCode] as String?; - final displayActionListMode = data[PreferencesKey.displayActionListModeKey] as int?; - final fiatApiMode = data[PreferencesKey.currentFiatApiModeKey] as int?; - final currentTheme = data[PreferencesKey.currentTheme] as int?; - final exchangeStatus = data[PreferencesKey.exchangeStatusKey] as int?; - final currentDefaultSettingsMigrationVersion = - data[PreferencesKey.currentDefaultSettingsMigrationVersion] as int?; - final moneroTransactionPriority = data[PreferencesKey.moneroTransactionPriority] as int?; - final bitcoinTransactionPriority = data[PreferencesKey.bitcoinTransactionPriority] as int?; - final sortBalanceTokensBy = data[PreferencesKey.sortBalanceBy] as int?; - final pinNativeTokenAtTop = data[PreferencesKey.pinNativeTokenAtTop] as bool?; - final useEtherscan = data[PreferencesKey.useEtherscan] as bool?; - final defaultNanoRep = data[PreferencesKey.defaultNanoRep] as String?; - final defaultBananoRep = data[PreferencesKey.defaultBananoRep] as String?; - final lookupsTwitter = data[PreferencesKey.lookupsTwitter] as bool?; - final lookupsMastodon = data[PreferencesKey.lookupsMastodon] as bool?; - final lookupsYatService = data[PreferencesKey.lookupsYatService] as bool?; - final lookupsUnstoppableDomains = data[PreferencesKey.lookupsUnstoppableDomains] as bool?; - final lookupsOpenAlias = data[PreferencesKey.lookupsOpenAlias] as bool?; - final lookupsENS = data[PreferencesKey.lookupsENS] as bool?; - final lookupsWellKnown = data[PreferencesKey.lookupsWellKnown] as bool?; - final syncAll = data[PreferencesKey.syncAllKey] as bool?; - final syncMode = data[PreferencesKey.syncModeKey] as int?; - final autoGenerateSubaddressStatus = - data[PreferencesKey.autoGenerateSubaddressStatusKey] as int?; - - await sharedPreferences.setString(PreferencesKey.currentWalletName, currentWalletName); - - if (currentNodeId != null) - await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, currentNodeId); - - if (currentBalanceDisplayMode != null) - await sharedPreferences.setInt( - PreferencesKey.currentBalanceDisplayModeKey, currentBalanceDisplayMode); - - await sharedPreferences.setInt(PreferencesKey.currentWalletType, currentWalletType); - - if (currentFiatCurrency != null) - await sharedPreferences.setString( - PreferencesKey.currentFiatCurrencyKey, currentFiatCurrency); - - if (shouldSaveRecipientAddress != null) - await sharedPreferences.setBool( - PreferencesKey.shouldSaveRecipientAddressKey, shouldSaveRecipientAddress); - - if (isAppSecure != null) - await sharedPreferences.setBool(PreferencesKey.isAppSecureKey, isAppSecure); - - if (disableTradeOption != null) - await sharedPreferences.setBool(PreferencesKey.disableTradeOption, disableTradeOption); - - if (currentTransactionPriorityKeyLegacy != null) - await sharedPreferences.setInt( - PreferencesKey.currentTransactionPriorityKeyLegacy, currentTransactionPriorityKeyLegacy); - - if (currentBitcoinElectrumSererId != null) - await sharedPreferences.setInt( - PreferencesKey.currentBitcoinElectrumSererIdKey, currentBitcoinElectrumSererId); - - if (currentLanguageCode != null) - await sharedPreferences.setString(PreferencesKey.currentLanguageCode, currentLanguageCode); - - if (displayActionListMode != null) - await sharedPreferences.setInt( - PreferencesKey.displayActionListModeKey, displayActionListMode); - - if (fiatApiMode != null) - await sharedPreferences.setInt(PreferencesKey.currentFiatApiModeKey, fiatApiMode); - if (autoGenerateSubaddressStatus != null) - await sharedPreferences.setInt( - PreferencesKey.autoGenerateSubaddressStatusKey, autoGenerateSubaddressStatus); - - if (currentTheme != null && DeviceInfo.instance.isMobile) { - await sharedPreferences.setInt(PreferencesKey.currentTheme, currentTheme); - // enforce dark theme on desktop platforms until the design is ready: - } else if (DeviceInfo.instance.isDesktop) { + if (DeviceInfo.instance.isDesktop) { await sharedPreferences.setInt(PreferencesKey.currentTheme, ThemeList.darkTheme.raw); } - if (exchangeStatus != null) - await sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, exchangeStatus); - - if (currentDefaultSettingsMigrationVersion != null) - await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, - currentDefaultSettingsMigrationVersion); - - if (moneroTransactionPriority != null) - await sharedPreferences.setInt( - PreferencesKey.moneroTransactionPriority, moneroTransactionPriority); - - if (bitcoinTransactionPriority != null) - await sharedPreferences.setInt( - PreferencesKey.bitcoinTransactionPriority, bitcoinTransactionPriority); - - if (sortBalanceTokensBy != null) - await sharedPreferences.setInt(PreferencesKey.sortBalanceBy, sortBalanceTokensBy); - - if (pinNativeTokenAtTop != null) - await sharedPreferences.setBool(PreferencesKey.pinNativeTokenAtTop, pinNativeTokenAtTop); - - if (useEtherscan != null) - await sharedPreferences.setBool(PreferencesKey.useEtherscan, useEtherscan); - - if (defaultNanoRep != null) - await sharedPreferences.setString(PreferencesKey.defaultNanoRep, defaultNanoRep); - - if (defaultBananoRep != null) - await sharedPreferences.setString(PreferencesKey.defaultBananoRep, defaultBananoRep); - - if (syncAll != null) await sharedPreferences.setBool(PreferencesKey.syncAllKey, syncAll); - if (lookupsTwitter != null) - await sharedPreferences.setBool(PreferencesKey.lookupsTwitter, lookupsTwitter); - - if (lookupsMastodon != null) - await sharedPreferences.setBool(PreferencesKey.lookupsMastodon, lookupsMastodon); - - if (lookupsYatService != null) - await sharedPreferences.setBool(PreferencesKey.lookupsYatService, lookupsYatService); - - if (lookupsUnstoppableDomains != null) - await sharedPreferences.setBool( - PreferencesKey.lookupsUnstoppableDomains, lookupsUnstoppableDomains); - - if (lookupsOpenAlias != null) - await sharedPreferences.setBool(PreferencesKey.lookupsOpenAlias, lookupsOpenAlias); - - if (lookupsENS != null) await sharedPreferences.setBool(PreferencesKey.lookupsENS, lookupsENS); - - if (lookupsWellKnown != null) - await sharedPreferences.setBool(PreferencesKey.lookupsWellKnown, lookupsWellKnown); - - if (syncAll != null) await sharedPreferences.setBool(PreferencesKey.syncAllKey, syncAll); - - if (syncMode != null) await sharedPreferences.setInt(PreferencesKey.syncModeKey, syncMode); - await preferencesFile.delete(); } @@ -378,11 +261,6 @@ class $BackupService { await keyService.saveWalletPassword(walletName: name, password: password); } - @Deprecated('Use v2 instead') - Future _exportKeychainDumpV1(String password, - {required String nonce, String keychainSalt = secrets.backupKeychainSalt}) async => - throw Exception('Deprecated'); - Future exportKeychainDumpV2(String password, {String keychainSalt = secrets.backupKeychainSalt}) async { final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); @@ -402,59 +280,18 @@ class $BackupService { return encrypted; } + static const List _excludedPrefsKeys = [ + PreferencesKey.currentPinLength, + PreferencesKey.showCameraConsent, + PreferencesKey.lastSeenAppVersion, + PreferencesKey.failedTotpTokenTrials, + ]; + Future exportPreferencesJSON() async { - final preferences = { - PreferencesKey.currentWalletName: - sharedPreferences.getString(PreferencesKey.currentWalletName), - PreferencesKey.currentNodeIdKey: sharedPreferences.getInt(PreferencesKey.currentNodeIdKey), - PreferencesKey.currentBalanceDisplayModeKey: - sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey), - PreferencesKey.currentWalletType: sharedPreferences.getInt(PreferencesKey.currentWalletType), - PreferencesKey.currentFiatCurrencyKey: - sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey), - PreferencesKey.shouldSaveRecipientAddressKey: - sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey), - PreferencesKey.disableTradeOption: sharedPreferences.getBool(PreferencesKey.disableTradeOption), - PreferencesKey.currentTransactionPriorityKeyLegacy: - sharedPreferences.getInt(PreferencesKey.currentTransactionPriorityKeyLegacy), - PreferencesKey.currentBitcoinElectrumSererIdKey: - sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey), - PreferencesKey.currentLanguageCode: - sharedPreferences.getString(PreferencesKey.currentLanguageCode), - PreferencesKey.displayActionListModeKey: - sharedPreferences.getInt(PreferencesKey.displayActionListModeKey), - PreferencesKey.currentTheme: sharedPreferences.getInt(PreferencesKey.currentTheme), - PreferencesKey.exchangeStatusKey: sharedPreferences.getInt(PreferencesKey.exchangeStatusKey), - PreferencesKey.currentDefaultSettingsMigrationVersion: - sharedPreferences.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion), - PreferencesKey.bitcoinTransactionPriority: - sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority), - PreferencesKey.moneroTransactionPriority: - sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority), - PreferencesKey.currentFiatApiModeKey: - sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey), - PreferencesKey.sortBalanceBy: sharedPreferences.getInt(PreferencesKey.sortBalanceBy), - PreferencesKey.pinNativeTokenAtTop: - sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop), - PreferencesKey.useEtherscan: sharedPreferences.getBool(PreferencesKey.useEtherscan), - PreferencesKey.defaultNanoRep: sharedPreferences.getString(PreferencesKey.defaultNanoRep), - PreferencesKey.defaultBananoRep: - sharedPreferences.getString(PreferencesKey.defaultBananoRep), - PreferencesKey.lookupsTwitter: sharedPreferences.getBool(PreferencesKey.lookupsTwitter), - PreferencesKey.lookupsMastodon: sharedPreferences.getBool(PreferencesKey.lookupsMastodon), - PreferencesKey.lookupsYatService: - sharedPreferences.getBool(PreferencesKey.lookupsYatService), - PreferencesKey.lookupsUnstoppableDomains: - sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains), - PreferencesKey.lookupsOpenAlias: sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias), - PreferencesKey.lookupsENS: sharedPreferences.getBool(PreferencesKey.lookupsENS), - PreferencesKey.lookupsWellKnown: - sharedPreferences.getBool(PreferencesKey.lookupsWellKnown), - PreferencesKey.syncModeKey: sharedPreferences.getInt(PreferencesKey.syncModeKey), - PreferencesKey.syncAllKey: sharedPreferences.getBool(PreferencesKey.syncAllKey), - PreferencesKey.autoGenerateSubaddressStatusKey: - sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey), - }; + final preferences = {}; + sharedPreferences.getKeys().forEach((key) => preferences[key] = sharedPreferences.get(key)); + + _excludedPrefsKeys.forEach((key) => preferences.remove(key)); return json.encode(preferences); } @@ -466,10 +303,6 @@ class $BackupService { return Uint8List.fromList(bytes); } - @Deprecated('Use v2 instead') - Future _encryptV1(Uint8List data, String secretKeySource, String nonceBase64) async => - throw Exception('Deprecated'); - Future _decryptV1(Uint8List data, String secretKeySource, String nonceBase64, {int macLength = 16}) async { final secretKeyHash = await Cryptography.instance.sha256().hash(utf8.encode(secretKeySource)); From 3e25be6dcf283219ceee852d056356ef2dde098a Mon Sep 17 00:00:00 2001 From: cyan Date: Tue, 22 Apr 2025 21:41:31 +0200 Subject: [PATCH 046/142] fix: allow bakcups to be created even if one of the wallets is corrupted (#2221) --- lib/core/backup_service.dart | 20 ++++++++++++++------ tool/configure.dart | 8 ++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/lib/core/backup_service.dart b/lib/core/backup_service.dart index 94599d5f2..385598060 100644 --- a/lib/core/backup_service.dart +++ b/lib/core/backup_service.dart @@ -265,16 +265,24 @@ class $BackupService { {String keychainSalt = secrets.backupKeychainSalt}) async { final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); final wallets = await Future.wait(walletInfoSource.values.map((walletInfo) async { - return { - 'name': walletInfo.name, - 'type': walletInfo.type.toString(), - 'password': await keyService.getWalletPassword(walletName: walletInfo.name) - }; + try { + return { + 'name': walletInfo.name, + 'type': walletInfo.type.toString(), + 'password': await keyService.getWalletPassword(walletName: walletInfo.name) + }; + } catch (e) { + return { + 'name': walletInfo.name, + 'type': walletInfo.type.toString(), + 'password': '' + }; + } })); final backupPasswordKey = generateStoreKeyFor(key: SecretStoreKey.backupPassword); final backupPassword = await _secureStorage.read(key: backupPasswordKey); final data = utf8.encode( - json.encode({'wallets': wallets, backupPasswordKey: backupPassword})); + json.encode({'wallets': wallets, backupPasswordKey: backupPassword, '_all': await _secureStorage.readAll()})); final encrypted = await _encryptV2(Uint8List.fromList(data), '$keychainSalt$password'); return encrypted; diff --git a/tool/configure.dart b/tool/configure.dart index 4711373b7..2a29195c6 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -1629,6 +1629,7 @@ abstract class SecureStorage { Future delete({required String key}); // Legacy Future readNoIOptions({required String key}); + Future> readAll(); }"""; const defaultSecureStorage = """ class DefaultSecureStorage extends SecureStorage { @@ -1667,6 +1668,11 @@ class DefaultSecureStorage extends SecureStorage { iOptions: useNoIOptions ? IOSOptions() : null, ); } + + @override + Future> readAll() async { + return await _secureStorage.readAll(); + } }"""; const fakeSecureStorage = """ class FakeSecureStorage extends SecureStorage { @@ -1678,6 +1684,8 @@ class FakeSecureStorage extends SecureStorage { Future delete({required String key}) async {} @override Future readNoIOptions({required String key}) async => null; + @override + Future> readAll() async => {}; }"""; final outputFile = File(secureStoragePath); final header = hasFlutterSecureStorage From 6b95b7c6ac6094961df267f48f5ced4ed3ef540b Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Tue, 22 Apr 2025 23:56:29 +0200 Subject: [PATCH 047/142] Add fallback for tx status [skip ci] --- lib/exchange/trade_state.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/exchange/trade_state.dart b/lib/exchange/trade_state.dart index e1c4470b4..d09751604 100644 --- a/lib/exchange/trade_state.dart +++ b/lib/exchange/trade_state.dart @@ -41,8 +41,8 @@ class TradeState extends EnumerableItem with Serializable { static const exchanging = TradeState(raw: 'exchanging', title: 'Exchanging'); static const sending = TradeState(raw: 'sending', title: 'Sending'); static const success = TradeState(raw: 'success', title: 'Success'); - static TradeState deserialize({required String raw}) { + static TradeState deserialize({required String raw}) { switch (raw) { case '1': return unpaid; @@ -138,7 +138,7 @@ class TradeState extends EnumerableItem with Serializable { case 'awaiting': return awaiting; default: - throw Exception('Unexpected token: $raw in TradeState deserialize'); + return TradeState(raw: raw, title: raw); } } From 698f0199d04166a855f706293b6a50529bac54a9 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Wed, 23 Apr 2025 12:19:59 +0200 Subject: [PATCH 048/142] Add the code to enable removing Haven wallets (#2222) --- lib/di.dart | 4 +- lib/haven/cw_haven.dart | 362 +++++----------------------------------- 2 files changed, 49 insertions(+), 317 deletions(-) diff --git a/lib/di.dart b/lib/di.dart index 6354a0c0f..27636c606 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -33,6 +33,7 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/hardware_wallet/require_hardware_wallet_connection.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart'; +import 'package:cake_wallet/haven/cw_haven.dart'; import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart'; import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart'; import 'package:cake_wallet/src/screens/settings/background_sync_page.dart'; @@ -1115,8 +1116,9 @@ Future setup({ return zano!.createZanoWalletService(_walletInfoSource); case WalletType.decred: return decred!.createDecredWalletService(_walletInfoSource, _unspentCoinsInfoSource); - case WalletType.none: case WalletType.haven: + return HavenWalletService(_walletInfoSource); + case WalletType.none: throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService'); } }); diff --git a/lib/haven/cw_haven.dart b/lib/haven/cw_haven.dart index c54e47eb4..b69e733c3 100644 --- a/lib/haven/cw_haven.dart +++ b/lib/haven/cw_haven.dart @@ -1,348 +1,78 @@ -part of 'haven.dart'; +import 'dart:io'; -class CWHavenAccountList extends HavenAccountList { - CWHavenAccountList(this._wallet); +import 'package:cw_core/balance.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/transaction_history.dart'; +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:hive/hive.dart'; - final Object _wallet; +class HavenWalletService extends WalletService { + final Box walletInfoSource; + + HavenWalletService(this.walletInfoSource); @override - @computed - ObservableList get accounts { - final havenWallet = _wallet as HavenWallet; - final accounts = havenWallet.walletAddresses.accountList.accounts - .map((acc) => Account(id: acc.id, label: acc.label)) - .toList(); - return ObservableList.of(accounts); - } + WalletType getType() => WalletType.haven; @override - void update(Object wallet) { - final havenWallet = wallet as HavenWallet; - havenWallet.walletAddresses.accountList.update(); - } + Future remove(String wallet) async { + final path = await pathForWalletDir(name: wallet, type: WalletType.haven); - @override - void refresh(Object wallet) { - final havenWallet = wallet as HavenWallet; - havenWallet.walletAddresses.accountList.refresh(); - } + final file = Directory(path); + final isExist = file.existsSync(); - @override - List getAll(Object wallet) { - final havenWallet = wallet as HavenWallet; - return havenWallet.walletAddresses.accountList - .getAll() - .map((acc) => Account(id: acc.id, label: acc.label)) - .toList(); - } - - @override - Future addAccount(Object wallet, {required String label}) async { - final havenWallet = wallet as HavenWallet; - await havenWallet.walletAddresses.accountList.addAccount(label: label); - } - - @override - Future setLabelAccount(Object wallet, - {required int accountIndex, required String label}) async { - final havenWallet = wallet as HavenWallet; - await havenWallet.walletAddresses.accountList - .setLabelAccount(accountIndex: accountIndex, label: label); - } -} - -class CWHavenSubaddressList extends MoneroSubaddressList { - CWHavenSubaddressList(this._wallet); - - final Object _wallet; - - @override - @computed - ObservableList get subaddresses { - final havenWallet = _wallet as HavenWallet; - final subAddresses = havenWallet.walletAddresses.subaddressList.subaddresses - .map((sub) => Subaddress(id: sub.id, address: sub.address, label: sub.label)) - .toList(); - return ObservableList.of(subAddresses); - } - - @override - void update(Object wallet, {required int accountIndex}) { - final havenWallet = wallet as HavenWallet; - havenWallet.walletAddresses.subaddressList.update(accountIndex: accountIndex); - } - - @override - void refresh(Object wallet, {required int accountIndex}) { - final havenWallet = wallet as HavenWallet; - havenWallet.walletAddresses.subaddressList.refresh(accountIndex: accountIndex); - } - - @override - List getAll(Object wallet) { - final havenWallet = wallet as HavenWallet; - return havenWallet.walletAddresses.subaddressList - .getAll() - .map((sub) => Subaddress(id: sub.id, label: sub.label, address: sub.address)) - .toList(); - } - - @override - Future addSubaddress(Object wallet, - {required int accountIndex, required String label}) async { - final havenWallet = wallet as HavenWallet; - await havenWallet.walletAddresses.subaddressList - .addSubaddress(accountIndex: accountIndex, label: label); - } - - @override - Future setLabelSubaddress(Object wallet, - {required int accountIndex, required int addressIndex, required String label}) async { - final havenWallet = wallet as HavenWallet; - await havenWallet.walletAddresses.subaddressList - .setLabelSubaddress(accountIndex: accountIndex, addressIndex: addressIndex, label: label); - } -} - -class CWHavenWalletDetails extends HavenWalletDetails { - CWHavenWalletDetails(this._wallet); - - final Object _wallet; - - @computed - @override - Account get account { - final havenWallet = _wallet as HavenWallet; - final acc = havenWallet.walletAddresses.account as monero_account.Account; - return Account(id: acc.id, label: acc.label); - } - - @computed - @override - HavenBalance get balance { - final havenWallet = _wallet as HavenWallet; - final balance = havenWallet.balance; - throw Exception('Unimplemented'); - //return HavenBalance( - // fullBalance: balance.fullBalance, - // unlockedBalance: balance.unlockedBalance); - } -} - -class CWHaven extends Haven { - @override - HavenAccountList getAccountList(Object wallet) { - return CWHavenAccountList(wallet); - } - - @override - MoneroSubaddressList getSubaddressList(Object wallet) { - return CWHavenSubaddressList(wallet); - } - - @override - TransactionHistoryBase getTransactionHistory(Object wallet) { - final havenWallet = wallet as HavenWallet; - return havenWallet.transactionHistory; - } - - @override - HavenWalletDetails getMoneroWalletDetails(Object wallet) { - return CWHavenWalletDetails(wallet); - } - - @override - int getHeightByDate({required DateTime date}) => getHavenHeightByDate(date: date); - - @override - Future getCurrentHeight() => getHavenCurrentHeight(); - - @override - TransactionPriority getDefaultTransactionPriority() { - return MoneroTransactionPriority.automatic; - } - - @override - TransactionPriority deserializeMoneroTransactionPriority({required int raw}) { - return MoneroTransactionPriority.deserialize(raw: raw); - } - - @override - List getTransactionPriorities() { - return MoneroTransactionPriority.all; - } - - @override - List getMoneroWordList(String language) { - switch (language.toLowerCase()) { - case 'english': - return EnglishMnemonics.words; - case 'chinese (simplified)': - return ChineseSimplifiedMnemonics.words; - case 'dutch': - return DutchMnemonics.words; - case 'german': - return GermanMnemonics.words; - case 'japanese': - return JapaneseMnemonics.words; - case 'portuguese': - return PortugueseMnemonics.words; - case 'russian': - return RussianMnemonics.words; - case 'spanish': - return SpanishMnemonics.words; - case 'french': - return FrenchMnemonics.words; - case 'italian': - return ItalianMnemonics.words; - default: - return EnglishMnemonics.words; + if (isExist) { + await file.delete(recursive: true); } + + final walletInfo = walletInfoSource.values + .firstWhere((info) => info.id == WalletBase.idFor(wallet, getType())); + await walletInfoSource.delete(walletInfo.key); } @override - WalletCredentials createHavenRestoreWalletFromKeysCredentials( - {required String name, - required String spendKey, - required String viewKey, - required String address, - required String password, - required String language, - required int height}) { - return HavenRestoreWalletFromKeysCredentials( - name: name, - spendKey: spendKey, - viewKey: viewKey, - address: address, - password: password, - language: language, - height: height); + Future, TransactionInfo>> create( + WalletCredentials credentials, + {bool? isTestnet}) { + throw UnimplementedError(); } @override - WalletCredentials createHavenRestoreWalletFromSeedCredentials( - {required String name, - required String password, - required int height, - required String mnemonic}) { - return HavenRestoreWalletFromSeedCredentials( - name: name, password: password, height: height, mnemonic: mnemonic); + Future isWalletExit(String name) { + throw UnimplementedError(); } @override - WalletCredentials createHavenNewWalletCredentials( - {required String name, required String language, String? password}) { - return HavenNewWalletCredentials(name: name, password: password, language: language); + Future, TransactionInfo>> openWallet( + String name, String password) { + throw UnimplementedError(); } @override - Map getKeys(Object wallet) { - final havenWallet = wallet as HavenWallet; - final keys = havenWallet.keys; - return { - 'privateSpendKey': keys.privateSpendKey, - 'privateViewKey': keys.privateViewKey, - 'publicSpendKey': keys.publicSpendKey, - 'publicViewKey': keys.publicViewKey - }; + Future rename(String currentName, String password, String newName) { + throw UnimplementedError(); } @override - Object createHavenTransactionCreationCredentials( - {required List outputs, - required TransactionPriority priority, - required String assetType}) { - return HavenTransactionCreationCredentials( - outputs: outputs - .map((out) => OutputInfo( - fiatAmount: out.fiatAmount, - cryptoAmount: out.cryptoAmount, - address: out.address, - note: out.note, - sendAll: out.sendAll, - extractedAddress: out.extractedAddress, - isParsedAddress: out.isParsedAddress, - formattedCryptoAmount: out.formattedCryptoAmount)) - .toList(), - priority: priority as MoneroTransactionPriority, - assetType: assetType); + Future, TransactionInfo>> + restoreFromHardwareWallet(WalletCredentials credentials) { + throw UnimplementedError(); } @override - String formatterMoneroAmountToString({required int amount}) { - return moneroAmountToString(amount: amount); + Future, TransactionInfo>> + restoreFromKeys(WalletCredentials credentials, {bool? isTestnet}) { + throw UnimplementedError(); } @override - double formatterMoneroAmountToDouble({required int amount}) { - return moneroAmountToDouble(amount: amount); + Future, TransactionInfo>> + restoreFromSeed(WalletCredentials credentials, {bool? isTestnet}) { + throw UnimplementedError(); } - - @override - int formatterMoneroParseAmount({required String amount}) { - return moneroParseAmount(amount: amount); - } - - @override - Account getCurrentAccount(Object wallet) { - final havenWallet = wallet as HavenWallet; - final acc = havenWallet.walletAddresses.account as monero_account.Account; - return Account(id: acc.id, label: acc.label); - } - - @override - void setCurrentAccount(Object wallet, int id, String label) { - final havenWallet = wallet as HavenWallet; - havenWallet.walletAddresses.account = monero_account.Account(id: id, label: label); - } - - @override - void onStartup() { - monero_wallet_api.onStartup(); - } - - @override - int getTransactionInfoAccountId(TransactionInfo tx) { - final havenTransactionInfo = tx as HavenTransactionInfo; - return havenTransactionInfo.accountIndex; - } - - @override - Future backupHavenSeeds(Box havenSeedStore) async { - final walletInfoSource = await CakeHive.openBox(WalletInfo.boxName); - final wallets = walletInfoSource.values - .where((element) => element.type == WalletType.haven); - for (var w in wallets) { - final walletService = HavenWalletService(walletInfoSource); - final flutterSecureStorage = secureStorageShared; - final keyService = KeyService(flutterSecureStorage); - final password = await keyService.getWalletPassword(walletName: w.name); - final wallet = await walletService.openWallet(w.name, password); - await havenSeedStore.add(HavenSeedStore(id: wallet.id, seed: wallet.seed)); - wallet.close(); - } - await havenSeedStore.flush(); - } - - @override - WalletService createHavenWalletService(Box walletInfoSource) { - return HavenWalletService(walletInfoSource); - } - - @override - String getTransactionAddress(Object wallet, int accountIndex, int addressIndex) { - final havenWallet = wallet as HavenWallet; - return havenWallet.getTransactionAddress(accountIndex, addressIndex); - } - - @override - CryptoCurrency assetOfTransaction(TransactionInfo tx) { - final transaction = tx as HavenTransactionInfo; - final asset = CryptoCurrency.fromString(transaction.assetType); - return asset; - } - - @override - List getAssetRate() => - getRate().map((rate) => AssetRate(rate.getAssetType(), rate.getRate())).toList(); } From 1b04619c2a5d39749bab209f3d1473345bc57e06 Mon Sep 17 00:00:00 2001 From: David Adegoke <64401859+Blazebrain@users.noreply.github.com> Date: Wed, 23 Apr 2025 23:57:18 +0100 Subject: [PATCH 049/142] CW-1035: Solana New Fixes(Cont.) (#2206) * fix: Error while restoring Solana Wallet with PrivateKey * fix: Enhance Solana Error Messages, especially for ATA Creation Errors * - Optimize Solana Transaction History, now more smoother and faster - fix bug with transactions history not being displayed in real time until next reload * fix: Resolve Solana Issue from Github where user transaction showed amount and fee as the same --- cw_solana/lib/solana_client.dart | 98 ++++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 25 deletions(-) diff --git a/cw_solana/lib/solana_client.dart b/cw_solana/lib/solana_client.dart index 858fb25d4..b157367e6 100644 --- a/cw_solana/lib/solana_client.dart +++ b/cw_solana/lib/solana_client.dart @@ -171,37 +171,85 @@ class SolanaWalletClient { if (programId == SystemProgramConst.programId) { // For native solana transactions - if (instruction.accounts.length < 2) continue; - final senderIndex = instruction.accounts[0]; - final receiverIndex = instruction.accounts[1]; - sender = message.accountKeys[senderIndex].address; - receiver = message.accountKeys[receiverIndex].address; + if (txResponse.version == TransactionType.legacy) { + // For legacy transfers, the fee payer (index 0) is the sender. + sender = message.accountKeys[0].address; - final feeForTx = fee / SolanaUtils.lamportsPerSol; + final senderPreBalance = meta.preBalances[0]; + final senderPostBalance = meta.postBalances[0]; + final feeForTx = fee / SolanaUtils.lamportsPerSol; - final preBalances = meta.preBalances; - final postBalances = meta.postBalances; + // The loss on the sender's account would include both the transfer amount and the fee. + // So we would subtract the fee to calculate the actual amount that was transferred (in lamports). + final transferLamports = (senderPreBalance - senderPostBalance) - BigInt.from(fee); - final amountInString = - (((preBalances[senderIndex] - postBalances[senderIndex]) / BigInt.from(1e9)) - .toDouble() - - feeForTx) - .toStringAsFixed(6); + // Next, we attempt to find the receiver by comparing the balance changes. + // (The index 0 is for the sender so we skip it.) + bool foundReceiver = false; + for (int i = 1; i < meta.preBalances.length; i++) { + // The increase in balance on the receiver account should correspond to the transfer amount we calculated earlieer. + final pre = meta.preBalances[i]; + final post = meta.postBalances[i]; + if ((post - pre) == transferLamports) { + receiver = message.accountKeys[i].address; + foundReceiver = true; + break; + } + } - final amount = double.parse(amountInString); + if (!foundReceiver) { + // Optionally (and rarely), if no account shows the exact expected change, + // we set the receiver address to unknown. + receiver = "unknown"; + } - return SolanaTransactionModel( - isOutgoingTx: sender == walletAddress, - from: sender, - to: receiver, - id: signature, - amount: amount.abs(), - programId: SystemProgramConst.programId.address, - tokenSymbol: 'SOL', - blockTimeInInt: blockTime?.toInt() ?? 0, - fee: feeForTx, - ); + final amount = transferLamports / BigInt.from(1e9); + + return SolanaTransactionModel( + isOutgoingTx: sender == walletAddress, + from: sender, + to: receiver, + id: signature, + amount: amount.abs(), + programId: SystemProgramConst.programId.address, + tokenSymbol: 'SOL', + blockTimeInInt: blockTime?.toInt() ?? 0, + fee: feeForTx, + ); + } else { + if (instruction.accounts.length < 2) continue; + final senderIndex = instruction.accounts[0]; + final receiverIndex = instruction.accounts[1]; + + sender = message.accountKeys[senderIndex].address; + receiver = message.accountKeys[receiverIndex].address; + + final feeForTx = fee / SolanaUtils.lamportsPerSol; + + final preBalances = meta.preBalances; + final postBalances = meta.postBalances; + + final amountInString = + (((preBalances[senderIndex] - postBalances[senderIndex]) / BigInt.from(1e9)) + .toDouble() - + feeForTx) + .toStringAsFixed(6); + + final amount = double.parse(amountInString); + + return SolanaTransactionModel( + isOutgoingTx: sender == walletAddress, + from: sender, + to: receiver, + id: signature, + amount: amount.abs(), + programId: SystemProgramConst.programId.address, + tokenSymbol: 'SOL', + blockTimeInInt: blockTime?.toInt() ?? 0, + fee: feeForTx, + ); + } } else if (programId == SPLTokenProgramConst.tokenProgramId) { // For SPL Token transactions if (instruction.accounts.length < 2) continue; From 526796e2d7a6ed3b843ff9b8292f5fcb07e9af07 Mon Sep 17 00:00:00 2001 From: Serhii Date: Thu, 24 Apr 2025 02:08:14 +0300 Subject: [PATCH 050/142] fix: use correct navigator for dialog (#2223) --- lib/src/screens/unspent_coins/unspent_coins_list_page.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/screens/unspent_coins/unspent_coins_list_page.dart b/lib/src/screens/unspent_coins/unspent_coins_list_page.dart index d44107939..43e019360 100644 --- a/lib/src/screens/unspent_coins/unspent_coins_list_page.dart +++ b/lib/src/screens/unspent_coins/unspent_coins_list_page.dart @@ -97,6 +97,7 @@ class UnspentCoinsListFormState extends State { void _showSavingDataAlert() { showDialog( context: context, + useRootNavigator: false, builder: (BuildContext context) { return AlertWithNoAction( alertContent: 'Updating, please wait…', From e6c9cf54fbf0e8b6d171cc11309a098312beff02 Mon Sep 17 00:00:00 2001 From: David Adegoke <64401859+Blazebrain@users.noreply.github.com> Date: Thu, 24 Apr 2025 17:39:58 +0100 Subject: [PATCH 051/142] Explore-Reown-For-WalletConnect (#2156) * Reown WalletConnect Implementation - Setup for Solana Chain - Setup for EVM chain - Enable signing messages and transactions on Solana Chain - Fix issues related to signinig of transactions on Solana chain - Decode mesage to human readable format - WIP * Reown WalletConnect Implementation Continuation: - Add readable text for signedTypedDatav4 and sendTransaction event requests - Modify styling for bottomsheetModel - Switch minimum deployment target to 13.0 - Gracefully handle errors - Fix issues discovered while testing across dApps * chore: ReownWalletConnectImplementation (Cont.) - Remove unneeded code and files - Rename files to match classes and vice versa - Add maven jitpack repository for reown.com/yttrium repo - Add better folder structuring to entire feature - Add localization for all feature related texts - Add testnet and devnet access for Solana Chain Service - Add a better error message for Moralis NFT fetch * feat: - Add field to enter wc uri directly - Handle bottomsheet not coming up after initial display - Remove unneeded print statements - Remove double display of success popup for session proposal bottomsheets * fix: - Add visual feedback to WC RelayClient Errors - Adjust theme for bottomSheets * Testing a theroy * Temporarily add flags back * Remove added flags and update proguard rules * fix(android): update proguard-rules.pro and drop minify flags (#2199) Co-authored-by: Omar Hatem * fix: Switch bottomsheet result from signOne to signAll --------- Co-authored-by: cyan Co-authored-by: Omar Hatem --- android/app/build.gradle | 1 - android/app/proguard-rules.pro | 95 ++- android/build.gradle | 1 + cw_solana/lib/solana_client.dart | 1 + ios/Podfile | 4 +- ios/Runner.xcodeproj/project.pbxproj | 6 +- .../chain_service/chain_service.dart | 5 - .../chain_service/eth/evm_chain_service.dart | 304 --------- .../solana/entities/solana_sign_message.dart | 28 - .../entities/solana_sign_transaction.dart | 106 --- .../chain_service/solana/solana_chain_id.dart | 30 - .../solana/solana_chain_service.dart | 170 ----- .../wallet_connect/eth_transaction_model.dart | 60 -- .../models/auth_request_model.dart | 16 - .../models/chain_key_model.dart | 16 - .../models/session_request_model.dart | 14 - .../wallet_connect/web3wallet_service.dart | 416 ------------ lib/di.dart | 23 +- .../on_authentication_state_change.dart | 4 +- lib/reactions/wallet_connect.dart | 19 +- lib/router.dart | 4 +- lib/src/screens/dashboard/dashboard_page.dart | 4 +- .../desktop_sidebar_wrapper.dart | 4 +- .../models/bottom_sheet_queue_item_model.dart | 2 + .../models/wc_connection_model.dart} | 6 +- .../services/bottom_sheet_service.dart} | 28 +- .../chain_service/eth/evm_chain_id.dart | 4 +- .../chain_service/eth/evm_chain_service.dart | 581 ++++++++++++++++ .../eth/evm_supported_methods.dart | 31 + .../chain_service/solana/solana_chain_id.dart | 22 + .../solana/solana_chain_service.dart | 287 ++++++++ .../solana/solana_supported_methods.dart | 16 + .../services/key_service/chain_key_model.dart | 37 ++ .../wallet_connect_key_service.dart | 2 +- .../services/walletkit_service.dart | 623 ++++++++++++++++++ .../wallet_connect/utils/eth_utils.dart | 88 +++ .../wallet_connect/utils/method_utils.dart | 139 ++++ .../utils/namespace_model_builder.dart | 101 +-- .../wallet_connect/utils/string_parsing.dart | 14 - .../wc_connections_listing_view.dart | 73 +- .../wc_pairing_detail_page.dart | 92 ++- .../bottom_sheet_listener_widget.dart | 100 +++ .../bottom_sheet_message_display_widget.dart} | 5 +- .../widgets/connection_request_widget.dart | 180 ----- .../enter_wallet_connect_uri_widget.dart | 23 +- .../widgets/modals/bottom_sheet_listener.dart | 64 -- .../widgets/modals/web3_request_modal.dart | 48 -- ...et.dart => wc_connection_item_widget.dart} | 12 +- .../widgets/wc_connection_request_widget.dart | 100 +++ ..._widget.dart => wc_connection_widget.dart} | 18 +- ...idget.dart => wc_pairing_item_widget.dart} | 7 +- .../widgets/wc_request_widget.dart | 79 +++ .../wc_session_auth_request_widget.dart | 61 ++ .../widgets/wc_verify_context_widget.dart | 137 ++++ lib/store/app_store.dart | 9 +- lib/view_model/dashboard/nft_view_model.dart | 7 +- pubspec_base.yaml | 6 +- res/values/strings_ar.arb | 27 + res/values/strings_bg.arb | 27 + res/values/strings_cs.arb | 27 + res/values/strings_de.arb | 29 +- res/values/strings_en.arb | 27 + res/values/strings_es.arb | 27 + res/values/strings_fr.arb | 27 + res/values/strings_ha.arb | 27 + res/values/strings_hi.arb | 27 + res/values/strings_hr.arb | 27 + res/values/strings_hy.arb | 27 + res/values/strings_id.arb | 27 + res/values/strings_it.arb | 27 + res/values/strings_ja.arb | 27 + res/values/strings_ko.arb | 27 + res/values/strings_my.arb | 27 + res/values/strings_nl.arb | 27 + res/values/strings_pl.arb | 27 + res/values/strings_pt.arb | 27 + res/values/strings_ru.arb | 27 + res/values/strings_th.arb | 27 + res/values/strings_tl.arb | 27 + res/values/strings_tr.arb | 27 + res/values/strings_uk.arb | 27 + res/values/strings_ur.arb | 27 + res/values/strings_vi.arb | 27 + res/values/strings_yo.arb | 27 + res/values/strings_zh.arb | 27 + 85 files changed, 3453 insertions(+), 1638 deletions(-) delete mode 100644 lib/core/wallet_connect/chain_service/chain_service.dart delete mode 100644 lib/core/wallet_connect/chain_service/eth/evm_chain_service.dart delete mode 100644 lib/core/wallet_connect/chain_service/solana/entities/solana_sign_message.dart delete mode 100644 lib/core/wallet_connect/chain_service/solana/entities/solana_sign_transaction.dart delete mode 100644 lib/core/wallet_connect/chain_service/solana/solana_chain_id.dart delete mode 100644 lib/core/wallet_connect/chain_service/solana/solana_chain_service.dart delete mode 100644 lib/core/wallet_connect/eth_transaction_model.dart delete mode 100644 lib/core/wallet_connect/models/auth_request_model.dart delete mode 100644 lib/core/wallet_connect/models/chain_key_model.dart delete mode 100644 lib/core/wallet_connect/models/session_request_model.dart delete mode 100644 lib/core/wallet_connect/web3wallet_service.dart rename lib/{core => src/screens}/wallet_connect/models/bottom_sheet_queue_item_model.dart (90%) rename lib/{core/wallet_connect/models/connection_model.dart => src/screens/wallet_connect/models/wc_connection_model.dart} (60%) rename lib/{core/wallet_connect/wc_bottom_sheet_service.dart => src/screens/wallet_connect/services/bottom_sheet_service.dart} (53%) rename lib/{core/wallet_connect => src/screens/wallet_connect/services}/chain_service/eth/evm_chain_id.dart (78%) create mode 100644 lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart create mode 100644 lib/src/screens/wallet_connect/services/chain_service/eth/evm_supported_methods.dart create mode 100644 lib/src/screens/wallet_connect/services/chain_service/solana/solana_chain_id.dart create mode 100644 lib/src/screens/wallet_connect/services/chain_service/solana/solana_chain_service.dart create mode 100644 lib/src/screens/wallet_connect/services/chain_service/solana/solana_supported_methods.dart create mode 100644 lib/src/screens/wallet_connect/services/key_service/chain_key_model.dart rename lib/{core/wallet_connect => src/screens/wallet_connect/services/key_service}/wallet_connect_key_service.dart (96%) create mode 100644 lib/src/screens/wallet_connect/services/walletkit_service.dart create mode 100644 lib/src/screens/wallet_connect/utils/eth_utils.dart create mode 100644 lib/src/screens/wallet_connect/utils/method_utils.dart create mode 100644 lib/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_listener_widget.dart rename lib/src/screens/wallet_connect/widgets/{message_display_widget.dart => bottom_sheet/bottom_sheet_message_display_widget.dart} (81%) delete mode 100644 lib/src/screens/wallet_connect/widgets/connection_request_widget.dart delete mode 100644 lib/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart delete mode 100644 lib/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart rename lib/src/screens/wallet_connect/widgets/{connection_item_widget.dart => wc_connection_item_widget.dart} (88%) create mode 100644 lib/src/screens/wallet_connect/widgets/wc_connection_request_widget.dart rename lib/src/screens/wallet_connect/widgets/{connection_widget.dart => wc_connection_widget.dart} (61%) rename lib/src/screens/wallet_connect/widgets/{pairing_item_widget.dart => wc_pairing_item_widget.dart} (91%) create mode 100644 lib/src/screens/wallet_connect/widgets/wc_request_widget.dart create mode 100644 lib/src/screens/wallet_connect/widgets/wc_session_auth_request_widget.dart create mode 100644 lib/src/screens/wallet_connect/widgets/wc_verify_context_widget.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 6c299c929..c5a80bcea 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -73,7 +73,6 @@ android { buildTypes { release { signingConfig signingConfigs.release - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index 921ee4d4c..a733bae9e 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -6,4 +6,97 @@ -keep class io.flutter.** { *; } -keep class io.flutter.plugins.** { *; } -dontwarn io.flutter.embedding.** --dontwarn com.google.android.play.core.splitcompat.SplitCompatApplication \ No newline at end of file +-dontwarn com.google.android.play.core.splitcompat.SplitCompatApplication + +# start reown +-dontwarn com.github.luben.zstd.BufferPool +-dontwarn com.github.luben.zstd.ZstdInputStream +-dontwarn com.github.luben.zstd.ZstdOutputStream +-dontwarn com.google.api.client.http.GenericUrl +-dontwarn com.google.api.client.http.HttpHeaders +-dontwarn com.google.api.client.http.HttpRequest +-dontwarn com.google.api.client.http.HttpRequestFactory +-dontwarn com.google.api.client.http.HttpResponse +-dontwarn com.google.api.client.http.HttpTransport +-dontwarn com.google.api.client.http.javanet.NetHttpTransport$Builder +-dontwarn com.google.api.client.http.javanet.NetHttpTransport +-dontwarn java.awt.Color +-dontwarn java.awt.Dimension +-dontwarn java.awt.Graphics2D +-dontwarn java.awt.Graphics +-dontwarn java.awt.Image +-dontwarn java.awt.Point +-dontwarn java.awt.Polygon +-dontwarn java.awt.Shape +-dontwarn java.awt.color.ColorSpace +-dontwarn java.awt.geom.AffineTransform +-dontwarn java.awt.image.BufferedImage +-dontwarn java.awt.image.ColorModel +-dontwarn java.awt.image.ComponentColorModel +-dontwarn java.awt.image.ComponentSampleModel +-dontwarn java.awt.image.DataBuffer +-dontwarn java.awt.image.DataBufferByte +-dontwarn java.awt.image.DataBufferInt +-dontwarn java.awt.image.DataBufferUShort +-dontwarn java.awt.image.ImageObserver +-dontwarn java.awt.image.MultiPixelPackedSampleModel +-dontwarn java.awt.image.Raster +-dontwarn java.awt.image.RenderedImage +-dontwarn java.awt.image.SampleModel +-dontwarn java.awt.image.SinglePixelPackedSampleModel +-dontwarn java.awt.image.WritableRaster +-dontwarn java.beans.BeanInfo +-dontwarn java.beans.FeatureDescriptor +-dontwarn java.beans.IntrospectionException +-dontwarn java.beans.Introspector +-dontwarn java.beans.PropertyDescriptor +-dontwarn java.lang.reflect.InaccessibleObjectException +-dontwarn javax.imageio.IIOImage +-dontwarn javax.imageio.ImageIO +-dontwarn javax.imageio.ImageWriteParam +-dontwarn javax.imageio.ImageWriter +-dontwarn javax.imageio.metadata.IIOMetadata +-dontwarn javax.imageio.stream.ImageOutputStream +-dontwarn javax.swing.JComponent +-dontwarn javax.swing.JFileChooser +-dontwarn javax.swing.JFrame +-dontwarn javax.swing.JPanel +-dontwarn javax.swing.ProgressMonitor +-dontwarn javax.swing.SwingUtilities +-dontwarn org.brotli.dec.BrotliInputStream +-dontwarn org.joda.time.Instant +-dontwarn org.objectweb.asm.AnnotationVisitor +-dontwarn org.objectweb.asm.Attribute +-dontwarn org.objectweb.asm.ClassReader +-dontwarn org.objectweb.asm.ClassVisitor +-dontwarn org.objectweb.asm.FieldVisitor +-dontwarn org.objectweb.asm.Label +-dontwarn org.objectweb.asm.MethodVisitor +-dontwarn org.objectweb.asm.Type +-dontwarn org.tukaani.xz.ARMOptions +-dontwarn org.tukaani.xz.ARMThumbOptions +-dontwarn org.tukaani.xz.DeltaOptions +-dontwarn org.tukaani.xz.FilterOptions +-dontwarn org.tukaani.xz.FinishableOutputStream +-dontwarn org.tukaani.xz.FinishableWrapperOutputStream +-dontwarn org.tukaani.xz.IA64Options +-dontwarn org.tukaani.xz.LZMA2InputStream +-dontwarn org.tukaani.xz.LZMA2Options +-dontwarn org.tukaani.xz.LZMAInputStream +-dontwarn org.tukaani.xz.LZMAOutputStream +-dontwarn org.tukaani.xz.MemoryLimitException +-dontwarn org.tukaani.xz.PowerPCOptions +-dontwarn org.tukaani.xz.SPARCOptions +-dontwarn org.tukaani.xz.SingleXZInputStream +-dontwarn org.tukaani.xz.UnsupportedOptionsException +-dontwarn org.tukaani.xz.X86Options +-dontwarn org.tukaani.xz.XZ +-dontwarn org.tukaani.xz.XZInputStream +-dontwarn org.tukaani.xz.XZOutputStream +-dontwarn us.hebi.matlab.mat.ejml.Mat5Ejml +-dontwarn us.hebi.matlab.mat.format.Mat5 +-dontwarn us.hebi.matlab.mat.format.Mat5File +-dontwarn us.hebi.matlab.mat.types.Array +-dontwarn us.hebi.matlab.mat.types.MatFile$Entry +-dontwarn us.hebi.matlab.mat.types.MatFile +# end reown \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index bc157bd1a..d42aa24b4 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,6 +2,7 @@ allprojects { repositories { google() mavenCentral() + maven { url "https://jitpack.io" } } } diff --git a/cw_solana/lib/solana_client.dart b/cw_solana/lib/solana_client.dart index b157367e6..05b0cec82 100644 --- a/cw_solana/lib/solana_client.dart +++ b/cw_solana/lib/solana_client.dart @@ -890,6 +890,7 @@ class SolanaWalletClient { }) async { /// Sign the transaction with the owner's private key. final ownerSignature = ownerPrivateKey.sign(transaction.serializeMessage()); + transaction.addSignature(ownerPrivateKey.publicKey().toAddress(), ownerSignature); /// Serialize the transaction. diff --git a/ios/Podfile b/ios/Podfile index f0a0721a6..cf24cb605 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '12.0' +platform :ios, '13.0' source 'https://github.com/CocoaPods/Specs.git' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. @@ -43,7 +43,7 @@ post_install do |installer| flutter_additional_ios_build_settings(target) target.build_configurations.each do |config| - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0' + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ '$(inherited)', diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index d9ff6b66e..794305883 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -501,7 +501,7 @@ "$(PROJECT_DIR)", ); INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -649,7 +649,7 @@ "$(PROJECT_DIR)", ); INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -690,7 +690,7 @@ "$(PROJECT_DIR)", ); INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/lib/core/wallet_connect/chain_service/chain_service.dart b/lib/core/wallet_connect/chain_service/chain_service.dart deleted file mode 100644 index 1e3ce3efd..000000000 --- a/lib/core/wallet_connect/chain_service/chain_service.dart +++ /dev/null @@ -1,5 +0,0 @@ -abstract class ChainService { - String getNamespace(); - String getChainId(); - List getEvents(); -} diff --git a/lib/core/wallet_connect/chain_service/eth/evm_chain_service.dart b/lib/core/wallet_connect/chain_service/eth/evm_chain_service.dart deleted file mode 100644 index 6f3c8fa98..000000000 --- a/lib/core/wallet_connect/chain_service/eth/evm_chain_service.dart +++ /dev/null @@ -1,304 +0,0 @@ -import 'dart:convert'; -import 'dart:developer'; -import 'dart:typed_data'; - -import 'package:cake_wallet/core/wallet_connect/eth_transaction_model.dart'; -import 'package:cake_wallet/core/wallet_connect/chain_service/eth/evm_chain_id.dart'; -import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/reactions/wallet_connect.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart'; -import 'package:cake_wallet/store/app_store.dart'; -import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart'; -import 'package:cake_wallet/core/wallet_connect/models/connection_model.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_widget.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/utils/string_parsing.dart'; -import 'package:convert/convert.dart'; -import 'package:eth_sig_util/eth_sig_util.dart'; -import 'package:eth_sig_util/util/utils.dart'; -import 'package:http/http.dart' as http; -import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; -import 'package:web3dart/web3dart.dart'; -import '../chain_service.dart'; -import '../../wallet_connect_key_service.dart'; - -class EvmChainServiceImpl implements ChainService { - final AppStore appStore; - final BottomSheetService bottomSheetService; - final Web3Wallet wallet; - final WalletConnectKeyService wcKeyService; - - static const namespace = 'eip155'; - static const pSign = 'personal_sign'; - static const eSign = 'eth_sign'; - static const eSignTransaction = 'eth_signTransaction'; - static const eSignTypedData = 'eth_signTypedData_v4'; - static const eSendTransaction = 'eth_sendTransaction'; - - final EVMChainId reference; - - final Web3Client ethClient; - - EvmChainServiceImpl({ - required this.reference, - required this.appStore, - required this.wcKeyService, - required this.bottomSheetService, - required this.wallet, - Web3Client? web3Client, - }) : ethClient = web3Client ?? - Web3Client( - appStore.settingsStore.getCurrentNode(appStore.wallet!.type).uri.toString(), - http.Client(), - ) { - for (final String event in getEvents()) { - wallet.registerEventEmitter(chainId: getChainId(), event: event); - } - wallet.registerRequestHandler( - chainId: getChainId(), - method: pSign, - handler: personalSign, - ); - wallet.registerRequestHandler( - chainId: getChainId(), - method: eSign, - handler: ethSign, - ); - wallet.registerRequestHandler( - chainId: getChainId(), - method: eSignTransaction, - handler: ethSignTransaction, - ); - wallet.registerRequestHandler( - chainId: getChainId(), - method: eSendTransaction, - handler: ethSignTransaction, - ); - wallet.registerRequestHandler( - chainId: getChainId(), - method: eSignTypedData, - handler: ethSignTypedData, - ); - } - - @override - String getNamespace() { - return namespace; - } - - @override - String getChainId() { - return reference.chain(); - } - - @override - List getEvents() { - return ['chainChanged', 'accountsChanged']; - } - - Future requestAuthorization(String? text) async { - // Show the bottom sheet - final bool? isApproved = await bottomSheetService.queueBottomSheet( - widget: Web3RequestModal( - child: ConnectionWidget( - title: S.current.signTransaction, - info: [ - ConnectionModel( - text: text, - ), - ], - ), - ), - ) as bool?; - - if (isApproved != null && isApproved == false) { - return 'User rejected signature'; - } - - return null; - } - - Future personalSign(String topic, dynamic parameters) async { - log('received personal sign request: $parameters'); - - final String message; - if (parameters[0] == null) { - message = ''; - } else { - message = parameters[0].toString().utf8Message; - } - - final String? authError = await requestAuthorization(message); - - if (authError != null) { - return authError; - } - - try { - // Load the private key - final List keys = wcKeyService - .getKeysForChain(appStore.wallet!); - - final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey); - - final String signature = hex.encode( - credentials.signPersonalMessageToUint8List(Uint8List.fromList(utf8.encode(message))), - ); - - return '0x$signature'; - } catch (e) { - log(e.toString()); - bottomSheetService.queueBottomSheet( - isModalDismissible: true, - widget: BottomSheetMessageDisplayWidget( - message: '${S.current.errorGettingCredentials} ${e.toString()}', - ), - ); - return 'Failed: Error while getting credentials'; - } - } - - Future ethSign(String topic, dynamic parameters) async { - log('received eth sign request: $parameters'); - - final String message; - if (parameters[1] == null) { - message = ''; - } else { - message = parameters[1].toString().utf8Message; - } - - final String? authError = await requestAuthorization(message); - if (authError != null) { - return authError; - } - - try { - // Load the private key - final List keys = wcKeyService - .getKeysForChain(appStore.wallet!); - - final EthPrivateKey credentials = EthPrivateKey.fromHex(keys[0].privateKey); - - final String signature = hex.encode( - credentials.signPersonalMessageToUint8List( - Uint8List.fromList(utf8.encode(message)), - chainId: getChainIdBasedOnWalletType(appStore.wallet!.type), - ), - ); - log(signature); - - return '0x$signature'; - } catch (e) { - log('error: ${e.toString()}'); - bottomSheetService.queueBottomSheet( - isModalDismissible: true, - widget: BottomSheetMessageDisplayWidget(message: '${S.current.error}: ${e.toString()}'), - ); - return 'Failed'; - } - } - - Future ethSignTransaction(String topic, dynamic parameters) async { - log('received eth sign transaction request: $parameters'); - - final paramsData = parameters[0] as Map; - - final message = _convertToReadable(paramsData); - - final String? authError = await requestAuthorization(message); - - if (authError != null) { - return authError; - } - - // Load the private key - final List keys = wcKeyService - .getKeysForChain(appStore.wallet!); - - final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey); - - WCEthereumTransactionModel ethTransaction = - WCEthereumTransactionModel.fromJson(parameters[0] as Map); - - final transaction = Transaction( - from: EthereumAddress.fromHex(ethTransaction.from), - to: EthereumAddress.fromHex(ethTransaction.to), - maxGas: ethTransaction.gasLimit != null ? int.tryParse(ethTransaction.gasLimit ?? "") : null, - gasPrice: ethTransaction.gasPrice != null - ? EtherAmount.inWei(BigInt.parse(ethTransaction.gasPrice ?? "")) - : null, - value: EtherAmount.inWei(BigInt.parse(ethTransaction.value)), - data: hexToBytes(ethTransaction.data ?? ""), - nonce: ethTransaction.nonce != null ? int.tryParse(ethTransaction.nonce ?? "") : null, - ); - - try { - final result = await ethClient.sendTransaction( - credentials, - transaction, - chainId: getChainIdBasedOnWalletType(appStore.wallet!.type), - ); - - log('Result: $result'); - - bottomSheetService.queueBottomSheet( - isModalDismissible: true, - widget: BottomSheetMessageDisplayWidget( - message: S.current.awaitDAppProcessing, - isError: false, - ), - ); - - return result; - } catch (e) { - log('An error has occurred while signing transaction: ${e.toString()}'); - bottomSheetService.queueBottomSheet( - isModalDismissible: true, - widget: BottomSheetMessageDisplayWidget( - message: '${S.current.errorSigningTransaction}: ${e.toString()}', - ), - ); - return 'Failed'; - } - } - - Future ethSignTypedData(String topic, dynamic parameters) async { - log('received eth sign typed data request: $parameters'); - final String? data = parameters[1] as String?; - - final String? authError = await requestAuthorization(data); - - if (authError != null) { - return authError; - } - - final List keys = wcKeyService - .getKeysForChain(appStore.wallet!); - - return EthSigUtil.signTypedData( - privateKey: keys[0].privateKey, - jsonData: data ?? '', - version: TypedDataVersion.V4, - ); - } - - String _convertToReadable(Map data) { - final tokenName = getTokenNameBasedOnWalletType(appStore.wallet!.type); - String gas = int.parse((data['gas'] as String).substring(2), radix: 16).toString(); - String value = data['value'] != null - ? (int.parse((data['value'] as String).substring(2), radix: 16) / 1e18).toString() + - ' $tokenName' - : '0 $tokenName'; - String from = data['from'] as String; - String to = data['to'] as String; - - return ''' - Gas: $gas\n - Value: $value\n - From: $from\n - To: $to - '''; - } -} diff --git a/lib/core/wallet_connect/chain_service/solana/entities/solana_sign_message.dart b/lib/core/wallet_connect/chain_service/solana/entities/solana_sign_message.dart deleted file mode 100644 index e462adbb5..000000000 --- a/lib/core/wallet_connect/chain_service/solana/entities/solana_sign_message.dart +++ /dev/null @@ -1,28 +0,0 @@ -class SolanaSignMessage { - final String pubkey; - final String message; - - SolanaSignMessage({ - required this.pubkey, - required this.message, - }); - - factory SolanaSignMessage.fromJson(Map json) { - return SolanaSignMessage( - pubkey: json['pubkey'] as String, - message: json['message'] as String, - ); - } - - Map toJson() { - return { - 'pubkey': pubkey, - 'message': message, - }; - } - - @override - String toString() { - return 'SolanaSignMessage(pubkey: $pubkey, message: $message)'; - } -} diff --git a/lib/core/wallet_connect/chain_service/solana/entities/solana_sign_transaction.dart b/lib/core/wallet_connect/chain_service/solana/entities/solana_sign_transaction.dart deleted file mode 100644 index 2cdf4697e..000000000 --- a/lib/core/wallet_connect/chain_service/solana/entities/solana_sign_transaction.dart +++ /dev/null @@ -1,106 +0,0 @@ -class SolanaSignTransaction { - final String? feePayer; - final String? recentBlockhash; - final String transaction; - final List? instructions; - - SolanaSignTransaction({ - required this.feePayer, - required this.recentBlockhash, - required this.instructions, - required this.transaction, - }); - - factory SolanaSignTransaction.fromJson(Map json) { - return SolanaSignTransaction( - feePayer:json['feePayer'] !=null ? json['feePayer'] as String: null, - recentBlockhash: json['recentBlockhash']!=null? json['recentBlockhash'] as String: null, - instructions:json['instructions']!=null? (json['instructions'] as List) - .map((e) => SolanaInstruction.fromJson(e as Map)) - .toList(): null, - transaction: json['transaction'] as String, - ); - } - - Map toJson() { - return { - 'feePayer': feePayer, - 'recentBlockhash': recentBlockhash, - 'instructions': instructions, - 'transaction': transaction, - }; - } - - @override - String toString() { - return 'SolanaSignTransaction(feePayer: $feePayer, recentBlockhash: $recentBlockhash, instructions: $instructions, transaction: $transaction)'; - } -} - -class SolanaInstruction { - final String programId; - final List keys; - final List data; - - SolanaInstruction({ - required this.programId, - required this.keys, - required this.data, - }); - - factory SolanaInstruction.fromJson(Map json) { - return SolanaInstruction( - programId: json['programId'] as String, - keys: (json['keys'] as List) - .map((e) => SolanaKeyMetadata.fromJson(e as Map)) - .toList(), - data: (json['data'] as List).map((e) => e as int).toList(), - ); - } - - Map toJson() { - return { - 'programId': programId, - 'keys': keys, - 'data': data, - }; - } - - @override - String toString() { - return 'SolanaInstruction(programId: $programId, keys: $keys, data: $data)'; - } -} - -class SolanaKeyMetadata { - final String pubkey; - final bool isSigner; - final bool isWritable; - - SolanaKeyMetadata({ - required this.pubkey, - required this.isSigner, - required this.isWritable, - }); - - factory SolanaKeyMetadata.fromJson(Map json) { - return SolanaKeyMetadata( - pubkey: json['pubkey'] as String, - isSigner: json['isSigner'] as bool, - isWritable: json['isWritable'] as bool, - ); - } - - Map toJson() { - return { - 'pubkey': pubkey, - 'isSigner': isSigner, - 'isWritable': isWritable, - }; - } - - @override - String toString() { - return 'SolanaKeyMetadata(pubkey: $pubkey, isSigner: $isSigner, isWritable: $isWritable)'; - } -} diff --git a/lib/core/wallet_connect/chain_service/solana/solana_chain_id.dart b/lib/core/wallet_connect/chain_service/solana/solana_chain_id.dart deleted file mode 100644 index ed80a4f3f..000000000 --- a/lib/core/wallet_connect/chain_service/solana/solana_chain_id.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'solana_chain_service.dart'; - -enum SolanaChainId { - mainnet, - // testnet, - // devnet, -} - -extension SolanaChainIdX on SolanaChainId { - String chain() { - String name = ''; - - switch (this) { - case SolanaChainId.mainnet: - name = '4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ'; - // solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp - break; - // case SolanaChainId.devnet: - // name = '8E9rvCKLFQia2Y35HXjjpWzj8weVo44K'; - // // solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1 - // break; - // case SolanaChainId.testnet: - // name = ''; - // // solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z - // break; - } - - return '${SolanaChainServiceImpl.namespace}:$name'; - } -} diff --git a/lib/core/wallet_connect/chain_service/solana/solana_chain_service.dart b/lib/core/wallet_connect/chain_service/solana/solana_chain_service.dart deleted file mode 100644 index ca4017b98..000000000 --- a/lib/core/wallet_connect/chain_service/solana/solana_chain_service.dart +++ /dev/null @@ -1,170 +0,0 @@ -import 'dart:convert'; -import 'dart:developer'; - -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:cake_wallet/core/wallet_connect/chain_service/solana/entities/solana_sign_message.dart'; -import 'package:cake_wallet/core/wallet_connect/chain_service/solana/solana_chain_id.dart'; -import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart'; -import 'package:cake_wallet/core/wallet_connect/models/connection_model.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_widget.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart'; -import 'package:cw_core/solana_rpc_http_service.dart'; -import 'package:cw_core/utils/print_verbose.dart'; -import 'package:on_chain/solana/solana.dart'; -import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; -import '../chain_service.dart'; -import '../../wallet_connect_key_service.dart'; -import 'entities/solana_sign_transaction.dart'; - -class SolanaChainServiceImpl implements ChainService { - final BottomSheetService bottomSheetService; - final Web3Wallet wallet; - final WalletConnectKeyService wcKeyService; - - static const namespace = 'solana'; - static const solSignTransaction = 'solana_signTransaction'; - static const solSignMessage = 'solana_signMessage'; - - final SolanaChainId reference; - - final SolanaRPC solanaProvider; - - final SolanaPrivateKey? ownerPrivateKey; - - SolanaChainServiceImpl({ - required this.reference, - required this.wcKeyService, - required this.bottomSheetService, - required this.wallet, - required this.ownerPrivateKey, - required String formattedRPCUrl, - SolanaRPC? solanaProvider, - }) : solanaProvider = solanaProvider ?? SolanaRPC(SolanaRPCHTTPService(url: formattedRPCUrl)) { - for (final String event in getEvents()) { - wallet.registerEventEmitter(chainId: getChainId(), event: event); - } - wallet.registerRequestHandler( - chainId: getChainId(), - method: solSignTransaction, - handler: solanaSignTransaction, - ); - wallet.registerRequestHandler( - chainId: getChainId(), - method: solSignMessage, - handler: solanaSignMessage, - ); - } - - @override - String getNamespace() { - return namespace; - } - - @override - String getChainId() { - return reference.chain(); - } - - @override - List getEvents() { - return ['chainChanged', 'accountsChanged']; - } - - Future requestAuthorization(String? text) async { - // Show the bottom sheet - final bool? isApproved = await bottomSheetService.queueBottomSheet( - widget: Web3RequestModal( - child: ConnectionWidget( - title: S.current.signTransaction, - info: [ - ConnectionModel( - text: text, - ), - ], - ), - ), - ) as bool?; - - if (isApproved != null && isApproved == false) { - return 'User rejected signature'; - } - - return null; - } - - Future solanaSignTransaction(String topic, dynamic parameters) async { - log('received solana sign transaction request $parameters'); - - final solanaSignTx = SolanaSignTransaction.fromJson(parameters as Map); - - final String? authError = await requestAuthorization('Confirm request to sign transaction?'); - - if (authError != null) { - return authError; - } - - try { - // Convert transaction string to bytes - List transactionBytes = base64Decode(solanaSignTx.transaction); - - final message = SolanaTransactionUtils.deserializeMessageLegacy(transactionBytes); - - final sign = ownerPrivateKey!.sign(message.serialize()); - - final signature = solanaProvider.request( - SolanaRPCSendTransaction( - encodedTransaction: Base58Encoder.encode(sign), - commitment: Commitment.confirmed, - ), - ); - - bottomSheetService.queueBottomSheet( - isModalDismissible: true, - widget: BottomSheetMessageDisplayWidget( - message: S.current.awaitDAppProcessing, - isError: false, - ), - ); - - return signature; - } catch (e) { - log('An error has occurred while signing transaction: ${e.toString()}'); - bottomSheetService.queueBottomSheet( - isModalDismissible: true, - widget: BottomSheetMessageDisplayWidget( - message: '${S.current.errorSigningTransaction}: ${e.toString()}', - ), - ); - return 'Failed'; - } - } - - Future solanaSignMessage(String topic, dynamic parameters) async { - log('received solana sign message request: $parameters'); - - final solanaSignMessage = SolanaSignMessage.fromJson(parameters as Map); - - final String? authError = await requestAuthorization('Confirm request to sign message?'); - - if (authError != null) { - return authError; - } - List? sign; - - try { - sign = ownerPrivateKey!.sign(Base58Decoder.decode(solanaSignMessage.message)); - } catch (e) { - printV(e); - } - - if (sign == null) { - return ''; - } - - final signature = Base58Encoder.encode(sign); - - return signature; - } -} diff --git a/lib/core/wallet_connect/eth_transaction_model.dart b/lib/core/wallet_connect/eth_transaction_model.dart deleted file mode 100644 index deb33586f..000000000 --- a/lib/core/wallet_connect/eth_transaction_model.dart +++ /dev/null @@ -1,60 +0,0 @@ -class WCEthereumTransactionModel { - final String from; - final String to; - final String value; - final String? nonce; - final String? gasPrice; - final String? maxFeePerGas; - final String? maxPriorityFeePerGas; - final String? gas; - final String? gasLimit; - final String? data; - - WCEthereumTransactionModel({ - required this.from, - required this.to, - required this.value, - this.nonce, - this.gasPrice, - this.maxFeePerGas, - this.maxPriorityFeePerGas, - this.gas, - this.gasLimit, - this.data, - }); - - factory WCEthereumTransactionModel.fromJson(Map json) { - return WCEthereumTransactionModel( - from: json['from'] as String, - to: json['to'] as String, - value: json['value'] as String, - nonce: json['nonce'] as String?, - gasPrice: json['gasPrice'] as String?, - maxFeePerGas: json['maxFeePerGas'] as String?, - maxPriorityFeePerGas: json['maxPriorityFeePerGas'] as String?, - gas: json['gas'] as String?, - gasLimit: json['gasLimit'] as String?, - data: json['data'] as String?, - ); - } - - Map toJson() { - return { - 'from': from, - 'to': to, - 'value': value, - 'nonce': nonce, - 'gasPrice': gasPrice, - 'maxFeePerGas': maxFeePerGas, - 'maxPriorityFeePerGas': maxPriorityFeePerGas, - 'gas': gas, - 'gasLimit': gasLimit, - 'data': data, - }; - } - - @override - String toString() { - return 'EthereumTransactionModel(from: $from, to: $to, nonce: $nonce, gasPrice: $gasPrice, maxFeePerGas: $maxFeePerGas, maxPriorityFeePerGas: $maxPriorityFeePerGas, gas: $gas, gasLimit: $gasLimit, value: $value, data: $data)'; - } -} diff --git a/lib/core/wallet_connect/models/auth_request_model.dart b/lib/core/wallet_connect/models/auth_request_model.dart deleted file mode 100644 index f7fd984c8..000000000 --- a/lib/core/wallet_connect/models/auth_request_model.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; - -class AuthRequestModel { - final String iss; - final AuthRequest request; - - AuthRequestModel({ - required this.iss, - required this.request, - }); - - @override - String toString() { - return 'AuthRequestModel(iss: $iss, request: $request)'; - } -} diff --git a/lib/core/wallet_connect/models/chain_key_model.dart b/lib/core/wallet_connect/models/chain_key_model.dart deleted file mode 100644 index 5cd2764da..000000000 --- a/lib/core/wallet_connect/models/chain_key_model.dart +++ /dev/null @@ -1,16 +0,0 @@ -class ChainKeyModel { - final List chains; - final String privateKey; - final String publicKey; - - ChainKeyModel({ - required this.chains, - required this.privateKey, - required this.publicKey, - }); - - @override - String toString() { - return 'ChainKeyModel(chains: $chains, privateKey: $privateKey, publicKey: $publicKey)'; - } -} diff --git a/lib/core/wallet_connect/models/session_request_model.dart b/lib/core/wallet_connect/models/session_request_model.dart deleted file mode 100644 index 0c7a5d876..000000000 --- a/lib/core/wallet_connect/models/session_request_model.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; - -class SessionRequestModel { - final ProposalData request; - - SessionRequestModel({ - required this.request, - }); - - @override - String toString() { - return 'SessionRequestModel(request: $request)'; - } -} diff --git a/lib/core/wallet_connect/web3wallet_service.dart b/lib/core/wallet_connect/web3wallet_service.dart deleted file mode 100644 index 898433c62..000000000 --- a/lib/core/wallet_connect/web3wallet_service.dart +++ /dev/null @@ -1,416 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:developer'; -import 'dart:typed_data'; - -import 'package:cake_wallet/core/wallet_connect/chain_service/eth/evm_chain_id.dart'; -import 'package:cake_wallet/core/wallet_connect/chain_service/eth/evm_chain_service.dart'; -import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart'; -import 'package:cake_wallet/entities/preferences_key.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/core/wallet_connect/models/auth_request_model.dart'; -import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart'; -import 'package:cake_wallet/core/wallet_connect/models/session_request_model.dart'; -import 'package:cake_wallet/reactions/wallet_connect.dart'; -import 'package:cake_wallet/solana/solana.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_request_widget.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart'; -import 'package:cake_wallet/store/app_store.dart'; -import 'package:cw_core/utils/print_verbose.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:eth_sig_util/eth_sig_util.dart'; -import 'package:flutter/material.dart'; -import 'package:mobx/mobx.dart'; -import 'package:on_chain/solana/solana.dart' hide Store; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; - -import 'chain_service/solana/solana_chain_id.dart'; -import 'chain_service/solana/solana_chain_service.dart'; -import 'wc_bottom_sheet_service.dart'; -import 'package:cake_wallet/.secrets.g.dart' as secrets; - -part 'web3wallet_service.g.dart'; - -class Web3WalletService = Web3WalletServiceBase with _$Web3WalletService; - -abstract class Web3WalletServiceBase with Store { - final AppStore appStore; - final SharedPreferences sharedPreferences; - final BottomSheetService _bottomSheetHandler; - final WalletConnectKeyService walletKeyService; - - late Web3Wallet _web3Wallet; - - @observable - bool isInitialized; - - /// The list of requests from the dapp - /// Potential types include, but aren't limited to: - /// [SessionProposalEvent], [AuthRequest] - @observable - ObservableList pairings; - - @observable - ObservableList sessions; - - @observable - ObservableList auth; - - Web3WalletServiceBase( - this._bottomSheetHandler, this.walletKeyService, this.appStore, this.sharedPreferences) - : pairings = ObservableList(), - sessions = ObservableList(), - auth = ObservableList(), - isInitialized = false; - - @action - void create() { - // Create the web3wallet client - _web3Wallet = Web3Wallet( - core: Core(projectId: secrets.walletConnectProjectId), - metadata: const PairingMetadata( - name: 'Cake Wallet', - description: 'Cake Wallet', - url: 'https://cakewallet.com', - icons: ['https://cakewallet.com/assets/image/cake_logo.png'], - ), - ); - - // Setup our accounts - List chainKeys = walletKeyService.getKeys(appStore.wallet!); - for (final chainKey in chainKeys) { - for (final chainId in chainKey.chains) { - _web3Wallet.registerAccount( - chainId: chainId, - accountAddress: chainKey.publicKey, - ); - } - } - - // Setup our listeners - log('Created instance of web3wallet'); - _web3Wallet.core.pairing.onPairingInvalid.subscribe(_onPairingInvalid); - _web3Wallet.core.pairing.onPairingCreate.subscribe(_onPairingCreate); - _web3Wallet.core.pairing.onPairingDelete.subscribe(_onPairingDelete); - _web3Wallet.core.pairing.onPairingExpire.subscribe(_onPairingDelete); - _web3Wallet.pairings.onSync.subscribe(_onPairingsSync); - _web3Wallet.onSessionProposal.subscribe(_onSessionProposal); - _web3Wallet.onSessionProposalError.subscribe(_onSessionProposalError); - _web3Wallet.onSessionConnect.subscribe(_onSessionConnect); - _web3Wallet.onAuthRequest.subscribe(_onAuthRequest); - } - - @action - Future init() async { - // Await the initialization of the web3wallet - log('Intializing web3wallet'); - if (!isInitialized) { - try { - await _web3Wallet.init(); - log('Initialized'); - isInitialized = true; - } catch (e) { - log('Experimentallllll: $e'); - isInitialized = false; - } - } - - _refreshPairings(); - - final newSessions = _web3Wallet.sessions.getAll(); - sessions.addAll(newSessions); - - final newAuthRequests = _web3Wallet.completeRequests.getAll(); - auth.addAll(newAuthRequests); - - if (isEVMCompatibleChain(appStore.wallet!.type)) { - for (final cId in EVMChainId.values) { - EvmChainServiceImpl( - reference: cId, - appStore: appStore, - wcKeyService: walletKeyService, - bottomSheetService: _bottomSheetHandler, - wallet: _web3Wallet, - ); - } - } - - if (appStore.wallet!.type == WalletType.solana) { - for (final cId in SolanaChainId.values) { - final node = appStore.settingsStore.getCurrentNode(appStore.wallet!.type); - - String formattedUrl; - String protocolUsed = node.isSSL ? "https" : "http"; - - if (node.uriRaw == 'rpc.ankr.com') { - String ankrApiKey = secrets.ankrApiKey; - - formattedUrl = '$protocolUsed://${node.uriRaw}/$ankrApiKey'; - } else if (node.uriRaw == 'solana-mainnet.core.chainstack.com') { - String chainStackApiKey = secrets.chainStackApiKey; - - formattedUrl = '$protocolUsed://${node.uriRaw}/$chainStackApiKey'; - } else { - formattedUrl = '$protocolUsed://${node.uriRaw}'; - } - - SolanaChainServiceImpl( - reference: cId, - formattedRPCUrl: formattedUrl, - wcKeyService: walletKeyService, - bottomSheetService: _bottomSheetHandler, - wallet: _web3Wallet, - ownerPrivateKey: SolanaPrivateKey.fromSeedHex(solana!.getPrivateKey(appStore.wallet!)), - ); - } - } - } - - @action - FutureOr onDispose() { - log('web3wallet dispose'); - _web3Wallet.core.pairing.onPairingInvalid.unsubscribe(_onPairingInvalid); - _web3Wallet.pairings.onSync.unsubscribe(_onPairingsSync); - _web3Wallet.onSessionProposal.unsubscribe(_onSessionProposal); - _web3Wallet.onSessionProposalError.unsubscribe(_onSessionProposalError); - _web3Wallet.onSessionConnect.unsubscribe(_onSessionConnect); - _web3Wallet.onAuthRequest.unsubscribe(_onAuthRequest); - _web3Wallet.core.pairing.onPairingDelete.unsubscribe(_onPairingDelete); - _web3Wallet.core.pairing.onPairingExpire.unsubscribe(_onPairingDelete); - isInitialized = false; - } - - Web3Wallet getWeb3Wallet() { - return _web3Wallet; - } - - void _onPairingsSync(StoreSyncEvent? args) { - if (args != null) { - _refreshPairings(); - } - } - - void _onPairingDelete(PairingEvent? event) { - _refreshPairings(); - } - - Future _onSessionProposalError(SessionProposalErrorEvent? args) async { - log(args.toString()); - } - - void _onSessionProposal(SessionProposalEvent? args) async { - if (args != null) { - final chaindIdNamespace = getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type); - final Widget modalWidget = Web3RequestModal( - child: ConnectionRequestWidget( - chaindIdNamespace: chaindIdNamespace, - wallet: _web3Wallet, - sessionProposal: SessionRequestModel(request: args.params), - ), - ); - // show the bottom sheet - final bool? isApproved = await _bottomSheetHandler.queueBottomSheet( - widget: modalWidget, - ) as bool?; - - if (isApproved != null && isApproved) { - _web3Wallet.approveSession( - id: args.id, - namespaces: args.params.generatedNamespaces!, - ); - } else { - _web3Wallet.rejectSession( - id: args.id, - reason: Errors.getSdkError( - Errors.USER_REJECTED, - ), - ); - } - } - } - - @action - void _onPairingInvalid(PairingInvalidEvent? args) { - log('Pairing Invalid Event: $args'); - _bottomSheetHandler.queueBottomSheet( - isModalDismissible: true, - widget: BottomSheetMessageDisplayWidget(message: '${S.current.pairingInvalidEvent}: $args'), - ); - } - - @action - Future pairWithUri(Uri uri) async { - try { - log('Pairing with URI: $uri'); - await _web3Wallet.pair(uri: uri); - } on WalletConnectError catch (e) { - _bottomSheetHandler.queueBottomSheet( - isModalDismissible: true, - widget: BottomSheetMessageDisplayWidget(message: e.message), - ); - } catch (e) { - _bottomSheetHandler.queueBottomSheet( - isModalDismissible: true, - widget: BottomSheetMessageDisplayWidget(message: e.toString()), - ); - } - } - - @action - void _refreshPairings() { - printV('Refreshing pairings'); - pairings.clear(); - - final allPairings = _web3Wallet.pairings.getAll(); - - final keyForWallet = getKeyForStoringTopicsForWallet(); - - if (keyForWallet.isEmpty) return; - - final currentTopicsForWallet = getPairingTopicsForWallet(keyForWallet); - - final filteredPairings = - allPairings.where((pairing) => currentTopicsForWallet.contains(pairing.topic)).toList(); - - pairings.addAll(filteredPairings); - } - - void _onPairingCreate(PairingEvent? args) { - log('Pairing Create Event: $args'); - } - - @action - Future _onSessionConnect(SessionConnect? args) async { - if (args != null) { - log('Session Connected $args'); - - await savePairingTopicToLocalStorage(args.session.pairingTopic); - - sessions.add(args.session); - - _refreshPairings(); - } - } - - @action - Future _onAuthRequest(AuthRequest? args) async { - if (args != null) { - final chaindIdNamespace = getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type); - List chainKeys = walletKeyService.getKeysForChain(appStore.wallet!); - // Create the message to be signed - final String iss = 'did:pkh:$chaindIdNamespace:${chainKeys.first.publicKey}'; - final Widget modalWidget = Web3RequestModal( - child: ConnectionRequestWidget( - chaindIdNamespace: chaindIdNamespace, - wallet: _web3Wallet, - authRequest: AuthRequestModel(iss: iss, request: args), - ), - ); - final bool? isAuthenticated = await _bottomSheetHandler.queueBottomSheet( - widget: modalWidget, - ) as bool?; - - if (isAuthenticated != null && isAuthenticated) { - final String message = _web3Wallet.formatAuthMessage( - iss: iss, - cacaoPayload: CacaoRequestPayload.fromPayloadParams( - args.payloadParams, - ), - ); - - final String sig = EthSigUtil.signPersonalMessage( - message: Uint8List.fromList(message.codeUnits), - privateKey: chainKeys.first.privateKey, - ); - - await _web3Wallet.respondAuthRequest( - id: args.id, - iss: iss, - signature: CacaoSignature( - t: CacaoSignature.EIP191, - s: sig, - ), - ); - } else { - await _web3Wallet.respondAuthRequest( - id: args.id, - iss: iss, - error: Errors.getSdkError( - Errors.USER_REJECTED_AUTH, - ), - ); - } - } - } - - @action - Future disconnectSession(String topic) async { - final session = sessions.firstWhere((element) => element.pairingTopic == topic); - - await _web3Wallet.core.pairing.disconnect(topic: topic); - await _web3Wallet.disconnectSession( - topic: session.topic, reason: Errors.getSdkError(Errors.USER_DISCONNECTED)); - } - - @action - List getSessionsForPairingInfo(PairingInfo pairing) { - return sessions.where((element) => element.pairingTopic == pairing.topic).toList(); - } - - String getKeyForStoringTopicsForWallet() { - List chainKeys = walletKeyService.getKeysForChain(appStore.wallet!); - - if (chainKeys.isEmpty) { - return ''; - } - - final keyForPairingTopic = - PreferencesKey.walletConnectPairingTopicsListForWallet(chainKeys.first.publicKey); - - return keyForPairingTopic; - } - - List getPairingTopicsForWallet(String key) { - // Get the JSON-encoded string from shared preferences - final jsonString = sharedPreferences.getString(key); - - // If the string is null, return an empty list - if (jsonString == null) { - return []; - } - - // Decode the JSON string to a list of strings - final List jsonList = jsonDecode(jsonString) as List; - - // Cast each item to a string - return jsonList.map((item) => item as String).toList(); - } - - Future savePairingTopicToLocalStorage(String pairingTopic) async { - // Get key specific to the current wallet - final key = getKeyForStoringTopicsForWallet(); - - if (key.isEmpty) return; - - // Get all pairing topics attached to this key - final pairingTopicsForWallet = getPairingTopicsForWallet(key); - - printV(pairingTopicsForWallet); - - bool isPairingTopicAlreadySaved = pairingTopicsForWallet.contains(pairingTopic); - printV('Is Pairing Topic Saved: $isPairingTopicAlreadySaved'); - - if (!isPairingTopicAlreadySaved) { - // Update the list with the most recent pairing topic - pairingTopicsForWallet.add(pairingTopic); - - // Convert the list of updated pairing topics to a JSON-encoded string - final jsonString = jsonEncode(pairingTopicsForWallet); - - // Save the encoded string to shared preferences - await sharedPreferences.setString(key, jsonString); - } - } -} diff --git a/lib/di.dart b/lib/di.dart index 27636c606..87092a7eb 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -20,9 +20,6 @@ import 'package:cake_wallet/core/new_wallet_type_arguments.dart'; import 'package:cake_wallet/core/secure_storage.dart'; import 'package:cake_wallet/core/selectable_option.dart'; import 'package:cake_wallet/core/totp_request_details.dart'; -import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart'; -import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; -import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart'; import 'package:cake_wallet/core/wallet_creation_service.dart'; import 'package:cake_wallet/core/wallet_loading_service.dart'; import 'package:cake_wallet/core/yat_service.dart'; @@ -37,6 +34,9 @@ import 'package:cake_wallet/haven/cw_haven.dart'; import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart'; import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart'; import 'package:cake_wallet/src/screens/settings/background_sync_page.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart'; import 'package:cake_wallet/view_model/dev/monero_background_sync.dart'; import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cake_wallet/tron/tron.dart'; @@ -636,11 +636,15 @@ Future setup({ getIt.registerLazySingleton(() => KeyServiceImpl()); - getIt.registerLazySingleton(() { - final Web3WalletService web3WalletService = Web3WalletService(getIt.get(), - getIt.get(), appStore, getIt.get()); - web3WalletService.create(); - return web3WalletService; + getIt.registerLazySingleton(() { + final WalletKitService walletKitService = WalletKitService( + getIt.get(), + getIt.get(), + appStore, + getIt.get(), + ); + walletKitService.create(); + return walletKitService; }); getIt.registerFactory(() => BalancePage( @@ -1441,7 +1445,8 @@ Future setup({ }); getIt.registerFactory( - () => WalletConnectConnectionsView(web3walletService: getIt.get())); + () => WalletConnectConnectionsView(walletKitService: getIt.get()), + ); getIt.registerFactory(() => NFTViewModel(appStore, getIt.get())); getIt.registerFactory(() => TorPage(getIt.get())); diff --git a/lib/reactions/on_authentication_state_change.dart b/lib/reactions/on_authentication_state_change.dart index bce160b4a..46ac481bb 100644 --- a/lib/reactions/on_authentication_state_change.dart +++ b/lib/reactions/on_authentication_state_change.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/hardware_wallet/require_hardware_wallet_connection.dart'; import 'package:cake_wallet/entities/load_current_wallet.dart'; @@ -8,6 +7,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/store/authentication_store.dart'; import 'package:cake_wallet/store/settings_store.dart'; @@ -71,7 +71,7 @@ void startAuthenticationStateChange( buttonAction: () => Navigator.of(context).pop()), ); await loadCurrentWallet(); - getIt.get().resetCurrentSheet(); + getIt.get().showNext(); await navigatorKey.currentState! .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false); }, diff --git a/lib/reactions/wallet_connect.dart b/lib/reactions/wallet_connect.dart index 217664eaa..37a0b92ec 100644 --- a/lib/reactions/wallet_connect.dart +++ b/lib/reactions/wallet_connect.dart @@ -1,5 +1,7 @@ -import 'package:cake_wallet/core/wallet_connect/chain_service/eth/evm_chain_id.dart'; -import 'package:cake_wallet/core/wallet_connect/chain_service/solana/solana_chain_id.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/eth/evm_chain_id.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/eth/evm_supported_methods.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/solana/solana_chain_id.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/solana/solana_supported_methods.dart'; import 'package:cw_core/wallet_type.dart'; bool isEVMCompatibleChain(WalletType walletType) { @@ -47,6 +49,19 @@ String getChainNameSpaceAndIdBasedOnWalletType(WalletType walletType) { } } +List getChainSupportedMethodsOnWalletType(WalletType walletType) { + switch (walletType) { + case WalletType.ethereum: + return EVMSupportedMethods.values.map((e) => e.name).toList(); + case WalletType.polygon: + return EVMSupportedMethods.values.map((e) => e.name).toList(); + case WalletType.solana: + return SolanaSupportedMethods.values.map((e) => e.name).toList(); + default: + return []; + } +} + int getChainIdBasedOnWalletType(WalletType walletType) { switch (walletType) { case WalletType.polygon: diff --git a/lib/router.dart b/lib/router.dart index 8511714fc..7fea601f8 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -4,7 +4,6 @@ import 'package:cake_wallet/core/new_wallet_arguments.dart'; import 'package:cake_wallet/buy/order.dart'; import 'package:cake_wallet/core/new_wallet_type_arguments.dart'; import 'package:cake_wallet/core/totp_request_details.dart'; -import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/qr_view_data.dart'; @@ -106,6 +105,7 @@ import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart'; import 'package:cake_wallet/src/screens/ur/animated_ur_page.dart'; import 'package:cake_wallet/src/screens/wallet/wallet_edit_page.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart'; import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart'; import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart'; import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart'; @@ -777,7 +777,7 @@ Route createRoute(RouteSettings settings) { case Routes.walletConnectConnectionsListing: return MaterialPageRoute( builder: (_) => WalletConnectConnectionsView( - web3walletService: getIt.get(), + walletKitService: getIt.get(), launchUri: settings.arguments as Uri?, )); diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart index cf1f6fa17..52cc82dde 100644 --- a/lib/src/screens/dashboard/dashboard_page.dart +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -1,10 +1,10 @@ import 'dart:async'; -import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart'; import 'package:cake_wallet/src/screens/dashboard/pages/cake_features_page.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_listener_widget.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; import 'package:cake_wallet/src/widgets/gradient_background.dart'; import 'package:cake_wallet/src/widgets/haven_wallet_removal_popup.dart'; import 'package:cake_wallet/src/widgets/services_updates_widget.dart'; diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart index d9b509136..7f817fe5a 100644 --- a/lib/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart @@ -1,4 +1,3 @@ -import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/auth/auth_page.dart'; @@ -8,7 +7,8 @@ import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sideba import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar/side_menu_item.dart'; import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_listener_widget.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; import 'package:cake_wallet/src/widgets/services_updates_widget.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart'; diff --git a/lib/core/wallet_connect/models/bottom_sheet_queue_item_model.dart b/lib/src/screens/wallet_connect/models/bottom_sheet_queue_item_model.dart similarity index 90% rename from lib/core/wallet_connect/models/bottom_sheet_queue_item_model.dart rename to lib/src/screens/wallet_connect/models/bottom_sheet_queue_item_model.dart index 49eecac0f..44ee5e8a4 100644 --- a/lib/core/wallet_connect/models/bottom_sheet_queue_item_model.dart +++ b/lib/src/screens/wallet_connect/models/bottom_sheet_queue_item_model.dart @@ -6,11 +6,13 @@ class BottomSheetQueueItemModel { final Widget widget; final bool isModalDismissible; final Completer completer; + final int closeAfter; BottomSheetQueueItemModel({ required this.widget, required this.completer, this.isModalDismissible = false, + this.closeAfter = 0, }); @override diff --git a/lib/core/wallet_connect/models/connection_model.dart b/lib/src/screens/wallet_connect/models/wc_connection_model.dart similarity index 60% rename from lib/core/wallet_connect/models/connection_model.dart rename to lib/src/screens/wallet_connect/models/wc_connection_model.dart index 63cc8260f..f4cbb5723 100644 --- a/lib/core/wallet_connect/models/connection_model.dart +++ b/lib/src/screens/wallet_connect/models/wc_connection_model.dart @@ -1,10 +1,10 @@ -class ConnectionModel { +class WCConnectionModel { final String? title; final String? text; final List? elements; final Map? elementActions; - ConnectionModel({ + WCConnectionModel({ this.title, this.text, this.elements, @@ -13,6 +13,6 @@ class ConnectionModel { @override String toString() { - return 'WalletConnectRequestModel(title: $title, text: $text, elements: $elements, elementActions: $elementActions)'; + return 'WCConnectionModel(title: $title, text: $text, elements: $elements, elementActions: $elementActions)'; } } diff --git a/lib/core/wallet_connect/wc_bottom_sheet_service.dart b/lib/src/screens/wallet_connect/services/bottom_sheet_service.dart similarity index 53% rename from lib/core/wallet_connect/wc_bottom_sheet_service.dart rename to lib/src/screens/wallet_connect/services/bottom_sheet_service.dart index 3da8660f0..44637ac1b 100644 --- a/lib/core/wallet_connect/wc_bottom_sheet_service.dart +++ b/lib/src/screens/wallet_connect/services/bottom_sheet_service.dart @@ -1,19 +1,24 @@ import 'dart:async'; -import 'package:cake_wallet/core/wallet_connect/models/bottom_sheet_queue_item_model.dart'; +import 'dart:collection'; +import 'package:cake_wallet/src/screens/wallet_connect/models/bottom_sheet_queue_item_model.dart'; import 'package:flutter/material.dart'; +enum WCBottomSheetResult { reject, one, all } + abstract class BottomSheetService { abstract final ValueNotifier currentSheet; Future queueBottomSheet({ required Widget widget, bool isModalDismissible = false, + int closeAfter = 0, }); - void resetCurrentSheet(); + void showNext(); } class BottomSheetServiceImpl implements BottomSheetService { + Queue queue = Queue(); @override final ValueNotifier currentSheet = ValueNotifier(null); @@ -21,6 +26,7 @@ class BottomSheetServiceImpl implements BottomSheetService { @override Future queueBottomSheet({ required Widget widget, + int closeAfter = 0, bool isModalDismissible = false, }) async { // Create the bottom sheet queue item @@ -28,16 +34,28 @@ class BottomSheetServiceImpl implements BottomSheetService { final queueItem = BottomSheetQueueItemModel( widget: widget, completer: completer, + closeAfter: closeAfter, isModalDismissible: isModalDismissible, ); - currentSheet.value = queueItem; + // If the current sheet it null, set it to the queue item + if (currentSheet.value == null) { + currentSheet.value = queueItem; + } else { + // Otherwise, add it to the queue + queue.add(queueItem); + } + // Return the future return await completer.future; } @override - void resetCurrentSheet() { - currentSheet.value = null; + void showNext() { + if (queue.isEmpty) { + currentSheet.value = null; + } else { + currentSheet.value = queue.removeFirst(); + } } } diff --git a/lib/core/wallet_connect/chain_service/eth/evm_chain_id.dart b/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_id.dart similarity index 78% rename from lib/core/wallet_connect/chain_service/eth/evm_chain_id.dart rename to lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_id.dart index 0be21b1b2..6ecd09d35 100644 --- a/lib/core/wallet_connect/chain_service/eth/evm_chain_id.dart +++ b/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_id.dart @@ -1,5 +1,3 @@ -import 'package:cake_wallet/core/wallet_connect/chain_service/eth/evm_chain_service.dart'; - enum EVMChainId { ethereum, polygon, @@ -30,6 +28,6 @@ extension EVMChainIdX on EVMChainId { break; } - return '${EvmChainServiceImpl.namespace}:$name'; + return 'eip155:$name'; } } diff --git a/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart b/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart new file mode 100644 index 000000000..05c2a1f55 --- /dev/null +++ b/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart @@ -0,0 +1,581 @@ +import 'dart:convert'; + +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; +import 'package:eth_sig_util/eth_sig_util.dart'; +import 'package:eth_sig_util/util/utils.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:reown_walletkit/reown_walletkit.dart'; + +import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/eth/evm_chain_id.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/eth/evm_supported_methods.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/models/wc_connection_model.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/utils/eth_utils.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/utils/method_utils.dart'; +import 'package:cake_wallet/store/app_store.dart'; +import 'package:cake_wallet/.secrets.g.dart' as secrets; + +class EvmChainServiceImpl { + Map get sessionRequestHandlers => { + EVMSupportedMethods.ethSign.name: ethSign, + EVMSupportedMethods.ethSignTransaction.name: ethSignTransaction, + EVMSupportedMethods.ethSignTypedData.name: ethSignTypedData, + EVMSupportedMethods.ethSignTypedDataV4.name: ethSignTypedDataV4, + }; + + Map get methodRequestHandlers => { + EVMSupportedMethods.personalSign.name: personalSign, + EVMSupportedMethods.ethSendTransaction.name: ethSendTransaction, + }; + + EvmChainServiceImpl({ + required this.reference, + required this.appStore, + required this.wcKeyService, + required this.bottomSheetService, + required this.walletKit, + Web3Client? web3Client, + }) : ethClient = web3Client ?? + Web3Client( + appStore.settingsStore.getCurrentNode(appStore.wallet!.type).uri.toString(), + http.Client(), + ) { + for (final event in EventsConstants.allEvents) { + walletKit.registerEventEmitter( + chainId: getChainId(), + event: event, + ); + } + + for (var handler in methodRequestHandlers.entries) { + walletKit.registerRequestHandler( + chainId: getChainId(), + method: handler.key, + handler: handler.value, + ); + } + for (var handler in sessionRequestHandlers.entries) { + walletKit.registerRequestHandler( + chainId: getChainId(), + method: handler.key, + handler: handler.value, + ); + } + + walletKit.onSessionRequest.subscribe(_onSessionRequest); + } + + final AppStore appStore; + final EVMChainId reference; + final Web3Client ethClient; + final ReownWalletKit walletKit; + final WalletConnectKeyService wcKeyService; + final BottomSheetService bottomSheetService; + + String getChainId() => reference.chain(); + + Future personalSign(String topic, dynamic parameters) async { + debugPrint('personalSign request: $parameters'); + + final pRequest = walletKit.pendingRequests.getAll().last; + final address = EthUtils.getAddressFromSessionRequest(pRequest); + final data = EthUtils.getDataFromSessionRequest(pRequest); + final message = EthUtils.getUtf8Message(data.toString()); + var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0'); + + final isApproved = await MethodsUtils.requestApproval( + message, + method: pRequest.method, + chainId: pRequest.chainId, + address: address, + transportType: pRequest.transportType.name, + verifyContext: pRequest.verifyContext, + ); + + if (isApproved) { + try { + // Load the private key + final keys = wcKeyService.getKeysForChain(appStore.wallet!); + final credentials = EthPrivateKey.fromHex(keys[0].privateKey); + + final signature = credentials.signPersonalMessageToUint8List( + utf8.encode(message), + ); + final signedTx = bytesToHex(signature, include0x: true); + + isValidSignature(signedTx, message, credentials.address.hex); + + response = response.copyWith(result: signedTx); + } catch (e) { + debugPrint('personalSign error $e'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + response = response.copyWith( + error: JsonRpcError(code: error.code, message: error.message), + ); + } + } else { + final error = Errors.getSdkError(Errors.USER_REJECTED); + response = response.copyWith( + error: JsonRpcError(code: error.code, message: error.message), + ); + } + + _handleResponseForTopic(topic, response); + } + + Future ethSign(String topic, dynamic parameters) async { + debugPrint('ethSign request: $parameters'); + + final pRequest = walletKit.pendingRequests.getAll().last; + final address = EthUtils.getAddressFromSessionRequest(pRequest); + final data = EthUtils.getDataFromSessionRequest(pRequest); + final message = EthUtils.getUtf8Message(data.toString()); + var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0'); + + final isApproved = await MethodsUtils.requestApproval( + message, + method: pRequest.method, + chainId: pRequest.chainId, + address: address, + transportType: pRequest.transportType.name, + verifyContext: pRequest.verifyContext, + ); + + if (isApproved) { + try { + // Load the private key + final keys = wcKeyService.getKeysForChain(appStore.wallet!); + final credentials = EthPrivateKey.fromHex(keys[0].privateKey); + + final signature = credentials.signPersonalMessageToUint8List( + utf8.encode(message), + ); + final signedTx = bytesToHex(signature, include0x: true); + + isValidSignature(signedTx, message, credentials.address.hex); + + response = response.copyWith(result: signedTx); + } catch (e) { + debugPrint('ethSign error $e'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + response = response.copyWith( + error: JsonRpcError(code: error.code, message: error.message), + ); + } + } else { + final error = Errors.getSdkError(Errors.USER_REJECTED).toSignError(); + response = response.copyWith( + error: JsonRpcError(code: error.code, message: error.message), + ); + } + + _handleResponseForTopic(topic, response); + } + + Future ethSignTypedData(String topic, dynamic parameters) async { + debugPrint('ethSignTypedData request: $parameters'); + + final pRequest = walletKit.pendingRequests.getAll().last; + final address = EthUtils.getAddressFromSessionRequest(pRequest); + final data = EthUtils.getDataFromSessionRequest(pRequest) as String; + var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0'); + + final isApproved = await MethodsUtils.requestApproval( + data, + method: pRequest.method, + chainId: pRequest.chainId, + address: address, + transportType: pRequest.transportType.name, + verifyContext: pRequest.verifyContext, + ); + + if (isApproved) { + try { + final keys = wcKeyService.getKeysForChain(appStore.wallet!); + + final signature = EthSigUtil.signTypedData( + privateKey: keys[0].privateKey, + jsonData: data, + version: TypedDataVersion.V4, + ); + + response = response.copyWith(result: signature); + } catch (e) { + debugPrint('ethSignTypedData error $e'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + response = response.copyWith( + error: JsonRpcError(code: error.code, message: error.message), + ); + } + } else { + final error = Errors.getSdkError(Errors.USER_REJECTED).toSignError(); + response = response.copyWith( + error: JsonRpcError(code: error.code, message: error.message), + ); + } + + _handleResponseForTopic(topic, response); + } + + Future ethSignTypedDataV4(String topic, dynamic parameters) async { + debugPrint('ethSignTypedDataV4 request: $parameters'); + + final permitRequestMessage = await extractPermitData(parameters); + + final pRequest = walletKit.pendingRequests.getAll().last; + final address = EthUtils.getAddressFromSessionRequest(pRequest); + final data = EthUtils.getDataFromSessionRequest(pRequest) as String; + var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0'); + + final isApproved = await MethodsUtils.requestApproval( + permitRequestMessage, + method: pRequest.method, + chainId: pRequest.chainId, + address: address, + transportType: pRequest.transportType.name, + verifyContext: pRequest.verifyContext, + ); + + if (isApproved) { + try { + final keys = wcKeyService.getKeysForChain(appStore.wallet!); + + final signature = EthSigUtil.signTypedData( + privateKey: keys[0].privateKey, + jsonData: data, + version: TypedDataVersion.V4, + ); + + response = response.copyWith(result: signature); + } catch (e) { + debugPrint('ethSignTypedDataV4 error $e'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + response = response.copyWith( + error: JsonRpcError(code: error.code, message: error.message), + ); + } + } else { + response = response.copyWith( + error: JsonRpcError(code: 5002, message: S.current.user_rejected_method), + ); + } + + _handleResponseForTopic(topic, response); + } + + Future ethSignTransaction(String topic, dynamic parameters) async { + debugPrint('ethSignTransaction request: $parameters'); + + final SessionRequest pRequest = walletKit.pendingRequests.getAll().last; + final data = EthUtils.getTransactionFromSessionRequest(pRequest); + + if (data == null) return; + + final address = EthUtils.getAddressFromSessionRequest(pRequest); + var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0'); + + final transaction = await _approveTransaction( + data, + method: pRequest.method, + chainId: pRequest.chainId, + address: address, + transportType: pRequest.transportType.name, + verifyContext: pRequest.verifyContext, + ); + + if (transaction is Transaction) { + try { + // Load the private key + final keys = wcKeyService.getKeysForChain(appStore.wallet!); + final credentials = EthPrivateKey.fromHex(keys[0].privateKey); + + final chainId = getChainId().split(':').last; + + final signature = await ethClient.signTransaction( + credentials, + transaction, + chainId: int.parse(chainId), + ); + + // Sign the transaction + final signedTx = bytesToHex(signature, include0x: true); + response = response.copyWith(result: signedTx); + } on RPCError catch (e) { + debugPrint('ethSignTransaction error $e'); + response = response.copyWith( + error: JsonRpcError(code: e.errorCode, message: e.message), + ); + } catch (e) { + debugPrint('ethSignTransaction error $e'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + response = response.copyWith( + error: JsonRpcError(code: error.code, message: error.message), + ); + } + } else { + response = response.copyWith(error: transaction as JsonRpcError); + } + + _handleResponseForTopic(topic, response); + } + + Future ethSendTransaction(String topic, dynamic parameters) async { + debugPrint('ethSendTransaction request: $parameters'); + final SessionRequest pRequest = walletKit.pendingRequests.getAll().last; + + final data = EthUtils.getTransactionFromSessionRequest(pRequest); + if (data == null) return; + + var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0'); + + final transaction = await _approveTransaction( + data, + method: pRequest.method, + chainId: pRequest.chainId, + transportType: pRequest.transportType.name, + verifyContext: pRequest.verifyContext, + ); + if (transaction is Transaction) { + try { + // Load the private key + final keys = wcKeyService.getKeysForChain(appStore.wallet!); + final credentials = EthPrivateKey.fromHex(keys[0].privateKey); + final chainId = getChainId().split(':').last; + + final signedTx = await ethClient.sendTransaction( + credentials, + transaction, + chainId: int.parse(chainId), + ); + + response = response.copyWith(result: signedTx); + } on RPCError catch (e) { + debugPrint('ethSendTransaction error $e'); + response = response.copyWith( + error: JsonRpcError(code: e.errorCode, message: e.message), + ); + } catch (e) { + debugPrint('ethSendTransaction error $e'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + response = response.copyWith( + error: JsonRpcError(code: error.code, message: error.message), + ); + } + } else { + response = response.copyWith(error: transaction as JsonRpcError); + } + + _handleResponseForTopic(topic, response); + } + + void _handleResponseForTopic(String topic, JsonRpcResponse response) async { + final session = walletKit.sessions.get(topic); + + try { + await walletKit.respondSessionRequest( + topic: topic, + response: response, + ); + MethodsUtils.handleRedirect( + topic, + session!.peer.metadata.redirect, + response.error?.message, + response.error == null, + ); + } on ReownSignError catch (error) { + MethodsUtils.handleRedirect( + topic, + session!.peer.metadata.redirect, + error.message, + ); + } + } + + Future _approveTransaction( + Map transactionJson, { + String? title, + String? method, + String? chainId, + String? address, + VerifyContext? verifyContext, + required String transportType, + }) async { + Transaction transaction = transactionJson.toTransaction(); + + final gasPrice = await ethClient.getGasPrice(); + try { + final gasLimit = await ethClient.estimateGas( + sender: transaction.from, + to: transaction.to, + value: transaction.value, + data: transaction.data, + gasPrice: gasPrice, + ); + + transaction = transaction.copyWith( + gasPrice: gasPrice, + maxGas: gasLimit.toInt(), + ); + } on RPCError catch (e) { + return JsonRpcError(code: e.errorCode, message: e.message); + } + + final gweiGasPrice = (transaction.gasPrice?.getInWei ?? BigInt.zero) / BigInt.from(1000000000); + + final amount = (transaction.value?.getInWei ?? BigInt.zero) / BigInt.from(1e18); + + final txMessageText = '${S.current.value}: ${amount.toStringAsFixed(9)} ETH\n' + '${S.current.from}: ${transaction.from?.hex}\n' + '${S.current.to}: ${transaction.to?.hex}'; + + if (await MethodsUtils.requestApproval( + txMessageText, + title: title, + method: method, + chainId: chainId, + address: address, + transportType: transportType, + verifyContext: verifyContext, + extraModels: [ + WCConnectionModel( + title: S.current.gas_price, + elements: ['${gweiGasPrice.toStringAsFixed(2)} GWEI'], + ), + ], + )) { + return transaction; + } + + return JsonRpcError(code: 5002, message: S.current.user_rejected_method); + } + + void _onSessionRequest(SessionRequestEvent? args) async { + if (args != null && args.chainId == getChainId()) { + debugPrint('_onSessionRequest ${args.toString()}'); + final handler = sessionRequestHandlers[args.method]; + if (handler != null) { + await handler(args.topic, args.params); + } + } + } + + bool isValidSignature(String hexSignature, String message, String hexAddress) { + try { + debugPrint('isValidSignature: $hexSignature, $message, $hexAddress'); + final recoveredAddress = EthSigUtil.recoverPersonalSignature( + signature: hexSignature, + message: utf8.encode(message), + ); + debugPrint('recoveredAddress: $recoveredAddress'); + + final recoveredAddress2 = EthSigUtil.recoverSignature( + signature: hexSignature, + message: utf8.encode(message), + ); + debugPrint('recoveredAddress2: $recoveredAddress2'); + + final isValid = recoveredAddress == hexAddress; + return isValid; + } catch (e) { + return false; + } + } + + Future extractPermitData(dynamic data) async { + if (data is List && data.length >= 2) { + final typedData = jsonDecode(data[1] as String) as Map; + + // Extracting domain details. + final domain = typedData['domain'] ?? {} as Map; + final domainName = domain['name']?.toString() ?? ''; + final verifyingContract = domain['verifyingContract']?.toString() ?? ''; + + final chainId = domain['chainId']?.toString() ?? ''; + final chainName = getChainNameBasedOnWalletType(appStore.wallet!.type); + + // Get the primary type. + final primaryType = typedData['primaryType']?.toString() ?? ''; + + // Extracting message details. + final message = typedData['message'] ?? {} as Map; + final details = message['details'] ?? {} as Map; + final amount = details['amount']?.toString() ?? ''; + final expirationRaw = details['expiration']?.toString() ?? ''; + final nonce = details['nonce']?.toString() ?? ''; + + final tokenAddress = details['token']?.toString() ?? ''; + final token = await getTokenDetails(tokenAddress, chainName); + + final spender = message['spender']?.toString() ?? ''; + final sigDeadlineRaw = message['sigDeadline']?.toString() ?? ''; + + // Converting expiration and sigDeadline from Unix time (seconds) to DateTime. + DateTime? expirationDate; + DateTime? sigDeadlineDate; + try { + if (expirationRaw.isNotEmpty) { + final int expirationInt = int.parse(expirationRaw); + expirationDate = DateTime.fromMillisecondsSinceEpoch(expirationInt * 1000); + } + if (sigDeadlineRaw.isNotEmpty) { + final int sigDeadlineInt = int.parse(sigDeadlineRaw); + sigDeadlineDate = DateTime.fromMillisecondsSinceEpoch(sigDeadlineInt * 1000); + } + } catch (e) { + // Parsing failed; we leave dates as null. + } + + final permitData = { + 'domainName': domainName, + 'chainId': chainId, + 'verifyingContract': verifyingContract, + 'primaryType': primaryType, + 'token': token, + 'amount': amount, + 'expiration': expirationDate, + 'nonce': nonce, + 'spender': spender, + 'sigDeadline': sigDeadlineDate, + }; + + return 'Domain: ${permitData['domainName']}' + 'Chain ID: ${permitData['chainId']}' + 'Verifying Contract: ${permitData['verifyingContract']}' + 'Primary Type: ${permitData['primaryType']}' + 'Token: ${permitData['token']}' + 'Expiration: ${permitData['expiration'] != null ? permitData['expiration'] : 'N/A'}' + 'Spender: ${permitData['spender']}' + 'Signature Deadline: ${permitData['sigDeadline'] != null ? permitData['sigDeadline'] : 'N/A'}'; + } + return ''; + } + + Future getTokenDetails(String contractAddress, String chainName) async { + final uri = Uri.https( + 'deep-index.moralis.io', + '/api/v2.2/erc20/metadata', + { + "chain": chainName, + "addresses": contractAddress, + }, + ); + + final response = await http.get( + uri, + headers: { + "Accept": "application/json", + "X-API-Key": secrets.moralisApiKey, + }, + ); + + final decodedResponse = jsonDecode(response.body)[0] as Map; + + final symbol = (decodedResponse['symbol'] ?? '') as String; + + final name = decodedResponse['name'] ?? ''; + return '$name ($symbol)'; + } +} diff --git a/lib/src/screens/wallet_connect/services/chain_service/eth/evm_supported_methods.dart b/lib/src/screens/wallet_connect/services/chain_service/eth/evm_supported_methods.dart new file mode 100644 index 000000000..32e00570c --- /dev/null +++ b/lib/src/screens/wallet_connect/services/chain_service/eth/evm_supported_methods.dart @@ -0,0 +1,31 @@ +enum EVMSupportedMethods { + ethSign, + ethSignTransaction, + ethSignTypedData, + ethSignTypedDataV4, + switchChain, + addChain, + personalSign, + ethSendTransaction; + + String get name { + switch (this) { + case ethSign: + return 'eth_sign'; + case ethSignTransaction: + return 'eth_signTransaction'; + case ethSignTypedData: + return 'eth_signTypedData'; + case ethSignTypedDataV4: + return 'eth_signTypedData_v4'; + case switchChain: + return 'wallet_switchEthereumChain'; + case addChain: + return 'wallet_addEthereumChain'; + case personalSign: + return 'personal_sign'; + case ethSendTransaction: + return 'eth_sendTransaction'; + } + } +} \ No newline at end of file diff --git a/lib/src/screens/wallet_connect/services/chain_service/solana/solana_chain_id.dart b/lib/src/screens/wallet_connect/services/chain_service/solana/solana_chain_id.dart new file mode 100644 index 000000000..c5a3c9d25 --- /dev/null +++ b/lib/src/screens/wallet_connect/services/chain_service/solana/solana_chain_id.dart @@ -0,0 +1,22 @@ +enum SolanaChainId { mainnet, devnet, testnet } + +extension SolanaChainIdX on SolanaChainId { + String chain() { + String name = ''; + + switch (this) { + case SolanaChainId.mainnet: + name = '4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ'; + // '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'; + break; + case SolanaChainId.devnet: + name = 'EtWTRABZaYq6iMfeYKouRu166VU2xqa1'; + break; + case SolanaChainId.testnet: + name = '4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z'; + break; + } + + return 'solana:$name'; + } +} diff --git a/lib/src/screens/wallet_connect/services/chain_service/solana/solana_chain_service.dart b/lib/src/screens/wallet_connect/services/chain_service/solana/solana_chain_service.dart new file mode 100644 index 000000000..076c63228 --- /dev/null +++ b/lib/src/screens/wallet_connect/services/chain_service/solana/solana_chain_service.dart @@ -0,0 +1,287 @@ +import 'dart:convert'; + +import 'package:blockchain_utils/base58/base58.dart'; +import 'package:blockchain_utils/blockchain_utils.dart' as blockchain_utils; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/solana/solana_supported_methods.dart'; +import 'package:flutter/material.dart'; +import 'package:on_chain/solana/solana.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; + +import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/solana/solana_chain_id.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/utils/method_utils.dart'; +import 'package:cake_wallet/store/app_store.dart'; + +class SolanaChainService { + Map get solanaRequestHandlers => { + SolanaSupportedMethods.solSignMessage.name: solanaSignMessage, + SolanaSupportedMethods.solSignTransaction.name: solanaSignTransaction, + SolanaSupportedMethods.solSignAllTransaction.name: solanaSignAllTransaction, + }; + + SolanaChainService({ + required this.appStore, + required this.bottomSheetService, + required this.walletKit, + required this.wcKeyService, + required this.reference, + }) { + for (var handler in solanaRequestHandlers.entries) { + walletKit.registerRequestHandler( + chainId: getChainId(), + method: handler.key, + handler: handler.value, + ); + } + } + + final AppStore appStore; + final BottomSheetService bottomSheetService; + final ReownWalletKit walletKit; + final WalletConnectKeyService wcKeyService; + final SolanaChainId reference; + + String getChainId() => reference.chain(); + + Future solanaSignMessage(String topic, dynamic parameters) async { + debugPrint('solanaSignMessage request: $parameters'); + + final pRequest = walletKit.pendingRequests.getAll().last; + var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0'); + + try { + final params = parameters as Map; + final message = params['message'].toString(); + + final privateKey = _getSolanaPrivateKey(); + + // it's sent as base58 encoded from the dapp + final base58Decoded = base58.decode(message); + final decodedMessage = utf8.decode(base58Decoded); + + final isApproved = await MethodsUtils.requestApproval( + decodedMessage, + method: pRequest.method, + chainId: pRequest.chainId, + address: privateKey.publicKey().toAddress().address, + transportType: pRequest.transportType.name, + ); + + if (isApproved) { + final signedBytes = await privateKey.sign(base58Decoded); + + final signature = blockchain_utils.Base58Encoder.encode(signedBytes); + + response = response.copyWith(result: {'signature': signature}); + } else { + final error = Errors.getSdkError(Errors.USER_REJECTED); + response = response.copyWith( + error: JsonRpcError(code: error.code, message: error.message), + ); + } + // + } catch (e) { + debugPrint('solanaSignMessage error $e'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + + response = response.copyWith( + error: JsonRpcError(code: error.code, message: error.message), + ); + } + + await walletKit.respondSessionRequest(topic: topic, response: response); + + _handleResponseForTopic(topic, response); + } + + Future solanaSignTransaction(String topic, dynamic parameters) async { + debugPrint('solanaSignTransaction: ${jsonEncode(parameters)}'); + + final pRequest = walletKit.pendingRequests.getAll().last; + var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0'); + + try { + final params = parameters as Map; + final privateKey = _getSolanaPrivateKey(); + + final beautifiedTrx = const JsonEncoder.withIndent(' ').convert(params); + + SolanaTransaction unSignedTransaction; + if (params.containsKey('transaction')) { + final transaction = params['transaction'] as String; + final transactionBytes = base64.decode(transaction); + unSignedTransaction = SolanaTransaction.deserialize(transactionBytes); + } else { + final feePayer = params['feePayer'].toString(); + final recentBlockHash = params['recentBlockhash'].toString(); + final instructionsList = params['instructions'] as List; + + final instructions = instructionsList.map((json) { + return (json as Map).toInstruction(); + }).toList(); + + unSignedTransaction = SolanaTransaction( + payerKey: SolAddress(feePayer), + instructions: instructions, + recentBlockhash: SolAddress(recentBlockHash), + ); + } + + final isApproved = await MethodsUtils.requestApproval( + beautifiedTrx, + method: pRequest.method, + chainId: pRequest.chainId, + address: privateKey.publicKey().toAddress().address, + transportType: pRequest.transportType.name, + ); + + if (isApproved) { + final signedTx = await privateKey.sign(unSignedTransaction.serializeMessage()); + + final signature = Base58Encoder.encode(signedTx.toList(growable: false)); + + response = response.copyWith(result: {'signature': signature}); + } else { + final error = Errors.getSdkError(Errors.USER_REJECTED); + response = response.copyWith( + error: JsonRpcError(code: error.code, message: error.message), + ); + } + } catch (e, s) { + debugPrint('solanaSignTransaction error $e, $s'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + + response = response.copyWith( + error: JsonRpcError(code: error.code, message: error.message), + ); + } + + await walletKit.respondSessionRequest(topic: topic, response: response); + + _handleResponseForTopic(topic, response); + } + + Future solanaSignAllTransaction(String topic, dynamic parameters) async { + debugPrint('solanaSignAllTransaction: ${jsonEncode(parameters)}'); + + final pRequest = walletKit.pendingRequests.getAll().last; + var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0'); + + try { + final params = parameters as Map; + final beautifiedTrx = const JsonEncoder.withIndent(' ').convert(params); + + final privateKey = _getSolanaPrivateKey(); + + final isApproved = await MethodsUtils.requestApproval( + beautifiedTrx, + method: pRequest.method, + chainId: pRequest.chainId, + address: privateKey.publicKey().toAddress().address, + transportType: pRequest.transportType.name, + ); + + if (isApproved) { + if (params.containsKey('transactions')) { + final transactions = params['transactions'] as List; + + List signedTransactions = []; + for (var transaction in transactions) { + final transactionBytes = base64.decode(transaction); + + final unsignedTx = SolanaTransaction.deserialize(transactionBytes); + + final serializedTx = await privateKey.sign(unsignedTx.serializeMessage()); + + unsignedTx.addSignature(privateKey.publicKey().toAddress(), serializedTx); + + final reEncodedTx = unsignedTx.serializeString( + encoding: TransactionSerializeEncoding.base64, + ); + + signedTransactions.add(reEncodedTx); + } + + response = response.copyWith(result: {'transactions': signedTransactions}); + } + } else { + final error = Errors.getSdkError(Errors.USER_REJECTED); + response = response.copyWith( + error: JsonRpcError(code: error.code, message: error.message), + ); + } + } catch (e, s) { + debugPrint('solanaSignAllTransactions error $e, $s'); + final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); + + response = response.copyWith( + error: JsonRpcError(code: error.code, message: error.message), + ); + } + + await walletKit.respondSessionRequest(topic: topic, response: response); + + _handleResponseForTopic(topic, response); + } + + SolanaPrivateKey _getSolanaPrivateKey() { + final keys = wcKeyService.getKeysForChain(appStore.wallet!); + + return SolanaPrivateKey.fromSeedHex(keys[0].privateKey); + } + + void _handleResponseForTopic(String topic, JsonRpcResponse response) async { + final session = walletKit.sessions.get(topic); + + try { + await walletKit.respondSessionRequest(topic: topic, response: response); + + MethodsUtils.handleRedirect( + topic, + session!.peer.metadata.redirect, + response.error?.message, + ); + } on ReownSignError catch (error) { + if (error.message.contains('No matching key')) { + MethodsUtils.handleRedirect( + topic, + session!.peer.metadata.redirect, + '${S.current.error_while_processing} ${S.current.youCanGoBackToYourDapp}', + ); + } else { + MethodsUtils.handleRedirect( + topic, + session!.peer.metadata.redirect, + error.message, + ); + } + } + } +} + +extension on Map { + TransactionInstruction toInstruction() { + final programId = this['programId'] as String; + + final data = (this['data'] as List).map((e) => e as int).toList(); + // final data58 = base58.encode(Uint8List.fromList(data)); + // final dataBytes = ByteArray.fromBase58(data58); + + final keys = this['keys'] as List; + return TransactionInstruction.fromBytes( + programId: SolAddress(programId), + instructionBytes: data, + keys: keys.map((k) { + final kParams = (k as Map); + return AccountMeta( + publicKey: + SolanaPublicKey.fromBytes(base58.decode(kParams['pubkey'] as String)).toAddress(), + isWritable: kParams['isWritable'] as bool, + isSigner: kParams['isSigner'] as bool, + ); + }).toList(), + ); + } +} diff --git a/lib/src/screens/wallet_connect/services/chain_service/solana/solana_supported_methods.dart b/lib/src/screens/wallet_connect/services/chain_service/solana/solana_supported_methods.dart new file mode 100644 index 000000000..c51482d3a --- /dev/null +++ b/lib/src/screens/wallet_connect/services/chain_service/solana/solana_supported_methods.dart @@ -0,0 +1,16 @@ +enum SolanaSupportedMethods { + solSignMessage, + solSignTransaction, + solSignAllTransaction; + + String get name { + switch (this) { + case solSignMessage: + return 'solana_signMessage'; + case solSignTransaction: + return 'solana_signTransaction'; + case solSignAllTransaction: + return 'solana_signAllTransactions'; + } + } +} diff --git a/lib/src/screens/wallet_connect/services/key_service/chain_key_model.dart b/lib/src/screens/wallet_connect/services/key_service/chain_key_model.dart new file mode 100644 index 000000000..e85c31ac3 --- /dev/null +++ b/lib/src/screens/wallet_connect/services/key_service/chain_key_model.dart @@ -0,0 +1,37 @@ +import 'dart:convert'; + +class ChainKeyModel { + final List chains; + final String privateKey; + final String publicKey; + + ChainKeyModel({ + required this.chains, + required this.privateKey, + required this.publicKey, + }); + + String get namespace { + if (chains.isNotEmpty) { + return chains.first.split(':').first; + } + return ''; + } + + Map toJson() => { + 'chains': chains, + 'privateKey': privateKey, + 'publicKey': privateKey, + }; + + factory ChainKeyModel.fromJson(Map json) { + return ChainKeyModel( + chains: (json['chains'] as List).map((e) => '$e').toList(), + privateKey: json['privateKey'] as String, + publicKey: json['publicKey'] as String, + ); + } + + @override + String toString() => jsonEncode(toJson()); +} diff --git a/lib/core/wallet_connect/wallet_connect_key_service.dart b/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart similarity index 96% rename from lib/core/wallet_connect/wallet_connect_key_service.dart rename to lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart index f05adad97..a7f40455c 100644 --- a/lib/core/wallet_connect/wallet_connect_key_service.dart +++ b/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart @@ -1,8 +1,8 @@ import 'package:cake_wallet/ethereum/ethereum.dart'; -import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/solana/solana.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/chain_key_model.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; diff --git a/lib/src/screens/wallet_connect/services/walletkit_service.dart b/lib/src/screens/wallet_connect/services/walletkit_service.dart new file mode 100644 index 000000000..9b48cd233 --- /dev/null +++ b/lib/src/screens/wallet_connect/services/walletkit_service.dart @@ -0,0 +1,623 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:cw_core/wallet_type.dart'; +import 'package:eth_sig_util/util/utils.dart'; +import 'package:flutter/material.dart'; +import 'package:mobx/mobx.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:cake_wallet/entities/preferences_key.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/eth/evm_chain_id.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/chain_key_model.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/utils/eth_utils.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/utils/method_utils.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_connection_request_widget.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_message_display_widget.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_request_widget.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_session_auth_request_widget.dart'; +import 'package:cake_wallet/store/app_store.dart'; + +import 'bottom_sheet_service.dart'; +import 'chain_service/solana/solana_chain_id.dart'; +import 'chain_service/solana/solana_chain_service.dart'; + +part 'walletkit_service.g.dart'; + +class WalletKitService = WalletKitServiceBase with _$WalletKitService; + +abstract class WalletKitServiceBase with Store { + WalletKitServiceBase( + this._bottomSheetHandler, + this.walletKeyService, + this.appStore, + this.sharedPreferences, + ) : pairings = ObservableList(), + sessions = ObservableList(), + auth = ObservableList(), + isInitialized = false; + + final AppStore appStore; + final SharedPreferences sharedPreferences; + final BottomSheetService _bottomSheetHandler; + final WalletConnectKeyService walletKeyService; + + late ReownWalletKit _walletKit; + + @observable + bool isInitialized; + + /// The list of requests from the dapp + /// Potential types include, but aren't limited to: + /// [SessionProposalEvent], [SessionAuthRequest] + @observable + ObservableList pairings; + + @observable + ObservableList sessions; + + @observable + ObservableList auth; + + @action + void create() { + // Create the walletkit client + _walletKit = ReownWalletKit( + core: ReownCore( + projectId: secrets.walletConnectProjectId, + ), + metadata: const PairingMetadata( + name: 'Cake Wallet', + description: 'Cake Wallet', + url: 'https://cakewallet.com', + icons: ['https://cakewallet.com/assets/image/cake_logo.png'], + ), + ); + + _walletKit.core.addLogListener(_logListener); + + // Setup our listeners + log('Created instance of walletKit'); + + _walletKit.core.pairing.onPairingInvalid.subscribe(_onPairingInvalid); + _walletKit.core.pairing.onPairingCreate.subscribe(_onPairingCreate); + _walletKit.core.relayClient.onRelayClientError.subscribe(_onRelayClientError); + _walletKit.core.relayClient.onRelayClientMessage.subscribe(_onRelayClientMessage); + + _walletKit.onSessionProposal.subscribe(_onSessionProposal); + _walletKit.onSessionProposalError.subscribe(_onSessionProposalError); + _walletKit.onSessionConnect.subscribe(_onSessionConnect); + _walletKit.onSessionAuthRequest.subscribe(_onSessionAuthRequest); + + _walletKit.pairings.onSync.subscribe(_onPairingsSync); + _walletKit.core.pairing.onPairingDelete.subscribe(_onPairingDelete); + _walletKit.core.pairing.onPairingExpire.subscribe(_onPairingDelete); + + // Setup our accounts + List chainKeys = walletKeyService.getKeys(appStore.wallet!); + for (final chainKey in chainKeys) { + for (final chainId in chainKey.chains) { + final chainNameSpace = getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type); + if (chainNameSpace == chainId) { + final account = '$chainId:${chainKey.publicKey}'; + debugPrint('registerAccount $account'); + _walletKit.registerAccount( + chainId: chainId, + accountAddress: chainKey.publicKey, + ); + } + } + } + } + + void _logListener(String event) { + debugPrint('[WalletKit] $event'); + } + + @action + Future init() async { + // Await the initialization of walletKit + debugPrint('Intializing walletKit'); + if (!isInitialized) { + try { + await _walletKit.init(); + debugPrint('Initialized'); + isInitialized = true; + } catch (e) { + debugPrint('init Error: ${e.toString()}'); + isInitialized = false; + } + } + + await _emitEvent(); + + _refreshPairings(); + + final newSessions = _walletKit.sessions.getAll(); + sessions.addAll(newSessions); + + final newAuthRequests = _walletKit.sessionAuthRequests.getAll(); + auth.addAll(newAuthRequests); + + if (isEVMCompatibleChain(appStore.wallet!.type)) { + for (final cId in EVMChainId.values) { + EvmChainServiceImpl( + reference: cId, + appStore: appStore, + wcKeyService: walletKeyService, + bottomSheetService: _bottomSheetHandler, + walletKit: _walletKit, + ); + } + } + + if (appStore.wallet!.type == WalletType.solana) { + for (final cId in SolanaChainId.values) { + SolanaChainService( + reference: cId, + appStore: appStore, + wcKeyService: walletKeyService, + bottomSheetService: _bottomSheetHandler, + walletKit: _walletKit, + ); + } + } + } + + @action + Future _emitEvent() async { + final isOnline = _walletKit.core.connectivity.isOnline.value; + if (!isOnline) { + await Future.delayed(const Duration(milliseconds: 500)); + _emitEvent(); + return; + } + + final sessions = _walletKit.sessions.getAll(); + for (var session in sessions) { + final chainKeys = walletKeyService.getKeysForChain(appStore.wallet!); + for (var chain in chainKeys) { + for (var chainID in chain.chains) { + try { + final events = NamespaceUtils.getNamespacesEventsForChain( + chainId: chainID, + namespaces: session.namespaces, + ); + if (events.contains('accountsChanged')) { + final chainKeys = walletKeyService.getKeysForChain(appStore.wallet!); + _walletKit.emitSessionEvent( + topic: session.topic, + chainId: chainID, + event: SessionEventParams( + name: 'accountsChanged', + data: [chainKeys.first.publicKey], + ), + ); + } + } catch (_) {} + } + } + } + } + + @action + FutureOr onDispose() { + log('walletKit dispose'); + _walletKit.core.removeLogListener(_logListener); + + _walletKit.core.pairing.onPairingInvalid.unsubscribe(_onPairingInvalid); + _walletKit.core.pairing.onPairingCreate.unsubscribe(_onPairingCreate); + _walletKit.core.relayClient.onRelayClientError.unsubscribe(_onRelayClientError); + _walletKit.core.relayClient.onRelayClientMessage.unsubscribe(_onRelayClientMessage); + + _walletKit.onSessionProposal.unsubscribe(_onSessionProposal); + _walletKit.onSessionProposalError.unsubscribe(_onSessionProposalError); + _walletKit.onSessionConnect.unsubscribe(_onSessionConnect); + _walletKit.onSessionAuthRequest.unsubscribe(_onSessionAuthRequest); + + _walletKit.pairings.onSync.unsubscribe(_onPairingsSync); + _walletKit.core.pairing.onPairingDelete.unsubscribe(_onPairingDelete); + _walletKit.core.pairing.onPairingExpire.unsubscribe(_onPairingDelete); + + isInitialized = false; + } + + ReownWalletKit get walletKit => _walletKit; + + void _onRelayClientMessage(MessageEvent? event) async { + if (event != null) { + final jsonObject = await EthUtils.decodeMessageEvent(event); + debugPrint('_onRelayClientMessage $jsonObject'); + + if (jsonObject is JsonRpcRequest) { + debugPrint(jsonObject.id.toString()); + debugPrint(jsonObject.method); + + if (jsonObject.method == 'wc_sessionDelete') { + await disconnectSession(topic: event.topic); + } + } + } + } + + void _onPairingsSync(StoreSyncEvent? args) { + if (args != null) { + _refreshPairings(); + } + } + + void _onPairingDelete(PairingEvent? event) { + _refreshPairings(); + } + + @action + Future _onSessionProposal(SessionProposalEvent? args) async { + debugPrint('_onSessionProposal ${jsonEncode(args?.params)}'); + + if (args != null) { + final proposer = args.params.proposer; + final result = (await _bottomSheetHandler.queueBottomSheet( + widget: WCRequestWidget( + verifyContext: args.verifyContext, + child: WCConnectionRequestWidget( + proposalData: args.params, + verifyContext: args.verifyContext, + requester: proposer, + walletKeyService: walletKeyService, + walletKit: walletKit, + appStore: appStore, + ), + ), + )) ?? + WCBottomSheetResult.reject; + + if (result != WCBottomSheetResult.reject) { + try { + await _walletKit.approveSession( + id: args.id, + namespaces: NamespaceUtils.regenerateNamespacesWithChains( + args.params.generatedNamespaces!, + ), + sessionProperties: args.params.sessionProperties, + ); + } on ReownSignError catch (error) { + MethodsUtils.handleRedirect( + '', + proposer.metadata.redirect, + error.message, + ); + } + } else { + final error = Errors.getSdkError(Errors.USER_REJECTED).toSignError(); + await _walletKit.rejectSession(id: args.id, reason: error); + await _walletKit.core.pairing.disconnect(topic: args.params.pairingTopic); + MethodsUtils.handleRedirect( + '', + proposer.metadata.redirect, + error.message, + ); + } + } + } + + @action + Future _onSessionProposalError(SessionProposalErrorEvent? args) async { + debugPrint('_onSessionProposalError $args'); + + if (args != null) { + String errorMessage = args.error.message; + if (args.error.code == 5100) { + errorMessage = + errorMessage.replaceFirst('${S.current.requested}:', '\n\n${S.current.requested}:'); + errorMessage = + errorMessage.replaceFirst('${S.current.supported}:', '\n\n${S.current.supported}:'); + } + MethodsUtils.goBackModal( + title: S.current.error, + message: errorMessage, + success: false, + ); + } + } + + @action + Future _onSessionConnect(SessionConnect? args) async { + if (args != null) { + final session = jsonEncode(args.session.toJson()); + + debugPrint('_onSessionConnect $session'); + + await savePairingTopicToLocalStorage(args.session.pairingTopic); + + sessions.add(args.session); + + _refreshPairings(); + + MethodsUtils.handleRedirect( + args.session.topic, + args.session.peer.metadata.redirect, + '', + true, + ); + } + } + + @action + void _onRelayClientError(ErrorEvent? args) { + debugPrint('_onRelayClientError ${args?.error}'); + // _bottomSheetHandler.queueBottomSheet( + // isModalDismissible: true, + // widget: BottomSheetMessageDisplayWidget( + // message: "WC RelayClient Error: ${args?.error}", + // ), + // ); + } + + @action + void _onPairingInvalid(PairingInvalidEvent? args) { + debugPrint('_onPairingInvalid $args'); + _bottomSheetHandler.queueBottomSheet( + isModalDismissible: true, + widget: BottomSheetMessageDisplayWidget( + message: '${S.current.pairingInvalidEvent}: $args', + ), + ); + } + + @action + void _onPairingCreate(PairingEvent? args) { + debugPrint('_onPairingCreate $args'); + } + + Future _onSessionAuthRequest(SessionAuthRequest? args) async { + if (args != null) { + final SessionAuthPayload authPayload = args.authPayload; + final jsonPyaload = jsonEncode(authPayload.toJson()); + + debugPrint('_onSessionAuthRequest $jsonPyaload'); + + final chainKeys = walletKeyService.getKeysForChain(appStore.wallet!); + final supportedChains = chainKeys.first.chains; + final supportedMethods = getChainSupportedMethodsOnWalletType(appStore.wallet!.type); + + final newAuthPayload = AuthSignature.populateAuthPayload( + authPayload: authPayload, + chains: supportedChains.toList(), + methods: supportedMethods.toList(), + ); + final cacaoRequestPayload = CacaoRequestPayload.fromSessionAuthPayload( + newAuthPayload, + ); + + final List> formattedMessages = []; + for (var chain in newAuthPayload.chains) { + final chainKeys = walletKeyService.getKeysForChain(appStore.wallet!); + final iss = 'did:pkh:$chain:${chainKeys.first.publicKey}'; + + final message = _walletKit.formatAuthMessage( + iss: iss, + cacaoPayload: cacaoRequestPayload, + ); + formattedMessages.add({iss: message}); + } + + final WCBottomSheetResult result = (await _bottomSheetHandler.queueBottomSheet( + widget: WCSessionAuthRequestWidget( + child: WCConnectionRequestWidget( + sessionAuthPayload: newAuthPayload, + verifyContext: args.verifyContext, + requester: args.requester, + walletKeyService: walletKeyService, + walletKit: _walletKit, + appStore: appStore, + ), + ), + ) as WCBottomSheetResult?) ?? + WCBottomSheetResult.reject; + + if (result != WCBottomSheetResult.reject) { + final chainKeys = walletKeyService.getKeysForChain(appStore.wallet!); + final privateKey = '0x${chainKeys.first.privateKey}'; + final credentials = EthPrivateKey.fromHex(privateKey); + // + final messageToSign = formattedMessages.length; + final count = (result == WCBottomSheetResult.one) ? 1 : messageToSign; + // + final List cacaos = []; + for (var i = 0; i < count; i++) { + final iss = formattedMessages[i].keys.first; + final message = formattedMessages[i].values.first as String; + + final signature = credentials.signPersonalMessageToUint8List( + Uint8List.fromList(message.codeUnits), + ); + final hexSignature = bytesToHex(signature, include0x: true); + + cacaos.add( + AuthSignature.buildAuthObject( + requestPayload: cacaoRequestPayload, + signature: CacaoSignature(t: CacaoSignature.EIP191, s: hexSignature), + iss: iss, + ), + ); + } + // + try { + final session = await _walletKit.approveSessionAuthenticate( + id: args.id, + auths: cacaos, + ); + + debugPrint('_onSessionAuthRequest - approveSessionAuthenticate $session'); + + MethodsUtils.handleRedirect( + session.topic, + session.session?.peer.metadata.redirect, + '', + true, + ); + } on ReownSignError catch (error) { + MethodsUtils.handleRedirect( + args.topic, + args.requester.metadata.redirect, + error.message, + ); + } + } else { + final error = Errors.getSdkError(Errors.USER_REJECTED_AUTH); + await _walletKit.rejectSessionAuthenticate( + id: args.id, + reason: error.toSignError(), + ); + MethodsUtils.handleRedirect( + args.topic, + args.requester.metadata.redirect, + error.message, + ); + } + } + } + + @action + Future deletePairing({required String topic}) async { + final topicSessions = sessions.where((element) => element.pairingTopic == topic); + + await _walletKit.core.pairing.disconnect(topic: topic); + for (var session in topicSessions) { + await _walletKit.disconnectSession( + topic: session.topic, + reason: Errors.getSdkError(Errors.USER_DISCONNECTED).toSignError(), + ); + } + } + + @action + Future disconnectSession({required String topic}) async { + await walletKit.disconnectSession( + topic: topic, + reason: Errors.getSdkError(Errors.USER_DISCONNECTED).toSignError(), + ); + } + + @action + Future updateSession({ + required String topic, + required Map namespaces, + }) async { + await walletKit.updateSession(topic: topic, namespaces: namespaces); + } + + @action + Future extendSession({required String topic}) async { + await walletKit.extendSession(topic: topic); + } + + @action + Future pairWithUri(Uri uri) async { + try { + debugPrint('pairWithUri - Pairing with URI: $uri'); + await _walletKit.pair(uri: uri); + } on ReownSignError catch (e) { + _bottomSheetHandler.queueBottomSheet( + isModalDismissible: true, + widget: BottomSheetMessageDisplayWidget(message: e.message), + ); + } catch (e) { + _bottomSheetHandler.queueBottomSheet( + isModalDismissible: true, + widget: BottomSheetMessageDisplayWidget(message: e.toString()), + ); + } + } + + @action + void _refreshPairings() { + debugPrint('_refreshPairings - Refreshing pairings'); + + pairings.clear(); + + final allPairings = _walletKit.pairings.getAll(); + + final keyForWallet = getKeyForStoringTopicsForWallet(); + + if (keyForWallet.isEmpty) return; + + final currentTopicsForWallet = getPairingTopicsForWallet(keyForWallet); + + final filteredPairings = allPairings.where( + (pairing) { + bool isInCurrentTopics = currentTopicsForWallet.contains(pairing.topic); + bool isActive = pairing.active; + + return isInCurrentTopics && isActive; + }, + ).toList(); + + pairings.addAll(filteredPairings); + } + + @action + List getSessionsForPairingInfo(PairingInfo pairing) { + return sessions.where((element) => element.pairingTopic == pairing.topic).toList(); + } + + String getKeyForStoringTopicsForWallet() { + List chainKeys = walletKeyService.getKeysForChain(appStore.wallet!); + + if (chainKeys.isEmpty) { + return ''; + } + + final keyForPairingTopic = + PreferencesKey.walletConnectPairingTopicsListForWallet(chainKeys.first.publicKey); + + return keyForPairingTopic; + } + + List getPairingTopicsForWallet(String key) { + // Get the JSON-encoded string from shared preferences + final jsonString = sharedPreferences.getString(key); + + // If the string is null, return an empty list + if (jsonString == null) { + return []; + } + + // Decode the JSON string to a list of strings + final List jsonList = jsonDecode(jsonString) as List; + + // Cast each item to a string + return jsonList.map((item) => item as String).toList(); + } + + Future savePairingTopicToLocalStorage(String pairingTopic) async { + // Get key specific to the current wallet + final key = getKeyForStoringTopicsForWallet(); + + if (key.isEmpty) return; + + // Get all pairing topics attached to this key + final pairingTopicsForWallet = getPairingTopicsForWallet(key); + + bool isPairingTopicAlreadySaved = pairingTopicsForWallet.contains(pairingTopic); + debugPrint('Is Pairing Topic Saved: $isPairingTopicAlreadySaved'); + + if (!isPairingTopicAlreadySaved) { + // Update the list with the most recent pairing topic + pairingTopicsForWallet.add(pairingTopic); + + // Convert the list of updated pairing topics to a JSON-encoded string + final jsonString = jsonEncode(pairingTopicsForWallet); + + // Save the encoded string to shared preferences + await sharedPreferences.setString(key, jsonString); + } + } +} diff --git a/lib/src/screens/wallet_connect/utils/eth_utils.dart b/lib/src/screens/wallet_connect/utils/eth_utils.dart new file mode 100644 index 000000000..766182b75 --- /dev/null +++ b/lib/src/screens/wallet_connect/utils/eth_utils.dart @@ -0,0 +1,88 @@ +import 'dart:convert'; + +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart'; +import 'package:convert/convert.dart'; +import 'package:flutter/foundation.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; + +class EthUtils { + static String getUtf8Message(String maybeHex) { + if (maybeHex.startsWith('0x')) { + final List decoded = hex.decode( + maybeHex.substring(2), + ); + return utf8.decode(decoded); + } + + return maybeHex; + } + + static String? getAddressFromSessionRequest(SessionRequest request) { + try { + final paramsList = List.from((request.params as List)); + if (request.method == 'personal_sign') { + // for `personal_sign` first value in params has to be always the message + paramsList.removeAt(0); + } + + return paramsList.firstWhere((p) { + try { + EthereumAddress.fromHex(p ?? ''); + return true; + } catch (e) { + return false; + } + }); + } catch (e) { + debugPrint(e.toString()); + return null; + } + } + + static dynamic getDataFromSessionRequest(SessionRequest request) { + try { + final paramsList = List.from((request.params as List)); + if (request.method == 'personal_sign') { + return paramsList.first; + } + return paramsList.firstWhere((p) { + final address = getAddressFromSessionRequest(request); + return p != address; + }); + } catch (e) { + debugPrint('getDataFromSessionRequest $e'); + return null; + } + } + + static Map? getTransactionFromSessionRequest( + SessionRequest request, + ) { + try { + final param = (request.params as List).first; + return param as Map; + } catch (e) { + debugPrint('getTransactionFromSessionRequest $e'); + return null; + } + } + + static Future decodeMessageEvent(MessageEvent event) async { + final walletKit = getIt().walletKit; + + final payloadString = await walletKit.core.crypto.decode( + event.topic, + event.message, + ); + + if (payloadString == null) return null; + + final data = jsonDecode(payloadString) as Map; + if (data.containsKey('method')) { + return JsonRpcRequest.fromJson(data); + } else { + return JsonRpcResponse.fromJson(data); + } + } +} diff --git a/lib/src/screens/wallet_connect/utils/method_utils.dart b/lib/src/screens/wallet_connect/utils/method_utils.dart new file mode 100644 index 000000000..41e947263 --- /dev/null +++ b/lib/src/screens/wallet_connect/utils/method_utils.dart @@ -0,0 +1,139 @@ +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/models/wc_connection_model.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_connection_widget.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_request_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; + +class MethodsUtils { + static final walletKit = getIt.get().walletKit; + static final bottomSheetService = getIt.get(); + + static Future requestApproval( + String text, { + String? title, + String? method, + String? chainId, + String? address, + required String transportType, + List extraModels = const [], + VerifyContext? verifyContext, + }) async { + final WCBottomSheetResult result = (await bottomSheetService.queueBottomSheet( + widget: WCRequestWidget( + verifyContext: verifyContext, + child: WCConnectionWidget( + title: title ?? S.current.approve_request, + info: [ + WCConnectionModel( + title: '${S.current.method}: $method\n' + '${S.current.transport_type}: ${transportType.toUpperCase()}\n' + '${S.current.chain_id}: $chainId\n' + '${S.current.address}: $address\n\n' + '${S.current.message}:', + elements: [text], + ), + ...extraModels, + ], + ), + ), + ) as WCBottomSheetResult?) ?? + WCBottomSheetResult.reject; + + return result != WCBottomSheetResult.reject; + } + + static void handleRedirect( + String topic, + Redirect? redirect, [ + String? error, + bool success = false, + ]) { + debugPrint('handleRedirect topic: $topic, redirect: $redirect, error: $error'); + openApp( + topic, + redirect, + onFail: (e) => goBackModal( + title: success ? S.current.success : S.current.error, + message: error, + success: success, + ), + ); + } + + static void openApp( + String topic, + Redirect? redirect, { + int delay = 100, + Function(ReownSignError? error)? onFail, + }) async { + await Future.delayed(Duration(milliseconds: delay)); + try { + await walletKit.redirectToDapp( + topic: topic, + redirect: redirect, + ); + } on ReownSignError catch (e) { + onFail?.call(e); + } + } + + static void goBackModal({ + String? title, + String? message, + bool success = true, + }) async { + await bottomSheetService.queueBottomSheet( + closeAfter: success ? 3 : 0, + widget: GoBackModalWidget( + isSuccess: success, + title: title, + message: message, + ), + ); + } +} + +class GoBackModalWidget extends StatelessWidget { + const GoBackModalWidget({ + required this.isSuccess, + this.message, + this.title, + super.key, + }); + + final bool isSuccess; + final String? title; + final String? message; + + @override + Widget build(BuildContext context) { + return Container( + color: Theme.of(context).colorScheme.background, + height: 280.0, + width: double.infinity, + padding: const EdgeInsets.all(20.0), + child: Column( + children: [ + Icon( + isSuccess ? Icons.check_circle_sharp : Icons.error_outline_sharp, + color: isSuccess ? Colors.green[100] : Colors.red[100], + size: 80.0, + ), + Text( + title ?? S.current.connected, + style: TextStyle( + color: Theme.of(context).appBarTheme.titleTextStyle!.color!, + fontSize: 18.0, + fontWeight: FontWeight.w600, + ), + ), + Text(message ?? S.current.youCanGoBackToYourDapp), + ], + ), + ); + } +} diff --git a/lib/src/screens/wallet_connect/utils/namespace_model_builder.dart b/lib/src/screens/wallet_connect/utils/namespace_model_builder.dart index 936df93d3..26cb3bbcf 100644 --- a/lib/src/screens/wallet_connect/utils/namespace_model_builder.dart +++ b/lib/src/screens/wallet_connect/utils/namespace_model_builder.dart @@ -1,71 +1,76 @@ import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_widget.dart'; -import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; - -import '../../../../core/wallet_connect/models/connection_model.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/models/wc_connection_model.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_connection_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; class ConnectionWidgetBuilder { - static List buildFromRequiredNamespaces( - Map requiredNamespaces, + static List buildFromRequiredNamespaces( + Map generatedNamespaces, ) { - final List views = []; - for (final key in requiredNamespaces.keys) { - RequiredNamespace ns = requiredNamespaces[key]!; - final List models = []; - // If the chains property is present, add the chain data to the models - if (ns.chains != null) { - models.add(ConnectionModel(title: S.current.chains, elements: ns.chains!)); - } - models.add(ConnectionModel(title: S.current.methods, elements: ns.methods)); - models.add(ConnectionModel(title: S.current.events, elements: ns.events)); + final List views = []; + for (final key in generatedNamespaces.keys) { + final namespaces = generatedNamespaces[key]!; + final chains = NamespaceUtils.getChainsFromAccounts(namespaces.accounts); - views.add(ConnectionWidget(title: key, info: models)); + final List models = []; + + // If the chains property is present, add the chain data to the models + models.add(WCConnectionModel(title: S.current.chains, elements: chains)); + models.add(WCConnectionModel(title: S.current.methods, elements: namespaces.methods)); + + if (namespaces.events.isNotEmpty) { + models.add(WCConnectionModel(title: S.current.events, elements: namespaces.events)); + } + + views.add(WCConnectionWidget(title: key, info: models)); } return views; } - static List buildFromNamespaces( + static List buildFromNamespaces( String topic, Map namespaces, - Web3Wallet web3wallet, + BuildContext context, ) { - final List views = []; + final List views = []; for (final key in namespaces.keys) { - final Namespace ns = namespaces[key]!; - final List models = []; + final ns = namespaces[key]!; + final List models = []; + // If the chains property is present, add the chain data to the models - models.add( - ConnectionModel( - title: S.current.chains, - elements: ns.accounts, - ), - ); - models.add(ConnectionModel( - title: S.current.methods, - elements: ns.methods, - )); + models.add(WCConnectionModel(title: S.current.accounts, elements: ns.accounts)); + models.add(WCConnectionModel(title: S.current.methods, elements: ns.methods)); - Map actions = {}; - for (final String event in ns.events) { - actions[event] = () async { - final String chainId = NamespaceUtils.isValidChainId(key) - ? key - : NamespaceUtils.getChainFromAccount(ns.accounts.first); - await web3wallet.emitSessionEvent( - topic: topic, - chainId: chainId, - event: SessionEventParams(name: event, data: '${S.current.event}: $event'), - ); - }; + if (ns.events.isNotEmpty) { + models.add(WCConnectionModel(title: S.current.events, elements: ns.events)); } - models.add( - ConnectionModel(title: S.current.events, elements: ns.events, elementActions: actions), - ); - views.add(ConnectionWidget(title: key, info: models)); + views.add(WCConnectionWidget(title: key, info: models)); } return views; } + + static Map updateNamespaces( + Map currentNamespaces, + String namespace, + List newChains, + ) { + final updatedNamespaces = Map.from(currentNamespaces); + + final accounts = currentNamespaces[namespace]!.accounts; + final address = NamespaceUtils.getAccount(accounts.first); + final newAccounts = newChains.map((c) => '$c:$address').toList(); + + final newNamespaces = currentNamespaces[namespace]!.copyWith( + chains: NamespaceUtils.getChainsFromAccounts(accounts)..addAll(newChains), + accounts: List.from(accounts)..addAll(newAccounts), + ); + + updatedNamespaces[namespace] = newNamespaces; + + return updatedNamespaces; + } } diff --git a/lib/src/screens/wallet_connect/utils/string_parsing.dart b/lib/src/screens/wallet_connect/utils/string_parsing.dart index 0aed1b9e9..bb36e1879 100644 --- a/lib/src/screens/wallet_connect/utils/string_parsing.dart +++ b/lib/src/screens/wallet_connect/utils/string_parsing.dart @@ -1,20 +1,6 @@ -import 'dart:convert'; import 'dart:math'; -import 'package:convert/convert.dart'; - extension StringParsing on String { - String get utf8Message { - if (startsWith('0x')) { - final List decoded = hex.decode( - substring(2), - ); - return utf8.decode(decoded); - } - - return this; - } - String safeSubString(int start, int end) { return this.substring(0, min(this.toString().length, 12)); } diff --git a/lib/src/screens/wallet_connect/wc_connections_listing_view.dart b/lib/src/screens/wallet_connect/wc_connections_listing_view.dart index 3a78f6af6..36a8d2d72 100644 --- a/lib/src/screens/wallet_connect/wc_connections_listing_view.dart +++ b/lib/src/screens/wallet_connect/wc_connections_listing_view.dart @@ -1,7 +1,6 @@ -import 'dart:developer'; -import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart'; import 'package:cake_wallet/src/screens/wallet_connect/widgets/enter_wallet_connect_uri_widget.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; @@ -9,19 +8,19 @@ import 'package:cake_wallet/utils/device_info.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:permission_handler/permission_handler.dart'; -import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; import 'package:cake_wallet/entities/qr_scanner.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/utils/permission_handler.dart'; -import 'widgets/pairing_item_widget.dart'; +import 'widgets/wc_pairing_item_widget.dart'; import 'wc_pairing_detail_page.dart'; class WalletConnectConnectionsView extends StatelessWidget { - final Web3WalletService web3walletService; + final WalletKitService walletKitService; - WalletConnectConnectionsView({required this.web3walletService, Uri? launchUri, Key? key}) + WalletConnectConnectionsView({required this.walletKitService, Uri? launchUri, Key? key}) : super(key: key) { _triggerPairingFromDeeplink(launchUri); } @@ -39,42 +38,38 @@ class WalletConnectConnectionsView extends StatelessWidget { final uriData = Uri.parse(uri); - await web3walletService.pairWithUri(uriData); + await walletKitService.pairWithUri(uriData); } @override Widget build(BuildContext context) { - return WCPairingsWidget(web3walletService: web3walletService); + return WCPairingsWidget(walletKitService: walletKitService); } } class WCPairingsWidget extends BasePage { - WCPairingsWidget({required this.web3walletService, Key? key}) - : web3wallet = web3walletService.getWeb3Wallet(); + WCPairingsWidget({required this.walletKitService, Key? key}) + : walletKit = walletKitService.walletKit; - final Web3Wallet web3wallet; - final Web3WalletService web3walletService; + final ReownWalletKit walletKit; + final WalletKitService walletKitService; @override String get title => S.current.walletConnect; - Future _onScanQrCode(BuildContext context, Web3Wallet web3Wallet) async { + Future _onScanQrCode(BuildContext context, ReownWalletKit web3Wallet) async { final String? uri; if (DeviceInfo.instance.isMobile) { bool isCameraPermissionGranted = - await PermissionHandler.checkPermission(Permission.camera, context); + await PermissionHandler.checkPermission(Permission.camera, context); if (!isCameraPermissionGranted) return; uri = await presentQRScanner(context); } else { uri = await _showEnterWalletConnectURIPopUp(context); } - if (uri == null) return _invalidUriToast(context, S.current.nullURIError); - - log('_onFoundUri: $uri'); - final Uri uriData = Uri.parse(uri); - await web3walletService.pairWithUri(uriData); + await _handleWalletConnectURI(uri, context); } Future _showEnterWalletConnectURIPopUp(BuildContext context) async { @@ -87,6 +82,17 @@ class WCPairingsWidget extends BasePage { return walletConnectURI; } + Future _handleWalletConnectURI( + String? walletConnectURI, + BuildContext context, + ) async { + if (walletConnectURI == null) return _invalidUriToast(context, S.current.nullURIError); + + log('_onFoundUri: $walletConnectURI'); + final Uri uriData = Uri.parse(walletConnectURI); + await walletKitService.pairWithUri(uriData); + } + Future _invalidUriToast(BuildContext context, String message) async { await showPopUp( context: context, @@ -126,15 +132,30 @@ class WCPairingsWidget extends BasePage { text: S.current.newConnection, color: Theme.of(context).primaryColor, textColor: Colors.white, - onPressed: () => _onScanQrCode(context, web3wallet), + onPressed: () => _onScanQrCode(context, walletKit), + ), + SizedBox(height: 4), + TextButton( + onPressed: () async { + final uri = await _showEnterWalletConnectURIPopUp(context); + await _handleWalletConnectURI(uri, context); + }, + child: Text( + 'Click to paste WalletConnect Link', + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.normal, + color: Theme.of(context).extension()!.titleColor, + ), + ), ), ], ), ), - SizedBox(height: 48), + SizedBox(height: 16), Expanded( child: Visibility( - visible: web3walletService.pairings.isEmpty, + visible: walletKitService.pairings.isEmpty, child: Center( child: Text( S.current.activeConnectionsPrompt, @@ -147,10 +168,10 @@ class WCPairingsWidget extends BasePage { ), ), replacement: ListView.builder( - itemCount: web3walletService.pairings.length, + itemCount: walletKitService.pairings.length, itemBuilder: (BuildContext context, int index) { - final pairing = web3walletService.pairings[index]; - return PairingItemWidget( + final pairing = walletKitService.pairings[index]; + return WCPairingItemWidget( key: ValueKey(pairing.topic), pairing: pairing, onTap: () { @@ -159,7 +180,7 @@ class WCPairingsWidget extends BasePage { MaterialPageRoute( builder: (context) => WalletConnectPairingDetailsPage( pairing: pairing, - web3walletService: web3walletService, + walletKitService: walletKitService, ), ), ); diff --git a/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart b/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart index 321d1354a..e479fe94c 100644 --- a/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart +++ b/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart @@ -1,24 +1,22 @@ -import 'dart:developer'; - -import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:flutter/material.dart'; -import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; import 'utils/namespace_model_builder.dart'; class WalletConnectPairingDetailsPage extends StatefulWidget { final PairingInfo pairing; - final Web3WalletService web3walletService; + final WalletKitService walletKitService; const WalletConnectPairingDetailsPage({ required this.pairing, - required this.web3walletService, + required this.walletKitService, super.key, }); @@ -33,7 +31,9 @@ class WalletConnectPairingDetailsPageState extends State sessions = widget.web3walletService.getSessionsForPairingInfo(widget.pairing); + List sessions = widget.walletKitService.getSessionsForPairingInfo(widget.pairing); for (final SessionData session in sessions) { List namespaceWidget = ConnectionWidgetBuilder.buildFromNamespaces( session.topic, session.namespaces, - widget.web3walletService.getWeb3Wallet(), + context, ); // Loop through and add the namespace widgets, but put 20 pixels between each one for (int i = 0; i < namespaceWidget.length; i++) { @@ -61,6 +61,59 @@ class WalletConnectPairingDetailsPageState extends State sessionWidgets; - final Web3WalletService web3walletService; + final WalletKitService walletKitService; @override Widget body(BuildContext context) { @@ -141,7 +194,7 @@ class WCCDetailsWidget extends BasePage { const SizedBox(height: 20.0), PrimaryButton( onPressed: () => - _onDeleteButtonPressed(context, pairing.peerMetadata!.name, web3walletService), + _onDeleteButtonPressed(context, pairing.peerMetadata!.name, walletKitService), text: S.current.delete, color: Theme.of(context).primaryColor, textColor: Colors.white, @@ -154,7 +207,10 @@ class WCCDetailsWidget extends BasePage { } Future _onDeleteButtonPressed( - BuildContext context, String dAppName, Web3WalletService web3walletService) async { + BuildContext context, + String dAppName, + WalletKitService walletKitService, + ) async { bool confirmed = false; await showPopUp( @@ -175,11 +231,13 @@ class WCCDetailsWidget extends BasePage { ); if (confirmed) { try { - await web3walletService.disconnectSession(pairing.topic); + await walletKitService.deletePairing(topic: pairing.topic); - Navigator.of(context).pop(); + if (Navigator.canPop(context)) { + Navigator.pop(context); + } } catch (e) { - log(e.toString()); + debugPrint(e.toString()); } } } diff --git a/lib/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_listener_widget.dart b/lib/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_listener_widget.dart new file mode 100644 index 000000000..fa41ef415 --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_listener_widget.dart @@ -0,0 +1,100 @@ +import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/models/bottom_sheet_queue_item_model.dart'; +import 'package:flutter/material.dart'; + +class BottomSheetListener extends StatefulWidget { + final BottomSheetService bottomSheetService; + final Widget child; + + const BottomSheetListener({ + required this.bottomSheetService, + required this.child, + super.key, + }); + + @override + BottomSheetListenerState createState() => BottomSheetListenerState(); +} + +class BottomSheetListenerState extends State { + @override + void initState() { + super.initState(); + widget.bottomSheetService.currentSheet.addListener(_showBottomSheet); + } + + @override + void dispose() { + widget.bottomSheetService.currentSheet.removeListener(_showBottomSheet); + super.dispose(); + } + + Future _showBottomSheet() async { + if (widget.bottomSheetService.currentSheet.value != null) { + BottomSheetQueueItemModel item = widget.bottomSheetService.currentSheet.value!; + final value = await showModalBottomSheet( + context: context, + isDismissible: item.isModalDismissible, + backgroundColor: Color.fromARGB(0, 0, 0, 0), + isScrollControlled: true, + constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.9), + builder: (context) { + if (item.closeAfter > 0) { + Future.delayed(Duration(seconds: item.closeAfter), () { + try { + if (!mounted) return; + if (Navigator.canPop(context)) { + Navigator.pop(context); + } + } catch (e, s) { + debugPrint('[$runtimeType] close $e $s'); + } + }); + } + return Material( + color: Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.all(Radius.circular(16)), + child: Padding( + padding: EdgeInsets.only( + top: 16, + left: 16, + right: 16, + bottom: MediaQuery.of(context).viewInsets.bottom + 24, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconButton( + color: Theme.of(context).appBarTheme.titleTextStyle!.color!, + padding: const EdgeInsets.all(0.0), + visualDensity: VisualDensity.compact, + onPressed: () { + if (Navigator.canPop(context)) { + Navigator.pop(context); + } + }, + icon: const Icon(Icons.close_sharp), + ), + ], + ), + Flexible(child: item.widget), + ], + ), + ), + ); + }, + ); + + if (!item.completer.isCompleted) { + item.completer.complete(value); + } + widget.bottomSheetService.showNext(); + } + } + + @override + Widget build(BuildContext context) => widget.child; +} diff --git a/lib/src/screens/wallet_connect/widgets/message_display_widget.dart b/lib/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_message_display_widget.dart similarity index 81% rename from lib/src/screens/wallet_connect/widgets/message_display_widget.dart rename to lib/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_message_display_widget.dart index 044915511..1f5dd6768 100644 --- a/lib/src/screens/wallet_connect/widgets/message_display_widget.dart +++ b/lib/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_message_display_widget.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:flutter/material.dart'; class BottomSheetMessageDisplayWidget extends StatelessWidget { @@ -18,7 +19,7 @@ class BottomSheetMessageDisplayWidget extends StatelessWidget { style: TextStyle( fontSize: 16, fontWeight: FontWeight.normal, - color: Colors.white, + color: Theme.of(context).extension()!.titleColor, ), ), SizedBox(height: 8), @@ -30,7 +31,7 @@ class BottomSheetMessageDisplayWidget extends StatelessWidget { style: TextStyle( fontSize: 14, fontWeight: FontWeight.normal, - color: Colors.white, + color: Theme.of(context).extension()!.secondaryTextColor, ), ), ), diff --git a/lib/src/screens/wallet_connect/widgets/connection_request_widget.dart b/lib/src/screens/wallet_connect/widgets/connection_request_widget.dart deleted file mode 100644 index 179bd6b93..000000000 --- a/lib/src/screens/wallet_connect/widgets/connection_request_widget.dart +++ /dev/null @@ -1,180 +0,0 @@ -// ignore_for_file: public_member_api_docs, sort_constructors_first -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:flutter/material.dart'; -import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; - -import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; - -import '../../../../core/wallet_connect/models/auth_request_model.dart'; -import '../../../../core/wallet_connect/models/connection_model.dart'; -import '../../../../core/wallet_connect/models/session_request_model.dart'; -import '../utils/namespace_model_builder.dart'; -import 'connection_widget.dart'; - -class ConnectionRequestWidget extends StatefulWidget { - const ConnectionRequestWidget({ - required this.wallet, - required this.chaindIdNamespace, - this.authRequest, - this.sessionProposal, - Key? key, - }) : super(key: key); - - final Web3Wallet wallet; - final String chaindIdNamespace; - final AuthRequestModel? authRequest; - final SessionRequestModel? sessionProposal; - - @override - State createState() => _ConnectionRequestWidgetState(); -} - -class _ConnectionRequestWidgetState extends State { - ConnectionMetadata? metadata; - - @override - void initState() { - super.initState(); - // Get the connection metadata - metadata = widget.authRequest?.request.requester ?? widget.sessionProposal?.request.proposer; - } - - @override - Widget build(BuildContext context) { - if (metadata == null) { - return Text( - S.current.error, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, - color: Theme.of(context).extension()!.secondaryTextColor, - ), - ); - } - - return _ConnectionMetadataDisplayWidget( - metadata: metadata, - wallet: widget.wallet, - authRequest: widget.authRequest, - sessionProposal: widget.sessionProposal, - chaindIdNamespace: widget.chaindIdNamespace, - ); - } -} - -class _ConnectionMetadataDisplayWidget extends StatelessWidget { - const _ConnectionMetadataDisplayWidget({ - required this.wallet, - required this.metadata, - required this.sessionProposal, - required this.chaindIdNamespace, - this.authRequest, - }); - - final ConnectionMetadata? metadata; - final Web3Wallet wallet; - final String chaindIdNamespace; - final AuthRequestModel? authRequest; - final SessionRequestModel? sessionProposal; - - @override - Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - color: Color.fromARGB(255, 18, 18, 19), - borderRadius: BorderRadius.circular(8), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - metadata!.metadata.name, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.normal, - color: Theme.of(context).extension()!.secondaryTextColor, - ), - textAlign: TextAlign.center, - ), - Text( - S.current.wouoldLikeToConnect, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, - color: Theme.of(context).extension()!.secondaryTextColor, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - Text( - metadata!.metadata.url, - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.normal, - color: Theme.of(context).extension()!.secondaryTextColor, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - Visibility( - visible: authRequest != null, - child: _AuthRequestWidget( - wallet: wallet, - authRequest: authRequest, - chaindIdNamespace: chaindIdNamespace, - ), - - //If authRequest is null, sessionProposal is not null. - replacement: _SessionProposalWidget(sessionProposal: sessionProposal!), - ), - ], - ), - ); - } -} - -class _AuthRequestWidget extends StatelessWidget { - const _AuthRequestWidget({ - required this.wallet, - required this.chaindIdNamespace, - this.authRequest, - }); - - final Web3Wallet wallet; - final String chaindIdNamespace; - final AuthRequestModel? authRequest; - - @override - Widget build(BuildContext context) { - final model = ConnectionModel( - text: wallet.formatAuthMessage( - iss: 'did:pkh:$chaindIdNamespace:${authRequest!.iss}', - cacaoPayload: CacaoRequestPayload.fromPayloadParams( - authRequest!.request.payloadParams, - ), - ), - ); - return ConnectionWidget( - title: S.current.message, - info: [model], - ); - } -} - -class _SessionProposalWidget extends StatelessWidget { - const _SessionProposalWidget({required this.sessionProposal}); - - final SessionRequestModel sessionProposal; - - @override - Widget build(BuildContext context) { - // Create the connection models using the required and optional namespaces provided by the proposal data - // The key is the title and the list of values is the data - final List views = ConnectionWidgetBuilder.buildFromRequiredNamespaces( - sessionProposal.request.requiredNamespaces, - ); - - return Column(children: views); - } -} diff --git a/lib/src/screens/wallet_connect/widgets/enter_wallet_connect_uri_widget.dart b/lib/src/screens/wallet_connect/widgets/enter_wallet_connect_uri_widget.dart index 927e7fb02..79a9fd38a 100644 --- a/lib/src/screens/wallet_connect/widgets/enter_wallet_connect_uri_widget.dart +++ b/lib/src/screens/wallet_connect/widgets/enter_wallet_connect_uri_widget.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/base_alert_dialog.dart'; +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -29,14 +30,14 @@ class _EnterWallectConnectURIWrapperWidgetState extends State()!.secondaryTextColor, + ), decoration: InputDecoration( suffixIcon: Container( width: 24, @@ -79,14 +87,13 @@ class _EnterWalletConnectURIWidget extends BaseAlertDialog { child: InkWell( onTap: () => _pasteWalletConnectURI(), child: Container( - padding: EdgeInsets.all(8), + padding: EdgeInsets.all(10), decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(6)), ), child: Image.asset( 'assets/images/paste_ios.png', - color: - Theme.of(context).extension()!.textFieldButtonIconColor, + color: Theme.of(context).extension()!.titleColor, ), ), ), @@ -99,7 +106,7 @@ class _EnterWalletConnectURIWidget extends BaseAlertDialog { ), ), hintStyle: TextStyle( - color: Theme.of(context).extension()!.textFieldHintColor, + color: Theme.of(context).extension()!.secondaryTextColor, fontWeight: FontWeight.w500, fontSize: 14, ), diff --git a/lib/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart b/lib/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart deleted file mode 100644 index 42bef5135..000000000 --- a/lib/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; -import 'package:flutter/material.dart'; - -import '../../../../../core/wallet_connect/models/bottom_sheet_queue_item_model.dart'; - -class BottomSheetListener extends StatefulWidget { - final BottomSheetService bottomSheetService; - final Widget child; - - const BottomSheetListener({ - required this.child, - required this.bottomSheetService, - super.key, - }); - - @override - BottomSheetListenerState createState() => BottomSheetListenerState(); -} - -class BottomSheetListenerState extends State { - - @override - void initState() { - super.initState(); - widget.bottomSheetService.currentSheet.addListener(_showBottomSheet); - } - - @override - void dispose() { - widget.bottomSheetService.currentSheet.removeListener(_showBottomSheet); - super.dispose(); - } - - Future _showBottomSheet() async { - if (widget.bottomSheetService.currentSheet.value != null) { - BottomSheetQueueItemModel item = widget.bottomSheetService.currentSheet.value!; - final value = await showModalBottomSheet( - context: context, - isDismissible: item.isModalDismissible, - backgroundColor: Color.fromARGB(0, 0, 0, 0), - isScrollControlled: true, - constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.9), - builder: (context) { - return Container( - decoration: const BoxDecoration( - color: Color.fromARGB(255, 18, 18, 19), - borderRadius: BorderRadius.all(Radius.circular(16)), - ), - padding: const EdgeInsets.all(16), - margin: const EdgeInsets.all(16), - child: item.widget, - ); - }, - ); - if (!item.completer.isCompleted) { - item.completer.complete(value); - } - widget.bottomSheetService.resetCurrentSheet(); - } - } - - @override - Widget build(BuildContext context) => widget.child; -} diff --git a/lib/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart b/lib/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart deleted file mode 100644 index f16dcc0f8..000000000 --- a/lib/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/widgets/primary_button.dart'; -import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; -import 'package:flutter/material.dart'; - -class Web3RequestModal extends StatelessWidget { - const Web3RequestModal({required this.child, this.onAccept, this.onReject, super.key}); - - final Widget child; - final VoidCallback? onAccept; - final VoidCallback? onReject; - - @override - Widget build(BuildContext context) { - return SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - child, - const SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - - Expanded( - child: PrimaryButton( - onPressed: onReject ?? () => Navigator.of(context).pop(false), - text: S.current.reject, - color: Theme.of(context).colorScheme.error, - textColor: Theme.of(context).colorScheme.onError, - ), - ), - const SizedBox(width: 16), - Expanded( - child: PrimaryButton( - onPressed: onAccept ?? () => Navigator.of(context).pop(true), - text: S.current.approve, - color: Theme.of(context).primaryColor, - textColor: Theme.of(context).extension()!.titleColor, - ), - ), - ], - ), - ], - ), - ); - } -} diff --git a/lib/src/screens/wallet_connect/widgets/connection_item_widget.dart b/lib/src/screens/wallet_connect/widgets/wc_connection_item_widget.dart similarity index 88% rename from lib/src/screens/wallet_connect/widgets/connection_item_widget.dart rename to lib/src/screens/wallet_connect/widgets/wc_connection_item_widget.dart index 77c30417a..078676495 100644 --- a/lib/src/screens/wallet_connect/widgets/connection_item_widget.dart +++ b/lib/src/screens/wallet_connect/widgets/wc_connection_item_widget.dart @@ -1,11 +1,11 @@ +import 'package:cake_wallet/src/screens/wallet_connect/models/wc_connection_model.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:flutter/material.dart'; -import '../../../../core/wallet_connect/models/connection_model.dart'; -class ConnectionItemWidget extends StatelessWidget { - const ConnectionItemWidget({required this.model, Key? key}) : super(key: key); +class WCConnectionItemWidget extends StatelessWidget { + const WCConnectionItemWidget({required this.model, Key? key}) : super(key: key); - final ConnectionModel model; + final WCConnectionModel model; @override Widget build(BuildContext context) { @@ -52,7 +52,7 @@ class ConnectionItemWidget extends StatelessWidget { class _NoModelElementWidget extends StatelessWidget { const _NoModelElementWidget({required this.model}); - final ConnectionModel model; + final WCConnectionModel model; @override Widget build(BuildContext context) { @@ -73,7 +73,7 @@ class _ModelElementWidget extends StatelessWidget { required this.modelElement, }); - final ConnectionModel model; + final WCConnectionModel model; final String modelElement; @override diff --git a/lib/src/screens/wallet_connect/widgets/wc_connection_request_widget.dart b/lib/src/screens/wallet_connect/widgets/wc_connection_request_widget.dart new file mode 100644 index 000000000..5bf91f65e --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/wc_connection_request_widget.dart @@ -0,0 +1,100 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/models/wc_connection_model.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/utils/namespace_model_builder.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_connection_widget.dart'; +import 'package:cake_wallet/store/app_store.dart'; +import 'package:flutter/material.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; + +class WCConnectionRequestWidget extends StatelessWidget { + WCConnectionRequestWidget({ + this.sessionAuthPayload, + this.proposalData, + this.requester, + this.verifyContext, + required this.walletKeyService, + required this.walletKit, + required this.appStore, + }); + + final SessionAuthPayload? sessionAuthPayload; + final ProposalData? proposalData; + final ConnectionMetadata? requester; + final VerifyContext? verifyContext; + final WalletConnectKeyService walletKeyService; + final AppStore appStore; + final ReownWalletKit walletKit; + + @override + Widget build(BuildContext context) { + if (requester == null) { + return Text(S.current.error.toUpperCase()); + } + + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 8), + Text( + '${requester!.metadata.name} ${S.current.wouoldLikeToConnect}', + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + (sessionAuthPayload != null) + ? _buildSessionAuthRequestView() + : _buildSessionProposalView(context), + ], + ), + ); + } + + Widget _buildSessionAuthRequestView() { + final cacaoPayload = CacaoRequestPayload.fromSessionAuthPayload( + sessionAuthPayload!, + ); + + final List messagesModels = []; + for (var chain in sessionAuthPayload!.chains) { + final chainKeys = walletKeyService.getKeysForChain(appStore.wallet!); + final iss = 'did:pkh:$chain:${chainKeys.first.publicKey}'; + + final message = walletKit.formatAuthMessage( + iss: iss, + cacaoPayload: cacaoPayload, + ); + + messagesModels.add( + WCConnectionModel( + title: '${S.current.message} ${messagesModels.length + 1}', + elements: [message], + ), + ); + } + + return WCConnectionWidget( + title: '${messagesModels.length} ${S.current.messages}', + info: messagesModels, + ); + } + + Widget _buildSessionProposalView(BuildContext context) { + // Create the connection models using the required and optional namespaces provided by the proposal data + + final views = ConnectionWidgetBuilder.buildFromRequiredNamespaces( + proposalData!.generatedNamespaces ?? {}, + ); + + return Column(children: views); + } +} + diff --git a/lib/src/screens/wallet_connect/widgets/connection_widget.dart b/lib/src/screens/wallet_connect/widgets/wc_connection_widget.dart similarity index 61% rename from lib/src/screens/wallet_connect/widgets/connection_widget.dart rename to lib/src/screens/wallet_connect/widgets/wc_connection_widget.dart index 921d8ea5c..e8ae857b1 100644 --- a/lib/src/screens/wallet_connect/widgets/connection_widget.dart +++ b/lib/src/screens/wallet_connect/widgets/wc_connection_widget.dart @@ -1,17 +1,17 @@ -import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/models/wc_connection_model.dart'; import 'package:flutter/material.dart'; -import '../../../../core/wallet_connect/models/connection_model.dart'; -import 'connection_item_widget.dart'; +import 'wc_connection_item_widget.dart'; -class ConnectionWidget extends StatelessWidget { - const ConnectionWidget({required this.title, required this.info, super.key}); +class WCConnectionWidget extends StatelessWidget { + const WCConnectionWidget({required this.title, required this.info, super.key}); final String title; - final List info; + final List info; @override Widget build(BuildContext context) { + return Container( decoration: BoxDecoration( color: Theme.of(context).primaryColorLight, @@ -23,7 +23,7 @@ class ConnectionWidget extends StatelessWidget { children: [ Container( decoration: BoxDecoration( - color: Theme.of(context).colorScheme.background, + color: Theme.of(context) .colorScheme.background, borderRadius: BorderRadius.circular(8), ), padding: EdgeInsets.symmetric(vertical: 8, horizontal: 8), @@ -32,12 +32,12 @@ class ConnectionWidget extends StatelessWidget { style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, - color: Theme.of(context).extension()!.titleColor, + color: Theme.of(context).appBarTheme.titleTextStyle!.color! ), ), ), const SizedBox(height: 8), - ...info.map((e) => ConnectionItemWidget(model: e)), + ...info.map((e) => WCConnectionItemWidget(model: e)), ], ), ); diff --git a/lib/src/screens/wallet_connect/widgets/pairing_item_widget.dart b/lib/src/screens/wallet_connect/widgets/wc_pairing_item_widget.dart similarity index 91% rename from lib/src/screens/wallet_connect/widgets/pairing_item_widget.dart rename to lib/src/screens/wallet_connect/widgets/wc_pairing_item_widget.dart index 518cf32f7..5561c6f61 100644 --- a/lib/src/screens/wallet_connect/widgets/pairing_item_widget.dart +++ b/lib/src/screens/wallet_connect/widgets/wc_pairing_item_widget.dart @@ -3,10 +3,10 @@ import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/themes/extensions/receive_page_theme.dart'; import 'package:flutter/material.dart'; -import 'package:walletconnect_flutter_v2/apis/core/pairing/utils/pairing_models.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; -class PairingItemWidget extends StatelessWidget { - const PairingItemWidget({required this.pairing, required this.onTap, super.key}); +class WCPairingItemWidget extends StatelessWidget { + const WCPairingItemWidget({required this.pairing, required this.onTap, super.key}); final PairingInfo pairing; final void Function() onTap; @@ -14,6 +14,7 @@ class PairingItemWidget extends StatelessWidget { @override Widget build(BuildContext context) { PairingMetadata? metadata = pairing.peerMetadata; + if (metadata == null) { return SizedBox.shrink(); } diff --git a/lib/src/screens/wallet_connect/widgets/wc_request_widget.dart b/lib/src/screens/wallet_connect/widgets/wc_request_widget.dart new file mode 100644 index 000000000..77ca7e681 --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/wc_request_widget.dart @@ -0,0 +1,79 @@ +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_verify_context_widget.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; +import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:flutter/material.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; + +class WCRequestWidget extends StatelessWidget { + WCRequestWidget({ + required this.child, + this.verifyContext, + this.onAccept, + this.onReject, + }); + + final Widget child; + final VerifyContext? verifyContext; + final VoidCallback? onAccept; + final VoidCallback? onReject; + + @override + Widget build(BuildContext context) { + final currentTheme = getIt.get().currentTheme; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + WCVerifyContextWidget( + currentTheme: currentTheme, + verifyContext: verifyContext, + ), + const SizedBox(height: 8), + Flexible( + child: SingleChildScrollView(child: child), + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: PrimaryButton( + onPressed: onReject ?? + () { + if (Navigator.canPop(context)) { + Navigator.of(context).pop(WCBottomSheetResult.reject); + } + }, + text: S.current.reject, + color: Theme.of(context).colorScheme.error, + textColor: Theme.of(context).colorScheme.onError, + ), + ), + const SizedBox(width: 16), + Expanded( + child: PrimaryButton( + onPressed: onAccept ?? + () { + if (Navigator.canPop(context)) { + Navigator.of(context).pop(WCBottomSheetResult.one); + } + }, + text: S.current.approve, + color: Theme.of(context).primaryColor, + textColor: currentTheme.type == ThemeType.dark + ? Theme.of(context).extension()!.textColor + : Theme.of(context).extension()!.buttonTextColor, + ), + ), + ], + ), + ], + ); + } +} diff --git a/lib/src/screens/wallet_connect/widgets/wc_session_auth_request_widget.dart b/lib/src/screens/wallet_connect/widgets/wc_session_auth_request_widget.dart new file mode 100644 index 000000000..a6d4329a8 --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/wc_session_auth_request_widget.dart @@ -0,0 +1,61 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; +import 'package:flutter/material.dart'; + +class WCSessionAuthRequestWidget extends StatelessWidget { + const WCSessionAuthRequestWidget({super.key, required this.child}); + + final Widget child; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: SingleChildScrollView(child: child), + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + PrimaryButton( + onPressed: () { + if (Navigator.canPop(context)) { + Navigator.of(context).pop(WCBottomSheetResult.reject); + } + }, + text: S.current.cancel, + color: Theme.of(context).colorScheme.error, + textColor: Theme.of(context).colorScheme.onError, + ), + const SizedBox(width: 8), + PrimaryButton( + onPressed: () { + if (Navigator.canPop(context)) { + Navigator.of(context).pop(WCBottomSheetResult.one); + } + }, + text: S.current.sign_one, + color: Theme.of(context).primaryColor, + textColor: Theme.of(context).extension()!.titleColor, + ), + const SizedBox(width: 8), + PrimaryButton( + onPressed: () { + if (Navigator.canPop(context)) { + Navigator.of(context).pop(WCBottomSheetResult.all); + } + }, + text: S.current.sign_all, + color: Theme.of(context).secondaryHeaderColor, + textColor: Theme.of(context).extension()!.titleColor, + ), + ], + ), + ], + ); + } +} diff --git a/lib/src/screens/wallet_connect/widgets/wc_verify_context_widget.dart b/lib/src/screens/wallet_connect/widgets/wc_verify_context_widget.dart new file mode 100644 index 000000000..b8929fb4e --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/wc_verify_context_widget.dart @@ -0,0 +1,137 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; +import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:flutter/material.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; + +class WCVerifyContextWidget extends StatelessWidget { + const WCVerifyContextWidget({ + super.key, + required this.verifyContext, + required this.currentTheme, + }); + + final VerifyContext? verifyContext; + final ThemeBase currentTheme; + + @override + Widget build(BuildContext context) { + if (verifyContext == null) { + return const SizedBox.shrink(); + } + + if (verifyContext!.validation.scam) { + return VerifyBanner( + color: Theme.of(context).colorScheme.error, + origin: verifyContext!.origin, + title: S.current.security_risk, + text: S.current.security_risk_description, + ); + } + if (verifyContext!.validation.invalid) { + return VerifyBanner( + color: Theme.of(context).colorScheme.error, + origin: verifyContext!.origin, + title: S.current.domain_mismatch, + text: S.current.domain_mismatch_description, + ); + } + if (verifyContext!.validation.valid) { + return VerifyHeader( + iconColor: currentTheme.type == ThemeType.dark + ? Theme.of(context).extension()!.textColor + : Theme.of(context).extension()!.buttonTextColor, + title: verifyContext!.origin, + ); + } + return VerifyBanner( + color: Colors.orange, + origin: verifyContext!.origin, + title: S.current.cannot_verify, + text: S.current.cannot_verify_description, + ); + } +} + +class VerifyHeader extends StatelessWidget { + const VerifyHeader({ + super.key, + required this.iconColor, + required this.title, + }); + final Color iconColor; + final String title; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.shield_outlined, + color: iconColor, + ), + const SizedBox(width: 8), + Text( + title, + style: TextStyle( + color: iconColor, + fontWeight: FontWeight.bold, + ), + ), + ], + ); + } +} + +class VerifyBanner extends StatelessWidget { + const VerifyBanner({ + super.key, + required this.origin, + required this.title, + required this.text, + required this.color, + }); + final String origin, title, text; + final Color color; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Text( + origin, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox.square(dimension: 8.0), + Container( + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: color.withOpacity(0.2), + borderRadius: const BorderRadius.all(Radius.circular(12.0)), + ), + child: Column( + children: [ + VerifyHeader( + iconColor: color, + title: title, + ), + const SizedBox(height: 4.0), + Text( + text, + textAlign: TextAlign.center, + style: TextStyle( + color: color, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/store/app_store.dart b/lib/store/app_store.dart index ff3ba0535..1ab8f716a 100644 --- a/lib/store/app_store.dart +++ b/lib/store/app_store.dart @@ -1,7 +1,8 @@ -import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart'; + import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart'; import 'package:cake_wallet/utils/exception_handler.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/wallet_type.dart'; @@ -46,9 +47,9 @@ abstract class AppStoreBase with Store { this.wallet!.setExceptionHandler(ExceptionHandler.onError); if (isWalletConnectCompatibleChain(wallet.type)) { - await getIt.get().onDispose(); - getIt.get().create(); - await getIt.get().init(); + await getIt.get().onDispose(); + getIt.get().create(); + await getIt.get().init(); } getIt.get().setString(PreferencesKey.currentWalletName, wallet.name); getIt diff --git a/lib/view_model/dashboard/nft_view_model.dart b/lib/view_model/dashboard/nft_view_model.dart index b0474604b..766e588e7 100644 --- a/lib/view_model/dashboard/nft_view_model.dart +++ b/lib/view_model/dashboard/nft_view_model.dart @@ -1,10 +1,11 @@ import 'dart:convert'; import 'dart:developer'; -import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; import 'package:cake_wallet/entities/solana_nft_asset_model.dart'; +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_message_display_widget.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:http/http.dart' as http; import 'package:mobx/mobx.dart'; @@ -121,7 +122,7 @@ abstract class NFTViewModelBase with Store { bottomSheetService.queueBottomSheet( isModalDismissible: true, widget: BottomSheetMessageDisplayWidget( - message: e.toString(), + message: S.current.moralis_nft_error, ), ); } diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 2275e3164..c3267c6da 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -54,7 +54,7 @@ dependencies: basic_utils: ^5.6.1 get_it: ^7.2.0 # connectivity: ^3.0.3 - connectivity_plus: ^5.0.2 + connectivity_plus: ^6.1.3 keyboard_actions: ^4.0.1 another_flushbar: ^1.12.29 archive: ^4.0.3 @@ -83,13 +83,13 @@ dependencies: version: 1.0.0 flutter_plugin_android_lifecycle: 2.0.23 path_provider_android: ^2.2.1 + shared_preferences_android: ^2.4.8 url_launcher_android: 6.3.14 url_launcher_linux: 3.1.1 # https://github.com/flutter/flutter/issues/153083 sensitive_clipboard: git: url: https://github.com/MrCyjaneK/sensitive_clipboard ref: 288c7ee2d63b459bc735f7dc89321b29a1f12fae - walletconnect_flutter_v2: ^2.1.4 eth_sig_util: ^0.0.9 ens_dart: git: @@ -113,6 +113,7 @@ dependencies: git: url: https://github.com/cake-tech/on_chain.git ref: cake-update-v2 + reown_walletkit: ^1.1.2 blockchain_utils: git: url: https://github.com/cake-tech/blockchain_utils @@ -161,6 +162,7 @@ dependency_overrides: git: url: https://github.com/vespr-wallet/ledger-flutter-plus ref: c2e341d8038f1108690ad6f80f7b4b7156aacc76 + web_socket_channel: ^3.0.2 flutter_icons: image_path: "assets/images/app_logo.png" diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 562d0221a..27587a331 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -53,6 +53,7 @@ "anonpay_description": "توليد ${type}. يمكن للمستلم ${method} بأي عملة مشفرة مدعومة ، وستتلقى أموالاً في هذه", "apk_update": "تحديث APK", "approve": "ﺪﻤﺘﻌﻳ", + "approve_request": "الموافقة على الطلب", "arrive_in_this_address": "سيصل ${currency} ${tag}إلى هذا العنوان", "ascending": "تصاعدي", "ask_each_time": "اسأل في كل مرة", @@ -116,9 +117,12 @@ "camera_consent": ".ﻞﻴﺻﺎﻔﺘﻟﺍ ﻰﻠﻋ ﻝﻮﺼﺤﻠﻟ ﻢﻬﺑ ﺔﺻﺎﺨﻟﺍ ﺔﻴﺻﻮﺼﺨﻟﺍ ﺔﺳﺎﻴﺳ ﻦﻣ ﻖﻘﺤﺘﻟﺍ ﻰﺟﺮﻳ .${provider} ﻝﻮﻠ", "camera_permission_is_required": ".ﺍﺮﻴﻣﺎﻜﻟﺍ ﻥﺫﺇ ﺏﻮﻠﻄﻣ", "cancel": "إلغاء", + "cannot_verify": "لا يمكن التحقق", + "cannot_verify_description": "لا يمكن التحقق من هذا المجال. تحقق من الطلب بعناية قبل الموافقة.", "card_address": "العنوان:", "cardholder_agreement": "اتفاقية حامل البطاقة", "cards": "البطاقات", + "chain_id": "معرف سلسلة", "chains": "ﻞﺳﻼﺴﻟﺍ", "change": "تغير", "change_backup_password_alert": "لن تكون ملفات النسخ الاحتياطي السابقة متاحة للاستيراد بكلمة مرور نسخ احتياطي جديدة. سيتم استخدام كلمة مرور النسخ الاحتياطي الجديدة لملفات النسخ الاحتياطي الجديدة فقط. هل أنت متأكد أنك تريد تغيير كلمة المرور الاحتياطية؟", @@ -174,6 +178,7 @@ "connect_yats": "توصيل Yats", "connect_your_hardware_wallet": "قم بتوصيل محفظة الأجهزة الخاصة بك باستخدام Bluetooth أو USB", "connect_your_hardware_wallet_ios": "قم بتوصيل محفظة الأجهزة الخاصة بك باستخدام Bluetooth", + "connected": "متصل", "connection_sync": "الاتصال والمزامنة", "connectWalletPrompt": "ﺕﻼﻣﺎﻌﻤﻟﺍ ءﺍﺮﺟﻹ WalletConnect ﻊﻣ ﻚﺘﻈﻔﺤﻣ ﻞﻴﺻﻮﺘﺑ ﻢﻗ", "contact": "تواصل", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "تعطيل تحسين البطارية", "disableBatteryOptimizationDescription": "هل تريد تعطيل تحسين البطارية من أجل جعل الخلفية مزامنة تعمل بحرية وسلاسة؟", "disabled": "معطلة", + "disconnect_session": "فصل الجلسة", "discount": "وفر ${value}٪", "display_settings": "اعدادات العرض", "displayable": "قابل للعرض", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "لا تشارك هذه مع أي شخص آخر ، بما في ذلك الدعم.\n\nيمكن أن تتم سرقة أموالك!", "do_not_show_me": "لا ترني هذا مجددا", "domain_looks_up": "ﻝﺎﺠﻤﻟﺍ ﺚﺤﺑ ﺕﺎﻴﻠﻤﻋ", + "domain_mismatch": "عدم تطابق المجال", + "domain_mismatch_description": "يحتوي هذا الموقع على مجال لا يتطابق مع مرسل هذا الطلب. قد تؤدي الموافقة على فقدان الأموال.", "donation_link_details": "تفاصيل رابط التبرع", "e_sign_consent": "الموافقة على التوقيع الإلكتروني", "edit": "تعديل", @@ -298,6 +306,7 @@ "error_text_template": "لا يمكن أن يحتوي اسم القالب وعنوانه على رموز ` , \"\nويجب أن يتراوح طولها بين 1 و 106 حرفًا", "error_text_wallet_name": "يمكن أن يحتوي اسم المحفظة على أحرف وأرقام ورموز _ - فقط\nويجب أن يتراوح طولها بين حرف واحد و 33 حرفًا", "error_text_xmr": "لا يمكن أن تتجاوز قيمة XMR الرصيد المتاح.\nيجب أن يكون عدد الكسور أقل من أو يساوي 12", + "error_while_processing": "حدث خطأ أثناء التنقل", "errorGettingCredentials": "ﺩﺎﻤﺘﻋﻻﺍ ﺕﺎﻧﺎﻴﺑ ﻰﻠﻋ ﻝﻮﺼﺤﻟﺍ ءﺎﻨﺛﺃ ﺄﻄﺧ ﺙﺪﺣ :ﻞﺸﻓ", "errorSigningTransaction": "ﺔﻠﻣﺎﻌﻤﻟﺍ ﻊﻴﻗﻮﺗ ءﺎﻨﺛﺃ ﺄﻄﺧ ﺙﺪﺣ", "estimated": "مُقدَّر", @@ -323,6 +332,7 @@ "export_backup": "تصدير نسخة احتياطية", "export_logs": "سجلات التصدير", "export_outputs": "مخرجات التصدير", + "extend_session": "تمديد الجلسة", "extra_id": "معرف إضافي:", "extracted_address_content": "سوف ترسل الأموال إلى\n${recipient_name}", "failed_authentication": "${state_error} فشل المصادقة.", @@ -344,10 +354,12 @@ "forgot_password": "هل نسيت كلمة السر", "freeze": "تجميد", "frequently_asked_questions": "الأسئلة الشائعة", + "from": "من", "frozen": "مجمدة", "frozen_balance": "التوازن المجمد", "full_balance": "الرصيد الكامل", "gas_exceeds_allowance": "الغاز المطلوب بالمعاملة يتجاوز البدل.", + "gas_price": "سعر الغاز", "generate_name": "توليد الاسم", "generating_gift_card": "يتم توليد بطاقة هدية", "generating_transaction": "توليد معاملة", @@ -440,6 +452,8 @@ "memo": "مذكرة:", "message": "ﺔﻟﺎﺳﺭ", "message_verified": "تم التحقق من الرسالة بنجاح", + "messages": "رسائل", + "method": "طريقة", "methods": " ﻕﺮﻃُ", "min_amount": "الحد الأدنى: ${value}", "min_value": "الحد الأدنى: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "موضوع مونيرو الظلام", "monero_light_theme": " ضوء مونيرو", "moonpay_alert_text": "يجب أن تكون قيمة المبلغ أكبر من أو تساوي ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "حدث خطأ أثناء جلب NFTs. يرجى التحقق من اتصال الإنترنت الخاص بك وحاول مرة أخرى.", "more_options": "المزيد من الخيارات", "multiple_addresses_detected": "عناوين متعددة تم اكتشافها", "mweb_confirmed": "أكد MWEB", @@ -589,6 +604,7 @@ "rep_warning_sub": "لا يبدو أن ممثلك في وضع جيد. اضغط هنا لاختيار واحدة جديدة", "repeat_wallet_password": "كرر كلمة مرور المحفظة", "repeated_password_is_incorrect": "كلمة المرور المتكررة غير صحيحة. يرجى تكرار كلمة مرور المحفظة مرة أخرى.", + "requested": "مطلوب", "require_for_adding_contacts": "تتطلب إضافة جهات اتصال", "require_for_all_security_and_backup_settings": "مطلوب لجميع إعدادات الأمان والنسخ الاحتياطي", "require_for_assessing_wallet": "تتطلب الوصول إلى المحفظة", @@ -650,6 +666,8 @@ "second_intro_content": "Yat الخاص بك هو عنوان تعبيري فريد يحل محل جميع العناوين السداسية العشرية الطويلة لجميع عملاتك.", "second_intro_title": "عنوان تعبيري ايموجي واحد يحكمهم جميعا!", "security_and_backup": "الأمان والنسخ الاحتياطي", + "security_risk": "خطر الأمن", + "security_risk_description": "تم وضع علامة على هذا المجال على أنه غير آمن من قبل مقدمي الأمان المتعددين. اترك على الفور لحماية أصولك.", "seed_alert_back": "العودة إلى الوراء", "seed_alert_content": "السييد هي الطريقة الوحيدة لاسترداد محفظتك. هل كتبتها؟", "seed_alert_title": "انتباه", @@ -764,7 +782,9 @@ "show_keys": "اظهار السييد / المفاتيح", "show_market_place": "إظهار السوق", "show_seed": "عرض السييد", + "sign_all": "قم بتوقيع الكل", "sign_message": "تسجيل رسالة", + "sign_one": "وقع واحد", "sign_up": "اشتراك", "sign_verify_message": "تسجيل / تحقق", "sign_verify_message_sub": "قم بتوقيع أو التحقق من رسالة باستخدام المفتاح الخاص بك", @@ -798,6 +818,7 @@ "subaddress_title": "قائمة العناوين الفرعية", "subaddresses": "العناوين الفرعية", "submit_request": "تقديم طلب", + "success": "نجاح", "successful": "ﺢﺟﺎﻧ", "support_description_guides": "توثيق ودعم القضايا المشتركة", "support_description_live_chat": "حرة وسريعة! ممثلو الدعم المدربين متاحون للمساعدة", @@ -805,6 +826,7 @@ "support_title_guides": "مستندات محفظة كعكة", "support_title_live_chat": "الدعم المباشر", "support_title_other_links": "روابط دعم أخرى", + "supported": "مدعوم", "swap": "تبديل", "sweeping_wallet": "كنس المحفظة", "sweeping_wallet_alert": "لن يستغرق هذا وقتًا طويلاً. لا تترك هذه الشاشة وإلا فقد يتم فقد أموال سويبت", @@ -837,6 +859,7 @@ "thorchain_taproot_address_not_supported": "لا يدعم مزود Thorchain عناوين Taproot. يرجى تغيير العنوان أو تحديد مزود مختلف.", "time": "${minutes}د ${seconds}س", "tip": "بقشيش:", + "to": "ل", "today": "اليوم", "token_contract_address": "عنوان عقد الرمز", "token_decimal": "رمز عشري", @@ -904,6 +927,7 @@ "transaction_sent_notice": "إذا لم تستمر الشاشة بعد دقيقة واحدة ، فتحقق من مستكشف البلوك والبريد الإلكتروني.", "transactions": "المعاملات", "transactions_by_date": "المعاملات حسب التاريخ", + "transport_type": "نوع النقل", "trongrid_history": "تاريخ ترونغريد", "trusted": "موثوق به", "tx_commit_exception_no_dust_on_change": "يتم رفض المعاملة مع هذا المبلغ. باستخدام هذه العملات المعدنية ، يمكنك إرسال ${min} دون تغيير أو ${max} الذي يعيد التغيير.", @@ -932,6 +956,7 @@ "unspent_coins_details_title": "تفاصيل العملات الغير المنفقة", "unspent_coins_title": "العملات الغير المنفقة", "unsupported_asset": ".ﻡﻮﻋﺪﻣ ﻞﺻﺃ ﻉﻮﻧ ﻦﻣ ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻭﺃ ءﺎﺸﻧﺇ ﻰﺟﺮﻳ .ﻞﺻﻷﺍ ﺍﺬﻬﻟ ءﺍﺮﺟﻹﺍ ﺍﺬﻫ ﻢﻋﺪﻧ ﻻ ﻦﺤﻧ", + "update_session": "جلسة التحديث", "uptime": "مدة التشغيل", "upto": "حتى ${value}", "usb": "USB", @@ -941,6 +966,7 @@ "use_ssl": "استخدم SSL", "use_suggested": "استخدام المقترح", "use_testnet": "استخدم testnet", + "user_rejected_method": "طريقة رفض المستخدم", "value": "قيمة", "value_type": "نوع القيمة", "variable_pair_not_supported": "هذا الزوج المتغير غير مدعوم في التبادلات المحددة", @@ -1024,5 +1050,6 @@ "you_will_get": "حول الى", "you_will_receive_estimated_amount": "سوف تتلقى(ooded )", "you_will_send": "تحويل من", + "youCanGoBackToYourDapp": "يمكنك العودة إلى DAPP الخاص بك الآن", "yy": "YY" } \ No newline at end of file diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 469621769..90e2e1ca9 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -53,6 +53,7 @@ "anonpay_description": "Генерирайте ${type}. Получателят може да ${method} с всяка поддържана криптовалута и вие ще получите средства в този портфейл.", "apk_update": "APK ъпдейт", "approve": "Одобряване", + "approve_request": "Одобрете искане", "arrive_in_this_address": "${currency} ${tag}ще отидат на този адрес", "ascending": "Възходящ", "ask_each_time": "Питайте всеки път", @@ -116,9 +117,12 @@ "camera_consent": "Вашият фотоапарат ще бъде използван за заснемане на изображение с цел идентификация от ${provider}. Моля, проверете тяхната политика за поверителност за подробности.", "camera_permission_is_required": "Изисква се разрешение за камерата.\nМоля, активирайте го от настройките на приложението.", "cancel": "Откажи", + "cannot_verify": "Не може да провери", + "cannot_verify_description": "Този домейн не може да бъде проверен. Проверете внимателно заявката, преди да одобрите.", "card_address": "Адрес:", "cardholder_agreement": "Съгласие за картодържец", "cards": "Карти", + "chain_id": "Идентификационен номер на веригата", "chains": "Вериги", "change": "Промени", "change_backup_password_alert": "Предишните резервни копия не могат да бъдат импортирани с новата парола. Те ще се използва само за нови такива. Are you sure that you want to change backup password?", @@ -174,6 +178,7 @@ "connect_yats": "Добавете Yats", "connect_your_hardware_wallet": "Свържете хардуерния си портфейл с помощта на Bluetooth или USB", "connect_your_hardware_wallet_ios": "Свържете хардуерния си портфейл с помощта на Bluetooth", + "connected": "Свързани", "connection_sync": "Свързване и синхронизиране", "connectWalletPrompt": "Свържете портфейла си с WalletConnect, за да извършвате транзакции", "contact": "Контакт", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "Деактивирайте оптимизацията на батерията", "disableBatteryOptimizationDescription": "Искате ли да деактивирате оптимизацията на батерията, за да направите синхронизирането на фона да работи по -свободно и гладко?", "disabled": "Деактивирано", + "disconnect_session": "Изключете сесията", "discount": "Спестете ${value}%", "display_settings": "Настройки на екрана", "displayable": "Възможност за показване", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "Не споделяйте това с никого, дори и отдел поддръжка.\n\nПарите Ви могат и ще бъдат откраднати!", "do_not_show_me": "Не показвай повече това", "domain_looks_up": "Търсене на домейни", + "domain_mismatch": "Несъответствие на домейна", + "domain_mismatch_description": "Този уебсайт има домейн, който не съответства на подателя на тази заявка. Одобряването може да доведе до загуба на средства.", "donation_link_details": "Подробности за връзката за дарение", "e_sign_consent": "E-Sign съгласие", "edit": "Промени", @@ -298,6 +306,7 @@ "error_text_template": "Имената на шаблони и адреси не могат да съдържат ` , ' \" \nи трябва да са между 1 и 106 символа.", "error_text_wallet_name": "Името на портфейла може да съдържа само букви, цифри, и символите _ и - \n и трябва да е между 1 и 33 символа", "error_text_xmr": "XMR сумата не може да надхвърля наличния баланс.\nБроят на цифрите след десетичната запетая може да бъде най-много 12", + "error_while_processing": "Възникна грешка при приемане", "errorGettingCredentials": "Неуспешно: Грешка при получаване на идентификационни данни", "errorSigningTransaction": "Възникна грешка при подписване на транзакция", "estimated": "Изчислено", @@ -323,6 +332,7 @@ "export_backup": "Експортиране на резервно копие", "export_logs": "Експортни дневници", "export_outputs": "Експортни резултати", + "extend_session": "Удължаване на сесията", "extra_id": "Допълнително ID:", "extracted_address_content": "Ще изпратите средства на \n${recipient_name}", "failed_authentication": "Неуспешно удостоверяване. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "Забравена парола", "freeze": "Замразяване", "frequently_asked_questions": "Често задавани въпроси", + "from": "От", "frozen": "Замразени", "frozen_balance": "Замразен баланс", "full_balance": "Пълен баланс", "gas_exceeds_allowance": "Газът, изискван от транзакцията, надвишава надбавката.", + "gas_price": "Цена на газ", "generate_name": "Генериране на име", "generating_gift_card": "Създаване на Gift Card", "generating_transaction": "Генериране на транзакция", @@ -440,6 +452,8 @@ "memo": "Мемо:", "message": "Съобщение", "message_verified": "Съобщението беше успешно проверено", + "messages": "Съобщения", + "method": "Метод", "methods": "Методи", "min_amount": "Мин: ${value}", "min_value": "Мин: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "Тъмна тема Monero", "monero_light_theme": "Лека тема Monero", "moonpay_alert_text": "Сумата трябва да бъде най-малко ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "Възникна грешка при извличането на NFT. Моля, проверете вашата интернет връзка и опитайте отново.", "more_options": "Още настройки", "multiple_addresses_detected": "Открити множество адреси", "mweb_confirmed": "Потвърден MWeb", @@ -589,6 +604,7 @@ "rep_warning_sub": "Вашият представител изглежда не е в добро състояние. Докоснете тук, за да изберете нов", "repeat_wallet_password": "Повторете паролата на портфейла", "repeated_password_is_incorrect": "Многократната парола е неправилна. Моля, повторете отново паролата за портфейла.", + "requested": "Поискано", "require_for_adding_contacts": "Изисква се за добавяне на контакти", "require_for_all_security_and_backup_settings": "Изисква се за всички настройки за сигурност и архивиране", "require_for_assessing_wallet": "Изискване за достъп до портфейла", @@ -650,6 +666,8 @@ "second_intro_content": "Вашият Yat е уникален адрес във формата на емоджи, който играе ролята на всички Ваши дълги шестнайсетични портфейли за всяка валута.", "second_intro_title": "Един емоджи адрес, обединяващ всички останали.", "security_and_backup": "Сигурност и резервни копия", + "security_risk": "Риск за сигурността", + "security_risk_description": "Този домейн се маркира като опасен от множество доставчици на сигурност. Оставете незабавно, за да защитите активите си.", "seed_alert_back": "Назад", "seed_alert_content": "Seed-ът е единственият начин да възстановите портфейла си. Записахте ли го?", "seed_alert_title": "Внимание", @@ -764,7 +782,9 @@ "show_keys": "Покажи seed/keys", "show_market_place": "Покажи пазар", "show_seed": "Покажи seed", + "sign_all": "Подпишете всички", "sign_message": "Съобщение за подписване", + "sign_one": "Подпишете един", "sign_up": "Регистрация", "sign_verify_message": "Подпишете / проверете", "sign_verify_message_sub": "Подпишете или проверете съобщение с помощта на вашия личен ключ", @@ -798,6 +818,7 @@ "subaddress_title": "Лист от подадреси", "subaddresses": "Подадреси", "submit_request": "изпращане на заявка", + "success": "Успех", "successful": "Успешен", "support_description_guides": "Документация и подкрепа за общи проблеми", "support_description_live_chat": "Безплатно и бързо! Обучени представители на подкрепата са на разположение за подпомагане", @@ -805,6 +826,7 @@ "support_title_guides": "Документи за портфейл за торта", "support_title_live_chat": "Подкрепа на живо", "support_title_other_links": "Други връзки за поддръжка", + "supported": "Поддържано", "swap": "Разметка", "sweeping_wallet": "Метещ портфейл", "sweeping_wallet_alert": "Това не трябва да отнема много време. Не оставяйте този екран или пометените средства могат да бъдат загубени.", @@ -837,6 +859,7 @@ "thorchain_taproot_address_not_supported": "Доставчикът на Thorchain не поддържа адреси на TapRoot. Моля, променете адреса или изберете друг доставчик.", "time": "${minutes} мин ${seconds} сек", "tip": "Tip:", + "to": "Да", "today": "Днес", "token_contract_address": "Адрес на токен договор", "token_decimal": "Токен десетичен", @@ -904,6 +927,7 @@ "transaction_sent_notice": "Ако процесът продължи повече от 1 минута, проверете някой block explorer и своя имейл.", "transactions": "Транзакции", "transactions_by_date": "Транзакции по дата", + "transport_type": "Тип транспорт", "trongrid_history": "Trongrid History", "trusted": "Надежден", "tx_commit_exception_no_dust_on_change": "Сделката се отхвърля с тази сума. С тези монети можете да изпратите ${min} без промяна или ${max}, която връща промяна.", @@ -932,6 +956,7 @@ "unspent_coins_details_title": "Подробности за неизползваните монети", "unspent_coins_title": "Неизползвани монети", "unsupported_asset": "Не поддържаме това действие за този актив. Моля, създайте или преминете към портфейл от поддържан тип актив.", + "update_session": "Сесия за актуализиране", "uptime": "Време за работа", "upto": "до ${value}", "usb": "USB", @@ -941,6 +966,7 @@ "use_ssl": "Използване на SSL", "use_suggested": "Използване на предложеното", "use_testnet": "Използвайте TestNet", + "user_rejected_method": "Отхвърлен метод на потребителя", "value": "Стойност", "value_type": "Тип стойност", "variable_pair_not_supported": "Този variable pair не се поддържа от избраната борса", @@ -1024,5 +1050,6 @@ "you_will_get": "Обръщане в", "you_will_receive_estimated_amount": "Ще получите(прогнозно )", "you_will_send": "Обръщане от", + "youCanGoBackToYourDapp": "Можете да се върнете при вашия Dapp сега", "yy": "гг" } \ No newline at end of file diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 2bcb5fde2..7f878075f 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -53,6 +53,7 @@ "anonpay_description": "Vygenerujte ${type}. Příjemce může ${method} s jakoukoli podporovanou kryptoměnou a vy obdržíte prostředky v této peněžence.", "apk_update": "aktualizace APK", "approve": "Schvalovat", + "approve_request": "Schválit žádost", "arrive_in_this_address": "${currency} ${tag}přijde na tuto adresu", "ascending": "Vzestupné", "ask_each_time": "Zeptejte se pokaždé", @@ -116,9 +117,12 @@ "camera_consent": "Váš fotoaparát použije k pořízení snímku pro účely identifikace ${provider}. Podrobnosti najdete v jejich Zásadách ochrany osobních údajů.", "camera_permission_is_required": "Vyžaduje se povolení fotoaparátu.\nPovolte jej v nastavení aplikace.", "cancel": "Zrušit", + "cannot_verify": "Nelze ověřit", + "cannot_verify_description": "Tuto doménu nelze ověřit. Před schválením pečlivě zkontrolujte požadavek.", "card_address": "Adresa:", "cardholder_agreement": "Smlouva držitele karty", "cards": "Karty", + "chain_id": "ID řetězce", "chains": "Řetězy", "change": "Změnit", "change_backup_password_alert": "Vaše předchozí soubory se zálohami nebude možné naimportovat s novým heslem. Nové heslo bude použito pouze pro nové zálohy. Opravdu chcete změnit heslo pro zálohy?", @@ -174,6 +178,7 @@ "connect_yats": "Připojit Yaty", "connect_your_hardware_wallet": "Připojte hardwarovou peněženku pomocí Bluetooth nebo USB", "connect_your_hardware_wallet_ios": "Připojte hardwarovou peněženku pomocí Bluetooth", + "connected": "Připojeno", "connection_sync": "Připojení a synch.", "connectWalletPrompt": "Propojte svou peněženku s WalletConnect a provádějte transakce", "contact": "Kontakt", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "Zakázat optimalizaci baterie", "disableBatteryOptimizationDescription": "Chcete deaktivovat optimalizaci baterie, aby se synchronizovala pozadí volně a hladce?", "disabled": "Zakázáno", + "disconnect_session": "Odpojit relace", "discount": "Ušetříte ${value}%", "display_settings": "Nastavení zobrazení", "displayable": "Zobrazitelné", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "Toto nesdílejte s nikým jiným, ani s podporou.\n\nJinak mohou být Vaše prostředky ukradeny!", "do_not_show_me": "Příště nezobrazovat", "domain_looks_up": "Vyhledávání domén", + "domain_mismatch": "Neshoda domény", + "domain_mismatch_description": "Tento web má doménu, která neodpovídá odesílateli této žádosti. Schválení může vést ke ztrátě finančních prostředků.", "donation_link_details": "Podrobnosti odkazu na darování", "e_sign_consent": "E-Sign souhlas", "edit": "Upravit", @@ -298,6 +306,7 @@ "error_text_template": "Jméno šablony a adresa nemohou obsahovat symboly ` , ' \" \na musí mít délku 1 až 106 znaků", "error_text_wallet_name": "Jméno peněženky může obsahovat pouze písmena, čísla, symbol _ \na musí mít délku 1 až 33 znaků", "error_text_xmr": "Hodnota XMR nemůže překročit dostupný zůstatek.\nPočet desetinných míst musí být menší, nebo roven 12", + "error_while_processing": "Při převádění došlo k chybě", "errorGettingCredentials": "Selhalo: Chyba při získávání přihlašovacích údajů", "errorSigningTransaction": "Při podepisování transakce došlo k chybě", "estimated": "Odhadováno", @@ -323,6 +332,7 @@ "export_backup": "Exportovat zálohu", "export_logs": "Vývozní protokoly", "export_outputs": "Vývozní výstupy", + "extend_session": "Prodloužit relaci", "extra_id": "Extra ID:", "extracted_address_content": "Prostředky budete posílat na\n${recipient_name}", "failed_authentication": "Ověřování selhalo. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "Zapomenuté heslo", "freeze": "Zmrazit", "frequently_asked_questions": "Často kladené otázky", + "from": "Z", "frozen": "Zmraženo", "frozen_balance": "Zmrazená rovnováha", "full_balance": "Celkový zůstatek", "gas_exceeds_allowance": "Plyn vyžadovaný transakcí přesahuje příspěvek.", + "gas_price": "Cena plynu", "generate_name": "Generovat jméno", "generating_gift_card": "Generuji dárkovou kartu", "generating_transaction": "Generování transakce", @@ -440,6 +452,8 @@ "memo": "Memo:", "message": "Zpráva", "message_verified": "Zpráva byla úspěšně ověřena", + "messages": "Zprávy", + "method": "Metoda", "methods": "Metody", "min_amount": "Min: ${value}", "min_value": "Min: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "Tmavé téma Monero", "monero_light_theme": "Světlé téma Monero", "moonpay_alert_text": "Částka musí být větší nebo rovna ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "Při načítání NFT došlo k chybě. Laskavě zkontrolujte připojení k internetu a zkuste to znovu.", "more_options": "Více možností", "multiple_addresses_detected": "Detekované více adres", "mweb_confirmed": "Potvrzený mweb", @@ -589,6 +604,7 @@ "rep_warning_sub": "Zdá se, že váš zástupce není v dobrém stavu. Klepnutím zde vyberte nový", "repeat_wallet_password": "Opakujte heslo peněženky", "repeated_password_is_incorrect": "Opakované heslo je nesprávné. Znovu opakujte heslo peněženky.", + "requested": "Požadováno", "require_for_adding_contacts": "Vyžadovat pro přidání kontaktů", "require_for_all_security_and_backup_settings": "Vyžadovat všechna nastavení zabezpečení a zálohování", "require_for_assessing_wallet": "Vyžadovat pro přístup k peněžence", @@ -650,6 +666,8 @@ "second_intro_content": "Váš Yat je jediná unikátní emoji adresa, která nahrazuje všechny Vaše dlouhé hexadecimální adresy pro všechny Vaše měny.", "second_intro_title": "Jedna emoji adresa vládne všem", "security_and_backup": "Bezpečnost a zálohy", + "security_risk": "Bezpečnostní riziko", + "security_risk_description": "Tato doména je označena jako nebezpečná více poskytovateli zabezpečení. Okamžitě nechte chránit vaše aktiva.", "seed_alert_back": "Zpět", "seed_alert_content": "Tento seed představuje jedinou možnost, jak obnovit peněženku. Zapsali jste si ho?", "seed_alert_title": "Pozor", @@ -764,7 +782,9 @@ "show_keys": "Zobrazit seed/klíče", "show_market_place": "Zobrazit trh", "show_seed": "Zobrazit seed", + "sign_all": "Podepište všechny", "sign_message": "Podepsat zprávu", + "sign_one": "Podepsat jeden", "sign_up": "Registrovat se", "sign_verify_message": "Podepsat / ověřit", "sign_verify_message_sub": "Podepište nebo ověřte zprávu pomocí soukromého klíče", @@ -798,6 +818,7 @@ "subaddress_title": "Seznam subadres", "subaddresses": "Subadresy", "submit_request": "odeslat požadavek", + "success": "Úspěch", "successful": "Úspěšný", "support_description_guides": "Dokumentace a podpora běžných otázek", "support_description_live_chat": "Zdarma a rychle! K dispozici jsou zástupci vyškolených podpůrných podpory", @@ -805,6 +826,7 @@ "support_title_guides": "Dokumenty peněženky dortu", "support_title_live_chat": "Živá podpora", "support_title_other_links": "Další odkazy na podporu", + "supported": "Podporováno", "swap": "Swap", "sweeping_wallet": "Zametací peněženka", "sweeping_wallet_alert": "To by nemělo trvat dlouho. Nenechávejte tuto obrazovku, jinak mohou být ztraceny prostředky.", @@ -837,6 +859,7 @@ "thorchain_taproot_address_not_supported": "Poskytovatel Thorchain nepodporuje adresy Taproot. Změňte adresu nebo vyberte jiného poskytovatele.", "time": "${minutes}m ${seconds}s", "tip": "Spropitné:", + "to": "Na", "today": "Dnes", "token_contract_address": "Adresa tokenové smlouvy", "token_decimal": "Token v desítkové soustavě", @@ -904,6 +927,7 @@ "transaction_sent_notice": "Pokud proces nepokročí během 1 minuty, zkontrolujte block explorer a svůj e-mail.", "transactions": "Transakce", "transactions_by_date": "Transakce podle data", + "transport_type": "Typ transportu", "trongrid_history": "Trongridní historie", "trusted": "Důvěřovat", "tx_commit_exception_no_dust_on_change": "Transakce je zamítnuta s touto částkou. S těmito mincemi můžete odeslat ${min} bez změny nebo ${max}, které se vrátí změna.", @@ -932,6 +956,7 @@ "unspent_coins_details_title": "Podrobnosti o neutracených mincích", "unspent_coins_title": "Neutracené mince", "unsupported_asset": "Tuto akci u tohoto díla nepodporujeme. Vytvořte nebo přepněte na peněženku podporovaného typu aktiv.", + "update_session": "Aktualizační relace", "uptime": "Uptime", "upto": "až ${value}", "usb": "USB", @@ -941,6 +966,7 @@ "use_ssl": "Použít SSL", "use_suggested": "Použít doporučený", "use_testnet": "Použijte testNet", + "user_rejected_method": "Metoda odmítnutá uživatele", "value": "Hodnota", "value_type": "Typ hodnoty", "variable_pair_not_supported": "Tento pár s tržním kurzem není ve zvolené směnárně podporován", @@ -1024,5 +1050,6 @@ "you_will_get": "Směnit na", "you_will_receive_estimated_amount": "Obdržíte(odhadovaný )", "you_will_send": "Směnit z", + "youCanGoBackToYourDapp": "Nyní se můžete vrátit do svého dappu", "yy": "YY" } \ No newline at end of file diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 361ab5aa2..e3a725d9c 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -53,6 +53,7 @@ "anonpay_description": "Generieren Sie ${type}. Der Empfänger kann ${method} mit jeder unterstützten Kryptowährung verwenden, und Sie erhalten Geld in dieser Wallet.", "apk_update": "APK-Update", "approve": "Genehmigen", + "approve_request": "Anfrage genehmigen", "arrive_in_this_address": "${currency} ${tag} wird an dieser Adresse ankommen", "ascending": "Aufsteigend", "ask_each_time": "Jedes Mal fragen", @@ -116,9 +117,12 @@ "camera_consent": "Mit Ihrer Kamera wird bis zum ${provider} ein Bild zur Identifizierung aufgenommen. Weitere Informationen finden Sie in deren Datenschutzbestimmungen.", "camera_permission_is_required": "Eine Kameraerlaubnis ist erforderlich.\nBitte aktivieren Sie es in den App-Einstellungen.", "cancel": "Abbrechen", + "cannot_verify": "Kann nicht überprüfen", + "cannot_verify_description": "Diese Domäne kann nicht verifiziert werden. Überprüfen Sie die Anfrage vor der Genehmigung sorgfältig.", "card_address": "Adresse:", "cardholder_agreement": "Karteninhabervertrag", "cards": "Karten", + "chain_id": "Ketten -ID", "chains": "Blockchains", "change": "Ändern", "change_backup_password_alert": "Ihre vorherigen Sicherungsdateien können nicht mit einem neuen Sicherungskennwort importiert werden. Das neue Sicherungskennwort wird nur für neue Sicherungsdateien verwendet. Sind Sie sicher, dass Sie das Sicherungskennwort ändern möchten?", @@ -174,6 +178,7 @@ "connect_yats": "Yats verbinden", "connect_your_hardware_wallet": "Verbinden Sie Ihre Hardware-Wallet über Bluetooth oder USB", "connect_your_hardware_wallet_ios": "Verbinden Sie Ihre Hardware-Wallet über Bluetooth", + "connected": "Verbunden", "connection_sync": "Verbindung und Synchronisierung", "connectWalletPrompt": "Verbinden Sie Ihr Wallet mit WalletConnect, um Transaktionen durchzuführen", "contact": "Kontakt", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "Batterieoptimierung deaktivieren", "disableBatteryOptimizationDescription": "Möchten Sie die Batterieoptimierung deaktivieren, um die Hintergrundsynchronisierung reibungsloser zu gestalten?", "disabled": "Deaktiviert", + "disconnect_session": "Sitzung trennen", "discount": "${value} % sparen", "display_settings": "Anzeigeeinstellungen", "displayable": "Anzeigebar", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "Teilen Sie diese nicht mit anderen, einschließlich Support.\n\nIhr Geld kann und wird gestohlen werden!", "do_not_show_me": "Zeig mir das nicht noch einmal", "domain_looks_up": "Domain-Suchen", + "domain_mismatch": "Domänenfehlanpassung", + "domain_mismatch_description": "Diese Website hat eine Domain, die nicht mit dem Absender dieser Anfrage übereinstimmt. Die Genehmigung kann zum Verlust von Geldern führen.", "donation_link_details": "Details zum Spendenlink", "e_sign_consent": "E-Sign-Zustimmung", "edit": "Bearbeiten", @@ -298,6 +306,7 @@ "error_text_template": "Vorlagenname und Adresse dürfen nicht die Zeichen ` , ' \" enthalten\nund müssen zwischen 1 und 106 Zeichen lang sein", "error_text_wallet_name": "Der Wallet-Name darf nur Buchstaben, Zahlen und _- Symbole enthalten\nund muss zwischen 1 und 33 Zeichen lang sein", "error_text_xmr": "Der XMR-Wert darf das verfügbare Guthaben nicht überschreiten.\nDie Anzahl der Nachkommastellen muss kleiner oder gleich 12 sein", + "error_while_processing": "Ein Fehler beim Proceessing trat ein Fehler auf", "errorGettingCredentials": "Fehlgeschlagen: Fehler beim Abrufen der Anmeldeinformationen", "errorSigningTransaction": "Beim Signieren der Transaktion ist ein Fehler aufgetreten", "estimated": "Geschätzt", @@ -323,6 +332,7 @@ "export_backup": "Sicherung exportieren", "export_logs": "Exportprotokolle", "export_outputs": "Exportausgaben", + "extend_session": "Sitzung verlängern", "extra_id": "Extra ID:", "extracted_address_content": "Sie senden Geld an\n${recipient_name}", "failed_authentication": "Authentifizierung fehlgeschlagen. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "Passwort vergessen", "freeze": "Einfrieren", "frequently_asked_questions": "Häufig gestellte Fragen", + "from": "Aus", "frozen": "Gefroren", "frozen_balance": "Gefrorenes Guthaben", "full_balance": "Gesamtguthaben", "gas_exceeds_allowance": "Die durch Transaktion erforderliche Gas übertrifft die Zulage.", + "gas_price": "Gaspreis", "generate_name": "Namen generieren", "generating_gift_card": "Geschenkkarte wird erstellt", "generating_transaction": "Transaktion erzeugen", @@ -440,6 +452,8 @@ "memo": "Memo:", "message": "Nachricht", "message_verified": "Die Nachricht wurde erfolgreich überprüft", + "messages": "Nachrichten", + "method": "Verfahren", "methods": "Methoden", "min_amount": "Min: ${value}", "min_value": "Min: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "Dunkles Monero-Thema", "monero_light_theme": "Monero Light-Thema", "moonpay_alert_text": "Der Wert des Betrags muss größer oder gleich ${minAmount} ${fiatCurrency} sein", + "moralis_nft_error": "Ein Fehler trat beim Abrufen von NFTs auf. Bitte überprüfen Sie Ihre Internetverbindung und versuchen Sie es erneut.", "more_options": "Weitere Optionen", "multiple_addresses_detected": "Mehrere Adressen erkannt", "mweb_confirmed": "Bestätigt MWeb", @@ -535,8 +550,8 @@ "please_choose_one": "Bitte wählen Sie einen", "please_fill_totp": "Bitte geben Sie den 8-stelligen Code ein, der auf Ihrem anderen Gerät vorhanden ist", "please_make_selection": "Bitte treffen Sie unten eine Auswahl zum Erstellen oder Wiederherstellen Ihrer Wallet.", - "please_reference_document": "Bitte verweisen Sie auf die folgenden Dokumente, um weitere Informationen zu erhalten.", "Please_reference_document": "Weitere Informationen finden Sie in den Dokumenten unten.", + "please_reference_document": "Bitte verweisen Sie auf die folgenden Dokumente, um weitere Informationen zu erhalten.", "please_select": "Bitte auswählen:", "please_select_backup_file": "Bitte wählen Sie die Sicherungsdatei und geben Sie das Sicherungskennwort ein.", "please_try_to_connect_to_another_node": "Bitte versuchen Sie, sich mit einem anderen Knoten zu verbinden", @@ -590,6 +605,7 @@ "rep_warning_sub": "Ihr Vertreter scheint nicht gut zu sein. Tippen Sie hier, um eine neue auszuwählen", "repeat_wallet_password": "Wiederholen Sie das Walletkennwort", "repeated_password_is_incorrect": "Wiederholtes Passwort ist falsch. Bitte wiederholen Sie das Walletkennwort erneut.", + "requested": "Angefordert", "require_for_adding_contacts": "Erforderlich zum Hinzufügen von Kontakten", "require_for_all_security_and_backup_settings": "Für alle Sicherheits- und Sicherungseinstellungen erforderlich", "require_for_assessing_wallet": "Für den Zugriff auf die Wallet erforderlich", @@ -651,6 +667,8 @@ "second_intro_content": "Ihr Yat ist eine einzige eindeutige Emoji-Adresse, die alle Ihre langen hexadezimalen Adressen für alle Ihre Währungen ersetzt.", "second_intro_title": "Eine Emoji-Adresse, um sie alle zu beherrschen", "security_and_backup": "Sicherheit und Datensicherung", + "security_risk": "Sicherheitsrisiko", + "security_risk_description": "Diese Domäne wird von mehreren Sicherheitsanbietern als unsicher gekennzeichnet. Gehen Sie sofort auf, um Ihr Vermögen zu schützen.", "seed_alert_back": "Zurückgehen", "seed_alert_content": "Der Seed ist der einzige Weg, Ihre Wallet wiederherzustellen. Haben Sie ihn aufgeschrieben?", "seed_alert_title": "Achtung", @@ -765,7 +783,9 @@ "show_keys": "Seed/Schlüssel anzeigen", "show_market_place": "Marktplatz anzeigen", "show_seed": "Seed zeigen", + "sign_all": "Alle unterschreiben", "sign_message": "Nachricht unterschreiben", + "sign_one": "Unterschreiben", "sign_up": "Anmelden", "sign_verify_message": "Zeichen / überprüfen", "sign_verify_message_sub": "Unterschreiben oder überprüfen Sie eine Nachricht mit Ihrem privaten Schlüssel", @@ -799,6 +819,7 @@ "subaddress_title": "Unteradressenliste", "subaddresses": "Unteradressen", "submit_request": "Eine Anfrage stellen", + "success": "Erfolg", "successful": "Erfolgreich", "support_description_guides": "Dokumentation und Hilfe für bekannte Probleme", "support_description_live_chat": "Kostenlos und schnell! Ausgebildete Mitarbeiter stehen zur Unterstützung bereit, um zu helfen", @@ -806,6 +827,7 @@ "support_title_guides": "Cake Wallet Docs", "support_title_live_chat": "Live Support", "support_title_other_links": "Andere Support-Links", + "supported": "Unterstützt", "swap": "Tauschen", "sweeping_wallet": "Wallet leeren", "sweeping_wallet_alert": "Das sollte nicht lange dauern. VERLASSEN SIE DIESEN BILDSCHIRM NICHT, ANDERNFALLS KÖNNEN DIE GELDER VERLOREN GEHEN", @@ -838,6 +860,7 @@ "thorchain_taproot_address_not_supported": "Der Thorchain-Anbieter unterstützt keine Taproot-Adressen. Bitte ändern Sie die Adresse oder wählen Sie einen anderen Anbieter aus.", "time": "${minutes}m ${seconds}s", "tip": "Hinweis:", + "to": "Zu", "today": "Heute", "token_contract_address": "Token-Contract-Adresse", "token_decimal": "Token-Dezimalzahl", @@ -905,6 +928,7 @@ "transaction_sent_notice": "Wenn der Bildschirm nach 1 Minute nicht weitergeht, überprüfen Sie einen Block-Explorer und Ihre E-Mail.", "transactions": "Transaktionen", "transactions_by_date": "Transaktionen nach Datum", + "transport_type": "Transporttyp", "trongrid_history": "Trongrid-Historie", "trusted": "Vertrauenswürdige", "tx_commit_exception_no_dust_on_change": "Die Transaktion wird diesen Betrag abgelehnt. Mit diesen Münzen können Sie ${min} ohne Veränderung oder ${max} senden, die Änderungen zurückgeben.", @@ -934,6 +958,7 @@ "unspent_coins_details_title": "Details zu nicht ausgegebenen Coins", "unspent_coins_title": "Nicht ausgegebene Coins", "unsupported_asset": "Wir unterstützen diese Aktion für dieses Asset nicht. Bitte erstellen Sie eine Wallet eines unterstützten Asset-Typs oder wechseln Sie zu einer Wallet.", + "update_session": "Sitzung aktualisieren", "uptime": "Betriebszeit", "upto": "bis zu ${value}", "usb": "USB", @@ -943,6 +968,7 @@ "use_ssl": "SSL verwenden", "use_suggested": "Vorgeschlagen verwenden", "use_testnet": "TESTNET verwenden", + "user_rejected_method": "Benutzerverletzte Methode", "value": "Wert", "value_type": "Werttyp", "variable_pair_not_supported": "Dieses Variablenpaar wird von den ausgewählten Börsen nicht unterstützt", @@ -1027,5 +1053,6 @@ "you_will_get": "Konvertieren zu", "you_will_receive_estimated_amount": "Sie erhalten(geschätzt )", "you_will_send": "Konvertieren von", + "youCanGoBackToYourDapp": "Sie können jetzt zu Ihrem Dapp zurückkehren", "yy": "YY" } \ No newline at end of file diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 0fd842f83..abd406010 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -53,6 +53,7 @@ "anonpay_description": "Generate ${type}. The recipient can ${method} with any supported cryptocurrency, and you will receive funds in this wallet.", "apk_update": "APK update", "approve": "Approve", + "approve_request": "Approve Request", "arrive_in_this_address": "${currency} ${tag}will arrive in this address", "ascending": "Ascending", "ask_each_time": "Ask each time", @@ -116,9 +117,12 @@ "camera_consent": "Your camera will be used to capture an image for identification purposes by ${provider}. Please check their Privacy Policy for details.", "camera_permission_is_required": "Camera permission is required. \nPlease enable it from app settings.", "cancel": "Cancel", + "cannot_verify": "Cannot Verify", + "cannot_verify_description": "This domain cannot be verified. Check the request carefully before approving.", "card_address": "Address:", "cardholder_agreement": "Cardholder Agreement", "cards": "Cards", + "chain_id": "Chain ID", "chains": "Chains", "change": "Change", "change_backup_password_alert": "Your previous backup files will be not available to import with new backup password. New backup password will be used only for new backup files. Are you sure that you want to change backup password?", @@ -174,6 +178,7 @@ "connect_yats": "Connect Yats", "connect_your_hardware_wallet": "Connect your hardware wallet using Bluetooth or USB", "connect_your_hardware_wallet_ios": "Connect your hardware wallet using Bluetooth", + "connected": "Connected", "connection_sync": "Connection and sync", "connectWalletPrompt": "Connect your wallet with WalletConnect to make transactions", "contact": "Contact", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "Disable Battery Optimization", "disableBatteryOptimizationDescription": "Do you want to disable battery optimization in order to make background sync run more freely and smoothly?", "disabled": "Disabled", + "disconnect_session": "Disconnect Session", "discount": "Save ${value}%", "display_settings": "Display settings", "displayable": "Displayable", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "Do not share these with anyone else, including support.\n\nYour funds can and will be stolen!", "do_not_show_me": "Do not show me this again", "domain_looks_up": "Domain lookups", + "domain_mismatch": "Domain Mismatch", + "domain_mismatch_description": "This website has a domain that does not match the sender of this request. Approving may lead to loss of funds.", "donation_link_details": "Donation link details", "e_sign_consent": "E-Sign Consent", "edit": "Edit", @@ -298,6 +306,7 @@ "error_text_template": "Template name and address can't contain ` , ' \" symbols\nand must be between 1 and 106 characters long", "error_text_wallet_name": "Wallet name can only contain letters, numbers, _ - symbols \nand must be between 1 and 33 characters long", "error_text_xmr": "XMR value can't exceed available balance.\nThe number of fraction digits must be less or equal to 12", + "error_while_processing": "An error occurred while proceessing", "errorGettingCredentials": "Failed: Error while getting credentials", "errorSigningTransaction": "An error has occured while signing transaction", "estimated": "Estimated", @@ -323,6 +332,7 @@ "export_backup": "Export backup", "export_logs": "Export logs", "export_outputs": "Export outputs", + "extend_session": "Extend Session", "extra_id": "Extra ID:", "extracted_address_content": "You will be sending funds to\n${recipient_name}", "failed_authentication": "Failed authentication. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "Forgot Password", "freeze": "Freeze", "frequently_asked_questions": "Frequently asked questions", + "from": "From", "frozen": "Frozen", "frozen_balance": "Frozen Balance", "full_balance": "Full Balance", "gas_exceeds_allowance": "Gas required by transaction exceeds allowance.", + "gas_price": "Gas price", "generate_name": "Generate Name", "generating_gift_card": "Generating Gift Card", "generating_transaction": "Generating transaction", @@ -440,6 +452,8 @@ "memo": "Memo:", "message": "Message", "message_verified": "The message was successfully verified", + "messages": "Messages", + "method": "Method", "methods": "Methods", "min_amount": "Min: ${value}", "min_value": "Min: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "Monero Dark Theme", "monero_light_theme": "Monero Light Theme", "moonpay_alert_text": "Value of the amount must be more or equal to ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "An error occurred while fetching NFTs. Kindly check your internet connection and try again.", "more_options": "More Options", "multiple_addresses_detected": "Multiple addresses detected", "mweb_confirmed": "Confirmed MWEB", @@ -590,6 +605,7 @@ "rep_warning_sub": "Your representative does not appear to be in good standing. Tap here to select a new one", "repeat_wallet_password": "Repeat the wallet password", "repeated_password_is_incorrect": "Repeated password is incorrect. Please repeat the wallet password again.", + "requested": "Requested", "require_for_adding_contacts": "Require for adding contacts", "require_for_all_security_and_backup_settings": "Require for all security and backup settings", "require_for_assessing_wallet": "Require for accessing wallet", @@ -651,6 +667,8 @@ "second_intro_content": "Your Yat is a single unique emoji address that replaces all of your long hexadecimal addresses for all of your currencies.", "second_intro_title": "One emoji address to rule them all", "security_and_backup": "Security and backup", + "security_risk": "Security Risk", + "security_risk_description": "This domain is flagged as unsafe by multiple security providers. Leave immediately to protect your assets.", "seed_alert_back": "Go back", "seed_alert_content": "The seed is the only way to recover your wallet. Have you written it down?", "seed_alert_title": "Attention", @@ -765,7 +783,9 @@ "show_keys": "Show seed/keys", "show_market_place": "Show Marketplace", "show_seed": "Show seed", + "sign_all": "Sign All", "sign_message": "Sign Message", + "sign_one": "Sign One", "sign_up": "Sign Up", "sign_verify_message": "Sign / Verify", "sign_verify_message_sub": "Sign or verify a message using your private key", @@ -799,6 +819,7 @@ "subaddress_title": "Subaddress list", "subaddresses": "Subaddresses", "submit_request": "submit a request", + "success": "Success", "successful": "Successful", "support_description_guides": "Documentation and support for common issues", "support_description_live_chat": "Free and fast! Trained support representatives are available to assist", @@ -806,6 +827,7 @@ "support_title_guides": "Cake Wallet docs", "support_title_live_chat": "Live support", "support_title_other_links": "Other support links", + "supported": "Supported", "swap": "Swap", "sweeping_wallet": "Sweeping wallet", "sweeping_wallet_alert": "This shouldn’t take long. DO NOT LEAVE THIS SCREEN OR THE SWEPT FUNDS MAY BE LOST.", @@ -838,6 +860,7 @@ "thorchain_taproot_address_not_supported": "The ThorChain provider does not support Taproot addresses. Please change the address or select a different provider.", "time": "${minutes}m ${seconds}s", "tip": "Tip:", + "to": "To", "today": "Today", "token_contract_address": "Token contract address", "token_decimal": "Token decimal", @@ -905,6 +928,7 @@ "transaction_sent_notice": "If the screen doesn’t proceed after 1 minute, check a block explorer and your email.", "transactions": "Transactions", "transactions_by_date": "Transactions by date", + "transport_type": "Transport Type", "trongrid_history": "TronGrid history", "trusted": "Trusted", "tx_commit_exception_no_dust_on_change": "The transaction is rejected with this amount. With these coins you can send ${min} without change or ${max} that returns change.", @@ -933,6 +957,7 @@ "unspent_coins_details_title": "Unspent coins details", "unspent_coins_title": "Unspent coins", "unsupported_asset": "We don't support this action for this asset. Please create or switch to a wallet of a supported asset type.", + "update_session": "Update Session", "uptime": "Uptime", "upto": "up to ${value}", "usb": "USB", @@ -942,6 +967,7 @@ "use_ssl": "Use SSL", "use_suggested": "Use Suggested", "use_testnet": "Use Testnet", + "user_rejected_method": "User rejected method", "value": "Value", "value_type": "Value Type", "variable_pair_not_supported": "This variable pair is not supported with the selected exchanges", @@ -1025,5 +1051,6 @@ "you_will_get": "Convert to", "you_will_receive_estimated_amount": "You will receive (estimated)", "you_will_send": "Convert from", + "youCanGoBackToYourDapp": "You can go back to your dApp now", "yy": "YY" } \ No newline at end of file diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index f4a4a6bfe..c175f8393 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -53,6 +53,7 @@ "anonpay_description": "Genera ${type}. El destinatario puede ${method} con cualquier criptomoneda admitida, y recibirá fondos en esta billetera.", "apk_update": "Actualización de APK", "approve": "Aprobar", + "approve_request": "Aprobar la solicitud", "arrive_in_this_address": "${currency} ${tag}llegará a esta dirección", "ascending": "Ascendente", "ask_each_time": "Pregunta cada vez", @@ -116,9 +117,12 @@ "camera_consent": "Su cámara será utilizada para capturar una imagen con fines de identificación por ${provider}. Consulta tu Política de privacidad para obtener más detalles.", "camera_permission_is_required": "Se requiere permiso de la cámara.\nHabilítalo desde la configuración de la aplicación.", "cancel": "Cancelar", + "cannot_verify": "No se puede verificar", + "cannot_verify_description": "Este dominio no se puede verificar. Verifique la solicitud cuidadosamente antes de aprobar.", "card_address": "Dirección:", "cardholder_agreement": "Acuerdo del titular de la tarjeta", "cards": "Cartas", + "chain_id": "ID de cadena", "chains": "Cadenas", "change": "Cambio", "change_backup_password_alert": "Tus archivos de respaldo anteriores no estarán disponibles para importar con la nueva contraseña de respaldo. La nueva contraseña de respaldo se utilizará solo para los nuevos archivos de respaldo. ¿Está seguro de que desea cambiar la contraseña de respaldo?", @@ -174,6 +178,7 @@ "connect_yats": "Conectar Yats", "connect_your_hardware_wallet": "Conecta tu billetera de hardware con Bluetooth o USB", "connect_your_hardware_wallet_ios": "Conecta tu billetera de hardware con Bluetooth", + "connected": "Conectado", "connection_sync": "Conexión y sincronización", "connectWalletPrompt": "Conecte tu billetera con WalletConnect para realizar transacciones", "contact": "Contacto", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "Deshabilitar la optimización de la batería", "disableBatteryOptimizationDescription": "¿Desea deshabilitar la optimización de la batería para que la sincronización de fondo se ejecute más libremente y sin problemas?", "disabled": "Desactivado", + "disconnect_session": "Desconectar la sesión", "discount": "Ahorra ${value}%", "display_settings": "Configuración de pantalla", "displayable": "Visualizable", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "No compartas estos con nadie más, incluido el soporte.\n\n¡Tus fondos pueden ser y serán robados!", "do_not_show_me": "no me muestres esto otra vez", "domain_looks_up": "Búsquedas de dominio", + "domain_mismatch": "Desajuste de dominio", + "domain_mismatch_description": "Este sitio web tiene un dominio que no coincide con el remitente de esta solicitud. La aprobación puede conducir a la pérdida de fondos.", "donation_link_details": "Detalles del enlace de donación", "e_sign_consent": "Consentimiento de firma electrónica", "edit": "Editar", @@ -298,6 +306,7 @@ "error_text_template": "El nombre y la dirección de la plantilla no pueden contener símbolos ` , '\" \ny debe tener entre 1 y 106 caracteres de longitud", "error_text_wallet_name": "El nombre de la billetera solo puede contener letras, números , _ - símbolos\ny debe tener entre 1 y 33 caracteres de longitud", "error_text_xmr": "El valor XMR no puede exceder el saldo disponible.\nTEl número de dígitos de fracción debe ser menor o igual a 12", + "error_while_processing": "Se produjo un error mientras procesaba", "errorGettingCredentials": "Error: error al obtener las credenciales", "errorSigningTransaction": "Se ha producido un error al firmar la transacción.", "estimated": "Estimado", @@ -323,6 +332,7 @@ "export_backup": "Exportar copia de seguridad", "export_logs": "Registros de exportación", "export_outputs": "Exportaciones de exportación", + "extend_session": "Extender la sesión", "extra_id": "ID adicional:", "extracted_address_content": "Enviará fondos a\n${recipient_name}", "failed_authentication": "Autenticación fallida. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "Olvidé mi contraseña", "freeze": "Congelar", "frequently_asked_questions": "Preguntas frecuentes", + "from": "De", "frozen": "Congelada", "frozen_balance": "Equilibrio congelado", "full_balance": "Balance completo", "gas_exceeds_allowance": "El gas requerido por la transacción excede la asignación.", + "gas_price": "Precio de gas", "generate_name": "Generar nombre", "generating_gift_card": "Generando tarjeta de regalo", "generating_transaction": "Generación de transacciones", @@ -440,6 +452,8 @@ "memo": "Memorándum:", "message": "Mensaje", "message_verified": "El mensaje fue verificado con éxito", + "messages": "Mensajes", + "method": "Método", "methods": "Métodos", "min_amount": "Mínimo: ${value}", "min_value": "Min: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "Tema oscuro de Monero", "monero_light_theme": "Tema ligero de Monero", "moonpay_alert_text": "El valor de la cantidad debe ser mayor o igual a ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "Se produjo un error al alcanzar las NFT. Por favor, consulte su conexión a Internet y vuelva a intentarlo.", "more_options": "Más Opciones", "multiple_addresses_detected": "Múltiples direcciones detectadas", "mweb_confirmed": "Confirmado mweb", @@ -590,6 +605,7 @@ "rep_warning_sub": "Tu representante no parece estar en buena posición. Toca aquí para seleccionar uno nuevo", "repeat_wallet_password": "Repite la contraseña de billetera", "repeated_password_is_incorrect": "La contraseña repetida es incorrecta. Repite la contraseña de la billetera nuevamente.", + "requested": "Solicitado", "require_for_adding_contacts": "Requerido para agregar contactos", "require_for_all_security_and_backup_settings": "Requerido para todas las configuraciones de seguridad y copia de seguridad", "require_for_assessing_wallet": "Requerido para acceder a la billetera", @@ -651,6 +667,8 @@ "second_intro_content": "Tu Yat es una única dirección emoji única que reemplaza todas tus direcciones hexadecimales largas para todas tus monedas.", "second_intro_title": "Una dirección de emoji para gobernarlos a todos", "security_and_backup": "Seguridad y respaldo", + "security_risk": "Riesgo de seguridad", + "security_risk_description": "Este dominio es marcado como inseguro por múltiples proveedores de seguridad. Deje inmediatamente para proteger sus activos.", "seed_alert_back": "Regresa", "seed_alert_content": "La semilla es la única forma de recuperar su billetera. ¿Lo has escrito?", "seed_alert_title": "Atención", @@ -765,7 +783,9 @@ "show_keys": "Mostrar semilla/claves", "show_market_place": "Mostrar mercado", "show_seed": "Mostrar semilla", + "sign_all": "Firmar todo", "sign_message": "Mensaje de firma", + "sign_one": "Firmar", "sign_up": "Registrarse", "sign_verify_message": "Firmar / verificar", "sign_verify_message_sub": "Firmar o verificar un mensaje usando su clave privada", @@ -799,6 +819,7 @@ "subaddress_title": "Lista de subdirecciones", "subaddresses": "Subdirecciones", "submit_request": "presentar una solicitud", + "success": "Éxito", "successful": "Exitoso", "support_description_guides": "Documentación y apoyo para problemas comunes", "support_description_live_chat": "¡GRATIS y RÁPIDO! Los representantes de apoyo capacitado están disponibles para ayudar", @@ -806,6 +827,7 @@ "support_title_guides": "Documentos de billetera de pastel", "support_title_live_chat": "Soporte en tiempo real", "support_title_other_links": "Otros enlaces de soporte", + "supported": "Compatible", "swap": "Intercambio", "sweeping_wallet": "Barrer billetera (gastar todos los fondos disponibles)", "sweeping_wallet_alert": "Esto no debería llevar mucho tiempo. NO DEJES ESTA PANTALLA O SE PUEDEN PERDER LOS FONDOS BARRIDOS", @@ -838,6 +860,7 @@ "thorchain_taproot_address_not_supported": "El proveedor de Thorchain no admite las direcciones de Taproot. Cambia la dirección o selecciona un proveedor diferente.", "time": "${minutes}m ${seconds}s", "tip": "Consejo:", + "to": "A", "today": "Hoy", "token_contract_address": "Dirección de contrato de token", "token_decimal": "Token decimal", @@ -905,6 +928,7 @@ "transaction_sent_notice": "Si la pantalla no continúa después de 1 minuto, revisa un explorador de bloques y tu correo electrónico.", "transactions": "Transacciones", "transactions_by_date": "Transacciones por fecha", + "transport_type": "Tipo de transporte", "trongrid_history": "Historia trongrid", "trusted": "de confianza", "tx_commit_exception_no_dust_on_change": "La transacción se rechaza con esta cantidad. Con estas monedas puede enviar ${min} sin cambios o ${max} que devuelve el cambio.", @@ -933,6 +957,7 @@ "unspent_coins_details_title": "Detalles de monedas no gastadas", "unspent_coins_title": "Monedas no gastadas", "unsupported_asset": "No admitimos esta acción para este activo. Cree o cambie a una billetera de un tipo de activo admitido.", + "update_session": "Sesión de actualización", "uptime": "Tiempo de actividad", "upto": "hasta ${value}", "usb": "USB", @@ -942,6 +967,7 @@ "use_ssl": "Utiliza SSL", "use_suggested": "Usar sugerido", "use_testnet": "Usar TestNet", + "user_rejected_method": "Método rechazado por el usuario", "value": "Valor", "value_type": "Tipo de valor", "variable_pair_not_supported": "Este par de variables no es compatible con los intercambios seleccionados", @@ -1025,5 +1051,6 @@ "you_will_get": "Convertir a", "you_will_receive_estimated_amount": "Recibirá(estimado )", "you_will_send": "Convertir de", + "youCanGoBackToYourDapp": "Puedes volver a tu dapp ahora", "yy": "YY" } \ No newline at end of file diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 1400ee9ee..fda6781b8 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -53,6 +53,7 @@ "anonpay_description": "Générez ${type}. Le destinataire peut ${method} avec n'importe quelle crypto-monnaie prise en charge, et vous recevrez des fonds dans ce portefeuille (wallet).", "apk_update": "Mise à jour d'APK", "approve": "Approuver", + "approve_request": "Approuver la demande", "arrive_in_this_address": "${currency} ${tag}arrivera à cette adresse", "ascending": "Ascendant", "ask_each_time": "Demander à chaque fois", @@ -116,9 +117,12 @@ "camera_consent": "Votre appareil photo sera utilisé pour capturer une image à des fins d'identification par ${provider}. Veuillez consulter leur politique de confidentialité pour plus de détails.", "camera_permission_is_required": "L'autorisation de la caméra est requise.\nVeuillez l'activer à partir des paramètres de l'application.", "cancel": "Annuler", + "cannot_verify": "Ne peut pas vérifier", + "cannot_verify_description": "Ce domaine ne peut pas être vérifié. Vérifiez attentivement la demande avant d'approuver.", "card_address": "Adresse:", "cardholder_agreement": "Contrat de titulaire de carte", "cards": "Cartes", + "chain_id": "Chaîne ID", "chains": "Chaînes", "change": "Changer", "change_backup_password_alert": "Vos fichiers de sauvegarde précédents ne pourront pas être importés avec le nouveau mot de passe de sauvegarde. Le nouveau mot de passe ne sera utilisé que pour les nouveaux fichiers de sauvegarde. Êtes vous certain de vouloir modifier le mot de passe de sauvegarde ?", @@ -174,6 +178,7 @@ "connect_yats": "Connecter Yats", "connect_your_hardware_wallet": "Connectez votre portefeuille matériel à l'aide de Bluetooth ou USB", "connect_your_hardware_wallet_ios": "Connectez votre portefeuille matériel à l'aide de Bluetooth", + "connected": "Connecté", "connection_sync": "Connexion et synchronisation", "connectWalletPrompt": "Connectez votre portefeuille (wallet) avec WalletConnect pour effectuer des transactions", "contact": "Contact", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "Désactiver l'optimisation de la batterie", "disableBatteryOptimizationDescription": "Voulez-vous désactiver l'optimisation de la batterie afin de faire fonctionner la synchronisation d'arrière-plan plus librement et en douceur?", "disabled": "Désactivé", + "disconnect_session": "Débrancher la session", "discount": "Économisez ${value}%", "display_settings": "Paramètres d'affichage", "displayable": "Visible", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "Ne les partagez avec personne, y compris avec l'assistance.\n\nVos fonds seraient inmanquablement volés !", "do_not_show_me": "Ne plus me montrer ceci à l'avenir", "domain_looks_up": "Résolution de nom", + "domain_mismatch": "Décalage du domaine", + "domain_mismatch_description": "Ce site Web a un domaine qui ne correspond pas à l'expéditeur de cette demande. L'approbation peut entraîner une perte de fonds.", "donation_link_details": "Détails du lien de don", "e_sign_consent": "Consentement de signature électronique", "edit": "Modifier", @@ -298,6 +306,7 @@ "error_text_template": "Le nom du modèle et l'adresse ne peuvent pas contenir les symboles ` , ' \"\net leur longueur doit être comprise entre 1 et 106 caractères", "error_text_wallet_name": "Le nom du portefeuille (wallet) ne peut contenir que des lettres et des chiffres\net sa longueur doit être comprise entre 1 et 15 caractères", "error_text_xmr": "La valeur de XMR dépasse le solde disponible.\nLa partie décimale doit comporter au plus 12 chiffres", + "error_while_processing": "Une erreur s'est produite lors de la procédure", "errorGettingCredentials": "Échec : erreur lors de l'obtention des informations d'identification", "errorSigningTransaction": "Une erreur s'est produite lors de la signature de la transaction", "estimated": "Estimé", @@ -323,6 +332,7 @@ "export_backup": "Exporter la sauvegarde", "export_logs": "Journaux d'exportation", "export_outputs": "Exportation des sorties", + "extend_session": "Prolonger la séance", "extra_id": "ID supplémentaire :", "extracted_address_content": "Vous allez envoyer des fonds à\n${recipient_name}", "failed_authentication": "Échec d'authentification. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "Mot de passe oublié", "freeze": "Geler", "frequently_asked_questions": "Foire aux questions", + "from": "Depuis", "frozen": "Gelées", "frozen_balance": "Équilibre gelé", "full_balance": "Solde Complet", "gas_exceeds_allowance": "Le gaz requis par la transaction dépasse l'allocation.", + "gas_price": "Prix ​​du gaz", "generate_name": "Générer un nom", "generating_gift_card": "Génération d'une carte-cadeau", "generating_transaction": "Transaction de génération", @@ -440,6 +452,8 @@ "memo": "Mémo :", "message": "Message", "message_verified": "Le message a été vérifié avec succès", + "messages": "Messages", + "method": "Méthode", "methods": "Méthodes", "min_amount": "Min : ${value}", "min_value": "Min: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "Thème sombre Monero", "monero_light_theme": "Thème de lumière Monero", "moonpay_alert_text": "Le montant doit être au moins égal à ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "Une erreur s'est produite tout en récupérant les NFT. Veuillez vérifier votre connexion Internet et réessayer.", "more_options": "Plus d'options", "multiple_addresses_detected": "Plusieurs adresses détectées", "mweb_confirmed": "Confirmé MWEB", @@ -589,6 +604,7 @@ "rep_warning_sub": "Votre représentant ne semble pas être en règle. Appuyez ici pour en sélectionner un nouveau", "repeat_wallet_password": "Répétez le mot de passe du portefeuille", "repeated_password_is_incorrect": "Le mot de passe répété est incorrect. Veuillez répéter le mot de passe du portefeuille.", + "requested": "Demandé", "require_for_adding_contacts": "Requis pour ajouter des contacts", "require_for_all_security_and_backup_settings": "Exiger pour tous les paramètres de sécurité et de sauvegarde", "require_for_assessing_wallet": "Nécessaire pour accéder au portefeuille", @@ -650,6 +666,8 @@ "second_intro_content": "Votre Yat est une seule et unique adresse emoji qui remplace toutes vos longues adresses hexadécimales pour toutes vos cryptomonnaies.", "second_intro_title": "Une adresse emoji pour les gouverner toutes", "security_and_backup": "Sécurité et sauvegarde", + "security_risk": "Risque de sécurité", + "security_risk_description": "Ce domaine est signalé comme dangereux par plusieurs fournisseurs de sécurité. Laissez immédiatement pour protéger vos actifs.", "seed_alert_back": "Retour", "seed_alert_content": "La phrase secrète (seed) est la seule façon de restaurer votre portefeuille (wallet). L'avez-vous correctement écrite ?", "seed_alert_title": "Attention", @@ -764,7 +782,9 @@ "show_keys": "Visualiser la phrase secrète (seed) et les clefs", "show_market_place": "Afficher la place de marché", "show_seed": "Visualiser la phrase secrète (seed)", + "sign_all": "Signer tout", "sign_message": "Signer le message", + "sign_one": "Signer un", "sign_up": "S'inscrire", "sign_verify_message": "Signe / vérifier", "sign_verify_message_sub": "Signez ou vérifiez un message en utilisant votre clé privée", @@ -798,6 +818,7 @@ "subaddress_title": "Liste des sous-adresses", "subaddresses": "Sous-adresses", "submit_request": "soumettre une requête", + "success": "Succès", "successful": "Réussi", "support_description_guides": "Documentation et support pour les problèmes communs", "support_description_live_chat": "GRATUIT ET RAPIDE ! Des représentants de soutien formé sont disponibles pour aider", @@ -805,6 +826,7 @@ "support_title_guides": "Docs de portefeuille à gâteau", "support_title_live_chat": "Support en direct", "support_title_other_links": "Autres liens d'assistance", + "supported": "Soutenu", "swap": "Échanger", "sweeping_wallet": "Portefeuille (wallet) de consolidation", "sweeping_wallet_alert": "Cela ne devrait pas prendre longtemps. NE QUITTEZ PAS CET ÉCRAN OU LES FONDS TRANSFÉRÉS POURRAIENT ÊTRE PERDUS.", @@ -837,6 +859,7 @@ "thorchain_taproot_address_not_supported": "Le fournisseur de Thorchain ne prend pas en charge les adresses de tapoot. Veuillez modifier l'adresse ou sélectionner un autre fournisseur.", "time": "${minutes}m ${seconds}s", "tip": "Pourboire:", + "to": "À", "today": "Aujourd'hui", "token_contract_address": "Adresse du contrat de token", "token_decimal": "Décimales de token", @@ -904,6 +927,7 @@ "transaction_sent_notice": "Si l'écran ne continue pas après 1 minute, vérifiez un explorateur de blocs et votre e-mail.", "transactions": "Transactions", "transactions_by_date": "Transactions par date", + "transport_type": "Type de transport", "trongrid_history": "Histoire de la trongride", "trusted": "de confiance", "tx_commit_exception_no_dust_on_change": "La transaction est rejetée avec ce montant. Avec ces pièces, vous pouvez envoyer ${min} sans changement ou ${max} qui renvoie le changement.", @@ -932,6 +956,7 @@ "unspent_coins_details_title": "Détails des pièces (coins) non dépensées", "unspent_coins_title": "Pièces (coins) non dépensées", "unsupported_asset": "Nous ne prenons pas en charge cette action pour cet élément. Veuillez créer ou passer à un portefeuille d'un type d'actif pris en charge.", + "update_session": "Mettre à jour la session", "uptime": "Durée de la baisse", "upto": "jusqu'à ${value}", "usb": "USB", @@ -941,6 +966,7 @@ "use_ssl": "Utiliser SSL", "use_suggested": "Suivre la suggestion", "use_testnet": "Utiliser TestNet", + "user_rejected_method": "Méthode rejetée par l'utilisateur", "value": "Valeur", "value_type": "Type de valeur", "variable_pair_not_supported": "Cette paire variable n'est pas prise en charge avec les échanges sélectionnés", @@ -1024,5 +1050,6 @@ "you_will_get": "Convertir vers", "you_will_receive_estimated_amount": "Vous recevrez ( estimé )", "you_will_send": "Convertir depuis", + "youCanGoBackToYourDapp": "Vous pouvez retourner à votre Dapp maintenant", "yy": "AA" } \ No newline at end of file diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 27214fa30..c57635f31 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -53,6 +53,7 @@ "anonpay_description": "Ƙirƙirar ${type}. Maƙiyantun mai nasara zai iya ${method} da duk abubuwan da ke samun lambar waya, kuma zaku samu kuɗin dama a wannan kashi.", "apk_update": "apk sabunta", "approve": "Amincewa", + "approve_request": "Amince da bukata", "arrive_in_this_address": "${currency} ${tag} zai je wurin wannan adireshi", "ascending": "Hau", "ask_each_time": "Tambaya kowane lokaci", @@ -116,9 +117,12 @@ "camera_consent": "Za a yi amfani da kyamarar ku don ɗaukar hoto don dalilai na tantancewa ta ${provider}. Da fatan za a duba Manufar Sirri don cikakkun bayanai.", "camera_permission_is_required": "Ana buƙatar izinin kyamara.\nDa fatan za a kunna shi daga saitunan app.", "cancel": "Soke", + "cannot_verify": "Ba zai iya tabbatarwa", + "cannot_verify_description": "Ba za a iya tabbatar da wannan yanki ba. Duba buƙatun a hankali kafin amincewa.", "card_address": "Adireshin:", "cardholder_agreement": "Yarjejeniyar mai katin", "cards": "Katuna", + "chain_id": "ID na ID", "chains": "Sarkoki", "change": "Canja", "change_backup_password_alert": "Fayilolin madadin ku na baya ba za su kasance don shigo da sabon kalmar sirri ta madadin ba. Sabuwar kalmar sirri ta ajiya za a yi amfani da ita kawai don sabbin fayilolin madadin. Shin kun tabbata cewa kuna son canza kalmar wucewa?", @@ -174,6 +178,7 @@ "connect_yats": "Haɗa Yats", "connect_your_hardware_wallet": "Haɗa Wallake Wallware ɗinku ta Bluetooth ko USB", "connect_your_hardware_wallet_ios": "Haɗa kayan aikinku ta Bluetooth", + "connected": "Wanda aka haɗa", "connection_sync": "Haɗi da daidaitawa", "connectWalletPrompt": "Haɗa walat ɗin ku tare da WalletConnect don yin ma'amala", "contact": "Tuntuɓar", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "Kashe ingantawa baturi", "disableBatteryOptimizationDescription": "Shin kana son kashe ingantawa baturi don yin setnc bankwali gudu da yar kyauta da kyau?", "disabled": "tsaya", + "disconnect_session": "Cire haɗin", "discount": "Ajiye ${value}%", "display_settings": "Nuni saituna", "displayable": "Ana iya nunawa", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "Kada ku raba waɗannan ga kowa, gami da tallafi.\n\nZa a iya sace kuɗin ku kuma za a sace!", "do_not_show_me": "Kar ka sake nuna min wannan", "domain_looks_up": "Binciken yanki", + "domain_mismatch": "Yankin zalunci", + "domain_mismatch_description": "Wannan rukunin yanar gizon yana da yanki wanda bai dace da aika wannan bukatar ba. Yarda na iya haifar da asarar kudade.", "donation_link_details": "Bayanin hanyar sadaka", "e_sign_consent": "Izinin Alamar E-Sign", "edit": "Gyara", @@ -298,6 +306,7 @@ "error_text_template": "Sunan na tushe da adireshin ba zai iya ɗaukar ` , ' \" haruffa\nkuma ya zama tsakanin 1 zuwa 106 haruffa", "error_text_wallet_name": "Sunan hujja kawai zai iya ɗauka ne haruffa, lambobi, _ - haruffa\nkuma ya zama tsakanin 1 zuwa 33 haruffa", "error_text_xmr": "XMR adadin ba zai iya wuce available balance.\nAdadin haruffan gaba zai kamata ya zama ko ƙasa daga na 12", + "error_while_processing": "Kuskure ya faru yayin bincike", "errorGettingCredentials": "Ba a yi nasara ba: Kuskure yayin samun takaddun shaida", "errorSigningTransaction": "An sami kuskure yayin sanya hannu kan ciniki", "estimated": "Kiyasta", @@ -323,6 +332,7 @@ "export_backup": "Ajiyayyen fitarwa", "export_logs": "Injin fitarwa", "export_outputs": "Fitarwar fitarwa", + "extend_session": "Mika zaman", "extra_id": "Karin ID:", "extracted_address_content": "Za ku aika da kudade zuwa\n${recipient_name}", "failed_authentication": "Binne wajen shiga. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "Manta Kalmar wucewa", "freeze": "Daskare", "frequently_asked_questions": "Tambayoyin da ake yawan yi", + "from": "Daga", "frozen": "Daskararre", "frozen_balance": "Daidaituwa mai sanyi", "full_balance": "DUKAN KUDI", "gas_exceeds_allowance": "Gas da ake buƙata ta hanyar ma'amala ya wuce izini.", + "gas_price": "Farashin gas", "generate_name": "Ƙirƙirar Suna", "generating_gift_card": "Samar da Katin Kyauta", "generating_transaction": "Ma'amala samar da ma'amala", @@ -440,6 +452,8 @@ "memo": "Memo:", "message": "Sako", "message_verified": "An yi nasarar tabbatar da sakon", + "messages": "Manegiya", + "method": "Hanya", "methods": "Hanyoyin", "min_amount": "Min: ${value}", "min_value": "Min: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "Monero Dark Jigo", "monero_light_theme": "Jigon Hasken Monero", "moonpay_alert_text": "Darajar adadin dole ne ya zama fiye ko daidai da ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "Kuskuren ya faru yayin da ake kawo NFFTs. Da fatan za a duba haɗin intanet ɗinka kuma sake gwadawa.", "more_options": "Ƙarin Zaɓuɓɓuka", "multiple_addresses_detected": "An gano adiresoshin da aka gano", "mweb_confirmed": "Tabbatar da Mweb", @@ -591,6 +606,7 @@ "rep_warning_sub": "Wakilinku bai bayyana ya kasance cikin kyakkyawan yanayi ba. Matsa nan don zaɓar sabon", "repeat_wallet_password": "Maimaita kalmar sirri", "repeated_password_is_incorrect": "Maimaita kalmar sirri ba daidai ba ce. Da fatan za a sake maimaita kalmar sirri.", + "requested": "Nema", "require_for_adding_contacts": "Bukatar ƙara lambobin sadarwa", "require_for_all_security_and_backup_settings": "Bukatar duk tsaro da saitunan wariyar ajiya", "require_for_assessing_wallet": "Bukatar samun damar walat", @@ -652,6 +668,8 @@ "second_intro_content": "Your Yat adireshi ne na musamman na Emoji guda ɗaya wanda ke maye gurbin duk dogayen adiresoshin ku na hexadecimal na duk kudaden ku.", "second_intro_title": "Adireshin emoji ɗaya don sarrafa su duka", "security_and_backup": "Tsaro da madadin", + "security_risk": "Hadarin tsaro", + "security_risk_description": "An yiwa wannan yanki a matsayin marasa tsaro da masu ba da izini na tsaro. Bar nan da nan don kare kadarorin ku.", "seed_alert_back": "juya baya", "seed_alert_content": "Irin ita ce kawai hanya don dawo da walat ɗin ku. Kun rubuta shi?", "seed_alert_title": "Hankali", @@ -766,7 +784,9 @@ "show_keys": "Nuna iri/maɓallai", "show_market_place": "Nuna dan kasuwa", "show_seed": "Nuna iri", + "sign_all": "Shiga All", "sign_message": "Sa hannu", + "sign_one": "Alamar daya", "sign_up": "Shiga", "sign_verify_message": "Sa hannu / Tabbatar", "sign_verify_message_sub": "Shiga ko tabbatar da saƙo ta amfani da Maɓallinku na sirri", @@ -800,6 +820,7 @@ "subaddress_title": "Jagorar subaddress", "subaddresses": "Subaddresses", "submit_request": "gabatar da bukata", + "success": "Nasara", "successful": "Nasara", "support_description_guides": "Tallafi da tallafi don batutuwa na yau da kullun", "support_description_live_chat": "Kyauta da sauri! An horar da wakilan tallafi na tallafi don taimakawa", @@ -807,6 +828,7 @@ "support_title_guides": "Docs Bakin", "support_title_live_chat": "Tallafi na Live", "support_title_other_links": "Sauran hanyoyin tallafi", + "supported": "Goyan baya", "swap": "Musya", "sweeping_wallet": "Kashi na kasa", "sweeping_wallet_alert": "Wannan ba zai samu lokacin mai tsaski. KADA KA SAMU KUNGIYARAN KUHON, ZAMAN DADIN BANKUNCI ZAI HAŘA", @@ -839,6 +861,7 @@ "thorchain_taproot_address_not_supported": "Mai ba da tallafi na ThorChain baya goyan bayan adreshin taproot. Da fatan za a canza adireshin ko zaɓi mai bayarwa daban.", "time": "${minutes}m ${seconds}s", "tip": "Tukwici:", + "to": "Zuwa", "today": "Yau", "token_contract_address": "Adireshin kwangilar Token", "token_decimal": "Alamar ƙima", @@ -906,6 +929,7 @@ "transaction_sent_notice": "Idan allon bai ci gaba ba bayan minti 1, duba mai binciken toshewa da imel ɗin ku.", "transactions": "Ma'amaloli", "transactions_by_date": "Ma'amaloli ta kwanan wata", + "transport_type": "Nau'in sufuri", "trongrid_history": "Tarihin Trongrid", "trusted": "Amintacce", "tx_commit_exception_no_dust_on_change": "An ƙi ma'amala da wannan adadin. Tare da waɗannan tsabar kudi Zaka iya aika ${min}, ba tare da canji ba ko ${max} wanda ya dawo canzawa.", @@ -934,6 +958,7 @@ "unspent_coins_details_title": "Bayanan tsabar kudi da ba a kashe ba", "unspent_coins_title": "Tsabar da ba a kashe ba", "unsupported_asset": "Ba mu goyi bayan wannan aikin don wannan kadara. Da fatan za a ƙirƙira ko canza zuwa walat na nau'in kadara mai tallafi.", + "update_session": "Zaman gaba", "uptime": "Sama", "upto": "har zuwa ${value}", "usb": "Alib", @@ -943,6 +968,7 @@ "use_ssl": "Yi amfani da SSL", "use_suggested": "Amfani da Shawarwari", "use_testnet": "Amfani da gwaji", + "user_rejected_method": "Mai amfani da hanya", "value": "Daraja", "value_type": "Nau'in darajar", "variable_pair_not_supported": "Ba a samun goyan bayan wannan m biyu tare da zaɓaɓɓun musayar", @@ -1026,5 +1052,6 @@ "you_will_get": "Maida zuwa", "you_will_receive_estimated_amount": "Za ku (karɓi )", "you_will_send": "Maida daga", + "youCanGoBackToYourDapp": "Kuna iya komawa zuwa DPP ɗinku yanzu", "yy": "YY" } \ No newline at end of file diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 0ee0d3707..02e458749 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -53,6 +53,7 @@ "anonpay_description": "${type} उत्पन्न करें। प्राप्तकर्ता किसी भी समर्थित क्रिप्टोकरेंसी के साथ ${method} कर सकता है, और आपको इस वॉलेट में धन प्राप्त होगा।", "apk_update": "APK अद्यतन", "approve": "मंज़ूरी देना", + "approve_request": "अनुरोध को स्वीकृत करें", "arrive_in_this_address": "${currency} ${tag}इस पते पर पहुंचेंगे", "ascending": "आरोही", "ask_each_time": "हर बार पूछें", @@ -116,9 +117,12 @@ "camera_consent": "आपके कैमरे का उपयोग ${provider} द्वारा पहचान उद्देश्यों के लिए एक छवि कैप्चर करने के लिए किया जाएगा। विवरण के लिए कृपया उनकी गोपनीयता नीति जांचें।", "camera_permission_is_required": "कैमरे की अनुमति आवश्यक है.\nकृपया इसे ऐप सेटिंग से सक्षम करें।", "cancel": "रद्द करना", + "cannot_verify": "सत्यापित नहीं कर सकते", + "cannot_verify_description": "इस डोमेन को सत्यापित नहीं किया जा सकता है। अनुमोदन से पहले अनुरोध को ध्यान से देखें।", "card_address": "पता:", "cardholder_agreement": "कार्डधारक अनुबंध", "cards": "कार्ड", + "chain_id": "चेन आईडी", "chains": "चेन", "change": "परिवर्तन", "change_backup_password_alert": "आपकी पिछली बैकअप फाइलें नए बैकअप पासवर्ड के साथ आयात करने के लिए उपलब्ध नहीं होंगी। नए बैकअप पासवर्ड का उपयोग केवल नई बैकअप फ़ाइलों के लिए किया जाएगा। क्या आप वाकई बैकअप पासवर्ड बदलना चाहते हैं?", @@ -174,6 +178,7 @@ "connect_yats": "कनेक्ट Yats", "connect_your_hardware_wallet": "ब्लूटूथ या यूएसबी का उपयोग करके अपने हार्डवेयर वॉलेट को कनेक्ट करें", "connect_your_hardware_wallet_ios": "ब्लूटूथ का उपयोग करके अपने हार्डवेयर वॉलेट को कनेक्ट करें", + "connected": "जुड़े हुए", "connection_sync": "कनेक्शन और सिंक", "connectWalletPrompt": "लेन-देन करने के लिए अपने वॉलेट को वॉलेटकनेक्ट से कनेक्ट करें", "contact": "संपर्क करें", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "बैटरी अनुकूलन अक्षम करें", "disableBatteryOptimizationDescription": "क्या आप बैकग्राउंड सिंक को अधिक स्वतंत्र और सुचारू रूप से चलाने के लिए बैटरी ऑप्टिमाइज़ेशन को अक्षम करना चाहते हैं?", "disabled": "अक्षम", + "disconnect_session": "सत्र को डिस्कनेक्ट करें", "discount": "${value}% बचाएं", "display_settings": "प्रदर्शन सेटिंग्स", "displayable": "प्रदर्शन योग्य", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "समर्थन सहित, इन्हें किसी और के साथ साझा न करें।\n\nआपके धन की चोरी हो सकती है और होगी!", "do_not_show_me": "मुझे यह फिर न दिखाएं", "domain_looks_up": "डोमेन लुकअप", + "domain_mismatch": "डोमेन बेमेल", + "domain_mismatch_description": "इस वेबसाइट में एक डोमेन है जो इस अनुरोध के प्रेषक से मेल नहीं खाता है। अनुमोदन से धन की हानि हो सकती है।", "donation_link_details": "दान लिंक विवरण", "e_sign_consent": "ई-साइन सहमति", "edit": "संपादित करें", @@ -298,6 +306,7 @@ "error_text_template": "टेम्प्लेट का नाम और पता नहीं हो सकता ` , ' \" प्रतीकों\nऔर 1 और 106 वर्णों के बीच लंबा होना चाहिए", "error_text_wallet_name": "वॉलेट नाम में केवल अक्षर, संख्याएं, _ - प्रतीक हो सकते हैं\nऔर 1 और 33 वर्णों के बीच लंबा होना चाहिए", "error_text_xmr": "एक्सएमआर मूल्य उपलब्ध शेष राशि से अधिक नहीं हो सकता.\nअंश अंकों की संख्या 12 से कम या इसके बराबर होनी चाहिए", + "error_while_processing": "प्रोकसिंग करते समय एक त्रुटि हुई", "errorGettingCredentials": "विफल: क्रेडेंशियल प्राप्त करते समय त्रुटि", "errorSigningTransaction": "लेन-देन पर हस्ताक्षर करते समय एक त्रुटि उत्पन्न हुई है", "estimated": "अनुमानित", @@ -323,6 +332,7 @@ "export_backup": "निर्यात बैकअप", "export_logs": "निर्यात लॉग", "export_outputs": "निर्यात आउटपुट", + "extend_session": "सत्र का विस्तार करें", "extra_id": "अतिरिक्त आईडी:", "extracted_address_content": "आपको धनराशि भेजी जाएगी\n${recipient_name}", "failed_authentication": "प्रमाणीकरण विफल. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "पासवर्ड भूल गए", "freeze": "फ्रीज", "frequently_asked_questions": "अक्सर पूछे जाने वाले प्रश्न", + "from": "से", "frozen": "जमा हुआ", "frozen_balance": "जमे हुए संतुलन", "full_balance": "पूर्ण संतुलन", "gas_exceeds_allowance": "लेनदेन द्वारा आवश्यक गैस भत्ता से अधिक है।", + "gas_price": "गैस की कीमत", "generate_name": "नाम जनरेट करें", "generating_gift_card": "गिफ्ट कार्ड जनरेट कर रहा है", "generating_transaction": "सृजन लेन -देन", @@ -440,6 +452,8 @@ "memo": "ज्ञापन:", "message": "संदेश", "message_verified": "संदेश को सफलतापूर्वक सत्यापित किया गया था", + "messages": "संदेशों", + "method": "तरीका", "methods": "तरीकों", "min_amount": "न्यूनतम: ${value}", "min_value": "मिन: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "मोनेरो डार्क थीम", "monero_light_theme": "मोनेरो लाइट थीम", "moonpay_alert_text": "राशि का मूल्य अधिक है या करने के लिए बराबर होना चाहिए ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "एनएफटी लाने के दौरान एक त्रुटि हुई। कृपया अपने इंटरनेट कनेक्शन की जाँच करें और फिर से प्रयास करें।", "more_options": "और विकल्प", "multiple_addresses_detected": "कई पते का पता चला", "mweb_confirmed": "MWEB की पुष्टि की", @@ -591,6 +606,7 @@ "rep_warning_sub": "आपका प्रतिनिधि अच्छी स्थिति में नहीं दिखाई देता है। एक नया चयन करने के लिए यहां टैप करें", "repeat_wallet_password": "वॉलेट पासवर्ड दोहराएं", "repeated_password_is_incorrect": "बार -बार पासवर्ड गलत है। कृपया फिर से वॉलेट पासवर्ड दोहराएं।", + "requested": "अनुरोधित", "require_for_adding_contacts": "संपर्क जोड़ने के लिए आवश्यकता है", "require_for_all_security_and_backup_settings": "सभी सुरक्षा और बैकअप सेटिंग्स की आवश्यकता है", "require_for_assessing_wallet": "वॉलेट तक पहुँचने के लिए आवश्यकता है", @@ -652,6 +668,8 @@ "second_intro_content": "आपका Yat एक अद्वितीय इमोजी पता है जो आपकी सभी मुद्राओं के लिए आपके सभी लंबे हेक्साडेसिमल पतों को बदल देता है।", "second_intro_title": "उन सभी पर राज करने के लिए एक इमोजी पता", "security_and_backup": "सुरक्षा और बैकअप", + "security_risk": "सुरक्षा मे जोखिम", + "security_risk_description": "इस डोमेन को कई सुरक्षा प्रदाताओं द्वारा असुरक्षित के रूप में चिह्नित किया जाता है। अपनी संपत्ति की रक्षा के लिए तुरंत छोड़ दें।", "seed_alert_back": "वापस जाओ", "seed_alert_content": "बीज आपके बटुए को पुनर्प्राप्त करने का एकमात्र तरीका है। क्या आपने इसे लिखा है?", "seed_alert_title": "ध्यान", @@ -766,7 +784,9 @@ "show_keys": "बीज / कुंजियाँ दिखाएँ", "show_market_place": "बाज़ार दिखाएँ", "show_seed": "बीज दिखाओ", + "sign_all": "साइन इन करें", "sign_message": "हस्ताक्षर संदेश", + "sign_one": "साइन वन", "sign_up": "साइन अप करें", "sign_verify_message": "हस्ताक्षर / सत्यापित करें", "sign_verify_message_sub": "अपनी निजी कुंजी का उपयोग करके किसी संदेश पर हस्ताक्षर या सत्यापित करें", @@ -800,6 +820,7 @@ "subaddress_title": "उपखंड सूची", "subaddresses": "उप पते", "submit_request": "एक अनुरोध सबमिट करें", + "success": "सफलता", "successful": "सफल", "support_description_guides": "सामान्य मुद्दों के लिए प्रलेखन और समर्थन", "support_description_live_chat": "मुक्त और तेजी से! प्रशिक्षित सहायता प्रतिनिधि सहायता के लिए उपलब्ध हैं", @@ -807,6 +828,7 @@ "support_title_guides": "केक बटुए डॉक्स", "support_title_live_chat": "लाइव सहायता", "support_title_other_links": "अन्य समर्थन लिंक", + "supported": "का समर्थन किया", "swap": "बदलना", "sweeping_wallet": "स्वीपिंग वॉलेट", "sweeping_wallet_alert": "इसमें अधिक समय नहीं लगना चाहिए। इस स्क्रीन को न छोड़ें या स्वैप्ट फंड खो सकते हैं", @@ -839,6 +861,7 @@ "thorchain_taproot_address_not_supported": "थोरचेन प्रदाता टैपरोट पते का समर्थन नहीं करता है। कृपया पता बदलें या एक अलग प्रदाता का चयन करें।", "time": "${minutes}m ${seconds}s", "tip": "टिप:", + "to": "को", "today": "आज", "token_contract_address": "टोकन अनुबंध पता", "token_decimal": "सांकेतिक दशमलव", @@ -906,6 +929,7 @@ "transaction_sent_notice": "अगर 1 मिनट के बाद भी स्क्रीन आगे नहीं बढ़ती है, तो ब्लॉक एक्सप्लोरर और अपना ईमेल देखें।", "transactions": "लेन-देन", "transactions_by_date": "तारीख से लेन-देन", + "transport_type": "परिवहन प्रकार", "trongrid_history": "ट्रॉन्ग्रिड का इतिहास", "trusted": "भरोसा", "tx_commit_exception_no_dust_on_change": "लेनदेन को इस राशि से खारिज कर दिया जाता है। इन सिक्कों के साथ आप चेंज या ${min} के बिना ${max} को भेज सकते हैं जो परिवर्तन लौटाता है।", @@ -934,6 +958,7 @@ "unspent_coins_details_title": "अव्ययित सिक्कों का विवरण", "unspent_coins_title": "खर्च न किए गए सिक्के", "unsupported_asset": "हम इस संपत्ति के लिए इस कार्रवाई का समर्थन नहीं करते हैं. कृपया समर्थित परिसंपत्ति प्रकार का वॉलेट बनाएं या उस पर स्विच करें।", + "update_session": "अद्यतन सत्र", "uptime": "अपटाइम", "upto": "${value} तक", "usb": "USB", @@ -943,6 +968,7 @@ "use_ssl": "उपयोग SSL", "use_suggested": "सुझाए गए का प्रयोग करें", "use_testnet": "टेस्टनेट का उपयोग करें", + "user_rejected_method": "उपयोगकर्ता अस्वीकार विधि", "value": "कीमत", "value_type": "मान प्रकार", "variable_pair_not_supported": "यह परिवर्तनीय जोड़ी चयनित एक्सचेंजों के साथ समर्थित नहीं है", @@ -1026,5 +1052,6 @@ "you_will_get": "में बदलें", "you_will_receive_estimated_amount": "आपको#अनुमानित ( प्राप्त होगा)", "you_will_send": "से रूपांतरित करें", + "youCanGoBackToYourDapp": "अब आप अपने DAPP पर वापस जा सकते हैं", "yy": "वाईवाई" } \ No newline at end of file diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 497f22f16..1745d834a 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -53,6 +53,7 @@ "anonpay_description": "Generiraj ${type}. Primatelj može ${method} s bilo kojom podržanom kriptovalutom, a vi ćete primiti sredstva u ovaj novčanik.", "apk_update": "APK ažuriranje", "approve": "Odobriti", + "approve_request": "Odobriti zahtjev", "arrive_in_this_address": "${currency} ${tag}će stići na ovu adresu", "ascending": "Uzlazni", "ask_each_time": "Pitajte svaki put", @@ -116,9 +117,12 @@ "camera_consent": "Vaš će fotoaparat koristiti za snimanje slike u svrhu identifikacije od strane ${provider}. Pojedinosti potražite u njihovoj politici privatnosti.", "camera_permission_is_required": "Potrebno je dopuštenje kamere.\nOmogućite ga u postavkama aplikacije.", "cancel": "Poništi", + "cannot_verify": "Ne može provjeriti", + "cannot_verify_description": "Ova se domena ne može provjeriti. Pažljivo provjerite zahtjev prije odobrenja.", "card_address": "Adresa:", "cardholder_agreement": "Ugovor s vlasnikom kartice", "cards": "Kartice", + "chain_id": "Lanac", "chains": "Lanci", "change": "Promijeni", "change_backup_password_alert": "Nećemo moći uvesti Vaše prethodne datoteke sigurnosne kopije s novom lozinkom za sigurnosnu kopiju. Novu lozinku za sigurnosnu kopiju moći ćete koristiti samo za nove datoteke sigurnosne kopije. Jeste li sigurni da želite promijeniti lozinku za sigurnosnu kopiju?", @@ -174,6 +178,7 @@ "connect_yats": "Povežite Yats", "connect_your_hardware_wallet": "Spojite svoj hardverski novčanik pomoću Bluetooth -a ili USB -a", "connect_your_hardware_wallet_ios": "Spojite svoj hardverski novčanik pomoću Bluetooth -a", + "connected": "Povezan", "connection_sync": "Povezivanje i sinkronizacija", "connectWalletPrompt": "Povežite svoj novčanik s WalletConnectom za obavljanje transakcija", "contact": "Kontakt", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "Onemogući optimizaciju baterije", "disableBatteryOptimizationDescription": "Želite li onemogućiti optimizaciju baterije kako bi se pozadinska sinkronizacija radila slobodnije i glatko?", "disabled": "Onemogućeno", + "disconnect_session": "Odspojite sesiju", "discount": "Uštedite ${value}%", "display_settings": "Postavke zaslona", "displayable": "Dostupno za prikaz", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "Nemojte ih dijeliti ni s kim, uključujući podršku.\n\nVaša sredstva mogu i bit će ukradena!", "do_not_show_me": "Ne pokazuj mi ovo više", "domain_looks_up": "Pretraga domena", + "domain_mismatch": "Neusklađenost domena", + "domain_mismatch_description": "Ova web stranica ima domenu koja ne odgovara pošiljatelju ovog zahtjeva. Odobrenje može dovesti do gubitka sredstava.", "donation_link_details": "Detalji veza za donacije", "e_sign_consent": "E-Sign pristanak", "edit": "Uredi", @@ -298,6 +306,7 @@ "error_text_template": "Ime i adresa predloška ne smiju sadržavati znakove ` , ' \" \ni moraju biti dužine između 1 i 106 znakova", "error_text_wallet_name": "Naziv novčanika može sadržavati samo slova, brojeve, _ - simbole\ni mora imati između 1 i 33 znaka", "error_text_xmr": "XMR vrijednost ne smije biti veća od raspoloživog iznosa.\nBroj decimala smije biti 12 ili manji.", + "error_while_processing": "Došlo je do pogreške tijekom prožimanja", "errorGettingCredentials": "Neuspješno: Pogreška prilikom dobivanja vjerodajnica", "errorSigningTransaction": "Došlo je do pogreške prilikom potpisivanja transakcije", "estimated": "procijenjen", @@ -323,6 +332,7 @@ "export_backup": "Izvezi sigurnosnu kopiju", "export_logs": "Izvozni trupci", "export_outputs": "Izvoz izlaza", + "extend_session": "Proširiti sesiju", "extra_id": "Dodatni ID:", "extracted_address_content": "Poslat ćete sredstva primatelju\n${recipient_name}", "failed_authentication": "Autentifikacija neuspješna. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "Zaboravljena lozinka", "freeze": "Zamrznuti", "frequently_asked_questions": "Često postavljana pitanja", + "from": "Iz", "frozen": "Smrznuto", "frozen_balance": "Smrznuta ravnoteža", "full_balance": "Pun iznos", "gas_exceeds_allowance": "Plin potreban transakcijom premašuje dodatak.", + "gas_price": "Cijena plina", "generate_name": "Generiraj ime", "generating_gift_card": "Generiranje darovne kartice", "generating_transaction": "Generiranje transakcije", @@ -440,6 +452,8 @@ "memo": "Memo:", "message": "Poruka", "message_verified": "Poruka je uspješno provjerena", + "messages": "Poruke", + "method": "Metoda", "methods": "Metode", "min_amount": "Minimalno: ${value}", "min_value": "Min.: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "Monero tamna tema", "monero_light_theme": "Monero lagana tema", "moonpay_alert_text": "Vrijednost iznosa mora biti veća ili jednaka ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "Došlo je do pogreške tijekom dohvaćanja NFT -a. Ljubazno provjerite internetsku vezu i pokušajte ponovo.", "more_options": "Više opcija", "multiple_addresses_detected": "Otkrivene više adresa", "mweb_confirmed": "Potvrđen MWeb", @@ -589,6 +604,7 @@ "rep_warning_sub": "Čini se da vaš predstavnik nije u dobrom stanju. Dodirnite ovdje za odabir novog", "repeat_wallet_password": "Ponovite lozinku za novčanik", "repeated_password_is_incorrect": "Ponovljena lozinka je netočna. Molimo ponovite lozinku za novčanik.", + "requested": "Tražen", "require_for_adding_contacts": "Zahtijeva za dodavanje kontakata", "require_for_all_security_and_backup_settings": "Zahtijeva za sve postavke sigurnosti i sigurnosne kopije", "require_for_assessing_wallet": "Potreban za pristup novčaniku", @@ -650,6 +666,8 @@ "second_intro_content": "Vaš Yat jedinstvena je adresa emojija koja zamjenjuje sve vaše duge heksadecimalne adrese za sve vaše valute.", "second_intro_title": "Jedna adresa emojija koja će svima njima vladati", "security_and_backup": "Sigurnost i sigurnosna kopija", + "security_risk": "Sigurnosni rizik", + "security_risk_description": "Ova je domena označena kao nesigurna od strane više pružatelja usluga sigurnosti. Ostavite odmah da zaštitite svoju imovinu.", "seed_alert_back": "Vrati se natrag", "seed_alert_content": "Pristupni izraz jedini je način za oporavak novčanika. Jeste li ga zapisali?", "seed_alert_title": "Upozorenje", @@ -764,7 +782,9 @@ "show_keys": "Prikaži pristupni izraz/ključ", "show_market_place": "Prikaži tržište", "show_seed": "Prikaži pristupni izraz", + "sign_all": "Potpisati sve", "sign_message": "Poruka", + "sign_one": "Potpisati jedan", "sign_up": "Prijavite se", "sign_verify_message": "Potpisati / provjeriti", "sign_verify_message_sub": "Potpišite ili provjerite poruku pomoću privatnog ključa", @@ -798,6 +818,7 @@ "subaddress_title": "Lista podadresa", "subaddresses": "Podadrese", "submit_request": "podnesi zahtjev", + "success": "Uspjeh", "successful": "Uspješno", "support_description_guides": "Dokumentacija i podrška za uobičajena pitanja", "support_description_live_chat": "Besplatno i brzo! Obučeni predstavnici podrške dostupni su za pomoć", @@ -805,6 +826,7 @@ "support_title_guides": "Dokumenti s kolačem kolača", "support_title_live_chat": "Podrška uživo", "support_title_other_links": "Ostale veze za podršku", + "supported": "Potpomognut", "swap": "Mijenjati", "sweeping_wallet": "Čisti novčanik", "sweeping_wallet_alert": "Ovo ne bi trebalo dugo trajati. NE NAPUŠTAJTE OVAJ ZASLON INAČE SE POBREŠENA SREDSTVA MOGU IZGUBITI", @@ -837,6 +859,7 @@ "thorchain_taproot_address_not_supported": "Thorchain pružatelj ne podržava Taproot adrese. Promijenite adresu ili odaberite drugog davatelja usluga.", "time": "${minutes}m ${seconds}s", "tip": "Savjet:", + "to": "Do", "today": "Danas", "token_contract_address": "Adresa ugovora tokena", "token_decimal": "Token decimalni", @@ -904,6 +927,7 @@ "transaction_sent_notice": "Ako se zaslon ne nastavi nakon 1 minute, provjerite block explorer i svoju e-poštu.", "transactions": "Transakcije", "transactions_by_date": "Transakcije prema datumu", + "transport_type": "Transportni tip", "trongrid_history": "Povijest Trongrida", "trusted": "vjerovao", "tx_commit_exception_no_dust_on_change": "Transakcija se odbija s tim iznosom. Pomoću ovih kovanica možete poslati ${min} bez promjene ili ${max} koja vraća promjenu.", @@ -932,6 +956,7 @@ "unspent_coins_details_title": "Nepotrošeni detalji o novčićima", "unspent_coins_title": "Nepotrošeni novčići", "unsupported_asset": "Ne podržavamo ovu radnju za ovaj materijal. Izradite ili prijeđite na novčanik podržane vrste sredstava.", + "update_session": "Sesija ažuriranja", "uptime": "Radno vrijeme", "upto": "do ${value}", "usb": "USB", @@ -941,6 +966,7 @@ "use_ssl": "Koristi SSL", "use_suggested": "Koristite predloženo", "use_testnet": "Koristite TestNet", + "user_rejected_method": "Korisnik odbijena metoda", "value": "Vrijednost", "value_type": "Tipa vrijednosti", "variable_pair_not_supported": "Ovaj par varijabli nije podržan s odabranim burzama", @@ -1024,5 +1050,6 @@ "you_will_get": "Razmijeni u", "you_will_receive_estimated_amount": "Primit ćete(procijenjeno )", "you_will_send": "Razmijeni iz", + "youCanGoBackToYourDapp": "Sada se možete vratiti na svoj dapp", "yy": "GG" } \ No newline at end of file diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index a421287e9..837df61d2 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -53,6 +53,7 @@ "anonpay_description": "${type} ստեղծել: Ստացողը կարող է ${method} ցանկացած աջակցվող կրիպտոարժույթով, և դուք կստանաք միջոցներ այս դրամապանակում։", "apk_update": "APK թարմացում", "approve": "Հաստատել", + "approve_request": "Հաստատում է հայցը", "arrive_in_this_address": "${currency} ${tag}կժամանի այս հասցեում", "ascending": "Աճող", "ask_each_time": "Հարցնել ամեն անգամ", @@ -116,9 +117,12 @@ "camera_consent": "Ձեր տեսախցիկը կօգտագործվի ${provider}-ի կողմից ինքնությունը հաստատելու նպատակով: Խնդրում ենք ծանոթանալ նրանց Գաղտնիության Քաղաքականության հետ:", "camera_permission_is_required": "Տեսախցիկի թույլտվություն է պահանջվում: \nԽնդրում ենք այն ակտիվացնել հավելվածի կարգավորումներից:", "cancel": "Չեղարկել", + "cannot_verify": "Չի կարող հաստատել", + "cannot_verify_description": "Այս տիրույթը հնարավոր չէ ստուգել: Հաստատելուց առաջ ուշադիր ստուգեք հարցումը:", "card_address": "Հասցե։", "cardholder_agreement": "Քարտապանի Պայմանագիր", "cards": "Քարտեր", + "chain_id": "Շղթայի ID", "chains": "Շղթաներ", "change": "Փոփոխել", "change_backup_password_alert": "Ձեր նախորդ կրկնօրինակ ֆայլերը չեն հասանելի լինի ներմուծել նոր կրկնօրինակի գաղտնաբառով: Նոր կրկնօրինակի գաղտնաբառը կօգտագործվի միայն նոր կրկնօրինակ ֆայլերի համար: Վստահ եք, որ ցանկանում եք փոխել կրկնօրինակի գաղտնաբառը?", @@ -174,6 +178,7 @@ "connect_yats": "Միացրեք Yat-ները", "connect_your_hardware_wallet": "Միացրեք ձեր ապարատային դրամապանակը Bluetooth-ի կամ USB-ի միջոցով", "connect_your_hardware_wallet_ios": "Միացրեք ձեր ապարատային դրամապանակը Bluetooth-ի միջոցով", + "connected": "Միացված", "connection_sync": "Կապ և սինխրոնիզացիա", "connectWalletPrompt": "Միացրեք ձեր դրամապանակը WalletConnect-ի միջոցով գործարքներ կատարելու համար", "contact": "Կոնտակտ", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "Անջատել մարտկոցի օպտիմիզացիան", "disableBatteryOptimizationDescription": "Դուք ցանկանում եք անջատել մարտկոցի օպտիմիզացիան ֆոնային համաժամացման ավելի ազատ և հարթ ընթացքի համար?", "disabled": "Անջատված", + "disconnect_session": "Անջատել նստաշրջանը", "discount": "Խնայեք ${value}%", "display_settings": "Ցուցադրման կարգավորումներ", "displayable": "Ցուցադրվող", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "Մի կիսեք այս տեղեկատվությունը որևէ մեկի հետ, այդ թվում նաև աջակցության հետ: \n\nՁեր միջոցները կարող են գողանալ կորցնել!", "do_not_show_me": "Մի ցուցադրեք ինձ նորից", "domain_looks_up": "Դոմեյնի որոնում", + "domain_mismatch": "Դոմենի անհամապատասխանություն", + "domain_mismatch_description": "Այս կայքը ունի տիրույթ, որը չի համապատասխանում այս խնդրանքի ուղարկողին: Հաստատումը կարող է հանգեցնել միջոցների կորստի:", "donation_link_details": "Նվիրատվության հղումի մանրամասներ", "e_sign_consent": "Էլեկտրոնային ստորագրության համաձայնություն", "edit": "Խմբագրել", @@ -298,6 +306,7 @@ "error_text_template": "Տեսակի անունը և հասցեն չեն կարող պարունակել , '' \" նշանները\nև պետք է լինի 1-ից 106 նիշ երկարությամբ", "error_text_wallet_name": "Գաղտնաբառը կարող է պարունակել միայն տառեր, թվեր, _ - նշաններ\nև պետք է լինի 1-ից 33 նիշ երկարությամբ", "error_text_xmr": "XMR արժեքը չի կարող գերազանցել հասանելի մնացորդը:\nԿոտորակային թվերի քանակը պետք է լինի 12-ից պակաս կամ հավասար", + "error_while_processing": "Նախագծման ժամանակ սխալ է տեղի ունեցել", "errorGettingCredentials": "Սխալ. ծանրաբեռնված վստահագրեր ստանալիս", "errorSigningTransaction": "Սխալ է տեղի ունեցել գործարքը ստորագրելիս", "estimated": "Գնահատված", @@ -323,6 +332,7 @@ "export_backup": "Արտահանել կրկնօրինակը", "export_logs": "Արտահանման տեղեկամատյաններ", "export_outputs": "Արտահանման արդյունքներ", + "extend_session": "Ընդլայնված նիստ", "extra_id": "Լրացուցիչ ID", "extracted_address_content": "Դուք կուղարկեք գումար ${recipient_name}", "failed_authentication": "Վավերացումը ձախողվեց. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "Մոռացել եմ գաղտնաբառը", "freeze": "Կասեցնել", "frequently_asked_questions": "Հաճախ տրվող հարցեր", + "from": "Դեպի", "frozen": "Կասեցված", "frozen_balance": "Սառեցված հավասարակշռություն", "full_balance": "Լրիվ մնացորդ", "gas_exceeds_allowance": "Գործարքով պահանջվող գազը գերազանցում է նպաստը:", + "gas_price": "Գազի գին", "generate_name": "Գեներացնել անուն", "generating_gift_card": "Գեներացնում է նվեր քարտ", "generating_transaction": "Ստեղծող գործարք", @@ -440,6 +452,8 @@ "memo": "Մեմո:", "message": "Հաղորդագրություն", "message_verified": "Հաղորդագրությունը հաջողությամբ հաստատվեց", + "messages": "Հաղորդագրություններ", + "method": "Մեթոդ", "methods": "Մեթոդներ", "min_amount": "Նվազը: ${value}", "min_value": "Նվազը: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "Monero մութ տեսք", "monero_light_theme": "Monero պայծառ տեսք", "moonpay_alert_text": "Գումարի արժեքը պետք է լինի հավասար կամ ավելի քան ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "NFT- ները հանելիս սխալ է տեղի ունեցել: Սիրով ստուգեք ձեր ինտերնետային կապը եւ կրկին փորձեք:", "more_options": "Այլ տարբերակներ", "multiple_addresses_detected": "Հայտնաբերվել են բազմաթիվ հասցեներ", "mweb_confirmed": "Հաստատված MWEB", @@ -588,6 +603,7 @@ "rep_warning_sub": "Ձեր ներկայացուցիչը չի հայտնվում լավ վիճակում։ Սեղմեք այստեղ նոր ներկայացուցիչ ընտրելու համար", "repeat_wallet_password": "Վերականգնել դրամապանակի գաղտնաբառ", "repeated_password_is_incorrect": "Վերականգնված գաղտնաբառը սխալ է։ Խնդրում ենք վերականգնել դրամապանակի գաղտնաբառը", + "requested": "Պահանջել է", "require_for_adding_contacts": "Պահանջվում է կոնտակտներ ավելացնելու համար", "require_for_all_security_and_backup_settings": "Պահանջվում է բոլոր անվտանգության և կրկնօրինակման կարգավորումների համար", "require_for_assessing_wallet": "Պահանջվում է դրամապանակ մուտք գործելու համար", @@ -649,6 +665,8 @@ "second_intro_content": "Ձեր Yat-ը միակ եզակի էմոջի հասցե է, որը փոխարինում է ձեր բոլոր արժույթների համար ձեր բոլոր երկար հեքսադեցիմալ հասցեները", "second_intro_title": "Մի էմոջի հասցե, որը կառավարում է դրանք բոլորը", "security_and_backup": "Անվտանգություն և կրկնօրինակ", + "security_risk": "Անվտանգության ռիսկ", + "security_risk_description": "Այս տիրույթը դրոշմված է որպես անվտանգության ոչ անվտանգ `բազմաթիվ անվտանգության մատակարարների կողմից: Անմիջապես թողեք ձեր ակտիվները պաշտպանելու համար:", "seed_alert_back": "Վերադառնալ", "seed_alert_content": "Սերմը վերականգնելու ձեր դրամապանակի միակ միջոցն է։ Դուք արդեն գրի եք առել այն?", "seed_alert_title": "Ուշադրություն", @@ -762,7 +780,9 @@ "show_keys": "Ցուցադրել բանալիներ", "show_market_place": "Ցուցադրել շուկան", "show_seed": "Ցուցադրել սերմ", + "sign_all": "Ստորագրեք բոլորը", "sign_message": "Կնքել հաղորդագրություն", + "sign_one": "Ստորագրել մեկը", "sign_up": "Գրանցվել", "sign_verify_message": "Նշեք / ստուգեք", "sign_verify_message_sub": "Կնքել կամ ստուգել հաղորդագրությունը ձեր գախտնի բանալիով", @@ -796,6 +816,7 @@ "subaddress_title": "Ենթահասցեների ցանկ", "subaddresses": "Ենթահասցեներ", "submit_request": "Ուղարկել հարցում", + "success": "Հաջողություն", "successful": "Հաջողված", "support_description_guides": "Տարածված խնդիրների փաստաթղթավորում և աջակցություն", "support_description_live_chat": "Անվճար և արագ աջակցություն! Պատրաստված մասնագետները պատրաստ են օգնել", @@ -803,6 +824,7 @@ "support_title_guides": "Տորթ դրամապանակի փաստաթղթեր", "support_title_live_chat": "Անմիջական աջակցություն", "support_title_other_links": "Այլ աջակցության հղումներ", + "supported": "Աջակցել", "swap": "Փոխանակել", "sweeping_wallet": "Դրամապանակը մաքրվում է", "sweeping_wallet_alert": "Սա չի տևի երկար։ Խնդրում ենք չլքել այս էկրանը կամ մաքրված միջոցները կկորչեն։", @@ -835,6 +857,7 @@ "thorchain_taproot_address_not_supported": "ThorChain մատակարարը չի աջակցում Taproot հասցեները: Խնդրում ենք փոխել հասցեն կամ ընտրել այլ մատակարար:", "time": "${minutes}ր ${seconds}վ", "tip": "Թեյավճար", + "to": "Դեպի", "today": "Այսօր", "token_contract_address": "Token-ի պայմանագրի հասցե", "token_decimal": "Token-ի տասանիշ", @@ -902,6 +925,7 @@ "transaction_sent_notice": "Եթե էկրանը 1 րոպեի ընթացքում չի թարմանում, խնդրում ենք ստուգել բլոկի բացահայտիչը և Ձեր էլ. փոստը", "transactions": "Փոխանցումներ", "transactions_by_date": "Փոխանցումներ ամսաթվով", + "transport_type": "Տրանսպորտի տեսակը", "trongrid_history": "TronGrid պատմություն", "trusted": "Վստահելի", "tx_commit_exception_no_dust_on_change": "Փոխանցումը մերժվել է այս գումարով: Այս արժույթներով կարող եք ուղարկել ${min} առանց փոփոխության կամ ${max} որը վերադարձնում է փոփոխությունը", @@ -930,6 +954,7 @@ "unspent_coins_details_title": "Չծախսված արժույթների մանրամասները", "unspent_coins_title": "Չծախսված արժույթներ", "unsupported_asset": "Մենք չենք աջակցում այս գործողությունը այս ակտիվի համար։ Խնդրում ենք ստեղծել կամ անցնել աջակցվող ակտիվի տեսակին համապատասխան դրամապանակի։", + "update_session": "Թարմացրեք նիստը", "uptime": "Աշխատանքային ժամանակ", "upto": "մինչև ${value}", "usb": "USB", @@ -939,6 +964,7 @@ "use_ssl": "Օգտագործել SSL", "use_suggested": "Օգտագործել առաջարկվածը", "use_testnet": "Օգտագործել Testnet", + "user_rejected_method": "Օգտագործողի մերժված մեթոդը", "value": "Արժեք", "value_type": "Արժեքի տեսակ", "variable_pair_not_supported": "Այս փոփոխականի զույգը չի աջակցվում ընտրված բորսաների հետ", @@ -1022,5 +1048,6 @@ "you_will_get": "Ստացեք", "you_will_receive_estimated_amount": "Դուք կստանաք ( գնահատված )", "you_will_send": "Փոխանակեք", + "youCanGoBackToYourDapp": "Այժմ կարող եք վերադառնալ ձեր DAPP- ին", "yy": "ՏՏ" } \ No newline at end of file diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 3f9a16b5d..f159a2672 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -53,6 +53,7 @@ "anonpay_description": "Hasilkan ${type}. Penerima dapat ${method} dengan cryptocurrency apa pun yang didukung, dan Anda akan menerima dana di dompet ini.", "apk_update": "Pembaruan APK", "approve": "Menyetujui", + "approve_request": "Menyetujui permintaan", "arrive_in_this_address": "${currency} ${tag} akan tiba di alamat ini", "ascending": "Naik", "ask_each_time": "Tanyakan setiap kali", @@ -116,9 +117,12 @@ "camera_consent": "Kamera Anda akan digunakan untuk mengambil gambar untuk tujuan identifikasi oleh ${provider}. Silakan periksa Kebijakan Privasi mereka untuk detailnya.", "camera_permission_is_required": "Izin kamera diperlukan.\nSilakan aktifkan dari pengaturan aplikasi.", "cancel": "Batal", + "cannot_verify": "Tidak dapat memverifikasi", + "cannot_verify_description": "Domain ini tidak dapat diverifikasi. Periksa permintaan dengan cermat sebelum menyetujui.", "card_address": "Alamat:", "cardholder_agreement": "Persetujuan Pemegang Kartu", "cards": "Kartu", + "chain_id": "ID Rantai", "chains": "Rantai", "change": "Ubah", "change_backup_password_alert": "File cadangan sebelumnya tidak akan tersedia untuk diimpor dengan kata sandi cadangan baru. Kata sandi cadangan baru hanya akan digunakan untuk file cadangan baru. Apakah Anda yakin ingin mengubah kata sandi cadangan?", @@ -174,6 +178,7 @@ "connect_yats": "Hubungkan Yats", "connect_your_hardware_wallet": "Hubungkan dompet perangkat keras Anda menggunakan Bluetooth atau USB", "connect_your_hardware_wallet_ios": "Hubungkan dompet perangkat keras Anda menggunakan Bluetooth", + "connected": "Terhubung", "connection_sync": "Koneksi dan sinkronisasi", "connectWalletPrompt": "Hubungkan dompet Anda dengan WalletConnect untuk melakukan transaksi", "contact": "Kontak", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "Nonaktifkan optimasi baterai", "disableBatteryOptimizationDescription": "Apakah Anda ingin menonaktifkan optimasi baterai untuk membuat sinkronisasi latar belakang berjalan lebih bebas dan lancar?", "disabled": "Dinonaktifkan", + "disconnect_session": "Sesi Putuskan sambungan", "discount": "Hemat ${value}%", "display_settings": "Pengaturan tampilan", "displayable": "Dapat ditampilkan", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "Jangan berikan ini pada siapapun, termasuk dukungan.\n\nDana Anda bisa dan akan dicuri!", "do_not_show_me": "Jangan tampilkan ini lagi", "domain_looks_up": "Pencarian domain", + "domain_mismatch": "Ketidakcocokan Domain", + "domain_mismatch_description": "Situs web ini memiliki domain yang tidak cocok dengan pengirim permintaan ini. Menyetujui dapat menyebabkan hilangnya dana.", "donation_link_details": "Detail tautan donasi", "e_sign_consent": "E-Sign Consent", "edit": "Edit", @@ -298,6 +306,7 @@ "error_text_template": "Nama template dan alamat tidak boleh berisi simbol ` , ' \"\ndan harus antara 1 dan 106 karakter panjang", "error_text_wallet_name": "Nama dompet hanya dapat berisi huruf, angka, _ - simbol\ndan harus antara 1 dan 33 karakter panjang", "error_text_xmr": "Nilai XMR tidak boleh melebihi saldo yang tersedia.\nJumlah digit pecahan harus kurang atau sama dengan 12", + "error_while_processing": "Terjadi kesalahan saat prosedur", "errorGettingCredentials": "Gagal: Terjadi kesalahan saat mendapatkan kredensial", "errorSigningTransaction": "Terjadi kesalahan saat menandatangani transaksi", "estimated": "Diperkirakan", @@ -323,6 +332,7 @@ "export_backup": "Ekspor cadangan", "export_logs": "Log ekspor", "export_outputs": "Ekspor output", + "extend_session": "Perpanjang sesi", "extra_id": "ID tambahan:", "extracted_address_content": "Anda akan mengirim dana ke\n${recipient_name}", "failed_authentication": "Otentikasi gagal. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "Lupa Kata Sandi", "freeze": "Freeze", "frequently_asked_questions": "Pertanyaan yang sering diajukan", + "from": "Dari", "frozen": "Dibekukan", "frozen_balance": "Keseimbangan beku", "full_balance": "Saldo Penuh", "gas_exceeds_allowance": "Gas yang dibutuhkan oleh transaksi melebihi tunjangan.", + "gas_price": "Harga gas", "generate_name": "Hasilkan Nama", "generating_gift_card": "Membuat Kartu Hadiah", "generating_transaction": "Menghasilkan transaksi", @@ -440,6 +452,8 @@ "memo": "Memo:", "message": "Pesan", "message_verified": "Pesan itu berhasil diverifikasi", + "messages": "Pesan", + "method": "Metode", "methods": "Metode", "min_amount": "Min: ${value}", "min_value": "Min: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "Tema Gelap Monero", "monero_light_theme": "Tema Cahaya Monero", "moonpay_alert_text": "Nilai jumlah harus lebih atau sama dengan ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "Terjadi kesalahan saat mengambil NFT. Mohon periksa koneksi internet Anda dan coba lagi.", "more_options": "Opsi Lainnya", "multiple_addresses_detected": "Banyak alamat terdeteksi", "mweb_confirmed": "Mengkonfirmasi mWeb", @@ -591,6 +606,7 @@ "rep_warning_sub": "Perwakilan Anda tampaknya tidak bereputasi baik. Ketuk di sini untuk memilih yang baru", "repeat_wallet_password": "Ulangi Kata Sandi Dompet", "repeated_password_is_incorrect": "Kata sandi yang diulang tidak benar. Harap ulangi kata sandi dompet lagi.", + "requested": "Diminta", "require_for_adding_contacts": "Membutuhkan untuk menambahkan kontak", "require_for_all_security_and_backup_settings": "Memerlukan untuk semua pengaturan keamanan dan pencadangan", "require_for_assessing_wallet": "Diperlukan untuk mengakses dompet", @@ -653,6 +669,8 @@ "second_intro_content": "Yat Anda adalah satu alamat emoji yang unik yang menggantikan semua alamat heksadesimal panjang Anda untuk semua mata uang Anda.", "second_intro_title": "Satu alamat emoji untuk menguasainya semua", "security_and_backup": "Keamanan dan cadangan", + "security_risk": "Risiko keamanan", + "security_risk_description": "Domain ini ditandai sebagai tidak aman oleh beberapa penyedia keamanan. Segera pergi untuk melindungi aset Anda.", "seed_alert_back": "Kembali", "seed_alert_content": "Seed adalah satu-satunya cara untuk mengembalikan dompet Anda. Apakah Anda sudah menuliskannya?", "seed_alert_title": "Perhatian", @@ -767,7 +785,9 @@ "show_keys": "Tampilkan seed/kunci", "show_market_place": "Tampilkan Pasar", "show_seed": "Tampilkan seed", + "sign_all": "Menandatangani semua", "sign_message": "Pesan tanda", + "sign_one": "Tanda satu", "sign_up": "Daftar", "sign_verify_message": "Tanda / verifikasi", "sign_verify_message_sub": "Menandatangani atau memverifikasi pesan menggunakan kunci pribadi Anda", @@ -801,6 +821,7 @@ "subaddress_title": "Daftar sub-alamat", "subaddresses": "Sub-alamat", "submit_request": "kirim permintaan", + "success": "Kesuksesan", "successful": "Berhasil", "support_description_guides": "Dokumentasi dan dukungan untuk masalah umum", "support_description_live_chat": "Gratis dan Cepat! Perwakilan dukungan terlatih tersedia untuk membantu", @@ -808,6 +829,7 @@ "support_title_guides": "DOKS DOKO CAKE", "support_title_live_chat": "Dukungan langsung", "support_title_other_links": "Tautan dukungan lainnya", + "supported": "Didukung", "swap": "Menukar", "sweeping_wallet": "Dompet menyapu", "sweeping_wallet_alert": "Ini seharusnya tidak memakan waktu lama. Jangan tinggalkan layar ini atau dana swept mungkin hilang.", @@ -840,6 +862,7 @@ "thorchain_taproot_address_not_supported": "Penyedia Thorchain tidak mendukung alamat Taproot. Harap ubah alamatnya atau pilih penyedia yang berbeda.", "time": "${minutes}m ${seconds}s", "tip": "Tip:", + "to": "Ke", "today": "Hari ini", "token_contract_address": "Alamat kontrak token", "token_decimal": "Desimal token", @@ -907,6 +930,7 @@ "transaction_sent_notice": "Jika layar tidak bergerak setelah 1 menit, periksa block explorer dan email Anda.", "transactions": "Transaksi", "transactions_by_date": "Transaksi berdasarkan tanggal", + "transport_type": "Jenis transportasi", "trongrid_history": "Sejarah Trongrid", "trusted": "Dipercayai", "tx_commit_exception_no_dust_on_change": "Transaksi ditolak dengan jumlah ini. Dengan koin ini Anda dapat mengirim ${min} tanpa perubahan atau ${max} yang mengembalikan perubahan.", @@ -935,6 +959,7 @@ "unspent_coins_details_title": "Rincian koin yang tidak terpakai", "unspent_coins_title": "Koin yang tidak terpakai", "unsupported_asset": "Kami tidak mendukung tindakan ini untuk aset ini. Harap buat atau alihkan ke dompet dari jenis aset yang didukung.", + "update_session": "Perbarui sesi", "uptime": "Uptime", "upto": "hingga ${value}", "usb": "USB", @@ -944,6 +969,7 @@ "use_ssl": "Gunakan SSL", "use_suggested": "Gunakan yang Disarankan", "use_testnet": "Gunakan TestNet", + "user_rejected_method": "Metode yang ditolak pengguna", "value": "Nilai", "value_type": "Jenis Nilai", "variable_pair_not_supported": "Pasangan variabel ini tidak didukung dengan bursa yang dipilih", @@ -1027,5 +1053,6 @@ "you_will_get": "Konversi ke", "you_will_receive_estimated_amount": "Anda akan menerima(estimasi )", "you_will_send": "Konversi dari", + "youCanGoBackToYourDapp": "Anda dapat kembali ke dapp Anda sekarang", "yy": "YY" } \ No newline at end of file diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index dd52e5b9f..b05f292ee 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -53,6 +53,7 @@ "anonpay_description": "Genera ${type}. Il destinatario può ${method} con qualsiasi criptovaluta supportata, e riceverai fondi in questo portafoglio.", "apk_update": "Aggiornamento APK", "approve": "Approvare", + "approve_request": "Approvare la richiesta", "arrive_in_this_address": "${currency} ${tag}arriverà a questo indirizzo", "ascending": "Ascendente", "ask_each_time": "Chiedi ogni volta", @@ -116,9 +117,12 @@ "camera_consent": "La tua fotocamera verrà utilizzata per acquisire un'immagine a scopo identificativo da ${provider}. Si prega di controllare la loro Informativa sulla privacy per i dettagli.", "camera_permission_is_required": "È richiesta l'autorizzazione della fotocamera.\nAbilitalo dalle impostazioni dell'app.", "cancel": "Cancella", + "cannot_verify": "Non può verificare", + "cannot_verify_description": "Questo dominio non può essere verificato. Controlla attentamente la richiesta prima di approvare.", "card_address": "Indirizzo:", "cardholder_agreement": "Contratto del titolare della carta", "cards": "Carte", + "chain_id": "ID catena", "chains": "Catene", "change": "Cambia", "change_backup_password_alert": "I tuoi file di backup precedenti non potranno essere importati con la nuova password di backup. La nuova password di backup verrà usata soltanto per i nuovi file di backup. Sei sicuro di voler cambiare la tua password di backup?", @@ -174,6 +178,7 @@ "connect_yats": "Connetti Yats", "connect_your_hardware_wallet": "Collega il tuo portafoglio hardware tramite Bluetooth o USB", "connect_your_hardware_wallet_ios": "Collega il tuo portafoglio hardware tramite Bluetooth", + "connected": "Collegato", "connection_sync": "Connessione e sincronizzazione", "connectWalletPrompt": "Collega il tuo portafoglio con WalletConnect per effettuare transazioni", "contact": "Contatta", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "Disabilita l'ottimizzazione della batteria", "disableBatteryOptimizationDescription": "Vuoi disabilitare l'ottimizzazione della batteria per migliorare la sincronizzazione in background?", "disabled": "Disabilitato", + "disconnect_session": "Sessione di disconnessione", "discount": "Risparmia ${value}%", "display_settings": "Impostazioni di visualizzazione", "displayable": "Visualizzabile", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "Non condividerli con nessun altro, incluso il supporto.\n\nI tuoi fondi possono essere e saranno rubati!", "do_not_show_me": "Non mostrarmelo di nuovo", "domain_looks_up": "Cerca domini", + "domain_mismatch": "Dominio Mismatch", + "domain_mismatch_description": "Questo sito Web ha un dominio che non corrisponde al mittente di questa richiesta. L'approvazione può comportare la perdita di fondi.", "donation_link_details": "Dettagli del link di donazione", "e_sign_consent": "Consenso alla firma elettronica", "edit": "Modifica", @@ -298,6 +306,7 @@ "error_text_template": "Il nome del modello e l'indirizzo non possono contenere i simboli ` , ' \" \ne devono avere una lunghezza compresa tra 1 e 106 caratteri", "error_text_wallet_name": "Il nome del portafoglio può contenere solo lettere, numeri, _ - simboli\ne deve avere una lunghezza compresa tra 1 e 33 caratteri", "error_text_xmr": "Il valore in XMR non può eccedere il saldo disponibile.\nIl numero delle cifre decimali deve essere inferiore o uguale a 12", + "error_while_processing": "Si è verificato un errore durante la promozione", "errorGettingCredentials": "Non riuscito: errore durante il recupero delle credenziali", "errorSigningTransaction": "Si è verificato un errore durante la firma della transazione", "estimated": "Stimato", @@ -323,6 +332,7 @@ "export_backup": "Esporta backup", "export_logs": "Esporta log", "export_outputs": "Esporta output", + "extend_session": "Estendere la sessione", "extra_id": "Extra ID:", "extracted_address_content": "Invierai i tuoi fondi a\n${recipient_name}", "failed_authentication": "Autenticazione fallita. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "Password dimenticata", "freeze": "Congela", "frequently_asked_questions": "Domande frequenti", + "from": "Da", "frozen": "Congelato", "frozen_balance": "Saldo Congelato", "full_balance": "Saldo Completo", "gas_exceeds_allowance": "Il Gas richiesto dalla transazione supera il limite consentito.", + "gas_price": "Prezzo del gas", "generate_name": "Genera nome", "generating_gift_card": "Generazione carta regalo", "generating_transaction": "Generazione di transazione", @@ -440,6 +452,8 @@ "memo": "Memo:", "message": "Messaggio", "message_verified": "Il messaggio è stato verificato con successo", + "messages": "Messaggi", + "method": "Metodo", "methods": "Metodi", "min_amount": "Min: ${value}", "min_value": "Min: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "Tema scuro Monero", "monero_light_theme": "Tema chiaro Monero", "moonpay_alert_text": "Il valore dell'importo deve essere maggiore o uguale a ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "Si è verificato un errore durante il recupero di NFT. Si prega di controllare la tua connessione Internet e riprovare.", "more_options": "Altre opzioni", "multiple_addresses_detected": "Più indirizzi rilevati", "mweb_confirmed": "MWeb confermato", @@ -590,6 +605,7 @@ "rep_warning_sub": "Il tuo rappresentante non sembra essere in regola. Tocca qui per selezionarne uno nuovo", "repeat_wallet_password": "Ripeti la password del portafoglio", "repeated_password_is_incorrect": "La password ripetuta non è corretta. Si prega di ripetere di nuovo la password del portafoglio.", + "requested": "Richiesto", "require_for_adding_contacts": "Richiedi per l'aggiunta di contatti", "require_for_all_security_and_backup_settings": "Richiedi per tutte le impostazioni di sicurezza e backup", "require_for_assessing_wallet": "Richiedi per l'accesso al portafoglio", @@ -651,6 +667,8 @@ "second_intro_content": "Il tuo Yat è un unico indirizzo emoji univoco che sostituisce i lunghi indirizzi per tutte le tue valute.", "second_intro_title": "Un indirizzo emoji per domarli tutti", "security_and_backup": "Sicurezza e backup", + "security_risk": "Rischio di sicurezza", + "security_risk_description": "Questo dominio è contrassegnato come non sicuro da più fornitori di sicurezza. Lascia immediatamente per proteggere le tue risorse.", "seed_alert_back": "Torna indietro", "seed_alert_content": "Il seme è l'unico modo per recuperare il tuo portafoglio. L'hai trascritto?", "seed_alert_title": "Attenzione", @@ -765,7 +783,9 @@ "show_keys": "Mostra seme/chiavi", "show_market_place": "Mostra mercato", "show_seed": "Mostra seme", + "sign_all": "Firma tutto", "sign_message": "Firma messaggio", + "sign_one": "Segna uno", "sign_up": "Registrati", "sign_verify_message": "Firmare / verificare", "sign_verify_message_sub": "Firma o verifica un messaggio utilizzando la chiave privata", @@ -799,6 +819,7 @@ "subaddress_title": "Lista sottoindirizzi", "subaddresses": "Sottoindirizzi", "submit_request": "invia una richiesta", + "success": "Successo", "successful": "Riuscito", "support_description_guides": "Documentazione e supporto per problemi comuni", "support_description_live_chat": "Gratuito e veloce! I rappresentanti di supporto qualificati sono disponibili per assistervi", @@ -806,6 +827,7 @@ "support_title_guides": "Documentazione Cake Wallet", "support_title_live_chat": "Supporto in tempo reale", "support_title_other_links": "Altri collegamenti di supporto", + "supported": "Supportato", "swap": "Scambio", "sweeping_wallet": "Svuota portafoglio", "sweeping_wallet_alert": "Questo non dovrebbe richiedere molto tempo. NON LASCIARE QUESTA SCHERMATA O I FONDI DA TRASFERIRE POTREBBERO ANDARE PERSI", @@ -838,6 +860,7 @@ "thorchain_taproot_address_not_supported": "Il provider di Thorchain non supporta gli indirizzi di TapRoot. Si prega di modificare l'indirizzo o selezionare un fornitore diverso.", "time": "${minutes}m ${seconds}s", "tip": "Suggerimento:", + "to": "A", "today": "Oggi", "token_contract_address": "Indirizzo del contratto token", "token_decimal": "Decimale del token", @@ -905,6 +928,7 @@ "transaction_sent_notice": "Se la schermata non procede dopo 1 minuto, controlla un block explorer e la tua email.", "transactions": "Transazioni", "transactions_by_date": "Transazioni per data", + "transport_type": "Tipo di trasporto", "trongrid_history": "Cronologia TronGrid", "trusted": "Fidato", "tx_commit_exception_no_dust_on_change": "La transazione viene respinta con questo importo. Con queste monete è possibile inviare ${min} senza modifiche o ${max} che restituisce il cambiamento.", @@ -933,6 +957,7 @@ "unspent_coins_details_title": "Dettagli sulle monete non spese", "unspent_coins_title": "Monete non spese", "unsupported_asset": "Non supportiamo questa azione per questa risorsa. Crea o passa a un portafoglio di un tipo di asset supportato.", + "update_session": "Sessione di aggiornamento", "uptime": "Uptime", "upto": "fino a ${value}", "usb": "USB", @@ -942,6 +967,7 @@ "use_ssl": "Usa SSL", "use_suggested": "Usa suggerito", "use_testnet": "Usa TestNet", + "user_rejected_method": "Metodo respinto dall'utente", "value": "Valore", "value_type": "Tipo di valore", "variable_pair_not_supported": "Questa coppia di variabili non è supportata con gli scambi selezionati", @@ -1026,5 +1052,6 @@ "you_will_get": "Converti a", "you_will_receive_estimated_amount": "Riceverai(stimato )", "you_will_send": "Conveti da", + "youCanGoBackToYourDapp": "Puoi tornare al tuo DApp ora", "yy": "YY" } \ No newline at end of file diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index a7e2e6737..d6cb2c4b1 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -53,6 +53,7 @@ "anonpay_description": "${type} を生成します。受取人はサポートされている任意の暗号通貨で ${method} でき、あなたはこのウォレットで資金を受け取ります。", "apk_update": "APKアップデート", "approve": "承認する", + "approve_request": "リクエストを承認します", "arrive_in_this_address": "${currency} ${tag}はこの住所に到着します", "ascending": "上昇", "ask_each_time": "毎回尋ねてください", @@ -116,9 +117,12 @@ "camera_consent": "あなたのカメラは、${provider}_ までに識別目的で画像を撮影するために使用されます。詳細については、プライバシー ポリシーをご確認ください。", "camera_permission_is_required": "カメラの許可が必要です。\nアプリの設定から有効にしてください。", "cancel": "キャンセル", + "cannot_verify": "確認できません", + "cannot_verify_description": "このドメインを検証することはできません。承認する前に、リクエストを注意深く確認してください。", "card_address": "住所:", "cardholder_agreement": "カード所有者契約", "cards": "カード", + "chain_id": "チェーンID", "chains": "チェーン", "change": "変化する", "change_backup_password_alert": "以前のバックアップファイルは、新しいバックアップパスワードでインポートできなくなります。 新しいバックアップパスワードは、新しいバックアップファイルにのみ使用されます。 バックアップパスワードを変更してもよろしいですか?", @@ -174,6 +178,7 @@ "connect_yats": "Yatsを接続します", "connect_your_hardware_wallet": "BluetoothまたはUSBを使用して、ハードウェアウォレットを接続します", "connect_your_hardware_wallet_ios": "Bluetoothを使用してハードウェアウォレットを接続します", + "connected": "接続", "connection_sync": "接続と同期", "connectWalletPrompt": "ウォレットを WalletConnect に接続して取引を行う", "contact": "接触", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "バッテリーの最適化を無効にします", "disableBatteryOptimizationDescription": "バックグラウンドシンクをより自由かつスムーズに実行するために、バッテリーの最適化を無効にしたいですか?", "disabled": "無効", + "disconnect_session": "セッションを切断します", "discount": "${value}%を節約", "display_settings": "表示設定", "displayable": "表示可能", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "サポートを含め、これらを他の誰とも共有しないでください。\n\nあなたの資金は盗まれる可能性があります!", "do_not_show_me": "また僕にこれを見せないでください", "domain_looks_up": "ドメイン検索", + "domain_mismatch": "ドメインの不一致", + "domain_mismatch_description": "このWebサイトには、このリクエストの送信者と一致しないドメインがあります。承認すると、資金の損失につながる可能性があります。", "donation_link_details": "寄付リンクの詳細", "e_sign_consent": "電子署名の同意", "edit": "編集", @@ -298,6 +306,7 @@ "error_text_template": "テンプレートの名前とアドレスに含めることはできません ` , ' \" シンボル\n1〜106文字の長さである必要があります", "error_text_wallet_name": "ウォレット名には、文字、数字、_-記号のみを含めることができます\n長さは1〜33文字である必要があります", "error_text_xmr": "XMR値は利用可能な残高を超えることはできません.\n小数桁数は12以下でなければなりません", + "error_while_processing": "処理中にエラーが発生しました", "errorGettingCredentials": "失敗: 認証情報の取得中にエラーが発生しました", "errorSigningTransaction": "トランザクションの署名中にエラーが発生しました", "estimated": "推定", @@ -323,6 +332,7 @@ "export_backup": "バックアップのエクスポート", "export_logs": "ログをエクスポートします", "export_outputs": "エクスポート出力", + "extend_session": "セッションを拡張します", "extra_id": "追加ID:", "extracted_address_content": "に送金します\n${recipient_name}", "failed_authentication": "認証失敗. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "パスワードを忘れた", "freeze": "氷結", "frequently_asked_questions": "よくある質問", + "from": "から", "frozen": "凍った", "frozen_balance": "凍結バランス", "full_balance": "フルバランス", "gas_exceeds_allowance": "取引に必要なガスは、手当を超えています。", + "gas_price": "ガス価格", "generate_name": "名前の生成", "generated_gift_card": "ギフトカードの生成", "generating_gift_card": "ギフトカードの生成", @@ -441,6 +453,8 @@ "memo": "メモ:", "message": "メッセージ", "message_verified": "メッセージは正常に検証されました", + "messages": "メッセージ", + "method": "方法", "methods": "メソッド", "min_amount": "最小: ${value}", "min_value": "分: ${value} ${currency}", @@ -453,6 +467,7 @@ "monero_dark_theme": "モネロダークテーマ", "monero_light_theme": "モネロ ライト テーマ", "moonpay_alert_text": "金額の値は以上でなければなりません ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "NFTの取得中にエラーが発生しました。インターネット接続を確認して、もう一度やり直してください。", "more_options": "その他のオプション", "multiple_addresses_detected": "複数のアドレスが検出されました", "mweb_confirmed": "確認されたMWEB", @@ -590,6 +605,7 @@ "rep_warning_sub": "あなたの代表者は良好な状態ではないようです。ここをタップして、新しいものを選択します", "repeat_wallet_password": "ウォレットパスワードを繰り返します", "repeated_password_is_incorrect": "繰り返しパスワードが正しくありません。ウォレットのパスワードをもう一度繰り返してください。", + "requested": "リクエスト", "require_for_adding_contacts": "連絡先の追加に必要", "require_for_all_security_and_backup_settings": "すべてのセキュリティおよびバックアップ設定に必須", "require_for_assessing_wallet": "ウォレットにアクセスするために必要です", @@ -651,6 +667,8 @@ "second_intro_content": "Yatは、すべての通貨のすべての長い16進アドレスを置き換える単一の一意の絵文字アドレスです。", "second_intro_title": "それらすべてを支配する1つの絵文字アドレス", "security_and_backup": "セキュリティとバックアップ", + "security_risk": "セキュリティリスク", + "security_risk_description": "このドメインは、複数のセキュリティプロバイダーによって安全でないとフラグが付けられています。あなたの資産を保護するためにすぐに出発します。", "seed_alert_back": "戻る", "seed_alert_content": "種子はあなたの財布を回復する唯一の方法です。 書き留めましたか?", "seed_alert_title": "注意", @@ -765,7 +783,9 @@ "show_keys": "シード/キーを表示する", "show_market_place": "マーケットプレイスを表示", "show_seed": "シードを表示", + "sign_all": "すべてに署名します", "sign_message": "署名メッセージ", + "sign_one": "1つに署名します", "sign_up": "サインアップ", "sign_verify_message": "署名 /検証", "sign_verify_message_sub": "秘密鍵を使用してメッセージに署名または確認します", @@ -799,6 +819,7 @@ "subaddress_title": "サブアドレス一覧", "subaddresses": "サブアドレス", "submit_request": "リクエストを送信する", + "success": "成功", "successful": "成功", "support_description_guides": "一般的な問題のドキュメントとサポート", "support_description_live_chat": "無料で速い!訓練されたサポート担当者が支援することができます", @@ -806,6 +827,7 @@ "support_title_guides": "ケーキウォレットドキュメント", "support_title_live_chat": "ライブサポート", "support_title_other_links": "その他のサポートリンク", + "supported": "サポート", "swap": "スワップ", "sweeping_wallet": "スイープウォレット", "sweeping_wallet_alert": "これには時間がかかりません。この画面から離れないでください。そうしないと、スイープ ファンドが失われる可能性があります", @@ -838,6 +860,7 @@ "thorchain_taproot_address_not_supported": "Thorchainプロバイダーは、TapRootアドレスをサポートしていません。アドレスを変更するか、別のプロバイダーを選択してください。", "time": "${minutes}m ${seconds}s", "tip": "ヒント: ", + "to": "に", "today": "今日", "token_contract_address": "トークンコントラクトアドレス", "token_decimal": "トークン10進数", @@ -905,6 +928,7 @@ "transaction_sent_notice": "1分経っても画面が進まない場合は、ブロックエクスプローラーとメールアドレスを確認してください。", "transactions": "取引", "transactions_by_date": "日付ごとの取引", + "transport_type": "輸送タイプ", "trongrid_history": "トロンリッドの歴史", "trusted": "信頼できる", "tx_commit_exception_no_dust_on_change": "この金額ではトランザクションは拒否されます。 これらのコインを使用すると、おつりなしの ${min} またはおつりを返す ${max} を送信できます。", @@ -933,6 +957,7 @@ "unspent_coins_details_title": "未使用のコインの詳細", "unspent_coins_title": "未使用のコイン", "unsupported_asset": "このアセットに対するこのアクションはサポートされていません。サポートされているアセットタイプのウォレットを作成するか、ウォレットに切り替えてください。", + "update_session": "セッションを更新します", "uptime": "稼働時間", "upto": "up up ${value}", "usb": "USB", @@ -942,6 +967,7 @@ "use_ssl": "SSLを使用する", "use_suggested": "推奨を使用", "use_testnet": "テストネットを使用します", + "user_rejected_method": "ユーザーは拒否された方法", "value": "価値", "value_type": "値タイプ", "variable_pair_not_supported": "この変数ペアは、選択した取引所ではサポートされていません", @@ -1025,5 +1051,6 @@ "you_will_get": "に変換", "you_will_receive_estimated_amount": "あなたは(推定)を受け取ります", "you_will_send": "から変換", + "youCanGoBackToYourDapp": "あなたは今あなたのダップに戻ることができます", "yy": "YY" } \ No newline at end of file diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index bef6d359b..d451b69a6 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -53,6 +53,7 @@ "anonpay_description": "${type} 생성. 수신자는 지원되는 모든 암호화폐로 ${method}할 수 있으며 이 지갑에서 자금을 받게 됩니다.", "apk_update": "APK 업데이트", "approve": "승인하다", + "approve_request": "요청을 승인하십시오", "arrive_in_this_address": "${currency} ${tag}이(가) 이 주소로 도착합니다", "ascending": "오름차순", "ask_each_time": "매번 물어보십시오", @@ -116,9 +117,12 @@ "camera_consent": "귀하의 카메라는 ${provider}의 식별 목적으로 이미지를 캡처하는 데 사용됩니다. 자세한 내용은 해당 개인정보 보호정책을 확인하세요.", "camera_permission_is_required": "카메라 권한이 필요합니다.\n앱 설정에서 활성화해 주세요.", "cancel": "취소", + "cannot_verify": "확인할 수 없습니다", + "cannot_verify_description": "이 도메인은 확인할 수 없습니다. 승인하기 전에 요청을주의 깊게 확인하십시오.", "card_address": "주소:", "cardholder_agreement": "카드 소유자 계약", "cards": "카드", + "chain_id": "체인 ID", "chains": "쇠사슬", "change": "변화", "change_backup_password_alert": "이전 백업 파일은 새 백업 암호로 가져올 수 없습니다. 새 백업 암호는 새 백업 파일에만 사용됩니다. 백업 비밀번호를 변경 하시겠습니까?", @@ -174,6 +178,7 @@ "connect_yats": "야츠 연결", "connect_your_hardware_wallet": "Bluetooth 또는 USB를 사용하여 하드웨어 지갑을 연결하십시오", "connect_your_hardware_wallet_ios": "Bluetooth를 사용하여 하드웨어 지갑을 연결하십시오", + "connected": "연결", "connection_sync": "연결 및 동기화", "connectWalletPrompt": "거래를 하려면 WalletConnect에 지갑을 연결하세요.", "contact": "접촉", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "배터리 최적화를 비활성화합니다", "disableBatteryOptimizationDescription": "백그라운드 동기화를보다 자유롭고 매끄럽게 실행하기 위해 배터리 최적화를 비활성화하고 싶습니까?", "disabled": "장애가 있는", + "disconnect_session": "분리 세션", "discount": "${value}% 절약", "display_settings": "디스플레이 설정", "displayable": "표시 가능", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "지원을 포함하여 다른 사람과 이러한 정보를 공유하지 마십시오.\n\n귀하의 자금은 도난당할 수 있고 도난당할 수 있습니다!", "do_not_show_me": "나를 다시 표시하지 않음", "domain_looks_up": "도메인 조회", + "domain_mismatch": "도메인 불일치", + "domain_mismatch_description": "이 웹 사이트에는이 요청의 발신자와 일치하지 않는 도메인이 있습니다. 승인은 자금 손실로 이어질 수 있습니다.", "donation_link_details": "기부 링크 세부정보", "e_sign_consent": "전자 서명 동의", "edit": "편집하다", @@ -298,6 +306,7 @@ "error_text_template": "템플릿 이름과 주소는 포함 할 수 없습니다 ` , ' \" 기호 \n1 ~ 106 자 사이 여야합니다", "error_text_wallet_name": "지갑 이름은 문자, 숫자, _ - 기호만 포함할 수 있습니다.\n1~33자 사이여야 합니다.", "error_text_xmr": "XMR 값은 사용 가능한 잔액을 초과 할 수 없습니다.\n소수 자릿수는 12 이하 여야합니다", + "error_while_processing": "전환하는 동안 오류가 발생했습니다", "errorGettingCredentials": "실패: 자격 증명을 가져오는 중 오류가 발생했습니다.", "errorSigningTransaction": "거래에 서명하는 동안 오류가 발생했습니다.", "estimated": "예상", @@ -323,6 +332,7 @@ "export_backup": "백업 내보내기", "export_logs": "내보내기 로그", "export_outputs": "내보내기 출력", + "extend_session": "세션을 확장하십시오", "extra_id": "추가 ID:", "extracted_address_content": "당신은에 자금을 보낼 것입니다\n${recipient_name}", "failed_authentication": "인증 실패. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "비밀번호 찾기", "freeze": "얼다", "frequently_asked_questions": "자주 묻는 질문", + "from": "에서", "frozen": "겨울 왕국", "frozen_balance": "냉동 균형", "full_balance": "풀 밸런스", "gas_exceeds_allowance": "거래에 필요한 가스는 수당을 초과합니다.", + "gas_price": "가스 가격", "generate_name": "이름 생성", "generating_gift_card": "기프트 카드 생성 중", "generating_transaction": "거래 생성", @@ -440,6 +452,8 @@ "memo": "메모:", "message": "메시지", "message_verified": "메시지가 성공적으로 확인되었습니다", + "messages": "메시지", + "method": "방법", "methods": "행동 양식", "min_amount": "최소: ${value}", "min_value": "최소: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "모네로 다크 테마", "monero_light_theme": "모네로 라이트 테마", "moonpay_alert_text": "금액은 다음보다 크거나 같아야합니다 ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "NFT를 가져 오는 동안 오류가 발생했습니다. 인터넷 연결을 친절하게 확인하고 다시 시도하십시오.", "more_options": "추가 옵션", "multiple_addresses_detected": "여러 주소가 감지되었습니다", "mweb_confirmed": "확인 mweb", @@ -589,6 +604,7 @@ "rep_warning_sub": "귀하의 대표는 양호한 상태가 아닌 것 같습니다. 새 것을 선택하려면 여기를 탭하십시오", "repeat_wallet_password": "지갑 암호를 반복하십시오", "repeated_password_is_incorrect": "반복 된 비밀번호가 올바르지 않습니다. 지갑 암호를 다시 반복하십시오.", + "requested": "요청", "require_for_adding_contacts": "연락처 추가에 필요", "require_for_all_security_and_backup_settings": "모든 보안 및 백업 설정에 필요", "require_for_assessing_wallet": "지갑 접근을 위해 필요", @@ -650,6 +666,8 @@ "second_intro_content": "귀하의 Yat는 귀하의 모든 통화에 대해 긴 16진수 주소를 모두 대체하는 고유한 단일 이모지 주소입니다.", "second_intro_title": "그들을 모두 지배하는 하나의 이모티콘 주소", "security_and_backup": "보안 및 백업", + "security_risk": "보안 위험", + "security_risk_description": "이 도메인은 여러 보안 제공 업체가 안전하지 않은 것으로 표시됩니다. 자산을 보호하기 위해 즉시 떠나십시오.", "seed_alert_back": "돌아 가기", "seed_alert_content": "씨앗은 지갑을 복구하는 유일한 방법입니다. 적어 보셨나요?", "seed_alert_title": "주의", @@ -764,7 +782,9 @@ "show_keys": "시드 / 키 표시", "show_market_place": "마켓플레이스 표시", "show_seed": "종자 표시", + "sign_all": "모두 서명하십시오", "sign_message": "서명 메시지", + "sign_one": "하나를 서명하십시오", "sign_up": "가입", "sign_verify_message": "메시지에 서명하거나 확인하십시오", "sign_verify_message_sub": "개인 키를 사용하여 메시지에 서명하거나 확인하십시오", @@ -798,6 +818,7 @@ "subaddress_title": "하위 주소 목록", "subaddresses": "하위 주소", "submit_request": "요청을 제출", + "success": "성공", "successful": "성공적인", "support_description_guides": "일반적인 문제에 대한 문서화 및 지원", "support_description_live_chat": "자유롭고 빠릅니다! 훈련 된 지원 담당자가 지원할 수 있습니다", @@ -805,6 +826,7 @@ "support_title_guides": "케이크 지갑 문서", "support_title_live_chat": "실시간 지원", "support_title_other_links": "다른 지원 링크", + "supported": "지원", "swap": "교환", "sweeping_wallet": "스위핑 지갑", "sweeping_wallet_alert": "오래 걸리지 않습니다. 이 화면을 떠나지 마십시오. 그렇지 않으면 스웹트 자금이 손실될 수 있습니다.", @@ -837,6 +859,7 @@ "thorchain_taproot_address_not_supported": "Thorchain 제공 업체는 Taproot 주소를 지원하지 않습니다. 주소를 변경하거나 다른 공급자를 선택하십시오.", "time": "${minutes}m ${seconds}s", "tip": "팁:", + "to": "에게", "today": "오늘", "token_contract_address": "토큰 계약 주소", "token_decimal": "토큰 십진수", @@ -904,6 +927,7 @@ "transaction_sent_notice": "1분 후에도 화면이 진행되지 않으면 블록 익스플로러와 이메일을 확인하세요.", "transactions": "업무", "transactions_by_date": "날짜 별 거래", + "transport_type": "운송 유형", "trongrid_history": "트롱 트리드 역사", "trusted": "신뢰할 수 있는", "tx_commit_exception_no_dust_on_change": "이 금액으로 거래가 거부되었습니다. 이 코인을 사용하면 거스름돈 없이 ${min}를 보내거나 거스름돈을 반환하는 ${max}를 보낼 수 있습니다.", @@ -932,6 +956,7 @@ "unspent_coins_details_title": "사용하지 않은 동전 세부 정보", "unspent_coins_title": "사용하지 않은 동전", "unsupported_asset": "이 저작물에 대해 이 작업을 지원하지 않습니다. 지원되는 자산 유형의 지갑을 생성하거나 전환하십시오.", + "update_session": "업데이트 세션", "uptime": "가동 시간", "upto": "최대 ${value}", "usb": "USB", @@ -941,6 +966,7 @@ "use_ssl": "SSL 사용", "use_suggested": "추천 사용", "use_testnet": "TestNet을 사용하십시오", + "user_rejected_method": "사용자 거부 방법", "value": "값", "value_type": "가치 유형", "variable_pair_not_supported": "이 변수 쌍은 선택한 교환에서 지원되지 않습니다.", @@ -1024,6 +1050,7 @@ "you_will_get": "로 변환하다", "you_will_receive_estimated_amount": "(추정 )을 받게됩니다.", "you_will_send": "다음에서 변환", + "youCanGoBackToYourDapp": "이제 DAPP로 돌아갈 수 있습니다", "YY": "YY", "yy": "YY" } \ No newline at end of file diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 648650b3a..a49c1bb30 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -53,6 +53,7 @@ "anonpay_description": "${type} ကို ဖန်တီးပါ။ လက်ခံသူက ${method} ကို ပံ့ပိုးပေးထားသည့် cryptocurrency တစ်ခုခုဖြင့် လုပ်ဆောင်နိုင်ပြီး၊ သင်သည် ဤပိုက်ဆံအိတ်တွင် ရံပုံငွေများ ရရှိမည်ဖြစ်သည်။", "apk_update": "APK အပ်ဒိတ်", "approve": "လက်မခံပါ။", + "approve_request": "တောင်းဆိုမှုကိုအတည်ပြု", "arrive_in_this_address": "${currency} ${tag}ဤလိပ်စာသို့ ရောက်ရှိပါမည်။", "ascending": "တက်", "ask_each_time": "တစ်ခုချင်းစီကိုအချိန်မေးပါ", @@ -116,9 +117,12 @@ "camera_consent": "မှတ်ပုံတင်ခြင်းရည်ရွယ်ချက်များအတွက် ${provider} တွင် သင့်ကင်မရာကို အသုံးပြုပါမည်။ အသေးစိတ်အတွက် ၎င်းတို့၏ ကိုယ်ရေးကိုယ်တာမူဝါဒကို စစ်ဆေးပါ။", "camera_permission_is_required": "ကင်မရာခွင့်ပြုချက် လိုအပ်ပါသည်။\nအက်ပ်ဆက်တင်များမှ ၎င်းကိုဖွင့်ပါ။", "cancel": "မလုပ်တော့", + "cannot_verify": "အတည်ပြုလို့မရပါ", + "cannot_verify_description": "ဒီဒိုမိန်းကိုအတည်မပြုနိုင်ပါ။ အတည်ပြုခြင်းမပြုမီတောင်းဆိုမှုကိုဂရုတစိုက်စစ်ဆေးပါ။", "card_address": "လိပ်စာ-", "cardholder_agreement": "ကတ်ကိုင်ဆောင်သူ သဘောတူညီချက်", "cards": "ကတ်များ", + "chain_id": "Chain ID Chain ID", "chains": "ဆွဲကြိုး", "change": "ပြောင်းလဲပါ။", "change_backup_password_alert": "အရန်စကားဝှက်အသစ်ဖြင့် သင်၏ယခင်မိတ္တူဖိုင်များကို တင်သွင်းရန် မရနိုင်ပါ။ အရန်စကားဝှက်အသစ်ကို အရန်ဖိုင်အသစ်အတွက်သာ အသုံးပြုပါမည်။ အရန်စကားဝှက်ကို ပြောင်းလိုသည်မှာ သေချာပါသလား။", @@ -174,6 +178,7 @@ "connect_yats": "Yats ကိုချိတ်ဆက်ပါ။", "connect_your_hardware_wallet": "သင်၏ hardware ပိုက်ဆံအိတ်ကို Bluetooth သို့မဟုတ် USB ကို သုံး. ချိတ်ဆက်ပါ", "connect_your_hardware_wallet_ios": "သင်၏ hardware ပိုက်ဆံအိတ်ကို Bluetooth ကို အသုံးပြု. ချိတ်ဆက်ပါ", + "connected": "ချိတ်ဆက်ထားသော", "connection_sync": "ချိတ်ဆက်မှုနှင့် ထပ်တူပြုခြင်း။", "connectWalletPrompt": "အရောင်းအဝယ်ပြုလုပ်ရန် သင့်ပိုက်ဆံအိတ်ကို WalletConnect နှင့် ချိတ်ဆက်ပါ။", "contact": "ဆက်သွယ်ရန်", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "ဘက်ထရီ optimization ကိုပိတ်ပါ", "disableBatteryOptimizationDescription": "နောက်ခံထပ်တူပြုခြင်းနှင့်ချောချောမွေ့မွေ့ပြုလုပ်နိုင်ရန်ဘက်ထရီ optimization ကိုသင်ပိတ်ထားလိုပါသလား။", "disabled": "မသန်စွမ်း", + "disconnect_session": "session ကိုအဆက်ပြတ်", "discount": "${value}% ချွေတာ", "display_settings": "ပြသရန် ဆက်တင်များ", "displayable": "ပြသနိုင်သည်။", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "ပံ့ပိုးကူညီမှုအပါအဝင် ဤအရာများကို အခြားမည်သူနှင့်မျှ မမျှဝေပါနှင့်။\n\nသင့်ငွေများကို ခိုးယူခံရနိုင်သည်!", "do_not_show_me": "ဒါကို ထပ်မပြနဲ့", "domain_looks_up": "ဒိုမိန်းရှာဖွေမှုများ", + "domain_mismatch": "ဒိုမိန်းမတိုက်ဆိုင်", + "domain_mismatch_description": "ဤ 0 က်ဘ်ဆိုက်တွင်ဤတောင်းဆိုမှုကိုပေးပို့သူနှင့်မကိုက်ညီသောဒိုမိန်းရှိသည်။ အတည်ပြုခြင်းသည်ရန်ပုံငွေများဆုံးရှုံးမှုကိုဖြစ်ပေါ်စေနိုင်သည်။", "donation_link_details": "လှူဒါန်းရန်လင့်ခ်အသေးစိတ်", "e_sign_consent": "E-Sign သဘောတူညီချက်", "edit": "တည်းဖြတ်ပါ။", @@ -298,6 +306,7 @@ "error_text_template": "နမူနာပုံစံအမည်နှင့် လိပ်စာတွင် ` , ' \" သင်္ကေတများ မပါဝင်နိုင်ပါ\nနှင့် စာလုံးအရှည် 1 နှင့် 106 ကြား ရှိရမည်", "error_text_wallet_name": "ပိုက်ဆံအိတ်အမည်တွင် စာလုံးများ၊ နံပါတ်များ၊ _ - သင်္ကေတများသာ ပါဝင်နိုင်သည် \n နှင့် စာလုံးအရှည် 1 နှင့် 33 ကြားရှိရမည်", "error_text_xmr": "XMR တန်ဖိုးသည် ရနိုင်သောလက်ကျန်ကို ကျော်လွန်၍မရပါ။\nအပိုင်းကိန်းဂဏန်းများ သည် 12 နှင့် လျော့နည်းရမည်", + "error_while_processing": "procesing နေစဉ်အမှားတစ်ခုဖြစ်ပွားခဲ့သည်", "errorGettingCredentials": "မအောင်မြင်ပါ- အထောက်အထားများ ရယူနေစဉ် အမှားအယွင်း", "errorSigningTransaction": "ငွေပေးငွေယူ လက်မှတ်ထိုးစဉ် အမှားအယွင်းတစ်ခု ဖြစ်ပေါ်ခဲ့သည်။", "estimated": "ခန့်မှန်း", @@ -323,6 +332,7 @@ "export_backup": "အရန်ကူးထုတ်ရန်", "export_logs": "ပို့ကုန်မှတ်တမ်းများ", "export_outputs": "ပို့ကုန်ထုတ်ကုန်များ", + "extend_session": "session တစ်ခုတိုးချဲ့", "extra_id": "အပို ID-", "extracted_address_content": "သင်သည် \n${recipient_name} သို့ ရန်ပုံငွေများ ပေးပို့ပါမည်", "failed_authentication": "အထောက်အထားစိစစ်ခြင်း မအောင်မြင်ပါ။. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "စကားဝှက်မေ့နေပါသလား", "freeze": "အေးခဲ", "frequently_asked_questions": "မေးလေ့ရှိသောမေးခွန်းများ", + "from": "မှ", "frozen": "ဖြူဖြူ", "frozen_balance": "လက်ကျန်ငွေ", "full_balance": "Balance အပြည့်", "gas_exceeds_allowance": "ငွေပေးငွေယူမှလိုအပ်သောဓာတ်ငွေ့ထောက်ပံ့ကြေးကျော်လွန်။", + "gas_price": "ဓာတ်ငွေ့စျေးနှုန်း", "generate_name": "အမည်ဖန်တီးပါ။", "generating_gift_card": "လက်ဆောင်ကတ်ထုတ်ပေးခြင်း။", "generating_transaction": "ငွေပေးငွေယူကိုထုတ်လုပ်", @@ -440,6 +452,8 @@ "memo": "မှတ်စုတို:", "message": "မက်ဆေ့ချ်", "message_verified": "မက်ဆေ့ခ်ျကိုအောင်မြင်စွာအတည်ပြုခဲ့သည်", + "messages": "မက်ဆေ့ခ်ျ", + "method": "နည်းလမ်း", "methods": "နည်းလမ်းများ", "min_amount": "အနည်းဆုံး- ${value}", "min_value": "အနည်းဆုံး- ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "Monero Dark အပြင်အဆင်", "monero_light_theme": "Monero Light အပြင်အဆင်", "moonpay_alert_text": "ပမာဏ၏တန်ဖိုးသည် ${minAmount} ${fiatCurrency} နှင့် ပိုနေရမည်", + "moralis_nft_error": "nfts ရယူနေစဉ်အမှားတစ်ခုဖြစ်ပွားခဲ့သည်။ သင်၏အင်တာနက်ဆက်သွယ်မှုကိုကြင်နာစွာစစ်ဆေးပြီးထပ်ကြိုးစားပါ။", "more_options": "နောက်ထပ် ရွေးချယ်စရာများ", "multiple_addresses_detected": "အများအပြားလိပ်စာများရှာဖွေတွေ့ရှိ", "mweb_confirmed": "အတည်ပြုလိုက် mweb", @@ -589,6 +604,7 @@ "rep_warning_sub": "သင်၏ကိုယ်စားလှယ်သည်ကောင်းမွန်သောရပ်တည်မှုတွင်မဖြစ်သင့်ပါ။ အသစ်တစ်ခုကိုရွေးချယ်ရန်ဤနေရာတွင်အသာပုတ်ပါ", "repeat_wallet_password": "ပိုက်ဆံအိတ်စကားဝှက်ကိုပြန်လုပ်ပါ", "repeated_password_is_incorrect": "ထပ်ခါတလဲလဲစကားဝှက်မမှန်ကန်ပါ ကျေးဇူးပြုပြီးပိုက်ဆံအိတ်စကားဝှက်ကိုပြန်လုပ်ပါ။", + "requested": "တောင်းဆိုခဲ့သည်", "require_for_adding_contacts": "အဆက်အသွယ်များထည့်ရန် လိုအပ်သည်။", "require_for_all_security_and_backup_settings": "လုံခြုံရေးနှင့် အရန်ဆက်တင်များအားလုံးအတွက် လိုအပ်ပါသည်။", "require_for_assessing_wallet": "ပိုက်ဆံအိတ်ကို ဝင်သုံးရန် လိုအပ်သည်။", @@ -650,6 +666,8 @@ "second_intro_content": "သင်၏ Yat သည် သင့်ငွေကြေးအားလုံးအတွက် သင်၏ ရှည်လျားသော ဆဋ္ဌမကိန်းဂဏန်းများအားလုံးကို အစားထိုးသည့် တစ်မူထူးခြားသော အီမိုဂျီလိပ်စာတစ်ခုဖြစ်သည်။", "second_intro_title": "၎င်းတို့အားလုံးကို အုပ်ချုပ်ရန် အီမိုဂျီလိပ်စာတစ်ခု", "security_and_backup": "လုံခြုံရေးနှင့် မိတ္တူ", + "security_risk": "လုံခြုံရေးအန္တရာယ်", + "security_risk_description": "ဒီဒိုမိန်းကိုလုံခြုံရေးပံ့ပိုးပေးသူတွေရဲ့လုံခြုံမှုမရှိဘူး။ သင်၏ပိုင်ဆိုင်မှုများကိုကာကွယ်ရန်ချက်ချင်းချန်ထားပါ။", "seed_alert_back": "ပြန်သွားသည်", "seed_alert_content": "မျိုးစေ့သည် သင့်ပိုက်ဆံအိတ်ကို ပြန်လည်ရယူရန် တစ်ခုတည်းသောနည်းလမ်းဖြစ်သည်။ ရေးပြီးပြီလား။", "seed_alert_title": "အာရုံ", @@ -764,7 +782,9 @@ "show_keys": "မျိုးစေ့ /သော့များကို ပြပါ။", "show_market_place": "စျေးကွက်ကိုပြသပါ။", "show_seed": "မျိုးစေ့ကိုပြပါ။", + "sign_all": "အားလုံးလက်မှတ်ထိုးပါ", "sign_message": "လက်မှတ်စာ", + "sign_one": "လက်မှတ်ထိုးပါ", "sign_up": "ဆိုင်းအပ်", "sign_verify_message": "လက်မှတ်ထိုး / အတည်ပြုရန်", "sign_verify_message_sub": "သင်၏ကိုယ်ပိုင်သော့ကို သုံး. မက်ဆေ့ခ်ျကိုလက်မှတ်ထိုးပါ", @@ -798,6 +818,7 @@ "subaddress_title": "လိပ်စာစာရင်း", "subaddresses": "လိပ်စာများ", "submit_request": "တောင်းဆိုချက်တစ်ခုတင်ပြပါ။", + "success": "အောင်မြင်ခြင်း", "successful": "အောင်မြင်တယ်။", "support_description_guides": "ဘုံပြ issues နာများအတွက်စာရွက်စာတမ်းများနှင့်ထောက်ခံမှု", "support_description_live_chat": "အခမဲ့နှင့်အစာရှောင်ခြင်း! လေ့ကျင့်ထားသောထောက်ခံသူကိုယ်စားလှယ်များသည်ကူညီနိုင်သည်", @@ -805,6 +826,7 @@ "support_title_guides": "ကိတ်မုန့်ပိုက်ဆံအိတ်များ", "support_title_live_chat": "တိုက်ရိုက်ပံ့ပိုးမှု", "support_title_other_links": "အခြားအထောက်အပံ့လင့်များ", + "supported": "ထောက်ပံ့ခဲ့သည်", "swap": "လဲလှယ်", "sweeping_wallet": "ိုက်ဆံအိတ် တံမြက်လှည်း", "sweeping_wallet_alert": "ဒါက ကြာကြာမခံသင့်ပါဘူး။ ဤစခရင်ကို ချန်မထားပါနှင့် သို့မဟုတ် ထုတ်ယူထားသော ရန်ပုံငွေများ ဆုံးရှုံးနိုင်သည်", @@ -837,6 +859,7 @@ "thorchain_taproot_address_not_supported": "Thorchain Provider သည် Taproot လိပ်စာများကိုမထောက်ခံပါ။ ကျေးဇူးပြု. လိပ်စာကိုပြောင်းပါသို့မဟုတ်အခြားပံ့ပိုးပေးသူကိုရွေးချယ်ပါ။", "time": "${minutes}m ${seconds}s", "tip": "အကြံပြုချက်-", + "to": "သို့", "today": "ဒီနေ့", "token_contract_address": "တိုကင်စာချုပ်လိပ်စာ", "token_decimal": "တိုကင်ဒဿမ", @@ -904,6 +927,7 @@ "transaction_sent_notice": "မျက်နှာပြင်သည် ၁ မိနစ်အကြာတွင် ဆက်လက်မလုပ်ဆောင်ပါက၊ ပိတ်ဆို့ရှာဖွေသူနှင့် သင့်အီးမေးလ်ကို စစ်ဆေးပါ။", "transactions": "ငွေပေးငွေယူ", "transactions_by_date": "ရက်စွဲအလိုက် ငွေလွှဲမှုများ", + "transport_type": "သယ်ယူပို့ဆောင်ရေးအမျိုးအစား", "trongrid_history": "Trongrid သမိုင်း", "trusted": "ယုံတယ်။", "tx_commit_exception_no_dust_on_change": "အဆိုပါငွေပေးငွေယူကဒီပမာဏနှင့်အတူပယ်ချခံရသည်။ ဤဒင်္ဂါးပြားများနှင့်အတူပြောင်းလဲမှုကိုပြန်လည်ပြောင်းလဲခြင်းသို့မဟုတ် ${min} မပါဘဲ ${max} ပေးပို့နိုင်သည်။", @@ -932,6 +956,7 @@ "unspent_coins_details_title": "အသုံးမဝင်သော အကြွေစေ့အသေးစိတ်များ", "unspent_coins_title": "အသုံးမဝင်သော အကြွေစေ့များ", "unsupported_asset": "ဤပိုင်ဆိုင်မှုအတွက် ဤလုပ်ဆောင်ချက်ကို ကျွန်ုပ်တို့ မပံ့ပိုးပါ။ ကျေးဇူးပြု၍ ပံ့ပိုးပေးထားသော ပိုင်ဆိုင်မှုအမျိုးအစား၏ ပိုက်ဆံအိတ်ကို ဖန်တီးပါ သို့မဟုတ် ပြောင်းပါ။", + "update_session": "update session ကို", "uptime": "အထက်က", "upto": "${value} အထိ", "usb": "ယူအက်စ်ဘီ", @@ -941,6 +966,7 @@ "use_ssl": "SSL ကိုသုံးပါ။", "use_suggested": "အကြံပြုထားသည်ကို အသုံးပြုပါ။", "use_testnet": "testnet ကိုသုံးပါ", + "user_rejected_method": "အသုံးပြုသူသည်နည်းလမ်းကိုငြင်းပယ်ခဲ့သည်", "value": "အဘိုး", "value_type": "Value အမျိုးအစား", "variable_pair_not_supported": "ရွေးချယ်ထားသော ဖလှယ်မှုများဖြင့် ဤပြောင်းလဲနိုင်သောအတွဲကို ပံ့ပိုးမထားပါ။", @@ -1024,5 +1050,6 @@ "you_will_get": "သို့ပြောင်းပါ။", "you_will_receive_estimated_amount": "သင် ( ခန့်မှန်းခြေ ) လက်ခံရရှိလိမ့်မည်", "you_will_send": "မှပြောင်းပါ။", + "youCanGoBackToYourDapp": "သငျသညျယခုသင်၏ dapp ကိုပြန်သွားနိုင်ပါတယ်", "yy": "YY" } \ No newline at end of file diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 4cd39fa40..bdd1f5c0c 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -53,6 +53,7 @@ "anonpay_description": "Genereer ${type}. De ontvanger kan ${method} gebruiken met elke ondersteunde cryptocurrency en u ontvangt geld in deze portemonnee", "apk_update": "APK-update", "approve": "Goedkeuren", + "approve_request": "Het verzoek goedkeuren", "arrive_in_this_address": "${currency} ${tag}komt aan op dit adres", "ascending": "Stijgend", "ask_each_time": "Vraag het elke keer", @@ -116,9 +117,12 @@ "camera_consent": "Uw camera wordt gebruikt om vóór ${provider} een beeld vast te leggen voor identificatiedoeleinden. Raadpleeg hun privacybeleid voor meer informatie.", "camera_permission_is_required": "Cameratoestemming is vereist.\nSchakel dit in via de app-instellingen.", "cancel": "Annuleer", + "cannot_verify": "Kan niet verifiëren", + "cannot_verify_description": "Dit domein kan niet worden geverifieerd. Controleer het verzoek zorgvuldig voordat u het goedkeurt.", "card_address": "Adres:", "cardholder_agreement": "Kaarthouderovereenkomst", "cards": "Kaarten", + "chain_id": "Keten -ID", "chains": "Ketens", "change": "Verandering", "change_backup_password_alert": "Uw vorige back-upbestanden kunnen niet worden geïmporteerd met een nieuw back-upwachtwoord. Het nieuwe back-upwachtwoord wordt alleen gebruikt voor nieuwe back-upbestanden. Weet u zeker dat u het back-upwachtwoord wilt wijzigen?", @@ -174,6 +178,7 @@ "connect_yats": "Verbind Yats", "connect_your_hardware_wallet": "Sluit uw hardware -portemonnee aan met Bluetooth of USB", "connect_your_hardware_wallet_ios": "Sluit uw hardware -portemonnee aan met Bluetooth", + "connected": "Aangesloten", "connection_sync": "Verbinding en synchronisatie", "connectWalletPrompt": "Verbind uw portemonnee met WalletConnect om transacties uit te voeren", "contact": "Contact", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "Schakel de batterijoptimalisatie uit", "disableBatteryOptimizationDescription": "Wilt u de optimalisatie van de batterij uitschakelen om achtergrondsynchronisatie te laten werken, vrijer en soepeler?", "disabled": "Gehandicapt", + "disconnect_session": "Koppel de sessie los", "discount": "Bespaar ${value}%", "display_settings": "Weergave-instellingen", "displayable": "Weer te geven", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "Deel deze met niemand anders, ook niet met support.\n\nUw geld kan en zal worden gestolen!", "do_not_show_me": "laat me dit niet opnieuw zien", "domain_looks_up": "Domein opzoeken", + "domain_mismatch": "Domein mismatch", + "domain_mismatch_description": "Deze website heeft een domein dat niet overeenkomt met de afzender van dit verzoek. Goedkeuring kan leiden tot verlies van fondsen.", "donation_link_details": "Details van de donatielink", "e_sign_consent": "Toestemming e-ondertekenen", "edit": "Bewerk", @@ -298,6 +306,7 @@ "error_text_template": "Sjabloonnaam en -adres mogen niet bevatten ` , ' \" symbolen\nen moet tussen de 1 en 106 tekens lang zijn", "error_text_wallet_name": "Naam portemonnee kan alleen letters, cijfers , _ - symbolen bevatten\nen moet tussen de 1 en 33 tekens lang zijn", "error_text_xmr": "XMR-waarde kan het beschikbare saldo niet overschrijden.\nHet aantal breukcijfers moet kleiner zijn dan of gelijk zijn aan 12", + "error_while_processing": "Er is een fout opgetreden tijdens het procederen", "errorGettingCredentials": "Mislukt: fout bij het ophalen van inloggegevens", "errorSigningTransaction": "Er is een fout opgetreden tijdens het ondertekenen van de transactie", "estimated": "Geschatte", @@ -323,6 +332,7 @@ "export_backup": "Back-up exporteren", "export_logs": "Exporteer logboeken", "export_outputs": "Exportuitgangen exporteren", + "extend_session": "Sessie verlengen", "extra_id": "Extra ID:", "extracted_address_content": "U stuurt geld naar\n${recipient_name}", "failed_authentication": "Mislukte authenticatie. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "Wachtwoord vergeten", "freeze": "Bevriezen", "frequently_asked_questions": "Veelgestelde vragen", + "from": "Van", "frozen": "Bevroren", "frozen_balance": "Bevroren balans", "full_balance": "Volledig saldo", "gas_exceeds_allowance": "Gas vereist door transactie overschrijdt de vergoeding.", + "gas_price": "Gasprijs", "generate_name": "Naam genereren", "generating_gift_card": "Cadeaubon genereren", "generating_transaction": "Transactie genereren", @@ -440,6 +452,8 @@ "memo": "Memo:", "message": "Bericht", "message_verified": "Het bericht is succesvol geverifieerd", + "messages": "Berichten", + "method": "Methode", "methods": "Methoden", "min_amount": "Min: ${value}", "min_value": "Min: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "Monero donker thema", "monero_light_theme": "Monero Light-thema", "moonpay_alert_text": "Waarde van het bedrag moet meer of gelijk zijn aan ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "Er is een fout opgetreden bij het ophalen van NFT's. Controleer uw internetverbinding vriendelijk en probeer het opnieuw.", "more_options": "Meer opties", "multiple_addresses_detected": "Meerdere adressen gedetecteerd", "mweb_confirmed": "Bevestigde MWEB", @@ -589,6 +604,7 @@ "rep_warning_sub": "Uw vertegenwoordiger lijkt niet goed te staan. Tik hier om een ​​nieuwe te selecteren", "repeat_wallet_password": "Herhaal het Wallet -wachtwoord", "repeated_password_is_incorrect": "Herhaald wachtwoord is onjuist. Herhaal het Wallet -wachtwoord opnieuw.", + "requested": "Gevraagd", "require_for_adding_contacts": "Vereist voor het toevoegen van contacten", "require_for_all_security_and_backup_settings": "Vereist voor alle beveiligings- en back-upinstellingen", "require_for_assessing_wallet": "Vereist voor toegang tot portemonnee", @@ -650,6 +666,8 @@ "second_intro_content": "Je Yat is een enkel uniek emoji-adres dat al je lange hexadecimale adressen vervangt voor al je valuta's.", "second_intro_title": "Eén emoji-adres om ze allemaal te regeren", "security_and_backup": "Beveiliging en back-up", + "security_risk": "Beveiligingsrisico", + "security_risk_description": "Dit domein wordt door meerdere beveiligingsproviders gemarkeerd als onveilig. Vertrek onmiddellijk om uw vermogen te beschermen.", "seed_alert_back": "Ga terug", "seed_alert_content": "Het zaad is de enige manier om uw portemonnee te herstellen. Heb je het opgeschreven?", "seed_alert_title": "Aandacht", @@ -764,7 +782,9 @@ "show_keys": "Toon zaad/sleutels", "show_market_place": "Toon Marktplaats", "show_seed": "Toon zaad", + "sign_all": "Teken alles", "sign_message": "Aanmeldingsbericht", + "sign_one": "Ondertekenen een", "sign_up": "Aanmelden", "sign_verify_message": "Teken of verifieer bericht", "sign_verify_message_sub": "Teken of verifieer een bericht met uw privésleutel", @@ -798,6 +818,7 @@ "subaddress_title": "Subadreslijst", "subaddresses": "Subadressen", "submit_request": "een verzoek indienen", + "success": "Succes", "successful": "Succesvol", "support_description_guides": "Documentatie en ondersteuning voor gemeenschappelijke problemen", "support_description_live_chat": "Gratis en snel! Getrainde ondersteuningsvertegenwoordigers zijn beschikbaar om te helpen", @@ -805,6 +826,7 @@ "support_title_guides": "Cake -portemonnee documenten", "support_title_live_chat": "Live ondersteuning", "support_title_other_links": "Andere ondersteuningslinks", + "supported": "Ondersteund", "swap": "Ruil", "sweeping_wallet": "Vegende portemonnee", "sweeping_wallet_alert": "Dit duurt niet lang. VERLAAT DIT SCHERM NIET, ANDERS KAN HET SWEPT-GELD VERLOREN WORDEN", @@ -837,6 +859,7 @@ "thorchain_taproot_address_not_supported": "De Thorchain -provider ondersteunt geen Taprooot -adressen. Wijzig het adres of selecteer een andere provider.", "time": "${minutes}m ${seconds}s", "tip": "Tip:", + "to": "Naar", "today": "Vandaag", "token_contract_address": "Token contractadres", "token_decimal": "Token decimaal", @@ -904,6 +927,7 @@ "transaction_sent_notice": "Als het scherm na 1 minuut niet verder gaat, controleer dan een blokverkenner en je e-mail.", "transactions": "Transacties", "transactions_by_date": "Transacties op datum", + "transport_type": "Transporttype", "trongrid_history": "Trongrid geschiedenis", "trusted": "vertrouwd", "tx_commit_exception_no_dust_on_change": "De transactie wordt afgewezen met dit bedrag. Met deze munten kunt u ${min} verzenden zonder verandering of ${max} die wijziging retourneert.", @@ -932,6 +956,7 @@ "unspent_coins_details_title": "Details van niet-uitgegeven munten", "unspent_coins_title": "Ongebruikte munten", "unsupported_asset": "We ondersteunen deze actie niet voor dit item. Maak of schakel over naar een portemonnee van een ondersteund activatype.", + "update_session": "Updatesessie", "uptime": "Uptime", "upto": "tot ${value}", "usb": "USB", @@ -941,6 +966,7 @@ "use_ssl": "Gebruik SSL", "use_suggested": "Gebruik aanbevolen", "use_testnet": "Gebruik testnet", + "user_rejected_method": "Gebruiker afgewezen methode", "value": "Waarde", "value_type": "Waarde type", "variable_pair_not_supported": "Dit variabelenpaar wordt niet ondersteund met de geselecteerde uitwisselingen", @@ -1025,5 +1051,6 @@ "you_will_get": "Converteren naar", "you_will_receive_estimated_amount": "U ontvangt(geschat )", "you_will_send": "Converteren van", + "youCanGoBackToYourDapp": "U kunt nu terug naar uw DApp gaan", "yy": "JJ" } \ No newline at end of file diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 5605603d9..7199b0e90 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -53,6 +53,7 @@ "anonpay_description": "Wygeneruj ${type}. Odbiorca może ${method} z dowolną obsługiwaną kryptowalutą, a Ty otrzymasz środki w tym portfelu.", "apk_update": "Aktualizacja APK", "approve": "Zatwierdzić", + "approve_request": "Zatwierdzić żądanie", "arrive_in_this_address": "${currency} ${tag}dotrze na ten adres", "ascending": "Wznoszący się", "ask_each_time": "Zapytaj za każdym razem", @@ -116,9 +117,12 @@ "camera_consent": "Twój aparat zostanie użyty do przechwycenia obrazu w celach identyfikacyjnych przez ${provider}. Aby uzyskać szczegółowe informacje, sprawdź ich Politykę prywatności.", "camera_permission_is_required": "Wymagane jest pozwolenie na korzystanie z aparatu.\nWłącz tę funkcję w ustawieniach aplikacji.", "cancel": "Anuluj", + "cannot_verify": "Nie można zweryfikować", + "cannot_verify_description": "Tę domeny nie można zweryfikować. Przed zatwierdzeniem sprawdź prośbę.", "card_address": "Adres:", "cardholder_agreement": "Umowa posiadacza karty", "cards": "Karty", + "chain_id": "Identyfikator łańcucha", "chains": "Łańcuchy", "change": "Zmień", "change_backup_password_alert": "Twoje poprzednie pliki kopii zapasowej nie będą dostępne do zaimportowania z nowym hasłem kopii zapasowej. Nowe hasło kopii zapasowej będzie używane tylko dla nowych plików kopii zapasowych. Czy na pewno chcesz zmienić hasło zapasowe?", @@ -174,6 +178,7 @@ "connect_yats": "Połącz Yats", "connect_your_hardware_wallet": "Podłącz portfel sprzętowy za pomocą Bluetooth lub USB", "connect_your_hardware_wallet_ios": "Podłącz portfel sprzętowy za pomocą Bluetooth", + "connected": "Połączony", "connection_sync": "Połączenie i synchronizacja", "connectWalletPrompt": "Połącz swój portfel z WalletConnect, aby dokonywać transakcji", "contact": "Kontakt", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "Wyłącz optymalizację baterii", "disableBatteryOptimizationDescription": "Czy chcesz wyłączyć optymalizację baterii, aby synchronizacja tła działała swobodniej i płynnie?", "disabled": "Wyłączone", + "disconnect_session": "Odłącz sesję", "discount": "Zaoszczędź ${value}%", "display_settings": "Ustawienia wyświetlania", "displayable": "Wyświetlane", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "NIE udostępniaj ich nikomu innemu, w tym pomocy technicznej.\n\nJeśli to zrobisz twoje środki prawdopodobnie zostaną skradzione!", "do_not_show_me": "Nie pokazuj mi tego ponownie", "domain_looks_up": "Wyszukiwanie domen", + "domain_mismatch": "Niedopasowanie domeny", + "domain_mismatch_description": "Ta strona ma domenę, która nie pasuje do nadawcy tego żądania. Zatwierdzenie może prowadzić do utraty funduszy.", "donation_link_details": "Szczegóły linku darowizny", "e_sign_consent": "Zgoda na podpis elektroniczny", "edit": "Edytuj", @@ -298,6 +306,7 @@ "error_text_template": "Nazwa i adres szablonu nie mogą zawierać ` , ' \" symbolika\ni musi mieć od 1 do 106 znaków", "error_text_wallet_name": "Nazwa portfela może zawierać tylko litery, cyfry lub symbole _ - \ni musi mieć od 1 do 33 znaków", "error_text_xmr": "Wartość XMR nie może przekraczać dostępnego salda.\nLiczba cyfr dziesiętnych musi być mniejsza lub równa 12", + "error_while_processing": "Wystąpił błąd podczas przeróbki", "errorGettingCredentials": "Niepowodzenie: Błąd podczas uzyskiwania poświadczeń", "errorSigningTransaction": "Wystąpił błąd podczas podpisywania transakcji", "estimated": "Oszacowano", @@ -323,6 +332,7 @@ "export_backup": "Eksportuj kopię zapasową", "export_logs": "Eksportuj dzienniki", "export_outputs": "Eksportuj wyjscia", + "extend_session": "Rozszerzyć sesję", "extra_id": "Dodatkowy ID:", "extracted_address_content": "Wysyłasz środki na\n${recipient_name}", "failed_authentication": "Nieudane uwierzytelnienie. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "Zapomniałem hasła", "freeze": "Zamróź", "frequently_asked_questions": "Często zadawane pytania", + "from": "Z", "frozen": "Zamrożone", "frozen_balance": "Mrożona równowaga", "full_balance": "Pełne saldo", "gas_exceeds_allowance": "Gaz wymagany przez transakcję przekracza dodatek.", + "gas_price": "Cena gazu", "generate_name": "Wygeneruj nazwę", "generating_gift_card": "Generowanie karty podarunkowej", "generating_transaction": "Generowanie transakcji", @@ -440,6 +452,8 @@ "memo": "Notatka:", "message": "Wiadomość", "message_verified": "Wiadomość została pomyślnie zweryfikowana", + "messages": "Wiadomości", + "method": "Metoda", "methods": "Metody", "min_amount": "Min: ${value}", "min_value": "Min: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "Ciemny motyw Monero", "monero_light_theme": "Lekki motyw Monero", "moonpay_alert_text": "Wartość kwoty musi być większa lub równa ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "Wystąpił błąd podczas pobierania NFT. Uprzejmie sprawdź swoje połączenie internetowe i spróbuj ponownie.", "more_options": "Więcej opcji", "multiple_addresses_detected": "Wykryto wiele adresów", "mweb_confirmed": "Potwierdzone MWEB", @@ -589,6 +604,7 @@ "rep_warning_sub": "Twój przedstawiciel nie wydaje się mieć dobrej opinii. Stuknij tutaj, aby wybrać nowy", "repeat_wallet_password": "Powtórz hasło portfela", "repeated_password_is_incorrect": "Powtarzane hasło jest nieprawidłowe. Powtórz ponownie hasło portfela.", + "requested": "Wymagany", "require_for_adding_contacts": "Wymagane do dodania kontaktów", "require_for_all_security_and_backup_settings": "Wymagaj dla wszystkich ustawień zabezpieczeń i kopii zapasowych", "require_for_assessing_wallet": "Wymagaj dostępu do portfela", @@ -650,6 +666,8 @@ "second_intro_content": "Twój Yat to jeden unikalny adres emoji, który zastępuje wszystkie Twoje długie adresy szesnastkowe dla wszystkich Twoich walut.", "second_intro_title": "Jeden adres emoji, aby zarzadzać wszystkimi walutami", "security_and_backup": "Bezpieczeństwo i kopia zapasowa", + "security_risk": "Ryzyko bezpieczeństwa", + "security_risk_description": "Ta domena jest oznaczona jako niebezpieczna przez wielu dostawców bezpieczeństwa. Odejdź natychmiast, aby chronić swoje aktywa.", "seed_alert_back": "Wróć", "seed_alert_content": "Fraza Seed to jedyny sposób na odzyskanie portfela. Zapisałeś ją?", "seed_alert_title": "Uwaga", @@ -764,7 +782,9 @@ "show_keys": "Pokaż seed/klucze", "show_market_place": "Pokaż rynek", "show_seed": "Pokaż frazę seed", + "sign_all": "Podpisz wszystkie", "sign_message": "Podpisuj wiadomość", + "sign_one": "Podpisz jeden", "sign_up": "Zarejestruj się", "sign_verify_message": "Podpisz lub zweryfikuj wiadomość", "sign_verify_message_sub": "Podpisz lub zweryfikuj wiadomość za pomocą klucza prywatnego", @@ -798,6 +818,7 @@ "subaddress_title": "Lista podadresów", "subaddresses": "Podadresy", "submit_request": "Złóż wniosek", + "success": "Sukces", "successful": "Udany", "support_description_guides": "Dokumentacja i wsparcie dla typowych problemów", "support_description_live_chat": "Darmowe i szybkie! Do pomocy są dostępni przeszkoleni przedstawiciele wsparcia", @@ -805,6 +826,7 @@ "support_title_guides": "Dokumenty Cake Wallet", "support_title_live_chat": "Wsparcie na żywo", "support_title_other_links": "Inne linki wsparcia", + "supported": "Utrzymany", "swap": "Wymiana", "sweeping_wallet": "Zamiatanie portfela", "sweeping_wallet_alert": "To nie powinno zająć dużo czasu. NIE WYCHODŹ Z TEGO EKRANU, W PRZECIWNYM WYPADKU MOŻE ZOSTAĆ UTRACONA ŚRODKI", @@ -837,6 +859,7 @@ "thorchain_taproot_address_not_supported": "Dostawca Thorchain nie obsługuje adresów TAPROOT. Zmień adres lub wybierz innego dostawcę.", "time": "${minutes}m ${seconds}s", "tip": "tip:", + "to": "Do", "today": "Dzisiaj", "token_contract_address": "Adres kontraktu tokena", "token_decimal": "Token dziesiętny", @@ -904,6 +927,7 @@ "transaction_sent_notice": "Jeśli ekran nie zmieni się po 1 minucie, sprawdź eksplorator bloków i swój e-mail.", "transactions": "Transakcje", "transactions_by_date": "Transakcje według daty", + "transport_type": "Typ transportu", "trongrid_history": "Historia Trongrida", "trusted": "Zaufany", "tx_commit_exception_no_dust_on_change": "Transakcja została odrzucana z tą kwotą. Za pomocą tych monet możesz wysłać ${min} bez reszty lub ${max}, które zwrócą resztę.", @@ -932,6 +956,7 @@ "unspent_coins_details_title": "Szczegóły niewydanych monet", "unspent_coins_title": "Niewydane monety", "unsupported_asset": "Nie obsługujemy tego działania w przypadku tego zasobu. Utwórz lub przełącz się na portfel obsługiwanego typu aktywów.", + "update_session": "Aktualizacja sesji", "uptime": "Czas online", "upto": "do ${value}", "usb": "USB", @@ -941,6 +966,7 @@ "use_ssl": "Użyj SSL", "use_suggested": "Użyj sugerowane", "use_testnet": "Użyj testne", + "user_rejected_method": "Metoda odrzucona użytkownika", "value": "Wartość", "value_type": "Typ wartości", "variable_pair_not_supported": "Ta para zmiennych nie jest obsługiwana na wybranych giełdach", @@ -1024,5 +1050,6 @@ "you_will_get": "Konwertuj na", "you_will_receive_estimated_amount": "Otrzymasz(oszacowane )", "you_will_send": "Konwertuj z", + "youCanGoBackToYourDapp": "Możesz teraz wrócić do swojego dapp", "yy": "RR" } \ No newline at end of file diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 390ab40b9..d9e28005a 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -53,6 +53,7 @@ "anonpay_description": "Gere ${type}. O destinatário pode ${method} com qualquer criptomoeda suportada e você receberá fundos nesta carteira.", "apk_update": "Atualização de APK", "approve": "Aprovar", + "approve_request": "Aprovar solicitação", "arrive_in_this_address": "${currency} ${tag}chegará neste endereço", "ascending": "Ascendente", "ask_each_time": "Pergunte cada vez", @@ -116,9 +117,12 @@ "camera_consent": "Sua câmera será usada para capturar uma imagem para fins de identificação por ${provider}. Por favor, verifique a Política de Privacidade para obter detalhes.", "camera_permission_is_required": "É necessária permissão da câmera.\nAtive-o nas configurações do aplicativo.", "cancel": "Cancelar", + "cannot_verify": "Não pode verificar", + "cannot_verify_description": "Este domínio não pode ser verificado. Verifique a solicitação com cuidado antes de aprovar.", "card_address": "Endereço:", "cardholder_agreement": "Acordo do titular do cartão", "cards": "Cartões", + "chain_id": "ID da cadeia", "chains": "Correntes", "change": "Mudar", "change_backup_password_alert": "Seus arquivos de backup anteriores não estarão disponíveis para importação com a nova senha de backup. A nova senha de backup será usada apenas para novos arquivos de backup. Tem certeza que deseja alterar a senha de backup?", @@ -174,6 +178,7 @@ "connect_yats": "Connect Yats", "connect_your_hardware_wallet": "Conecte sua carteira de hardware usando Bluetooth ou USB", "connect_your_hardware_wallet_ios": "Conecte sua carteira de hardware usando o Bluetooth", + "connected": "Conectado", "connection_sync": "Conexão e sincronização", "connectWalletPrompt": "Conecte sua carteira ao WalletConnect para fazer transações", "contact": "Contato", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "Desative a otimização da bateria", "disableBatteryOptimizationDescription": "Deseja desativar a otimização da bateria para fazer a sincronização de fundo funcionar de forma mais livre e suave?", "disabled": "Desabilitado", + "disconnect_session": "Desconecte a sessão", "discount": "Economize ${value}%", "display_settings": "Configurações de exibição", "displayable": "Exibível", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "Não os compartilhe com mais ninguém, incluindo suporte.\n\nSeus fundos podem e serão roubados!", "do_not_show_me": "não me mostre isso novamente", "domain_looks_up": "Pesquisas de domínio", + "domain_mismatch": "Incompatibilidade de domínio", + "domain_mismatch_description": "Este site possui um domínio que não corresponde ao remetente desta solicitação. A aprovação pode levar à perda de fundos.", "donation_link_details": "Detalhes do link de doação", "e_sign_consent": "Consentimento de assinatura eletrônica", "edit": "Editar", @@ -298,6 +306,7 @@ "error_text_template": "O nome e o endereço do modelo não podem conter os símbolos ` , ' \" \ne deve ter entre 1 e 106 caracteres", "error_text_wallet_name": "O nome da carteira só pode conter letras, números, _ - símbolos\ne deve ter entre 1 e 33 caracteres", "error_text_xmr": "A quantia em XMR não pode exceder o saldo disponível.\nTO número de dígitos decimais deve ser menor ou igual a 12", + "error_while_processing": "Ocorreu um erro ao procurar", "errorGettingCredentials": "Falha: Erro ao obter credenciais", "errorSigningTransaction": "Ocorreu um erro ao assinar a transação", "estimated": "Estimado", @@ -323,6 +332,7 @@ "export_backup": "Backup de exportação", "export_logs": "Exportar logs", "export_outputs": "Saídas de exportação", + "extend_session": "Estender sessão", "extra_id": "ID extra:", "extracted_address_content": "Você enviará fundos para\n${recipient_name}", "failed_authentication": "Falha na autenticação. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "Esqueci a senha", "freeze": "Congelar", "frequently_asked_questions": "Perguntas frequentes", + "from": "De", "frozen": "Congeladas", "frozen_balance": "Equilíbrio congelado", "full_balance": "Saldo total", "gas_exceeds_allowance": "O gás exigido pela transação excede o subsídio.", + "gas_price": "Preço do gás", "generate_name": "Gerar nome", "generating_gift_card": "Gerando Cartão Presente", "generating_transaction": "Gerando transação", @@ -441,6 +453,8 @@ "memo": "Memorando:", "message": "Mensagem", "message_verified": "A mensagem foi verificada com sucesso", + "messages": "Mensagens", + "method": "Método", "methods": "Métodos", "min_amount": "Mínimo: ${valor}", "min_value": "Mín: ${value} ${currency}", @@ -453,6 +467,7 @@ "monero_dark_theme": "Monero Tema Escuro", "monero_light_theme": "Monero Light Theme", "moonpay_alert_text": "O valor do montante deve ser maior ou igual a ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "Ocorreu um erro ao buscar NFTs. Por favor, verifique sua conexão com a Internet e tente novamente.", "more_options": "Mais opções", "multiple_addresses_detected": "Vários endereços detectados", "mweb_confirmed": "MWEB confirmado", @@ -591,6 +606,7 @@ "rep_warning_sub": "Seu representante não parece estar em boa posição. Toque aqui para selecionar um novo", "repeat_wallet_password": "Repita a senha da carteira", "repeated_password_is_incorrect": "A senha repetida está incorreta. Repita a senha da carteira novamente.", + "requested": "Solicitado", "require_for_adding_contacts": "Requer para adicionar contatos", "require_for_all_security_and_backup_settings": "Exigir todas as configurações de segurança e backup", "require_for_assessing_wallet": "Requer para acessar a carteira", @@ -652,6 +668,8 @@ "second_intro_content": "Seu Yat é um endereço de emoji único e exclusivo que substitui todos os seus endereços hexadecimais longos para todas as suas moedas.", "second_intro_title": "Um endereço de emoji para governar todos eles", "security_and_backup": "Segurança e backup", + "security_risk": "Risco de segurança", + "security_risk_description": "Esse domínio é sinalizado como inseguro por vários provedores de segurança. Saia imediatamente para proteger seus ativos.", "seed_alert_back": "Volte", "seed_alert_content": "A semente é a única forma de recuperar sua carteira. Você escreveu isso?", "seed_alert_title": "Atenção", @@ -766,7 +784,9 @@ "show_keys": "Mostrar semente/chaves", "show_market_place": "Mostrar mercado", "show_seed": "Mostrar semente", + "sign_all": "Assine tudo", "sign_message": "Mensagem de assinar", + "sign_one": "Assine um", "sign_up": "Inscrever-se", "sign_verify_message": "Assinar ou verificar mensagem", "sign_verify_message_sub": "Assine ou verifique uma mensagem usando sua chave privada", @@ -800,6 +820,7 @@ "subaddress_title": "Sub-endereços", "subaddresses": "Sub-endereços", "submit_request": "enviar um pedido", + "success": "Sucesso", "successful": "Bem-sucedido", "support_description_guides": "Documentação e suporte para problemas comuns", "support_description_live_chat": "Livre e rápido! Representantes de suporte treinado estão disponíveis para ajudar", @@ -807,6 +828,7 @@ "support_title_guides": "Documentos da carteira de bolo", "support_title_live_chat": "Apoio ao vivo", "support_title_other_links": "Outros links de suporte", + "supported": "Suportado", "swap": "Trocar", "sweeping_wallet": "Carteira varrendo", "sweeping_wallet_alert": "To nie powinno zająć dużo czasu. NIE WYCHODŹ Z TEGO EKRANU, W PRZECIWNYM WYPADKU MOŻE ZOSTAĆ UTRACONA ŚRODKI", @@ -839,6 +861,7 @@ "thorchain_taproot_address_not_supported": "O provedor de Thorchain não suporta endereços de raiz de Tap. Altere o endereço ou selecione um provedor diferente.", "time": "${minutes}m ${seconds}s", "tip": "Dica:", + "to": "Para", "today": "Hoje", "token_contract_address": "Endereço do contrato de token", "token_decimal": "Token decimal", @@ -906,6 +929,7 @@ "transaction_sent_notice": "Se a tela não prosseguir após 1 minuto, verifique um explorador de blocos e seu e-mail.", "transactions": "Transações", "transactions_by_date": "Transações por data", + "transport_type": "Tipo de transporte", "trongrid_history": "História de Trongrid", "trusted": "confiável", "tx_commit_exception_no_dust_on_change": "A transação é rejeitada com esse valor. Com essas moedas, você pode enviar ${min} sem alteração ou ${max} que retorna alterações.", @@ -934,6 +958,7 @@ "unspent_coins_details_title": "Detalhes de moedas não gastas", "unspent_coins_title": "Moedas não gastas", "unsupported_asset": "Não oferecemos suporte a esta ação para este recurso. Crie ou mude para uma carteira de um tipo de ativo compatível.", + "update_session": "Sessão de atualização", "uptime": "Tempo de atividade", "upto": "até ${value}", "usb": "USB", @@ -943,6 +968,7 @@ "use_ssl": "Use SSL", "use_suggested": "Uso sugerido", "use_testnet": "Use testNet", + "user_rejected_method": "Método rejeitado pelo usuário", "value": "Valor", "value_type": "Tipo de valor", "variable_pair_not_supported": "Este par de variáveis não é compatível com as trocas selecionadas", @@ -1027,5 +1053,6 @@ "you_will_get": "Converter para", "you_will_receive_estimated_amount": "Você receberá(estimado )", "you_will_send": "Converter de", + "youCanGoBackToYourDapp": "Você pode voltar para o seu dapp agora", "yy": "aa" } \ No newline at end of file diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 848c7dd57..34c1968d1 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -53,6 +53,7 @@ "anonpay_description": "Создайте ${type}. Получатель может использовать ${method} с любой поддерживаемой криптовалютой, и вы получите средства на этот кошелек.", "apk_update": "Обновление APK", "approve": "Утвердить", + "approve_request": "Утвердить запрос", "arrive_in_this_address": "${currency} ${tag}придет на этот адрес", "ascending": "Восходящий", "ask_each_time": "Спросите каждый раз", @@ -116,9 +117,12 @@ "camera_consent": "Ваша камера будет использоваться для захвата изображения в целях идентификации ${provider}. Пожалуйста, ознакомьтесь с их Политикой конфиденциальности для получения подробной информации.", "camera_permission_is_required": "Требуется разрешение камеры.\nПожалуйста, включите его в настройках приложения.", "cancel": "Отменить", + "cannot_verify": "Не может проверить", + "cannot_verify_description": "Этот домен не может быть проверен. Тщательно проверьте запрос, прежде чем утверждать.", "card_address": "Адрес:", "cardholder_agreement": "Соглашение с держателем карты", "cards": "Карты", + "chain_id": "Цепочка", "chains": "Цепи", "change": "Изменить", "change_backup_password_alert": "Ваши предыдущие файлы резервных копий будут недоступны для импорта с новым паролем резервной копии. Новый пароль резервной копии будет использоваться только для новых файлов резервных копий. Вы уверены, что хотите изменить пароль резервной копии?", @@ -174,6 +178,7 @@ "connect_yats": "Подключить Yats", "connect_your_hardware_wallet": "Подключите свой аппаратный кошелек с помощью Bluetooth или USB", "connect_your_hardware_wallet_ios": "Подключите свой аппаратный кошелек с помощью Bluetooth", + "connected": "Подключенный", "connection_sync": "Подключение и синхронизация", "connectWalletPrompt": "Подключите свой кошелек к WalletConnect для совершения транзакций.", "contact": "Контакт", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "Отключить оптимизацию батареи", "disableBatteryOptimizationDescription": "Вы хотите отключить оптимизацию батареи, чтобы сделать фона синхронизации более свободно и плавно?", "disabled": "Отключено", + "disconnect_session": "Отключить сеанс", "discount": "Сэкономьте ${value}%", "display_settings": "Настройки отображения", "displayable": "Отображаемый", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "Не сообщайте их никому, включая техподдержку.\n\nВаши средства могут и будут украдены!", "do_not_show_me": "Не показывай мне это больше", "domain_looks_up": "Поиск доменов", + "domain_mismatch": "Несоответствие домена", + "domain_mismatch_description": "Этот веб -сайт имеет домен, который не соответствует отправителю этого запроса. Утверждение может привести к потере средств.", "donation_link_details": "Информация о ссылке для пожертвований", "e_sign_consent": "Согласие электронной подписи", "edit": "Редактировать", @@ -298,6 +306,7 @@ "error_text_template": "Имя и адрес шаблона не может содержать ` , ' \" символы\nи должно быть от 1 до 106 символов в длину", "error_text_wallet_name": "Имя кошелька может содержать только буквы, цифры, _ - символы\nи должно быть от 1 до 33 символов в длину", "error_text_xmr": "Значение XMR не может превышать доступный баланс.\nКоличество цифр после запятой должно быть меньше или равно 12", + "error_while_processing": "Произошла ошибка во время выплаты", "errorGettingCredentials": "Не удалось: ошибка при получении учетных данных.", "errorSigningTransaction": "Произошла ошибка при подписании транзакции", "estimated": "Примерно", @@ -323,6 +332,7 @@ "export_backup": "Экспорт резервной копии", "export_logs": "Экспортные журналы", "export_outputs": "Экспортные выходы", + "extend_session": "Продлить сессию", "extra_id": "Дополнительный ID:", "extracted_address_content": "Вы будете отправлять средства\n${recipient_name}", "failed_authentication": "Ошибка аутентификации. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "Забыли пароль", "freeze": "Заморозить", "frequently_asked_questions": "Часто задаваемые вопросы", + "from": "От", "frozen": "Заморожено", "frozen_balance": "Замороженный баланс", "full_balance": "Весь баланс", "gas_exceeds_allowance": "Газ, требуемый в результате транзакции, превышает пособие.", + "gas_price": "Цена газа", "generate_name": "Создать имя", "generating_gift_card": "Создание подарочной карты", "generating_transaction": "Генерирующая транзакция", @@ -440,6 +452,8 @@ "memo": "Памятка:", "message": "Сообщение", "message_verified": "Сообщение было успешно проверено", + "messages": "Сообщения", + "method": "Метод", "methods": "Методы", "min_amount": "Минимум: ${value}", "min_value": "Мин: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "Темная тема Monero", "monero_light_theme": "Светлая тема Monero", "moonpay_alert_text": "Сумма должна быть больше или равна ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "Произошла ошибка при получении NFT. Пожалуйста, проверьте подключение к Интернету и попробуйте еще раз.", "more_options": "Дополнительные параметры", "multiple_addresses_detected": "Обнаружено несколько адресов", "mweb_confirmed": "Подтверждено MWEB", @@ -590,6 +605,7 @@ "rep_warning_sub": "Ваш представитель, похоже, не в хорошей репутации. Нажмите здесь, чтобы выбрать новый", "repeat_wallet_password": "Повторите пароль кошелька", "repeated_password_is_incorrect": "Повторный пароль неверен. Пожалуйста, повторите пароль кошелька снова.", + "requested": "Запрошен", "require_for_adding_contacts": "Требовать добавления контактов", "require_for_all_security_and_backup_settings": "Требовать все настройки безопасности и резервного копирования", "require_for_assessing_wallet": "Требовать для доступа к кошельку", @@ -651,6 +667,8 @@ "second_intro_content": "Ваш Yat - это единственный уникальный адрес эмодзи, который заменяет длинные шестнадцатеричные адреса для всех ваших валют.", "second_intro_title": "Один адрес эмодзи для управления остальными адресами", "security_and_backup": "Безопасность и резервное копирование", + "security_risk": "Риск безопасности", + "security_risk_description": "Этот домен помечен как небезопасен несколькими поставщиками безопасности. Оставьте немедленно, чтобы защитить ваши активы.", "seed_alert_back": "Назад", "seed_alert_content": "Мнемоническая фраза - единственный способ восстановить ваш кошелек. Вы записали ее?", "seed_alert_title": "Внимание", @@ -765,7 +783,9 @@ "show_keys": "Показать мнемоническую фразу/ключи", "show_market_place": "Показать торговую площадку", "show_seed": "Показать мнемоническую фразу", + "sign_all": "Подписать все", "sign_message": "Сообщение о знаке", + "sign_one": "Подписать один", "sign_up": "Зарегистрироваться", "sign_verify_message": "Подписать или проверить сообщение", "sign_verify_message_sub": "Подписать или проверить сообщение, используя свой закрытый ключ", @@ -799,6 +819,7 @@ "subaddress_title": "Список субадресов", "subaddresses": "Субадреса", "submit_request": "отправить запрос", + "success": "Успех", "successful": "Успешный", "support_description_guides": "Документация и поддержка общих вопросов", "support_description_live_chat": "Бесплатно и быстро! Обученные представители поддержки доступны для оказания помощи", @@ -806,6 +827,7 @@ "support_title_guides": "Корт кошелек документов", "support_title_live_chat": "Живая поддержка", "support_title_other_links": "Другие ссылки на поддержку", + "supported": "Поддерживается", "swap": "Менять", "sweeping_wallet": "Подметание кошелька", "sweeping_wallet_alert": "Это не должно занять много времени. НЕ ПОКИДАЙТЕ ЭТОТ ЭКРАН, ИНАЧЕ ВЫЧИСЛЕННЫЕ СРЕДСТВА МОГУТ БЫТЬ ПОТЕРЯНЫ", @@ -838,6 +860,7 @@ "thorchain_taproot_address_not_supported": "Поставщик Thorchain не поддерживает адреса taproot. Пожалуйста, измените адрес или выберите другого поставщика.", "time": "${minutes}мин ${seconds}сек", "tip": "Совет:", + "to": "К", "today": "Сегодня", "token_contract_address": "Адрес контракта токена", "token_decimal": "Десятичный токен", @@ -905,6 +928,7 @@ "transaction_sent_notice": "Если экран не отображается через 1 минуту, проверьте обозреватель блоков и свою электронную почту.", "transactions": "Транзакции", "transactions_by_date": "Сортировать по дате", + "transport_type": "Транспортный тип", "trongrid_history": "История Тронгрида", "trusted": "доверенный", "tx_commit_exception_no_dust_on_change": "Транзакция отклоняется с этой суммой. С этими монетами вы можете отправлять ${min} без изменения или ${max}, которые возвращают изменение.", @@ -933,6 +957,7 @@ "unspent_coins_details_title": "Сведения о неизрасходованных монетах", "unspent_coins_title": "Неизрасходованные монеты", "unsupported_asset": "Мы не поддерживаем это действие для этого объекта. Пожалуйста, создайте или переключитесь на кошелек поддерживаемого типа активов.", + "update_session": "Обновление сессии", "uptime": "Время безотказной работы", "upto": "до ${value}", "usb": "USB", @@ -942,6 +967,7 @@ "use_ssl": "Использовать SSL", "use_suggested": "Использовать предложенный", "use_testnet": "Используйте Testnet", + "user_rejected_method": "Пользователь отклонил метод", "value": "Ценить", "value_type": "Тип значения", "variable_pair_not_supported": "Эта пара переменных не поддерживается выбранными биржами.", @@ -1025,5 +1051,6 @@ "you_will_get": "Конвертировать в", "you_will_receive_estimated_amount": "Вы получите(Оценку )", "you_will_send": "Конвертировать из", + "youCanGoBackToYourDapp": "Вы можете вернуться к своему даппу сейчас", "yy": "ГГ" } \ No newline at end of file diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index e9063cb2a..d9bb16118 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -53,6 +53,7 @@ "anonpay_description": "สร้าง ${type} ผู้รับสามารถ ${method} ด้วยสกุลเงินดิจิทัลที่รองรับ และคุณจะได้รับเงินในกระเป๋าสตางค์นี้", "apk_update": "ปรับปรุง APK", "approve": "อนุมัติ", + "approve_request": "อนุมัติคำขอ", "arrive_in_this_address": "${currency} ${tag}จะมาถึงที่อยู่นี้", "ascending": "จากน้อยไปมาก", "ask_each_time": "ถามทุกครั้ง", @@ -116,9 +117,12 @@ "camera_consent": "กล้องของคุณจะถูกนำมาใช้เพื่อจับภาพเพื่อวัตถุประสงค์ในการระบุตัวตนภายใน ${provider} โปรดตรวจสอบนโยบายความเป็นส่วนตัวเพื่อดูรายละเอียด", "camera_permission_is_required": "ต้องได้รับอนุญาตจากกล้อง\nโปรดเปิดใช้งานจากการตั้งค่าแอป", "cancel": "ยกเลิก", + "cannot_verify": "ไม่สามารถตรวจสอบได้", + "cannot_verify_description": "โดเมนนี้ไม่สามารถตรวจสอบได้ ตรวจสอบคำขออย่างละเอียดก่อนอนุมัติ", "card_address": "ที่อยู่:", "cardholder_agreement": "ข้อตกลงผู้ใช้บัตร", "cards": "บัตร", + "chain_id": "รหัสโซ่", "chains": "ห่วงโซ่", "change": "เปลี่ยน", "change_backup_password_alert": "ไฟล์สำรองที่ผ่านมาจะไม่สามารถนำเข้าด้วยรหัสผ่านสำรองใหม่ได้ รหัสผ่านสำรองใหม่จะถูกใช้เฉพาะสำหรับไฟล์สำรองใหม่ คุณแน่ใจหรือว่าต้องการเปลี่ยนรหัสผ่านสำรอง?", @@ -174,6 +178,7 @@ "connect_yats": "เชื่อมต่อ Yats", "connect_your_hardware_wallet": "เชื่อมต่อกระเป๋าเงินฮาร์ดแวร์ของคุณโดยใช้บลูทู ธ หรือ USB", "connect_your_hardware_wallet_ios": "เชื่อมต่อกระเป๋าเงินฮาร์ดแวร์ของคุณโดยใช้บลูทู ธ", + "connected": "ซึ่งเชื่อมต่อกัน", "connection_sync": "การเชื่อมต่อและการซิงค์", "connectWalletPrompt": "เชื่อมต่อกระเป๋าเงินของคุณด้วย WalletConnect เพื่อทำธุรกรรม", "contact": "ผู้ติดต่อ", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "ปิดใช้งานการเพิ่มประสิทธิภาพแบตเตอรี่", "disableBatteryOptimizationDescription": "คุณต้องการปิดใช้งานการเพิ่มประสิทธิภาพแบตเตอรี่เพื่อให้การซิงค์พื้นหลังทำงานได้อย่างอิสระและราบรื่นมากขึ้นหรือไม่?", "disabled": "ปิดใช้งาน", + "disconnect_session": "ตัดการเชื่อมต่อ", "discount": "ประหยัด ${value}%", "display_settings": "การตั้งค่าการแสดงผล", "displayable": "สามารถแสดงได้", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "อย่าแชร์ข้อมูลนี้กับใครอื่น รวมถึงฝ่ายสนับสนุนด้วย\n\nการเงินของคุณอาจถูกขโมยโดยไม่หวังดี!", "do_not_show_me": "อย่าแสดงข้อความนี้อีก", "domain_looks_up": "การค้นหาโดเมน", + "domain_mismatch": "โดเมนไม่ตรงกัน", + "domain_mismatch_description": "เว็บไซต์นี้มีโดเมนที่ไม่ตรงกับผู้ส่งคำขอนี้ การอนุมัติอาจนำไปสู่การสูญเสียเงินทุน", "donation_link_details": "รายละเอียดลิงค์บริจาค", "e_sign_consent": "การยอมรับ E-Sign", "edit": "แก้ไข", @@ -298,6 +306,7 @@ "error_text_template": "ชื่อแม่แบบและที่อยู่ไม่สามารถมีสัญลักษณ์ ` , '\" และต้องมีความยาวระหว่าง 1 ถึง 106 ตัวอักษร", "error_text_wallet_name": "ชื่อกระเป๋าสตางค์จะต้องประกอบด้วยตัวอักษร ตัวเลข และสัญลักษณ์ _ - และมีความยาวระหว่าง 1 ถึง 33 ตัวอักษร", "error_text_xmr": "มูลค่า XMR ไม่สามารถเกินยอดคงเหลือได้\nจำนวนสตริงทศนิยมต้องน้อยกว่าหรือเท่ากับ 12", + "error_while_processing": "เกิดข้อผิดพลาดในขณะที่ proceessing", "errorGettingCredentials": "ล้มเหลว: เกิดข้อผิดพลาดขณะรับข้อมูลรับรอง", "errorSigningTransaction": "เกิดข้อผิดพลาดขณะลงนามธุรกรรม", "estimated": "ประมาณการ", @@ -323,6 +332,7 @@ "export_backup": "ส่งออกข้อมูลสำรอง", "export_logs": "บันทึกการส่งออก", "export_outputs": "เอาต์พุตส่งออก", + "extend_session": "ขยายเซสชัน", "extra_id": "ไอดีเพิ่มเติม:", "extracted_address_content": "คุณกำลังจะส่งเงินไปยัง\n${recipient_name}", "failed_authentication": "การยืนยันสิทธิ์ล้มเหลว ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "ลืมรหัสผ่าน", "freeze": "ดักจับ", "frequently_asked_questions": "คำถามที่พบบ่อย", + "from": "จาก", "frozen": "ถูกดักจับ", "frozen_balance": "สมดุลแช่แข็ง", "full_balance": "ยอดคงเหลือทั้งหมด", "gas_exceeds_allowance": "ก๊าซที่ต้องการโดยการทำธุรกรรมเกินค่าเผื่อ", + "gas_price": "ราคาก๊าซ", "generate_name": "สร้างชื่อ", "generating_gift_card": "กำลังสร้างบัตรของขวัญ", "generating_transaction": "การสร้างธุรกรรม", @@ -440,6 +452,8 @@ "memo": "หมายเหตุ:", "message": "ข้อความ", "message_verified": "ข้อความได้รับการตรวจสอบอย่างประสบความสำเร็จ", + "messages": "ข้อความ", + "method": "วิธี", "methods": "วิธีการ", "min_amount": "จำนวนขั้นต่ำ: ${value}", "min_value": "ขั้นต่ำ: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "ธีมมืด Monero", "monero_light_theme": "ธีมแสง Monero", "moonpay_alert_text": "มูลค่าของจำนวนต้องมากกว่าหรือเท่ากับ ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "เกิดข้อผิดพลาดในขณะที่ดึง NFTs กรุณาตรวจสอบการเชื่อมต่ออินเทอร์เน็ตของคุณและลองอีกครั้ง", "more_options": "ตัวเลือกเพิ่มเติม", "multiple_addresses_detected": "ตรวจพบหลายที่อยู่", "mweb_confirmed": "MWEB ยืนยันแล้ว", @@ -589,6 +604,7 @@ "rep_warning_sub": "ตัวแทนของคุณดูเหมือนจะไม่อยู่ในสถานะที่ดี แตะที่นี่เพื่อเลือกอันใหม่", "repeat_wallet_password": "ทำซ้ำรหัสผ่านกระเป๋าเงิน", "repeated_password_is_incorrect": "รหัสผ่านซ้ำไม่ถูกต้อง โปรดทำซ้ำรหัสผ่านกระเป๋าเงินอีกครั้ง", + "requested": "ได้รับการร้องขอ", "require_for_adding_contacts": "ต้องการสำหรับการเพิ่มผู้ติดต่อ", "require_for_all_security_and_backup_settings": "จำเป็นสำหรับการตั้งค่าความปลอดภัยและการสำรองข้อมูลทั้งหมด", "require_for_assessing_wallet": "จำเป็นสำหรับการเข้าถึงกระเป๋าเงิน", @@ -650,6 +666,8 @@ "second_intro_content": "Yat ของคุณเป็นอิโมจิที่อยู่เดียวที่จะแทนที่ทุกที่อยู่และเลขฐานสิบหกของคุณสำหรับเหรียญคริปโตทุกชนิด", "second_intro_title": "อิโมจิที่อยู่เดียวที่จะควบคุมพวกเขาทั้งหมด", "security_and_backup": "ความปลอดภัยและการสำรองข้อมูล", + "security_risk": "ความเสี่ยงด้านความปลอดภัย", + "security_risk_description": "โดเมนนี้ถูกตั้งค่าสถานะว่าไม่ปลอดภัยโดยผู้ให้บริการความปลอดภัยหลายราย ออกทันทีเพื่อปกป้องทรัพย์สินของคุณ", "seed_alert_back": "กลับ", "seed_alert_content": "Seed เป็นเพียงวิธีเดียวที่จะกู้กระเป๋าของคุณ คุณได้เขียนมันขึ้นลงกระดาษหรือไม่?", "seed_alert_title": "ความสนใจ", @@ -764,7 +782,9 @@ "show_keys": "แสดงซีด/คีย์", "show_market_place": "แสดงตลาดกลาง", "show_seed": "แสดงซีด", + "sign_all": "ลงนามทั้งหมด", "sign_message": "ลงนามข้อความ", + "sign_one": "ลงชื่อเข้าใช้", "sign_up": "สมัครสมาชิก", "sign_verify_message": "ลงชื่อเข้าใช้หรือตรวจสอบข้อความ", "sign_verify_message_sub": "ลงชื่อเข้าใช้หรือตรวจสอบข้อความโดยใช้คีย์ส่วนตัวของคุณ", @@ -798,6 +818,7 @@ "subaddress_title": "รายการที่อยู่ย่อย", "subaddresses": "ที่อยู่ย่อย", "submit_request": "ส่งคำขอ", + "success": "ความสำเร็จ", "successful": "ประสบความสำเร็จ", "support_description_guides": "เอกสารและการสนับสนุนสำหรับปัญหาทั่วไป", "support_description_live_chat": "ฟรีและรวดเร็ว! ตัวแทนฝ่ายสนับสนุนที่ผ่านการฝึกอบรมพร้อมให้ความช่วยเหลือ", @@ -805,6 +826,7 @@ "support_title_guides": "เอกสารกระเป๋าเงินเค้ก", "support_title_live_chat": "การสนับสนุนสด", "support_title_other_links": "ลิงค์สนับสนุนอื่น ๆ", + "supported": "ที่ได้รับการสนับสนุน", "swap": "แลกเปลี่ยน", "sweeping_wallet": "กวาดกระเป๋าสตางค์", "sweeping_wallet_alert": "การดำเนินการนี้ใช้เวลาไม่นาน อย่าออกจากหน้าจอนี้ มิฉะนั้นเงินที่กวาดไปอาจสูญหาย", @@ -837,6 +859,7 @@ "thorchain_taproot_address_not_supported": "ผู้ให้บริการ Thorchain ไม่รองรับที่อยู่ taproot โปรดเปลี่ยนที่อยู่หรือเลือกผู้ให้บริการอื่น", "time": "${minutes}m ${seconds}s", "tip": "เพิ่มค่าตอบแทน:", + "to": "ถึง", "today": "วันนี้", "token_contract_address": "ที่อยู่สัญญาโทเค็น", "token_decimal": "โทเค็นทศนิยม", @@ -904,6 +927,7 @@ "transaction_sent_notice": "ถ้าหน้าจอไม่ขึ้นหลังจาก 1 นาทีแล้ว ให้ตรวจสอบ block explorer และอีเมลของคุณ", "transactions": "ธุรกรรม", "transactions_by_date": "ธุรกรรมตามวันที่", + "transport_type": "ประเภทการขนส่ง", "trongrid_history": "ประวัติศาสตร์ Trongrid", "trusted": "มั่นคง", "tx_commit_exception_no_dust_on_change": "ธุรกรรมถูกปฏิเสธด้วยจำนวนเงินนี้ ด้วยเหรียญเหล่านี้คุณสามารถส่ง ${min} โดยไม่ต้องเปลี่ยนแปลงหรือ ${max} ที่ส่งคืนการเปลี่ยนแปลง", @@ -932,6 +956,7 @@ "unspent_coins_details_title": "รายละเอียดเหรียญที่ไม่ได้ใช้", "unspent_coins_title": "เหรียญที่ไม่ได้ใช้", "unsupported_asset": "เราไม่สนับสนุนการกระทำนี้สำหรับเนื้อหานี้ โปรดสร้างหรือเปลี่ยนเป็นกระเป๋าเงินประเภทสินทรัพย์ที่รองรับ", + "update_session": "อัปเดตเซสชัน", "uptime": "เวลาทำงาน", "upto": "สูงสุด ${value}", "usb": "ยูเอสบี", @@ -941,6 +966,7 @@ "use_ssl": "ใช้ SSL", "use_suggested": "ใช้ที่แนะนำ", "use_testnet": "ใช้ testnet", + "user_rejected_method": "วิธีการปฏิเสธของผู้ใช้", "value": "ค่า", "value_type": "ประเภทค่า", "variable_pair_not_supported": "คู่ความสัมพันธ์ที่เปลี่ยนแปลงได้นี้ไม่สนับสนุนกับหุ้นที่เลือก", @@ -1024,5 +1050,6 @@ "you_will_get": "แปลงเป็น", "you_will_receive_estimated_amount": "คุณจะได้รับ(โดยประมาณ )", "you_will_send": "แปลงจาก", + "youCanGoBackToYourDapp": "คุณสามารถกลับไปที่ dapp ของคุณได้ทันที", "yy": "ปี" } \ No newline at end of file diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index a390b218a..31bf58691 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -53,6 +53,7 @@ "anonpay_description": "Bumuo ng ${type}. Ang tatanggap ay maaaring ${method} na may anumang suportadong cryptocurrency, at makakatanggap ka ng mga pondo sa wallet na ito.", "apk_update": "APK update", "approve": "Aprubahan", + "approve_request": "Aprubahan ang kahilingan", "arrive_in_this_address": "Ang ${currency} ${tag} ay darating sa address na ito", "ascending": "Umakyat", "ask_each_time": "Magtanong sa tuwing", @@ -116,9 +117,12 @@ "camera_consent": "Gagamitin ang iyong camera upang kumuha ng larawan para sa mga layunin ng pagkakakilanlan sa pamamagitan ng ${provider}. Pakisuri ang kanilang Patakaran sa Privacy para sa mga detalye.", "camera_permission_is_required": "Kinakailangan ang pahintulot sa camera.\nMangyaring paganahin ito mula sa mga setting ng app.", "cancel": "Kanselahin", + "cannot_verify": "Hindi ma -verify", + "cannot_verify_description": "Ang domain na ito ay hindi ma -verify. Suriin nang mabuti ang kahilingan bago aprubahan.", "card_address": "Address:", "cardholder_agreement": "Kasunduan sa Cardholder", "cards": "Mga Card", + "chain_id": "Chain ID", "chains": "Mga Chain", "change": "Sukli", "change_backup_password_alert": "Ang iyong mga nakaraang backup na file ay hindi magagamit upang i-import gamit ang bagong backup na password. Ang bagong backup na password ay gagamitin lamang para sa mga bagong backup na file. Sigurado ka bang gusto mong baguhin ang backup na password?", @@ -174,6 +178,7 @@ "connect_yats": "Ikonekta sa Yats", "connect_your_hardware_wallet": "Ikonekta ang iyong hardware wallet gamit ang Bluetooth o USB", "connect_your_hardware_wallet_ios": "Ikonekta ang iyong wallet gamit ang Bluetooth", + "connected": "Konektado", "connection_sync": "Koneksyon at pag-sync", "connectWalletPrompt": "Ikonekta ang iyong wallet sa WalletConnect upang gumawa ng mga transaksyon", "contact": "Contact", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "Huwag Paganahin ang Pag-optimize ng Baterya", "disableBatteryOptimizationDescription": "Nais mo bang huwag paganahin ang pag-optimize ng baterya upang gawing mas malaya at maayos ang background sync?", "disabled": "Hindi pinagana", + "disconnect_session": "Idiskonekta ang sesyon", "discount": "Makatipid ng ${value}%", "display_settings": "Mga setting ng pagpapakita", "displayable": "Maipapakita", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "Huwag ibahagi ang mga ito sa sinuman kasama ang tagatustos.\n\nMaaaring manakaw ang iyong mga pondo!", "do_not_show_me": "Huwag mo itong ipakita muli", "domain_looks_up": "Mga paghahanap ng domain", + "domain_mismatch": "Domain mismatch", + "domain_mismatch_description": "Ang website na ito ay may isang domain na hindi tumutugma sa nagpadala ng kahilingan na ito. Ang pag -apruba ay maaaring humantong sa pagkawala ng mga pondo.", "donation_link_details": "Mga detalye ng link ng donasyon", "e_sign_consent": "E-Sign Consent", "edit": "I-edit", @@ -298,6 +306,7 @@ "error_text_template": "Ang pangalan ng template at address ay hindi maaaring maglaman ng mga simbolo ng ',' \"\nat dapat sa pagitan ng 1 at 106 na character ang haba", "error_text_wallet_name": "Ang pangalan ng wallet ay maaari lamang maglaman ng mga titik, numero, _ - mga simbolo\nat dapat sa pagitan ng 1 at 33 character ang haba", "error_text_xmr": "Ang halaga ng XMR ay hindi maaaring lumampas sa magagamit na balanse.\nAng bilang ng mga numero ng fraction ay dapat na mas mababa o katumbas ng 12", + "error_while_processing": "May naganap na error habang nagpapatuloy", "errorGettingCredentials": "Nabigo: Error habang kumukuha ng mga kredensyal", "errorSigningTransaction": "Error habang pinipirmahan ang transaksyon", "estimated": "Tinatayang", @@ -323,6 +332,7 @@ "export_backup": "I-export ang backup", "export_logs": "Mga log ng pag -export", "export_outputs": "Mga output ng pag -export", + "extend_session": "Palawakin ang session", "extra_id": "Dagdag na ID:", "extracted_address_content": "Magpapadala ka ng pondo sa\n${recipient_name}", "failed_authentication": "Nabigo ang pagpapatunay. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "Nakalimutan ang Password", "freeze": "I-freeze", "frequently_asked_questions": "Mga madalas itanong", + "from": "Mula sa", "frozen": "Frozen", "frozen_balance": "Frozen na balanse", "full_balance": "Buong Balanse", "gas_exceeds_allowance": "Ang gas na kinakailangan ng transaksyon ay lumampas sa allowance.", + "gas_price": "Presyo ng Gas", "generate_name": "Bumuo ng pangalan", "generating_gift_card": "Bumubuo ng Gift Card", "generating_transaction": "Bumubuo ng transaksyon", @@ -440,6 +452,8 @@ "memo": "Memo:", "message": "Mensahe", "message_verified": "Ang mensahe ay matagumpay na na -verify", + "messages": "Mga mensahe", + "method": "Paraan", "methods": "Mga Paraan", "min_amount": "Min: ${value}", "min_value": "Min: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "Monero Dark Theme", "monero_light_theme": "Monero Light Theme", "moonpay_alert_text": "Ang halaga ay dapat na higit pa o katumbas ng ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "May naganap na error habang kinukuha ang mga NFT. Mabuting suriin ang iyong koneksyon sa internet at subukang muli.", "more_options": "Higit pang mga Pagpipilian", "multiple_addresses_detected": "Maramihang mga address na napansin", "mweb_confirmed": "Nakumpirma na MWeb", @@ -589,6 +604,7 @@ "rep_warning_sub": "Ang iyong representative ay hindi lilitaw na nasa mabuting kalagayan. Tapikin dito upang pumili ng bago", "repeat_wallet_password": "Ulitin ang password ng wallet", "repeated_password_is_incorrect": "Ang paulit-ulit na password ay hindi tama. Mangyaring ulitin muli ang password ng wallet.", + "requested": "Hiniling", "require_for_adding_contacts": "Nangangailangan para sa pagdaragdag ng mga contact", "require_for_all_security_and_backup_settings": "Nangangailangan para sa lahat ng mga setting ng seguridad at backup", "require_for_assessing_wallet": "Nangangailangan para sa pag-access ng wallet", @@ -650,6 +666,8 @@ "second_intro_content": "Ang iyong Yat ay isang natatanging emoji address na pumapalit sa lahat ng iyong mahabang hexadecimal address para sa lahat ng iyong pera.", "second_intro_title": "Isang emoji address para pamunuan silang lahat", "security_and_backup": "Seguridad at backup", + "security_risk": "Panganib sa seguridad", + "security_risk_description": "Ang domain na ito ay na -flag bilang hindi ligtas ng maraming mga nagbibigay ng seguridad. Mag -iwan kaagad upang maprotektahan ang iyong mga pag -aari.", "seed_alert_back": "Bumalik", "seed_alert_content": "Ang seed ay ang tanging paraan upang mabawi ang iyong wallet. Naisulat mo na ba?", "seed_alert_title": "Attention", @@ -764,7 +782,9 @@ "show_keys": "Ipakita ang mga seed/key", "show_market_place": "Ipakita ang Marketplace", "show_seed": "Ipakita ang seed", + "sign_all": "Mag -sign lahat", "sign_message": "Mag -sign Message", + "sign_one": "Mag -sign ng isa", "sign_up": "Mag-sign Up", "sign_verify_message": "Mag -sign o i -verify ang mensahe", "sign_verify_message_sub": "Mag -sign o i -verify ang isang mensahe gamit ang iyong pribadong key", @@ -798,6 +818,7 @@ "subaddress_title": "Listahan ng Subaddress", "subaddresses": "Mga Subaddress", "submit_request": "magsumite ng isang kahilingan", + "success": "Tagumpay", "successful": "Matagumpay", "support_description_guides": "Dokumentasyon at suporta para sa mga karaniwang isyu", "support_description_live_chat": "Libre at mabilis! Ang mga bihasang kinatawan ng suporta ay magagamit upang tulungan", @@ -805,6 +826,7 @@ "support_title_guides": "Cake wallet doc", "support_title_live_chat": "Live na suporta", "support_title_other_links": "Iba pang mga link sa suporta", + "supported": "Suportado", "swap": "Palitan", "sweeping_wallet": "Sweeping wallet", "sweeping_wallet_alert": "Hindi ito dapat magtagal. HUWAG iwanan ang screen na ito o maaaring mawala ang mga pondo.", @@ -837,6 +859,7 @@ "thorchain_taproot_address_not_supported": "Ang provider ng THORChain ay hindi sumusuporta sa mga address ng Taproot. Mangyaring baguhin ang address o pumili ng ibang provider.", "time": "${minutes} m ${seconds} s", "tip": "Tip:", + "to": "Sa", "today": "Ngayon", "token_contract_address": "Address ng token contract", "token_decimal": "Token decimal", @@ -904,6 +927,7 @@ "transaction_sent_notice": "Kung hindi magpapatuloy ang screen pagkatapos ng 1 minuto, tingnan ang block explorer at ang iyong email.", "transactions": "Mga Transaksyon", "transactions_by_date": "Mga transaksyon ayon sa petsa", + "transport_type": "Uri ng transportasyon", "trongrid_history": "Kasaysayan ng TronGrid", "trusted": "Pinagkakatiwalaan", "tx_commit_exception_no_dust_on_change": "Ang transaksyon ay tinanggihan sa halagang ito. Sa mga barya na ito maaari kang magpadala ng ${min} nang walang sukli o ${max} na nagbabalik ng sukli.", @@ -932,6 +956,7 @@ "unspent_coins_details_title": "Mga detalye ng mga hindi nagastos na barya", "unspent_coins_title": "Mga hindi nagamit na barya", "unsupported_asset": "Hindi namin sinusuportahan ang pagkilos na ito para sa asset na ito. Mangyaring lumikha o lumipat sa isang wallet ng isang suportadong uri ng asset.", + "update_session": "I -update ang Session", "uptime": "Uptime", "upto": "hanggang sa ${value}", "usb": "USB", @@ -941,6 +966,7 @@ "use_ssl": "Gumamit ng SSL", "use_suggested": "Gumamit ng iminungkahing", "use_testnet": "Gumamit ng testnet", + "user_rejected_method": "Tinanggihan ng gumagamit ang pamamaraan", "value": "Halaga", "value_type": "Uri ng halaga", "variable_pair_not_supported": "Ang variable na pares na ito ay hindi suportado sa mga napiling exchange", @@ -1024,5 +1050,6 @@ "you_will_get": "I-convert sa", "you_will_receive_estimated_amount": "Makakatanggap ka ng(tinantyang)", "you_will_send": "I-convert mula sa", + "youCanGoBackToYourDapp": "Maaari kang bumalik sa iyong dapp ngayon", "yy": "YY" } \ No newline at end of file diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index c4bd16b24..b8503af93 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -53,6 +53,7 @@ "anonpay_description": "${type} oluşturun. Alıcı, desteklenen herhangi bir kripto para birimi ile ${method} yapabilir ve bu cüzdanda para alırsınız.", "apk_update": "APK güncellemesi", "approve": "Onaylamak", + "approve_request": "Talebi Onaylama", "arrive_in_this_address": "${currency} ${tag}bu adrese ulaşacak", "ascending": "Yükselen", "ask_each_time": "Her seferinde sor", @@ -116,9 +117,12 @@ "camera_consent": "Kameranız ${provider} tarihine kadar tanımlama amacıyla bir görüntü yakalamak için kullanılacaktır. Ayrıntılar için lütfen Gizlilik Politikalarını kontrol edin.", "camera_permission_is_required": "Kamera izni gereklidir.\nLütfen uygulama ayarlarından etkinleştirin.", "cancel": "İptal", + "cannot_verify": "Doğrulamaz", + "cannot_verify_description": "Bu alan doğrulanamaz. Onaylamadan önce isteği dikkatlice kontrol edin.", "card_address": "Adres:", "cardholder_agreement": "Kart Sahibi Sözleşmesi", "cards": "Kartlar", + "chain_id": "Zincir kimliği", "chains": "Zincirler", "change": "Değiştir", "change_backup_password_alert": "Önceki yedekleme dosyaların yeni yedek parolası ile içe aktarılamayacaktır. Yeni yedekleme parolası yalnızca yeni yedekleme dosyaları için kullanılabilir olacak. Yedekleme parolasını değiştirmek istediğinden emin misin?", @@ -174,6 +178,7 @@ "connect_yats": "Yat'lara bağlan", "connect_your_hardware_wallet": "Bluetooth veya USB kullanarak donanım cüzdanınızı bağlayın", "connect_your_hardware_wallet_ios": "Bluetooth kullanarak donanım cüzdanınızı bağlayın", + "connected": "Bağlı", "connection_sync": "Bağlantı ve senkronizasyon", "connectWalletPrompt": "İşlem yapmak için cüzdanınızı WalletConnect'e bağlayın", "contact": "Rehber", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "Pil optimizasyonunu devre dışı bırakın", "disableBatteryOptimizationDescription": "Arka plan senkronizasyonunu daha özgür ve sorunsuz bir şekilde çalıştırmak için pil optimizasyonunu devre dışı bırakmak istiyor musunuz?", "disabled": "Devre dışı", + "disconnect_session": "SONRA KONSİYORUM", "discount": "%${value} tasarruf et", "display_settings": "Görüntü ayarları", "displayable": "Gösterilebilir", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "Bunları destek de dahil olmak üzere başka kimseyle paylaşma.\n\nParan çalınabilir ve çalınacaktır!", "do_not_show_me": "Bana bunu bir daha gösterme", "domain_looks_up": "Etki alanı aramaları", + "domain_mismatch": "Alan uyuşmazlığı", + "domain_mismatch_description": "Bu web sitesinde, bu isteğin göndereniyle eşleşmeyen bir etki alanı vardır. Onaylama fon kaybına yol açabilir.", "donation_link_details": "Bağış bağlantısı ayrıntıları", "e_sign_consent": "E-İmza Onayı", "edit": "Düzenle", @@ -298,6 +306,7 @@ "error_text_template": "Şablon adı ve adresi ` , ' \" sembolleri içeremez\nve 1 ila 106 karakter uzunluğunda olmalı", "error_text_wallet_name": "Cüzdan ismi yalnızca harf, rakam \nve 1 ile 33 uzunluğunda karakter içermelidir.", "error_text_xmr": "XMR miktarı kullanılabilir bakiyeyi aşamaz.\nKesir basamaklarının sayısı 12'ye eşit veya daha az olmalıdır", + "error_while_processing": "ProceSting sırasında bir hata oluştu", "errorGettingCredentials": "Başarısız: Kimlik bilgileri alınırken hata oluştu", "errorSigningTransaction": "İşlem imzalanırken bir hata oluştu", "estimated": "Tahmini", @@ -323,6 +332,7 @@ "export_backup": "Yedeği dışa aktar", "export_logs": "Dışa aktarma günlükleri", "export_outputs": "İhracat çıktıları", + "extend_session": "Oturumu Genişlet", "extra_id": "Ekstra ID:", "extracted_address_content": "Parayı buraya gönderceksin:\n${recipient_name}", "failed_authentication": "Doğrulama başarısız oldu. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "Parolamı unuttum", "freeze": "Dondur", "frequently_asked_questions": "Sıkça sorulan sorular", + "from": "İtibaren", "frozen": "Dondurulmuş", "frozen_balance": "Dondurulmuş denge", "full_balance": "Tüm bakiye", "gas_exceeds_allowance": "İşlemin gerektirdiği gaz ödeneği aşar.", + "gas_price": "Gaz fiyatı", "generate_name": "İsim Oluştur", "generating_gift_card": "Hediye Kartı Oluşturuluyor", "generating_transaction": "İşlem Oluşturma", @@ -440,6 +452,8 @@ "memo": "Memo:", "message": "İleti", "message_verified": "Mesaj başarıyla doğrulandı", + "messages": "Mesaj", + "method": "Yöntem", "methods": "Yöntemler", "min_amount": "Min: ${value}", "min_value": "En az: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "Monero Koyu Tema", "monero_light_theme": "Monero Hafif Tema", "moonpay_alert_text": "Tutar ${minAmount} ${fiatCurrency} miktarına eşit veya daha fazla olmalıdır", + "moralis_nft_error": "NFT'ler getirilirken bir hata oluştu. Lütfen internet bağlantınızı kontrol edin ve tekrar deneyin.", "more_options": "Daha Fazla Seçenek", "multiple_addresses_detected": "Birden çok adres tespit edildi", "mweb_confirmed": "Onaylanmış mweb", @@ -589,6 +604,7 @@ "rep_warning_sub": "Temsilciniz iyi durumda görünmüyor. Yeni bir tane seçmek için buraya dokunun", "repeat_wallet_password": "Cüzdan şifresini tekrarlayın", "repeated_password_is_incorrect": "Tekrarlanan şifre yanlış. Lütfen cüzdan şifresini tekrarlayın.", + "requested": "İstenmiş", "require_for_adding_contacts": "Kişi eklemek için gerekli", "require_for_all_security_and_backup_settings": "Tüm güvenlik ve yedekleme ayarları için iste", "require_for_assessing_wallet": "Cüzdana erişmek için gerekli", @@ -650,6 +666,8 @@ "second_intro_content": "Yat'ınız, tüm para birimleriniz için uzun onaltılık adreslerinizin yerini alan benzersiz emoji adresidir.", "second_intro_title": "Hepsini yönetebilen tek bir emoji adresi", "security_and_backup": "Güvenlik ve yedekleme", + "security_risk": "Güvenlik riski", + "security_risk_description": "Bu alan, birden fazla güvenlik sağlayıcısı tarafından güvensiz olarak işaretlenir. Varlıklarınızı korumak için hemen ayrılın.", "seed_alert_back": "Geriye dön", "seed_alert_content": "Cüzdanını kurtarmanın tek yolu tohumdur. Tohumunu yazdın mı?", "seed_alert_title": "Dikkat", @@ -764,7 +782,9 @@ "show_keys": "Tohumları/anahtarları göster", "show_market_place": "Pazar Yerini Göster", "show_seed": "Tohumları göster", + "sign_all": "Hepsini imzalamak", "sign_message": "İşaret mesajı", + "sign_one": "Bir İmza", "sign_up": "Kaydol", "sign_verify_message": "Mesajı işaretleyin veya doğrulayın", "sign_verify_message_sub": "Özel anahtarınızı kullanarak bir mesajı imzalayın veya doğrulayın", @@ -798,6 +818,7 @@ "subaddress_title": "Alt adres listesi", "subaddresses": "Alt adresler", "submit_request": "talep gönder", + "success": "Başarı", "successful": "Başarılı", "support_description_guides": "Ortak sorunlara belge ve destek", "support_description_live_chat": "Ücretsiz ve hızlı! Eğitimli destek temsilcileri yardımcı olmak için mevcuttur", @@ -805,6 +826,7 @@ "support_title_guides": "Kek Cüzdan Dokümanlar", "support_title_live_chat": "Canlı destek", "support_title_other_links": "Diğer destek bağlantıları", + "supported": "Desteklenen", "swap": "Takas", "sweeping_wallet": "Süpürme cüzdanı", "sweeping_wallet_alert": "Bu uzun sürmemeli. BU EKRANDAN BIRAKMAYIN YOKSA SÜPÜRÜLEN FONLAR KAYBOLABİLİR", @@ -837,6 +859,7 @@ "thorchain_taproot_address_not_supported": "Thorchain sağlayıcısı Taproot adreslerini desteklemiyor. Lütfen adresi değiştirin veya farklı bir sağlayıcı seçin.", "time": "${minutes}d ${seconds}s", "tip": "Bahşiş:", + "to": "İle", "today": "Bugün", "token_contract_address": "Token sözleşme adresi", "token_decimal": "Belirteç ondalık", @@ -904,6 +927,7 @@ "transaction_sent_notice": "Ekran 1 dakika sonra ilerlemezse, blok gezgininden ve e-postanızdan kontrol edin.", "transactions": "İşlemler", "transactions_by_date": "Tarihe göre transferler", + "transport_type": "Taşıma tipi", "trongrid_history": "Trongrid tarihi", "trusted": "Güvenilir", "tx_commit_exception_no_dust_on_change": "İşlem bu miktarla reddedilir. Bu madeni paralarla değişiklik yapmadan ${min} veya değişikliği döndüren ${max} gönderebilirsiniz.", @@ -932,6 +956,7 @@ "unspent_coins_details_title": "Harcanmamış koin detayları", "unspent_coins_title": "Harcanmamış koinler", "unsupported_asset": "Bu öğe için bu eylemi desteklemiyoruz. Lütfen desteklenen bir varlık türünde bir cüzdan oluşturun veya cüzdana geçiş yapın.", + "update_session": "Güncelleme Oturumu", "uptime": "Çalışma süresi", "upto": "Şu miktara kadar: ${value}", "usb": "USB", @@ -941,6 +966,7 @@ "use_ssl": "SSL kullan", "use_suggested": "Önerileni Kullan", "use_testnet": "TestNet kullanın", + "user_rejected_method": "Kullanıcı reddedilen yöntemi", "value": "Değer", "value_type": "Değer türü", "variable_pair_not_supported": "Bu değişken paritesi seçilen borsalarda desteklenmemekte", @@ -1024,5 +1050,6 @@ "you_will_get": "Biçimine dönüştür:", "you_will_receive_estimated_amount": "(Tahmini ) alacaksınız", "you_will_send": "Biçiminden dönüştür:", + "youCanGoBackToYourDapp": "Şimdi Dapp'ınıza geri dönebilirsin", "yy": "YY" } \ No newline at end of file diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 48039f0de..5ff9b72f6 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -53,6 +53,7 @@ "anonpay_description": "Згенерувати ${type}. Одержувач може ${method} будь-якою підтримуваною криптовалютою, і ви отримаєте кошти на цей гаманець.", "apk_update": "Оновлення APK", "approve": "Затвердити", + "approve_request": "Запитайте запит", "arrive_in_this_address": "${currency} ${tag}надійде на цю адресу", "ascending": "Висхід", "ask_each_time": "Запитайте кожен раз", @@ -116,9 +117,12 @@ "camera_consent": "Ваша камера використовуватиметься для зйомки зображення з метою ідентифікації ${provider}. Будь ласка, ознайомтеся з їхньою політикою конфіденційності, щоб дізнатися більше.", "camera_permission_is_required": "Потрібен дозвіл камери.\nУвімкніть його в налаштуваннях програми.", "cancel": "Відмінити", + "cannot_verify": "Не вдається перевірити", + "cannot_verify_description": "Цей домен неможливо перевірити. Ретельно перевірте запит перед затвердженням.", "card_address": "Адреса:", "cardholder_agreement": "Угода власника картки", "cards": "Картки", + "chain_id": "Ідентифікатор ланцюга", "chains": "Ланцюги", "change": "Змінити", "change_backup_password_alert": "Ваші попередні файли резервних копій будуть недоступні для імпорту з новим паролем резервної копії. Новий пароль резервної копії буде використовуватися тільки для нових файлів резервних копій. Ви впевнені, що хочете змінити пароль резервної копії?", @@ -174,6 +178,7 @@ "connect_yats": "Підключіть Yats", "connect_your_hardware_wallet": "Підключіть апаратний гаманець за допомогою Bluetooth або USB", "connect_your_hardware_wallet_ios": "Підключіть апаратний гаманець за допомогою Bluetooth", + "connected": "З'єднаний", "connection_sync": "Підключення та синхронізація", "connectWalletPrompt": "Підключіть свій гаманець до WalletConnect, щоб здійснювати транзакції", "contact": "Контакт", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "Вимкнути оптимізацію акумулятора", "disableBatteryOptimizationDescription": "Ви хочете відключити оптимізацію акумулятора, щоб зробити фонову синхронізацію більш вільно та плавно?", "disabled": "Вимкнено", + "disconnect_session": "Відключіть сеанс", "discount": "Зекономте ${value}%", "display_settings": "Налаштування дисплея", "displayable": "Відображуваний", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "Не діліться цим нікому, включно зі службою підтримки.\n\nВаші кошти можуть і будуть вкрадені!", "do_not_show_me": "Не показувати це знову", "domain_looks_up": "Пошук доменів", + "domain_mismatch": "Невідповідність домену", + "domain_mismatch_description": "Цей веб -сайт має домен, який не відповідає відправнику цього запиту. Затвердження може призвести до втрати коштів.", "donation_link_details": "Деталі посилання для пожертв", "e_sign_consent": "Згода електронного підпису", "edit": "Редагувати", @@ -298,6 +306,7 @@ "error_text_template": "Ім'я та адреса шаблону не може містити ` , ' \" символи\nі може бути від 1 до 106 символів в довжину", "error_text_wallet_name": "Ім'я гаманця може містити тільки букви, цифри, символи _ -\nі повинно бути від 1 до 33 символів в довжину", "error_text_xmr": "Значення XMR не може перевищувати доступний баланс.\nКількість цифр після коми повинно бути меншим або дорівнювати 12", + "error_while_processing": "Помилка сталася під час проектування", "errorGettingCredentials": "Помилка: помилка під час отримання облікових даних", "errorSigningTransaction": "Під час підписання транзакції сталася помилка", "estimated": "Приблизно ", @@ -323,6 +332,7 @@ "export_backup": "Експортувати резервну копію", "export_logs": "Експортні журнали", "export_outputs": "Експортні результати", + "extend_session": "Продовжити сеанс", "extra_id": "Додатковий ID:", "extracted_address_content": "Ви будете відправляти кошти\n${recipient_name}", "failed_authentication": "Помилка аутентифікації. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "Забули пароль", "freeze": "Заморозити", "frequently_asked_questions": "Часті запитання", + "from": "З", "frozen": "Заморожено", "frozen_balance": "Заморожений баланс", "full_balance": "Весь баланс", "gas_exceeds_allowance": "Газ, необхідний транзакціям, перевищує надбавку.", + "gas_price": "Ціна газу", "generate_name": "Згенерувати назву", "generating_gift_card": "Створення подарункової картки", "generating_transaction": "Генерування транзакції", @@ -440,6 +452,8 @@ "memo": "Пам’ятка:", "message": "повідомлення", "message_verified": "Повідомлення було успішно перевірено", + "messages": "Повідомлення", + "method": "Метод", "methods": "методи", "min_amount": "Мінімум: ${value}", "min_value": "Мін: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "Темна тема Monero", "monero_light_theme": "Легка тема Monero", "moonpay_alert_text": "Значення суми має бути більшим або дорівнювати ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "Під час отримання NFTS сталася помилка. Будь ласка, перевірте підключення до Інтернету та повторіть спробу.", "more_options": "Більше параметрів", "multiple_addresses_detected": "Виявлено кілька адрес", "mweb_confirmed": "Підтвердив Mweb", @@ -590,6 +605,7 @@ "rep_warning_sub": "Ваш представник, схоже, не має доброго становища. Торкніться тут, щоб вибрати новий", "repeat_wallet_password": "Повторіть пароль гаманця", "repeated_password_is_incorrect": "Повторний пароль невірний. Будь ласка, повторіть пароль гаманця ще раз.", + "requested": "Запитуваний", "require_for_adding_contacts": "Потрібен для додавання контактів", "require_for_all_security_and_backup_settings": "Вимагати всіх налаштувань безпеки та резервного копіювання", "require_for_assessing_wallet": "Потрібен доступ до гаманця", @@ -651,6 +667,8 @@ "second_intro_content": "Ваш Yat - це єдина унікальна адреса емодзі, яка замінює довгі шістнадцятиричні адреси для всіх ваших валют.", "second_intro_title": "Одна адреса емодзі для управління іншими адресами", "security_and_backup": "Безпека та резервне копіювання", + "security_risk": "Ризик безпеки", + "security_risk_description": "Цей домен позначений як небезпечно численними постачальниками безпеки. Залиште негайно, щоб захистити свої активи.", "seed_alert_back": "Назад", "seed_alert_content": "Мнемонічна фраза - єдиний спосіб відновити ваш гаманець. Ви записали її?", "seed_alert_title": "Увага", @@ -765,7 +783,9 @@ "show_keys": "Показати мнемонічну фразу/ключі", "show_market_place": "Відображати маркетплейс", "show_seed": "Показати мнемонічну фразу", + "sign_all": "Підписати все", "sign_message": "Підпишіть повідомлення", + "sign_one": "Підписати один", "sign_up": "Зареєструватися", "sign_verify_message": "Підпишіть або перевірити повідомлення", "sign_verify_message_sub": "Підпишіть або перевірте повідомлення за допомогою вашого приватного ключа", @@ -799,6 +819,7 @@ "subaddress_title": "Список Субадрес", "subaddresses": "Субадреси", "submit_request": "надіслати запит", + "success": "Успіх", "successful": "Успішний", "support_description_guides": "Документація та підтримка загальних питань", "support_description_live_chat": "Безкоштовно і швидко! Навчені представники підтримки доступні для надання допомоги", @@ -806,6 +827,7 @@ "support_title_guides": "Торт гаманці", "support_title_live_chat": "Жива підтримка", "support_title_other_links": "Інші посилання на підтримку", + "supported": "Підтримуваний", "swap": "Обміняти", "sweeping_wallet": "Підмітаня гаманця", "sweeping_wallet_alert": "Це не повинно зайняти багато часу. НЕ ЗАЛИШАЙТЕ ЦЬОГО ЕКРАНУ, АБО КОШТИ МОЖУТЬ БУТИ ВТРАЧЕНІ", @@ -838,6 +860,7 @@ "thorchain_taproot_address_not_supported": "Постачальник Thorchain не підтримує адреси Taproot. Будь ласка, змініть адресу або виберіть іншого постачальника.", "time": "${minutes}хв ${seconds}сек", "tip": "Порада:", + "to": "До", "today": "Сьогодні", "token_contract_address": "Адреса договору маркера", "token_decimal": "Токен десятковий", @@ -905,6 +928,7 @@ "transaction_sent_notice": "Якщо екран не відображається через 1 хвилину, перевірте провідник блоків і свою електронну пошту.", "transactions": "Транзакції", "transactions_by_date": "Сортувати по даті", + "transport_type": "Транспортний тип", "trongrid_history": "Тронгрідська історія", "trusted": "довіряють", "tx_commit_exception_no_dust_on_change": "Транзакція відхилена цією сумою. За допомогою цих монет ви можете надіслати ${min} без змін або ${max}, що повертає зміни.", @@ -933,6 +957,7 @@ "unspent_coins_details_title": "Відомості про невитрачені монети", "unspent_coins_title": "Невитрачені монети", "unsupported_asset": "Ми не підтримуємо цю дію для цього ресурсу. Створіть або перейдіть на гаманець підтримуваного типу активів.", + "update_session": "Оновити сеанс", "uptime": "Час роботи", "upto": "до ${value}", "usb": "USB", @@ -942,6 +967,7 @@ "use_ssl": "Використати SSL", "use_suggested": "Використати запропоноване", "use_testnet": "Використовуйте тестову мережу", + "user_rejected_method": "КОМПЛЕКТИВНИЙ МЕТОД", "value": "Цінність", "value_type": "Тип значення", "variable_pair_not_supported": "Ця пара змінних не підтримується вибраними біржами", @@ -1025,5 +1051,6 @@ "you_will_get": "Конвертувати в", "you_will_receive_estimated_amount": "Ви отримаєте(оцінюється )", "you_will_send": "Конвертувати з", + "youCanGoBackToYourDapp": "Ви можете повернутися до свого DAPP зараз", "yy": "YY" } \ No newline at end of file diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 1582abdb6..2e27ff85e 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -53,6 +53,7 @@ "anonpay_description": "${type} بنائیں۔ وصول کنندہ کسی بھی تعاون یافتہ کرپٹو کرنسی کے ساتھ ${method} کرسکتا ہے، اور آپ کو اس بٹوے میں فنڈز موصول ہوں گے۔", "apk_update": "APK اپ ڈیٹ", "approve": "ﻭﺮﮐ ﺭﻮﻈﻨﻣ", + "approve_request": "درخواست کو منظور کریں", "arrive_in_this_address": "${currency} ${tag}اس پتے پر پہنچے گا۔", "ascending": "چڑھنے", "ask_each_time": "ہر بار پوچھیں", @@ -116,9 +117,12 @@ "camera_consent": "۔ﮟﯿﮭﮑﯾﺩ ﯽﺴﯿﻟﺎﭘ ﯽﺴﯾﻮﯿﺋﺍﺮﭘ ﯽﮐ ﻥﺍ ﻡﺮﮐ ﮦﺍﺮﺑ ﮯﯿﻟ ﮯﮐ ﺕﻼ${provider}ﯿﺼﻔﺗ ۔ﺎﮔ ﮯﺋﺎﺟ ﺎﯿﮐ ﻝﺎﻤﻌﺘﺳﺍ ﮯﯿﻟ", "camera_permission_is_required": "۔ﮯﮨ ﺭﺎﮐﺭﺩ ﺕﺯﺎﺟﺍ ﯽﮐ ﮮﺮﻤﯿﮐ", "cancel": "منسوخ کریں۔", + "cannot_verify": "تصدیق نہیں کرسکتے ہیں", + "cannot_verify_description": "اس ڈومین کی تصدیق نہیں کی جاسکتی ہے۔ منظوری سے پہلے درخواست کو احتیاط سے چیک کریں۔", "card_address": "پتہ:", "cardholder_agreement": "کارڈ ہولڈر کا معاہدہ", "cards": "کارڈز", + "chain_id": "چین ID", "chains": "ﮟﯾﺮﯿﺠﻧﺯ", "change": "تبدیلی", "change_backup_password_alert": "آپ کی پچھلی بیک اپ فائلیں نئے بیک اپ پاس ورڈ کے ساتھ درآمد کرنے کے لیے دستیاب نہیں ہوں گی۔ نیا بیک اپ پاس ورڈ صرف نئی بیک اپ فائلوں کے لیے استعمال کیا جائے گا۔ کیا آپ واقعی بیک اپ پاس ورڈ تبدیل کرنا چاہتے ہیں؟", @@ -174,6 +178,7 @@ "connect_yats": "Yats کو جوڑیں۔", "connect_your_hardware_wallet": "بلوٹوتھ یا USB کا استعمال کرتے ہوئے اپنے ہارڈ ویئر پرس کو مربوط کریں", "connect_your_hardware_wallet_ios": "بلوٹوتھ کا استعمال کرتے ہوئے اپنے ہارڈ ویئر پرس کو جوڑیں", + "connected": "منسلک", "connection_sync": "کنکشن اور مطابقت پذیری", "connectWalletPrompt": "۔ﮟﯾﮌﻮﺟ ﮫﺗﺎﺳ ﮯﮐ WalletConnect ﻮﮐ ﮮﻮﭩﺑ ﮯﻨﭘﺍ ﮯﯿﻟ ﮯﮐ ﮯﻧﺮﮐ ﻦﯾﺩ ﻦﯿﻟ", "contact": "رابطہ کریں۔", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "بیٹری کی اصلاح کو غیر فعال کریں", "disableBatteryOptimizationDescription": "کیا آپ پس منظر کی مطابقت پذیری کو زیادہ آزادانہ اور آسانی سے چلانے کے لئے بیٹری کی اصلاح کو غیر فعال کرنا چاہتے ہیں؟", "disabled": "معذور", + "disconnect_session": "منقطع سیشن", "discount": "${value}% بچائیں", "display_settings": "ڈسپلے کی ترتیبات", "displayable": "قابل نمائش", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "سپورٹ سمیت کسی اور کے ساتھ ان کا اشتراک نہ کریں۔\\n\\nآپ کے فنڈز چوری ہو سکتے ہیں اور ہو جائیں گے!", "do_not_show_me": "مجھے یہ دوبارہ مت دکھانا", "domain_looks_up": "ڈومین تلاش کرنا", + "domain_mismatch": "ڈومین مماثل", + "domain_mismatch_description": "اس ویب سائٹ میں ایک ڈومین ہے جو اس درخواست کے مرسل سے مماثل نہیں ہے۔ منظوری سے فنڈز کا نقصان ہوسکتا ہے۔", "donation_link_details": "عطیہ کے لنک کی تفصیلات", "e_sign_consent": "ای سائن کنسنٹ", "edit": "ترمیم", @@ -298,6 +306,7 @@ "error_text_template": "ٹیمپلیٹ کا نام اور پتہ `` , \\' \" علامتوں پر مشتمل نہیں ہو سکتا ہے اور 1 اور 106 حروف کے درمیان ہونا چاہیے", "error_text_wallet_name": "والیٹ کے نام میں صرف حروف، اعداد، _ - علامتیں\\nاور 1 سے 33 حروف کے درمیان ہونی چاہئیں", "error_text_xmr": "XMR قدر دستیاب بیلنس سے زیادہ نہیں ہو سکتی۔\\nفرکشن ہندسوں کی تعداد 12 سے کم یا اس کے برابر ہونی چاہیے۔", + "error_while_processing": "ایک غلطی پیش کرتے وقت ہوئی", "errorGettingCredentials": "۔ﯽﺑﺍﺮﺧ ﮟﯿﻣ ﮯﻧﺮﮐ ﻞﺻﺎﺣ ﺩﺎﻨﺳﺍ :ﻡﺎﮐﺎﻧ", "errorSigningTransaction": "۔ﮯﮨ ﯽﺌﮔﺁ ﺶﯿﭘ ﯽﺑﺍﺮﺧ ﮏﯾﺍ ﺖﻗﻭ ﮯﺗﺮﮐ ﻂﺨﺘﺳﺩ ﺮﭘ ﻦﯾﺩ ﻦﯿﻟ", "estimated": "تخمینہ لگایا", @@ -323,6 +332,7 @@ "export_backup": "بیک اپ برآمد کریں۔", "export_logs": "نوشتہ جات برآمد کریں", "export_outputs": "برآمد کے نتائج", + "extend_session": "سیشن میں توسیع", "extra_id": "اضافی ID:", "extracted_address_content": "آپ فنڈز بھیج رہے ہوں گے\n${recipient_name}", "failed_authentication": "ناکام تصدیق۔ ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "پاسورڈ بھول گے", "freeze": "منجمد", "frequently_asked_questions": "اکثر پوچھے گئے سوالات", + "from": "سے", "frozen": "منجمد", "frozen_balance": "منجمد توازن", "full_balance": "مکمل بیلنس", "gas_exceeds_allowance": "لین دین کے ذریعہ درکار گیس الاؤنس سے زیادہ ہے۔", + "gas_price": "گیس کی قیمت", "generate_name": "نام پیدا کریں۔", "generating_gift_card": "گفٹ کارڈ تیار کرنا", "generating_transaction": "ٹرانزیکشن پیدا کرنا", @@ -440,6 +452,8 @@ "memo": "میمو:", "message": "ﻡﺎﻐﯿﭘ", "message_verified": "پیغام کی کامیابی کے ساتھ تصدیق کی گئی", + "messages": "پیغامات", + "method": "طریقہ", "methods": "ﮯﻘﯾﺮﻃ", "min_amount": "کم سے کم: ${value}", "min_value": "کم سے کم: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "مونیرو ڈارک تھیم", "monero_light_theme": "مونیرو لائٹ تھیم", "moonpay_alert_text": "رقم کی قدر ${minAmount} ${fiatCurrency} کے برابر یا زیادہ ہونی چاہیے۔", + "moralis_nft_error": "این ایف ٹی ایس لانے کے دوران ایک خرابی پیش آگئی۔ برائے مہربانی اپنا انٹرنیٹ کنیکشن چیک کریں اور دوبارہ کوشش کریں۔", "more_options": "مزید زرائے", "multiple_addresses_detected": "متعدد پتے کا پتہ چلا", "mweb_confirmed": "تصدیق شدہ MWEB", @@ -591,6 +606,7 @@ "rep_warning_sub": "آپ کا نمائندہ اچھ standing ے مقام پر نہیں دکھائی دیتا ہے۔ نیا منتخب کرنے کے لئے یہاں ٹیپ کریں", "repeat_wallet_password": "بٹوے کا پاس ورڈ دہرائیں", "repeated_password_is_incorrect": "بار بار پاس ورڈ غلط ہے۔ براہ کرم دوبارہ پرس کا پاس ورڈ دہرائیں۔", + "requested": "درخواست کی", "require_for_adding_contacts": "رابطوں کو شامل کرنے کی ضرورت ہے۔", "require_for_all_security_and_backup_settings": "تمام سیکورٹی اور بیک اپ کی ترتیبات کے لیے درکار ہے۔", "require_for_assessing_wallet": "بٹوے تک رسائی کے لیے درکار ہے۔", @@ -652,6 +668,8 @@ "second_intro_content": "آپ کا Yat ایک منفرد ایموجی ایڈریس ہے جو آپ کی تمام کرنسیوں کے لیے آپ کے تمام لمبے ہیکسا ڈیسیمل پتوں کی جگہ لے لیتا ہے۔", "second_intro_title": "ان سب پر حکمرانی کے لیے ایک ایموجی ایڈریس", "security_and_backup": "سیکیورٹی اور بیک اپ", + "security_risk": "سیکیورٹی کا خطرہ", + "security_risk_description": "اس ڈومین کو متعدد سیکیورٹی فراہم کرنے والوں کے ذریعہ غیر محفوظ کے طور پر جھنڈا لگایا گیا ہے۔ اپنے اثاثوں کی حفاظت کے لئے فورا. چھوڑ دیں۔", "seed_alert_back": "واپس جاو", "seed_alert_content": "بیج آپ کے بٹوے کو بازیافت کرنے کا واحد طریقہ ہے۔ کیا آپ نے اسے لکھا ہے؟", "seed_alert_title": "توجہ", @@ -766,7 +784,9 @@ "show_keys": "بیج / چابیاں دکھائیں۔", "show_market_place": "بازار دکھائیں۔", "show_seed": "بیج دکھائیں۔", + "sign_all": "سب پر دستخط کریں", "sign_message": "سائن پیغام", + "sign_one": "ایک پر دستخط کریں", "sign_up": "سائن اپ", "sign_verify_message": "پیغام پر دستخط کریں یا تصدیق کریں", "sign_verify_message_sub": "اپنی نجی کلید کا استعمال کرتے ہوئے کسی پیغام پر دستخط کریں یا اس کی تصدیق کریں", @@ -800,6 +820,7 @@ "subaddress_title": "ذیلی ایڈریس کی فہرست", "subaddresses": "ذیلی پتے", "submit_request": "درخواست بھیج دو", + "success": "کامیابی", "successful": "ﺏﺎﯿﻣﺎﮐ", "support_description_guides": "عام مسائل کے لئے دستاویزات اور مدد", "support_description_live_chat": "مفت اور تیز! تربیت یافتہ معاون نمائندے مدد کے لئے دستیاب ہیں", @@ -807,6 +828,7 @@ "support_title_guides": "کیک پرس کے دستاویزات", "support_title_live_chat": "براہ راست مدد", "support_title_other_links": "دوسرے سپورٹ لنکس", + "supported": "تائید", "swap": "تبادلہ", "sweeping_wallet": "جھاڑو دینے والا پرس", "sweeping_wallet_alert": "اس میں زیادہ وقت نہیں لینا چاہئے۔ اس اسکرین کو مت چھوڑیں یا بہہ جانے والے فنڈز ضائع ہوسکتے ہیں۔", @@ -839,6 +861,7 @@ "thorchain_taproot_address_not_supported": "تھورچین فراہم کنندہ ٹیپروٹ پتے کی حمایت نہیں کرتا ہے۔ براہ کرم پتہ تبدیل کریں یا ایک مختلف فراہم کنندہ کو منتخب کریں۔", "time": "${minutes}m ${seconds}s", "tip": "ٹپ:", + "to": "to", "today": "آج", "token_contract_address": "ٹوکن کنٹریکٹ ایڈریس", "token_decimal": "ٹوکن اعشاریہ", @@ -906,6 +929,7 @@ "transaction_sent_notice": "اگر اسکرین 1 منٹ کے بعد آگے نہیں بڑھتی ہے، تو بلاک ایکسپلورر اور اپنا ای میل چیک کریں۔", "transactions": "لین دین", "transactions_by_date": "تاریخ کے لحاظ سے لین دین", + "transport_type": "ٹرانسپورٹ کی قسم", "trongrid_history": "ٹرانگریڈ ہسٹری", "trusted": "قابل اعتماد", "tx_commit_exception_no_dust_on_change": "اس رقم سے لین دین کو مسترد کردیا گیا ہے۔ ان سککوں کے ذریعہ آپ بغیر کسی تبدیلی کے ${min} یا ${max} بھیج سکتے ہیں جو لوٹتے ہیں۔", @@ -934,6 +958,7 @@ "unspent_coins_details_title": "غیر خرچ شدہ سککوں کی تفصیلات", "unspent_coins_title": "غیر خرچ شدہ سکے ۔", "unsupported_asset": "۔ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﺱﺍ ﺎﯾ ﮟﯿﺋﺎﻨﺑ ﺱﺮﭘ ﺎﮐ ﻢﺴﻗ ﯽﮐ ﮧﺛﺎﺛﺍ ﮧﺘﻓﺎﯾ ﻥﻭﺎﻌﺗ ﻡﺮﮐ ﮦﺍﺮﺑ ۔ﮟﯿﮨ ﮯﺗﺮﮐ ﮟﯿﮩﻧ ﺖﯾﺎﻤﺣ ﯽﮐ ﯽﺋﺍﻭﺭﺭﺎﮐ ﺱﺍ ﮯﯿﻟ ﮯﮐ ﮧﺛﺎﺛﺍ ﺱﺍ ﻢﮨ", + "update_session": "سیشن کو اپ ڈیٹ کریں", "uptime": "اپ ٹائم", "upto": "${value} تک", "usb": "یو ایس بی", @@ -943,6 +968,7 @@ "use_ssl": "SSL استعمال کریں۔", "use_suggested": "تجویز کردہ استعمال کریں۔", "use_testnet": "ٹیسٹ نیٹ استعمال کریں", + "user_rejected_method": "صارف کو مسترد کرنے کا طریقہ", "value": "قدر", "value_type": "قدر کی قسم", "variable_pair_not_supported": "یہ متغیر جوڑا منتخب ایکسچینجز کے ساتھ تعاون یافتہ نہیں ہے۔", @@ -1026,5 +1052,6 @@ "you_will_get": "میں تبدیل کریں۔", "you_will_receive_estimated_amount": "آپ(تخمینہ ) وصول کریں گے", "you_will_send": "سے تبدیل کریں۔", + "youCanGoBackToYourDapp": "اب آپ اپنے ڈی اے پی پی پر واپس جاسکتے ہیں", "yy": "YY" } \ No newline at end of file diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index ef75afd57..7bbe35a4a 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -53,6 +53,7 @@ "anonpay_description": "Tạo ${type}. Người nhận có thể ${method} với bất kỳ loại tiền điện tử nào được hỗ trợ, và bạn sẽ nhận được tiền trong ví này.", "apk_update": "Cập nhật APK", "approve": "Phê duyệt", + "approve_request": "Phê duyệt yêu cầu", "arrive_in_this_address": "${currency} ${tag} sẽ đến địa chỉ này", "ascending": "Tăng dần", "ask_each_time": "Hỏi mỗi lần", @@ -116,9 +117,12 @@ "camera_consent": "Máy ảnh của bạn sẽ được sử dụng để chụp hình nhằm mục đích xác minh danh tính bởi ${provider}. Vui lòng kiểm tra Chính sách quyền riêng tư của họ để biết thêm chi tiết.", "camera_permission_is_required": "Cần có quyền truy cập máy ảnh. \nVui lòng bật nó từ cài đặt ứng dụng.", "cancel": "Hủy", + "cannot_verify": "Không thể xác minh", + "cannot_verify_description": "Miền này không thể được xác minh. Kiểm tra yêu cầu cẩn thận trước khi phê duyệt.", "card_address": "Địa chỉ:", "cardholder_agreement": "Thỏa thuận Chủ Thẻ", "cards": "Thẻ", + "chain_id": "ID chuỗi", "chains": "Chuỗi", "change": "Thay đổi", "change_backup_password_alert": "Các tệp sao lưu trước đây sẽ không còn khả dụng để nhập với mật khẩu sao lưu mới. Mật khẩu sao lưu mới chỉ được sử dụng cho các tệp sao lưu mới. Bạn có chắc chắn muốn thay đổi mật khẩu sao lưu không?", @@ -173,6 +177,7 @@ "connect_yats": "Kết nối Yats", "connect_your_hardware_wallet": "Kết nối ví phần cứng của bạn bằng Bluetooth hoặc USB", "connect_your_hardware_wallet_ios": "Kết nối ví phần cứng của bạn bằng Bluetooth", + "connected": "Kết nối", "connection_sync": "Kết nối và đồng bộ hóa", "connectWalletPrompt": "Kết nối ví của bạn với WalletConnect để thực hiện giao dịch", "contact": "Liên hệ", @@ -243,6 +248,7 @@ "disableBatteryOptimization": "Vô hiệu hóa Tối ưu hóa Pin", "disableBatteryOptimizationDescription": "Bạn có muốn vô hiệu hóa tối ưu hóa pin để đồng bộ hóa nền hoạt động mượt mà hơn không?", "disabled": "Đã vô hiệu hóa", + "disconnect_session": "Ngắt kết nối phiên", "discount": "Tiết kiệm ${value}%", "display_settings": "Cài đặt hiển thị", "displayable": "Có thể hiển thị", @@ -251,6 +257,8 @@ "do_not_share_warning_text": "Không chia sẻ điều này với bất kỳ ai, bao gồm cả bộ phận hỗ trợ.\n\nTài sản của bạn có thể và sẽ bị đánh cắp!", "do_not_show_me": "Không hiển thị lại cho tôi", "domain_looks_up": "Tra cứu tên miền", + "domain_mismatch": "Không phù hợp miền", + "domain_mismatch_description": "Trang web này có một tên miền không khớp với người gửi yêu cầu này. Phê duyệt có thể dẫn đến mất tiền.", "donation_link_details": "Chi tiết liên kết quyên góp", "e_sign_consent": "Đồng ý Ký Điện tử", "edit": "Chỉnh sửa", @@ -297,6 +305,7 @@ "error_text_template": "Tên và địa chỉ mẫu không được chứa ký tự ` , ' \"\nvà phải từ 1 đến 106 ký tự", "error_text_wallet_name": "Tên ví chỉ được chứa chữ cái, số, ký hiệu _ -\nvà phải từ 1 đến 33 ký tự", "error_text_xmr": "Giá trị XMR không được vượt quá số dư khả dụng.\nSố chữ số thập phân phải ít hơn hoặc bằng 12", + "error_while_processing": "Xảy ra lỗi trong khi sử dụng", "errorGettingCredentials": "Không thành công: Lỗi khi nhận thông tin xác thực", "errorSigningTransaction": "Đã xảy ra lỗi khi ký giao dịch", "estimated": "Ước tính", @@ -322,6 +331,7 @@ "export_backup": "Xuất sao lưu", "export_logs": "Nhật ký xuất khẩu", "export_outputs": "Đầu ra xuất khẩu", + "extend_session": "Mở rộng phiên", "extra_id": "ID bổ sung:", "extracted_address_content": "Bạn sẽ gửi tiền cho\n${recipient_name}", "failed_authentication": "Xác thực không thành công. ${state_error}", @@ -343,10 +353,12 @@ "forgot_password": "Quên mật khẩu", "freeze": "Đóng băng", "frequently_asked_questions": "Các câu hỏi thường gặp", + "from": "Từ", "frozen": "Đã đóng băng", "frozen_balance": "Cân bằng đông lạnh", "full_balance": "Số dư đầy đủ", "gas_exceeds_allowance": "Gas theo yêu cầu của giao dịch vượt quá trợ cấp.", + "gas_price": "Giá xăng", "generate_name": "Tạo tên", "generating_gift_card": "Đang tạo thẻ quà tặng", "generating_transaction": "Tạo giao dịch", @@ -439,6 +451,8 @@ "memo": "Ghi chú:", "message": "Tin nhắn", "message_verified": "Tin nhắn đã được xác minh thành công", + "messages": "Tin nhắn", + "method": "Phương pháp", "methods": "Phương pháp", "min_amount": "Tối thiểu: ${value}", "min_value": "Tối thiểu: ${value} ${currency}", @@ -451,6 +465,7 @@ "monero_dark_theme": "Chủ đề tối Monero", "monero_light_theme": "Chủ đề sáng Monero", "moonpay_alert_text": "Giá trị số tiền phải lớn hơn hoặc bằng ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "Một lỗi đã xảy ra trong khi tìm nạp NFT. Vui lòng kiểm tra kết nối Internet của bạn và thử lại.", "more_options": "Thêm tùy chọn", "multiple_addresses_detected": "Nhiều địa chỉ được phát hiện", "mweb_confirmed": "Xác nhận MWEB", @@ -587,6 +602,7 @@ "rep_warning_sub": "Đại diện của bạn dường như không còn trong tình trạng tốt. Nhấn vào đây để chọn một cái mới", "repeat_wallet_password": "Nhập lại mật khẩu ví", "repeated_password_is_incorrect": "Mật khẩu nhập lại không chính xác. Vui lòng nhập lại mật khẩu ví.", + "requested": "Được yêu cầu", "require_for_adding_contacts": "Yêu cầu khi thêm danh bạ", "require_for_all_security_and_backup_settings": "Yêu cầu cho tất cả các cài đặt bảo mật và sao lưu", "require_for_assessing_wallet": "Yêu cầu khi truy cập ví", @@ -648,6 +664,8 @@ "second_intro_content": "Yat của bạn là một địa chỉ emoji duy nhất thay thế tất cả các địa chỉ hex dài của bạn cho tất cả các loại tiền tệ của bạn.", "second_intro_title": "Một địa chỉ emoji để cai trị tất cả", "security_and_backup": "Bảo mật và sao lưu", + "security_risk": "Rủi ro bảo mật", + "security_risk_description": "Tên miền này được gắn cờ là không an toàn bởi nhiều nhà cung cấp bảo mật. Rời đi ngay lập tức để bảo vệ tài sản của bạn.", "seed_alert_back": "Quay lại", "seed_alert_content": "Hạt giống là cách duy nhất để khôi phục ví của bạn. Bạn đã ghi lại nó chưa?", "seed_alert_title": "Chú ý", @@ -761,7 +779,9 @@ "show_keys": "Hiển thị hạt giống/khóa", "show_market_place": "Hiển thị Thị trường", "show_seed": "Hiển thị hạt giống", + "sign_all": "Ký tất cả", "sign_message": "Ký tin nhắn", + "sign_one": "Ký một", "sign_up": "Đăng ký", "sign_verify_message": "Ký hoặc xác minh tin nhắn", "sign_verify_message_sub": "Ký hoặc xác minh một tin nhắn bằng khóa riêng của bạn", @@ -795,6 +815,7 @@ "subaddress_title": "Danh sách địa chỉ phụ", "subaddresses": "Địa chỉ phụ", "submit_request": "gửi yêu cầu", + "success": "Thành công", "successful": "Thành công", "support_description_guides": "Tài liệu và hỗ trợ cho các vấn đề phổ biến", "support_description_live_chat": "Miễn phí và nhanh chóng! Các đại diện hỗ trợ được đào tạo sẵn sàng hỗ trợ", @@ -802,6 +823,7 @@ "support_title_guides": "Cake Wallet Docs", "support_title_live_chat": "Hỗ trợ trực tiếp", "support_title_other_links": "Liên kết hỗ trợ khác", + "supported": "Được hỗ trợ", "swap": "Tráo đổi", "sweeping_wallet": "Quét ví", "sweeping_wallet_alert": "Việc này không nên mất nhiều thời gian. KHÔNG RỜI KHỎI MÀN HÌNH NÀY HOẶC CÁC KHOẢN TIỀN ĐƯỢC QUÉT CÓ THỂ BỊ MẤT.", @@ -834,6 +856,7 @@ "thorchain_taproot_address_not_supported": "Nhà cung cấp ThorChain không hỗ trợ địa chỉ Taproot. Vui lòng thay đổi địa chỉ hoặc chọn nhà cung cấp khác.", "time": "${minutes} phút ${seconds} giây", "tip": "Mẹo:", + "to": "ĐẾN", "today": "Hôm nay", "token_contract_address": "Địa chỉ hợp đồng token", "token_decimal": "Số thập phân của token", @@ -901,6 +924,7 @@ "transaction_sent_notice": "Nếu màn hình không tiếp tục sau 1 phút, hãy kiểm tra trình khám phá khối và email của bạn.", "transactions": "Giao dịch", "transactions_by_date": "Giao dịch theo ngày", + "transport_type": "Loại vận chuyển", "trongrid_history": "Lịch sử TronGrid", "trusted": "Đã tin cậy", "tx_commit_exception_no_dust_on_change": "Giao dịch bị từ chối với số tiền này. Với số tiền này bạn có thể gửi ${min} mà không cần đổi tiền lẻ hoặc ${max} trả lại tiền lẻ.", @@ -929,6 +953,7 @@ "unspent_coins_details_title": "Chi tiết các đồng tiền chưa chi tiêu", "unspent_coins_title": "Các đồng tiền chưa chi tiêu", "unsupported_asset": "Chúng tôi không hỗ trợ hành động này cho tài sản này. Vui lòng tạo hoặc chuyển sang ví của loại tài sản được hỗ trợ.", + "update_session": "Cập nhật phiên", "uptime": "Thời gian hoạt động", "upto": "lên đến ${value}", "usb": "USB", @@ -938,6 +963,7 @@ "use_ssl": "Sử dụng SSL", "use_suggested": "Sử dụng đề xuất", "use_testnet": "Sử dụng Testnet", + "user_rejected_method": "Phương thức từ chối người dùng", "value": "Giá trị", "value_type": "Loại giá trị", "variable_pair_not_supported": "Cặp biến này không được hỗ trợ với các sàn giao dịch đã chọn", @@ -1021,5 +1047,6 @@ "you_will_get": "Chuyển đổi thành", "you_will_receive_estimated_amount": "Bạn sẽ nhận được(ước tính )", "you_will_send": "Chuyển đổi từ", + "youCanGoBackToYourDapp": "Bạn có thể quay lại DAPP của mình ngay bây giờ", "yy": "YY" } \ No newline at end of file diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 5e4cc331c..6a5ff26fd 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -53,6 +53,7 @@ "anonpay_description": "Ṣe akọkọ ${type}. Awọn alabara le ${method} pẹlu eyikeyi iwo ise ati owo yoo wọle si iwe iwe yii.", "apk_update": "Àtúnse áàpù títun wà", "approve": "Fi ọwọ si", + "approve_request": "IKILỌ RẸ", "arrive_in_this_address": "${currency} ${tag} máa dé sí àdírẹ́sì yìí", "ascending": "Goke", "ask_each_time": "Beere lọwọ kọọkan", @@ -116,9 +117,12 @@ "camera_consent": "Kamẹra rẹ yoo ṣee lo lati ya aworan kan fun awọn idi idanimọ nipasẹ ${provider}. Jọwọ ṣayẹwo Ilana Aṣiri wọn fun awọn alaye.", "camera_permission_is_required": "A nilo igbanilaaye kamẹra.\nJọwọ jeki o lati app eto.", "cancel": "Fagi lé e", + "cannot_verify": "Ko le mọ daju", + "cannot_verify_description": "A ko le jẹrisi ipo yii. Ṣayẹwo ibeere ni pẹkipẹki ṣaaju gbigba.", "card_address": "Àdírẹ́sì:", "cardholder_agreement": "Àjọrò olùṣe káàdì ìrajà", "cards": "Àwọn káàdì", + "chain_id": "ID ID", "chains": "Awọn ẹwọn", "change": "Pààrọ̀", "change_backup_password_alert": "Ẹ kò lè fi ọ̀rọ̀ aṣínà títun ti ẹ̀dà nípamọ́ ṣí àwọn àkọsílẹ̀ nípamọ́ tẹ́lẹ̀tẹ́lẹ̀ yín. Ẹ máa fi ọ̀rọ̀ aṣínà ti ẹ̀dà nípamọ́ títun ṣí àwọn àkọsílẹ̀ nípamọ́ títun nìkan. Ṣé ó dá ẹ lójú pé ẹ fẹ́ pààrọ̀ aṣínà ti ẹ̀dà nípamọ́?", @@ -174,6 +178,7 @@ "connect_yats": "So àwọn Yat", "connect_your_hardware_wallet": "So apamọwọ irinṣẹ rẹ nipa lilo Bluetooth tabi USB", "connect_your_hardware_wallet_ios": "So apamọwọ ẹrọ rẹ ni lilo Bluetooth", + "connected": "Sopọ", "connection_sync": "Ìkànpọ̀ àti ìbádọ́gba", "connectWalletPrompt": "So apamọwọ rẹ pọ pẹlu WalletConnect lati ṣe awọn iṣowo", "contact": "Olùbásọ̀rọ̀", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "Mu Ifasi batiri", "disableBatteryOptimizationDescription": "Ṣe o fẹ lati mu iṣapelo batiri si lati le ṣiṣe ayẹwo ẹhin ati laisiyonu?", "disabled": "Wọ́n tí a ti pa", + "disconnect_session": "Koro igba ipade", "discount": "Pamọ́ ${value}%", "display_settings": "Fihàn àwọn ààtò", "displayable": "A lè ṣàfihàn ẹ̀", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "Ẹ kò pín wọnyìí sí ẹnikẹ́ni. Ẹ sì kò pin wọnyìí sí ìranlọ́wọ́. Ẹnikẹ́ni lè jí owó yín! Wọ́n máa jí owó yín!", "do_not_show_me": "Kò fi eléyìí hàn mi mọ́", "domain_looks_up": "Awọn wiwa agbegbe", + "domain_mismatch": "Ìkápá", + "domain_mismatch_description": "Oju opo wẹẹbu yii ni ašẹ ti ko baamu olufiranṣẹ ti ibeere yii. Itẹwọgba le ja si ipadanu awọn owo.", "donation_link_details": "Iru awọn ẹya ọrọ ti o funni", "dont_get_code": "Ṣé ẹ ti gba ọ̀rọ̀ ìdánimọ̀?", "e_sign_consent": "Jẹ́rìí sí lórí ayélujára", @@ -299,6 +307,7 @@ "error_text_template": "Orúkọ àwòṣe àti àdírẹ́sì kò lè ni nínú àwọn àmì ` , ' \"\nIye ẹyọ ọ̀rọ̀ náà gbọ́dọ̀ jẹ́ láàárín 1 àti 106", "error_text_wallet_name": "Orúkọ àpamọ́wọ́ lè ni nìkan nínú àwọn òǹkà àti ẹyọ ọ̀rọ̀ àti àmì _ -\nIye ẹyọ ọ̀rọ̀ gbọ́dọ̀ wà láàárín 1 àti 33", "error_text_xmr": "Iye XMR kò lè tóbi ju ìyókù.\nIye díjíìtì léyìn ẹsẹ kò gbọ́dọ̀ tóbi ju eéjìlá.", + "error_while_processing": "Aṣiṣe kan waye lakoko ti o duro", "errorGettingCredentials": "Kuna: Aṣiṣe lakoko gbigba awọn iwe-ẹri", "errorSigningTransaction": "Aṣiṣe kan ti waye lakoko ti o fowo si iṣowo", "estimated": "Ó tó a fojú díwọ̀n", @@ -324,6 +333,7 @@ "export_backup": "Sún ẹ̀dà nípamọ́ síta", "export_logs": "Wọle si okeere", "export_outputs": "Agbohunlu okeere", + "extend_session": "Ifaagun igba", "extra_id": "Àmì ìdánimọ̀ tó fikún:", "extracted_address_content": "Ẹ máa máa fi owó ránṣẹ́ sí\n${recipient_name}", "failed_authentication": "Ìfẹ̀rílàdí pipòfo. ${state_error}", @@ -345,10 +355,12 @@ "forgot_password": "Ẹ ti gbàgbé ọ̀rọ̀ aṣínà", "freeze": "Tì pa", "frequently_asked_questions": "Àwọn ìbéèrè la máa ń béèrè", + "from": "Lati", "frozen": "Ó l'a tì pa", "frozen_balance": "Iwontunwonsi ti o tutu", "full_balance": "Ìyókù owó kíkún", "gas_exceeds_allowance": "Gaasi ti a beere nipasẹ idunadura ju lọ.", + "gas_price": "Owo gaasi", "generate_name": "Ṣẹda Orukọ", "generating_gift_card": "À ń dá káàdì ìrajà t'á lò nínú irú kan ìtajà", "generating_transaction": "Ifọwọsi Iṣowo", @@ -441,6 +453,8 @@ "memo": "Àkọsílẹ̀:", "message": "Ifiranṣẹ", "message_verified": "Ifiranṣẹ naa ni aṣeyọri ni ifijišẹ", + "messages": "Awọn ifiranṣẹ", + "method": "Ọna", "methods": "Awọn ọna", "min_amount": "kò kéré ju: ${value}", "min_value": "kò gbọ́dọ̀ kéré ju ${value} ${currency}", @@ -453,6 +467,7 @@ "monero_dark_theme": "Monero Dudu Akori", "monero_light_theme": "Monero Light Akori", "moonpay_alert_text": "Iye owó kò gbọ́dọ̀ kéré ju ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "Aṣiṣe kan waye lakoko ti nfts. Fi inurere ṣayẹwo asopọ intanẹẹti rẹ ki o tun gbiyanju lẹẹkan si.", "more_options": "Ìyàn àfikún", "multiple_addresses_detected": "Awọn adirẹsi ọpọ rii", "mweb_confirmed": "Jẹrisi Mweb", @@ -590,6 +605,7 @@ "rep_warning_sub": "Aṣoju rẹ ko han lati wa ni iduro to dara. Fọwọ ba ibi lati yan ọkan titun kan", "repeat_wallet_password": "Tun ọrọ igbaniwọle apamọwọ naa", "repeated_password_is_incorrect": "Ọrọ igbaniwọle tun jẹ aṣiṣe. Jọwọ tun ọrọigbaniwọle apamọwọ lẹẹkansi.", + "requested": "Beere fun", "require_for_adding_contacts": "Beere fun fifi awọn olubasọrọ kun", "require_for_all_security_and_backup_settings": "Beere fun gbogbo aabo ati awọn eto afẹyinti", "require_for_assessing_wallet": "Beere fun wiwọle si apamọwọ", @@ -651,6 +667,8 @@ "second_intro_content": "Àdírẹ́sì kan tó dá lórí emójì tó kì í jọra ni Yat yín. Ó rọ́pò gbogbo àwọn àdírẹ́sì gígùn yín tó dá lórí ìlà mẹ́rìndínlógún ti gbogbo àwọn iye owó yín.", "second_intro_title": "Àdírẹ́sì kan t'á dá láti emójì tó pàṣẹ gbogbo ohun wà", "security_and_backup": "Ìṣọ́ àti ẹ̀dà nípamọ́", + "security_risk": "Ewu aabo", + "security_risk_description": "Aaye yii jẹ aami bi aiṣe nipasẹ awọn olupese aabo pupọ. Lọ kuro lẹsẹkẹsẹ lati daabo bo awọn ohun-ini rẹ.", "seed_alert_back": "Padà sọ́dọ̀", "seed_alert_content": "Hóró ni ọ̀nà nìkan kí ṣẹ̀dà àpamọ́wọ́ yín. Ṣé ẹ ti kọ ọ́ sílẹ̀?", "seed_alert_title": "Ẹ wo", @@ -765,7 +783,9 @@ "show_keys": "Wo hóró / àwọn kọ́kọ́rọ́", "show_market_place": "Wa Sopọ Pataki", "show_seed": "Wo hóró", + "sign_all": "Wọlé gbogbo", "sign_message": "Ifiranṣẹ Ami", + "sign_one": "Mikọ", "sign_up": "Forúkọ sílẹ̀", "sign_verify_message": "Ami tabi ṣayẹwo ifiranṣẹ", "sign_verify_message_sub": "Wọle tabi ṣayẹwo ifiranṣẹ kan nipa lilo bọtini ikọkọ rẹ", @@ -799,6 +819,7 @@ "subaddress_title": "Àkọsílẹ̀ ni nínú àwọn àdírẹ́sì tíwọn rẹ̀lẹ̀", "subaddresses": "Àwọn àdírẹ́sì kékeré", "submit_request": "Ṣé ìbéèrè", + "success": "Aṣeyọri", "successful": "Aseyori", "support_description_guides": "Iwe ati atilẹyin fun awọn ọran ti o wọpọ", "support_description_live_chat": "Free ati sare! Ti oṣiṣẹ awọn aṣoju wa lati ṣe iranlọwọ", @@ -806,6 +827,7 @@ "support_title_guides": "Awọn iwe apamọwọ oyinbo akara oyinbo", "support_title_live_chat": "Atilẹyin ifiwe", "support_title_other_links": "Awọn ọna asopọ atilẹyin miiran", + "supported": "Ni atilẹyin", "swap": "Eepo", "sweeping_wallet": "Fi owo iwe iwe wofo", "sweeping_wallet_alert": "Yio kọja pada si ikan yii. Kì yoo daadaa leede yii tabi owo ti o ti fi se iwe iwe naa yoo gbe.", @@ -838,6 +860,7 @@ "thorchain_taproot_address_not_supported": "Olupese Trockchain ko ṣe atilẹyin awọn adirẹsi Taproot. Jọwọ yi adirẹsi pada tabi yan olupese ti o yatọ.", "time": "${minutes}ìṣj ${seconds}ìṣs", "tip": "Owó àfikún:", + "to": "Si", "today": "Lénìí", "token_contract_address": "Àmi guide adirẹsi", "token_decimal": "Àmi eleemewa", @@ -905,6 +928,7 @@ "transaction_sent_notice": "Tí aṣàfihàn kò bá tẹ̀síwájú l'áàárín ìṣẹ́jú kan, ẹ tọ́ olùṣèwádìí àkójọpọ̀ àti ímeèlì yín wò.", "transactions": "Àwọn àránṣẹ́", "transactions_by_date": "Àwọn àránṣẹ́ t'á ti fi aago ṣa", + "transport_type": "Iru irinna", "trongrid_history": "Itan Trongrid", "trusted": "A ti fọkàn ẹ̀ tán", "tx_commit_exception_no_dust_on_change": "Iṣowo naa ti kọ pẹlu iye yii. Pẹlu awọn owó wọnyi o le firanṣẹ ${min} laisi ayipada tabi ${max} ni iyipada iyipada.", @@ -933,6 +957,7 @@ "unspent_coins_details_title": "Àwọn owó ẹyọ t'á kò tí ì san", "unspent_coins_title": "Àwọn owó ẹyọ t'á kò tí ì san", "unsupported_asset": "A ko ṣe atilẹyin iṣẹ yii fun dukia yii. Jọwọ ṣẹda tabi yipada si apamọwọ iru dukia atilẹyin.", + "update_session": "Apeere igba", "uptime": "Iduro", "upto": "kò tóbi ju ${value}", "usb": "USB", @@ -942,6 +967,7 @@ "use_ssl": "Lo SSL", "use_suggested": "Lo àbá", "use_testnet": "Lo tele", + "user_rejected_method": "Ọna ti o kọ olumulo", "value": "Iye", "value_type": "Iru iye", "variable_pair_not_supported": "A kì í ṣe k'á fi àwọn ilé pàṣípààrọ̀ yìí ṣe pàṣípààrọ̀ irú owó méji yìí", @@ -1025,5 +1051,6 @@ "you_will_get": "Ṣe pàṣípààrọ̀ sí", "you_will_receive_estimated_amount": "Iwọ yoo gba ( excimated )", "you_will_send": "Ṣe pàṣípààrọ̀ láti", + "youCanGoBackToYourDapp": "O le pada si tapla rẹ bayi", "yy": "Ọd" } \ No newline at end of file diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 089d4bd41..d75b2e3fb 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -53,6 +53,7 @@ "anonpay_description": "生成 ${type}。收款人可以使用任何受支持的加密货币 ${method},您将在此钱包中收到资金。", "apk_update": "APK更新", "approve": "批准", + "approve_request": "批准请求", "arrive_in_this_address": "${currency} ${tag}将到达此地址", "ascending": "上升", "ask_each_time": "每次问", @@ -116,9 +117,12 @@ "camera_consent": "${provider} 将使用您的相机拍摄图像以供识别之用。请查看他们的隐私政策了解详情。", "camera_permission_is_required": "需要相机许可。\n请从应用程序设置中启用它。", "cancel": "取消", + "cannot_verify": "无法验证", + "cannot_verify_description": "该域无法验证。批准之前,请仔细检查请求。", "card_address": "地址:", "cardholder_agreement": "持卡人协议", "cards": "卡片", + "chain_id": "连锁ID", "chains": "链条", "change": "更改", "change_backup_password_alert": "您以前的备份文件将无法使用新的备份密码導入。 新的备份密码将仅用于新的备份文件。 您确定要更改备份密码吗?", @@ -174,6 +178,7 @@ "connect_yats": "连接 Yats", "connect_your_hardware_wallet": "使用蓝牙或USB连接硬件钱包", "connect_your_hardware_wallet_ios": "使用蓝牙连接硬件钱包", + "connected": "连接", "connection_sync": "连接和同步", "connectWalletPrompt": "将您的钱包与 WalletConnect 连接以进行交易", "contact": "联系", @@ -244,6 +249,7 @@ "disableBatteryOptimization": "禁用电池优化", "disableBatteryOptimizationDescription": "您是否要禁用电池优化以使背景同步更加自由,平稳地运行?", "disabled": "禁用", + "disconnect_session": "断开会话", "discount": "节省 ${value}%", "display_settings": "显示设置", "displayable": "可显示", @@ -252,6 +258,8 @@ "do_not_share_warning_text": "请勿与其他任何人分享这些信息,包括支持人员。\n\n您的资金可能而且将会被盗!", "do_not_show_me": "不再提示", "domain_looks_up": "域名查找", + "domain_mismatch": "域不匹配", + "domain_mismatch_description": "该网站的域与此请求的发件人的发件人不匹配。批准可能导致资金损失。", "donation_link_details": "捐赠链接详情", "e_sign_consent": "电子签名同意", "edit": "编辑", @@ -298,6 +306,7 @@ "error_text_template": "模板名称和地址不能包含`,' \" 符号\n并且必须在1到106个字符之间", "error_text_wallet_name": "钱包名称只能包含字母、数字、_ - 符号\n并且长度必须在 1 到 33 个字符之间", "error_text_xmr": "XMR值不能超过可用余额.\n小数位数必须小于或等于12", + "error_while_processing": "发动机时发生错误", "errorGettingCredentials": "失败:获取凭据时出错", "errorSigningTransaction": "签署交易时发生错误", "estimated": "估计值", @@ -323,6 +332,7 @@ "export_backup": "导出备份", "export_logs": "导出日志", "export_outputs": "导出输出", + "extend_session": "扩展会话", "extra_id": "额外ID:", "extracted_address_content": "您将汇款至\n${recipient_name}", "failed_authentication": "身份验证失败. ${state_error}", @@ -344,10 +354,12 @@ "forgot_password": "忘记密码", "freeze": "凍結", "frequently_asked_questions": "常见问题", + "from": "从", "frozen": "凍結的", "frozen_balance": "冷冻平衡", "full_balance": "全部余额", "gas_exceeds_allowance": "交易要求的气体超出了津贴。", + "gas_price": "汽油价格", "generate_name": "生成名称", "generating_gift_card": "生成礼品卡", "generating_transaction": "生成交易", @@ -440,6 +452,8 @@ "memo": "备忘录:", "message": "信息", "message_verified": "该消息已成功验证", + "messages": "消息", + "method": "方法", "methods": "方法", "min_amount": "最小值: ${value}", "min_value": "最小: ${value} ${currency}", @@ -452,6 +466,7 @@ "monero_dark_theme": "门罗币深色主题", "monero_light_theme": "门罗币浅色主题", "moonpay_alert_text": "金额的价值必须大于或等于 ${minAmount} ${fiatCurrency}", + "moralis_nft_error": "获取NFT时发生错误。请检查您的互联网连接,然后重试。", "more_options": "更多选项", "multiple_addresses_detected": "检测到的多个地址", "mweb_confirmed": "确认的MWEB", @@ -589,6 +604,7 @@ "rep_warning_sub": "您的代表似乎并不信誉良好。点击这里选择一个新的", "repeat_wallet_password": "重复钱包密码", "repeated_password_is_incorrect": "重复密码不正确。请再次重复钱包密码。", + "requested": "要求", "require_for_adding_contacts": "需要添加联系人", "require_for_all_security_and_backup_settings": "需要所有安全和备份设置", "require_for_assessing_wallet": "需要访问钱包", @@ -650,6 +666,8 @@ "second_intro_content": "您的 Yat 是一個唯一的表情符號地址,可替換您所有貨幣的所有長十六進制地址。", "second_intro_title": "一個表情符號地址來統治他們", "security_and_backup": "安全和备份", + "security_risk": "安全风险", + "security_risk_description": "该域被多个安全提供者标记为不安全。立即离开以保护您的资产。", "seed_alert_back": "返回", "seed_alert_content": "种子是恢复钱包的唯一方法。记住了吗?", "seed_alert_title": "注意", @@ -764,7 +782,9 @@ "show_keys": "显示种子/密钥", "show_market_place": "显示市场", "show_seed": "显示种子", + "sign_all": "签署全部", "sign_message": "标志消息", + "sign_one": "签一个", "sign_up": "注册", "sign_verify_message": "签名或验证消息", "sign_verify_message_sub": "使用您的私钥签名或验证消息", @@ -798,6 +818,7 @@ "subaddress_title": "子地址列表", "subaddresses": "子地址", "submit_request": "提交请求", + "success": "成功", "successful": "成功的", "support_description_guides": "对常见问题的文档和支持", "support_description_live_chat": "免费快速!训练有素的支持代表可以协助", @@ -805,6 +826,7 @@ "support_title_guides": "蛋糕钱包文档", "support_title_live_chat": "实时支持", "support_title_other_links": "其他支持链接", + "supported": "支持", "swap": "交换", "sweeping_wallet": "扫一扫钱包", "sweeping_wallet_alert": "\n这应该不会花很长时间。请勿离开此屏幕,否则可能会丢失所掠取的资金", @@ -837,6 +859,7 @@ "thorchain_taproot_address_not_supported": "Thorchain提供商不支持Taproot地址。请更改地址或选择其他提供商。", "time": "${minutes}m ${seconds}s", "tip": "提示:", + "to": "到", "today": "今天", "token_contract_address": "代币合约地址", "token_decimal": "令牌十进制", @@ -904,6 +927,7 @@ "transaction_sent_notice": "如果屏幕在 1 分钟后没有继续,请检查区块浏览器和您的电子邮件。", "transactions": "交易情况", "transactions_by_date": "按日期交易", + "transport_type": "运输类型", "trongrid_history": "Trongrid历史", "trusted": "值得信赖", "tx_commit_exception_no_dust_on_change": "交易被此金额拒绝。使用这些硬币,您可以发送${min}无需更改或返回${max}的变化。", @@ -932,6 +956,7 @@ "unspent_coins_details_title": "未使用代幣詳情", "unspent_coins_title": "未使用的硬幣", "unsupported_asset": "我们不支持针对该资产采取此操作。请创建或切换到支持的资产类型的钱包。", + "update_session": "更新会话", "uptime": "正常运行时间", "upto": "最高 ${value}", "usb": "USB", @@ -941,6 +966,7 @@ "use_ssl": "使用SSL", "use_suggested": "使用建议", "use_testnet": "使用TestNet", + "user_rejected_method": "用户拒绝方法", "value": "价值", "value_type": "值类型", "variable_pair_not_supported": "所选交易所不支持此变量对", @@ -1024,5 +1050,6 @@ "you_will_get": "转换到", "you_will_receive_estimated_amount": "您将收到(估计的)", "you_will_send": "转换自", + "youCanGoBackToYourDapp": "您现在可以回到DAPP", "yy": "YY" } \ No newline at end of file From 02e74b5997b94b8fe86c4826f326a78075d09901 Mon Sep 17 00:00:00 2001 From: cyan Date: Thu, 24 Apr 2025 19:06:43 +0200 Subject: [PATCH 052/142] CW-1000 Background sync improvements (#2142) * feat: background sync improvements - dev options on ci build cherrypick - add permissions for background sync to AndroidManifestBase - enable desugaring + update java compatibility to 17 - update walletconnect_flutter_v2 - update ens_dart - update nostr_tools - add notification for new transactions found in background - expose more settings from flutter_daemon in UI - remove battery optimization setting when it's already disabled - fix notification permission handling - fix background sync last trigger saving - prevent notifications from being duplicated * potential fix for multiple notifications firing for the same tx * improve logging in background sync * ui improvements to ignore battery optimization popup * feat: logs for bg sync disable decred bgsync * fix: call store() directly to be sure that it is writing the data * chore: rename logs to background sync logs * Update lib/view_model/dashboard/dashboard_view_model.dart Co-authored-by: Omar Hatem * chore: remove unused key --------- Co-authored-by: Omar Hatem --- android/app/build.gradle | 9 + android/app/src/main/AndroidManifestBase.xml | 8 + cw_monero/lib/monero_wallet.dart | 7 +- cw_shared_external/android/build.gradle | 2 +- lib/core/background_sync.dart | 112 ++++- lib/core/wallet_loading_service.dart | 5 +- lib/di.dart | 35 +- lib/entities/preferences_key.dart | 2 + lib/router.dart | 11 + lib/routes.dart | 4 + .../dev/background_sync_logs_page.dart | 314 ++++++++++++++ .../screens/dev/shared_preferences_page.dart | 404 ++++++++++++++++++ .../settings/background_sync_page.dart | 107 +++-- .../settings/connection_sync_page.dart | 2 +- .../screens/settings/other_settings_page.dart | 14 +- .../dashboard/dashboard_view_model.dart | 85 +++- .../dev/background_sync_logs_view_model.dart | 44 ++ lib/view_model/dev/shared_preferences.dart | 92 ++++ lib/view_model/send/send_view_model.dart | 4 + pubspec_base.yaml | 12 +- res/values/strings_ar.arb | 6 + res/values/strings_bg.arb | 6 + res/values/strings_cs.arb | 6 + res/values/strings_de.arb | 6 + res/values/strings_en.arb | 6 + res/values/strings_es.arb | 6 + res/values/strings_fr.arb | 6 + res/values/strings_ha.arb | 6 + res/values/strings_hi.arb | 6 + res/values/strings_hr.arb | 6 + res/values/strings_hy.arb | 6 + res/values/strings_id.arb | 6 + res/values/strings_it.arb | 6 + res/values/strings_ja.arb | 6 + res/values/strings_ko.arb | 6 + res/values/strings_my.arb | 6 + res/values/strings_nl.arb | 6 + res/values/strings_pl.arb | 6 + res/values/strings_pt.arb | 6 + res/values/strings_ru.arb | 6 + res/values/strings_th.arb | 6 + res/values/strings_tl.arb | 6 + res/values/strings_tr.arb | 6 + res/values/strings_uk.arb | 6 + res/values/strings_ur.arb | 6 + res/values/strings_vi.arb | 6 + res/values/strings_yo.arb | 6 + res/values/strings_zh.arb | 6 + 48 files changed, 1373 insertions(+), 68 deletions(-) create mode 100644 lib/src/screens/dev/background_sync_logs_page.dart create mode 100644 lib/src/screens/dev/shared_preferences_page.dart create mode 100644 lib/view_model/dev/background_sync_logs_view_model.dart create mode 100644 lib/view_model/dev/shared_preferences.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index c5a80bcea..4a8045bb3 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -42,6 +42,14 @@ android { disable 'InvalidPackage' } + compileOptions { + coreLibraryDesugaringEnabled true + + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + + namespace "com.cakewallet.cake_wallet" defaultConfig { @@ -91,6 +99,7 @@ dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5' } configurations { implementation.exclude module:'proto-google-common-protos' diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index 4f15370c3..280a45b3c 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -24,6 +24,10 @@ + + + + + stopSync() async { if (isBackgroundSyncRunning) { printV("Stopping background sync"); - await save(); + monero.Wallet_store(wptr!); monero.Wallet_stopBackgroundSync(wptr!, ''); + monero_wallet.store(); isBackgroundSyncRunning = false; } await save(); @@ -268,9 +269,9 @@ abstract class MoneroWalletBase extends WalletBase stopBackgroundSync(String password) async { if (isBackgroundSyncRunning) { printV("Stopping background sync"); - await save(); + monero.Wallet_store(wptr!); monero.Wallet_stopBackgroundSync(wptr!, password); - await save(); + monero.Wallet_store(wptr!); isBackgroundSyncRunning = false; } } diff --git a/cw_shared_external/android/build.gradle b/cw_shared_external/android/build.gradle index 8d2b1b13d..360f518ff 100644 --- a/cw_shared_external/android/build.gradle +++ b/cw_shared_external/android/build.gradle @@ -9,7 +9,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.3.0' + classpath 'com.android.tools.build:gradle:8.7.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/lib/core/background_sync.dart b/lib/core/background_sync.dart index 0098b62b6..12eb81f99 100644 --- a/lib/core/background_sync.dart +++ b/lib/core/background_sync.dart @@ -1,26 +1,101 @@ import 'dart:async'; -import 'dart:math'; +import 'dart:io'; import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/core/wallet_loading_service.dart'; import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; import 'package:cw_core/sync_status.dart'; +import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:flutter/foundation.dart'; -import 'package:http/http.dart' as http; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:shared_preferences/shared_preferences.dart'; class BackgroundSync { + final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin(); + bool _isInitialized = false; + + Future _initializeNotifications() async { + if (_isInitialized) return; + + const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher'); + + const iosSettings = DarwinInitializationSettings( + requestAlertPermission: true, + requestBadgePermission: true, + requestSoundPermission: true, + ); + + const initializationSettings = InitializationSettings( + android: androidSettings, + iOS: iosSettings, + ); + + await _notificationsPlugin.initialize(initializationSettings); + _isInitialized = true; + } + + Future requestPermissions() async { + if (Platform.isIOS || Platform.isMacOS) { + return await _notificationsPlugin + .resolvePlatformSpecificImplementation() + ?.requestPermissions( + alert: true, + badge: true, + sound: true, + ) ?? false; + } else if (Platform.isAndroid) { + return await _notificationsPlugin + .resolvePlatformSpecificImplementation() + ?.areNotificationsEnabled() ?? false; + } + return false; + } + + Future showNotification(String title, String content) async { + await _initializeNotifications(); + final hasPermission = await requestPermissions(); + + if (!hasPermission) { + printV('Notification permissions not granted'); + return; + } + + const androidDetails = AndroidNotificationDetails( + 'transactions', + 'Transactions', + channelDescription: 'Channel for notifications about transactions', + importance: Importance.defaultImportance, + priority: Priority.defaultPriority, + ); + + const iosDetails = DarwinNotificationDetails(); + + const notificationDetails = NotificationDetails( + android: androidDetails, + iOS: iosDetails, + ); + + await _notificationsPlugin.show( + DateTime.now().millisecondsSinceEpoch.hashCode, + title, + content, + notificationDetails, + ); + } + Future sync() async { printV("Background sync started"); - await _syncMonero(); + await _syncWallets(); printV("Background sync completed"); } - Future _syncMonero() async { + Future _syncWallets() async { final walletLoadingService = getIt.get(); final walletListViewModel = getIt.get(); final settingsStore = getIt.get(); @@ -28,10 +103,10 @@ class BackgroundSync { final List moneroWallets = walletListViewModel.wallets .where((element) => !element.isHardware) - .where((element) => [WalletType.monero].contains(element.type)) + .where((element) => ![WalletType.haven, WalletType.decred].contains(element.type)) .toList(); for (int i = 0; i < moneroWallets.length; i++) { - final wallet = await walletLoadingService.load(moneroWallets[i].type, moneroWallets[i].name); + final wallet = await walletLoadingService.load(moneroWallets[i].type, moneroWallets[i].name, isBackground: true); int syncedTicks = 0; final keyService = getIt.get(); @@ -75,7 +150,7 @@ class BackgroundSync { } else { syncedTicks = 0; } - if (kDebugMode) { + if (FeatureFlag.hasDevOptions) { if (syncStatus is SyncingSyncStatus) { final blocksLeft = syncStatus.blocksLeft; printV("$blocksLeft Blocks Left"); @@ -100,6 +175,27 @@ class BackgroundSync { } } } + final txs = wallet.transactionHistory; + final sortedTxs = txs.transactions.values.toList()..sort((a, b) => a.date.compareTo(b.date)); + final sharedPreferences = await SharedPreferences.getInstance(); + for (final tx in sortedTxs) { + final lastTriggerString = sharedPreferences.getString(PreferencesKey.backgroundSyncLastTrigger(wallet.name)); + final lastTriggerDate = lastTriggerString != null + ? DateTime.parse(lastTriggerString) + : DateTime.now(); + final keys = sharedPreferences.getKeys(); + if (tx.date.isBefore(lastTriggerDate)) { + printV("w: ${wallet.name}, tx: ${tx.date} is before $lastTriggerDate (lastTriggerString: $lastTriggerString) (k: ${keys.length})"); + continue; + } + await sharedPreferences.setString(PreferencesKey.backgroundSyncLastTrigger(wallet.name), tx.date.add(Duration(minutes: 1)).toIso8601String()); + final action = tx.direction == TransactionDirection.incoming ? "Received" : "Sent"; + if (sharedPreferences.getBool(PreferencesKey.backgroundSyncNotificationsEnabled) ?? false) { + await showNotification("$action ${wallet.currency.fullName} in ${wallet.name}", "${tx.amountFormatted()}"); + } + printV("${wallet.currency.fullName} in ${wallet.name}: TX: ${tx.date} ${tx.amount} ${tx.direction}"); + } + wallet.id; await wallet.stopBackgroundSync(await keyService.getWalletPassword(walletName: wallet.name)); await wallet.close(shouldCleanup: true); } diff --git a/lib/core/wallet_loading_service.dart b/lib/core/wallet_loading_service.dart index f1996bae8..49f366808 100644 --- a/lib/core/wallet_loading_service.dart +++ b/lib/core/wallet_loading_service.dart @@ -52,8 +52,11 @@ class WalletLoadingService { } } - Future load(WalletType type, String name, {String? password}) async { + Future load(WalletType type, String name, {String? password, bool isBackground = false}) async { try { + if (!isBackground) { + await sharedPreferences.setString(PreferencesKey.backgroundSyncLastTrigger(name), DateTime.now().toIso8601String()); + } final walletService = walletServiceFactory.call(type); final walletPassword = password ?? (await keyService.getWalletPassword(walletName: name)); final wallet = await walletService.openWallet(name, walletPassword); diff --git a/lib/di.dart b/lib/di.dart index 87092a7eb..05f2c3f77 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -33,11 +33,13 @@ import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart'; import 'package:cake_wallet/haven/cw_haven.dart'; import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart'; import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart'; +import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart'; import 'package:cake_wallet/src/screens/settings/background_sync_page.dart'; import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart'; import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart'; import 'package:cake_wallet/view_model/dev/monero_background_sync.dart'; +import 'package:cake_wallet/view_model/dev/shared_preferences.dart'; import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart'; @@ -266,6 +268,8 @@ import 'buy/kryptonim/kryptonim.dart'; import 'buy/meld/meld_buy_provider.dart'; import 'src/screens/buy/buy_sell_page.dart'; import 'cake_pay/cake_pay_payment_credantials.dart'; +import 'package:cake_wallet/view_model/dev/background_sync_logs_view_model.dart'; +import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart'; final getIt = GetIt.instance; @@ -883,9 +887,8 @@ Future setup({ nanoAccountCreationViewModel: getIt.get(param1: account))); - getIt.registerFactory(() { - return DisplaySettingsViewModel(getIt.get()); - }); + getIt.registerFactory(() => + DisplaySettingsViewModel(getIt.get())); getIt.registerFactory(() => SilentPaymentsSettingsViewModel(getIt.get(), getIt.get().wallet!)); @@ -893,22 +896,20 @@ Future setup({ getIt.registerFactory( () => MwebSettingsViewModel(getIt.get(), getIt.get().wallet!)); - getIt.registerFactory(() { - return PrivacySettingsViewModel(getIt.get(), getIt.get().wallet!); - }); + getIt.registerFactory(() => + PrivacySettingsViewModel(getIt.get(), getIt.get().wallet!)); getIt.registerFactory(() => TrocadorExchangeProvider()); getIt.registerFactory(() => TrocadorProvidersViewModel( getIt.get(), getIt.get())); - getIt.registerFactory(() { - return OtherSettingsViewModel(getIt.get(), getIt.get().wallet!, - getIt.get());}); + getIt.registerFactory(() => + OtherSettingsViewModel(getIt.get(), getIt.get().wallet!, + getIt.get())); - getIt.registerFactory(() { - return SecuritySettingsViewModel(getIt.get()); - }); + getIt.registerFactory(() => + SecuritySettingsViewModel(getIt.get())); getIt.registerFactory(() => WalletSeedViewModel(getIt.get().wallet!)); @@ -916,6 +917,8 @@ Future setup({ getIt.registerFactory(() => DevMoneroBackgroundSync(getIt.get().wallet!)); + getIt.registerFactory(() => DevSharedPreferences()); + getIt.registerFactoryParam((bool isWalletCreated, _) => WalletSeedPage(getIt.get(), isNewWalletCreated: isWalletCreated)); @@ -1456,6 +1459,14 @@ Future setup({ getIt.registerFactory(() => SeedVerificationPage(getIt.get())); getIt.registerFactory(() => DevMoneroBackgroundSyncPage(getIt.get())); + getIt.registerFactory(() => DevMoneroCallProfilerPage()); + + getIt.registerFactory(() => DevSharedPreferencesPage(getIt.get())); + + getIt.registerFactory(() => BackgroundSyncLogsViewModel()); + + getIt.registerFactory(() => DevBackgroundSyncLogsPage(getIt.get())); + _isSetupFinished = true; } diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index 9e889ff46..59989ba14 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -105,4 +105,6 @@ class PreferencesKey { static const walletConnectPairingTopicsList = 'wallet_connect_pairing_topics_list'; static String walletConnectPairingTopicsListForWallet(String publicKey) => '${PreferencesKey.walletConnectPairingTopicsList}_${publicKey}'; + static String backgroundSyncLastTrigger(String walletId) => 'background_sync_last_trigger_${walletId}'; + static const backgroundSyncNotificationsEnabled = 'background_sync_notifications_enabled'; } diff --git a/lib/router.dart b/lib/router.dart index 7fea601f8..dfd163122 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -36,6 +36,8 @@ import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart'; import 'package:cake_wallet/src/screens/dashboard/sign_page.dart'; import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart'; import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart'; +import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart'; +import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart'; import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart'; import 'package:cake_wallet/src/screens/exchange/exchange_page.dart'; import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart'; @@ -836,6 +838,15 @@ Route createRoute(RouteSettings settings) { return MaterialPageRoute( builder: (_) => getIt.get(), ); + case Routes.devSharedPreferences: + return MaterialPageRoute( + builder: (_) => getIt.get(), + ); + + case Routes.devBackgroundSyncLogs: + return MaterialPageRoute( + builder: (_) => getIt.get(), + ); case Routes.devMoneroCallProfiler: return MaterialPageRoute( diff --git a/lib/routes.dart b/lib/routes.dart index f7f3e2f6f..a9a637c75 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -111,8 +111,12 @@ class Routes { static const importNFTPage = '/import_nft_page'; static const torPage = '/tor_page'; static const backgroundSync = '/background_sync'; + static const devMoneroBackgroundSync = '/dev/monero_background_sync'; static const devMoneroCallProfiler = '/dev/monero_call_profiler'; + static const devSharedPreferences = '/dev/shared_preferences'; + static const devBackgroundSyncLogs = '/dev/background_sync_logs'; + static const signPage = '/sign_page'; static const connectDevices = '/device/connect'; static const urqrAnimatedPage = '/urqr/animated_page'; diff --git a/lib/src/screens/dev/background_sync_logs_page.dart b/lib/src/screens/dev/background_sync_logs_page.dart new file mode 100644 index 000000000..b269083f2 --- /dev/null +++ b/lib/src/screens/dev/background_sync_logs_page.dart @@ -0,0 +1,314 @@ +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/view_model/dev/background_sync_logs_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:intl/intl.dart'; + +class DevBackgroundSyncLogsPage extends BasePage { + final BackgroundSyncLogsViewModel viewModel; + + DevBackgroundSyncLogsPage(this.viewModel) { + viewModel.loadLogs(); + } + + @override + String? get title => "[dev] background sync logs"; + + @override + Widget? trailing(BuildContext context) { + return IconButton( + icon: Icon(Icons.refresh), + onPressed: () => viewModel.loadLogs(), + ); + } + + @override + Widget body(BuildContext context) { + return Observer( + builder: (_) { + if (viewModel.isLoading) { + return Center(child: CircularProgressIndicator()); + } + + if (viewModel.error != null) { + return Center(child: Text("Error: ${viewModel.error}")); + } + + if (viewModel.logData == null) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("No logs loaded"), + SizedBox(height: 16), + ElevatedButton( + onPressed: () => viewModel.loadLogs(), + child: Text("Load Logs"), + ), + ], + ), + ); + } + + return DefaultTabController( + length: 2, + child: Column( + children: [ + TabBar( + tabs: [ + Tab(text: "Logs (${viewModel.logs.length})"), + Tab(text: "Sessions (${viewModel.sessions.length})"), + ], + ), + Expanded( + child: TabBarView( + children: [ + _buildLogsTab(context), + _buildSessionsTab(context), + ], + ), + ), + _buildActionButtons(context), + ], + ), + ); + }, + ); + } + + Widget _buildLogsTab(BuildContext context) { + final logs = viewModel.logs; + if (logs.isEmpty) { + return Center(child: Text("No logs available")); + } + + final dateFormat = DateFormat('yyyy-MM-dd HH:mm:ss.SSS'); + + return ListView.builder( + itemCount: logs.length, + itemBuilder: (context, index) { + final log = logs[index]; + return ListTile( + title: Text( + log.message, + style: TextStyle( + fontSize: 14, + fontFamily: 'Monospace', + ), + ), + subtitle: Text( + '${dateFormat.format(log.timestamp)} | ${log.level}' + + (log.sessionId != null ? ' | Session: ${log.sessionId}' : ''), + style: TextStyle( + fontSize: 12, + color: _getLevelColor(log.level), + ), + ), + dense: true, + onTap: () { + Clipboard.setData(ClipboardData(text: log.message)); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Log message copied to clipboard')), + ); + }, + onLongPress: () { + Clipboard.setData(ClipboardData( + text: '${dateFormat.format(log.timestamp)} [${log.level}] ${log.message}')); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Full log entry copied to clipboard')), + ); + }, + tileColor: index % 2 == 0 ? Colors.transparent : Colors.black.withOpacity(0.03), + ); + }, + ); + } + + Widget _buildSessionsTab(BuildContext context) { + final sessions = viewModel.sessions; + if (sessions.isEmpty) { + return Center(child: Text("No sessions available")); + } + + final dateFormat = DateFormat('yyyy-MM-dd HH:mm:ss'); + + return ListView.builder( + itemCount: sessions.length, + itemBuilder: (context, index) { + final session = sessions[index]; + final isActive = session.endTime == null; + + return ExpansionTile( + title: Text( + session.name, + style: TextStyle( + fontWeight: FontWeight.bold, + color: isActive ? Colors.green : null, + ), + ), + subtitle: Text( + 'ID: ${session.id} | Started: ${dateFormat.format(session.startTime)}', + style: TextStyle(fontSize: 12), + ), + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Start: ${session.startTime.toString()}'), + if (session.endTime != null) + Text('End: ${session.endTime.toString()}'), + if (session.duration != null) + Text('Duration: ${_formatDuration(session.duration!)}'), + SizedBox(height: 8), + _buildSessionLogs(context, session.id), + ], + ), + ), + ], + ); + }, + ); + } + + Widget _buildSessionLogs(BuildContext context, int sessionId) { + final sessionLogs = viewModel.logs + .where((log) => log.sessionId == sessionId) + .toList(); + + if (sessionLogs.isEmpty) { + return Text('No logs for this session'); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Session Logs (${sessionLogs.length}):', + style: TextStyle(fontWeight: FontWeight.bold)), + SizedBox(height: 8), + Container( + height: 200, + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.05), + borderRadius: BorderRadius.circular(4), + ), + child: ListView.builder( + itemCount: sessionLogs.length, + itemBuilder: (context, index) { + final log = sessionLogs[index]; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + child: Text( + '[${log.level}] ${log.message}', + style: TextStyle( + fontSize: 12, + fontFamily: 'Monospace', + color: _getLevelColor(log.level), + ), + ), + ); + }, + ), + ), + ], + ); + } + + Widget _buildActionButtons(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton.icon( + icon: Icon(Icons.refresh), + label: Text('Refresh'), + onPressed: () => viewModel.loadLogs(), + ), + ElevatedButton.icon( + icon: Icon(Icons.copy), + label: Text('Copy All'), + onPressed: () => _copyAllLogs(context), + ), + ElevatedButton.icon( + icon: Icon(Icons.delete), + label: Text('Clear'), + onPressed: () => _confirmClearLogs(context), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + foregroundColor: Colors.white, + ), + ), + ], + ), + ); + } + + void _copyAllLogs(BuildContext context) { + if (viewModel.logData == null) return; + + final buffer = StringBuffer(); + final dateFormat = DateFormat('yyyy-MM-dd HH:mm:ss.SSS'); + + for (final log in viewModel.logs) { + buffer.writeln('${dateFormat.format(log.timestamp)} [${log.level}] ${log.message}'); + } + + Clipboard.setData(ClipboardData(text: buffer.toString())); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('All logs copied to clipboard')), + ); + } + + void _confirmClearLogs(BuildContext context) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Clear Logs'), + content: Text('Are you sure you want to clear the logs display?'), + actions: [ + TextButton( + child: Text('Cancel'), + onPressed: () => Navigator.of(context).pop(), + ), + TextButton( + child: Text('Clear'), + style: TextButton.styleFrom(foregroundColor: Colors.red), + onPressed: () { + viewModel.clearLogs(); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + Color _getLevelColor(String level) { + switch (level.toLowerCase()) { + case 'error': + return Colors.red; + case 'warning': + return Colors.orange; + case 'info': + return Colors.blue; + case 'debug': + return Colors.green; + case 'trace': + return Colors.purple; + default: + return Colors.grey; + } + } + + String _formatDuration(Duration duration) { + String twoDigits(int n) => n.toString().padLeft(2, '0'); + String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60)); + String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60)); + return '${twoDigits(duration.inHours)}:$twoDigitMinutes:$twoDigitSeconds'; + } +} \ No newline at end of file diff --git a/lib/src/screens/dev/shared_preferences_page.dart b/lib/src/screens/dev/shared_preferences_page.dart new file mode 100644 index 000000000..cb21aaa78 --- /dev/null +++ b/lib/src/screens/dev/shared_preferences_page.dart @@ -0,0 +1,404 @@ +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/view_model/dev/shared_preferences.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; + +class DevSharedPreferencesPage extends BasePage { + final DevSharedPreferences viewModel; + + DevSharedPreferencesPage(this.viewModel); + + @override + String? get title => "[dev] shared preferences"; + + @override + Widget? trailing(BuildContext context) { + return IconButton( + icon: Icon(Icons.add), + onPressed: () => _showCreateDialog(context), + ); + } + + @override + Widget body(BuildContext context) { + return Observer( + builder: (_) { + if (viewModel.sharedPreferences == null) { + return Center(child: Text("No shared preferences found")); + } + final keys = viewModel.keys; + Map values = {}; + for (final key in keys) { + values[key] = viewModel.get(key); + } + Map types = {}; + for (final key in keys) { + types[key] = viewModel.getPreferenceType(key); + } + return ListView.builder( + itemCount: keys.length, + itemBuilder: (context, index) { + final key = keys[index]; + final type = types[key]!; + return ListTile( + onTap: () { + Clipboard.setData(ClipboardData(text: key + ": " + values[key].toString())); + }, + onLongPress: () { + _showEditDialog(context, key, type, values[key]); + }, + title: switch (type) { + PreferenceType.bool => Text(key, style: TextStyle(color: Colors.blue)), + PreferenceType.int => Text(key, style: TextStyle(color: Colors.green)), + PreferenceType.double => Text(key, style: TextStyle(color: Colors.yellow)), + PreferenceType.listString => Text(key, style: TextStyle(color: Colors.purple)), + PreferenceType.string => Text(key), + PreferenceType.unknown => Text(key), + }, + subtitle: switch (type) { + PreferenceType.bool => Text("bool: ${values[key]}"), + PreferenceType.int => Text("int: ${values[key]}"), + PreferenceType.double => Text("double: ${values[key]}"), + PreferenceType.listString => values[key].isEmpty as bool ? Text("listString: []") : Text("listString:\n- ${values[key].join("\n- ")}"), + PreferenceType.string => Text("string: ${values[key]}"), + PreferenceType.unknown => Text("UNKNOWN(${values[key].runtimeType}): ${values[key]}"), + }, + ); + }, + ); + }, + ); + } + + void _showEditDialog(BuildContext context, String key, PreferenceType type, dynamic currentValue) { + dynamic newValue = currentValue; + bool isListString = type == PreferenceType.listString; + List listItems = isListString ? List.from(currentValue as Iterable) : []; + TextEditingController textController = TextEditingController( + text: isListString ? '' : currentValue?.toString() ?? ''); + + showDialog( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (context, setState) { + return AlertDialog( + title: Text('Edit $key'), + content: SizedBox( + width: double.maxFinite, + height: double.maxFinite, + child: SingleChildScrollView( + child: _buildDialogContent( + type, + newValue, + listItems, + textController, + (value) => setState(() => newValue = value), + (items) => setState(() => listItems = items), + ), + ), + ), + actions: [ + TextButton( + child: Text('Delete'), + style: TextButton.styleFrom(foregroundColor: Colors.red), + onPressed: () { + _showDeleteConfirmation(context, key); + }, + ), + TextButton( + child: Text('Cancel'), + onPressed: () => Navigator.of(context).pop(), + ), + TextButton( + child: Text('Save'), + onPressed: () async { + if (_validateAndUpdateValue( + context, + type, + textController, + listItems, + (value) => newValue = value + )) { + await viewModel.set(key, type, newValue); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + }, + ); + } + + void _showDeleteConfirmation(BuildContext context, String key) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Delete Preference'), + content: Text('Are you sure you want to delete "$key"?'), + actions: [ + TextButton( + child: Text('Cancel'), + onPressed: () => Navigator.of(context).pop(), + ), + TextButton( + child: Text('Delete'), + style: TextButton.styleFrom(foregroundColor: Colors.red), + onPressed: () { + viewModel.delete(key); + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + Widget _buildDialogContent( + PreferenceType type, + dynamic value, + List listItems, + TextEditingController textController, + Function(dynamic) onValueChanged, + Function(List) onListChanged, + ) { + return switch (type) { + PreferenceType.bool => _buildBoolEditor(value as bool, onValueChanged), + PreferenceType.int => _buildNumberEditor(textController, 'Integer value', true), + PreferenceType.double => _buildNumberEditor(textController, 'Double value', false), + PreferenceType.string => _buildTextEditor(textController), + PreferenceType.listString => _buildListEditor(listItems, textController, onListChanged), + PreferenceType.unknown => Text('Cannot edit unknown type'), + }; + } + + Widget _buildBoolEditor(bool value, Function(bool) onChanged) { + return CheckboxListTile( + title: Text('Value'), + value: value, + onChanged: (newValue) { + if (newValue != null) onChanged(newValue); + }, + ); + } + + Widget _buildTextEditor(TextEditingController controller) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: controller, + decoration: InputDecoration(labelText: 'String value'), + maxLines: null, + ), + ], + ); + } + + Widget _buildNumberEditor(TextEditingController controller, String label, bool isInteger) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: controller, + decoration: InputDecoration(labelText: label), + keyboardType: isInteger + ? TextInputType.number + : TextInputType.numberWithOptions(decimal: true), + inputFormatters: isInteger + ? [FilteringTextInputFormatter.digitsOnly] + : [FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d*$'))], + ), + ], + ); + } + + Widget _buildListEditor( + List items, + TextEditingController controller, + Function(List) onListChanged, + ) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 200, + child: ReorderableListView( + shrinkWrap: true, + children: [ + for (int i = 0; i < items.length; i++) + ListTile( + key: Key('$i'), + title: Text(items[i]), + trailing: IconButton( + icon: Icon(Icons.delete), + onPressed: () { + final newList = List.from(items); + newList.removeAt(i); + onListChanged(newList); + }, + ), + ) + ], + onReorder: (int oldIndex, int newIndex) { + final newList = List.from(items); + if (oldIndex < newIndex) { + newIndex -= 1; + } + final item = newList.removeAt(oldIndex); + newList.insert(newIndex, item); + onListChanged(newList); + }, + ), + ), + Row( + children: [ + Expanded( + child: TextField( + controller: controller, + decoration: InputDecoration(labelText: 'New item'), + ), + ), + IconButton( + icon: Icon(Icons.add), + onPressed: () { + if (controller.text.isNotEmpty) { + final newList = List.from(items); + newList.add(controller.text); + onListChanged(newList); + controller.clear(); + } + }, + ), + ], + ), + ], + ); + } + + bool _validateAndUpdateValue( + BuildContext context, + PreferenceType type, + TextEditingController controller, + List listItems, + Function(dynamic) setNewValue, + ) { + switch (type) { + case PreferenceType.int: + if (controller.text.isNotEmpty) { + try { + setNewValue(int.parse(controller.text)); + } catch (e) { + _showErrorMessage(context, 'Invalid integer value'); + return false; + } + } + break; + case PreferenceType.double: + if (controller.text.isNotEmpty) { + try { + setNewValue(double.parse(controller.text)); + } catch (e) { + _showErrorMessage(context, 'Invalid double value'); + return false; + } + } + break; + case PreferenceType.string: + setNewValue(controller.text); + break; + case PreferenceType.listString: + setNewValue(listItems); + break; + default: + break; + } + return true; + } + + void _showErrorMessage(BuildContext context, String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message)), + ); + } + + void _showCreateDialog(BuildContext context) { + PreferenceType selectedType = PreferenceType.string; + TextEditingController keyController = TextEditingController(); + + showDialog( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (context, setState) { + return AlertDialog( + title: Text('Create Preference'), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: keyController, + decoration: InputDecoration(labelText: 'Preference Key'), + ), + SizedBox(height: 16), + DropdownButtonFormField( + value: selectedType, + decoration: InputDecoration(labelText: 'Type'), + items: [ + DropdownMenuItem(value: PreferenceType.string, child: Text('String')), + DropdownMenuItem(value: PreferenceType.bool, child: Text('Boolean')), + DropdownMenuItem(value: PreferenceType.int, child: Text('Integer')), + DropdownMenuItem(value: PreferenceType.double, child: Text('Double')), + DropdownMenuItem(value: PreferenceType.listString, child: Text('List of Strings')), + ], + onChanged: (value) { + if (value != null) { + setState(() { + selectedType = value; + }); + } + }, + ), + ], + ), + ), + actions: [ + TextButton( + child: Text('Cancel'), + onPressed: () => Navigator.of(context).pop(), + ), + TextButton( + child: Text('Create'), + onPressed: () { + if (keyController.text.isEmpty) { + _showErrorMessage(context, 'Key cannot be empty'); + return; + } + + viewModel.set(keyController.text, selectedType, switch (selectedType) { + PreferenceType.bool => false, + PreferenceType.int => 0, + PreferenceType.double => 0.0, + PreferenceType.string => '', + PreferenceType.listString => [], + PreferenceType.unknown => null, + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + }, + ); + } +} diff --git a/lib/src/screens/settings/background_sync_page.dart b/lib/src/screens/settings/background_sync_page.dart index f9589297d..91cab896b 100644 --- a/lib/src/screens/settings/background_sync_page.dart +++ b/lib/src/screens/settings/background_sync_page.dart @@ -12,6 +12,7 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/settings/sync_mode.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:permission_handler/permission_handler.dart'; class BackgroundSyncPage extends BasePage { BackgroundSyncPage(this.dashboardViewModel); @@ -28,30 +29,30 @@ class BackgroundSyncPage extends BasePage { child: Column( mainAxisSize: MainAxisSize.min, children: [ - if (dashboardViewModel.hasBatteryOptimization) - Observer(builder: (context) { - return SettingsSwitcherCell( - title: S.current.unrestricted_background_service, - value: !dashboardViewModel.batteryOptimizationEnabled, - onValueChange: (_, bool value) { - dashboardViewModel.disableBatteryOptimization(); - }, - ); - }), Observer(builder: (context) { return SettingsSwitcherCell( title: S.current.background_sync, value: dashboardViewModel.backgroundSyncEnabled, - onValueChange: (dashboardViewModel.batteryOptimizationEnabled && dashboardViewModel.hasBatteryOptimization) ? (_, bool value) { - unawaited(showPopUp(context: context, builder: (context) => AlertWithOneAction( - alertTitle: S.current.background_sync, - alertContent: S.current.unrestricted_background_service_notice, - buttonText: S.current.ok, - buttonAction: () => Navigator.of(context).pop(), - ))); - } : (_, bool value) { + onValueChange: (_, bool value) async { if (value) { - dashboardViewModel.enableBackgroundSync(); + if (dashboardViewModel.batteryOptimizationEnabled) { + await showPopUp(context: context, builder: (context) => AlertWithOneAction( + alertTitle: S.current.background_sync, + alertContent: S.current.unrestricted_background_service_notice, + buttonText: S.current.ok, + buttonAction: () => Navigator.of(context).pop(), + )); + await dashboardViewModel.disableBatteryOptimization(); + for (var i = 0; i < 4 * 60; i++) { + await Future.delayed(Duration(milliseconds: 250)); + if (!dashboardViewModel.batteryOptimizationEnabled) { + await dashboardViewModel.enableBackgroundSync(); + return; + } + } + } else { + dashboardViewModel.enableBackgroundSync(); + } } else { dashboardViewModel.disableBackgroundSync(); } @@ -68,22 +69,58 @@ class BackgroundSyncPage extends BasePage { dashboardViewModel.setSyncMode(syncMode); }); }), - - // Observer(builder: (context) { - // return SettingsSwitcherCell( - // title: S.current.background_sync_on_battery, - // value: dashboardViewModel.backgroundSyncOnBattery, - // onValueChange: (_, bool value) => - // dashboardViewModel.setBackgroundSyncOnBattery(value), - // ); - // }), - // Observer(builder: (context) { - // return SettingsSwitcherCell( - // title: S.current.background_sync_on_data, - // value: dashboardViewModel.backgroundSyncOnData, - // onValueChange: (_, bool value) => dashboardViewModel.setBackgroundSyncOnData(value), - // ); - // }), + if (dashboardViewModel.hasBgsyncNetworkConstraints) + Observer(builder: (context) { + return SettingsSwitcherCell( + title: S.current.background_sync_on_unmetered_network, + value: dashboardViewModel.backgroundSyncNetworkUnmetered, + onValueChange: (_, bool value) => dashboardViewModel.setBackgroundSyncNetworkUnmetered(value), + ); + }), + if (dashboardViewModel.hasBgsyncBatteryNotLowConstraints) + Observer(builder: (context) { + return SettingsSwitcherCell( + title: S.current.background_sync_on_battery_low, + value: !dashboardViewModel.backgroundSyncBatteryNotLow, + onValueChange: (_, bool value) => dashboardViewModel.setBackgroundSyncBatteryNotLow(!value), + ); + }), + if (dashboardViewModel.hasBgsyncChargingConstraints) + Observer(builder: (context) { + return SettingsSwitcherCell( + title: S.current.background_sync_on_charging, + value: dashboardViewModel.backgroundSyncCharging, + onValueChange: (_, bool value) => dashboardViewModel.setBackgroundSyncCharging(value), + ); + }), + if (dashboardViewModel.hasBgsyncDeviceIdleConstraints) + Observer(builder: (context) { + return SettingsSwitcherCell( + title: S.current.background_sync_on_device_idle, + value: dashboardViewModel.backgroundSyncDeviceIdle, + onValueChange: (_, bool value) => dashboardViewModel.setBackgroundSyncDeviceIdle(value), + ); + }), + Observer(builder: (context) { + return SettingsSwitcherCell( + title: S.current.new_transactions_notifications, + value: dashboardViewModel.backgroundSyncNotificationsEnabled, + onValueChange: (_, bool value) { + try { + dashboardViewModel.setBackgroundSyncNotificationsEnabled(value); + } catch (e) { + showPopUp(context: context, builder: (context) => AlertWithOneAction( + alertTitle: S.current.error, + alertContent: S.current.notification_permission_denied, + buttonText: S.current.ok, + buttonAction: () { + Navigator.of(context).pop(); + }, + )); + } + }, + ); + }), ], ), ); diff --git a/lib/src/screens/settings/connection_sync_page.dart b/lib/src/screens/settings/connection_sync_page.dart index 739e01c55..098f80644 100644 --- a/lib/src/screens/settings/connection_sync_page.dart +++ b/lib/src/screens/settings/connection_sync_page.dart @@ -49,7 +49,7 @@ class ConnectionSyncPage extends BasePage { title: S.current.manage_nodes, handler: (context) => Navigator.of(context).pushNamed(Routes.manageNodes), ), - if (dashboardViewModel.hasBackgroundSync && Platform.isAndroid && FeatureFlag.isBackgroundSyncEnabled) ...[ + if (Platform.isAndroid && FeatureFlag.isBackgroundSyncEnabled) ...[ SettingsCellWithArrow( title: S.current.background_sync, handler: (context) => Navigator.of(context).pushNamed(Routes.backgroundSync), diff --git a/lib/src/screens/settings/other_settings_page.dart b/lib/src/screens/settings/other_settings_page.dart index f841e2998..730699286 100644 --- a/lib/src/screens/settings/other_settings_page.dart +++ b/lib/src/screens/settings/other_settings_page.dart @@ -6,12 +6,10 @@ import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/settings/widgets/setting_priority_picker_cell.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart'; -import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_version_cell.dart'; import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -77,6 +75,18 @@ class OtherSettingsPage extends BasePage { handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.devMoneroCallProfiler), ), + if (FeatureFlag.hasDevOptions) + SettingsCellWithArrow( + title: '[dev] shared preferences', + handler: (BuildContext context) => + Navigator.of(context).pushNamed(Routes.devSharedPreferences), + ), + if (FeatureFlag.hasDevOptions) + SettingsCellWithArrow( + title: '[dev] background sync logs', + handler: (BuildContext context) => + Navigator.of(context).pushNamed(Routes.devBackgroundSyncLogs), + ), Spacer(), SettingsVersionCell( title: S.of(context).version(_otherSettingsViewModel.currentVersion)), diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index f5e89a023..e5f021783 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -51,6 +51,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_daemon/flutter_daemon.dart'; import 'package:http/http.dart' as http; import 'package:mobx/mobx.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../themes/theme_base.dart'; @@ -180,7 +181,7 @@ abstract class DashboardViewModelBase with Store { isShowThirdYatIntroduction = false; unawaited(isBackgroundSyncEnabled()); unawaited(isBatteryOptimizationEnabled()); - + unawaited(_loadConstraints()); final _wallet = wallet; if (_wallet.type == WalletType.monero) { @@ -536,6 +537,88 @@ abstract class DashboardViewModelBase with Store { return resp; } + @observable + late bool backgroundSyncNotificationsEnabled = sharedPreferences.getBool(PreferencesKey.backgroundSyncNotificationsEnabled) ?? false; + + @action + Future setBackgroundSyncNotificationsEnabled(bool value) async { + if (!value) { + backgroundSyncNotificationsEnabled = false; + sharedPreferences.setBool(PreferencesKey.backgroundSyncNotificationsEnabled, false); + return; + } + PermissionStatus permissionStatus = await Permission.notification.status; + if (permissionStatus != PermissionStatus.granted) { + final resp = await Permission.notification.request(); + if (resp == PermissionStatus.denied) { + throw Exception("Notification permission denied"); + } + } + backgroundSyncNotificationsEnabled = value; + await sharedPreferences.setBool(PreferencesKey.backgroundSyncNotificationsEnabled, value); + } + + + bool get hasBgsyncNetworkConstraints => Platform.isAndroid; + bool get hasBgsyncBatteryNotLowConstraints => Platform.isAndroid; + bool get hasBgsyncChargingConstraints => Platform.isAndroid; + bool get hasBgsyncDeviceIdleConstraints => Platform.isAndroid; + + @observable + bool backgroundSyncNetworkUnmetered = false; + + @observable + bool backgroundSyncBatteryNotLow = false; + + @observable + bool backgroundSyncCharging = false; + + @observable + bool backgroundSyncDeviceIdle = false; + + Future _loadConstraints() async { + backgroundSyncNetworkUnmetered = await FlutterDaemon().getNetworkType(); + backgroundSyncBatteryNotLow = await FlutterDaemon().getBatteryNotLow(); + backgroundSyncCharging = await FlutterDaemon().getRequiresCharging(); + backgroundSyncDeviceIdle = await FlutterDaemon().getDeviceIdle(); + } + + @action + Future setBackgroundSyncNetworkUnmetered(bool value) async { + backgroundSyncNetworkUnmetered = value; + await FlutterDaemon().setNetworkType(value); + if (await isBackgroundSyncEnabled()) { + await enableBackgroundSync(); + } + } + + @action + Future setBackgroundSyncBatteryNotLow(bool value) async { + backgroundSyncBatteryNotLow = value; + await FlutterDaemon().setBatteryNotLow(value); + if (await isBackgroundSyncEnabled()) { + await enableBackgroundSync(); + } + } + + @action + Future setBackgroundSyncCharging(bool value) async { + backgroundSyncCharging = value; + await FlutterDaemon().setRequiresCharging(value); + if (await isBackgroundSyncEnabled()) { + await enableBackgroundSync(); + } + } + + @action + Future setBackgroundSyncDeviceIdle(bool value) async { + backgroundSyncDeviceIdle = value; + await FlutterDaemon().setDeviceIdle(value); + if (await isBackgroundSyncEnabled()) { + await enableBackgroundSync(); + } + } + bool get hasBatteryOptimization => Platform.isAndroid; @observable diff --git a/lib/view_model/dev/background_sync_logs_view_model.dart b/lib/view_model/dev/background_sync_logs_view_model.dart new file mode 100644 index 000000000..a448dadb5 --- /dev/null +++ b/lib/view_model/dev/background_sync_logs_view_model.dart @@ -0,0 +1,44 @@ +import 'package:flutter_daemon/flutter_daemon.dart'; +import 'package:mobx/mobx.dart'; + +part 'background_sync_logs_view_model.g.dart'; +class BackgroundSyncLogsViewModel = BackgroundSyncLogsViewModelBase with _$BackgroundSyncLogsViewModel; + +abstract class BackgroundSyncLogsViewModelBase with Store { + final FlutterDaemon _daemon = FlutterDaemon(); + + @observable + LogData? logData; + + @observable + bool isLoading = false; + + @observable + String? error; + + @computed + List get logs => logData?.logs ?? []; + + @computed + List get sessions => logData?.sessions ?? []; + + @action + Future loadLogs() async { + isLoading = true; + error = null; + + try { + logData = await _daemon.getLogs(); + } catch (e) { + error = e.toString(); + } finally { + isLoading = false; + } + } + + @action + Future clearLogs() async { + await _daemon.clearLogs(); + await loadLogs(); + } +} \ No newline at end of file diff --git a/lib/view_model/dev/shared_preferences.dart b/lib/view_model/dev/shared_preferences.dart new file mode 100644 index 000000000..4c6be7d15 --- /dev/null +++ b/lib/view_model/dev/shared_preferences.dart @@ -0,0 +1,92 @@ +import 'package:mobx/mobx.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +part 'shared_preferences.g.dart'; + +class DevSharedPreferences = DevSharedPreferencesBase with _$DevSharedPreferences; + +enum PreferenceType { + unknown, + string, + int, + double, + bool, + listString +} + +abstract class DevSharedPreferencesBase with Store { + DevSharedPreferencesBase() { + SharedPreferences.getInstance().then((value) { + sharedPreferences = value; + }); + } + + @observable + SharedPreferences? sharedPreferences; + + @computed + List get keys => (sharedPreferences?.getKeys().toList()?..sort()) ?? []; + + @action + Future delete(String key) async { + if (sharedPreferences == null) { + return; + } + await sharedPreferences!.remove(key); + } + + dynamic get(String key) { + if (sharedPreferences == null) { + return null; + } + return sharedPreferences!.get(key); + } + + Future set(String key, PreferenceType type, dynamic value) async { + if (sharedPreferences == null) { + return; + } + switch (type) { + case PreferenceType.string: + await sharedPreferences!.setString(key, value as String); + break; + case PreferenceType.bool: + await sharedPreferences!.setBool(key, value as bool); + break; + case PreferenceType.int: + await sharedPreferences!.setInt(key, value as int); + break; + case PreferenceType.double: + await sharedPreferences!.setDouble(key, value as double); + break; + case PreferenceType.listString: + await sharedPreferences!.setStringList(key, List.from(value as Iterable)); + break; + default: + throw Exception("Unknown preference type: $type"); + } + } + + PreferenceType getPreferenceType(String key) { + if (sharedPreferences == null) { + return PreferenceType.unknown; + } + final value = sharedPreferences!.get(key); + if (value is String) { + return PreferenceType.string; + } + if (value is bool) { + return PreferenceType.bool; + } + if (value is int) { + return PreferenceType.int; + } + if (value is double) { + return PreferenceType.double; + } + if (value is List) { + return PreferenceType.listString; + } + return PreferenceType.unknown; + } +} diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 2244c9be3..b16edabd2 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -14,6 +14,7 @@ import 'package:cake_wallet/entities/evm_transaction_error_fees_handler.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/entities/parsed_address.dart'; import 'package:cake_wallet/entities/template.dart'; +import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/transaction_description.dart'; import 'package:cake_wallet/entities/wallet_contact.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; @@ -52,6 +53,7 @@ import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; +import 'package:shared_preferences/shared_preferences.dart'; part 'send_view_model.g.dart'; @@ -587,6 +589,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor transactionNote: note, )); } + final sharedPreferences = await SharedPreferences.getInstance(); + await sharedPreferences.setString(PreferencesKey.backgroundSyncLastTrigger(wallet.name), DateTime.now().add(Duration(minutes: 1)).toIso8601String()); state = TransactionCommitted(); } catch (e) { diff --git a/pubspec_base.yaml b/pubspec_base.yaml index c3267c6da..e43de5ca1 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -93,8 +93,8 @@ dependencies: eth_sig_util: ^0.0.9 ens_dart: git: - url: https://github.com/cake-tech/ens_dart.git - ref: main + url: https://github.com/MrCyjaneK/ens_dart.git + ref: 9fa09b9db69b8645d5d50a844652aa570451d101 fluttertoast: 8.2.12 # tor: # git: @@ -103,7 +103,10 @@ dependencies: socks5_proxy: ^1.0.4 flutter_svg: ^2.0.9 polyseed: ^0.0.7 - nostr_tools: ^1.0.9 + nostr_tools: + git: + url: https://github.com/MrCyjaneK/nostr_tools.git + ref: 089d5a2dd751429a040ba10fb24fcbae564053e5 ledger_flutter_plus: git: url: https://github.com/vespr-wallet/ledger-flutter-plus @@ -121,7 +124,8 @@ dependencies: flutter_daemon: git: url: https://github.com/MrCyjaneK/flutter_daemon - ref: 5c369e0e69e6f459357b9802bc694a221397298a + ref: 6d5270d64b5dd588fce12fd0a0c7314c37e6cff1 + flutter_local_notifications: ^19.0.0 dev_dependencies: flutter_test: diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 27587a331..e68f6dc3f 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "في انتظار تأكيد الدفع", "background_sync": "مزامنة الخلفية", "background_sync_mode": "وضع مزامنة الخلفية", + "background_sync_on_battery_low": "تزامن على البطارية المنخفضة", + "background_sync_on_charging": "تزامن فقط عند الشحن", + "background_sync_on_device_idle": "تزامن فقط عند عدم استخدام الجهاز", + "background_sync_on_unmetered_network": "تتطلب شبكة غير مستوفاة", "backup": "نسخ الاحتياطي", "backup_file": "ملف النسخ الاحتياطي", "backup_password": "كلمة مرور النسخ الاحتياطي", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "تسمية", "new_subaddress_title": "عنوان جديد", "new_template": "قالب جديد", + "new_transactions_notifications": "إرسال إشعارات حول المعاملات الجديدة", "new_wallet": "إنشاء محفظة جديدة", "newConnection": "ﺪﻳﺪﺟ ﻝﺎﺼﺗﺍ", "no_cards_found": "لم يتم العثور على بطاقات", @@ -507,6 +512,7 @@ "normal": "طبيعي", "note_optional": "ملاحظة (اختياري)", "note_tap_to_change": "ملاحظة (انقر للتغيير)", + "notification_permission_denied": "تم رفض إذن الإخطار بشكل جيد ، يرجى تمكينه يدويًا في الإعدادات", "nullURIError": "ﻍﺭﺎﻓ (URI) ﻢﻈﺘﻨﻤﻟﺍ ﺩﺭﺍﻮﻤﻟﺍ ﻑﺮﻌﻣ", "offer_expires_in": "ينتهي العرض في:", "offline": "غير متصل على الانترنت", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 90e2e1ca9..775cc8152 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "Чака се потвърждение на плащането", "background_sync": "Фон Синхх", "background_sync_mode": "Режим на синхронизиране на фона", + "background_sync_on_battery_low": "Синхронизирайте на ниска батерия", + "background_sync_on_charging": "Синхронизирайте само при зареждане", + "background_sync_on_device_idle": "Синхронизирайте само когато устройството не се използва", + "background_sync_on_unmetered_network": "Изисквайте незадоволена мрежа", "backup": "Резервно копие", "backup_file": "Резервно копие", "backup_password": "Парола за възстановяване", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "Име на Label", "new_subaddress_title": "Нов адрес", "new_template": "Нов шаблон", + "new_transactions_notifications": "Изпратете известия за нови транзакции", "new_wallet": "Нов портфейл", "newConnection": "Нова връзка", "no_cards_found": "Не са намерени карти", @@ -507,6 +512,7 @@ "normal": "нормално", "note_optional": "Бележка (не е задължително)", "note_tap_to_change": "Бележка (натиснете за промяна)", + "notification_permission_denied": "Разрешението за уведомяване е отказано, моля, моля, активирайте го в настройки", "nullURIError": "URI е нула", "offer_expires_in": "Предложението изтича след: ", "offline": "Офлайн", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 7f878075f..7f9e0fcf7 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "Čeká se na potvrzení platby", "background_sync": "Synchronizace pozadí", "background_sync_mode": "Režim synchronizace pozadí", + "background_sync_on_battery_low": "Synchronizace na nízké baterii", + "background_sync_on_charging": "Synchronizovat pouze při nabíjení", + "background_sync_on_device_idle": "Synchronizujte pouze tehdy, když se zařízení nepoužívá", + "background_sync_on_unmetered_network": "Vyžadovat nemetrovou síť", "backup": "Záloha", "backup_file": "Soubor se zálohou", "backup_password": "Heslo pro zálohy", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "Popisek", "new_subaddress_title": "Nová adresa", "new_template": "Nová šablona", + "new_transactions_notifications": "Zašlete oznámení o nových transakcích", "new_wallet": "Nová peněženka", "newConnection": "Nové připojení", "no_cards_found": "Žádné karty nenalezeny", @@ -507,6 +512,7 @@ "normal": "Normální", "note_optional": "Poznámka (nepovinné)", "note_tap_to_change": "Poznámka (poklepáním upravit)", + "notification_permission_denied": "Oznámení o oznámení bylo oprávněně zamítnuto, prosím ručně jej povolte v nastavení", "nullURIError": "URI je nulové", "offer_expires_in": "Nabídka vyprší: ", "offline": "Offline", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index e3a725d9c..b87ff03d3 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "Warten auf Zahlungsbestätigung", "background_sync": "Hintergrundsynchronisation", "background_sync_mode": "Hintergrundsynchronisierungsmodus", + "background_sync_on_battery_low": "Synchronisieren Sie einen niedrigen Akku", + "background_sync_on_charging": "Nur beim Laden synchronisieren", + "background_sync_on_device_idle": "Nur dann synchronisieren, wenn das Gerät nicht verwendet wird", + "background_sync_on_unmetered_network": "Erfordern ein nicht modisches Netzwerk", "backup": "Sicherung", "backup_file": "Sicherungsdatei", "backup_password": "Passwort sichern", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "Bezeichnung", "new_subaddress_title": "Neue Adresse", "new_template": "neue Vorlage", + "new_transactions_notifications": "Senden Sie Benachrichtigungen über neue Transaktionen", "new_wallet": "Neue Wallet", "newConnection": "Neue Verbindung", "no_cards_found": "Keine Karten gefunden", @@ -507,6 +512,7 @@ "normal": "Normal", "note_optional": "Bemerkung (optional)", "note_tap_to_change": "Bemerkung (zum Ändern tippen)", + "notification_permission_denied": "Die Benachrichtigungsgenehmigung wurde verweigert verweigert. Bitte ermöglichen Sie dies manuell in Einstellungen", "nullURIError": "URI ist null", "offer_expires_in": "Angebot läuft ab in: ", "offline": "offline", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index abd406010..69495ba72 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "Awaiting Payment Confirmation", "background_sync": "Background sync", "background_sync_mode": "Background sync mode", + "background_sync_on_battery_low": "Synchronize on low battery", + "background_sync_on_charging": "Synchronize only when charging", + "background_sync_on_device_idle": "Synchronize only when device is not being used", + "background_sync_on_unmetered_network": "Require unmetred network", "backup": "Backup", "backup_file": "Backup file", "backup_password": "Backup password", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "Label name", "new_subaddress_title": "New address", "new_template": "New Template", + "new_transactions_notifications": "Send notifications about new transactions", "new_wallet": "New Wallet", "newConnection": "New Connection", "no_cards_found": "No cards found", @@ -507,6 +512,7 @@ "normal": "Normal", "note_optional": "Note (optional)", "note_tap_to_change": "Note (tap to change)", + "notification_permission_denied": "Notification permission got permamently denied, please manually enable it in settings", "nullURIError": "URI is null", "offer_expires_in": "Offer expires in: ", "offline": "Offline", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index c175f8393..70a809243 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "Esperando confirmación de pago", "background_sync": "Sincronización en segundo plano", "background_sync_mode": "Modo de sincronización en segundo plano", + "background_sync_on_battery_low": "Sincronizar con batería baja", + "background_sync_on_charging": "Sincronizar solo al cargar", + "background_sync_on_device_idle": "Sincronizar solo cuando el dispositivo no se usa", + "background_sync_on_unmetered_network": "Requerir una red no metida", "backup": "Apoyo", "backup_file": "Archivo de respaldo", "backup_password": "Contraseña de respaldo", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "Nombre de etiqueta", "new_subaddress_title": "Nueva direccion", "new_template": "Nueva plantilla", + "new_transactions_notifications": "Enviar notificaciones sobre nuevas transacciones", "new_wallet": "Nueva billetera", "newConnection": "Nueva conexión", "no_cards_found": "No se encuentran cartas", @@ -507,6 +512,7 @@ "normal": "Normal", "note_optional": "Nota (opcional)", "note_tap_to_change": "Nota (toque para cambiar)", + "notification_permission_denied": "El permiso de notificación se negó de manera permanente, por favor, habilite manualmente en la configuración", "nullURIError": "URI es nula", "offer_expires_in": "Oferta expira en: ", "offline": "fuera de línea", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index fda6781b8..01510b7a9 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "En attente de confirmation de paiement", "background_sync": "Synchronisation de fond", "background_sync_mode": "Mode de synchronisation en arrière-plan", + "background_sync_on_battery_low": "Synchroniser sur une batterie basse", + "background_sync_on_charging": "Synchroniser uniquement lors de la charge", + "background_sync_on_device_idle": "Synchroniser uniquement lorsque l'appareil n'est pas utilisé", + "background_sync_on_unmetered_network": "Exiger un réseau non métallique", "backup": "Sauvegarde", "backup_file": "Fichier de sauvegarde", "backup_password": "Mot de passe de sauvegarde", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "Nom", "new_subaddress_title": "Nouvelle adresse", "new_template": "Nouveau Modèle", + "new_transactions_notifications": "Envoyer des notifications sur les nouvelles transactions", "new_wallet": "Nouveau Portefeuille (Wallet)", "newConnection": "Nouvelle connexion", "no_cards_found": "Pas de cartes trouvées", @@ -507,6 +512,7 @@ "normal": "Normal", "note_optional": "Note (optionnelle)", "note_tap_to_change": "Note (appuyez pour changer)", + "notification_permission_denied": "L'autorisation de notification a été refusée permanente, veuillez l'activer manuellement dans les paramètres", "nullURIError": "L'URI est nul", "offer_expires_in": "L'Offre expire dans: ", "offline": "Hors ligne", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index c57635f31..7629eec5f 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "Ana jiran Tabbacin Biyan Kuɗi", "background_sync": "Tunawa da Setc", "background_sync_mode": "Yanayin Sync", + "background_sync_on_battery_low": "Aiki tare a kan baturin", + "background_sync_on_charging": "Aiki tare kawai lokacin caji", + "background_sync_on_device_idle": "Aiki tare kawai lokacin da ba a amfani da na'urar", + "background_sync_on_unmetered_network": "Bukatar cibiyar sadarwar da ba ta dace ba", "backup": "Ajiyayyen", "backup_file": "Ajiyayyen fayil", "backup_password": "Ajiyayyen kalmar sirri", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "Lakabin suna", "new_subaddress_title": "Adireshin sabuwa", "new_template": "Sabon Samfura", + "new_transactions_notifications": "Aika sanarwa game da sababbin ma'amaloli", "new_wallet": "Sabuwar Wallet", "newConnection": "Sabuwar Haɗi", "no_cards_found": "Babu katunan da aka samo", @@ -507,6 +512,7 @@ "normal": "Na al'ada", "note_optional": "Bayani (optional)", "note_tap_to_change": "Bayani (tap don canja)", + "notification_permission_denied": "Izinin sanarwar da aka samu an ƙaryata game da shi, don Allah a kunna shi a cikin saiti", "nullURIError": "URI banza ne", "offer_expires_in": "tayin zai ƙare a:", "offline": "Offline", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 02e458749..73d168d47 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "भुगतान की पुष्टि की प्रतीक्षा में", "background_sync": "पृष्ठभूमि सिंक", "background_sync_mode": "बैकग्राउंड सिंक मोड", + "background_sync_on_battery_low": "कम बैटरी पर सिंक्रनाइज़ करें", + "background_sync_on_charging": "चार्ज करते समय केवल सिंक्रनाइज़ करें", + "background_sync_on_device_idle": "केवल तब सिंक्रनाइज़ करें जब डिवाइस का उपयोग नहीं किया जा रहा है", + "background_sync_on_unmetered_network": "अनमेट्रेड नेटवर्क की आवश्यकता है", "backup": "बैकअप", "backup_file": "बैकअपफ़ाइल", "backup_password": "बैकअप पासवर्ड", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "लेबल का नाम", "new_subaddress_title": "नया पता", "new_template": "नया टेम्पलेट", + "new_transactions_notifications": "नए लेनदेन के बारे में सूचनाएं भेजें", "new_wallet": "नया बटुआ", "newConnection": "नया कनेक्शन", "no_cards_found": "कोई कार्ड नहीं मिला", @@ -507,6 +512,7 @@ "normal": "सामान्य", "note_optional": "नोट (वैकल्पिक)", "note_tap_to_change": "नोट (टैप टू चेंज)", + "notification_permission_denied": "अधिसूचना की अनुमति को पारगम्य रूप से अस्वीकार कर दिया गया, कृपया इसे मैन्युअल रूप से सेटिंग्स में सक्षम करें", "nullURIError": "यूआरआई शून्य है", "offer_expires_in": "में ऑफर समाप्त हो रहा है: ", "offline": "ऑफ़लाइन", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 1745d834a..75aac2682 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "Čeka se potvrda plaćanja", "background_sync": "Sinkronizacija pozadine", "background_sync_mode": "Sinkronizacija u pozadini", + "background_sync_on_battery_low": "Sinkronizirati na niskoj bateriji", + "background_sync_on_charging": "Sinkronizirati samo prilikom punjenja", + "background_sync_on_device_idle": "Sinkronizirati samo kada se uređaj ne koristi", + "background_sync_on_unmetered_network": "Zahtijevaju nezadovoljnu mrežu", "backup": "Sigurnosna kopija", "backup_file": "Sigurnosna kopija datoteke", "backup_password": "Lozinka za sigurnosnu kopiju", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "Oznaka", "new_subaddress_title": "Nova adresa", "new_template": "novi predložak", + "new_transactions_notifications": "Pošaljite obavijesti o novim transakcijama", "new_wallet": "Novi novčanik", "newConnection": "Nova veza", "no_cards_found": "Nisu pronađene kartice", @@ -507,6 +512,7 @@ "normal": "Normalno", "note_optional": "Poruka (nije obvezno)", "note_tap_to_change": "Poruka (dodirnite za promjenu)", + "notification_permission_denied": "Dozvola za obavijest ostalo je odbijeno, molimo vas da ga ručno omogućite u postavkama", "nullURIError": "URI je nula", "offer_expires_in": "Ponuda istječe za: ", "offline": "izvan mreže", diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index 837df61d2..09585df8a 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "Վճարման հաստատման սպասում", "background_sync": "Ֆոնային համաժամեցում", "background_sync_mode": "Հետին պլանի համաժամացման ռեժիմ", + "background_sync_on_battery_low": "Համաժամեցրեք ցածր մարտկոցի վրա", + "background_sync_on_charging": "Համաժամացրեք միայն լիցքավորելու ժամանակ", + "background_sync_on_device_idle": "Համաժամացրեք միայն այն ժամանակ, երբ սարքը չի օգտագործվում", + "background_sync_on_unmetered_network": "Պահանջում են չմշակված ցանց", "backup": "Կրկնօրինակ", "backup_file": "Կրկնօրինակի ֆայլ", "backup_password": "Կրկնօրինակի գաղտնաբառ", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "Պիտակի անուն", "new_subaddress_title": "Նոր հասցե", "new_template": "Նոր նմուշ", + "new_transactions_notifications": "Ուղարկեք ծանուցումներ նոր գործարքների վերաբերյալ", "new_wallet": "Նոր դրամապանակ", "newConnection": "Նոր կապ", "no_cards_found": "Ոչ մի քարտ չի գտնվել", @@ -506,6 +511,7 @@ "normal": "Նորմալ", "note_optional": "Նշում (ոչ պարտադիր)", "note_tap_to_change": "Նշում (սեղմեք փոխելու համար)", + "notification_permission_denied": "Տեղեկացման թույլտվությունը թափանցում է, խնդրում ենք ձեռքով միացնել այն պարամետրերում", "nullURIError": "URI-ն դատարկ է", "offer_expires_in": "Առաջարկը վաղեմության է հասնում ", "offline": "Անցանց", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index f159a2672..a3e6abd1f 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "Menunggu Konfirmasi Pembayaran", "background_sync": "Sinkronisasi Latar Belakang", "background_sync_mode": "Mode Sinkronisasi Latar Belakang", + "background_sync_on_battery_low": "Sinkronisasi pada baterai rendah", + "background_sync_on_charging": "Menyinkronkan hanya saat pengisian", + "background_sync_on_device_idle": "Menyinkronkan hanya jika perangkat tidak digunakan", + "background_sync_on_unmetered_network": "Membutuhkan jaringan yang belum diproduksi", "backup": "Cadangan", "backup_file": "File cadangan", "backup_password": "Kata sandi cadangan", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "Nama label", "new_subaddress_title": "Alamat baru", "new_template": "Template Baru", + "new_transactions_notifications": "Kirim pemberitahuan tentang transaksi baru", "new_wallet": "Dompet Baru", "newConnection": "Koneksi Baru", "no_cards_found": "Tidak ada kartu yang ditemukan", @@ -507,6 +512,7 @@ "normal": "Normal", "note_optional": "Catatan (opsional)", "note_tap_to_change": "Catatan (tap untuk mengubah)", + "notification_permission_denied": "Izin pemberitahuan ditolak secara permanen, mohon aktifkan secara manual dalam pengaturan", "nullURIError": "URI adalah nol", "offer_expires_in": "Penawaran kedaluwarsa dalam: ", "offline": "Offline", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index b05f292ee..b558501d4 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "In attesa di conferma del pagamento", "background_sync": "Sincronizzazione in background", "background_sync_mode": "Modalità di sincronizzazione in background", + "background_sync_on_battery_low": "Sincronizza sulla batteria bassa", + "background_sync_on_charging": "Sincronizzare solo quando si carica", + "background_sync_on_device_idle": "Sincronizzare solo quando il dispositivo non viene utilizzato", + "background_sync_on_unmetered_network": "Richiedono una rete non riservata", "backup": "Backup", "backup_file": "Backup file", "backup_password": "Backup password", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "Nome etichetta", "new_subaddress_title": "Nuovo indirizzo", "new_template": "Nuovo modello", + "new_transactions_notifications": "Invia notifiche su nuove transazioni", "new_wallet": "Nuovo portafoglio", "newConnection": "Nuova connessione", "no_cards_found": "Nessuna carta trovata", @@ -507,6 +512,7 @@ "normal": "Normale", "note_optional": "Nota (opzionale)", "note_tap_to_change": "Nota (clicca per cambiare)", + "notification_permission_denied": "L'autorizzazione alla notifica è stata negato per via per via per via per via per via per via per via per via, per favore, abilitalo manualmente", "nullURIError": "L'URI è nullo", "offer_expires_in": "L'offerta termina tra: ", "offline": "Offline", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index d6cb2c4b1..933fb706a 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "支払い確認を待っています", "background_sync": "背景同期", "background_sync_mode": "バックグラウンド同期モード", + "background_sync_on_battery_low": "低いバッテリーで同期します", + "background_sync_on_charging": "充電の場合にのみ同期します", + "background_sync_on_device_idle": "デバイスが使用されていない場合にのみ同期します", + "background_sync_on_unmetered_network": "未成年のネットワークが必要です", "backup": "バックアップ", "backup_file": "バックアップファイル", "backup_password": "バックアップパスワード", @@ -484,6 +488,7 @@ "new_subaddress_label_name": "ラベル名", "new_subaddress_title": "新しいアドレス", "new_template": "新しいテンプレート", + "new_transactions_notifications": "新しいトランザクションに関する通知を送信します", "new_wallet": "新しいウォレット", "newConnection": "新しい接続", "no_cards_found": "カードは見つかりません", @@ -508,6 +513,7 @@ "normal": "普通", "note_optional": "注(オプション)", "note_tap_to_change": "注(タップして変更)", + "notification_permission_denied": "通知の許可はまったく拒否されました。設定で手動で有効にしてください", "nullURIError": "URIがnullです", "offer_expires_in": "で有効期限が切れます: ", "offline": "オフライン", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index d451b69a6..582ec8f45 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "결제 확인 대기 중", "background_sync": "배경 동기화", "background_sync_mode": "백그라운드 동기화 모드", + "background_sync_on_battery_low": "낮은 배터리에서 동기화하십시오", + "background_sync_on_charging": "충전 할 때만 동기화하십시오", + "background_sync_on_device_idle": "장치를 사용하지 않을 때만 동기화하십시오", + "background_sync_on_unmetered_network": "충족되지 않은 네트워크가 필요합니다", "backup": "지원", "backup_file": "백업 파일", "backup_password": "백업 비밀번호", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "라벨 이름", "new_subaddress_title": "새 주소", "new_template": "새 템플릿", + "new_transactions_notifications": "새로운 거래에 대한 알림을 보냅니다", "new_wallet": "새 월렛", "newConnection": "새로운 연결", "no_cards_found": "카드를 찾지 못했습니다", @@ -507,6 +512,7 @@ "normal": "정상", "note_optional": "참고 (선택 사항)", "note_tap_to_change": "메모 (변경하려면 탭하세요)", + "notification_permission_denied": "알림 허가가 부패하게 거부되었습니다. 설정에서 수동으로 활성화하십시오.", "nullURIError": "URI가 null입니다.", "offer_expires_in": "쿠폰 만료일: ", "offline": "오프라인", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index a49c1bb30..69eb337ee 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "ငွေပေးချေမှု အတည်ပြုချက်ကို စောင့်မျှော်နေပါသည်။", "background_sync": "နောက်ခံထပ်တူပြုခြင်း", "background_sync_mode": "နောက်ခံထပ်တူပြုခြင်း mode ကို", + "background_sync_on_battery_low": "အနိမ့်ဘက်ထရီအပေါ်တစ်ပြိုင်တည်းချိန်ကိုက်", + "background_sync_on_charging": "အားသွင်းသည့်အခါသာထပ်တူပြုခြင်း", + "background_sync_on_device_idle": "စက်ကိုအသုံးမပြုသည့်စက်ကိုသာတစ်ပြိုင်တည်းချိန်ကိုက်ပါ", + "background_sync_on_unmetered_network": "unmetred ကွန်ယက်လိုအပ်သည်", "backup": "မိတ္တူ", "backup_file": "အရန်ဖိုင်", "backup_password": "စကားဝှက်ကို အရန်သိမ်းဆည်းပါ။", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "အညွှန်းအမည်", "new_subaddress_title": "လိပ်စာအသစ်", "new_template": "ပုံစံအသစ်", + "new_transactions_notifications": "အသစ်သောအရောင်းအဝယ်အကြောင်းသတိပေးချက်များပေးပို့ပါ", "new_wallet": "ပိုက်ဆံအိတ်အသစ်", "newConnection": "ချိတ်ဆက်မှုအသစ်", "no_cards_found": "ကဒ်များမရှိပါ", @@ -507,6 +512,7 @@ "normal": "ပုံမှန်", "note_optional": "မှတ်ချက် (ချန်လှပ်ထားနိုင်သည်)", "note_tap_to_change": "မှတ်ချက် (ပြောင်းလဲရန် တို့ပါ)", + "notification_permission_denied": "အသိပေးချက်ခွင့်ပြုချက်ကိုအစက်အပြောက်ကိုငြင်းဆိုခဲ့သည်, ကျေးဇူးပြု. ၎င်းကိုချိန်ညှိချက်များတွင်လက်ဖြင့်ပြုလုပ်ပါ", "nullURIError": "URI သည် null ဖြစ်သည်။", "offer_expires_in": "ကမ်းလှမ်းချက် သက်တမ်းကုန်သည်:", "offline": "အော့ဖ်လိုင်း", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index bdd1f5c0c..da508e880 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "In afwachting van betalingsbevestiging", "background_sync": "Achtergrondsynchronisatie", "background_sync_mode": "Achtergrondsynchronisatiemodus", + "background_sync_on_battery_low": "Synchroniseren op lage batterij", + "background_sync_on_charging": "Synchroniseer alleen bij het opladen", + "background_sync_on_device_idle": "Synchroniseer alleen wanneer het apparaat niet wordt gebruikt", + "background_sync_on_unmetered_network": "Vereist een onvermekte netwerk", "backup": "Back-up", "backup_file": "Backup bestand", "backup_password": "Reservewachtwoord", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "Label naam", "new_subaddress_title": "Nieuw adres", "new_template": "Nieuwe sjabloon", + "new_transactions_notifications": "Stuur meldingen over nieuwe transacties", "new_wallet": "Nieuwe portemonnee", "newConnection": "Nieuwe verbinding", "no_cards_found": "Geen kaarten gevonden", @@ -507,6 +512,7 @@ "normal": "Normaal", "note_optional": "Opmerking (optioneel)", "note_tap_to_change": "Opmerking (tik om te wijzigen)", + "notification_permission_denied": "Meldingstoestemming is permanent geweigerd, schakel het handmatig in instellingen in", "nullURIError": "URI is nul", "offer_expires_in": "Aanbieding verloopt over: ", "offline": "Offline", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 7199b0e90..12765881c 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "Oczekiwanie na potwierdzenie płatności", "background_sync": "Synchronizacja w tle", "background_sync_mode": "Tryb synchronizacji w tle", + "background_sync_on_battery_low": "Synchronizować na niskiej baterii", + "background_sync_on_charging": "Synchronizować tylko podczas ładowania", + "background_sync_on_device_idle": "Synchronizować tylko wtedy, gdy urządzenie nie jest używane", + "background_sync_on_unmetered_network": "Wymagaj niezametrowanej sieci", "backup": "Kopia zapasowa", "backup_file": "Plik kopii zapasowej", "backup_password": "Hasło kpoii zapasowej", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "Etykieta nazwy adresu", "new_subaddress_title": "Nowy adres", "new_template": "Nowy szablon", + "new_transactions_notifications": "Wyślij powiadomienia o nowych transakcjach", "new_wallet": "Nowy portfel", "newConnection": "Nowe połączenie", "no_cards_found": "Nie znaleziono żadnych kart", @@ -507,6 +512,7 @@ "normal": "Normalna", "note_optional": "Notatka (opcjonalnie)", "note_tap_to_change": "Notatka (dotknij, aby zmienić)", + "notification_permission_denied": "Zezwolenie na powiadomienie zostało odrzucone, prosimy ręcznie włączyć go w ustawieniach", "nullURIError": "URI ma wartość zerową", "offer_expires_in": "Oferta wygasa za ", "offline": "Offline", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index d9e28005a..c91bb04a0 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "Aguardando confirmação de pagamento", "background_sync": "Sincronização de fundo", "background_sync_mode": "Modo de sincronização em segundo plano", + "background_sync_on_battery_low": "Sincronizar com bateria baixa", + "background_sync_on_charging": "Sincronize apenas ao carregar", + "background_sync_on_device_idle": "Sincronize apenas quando o dispositivo não está sendo usado", + "background_sync_on_unmetered_network": "Requer rede não meta", "backup": "Cópia de segurança", "backup_file": "Arquivo de backup", "backup_password": "Senha de backup", @@ -484,6 +488,7 @@ "new_subaddress_label_name": "Nome", "new_subaddress_title": "Novo endereço", "new_template": "Novo modelo", + "new_transactions_notifications": "Envie notificações sobre novas transações", "new_wallet": "Nova carteira", "newConnection": "Nova conexão", "no_cards_found": "Nenhum cartão encontrado", @@ -508,6 +513,7 @@ "normal": "Normal", "note_optional": "Nota (opcional)", "note_tap_to_change": "Nota (toque para alterar)", + "notification_permission_denied": "A permissão de notificação foi negada com permamer, por favor, ativá -la manualmente em configurações", "nullURIError": "URI é nulo", "offer_expires_in": "A oferta expira em: ", "offline": "offline", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 34c1968d1..37750c3ce 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "Ожидается подтверждения платежа", "background_sync": "Фоновая синхронизация", "background_sync_mode": "Режим фоновой синхронизации", + "background_sync_on_battery_low": "Синхронизировать на низкой батареи", + "background_sync_on_charging": "Синхронизировать только при зарядке", + "background_sync_on_device_idle": "Синхронизировать только тогда, когда устройство не используется", + "background_sync_on_unmetered_network": "Требуется незамеченная сеть", "backup": "Резервная копия", "backup_file": "Файл резервной копии", "backup_password": "Пароль резервной копии", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "Имя", "new_subaddress_title": "Новый адрес", "new_template": "Новый шаблон", + "new_transactions_notifications": "Отправить уведомления о новых транзакциях", "new_wallet": "Новый кошелёк", "newConnection": "Новое соединение", "no_cards_found": "Карт не найдено", @@ -507,6 +512,7 @@ "normal": "Нормальный", "note_optional": "Примечание (необязательно)", "note_tap_to_change": "Примечание (нажмите для изменения)", + "notification_permission_denied": "Разрешение уведомления было отклонено, пожалуйста, вручную включить его в настройках", "nullURIError": "URI имеет значение null", "offer_expires_in": "Предложение истекает через: ", "offline": "Не в сети", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index d9bb16118..cbc007035 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "รอการยืนยันการชำระเงิน", "background_sync": "การซิงค์พื้นหลัง", "background_sync_mode": "โหมดซิงค์พื้นหลัง", + "background_sync_on_battery_low": "ซิงโครไนซ์กับแบตเตอรี่ต่ำ", + "background_sync_on_charging": "ซิงโครไนซ์เฉพาะเมื่อชาร์จ", + "background_sync_on_device_idle": "ซิงโครไนซ์เฉพาะเมื่อไม่ใช้อุปกรณ์", + "background_sync_on_unmetered_network": "ต้องการเครือข่ายที่ไม่ได้รับการแก้ไข", "backup": "สำรองข้อมูล", "backup_file": "ไฟล์สำรองข้อมูล", "backup_password": "รหัสผ่านสำรองข้อมูล", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "ชื่อป้ายกำกับ", "new_subaddress_title": "ที่อยู่ใหม่", "new_template": "แม่แบบใหม่", + "new_transactions_notifications": "ส่งการแจ้งเตือนเกี่ยวกับธุรกรรมใหม่", "new_wallet": "กระเป๋าใหม่", "newConnection": "การเชื่อมต่อใหม่", "no_cards_found": "ไม่พบการ์ด", @@ -507,6 +512,7 @@ "normal": "ปกติ", "note_optional": "บันทึก (ไม่จำเป็น)", "note_tap_to_change": "หมายเหตุ (กดเพื่อเปลี่ยน)", + "notification_permission_denied": "การอนุญาตการแจ้งเตือนได้รับการปฏิเสธอย่างอนุญาตโปรดเปิดใช้งานด้วยตนเองในการตั้งค่า", "nullURIError": "URI เป็นโมฆะ", "offer_expires_in": "ข้อเสนอจะหมดอายุใน: ", "offline": "ออฟไลน์", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 31bf58691..356eae8c5 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "Nanghihintay ng Kumpirmasyon sa Pagbabayad", "background_sync": "Pag -sync ng background", "background_sync_mode": "Background sync mode", + "background_sync_on_battery_low": "Mag -synchronize sa mababang baterya", + "background_sync_on_charging": "Mag -synchronize lamang kapag singilin", + "background_sync_on_device_idle": "Mag -synchronize lamang kapag hindi ginagamit ang aparato", + "background_sync_on_unmetered_network": "Nangangailangan ng hindi natukoy na network", "backup": "Backup", "backup_file": "Backup na file", "backup_password": "Backup na password", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "Pangalan ng label", "new_subaddress_title": "Bagong address", "new_template": "Bagong Template", + "new_transactions_notifications": "Magpadala ng mga abiso tungkol sa mga bagong transaksyon", "new_wallet": "Bagong Wallet", "newConnection": "Bagong Koneksyon", "no_cards_found": "Walang nahanap na mga card", @@ -507,6 +512,7 @@ "normal": "Normal", "note_optional": "Tala (opsyonal)", "note_tap_to_change": "Tala (i-tap para baguhin)", + "notification_permission_denied": "Ang pahintulot ng abiso ay pinahihintulutan na tumanggi, mangyaring manu -manong paganahin ito sa mga setting", "nullURIError": "Ang URI ay null", "offer_expires_in": "Mag-expire ang alok sa: ", "offline": "Offline", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index b8503af93..5265e5f4e 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "Ödemenin onaylanması bekleniyor", "background_sync": "Arka plan senkronizasyonu", "background_sync_mode": "Arka Plan Senkronizasyon Modu", + "background_sync_on_battery_low": "Düşük pille senkronize edin", + "background_sync_on_charging": "Yalnızca şarj ederken senkronize edin", + "background_sync_on_device_idle": "Yalnızca cihaz kullanılmadığında senkronize edin", + "background_sync_on_unmetered_network": "Karşıdamlanmamış ağ gerektirir", "backup": "Yedek", "backup_file": "Yedek dosyası", "backup_password": "Yedek parolası", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "Etiket ismi", "new_subaddress_title": "Yeni adres", "new_template": "Yeni Şablon", + "new_transactions_notifications": "Yeni işlemler hakkında bildirimler gönderin", "new_wallet": "Yeni Cüzdan", "newConnection": "Yeni bağlantı", "no_cards_found": "Kart bulunamadı", @@ -507,6 +512,7 @@ "normal": "Normal", "note_optional": "Not (isteğe bağlı)", "note_tap_to_change": "Not (değiştirmek için dokunun)", + "notification_permission_denied": "Bildirim izni perdence reddedildi, lütfen ayarlarda manuel olarak etkinleştirin", "nullURIError": "URI boş", "offer_expires_in": "Teklifin bitmesine kalan: ", "offline": "Çevrimdışı", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 5ff9b72f6..99c94854b 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "Очікується підтвердження платежу", "background_sync": "Фонове синхронізація", "background_sync_mode": "Фоновий режим синхронізації", + "background_sync_on_battery_low": "Синхронізувати на низькому батареї", + "background_sync_on_charging": "Синхронізуватися лише при зарядці", + "background_sync_on_device_idle": "Синхронізувати лише тоді, коли пристрій не використовується", + "background_sync_on_unmetered_network": "Вимагати незадоволеної мережі", "backup": "Резервна копія", "backup_file": "Файл резервної копії", "backup_password": "Пароль резервної копії", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "Ім'я", "new_subaddress_title": "Нова адреса", "new_template": "Новий шаблон", + "new_transactions_notifications": "Надішліть сповіщення про нові транзакції", "new_wallet": "Новий гаманець", "newConnection": "Нове підключення", "no_cards_found": "Карт не знайдено", @@ -507,6 +512,7 @@ "normal": "нормальний", "note_optional": "Примітка (необов’язково)", "note_tap_to_change": "Примітка (натисніть для зміни)", + "notification_permission_denied": "Повідомлення дозволу отримали безперервно, будь ласка, вручну ввімкніть його в налаштуваннях", "nullURIError": "URI нульовий", "offer_expires_in": "Пропозиція закінчиться через: ", "offline": "Офлайн", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 2e27ff85e..152cd7d11 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "ادائیگی کی تصدیق کے منتظر", "background_sync": "پس منظر کی ہم آہنگی", "background_sync_mode": "پس منظر کی مطابقت پذیری کا موڈ", + "background_sync_on_battery_low": "کم بیٹری پر ہم وقت سازی کریں", + "background_sync_on_charging": "چارج کرتے وقت صرف ہم وقت سازی کریں", + "background_sync_on_device_idle": "صرف اس وقت مطابقت پذیر کریں جب آلہ استعمال نہ ہو", + "background_sync_on_unmetered_network": "بے ساختہ نیٹ ورک کی ضرورت ہے", "backup": "بیک اپ", "backup_file": "بیک اپ فائل", "backup_password": "بیک اپ پاس ورڈ", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "لیبل کا نام", "new_subaddress_title": "نیا پتہ", "new_template": "نیا سانچہ", + "new_transactions_notifications": "نئے لین دین کے بارے میں اطلاعات بھیجیں", "new_wallet": "نیا پرس", "newConnection": "ﻦﺸﮑﻨﮐ ﺎﯿﻧ", "no_cards_found": "کوئی کارڈ نہیں ملا", @@ -507,6 +512,7 @@ "normal": "نارمل", "note_optional": "نوٹ (اختیاری)", "note_tap_to_change": "نوٹ (تبدیل کرنے کے لیے تھپتھپائیں)", + "notification_permission_denied": "نوٹیفکیشن کی اجازت کو یقینی طور پر انکار کردیا گیا ، براہ کرم اسے دستی طور پر ترتیبات میں اہل بنائیں", "nullURIError": "URI ۔ﮯﮨ ﻡﺪﻌﻟﺎﮐ", "offer_expires_in": "پیشکش کی میعاد اس وقت ختم ہو جاتی ہے:", "offline": "آف لائن", diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index 7bbe35a4a..56a9305bf 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "Đang chờ xác nhận thanh toán", "background_sync": "Đồng bộ nền", "background_sync_mode": "Chế độ đồng bộ nền", + "background_sync_on_battery_low": "Đồng bộ hóa trên pin thấp", + "background_sync_on_charging": "Chỉ đồng bộ hóa khi sạc", + "background_sync_on_device_idle": "Chỉ đồng bộ hóa khi thiết bị không được sử dụng", + "background_sync_on_unmetered_network": "Yêu cầu mạng không được thiết kế", "backup": "Sao lưu", "backup_file": "Tập tin sao lưu", "backup_password": "Mật khẩu sao lưu", @@ -482,6 +486,7 @@ "new_subaddress_label_name": "Tên nhãn", "new_subaddress_title": "Địa chỉ mới", "new_template": "Mẫu mới", + "new_transactions_notifications": "Gửi thông báo về các giao dịch mới", "new_wallet": "Ví mới", "newConnection": "Kết nối mới", "no_cards_found": "Không tìm thấy thẻ", @@ -505,6 +510,7 @@ "normal": "Bình thường", "note_optional": "Ghi chú (tùy chọn)", "note_tap_to_change": "Ghi chú (nhấn để thay đổi)", + "notification_permission_denied": "Giấy phép thông báo bị từ chối, vui lòng bật thủ công nó trong cài đặt", "nullURIError": "URI là null", "offer_expires_in": "Ưu đãi hết hạn trong: ", "offline": "Ngoại tuyến", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 6a5ff26fd..c17f07eb6 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "À ń dúró de ìjẹ́rìísí àránṣẹ́", "background_sync": "Imuṣiṣẹ Labẹ", "background_sync_mode": "Ipo amuṣiṣẹpọ abẹlẹ", + "background_sync_on_battery_low": "Muuṣiṣẹ lori batiri kekere", + "background_sync_on_charging": "Muṣiṣẹpọ nikan nigbati gbigba agbara", + "background_sync_on_device_idle": "Muṣiṣẹpọ nikan nigbati ẹrọ ko lo", + "background_sync_on_unmetered_network": "Nilo nẹtiwọki ti ko ni aabo", "backup": "Ṣẹ̀dà", "backup_file": "Ṣẹ̀dà akọsílẹ̀", "backup_password": "Ṣẹ̀dà ọ̀rọ̀ aṣínà", @@ -484,6 +488,7 @@ "new_subaddress_label_name": "Orúkọ", "new_subaddress_title": "Àdírẹ́sì títun", "new_template": "Àwòṣe títun", + "new_transactions_notifications": "Firanṣẹ awọn iwifunni nipa awọn iṣowo tuntun", "new_wallet": "Àpamọ́wọ́ títun", "newConnection": "Tuntun Asopọ", "no_cards_found": "Ko si awọn kaadi ti a rii", @@ -508,6 +513,7 @@ "normal": "Deede", "note_optional": "Àkọsílẹ̀ (ìyàn nìyí)", "note_tap_to_change": "Àkọsílẹ̀ (ẹ tẹ̀ láti pààrọ̀)", + "notification_permission_denied": "Igbanilaaye iwifunni ni sẹsẹ sẹsẹ, jọwọ jẹ ki o mu ṣiṣẹ ni awọn eto", "nullURIError": "URI jẹ asan", "offer_expires_in": "Ìrònúdábàá máa gbẹ́mìí mì ní: ", "offline": "kò wà lórí ayélujára", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index d75b2e3fb..d0bbe9c08 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -73,6 +73,10 @@ "awaiting_payment_confirmation": "等待付款确认", "background_sync": "背景同步", "background_sync_mode": "后台同步模式", + "background_sync_on_battery_low": "在低电池上同步", + "background_sync_on_charging": "仅在充电时同步", + "background_sync_on_device_idle": "仅在不使用设备时同步", + "background_sync_on_unmetered_network": "需要未经许可的网络", "backup": "备份", "backup_file": "备份文件", "backup_password": "备份密码", @@ -483,6 +487,7 @@ "new_subaddress_label_name": "标签名称", "new_subaddress_title": "新地址", "new_template": "新模板", + "new_transactions_notifications": "发送有关新交易的通知", "new_wallet": "新钱包", "newConnection": "新连接", "no_cards_found": "找不到卡", @@ -507,6 +512,7 @@ "normal": "普通的", "note_optional": "注释(可选)", "note_tap_to_change": "注释(轻按即可更改)", + "notification_permission_denied": "通知许可被e opply拒绝,请在设置中手动启用它", "nullURIError": "URI 为空", "offer_expires_in": "优惠有效期至 ", "offline": "离线", From b5ba9385e8f7861d8baf6a9dd5a7059250963a2e Mon Sep 17 00:00:00 2001 From: David Adegoke <64401859+Blazebrain@users.noreply.github.com> Date: Thu, 24 Apr 2025 23:12:56 +0100 Subject: [PATCH 053/142] Handle Network Connection Errors (#2213) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(moralis-nft-errors): guard against concurrent NFT fetches and adjusts the message being presented to the user. Previously multiple calls to get NFTs for the currently opened wallet could overlap and queue the error bottom sheet multiple times. This change: - Registers the NFTViewModel as a lazySingleton so its isLoading flag persists. - Adds an early return in the call to fetch the wallet NFTs when isLoading is already true. - Cleans up the error message being displayed to the user when there is an error. * feat(moralis-nft-error): localize error message in NFTViewModel * feat(nft/wc-bottom-sheet): Revamped the flow, service, theme, and UI for smoother UX Revamps bottom‑sheet handling end‑to‑end to deliver a much more smoother experience. This change: - Refactors the BottomSheetService queueing logic to prevent races and ensure strict sequencing - Update theme extensions and styling for the bottom‑sheet components - Adds the option to either auto dismiss or allow user manually dismiss the bottomsheet * fix: Context clash when entering the wallets on airplane mode. The flushbar clashes with the bottomSheet and results in it blocking entry to the selected wallet. This change: - Moves the logic for fetching nft to the listing page, no need fetching if the user does not route to the page, - Routes to balance page when entering from wallet list page - Adds a fade transition when entering the dashboard - Reverts nftViewModel registeration to be a factory * fix: Revert animation for now, prior to when the UX overhaul for the app is done * fix: Remove duplicate registration --- lib/di.dart | 2 +- lib/entities/main_actions.dart | 13 +++++--- .../dashboard/pages/nft_listing_page.dart | 24 +++++++++++--- .../bottom_sheet_message_display_widget.dart | 32 +++++++++++++++---- lib/src/widgets/setting_actions.dart | 6 +++- lib/view_model/dashboard/nft_view_model.dart | 13 +++----- 6 files changed, 64 insertions(+), 26 deletions(-) diff --git a/lib/di.dart b/lib/di.dart index 05f2c3f77..60a0f240d 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -651,6 +651,7 @@ Future setup({ return walletKitService; }); + getIt.registerFactory(() => NFTViewModel(appStore, getIt.get())); getIt.registerFactory(() => BalancePage( nftViewModel: getIt.get(), dashboardViewModel: getIt.get(), @@ -1451,7 +1452,6 @@ Future setup({ () => WalletConnectConnectionsView(walletKitService: getIt.get()), ); - getIt.registerFactory(() => NFTViewModel(appStore, getIt.get())); getIt.registerFactory(() => TorPage(getIt.get())); getIt.registerFactory(() => SignViewModel(getIt.get().wallet!)); diff --git a/lib/entities/main_actions.dart b/lib/entities/main_actions.dart index 2e633bce8..0cb2e4058 100644 --- a/lib/entities/main_actions.dart +++ b/lib/entities/main_actions.dart @@ -9,8 +9,7 @@ class MainActions { final bool Function(DashboardViewModel viewModel)? isEnabled; final bool Function(DashboardViewModel viewModel)? canShow; - final Future Function( - BuildContext context, DashboardViewModel viewModel) onTap; + final Future Function(BuildContext context, DashboardViewModel viewModel) onTap; MainActions._({ required this.name, @@ -32,7 +31,12 @@ class MainActions { name: (context) => S.of(context).wallets, image: 'assets/images/wallet_new.png', onTap: (BuildContext context, DashboardViewModel viewModel) async { - Navigator.pushNamed(context, Routes.walletList); + Navigator.pushNamed( + context, + Routes.walletList, + arguments: (BuildContext context) => + Navigator.of(context).pushNamedAndRemoveUntil(Routes.dashboard, (route) => false), + ); }, ); @@ -65,7 +69,6 @@ class MainActions { }, ); - static MainActions tradeAction = MainActions._( name: (context) => S.of(context).exchange, image: 'assets/images/buy_sell.png', @@ -76,4 +79,4 @@ class MainActions { await Navigator.of(context).pushNamed(Routes.buySellPage, arguments: false); }, ); -} \ No newline at end of file +} diff --git a/lib/src/screens/dashboard/pages/nft_listing_page.dart b/lib/src/screens/dashboard/pages/nft_listing_page.dart index 8da63fce6..67af337e6 100644 --- a/lib/src/screens/dashboard/pages/nft_listing_page.dart +++ b/lib/src/screens/dashboard/pages/nft_listing_page.dart @@ -11,11 +11,27 @@ import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart'; import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart'; import 'package:cw_core/wallet_type.dart'; -class NFTListingPage extends StatelessWidget { +class NFTListingPage extends StatefulWidget { final NFTViewModel nftViewModel; const NFTListingPage({super.key, required this.nftViewModel}); + @override + State createState() => _NFTListingPageState(); +} + +class _NFTListingPageState extends State { + @override + void initState() { + super.initState(); + + fetchNFTsForWallet(); + } + + Future fetchNFTsForWallet() async { + await widget.nftViewModel.getNFTAssetByWallet(); + } + @override Widget build(BuildContext context) { final dashboardTheme = Theme.of(context).extension()!; @@ -36,11 +52,11 @@ class NFTListingPage extends StatelessWidget { onPressed: () => Navigator.pushNamed( context, Routes.importNFTPage, - arguments: nftViewModel, + arguments: widget.nftViewModel, ), ), ), - if (nftViewModel.isLoading) + if (widget.nftViewModel.isLoading) Expanded( child: Center( child: CircularProgressIndicator( @@ -53,7 +69,7 @@ class NFTListingPage extends StatelessWidget { ) else Expanded( - child: NFTListWidget(nftViewModel: nftViewModel), + child: NFTListWidget(nftViewModel: widget.nftViewModel), ), ], ); diff --git a/lib/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_message_display_widget.dart b/lib/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_message_display_widget.dart index 1f5dd6768..9c4016333 100644 --- a/lib/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_message_display_widget.dart +++ b/lib/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_message_display_widget.dart @@ -14,13 +14,30 @@ class BottomSheetMessageDisplayWidget extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - isError ? S.current.error : S.current.successful, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.normal, - color: Theme.of(context).extension()!.titleColor, - ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + isError ? S.current.error : S.current.successful, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + color: Theme.of(context).extension()!.titleColor, + ), + ), + IconButton( + color: Theme.of(context).appBarTheme.titleTextStyle!.color!, + padding: const EdgeInsets.all(0.0), + visualDensity: VisualDensity.compact, + onPressed: () { + if (Navigator.canPop(context)) { + Navigator.pop(context); + } + }, + icon: const Icon(Icons.close_sharp), + ), + ], ), SizedBox(height: 8), Row( @@ -37,6 +54,7 @@ class BottomSheetMessageDisplayWidget extends StatelessWidget { ), ], ), + SizedBox(height: 16), ], ); } diff --git a/lib/src/widgets/setting_actions.dart b/lib/src/widgets/setting_actions.dart index d383129cc..e170d61e6 100644 --- a/lib/src/widgets/setting_actions.dart +++ b/lib/src/widgets/setting_actions.dart @@ -73,7 +73,11 @@ class SettingActions { image: 'assets/images/wallet_menu.png', onTap: (BuildContext context) { Navigator.pop(context); - Navigator.of(context).pushNamed(Routes.walletList); + Navigator.of(context).pushNamed( + Routes.walletList, + arguments: (BuildContext context) => + Navigator.of(context).pushNamedAndRemoveUntil(Routes.dashboard, (route) => false), + ); }, ); diff --git a/lib/view_model/dashboard/nft_view_model.dart b/lib/view_model/dashboard/nft_view_model.dart index 766e588e7..7da63d399 100644 --- a/lib/view_model/dashboard/nft_view_model.dart +++ b/lib/view_model/dashboard/nft_view_model.dart @@ -23,11 +23,7 @@ abstract class NFTViewModelBase with Store { : isLoading = false, isImportNFTLoading = false, nftAssetByWalletModels = ObservableList(), - solanaNftAssetModels = ObservableList() { - getNFTAssetByWallet(); - - reaction((_) => appStore.wallet, (_) => getNFTAssetByWallet()); - } + solanaNftAssetModels = ObservableList(); final AppStore appStore; final BottomSheetService bottomSheetService; @@ -80,6 +76,8 @@ abstract class NFTViewModelBase with Store { } try { + if (isLoading) return; + isLoading = true; final response = await http.get( @@ -114,10 +112,7 @@ abstract class NFTViewModelBase with Store { nftAssetByWalletModels.addAll(result); } - - isLoading = false; } catch (e) { - isLoading = false; log(e.toString()); bottomSheetService.queueBottomSheet( isModalDismissible: true, @@ -125,6 +120,8 @@ abstract class NFTViewModelBase with Store { message: S.current.moralis_nft_error, ), ); + } finally { + isLoading = false; } } From 10294e088a42a68e4b5c63492717bd57b9b8c383 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Fri, 25 Apr 2025 20:38:48 +0300 Subject: [PATCH 054/142] v4.27.0 Release Candidate (#2225) * v4.27.0 Release Candidate * update app versions [skip ci] --- assets/text/Monerocom_Release_Notes.txt | 8 +++----- assets/text/Release_Notes.txt | 9 ++++----- scripts/android/app_env.sh | 8 ++++---- scripts/ios/app_env.sh | 8 ++++---- scripts/linux/app_env.sh | 4 ++-- scripts/macos/app_env.sh | 8 ++++---- scripts/windows/build_exe_installer.iss | 2 +- 7 files changed, 22 insertions(+), 25 deletions(-) diff --git a/assets/text/Monerocom_Release_Notes.txt b/assets/text/Monerocom_Release_Notes.txt index 0ac065a4f..852e6ad0d 100644 --- a/assets/text/Monerocom_Release_Notes.txt +++ b/assets/text/Monerocom_Release_Notes.txt @@ -1,6 +1,4 @@ -Monero 12-word seed support (Wallet Groups support as well) -Integrate DFX's OpenCryptoPay -Exchange flow enhancements -Hardware Wallets flow enhancements -Minor UI enhancements +Background sync improvements +Payment notifications +UI/UX improvements Bug fixes \ No newline at end of file diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt index 0ac065a4f..c766c39ff 100644 --- a/assets/text/Release_Notes.txt +++ b/assets/text/Release_Notes.txt @@ -1,6 +1,5 @@ -Monero 12-word seed support (Wallet Groups support as well) -Integrate DFX's OpenCryptoPay -Exchange flow enhancements -Hardware Wallets flow enhancements -Minor UI enhancements +Background sync improvements +Payment notifications +WalletConnect enhancements +UI/UX improvements Bug fixes \ No newline at end of file diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index 4de7a3373..817b6aeda 100644 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -14,15 +14,15 @@ TYPES=($MONERO_COM $CAKEWALLET) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="4.26.0" -MONERO_COM_BUILD_NUMBER=120 +MONERO_COM_VERSION="4.27.0" +MONERO_COM_BUILD_NUMBER=121 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" MONERO_COM_SCHEME="monero.com" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.26.0" -CAKEWALLET_BUILD_NUMBER=258 +CAKEWALLET_VERSION="4.27.0" +CAKEWALLET_BUILD_NUMBER=259 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" CAKEWALLET_SCHEME="cakewallet" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index b42a8a2da..0b30e7a88 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -12,13 +12,13 @@ TYPES=($MONERO_COM $CAKEWALLET) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="4.26.0" -MONERO_COM_BUILD_NUMBER=117 +MONERO_COM_VERSION="4.27.0" +MONERO_COM_BUILD_NUMBER=118 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.26.0" -CAKEWALLET_BUILD_NUMBER=312 +CAKEWALLET_VERSION="4.27.0" +CAKEWALLET_BUILD_NUMBER=313 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" diff --git a/scripts/linux/app_env.sh b/scripts/linux/app_env.sh index 574cce2c9..4bc735e60 100755 --- a/scripts/linux/app_env.sh +++ b/scripts/linux/app_env.sh @@ -14,8 +14,8 @@ if [ -n "$1" ]; then fi CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.26.0" -CAKEWALLET_BUILD_NUMBER=52 +CAKEWALLET_VERSION="4.27.0" +CAKEWALLET_BUILD_NUMBER=53 if ! [[ " ${TYPES[*]} " =~ " ${APP_LINUX_TYPE} " ]]; then echo "Wrong app type." diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh index 651ad48e9..bc40f02b8 100755 --- a/scripts/macos/app_env.sh +++ b/scripts/macos/app_env.sh @@ -16,13 +16,13 @@ if [ -n "$1" ]; then fi MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="4.26.0" -MONERO_COM_BUILD_NUMBER=49 +MONERO_COM_VERSION="4.27.0" +MONERO_COM_BUILD_NUMBER=50 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.26.0" -CAKEWALLET_BUILD_NUMBER=111 +CAKEWALLET_VERSION="4.27.0" +CAKEWALLET_BUILD_NUMBER=112 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then diff --git a/scripts/windows/build_exe_installer.iss b/scripts/windows/build_exe_installer.iss index 1f152f635..56285af77 100644 --- a/scripts/windows/build_exe_installer.iss +++ b/scripts/windows/build_exe_installer.iss @@ -1,5 +1,5 @@ #define MyAppName "Cake Wallet" -#define MyAppVersion "4.26.0" +#define MyAppVersion "4.27.0" #define MyAppPublisher "Cake Labs LLC" #define MyAppURL "https://cakewallet.com/" #define MyAppExeName "CakeWallet.exe" From d86c501fc55041b4bc551f63bd918009fc974b0a Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Mon, 28 Apr 2025 07:21:12 +0300 Subject: [PATCH 055/142] patch: running android related code on other platforms --- ios/Podfile.lock | 26 ++++++++++++++----- lib/utils/exception_handler.dart | 4 ++- .../dashboard/dashboard_view_model.dart | 19 +++++++------- scripts/ios/app_env.sh | 8 +++--- scripts/linux/app_env.sh | 2 +- scripts/macos/app_env.sh | 4 +-- 6 files changed, 39 insertions(+), 24 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d3ba59827..50ca9d6de 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,7 +1,6 @@ PODS: - connectivity_plus (0.0.1): - Flutter - - ReachabilitySwift - CryptoSwift (1.8.4) - cw_decred (0.0.1): - Flutter @@ -59,6 +58,8 @@ PODS: - OrderedSet (~> 6.0.3) - flutter_local_authentication (1.2.0): - Flutter + - flutter_local_notifications (0.0.1): + - Flutter - flutter_mailer (0.0.1): - Flutter - flutter_secure_storage (6.0.0): @@ -77,7 +78,9 @@ PODS: - FlutterMacOS - permission_handler_apple (9.3.0): - Flutter - - ReachabilitySwift (5.2.4) + - reown_yttrium (0.0.1): + - Flutter + - YttriumWrapper (= 0.8.35) - SDWebImage (5.20.0): - SDWebImage/Core (= 5.20.0) - SDWebImage/Core (5.20.0) @@ -100,6 +103,7 @@ PODS: - Flutter - wakelock_plus (0.0.1): - Flutter + - YttriumWrapper (0.8.35) DEPENDENCIES: - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) @@ -114,6 +118,7 @@ DEPENDENCIES: - Flutter (from `Flutter`) - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) - flutter_local_authentication (from `.symlinks/plugins/flutter_local_authentication/ios`) + - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_mailer (from `.symlinks/plugins/flutter_mailer/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) @@ -122,6 +127,7 @@ DEPENDENCIES: - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) + - reown_yttrium (from `.symlinks/plugins/reown_yttrium/ios`) - sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) @@ -137,9 +143,9 @@ SPEC REPOS: - DKImagePickerController - DKPhotoGallery - OrderedSet - - ReachabilitySwift - SDWebImage - SwiftyGif + - YttriumWrapper EXTERNAL SOURCES: connectivity_plus: @@ -164,6 +170,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_inappwebview_ios/ios" flutter_local_authentication: :path: ".symlinks/plugins/flutter_local_authentication/ios" + flutter_local_notifications: + :path: ".symlinks/plugins/flutter_local_notifications/ios" flutter_mailer: :path: ".symlinks/plugins/flutter_mailer/ios" flutter_secure_storage: @@ -180,6 +188,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/path_provider_foundation/darwin" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" + reown_yttrium: + :path: ".symlinks/plugins/reown_yttrium/ios" sensitive_clipboard: :path: ".symlinks/plugins/sensitive_clipboard/ios" share_plus: @@ -198,7 +208,7 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/wakelock_plus/ios" SPEC CHECKSUMS: - connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d + connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d CryptoSwift: e64e11850ede528a02a0f3e768cec8e9d92ecb90 cw_decred: 9c0e1df74745b51a1289ec5e91fb9e24b68fa14a cw_mweb: 22cd01dfb8ad2d39b15332006f22046aaa8352a3 @@ -212,6 +222,7 @@ SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb + flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83 flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f @@ -221,7 +232,7 @@ SPEC CHECKSUMS: package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 - ReachabilitySwift: 32793e867593cfc1177f5d16491e3a197d2fccda + reown_yttrium: c0e87e5965fa60a3559564cc35cffbba22976089 SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8 sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986 share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f @@ -231,8 +242,9 @@ SPEC CHECKSUMS: uni_links: d97da20c7701486ba192624d99bffaaffcfc298a universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - wakelock_plus: 373cfe59b235a6dd5837d0fb88791d2f13a90d56 + wakelock_plus: 76957ab028e12bfa4e66813c99e46637f367fc7e + YttriumWrapper: 31e937fe9fbe0f1314d2ca6be9ce9b379a059966 -PODFILE CHECKSUM: e448f662d4c41f0c0b1ccbb78afd57dbf895a597 +PODFILE CHECKSUM: 5296465b1c6d14d506230356756826012f65d97a COCOAPODS: 1.16.2 diff --git a/lib/utils/exception_handler.dart b/lib/utils/exception_handler.dart index 547ffa571..a9348dd33 100644 --- a/lib/utils/exception_handler.dart +++ b/lib/utils/exception_handler.dart @@ -230,6 +230,8 @@ class ExceptionHandler { static Future _addDeviceInfo(File file) async { final packageInfo = await PackageInfo.fromPlatform(); final currentVersion = packageInfo.version; + final appName = packageInfo.appName; + final package = packageInfo.packageName; final deviceInfoPlugin = DeviceInfoPlugin(); Map deviceInfo = {}; @@ -252,7 +254,7 @@ class ExceptionHandler { } await file.writeAsString( - "App Version: $currentVersion\n\nDevice Info $deviceInfo\n\n", + "App Version: $currentVersion\nApp Name: $appName\nPackage: $package\n\nDevice Info $deviceInfo\n\n", mode: FileMode.append, ); } diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index e5f021783..00353f117 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -4,7 +4,6 @@ import 'dart:io' show Platform; import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/bitcoin/bitcoin.dart'; -import 'package:cake_wallet/core/background_sync.dart'; import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/balance_display_mode.dart'; @@ -538,7 +537,8 @@ abstract class DashboardViewModelBase with Store { } @observable - late bool backgroundSyncNotificationsEnabled = sharedPreferences.getBool(PreferencesKey.backgroundSyncNotificationsEnabled) ?? false; + late bool backgroundSyncNotificationsEnabled = + sharedPreferences.getBool(PreferencesKey.backgroundSyncNotificationsEnabled) ?? false; @action Future setBackgroundSyncNotificationsEnabled(bool value) async { @@ -558,7 +558,6 @@ abstract class DashboardViewModelBase with Store { await sharedPreferences.setBool(PreferencesKey.backgroundSyncNotificationsEnabled, value); } - bool get hasBgsyncNetworkConstraints => Platform.isAndroid; bool get hasBgsyncBatteryNotLowConstraints => Platform.isAndroid; bool get hasBgsyncChargingConstraints => Platform.isAndroid; @@ -575,12 +574,14 @@ abstract class DashboardViewModelBase with Store { @observable bool backgroundSyncDeviceIdle = false; - + Future _loadConstraints() async { - backgroundSyncNetworkUnmetered = await FlutterDaemon().getNetworkType(); - backgroundSyncBatteryNotLow = await FlutterDaemon().getBatteryNotLow(); - backgroundSyncCharging = await FlutterDaemon().getRequiresCharging(); - backgroundSyncDeviceIdle = await FlutterDaemon().getDeviceIdle(); + if (Platform.isAndroid) { + backgroundSyncNetworkUnmetered = await FlutterDaemon().getNetworkType(); + backgroundSyncBatteryNotLow = await FlutterDaemon().getBatteryNotLow(); + backgroundSyncCharging = await FlutterDaemon().getRequiresCharging(); + backgroundSyncDeviceIdle = await FlutterDaemon().getDeviceIdle(); + } } @action @@ -618,7 +619,7 @@ abstract class DashboardViewModelBase with Store { await enableBackgroundSync(); } } - + bool get hasBatteryOptimization => Platform.isAndroid; @observable diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index 0b30e7a88..a6d01f16e 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -12,13 +12,13 @@ TYPES=($MONERO_COM $CAKEWALLET) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="4.27.0" -MONERO_COM_BUILD_NUMBER=118 +MONERO_COM_VERSION="4.27.1" +MONERO_COM_BUILD_NUMBER=120 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.27.0" -CAKEWALLET_BUILD_NUMBER=313 +CAKEWALLET_VERSION="4.27.1" +CAKEWALLET_BUILD_NUMBER=314 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" diff --git a/scripts/linux/app_env.sh b/scripts/linux/app_env.sh index 4bc735e60..9e3676b22 100755 --- a/scripts/linux/app_env.sh +++ b/scripts/linux/app_env.sh @@ -15,7 +15,7 @@ fi CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="4.27.0" -CAKEWALLET_BUILD_NUMBER=53 +CAKEWALLET_BUILD_NUMBER=54 if ! [[ " ${TYPES[*]} " =~ " ${APP_LINUX_TYPE} " ]]; then echo "Wrong app type." diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh index bc40f02b8..f99cee740 100755 --- a/scripts/macos/app_env.sh +++ b/scripts/macos/app_env.sh @@ -17,12 +17,12 @@ fi MONERO_COM_NAME="Monero.com" MONERO_COM_VERSION="4.27.0" -MONERO_COM_BUILD_NUMBER=50 +MONERO_COM_BUILD_NUMBER=51 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="4.27.0" -CAKEWALLET_BUILD_NUMBER=112 +CAKEWALLET_BUILD_NUMBER=113 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then From e527083871fda2f90da12d98ebf9c7003ada08f8 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Mon, 28 Apr 2025 15:12:42 +0300 Subject: [PATCH 056/142] Gracefully handle toast messages error on desktop platforms --- cw_zano/lib/zano_formatter.dart | 5 +++-- .../pages/balance/balance_row_widget.dart | 19 +++++++++++-------- lib/view_model/link_view_model.dart | 18 ++++++++++-------- macos/Podfile.lock | 12 +++++++----- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/cw_zano/lib/zano_formatter.dart b/cw_zano/lib/zano_formatter.dart index e20e3a5f7..4aa35b1b1 100644 --- a/cw_zano/lib/zano_formatter.dart +++ b/cw_zano/lib/zano_formatter.dart @@ -1,7 +1,6 @@ import 'dart:math'; import 'package:cw_core/utils/print_verbose.dart'; -import 'package:cw_zano/zano_wallet_api.dart'; import 'package:decimal/decimal.dart'; import 'package:decimal/intl.dart'; import 'package:fluttertoast/fluttertoast.dart'; @@ -57,7 +56,9 @@ class ZanoFormatter { static int parseAmount(String amount, [int decimalPoint = defaultDecimalPoint]) { final resultBigInt = (Decimal.parse(amount) * Decimal.fromBigInt(BigInt.from(10).pow(decimalPoint))).toBigInt(); if (!resultBigInt.isValidInt) { - Fluttertoast.showToast(msg: 'Cannot transfer $amount. Maximum is ${intAmountToString(resultBigInt.toInt(), decimalPoint)}.'); + try { + Fluttertoast.showToast(msg: 'Cannot transfer $amount. Maximum is ${intAmountToString(resultBigInt.toInt(), decimalPoint)}.'); + } catch (_) {} } return resultBigInt.toInt(); } diff --git a/lib/src/screens/dashboard/pages/balance/balance_row_widget.dart b/lib/src/screens/dashboard/pages/balance/balance_row_widget.dart index 47d522173..33839a25f 100644 --- a/lib/src/screens/dashboard/pages/balance/balance_row_widget.dart +++ b/lib/src/screens/dashboard/pages/balance/balance_row_widget.dart @@ -87,10 +87,7 @@ class BalanceRowWidget extends StatelessWidget { // ], ), child: TextButton( - onPressed: () => Fluttertoast.showToast( - msg: S.current.show_balance_toast, - backgroundColor: Color.fromRGBO(0, 0, 0, 0.85), - ), + onPressed: _showToast, onLongPress: () => dashboardViewModel.balanceViewModel.switchBalanceValue(), style: TextButton.styleFrom( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), @@ -366,10 +363,7 @@ class BalanceRowWidget extends StatelessWidget { // ], ), child: TextButton( - onPressed: () => Fluttertoast.showToast( - msg: S.current.show_balance_toast, - backgroundColor: Color.fromRGBO(0, 0, 0, 0.85), - ), + onPressed: _showToast, onLongPress: () => dashboardViewModel.balanceViewModel.switchBalanceValue(), style: TextButton.styleFrom( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), @@ -709,4 +703,13 @@ class BalanceRowWidget extends StatelessWidget { void _showBalanceDescription(BuildContext context, String content) { showPopUp(context: context, builder: (_) => InformationPage(information: content)); } + + void _showToast() async { + try { + await Fluttertoast.showToast( + msg: S.current.show_balance_toast, + backgroundColor: Color.fromRGBO(0, 0, 0, 0.85), + ); + } catch (_) {} + } } diff --git a/lib/view_model/link_view_model.dart b/lib/view_model/link_view_model.dart index 27f0c0560..1a89943d4 100644 --- a/lib/view_model/link_view_model.dart +++ b/lib/view_model/link_view_model.dart @@ -81,14 +81,16 @@ class LinkViewModel { } Future _errorToast(String message, {double fontSize = 16}) async { - Fluttertoast.showToast( - msg: message, - toastLength: Toast.LENGTH_LONG, - gravity: ToastGravity.SNACKBAR, - backgroundColor: Colors.black, - textColor: Colors.white, - fontSize: fontSize, - ); + try { + await Fluttertoast.showToast( + msg: message, + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.SNACKBAR, + backgroundColor: Colors.black, + textColor: Colors.white, + fontSize: fontSize, + ); + } catch (_) {} } Future handleLink() async { diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 689f0ea03..5b209b0cf 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -1,7 +1,6 @@ PODS: - connectivity_plus (0.0.1): - FlutterMacOS - - ReachabilitySwift - cw_mweb (0.0.1): - FlutterMacOS - device_info_plus (0.0.1): @@ -15,6 +14,8 @@ PODS: - OrderedSet (~> 6.0.3) - flutter_local_authentication (1.2.0): - FlutterMacOS + - flutter_local_notifications (0.0.1): + - FlutterMacOS - flutter_secure_storage_macos (6.1.1): - FlutterMacOS - FlutterMacOS (1.0.0) @@ -26,7 +27,6 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - ReachabilitySwift (5.2.4) - share_plus (0.0.1): - FlutterMacOS - shared_preferences_foundation (0.0.1): @@ -50,6 +50,7 @@ DEPENDENCIES: - fast_scanner (from `Flutter/ephemeral/.symlinks/plugins/fast_scanner/macos`) - flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`) - flutter_local_authentication (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_authentication/macos`) + - flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`) - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - in_app_review (from `Flutter/ephemeral/.symlinks/plugins/in_app_review/macos`) @@ -65,7 +66,6 @@ DEPENDENCIES: SPEC REPOS: trunk: - OrderedSet - - ReachabilitySwift EXTERNAL SOURCES: connectivity_plus: @@ -82,6 +82,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos flutter_local_authentication: :path: Flutter/ephemeral/.symlinks/plugins/flutter_local_authentication/macos + flutter_local_notifications: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos flutter_secure_storage_macos: :path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos FlutterMacOS: @@ -106,20 +108,20 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos SPEC CHECKSUMS: - connectivity_plus: 18d3c32514c886e046de60e9c13895109866c747 + connectivity_plus: 0a976dfd033b59192912fa3c6c7b54aab5093802 cw_mweb: 7440b12ead811dda972a9918442ea2a458e8742c device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f devicelocale: 9f0f36ac651cabae2c33f32dcff4f32b61c38225 fast_scanner: d31bae07e2653403a69dac99fb710c1722b16a97 flutter_inappwebview_macos: bdf207b8f4ebd58e86ae06cd96b147de99a67c9b flutter_local_authentication: 85674893931e1c9cfa7c9e4f5973cb8c56b018b0 + flutter_local_notifications: 4ccab5b7a22835214a6672e3f9c5e8ae207dab36 flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 in_app_review: a6a031b9acd03c7d103e341aa334adf2c493fb93 OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - ReachabilitySwift: 32793e867593cfc1177f5d16491e3a197d2fccda share_plus: 1fa619de8392a4398bfaf176d441853922614e89 shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 sp_scanner: 269d96e0ec3173e69156be7239b95182be3b8303 From da8a82a10f1d915342bcf73e7a4965a39dea2b1d Mon Sep 17 00:00:00 2001 From: cyan Date: Wed, 30 Apr 2025 08:29:08 +0200 Subject: [PATCH 057/142] feat(dev): add secrets dev screen (used to recover corrupted wallet) (#2234) * feat(dev): add secrets dev screen (used to recover corrupted wallet) * fix(dev): decode only password fields --- lib/core/backup_service.dart | 16 ++++ lib/di.dart | 6 ++ lib/router.dart | 6 ++ lib/routes.dart | 1 + .../screens/dev/secure_preferences_page.dart | 63 ++++++++++++++++ .../screens/settings/other_settings_page.dart | 6 ++ lib/view_model/dev/secure_preferences.dart | 75 +++++++++++++++++++ 7 files changed, 173 insertions(+) create mode 100644 lib/src/screens/dev/secure_preferences_page.dart create mode 100644 lib/view_model/dev/secure_preferences.dart diff --git a/lib/core/backup_service.dart b/lib/core/backup_service.dart index 385598060..cb36072fe 100644 --- a/lib/core/backup_service.dart +++ b/lib/core/backup_service.dart @@ -251,6 +251,22 @@ class $BackupService { await importWalletKeychainInfo(info); }); + for (var key in (keychainJSON['_all'] as Map).keys) { + try { + if (!key.startsWith('MONERO_WALLET_')) continue; + final decodedPassword = decodeWalletPassword(password: keychainJSON['_all'][key].toString()); + final walletName = key.split('_WALLET_')[1]; + final walletType = key.split('_WALLET_')[0].toLowerCase(); + await importWalletKeychainInfo({ + 'name': walletName, + 'type': "WalletType.$walletType", + 'password': decodedPassword, + }); + } catch (e) { + printV('Error importing wallet ($key) password: $e'); + } + } + keychainDumpFile.deleteSync(); } diff --git a/lib/di.dart b/lib/di.dart index 60a0f240d..067fd7c4f 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -33,12 +33,14 @@ import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart'; import 'package:cake_wallet/haven/cw_haven.dart'; import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart'; import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart'; +import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart'; import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart'; import 'package:cake_wallet/src/screens/settings/background_sync_page.dart'; import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart'; import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart'; import 'package:cake_wallet/view_model/dev/monero_background_sync.dart'; +import 'package:cake_wallet/view_model/dev/secure_preferences.dart'; import 'package:cake_wallet/view_model/dev/shared_preferences.dart'; import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cake_wallet/tron/tron.dart'; @@ -920,6 +922,8 @@ Future setup({ getIt.registerFactory(() => DevSharedPreferences()); + getIt.registerFactory(() => DevSecurePreferences()); + getIt.registerFactoryParam((bool isWalletCreated, _) => WalletSeedPage(getIt.get(), isNewWalletCreated: isWalletCreated)); @@ -1463,6 +1467,8 @@ Future setup({ getIt.registerFactory(() => DevMoneroCallProfilerPage()); getIt.registerFactory(() => DevSharedPreferencesPage(getIt.get())); + + getIt.registerFactory(() => DevSecurePreferencesPage(getIt.get())); getIt.registerFactory(() => BackgroundSyncLogsViewModel()); diff --git a/lib/router.dart b/lib/router.dart index dfd163122..322c79904 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -36,6 +36,7 @@ import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart'; import 'package:cake_wallet/src/screens/dashboard/sign_page.dart'; import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart'; import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart'; +import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart'; import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart'; import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart'; import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart'; @@ -853,6 +854,11 @@ Route createRoute(RouteSettings settings) { builder: (_) => getIt.get(), ); + case Routes.devSecurePreferences: + return MaterialPageRoute( + builder: (_) => getIt.get(), + ); + default: return MaterialPageRoute( builder: (_) => Scaffold( diff --git a/lib/routes.dart b/lib/routes.dart index a9a637c75..73fe8f9b5 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -115,6 +115,7 @@ class Routes { static const devMoneroBackgroundSync = '/dev/monero_background_sync'; static const devMoneroCallProfiler = '/dev/monero_call_profiler'; static const devSharedPreferences = '/dev/shared_preferences'; + static const devSecurePreferences = '/dev/secure_preferences'; static const devBackgroundSyncLogs = '/dev/background_sync_logs'; static const signPage = '/sign_page'; diff --git a/lib/src/screens/dev/secure_preferences_page.dart b/lib/src/screens/dev/secure_preferences_page.dart new file mode 100644 index 000000000..23ab0c9df --- /dev/null +++ b/lib/src/screens/dev/secure_preferences_page.dart @@ -0,0 +1,63 @@ +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/view_model/dev/secure_preferences.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; + +class DevSecurePreferencesPage extends BasePage { + final DevSecurePreferences viewModel; + + DevSecurePreferencesPage(this.viewModel); + + @override + String? get title => "[dev] secure preferences"; + + @override + Widget body(BuildContext context) { + return Observer( + builder: (_) { + if (viewModel.values.isEmpty) { + return Center(child: Text("No secure preferences found")); + } + final keys = viewModel.keys; + Map values = {}; + for (final key in keys) { + values[key] = viewModel.get(key); + } + Map types = {}; + for (final key in keys) { + types[key] = viewModel.getPreferenceType(key); + } + return ListView.builder( + itemCount: keys.length, + itemBuilder: (context, index) { + final key = keys[index]; + final type = types[key]!; + return ListTile( + onTap: () { + Clipboard.setData(ClipboardData(text: key + ": " + values[key].toString())); + }, + title: switch (type) { + PreferenceType.bool => Text(key, style: TextStyle(color: Colors.blue)), + PreferenceType.int => Text(key, style: TextStyle(color: Colors.green)), + PreferenceType.double => Text(key, style: TextStyle(color: Colors.yellow)), + PreferenceType.listString => Text(key, style: TextStyle(color: Colors.purple)), + PreferenceType.string => Text(key), + PreferenceType.unknown => Text(key), + }, + subtitle: switch (type) { + PreferenceType.bool => Text("bool: ${values[key]}"), + PreferenceType.int => Text("int: ${values[key]}"), + PreferenceType.double => Text("double: ${values[key]}"), + PreferenceType.listString => values[key].isEmpty as bool ? Text("listString: []") : Text("listString:\n- ${values[key].join("\n- ")}"), + PreferenceType.string => Text("string: ${values[key]}"), + PreferenceType.unknown => Text("UNKNOWN(${values[key].runtimeType}): ${values[key]}"), + }, + ); + }, + ); + }, + ); + } + +} \ No newline at end of file diff --git a/lib/src/screens/settings/other_settings_page.dart b/lib/src/screens/settings/other_settings_page.dart index 730699286..a52f9ae06 100644 --- a/lib/src/screens/settings/other_settings_page.dart +++ b/lib/src/screens/settings/other_settings_page.dart @@ -81,6 +81,12 @@ class OtherSettingsPage extends BasePage { handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.devSharedPreferences), ), + if (FeatureFlag.hasDevOptions) + SettingsCellWithArrow( + title: '[dev] secure storage preferences', + handler: (BuildContext context) => + Navigator.of(context).pushNamed(Routes.devSecurePreferences), + ), if (FeatureFlag.hasDevOptions) SettingsCellWithArrow( title: '[dev] background sync logs', diff --git a/lib/view_model/dev/secure_preferences.dart b/lib/view_model/dev/secure_preferences.dart new file mode 100644 index 000000000..379022f89 --- /dev/null +++ b/lib/view_model/dev/secure_preferences.dart @@ -0,0 +1,75 @@ +import 'package:cake_wallet/core/secure_storage.dart'; +import 'package:cake_wallet/entities/encrypt.dart'; +import 'package:mobx/mobx.dart'; + +part 'secure_preferences.g.dart'; + +class DevSecurePreferences = DevSecurePreferencesBase with _$DevSecurePreferences; + +enum PreferenceType { + unknown, + string, + int, + double, + bool, + listString +} + +abstract class DevSecurePreferencesBase with Store { + DevSecurePreferencesBase() { + secureStorageShared.readAll().then((value) { + values = value; + }); + } + + @observable + Map values = {}; + + @computed + List get keys => values.keys.toList()..sort(); + + @action + Future delete(String key) async { + + } + + dynamic get(String key) { + if (!values.containsKey(key)) { + return null; + } + if (!key.startsWith("MONERO_WALLET_")) return values[key]!; + try { + final decodedPassword = decodeWalletPassword(password: values[key]!); + return values[key]! + "\n\nDecoded: $decodedPassword"; + } catch (e) { + return values[key]! +"\n$e"; + } + } + + Future set(String key, PreferenceType type, dynamic value) async { + + } + + PreferenceType getPreferenceType(String key) { + if (!values.containsKey(key)) { + return PreferenceType.unknown; + } + final value = values[key]; + if (value is String) { + return PreferenceType.string; + } + if (value is bool) { + return PreferenceType.bool; + } + if (value is int) { + return PreferenceType.int; + } + if (value is double) { + return PreferenceType.double; + } + if (value is List) { + return PreferenceType.listString; + } + return PreferenceType.unknown; + } +} From 0188535eb97f3c64060485051e00f37c269f0806 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Wed, 30 Apr 2025 10:27:21 +0300 Subject: [PATCH 058/142] Enable error reports without cooldown in testing builds --- lib/utils/exception_handler.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/utils/exception_handler.dart b/lib/utils/exception_handler.dart index a9348dd33..7f6571f3f 100644 --- a/lib/utils/exception_handler.dart +++ b/lib/utils/exception_handler.dart @@ -20,7 +20,8 @@ import 'package:shared_preferences/shared_preferences.dart'; class ExceptionHandler { static bool _hasError = false; - static const _coolDownDurationInDays = 7; + static const _coolDownDurationInDays = + bool.fromEnvironment('hasDevOptions', defaultValue: kDebugMode) ? 0 : 7; static File? _file; static Future _saveException(String? error, StackTrace? stackTrace, From 4448adb49ddc698baeb97687d91970233111989c Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 30 Apr 2025 19:11:36 +0300 Subject: [PATCH 059/142] Cw 925 improve automatic subaddress generation behavior (#2217) * fix: update usedAddresses for monero * fix: subaddresses generation for bitcoin * fix: minor fix * fix: update receive address UI when used * minor fix [skip ci] --- cw_bitcoin/lib/bitcoin_address_record.dart | 9 +++-- cw_bitcoin/lib/electrum_wallet_addresses.dart | 39 +++++++++++-------- cw_monero/lib/monero_wallet_addresses.dart | 4 +- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/cw_bitcoin/lib/bitcoin_address_record.dart b/cw_bitcoin/lib/bitcoin_address_record.dart index 7e4b5f58f..1509f913a 100644 --- a/cw_bitcoin/lib/bitcoin_address_record.dart +++ b/cw_bitcoin/lib/bitcoin_address_record.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'package:mobx/mobx.dart'; import 'package:bitcoin_base/bitcoin_base.dart'; @@ -16,7 +17,7 @@ abstract class BaseBitcoinAddressRecord { }) : _txCount = txCount, _balance = balance, _name = name, - _isUsed = isUsed; + _isUsed = Observable(isUsed); @override bool operator ==(Object o) => o is BaseBitcoinAddressRecord && address == o.address; @@ -27,7 +28,7 @@ abstract class BaseBitcoinAddressRecord { int _txCount; int _balance; String _name; - bool _isUsed; + final Observable _isUsed; BasedUtxoNetwork? network; int get txCount => _txCount; @@ -40,9 +41,9 @@ abstract class BaseBitcoinAddressRecord { set balance(int value) => _balance = value; - bool get isUsed => _isUsed; + bool get isUsed => _isUsed.value; - void setAsUsed() => _isUsed = true; + void setAsUsed() => _isUsed.value = true; void setNewName(String label) => _name = label; int get hashCode => address.hashCode; diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 35c15e578..614a06a3b 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -144,27 +144,32 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { return silentAddress.toString(); } - String receiveAddress; + final typeMatchingAddresses = _addresses.where((addr) => !addr.isHidden && _isAddressPageTypeMatch(addr)).toList(); + final typeMatchingReceiveAddresses = typeMatchingAddresses.where((addr) => !addr.isUsed).toList(); - final typeMatchingReceiveAddresses = - receiveAddresses.where(_isAddressPageTypeMatch).where((addr) => !addr.isUsed); - - if ((isEnabledAutoGenerateSubaddress && receiveAddresses.isEmpty) || - typeMatchingReceiveAddresses.isEmpty) { - receiveAddress = generateNewAddress().address; - } else { - final previousAddressMatchesType = - previousAddressRecord != null && previousAddressRecord!.type == addressPageType; - - if (previousAddressMatchesType && - typeMatchingReceiveAddresses.first.address != addressesByReceiveType.first.address) { - receiveAddress = previousAddressRecord!.address; - } else { - receiveAddress = typeMatchingReceiveAddresses.first.address; + if (!isEnabledAutoGenerateSubaddress) { + if (previousAddressRecord != null && + previousAddressRecord!.type == addressPageType) { + return previousAddressRecord!.address; } + + if (typeMatchingAddresses.isNotEmpty) { + return typeMatchingAddresses.first.address; + } + + return generateNewAddress().address; } - return receiveAddress; + if (typeMatchingAddresses.isEmpty || typeMatchingReceiveAddresses.isEmpty) { + return generateNewAddress().address; + } + + final prev = previousAddressRecord; + if (prev != null && prev.type == addressPageType && !prev.isUsed) { + return prev.address; + } + + return typeMatchingReceiveAddresses.first.address; } @observable diff --git a/cw_monero/lib/monero_wallet_addresses.dart b/cw_monero/lib/monero_wallet_addresses.dart index 14b8a2b9b..d0cf87489 100644 --- a/cw_monero/lib/monero_wallet_addresses.dart +++ b/cw_monero/lib/monero_wallet_addresses.dart @@ -130,8 +130,8 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store { final transactions = _moneroTransactionHistory.transactions.values.toList(); transactions.forEach((element) { - final accountIndex = element.accountIndex; - final addressIndex = element.addressIndex; + final accountIndex = element.additionalInfo['accountIndex'] as int? ?? 0; + final addressIndex = element.additionalInfo['addressIndex'] as int? ?? 0; usedAddresses.add(getAddress(accountIndex: accountIndex, addressIndex: addressIndex)); }); } From 0e4c9bb1388fc43066f9d3b5b20da52a1b447a77 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Thu, 1 May 2025 16:21:47 +0300 Subject: [PATCH 060/142] Fix not accepting "," in some currencies --- lib/view_model/send/output.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/view_model/send/output.dart b/lib/view_model/send/output.dart index 23efe8c1a..6c3588404 100644 --- a/lib/view_model/send/output.dart +++ b/lib/view_model/send/output.dart @@ -250,7 +250,7 @@ abstract class OutputBase with Store { sendAll = false; } - cryptoAmount = amount; + cryptoAmount = amount.replaceAll(',', '.'); _updateFiatAmount(); } From d4190cbb0f741e2156f877fd1ea7b5e64c2e1aad Mon Sep 17 00:00:00 2001 From: rottenwheel <92872541+rottenwheel@users.noreply.github.com> Date: Thu, 1 May 2025 11:21:16 -0400 Subject: [PATCH 061/142] en: fix unmetered network typo (#2241) * fix: remove comma in OpenAlias title * spanish: some necessary, minor rewording for new background sync strings * en: fix unmetered network typo --- res/values/strings_en.arb | 4 ++-- res/values/strings_es.arb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 69495ba72..2072b149b 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -76,7 +76,7 @@ "background_sync_on_battery_low": "Synchronize on low battery", "background_sync_on_charging": "Synchronize only when charging", "background_sync_on_device_idle": "Synchronize only when device is not being used", - "background_sync_on_unmetered_network": "Require unmetred network", + "background_sync_on_unmetered_network": "Require unmetered network", "backup": "Backup", "backup_file": "Backup file", "backup_password": "Backup password", @@ -1059,4 +1059,4 @@ "you_will_send": "Convert from", "youCanGoBackToYourDapp": "You can go back to your dApp now", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 70a809243..bc3cbff48 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -1059,4 +1059,4 @@ "you_will_send": "Convertir de", "youCanGoBackToYourDapp": "Puedes volver a tu dapp ahora", "yy": "YY" -} \ No newline at end of file +} From 2a6dad22576ece73807a844f4cac66b369290b26 Mon Sep 17 00:00:00 2001 From: Tobias Wienkoop Date: Thu, 1 May 2025 17:22:54 +0200 Subject: [PATCH 062/142] Improve german translation (#2230) * Update strings_de.arb * Update strings_de.arb * Update strings_de.arb --- res/values/strings_de.arb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index b87ff03d3..c5743cd8b 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -73,10 +73,10 @@ "awaiting_payment_confirmation": "Warten auf Zahlungsbestätigung", "background_sync": "Hintergrundsynchronisation", "background_sync_mode": "Hintergrundsynchronisierungsmodus", - "background_sync_on_battery_low": "Synchronisieren Sie einen niedrigen Akku", + "background_sync_on_battery_low": "Bei niedrigem Akkustand synchronisieren", "background_sync_on_charging": "Nur beim Laden synchronisieren", "background_sync_on_device_idle": "Nur dann synchronisieren, wenn das Gerät nicht verwendet wird", - "background_sync_on_unmetered_network": "Erfordern ein nicht modisches Netzwerk", + "background_sync_on_unmetered_network": "Erfordere ein ungetaktetes Netzwerk", "backup": "Sicherung", "backup_file": "Sicherungsdatei", "backup_password": "Passwort sichern", @@ -276,7 +276,7 @@ "enable_mempool_api": "Mempool-API für genaue Gebühren und Daten", "enable_replace_by_fee": "Aktivieren Sie Ersatz für Fee", "enable_silent_payments_scanning": "Scannen Sie nach Silent Payments ihrer Adresse", - "enabled": "Ermöglicht", + "enabled": "Aktiviert", "enter_amount": "Betrag eingeben", "enter_backup_password": "Sicherungskennwort hier eingeben", "enter_code": "Code eingeben", @@ -1061,4 +1061,4 @@ "you_will_send": "Konvertieren von", "youCanGoBackToYourDapp": "Sie können jetzt zu Ihrem Dapp zurückkehren", "yy": "YY" -} \ No newline at end of file +} From cb257e1391325633acd818356f9e7fb77b8e4bb2 Mon Sep 17 00:00:00 2001 From: Serhii Date: Thu, 1 May 2025 18:23:56 +0300 Subject: [PATCH 063/142] fix: allow address only edits (#2233) --- lib/view_model/contact_list/contact_view_model.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/view_model/contact_list/contact_view_model.dart b/lib/view_model/contact_list/contact_view_model.dart index efa1f09f4..2030cf03e 100644 --- a/lib/view_model/contact_list/contact_view_model.dart +++ b/lib/view_model/contact_list/contact_view_model.dart @@ -57,7 +57,11 @@ abstract class ContactViewModelBase with Store { state = IsExecutingState(); final now = DateTime.now(); - if (doesContactNameExist(name)) { + final nameExists = _contact == null + ? doesContactNameExist(name) + : doesContactNameExist(name) && _contact.original.name != name; + + if (nameExists) { state = FailureState(S.current.contact_name_exists); return; } From dd8413bae264c79a3a9eb0fa1233004160fe3363 Mon Sep 17 00:00:00 2001 From: David Adegoke <64401859+Blazebrain@users.noreply.github.com> Date: Fri, 2 May 2025 00:56:06 +0100 Subject: [PATCH 064/142] CW 818: Improve Passphrase Discoverability When Restoring (#2224) * feat(passphrase for restore): improve passphrase discoverability for restore flow. Makes the option for adding a passphrase more visible for the user to see while restoring a wallet with seeds. This change: - Adds a checkbox on the restore from seeds page that asks if the wallet has a passphrase - Switches the main CTA for the page to "Add Passphrase" when the checkbox is checked - Pops up a bottomsheet with input fields for the passphrase when the "Add Passphrase" button is pressed - Completes the flow and restores the wallet with the passphrase when the "Restore" button on the bottomsheet is pressed - Hides the passphrase input fields in AdvancedSettings page when its a restore flow. * feat(passphrase for restore): improve passphrase discoverability for restore flow. (Cont.) This change: - Obscures the passpgrase input fields by default - Changes the color for the main CTA button * feat(passphrase for restore): improve passphrase discoverability for restore flow. (Cont.) This change: - Adjusts the colors for the buttons on both the seed page and the bottomsheet * feat(passphrase for restore): improve passphrase discoverability for restore flow. (Cont.) This change: - Combines the control for the visibility icons of the passphrase input fields - one tap to rule them all :) --- assets/images/passphrase_key.svg | 22 ++ .../advanced_privacy_settings_page.dart | 2 +- .../screens/restore/wallet_restore_page.dart | 154 +++++++----- lib/src/widgets/base_text_form_field.dart | 6 + .../add_passphrase_bottom_sheet_widget.dart | 220 ++++++++++++++++++ lib/view_model/wallet_restore_view_model.dart | 4 + res/values/strings_ar.arb | 5 + res/values/strings_bg.arb | 5 + res/values/strings_cs.arb | 5 + res/values/strings_de.arb | 5 + res/values/strings_en.arb | 5 + res/values/strings_es.arb | 5 + res/values/strings_fr.arb | 5 + res/values/strings_ha.arb | 5 + res/values/strings_hi.arb | 5 + res/values/strings_hr.arb | 5 + res/values/strings_hy.arb | 5 + res/values/strings_id.arb | 5 + res/values/strings_it.arb | 5 + res/values/strings_ja.arb | 5 + res/values/strings_ko.arb | 5 + res/values/strings_my.arb | 5 + res/values/strings_nl.arb | 5 + res/values/strings_pl.arb | 5 + res/values/strings_pt.arb | 5 + res/values/strings_ru.arb | 5 + res/values/strings_th.arb | 5 + res/values/strings_tl.arb | 5 + res/values/strings_tr.arb | 5 + res/values/strings_uk.arb | 5 + res/values/strings_ur.arb | 5 + res/values/strings_vi.arb | 5 + res/values/strings_yo.arb | 5 + res/values/strings_zh.arb | 5 + 34 files changed, 491 insertions(+), 57 deletions(-) create mode 100644 assets/images/passphrase_key.svg create mode 100644 lib/src/widgets/bottom_sheet/add_passphrase_bottom_sheet_widget.dart diff --git a/assets/images/passphrase_key.svg b/assets/images/passphrase_key.svg new file mode 100644 index 000000000..c577dc30a --- /dev/null +++ b/assets/images/passphrase_key.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart index d03f334a1..1511ffd83 100644 --- a/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart +++ b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart @@ -204,7 +204,7 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo ); return Container(); }), - if (widget.privacySettingsViewModel.hasPassphraseOption) + if (widget.privacySettingsViewModel.hasPassphraseOption && !widget.isFromRestore) Padding( padding: EdgeInsets.all(24), child: Form( diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index e2e149644..2c26741e1 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -5,10 +5,14 @@ import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/restore/wallet_restore_from_keys_form.dart'; import 'package:cake_wallet/src/screens/restore/wallet_restore_from_seed_form.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/src/widgets/bottom_sheet/add_passphrase_bottom_sheet_widget.dart'; import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/widgets/standard_checkbox.dart'; +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; +import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; -import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/restore/restore_mode.dart'; @@ -52,7 +56,6 @@ class WalletRestorePage extends BasePage { // String? derivationPath = null; DerivationInfo? derivationInfo; - @override Function(BuildContext)? get popWidget => (context) => seedSettingsViewModel.setPassphrase(null); @@ -102,37 +105,81 @@ class WalletRestorePage extends BasePage { padding: EdgeInsets.only(top: 20, bottom: 24, left: 24, right: 24), child: Column( children: [ + Observer( + builder: (context) { + return walletRestoreViewModel.mode == WalletRestoreMode.seed + ? StandardCheckbox( + value: walletRestoreViewModel.hasPassphrase, + caption: S.of(context).wallet_has_passphrase, + onChanged: (value) { + walletRestoreViewModel.hasPassphrase = value; + }, + ) + : SizedBox.shrink(); + }, + ), + SizedBox(height: 16), + PrimaryButton( + key: ValueKey('wallet_restore_advanced_settings_button_key'), + onPressed: () { + Navigator.of(context).pushNamed( + Routes.advancedPrivacySettings, + arguments: { + 'isFromRestore': true, + 'type': walletRestoreViewModel.type, + 'useTestnet': walletRestoreViewModel.useTestnet, + 'toggleTestnet': walletRestoreViewModel.toggleUseTestnet + }, + ); + }, + text: S.of(context).advanced_settings, + color: Theme.of(context).cardColor, + textColor: Theme.of(context).extension()!.buttonTextColor, + ), + SizedBox(height: 8), Observer( builder: (context) { return LoadingPrimaryButton( key: ValueKey('wallet_restore_seed_or_key_restore_button_key'), - onPressed: () async => await _confirmForm(context), - text: S.of(context).restore_recover, - color: Theme.of(context) - .extension()! - .createNewWalletButtonBackgroundColor, - textColor: Theme.of(context) - .extension()! - .restoreWalletButtonTextColor, + onPressed: () async { + if (walletRestoreViewModel.hasPassphrase) { + await showModalBottomSheet( + context: context, + isDismissible: false, + isScrollControlled: true, + builder: (BuildContext bottomSheetContext) { + return Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom, + ), + child: AddPassphraseBottomSheet( + currentTheme: currentTheme, + titleText: S.of(context).add_passphrase, + onRestoreButtonPressed: (passphrase) async { + await _onPassphraseBottomSheetRestoreButtonPressed( + passphrase, + context, + ); + }, + ), + ); + }, + ); + } else { + await _confirmForm(context); + } + }, + text: walletRestoreViewModel.hasPassphrase + ? S.of(context).add_passphrase + : S.of(context).restore_recover, + color: Theme.of(context).primaryColor, + textColor: Colors.white, isLoading: walletRestoreViewModel.state is IsExecutingState, isDisabled: !walletRestoreViewModel.isButtonEnabled, ); }, ), - const SizedBox(height: 25), - GestureDetector( - key: ValueKey('wallet_restore_advanced_settings_button_key'), - onTap: () { - Navigator.of(context) - .pushNamed(Routes.advancedPrivacySettings, arguments: { - 'isFromRestore': true, - 'type': walletRestoreViewModel.type, - 'useTestnet': walletRestoreViewModel.useTestnet, - 'toggleTestnet': walletRestoreViewModel.toggleUseTestnet - }); - }, - child: Text(S.of(context).advanced_settings), - ), + const SizedBox(height: 24), ], ), ) @@ -144,6 +191,14 @@ class WalletRestorePage extends BasePage { ); } + Future _onPassphraseBottomSheetRestoreButtonPressed( + String passphrase, + BuildContext context, + ) async { + walletRestoreViewModel.seedSettingsViewModel.setPassphrase(passphrase); + await _confirmForm(context); + } + Map _credentials() { final credentials = {}; @@ -172,9 +227,10 @@ class WalletRestorePage extends BasePage { walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text; credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text; if (walletRestoreViewModel.type != WalletType.decred) { - credentials['address'] = walletRestoreFromKeysFormKey.currentState!.addressController.text; + credentials['address'] = + walletRestoreFromKeysFormKey.currentState!.addressController.text; credentials['spendKey'] = - walletRestoreFromKeysFormKey.currentState!.spendKeyController.text; + walletRestoreFromKeysFormKey.currentState!.spendKeyController.text; credentials['height'] = walletRestoreFromKeysFormKey.currentState!.blockchainHeightKey.currentState!.height; } @@ -335,8 +391,8 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody> final initialIndex = walletRestoreViewModel.mode == WalletRestoreMode.seed ? 0 : _hasKeysTab - ? 1 - : 0; + ? 1 + : 0; _tabController = TabController(length: tabCount, vsync: this, initialIndex: initialIndex); @@ -429,7 +485,7 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody> padding: EdgeInsets.zero, tabs: [ Tab(text: S.of(context).widgets_seed), - if (_hasKeysTab) Tab(text: S.of(context).keys), + if (_hasKeysTab) Tab(text: S.of(context).keys), ], ), ), @@ -527,42 +583,30 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody> } bool _isValidSeed() { - final seedPhrase = walletRestoreFromSeedFormKey - .currentState!.seedWidgetStateKey.currentState!.text; + final seedPhrase = + walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.text; if (walletRestoreViewModel.isPolyseed(seedPhrase)) return true; final seedWords = seedPhrase.split(' '); - if (seedWords.length == 14 && - walletRestoreViewModel.type == WalletType.wownero) return true; - if (seedWords.length == 26 && - walletRestoreViewModel.type == WalletType.zano) return true; + if (seedWords.length == 14 && walletRestoreViewModel.type == WalletType.wownero) return true; + if (seedWords.length == 26 && walletRestoreViewModel.type == WalletType.zano) return true; - if (seedWords.length == 12 && - walletRestoreViewModel.type == WalletType.monero) { - return walletRestoreFromSeedFormKey - .currentState - ?.blockchainHeightKey - .currentState - ?.restoreHeightController - .text - .isNotEmpty == true; + if (seedWords.length == 12 && walletRestoreViewModel.type == WalletType.monero) { + return walletRestoreFromSeedFormKey.currentState?.blockchainHeightKey.currentState + ?.restoreHeightController.text.isNotEmpty == + true; } if ([WalletType.monero, WalletType.wownero, WalletType.haven] .contains(walletRestoreViewModel.type) && - seedWords.length == - WalletRestoreViewModelBase.moneroSeedMnemonicLength) { + seedWords.length == WalletRestoreViewModelBase.moneroSeedMnemonicLength) { return true; } // bip39: final validBip39SeedLengths = [12, 18, 24]; - final nonBip39WalletTypes = [ - WalletType.wownero, - WalletType.haven, - WalletType.decred - ]; + final nonBip39WalletTypes = [WalletType.wownero, WalletType.haven, WalletType.decred]; // if it's a bip39 wallet and the length is not valid return false if (!nonBip39WalletTypes.contains(walletRestoreViewModel.type) && !(validBip39SeedLengths.contains(seedWords.length))) { @@ -570,14 +614,12 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody> } if ((walletRestoreViewModel.type == WalletType.decred) && - seedWords.length != - WalletRestoreViewModelBase.decredSeedMnemonicLength) { + seedWords.length != WalletRestoreViewModelBase.decredSeedMnemonicLength) { return false; } - final words = walletRestoreFromSeedFormKey - .currentState!.seedWidgetStateKey.currentState!.words - .toSet(); + final words = + walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.words.toSet(); return seedWords.toSet().difference(words).toSet().isEmpty; } diff --git a/lib/src/widgets/base_text_form_field.dart b/lib/src/widgets/base_text_form_field.dart index 4648b88cc..806ceb04b 100644 --- a/lib/src/widgets/base_text_form_field.dart +++ b/lib/src/widgets/base_text_form_field.dart @@ -15,6 +15,8 @@ class BaseTextFormField extends StatelessWidget { this.textColor, this.hintColor, this.borderColor, + this.fillColor, + this.filled, this.prefix, this.prefixIcon, this.suffix, @@ -44,6 +46,8 @@ class BaseTextFormField extends StatelessWidget { final Color? textColor; final Color? hintColor; final Color? borderColor; + final Color? fillColor; + bool? filled; final Widget? prefix; final Widget? prefixIcon; final Widget? suffix; @@ -89,6 +93,8 @@ class BaseTextFormField extends StatelessWidget { prefixIcon: prefixIcon, suffix: suffix, suffixIcon: suffixIcon, + fillColor: fillColor, + filled: filled, hintStyle: placeholderTextStyle ?? TextStyle( color: hintColor ?? Theme.of(context).hintColor, diff --git a/lib/src/widgets/bottom_sheet/add_passphrase_bottom_sheet_widget.dart b/lib/src/widgets/bottom_sheet/add_passphrase_bottom_sheet_widget.dart new file mode 100644 index 000000000..b6d9b08f1 --- /dev/null +++ b/lib/src/widgets/bottom_sheet/add_passphrase_bottom_sheet_widget.dart @@ -0,0 +1,220 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +class AddPassphraseBottomSheet extends StatefulWidget { + AddPassphraseBottomSheet({ + required String titleText, + required this.currentTheme, + required this.onRestoreButtonPressed, + }); + + final void Function(String) onRestoreButtonPressed; + final ThemeBase currentTheme; + + @override + State createState() => _AddPassphraseBottomSheetState(); +} + +class _AddPassphraseBottomSheetState extends State { + late final TextEditingController passphraseController; + late final TextEditingController confirmPassphraseController; + + @override + void initState() { + super.initState(); + passphraseController = TextEditingController(); + confirmPassphraseController = TextEditingController(); + } + + @override + void dispose() { + passphraseController.dispose(); + confirmPassphraseController.dispose(); + super.dispose(); + } + + bool obscurePassphrase = true; + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.vertical(top: Radius.circular(30.0)), + color: Theme.of(context).dialogBackgroundColor, + ), + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + child: Column( + children: [ + Row( + children: [ + const Spacer(flex: 4), + Expanded( + flex: 2, + child: Container( + height: 6, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: + Theme.of(context).extension()!.titleColor.withOpacity(0.6), + ), + ), + ), + const Spacer(flex: 4), + ], + ), + SizedBox(height: 16), + Text( + S.of(context).add_passphrase, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.titleColor, + decoration: TextDecoration.none, + ), + ), + SvgPicture.asset('assets/images/passphrase_key.svg'), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: '${S.of(context).warning.toUpperCase()}: ', + style: TextStyle( + fontSize: 16, + fontFamily: 'Lato', + fontWeight: FontWeight.w700, + color: Palette.red, + decoration: TextDecoration.none, + ), + ), + TextSpan( + text: S.of(context).add_passphrase_warning_text, + ), + ], + ), + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + fontFamily: 'Lato', + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.titleColor.withOpacity(0.7), + decoration: TextDecoration.none, + ), + ), + ), + SizedBox(height: 24), + TextFormField( + key: ValueKey('add_passphrase_bottom_sheet_widget_passphrase_textfield_key'), + controller: passphraseController, + obscureText: obscurePassphrase, + decoration: InputDecoration( + contentPadding: EdgeInsets.symmetric(horizontal: 12), + filled: true, + fillColor: Theme.of(context).cardColor, + hintText: S.of(context).required_passphrase, + suffixIcon: GestureDetector( + onTap: () { + setState(() { + obscurePassphrase = !obscurePassphrase; + }); + }, + child: Icon( + obscurePassphrase ? Icons.visibility_off : Icons.visibility, + size: 24, + color: Theme.of(context).textTheme.bodyLarge?.color?.withOpacity(0.6), + ), + ), + border: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(16), + ), + ), + ), + SizedBox(height: 8), + TextFormField( + key: ValueKey('add_passphrase_bottom_sheet_widget_confirm_passphrase_textfield_key'), + controller: confirmPassphraseController, + obscureText: obscurePassphrase, + decoration: InputDecoration( + contentPadding: EdgeInsets.symmetric(horizontal: 12), + filled: true, + fillColor: Theme.of(context).cardColor, + hintText: S.of(context).confirm_passphrase, + suffixIcon: GestureDetector( + onTap: () { + setState(() { + obscurePassphrase = !obscurePassphrase; + }); + }, + child: Icon( + obscurePassphrase ? Icons.visibility_off : Icons.visibility, + size: 24, + color: Theme.of(context).textTheme.bodyLarge?.color?.withOpacity(0.6), + ), + ), + border: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(16), + ), + ), + validator: (text) { + if (text == passphraseController.text) { + return null; + } + + return S.of(context).passphrases_doesnt_match; + }, + ), + SizedBox(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Flexible( + child: Container( + padding: const EdgeInsets.only(right: 8.0, top: 8.0), + child: PrimaryButton( + key: ValueKey('add_passphrase_bottom_sheet_widget_cancel_button_key'), + onPressed: () { + Navigator.pop(context); + }, + text: S.of(context).cancel, + color: Theme.of(context).cardColor, + textColor: Theme.of(context).extension()!.buttonTextColor, + ), + ), + ), + Flexible( + child: Container( + padding: const EdgeInsets.only(left: 8.0, top: 8.0), + child: PrimaryButton( + key: ValueKey('add_passphrase_bottom_sheet_widget_restore_button_key'), + onPressed: () { + Navigator.pop(context); + widget.onRestoreButtonPressed(passphraseController.text); + }, + text: S.of(context).restore, + color: Theme.of(context).primaryColor, + textColor: Colors.white, + ), + ), + ), + ], + ), + ), + SizedBox(height: 24), + ], + ), + ), + ); + } +} diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index dec0be6b1..6e00ba4cc 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -43,6 +43,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { type == WalletType.solana || type == WalletType.tron, isButtonEnabled = false, + hasPassphrase = false, mode = restoredWallet?.restoreMode ?? WalletRestoreMode.seed, super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel, type: type, isRecovery: true) { @@ -89,6 +90,9 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { @observable WalletRestoreMode mode; + @observable + bool hasPassphrase; + @observable bool isButtonEnabled; diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index e68f6dc3f..4082e778c 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "أضف أموالاً مدفوعة مسبقًا إلى البطاقات (حتى ${value})", "add_new_node": "أضافة عقدة جديدة", "add_new_word": "أضف كلمة جديدة", + "add_passphrase": "أضف عبارة المرور", + "add_passphrase_warning_text": "أدخل عبارة المرور فقط إذا كنت قد استخدمت واحدة لهذه المحفظة في الماضي. إذا أدخلت عبارة الممر الخاطئ أو لم تستخدم عبارة تمريرة من قبل على هذه المحفظة ، فلن ترى أيًا من الأموال أو التاريخ الحالي.", "add_receiver": "أضف مستقبل آخر (اختياري)", "add_secret_code": " ﺔﻗﺩﺎﺼﻤﻟﺍ ﻖﻴﺒﻄﺗ ﻰﻟﺇ ﻱﺮﺴﻟﺍ ﺰﻣﺮﻟﺍ ﺍﺬﻫ ﻒﺿﺃ ﻭﺃ", "add_tip": "أضف بقشيش", @@ -621,10 +623,12 @@ "require_for_sends_to_internal_wallets": "تتطلب عمليات الإرسال إلى المحافظ الداخلية", "require_for_sends_to_non_contacts": "تتطلب لارسال لغير جهات الاتصال", "require_pin_after": "طلب PIN بعد", + "required_passphrase": "عبارة المرور", "rescan": "إعادة الفحص", "resend_code": "الرجاء إعادة إرسالها", "reset": "إعادة", "reset_password": "إعادة تعيين كلمة المرور", + "restore": "يعيد", "restore_active_seed": "السييد النشطة", "restore_address": "العنوان", "restore_bitcoin_description_from_keys": "قم باستعادة محفظتك من سلسلة WIF التي تم إنشاؤها من مفاتيحك الخاصة", @@ -999,6 +1003,7 @@ "wallet_group_description_view_seed": "يمكنك دائمًا عرض هذه البذرة مرة أخرى تحت", "wallet_group_empty_state_text_one": "يبدو أنه ليس لديك أي مجموعات محفظة متوافقة !\n\n انقر", "wallet_group_empty_state_text_two": "أدناه لجعل واحدة جديدة.", + "wallet_has_passphrase": "هذه المحفظة تحتوي على عبارة ممر", "wallet_keys": "سييد المحفظة / المفاتيح", "wallet_list_create_new_wallet": "إنشاء محفظة جديدة", "wallet_list_edit_group_name": "تحرير اسم المجموعة", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 775cc8152..32f886335 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "Добавете предплатени средства в картите (до ${value})", "add_new_node": "Добави нов node", "add_new_word": "Добавяне на нова дума", + "add_passphrase": "Добавете парола", + "add_passphrase_warning_text": "Въведете парола само ако сте използвали такава за този портфейл в миналото. Ако въведете грешна парола или не сте използвали парола преди в този портфейл, няма да видите нито един от съществуващите средства или история.", "add_receiver": "Добавяне на друг получател (не е задължително)", "add_secret_code": "Или добавете този таен код към приложение за удостоверяване", "add_tip": "Add Tip", @@ -621,10 +623,12 @@ "require_for_sends_to_internal_wallets": "Изискване за изпращане до вътрешни портфейли", "require_for_sends_to_non_contacts": "Изискване за изпращане до лица без контакт", "require_pin_after": "Въведете PIN след", + "required_passphrase": "Парола", "rescan": "Сканирай отново", "resend_code": "Повторно изпращане", "reset": "Нулиране", "reset_password": "Нулиране на парола", + "restore": "Възстановяване", "restore_active_seed": "Активиране на seed", "restore_address": "Адреси", "restore_bitcoin_description_from_keys": "Възстановяване на портфейл чрез WIF, изведен от Вашите private keys", @@ -999,6 +1003,7 @@ "wallet_group_description_view_seed": "Винаги можете да видите това семе отново под", "wallet_group_empty_state_text_one": "Изглежда, че нямате съвместими групи портфейли !\n\n tap", "wallet_group_empty_state_text_two": "по -долу, за да се направи нов.", + "wallet_has_passphrase": "Този портфейл има парола", "wallet_keys": "Seed/keys на портфейла", "wallet_list_create_new_wallet": "Създаване на нов портфейл", "wallet_list_edit_group_name": "Редактиране на име на групата", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 7f9e0fcf7..2ee9cb693 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "Všechny předplacené prostředky na kartě (až ${value})", "add_new_node": "Přidat nový uzel", "add_new_word": "Přidat nové slovo", + "add_passphrase": "Přidejte přístupovou frázi", + "add_passphrase_warning_text": "Zadejte přístupovou frázi pouze tehdy, pokud jste ji v minulosti použili pro tuto peněženku. Pokud zadáte nesprávnou přístupovou frázi nebo jste na této peněžence nepoužili přístupovou frázi, neuvidíte žádnou existující fondy nebo historii.", "add_receiver": "Přidat dalšího příjemce (nepovinné)", "add_secret_code": "Nebo přidejte tento tajný kód do ověřovací aplikace", "add_tip": "Přidat spropitné", @@ -621,10 +623,12 @@ "require_for_sends_to_internal_wallets": "Vyžadovat pro odesílání do interních peněženek", "require_for_sends_to_non_contacts": "Vyžadovat pro odesílání nekontaktním osobám", "require_pin_after": "Vyžadovat PIN po", + "required_passphrase": "Passphrase", "rescan": "Znovu prohledat", "resend_code": "Prosím poslat znovu", "reset": "Vymazat", "reset_password": "Resetovat heslo", + "restore": "Obnovit", "restore_active_seed": "Aktivní seed", "restore_address": "Adresa", "restore_bitcoin_description_from_keys": "Obnovte svou peněženku pomocí vygenerovaného WIF řetězce z vašich soukromých klíčů", @@ -999,6 +1003,7 @@ "wallet_group_description_view_seed": "Toto semeno si můžete vždy znovu prohlédnout", "wallet_group_empty_state_text_one": "Vypadá to, že nemáte žádné kompatibilní skupiny peněženky !\n\n", "wallet_group_empty_state_text_two": "Níže vytvořit nový.", + "wallet_has_passphrase": "Tato peněženka má přístupovou frázi", "wallet_keys": "Seed/klíče peněženky", "wallet_list_create_new_wallet": "Vytvořit novou peněženku", "wallet_list_edit_group_name": "Upravit název skupiny", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index c5743cd8b..16f661e97 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "Prepaid-Guthaben zu den Karten hinzufügen (bis zu ${value})", "add_new_node": "Neuen Knoten hinzufügen", "add_new_word": "Neues Wort hinzufügen", + "add_passphrase": "Fügen Sie Passphrase hinzu", + "add_passphrase_warning_text": "Geben Sie nur eine Passphrase ein, wenn Sie in der Vergangenheit eine für diese Brieftasche verwendet haben. Wenn Sie die falsche Passphrase eingeben oder in dieser Brieftasche noch keine Passphrase verwendet haben, werden Sie keine vorhandenen Gelder oder Geschichte sehen.", "add_receiver": "Fügen Sie einen weiteren Empfänger hinzu (optional)", "add_secret_code": "Oder fügen Sie diesen Geheimcode einer Authentifizierungs-App hinzu", "add_tip": "Tipp hinzufügen", @@ -622,10 +624,12 @@ "require_for_sends_to_internal_wallets": "Erforderlich für Sendungen an interne Wallets", "require_for_sends_to_non_contacts": "Erforderlich für Versendungen an Nichtkontakte", "require_pin_after": "PIN anfordern nach", + "required_passphrase": "Passphrase", "rescan": "Erneut scannen", "resend_code": "Bitte erneut senden", "reset": "Zurücksetzen", "reset_password": "Passwort zurücksetzen", + "restore": "Wiederherstellen", "restore_active_seed": "Aktiver Seed", "restore_address": "Adresse", "restore_bitcoin_description_from_keys": "Stellen Sie Ihre Wallet aus der generierten WIF-Zeichenfolge aus Ihren privaten Schlüsseln wieder her", @@ -1002,6 +1006,7 @@ "wallet_group_description_view_seed": "Sie können diesen Seed immer wieder untersuchen", "wallet_group_empty_state_text_one": "Sieht so aus, als hätten Sie keine kompatiblen Walletgruppen !\n\n TAP", "wallet_group_empty_state_text_two": "unten, um einen neuen zu machen.", + "wallet_has_passphrase": "Diese Brieftasche hat eine Passphrase", "wallet_keys": "Wallet-Seed/-Schlüssel", "wallet_list_create_new_wallet": "Neue Wallet erstellen", "wallet_list_edit_group_name": "Gruppenname bearbeiten", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 2072b149b..b045f944f 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "Add prepaid funds to the cards (up to ${value})", "add_new_node": "Add new node", "add_new_word": "Add new word", + "add_passphrase": "Add Passphrase", + "add_passphrase_warning_text": "Only enter a passphrase if you have used one for this wallet in the past. If you enter the wrong passphrase or have not used a passphrase before on this wallet, you won't see any of existing funds or history.", "add_receiver": "Add another receiver (optional)", "add_secret_code": "Or, add this secret code to an authenticator app", "add_tip": "Add Tip", @@ -622,10 +624,12 @@ "require_for_sends_to_internal_wallets": "Require for sends to internal wallets", "require_for_sends_to_non_contacts": "Require for sends to non-contacts", "require_pin_after": "Require PIN after", + "required_passphrase": "Passphrase", "rescan": "Rescan", "resend_code": "Please resend it", "reset": "Reset", "reset_password": "Reset Password", + "restore": "Restore", "restore_active_seed": "Active seed", "restore_address": "Address", "restore_bitcoin_description_from_keys": "Restore your wallet from generated WIF string from your private keys", @@ -1000,6 +1004,7 @@ "wallet_group_description_view_seed": "You can always view this seed again under", "wallet_group_empty_state_text_one": "Looks like you don't have any compatible wallet groups!\n\nTap", "wallet_group_empty_state_text_two": "below to make a new one.", + "wallet_has_passphrase": "This wallet has a passphrase", "wallet_keys": "Wallet seed/keys", "wallet_list_create_new_wallet": "Create New Wallet", "wallet_list_edit_group_name": "Edit Group Name", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index bc3cbff48..d877b1d24 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "Agregar fondos prepagos a las tarjetas (hasta ${value})", "add_new_node": "Agregar nuevo nodo", "add_new_word": "Agregar palabra nueva", + "add_passphrase": "Agregar frase de pases", + "add_passphrase_warning_text": "Solo ingrese una frase de pases si ha usado una para esta billetera en el pasado. Si ingresa a la frase de pases incorrecta o no ha utilizado una frase de pases antes en esta billetera, no verá ninguno de los fondos o historial existentes.", "add_receiver": "Agregar otro receptor (opcional)", "add_secret_code": "O agregue este código secreto a una aplicación de autenticación", "add_tip": "Agregar sugerencia", @@ -622,10 +624,12 @@ "require_for_sends_to_internal_wallets": "Requerido para envíos a billeteras internas", "require_for_sends_to_non_contacts": "Requerido para envíos a no contactos", "require_pin_after": "Requerir PIN después de", + "required_passphrase": "Frase", "rescan": "Reescanear", "resend_code": "Por favor reenvíalo", "reset": "Reiniciar", "reset_password": "Restablecer contraseña", + "restore": "Restaurar", "restore_active_seed": "Semilla activa", "restore_address": "Dirección", "restore_bitcoin_description_from_keys": "Restaure su billetera a partir de una cadena WIF generada a partir de sus claves privadas", @@ -1000,6 +1004,7 @@ "wallet_group_description_view_seed": "Siempre puedes ver esta semilla nuevamente debajo", "wallet_group_empty_state_text_one": "Parece que no tienes ningún grupo de billetera compatible !\n\n toque", "wallet_group_empty_state_text_two": "a continuación para hacer uno nuevo.", + "wallet_has_passphrase": "Esta billetera tiene una frase de pases", "wallet_keys": "Billetera semilla/claves", "wallet_list_create_new_wallet": "Crear nueva billetera", "wallet_list_edit_group_name": "Editar nombre de grupo", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 01510b7a9..e2a4617eb 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "Ajouter des fonds prépayés aux cartes (jusqu'à ${value})", "add_new_node": "Ajouter un nouveau nœud", "add_new_word": "Ajouter un nouveau mot", + "add_passphrase": "Ajouter la phrase secrète", + "add_passphrase_warning_text": "Entrez une phrase secrète si vous en avez utilisé un pour ce portefeuille dans le passé. Si vous entrez dans la mauvaise phrase de passe ou si vous n'avez pas utilisé de phrase de passe auparavant sur ce portefeuille, vous ne verrez aucun fonds ou historique existant.", "add_receiver": "Ajouter un autre bénéficiaire (optionnel)", "add_secret_code": "Ou ajoutez ce code secret à une application d'authentification", "add_tip": "Ajouter un pourboire", @@ -621,10 +623,12 @@ "require_for_sends_to_internal_wallets": "Exiger pour les envois vers des portefeuilles (wallets) internes", "require_for_sends_to_non_contacts": "Exiger pour les envois hors contacts", "require_pin_after": "Code PIN requis après", + "required_passphrase": "Phrase secrète", "rescan": "Analyser la blockchain", "resend_code": "Veuillez le renvoyer", "reset": "Réinitialiser", "reset_password": "Réinitialiser le mot de passe", + "restore": "Restaurer", "restore_active_seed": "Phrase secrète (seed) active", "restore_address": "Adresse", "restore_bitcoin_description_from_keys": "Restaurer votre portefeuille (wallet) d'après la chaîne WIF générée d'après vos clefs privées", @@ -999,6 +1003,7 @@ "wallet_group_description_view_seed": "Vous pouvez toujours revoir cette graine sous", "wallet_group_empty_state_text_one": "On dirait que vous n'avez pas de groupes de portefeuilles compatibles !\n\n Tap", "wallet_group_empty_state_text_two": "Ci-dessous pour en faire un nouveau.", + "wallet_has_passphrase": "Ce portefeuille a une phrase secrète", "wallet_keys": "Phrase secrète (seed)/Clefs du portefeuille (wallet)", "wallet_list_create_new_wallet": "Créer un Nouveau Portefeuille (Wallet)", "wallet_list_edit_group_name": "Modifier le nom du groupe", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 7629eec5f..9c138de7e 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "Ƙara kuɗin da aka riga aka biya a katunan (har zuwa ${value})", "add_new_node": "Ƙara sabon node", "add_new_word": "Ƙara kalma sabuwa", + "add_passphrase": "Addara fasphrase", + "add_passphrase_warning_text": "Kawai shigar da kalmar wucewa idan kun yi amfani da ɗaya don wannan walat a baya. Idan ka shigar da kalmar wucewa ko ba a yi amfani da kalmar wucewa ba kafin a wannan waljin, ba za ka ga wani kudaden da ake da su ba ko tarihi.", "add_receiver": "Ƙara wani mai karɓa (na zaɓi)", "add_secret_code": "Ko, ƙara wannan lambar sirrin zuwa ƙa'idar mai tabbatarwa", "add_tip": "Ƙara Tukwici", @@ -623,10 +625,12 @@ "require_for_sends_to_internal_wallets": "Bukatar aika zuwa wallet na ciki", "require_for_sends_to_non_contacts": "Bukatar aika zuwa waɗanda ba lambobin sadarwa ba", "require_pin_after": "Bukatar PIN bayan", + "required_passphrase": "Mashiganya", "rescan": "Rescan", "resend_code": "Da fatan za a sake aika shi", "reset": "Sake saiti", "reset_password": "Sake saita kalmar wucewa", + "restore": "Sabunta", "restore_active_seed": "iri mai aiki", "restore_address": "Address", "restore_bitcoin_description_from_keys": "Dawo da kwalinku daga WIF string dake generate daga maɓallan sirri", @@ -1001,6 +1005,7 @@ "wallet_group_description_view_seed": "Koyaushe zaka iya duba wannan zuriya", "wallet_group_empty_state_text_one": "Kamar dai ba ku da wata ƙungiya matattara !\n\n Taɓa", "wallet_group_empty_state_text_two": "da ke ƙasa don yin sabo.", + "wallet_has_passphrase": "Wannan walat ɗin yana da kalmar wucewa", "wallet_keys": "Iri/maɓalli na walat", "wallet_list_create_new_wallet": "Ƙirƙiri Sabon Wallet", "wallet_list_edit_group_name": "Shirya sunan rukuni", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 73d168d47..0acb517d5 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "कार्ड में प्रीपेड धनराशि जोड़ें (${value} तक)", "add_new_node": "नया नोड जोड़ें", "add_new_word": "नया शब्द जोड़ें", + "add_passphrase": "पासफ़्रेज़ जोड़ें", + "add_passphrase_warning_text": "यदि आपने अतीत में इस बटुए के लिए एक का उपयोग किया है, तो केवल एक पासफ्रेज़ दर्ज करें। यदि आप गलत पासफ्रेज़ में प्रवेश करते हैं या इस वॉलेट पर पहले पासफ्रेज़ का उपयोग नहीं करते हैं, तो आप मौजूदा फंड या इतिहास में से कोई भी नहीं देखेंगे।", "add_receiver": "एक और रिसीवर जोड़ें (वैकल्पिक)", "add_secret_code": "या, इस गुप्त कोड को प्रमाणक ऐप में जोड़ें", "add_tip": "टिप जोड़ें", @@ -623,10 +625,12 @@ "require_for_sends_to_internal_wallets": "आंतरिक वॉलेट में भेजने की आवश्यकता है", "require_for_sends_to_non_contacts": "गैर-संपर्कों को भेजने की आवश्यकता", "require_pin_after": "इसके बाद पिन आवश्यक है", + "required_passphrase": "पदबंध", "rescan": "पुन: स्कैन", "resend_code": "कृपया इसे फिर से भेजें", "reset": "रीसेट", "reset_password": "पासवर्ड रीसेट करें", + "restore": "पुनर्स्थापित करना", "restore_active_seed": "सक्रिय बीज", "restore_address": "पता", "restore_bitcoin_description_from_keys": "अपने निजी कुंजी से उत्पन्न WIF स्ट्रिंग से अपने वॉलेट को पुनर्स्थापित करें", @@ -1001,6 +1005,7 @@ "wallet_group_description_view_seed": "आप हमेशा इस बीज को फिर से देख सकते हैं", "wallet_group_empty_state_text_one": "लगता है कि आपके पास कोई संगत बटुआ समूह नहीं है!\n\nनल", "wallet_group_empty_state_text_two": "नीचे एक नया बनाने के लिए।", + "wallet_has_passphrase": "इस बटुए में एक पासफ़्रेज़ है", "wallet_keys": "बटुआ बीज / चाबियाँ", "wallet_list_create_new_wallet": "नया बटुआ बनाएँ", "wallet_list_edit_group_name": "समूह का नाम संपादित करें", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 75aac2682..4657ef754 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "Dodajte unaprijed uplaćena sredstva na kartice (do ${value})", "add_new_node": "Dodaj novi node", "add_new_word": "Dodaj novu riječ", + "add_passphrase": "Dodajte prolaznu frazu", + "add_passphrase_warning_text": "U prošlosti unesite samo prolaznu frazu ako ste je koristili za ovaj novčanik. Ako uđete u pogrešnu lozu ili niste prije koristili prolaznu frazu na ovom novčaniku, nećete vidjeti nijedno postojeće fondove ili povijest.", "add_receiver": "Dodajte drugi prijemnik (izborno)", "add_secret_code": "Ili dodajte ovaj tajni kod u aplikaciju za autentifikaciju", "add_tip": "Dodaj savjet", @@ -621,10 +623,12 @@ "require_for_sends_to_internal_wallets": "Zahtijeva za slanje u interne novčanike", "require_for_sends_to_non_contacts": "Zahtijeva za slanje nekontaktima", "require_pin_after": "Zahtijevaj PIN nakon", + "required_passphrase": "Prolazna fraza", "rescan": "Ponovno skeniranje", "resend_code": "Molimo da ga ponovno pošaljete", "reset": "Resetiraj", "reset_password": "Poništi lozinku", + "restore": "Vratiti", "restore_active_seed": "Aktivan pristupni izraz", "restore_address": "Adresa", "restore_bitcoin_description_from_keys": "Oporavi novčanik pomoću WIF niza generiranog iz vlastitih privatnih ključeva (keys)", @@ -999,6 +1003,7 @@ "wallet_group_description_view_seed": "Uvijek možete ponovo pogledati ovo sjeme ispod", "wallet_group_empty_state_text_one": "Izgleda da nemate nikakve kompatibilne grupe novčanika !\n\n", "wallet_group_empty_state_text_two": "Ispod da napravite novi.", + "wallet_has_passphrase": "Ovaj novčanik ima prolaznu frazu", "wallet_keys": "Pristupni izraz/ključ novčanika", "wallet_list_create_new_wallet": "Izradi novi novčanik", "wallet_list_edit_group_name": "Uredi naziv grupe", diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index 09585df8a..4ec2ae678 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "Ավելացնել նախավճար քարտերի վրա (մինչև ${value})", "add_new_node": "Ավելացնել նոր հանգույց", "add_new_word": "Ավելացնել նոր բառ", + "add_passphrase": "Ավելացնել գաղտնաբառ", + "add_passphrase_warning_text": "Մուտքագրեք միայն գաղտնաբառ, եթե նախկինում այս դրամապանակի համար օգտագործեք մեկը: Եթե ​​մուտքագրեք սխալ գաղտնաբառ կամ այս դրամապանակում նախկինում չեք օգտագործել գաղտնաբառ, ապա առկա միջոցներից կամ պատմություն չեք տեսնի:", "add_receiver": "Ավելացնել ևս մեկ ստացող (ընտրովի)", "add_secret_code": "Կամ ավելացրեք այս գաղտնի կոդը վավերացնող հավելվածում", "add_tip": "Ավելացնել Թեյավճար", @@ -620,10 +622,12 @@ "require_for_sends_to_internal_wallets": "Պահանջվում է ներքին դրամապանակներ ուղարկելու համար", "require_for_sends_to_non_contacts": "Պահանջվում է ոչ կոնտակտ անձանց ուղարկելու համար", "require_pin_after": "Պահանջվում է PIN-ը հետո", + "required_passphrase": "Փոշի", "rescan": "Վերասկանավորել", "resend_code": "Խնդրում ենք կրկին ուղարկել", "reset": "Վերասահմանել", "reset_password": "Վերասահմանել գաղտնաբառը", + "restore": "Վերականգնել", "restore_active_seed": "Ակտիվ սերմ", "restore_address": "Հասցե", "restore_bitcoin_description_from_keys": "Վերականգնեք ձեր դրամապանակը ձեր գախտնի բանալիներից ստացված WIF տողից", @@ -997,6 +1001,7 @@ "wallet_group_description_view_seed": "Միշտ կարող եք կրկին դիտել այս սերմը ներքեւում", "wallet_group_empty_state_text_one": "Կարծես թե որեւէ համատեղելի դրամապանակի խմբեր չունեք:\n\nԹակել", "wallet_group_empty_state_text_two": "ներքեւում `նորը կազմելու համար:", + "wallet_has_passphrase": "Այս դրամապանակն ունի գաղտնաբառ", "wallet_keys": "Դրամապանակի սերմ/բանալիներ", "wallet_list_create_new_wallet": "Ստեղծել Նոր Դրամապանակ", "wallet_list_edit_group_name": "Խմբագրել խմբի անվանումը", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index a3e6abd1f..be1eb8c35 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "Tambahkan dana pra-bayar ke kartu (hingga ${value})", "add_new_node": "Tambah node baru", "add_new_word": "Tambahkan kata baru", + "add_passphrase": "Tambahkan frasa sandi", + "add_passphrase_warning_text": "Hanya masukkan frasa sandi jika Anda telah menggunakan satu untuk dompet ini di masa lalu. Jika Anda memasukkan frasa sandi yang salah atau belum menggunakan frasa sandi sebelumnya di dompet ini, Anda tidak akan melihat dana atau sejarah yang ada.", "add_receiver": "Tambahkan penerima lain (opsional)", "add_secret_code": "Atau, tambahkan kode rahasia ini ke aplikasi autentikator", "add_tip": "Tambahkan Tip", @@ -623,10 +625,12 @@ "require_for_sends_to_internal_wallets": "Diperlukan untuk mengirim ke dompet internal", "require_for_sends_to_non_contacts": "Wajibkan untuk mengirim ke non-kontak", "require_pin_after": "Meminta PIN setelah", + "required_passphrase": "Frasa sandi", "rescan": "Pindai ulang", "resend_code": "Silakan kirim ulang", "reset": "Reset", "reset_password": "Atur Ulang Kata Sandi", + "restore": "Memulihkan", "restore_active_seed": "Seed aktif", "restore_address": "Alamat", "restore_bitcoin_description_from_keys": "Pulihkan dompet Anda dari string WIF yang dihasilkan dari private keys Anda", @@ -1002,6 +1006,7 @@ "wallet_group_description_view_seed": "Anda selalu dapat melihat benih ini lagi di bawah", "wallet_group_empty_state_text_one": "Sepertinya Anda tidak memiliki grup dompet yang kompatibel !\n\n tap", "wallet_group_empty_state_text_two": "di bawah ini untuk membuat yang baru.", + "wallet_has_passphrase": "Dompet ini memiliki frasa sandi", "wallet_keys": "Seed/kunci dompet", "wallet_list_create_new_wallet": "Buat Dompet Baru", "wallet_list_edit_group_name": "Edit Nama Grup", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index b558501d4..427bf09cb 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "Aggiungi fondi prepagati alle carte (fino a ${value})", "add_new_node": "Aggiungi nuovo nodo", "add_new_word": "Aggiungi nuova parola", + "add_passphrase": "Aggiungi passphrase", + "add_passphrase_warning_text": "Inserisci una passphrase solo se ne hai usato uno per questo portafoglio in passato. Se si inserisce la passphrase sbagliata o non hai prima utilizzato una passphrase su questo portafoglio, non vedrai nessuno dei fondi o della storia esistenti.", "add_receiver": "Aggiungi un altro ricevitore (opzionale)", "add_secret_code": "Oppure aggiungi questo codice segreto a un'app di autenticazione", "add_tip": "Aggiungi suggerimento", @@ -622,10 +624,12 @@ "require_for_sends_to_internal_wallets": "Richiedi per invii a portafogli interni", "require_for_sends_to_non_contacts": "Richiedi per invii a non contatti", "require_pin_after": "Richiedi PIN dopo", + "required_passphrase": "Passphrase", "rescan": "Scansiona di nuovo", "resend_code": "Per favore, invialo nuovamente", "reset": "Ripristina", "reset_password": "Reimposta password", + "restore": "Ripristinare", "restore_active_seed": "Seme attivo", "restore_address": "Indirizzo", "restore_bitcoin_description_from_keys": "Recupera il tuo portafoglio da una stringa WIF generata dalle tue chiavi private", @@ -1001,6 +1005,7 @@ "wallet_group_description_view_seed": "Puoi sempre visualizzare di nuovo questo seme sotto", "wallet_group_empty_state_text_one": "Sembra che tu non abbia alcun gruppo di portafoglio compatibile!\n\nPremi", "wallet_group_empty_state_text_two": "Di seguito per crearne uno nuovo.", + "wallet_has_passphrase": "Questo portafoglio ha una passphrase", "wallet_keys": "Seme Portafoglio /chiavi", "wallet_list_create_new_wallet": "Crea Nuovo Portafoglio", "wallet_list_edit_group_name": "Modifica nome del gruppo", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 933fb706a..c6395572e 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "プリペイド資金をカードに追加します(最大 ${value})", "add_new_node": "新しいノードを追加", "add_new_word": "新しい単語を追加", + "add_passphrase": "パスフレーズを追加します", + "add_passphrase_warning_text": "過去にこのウォレットに使用した場合にのみ、パスフレーズを入力してください。間違ったパスフレーズに入ったり、このウォレットでパスフレーズを使用したことがない場合、既存の資金や歴史はありません。", "add_receiver": "別のレシーバーを追加します(オプション)", "add_secret_code": "または、このシークレット コードを認証アプリに追加します", "add_tip": "ヒントを追加", @@ -622,10 +624,12 @@ "require_for_sends_to_internal_wallets": "内部ウォレットへの送信に必須", "require_for_sends_to_non_contacts": "非連絡先への送信に必須", "require_pin_after": "後に PIN が必要", + "required_passphrase": "パスフレーズ", "rescan": "再スキャン", "resend_code": "再送してください", "reset": "リセットする", "reset_password": "パスワードのリセット", + "restore": "復元する", "restore_active_seed": "アクティブシード", "restore_address": "住所", "restore_bitcoin_description_from_keys": "秘密鍵から生成されたWIF文字列からウォレットを復元します", @@ -1000,6 +1004,7 @@ "wallet_group_description_view_seed": "いつでもこの種を再び見ることができます", "wallet_group_empty_state_text_one": "互換性のあるウォレットグループがないようです!\n\nタップ", "wallet_group_empty_state_text_two": "以下に新しいものを作るために。", + "wallet_has_passphrase": "このウォレットにはパスフレーズがあります", "wallet_keys": "ウォレットシード/キー", "wallet_list_create_new_wallet": "新しいウォレットを作成", "wallet_list_edit_group_name": "グループ名を編集します", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 582ec8f45..6dffa5db4 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "카드에 선불 금액 추가(최대 ${value})", "add_new_node": "새 노드 추가", "add_new_word": "새로운 단어 추가", + "add_passphrase": "암호를 추가하십시오", + "add_passphrase_warning_text": "과거 에이 지갑에 사용한 경우에만 암호를 입력하십시오. 이 지갑에서 잘못된 암호를 입력하거나 암호를 사용하지 않은 경우 기존 자금이나 이력이 보이지 않을 것입니다.", "add_receiver": "다른 수신기 추가(선택 사항)", "add_secret_code": "또는 이 비밀 코드를 인증 앱에 추가하세요.", "add_tip": "팁 추가", @@ -621,10 +623,12 @@ "require_for_sends_to_internal_wallets": "내부 지갑으로 보내는 데 필요", "require_for_sends_to_non_contacts": "비접촉자에게 보내는 데 필요", "require_pin_after": "다음 이후에 PIN 필요", + "required_passphrase": "암호", "rescan": "재검색", "resend_code": "다시 보내주세요", "reset": "다시 놓기", "reset_password": "비밀번호 재설정", + "restore": "복원하다", "restore_active_seed": "활성 종자", "restore_address": "주소", "restore_bitcoin_description_from_keys": "개인 키에서 생성 된 WIF 문자열에서 지갑 복원", @@ -999,6 +1003,7 @@ "wallet_group_description_view_seed": "이 씨앗을 언제든지 다시 볼 수 있습니다", "wallet_group_empty_state_text_one": "호환 지갑 그룹이없는 것 같습니다 !\n\n TAP", "wallet_group_empty_state_text_two": "아래에서 새로운 것을 만들기 위해.", + "wallet_has_passphrase": "이 지갑에는 암호가 있습니다", "wallet_keys": "지갑 시드 / 키", "wallet_list_create_new_wallet": "새 월렛 만들기", "wallet_list_edit_group_name": "그룹 이름 편집", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 69eb337ee..b9b3ad37e 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "ကတ်များသို့ ကြိုတင်ငွေပေးငွေများ ထည့်ပါ (${value} အထိ)", "add_new_node": "နှာခေါင်း အသစ်ထည့်ပါ။", "add_new_word": "စကားလုံးအသစ်ထည့်ပါ။", + "add_passphrase": "passphrase ထည့်ပါ", + "add_passphrase_warning_text": "အကယ်. သင်သည်ယခင်ကဤပိုက်ဆံအိတ်အတွက်တစ်ခုသုံးခဲ့လျှင် passphrase တစ်ခုသာရိုက်ထည့်ပါ။ အကယ်. သင်သည်မှားယွင်းသော passphrase ကို 0 င်ရောက်ခြင်းသို့မဟုတ်ဤပိုက်ဆံအိတ်ပေါ်တွင် passphrase မသုံးပါကလက်ရှိရန်ပုံငွေများသို့မဟုတ်သမိုင်းကိုသင်မတွေ့ရပါ။", "add_receiver": "အခြားလက်ခံသူ ထည့်ပါ (ချန်လှပ်ထားနိုင်သည်)", "add_secret_code": "သို့မဟုတ် ဤလျှို့ဝှက်ကုဒ်ကို အထောက်အထားစိစစ်ခြင်းအက်ပ်တစ်ခုသို့ ထည့်ပါ။", "add_tip": "အကြံပြုချက်ထည့်ပါ။", @@ -621,10 +623,12 @@ "require_for_sends_to_internal_wallets": "အတွင်းပိုင်း ပိုက်ဆံအိတ်များသို့ ပေးပို့ရန် လိုအပ်သည်။", "require_for_sends_to_non_contacts": "အဆက်အသွယ်မရှိသူများထံ ပေးပို့ရန် လိုအပ်သည်။", "require_pin_after": "ပြီးနောက် PIN လိုအပ်ပါသည်။", + "required_passphrase": "စကားဝှက်PPRase", "rescan": "ပြန်စကင်န်လုပ်ပါ။", "resend_code": "ကျေးဇူးပြု၍ ပြန်ပို့ပါ။", "reset": "ပြန်လည်သတ်မှတ်ပါ။", "reset_password": "လျှို့ဝှတ်နံပါတ်အားမူလအတိုင်းပြန်လုပ်သည်", + "restore": "ပြန်လည်တည်ထောင်", "restore_active_seed": "တက်ကြွသောအစေ့", "restore_address": "လိပ်စာ", "restore_bitcoin_description_from_keys": "သင့်ကိုယ်ပိုင်သော့များမှ ထုတ်လုပ်ထားသော WIF စာကြောင်းမှ သင့်ပိုက်ဆံအိတ်ကို ပြန်လည်ရယူပါ။", @@ -999,6 +1003,7 @@ "wallet_group_description_view_seed": "သင်သည်ဤမျိုးစေ့ကိုနောက်တဖန်ရှုမြင်နိုင်သည်", "wallet_group_empty_state_text_one": "သင့်တွင်သဟဇာတဖြစ်သောပိုက်ဆံအိတ်အုပ်စုများမရှိပါ။ !\n\n ကိုအသာပုတ်ပါ", "wallet_group_empty_state_text_two": "အသစ်တစ်ခုကိုတစ်ခုလုပ်ဖို့အောက်တွင်ဖော်ပြထားသော။", + "wallet_has_passphrase": "ဒီပိုက်ဆံအိတ်က passphrase ရှိတယ်", "wallet_keys": "ပိုက်ဆံအိတ် အစေ့/သော့များ", "wallet_list_create_new_wallet": "Wallet အသစ်ဖန်တီးပါ။", "wallet_list_edit_group_name": "အုပ်စုအမည်ကိုတည်းဖြတ်ပါ", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index da508e880..7c2881a39 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "Voeg prepaid tegoed toe aan de kaarten (tot ${value})", "add_new_node": "Voeg een nieuw knooppunt toe", "add_new_word": "Nieuw woord toevoegen", + "add_passphrase": "Voeg wachtwoordzin toe", + "add_passphrase_warning_text": "Voer alleen een wachtwoordzin in als u er in het verleden een voor deze portemonnee hebt gebruikt. Als u de verkeerde wachtwoordzin invoert of nog niet eerder op deze portemonnee een wachtwoordzin hebt gebruikt, ziet u geen bestaande fondsen of geschiedenis.", "add_receiver": "Nog een ontvanger toevoegen (optioneel)", "add_secret_code": "Of voeg deze geheime code toe aan een authenticator-app", "add_tip": "Tip toevoegen", @@ -621,10 +623,12 @@ "require_for_sends_to_internal_wallets": "Vereist voor verzendingen naar interne portefeuilles", "require_for_sends_to_non_contacts": "Vereist voor verzendingen naar niet-contacten", "require_pin_after": "Pincode vereist na", + "required_passphrase": "Wachtwoordzin", "rescan": "Opnieuw scannen", "resend_code": "Stuur het alstublieft opnieuw", "reset": "Reset", "reset_password": "Wachtwoord resetten", + "restore": "Herstellen", "restore_active_seed": "Actief zaad", "restore_address": "Adres", "restore_bitcoin_description_from_keys": "Herstel uw portemonnee van de gegenereerde WIF-string van uw privésleutels", @@ -1000,6 +1004,7 @@ "wallet_group_description_view_seed": "Je kunt dit zaad altijd opnieuw bekijken", "wallet_group_empty_state_text_one": "Het lijkt erop dat je geen compatibele portemonnee -groepen hebt !\n\n TAP", "wallet_group_empty_state_text_two": "hieronder om een ​​nieuwe te maken.", + "wallet_has_passphrase": "Deze portemonnee heeft een wachtwoordzin", "wallet_keys": "Portemonnee zaad/sleutels", "wallet_list_create_new_wallet": "Maak een nieuwe portemonnee", "wallet_list_edit_group_name": "Groepsnaam bewerken", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 12765881c..3feece87f 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "Dodaj przedpłacone środki do kart (do ${value})", "add_new_node": "Dodaj nowy węzeł", "add_new_word": "Dodaj nowe słowo", + "add_passphrase": "Dodaj hasło", + "add_passphrase_warning_text": "Wprowadź hasło tylko wtedy, gdy w przeszłości używałeś jednego do tego portfela. Jeśli wejdziesz do niewłaściwej hasła lub nie użyjesz wcześniejszego pensjonatu na tym portfelu, nie zobaczysz żadnego z istniejących funduszy ani historii.", "add_receiver": "Dodaj kolejnego odbiorcę (opcjonalnie)", "add_secret_code": "Możesz też dodać ten tajny kod do aplikacji uwierzytelniającej", "add_tip": "Dodaj wskazówkę", @@ -621,10 +623,12 @@ "require_for_sends_to_internal_wallets": "Wymagaj wysyłania do portfeli wewnętrznych", "require_for_sends_to_non_contacts": "Wymagaj wysyłania do osób niekontaktowych", "require_pin_after": "Wymagaj kodu PIN po", + "required_passphrase": "Fraza", "rescan": "Skanuj ponownie", "resend_code": "Wyślij go ponownie", "reset": "Wyczyść", "reset_password": "Zresetuj hasło", + "restore": "Przywrócić", "restore_active_seed": "Aktywne seedy", "restore_address": "Adres", "restore_bitcoin_description_from_keys": "Przywróć swój portfel z klucza prywatnego", @@ -999,6 +1003,7 @@ "wallet_group_description_view_seed": "Zawsze możesz ponownie zobaczyć to ziarno pod", "wallet_group_empty_state_text_one": "Wygląda na to, że nie masz żadnych kompatybilnych grup portfeli !\n\n Tap", "wallet_group_empty_state_text_two": "poniżej, aby zrobić nowy.", + "wallet_has_passphrase": "Ten portfel ma panie", "wallet_keys": "Klucze portfela", "wallet_list_create_new_wallet": "Utwórz nowy portfel", "wallet_list_edit_group_name": "Edytuj nazwę grupy", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index c91bb04a0..f20a39e2d 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "Adicionar fundos pré-pagos aos cartões (até ${value})", "add_new_node": "Adicionar novo nó", "add_new_word": "Adicionar nova palavra", + "add_passphrase": "Adicione a senha", + "add_passphrase_warning_text": "Digite apenas uma senha se você usou uma para esta carteira no passado. Se você inserir a senha errada ou não já usou uma senha antes nesta carteira, não verá nenhum dos fundos ou histórico existentes.", "add_receiver": "Adicione outro receptor (opcional)", "add_secret_code": "Ou adicione este código secreto a um aplicativo autenticador", "add_tip": "Adicionar Dica", @@ -623,10 +625,12 @@ "require_for_sends_to_internal_wallets": "Exigir envios para carteiras internas", "require_for_sends_to_non_contacts": "Exigir para envios para não-contatos", "require_pin_after": "Exigir PIN após", + "required_passphrase": "Senha", "rescan": "Reescanear", "resend_code": "Por favor, reenvie", "reset": "Limpar", "reset_password": "Redefinir senha", + "restore": "Restaurar", "restore_active_seed": "Semente ativa", "restore_address": "Endereço", "restore_bitcoin_description_from_keys": "Restaure sua carteira a partir da string WIF gerada de suas chaves privadas", @@ -1002,6 +1006,7 @@ "wallet_group_description_view_seed": "Você sempre pode ver esta semente novamente em", "wallet_group_empty_state_text_one": "Parece que você não tem nenhum grupo de carteira compatível !\n\n Toque", "wallet_group_empty_state_text_two": "abaixo para fazer um novo.", + "wallet_has_passphrase": "Esta carteira tem uma senha", "wallet_keys": "Semente/chaves da carteira", "wallet_list_create_new_wallet": "Criar nova carteira", "wallet_list_edit_group_name": "Editar o nome do grupo", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 37750c3ce..6559c8d1d 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "Добавить предоплаченные средства на карты (до ${value})", "add_new_node": "Добавить новую ноду", "add_new_word": "Добавить новое слово", + "add_passphrase": "Добавить пасфраз", + "add_passphrase_warning_text": "Введите фразу только в том случае, если вы использовали один для этого кошелька в прошлом. Если вы введете неверную пассисную фразу или не использовали пасфразу до этого кошелька, вы не увидите ни одного существующего фонда или истории.", "add_receiver": "Добавить получателя (необязательно)", "add_secret_code": "Или добавьте этот секретный код в приложение для аутентификации.", "add_tip": "Добавить подсказку", @@ -622,10 +624,12 @@ "require_for_sends_to_internal_wallets": "Требовать отправки на внутренние кошельки", "require_for_sends_to_non_contacts": "Требовать для отправки не контактам", "require_pin_after": "Требовать ПИН после", + "required_passphrase": "Пасфраза", "rescan": "Пересканировать", "resend_code": "Пожалуйста, отправьте еще раз", "reset": "Сброс", "reset_password": "Сбросить пароль", + "restore": "Восстановить", "restore_active_seed": "Активная мнемоническая фраза", "restore_address": "Адрес", "restore_bitcoin_description_from_keys": "Вы можете восстановить кошелёк с помощью WIF", @@ -1000,6 +1004,7 @@ "wallet_group_description_view_seed": "Вы всегда можете просматривать это семя снова под", "wallet_group_empty_state_text_one": "Похоже, у вас нет никаких совместимых групп кошелька !\n\n tap", "wallet_group_empty_state_text_two": "ниже, чтобы сделать новый.", + "wallet_has_passphrase": "Этот кошелек имеет фразу", "wallet_keys": "Мнемоническая фраза/ключи кошелька", "wallet_list_create_new_wallet": "Создать новый кошелёк", "wallet_list_edit_group_name": "Редактировать название группы", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index cbc007035..b42fc600b 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "เพิ่มเงินสำรองไว้บนบัตร (ถึง ${value})", "add_new_node": "เพิ่มโหนดใหม่", "add_new_word": "เพิ่มคำใหม่", + "add_passphrase": "เพิ่มวลีรหัสผ่าน", + "add_passphrase_warning_text": "ป้อนวลีรหัสผ่านเท่านั้นหากคุณเคยใช้สำหรับกระเป๋าเงินนี้ในอดีต หากคุณป้อนวลีรหัสผ่านที่ไม่ถูกต้องหรือไม่เคยใช้ข้อความรหัสผ่านมาก่อนในกระเป๋าเงินนี้คุณจะไม่เห็นเงินทุนหรือประวัติใด ๆ ที่มีอยู่", "add_receiver": "เพิ่มผู้รับอื่น ๆ (ตัวเลือก)", "add_secret_code": "หรือเพิ่มรหัสลับนี้ลงในแอปตรวจสอบความถูกต้อง", "add_tip": "เพิ่มคำแนะนำ", @@ -621,10 +623,12 @@ "require_for_sends_to_internal_wallets": "จำเป็นต้องส่งไปยังกระเป๋าเงินภายใน", "require_for_sends_to_non_contacts": "จำเป็นต้องส่งไปยังผู้ที่ไม่ได้ติดต่อ", "require_pin_after": "ต้องการ PIN หลังจาก", + "required_passphrase": "วรรณะ", "rescan": "สแกนใหม่", "resend_code": "โปรดส่งอีกครั้ง", "reset": "รีเซ็ต", "reset_password": "รีเซ็ตรหัสผ่าน", + "restore": "คืนค่า", "restore_active_seed": "ซีดที่ใช้งานอยู่", "restore_address": "ที่อยู่", "restore_bitcoin_description_from_keys": "กู้กระเป๋าของคุณจากสตริง WIF ที่สร้างขึ้นจากคีย์ส่วนตัวของคุณ", @@ -999,6 +1003,7 @@ "wallet_group_description_view_seed": "คุณสามารถดูเมล็ดพันธุ์นี้ได้อีกครั้งภายใต้", "wallet_group_empty_state_text_one": "ดูเหมือนว่าคุณจะไม่มีกลุ่มกระเป๋าเงินที่เข้ากันได้ !\n\n แตะ", "wallet_group_empty_state_text_two": "ด้านล่างเพื่อสร้างใหม่", + "wallet_has_passphrase": "กระเป๋าเงินนี้มีข้อความรหัสผ่าน", "wallet_keys": "ซีดของกระเป๋า/คีย์", "wallet_list_create_new_wallet": "สร้างกระเป๋าใหม่", "wallet_list_edit_group_name": "แก้ไขชื่อกลุ่ม", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 356eae8c5..1a881cbba 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "Magdagdag ng mga prepaid na pondo sa card (hanggang sa ${value})", "add_new_node": "Magdagdag ng bagong node", "add_new_word": "Magdagdag ng bagong salita", + "add_passphrase": "Magdagdag ng passphrase", + "add_passphrase_warning_text": "Magpasok lamang ng isang passphrase kung ginamit mo ang isa para sa pitaka na ito sa nakaraan. Kung nagpasok ka ng maling passphrase o hindi pa gumamit ng isang passphrase bago sa pitaka na ito, hindi mo makikita ang alinman sa umiiral na pondo o kasaysayan.", "add_receiver": "Magdagdag ng isa pang tatanggap (opsyonal)", "add_secret_code": "O, idagdag ang sikretong code na ito sa isang authenticator app", "add_tip": "Magdagdag ng Tip", @@ -621,10 +623,12 @@ "require_for_sends_to_internal_wallets": "Nangangailangan para sa pagpapadala sa mga panloob na wallet", "require_for_sends_to_non_contacts": "Nangangailangan para sa pagpapadala sa mga hindi contact", "require_pin_after": "Nangangailangan ng PIN pagkatapos", + "required_passphrase": "Passphrase", "rescan": "Muling i-scan", "resend_code": "Mangyaring ipadala ito muli", "reset": "I-reset", "reset_password": "I-reset ang password", + "restore": "Ibalik", "restore_active_seed": "Aktibong seed", "restore_address": "Address", "restore_bitcoin_description_from_keys": "Ibalik ang iyong wallet mula sa nabuong WIF string mula sa iyong mga private key", @@ -999,6 +1003,7 @@ "wallet_group_description_view_seed": "Maaari mong palaging tingnan ang binhi na ito sa ilalim", "wallet_group_empty_state_text_one": "Mukhang wala kang anumang mga katugmang pangkat ng pitaka!\n\ntap", "wallet_group_empty_state_text_two": "sa ibaba upang gumawa ng bago.", + "wallet_has_passphrase": "Ang pitaka na ito ay may isang passphrase", "wallet_keys": "Wallet seed/keys", "wallet_list_create_new_wallet": "Lumikha ng bagong wallet", "wallet_list_edit_group_name": "I -edit ang Pangalan ng Grupo", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 5265e5f4e..53ef3574b 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "Ön ödemeli kartlara para ekle (En fazla yüklenebilir tutar: ${value})", "add_new_node": "Yeni düğüm ekle", "add_new_word": "Yeni kelime ekle", + "add_passphrase": "Parola ekle", + "add_passphrase_warning_text": "Sadece geçmişte bu cüzdan için bir tane kullandıysanız bir parola girin. Yanlış parola girerseniz veya daha önce bu cüzdanda bir parola kullanmadıysanız, mevcut fonlardan veya geçmişi görmezsiniz.", "add_receiver": "Başka bir alıcı ekle (isteğe bağlı)", "add_secret_code": "Veya bu gizli kodu bir kimlik doğrulama uygulamasına ekleyin", "add_tip": "Bahşiş Ekle", @@ -621,10 +623,12 @@ "require_for_sends_to_internal_wallets": "Dahili cüzdanlara yapılan gönderimler için gereklilik", "require_for_sends_to_non_contacts": "Kişi olmayan kişilere göndermeler için gerekli kıl", "require_pin_after": "Şu kadar süre sonra PIN iste", + "required_passphrase": "Parola", "rescan": "Yeniden Tara", "resend_code": "Lütfen tekrar gönder", "reset": "Sıfırla", "reset_password": "Parolamı sıfırla", + "restore": "Eski haline getirmek", "restore_active_seed": "Tohumu aktifleştir", "restore_address": "Adres", "restore_bitcoin_description_from_keys": "Cüzdanını, oluşturulan WIF dizesinden veya özel anahtarlarından geri yükle", @@ -999,6 +1003,7 @@ "wallet_group_description_view_seed": "Bu tohumu her zaman tekrar görebilirsiniz", "wallet_group_empty_state_text_one": "Herhangi bir uyumlu cüzdan grubunuz yok gibi görünüyor !\n\n TAP", "wallet_group_empty_state_text_two": "Yeni bir tane yapmak için aşağıda.", + "wallet_has_passphrase": "Bu cüzdanın bir parola var", "wallet_keys": "Cüzdan tohumu/anahtarları", "wallet_list_create_new_wallet": "Yeni Cüzdan Oluştur", "wallet_list_edit_group_name": "Grup Adını Düzenle", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 99c94854b..1123bf1b8 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "Додайте передплачені кошти на картки (до ${value})", "add_new_node": "Додати новий вузол", "add_new_word": "Добавити нове слово", + "add_passphrase": "Додати фразу", + "add_passphrase_warning_text": "Введіть парольну фразу, якщо ви використовували її для цього гаманця в минулому. Якщо ви вводите неправильну пасфразу або раніше не використовували парольну фразу на цьому гаманці, ви не побачите жодного з існуючих коштів чи історії.", "add_receiver": "Додати одержувача (необов'язково)", "add_secret_code": "Або додайте цей секретний код до програми автентифікації", "add_tip": "Додати підказку", @@ -622,10 +624,12 @@ "require_for_sends_to_internal_wallets": "Вимагати надсилання на внутрішні гаманці", "require_for_sends_to_non_contacts": "Вимагати для надсилання неконтактним особам", "require_pin_after": "Вимагати PIN після", + "required_passphrase": "Пропуск", "rescan": "Пересканувати", "resend_code": "Будь ласка, надішліть його повторно", "reset": "Скинути", "reset_password": "Скинути пароль", + "restore": "Відновити", "restore_active_seed": "Активна мнемонічна фраза", "restore_address": "Адреса", "restore_bitcoin_description_from_keys": "Ви можете відновити гаманець за допомогою WIF", @@ -1000,6 +1004,7 @@ "wallet_group_description_view_seed": "Ви завжди можете переглянути це насіння ще раз під", "wallet_group_empty_state_text_one": "Схоже, у вас немає сумісних груп гаманця !\n\n Торкніться", "wallet_group_empty_state_text_two": "нижче, щоб зробити новий.", + "wallet_has_passphrase": "Цей гаманець має фразу", "wallet_keys": "Мнемонічна фраза/ключі гаманця", "wallet_list_create_new_wallet": "Створити новий гаманець", "wallet_list_edit_group_name": "Назва групи редагування", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 152cd7d11..3c4dabda7 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "کارڈز میں پری پیڈ فنڈز شامل کریں (${value} تک)", "add_new_node": "نیا نوڈ شامل کریں۔", "add_new_word": "نیا لفظ شامل کریں۔", + "add_passphrase": "پاسفریز شامل کریں", + "add_passphrase_warning_text": "صرف ایک پاسفریز درج کریں اگر آپ نے ماضی میں اس پرس کے لئے ایک استعمال کیا ہو۔ اگر آپ غلط پاسفریس میں داخل ہوتے ہیں یا اس پرس سے پہلے پاس فیز کا استعمال نہیں کرتے ہیں تو ، آپ کو موجودہ فنڈز یا تاریخ میں سے کوئی بھی نظر نہیں آئے گا۔", "add_receiver": "دوسرا وصول کنندہ شامل کریں (اختیاری)", "add_secret_code": " ۔ﮟﯾﺮﮐ ﻞﻣﺎﺷ ﮟﯿﻣ ﭗﯾﺍ ﮦﺪﻨﻨﮐ ﻖﯾﺪﺼﺗ ﻮﮐ ﮈﻮﮐ ﮧﯿﻔﺧ ﺱﺍ ،ﺎﯾ", "add_tip": "ٹپ شامل کریں۔", @@ -623,10 +625,12 @@ "require_for_sends_to_internal_wallets": "اندرونی بٹوے پر بھیجنے کے لیے درکار ہے۔", "require_for_sends_to_non_contacts": "غیر رابطوں کو بھیجنے کی ضرورت ہے۔", "require_pin_after": "اس کے بعد PIN کی ضرورت ہے۔", + "required_passphrase": "پاسفریز", "rescan": "دوبارہ اسکین کریں۔", "resend_code": "براہ کرم اسے دوبارہ بھیجیں۔", "reset": "دوبارہ ترتیب دیں۔", "reset_password": "پاس ورڈ ری سیٹ", + "restore": "بحال کریں", "restore_active_seed": "فعال بیج", "restore_address": "پتہ", "restore_bitcoin_description_from_keys": "اپنی نجی کلیدوں سے تیار کردہ WIF سٹرنگ سے اپنے بٹوے کو بحال کریں۔", @@ -1001,6 +1005,7 @@ "wallet_group_description_view_seed": "آپ ہمیشہ اس بیج کو دوبارہ دیکھ سکتے ہیں", "wallet_group_empty_state_text_one": "ایسا لگتا ہے کہ آپ کے پاس کوئی مطابقت پذیر والیٹ گروپس نہیں ہیں !\n\n نل", "wallet_group_empty_state_text_two": "ایک نیا بنانے کے لئے ذیل میں.", + "wallet_has_passphrase": "اس پرس میں پاسفریس ہے", "wallet_keys": "بٹوے کے بیج / چابیاں", "wallet_list_create_new_wallet": "نیا والیٹ بنائیں", "wallet_list_edit_group_name": "گروپ کے نام میں ترمیم کریں", diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index 56a9305bf..61bb0e5d9 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "Thêm tiền trả trước vào thẻ (tối đa ${value})", "add_new_node": "Thêm nút mới", "add_new_word": "Thêm từ mới", + "add_passphrase": "Thêm cụm mật khẩu", + "add_passphrase_warning_text": "Chỉ nhập một cụm mật khẩu nếu bạn đã sử dụng một cái cho ví này trong quá khứ. Nếu bạn nhập sai cụm từ hoặc chưa sử dụng cụm mật khẩu trước đây trên ví này, bạn sẽ không thấy bất kỳ quỹ hoặc lịch sử hiện có nào.", "add_receiver": "Thêm người nhận khác (tùy chọn)", "add_secret_code": "Hoặc, thêm mã bí mật này vào ứng dụng xác thực", "add_tip": "Thêm tiền boa", @@ -619,10 +621,12 @@ "require_for_sends_to_internal_wallets": "Yêu cầu khi gửi đến ví nội bộ", "require_for_sends_to_non_contacts": "Yêu cầu khi gửi đến người không phải danh bạ", "require_pin_after": "Yêu cầu PIN sau", + "required_passphrase": "Cụm cụm", "rescan": "Quét lại", "resend_code": "Vui lòng gửi lại", "reset": "Đặt lại", "reset_password": "Đặt lại mật khẩu", + "restore": "Khôi phục", "restore_active_seed": "Hạt giống hoạt động", "restore_address": "Địa chỉ", "restore_bitcoin_description_from_keys": "Khôi phục ví của bạn từ chuỗi WIF được tạo từ khóa riêng của bạn", @@ -996,6 +1000,7 @@ "wallet_group_description_view_seed": "Bạn luôn có thể xem lại hạt giống này dưới", "wallet_group_empty_state_text_one": "Có vẻ như bạn không có bất kỳ nhóm ví tương thích nào !\n\n Tap", "wallet_group_empty_state_text_two": "Dưới đây để làm một cái mới.", + "wallet_has_passphrase": "Ví này có một cụm từ", "wallet_keys": "Hạt giống/khóa ví", "wallet_list_create_new_wallet": "Tạo ví mới", "wallet_list_edit_group_name": "Chỉnh sửa tên nhóm", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index c17f07eb6..6e9ebd16e 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "Ẹ fikún owó sí àwọn káàdì (kò tóbi ju ${value})", "add_new_node": "Fi apẹka kún", "add_new_word": "Fikún ọ̀rọ̀ títun", + "add_passphrase": "Ṣafikun Kọwe", + "add_passphrase_warning_text": "Tẹ ni iwe ikawe nikan ti o ba ti lo ọkan fun apamọwọ yii ni igba atijọ. Ti o ba tẹ iwe ọrọ kukuru ti ko tọ tabi ko lo iwe kukuru ṣaaju lori apamọwọ yii, iwọ kii yoo wo eyikeyi awọn owo tabi itan-akọọlẹ.", "add_receiver": "Fikún àdírẹ́sì mìíràn (ìyàn nìyí)", "add_secret_code": "Tabi, ṣafikun koodu aṣiri yii si ohun elo onijeri kan", "add_tip": "Fún owó àfikún", @@ -622,10 +624,12 @@ "require_for_sends_to_internal_wallets": "Beere fun fifiranṣẹ si awọn apamọwọ inu", "require_for_sends_to_non_contacts": "Beere fun fifiranṣẹ si awọn ti kii ṣe awọn olubasọrọ", "require_pin_after": "Ẹ nílò òǹkà ìdánimọ̀ àdáni láàárín", + "required_passphrase": "Kukurukọni", "rescan": "Tún Wá", "resend_code": "Ẹ jọ̀wọ́ tún un ránṣé", "reset": "Tún ṣe", "reset_password": "Tún ọ̀rọ̀ aṣínà ṣe", + "restore": "Mu pada", "restore_active_seed": "Hóró lọ́wọ́", "restore_address": "Àdírẹ́sì", "restore_bitcoin_description_from_keys": "Mú àpamọ́wọ́ yín padà láti ọ̀rọ̀ WIF t'á ti dá láti kọ́kọ́rọ́ àdáni yín", @@ -1000,6 +1004,7 @@ "wallet_group_description_view_seed": "O le nigbagbogbo wo irugbin yii lẹẹkansi labẹ", "wallet_group_empty_state_text_one": "O dabi pe o ko ni eyikeyi awọn ẹgbẹ ti o ni ibamu!\n\ntẹ ni kia kia", "wallet_group_empty_state_text_two": "ni isalẹ lati ṣe ọkan titun.", + "wallet_has_passphrase": "Apamọwọ yii ni iwe kukuru kan", "wallet_keys": "Hóró/kọ́kọ́rọ́ àpamọ́wọ́", "wallet_list_create_new_wallet": "Ṣe àpamọ́wọ́ títun", "wallet_list_edit_group_name": "Ṣatunṣe Orukọ Ẹgbẹ", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index d0bbe9c08..a9b3dac58 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -15,6 +15,8 @@ "add_fund_to_card": "向卡中添加预付资金(最多 ${value})", "add_new_node": "添加新节点", "add_new_word": "添加新词", + "add_passphrase": "添加密码", + "add_passphrase_warning_text": "仅当您过去曾经为此钱包使用一个时,才输入密码。如果您在此钱包上输入错误的密码短语或在此钱包上之前没有使用过密码,则不会看到任何现有的资金或历史记录。", "add_receiver": "添加另一個接收器(可選)", "add_secret_code": "或者,将此密码添加到身份验证器应用程序中", "add_tip": "添加提示", @@ -621,10 +623,12 @@ "require_for_sends_to_internal_wallets": "需要发送到内部钱包", "require_for_sends_to_non_contacts": "需要发送给非联系人", "require_pin_after": "之后需要 PIN", + "required_passphrase": "密码", "rescan": "重新扫描", "resend_code": "请重新发送", "reset": "重置", "reset_password": "重置密码", + "restore": "恢复", "restore_active_seed": "活动种子", "restore_address": "地址", "restore_bitcoin_description_from_keys": "从私钥中生成的WIF字符串恢复您钱包", @@ -999,6 +1003,7 @@ "wallet_group_description_view_seed": "您可以随时再次在下面查看此种子", "wallet_group_empty_state_text_one": "看起来您没有任何兼容的钱包组!\n\n tap", "wallet_group_empty_state_text_two": "下面是一个新的。", + "wallet_has_passphrase": "这个钱包有一个密码", "wallet_keys": "钱包种子/密钥", "wallet_list_create_new_wallet": "创建新钱包", "wallet_list_edit_group_name": "编辑组名称", From a2294c4a061c40194223600a12aabacca3b15bc4 Mon Sep 17 00:00:00 2001 From: cyan Date: Fri, 2 May 2025 14:30:39 +0200 Subject: [PATCH 065/142] fix(cw_monero): prevent monero wallet from breaking during rename (#2214) * fix(cw_monero): prevent monero wallet from breaking during rename * update to cleaned up monero.dart * fix: transaction screen not refreshing in monero * fix: wallets not opening until app restart after rename. * fix(cw_decred): wallet renaming throwing * fix: transaction not being shown after sending until 1st confirmation * fix(cw_monero): loop safeguard * fix: don't await wallet.fetchTransactions --- cw_decred/lib/wallet.dart | 21 +- cw_decred/lib/wallet_service.dart | 4 + cw_monero/lib/api/account_list.dart | 67 ++-- cw_monero/lib/api/coins_info.dart | 19 +- cw_monero/lib/api/subaddress_list.dart | 68 ++-- cw_monero/lib/api/transaction_history.dart | 193 +++++----- cw_monero/lib/api/wallet.dart | 157 ++++---- cw_monero/lib/api/wallet_manager.dart | 339 ++++++------------ cw_monero/lib/ledger.dart | 16 +- cw_monero/lib/monero_account_list.dart | 28 +- cw_monero/lib/monero_unspent.dart | 2 +- cw_monero/lib/monero_wallet.dart | 171 +++++---- cw_monero/lib/monero_wallet_service.dart | 57 +-- cw_monero/pubspec.lock | 4 +- cw_monero/pubspec.yaml | 2 +- cw_wownero/pubspec.lock | 4 +- cw_wownero/pubspec.yaml | 2 +- cw_zano/pubspec.lock | 4 +- cw_zano/pubspec.yaml | 2 +- ios/Podfile.lock | 56 ++- lib/core/wallet_loading_service.dart | 33 +- lib/monero/cw_monero.dart | 4 +- .../dashboard/dashboard_view_model.dart | 32 +- lib/view_model/send/send_view_model.dart | 4 +- scripts/prepare_moneroc.sh | 2 +- 25 files changed, 577 insertions(+), 714 deletions(-) diff --git a/cw_decred/lib/wallet.dart b/cw_decred/lib/wallet.dart index a63a5e2e5..ac70a4aaa 100644 --- a/cw_decred/lib/wallet.dart +++ b/cw_decred/lib/wallet.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:path/path.dart' as p; import 'package:cw_core/exceptions.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/utils/print_verbose.dart'; @@ -602,7 +603,25 @@ abstract class DecredWalletBase throw "wallet already exists at $newDirPath"; } - await Directory(currentDirPath).rename(newDirPath); + final sourceDir = Directory(currentDirPath); + final targetDir = Directory(newDirPath); + + if (!targetDir.existsSync()) { + await targetDir.create(recursive: true); + } + + await for (final entity in sourceDir.list(recursive: true)) { + final relativePath = entity.path.substring(sourceDir.path.length+1); + final targetPath = p.join(targetDir.path, relativePath); + + if (entity is File) { + await entity.rename(targetPath); + } else if (entity is Directory) { + await Directory(targetPath).create(recursive: true); + } + } + + await sourceDir.delete(recursive: true); } @override diff --git a/cw_decred/lib/wallet_service.dart b/cw_decred/lib/wallet_service.dart index 161184b0a..e2313904e 100644 --- a/cw_decred/lib/wallet_service.dart +++ b/cw_decred/lib/wallet_service.dart @@ -118,6 +118,10 @@ class DecredWalletService extends WalletService< currentWalletInfo.derivationInfo?.derivationPath == pubkeyRestorePathTestnet ? testnet : mainnet; + if (libwallet == null) { + libwallet = await Libwallet.spawn(); + libwallet!.initLibdcrwallet("", "err"); + } final currentWallet = DecredWallet( currentWalletInfo, password, this.unspentCoinsInfoSource, libwallet!, closeLibwallet); diff --git a/cw_monero/lib/api/account_list.dart b/cw_monero/lib/api/account_list.dart index 3ceef5815..7f6a3f1aa 100644 --- a/cw_monero/lib/api/account_list.dart +++ b/cw_monero/lib/api/account_list.dart @@ -2,31 +2,31 @@ import 'dart:async'; import 'package:cw_monero/api/wallet.dart'; import 'package:cw_monero/monero_account_list.dart'; -import 'package:monero/monero.dart' as monero; +import 'package:monero/src/wallet2.dart'; +import 'package:monero/src/monero.dart'; -monero.wallet? wptr = null; -bool get isViewOnly => int.tryParse(monero.Wallet_secretSpendKey(wptr!)) == 0; +Wallet2Wallet? currentWallet = null; +bool get isViewOnly => int.tryParse(currentWallet!.secretSpendKey()) == 0; int _wlptrForW = 0; -monero.WalletListener? _wlptr = null; +Wallet2WalletListener? _wlptr = null; -monero.WalletListener? getWlptr() { - if (wptr == null) return null; - if (wptr!.address == _wlptrForW) return _wlptr!; - _wlptrForW = wptr!.address; - _wlptr = monero.MONERO_cw_getWalletListener(wptr!); +Wallet2WalletListener? getWlptr() { + if (currentWallet == null) return null; + _wlptrForW = currentWallet!.ffiAddress(); + _wlptr = currentWallet!.getWalletListener(); return _wlptr!; } -monero.SubaddressAccount? subaddressAccount; +Wallet2SubaddressAccount? subaddressAccount; bool isUpdating = false; void refreshAccounts() { try { isUpdating = true; - subaddressAccount = monero.Wallet_subaddressAccount(wptr!); - monero.SubaddressAccount_refresh(subaddressAccount!); + subaddressAccount = currentWallet!.subaddressAccount(); + subaddressAccount!.refresh(); isUpdating = false; } catch (e) { isUpdating = false; @@ -34,45 +34,28 @@ void refreshAccounts() { } } -List getAllAccount() { + List getAllAccount() { // final size = monero.Wallet_numSubaddressAccounts(wptr!); refreshAccounts(); - int size = monero.SubaddressAccount_getAll_size(subaddressAccount!); + int size = subaddressAccount!.getAll_size(); if (size == 0) { - monero.Wallet_addSubaddressAccount(wptr!); - monero.Wallet_status(wptr!); + currentWallet!.addSubaddressAccount(); + currentWallet!.status(); return []; } return List.generate(size, (index) { - return monero.SubaddressAccount_getAll_byIndex(subaddressAccount!, index: index); + return subaddressAccount!.getAll_byIndex(index); }); } -void addAccountSync({required String label}) { - monero.Wallet_addSubaddressAccount(wptr!, label: label); -} - -void setLabelForAccountSync({required int accountIndex, required String label}) { - monero.SubaddressAccount_setLabel(subaddressAccount!, accountIndex: accountIndex, label: label); - MoneroAccountListBase.cachedAccounts[wptr!.address] = []; - refreshAccounts(); -} - -void _addAccount(String label) => addAccountSync(label: label); - -void _setLabelForAccount(Map args) { - final label = args['label'] as String; - final accountIndex = args['accountIndex'] as int; - - setLabelForAccountSync(label: label, accountIndex: accountIndex); -} - -Future addAccount({required String label}) async { - _addAccount(label); +void addAccount({required String label}) { + currentWallet!.addSubaddressAccount(label: label); unawaited(store()); } -Future setLabelForAccount({required int accountIndex, required String label}) async { - _setLabelForAccount({'accountIndex': accountIndex, 'label': label}); - unawaited(store()); -} \ No newline at end of file +void setLabelForAccount({required int accountIndex, required String label}) { + subaddressAccount!.setLabel(accountIndex: accountIndex, label: label); + MoneroAccountListBase.cachedAccounts[currentWallet!.ffiAddress()] = []; + refreshAccounts(); + unawaited(store()); +} diff --git a/cw_monero/lib/api/coins_info.dart b/cw_monero/lib/api/coins_info.dart index 83382f001..b08dbb1f7 100644 --- a/cw_monero/lib/api/coins_info.dart +++ b/cw_monero/lib/api/coins_info.dart @@ -3,17 +3,18 @@ import 'dart:isolate'; import 'package:cw_monero/api/account_list.dart'; import 'package:monero/monero.dart' as monero; +import 'package:monero/src/wallet2.dart'; import 'package:mutex/mutex.dart'; -monero.Coins? coins = null; +Wallet2Coins? coins = null; final coinsMutex = Mutex(); Future refreshCoins(int accountIndex) async { if (coinsMutex.isLocked) { return; } - coins = monero.Wallet_coins(wptr!); - final coinsPtr = coins!.address; + coins = currentWallet!.coins(); + final coinsPtr = coins!.ffiAddress(); await coinsMutex.acquire(); await Isolate.run(() => monero.Coins_refresh(Pointer.fromAddress(coinsPtr))); coinsMutex.release(); @@ -21,14 +22,14 @@ Future refreshCoins(int accountIndex) async { Future countOfCoins() async { await coinsMutex.acquire(); - final count = monero.Coins_count(coins!); + final count = coins!.count(); coinsMutex.release(); return count; } -Future getCoin(int index) async { +Future getCoin(int index) async { await coinsMutex.acquire(); - final coin = monero.Coins_coin(coins!, index); + final coin = coins!.coin(index); coinsMutex.release(); return coin; } @@ -37,7 +38,7 @@ Future getCoinByKeyImage(String keyImage) async { final count = await countOfCoins(); for (int i = 0; i < count; i++) { final coin = await getCoin(i); - final coinAddress = monero.CoinsInfo_keyImage(coin); + final coinAddress = coin.keyImage; if (keyImage == coinAddress) { return i; } @@ -47,14 +48,14 @@ Future getCoinByKeyImage(String keyImage) async { Future freezeCoin(int index) async { await coinsMutex.acquire(); - final coinsPtr = coins!.address; + final coinsPtr = coins!.ffiAddress(); await Isolate.run(() => monero.Coins_setFrozen(Pointer.fromAddress(coinsPtr), index: index)); coinsMutex.release(); } Future thawCoin(int index) async { await coinsMutex.acquire(); - final coinsPtr = coins!.address; + final coinsPtr = coins!.ffiAddress(); await Isolate.run(() => monero.Coins_thaw(Pointer.fromAddress(coinsPtr), index: index)); coinsMutex.release(); } diff --git a/cw_monero/lib/api/subaddress_list.dart b/cw_monero/lib/api/subaddress_list.dart index 5db8a5d5f..46bdc10bc 100644 --- a/cw_monero/lib/api/subaddress_list.dart +++ b/cw_monero/lib/api/subaddress_list.dart @@ -2,7 +2,8 @@ import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/transaction_history.dart'; import 'package:cw_monero/api/wallet.dart'; -import 'package:monero/monero.dart' as monero; +import 'package:monero/monero.dart'; +import 'package:monero/src/monero.dart'; bool isUpdating = false; @@ -16,7 +17,7 @@ class SubaddressInfoMetadata { SubaddressInfoMetadata? subaddress = null; String getRawLabel({required int accountIndex, required int addressIndex}) { - return monero.Wallet_getSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: addressIndex); + return currentWallet!.getSubaddressLabel(accountIndex: accountIndex, addressIndex: addressIndex); } void refreshSubaddresses({required int accountIndex}) { @@ -46,7 +47,7 @@ class Subaddress { final int received; final int txCount; String get label { - final localLabel = monero.Wallet_getSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: addressIndex); + final localLabel = currentWallet!.getSubaddressLabel(accountIndex: accountIndex, addressIndex: addressIndex); if (localLabel.startsWith("#$addressIndex")) return localLabel; // don't duplicate the ID if it was user-providen return "#$addressIndex ${localLabel}".trim(); } @@ -66,26 +67,26 @@ int lastTxCount = 0; List ttDetails = []; List getAllSubaddresses() { - txhistory = monero.Wallet_history(wptr!); - final txCount = monero.TransactionHistory_count(txhistory!); - if (lastTxCount != txCount && lastWptr != wptr!.address) { + txhistory = currentWallet!.history(); + final txCount = txhistory!.count(); + if (lastTxCount != txCount && lastWptr != currentWallet!.ffiAddress()) { final List newttDetails = []; lastTxCount = txCount; - lastWptr = wptr!.address; + lastWptr = currentWallet!.ffiAddress(); for (var i = 0; i < txCount; i++) { - final tx = monero.TransactionHistory_transaction(txhistory!, index: i); - if (monero.TransactionInfo_direction(tx) == monero.TransactionInfo_Direction.Out) continue; - final subaddrs = monero.TransactionInfo_subaddrIndex(tx).split(","); - final account = monero.TransactionInfo_subaddrAccount(tx); + final tx = txhistory!.transaction(i); + if (tx.direction() == TransactionInfo_Direction.Out.index) continue; + final subaddrs = tx.subaddrIndex().split(","); + final account = tx.subaddrAccount(); newttDetails.add(TinyTransactionDetails( address: List.generate(subaddrs.length, (index) => getAddress(accountIndex: account, addressIndex: int.tryParse(subaddrs[index])??0)), - amount: monero.TransactionInfo_amount(tx), + amount: tx.amount(), )); } ttDetails.clear(); ttDetails.addAll(newttDetails); } - final size = monero.Wallet_numSubaddresses(wptr!, accountIndex: subaddress!.accountIndex); + final size = currentWallet!.numSubaddresses(accountIndex: subaddress!.accountIndex); final list = List.generate(size, (index) { final ttDetailsLocal = ttDetails.where((element) { final address = getAddress( @@ -119,46 +120,17 @@ List getAllSubaddresses() { } int numSubaddresses(int subaccountIndex) { - return monero.Wallet_numSubaddresses(wptr!, accountIndex: subaccountIndex); -} - -void addSubaddressSync({required int accountIndex, required String label}) { - monero.Wallet_addSubaddress(wptr!, accountIndex: accountIndex, label: label); - refreshSubaddresses(accountIndex: accountIndex); -} - -void setLabelForSubaddressSync( - {required int accountIndex, required int addressIndex, required String label}) { - monero.Wallet_setSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: addressIndex, label: label); -} - -void _addSubaddress(Map args) { - final label = args['label'] as String; - final accountIndex = args['accountIndex'] as int; - - addSubaddressSync(accountIndex: accountIndex, label: label); -} - -void _setLabelForSubaddress(Map args) { - final label = args['label'] as String; - final accountIndex = args['accountIndex'] as int; - final addressIndex = args['addressIndex'] as int; - - setLabelForSubaddressSync( - accountIndex: accountIndex, addressIndex: addressIndex, label: label); + return currentWallet!.numSubaddresses(accountIndex: subaccountIndex); } Future addSubaddress({required int accountIndex, required String label}) async { - _addSubaddress({'accountIndex': accountIndex, 'label': label}); + currentWallet!.addSubaddress(accountIndex: accountIndex, label: label); + refreshSubaddresses(accountIndex: accountIndex); await store(); } Future setLabelForSubaddress( - {required int accountIndex, required int addressIndex, required String label}) async { - _setLabelForSubaddress({ - 'accountIndex': accountIndex, - 'addressIndex': addressIndex, - 'label': label - }); + {required int accountIndex, required int addressIndex, required String label}) async { + currentWallet!.setSubaddressLabel(accountIndex: accountIndex, addressIndex: addressIndex, label: label); await store(); -} +} \ No newline at end of file diff --git a/cw_monero/lib/api/transaction_history.dart b/cw_monero/lib/api/transaction_history.dart index 8c7b1e902..4360149d2 100644 --- a/cw_monero/lib/api/transaction_history.dart +++ b/cw_monero/lib/api/transaction_history.dart @@ -9,36 +9,38 @@ import 'package:cw_monero/api/structs/pending_transaction.dart'; import 'package:cw_monero/api/wallet.dart'; import 'package:cw_monero/exceptions/monero_transaction_creation_exception.dart'; import 'package:ffi/ffi.dart'; +import 'package:monero/src/monero.dart'; import 'package:monero/monero.dart' as monero; +import 'package:monero/src/wallet2.dart'; import 'package:monero/src/generated_bindings_monero.g.dart' as monero_gen; import 'package:mutex/mutex.dart'; Map> txKeys = {}; String getTxKey(String txId) { - txKeys[wptr!.address] ??= {}; - if (txKeys[wptr!.address]![txId] != null) { - return txKeys[wptr!.address]![txId]!; + txKeys[currentWallet!.ffiAddress()] ??= {}; + if (txKeys[currentWallet!.ffiAddress()]![txId] != null) { + return txKeys[currentWallet!.ffiAddress()]![txId]!; } - final txKey = monero.Wallet_getTxKey(wptr!, txid: txId); - final status = monero.Wallet_status(wptr!); + final txKey = currentWallet!.getTxKey(txid: txId); + final status = currentWallet!.status(); if (status != 0) { - monero.Wallet_errorString(wptr!); - txKeys[wptr!.address]![txId] = ""; + currentWallet!.errorString(); + txKeys[currentWallet!.ffiAddress()]![txId] = ""; return ""; } - txKeys[wptr!.address]![txId] = txKey; + txKeys[currentWallet!.ffiAddress()]![txId] = txKey; return txKey; } final txHistoryMutex = Mutex(); -monero.TransactionHistory? txhistory; +Wallet2TransactionHistory? txhistory; bool isRefreshingTx = false; Future refreshTransactions() async { if (isRefreshingTx == true) return; isRefreshingTx = true; - txhistory ??= monero.Wallet_history(wptr!); - final ptr = txhistory!.address; + txhistory ??= currentWallet!.history(); + final ptr = txhistory!.ffiAddress(); await txHistoryMutex.acquire(); await Isolate.run(() { monero.TransactionHistory_refresh(Pointer.fromAddress(ptr)); @@ -48,14 +50,14 @@ Future refreshTransactions() async { isRefreshingTx = false; } -int countOfTransactions() => monero.TransactionHistory_count(txhistory!); +int countOfTransactions() => txhistory!.count(); Future> getAllTransactions() async { List dummyTxs = []; await txHistoryMutex.acquire(); - txhistory ??= monero.Wallet_history(wptr!); - final startAddress = txhistory!.address * wptr!.address; + txhistory ??= currentWallet!.history(); + final startAddress = txhistory!.ffiAddress() * currentWallet!.ffiAddress(); int size = countOfTransactions(); final list = []; for (int index = 0; index < size; index++) { @@ -63,21 +65,21 @@ Future> getAllTransactions() async { // Give main thread a chance to do other things. await Future.delayed(Duration.zero); } - if (txhistory!.address * wptr!.address != startAddress) { + if (txhistory!.ffiAddress() * currentWallet!.ffiAddress() != startAddress) { printV("Loop broken because txhistory!.address * wptr!.address != startAddress"); break; } - final txInfo = monero.TransactionHistory_transaction(txhistory!, index: index); - final txHash = monero.TransactionInfo_hash(txInfo); - txCache[wptr!.address] ??= {}; - txCache[wptr!.address]![txHash] = Transaction(txInfo: txInfo); - list.add(txCache[wptr!.address]![txHash]!); + final txInfo = txhistory!.transaction(index); + final txHash = txInfo.hash(); + txCache[currentWallet!.ffiAddress()] ??= {}; + txCache[currentWallet!.ffiAddress()]![txHash] = Transaction(txInfo: txInfo); + list.add(txCache[currentWallet!.ffiAddress()]![txHash]!); } txHistoryMutex.release(); - final accts = monero.Wallet_numSubaddressAccounts(wptr!); + final accts = currentWallet!.numSubaddressAccounts(); for (var i = 0; i < accts; i++) { - final fullBalance = monero.Wallet_balance(wptr!, accountIndex: i); - final availBalance = monero.Wallet_unlockedBalance(wptr!, accountIndex: i); + final fullBalance = currentWallet!.balance(accountIndex: i); + final availBalance = currentWallet!.unlockedBalance(accountIndex: i); if (fullBalance > availBalance) { if (list.where((element) => element.accountIndex == i && element.isConfirmed == false).isEmpty) { dummyTxs.add( @@ -95,7 +97,7 @@ Future> getAllTransactions() async { isSpend: false, hash: "pending", key: "", - txInfo: Pointer.fromAddress(0), + txInfo: DummyTransaction(), )..timeStamp = DateTime.now() ); } @@ -105,16 +107,21 @@ Future> getAllTransactions() async { return list; } +class DummyTransaction implements Wallet2TransactionInfo { + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); +} + Map> txCache = {}; Future getTransaction(String txId) async { - if (txCache[wptr!.address] != null && txCache[wptr!.address]![txId] != null) { - return txCache[wptr!.address]![txId]!; + if (txCache[currentWallet!.ffiAddress()] != null && txCache[currentWallet!.ffiAddress()]![txId] != null) { + return txCache[currentWallet!.ffiAddress()]![txId]!; } await txHistoryMutex.acquire(); - final tx = monero.TransactionHistory_transactionById(txhistory!, txid: txId); + final tx = txhistory!.transactionById(txId); final txDart = Transaction(txInfo: tx); - txCache[wptr!.address] ??= {}; - txCache[wptr!.address]![txId] = txDart; + txCache[currentWallet!.ffiAddress()] ??= {}; + txCache[currentWallet!.ffiAddress()]![txId] = txDart; txHistoryMutex.release(); return txDart; } @@ -127,9 +134,9 @@ Future createTransactionSync( int accountIndex = 0, List preferredInputs = const []}) async { - final amt = amount == null ? 0 : monero.Wallet_amountFromString(amount); + final amt = amount == null ? 0 : currentWallet!.amountFromString(amount); - final waddr = wptr!.address; + final waddr = currentWallet!.ffiAddress(); // force reconnection in case the os killed the connection? // fixes failed to get block height error. @@ -149,7 +156,7 @@ Future createTransactionSync( final paymentIdAddr = paymentId_.address; final preferredInputsAddr = preferredInputs_.address; final spaddr = monero.defaultSeparator.address; - final pendingTx = Pointer.fromAddress(await Isolate.run(() { + final pendingTxPtr = Pointer.fromAddress(await Isolate.run(() { final tx = monero_gen.MoneroC(DynamicLibrary.open(monero.libPath)).MONERO_Wallet_createTransaction( Pointer.fromAddress(waddr), Pointer.fromAddress(addraddr).cast(), @@ -163,15 +170,16 @@ Future createTransactionSync( ); return tx.address; })); + final Wallet2PendingTransaction pendingTx = MoneroPendingTransaction(pendingTxPtr); calloc.free(address_); calloc.free(paymentId_); calloc.free(preferredInputs_); final String? error = (() { - final status = monero.PendingTransaction_status(pendingTx); + final status = pendingTx.status(); if (status == 0) { return null; } - return monero.PendingTransaction_errorString(pendingTx); + return pendingTx.errorString(); })(); if (error != null) { @@ -182,10 +190,10 @@ Future createTransactionSync( throw CreationTransactionException(message: message); } - final rAmt = monero.PendingTransaction_amount(pendingTx); - final rFee = monero.PendingTransaction_fee(pendingTx); - final rHash = monero.PendingTransaction_txid(pendingTx, ''); - final rHex = monero.PendingTransaction_hex(pendingTx, ''); + final rAmt = pendingTx.amount(); + final rFee = pendingTx.fee(); + final rHash = pendingTx.txid(''); + final rHex = pendingTx.hex(''); final rTxKey = rHash; return PendingTransactionDescription( @@ -194,7 +202,7 @@ Future createTransactionSync( hash: rHash, hex: rHex, txKey: rTxKey, - pointerAddress: pendingTx.address, + pointerAddress: pendingTx.ffiAddress(), ); } @@ -206,9 +214,9 @@ Future createTransactionMultDest( List preferredInputs = const []}) async { final dstAddrs = outputs.map((e) => e.address).toList(); - final amounts = outputs.map((e) => monero.Wallet_amountFromString(e.amount)).toList(); + final amounts = outputs.map((e) => currentWallet!.amountFromString(e.amount)).toList(); - final waddr = wptr!.address; + final waddr = currentWallet!.ffiAddress(); // force reconnection in case the os killed the connection Isolate.run(() async { @@ -227,49 +235,50 @@ Future createTransactionMultDest( ).address; })); - if (monero.PendingTransaction_status(txptr) != 0) { - throw CreationTransactionException(message: monero.PendingTransaction_errorString(txptr)); + final Wallet2PendingTransaction tx = MoneroPendingTransaction(txptr); + + if (tx.status() != 0) { + throw CreationTransactionException(message: tx.errorString()); } return PendingTransactionDescription( - amount: monero.PendingTransaction_amount(txptr), - fee: monero.PendingTransaction_fee(txptr), - hash: monero.PendingTransaction_txid(txptr, ''), - hex: monero.PendingTransaction_hex(txptr, ''), - txKey: monero.PendingTransaction_txid(txptr, ''), - pointerAddress: txptr.address, + amount: tx.amount(), + fee: tx.fee(), + hash: tx.txid(''), + hex: tx.hex(''), + txKey: tx.txid(''), + pointerAddress: tx.ffiAddress(), ); } String? commitTransactionFromPointerAddress({required int address, required bool useUR}) => - commitTransaction(transactionPointer: monero.PendingTransaction.fromAddress(address), useUR: useUR); + commitTransaction(tx: MoneroPendingTransaction(Pointer.fromAddress(address)), useUR: useUR); -String? commitTransaction({required monero.PendingTransaction transactionPointer, required bool useUR}) { - final transactionPointerAddress = transactionPointer.address; +String? commitTransaction({required Wallet2PendingTransaction tx, required bool useUR}) { final txCommit = useUR - ? monero.PendingTransaction_commitUR(transactionPointer, 120) + ? tx.commitUR(120) : Isolate.run(() { monero.PendingTransaction_commit( - Pointer.fromAddress(transactionPointerAddress), + Pointer.fromAddress(tx.ffiAddress()), filename: '', overwrite: false, ); }); String? error = (() { - final status = monero.PendingTransaction_status(transactionPointer.cast()); + final status = tx.status(); if (status == 0) { return null; } - return monero.PendingTransaction_errorString(transactionPointer.cast()); + return tx.errorString(); })(); if (error == null) { error = (() { - final status = monero.Wallet_status(wptr!); + final status = currentWallet!.status(); if (status == 0) { return null; } - return monero.Wallet_errorString(wptr!); + return currentWallet!.errorString(); })(); } @@ -283,43 +292,9 @@ String? commitTransaction({required monero.PendingTransaction transactionPointer } } -Future _createTransactionSync(Map args) async { - final address = args['address'] as String; - final paymentId = args['paymentId'] as String; - final amount = args['amount'] as String?; - final priorityRaw = args['priorityRaw'] as int; - final accountIndex = args['accountIndex'] as int; - final preferredInputs = args['preferredInputs'] as List; - - return createTransactionSync( - address: address, - paymentId: paymentId, - amount: amount, - priorityRaw: priorityRaw, - accountIndex: accountIndex, - preferredInputs: preferredInputs); -} - -Future createTransaction( - {required String address, - required int priorityRaw, - String? amount, - String paymentId = '', - int accountIndex = 0, - List preferredInputs = const []}) async => - _createTransactionSync({ - 'address': address, - 'paymentId': paymentId, - 'amount': amount, - 'priorityRaw': priorityRaw, - 'accountIndex': accountIndex, - 'preferredInputs': preferredInputs - }); - class Transaction { final String displayLabel; - late final String subaddressLabel = monero.Wallet_getSubaddressLabel( - wptr!, + late final String subaddressLabel = currentWallet!.getSubaddressLabel( accountIndex: accountIndex, addressIndex: addressIndex, ); @@ -372,26 +347,26 @@ class Transaction { // final SubAddress? subAddress; // List transfers = []; // final int txIndex; - final monero.TransactionInfo txInfo; + final Wallet2TransactionInfo txInfo; Transaction({ required this.txInfo, - }) : displayLabel = monero.TransactionInfo_label(txInfo), - hash = monero.TransactionInfo_hash(txInfo), + }) : displayLabel = txInfo.label(), + hash = txInfo.hash(), timeStamp = DateTime.fromMillisecondsSinceEpoch( - monero.TransactionInfo_timestamp(txInfo) * 1000, + txInfo.timestamp() * 1000, ), - isSpend = monero.TransactionInfo_direction(txInfo) == - monero.TransactionInfo_Direction.Out, - amount = monero.TransactionInfo_amount(txInfo), - paymentId = monero.TransactionInfo_paymentId(txInfo), - accountIndex = monero.TransactionInfo_subaddrAccount(txInfo), - addressIndex = int.tryParse(monero.TransactionInfo_subaddrIndex(txInfo).split(", ")[0]) ?? 0, - addressIndexList = monero.TransactionInfo_subaddrIndex(txInfo).split(", ").map((e) => int.tryParse(e) ?? 0).toList(), - blockheight = monero.TransactionInfo_blockHeight(txInfo), - confirmations = monero.TransactionInfo_confirmations(txInfo), - fee = monero.TransactionInfo_fee(txInfo), - description = monero.TransactionInfo_description(txInfo), - key = getTxKey(monero.TransactionInfo_hash(txInfo)); + isSpend = txInfo.direction() == + monero.TransactionInfo_Direction.Out.index, + amount = txInfo.amount(), + paymentId = txInfo.paymentId(), + accountIndex = txInfo.subaddrAccount(), + addressIndex = int.tryParse(txInfo.subaddrIndex().split(", ")[0]) ?? 0, + addressIndexList = txInfo.subaddrIndex().split(", ").map((e) => int.tryParse(e) ?? 0).toList(), + blockheight = txInfo.blockHeight(), + confirmations = txInfo.confirmations(), + fee = txInfo.fee(), + description = txInfo.description(), + key = getTxKey(txInfo.hash()); Transaction.dummy({ required this.displayLabel, diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index 7e64c7f08..87abdd95b 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -5,8 +5,6 @@ import 'dart:isolate'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart'; -import 'package:cw_monero/api/wallet_manager.dart'; -import 'package:flutter/foundation.dart'; import 'package:monero/monero.dart' as monero; import 'package:mutex/mutex.dart'; import 'package:polyseed/polyseed.dart'; @@ -15,36 +13,37 @@ bool debugMonero = false; int getSyncingHeight() { // final height = monero.MONERO_cw_WalletListener_height(getWlptr()); - final h2 = monero.Wallet_blockChainHeight(wptr!); + if (currentWallet == null) return 0; + final h2 = currentWallet!.blockChainHeight(); // printV("height: $height / $h2"); return h2; } bool isNeededToRefresh() { - final wlptr = getWlptr(); - if (wlptr == null) return false; - final ret = monero.MONERO_cw_WalletListener_isNeedToRefresh(wlptr); - monero.MONERO_cw_WalletListener_resetNeedToRefresh(wlptr); + final wl = getWlptr(); + if (wl == null) return false; + final ret = wl.isNeedToRefresh(); + wl.resetNeedToRefresh(); return ret; } bool isNewTransactionExist() { final wlptr = getWlptr(); if (wlptr == null) return false; - final ret = monero.MONERO_cw_WalletListener_isNewTransactionExist(wlptr); - monero.MONERO_cw_WalletListener_resetIsNewTransactionExist(wlptr); + final ret = wlptr.isNewTransactionExist(); + wlptr.resetIsNewTransactionExist(); return ret; } -String getFilename() => monero.Wallet_filename(wptr!); +String getFilename() => currentWallet!.filename(); String getSeed() { // monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed); final cakepolyseed = - monero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.seed"); + currentWallet!.getCacheAttribute(key: "cakewallet.seed"); final cakepassphrase = getPassphrase(); - final weirdPolyseed = monero.Wallet_getPolyseed(wptr!, passphrase: cakepassphrase); + final weirdPolyseed = currentWallet!.getPolyseed(passphrase: cakepassphrase); if (weirdPolyseed != "") return weirdPolyseed; if (cakepolyseed != "") { @@ -63,7 +62,7 @@ String getSeed() { return cakepolyseed; } - final bip39 = monero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.seed.bip39"); + final bip39 = currentWallet!.getCacheAttribute(key: "cakewallet.seed.bip39"); if(bip39.isNotEmpty) return bip39; @@ -85,29 +84,29 @@ String? getSeedLanguage(String? language) { String getSeedLegacy(String? language) { final cakepassphrase = getPassphrase(); language = getSeedLanguage(language); - var legacy = monero.Wallet_seed(wptr!, seedOffset: cakepassphrase); - if (monero.Wallet_status(wptr!) != 0) { - if (monero.Wallet_errorString(wptr!).contains("seed_language")) { - monero.Wallet_setSeedLanguage(wptr!, language: "English"); - legacy = monero.Wallet_seed(wptr!, seedOffset: cakepassphrase); + var legacy = currentWallet!.seed(seedOffset: cakepassphrase); + if (currentWallet!.status() != 0) { + if (currentWallet!.errorString().contains("seed_language")) { + currentWallet!.setSeedLanguage(language: "English"); + legacy = currentWallet!.seed(seedOffset: cakepassphrase); } } if (language != null) { - monero.Wallet_setSeedLanguage(wptr!, language: language); - final status = monero.Wallet_status(wptr!); + currentWallet!.setSeedLanguage(language: language); + final status = currentWallet!.status(); if (status != 0) { - final err = monero.Wallet_errorString(wptr!); + final err = currentWallet!.errorString(); if (legacy.isNotEmpty) { return "$err\n\n$legacy"; } return err; } - legacy = monero.Wallet_seed(wptr!, seedOffset: cakepassphrase); + legacy = currentWallet!.seed(seedOffset: cakepassphrase); } - if (monero.Wallet_status(wptr!) != 0) { - final err = monero.Wallet_errorString(wptr!); + if (currentWallet!.status() != 0) { + final err = currentWallet!.errorString(); if (legacy.isNotEmpty) { return "$err\n\n$legacy"; } @@ -117,7 +116,7 @@ String getSeedLegacy(String? language) { } String getPassphrase() { - return monero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.passphrase"); + return currentWallet!.getCacheAttribute(key: "cakewallet.passphrase"); } Map>> addressCache = {}; @@ -125,31 +124,31 @@ Map>> addressCache = {}; String getAddress({int accountIndex = 0, int addressIndex = 0}) { // printV("getaddress: ${accountIndex}/${addressIndex}: ${monero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex)}: ${monero.Wallet_address(wptr!, accountIndex: accountIndex, addressIndex: addressIndex)}"); // this could be a while loop, but I'm in favor of making it if to not cause freezes - if (monero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex)-1 < addressIndex) { - if (monero.Wallet_numSubaddressAccounts(wptr!) < accountIndex) { - monero.Wallet_addSubaddressAccount(wptr!); + if (currentWallet!.numSubaddresses(accountIndex: accountIndex)-1 < addressIndex) { + if (currentWallet!.numSubaddressAccounts() < accountIndex) { + currentWallet!.addSubaddressAccount(); } else { - monero.Wallet_addSubaddress(wptr!, accountIndex: accountIndex); + currentWallet!.addSubaddress(accountIndex: accountIndex); } } - addressCache[wptr!.address] ??= {}; - addressCache[wptr!.address]![accountIndex] ??= {}; - addressCache[wptr!.address]![accountIndex]![addressIndex] ??= monero.Wallet_address(wptr!, + addressCache[currentWallet!.ffiAddress()] ??= {}; + addressCache[currentWallet!.ffiAddress()]![accountIndex] ??= {}; + addressCache[currentWallet!.ffiAddress()]![accountIndex]![addressIndex] ??= currentWallet!.address( accountIndex: accountIndex, addressIndex: addressIndex); - return addressCache[wptr!.address]![accountIndex]![addressIndex]!; + return addressCache[currentWallet!.ffiAddress()]![accountIndex]![addressIndex]!; } int getFullBalance({int accountIndex = 0}) => - monero.Wallet_balance(wptr!, accountIndex: accountIndex); + currentWallet!.balance(accountIndex: accountIndex); int getUnlockedBalance({int accountIndex = 0}) => - monero.Wallet_unlockedBalance(wptr!, accountIndex: accountIndex); + currentWallet!.unlockedBalance(accountIndex: accountIndex); -int getCurrentHeight() => monero.Wallet_blockChainHeight(wptr!); +int getCurrentHeight() => currentWallet!.blockChainHeight(); -int getNodeHeightSync() => monero.Wallet_daemonBlockChainHeight(wptr!); +int getNodeHeightSync() => currentWallet!.daemonBlockChainHeight(); -bool isConnectedSync() => monero.Wallet_connected(wptr!) != 0; +bool isConnectedSync() => currentWallet!.connected() != 0; Future setupNodeSync( {required String address, @@ -168,7 +167,7 @@ Future setupNodeSync( daemonPassword: $password ?? '' } '''); - final addr = wptr!.address; + final addr = currentWallet!.ffiAddress(); printV("init: start"); await Isolate.run(() { monero.Wallet_init(Pointer.fromAddress(addr), @@ -180,10 +179,10 @@ Future setupNodeSync( }); printV("init: end"); - final status = monero.Wallet_status(wptr!); + final status = currentWallet!.status(); if (status != 0) { - final error = monero.Wallet_errorString(wptr!); + final error = currentWallet!.errorString(); if (error != "no tx keys found for this txid") { printV("error: $error"); throw SetupWalletException(message: error); @@ -191,8 +190,8 @@ Future setupNodeSync( } if (true) { - monero.Wallet_init3( - wptr!, argv0: '', + currentWallet!.init3( + argv0: '', defaultLogBaseName: 'moneroc', console: true, logPath: '', @@ -203,19 +202,19 @@ Future setupNodeSync( } void startRefreshSync() { - monero.Wallet_refreshAsync(wptr!); - monero.Wallet_startRefresh(wptr!); + currentWallet!.refreshAsync(); + currentWallet!.startRefresh(); } void setRefreshFromBlockHeight({required int height}) { - monero.Wallet_setRefreshFromBlockHeight(wptr!, + currentWallet!.setRefreshFromBlockHeight( refresh_from_block_height: height); } void setRecoveringFromSeed({required bool isRecovery}) { - monero.Wallet_setRecoveringFromSeed(wptr!, recoveringFromSeed: isRecovery); - monero.Wallet_store(wptr!); + currentWallet!.setRecoveringFromSeed(recoveringFromSeed: isRecovery); + currentWallet!.store(); } final storeMutex = Mutex(); @@ -224,18 +223,18 @@ final storeMutex = Mutex(); int lastStorePointer = 0; int lastStoreHeight = 0; void storeSync({bool force = false}) async { - final addr = wptr!.address; + final addr = currentWallet!.ffiAddress(); final synchronized = await Isolate.run(() { return monero.Wallet_synchronized(Pointer.fromAddress(addr)); }); - if (lastStorePointer == wptr!.address && - lastStoreHeight + 5000 > monero.Wallet_blockChainHeight(wptr!) && + if (lastStorePointer == addr && + lastStoreHeight + 5000 > currentWallet!.blockChainHeight() && !synchronized && !force) { return; } - lastStorePointer = wptr!.address; - lastStoreHeight = monero.Wallet_blockChainHeight(wptr!); + lastStorePointer = currentWallet!.ffiAddress(); + lastStoreHeight = currentWallet!.blockChainHeight(); await storeMutex.acquire(); await Isolate.run(() { monero.Wallet_store(Pointer.fromAddress(addr)); @@ -244,25 +243,25 @@ void storeSync({bool force = false}) async { } void setPasswordSync(String password) { - monero.Wallet_setPassword(wptr!, password: password); + currentWallet!.setPassword(password: password); - final status = monero.Wallet_status(wptr!); + final status = currentWallet!.status(); if (status != 0) { - throw Exception(monero.Wallet_errorString(wptr!)); + throw Exception(currentWallet!.errorString()); } } void closeCurrentWallet() { - monero.Wallet_stop(wptr!); + currentWallet!.stop(); } -String getSecretViewKey() => monero.Wallet_secretViewKey(wptr!); +String getSecretViewKey() => currentWallet!.secretViewKey(); -String getPublicViewKey() => monero.Wallet_publicViewKey(wptr!); +String getPublicViewKey() => currentWallet!.publicViewKey(); -String getSecretSpendKey() => monero.Wallet_secretSpendKey(wptr!); +String getSecretSpendKey() => currentWallet!.secretSpendKey(); -String getPublicSpendKey() => monero.Wallet_publicSpendKey(wptr!); +String getPublicSpendKey() => currentWallet!.publicSpendKey(); class SyncListener { SyncListener(this.onNewBlock, this.onNewTransaction) @@ -360,52 +359,32 @@ Future _setupNodeSync(Map args) async { socksProxyAddress: socksProxyAddress); } -bool _isConnected(Object _) => isConnectedSync(); - -int _getNodeHeight(Object _) => getNodeHeightSync(); - void startRefresh() => startRefreshSync(); -Future setupNode( - {required String address, - String? login, - String? password, - bool useSSL = false, - String? socksProxyAddress, - bool isLightWallet = false}) async => - _setupNodeSync({ - 'address': address, - 'login': login, - 'password': password, - 'useSSL': useSSL, - 'isLightWallet': isLightWallet, - 'socksProxyAddress': socksProxyAddress - }); - Future store() async => _storeSync(0); -Future isConnected() async => _isConnected(0); +Future isConnected() async => isConnectedSync(); -Future getNodeHeight() async => _getNodeHeight(0); +Future getNodeHeight() async => getNodeHeightSync(); -void rescanBlockchainAsync() => monero.Wallet_rescanBlockchainAsync(wptr!); +void rescanBlockchainAsync() => currentWallet!.rescanBlockchainAsync(); String getSubaddressLabel(int accountIndex, int addressIndex) { - return monero.Wallet_getSubaddressLabel(wptr!, + return currentWallet!.getSubaddressLabel( accountIndex: accountIndex, addressIndex: addressIndex); } Future setTrustedDaemon(bool trusted) async => - monero.Wallet_setTrustedDaemon(wptr!, arg: trusted); + currentWallet!.setTrustedDaemon(arg: trusted); -Future trustedDaemon() async => monero.Wallet_trustedDaemon(wptr!); +Future trustedDaemon() async => currentWallet!.trustedDaemon(); String signMessage(String message, {String address = ""}) { - return monero.Wallet_signMessage(wptr!, message: message, address: address); + return currentWallet!.signMessage(message: message, address: address); } bool verifyMessage(String message, String address, String signature) { - return monero.Wallet_verifySignedMessage(wptr!, message: message, address: address, signature: signature); + return currentWallet!.verifySignedMessage(message: message, address: address, signature: signature); } Map> debugCallLength() => monero.debugCallLength; diff --git a/cw_monero/lib/api/wallet_manager.dart b/cw_monero/lib/api/wallet_manager.dart index b43773447..9b369c155 100644 --- a/cw_monero/lib/api/wallet_manager.dart +++ b/cw_monero/lib/api/wallet_manager.dart @@ -12,6 +12,8 @@ import 'package:cw_monero/api/transaction_history.dart'; import 'package:cw_monero/api/wallet.dart'; import 'package:cw_monero/ledger.dart'; import 'package:flutter/foundation.dart'; +import 'package:monero/src/monero.dart'; +import 'package:monero/src/wallet2.dart'; import 'package:monero/monero.dart' as monero; class MoneroCException implements Exception { @@ -24,9 +26,10 @@ class MoneroCException implements Exception { } void checkIfMoneroCIsFine() { - final cppCsCpp = monero.MONERO_checksum_wallet2_api_c_cpp(); - final cppCsH = monero.MONERO_checksum_wallet2_api_c_h(); - final cppCsExp = monero.MONERO_checksum_wallet2_api_c_exp(); + final checksum = MoneroWalletChecksum(); + final cppCsCpp = checksum.checksum_wallet2_api_c_cpp(); + final cppCsH = checksum.checksum_wallet2_api_c_h(); + final cppCsExp = checksum.checksum_wallet2_api_c_exp(); final dartCsCpp = monero.wallet2_api_c_cpp_sha256; final dartCsH = monero.wallet2_api_c_h_sha256; @@ -44,36 +47,35 @@ void checkIfMoneroCIsFine() { throw MoneroCException("monero_c and monero.dart wrapper export list mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsExp'\ndart: '$dartCsExp'"); } } -monero.WalletManager? _wmPtr; -final monero.WalletManager wmPtr = Pointer.fromAddress((() { +Wallet2WalletManager? _wmPtr; +Wallet2WalletManager wmPtr = (() { try { // Problems with the wallet? Crashes? Lags? this will print all calls to xmr // codebase, so it will be easier to debug what happens. At least easier // than plugging gdb in. Especially on windows/android. monero.printStarts = false; if (kDebugMode && debugMonero) { - monero.WalletManagerFactory_setLogLevel(4); + MoneroWalletManagerFactory().setLogLevel(4); } - _wmPtr ??= monero.WalletManagerFactory_getWalletManager(); + _wmPtr ??= MoneroWalletManagerFactory().getWalletManager(); if (kDebugMode && debugMonero) { - monero.WalletManagerFactory_setLogLevel(4); + MoneroWalletManagerFactory().setLogLevel(4); } printV("ptr: $_wmPtr"); } catch (e) { printV(e); rethrow; } - return _wmPtr!.address; -})()); + return _wmPtr!; +})(); -void createWalletPointer() { - final newWptr = monero.WalletManager_createWallet(wmPtr, +Wallet2Wallet createWalletPointer() { + final newWptr = wmPtr.createWallet( path: "", password: "", language: "", networkType: 0); - - wptr = newWptr; + return newWptr; } -void createWalletSync( +void createWallet( {required String path, required String password, required String language, @@ -81,28 +83,25 @@ void createWalletSync( int nettype = 0}) { txhistory = null; language = getSeedLanguage(language)!; - final newWptr = monero.WalletManager_createWallet(wmPtr, + final newW = wmPtr.createWallet( path: path, password: password, language: language, networkType: 0); - int status = monero.Wallet_status(newWptr); + int status = newW.status(); if (status != 0) { - throw WalletCreationException(message: monero.Wallet_errorString(newWptr)); + throw WalletCreationException(message: newW.errorString()); } - setupBackgroundSync(password, newWptr); + setupBackgroundSync(password, newW); - wptr = newWptr; - monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: passphrase); - monero.Wallet_store(wptr!, path: path); - openedWalletsByPath[path] = wptr!; + currentWallet = newW; + currentWallet!.setCacheAttribute(key: "cakewallet.passphrase", value: passphrase); + currentWallet!.store(path: path); + openedWalletsByPath[path] = currentWallet!; _lastOpenedWallet = path; - - // is the line below needed? - // setupNodeSync(address: "node.moneroworld.com:18089"); } -bool isWalletExistSync({required String path}) { - return monero.WalletManager_walletExists(wmPtr, path); +bool isWalletExist({required String path}) { + return wmPtr.walletExists(path); } void restoreWalletFromSeedSync( @@ -113,8 +112,7 @@ void restoreWalletFromSeedSync( int nettype = 0, int restoreHeight = 0}) { txhistory = null; - final newWptr = monero.WalletManager_recoveryWallet( - wmPtr, + final newW = wmPtr.recoveryWallet( path: path, password: password, mnemonic: seed, @@ -123,10 +121,10 @@ void restoreWalletFromSeedSync( networkType: 0, ); - final status = monero.Wallet_status(newWptr); + final status = newW.status(); if (status != 0) { - final error = monero.Wallet_errorString(newWptr); + final error = newW.errorString(); if (error.contains('word list failed verification')) { throw WalletRestoreFromSeedException( message: "Seed verification failed, please make sure you entered the correct seed with the correct words order", @@ -134,20 +132,20 @@ void restoreWalletFromSeedSync( } throw WalletRestoreFromSeedException(message: error); } - wptr = newWptr; + currentWallet = newW; setRefreshFromBlockHeight(height: restoreHeight); - setupBackgroundSync(password, newWptr); + setupBackgroundSync(password, newW); - monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: passphrase); + currentWallet!.setCacheAttribute(key: "cakewallet.passphrase", value: passphrase); - openedWalletsByPath[path] = wptr!; + openedWalletsByPath[path] = currentWallet!; - monero.Wallet_store(wptr!); + currentWallet!.store(path: path); _lastOpenedWallet = path; } -void restoreWalletFromKeysSync( +void restoreWalletFromKeys( {required String path, required String password, required String language, @@ -157,8 +155,8 @@ void restoreWalletFromKeysSync( int nettype = 0, int restoreHeight = 0}) { txhistory = null; - var newWptr = (spendKey != "") - ? monero.WalletManager_createDeterministicWalletFromSpendKey(wmPtr, + var newW = (spendKey != "") + ? wmPtr.createDeterministicWalletFromSpendKey( path: path, password: password, language: language, @@ -166,8 +164,7 @@ void restoreWalletFromKeysSync( newWallet: true, // TODO(mrcyjanek): safe to remove restoreHeight: restoreHeight) - : monero.WalletManager_createWalletFromKeys( - wmPtr, + : wmPtr.createWalletFromKeys( path: path, password: password, restoreHeight: restoreHeight, @@ -177,22 +174,21 @@ void restoreWalletFromKeysSync( nettype: 0, ); - int status = monero.Wallet_status(newWptr); + int status = newW.status(); if (status != 0) { throw WalletRestoreFromKeysException( - message: monero.Wallet_errorString(newWptr)); + message: newW.errorString()); } // CW-712 - Try to restore deterministic wallet first, if the view key doesn't // match the view key provided if (spendKey != "") { - final viewKeyRestored = monero.Wallet_secretViewKey(newWptr); + final viewKeyRestored = newW.secretViewKey(); if (viewKey != viewKeyRestored && viewKey != "") { - monero.WalletManager_closeWallet(wmPtr, newWptr, false); + wmPtr.closeWallet(newW, false); File(path).deleteSync(); File(path + ".keys").deleteSync(); - newWptr = monero.WalletManager_createWalletFromKeys( - wmPtr, + newW = wmPtr.createWalletFromKeys( path: path, password: password, restoreHeight: restoreHeight, @@ -201,19 +197,19 @@ void restoreWalletFromKeysSync( spendKeyString: spendKey, nettype: 0, ); - int status = monero.Wallet_status(newWptr); + int status = newW.status(); if (status != 0) { throw WalletRestoreFromKeysException( - message: monero.Wallet_errorString(newWptr)); + message: newW.errorString()); } - setupBackgroundSync(password, newWptr); + setupBackgroundSync(password, newW); } } - wptr = newWptr; + currentWallet = newW; - openedWalletsByPath[path] = wptr!; + openedWalletsByPath[path] = currentWallet!; _lastOpenedWallet = path; } @@ -228,8 +224,7 @@ void restoreWalletFromPolyseedWithOffset( int nettype = 0}) { txhistory = null; - final newWptr = monero.WalletManager_createWalletFromPolyseed( - wmPtr, + final newW = wmPtr.createWalletFromPolyseed( path: path, password: password, networkType: nettype, @@ -240,24 +235,24 @@ void restoreWalletFromPolyseedWithOffset( kdfRounds: 1, ); - int status = monero.Wallet_status(newWptr); + int status = newW.status(); if (status != 0) { - final err = monero.Wallet_errorString(newWptr); + final err = newW.errorString(); printV("err: $err"); throw WalletRestoreFromKeysException(message: err); } - wptr = newWptr; + currentWallet = newW; - monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed); - monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: seedOffset); - monero.Wallet_store(wptr!); + currentWallet!.setCacheAttribute(key: "cakewallet.seed", value: seed); + currentWallet!.setCacheAttribute(key: "cakewallet.passphrase", value: seedOffset); + currentWallet!.store(path: path); - setupBackgroundSync(password, newWptr); + setupBackgroundSync(password, currentWallet!); storeSync(); - openedWalletsByPath[path] = wptr!; + openedWalletsByPath[path] = currentWallet!; } @@ -282,8 +277,7 @@ void restoreWalletFromSpendKeySync( // ); txhistory = null; - final newWptr = monero.WalletManager_createDeterministicWalletFromSpendKey( - wmPtr, + final newW = wmPtr.createDeterministicWalletFromSpendKey( path: path, password: password, language: language, @@ -292,23 +286,23 @@ void restoreWalletFromSpendKeySync( restoreHeight: restoreHeight, ); - int status = monero.Wallet_status(newWptr); + int status = newW.status(); if (status != 0) { - final err = monero.Wallet_errorString(newWptr); + final err = newW.errorString(); printV("err: $err"); throw WalletRestoreFromKeysException(message: err); } - wptr = newWptr; + currentWallet = newW; - monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed); + currentWallet!.setCacheAttribute(key: "cakewallet.seed", value: seed); storeSync(); - setupBackgroundSync(password, newWptr); + setupBackgroundSync(password, currentWallet!); - openedWalletsByPath[path] = wptr!; + openedWalletsByPath[path] = currentWallet!; _lastOpenedWallet = path; } @@ -321,41 +315,42 @@ Future restoreWalletFromHardwareWallet( int nettype = 0, int restoreHeight = 0}) async { txhistory = null; - + final wmPtr = MoneroWalletManagerFactory().getWalletManager().ffiAddress(); final newWptrAddr = await Isolate.run(() { - return monero.WalletManager_createWalletFromDevice(wmPtr, + return monero.WalletManager_createWalletFromDevice(Pointer.fromAddress(wmPtr), path: path, password: password, restoreHeight: restoreHeight, deviceName: deviceName) .address; }); - final newWptr = Pointer.fromAddress(newWptrAddr); + final newW = MoneroWallet(Pointer.fromAddress(newWptrAddr)); - final status = monero.Wallet_status(newWptr); + final status = newW.status(); if (status != 0) { - final error = monero.Wallet_errorString(newWptr); + final error = newW.errorString(); throw WalletRestoreFromSeedException(message: error); } - wptr = newWptr; + currentWallet = newW; + currentWallet!.store(path: path); _lastOpenedWallet = path; - openedWalletsByPath[path] = wptr!; + openedWalletsByPath[path] = currentWallet!; } -Map openedWalletsByPath = {}; +Map openedWalletsByPath = {}; Future loadWallet( {required String path, required String password, int nettype = 0}) async { if (openedWalletsByPath[path] != null) { txhistory = null; - wptr = openedWalletsByPath[path]!; + currentWallet = openedWalletsByPath[path]!; return; } - if (wptr == null || path != _lastOpenedWallet) { - if (wptr != null) { - final addr = wptr!.address; + if (currentWallet == null || path != _lastOpenedWallet) { + if (currentWallet != null) { + final addr = currentWallet!.ffiAddress(); Isolate.run(() { monero.Wallet_store(Pointer.fromAddress(addr)); }); @@ -366,19 +361,24 @@ Future loadWallet( /// 0: Software Wallet /// 1: Ledger /// 2: Trezor - late final deviceType; + var deviceType = 0; if (Platform.isAndroid || Platform.isIOS) { - deviceType = monero.WalletManager_queryWalletDevice( - wmPtr, + deviceType = wmPtr.queryWalletDevice( keysFileName: "$path.keys", password: password, kdfRounds: 1, ); - final status = monero.WalletManager_errorString(wmPtr); + final status = wmPtr.errorString(); if (status != "") { printV("loadWallet:"+status); - throw WalletOpeningException(message: status); + // This is most likely closeWallet call leaking error. This is fine. + if (status.contains("failed to save file")) { + printV("loadWallet: error leaked: $status"); + deviceType = 0; + } else { + throw WalletOpeningException(message: status); + } } } else { deviceType = 0; @@ -388,107 +388,47 @@ Future loadWallet( if (gLedger == null) { throw Exception("Tried to open a ledger wallet with no ledger connected"); } - final dummyWPtr = wptr ?? - monero.WalletManager_openWallet(wmPtr, path: '', password: ''); + final dummyWPtr = (currentWallet ?? + wmPtr.openWallet(path: '', password: '')); enableLedgerExchange(dummyWPtr, gLedger!); } - final addr = wmPtr.address; + final addr = wmPtr.ffiAddress(); final newWptrAddr = await Isolate.run(() { return monero.WalletManager_openWallet(Pointer.fromAddress(addr), path: path, password: password) .address; }); - final newWptr = Pointer.fromAddress(newWptrAddr); + final newW = MoneroWallet(Pointer.fromAddress(newWptrAddr)); - int status = monero.Wallet_status(newWptr); + int status = newW.status(); if (status != 0) { - final err = monero.Wallet_errorString(newWptr); + final err = newW.errorString(); printV("loadWallet:"+err); throw WalletOpeningException(message: err); } if (deviceType == 0) { - setupBackgroundSync(password, newWptr); + setupBackgroundSync(password, newW); } - wptr = newWptr; + currentWallet = newW; _lastOpenedWallet = path; - openedWalletsByPath[path] = wptr!; + openedWalletsByPath[path] = currentWallet!; } } -void setupBackgroundSync(String password, Pointer? wptrOverride) { - if (isViewOnlyBySpendKey(wptrOverride)) { +void setupBackgroundSync(String password, Wallet2Wallet wallet) { + if (isViewOnlyBySpendKey(wallet)) { return; } - monero.Wallet_setupBackgroundSync(wptrOverride ?? wptr!, backgroundSyncType: 2, walletPassword: password, backgroundCachePassword: ''); - if (monero.Wallet_status(wptrOverride ?? wptr!) != 0) { + wallet.setupBackgroundSync(backgroundSyncType: 2, walletPassword: password, backgroundCachePassword: ''); + if (wallet.status() != 0) { // We simply ignore the error. - printV("setupBackgroundSync: ${monero.Wallet_errorString(wptrOverride ?? wptr!)}"); + printV("setupBackgroundSync: ${wallet.errorString()}"); } } -void _createWallet(Map args) { - final path = args['path'] as String; - final password = args['password'] as String; - final language = args['language'] as String; - final passphrase = args['passphrase'] as String; - - createWalletSync(path: path, password: password, language: language, passphrase: passphrase); -} - -void _restoreFromSeed(Map args) { - final path = args['path'] as String; - final password = args['password'] as String; - final passphrase = args['passphrase'] as String; - final seed = args['seed'] as String; - final restoreHeight = args['restoreHeight'] as int; - - return restoreWalletFromSeedSync( - path: path, password: password, passphrase: passphrase, seed: seed, restoreHeight: restoreHeight); -} - -void _restoreFromKeys(Map args) { - final path = args['path'] as String; - final password = args['password'] as String; - final language = args['language'] as String; - final restoreHeight = args['restoreHeight'] as int; - final address = args['address'] as String; - final viewKey = args['viewKey'] as String; - final spendKey = args['spendKey'] as String; - - restoreWalletFromKeysSync( - path: path, - password: password, - language: language, - restoreHeight: restoreHeight, - address: address, - viewKey: viewKey, - spendKey: spendKey); -} - -void _restoreFromSpendKey(Map args) { - final path = args['path'] as String; - final password = args['password'] as String; - final seed = args['seed'] as String; - final language = args['language'] as String; - final spendKey = args['spendKey'] as String; - final restoreHeight = args['restoreHeight'] as int; - - restoreWalletFromSpendKeySync( - path: path, - password: password, - seed: seed, - language: language, - restoreHeight: restoreHeight, - spendKey: spendKey); -} - -Future _openWallet(Map args) async => loadWallet( - path: args['path'] as String, password: args['password'] as String); - -bool _isWalletExist(String path) => isWalletExistSync(path: path); Future openWallet( {required String path, @@ -496,77 +436,4 @@ Future openWallet( int nettype = 0}) async => loadWallet(path: path, password: password, nettype: nettype); -Future openWalletAsync(Map args) async => - _openWallet(args); - -Future createWallet( - {required String path, - required String password, - required String language, - required String passphrase, - int nettype = 0}) async => - _createWallet({ - 'path': path, - 'password': password, - 'language': language, - 'passphrase': passphrase, - 'nettype': nettype - }); - -void restoreFromSeed( - {required String path, - required String password, - required String passphrase, - required String seed, - int nettype = 0, - int restoreHeight = 0}) => - _restoreFromSeed({ - 'path': path, - 'password': password, - 'passphrase': passphrase, - 'seed': seed, - 'nettype': nettype, - 'restoreHeight': restoreHeight - }); - -Future restoreFromKeys( - {required String path, - required String password, - required String language, - required String address, - required String viewKey, - required String spendKey, - int nettype = 0, - int restoreHeight = 0}) async => - _restoreFromKeys({ - 'path': path, - 'password': password, - 'language': language, - 'address': address, - 'viewKey': viewKey, - 'spendKey': spendKey, - 'nettype': nettype, - 'restoreHeight': restoreHeight - }); - -Future restoreFromSpendKey( - {required String path, - required String password, - required String seed, - required String language, - required String spendKey, - int nettype = 0, - int restoreHeight = 0}) async => - _restoreFromSpendKey({ - 'path': path, - 'password': password, - 'seed': seed, - 'language': language, - 'spendKey': spendKey, - 'nettype': nettype, - 'restoreHeight': restoreHeight - }); - -bool isWalletExist({required String path}) => _isWalletExist(path); - -bool isViewOnlyBySpendKey(Pointer? wptrOverride) => int.tryParse(monero.Wallet_secretSpendKey(wptrOverride ?? wptr!)) == 0; +bool isViewOnlyBySpendKey(Wallet2Wallet? wallet) => int.tryParse((wallet??currentWallet!).secretSpendKey()) == 0; diff --git a/cw_monero/lib/ledger.dart b/cw_monero/lib/ledger.dart index 219d45bb5..d931b58d0 100644 --- a/cw_monero/lib/ledger.dart +++ b/cw_monero/lib/ledger.dart @@ -7,26 +7,26 @@ import 'package:cw_core/utils/print_verbose.dart'; import 'package:ffi/ffi.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus_dart.dart'; -import 'package:monero/monero.dart' as monero; +import 'package:monero/src/wallet2.dart'; LedgerConnection? gLedger; Timer? _ledgerExchangeTimer; Timer? _ledgerKeepAlive; -void enableLedgerExchange(monero.wallet ptr, LedgerConnection connection) { +void enableLedgerExchange(Wallet2Wallet wallet, LedgerConnection connection) { _ledgerExchangeTimer?.cancel(); _ledgerExchangeTimer = Timer.periodic(Duration(milliseconds: 1), (_) async { - final ledgerRequestLength = monero.Wallet_getSendToDeviceLength(ptr); - final ledgerRequest = monero.Wallet_getSendToDevice(ptr) + final ledgerRequestLength = wallet.getSendToDeviceLength(); + final ledgerRequest = wallet.getSendToDevice() .cast() .asTypedList(ledgerRequestLength); if (ledgerRequestLength > 0) { _ledgerKeepAlive?.cancel(); final Pointer emptyPointer = malloc(0); - monero.Wallet_setDeviceSendData( - ptr, emptyPointer.cast(), 0); + wallet.setDeviceSendData( + emptyPointer.cast(), 0); malloc.free(emptyPointer); _logLedgerCommand(ledgerRequest, false); @@ -45,8 +45,8 @@ void enableLedgerExchange(monero.wallet ptr, LedgerConnection connection) { result.asTypedList(response.length)[i] = response[i]; } - monero.Wallet_setDeviceReceivedData( - ptr, result.cast(), response.length); + wallet.setDeviceReceivedData( + result.cast(), response.length); malloc.free(result); keepAlive(connection); } diff --git a/cw_monero/lib/monero_account_list.dart b/cw_monero/lib/monero_account_list.dart index c9a48a939..4190a4a1e 100644 --- a/cw_monero/lib/monero_account_list.dart +++ b/cw_monero/lib/monero_account_list.dart @@ -4,7 +4,7 @@ import 'package:cw_monero/api/wallet_manager.dart'; import 'package:mobx/mobx.dart'; import 'package:cw_core/account.dart'; import 'package:cw_monero/api/account_list.dart' as account_list; -import 'package:monero/monero.dart' as monero; +import 'package:monero/src/monero.dart'; part 'monero_account_list.g.dart'; @@ -50,32 +50,32 @@ abstract class MoneroAccountListBase with Store { List getAll() { final allAccounts = account_list.getAllAccount(); final currentCount = allAccounts.length; - cachedAccounts[account_list.wptr!.address] ??= []; + cachedAccounts[account_list.currentWallet!.ffiAddress()] ??= []; - if (cachedAccounts[account_list.wptr!.address]!.length == currentCount) { - return cachedAccounts[account_list.wptr!.address]!; + if (cachedAccounts[account_list.currentWallet!.ffiAddress()]!.length == currentCount) { + return cachedAccounts[account_list.currentWallet!.ffiAddress()]!; } - cachedAccounts[account_list.wptr!.address] = allAccounts.map((accountRow) { - final balance = monero.SubaddressAccountRow_getUnlockedBalance(accountRow); + cachedAccounts[account_list.currentWallet!.ffiAddress()] = allAccounts.map((accountRow) { + final balance = accountRow.getUnlockedBalance(); return Account( - id: monero.SubaddressAccountRow_getRowId(accountRow), - label: monero.SubaddressAccountRow_getLabel(accountRow), - balance: moneroAmountToString(amount: monero.Wallet_amountFromString(balance)), + id: accountRow.getRowId(), + label: accountRow.getLabel(), + balance: moneroAmountToString(amount: account_list.currentWallet!.amountFromString(balance)), ); }).toList(); - return cachedAccounts[account_list.wptr!.address]!; + return cachedAccounts[account_list.currentWallet!.ffiAddress()]!; } - Future addAccount({required String label}) async { - await account_list.addAccount(label: label); + void addAccount({required String label}) { + account_list.addAccount(label: label); update(); } - Future setLabelAccount({required int accountIndex, required String label}) async { - await account_list.setLabelForAccount(accountIndex: accountIndex, label: label); + void setLabelAccount({required int accountIndex, required String label}) { + account_list.setLabelForAccount(accountIndex: accountIndex, label: label); update(); } diff --git a/cw_monero/lib/monero_unspent.dart b/cw_monero/lib/monero_unspent.dart index 224e33b2a..a88b98323 100644 --- a/cw_monero/lib/monero_unspent.dart +++ b/cw_monero/lib/monero_unspent.dart @@ -1,7 +1,7 @@ import 'package:cw_core/unspent_transaction_output.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_monero/api/coins_info.dart'; -import 'package:monero/monero.dart' as monero; +import 'package:monero/src/monero.dart'; class MoneroUnspent extends Unspent { static Future fromUnspent(String address, String hash, String keyImage, int value, bool isFrozen, bool isUnlocked) async { diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 69c894908..7a4d943fe 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -39,6 +39,7 @@ import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:mobx/mobx.dart'; +import 'package:monero/src/monero.dart' as m; import 'package:monero/monero.dart' as monero; part 'monero_wallet.g.dart'; @@ -84,7 +85,7 @@ abstract class MoneroWalletBase extends WalletBase isEnabledAutoGenerateSubaddress, (bool enabled) { @@ -139,7 +140,7 @@ abstract class MoneroWalletBase extends WalletBase - transactionHistory.transactions.values.firstOrNull?.height ?? monero.Wallet_getRefreshFromBlockHeight(wptr!); + transactionHistory.transactions.values.firstOrNull?.height ?? currentWallet?.getRefreshFromBlockHeight(); monero_wallet.SyncListener? _listener; ReactionDisposer? _onAccountChangeReaction; @@ -169,7 +170,7 @@ abstract class MoneroWalletBase extends WalletBase connectToNode({required Node node}) async { try { syncStatus = ConnectingSyncStatus(); - await monero_wallet.setupNode( + await monero_wallet.setupNodeSync( address: node.uri.toString(), login: node.login, password: node.password, @@ -237,10 +247,10 @@ abstract class MoneroWalletBase extends WalletBase stopSync() async { if (isBackgroundSyncRunning) { printV("Stopping background sync"); - monero.Wallet_store(wptr!); - monero.Wallet_stopBackgroundSync(wptr!, ''); - monero_wallet.store(); + currentWallet!.store(); + currentWallet!.stopBackgroundSync(''); + currentWallet!.store(); isBackgroundSyncRunning = false; } await save(); @@ -269,9 +279,9 @@ abstract class MoneroWalletBase extends WalletBase stopBackgroundSync(String password) async { if (isBackgroundSyncRunning) { printV("Stopping background sync"); - monero.Wallet_store(wptr!); - monero.Wallet_stopBackgroundSync(wptr!, password); - monero.Wallet_store(wptr!); + currentWallet!.store(); + currentWallet!.stopBackgroundSync(password); + currentWallet!.store(); isBackgroundSyncRunning = false; } } @@ -308,44 +318,44 @@ abstract class MoneroWalletBase extends WalletBase submitTransactionUR(String ur) async { - final retStatus = monero.Wallet_submitTransactionUR(wptr!, ur); - final status = monero.Wallet_status(wptr!); + final retStatus = currentWallet!.submitTransactionUR(ur); + final status = currentWallet!.status(); if (status != 0) { - final err = monero.Wallet_errorString(wptr!); + final err = currentWallet!.errorString(); throw MoneroTransactionCreationException("unable to broadcast signed transaction: $err"); } return retStatus; } bool importKeyImagesUR(String ur) { - final retStatus = monero.Wallet_importKeyImagesUR(wptr!, ur); - final status = monero.Wallet_status(wptr!); + final retStatus = currentWallet!.importKeyImagesUR(ur); + final status = currentWallet!.status(); if (status != 0) { - final err = monero.Wallet_errorString(wptr!); + final err = currentWallet!.errorString(); throw Exception("unable to import key images: $err"); } return retStatus; } String exportOutputsUR(bool all) { - final str = monero.Wallet_exportOutputsUR(wptr!, all: all); - final status = monero.Wallet_status(wptr!); + final str = currentWallet!.exportOutputsUR(all: all); + final status = currentWallet!.status(); if (status != 0) { - final err = monero.Wallet_errorString(wptr!); + final err = currentWallet!.errorString(); throw MoneroTransactionCreationException("unable to export UR: $err"); } return str; } bool needExportOutputs(int amount) { - if (int.tryParse(monero.Wallet_secretSpendKey(wptr!)) != 0) { + if (int.tryParse(currentWallet!.secretSpendKey()) != 0) { return false; } // viewOnlyBalance - balance that we can spend // TODO(mrcyjanek): remove hasUnknownKeyImages when we cleanup coin control - return (monero.Wallet_viewOnlyBalance(wptr!, + return (currentWallet!.viewOnlyBalance( accountIndex: walletAddresses.account!.id) < amount) || - monero.Wallet_hasUnknownKeyImages(wptr!); + currentWallet!.hasUnknownKeyImages(); } @override @@ -425,12 +435,13 @@ abstract class MoneroWalletBase extends WalletBase _askForUpdateTransactionHistory() async => - await updateTransactions(); - int _getUnlockedBalance() => monero_wallet.getUnlockedBalance( accountIndex: walletAddresses.account!.id); @@ -852,13 +874,13 @@ abstract class MoneroWalletBase extends WalletBase.delayed(Duration(seconds: 1)); } catch (e) { @@ -917,8 +939,7 @@ abstract class MoneroWalletBase extends WalletBase info.id == WalletBase.idFor(name, getType())); final wallet = MoneroWallet( @@ -217,13 +220,23 @@ class MoneroWalletService extends WalletService< if (openedWalletsByPath["$path/$wallet"] != null) { // NOTE: this is realistically only required on windows. printV("closing wallet"); - final wmaddr = wmPtr.address; - final waddr = openedWalletsByPath["$path/$wallet"]!.address; - // await Isolate.run(() { - monero.WalletManager_closeWallet( - Pointer.fromAddress(wmaddr), Pointer.fromAddress(waddr), false); - // }); + final w = openedWalletsByPath["$path/$wallet"]!; + final wmaddr = wmPtr.ffiAddress(); + final waddr = w.ffiAddress(); openedWalletsByPath.remove("$path/$wallet"); + if (Platform.isWindows) { + await Isolate.run(() { + monero.WalletManager_closeWallet( + Pointer.fromAddress(wmaddr), Pointer.fromAddress(waddr), true); + monero.WalletManager_errorString(Pointer.fromAddress(wmaddr)); + }); + } else { + unawaited(Isolate.run(() { + monero.WalletManager_closeWallet( + Pointer.fromAddress(wmaddr), Pointer.fromAddress(waddr), true); + monero.WalletManager_errorString(Pointer.fromAddress(wmaddr)); + })); + } printV("wallet closed"); } @@ -263,7 +276,7 @@ class MoneroWalletService extends WalletService< {bool? isTestnet}) async { try { final path = await pathForWallet(name: credentials.name, type: getType()); - await monero_wallet_manager.restoreFromKeys( + monero_wallet_manager.restoreWalletFromKeys( path: path, password: credentials.password!, language: credentials.language, @@ -293,9 +306,13 @@ class MoneroWalletService extends WalletService< final password = credentials.password; final height = credentials.height; - if (wptr == null) monero_wallet_manager.createWalletPointer(); + if (currentWallet == null) { + final tmpWptr = monero_wallet_manager.createWalletPointer(); + enableLedgerExchange(tmpWptr, credentials.ledgerConnection); + } else { + enableLedgerExchange(currentWallet!, credentials.ledgerConnection); + } - enableLedgerExchange(wptr!, credentials.ledgerConnection); await monero_wallet_manager.restoreWalletFromHardwareWallet( path: path, password: password!, @@ -352,7 +369,7 @@ class MoneroWalletService extends WalletService< try { final path = await pathForWallet(name: credentials.name, type: getType()); - monero_wallet_manager.restoreFromSeed( + monero_wallet_manager.restoreWalletFromSeedSync( path: path, password: credentials.password!, passphrase: credentials.passphrase, @@ -393,7 +410,7 @@ class MoneroWalletService extends WalletService< walletInfo.isRecovery = true; walletInfo.restoreHeight = height; - monero_wallet_manager.restoreFromSeed( + monero_wallet_manager.restoreWalletFromSeedSync( path: path, password: password, passphrase: '', @@ -401,12 +418,12 @@ class MoneroWalletService extends WalletService< restoreHeight: height, ); - monero.Wallet_setCacheAttribute(wptr!, + currentWallet!.setCacheAttribute( key: "cakewallet.seed.bip39", value: mnemonic); - monero.Wallet_setCacheAttribute(wptr!, + currentWallet!.setCacheAttribute( key: "cakewallet.passphrase", value: passphrase ?? ''); - monero.Wallet_store(wptr!); + currentWallet!.store(); final wallet = MoneroWallet( walletInfo: walletInfo, @@ -472,7 +489,7 @@ class MoneroWalletService extends WalletService< walletInfo.isRecovery = true; walletInfo.restoreHeight = height; - await monero_wallet_manager.restoreFromSpendKey( + monero_wallet_manager.restoreWalletFromSpendKeySync( path: path, password: password, seed: seed, @@ -481,8 +498,8 @@ class MoneroWalletService extends WalletService< spendKey: spendKey); - monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed); - monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: passphrase??''); + currentWallet!.setCacheAttribute(key: "cakewallet.seed", value: seed); + currentWallet!.setCacheAttribute(key: "cakewallet.passphrase", value: passphrase??''); final wallet = MoneroWallet( walletInfo: walletInfo, @@ -529,7 +546,7 @@ class MoneroWalletService extends WalletService< if (walletFilesExist(path)) await repairOldAndroidWallet(name); await monero_wallet_manager - .openWalletAsync({'path': path, 'password': password}); + .openWallet(path: path, password: password); final walletInfo = walletInfoSource.values .firstWhere((info) => info.id == WalletBase.idFor(name, getType())); final wallet = MoneroWallet( diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index 278226b07..e48cf1dca 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -573,8 +573,8 @@ packages: dependency: "direct main" description: path: "impls/monero.dart" - ref: "84e52393e395d75f449bcd81e23028889538118f" - resolved-ref: "84e52393e395d75f449bcd81e23028889538118f" + ref: b335585a7fb94b315eb52bd88f2da6d3489fa508 + resolved-ref: b335585a7fb94b315eb52bd88f2da6d3489fa508 url: "https://github.com/mrcyjanek/monero_c" source: git version: "0.0.0" diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index 0e1537ee0..90d0b85ab 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -27,7 +27,7 @@ dependencies: monero: git: url: https://github.com/mrcyjanek/monero_c - ref: 84e52393e395d75f449bcd81e23028889538118f + ref: b335585a7fb94b315eb52bd88f2da6d3489fa508 path: impls/monero.dart mutex: ^3.1.0 ledger_flutter_plus: ^1.4.1 diff --git a/cw_wownero/pubspec.lock b/cw_wownero/pubspec.lock index 404060e43..66fd1fc56 100644 --- a/cw_wownero/pubspec.lock +++ b/cw_wownero/pubspec.lock @@ -480,8 +480,8 @@ packages: dependency: "direct main" description: path: "impls/monero.dart" - ref: "84e52393e395d75f449bcd81e23028889538118f" - resolved-ref: "84e52393e395d75f449bcd81e23028889538118f" + ref: b335585a7fb94b315eb52bd88f2da6d3489fa508 + resolved-ref: b335585a7fb94b315eb52bd88f2da6d3489fa508 url: "https://github.com/mrcyjanek/monero_c" source: git version: "0.0.0" diff --git a/cw_wownero/pubspec.yaml b/cw_wownero/pubspec.yaml index b35f36071..7b9ec4c41 100644 --- a/cw_wownero/pubspec.yaml +++ b/cw_wownero/pubspec.yaml @@ -25,7 +25,7 @@ dependencies: monero: git: url: https://github.com/mrcyjanek/monero_c - ref: 84e52393e395d75f449bcd81e23028889538118f # monero_c hash + ref: b335585a7fb94b315eb52bd88f2da6d3489fa508 # monero_c hash path: impls/monero.dart mutex: ^3.1.0 diff --git a/cw_zano/pubspec.lock b/cw_zano/pubspec.lock index 50835ed3f..b5486e0c1 100644 --- a/cw_zano/pubspec.lock +++ b/cw_zano/pubspec.lock @@ -485,8 +485,8 @@ packages: dependency: "direct main" description: path: "impls/monero.dart" - ref: "84e52393e395d75f449bcd81e23028889538118f" - resolved-ref: "84e52393e395d75f449bcd81e23028889538118f" + ref: b335585a7fb94b315eb52bd88f2da6d3489fa508 + resolved-ref: b335585a7fb94b315eb52bd88f2da6d3489fa508 url: "https://github.com/mrcyjanek/monero_c" source: git version: "0.0.0" diff --git a/cw_zano/pubspec.yaml b/cw_zano/pubspec.yaml index 9bf9c66eb..ff2c1e9a6 100644 --- a/cw_zano/pubspec.yaml +++ b/cw_zano/pubspec.yaml @@ -26,7 +26,7 @@ dependencies: monero: git: url: https://github.com/mrcyjanek/monero_c - ref: 84e52393e395d75f449bcd81e23028889538118f # monero_c hash + ref: b335585a7fb94b315eb52bd88f2da6d3489fa508 # monero_c hash path: impls/monero.dart dev_dependencies: flutter_test: diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 50ca9d6de..3d39d589e 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -208,42 +208,40 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/wakelock_plus/ios" SPEC CHECKSUMS: - connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d + connectivity_plus: 481668c94744c30c53b8895afb39159d1e619bdf CryptoSwift: e64e11850ede528a02a0f3e768cec8e9d92ecb90 - cw_decred: 9c0e1df74745b51a1289ec5e91fb9e24b68fa14a - cw_mweb: 22cd01dfb8ad2d39b15332006f22046aaa8352a3 - device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7 - device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 - devicelocale: 35ba84dc7f45f527c3001535d8c8d104edd5d926 + cw_decred: a02cf30175a46971c1e2fa22c48407534541edc6 + cw_mweb: 3aea2fb35b2bd04d8b2d21b83216f3b8fb768d85 + device_display_brightness: 04374ebd653619292c1d996f00f42877ea19f17f + device_info_plus: 335f3ce08d2e174b9fdc3db3db0f4e3b1f66bd89 + devicelocale: bd64aa714485a8afdaded0892c1e7d5b7f680cf8 DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 - fast_scanner: 44c00940355a51258cd6c2085734193cd23d95bc - file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655 + fast_scanner: 2cb1ad3e69e645e9980fb4961396ce5804caa3e3 + file_picker: 9b3292d7c8bc68c8a7bf8eb78f730e49c8efc517 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 - flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb - flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f - flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83 - flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be - fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f - in_app_review: a31b5257259646ea78e0e35fc914979b0031d011 - integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 + flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 + flutter_local_authentication: 989278c681612f1ee0e36019e149137f114b9d7f + flutter_mailer: 3a8cd4f36c960fb04528d5471097270c19fec1c4 + flutter_secure_storage: 2c2ff13db9e0a5647389bff88b0ecac56e3f3418 + fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 + in_app_review: 5596fe56fab799e8edb3561c03d053363ab13457 + integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 - package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 - reown_yttrium: c0e87e5965fa60a3559564cc35cffbba22976089 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d + ReachabilitySwift: 32793e867593cfc1177f5d16491e3a197d2fccda SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8 - sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986 - share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - sp_scanner: eaa617fa827396b967116b7f1f43549ca62e9a12 + sensitive_clipboard: 161e9abc3d56b3131309d8a321eb4690a803c16b + share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + sp_scanner: b1bc9321690980bdb44bba7ec85d5543e716d1b5 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 - uni_links: d97da20c7701486ba192624d99bffaaffcfc298a - universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6 - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - wakelock_plus: 76957ab028e12bfa4e66813c99e46637f367fc7e - YttriumWrapper: 31e937fe9fbe0f1314d2ca6be9ce9b379a059966 + uni_links: ed8c961e47ed9ce42b6d91e1de8049e38a4b3152 + universal_ble: ff19787898040d721109c6324472e5dd4bc86adc + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d + wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556 PODFILE CHECKSUM: 5296465b1c6d14d506230356756826012f65d97a diff --git a/lib/core/wallet_loading_service.dart b/lib/core/wallet_loading_service.dart index 49f366808..382b1d6c2 100644 --- a/lib/core/wallet_loading_service.dart +++ b/lib/core/wallet_loading_service.dart @@ -32,23 +32,28 @@ class WalletLoadingService { Future renameWallet(WalletType type, String name, String newName, {String? password}) async { - final walletService = walletServiceFactory.call(type); - final walletPassword = password ?? (await keyService.getWalletPassword(walletName: name)); + try { + final walletService = walletServiceFactory.call(type); + final walletPassword = password ?? (await keyService.getWalletPassword(walletName: name)); - // Save the current wallet's password to the new wallet name's key - await keyService.saveWalletPassword(walletName: newName, password: walletPassword); - // Delete previous wallet name from keyService to keep only new wallet's name - // otherwise keeps duplicate (old and new names) - await keyService.deleteWalletPassword(walletName: name); + // Save the current wallet's password to the new wallet name's key + await keyService.saveWalletPassword(walletName: newName, password: walletPassword); - await walletService.rename(name, walletPassword, newName); + await walletService.rename(name, walletPassword, newName); + // Delete previous wallet name from keyService to keep only new wallet's name + // otherwise keeps duplicate (old and new names) + await keyService.deleteWalletPassword(walletName: name); - // set shared preferences flag based on previous wallet name - if (type == WalletType.monero) { - final oldNameKey = PreferencesKey.moneroWalletUpdateV1Key(name); - final isPasswordUpdated = sharedPreferences.getBool(oldNameKey) ?? false; - final newNameKey = PreferencesKey.moneroWalletUpdateV1Key(newName); - await sharedPreferences.setBool(newNameKey, isPasswordUpdated); + // set shared preferences flag based on previous wallet name + if (type == WalletType.monero) { + final oldNameKey = PreferencesKey.moneroWalletUpdateV1Key(name); + final isPasswordUpdated = sharedPreferences.getBool(oldNameKey) ?? false; + final newNameKey = PreferencesKey.moneroWalletUpdateV1Key(newName); + await sharedPreferences.setBool(newNameKey, isPasswordUpdated); + } + } catch (error, stack) { + await ExceptionHandler.resetLastPopupDate(); + await ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack)); } } diff --git a/lib/monero/cw_monero.dart b/lib/monero/cw_monero.dart index b6be123c2..329c87338 100644 --- a/lib/monero/cw_monero.dart +++ b/lib/monero/cw_monero.dart @@ -39,14 +39,14 @@ class CWMoneroAccountList extends MoneroAccountList { @override Future addAccount(Object wallet, {required String label}) async { final moneroWallet = wallet as MoneroWallet; - await moneroWallet.walletAddresses.accountList.addAccount(label: label); + moneroWallet.walletAddresses.accountList.addAccount(label: label); } @override Future setLabelAccount(Object wallet, {required int accountIndex, required String label}) async { final moneroWallet = wallet as MoneroWallet; - await moneroWallet.walletAddresses.accountList + moneroWallet.walletAddresses.accountList .setLabelAccount(accountIndex: accountIndex, label: label); } } diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 00353f117..b60533130 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -271,10 +271,19 @@ abstract class DashboardViewModelBase with Store { }); _transactionDisposer?.reaction.dispose(); - _transactionDisposer = reaction( - (_) => appStore.wallet!.transactionHistory.transactions.length, - _transactionDisposerCallback, - ); + _transactionDisposer = reaction((_) { + final length = appStore.wallet!.transactionHistory.transactions.length; + if (length == 0) { + return 0; + } + int confirmations = 1; + if (![WalletType.solana, WalletType.tron].contains(wallet.type)) { + try { + confirmations = appStore.wallet!.transactionHistory.transactions.values.first.confirmations + 1; + } catch (_) {} + } + return length * confirmations; + }, _transactionDisposerCallback); if (hasSilentPayments) { silentPaymentsScanningActive = bitcoin!.getScanningActive(wallet); @@ -891,8 +900,19 @@ abstract class DashboardViewModelBase with Store { _transactionDisposer?.reaction.dispose(); - _transactionDisposer = reaction((_) => appStore.wallet!.transactionHistory.transactions.length, - _transactionDisposerCallback); + _transactionDisposer = reaction((_) { + final length = appStore.wallet!.transactionHistory.transactions.length; + if (length == 0) { + return 0; + } + int confirmations = 1; + if (![WalletType.solana, WalletType.tron].contains(wallet.type)) { + try { + confirmations = appStore.wallet!.transactionHistory.transactions.values.first.confirmations + 1; + } catch (_) {} + } + return length * confirmations; + }, _transactionDisposerCallback); } @action diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index b16edabd2..01905ba34 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/core/address_validator.dart'; import 'package:cake_wallet/core/amount_validator.dart'; @@ -591,7 +593,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } final sharedPreferences = await SharedPreferences.getInstance(); await sharedPreferences.setString(PreferencesKey.backgroundSyncLastTrigger(wallet.name), DateTime.now().add(Duration(minutes: 1)).toIso8601String()); - + unawaited(wallet.fetchTransactions()); state = TransactionCommitted(); } catch (e) { state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency)); diff --git a/scripts/prepare_moneroc.sh b/scripts/prepare_moneroc.sh index 133c0ab2f..b49ad2c34 100755 --- a/scripts/prepare_moneroc.sh +++ b/scripts/prepare_moneroc.sh @@ -8,7 +8,7 @@ if [[ ! -d "monero_c/.git" ]]; then git clone https://github.com/mrcyjanek/monero_c --branch master monero_c cd monero_c - git checkout 84e52393e395d75f449bcd81e23028889538118f + git checkout b335585a7fb94b315eb52bd88f2da6d3489fa508 git reset --hard git submodule update --init --force --recursive ./apply_patches.sh monero From eccc94a019b9c6dcc6473640605eb14aa10d3c3f Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Mon, 5 May 2025 11:50:04 +0300 Subject: [PATCH 066/142] temp. ignore secure storage iOS issue --- lib/utils/exception_handler.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils/exception_handler.dart b/lib/utils/exception_handler.dart index 7f6571f3f..e5b1c3579 100644 --- a/lib/utils/exception_handler.dart +++ b/lib/utils/exception_handler.dart @@ -225,7 +225,7 @@ class ExceptionHandler { // just ignoring until we find a solution to this issue or migrate from flutter secure storage "core/auth_service.dart:64", "core/key_service.dart:14", - "core/wallet_loading_service.dart:131", + "core/wallet_loading_service.dart:134", ]; static Future _addDeviceInfo(File file) async { From b7473594cb30b317602b14c8bf9b15e5443c5b14 Mon Sep 17 00:00:00 2001 From: Papakia <111633267+Papakia887@users.noreply.github.com> Date: Mon, 5 May 2025 18:55:11 +0900 Subject: [PATCH 067/142] Update strings_ko.arb (#2250) --- res/values/strings_ko.arb | 1582 ++++++++++++++++++------------------- 1 file changed, 791 insertions(+), 791 deletions(-) diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 6dffa5db4..e5ffe877e 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -1,5 +1,5 @@ { - "about_cake_pay": "Cake Pay를 사용하면 미국 내 150,000개 이상의 가맹점에서 즉시 사용할 수 있는 가상 자산이 포함된 기프트 카드를 쉽게 구입할 수 있습니다.", + "about_cake_pay": "Cake Pay를 사용하면 가상 자산으로 기프트 카드를 쉽게 구매하여 미국 내 150,000개 이상의 가맹점에서 즉시 사용할 수 있습니다.", "account": "계정", "accounts": "계정", "accounts_subaddresses": "계정 및 하위 주소", @@ -7,751 +7,752 @@ "active": "활성", "active_cards": "활성 카드", "activeConnectionsPrompt": "활성 연결이 여기에 표시됩니다", - "add": "더하다", - "add_contact": "주소록에 추가", - "add_contact_to_address_book": "이 연락처를 주소록에 추가 하시겠습니까?", - "add_custom_node": "새 사용자 정의 노드 추가", - "add_custom_redemption": "사용자 지정 상환 추가", - "add_fund_to_card": "카드에 선불 금액 추가(최대 ${value})", + "add": "추가", + "add_contact": "연락처 추가", + "add_contact_to_address_book": "이 연락처를 주소록에 추가하시겠습니까?", + "add_custom_node": "새 사용자 지정 노드 추가", + "add_custom_redemption": "사용자 지정 사용 추가", + "add_fund_to_card": "카드에 선불 자금 추가 (${value}까지)", "add_new_node": "새 노드 추가", - "add_new_word": "새로운 단어 추가", - "add_passphrase": "암호를 추가하십시오", - "add_passphrase_warning_text": "과거 에이 지갑에 사용한 경우에만 암호를 입력하십시오. 이 지갑에서 잘못된 암호를 입력하거나 암호를 사용하지 않은 경우 기존 자금이나 이력이 보이지 않을 것입니다.", - "add_receiver": "다른 수신기 추가(선택 사항)", - "add_secret_code": "또는 이 비밀 코드를 인증 앱에 추가하세요.", + "add_new_word": "새 단어 추가", + "add_passphrase": "암호 구문 추가", + "add_passphrase_warning_text": "과거에 이 지갑에 암호 구문을 사용한 경우에만 입력하십시오. 잘못된 암호 구문을 입력하거나 이 지갑에서 암호 구문을 사용한 적이 없다면 기존 자금이나 내역이 보이지 않습니다.", + "add_receiver": "다른 수신자 추가 (선택 사항)", + "add_secret_code": "또는 이 비밀 코드를 인증 앱에 추가하세요", "add_tip": "팁 추가", - "add_token_disclaimer_check": "신뢰할 수 있는 출처를 통해 토큰 컨트랙트 주소와 정보를 확인했습니다. 악의적이거나 잘못된 정보를 추가하면 자금 손실이 발생할 수 있습니다.", - "add_token_warning": "사기꾼의 지시에 따라 토큰을 편집하거나 추가하지 마십시오.\n항상 신뢰할 수 있는 출처를 통해 토큰 주소를 확인하세요!", + "add_token_disclaimer_check": "신뢰할 수 있는 출처를 통해 토큰 계약 주소와 정보를 확인했습니다. 악의적이거나 잘못된 정보를 추가하면 자금 손실이 발생할 수 있습니다.", + "add_token_warning": "사기꾼의 지시에 따라 토큰을 편집하거나 추가하지 마십시오.\n항상 신뢰할 수 있는 출처를 통해 토큰 주소를 확인하십시오!", "add_value": "값 추가", "added_message_for_ata_error": "거래를 완료하기에 충분한 SOL 잔액이 있는지 확인하십시오.", "address": "주소", "address_book": "주소록", "address_book_menu": "주소록", - "address_detected": "주소 감지", - "address_from_domain": "이 주소는 ${domain} 의 주소입니다 Unstoppable Domains", - "address_from_yat": "이 주소는 ${emoji} 의 주소입니다 Yat", - "address_label": "Address label", - "address_remove_contact": "연락처 삭제", - "address_remove_content": "선택한 연락처를 삭제 하시겠습니까?", - "addresses": "구애", + "address_detected": "주소가 감지되었습니다", + "address_from_domain": "이 주소는 Unstoppable Domains의 ${domain}에서 온 것입니다", + "address_from_yat": "이 주소는 Yat의 ${emoji}에서 온 것입니다", + "address_label": "주소 라벨", + "address_remove_contact": "연락처 제거", + "address_remove_content": "선택한 연락처를 정말로 제거하시겠습니까?", + "addresses": "주소", "advanced_settings": "고급 설정", - "aggressive": "지나치게 열심인", - "agree": "동의하다", - "agree_and_continue": "동의 및 계속", - "agree_to": "계정을 생성하면 ", - "alert_notice": "알아채다", - "all": "모든", - "all_coins": "모든 동전", - "all_trades": "A모든 거래", - "all_transactions": "모든 거래 창구", + "aggressive": "적극적", + "agree": "동의", + "agree_and_continue": "동의하고 계속하기", + "agree_to": "계정을 생성함으로써 다음에 동의합니다 ", + "alert_notice": "알림", + "all": "전체", + "all_coins": "모든 코인", + "all_trades": "모든 거래", + "all_transactions": "모든 트랜잭션", "alphabetical": "알파벳순", - "already_have_account": "이미 계정이 있습니까?", - "always": "언제나", - "amount": "양: ", - "amount_is_below_minimum_limit": "수수료 후 잔액은 Exchange (${min})에 필요한 최소 금액보다 적습니다.", - "amount_is_estimate": "수신 금액은 견적입니다", - "amount_is_guaranteed": "수령 금액이 보장됩니다.", - "and": "그리고", - "anonpay_description": "${type} 생성. 수신자는 지원되는 모든 암호화폐로 ${method}할 수 있으며 이 지갑에서 자금을 받게 됩니다.", + "already_have_account": "이미 계정이 있으신가요?", + "always": "항상", + "amount": "금액: ", + "amount_is_below_minimum_limit": "수수료 차감 후 잔액이 교환에 필요한 최소 금액(${min})보다 적습니다", + "amount_is_estimate": "수령 금액은 예상치입니다", + "amount_is_guaranteed": "수령 금액은 보장됩니다", + "and": "및", + "anonpay_description": "${type} 생성. 수신자는 지원되는 모든 암호화폐로 ${method}할 수 있으며, 귀하는 이 지갑으로 자금을 받게 됩니다.", "apk_update": "APK 업데이트", - "approve": "승인하다", - "approve_request": "요청을 승인하십시오", + "approve": "승인", + "approve_request": "요청 승인", "arrive_in_this_address": "${currency} ${tag}이(가) 이 주소로 도착합니다", "ascending": "오름차순", - "ask_each_time": "매번 물어보십시오", - "auth_store_ban_timeout": "타임 아웃 금지", - "auth_store_banned_for": "금지", - "auth_store_banned_minutes": " 의사록", + "ask_each_time": "매번 묻기", + "auth_store_ban_timeout": "차단_시간 초과", + "auth_store_banned_for": "다음 시간 동안 차단됨 ", + "auth_store_banned_minutes": " 분", "auth_store_incorrect_password": "잘못된 PIN", - "authenticated": "인증", - "authentication": "입증", - "auto_generate_addresses": "자동 생성 주소", - "auto_generate_subaddresses": "자동 생성 서브 아드 드레스", - "automatic": "자동적 인", - "available_balance": "사용 가능한 잔액", - "available_balance_description": "이 지갑에서 사용할 수 있는 잔액입니다. 이 잔액은 블록체인에서 가져온 것이며, Cake Wallet이 사용할 수 없습니다.", - "avg_savings": "평균 절감액", - "awaitDAppProcessing": "dApp이 처리를 마칠 때까지 기다려주세요.", + "authenticated": "인증됨", + "authentication": "인증", + "auto_generate_addresses": "주소 자동 생성", + "auto_generate_subaddresses": "하위 주소 자동 생성", + "automatic": "자동", + "available_balance": "사용 가능 잔액", + "available_balance_description": "“사용 가능 잔액” 또는 “확정 잔액”은 즉시 사용할 수 있는 자금입니다. 자금이 아래 잔액에는 표시되지만 위 잔액에는 표시되지 않는 경우, 들어오는 자금이 더 많은 네트워크 확인을 받을 때까지 몇 분 정도 기다려야 합니다. 더 많은 확인을 받은 후 사용할 수 있게 됩니다.", + "avg_savings": "평균 절약액", + "awaitDAppProcessing": "dApp이 처리를 완료할 때까지 기다려 주십시오.", "awaiting_payment_confirmation": "결제 확인 대기 중", - "background_sync": "배경 동기화", + "background_sync": "백그라운드 동기화", "background_sync_mode": "백그라운드 동기화 모드", - "background_sync_on_battery_low": "낮은 배터리에서 동기화하십시오", - "background_sync_on_charging": "충전 할 때만 동기화하십시오", - "background_sync_on_device_idle": "장치를 사용하지 않을 때만 동기화하십시오", - "background_sync_on_unmetered_network": "충족되지 않은 네트워크가 필요합니다", - "backup": "지원", + "background_sync_on_battery_low": "배터리 부족 시 동기화", + "background_sync_on_charging": "충전 중일 때만 동기화", + "background_sync_on_device_idle": "기기를 사용하지 않을 때만 동기화", + "background_sync_on_unmetered_network": "무제한 네트워크 필요", + "backup": "백업", "backup_file": "백업 파일", "backup_password": "백업 비밀번호", - "balance": "균형", + "balance": "잔액", "balance_page": "잔액 페이지", "bill_amount": "청구 금액", - "billing_address_info": "청구서 수신 주소를 묻는 메시지가 표시되면 배송 주소를 입력하세요.", - "biometric_auth_reason": "지문을 스캔하여 인증", + "billing_address_info": "청구지 주소를 묻는 경우 배송 주소를 제공하세요", + "biometric_auth_reason": "인증하려면 지문을 스캔하세요", "bitcoin_dark_theme": "비트코인 다크 테마", "bitcoin_light_theme": "비트코인 라이트 테마", - "bitcoin_payments_require_1_confirmation": "비트코인 결제는 1번의 확인이 필요하며 20분 이상이 소요될 수 있습니다. 기다려 주셔서 감사합니다! 결제가 확인되면 이메일이 전송됩니다.", + "bitcoin_payments_require_1_confirmation": "비트코인 결제는 1번의 확인이 필요하며, 이는 20분 이상 소요될 수 있습니다. 기다려 주셔서 감사합니다! 결제가 확인되면 이메일로 알려드립니다.", "block_height": "블록 높이", - "block_remaining": "남은 블록 1 개", - "Blocks_remaining": "${status} 남은 블록", + "block_remaining": "1 블록 남음", + "Blocks_remaining": "${status} 블록 남음", "bluetooth": "블루투스", - "bright_theme": "선명한", - "bump_fee": "범프 요금", - "buy": "구입", - "buy_alert_content": "현재 Bitcoin, Ethereum, Litecoin 및 Monero 구매만 지원합니다. Bitcoin, Ethereum, Litecoin 또는 Monero 지갑을 생성하거나 전환하십시오.", - "buy_bitcoin": "비트 코인 구매", - "buy_now": "지금 구매하십시오", - "buy_provider_unavailable": "제공자는 현재 사용할 수 없습니다.", - "buy_sell_pair_is_not_supported_warning": "이 통화 쌍은 선택한 결제 방법을 제공하는 모든 공급자가 지원하지 않습니다. 다른 쌍을 선택하거나 결제 방법을 변경하십시오.", - "buy_with": "구매", - "by_cake_pay": "Cake Pay로", - "cake_2fa_preset": "케이크 2FA 프리셋", - "cake_dark_theme": "케이크 다크 테마", - "cake_pay_account_note": "이메일 주소로 가입하면 카드를 보고 구매할 수 있습니다. 일부는 할인된 가격으로 사용 가능합니다!", + "bright_theme": "밝은 테마", + "bump_fee": "수수료 인상", + "buy": "구매", + "buy_alert_content": "현재 비트코인, 이더리움, 라이트코인, 모네로만 구매를 지원합니다. 비트코인, 이더리움, 라이트코인 또는 모네로 지갑을 생성하거나 전환하세요.", + "buy_bitcoin": "비트코인 구매", + "buy_now": "지금 구매", + "buy_provider_unavailable": "제공 업체를 현재 사용할 수 없습니다.", + "buy_sell_pair_is_not_supported_warning": "선택한 결제 방법으로는 이 통화 쌍이 어떤 제공 업체에서도 지원되지 않습니다. 다른 쌍을 선택하거나 결제 방법을 변경해 보세요.", + "buy_with": "다음으로 구매", + "by_cake_pay": "Cake Pay 제공", + "cake_2fa_preset": "Cake 2FA 사전 설정", + "cake_dark_theme": "Cake 다크 테마", + "cake_pay_account_note": "이메일 주소만으로 가입하여 카드를 확인하고 구매하세요. 일부는 할인된 가격으로도 이용 가능합니다!", "cake_pay_learn_more": "앱에서 즉시 기프트 카드를 구매하고 사용하세요!\n자세히 알아보려면 왼쪽에서 오른쪽으로 스와이프하세요.", - "cake_pay_save_order": "카드는 영업일 1 일 이내에 이메일로 전자 메일로 보내야합니다. \n 주문 ID 저장 :", - "cake_pay_subtitle": "전세계 선불 카드와 기프트 카드를 구입하십시오", + "cake_pay_save_order": "카드는 영업일 기준 1일 이내에 이메일로 발송됩니다.\n주문 ID를 저장하세요:", + "cake_pay_subtitle": "전 세계 선불 카드 및 기프트 카드 구매", "cake_pay_web_cards_subtitle": "전 세계 선불 카드 및 기프트 카드 구매", - "cake_pay_web_cards_title": "케이크페이 웹카드", - "cake_seeds_save_disclaimer": "이 단어를 안전한 장소에 저장하십시오! 새 장치에서 지갑을 복원하려면이 단어가 필요합니다.", + "cake_pay_web_cards_title": "Cake Pay 웹 카드", + "cake_seeds_save_disclaimer": "이 단어들을 안전한 곳에 저장하세요! 새 기기에서 지갑을 복구하려면 이 단어들이 필요합니다.", "cake_wallet": "Cake Wallet", - "cakepay_confirm_no_vpn": "프록시 나 VPN을 사용하지 않는지 확인합니다.", + "cakepay_confirm_no_vpn": "프록시 또는 VPN을 사용하고 있지 않음을 확인합니다", "cakepay_confirm_purchase": "구매 확인", - "cakepay_confirm_terms_agreed": "여기에 제시된 이용 약관에 동의합니다.", - "cakepay_confirm_voided_refund": "제한된 국가의 상환 시도는 환불을 무효화한다는 것을 이해합니다.", - "cakepay_ios_not_available": "죄송합니다.이 기프트 카드는 iOS에서 사용할 수 없습니다. Android 또는 웹 사이트를 통해 구매할 수 있습니다.", + "cakepay_confirm_terms_agreed": "여기에 제시된 이용 약관에 동의합니다:", + "cakepay_confirm_voided_refund": "제한된 국가에서의 사용 시도는 환불이 무효화됨을 이해합니다", + "cakepay_ios_not_available": "죄송합니다. 이 기프트 카드는 iOS에서 사용할 수 없습니다. 대신 Android나 저희 웹사이트를 통해 구매할 수 있습니다.", "cakepay_prepaid_card": "CakePay 선불 직불 카드", - "camera_consent": "귀하의 카메라는 ${provider}의 식별 목적으로 이미지를 캡처하는 데 사용됩니다. 자세한 내용은 해당 개인정보 보호정책을 확인하세요.", + "camera_consent": "${provider}에서 신원 확인 목적으로 이미지를 캡처하기 위해 카메라를 사용합니다. 자세한 내용은 해당 업체의 개인정보 보호정책을 확인하세요.", "camera_permission_is_required": "카메라 권한이 필요합니다.\n앱 설정에서 활성화해 주세요.", "cancel": "취소", - "cannot_verify": "확인할 수 없습니다", - "cannot_verify_description": "이 도메인은 확인할 수 없습니다. 승인하기 전에 요청을주의 깊게 확인하십시오.", + "cannot_verify": "확인할 수 없음", + "cannot_verify_description": "이 도메인은 확인할 수 없습니다. 승인하기 전에 요청을 주의 깊게 확인하세요.", "card_address": "주소:", - "cardholder_agreement": "카드 소유자 계약", + "cardholder_agreement": "카드 소지자 계약", "cards": "카드", "chain_id": "체인 ID", - "chains": "쇠사슬", - "change": "변화", - "change_backup_password_alert": "이전 백업 파일은 새 백업 암호로 가져올 수 없습니다. 새 백업 암호는 새 백업 파일에만 사용됩니다. 백업 비밀번호를 변경 하시겠습니까?", + "chains": "체인", + "change": "변경", + "change_backup_password_alert": "이전 백업 파일은 새 백업 비밀번호로 가져올 수 없습니다. 새 백업 비밀번호는 새 백업 파일에만 사용됩니다. 백업 비밀번호를 변경하시겠습니까?", "change_currency": "통화 변경", - "change_current_node": "현재 노드를 다음으로 변경 하시겠습니까 ${node}?", + "change_current_node": "현재 노드를 ${node}(으)로 변경하시겠습니까?", "change_current_node_title": "현재 노드 변경", - "change_exchange_provider": "스왑 제공 업체를 변경하십시오", + "change_exchange_provider": "스왑 제공 업체 변경", "change_language": "언어 변경", - "change_language_to": "언어를로 변경 ${language}?", + "change_language_to": "언어를 ${language}(으)로 변경하시겠습니까?", "change_password": "비밀번호 변경", - "change_rep": "대표를 변경하십시오", - "change_rep_message": "대표를 바꾸고 싶습니까?", - "change_rep_successful": "대리인이 성공적으로 변경되었습니다", - "change_selected_exchanges": "선택된 거래소 변경", - "change_selected_pair": "선택한 쌍을 변경하십시오", - "change_wallet_alert_content": "현재 지갑을 다음으로 변경 하시겠습니까 ${wallet_name}?", + "change_rep": "대표자 변경", + "change_rep_message": "정말로 대표자를 변경하시겠습니까?", + "change_rep_successful": "대표자가 성공적으로 변경되었습니다", + "change_selected_exchanges": "선택한 교환소 변경", + "change_selected_pair": "선택한 쌍 변경", + "change_wallet_alert_content": "현재 지갑을 ${wallet_name}(으)로 변경하시겠습니까?", "change_wallet_alert_title": "현재 지갑 변경", - "choose_a_payment_method": "결제 방법을 선택하십시오", - "choose_a_provider": "제공자를 선택하십시오", - "choose_account": "계정을 선택하십시오", - "choose_address": "\n\n주소를 선택하십시오:", - "choose_card_value": "카드 값을 선택하십시오", - "choose_derivation": "지갑 파생을 선택하십시오", - "choose_from_available_options": "사용 가능한 옵션에서 선택:", + "choose_a_payment_method": "결제 방법 선택", + "choose_a_provider": "제공 업체 선택", + "choose_account": "계정 선택", + "choose_address": "\n\n주소를 선택하세요:", + "choose_card_value": "카드 금액 선택", + "choose_derivation": "지갑 파생 경로 선택", + "choose_from_available_options": "사용 가능한 옵션 중에서 선택하세요:", "choose_one": "하나 선택", - "choose_relay": "사용할 릴레이를 선택해주세요", - "choose_wallet_currency": "지갑 통화를 선택하십시오:", - "choose_wallet_group": "지갑 그룹을 선택하십시오", - "clear": "명확한", + "choose_relay": "사용할 릴레이를 선택하세요", + "choose_wallet_currency": "지갑 통화를 선택하세요:", + "choose_wallet_group": "지갑 그룹 선택", + "clear": "지우기", "clearnet_link": "클리어넷 링크", - "close": "닫다", - "coin_control": "코인 제어 (옵션)", - "cold_or_recover_wallet": "Cupcake 또는 차가운 지갑에서 읽기 전용 지갑을 추가하거나 종이 지갑을 복구하십시오.", - "collection_address": "수집 주소", + "close": "닫기", + "coin_control": "코인 제어 (선택 사항)", + "cold_or_recover_wallet": "Cupcake의 읽기 전용 지갑 또는 콜드 월렛을 추가하거나 페이퍼 월렛을 복구합니다", + "collection_address": "컬렉션 주소", "collection_description": "컬렉션 설명", - "collection_name": "수집 이름", + "collection_name": "컬렉션 이름", "color_theme": "색상 테마", - "commit_transaction_amount_fee": "커밋 거래\n양: ${amount}\n보수: ${fee}", + "commit_transaction_amount_fee": "트랜잭션 커밋\n금액: ${amount}\n수수료: ${fee}", "confirm": "확인", - "confirm_delete_template": "이 작업은이 템플릿을 삭제합니다. 계속 하시겠습니까?", - "confirm_delete_wallet": "이 작업은이 지갑을 삭제합니다. 계속 하시겠습니까?", - "confirm_fee_deduction": "수수료 공제를 확인하십시오", - "confirm_fee_deduction_content": "출력에서 수수료를 공제하는 데 동의하십니까?", - "confirm_passphrase": "암호를 확인하십시오", + "confirm_delete_template": "이 작업은 이 템플릿을 삭제합니다. 계속하시겠습니까?", + "confirm_delete_wallet": "이 작업은 이 지갑을 삭제합니다. 계속하시겠습니까?", + "confirm_fee_deduction": "수수료 차감 확인", + "confirm_fee_deduction_content": "출금액에서 수수료를 차감하는 데 동의하십니까?", + "confirm_passphrase": "암호 구문 확인", "confirm_sending": "전송 확인", - "confirm_silent_payments_switch_node": "현재 노드는 무음 지불을 지원하지 않습니다 \\ ncake 지갑은 스캔을 위해 호환 가능한 노드로 전환됩니다.", - "confirm_transaction": "거래 확인", + "confirm_silent_payments_switch_node": "현재 노드는 사일런트 페이먼트를 지원하지 않습니다.\\n\\nCake Wallet은 스캔하는 동안 호환되는 노드로 전환됩니다.", + "confirm_transaction": "트랜잭션 확인", "confirmations": "확인", - "confirmed": "확인된 잔액", - "confirmed_tx": "확인", + "confirmed": "확정 잔액", + "confirmed_tx": "확정됨", "congratulations": "축하합니다!", "connect_an_existing_yat": "기존 Yat 연결", - "connect_yats": "야츠 연결", - "connect_your_hardware_wallet": "Bluetooth 또는 USB를 사용하여 하드웨어 지갑을 연결하십시오", - "connect_your_hardware_wallet_ios": "Bluetooth를 사용하여 하드웨어 지갑을 연결하십시오", - "connected": "연결", + "connect_yats": "Yat 연결", + "connect_your_hardware_wallet": "블루투스 또는 USB를 사용하여 하드웨어 지갑 연결", + "connect_your_hardware_wallet_ios": "블루투스를 사용하여 하드웨어 지갑 연결", + "connected": "연결됨", "connection_sync": "연결 및 동기화", - "connectWalletPrompt": "거래를 하려면 WalletConnect에 지갑을 연결하세요.", - "contact": "접촉", - "contact_list_contacts": "콘택트 렌즈", + "connectWalletPrompt": "거래를 하려면 WalletConnect로 지갑을 연결하세요", + "contact": "연락처", + "contact_list_contacts": "연락처", "contact_list_wallets": "내 지갑", - "contact_name": "담당자 이름", - "contact_name_exists": "해당 이름을 가진 연락처가 이미 존재합니다. 다른 이름을 선택하세요.", - "contact_support": "지원팀에 문의", - "continue_text": "잇다", - "contract_warning": "이 계약 주소는 잠재적으로 사기성으로 표시되었습니다. 주의해서 계속 진행하십시오.", - "contractName": "계약명", - "contractSymbol": "계약 기호", - "copied_key_to_clipboard": "복사 ${key} 클립 보드로", - "copied_to_clipboard": "클립 보드에 복사", - "copy": "부", + "contact_name": "연락처 이름", + "contact_name_exists": "해당 이름의 연락처가 이미 존재합니다. 다른 이름을 선택하십시오.", + "contact_support": "고객 지원 문의", + "continue_text": "계속", + "contract_warning": "이 계약 주소는 잠재적으로 사기일 수 있다는 플래그가 지정되었습니다. 주의하여 진행하십시오.", + "contractName": "계약 이름", + "contractSymbol": "계약 심볼", + "copied_key_to_clipboard": "${key} 클립보드에 복사됨", + "copied_to_clipboard": "클립보드에 복사됨", + "copy": "복사", "copy_address": "주소 복사", - "copy_id": "부 ID", - "copyWalletConnectLink": "dApp에서 WalletConnect 링크를 복사하여 여기에 붙여넣으세요.", - "corrupted_seed_notice": "이 지갑의 파일은 손상되어 열 수 없습니다. 씨앗 문구를보고 저장하고 지갑을 복원하십시오.\n\n값이 비어 있으면 씨앗을 올바르게 회수 할 수 없었습니다.", + "copy_id": "ID 복사", + "copyWalletConnectLink": "dApp에서 WalletConnect 링크를 복사하여 여기에 붙여넣으세요", + "corrupted_seed_notice": "이 지갑의 파일이 손상되어 열 수 없습니다. 시드 구문을 보고 저장한 다음 지갑을 복구하세요.\n\n값이 비어 있으면 시드를 올바르게 복구할 수 없었습니다.", "countries": "국가", - "create_account": "계정 만들기", + "create_account": "계정 생성", "create_backup": "백업 생성", - "create_donation_link": "기부 링크 만들기", + "create_donation_link": "기부 링크 생성", "create_invoice": "인보이스 생성", - "create_new": "새 월렛 만들기", - "create_new_account": "새 계정을 만들", - "create_new_seed": "새 씨앗을 만듭니다", - "creating_new_wallet": "새 지갑 생성", + "create_new": "새 지갑 생성", + "create_new_account": "새 계정 생성", + "create_new_seed": "새 시드 생성", + "creating_new_wallet": "새 지갑 생성 중", "creating_new_wallet_error": "오류: ${description}", - "creation_date": "생산 일", - "custom": "커스텀", - "custom_drag": "사용자 정의 (홀드 앤 드래그)", - "custom_redeem_amount": "사용자 지정 상환 금액", - "custom_value": "맞춤 가치", - "dark_theme": "어두운", + "creation_date": "생성 날짜", + "custom": "사용자 지정", + "custom_drag": "사용자 지정 (길게 누르고 드래그)", + "custom_redeem_amount": "사용자 지정 사용 금액", + "custom_value": "사용자 지정 값", + "dark_theme": "다크 테마", "debit_card": "직불 카드", - "debit_card_terms": "이 디지털 지갑에 있는 귀하의 지불 카드 번호(및 귀하의 지불 카드 번호에 해당하는 자격 증명)의 저장 및 사용은 부터 발효되는 지불 카드 발행자와의 해당 카드 소지자 계약의 이용 약관을 따릅니다. 수시로.", - "decimal_places_error": "소수점 이하 자릿수가 너무 많습니다.", - "decimals_cannot_be_zero": "토큰 소수점은 0이 될 수 없습니다.", - "decred_info_card_details": "Decred는 정상적인 비트 코인 지갑보다 더 오래 걸리는 \"SPV\"로 알려진 분산 및 개인 정보 보호 동기화 방법을 사용합니다. 자세한 내용은 아래를 누릅니다.", - "decred_info_title": "Decred의 동기화", - "default_buy_provider": "기본 구매 제공자", - "default_sell_provider": "기본 판매 공급자", - "delete": "지우다", + "debit_card_terms": "이 디지털 지갑에 결제 카드 번호(및 결제 카드 번호에 해당하는 자격 증명)를 저장하고 사용하는 것은 해당 카드 발급사와의 카드 소지자 계약 이용 약관(수시로 효력 발생)의 적용을 받습니다.", + "decimal_places_error": "소수 자릿수가 너무 많습니다", + "decimals_cannot_be_zero": "토큰 소수 자릿수는 0이 될 수 없습니다.", + "decred_info_card_details": "Decred는 “SPV”로 알려진 분산형 및 개인 정보 보호 동기화 방법을 사용하며, 이는 일반 비트코인 지갑보다 오래 걸립니다. 자세히 알아보려면 아래를 탭하세요.", + "decred_info_title": "Decred 동기화", + "default_buy_provider": "기본 구매 제공 업체", + "default_sell_provider": "기본 판매 제공 업체", + "delete": "삭제", "delete_account": "계정 삭제", "delete_wallet": "지갑 삭제", - "delete_wallet_confirm_message": "${wallet_name} 지갑을 삭제하시겠습니까?", - "deleteConnectionConfirmationPrompt": "다음 연결을 삭제하시겠습니까?", - "denominations": "교파", + "delete_wallet_confirm_message": "${wallet_name} 지갑을 정말로 삭제하시겠습니까?", + "deleteConnectionConfirmationPrompt": "연결을 삭제하시겠습니까?", + "denominations": "액면가", "derivationpath": "파생 경로", "descending": "내림차순", "description": "설명", "destination_tag": "목적지 태그:", - "dfx_option_description": "EUR & CHF로 암호화를 구입하십시오. 유럽의 소매 및 기업 고객을 위해", - "didnt_get_code": "코드를 받지 못하셨습니까?", - "digit_pin": "숫자 PIN", - "digital_and_physical_card": " 디지털 및 실제 선불 직불 카드", - "disable": "장애를 입히다", - "disable_bulletin": "서비스 상태 게시판을 비활성화합니다", - "disable_buy": "구매 행동 비활성화", - "disable_cake_2fa": "케이크 2FA 비활성화", + "dfx_option_description": "EUR 및 CHF로 암호화폐 구매. 유럽의 개인 및 기업 고객 대상", + "didnt_get_code": "코드를 받지 못했나요?", + "digit_pin": "자리 PIN", + "digital_and_physical_card": " 디지털 및 실물 선불 직불 카드", + "disable": "비활성화", + "disable_bulletin": "서비스 상태 게시판 비활성화", + "disable_buy": "구매 기능 비활성화", + "disable_cake_2fa": "Cake 2FA 비활성화", "disable_exchange": "교환 비활성화", - "disable_exchange_option": "교환 옵션을 비활성화합니다", - "disable_fee_api_warning": "이것을 끄면 경우에 따라 수수료가 부정확 할 수 있으므로 거래 수수료를 초과 지불하거나 지불 할 수 있습니다.", + "disable_exchange_option": "교환 옵션 비활성화", + "disable_fee_api_warning": "이 기능을 끄면 경우에 따라 수수료율이 정확하지 않을 수 있으며, 트랜잭션 수수료를 과다 또는 과소 지불할 수 있습니다.", "disable_fiat": "법정화폐 비활성화", - "disable_sell": "판매 조치 비활성화", + "disable_sell": "판매 기능 비활성화", "disable_trade_option": "거래 옵션 비활성화", - "disableBatteryOptimization": "배터리 최적화를 비활성화합니다", - "disableBatteryOptimizationDescription": "백그라운드 동기화를보다 자유롭고 매끄럽게 실행하기 위해 배터리 최적화를 비활성화하고 싶습니까?", - "disabled": "장애가 있는", - "disconnect_session": "분리 세션", + "disableBatteryOptimization": "배터리 최적화 비활성화", + "disableBatteryOptimizationDescription": "백그라운드 동기화가 더 자유롭고 원활하게 실행되도록 배터리 최적화를 비활성화하시겠습니까?", + "disabled": "비활성화됨", + "disconnect_session": "세션 연결 해제", "discount": "${value}% 절약", - "display_settings": "디스플레이 설정", + "display_settings": "표시 설정", "displayable": "표시 가능", - "do_not_have_enough_gas_asset": "현재 블록체인 네트워크 조건으로 거래를 하기에는 ${currency}이(가) 충분하지 않습니다. 다른 자산을 보내더라도 블록체인 네트워크 수수료를 지불하려면 ${currency}가 더 필요합니다.", - "do_not_send": "보내지 마세요", - "do_not_share_warning_text": "지원을 포함하여 다른 사람과 이러한 정보를 공유하지 마십시오.\n\n귀하의 자금은 도난당할 수 있고 도난당할 수 있습니다!", - "do_not_show_me": "나를 다시 표시하지 않음", + "do_not_have_enough_gas_asset": "현재 블록체인 네트워크 상태에서 트랜잭션을 생성하기에 ${currency}이(가) 충분하지 않습니다. 다른 자산을 보내는 경우에도 블록체인 네트워크 수수료를 지불하기 위해 더 많은 ${currency}이(가) 필요합니다.", + "do_not_send": "보내지 않음", + "do_not_share_warning_text": "지원을 포함하여 다른 누구와도 공유하지 마십시오.\n\n자금이 도난당할 수 있습니다!", + "do_not_show_me": "다시 표시하지 않음", "domain_looks_up": "도메인 조회", "domain_mismatch": "도메인 불일치", - "domain_mismatch_description": "이 웹 사이트에는이 요청의 발신자와 일치하지 않는 도메인이 있습니다. 승인은 자금 손실로 이어질 수 있습니다.", - "donation_link_details": "기부 링크 세부정보", + "domain_mismatch_description": "이 웹사이트의 도메인이 이 요청의 발신자와 일치하지 않습니다. 승인하면 자금 손실로 이어질 수 있습니다.", + "donation_link_details": "기부 링크 세부 정보", "e_sign_consent": "전자 서명 동의", - "edit": "편집하다", - "edit_backup_password": "편집 백업 암호", + "edit": "편집", + "edit_backup_password": "백업 비밀번호 편집", "edit_node": "노드 편집", "edit_token": "토큰 편집", - "electrum_address_disclaimer": "사용할 때마다 새 주소가 생성되지만 이전 주소는 계속 작동합니다.", + "electrum_address_disclaimer": "사용할 때마다 새 주소를 생성하지만 이전 주소도 계속 작동합니다", "email_address": "이메일 주소", - "enable": "할 수 있게 하다", - "enable_mempool_api": "정확한 수수료 및 날짜에 대한 Mempool API", - "enable_replace_by_fee": "대체별로 활성화하십시오", - "enable_silent_payments_scanning": "팁에 도달 할 때까지 사일런트 지불을 스캔하기 시작합니다.", - "enabled": "사용", + "enable": "활성화", + "enable_mempool_api": "정확한 수수료 및 날짜를 위한 Mempool API", + "enable_replace_by_fee": "RBF(Replace-By-Fee) 활성화", + "enable_silent_payments_scanning": "사일런트 페이먼트 주소로 전송된 트랜잭션 스캔 시작", + "enabled": "활성화됨", "enter_amount": "금액 입력", - "enter_backup_password": "여기에 백업 비밀번호를 입력하세요.", + "enter_backup_password": "여기에 백업 비밀번호 입력", "enter_code": "코드 입력", - "enter_seed_phrase": "시드 문구를 입력하십시오", + "enter_seed_phrase": "시드 구문 입력", "enter_totp_code": "TOTP 코드를 입력하세요.", - "enter_wallet_password": "지갑 암호를 입력하십시오", + "enter_wallet_password": "지갑 비밀번호 입력", "enter_your_note": "메모를 입력하세요…", - "enter_your_pin": "PIN을 입력하십시오", - "enter_your_pin_again": "다시 핀을 입력", - "enterTokenID": "토큰 ID를 입력하세요", - "enterWalletConnectURI": "WalletConnect URI를 입력하세요.", + "enter_your_pin": "PIN 입력", + "enter_your_pin_again": "PIN 다시 입력", + "enterTokenID": "토큰 ID 입력", + "enterWalletConnectURI": "WalletConnect URI 입력", "error": "오류", - "error_dialog_content": "죄송합니다. 오류가 발생했습니다.\n\n오류 보고서를 지원 팀에 보내 응용 프로그램을 개선하십시오.", - "error_text_account_name": "계정 이름은 문자, 숫자 만 포함 할 수 있습니다\n1 ~ 15 자 사이 여야합니다", - "error_text_address": "지갑 주소는 유형과 일치해야합니다\n암호 화폐", - "error_text_amount": "금액은 숫자 만 포함 할 수 있습니다", - "error_text_contact_name": "담당자 이름은 포함 할 수 없습니다 ` , ' \" 기호\n1 자에서 32 자 사이 여야합니다", - "error_text_crypto_currency": "소수 자릿수\n12 이하 여야합니다", - "error_text_fiat": "금액은 사용 가능한 잔액을 초과 할 수 없습니다.\n소수 자릿수는 2보다 작거나 같아야합니다", - "error_text_input_above_maximum_limit": "금액이 최대 값보다 많습니다.", - "error_text_input_below_minimum_limit": "금액이 최소보다 적습니다.", - "error_text_keys": "지갑 키는 16 진수로 64 자만 포함 할 수 있습니다", - "error_text_limits_loading_failed": "거래 ${provider} 가 생성되지 않습니다. 로딩 실패", - "error_text_maximum_limit": "거래 ${provider} 가 생성되지 않습니다. 금액이 최대 값보다 많습니다. ${max} ${currency}", - "error_text_minimal_limit": "거래 ${provider} 가 생성되지 않습니다. 금액이 최소보다 적습니다. ${min} ${currency}", - "error_text_node_address": "iPv4 주소를 입력하십시오", - "error_text_node_port": "노드 포트는 0에서 65535 사이의 숫자 만 포함 할 수 있습니다", - "error_text_node_proxy_address": ":<포트>를 입력하십시오(예: 127.0.0.1:9050).", - "error_text_payment_id": "지불 ID는 16 ~ 64 자의 16 진 문자 만 포함 할 수 있습니다", - "error_text_subaddress_name": "하위 주소 이름은 포함 할 수 없습니다 ` , ' \" 기호 \n1 ~ 20 자 사이 여야합니다", - "error_text_template": "템플릿 이름과 주소는 포함 할 수 없습니다 ` , ' \" 기호 \n1 ~ 106 자 사이 여야합니다", - "error_text_wallet_name": "지갑 이름은 문자, 숫자, _ - 기호만 포함할 수 있습니다.\n1~33자 사이여야 합니다.", - "error_text_xmr": "XMR 값은 사용 가능한 잔액을 초과 할 수 없습니다.\n소수 자릿수는 12 이하 여야합니다", - "error_while_processing": "전환하는 동안 오류가 발생했습니다", - "errorGettingCredentials": "실패: 자격 증명을 가져오는 중 오류가 발생했습니다.", - "errorSigningTransaction": "거래에 서명하는 동안 오류가 발생했습니다.", + "error_dialog_content": "죄송합니다. 오류가 발생했습니다.\n\n애플리케이션 개선을 위해 오류 보고서를 지원팀에 보내주세요.", + "error_text_account_name": "계정 이름은 문자, 숫자만 포함할 수 있으며\n1~15자 사이여야 합니다.", + "error_text_address": "지갑 주소는 암호화폐 유형과\n일치해야 합니다.", + "error_text_amount": "금액은 숫자만 포함할 수 있습니다.", + "error_text_contact_name": "연락처 이름에는 ` , ' \" 기호를 포함할 수 없으며\n1~32자 사이여야 합니다.", + "error_text_crypto_currency": "소수 자릿수는\n12자리 이하여야 합니다.", + "error_text_fiat": "금액 값이 사용 가능 잔액을 초과할 수 없습니다.\n소수 자릿수는 2자리 이하여야 합니다.", + "error_text_input_above_maximum_limit": "금액이 최대 한도를 초과합니다.", + "error_text_input_below_minimum_limit": "금액이 최소 한도 미만입니다.", + "error_text_keys": "지갑 키는 16진수로 64자만 포함할 수 있습니다.", + "error_text_limits_loading_failed": "${provider} 거래가 생성되지 않았습니다. 한도 로딩 실패", + "error_text_maximum_limit": "${provider} 거래가 생성되지 않았습니다. 금액이 최대값(${max} ${currency})보다 큽니다.", + "error_text_minimal_limit": "${provider} 거래가 생성되지 않았습니다. 금액이 최소값(${min} ${currency})보다 작습니다.", + "error_text_node_address": "IPv4 주소를 입력하세요.", + "error_text_node_port": "노드 포트는 0에서 65535 사이의 숫자만 포함할 수 있습니다.", + "error_text_node_proxy_address": ":<포트> 형식으로 입력하세요. 예: 127.0.0.1:9050", + "error_text_payment_id": "결제 ID는 16진수로 16~64자만 포함할 수 있습니다.", + "error_text_subaddress_name": "하위 주소 이름에는 ` , ' \" 기호를 포함할 수 없으며\n1~20자 사이여야 합니다.", + "error_text_template": "템플릿 이름과 주소에는 ` , ' \" 기호를 포함할 수 없으며\n1~106자 사이여야 합니다.", + "error_text_wallet_name": "지갑 이름은 문자, 숫자, _ - 기호만 포함할 수 있으며\n1~33자 사이여야 합니다.", + "error_text_xmr": "XMR 값이 사용 가능 잔액을 초과할 수 없습니다.\n소수 자릿수는 12자리 이하여야 합니다.", + "error_while_processing": "처리 중 오류가 발생했습니다", + "errorGettingCredentials": "실패: 자격 증명을 가져오는 중 오류 발생", + "errorSigningTransaction": "트랜잭션 서명 중 오류가 발생했습니다", "estimated": "예상", - "estimated_new_fee": "예상 새로운 수수료", - "estimated_receive_amount": "예상 수신 금액", - "etherscan_history": "이더스캔 역사", + "estimated_new_fee": "예상 새 수수료", + "estimated_receive_amount": "예상 수령 금액", + "etherscan_history": "Etherscan 내역", "event": "이벤트", "events": "이벤트", "exchange": "교환", - "exchange_incorrect_current_wallet_for_xmr": "케이크 지갑 Monero Balance에서 XMR을 교체하려면 먼저 Monero 지갑으로 전환하십시오.", + "exchange_incorrect_current_wallet_for_xmr": "Cake Wallet 모네로 잔액에서 XMR을 스왑하려면 먼저 모네로 지갑으로 전환하세요.", "exchange_new_template": "새 템플릿", - "exchange_provider_unsupported": "${providerName}은 더 이상 지원되지 않습니다!", - "exchange_result_confirm": "확인을 누르면 전송됩니다 ${fetchingLabel} ${from} 지갑에서 ${walletName} 아래 주소로. 또는 외부 지갑에서 아래 주소로 보낼 수 있습니다 / QR 코드로 보낼 수 있습니다.\n\n확인을 눌러 계속하거나 금액을 변경하려면 돌아가십시오.", - "exchange_result_description": "다음 페이지에 표시된 주소로 최소 ${fetchingLabel} ${from} 를 보내야합니다. ${fetchingLabel} ${from} 보다 적은 금액을 보내면 변환되지 않고 환불되지 않을 수 있습니다.", - "exchange_result_write_down_ID": "*위에 표시된 ID를 복사하거나 적어 두십시오.", - "exchange_result_write_down_trade_id": "계속하려면 거래 ID를 복사하거나 적어 두십시오..", - "exchange_sync_alert_content": "지갑이 동기화 될 때까지 기다리십시오", - "exchange_trade_result_confirm": "Cake 지갑에서 보내기를 누르면 ${walletName}이라는 지갑에서 아래에 표시된 주소로 ${fetchingLabel} ${from}을 보내는 것입니다. 또는 외부 지갑에서 외부 지갑 세부 정보 페이지에서 주소 / QR 코드로 보낼 수 있습니다.", - "expired": "만료", + "exchange_provider_unsupported": "${providerName}은(는) 더 이상 지원되지 않습니다!", + "exchange_result_confirm": "확인을 누르면 ${walletName} 지갑에서 아래 표시된 주소로 ${fetchingLabel} ${from}을(를) 보냅니다. 또는 외부 지갑에서 아래 주소/QR 코드로 보낼 수 있습니다.\n\n계속하려면 확인을 누르거나 뒤로 가서 금액을 변경하세요.", + "exchange_result_description": "다음 페이지에 표시된 주소로 최소 ${fetchingLabel} ${from}을(를) 보내야 합니다. ${fetchingLabel} ${from}보다 적은 금액을 보내면 전환되지 않거나 환불되지 않을 수 있습니다.", + "exchange_result_write_down_ID": "*위에 표시된 ID를 복사하거나 적어두세요.", + "exchange_result_write_down_trade_id": "계속하려면 거래 ID를 복사하거나 적어두세요.", + "exchange_sync_alert_content": "지갑이 동기화될 때까지 기다려 주세요.", + "exchange_trade_result_confirm": "Cake Wallet에서 보내기를 누르면 ${walletName} 지갑에서 아래 표시된 주소로 ${fetchingLabel} ${from}을(를) 보냅니다. 또는 외부 지갑에서 보내기 세부 정보 페이지의 주소/QR 코드로 보낼 수 있습니다.\n\n계속하려면 버튼 중 하나를 누르거나 뒤로 가서 금액을 변경하세요.", + "expired": "만료됨", "expires": "만료", - "expiresOn": "만료 날짜", - "expiry_and_validity": "만료와 타당성", + "expiresOn": "만료일", + "expiry_and_validity": "만료 및 유효성", "export_backup": "백업 내보내기", - "export_logs": "내보내기 로그", - "export_outputs": "내보내기 출력", - "extend_session": "세션을 확장하십시오", + "export_logs": "로그 내보내기", + "export_outputs": "출력 내보내기", + "extend_session": "세션 연장", "extra_id": "추가 ID:", - "extracted_address_content": "당신은에 자금을 보낼 것입니다\n${recipient_name}", + "extracted_address_content": "다음 주소로 자금을 보냅니다\n${recipient_name}", "failed_authentication": "인증 실패. ${state_error}", "faq": "FAQ", - "features": "특징", - "fee_less_than_min": "선택된 수수료는 최소값보다 적습니다. 거래를 보낼 수있는 수수료를 늘리십시오.", - "fee_rate": "수수료", - "fetching": "가져 오는 중", - "fiat_api": "명목 화폐 API", - "fiat_balance": "피아트 잔액", + "features": "기능", + "fee_less_than_min": "선택한 수수료가 최소값보다 작습니다. 트랜잭션을 보낼 수 있도록 수수료를 높여주세요.", + "fee_rate": "수수료율", + "fetching": "가져오는 중", + "fiat_api": "법정화폐 API", + "fiat_balance": "법정화폐 잔액", "field_required": "이 필드는 필수입니다", - "file_saved": "파일이 저장되었습니다", - "fill_code": "이메일에 제공된 인증 코드를 입력하세요.", - "filter_by": "필터링 기준", - "first_wallet_text": "Monero, Bitcoin, Ethereum, Litecoin 및 Haven을 위한 멋진 지갑", - "fixed_pair_not_supported": "이 고정 쌍은 선택한 스왑 서비스에서 지원되지 않습니다.", - "fixed_rate": "고정 비율", - "fixed_rate_alert": "고정 금리 모드 체크시 수취 금액 입력이 가능합니다. 고정 속도 모드로 전환 하시겠습니까?", + "file_saved": "파일 저장됨", + "fill_code": "이메일로 제공된 인증 코드를 입력하세요", + "filter_by": "필터 기준", + "first_wallet_text": "모네로, 비트코인, 이더리움, 라이트코인, 헤이븐을 위한 멋진 지갑", + "fixed_pair_not_supported": "이 고정 쌍은 선택한 스왑 서비스에서 지원되지 않습니다", + "fixed_rate": "고정 환율", + "fixed_rate_alert": "고정 환율 모드가 선택되면 수령 금액을 입력할 수 있습니다. 고정 환율 모드로 전환하시겠습니까?", "forgot_password": "비밀번호 찾기", - "freeze": "얼다", + "freeze": "동결", "frequently_asked_questions": "자주 묻는 질문", - "from": "에서", - "frozen": "겨울 왕국", - "frozen_balance": "냉동 균형", - "full_balance": "풀 밸런스", - "gas_exceeds_allowance": "거래에 필요한 가스는 수당을 초과합니다.", + "from": "보내는 통화", + "frozen": "동결됨", + "frozen_balance": "동결 잔액", + "full_balance": "전체 잔액", + "gas_exceeds_allowance": "트랜잭션에 필요한 가스가 허용 한도를 초과합니다.", "gas_price": "가스 가격", "generate_name": "이름 생성", "generating_gift_card": "기프트 카드 생성 중", - "generating_transaction": "거래 생성", - "get_a": "가져오기", - "get_card_note": " 디지털 통화로 충전할 수 있습니다. 추가 정보가 필요하지 않습니다!", - "get_your_yat": "당신의 Yat를 얻으십시오", + "generating_transaction": "트랜잭션 생성 중", + "get_a": "받기 ", + "get_card_note": " 디지털 통화로 재충전할 수 있습니다. 추가 정보는 필요하지 않습니다!", + "get_your_yat": "Yat 받기", "gift_card_amount": "기프트 카드 금액", - "gift_card_balance_note": "잔액이 남아 있는 기프트 카드가 여기에 표시됩니다.", + "gift_card_balance_note": "잔액이 남은 기프트 카드가 여기에 표시됩니다", "gift_card_is_generated": "기프트 카드가 생성되었습니다", "gift_card_number": "기프트 카드 번호", - "gift_card_redeemed_note": "사용한 기프트 카드가 여기에 표시됩니다.", + "gift_card_redeemed_note": "사용한 기프트 카드가 여기에 표시됩니다", "gift_cards": "기프트 카드", - "gift_cards_unavailable": "기프트 카드는 현재 Monero, Bitcoin 및 Litecoin을 통해서만 구매할 수 있습니다.", - "got_it": "알았다", + "gift_cards_unavailable": "기프트 카드는 현재 모네로, 비트코인, 라이트코인으로만 구매할 수 있습니다", + "got_it": "확인", "gross_balance": "총 잔액", - "group_by_type": "유형별 그룹", + "group_by_type": "유형별 그룹화", "haven_app": "Haven by Cake Wallet", - "haven_app_wallet_text": "Awesome wallet for Haven", - "help": "돕다", + "haven_app_wallet_text": "Haven을 위한 멋진 지갑", + "help": "도움말", "hidden_addresses": "숨겨진 주소", - "hidden_balance": "숨겨진 균형", - "hide": "숨다", + "hidden_balance": "숨겨진 잔액", + "hide": "숨기기", "hide_details": "세부 정보 숨기기", "high_contrast_theme": "고대비 테마", "home_screen_settings": "홈 화면 설정", - "how_to_use": "사용하는 방법", - "how_to_use_card": "이 카드를 사용하는 방법", + "how_to_use": "사용 방법", + "how_to_use_card": "이 카드 사용 방법", "id": "ID: ", - "if_you_dont_see_your_device": "위의 장치가 표시되지 않으면 원장이 깨어 있고 잠금 해제되었는지 확인하십시오!", - "ignor": "무시하다", - "import": "수입", + "if_you_dont_see_your_device": "위에 기기가 보이지 않으면 Ledger가 켜져 있고 잠금 해제되어 있는지 확인하세요!", + "ignor": "무시", + "import": "가져오기", "importNFTs": "NFT 가져오기", "in_store": "매장 내", - "incoming": "들어오는", - "incorrect_seed": "입력하신 텍스트가 유효하지 않습니다.", - "incorrect_seed_option": "잘못된. 다시 시도하십시오", - "incorrect_seed_option_back": "잘못된. 씨앗이 올바르게 저장되어 있는지 확인하고 다시 시도하십시오.", + "incoming": "수신", + "incorrect_seed": "입력한 텍스트가 유효하지 않습니다.", + "incorrect_seed_option": "잘못되었습니다. 다시 시도하세요.", + "incorrect_seed_option_back": "잘못되었습니다. 시드가 올바르게 저장되었는지 확인하고 다시 시도하세요.", "inputs": "입력", - "insufficient_funds_for_tx": "거래를 성공적으로 실행하기위한 자금이 충분하지 않습니다.", - "insufficient_lamport_for_tx": "거래 및 거래 수수료를 충당하기에 충분한 SOL이 없습니다. 지갑에 더 많은 솔을 추가하거나 보내는 솔을 줄입니다.", - "insufficient_lamports": "거래 및 거래 수수료를 충당하기에 충분한 SOL이 없습니다. 최소 ${solValueNeeded} sol이 필요합니다. 지갑에 더 많은 솔을 추가하거나 보내는 솔을 줄이십시오.", - "insufficientFundsForRentError": "거래 수수료와 계좌 임대료를 충당하기에 충분한 SOL이 없습니다. 지갑에 더 많은 솔을 추가하거나 보내는 솔을 줄이십시오.", - "introducing_cake_pay": "소개 Cake Pay!", + "insufficient_funds_for_tx": "트랜잭션을 성공적으로 실행하기에 자금이 부족합니다.", + "insufficient_lamport_for_tx": "트랜잭션 및 트랜잭션 수수료를 충당하기에 SOL이 부족합니다. 지갑에 SOL을 더 추가하거나 보내는 SOL 금액을 줄이세요.", + "insufficient_lamports": "트랜잭션 및 트랜잭션 수수료를 충당하기에 SOL이 부족합니다. 최소 ${solValueNeeded} SOL이 필요합니다. 지갑에 SOL을 더 추가하거나 보내는 SOL 금액을 줄이세요.", + "insufficientFundsForRentError": "계정의 트랜잭션 수수료 및 렌트를 충당하기에 SOL이 부족합니다. 지갑에 SOL을 더 추가하거나 보내는 SOL 금액을 줄이세요.", + "introducing_cake_pay": "Cake Pay를 소개합니다!", "invalid_input": "잘못된 입력", - "invalid_password": "유효하지 않은 비밀번호", - "invoice_details": "인보이스 세부정보", - "is_percentage": "이다", + "invalid_password": "잘못된 비밀번호", + "invoice_details": "인보이스 세부 정보", + "is_percentage": "는", "keys": "키", - "last_30_days": "지난 30일", + "last_30_days": "최근 30일", "learn_more": "더 알아보기", - "ledger_connection_error": "원장에 연결하지 못했습니다. 다시 시도하십시오.", - "ledger_error_device_locked": "원장이 잠겨 있습니다", - "ledger_error_tx_rejected_by_user": "장치에서 거래가 거부되었습니다", - "ledger_error_wrong_app": "원장에서 올바른 앱을 반대하는지 확인하십시오.", - "ledger_please_enable_bluetooth": "Bluetooth가 원장을 감지 할 수 있도록하십시오", - "legacy": "유산", - "light_theme": "빛", - "litecoin_enable_mweb_sync": "mweb 스캔을 활성화합니다", - "litecoin_mweb": "mweb", - "litecoin_mweb_allow_coins": "mweb 코인을 허용하십시오", - "litecoin_mweb_always_scan": "mweb는 항상 스캔을 설정합니다", - "litecoin_mweb_description": "MWEB는 Litecoin에 더 빠르고 저렴하며 개인 거래를 제공하는 새로운 프로토콜입니다.", - "litecoin_mweb_dismiss": "해고하다", - "litecoin_mweb_display_card": "mweb 카드를 보여주십시오", - "litecoin_mweb_enable": "mweb 활성화", - "litecoin_mweb_enable_later": "디스플레이 설정에서 MWEB를 다시 활성화하도록 선택할 수 있습니다.", - "litecoin_mweb_logs": "mweb 로그", - "litecoin_mweb_node": "mweb 노드", - "litecoin_mweb_pegin": "페그를 입력하십시오", - "litecoin_mweb_pegout": "죽다", - "litecoin_mweb_scanning": "mweb 스캔", - "litecoin_mweb_settings": "mweb 설정", - "litecoin_mweb_warning": "MWEB를 사용하면 처음에는 ~ 600MB의 데이터를 다운로드하며 네트워크 속도에 따라 최대 30 분이 소요될 수 있습니다. 이 초기 데이터는 한 번만 다운로드하여 모든 조명 지갑에 사용할 수 있습니다.", - "litecoin_what_is_mweb": "MWEB 란 무엇입니까?", - "live_fee_rates": "API를 통한 라이브 요금 요금", - "load_more": "더로드하십시오", - "loading_your_wallet": "지갑 넣기", + "ledger_connection_error": "Ledger 연결에 실패했습니다. 다시 시도하세요.", + "ledger_error_device_locked": "Ledger가 잠겨 있습니다", + "ledger_error_tx_rejected_by_user": "기기에서 트랜잭션 거부됨", + "ledger_error_wrong_app": "Ledger에서 올바른 앱을 열었는지 확인하세요.", + "ledger_please_enable_bluetooth": "Ledger를 감지하려면 블루투스를 활성화하세요.", + "legacy": "레거시", + "light_theme": "라이트 테마", + "litecoin_enable_mweb_sync": "MWEB 스캔 활성화", + "litecoin_mweb": "MWEB", + "litecoin_mweb_allow_coins": "MWEB 코인 허용", + "litecoin_mweb_always_scan": "MWEB 항상 스캔 설정", + "litecoin_mweb_description": "MWEB는 라이트코인에 더 빠르고, 저렴하며, 더 비공개적인 트랜잭션을 제공하는 새로운 프로토콜입니다.", + "litecoin_mweb_dismiss": "닫기", + "litecoin_mweb_display_card": "MWEB 카드 표시", + "litecoin_mweb_enable": "MWEB 활성화", + "litecoin_mweb_enable_later": "표시 설정에서 MWEB를 다시 활성화하도록 선택할 수 있습니다.", + "litecoin_mweb_logs": "MWEB 로그", + "litecoin_mweb_node": "MWEB 노드", + "litecoin_mweb_pegin": "페그 인", + "litecoin_mweb_pegout": "페그 아웃", + "litecoin_mweb_scanning": "MWEB 스캔 중", + "litecoin_mweb_settings": "MWEB 설정", + "litecoin_mweb_warning": "MWEB를 사용하면 초기에 약 600MB의 데이터가 다운로드되며 네트워크 속도에 따라 최대 30분이 소요될 수 있습니다. 이 초기 데이터는 한 번만 다운로드되며 모든 라이트코인 지갑에서 사용할 수 있습니다.", + "litecoin_what_is_mweb": "MWEB란 무엇인가요?", + "live_fee_rates": "API를 통한 실시간 수수료율", + "load_more": "더 로드", + "loading_your_wallet": "지갑 로딩 중", "login": "로그인", "logout": "로그아웃", "low_fee": "낮은 수수료", - "low_fee_alert": "현재 낮은 네트워크 요금 우선 순위를 사용하고 있습니다. 이로 인해 긴 대기 시간, 다른 요금 또는 취소된 거래가 발생할 수 있습니다. 더 나은 경험을 위해 더 높은 요금을 설정하는 것이 좋습니다.", + "low_fee_alert": "현재 낮은 네트워크 수수료 우선순위를 사용하고 있습니다. 이로 인해 대기 시간이 길어지거나, 환율이 달라지거나, 거래가 취소될 수 있습니다. 더 나은 경험을 위해 더 높은 수수료를 설정하는 것이 좋습니다.", "manage_nodes": "노드 관리", "manage_pow_nodes": "PoW 노드 관리", - "manage_yats": "관리하다 Yats", + "manage_yats": "Yat 관리", "mark_as_redeemed": "사용한 것으로 표시", "market_place": "마켓플레이스", "matrix_green_dark_theme": "매트릭스 그린 다크 테마", "max_amount": "최대: ${value}", - "max_value": "맥스: ${value} ${currency}", + "max_value": "최대: ${value} ${currency}", "memo": "메모:", "message": "메시지", "message_verified": "메시지가 성공적으로 확인되었습니다", "messages": "메시지", "method": "방법", - "methods": "행동 양식", + "methods": "방법", "min_amount": "최소: ${value}", "min_value": "최소: ${value} ${currency}", "mint_address": "민트 주소", - "minutes_to_pin_code": "${minute}분", - "mm": "mm", - "modify_2fa": "수정 케이크 2FA", + "minutes_to_pin_code": "${minute} 분", + "mm": "MM", + "modify_2fa": "Cake 2FA 수정", "monero_com": "Monero.com by Cake Wallet", - "monero_com_wallet_text": "Awesome wallet for Monero", + "monero_com_wallet_text": "모네로를 위한 멋진 지갑", "monero_dark_theme": "모네로 다크 테마", "monero_light_theme": "모네로 라이트 테마", - "moonpay_alert_text": "금액은 다음보다 크거나 같아야합니다 ${minAmount} ${fiatCurrency}", - "moralis_nft_error": "NFT를 가져 오는 동안 오류가 발생했습니다. 인터넷 연결을 친절하게 확인하고 다시 시도하십시오.", + "moonpay_alert_text": "금액 값은 ${minAmount} ${fiatCurrency} 이상이어야 합니다", + "moralis_nft_error": "NFT를 가져오는 중 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도하십시오.", "more_options": "추가 옵션", - "multiple_addresses_detected": "여러 주소가 감지되었습니다", - "mweb_confirmed": "확인 mweb", - "mweb_unconfirmed": "확인되지 않은 mweb", + "multiple_addresses_detected": "여러 주소 감지됨", + "mweb_confirmed": "확정된 MWEB", + "mweb_unconfirmed": "미확정 MWEB", "name": "이름", - "nano_current_rep": "현재 대표", - "nano_gpt_thanks_message": "Nanogpt를 사용해 주셔서 감사합니다! 거래가 완료된 후 브라우저로 돌아가는 것을 잊지 마십시오!", - "nano_pick_new_rep": "새로운 담당자를 선택하십시오", - "nanogpt_subtitle": "모든 최신 모델 (GPT-4, Claude). \\ nno 구독, Crypto로 지불하십시오.", - "narrow": "좁은", - "new_first_wallet_text": "암호화를 안전하게 유지하는 것은 케이크 조각입니다", - "new_node_testing": "새로운 노드 테스트", - "new_subaddress_create": "몹시 떠들어 대다", + "nano_current_rep": "현재 대표자", + "nano_gpt_thanks_message": "NanoGPT를 사용해 주셔서 감사합니다! 트랜잭션 완료 후 브라우저로 돌아가는 것을 잊지 마세요!", + "nano_pick_new_rep": "새 대표자 선택", + "nanogpt_subtitle": "모든 최신 모델 (GPT-4, Claude).\n구독 없이 암호화폐로 결제.", + "narrow": "좁게", + "new_first_wallet_text": "암호화폐를 안전하게 보관하는 것은 식은 죽 먹기", + "new_node_testing": "새 노드 테스트 중", + "new_subaddress_create": "생성", "new_subaddress_label_name": "라벨 이름", "new_subaddress_title": "새 주소", "new_template": "새 템플릿", - "new_transactions_notifications": "새로운 거래에 대한 알림을 보냅니다", - "new_wallet": "새 월렛", - "newConnection": "새로운 연결", - "no_cards_found": "카드를 찾지 못했습니다", - "no_extra_detail": "추가 세부 정보가 없습니다", - "no_id_needed": "ID가 필요하지 않습니다!", - "no_id_required": "신분증이 필요하지 않습니다. 충전하고 어디에서나 사용하세요", - "no_providers_available": "제공되는 제공자가 없습니다", - "no_relay_on_domain": "사용자 도메인에 릴레이가 없거나 릴레이를 사용할 수 없습니다. 사용할 릴레이를 선택해주세요.", + "new_transactions_notifications": "새 트랜잭션 알림 보내기", + "new_wallet": "새 지갑", + "newConnection": "새 연결", + "no_cards_found": "카드를 찾을 수 없습니다", + "no_extra_detail": "추가 세부 정보 없음", + "no_id_needed": "ID 필요 없음!", + "no_id_required": "ID 필요 없음. 충전하고 어디서나 사용하세요", + "no_providers_available": "사용 가능한 제공 업체 없음", + "no_relay_on_domain": "사용자 도메인에 릴레이가 없거나 릴레이를 사용할 수 없습니다. 사용할 릴레이를 선택하세요.", "no_relays": "릴레이 없음", - "no_relays_message": "이 사용자에 대한 Nostr NIP-05 레코드를 찾았지만 릴레이가 포함되어 있지 않습니다. 수신자에게 Nostr 기록에 릴레이를 추가하도록 지시하십시오.", + "no_relays_message": "이 사용자의 Nostr NIP-05 기록을 찾았지만 릴레이가 포함되어 있지 않습니다. 수신자에게 Nostr 기록에 릴레이를 추가하도록 지시하세요.", "node_address": "노드 주소", "node_connection_failed": "연결 실패", - "node_connection_successful": "성공적으로 연결되었습니다.", - "node_new": "새로운 노드", + "node_connection_successful": "연결 성공", + "node_new": "새 노드", "node_port": "노드 포트", "node_reset_settings_title": "설정 초기화", "node_test": "테스트", "nodes": "노드", - "nodes_list_reset_to_default_message": "설정을 기본값으로 재설정 하시겠습니까?", - "none_of_selected_providers_can_exchange": "선택한 공급자 중 어느 것도이 교환을 할 수 없습니다", - "noNFTYet": "아직 NFT가 없습니다", - "normal": "정상", - "note_optional": "참고 (선택 사항)", - "note_tap_to_change": "메모 (변경하려면 탭하세요)", - "notification_permission_denied": "알림 허가가 부패하게 거부되었습니다. 설정에서 수동으로 활성화하십시오.", - "nullURIError": "URI가 null입니다.", - "offer_expires_in": "쿠폰 만료일: ", + "nodes_list_reset_to_default_message": "설정을 기본값으로 초기화하시겠습니까?", + "none_of_selected_providers_can_exchange": "선택한 제공 업체 중 이 스왑을 할 수 있는 곳이 없습니다", + "noNFTYet": "아직 NFT 없음", + "normal": "보통", + "note_optional": "메모 (선택 사항)", + "note_tap_to_change": "메모 (탭하여 변경)", + "notification_permission_denied": "알림 권한이 영구적으로 거부되었습니다. 설정에서 수동으로 활성화하세요.", + "nullURIError": "URI가 null입니다", + "offer_expires_in": "오퍼 만료 시간: ", "offline": "오프라인", - "ok": "승인", - "old_fee": "옛 수수료", - "onion_link": "양파 링크", + "ok": "확인", + "old_fee": "이전 수수료", + "onion_link": "Onion 링크", "online": "온라인", - "onramper_option_description": "많은 결제 방법으로 암호화를 신속하게 구입하십시오. 대부분의 국가에서 사용할 수 있습니다. 스프레드와 수수료는 다양합니다.", + "onramper_option_description": "다양한 결제 방법으로 빠르게 암호화폐 구매. 대부분의 국가에서 사용 가능. 스프레드 및 수수료는 다양함.", "open_gift_card": "기프트 카드 열기", - "open_wallet": "오픈 지갑", + "open_wallet": "지갑 열기", "optional_description": "선택적 설명", "optional_email_hint": "선택적 수취인 알림 이메일", "optional_name": "선택적 수신자 이름", - "optionally_order_card": "선택적으로 실제 카드를 주문하십시오.", - "orbot_running_alert": "이 노드에 연결하기 전에 Orbot이 실행 중인지 확인하십시오.", - "order_by": "주문", + "optionally_order_card": "선택적으로 실물 카드 주문.", + "orbot_running_alert": "이 노드에 연결하기 전에 Orbot이 실행 중인지 확인하세요.", + "order_by": "정렬 기준", "order_id": "주문 ID", - "order_physical_card": "물리적 카드 주문", + "order_physical_card": "실물 카드 주문", "other_settings": "기타 설정", - "outdated_electrum_wallet_description": "Cake에서 생성 된 새로운 비트 코인 지갑에는 이제 24 단어 시드가 있습니다. 새로운 비트 코인 지갑을 생성하고 모든 자금을 새로운 24 단어 지갑으로 이체하고 12 단어 시드가있는 지갑 사용을 중지해야합니다. 자금을 확보하려면 즉시이 작업을 수행하십시오.", - "outdated_electrum_wallet_receive_warning": "이 지갑에 12 단어 시드가 있고 Cake에서 생성 된 경우이 지갑에 비트 코인을 입금하지 마십시오. 이 지갑으로 전송 된 모든 BTC는 손실 될 수 있습니다. 새로운 24 단어 지갑을 생성하고 (오른쪽 상단의 메뉴를 탭하고 지갑을 선택한 다음 새 지갑 생성을 선택한 다음 비트 코인을 선택하십시오) 즉시 BTC를 그곳으로 이동하십시오. Cake의 새로운 (24 단어) BTC 지갑은 안전합니다", - "outgoing": "나가는", + "outdated_electrum_wallet_description": "Cake에서 생성된 새 비트코인 지갑은 이제 24단어 시드를 사용합니다. 새 비트코인 지갑을 생성하고 모든 자금을 새 24단어 지갑으로 이전하고 12단어 시드를 사용하는 지갑 사용을 중단해야 합니다. 자금 보호를 위해 즉시 이 작업을 수행하십시오.", + "outdated_electrum_wallet_receive_warning": "이 지갑에 12단어 시드가 있고 Cake에서 생성된 경우 이 지갑에 비트코인을 입금하지 마십시오. 이 지갑으로 전송된 모든 BTC는 손실될 수 있습니다. 새 24단어 지갑을 생성(오른쪽 상단 메뉴 탭, 지갑 선택, 새 지갑 생성 선택, 비트코인 선택)하고 즉시 BTC를 그곳으로 옮기십시오. Cake의 새 (24단어) BTC 지갑은 안전합니다.", + "outgoing": "발신", "outputs": "출력", - "overshot": "오버 샷", - "overwrite_amount": "Overwrite amount", - "pairingInvalidEvent": "잘못된 이벤트 페어링", - "passphrase": "암호화 (선택 사항)", - "passphrases_doesnt_match": "패스 프레이즈가 일치하지 않습니다. 다시 시도하십시오", - "password": "암호", - "paste": "풀", - "pause_wallet_creation": "Haven Wallet 생성 기능이 현재 일시 중지되었습니다.", - "payment_id": "지불 ID: ", + "overshot": "초과", + "overwrite_amount": "금액 덮어쓰기", + "pairingInvalidEvent": "페어링 잘못된 이벤트", + "passphrase": "암호 구문 (선택 사항)", + "passphrase_view_keys": "암호 구문", + "passphrases_doesnt_match": "암호 구문이 일치하지 않습니다. 다시 시도하세요.", + "password": "비밀번호", + "paste": "붙여넣기", + "pause_wallet_creation": "현재 Haven 지갑 생성 기능이 일시 중지되었습니다.", + "payment_id": "결제 ID: ", "payment_was_received": "결제가 접수되었습니다.", - "pending": " (보류 중)", - "percentageOf": "${amount} 중", - "pin_at_top": "상단에 ${token} 고정", + "pending": " (대기 중)", + "percentageOf": "${amount}의", + "pin_at_top": "${token} 상단에 고정", "pin_is_incorrect": "PIN이 잘못되었습니다", "pin_number": "PIN 번호", "placeholder_contacts": "연락처가 여기에 표시됩니다", - "placeholder_transactions": "거래가 여기에 표시됩니다", - "please_choose_one": "하나를 선택하십시오", - "please_fill_totp": "다른 기기에 있는 8자리 코드를 입력하세요.", - "please_make_selection": "아래에서 선택하십시오 지갑 만들기 또는 복구.", - "please_reference_document": "자세한 내용은 아래 문서를 참조하십시오.", - "please_select": "선택 해주세요:", - "please_select_backup_file": "백업 파일을 선택하고 백업 암호를 입력하십시오.", - "please_try_to_connect_to_another_node": "다른 노드에 연결을 시도하십시오", - "please_wait": "기다리세요", - "polygonscan_history": "다각형 스캔 기록", + "placeholder_transactions": "트랜잭션이 여기에 표시됩니다", + "please_choose_one": "하나를 선택하세요", + "please_fill_totp": "다른 기기에 표시된 8자리 코드를 입력하세요", + "please_make_selection": "지갑을 생성하거나 복구하려면 아래에서 선택하세요.", + "please_reference_document": "자세한 내용은 아래 문서를 참조하세요.", + "please_select": "선택하세요:", + "please_select_backup_file": "백업 파일을 선택하고 백업 비밀번호를 입력하세요.", + "please_try_to_connect_to_another_node": "다른 노드에 연결해 보세요", + "please_wait": "기다려 주세요", + "polygonscan_history": "PolygonScan 내역", "potential_scam": "잠재적 사기", - "powered_by": "에 의해 구동 ${title}", - "pre_seed_button_text": "이해 했어요. 내 씨앗을 보여줘", - "pre_seed_description": "다음 페이지에는 일련의 단어가 표시됩니다. 이것은 독특하고 개인적인 씨앗이며 손실이나 오작동의 경우 지갑을 회수하는 유일한 방법입니다. 케이크 지갑 앱 외부의 안전한 장소에 그것을 적어두고 보관하는 것은 귀하의 책임입니다.", - "pre_seed_title": "중대한", + "powered_by": "${title} 제공", + "pre_seed_button_text": "이해했습니다. 시드를 보여주세요", + "pre_seed_description": "다음 페이지에서 일련의 단어를 보게 됩니다. 이것은 귀하의 고유하고 비공개적인 시드이며, 분실 또는 오작동 시 지갑을 복구할 수 있는 유일한 방법입니다. Cake Wallet 앱 외부에 안전한 장소에 적어두고 보관하는 것은 귀하의 책임입니다.", + "pre_seed_title": "중요", "prepaid_cards": "선불 카드", "prevent_screenshots": "스크린샷 및 화면 녹화 방지", "primary_address": "기본 주소", - "privacy": "프라이버시", - "privacy_policy": "개인 정보 보호 정책", - "privacy_settings": "개인정보 설정", + "privacy": "개인 정보 보호", + "privacy_policy": "개인정보 보호정책", + "privacy_settings": "개인 정보 보호 설정", "private_key": "개인 키", "proceed_after_one_minute": "1분 후에도 화면이 진행되지 않으면 이메일을 확인하세요.", - "proceed_on_device": "장치를 진행하십시오", - "proceed_on_device_description": "하드웨어 지갑에 표시된 지침을 따르십시오", - "processing": "처리", + "proceed_on_device": "기기에서 진행", + "proceed_on_device_description": "하드웨어 지갑에 표시되는 지침을 따르세요.", + "processing": "처리 중", "profile": "프로필", "provider_error": "${provider} 오류", "public_key": "공개 키", "purchase_gift_card": "기프트 카드 구매", - "purple_dark_theme": "보라색 어두운 테마", - "qr_fullscreen": "전체 화면 QR 코드를 열려면 탭하세요.", - "qr_payment_amount": "This QR code contains a payment amount. Do you want to overwrite the current value?", + "purple_dark_theme": "보라색 다크 테마", + "qr_fullscreen": "전체 화면 QR 코드를 열려면 탭하세요", + "qr_payment_amount": "이 QR 코드에는 결제 금액이 포함되어 있습니다. 현재 값을 덮어쓰시겠습니까?", "quantity": "수량", - "question_to_disable_2fa": "Cake 2FA를 비활성화하시겠습니까? 지갑 및 특정 기능에 액세스하는 데 더 이상 2FA 코드가 필요하지 않습니다.", - "receivable_balance": "채권 잔액", - "receive": "받다", - "receive_amount": "양", - "received": "받았습니다", - "recipient_address": "받는 사람 주소", + "question_to_disable_2fa": "Cake 2FA를 비활성화하시겠습니까? 더 이상 지갑 및 특정 기능에 액세스하는 데 2FA 코드가 필요하지 않습니다.", + "receivable_balance": "수취 가능 잔액", + "receive": "받기", + "receive_amount": "금액", + "received": "받음", + "recipient_address": "수신자 주소", "reconnect": "다시 연결", - "reconnect_alert_text": "다시 연결 하시겠습니까?", - "reconnect_your_hardware_wallet": "하드웨어 지갑을 다시 연결하십시오", - "reconnection": "재 연결", - "red_dark_theme": "빨간 어두운 테마", - "red_light_theme": "빨간불 테마", - "redeemed": "구함", + "reconnect_alert_text": "정말 다시 연결하시겠습니까?", + "reconnect_your_hardware_wallet": "하드웨어 지갑 다시 연결", + "reconnection": "재연결", + "red_dark_theme": "빨간색 다크 테마", + "red_light_theme": "빨간색 라이트 테마", + "redeemed": "사용됨", "refund_address": "환불 주소", - "reject": "거부하다", - "remaining": "남은", - "remove": "없애다", + "reject": "거부", + "remaining": "남음", + "remove": "제거", "remove_node": "노드 제거", - "remove_node_message": "선택한 노드를 제거 하시겠습니까?", - "rename": "이름 바꾸기", - "rep_warning": "대표 경고", - "rep_warning_sub": "귀하의 대표는 양호한 상태가 아닌 것 같습니다. 새 것을 선택하려면 여기를 탭하십시오", - "repeat_wallet_password": "지갑 암호를 반복하십시오", - "repeated_password_is_incorrect": "반복 된 비밀번호가 올바르지 않습니다. 지갑 암호를 다시 반복하십시오.", - "requested": "요청", - "require_for_adding_contacts": "연락처 추가에 필요", + "remove_node_message": "선택한 노드를 정말로 제거하시겠습니까?", + "rename": "이름 변경", + "rep_warning": "대표자 경고", + "rep_warning_sub": "귀하의 대표자가 정상 상태가 아닌 것 같습니다. 여기를 탭하여 새 대표자를 선택하세요.", + "repeat_wallet_password": "지갑 비밀번호 다시 입력", + "repeated_password_is_incorrect": "반복 입력한 비밀번호가 잘못되었습니다. 지갑 비밀번호를 다시 입력하세요.", + "requested": "요청됨", + "require_for_adding_contacts": "연락처 추가 시 필요", "require_for_all_security_and_backup_settings": "모든 보안 및 백업 설정에 필요", - "require_for_assessing_wallet": "지갑 접근을 위해 필요", - "require_for_creating_new_wallets": "새 지갑 생성에 필요", - "require_for_exchanges_to_external_wallets": "외부 지갑으로의 교환을 위해 필요", - "require_for_exchanges_to_internal_wallets": "내부 지갑으로의 교환에 필요", - "require_for_sends_to_contacts": "연락처로 보내기에 필요", - "require_for_sends_to_internal_wallets": "내부 지갑으로 보내는 데 필요", - "require_for_sends_to_non_contacts": "비접촉자에게 보내는 데 필요", - "require_pin_after": "다음 이후에 PIN 필요", - "required_passphrase": "암호", - "rescan": "재검색", + "require_for_assessing_wallet": "지갑 접근 시 필요", + "require_for_creating_new_wallets": "새 지갑 생성 시 필요", + "require_for_exchanges_to_external_wallets": "외부 지갑으로 교환 시 필요", + "require_for_exchanges_to_internal_wallets": "내부 지갑으로 교환 시 필요", + "require_for_sends_to_contacts": "연락처로 전송 시 필요", + "require_for_sends_to_internal_wallets": "내부 지갑으로 전송 시 필요", + "require_for_sends_to_non_contacts": "연락처 외 대상으로 전송 시 필요", + "require_pin_after": "다음 시간 후 PIN 필요", + "required_passphrase": "암호 구문", + "rescan": "재스캔", "resend_code": "다시 보내주세요", - "reset": "다시 놓기", + "reset": "초기화", "reset_password": "비밀번호 재설정", - "restore": "복원하다", - "restore_active_seed": "활성 종자", + "restore": "복구", + "restore_active_seed": "활성 시드", "restore_address": "주소", - "restore_bitcoin_description_from_keys": "개인 키에서 생성 된 WIF 문자열에서 지갑 복원", - "restore_bitcoin_description_from_seed": "24 단어 조합 코드에서 지갑 복원", - "restore_bitcoin_title_from_keys": "WIF에서 복원", - "restore_description_from_backup": "백업 파일에서 전체 Cake Wallet 앱을 복원 할 수 있습니다.", - "restore_description_from_hardware_wallet": "원장 하드웨어 지갑에서 복원하십시오", - "restore_description_from_keys": "개인 키에서 저장된 생성 된 키 스트로크에서 월렛 복원", - "restore_description_from_seed": "25 단어 또는 13 단어 조합 코드에서 지갑을 복원하십시오.", - "restore_description_from_seed_keys": "안전한 장소에 저장 한 종자 / 키로 지갑을 되 찾으십시오.", - "restore_existing_wallet": "기존 지갑을 복원하십시오", - "restore_from_date_or_blockheight": "이 지갑을 생성하기 며칠 전에 날짜를 입력하십시오. 또는 블록 높이를 알고있는 경우 대신 입력하십시오.", - "restore_from_seed_placeholder": "여기에 코드 문구를 입력하거나 붙여 넣으십시오.", - "restore_new_seed": "새로운 씨앗", - "restore_next": "다음 것", - "restore_recover": "다시 덮다", - "restore_restore_wallet": "월렛 복원", - "restore_seed_keys_restore": "종자 / 키 복원", - "restore_spend_key_private": "지출 키 (은밀한)", - "restore_title_from_backup": "백업 파일에서 복원", - "restore_title_from_hardware_wallet": "하드웨어 지갑에서 복원하십시오", - "restore_title_from_keys": "키에서 복원", - "restore_title_from_seed": "종자에서 복원", - "restore_title_from_seed_keys": "시드 / 키에서 복원", - "restore_view_key_private": "키보기 (은밀한)", - "restore_wallet": "월렛 복원", + "restore_bitcoin_description_from_keys": "개인 키에서 생성된 WIF 문자열로 지갑 복구", + "restore_bitcoin_description_from_seed": "24단어 조합 코드로 지갑 복구", + "restore_bitcoin_title_from_keys": "WIF에서 복구", + "restore_description_from_backup": "백업 파일에서 전체 Cake Wallet 앱을 복구할 수 있습니다", + "restore_description_from_hardware_wallet": "Ledger 하드웨어 지갑에서 복구", + "restore_description_from_keys": "개인 키에서 저장된 키 입력으로 지갑 복구", + "restore_description_from_seed": "25단어 또는 13단어 조합 코드로 지갑 복구", + "restore_description_from_seed_keys": "안전한 장소에 저장한 시드/키로 지갑 복구", + "restore_existing_wallet": "기존 지갑 복구", + "restore_from_date_or_blockheight": "이 지갑을 생성하기 며칠 전의 날짜를 입력하세요. 또는 블록 높이를 아는 경우 대신 입력하세요", + "restore_from_seed_placeholder": "여기에 시드를 입력하거나 붙여넣으세요", + "restore_new_seed": "새 시드", + "restore_next": "다음", + "restore_recover": "복구", + "restore_restore_wallet": "지갑 복구", + "restore_seed_keys_restore": "시드/키 복구", + "restore_spend_key_private": "지출 키 (개인)", + "restore_title_from_backup": "백업에서 복구", + "restore_title_from_hardware_wallet": "하드웨어 지갑에서 복구", + "restore_title_from_keys": "키에서 복구", + "restore_title_from_seed": "시드에서 복구", + "restore_title_from_seed_keys": "시드/키에서 복구", + "restore_view_key_private": "보기 키 (개인)", + "restore_wallet": "지갑 복구", "restore_wallet_name": "지갑 이름", - "restore_wallet_restore_description": "월렛 복원 설명", - "robinhood_option_description": "직불 카드, 은행 계좌 또는 Robinhood 잔액을 사용하여 즉시 구매 및 양도하십시오. 미국 만.", - "router_no_route": "에 정의 된 경로가 없습니다 ${name}", - "save": "구하다", - "save_backup_password": "백업 암호를 저장했는지 확인하십시오. 그것 없이는 백업 파일을 가져올 수 없습니다.", + "restore_wallet_restore_description": "지갑 복구 설명", + "robinhood_option_description": "직불 카드, 은행 계좌 또는 Robinhood 잔액을 사용하여 즉시 구매 및 이체. 미국 전용.", + "router_no_route": "${name}에 대해 정의된 경로 없음", + "save": "저장", + "save_backup_password": "백업 비밀번호를 저장했는지 확인하세요. 없으면 백업 파일을 가져올 수 없습니다.", "save_backup_password_alert": "백업 비밀번호 저장", "save_to_downloads": "다운로드에 저장", "saved_the_trade_id": "거래 ID를 저장했습니다", - "scan_one_block": "하나의 블록을 스캔하십시오", + "scan_one_block": "블록 하나 스캔", "scan_qr_code": "QR 코드 스캔", - "scan_qr_code_to_get_address": "QR 코드를 스캔하여 주소를 얻습니다.", - "scan_qr_on_device": "다른 기기에서 이 QR 코드를 스캔하세요.", - "search": "찾다", - "search_add_token": "검색 / 토큰 추가", - "search_category": "검색 카테고리", + "scan_qr_code_to_get_address": "주소를 얻으려면 QR 코드를 스캔하세요", + "scan_qr_on_device": "다른 기기에서 이 QR 코드 스캔", + "search": "검색", + "search_add_token": "토큰 검색 / 추가", + "search_category": "카테고리 검색", "search_currency": "통화 검색", - "search_language": "검색 언어", - "second_intro_content": "귀하의 Yat는 귀하의 모든 통화에 대해 긴 16진수 주소를 모두 대체하는 고유한 단일 이모지 주소입니다.", - "second_intro_title": "그들을 모두 지배하는 하나의 이모티콘 주소", + "search_language": "언어 검색", + "second_intro_content": "Yat은 모든 통화에 대한 긴 16진수 주소를 대체하는 고유한 이모지 주소입니다.", + "second_intro_title": "하나의 이모지 주소로 모든 것을", "security_and_backup": "보안 및 백업", "security_risk": "보안 위험", - "security_risk_description": "이 도메인은 여러 보안 제공 업체가 안전하지 않은 것으로 표시됩니다. 자산을 보호하기 위해 즉시 떠나십시오.", - "seed_alert_back": "돌아 가기", - "seed_alert_content": "씨앗은 지갑을 복구하는 유일한 방법입니다. 적어 보셨나요?", + "security_risk_description": "이 도메인은 여러 보안 제공 업체에서 안전하지 않다고 플래그 지정되었습니다. 자산을 보호하려면 즉시 나가십시오.", + "seed_alert_back": "뒤로 가기", + "seed_alert_content": "시드는 지갑을 복구하는 유일한 방법입니다. 적어두셨습니까?", "seed_alert_title": "주의", - "seed_alert_yes": "네, 있어요", - "seed_choose": "시드 언어를 선택하십시오", - "seed_display_path": "메뉴 -> 보안 및 백업 -> 키/씨앗 표시", - "seed_hex_form": "지갑 씨앗 (16 진 양식)", + "seed_alert_yes": "네, 했습니다", + "seed_choose": "시드 언어 선택", + "seed_display_path": "메뉴 -> 보안 및 백업 -> 키/시드 표시", + "seed_hex_form": "지갑 시드 (16진수 형식)", "seed_key": "시드 키", - "seed_language": "종자 언어", - "seed_language_chinese": "중국말", - "seed_language_chinese_traditional": "중국 전통)", - "seed_language_czech": "체코 사람", - "seed_language_dutch": "네덜란드 사람", + "seed_language": "시드 언어", + "seed_language_chinese": "중국어", + "seed_language_chinese_traditional": "중국어 (번체)", + "seed_language_czech": "체코어", + "seed_language_dutch": "네덜란드어", "seed_language_english": "영어", - "seed_language_french": "프랑스 국민", - "seed_language_german": "독일 사람", - "seed_language_italian": "이탈리아 사람", + "seed_language_french": "프랑스어", + "seed_language_german": "독일어", + "seed_language_italian": "이탈리아어", "seed_language_japanese": "일본어", - "seed_language_korean": "한국인", - "seed_language_next": "다음 것", - "seed_language_portuguese": "포르투갈 인", - "seed_language_russian": "러시아인", - "seed_language_spanish": "스페인의", - "seed_phrase_length": "시드 문구 길이", - "seed_position_question_one": "무엇입니까", - "seed_position_question_two": "당신의 씨앗 문구의 말?", - "seed_reminder": "휴대 전화를 분실하거나 닦을 경우를 대비해 적어 두세요.", + "seed_language_korean": "한국어", + "seed_language_next": "다음", + "seed_language_portuguese": "포르투갈어", + "seed_language_russian": "러시아어", + "seed_language_spanish": "스페인어", + "seed_phrase_length": "시드 구문 길이", + "seed_position_question_one": "시드 구문의", + "seed_position_question_two": "번째 단어는 무엇입니까?", + "seed_reminder": "휴대폰을 분실하거나 초기화할 경우를 대비해 적어두세요.", "seed_share": "시드 공유", - "seed_title": "씨", - "seed_verified": "종자 확인", - "seed_verified_subtext": "나중에 저장된 씨앗을 사용하여 부패 또는 장치를 잃을 때이 지갑을 복원 할 수 있습니다.\n\n이 씨앗을 다시 볼 수 있습니다.", - "seedtype": "시드 타입", - "seedtype_alert_content": "다른 지갑과 씨앗을 공유하는 것은 BIP39 SeedType에서만 가능합니다.", - "seedtype_alert_title": "종자 경보", + "seed_title": "시드", + "seed_verified": "시드 확인됨", + "seed_verified_subtext": "저장된 시드를 사용하여 나중에 기기 손상 또는 분실 시 이 지갑을 복구할 수 있습니다.\n\n다음에서 이 시드를 다시 볼 수 있습니다.", + "seedtype": "시드 유형", + "seedtype_alert_content": "다른 지갑과의 시드 공유는 BIP39 시드 유형에서만 가능합니다.", + "seedtype_alert_title": "시드 유형 알림", "select_backup_file": "백업 파일 선택", - "select_buy_provider_notice": "위의 구매 제공자를 선택하십시오. 앱 설정에서 기본 구매 제공자를 설정 하여이 화면을 건너 뛸 수 있습니다.", - "select_destination": "백업 파일의 대상을 선택하십시오.", - "select_hw_account_below": "아래를 복원 할 계정을 선택하십시오.", - "select_sell_provider_notice": "위에서 판매 공급자를 선택하세요. 앱 설정에서 기본 판매 공급자를 설정하면 이 화면을 건너뛸 수 있습니다.", - "select_your_country": "국가를 선택하십시오", - "selected_trocador_provider": "선정 된 트로 코더 제공 업체", - "sell": "팔다", - "sell_alert_content": "현재 Bitcoin, Ethereum 및 Litecoin의 판매만 지원합니다. Bitcoin, Ethereum 또는 Litecoin 지갑을 생성하거나 전환하십시오.", - "sell_monero_com_alert_content": "지원되지 않습니다.", - "send": "보내다", + "select_buy_provider_notice": "위에서 구매 제공 업체를 선택하세요. 앱 설정에서 기본 구매 제공 업체를 설정하여 이 화면을 건너뛸 수 있습니다.", + "select_destination": "백업 파일의 대상을 선택하세요.", + "select_hw_account_below": "아래에서 복구할 계정을 선택하세요:", + "select_sell_provider_notice": "위에서 판매 제공 업체를 선택하세요. 앱 설정에서 기본 판매 제공 업체를 설정하여 이 화면을 건너뛸 수 있습니다.", + "select_your_country": "국가를 선택하세요", + "selected_trocador_provider": "선택된 Trocador 제공 업체", + "sell": "판매", + "sell_alert_content": "현재 비트코인, 이더리움, 라이트코인 판매만 지원합니다. 비트코인, 이더리움 또는 라이트코인 지갑을 생성하거나 전환하세요.", + "sell_monero_com_alert_content": "모네로 판매는 아직 지원되지 않습니다.", + "send": "보내기", "send_address": "${cryptoCurrency} 주소", - "send_amount": "양:", - "send_change_to_you": "당신에게 변경 :", - "send_creating_transaction": "거래 생성", - "send_error_currency": "통화는 숫자 만 포함 할 수 있습니다", - "send_error_minimum_value": "금액의 최소값은 0.01입니다", + "send_amount": "금액:", + "send_change_to_you": "잔돈, 귀하에게:", + "send_creating_transaction": "트랜잭션 생성 중", + "send_error_currency": "통화는 숫자만 포함할 수 있습니다", + "send_error_minimum_value": "최소 금액 값은 0.01입니다", "send_estimated_fee": "예상 수수료:", - "send_fee": "회비:", - "send_from_cake_wallet": "케이크 지갑에서 보내십시오", - "send_from_external_wallet": "외부 지갑에서 보내십시오", + "send_fee": "수수료:", + "send_from_cake_wallet": "Cake Wallet에서 보내기", + "send_from_external_wallet": "외부 지갑에서 보내기", "send_name": "이름", - "send_new": "새로운", - "send_payment_id": "지불 ID (optional)", - "send_priority": "현재 수수료는 ${transactionPriority} 우선 순위.\n거래 우선 순위는 설정에서 조정할 수 있습니다", - "send_sending": "배상...", - "send_success": "${crypto}가 성공적으로 전송되었습니다", + "send_new": "새로 만들기", + "send_payment_id": "결제 ID (선택 사항)", + "send_priority": "현재 수수료는 ${transactionPriority} 우선순위로 설정되어 있습니다.\n트랜잭션 우선순위는 설정에서 조정할 수 있습니다.", + "send_sending": "보내는 중...", + "send_success": "${crypto}이(가) 성공적으로 전송되었습니다", "send_templates": "템플릿", - "send_title": "보내다", - "send_to_this_address": "이 주소로 ${currency} ${tag}송금", - "send_xmr": "보내다 XMR", - "send_your_wallet": "지갑", - "sending": "배상", + "send_title": "보내기", + "send_to_this_address": "${currency} ${tag}을(를) 이 주소로 보내기", + "send_xmr": "XMR 보내기", + "send_your_wallet": "내 지갑", + "sending": "보내는 중", "sent": "보냄", - "service_health_disabled": "서비스 건강 게시판이 장애가되었습니다", - "service_health_disabled_message": "이것은 서비스 건강 게시판 페이지입니다. 설정 에서이 페이지를 활성화 할 수 있습니다 -> 개인 정보", - "set_a_pin": "핀을 설정하십시오", - "set_up_a_wallet": "지갑을 설정하십시오", + "service_health_disabled": "서비스 상태 게시판 비활성화됨", + "service_health_disabled_message": "이것은 서비스 상태 게시판 페이지이며, 설정 -> 개인 정보 보호에서 이 페이지를 활성화할 수 있습니다.", + "set_a_pin": "PIN 설정", + "set_up_a_wallet": "지갑 설정", "settings": "설정", - "settings_all": "모든", + "settings_all": "전체", "settings_allow_biometrical_authentication": "생체 인증 허용", "settings_can_be_changed_later": "이 설정은 나중에 앱 설정에서 변경할 수 있습니다.", "settings_change_language": "언어 변경", @@ -759,309 +760,308 @@ "settings_currency": "통화", "settings_current_node": "현재 노드", "settings_dark_mode": "다크 모드", - "settings_display_balance": "디스플레이 잔액", - "settings_display_on_dashboard_list": "대시 보드 목록에 표시", - "settings_fee_priority": "수수료 우선", + "settings_display_balance": "잔액 표시", + "settings_display_on_dashboard_list": "대시보드 목록에 표시", + "settings_fee_priority": "수수료 우선순위", "settings_nodes": "노드", "settings_none": "없음", - "settings_only_trades": "거래 만", - "settings_only_transactions": "거래 만", - "settings_personal": "개인적인", + "settings_only_trades": "거래만", + "settings_only_transactions": "트랜잭션만", + "settings_personal": "개인", "settings_save_recipient_address": "수신자 주소 저장", - "settings_support": "지원하다", + "settings_support": "지원", "settings_terms_and_conditions": "이용 약관", "settings_title": "설정", "settings_trades": "거래", - "settings_transactions": "업무", + "settings_transactions": "트랜잭션", "settings_wallets": "지갑", - "setup_2fa": "케이크 2FA 설정", - "setup_2fa_text": "Cake 2FA는 TOTP를 두 번째 인증 요소로 사용하여 작동합니다.\n\nCake 2FA의 TOTP에는 SHA-512 및 8자리 지원이 필요합니다. 이는 보안을 강화합니다. 자세한 정보와 지원되는 앱은 가이드에서 확인할 수 있습니다.", - "setup_pin": "설정 PIN", + "setup_2fa": "Cake 2FA 설정", + "setup_2fa_text": "Cake 2FA는 두 번째 인증 요소로 TOTP를 사용합니다.\n\nCake 2FA의 TOTP는 SHA-512 및 8자리 지원이 필요하며 이는 보안을 강화합니다. 자세한 정보 및 지원되는 앱은 가이드에서 찾을 수 있습니다.", + "setup_pin": "PIN 설정", "setup_successful": "PIN이 성공적으로 설정되었습니다!", "setup_totp_recommended": "TOTP 설정", - "setup_warning_2fa_text": "Cake 2FA는 지갑의 특정 작업에 대한 두 번째 인증입니다. 냉장 보관만큼 안전하지 않습니다.\n\n2FA 앱 또는 TOTP 키에 대한 액세스 권한을 상실하면 이 지갑에 대한 액세스 권한도 잃게 됩니다. 니모닉 시드에서 지갑을 복원해야 합니다.\n\n2FA 또는 니모닉 시드에 액세스할 수 없는 경우 Cake 지원팀에서 도움을 드릴 수 없습니다.\nCake 2FA를 사용하기 전에 가이드를 읽어 보시기 바랍니다.", - "setup_your_debit_card": "직불카드 설정", - "share": "공유하다", + "setup_warning_2fa_text": "Cake 2FA는 지갑의 특정 작업에 대한 두 번째 인증입니다. 콜드 스토리지처럼 안전하지는 않습니다.\n\n2FA 앱 또는 TOTP 키에 대한 액세스 권한을 잃으면 이 지갑에 대한 액세스 권한도 잃게 됩니다. 니모닉 시드에서 지갑을 복구해야 합니다.\n\n2FA 또는 니모닉 시드에 대한 액세스 권한을 잃으면 Cake 지원팀에서 도움을 드릴 수 없습니다.\nCake 2FA를 사용하기 전에 가이드를 읽어보는 것이 좋습니다.", + "setup_your_debit_card": "직불 카드 설정", + "share": "공유", "share_address": "주소 공유", - "shared_seed_wallet_groups": "공유 종자 지갑 그룹", - "show": "보여주다", - "show_address_book_popup": "주소록 팝업을 보여주십시오", - "show_balance": "균형을 보여주기 위해 긴 언론", - "show_balance_toast": "균형을 숨기거나 보여주기 위해 긴 누르십시오", - "show_details": "세부정보 표시", - "show_keys": "시드 / 키 표시", + "shared_seed_wallet_groups": "공유 시드 지갑 그룹", + "show": "표시", + "show_address_book_popup": "주소록 팝업 표시", + "show_balance": "길게 눌러 잔액 표시", + "show_balance_toast": "길게 눌러 잔액 숨기기 또는 표시", + "show_details": "세부 정보 표시", + "show_keys": "시드/키 표시", "show_market_place": "마켓플레이스 표시", - "show_seed": "종자 표시", - "sign_all": "모두 서명하십시오", - "sign_message": "서명 메시지", - "sign_one": "하나를 서명하십시오", + "show_seed": "시드 표시", + "sign_all": "모두 서명", + "sign_message": "메시지 서명", + "sign_one": "하나 서명", "sign_up": "가입", - "sign_verify_message": "메시지에 서명하거나 확인하십시오", - "sign_verify_message_sub": "개인 키를 사용하여 메시지에 서명하거나 확인하십시오", - "sign_verify_title": "서명 / 확인", + "sign_verify_message": "서명 / 검증", + "sign_verify_message_sub": "개인 키를 사용하여 메시지 서명 또는 검증", + "sign_verify_title": "서명 / 검증", "signature": "서명", - "signature_invalid_error": "서명은 주어진 메시지에 유효하지 않습니다", - "signTransaction": "거래 서명", - "signup_for_card_accept_terms": "카드에 가입하고 약관에 동의합니다.", - "silent_payment": "조용한 지불", - "silent_payments": "조용한 지불", - "silent_payments_always_scan": "무음금을 항상 스캔합니다", - "silent_payments_disclaimer": "새로운 주소는 새로운 정체성이 아닙니다. 다른 레이블로 기존 신원을 재사용하는 것입니다.", - "silent_payments_display_card": "사일런트 지불 카드 표시", - "silent_payments_scan_from_date": "날짜부터 스캔하십시오", - "silent_payments_scan_from_date_or_blockheight": "들어오는 사일런트 결제를 위해 스캔을 시작하려는 블록 높이를 입력하거나 대신 날짜를 사용하십시오. 지갑이 모든 블록을 계속 스캔하는지 여부를 선택하거나 지정된 높이 만 확인할 수 있습니다.", - "silent_payments_scan_from_height": "블록 높이에서 스캔하십시오", - "silent_payments_scanned_tip": "팁을 스캔했습니다! (${tip})", - "silent_payments_scanning": "조용한 지불 스캔", - "silent_payments_settings": "조용한 지불 설정", - "single_seed_wallets_group": "단일 씨앗 지갑", - "slidable": "슬라이딩 가능", - "solana_create_associated_token_account_exception": "오류 생성 관련 토큰 계정 수령자 주소에 대한 계정.", - "solana_no_associated_token_account_exception": "이 주소에는 관련 토큰 계정이 없습니다.", - "solana_sign_native_transaction_rent_exception": "거래는 완료 될 수 없습니다. 거래 후 SOL이 임대료로 남았습니다. SOL 밸런스를 친절하게 위로 올리거나 보내는 솔의 양을 줄입니다.", - "solana_sign_spl_token_transaction_rent_exception": "거래는 완료 될 수 없습니다. 거래 후 SOL이 임대료로 남았습니다. SOL 균형을 친절하게 위에 올리십시오.", + "signature_invalid_error": "주어진 메시지에 대한 서명이 유효하지 않습니다", + "signTransaction": "트랜잭션 서명", + "signup_for_card_accept_terms": "카드에 가입하고 약관에 동의하세요.", + "silent_payment": "사일런트 페이먼트", + "silent_payments": "사일런트 페이먼트", + "silent_payments_always_scan": "사일런트 페이먼트 항상 스캔 설정", + "silent_payments_disclaimer": "새 주소는 새 신원이 아닙니다. 다른 라벨이 있는 기존 신원의 재사용입니다.", + "silent_payments_display_card": "사일런트 페이먼트 카드 표시", + "silent_payments_scan_from_date": "날짜부터 스캔", + "silent_payments_scan_from_date_or_blockheight": "들어오는 사일런트 페이먼트 스캔을 시작할 블록 높이를 입력하거나 대신 날짜를 사용하세요. 지갑이 모든 블록을 계속 스캔할지 또는 지정된 높이만 확인할지 선택할 수 있습니다.", + "silent_payments_scan_from_height": "블록 높이부터 스캔", + "silent_payments_scanned_tip": "팁까지 스캔됨! (${tip})", + "silent_payments_scanning": "사일런트 페이먼트 스캔 중", + "silent_payments_settings": "사일런트 페이먼트 설정", + "single_seed_wallets_group": "단일 시드 지갑", + "slidable": "슬라이드 가능", + "solana_create_associated_token_account_exception": "수신자 주소에 대한 연관 토큰 계정 생성 오류.", + "solana_no_associated_token_account_exception": "이 주소에 대한 연관 토큰 계정이 없습니다.", + "solana_sign_native_transaction_rent_exception": "트랜잭션을 완료할 수 없습니다. 트랜잭션 후 렌트에 필요한 SOL이 부족합니다. SOL 잔액을 충전하거나 보내는 SOL 금액을 줄이세요.", + "solana_sign_spl_token_transaction_rent_exception": "트랜잭션을 완료할 수 없습니다. 트랜잭션 후 렌트에 필요한 SOL이 부족합니다. SOL 잔액을 충전하세요.", "sort_by": "정렬 기준", - "spend_key_private": "지출 키 (은밀한)", - "spend_key_public": "지출 키 (공공의)", - "status": "지위: ", - "string_default": "기본", + "spend_key_private": "지출 키 (개인)", + "spend_key_public": "지출 키 (공개)", + "status": "상태: ", + "string_default": "기본값", "subaddress_title": "하위 주소 목록", "subaddresses": "하위 주소", - "submit_request": "요청을 제출", + "submit_request": "요청 제출", "success": "성공", - "successful": "성공적인", - "support_description_guides": "일반적인 문제에 대한 문서화 및 지원", - "support_description_live_chat": "자유롭고 빠릅니다! 훈련 된 지원 담당자가 지원할 수 있습니다", - "support_description_other_links": "다른 방법을 통해 커뮤니티에 가입하거나 파트너에게 연락하십시오.", - "support_title_guides": "케이크 지갑 문서", + "successful": "성공적", + "support_description_guides": "일반적인 문제에 대한 문서 및 지원", + "support_description_live_chat": "무료 및 빠름! 숙련된 지원 담당자가 도움을 드릴 수 있습니다", + "support_description_other_links": "커뮤니티에 가입하거나 다른 방법을 통해 저희 또는 파트너에게 연락하세요", + "support_title_guides": "Cake Wallet 문서", "support_title_live_chat": "실시간 지원", - "support_title_other_links": "다른 지원 링크", - "supported": "지원", - "swap": "교환", - "sweeping_wallet": "스위핑 지갑", - "sweeping_wallet_alert": "오래 걸리지 않습니다. 이 화면을 떠나지 마십시오. 그렇지 않으면 스웹트 자금이 손실될 수 있습니다.", - "switchToETHWallet": "이더리움 지갑으로 전환한 후 다시 시도해 주세요.", - "switchToEVMCompatibleWallet": "EVM 호환 지갑으로 전환 후 다시 시도해 주세요. (이더리움, 폴리곤)", - "symbol": "상징", + "support_title_other_links": "기타 지원 링크", + "supported": "지원됨", + "swap": "스왑", + "sweeping_wallet": "지갑 스윕 중", + "sweeping_wallet_alert": "오래 걸리지 않습니다. 이 화면을 떠나거나 스윕된 자금을 잃을 수 있습니다.", + "switchToETHWallet": "이더리움 지갑으로 전환하고 다시 시도하세요", + "switchToEVMCompatibleWallet": "EVM 호환 지갑(이더리움, 폴리곤)으로 전환하고 다시 시도하세요", + "symbol": "심볼", "sync_all_wallets": "모든 지갑 동기화", - "sync_status_attempting_scan": "스캔 시도", + "sync_status_attempting_scan": "스캔 시도 중", "sync_status_attempting_sync": "동기화 시도 중", "sync_status_connected": "연결됨", "sync_status_connecting": "연결 중", - "sync_status_failed_connect": "연결 해제", - "sync_status_not_connected": "연결되지 않은", - "sync_status_starting_scan": "시작 스캔 (${height} 에서)", - "sync_status_starting_sync": "동기화 시작", - "sync_status_syncronized": "동기화", - "sync_status_syncronizing": "동기화", + "sync_status_failed_connect": "연결 끊김", + "sync_status_not_connected": "연결되지 않음", + "sync_status_starting_scan": "스캔 시작 중 (${height}부터)", + "sync_status_starting_sync": "동기화 시작 중", + "sync_status_syncronized": "동기화됨", + "sync_status_syncronizing": "동기화 중", "sync_status_timed_out": "시간 초과", - "sync_status_unsupported": "지원되지 않은 노드", - "synchronizing": "동기화", - "syncing_wallet_alert_content": "상단에 \"동기화됨\"이라고 표시될 때까지 잔액 및 거래 목록이 완전하지 않을 수 있습니다. 자세히 알아보려면 클릭/탭하세요.", + "sync_status_unsupported": "지원되지 않는 노드", + "synchronizing": "동기화 중", + "syncing_wallet_alert_content": "상단에 “동기화됨”이라고 표시될 때까지 잔액 및 트랜잭션 목록이 완전하지 않을 수 있습니다. 자세히 알아보려면 클릭/탭하세요.", "syncing_wallet_alert_title": "지갑 동기화 중", - "template": "주형", + "template": "템플릿", "template_name": "템플릿 이름", - "testnet_coins_no_value": "Testnet 코인은 가치가 없습니다", - "third_intro_content": "Yats는 Cake Wallet 밖에서도 살고 있습니다. 지구상의 모든 지갑 주소는 Yat!", - "third_intro_title": "Yat는 다른 사람들과 잘 놉니다.", - "this_pair_is_not_supported_warning": "이 쌍은 현재 선택된 교환으로 지원되지 않습니다. 다른 교환을 선택하십시오.", - "thorchain_contract_address_not_supported": "Thorchain은 계약 주소로 보내는 것을 지원하지 않습니다", - "thorchain_taproot_address_not_supported": "Thorchain 제공 업체는 Taproot 주소를 지원하지 않습니다. 주소를 변경하거나 다른 공급자를 선택하십시오.", - "time": "${minutes}m ${seconds}s", + "testnet_coins_no_value": "테스트넷 코인은 가치가 없습니다", + "third_intro_content": "Yat은 Cake Wallet 외부에서도 사용됩니다. 지구상의 모든 지갑 주소는 Yat으로 대체될 수 있습니다!", + "third_intro_title": "Yat은 다른 것과 잘 어울립니다", + "this_pair_is_not_supported_warning": "현재 선택된 교환소에서는 이 쌍이 지원되지 않습니다. 다른 교환소를 선택하세요.", + "thorchain_contract_address_not_supported": "THORChain은 계약 주소로 보내는 것을 지원하지 않습니다", + "thorchain_taproot_address_not_supported": "ThorChain 제공 업체는 Taproot 주소를 지원하지 않습니다. 주소를 변경하거나 다른 제공 업체를 선택하세요.", + "time": "${minutes}분 ${seconds}초", "tip": "팁:", - "to": "에게", + "to": "받는 통화", "today": "오늘", "token_contract_address": "토큰 계약 주소", - "token_decimal": "토큰 십진수", - "token_name": "토큰 이름 예: Tether", - "token_symbol": "토큰 기호 예: USDT", + "token_decimal": "토큰 소수 자릿수", + "token_name": "토큰 이름 (예: Tether)", + "token_symbol": "토큰 심볼 (예: USDT)", "tokenID": "ID", - "tor_connection": "토르 연결", - "tor_only": "Tor 뿐", - "total": "총", - "total_saving": "총 절감액", - "totp_2fa_failure": "잘못된 코드입니다. 다른 코드를 시도하거나 새 비밀 키를 생성하십시오. 8자리 코드와 SHA512를 지원하는 호환되는 2FA 앱을 사용하세요.", - "totp_2fa_success": "성공! 이 지갑에 케이크 2FA가 활성화되었습니다. 지갑 액세스 권한을 잃을 경우를 대비하여 니모닉 시드를 저장하는 것을 잊지 마십시오.", + "tor_connection": "Tor 연결", + "tor_only": "Tor 전용", + "total": "합계", + "total_saving": "총 절약액", + "totp_2fa_failure": "잘못된 코드입니다. 다른 코드를 시도하거나 새 비밀 키를 생성하세요. 8자리 코드와 SHA512를 지원하는 호환 2FA 앱을 사용하세요.", + "totp_2fa_success": "성공! 이 지갑에 Cake 2FA가 활성화되었습니다. 지갑 액세스 권한을 잃을 경우를 대비하여 니모닉 시드를 저장하는 것을 잊지 마세요.", "totp_auth_url": "TOTP 인증 URL", "totp_code": "TOTP 코드", "totp_secret_code": "TOTP 비밀 코드", - "totp_verification_success": "확인 성공!", - "track": "길", - "trade_details_copied": "${title} 클립 보드에 복사", - "trade_details_created_at": "에 작성", - "trade_details_fetching": "가져 오는 중", + "totp_verification_success": "검증 성공!", + "track": "추적", + "trade_details_copied": "${title} 클립보드에 복사됨", + "trade_details_created_at": "생성 날짜", + "trade_details_fetching": "가져오는 중", "trade_details_id": "ID", "trade_details_pair": "쌍", - "trade_details_provider": "공급자", + "trade_details_provider": "제공 업체", "trade_details_state": "상태", - "trade_details_title": "거래 세부 사항", - "trade_for_not_created": "거래 ${title} 생성되지 않습니다.", - "trade_history_title": "무역 역사", - "trade_id": "무역 ID:", - "trade_id_not_found": "무역 ${tradeId} 의 ${title} 찾을 수 없습니다.", - "trade_is_powered_by": "이 거래는 ${provider}", - "trade_not_created": "거래가 생성되지 않았습니다", - "trade_not_found": "거래를 찾을 수 없습니다.", - "trade_state_btc_sent": "보냄", - "trade_state_complete": "완전한", + "trade_details_title": "거래 세부 정보", + "trade_for_not_created": "${title}에 대한 거래가 생성되지 않았습니다.", + "trade_history_title": "거래 내역", + "trade_id": "거래 ID:", + "trade_id_not_found": "${title}의 거래 ${tradeId}를 찾을 수 없습니다.", + "trade_is_powered_by": "이 거래는 ${provider}에서 제공합니다", + "trade_not_created": "거래가 생성되지 않음", + "trade_not_found": "거래를 찾을 수 없음.", + "trade_state_btc_sent": "BTC 전송됨", + "trade_state_complete": "완료", "trade_state_confirming": "확인 중", - "trade_state_created": "만들어진", - "trade_state_finished": "끝마친", - "trade_state_paid": "유료", - "trade_state_paid_unconfirmed": "미확인 유료", + "trade_state_created": "생성됨", + "trade_state_finished": "완료됨", + "trade_state_paid": "지불됨", + "trade_state_paid_unconfirmed": "지불됨 (미확인)", "trade_state_pending": "대기 중", - "trade_state_timeout": "타임 아웃", - "trade_state_to_be_created": "만들려면", - "trade_state_traded": "거래", - "trade_state_trading": "거래", - "trade_state_underpaid": "미지급", - "trade_state_unpaid": "미지급", + "trade_state_timeout": "시간 초과", + "trade_state_to_be_created": "생성 예정", + "trade_state_traded": "거래됨", + "trade_state_trading": "거래 중", + "trade_state_underpaid": "과소 지불됨", + "trade_state_unpaid": "미지불", "trades": "거래", - "transaction_cost": "거래 비용", - "transaction_details_amount": "양", - "transaction_details_copied": "${title} 클립 보드에 복사", + "transaction_cost": "트랜잭션 비용", + "transaction_details_amount": "금액", + "transaction_details_copied": "${title} 클립보드에 복사됨", "transaction_details_date": "날짜", - "transaction_details_fee": "회비", - "transaction_details_height": "신장", - "transaction_details_recipient_address": "받는 사람 주소", - "transaction_details_source_address": "소스 주소", - "transaction_details_title": "상세 거래 내역", + "transaction_details_fee": "수수료", + "transaction_details_height": "높이", + "transaction_details_recipient_address": "수신자 주소", + "transaction_details_source_address": "송신자 주소", + "transaction_details_title": "트랜잭션 세부 정보", "transaction_details_transaction_id": "트랜잭션 ID", - "transaction_key": "거래 키", - "transaction_priority_fast": "빠른", - "transaction_priority_fastest": "가장 빠른", - "transaction_priority_medium": "매질", - "transaction_priority_regular": "정규병", - "transaction_priority_slow": "느린", - "transaction_sent": "거래가 전송되었습니다!", - "transaction_sent_notice": "1분 후에도 화면이 진행되지 않으면 블록 익스플로러와 이메일을 확인하세요.", - "transactions": "업무", - "transactions_by_date": "날짜 별 거래", - "transport_type": "운송 유형", - "trongrid_history": "트롱 트리드 역사", - "trusted": "신뢰할 수 있는", - "tx_commit_exception_no_dust_on_change": "이 금액으로 거래가 거부되었습니다. 이 코인을 사용하면 거스름돈 없이 ${min}를 보내거나 거스름돈을 반환하는 ${max}를 보낼 수 있습니다.", - "tx_commit_failed": "거래 커밋이 실패했습니다. 지원에 연락하십시오.", - "tx_commit_failed_no_peers": "트랜잭션이 방송에 실패했는데 1 초 정도 후에 다시 시도하십시오.", - "tx_invalid_input": "이 유형의 지불에 잘못 입력 유형을 사용하고 있습니다.", - "tx_no_dust_exception": "너무 작은 금액을 보내면 거래가 거부됩니다. 금액을 늘리십시오.", - "tx_not_enough_inputs_exception": "사용 가능한 입력이 충분하지 않습니다. 코인 컨트롤에서 더 많은 것을 선택하십시오", - "tx_rejected_bip68_final": "거래는 확인되지 않은 입력을 받았으며 수수료로 교체하지 못했습니다.", - "tx_rejected_dust_change": "네트워크 규칙, 낮은 변경 금액 (먼지)에 의해 거부 된 거래. 전부를 보내거나 금액을 줄이십시오.", - "tx_rejected_dust_output": "네트워크 규칙, 낮은 출력 금액 (먼지)에 의해 거부 된 거래. 금액을 늘리십시오.", - "tx_rejected_dust_output_send_all": "네트워크 규칙, 낮은 출력 금액 (먼지)에 의해 거부 된 거래. 동전 제어에서 선택한 동전의 균형을 확인하십시오.", - "tx_rejected_vout_negative": "이 거래 수수료를 지불하기에 잔액이 충분하지 않습니다. 동전 통제하에 동전의 균형을 확인하십시오.", - "tx_wrong_balance_exception": "이 금액을 보내기에 충분한 ${currency}가 충분하지 않습니다.", - "tx_wrong_balance_with_amount_exception": "충분하지 않습니다 ${currency} 총 ${amount} 총 금액을 보내십시오.", - "tx_zero_fee_exception": "0 수수료로 거래를 보낼 수 없습니다. 최신 견적에 대해서는 속도를 높이거나 연결을 확인하십시오.", - "unavailable_balance": "사용할 수 없는 잔액", - "unavailable_balance_description": "사용할 수 없는 잔액: 이 총계에는 보류 중인 거래에 잠겨 있는 자금과 코인 관리 설정에서 적극적으로 동결된 자금이 포함됩니다. 잠긴 잔액은 해당 거래가 완료되면 사용할 수 있게 되며, 동결된 잔액은 동결을 해제하기 전까지 거래에 액세스할 수 없습니다.", - "unconfirmed": "확인되지 않은 잔액", - "understand": "이해 했어요", - "unlock": "터놓다", - "unmatched_currencies": "현재 지갑의 통화가 스캔한 QR의 통화와 일치하지 않습니다.", - "unrestricted_background_service": "무제한 배경 서비스", - "unrestricted_background_service_notice": "배경 동기화를 활성화하려면 무제한 배경 서비스를 활성화해야합니다.", - "unspent_change": "변화", - "unspent_coins_details_title": "사용하지 않은 동전 세부 정보", - "unspent_coins_title": "사용하지 않은 동전", - "unsupported_asset": "이 저작물에 대해 이 작업을 지원하지 않습니다. 지원되는 자산 유형의 지갑을 생성하거나 전환하십시오.", - "update_session": "업데이트 세션", + "transaction_key": "트랜잭션 키", + "transaction_priority_fast": "빠름", + "transaction_priority_fastest": "가장 빠름", + "transaction_priority_medium": "중간", + "transaction_priority_regular": "보통", + "transaction_priority_slow": "느림", + "transaction_sent": "트랜잭션 전송됨!", + "transaction_sent_notice": "1분 후에도 화면이 진행되지 않으면 블록 탐색기와 이메일을 확인하세요.", + "transactions": "트랜잭션", + "transactions_by_date": "날짜별 트랜잭션", + "transport_type": "전송 유형", + "trongrid_history": "TronGrid 내역", + "trusted": "신뢰됨", + "tx_commit_exception_no_dust_on_change": "이 금액으로는 트랜잭션이 거부됩니다. 이 코인으로는 잔돈 없이 ${min}을(를) 보내거나 잔돈이 반환되는 ${max}을(를) 보낼 수 있습니다.", + "tx_commit_failed": "트랜잭션 커밋 실패. 지원팀에 문의하세요.", + "tx_commit_failed_no_peers": "트랜잭션 전파 실패. 잠시 후 다시 시도하세요.", + "tx_invalid_input": "이 유형의 결제에 잘못된 입력 유형을 사용하고 있습니다.", + "tx_no_dust_exception": "너무 적은 금액을 보내 트랜잭션이 거부되었습니다. 금액을 늘려보세요.", + "tx_not_enough_inputs_exception": "사용 가능한 입력이 충분하지 않습니다. 코인 제어에서 더 많이 선택하세요.", + "tx_rejected_bip68_final": "트랜잭션에 미확인 입력이 있으며 수수료로 교체(RBF)하지 못했습니다.", + "tx_rejected_dust_change": "네트워크 규칙에 의해 트랜잭션 거부됨, 낮은 잔돈 금액 (더스트). 전체 보내기를 시도하거나 금액을 줄이세요.", + "tx_rejected_dust_output": "네트워크 규칙에 의해 트랜잭션 거부됨, 낮은 출력 금액 (더스트). 금액을 늘리세요.", + "tx_rejected_dust_output_send_all": "네트워크 규칙에 의해 트랜잭션 거부됨, 낮은 출력 금액 (더스트). 코인 제어에서 선택한 코인 잔액을 확인하세요.", + "tx_rejected_vout_negative": "이 트랜잭션 수수료를 지불하기에 잔액이 부족합니다. 코인 제어에서 코인 잔액을 확인하세요.", + "tx_wrong_balance_exception": "이 금액을 보내기에 ${currency}이(가) 충분하지 않습니다.", + "tx_wrong_balance_with_amount_exception": "총 금액 ${amount}을(를) 보내기에 ${currency}이(가) 충분하지 않습니다.", + "tx_zero_fee_exception": "수수료 0으로 트랜잭션을 보낼 수 없습니다. 요율을 높이거나 연결을 확인하여 최신 예상치를 확인하세요.", + "unavailable_balance": "사용 불가 잔액", + "unavailable_balance_description": "사용 불가 잔액: 이 총액에는 보류 중인 트랜잭션에 잠긴 자금과 코인 제어 설정에서 적극적으로 동결한 자금이 포함됩니다. 잠긴 잔액은 해당 트랜잭션이 완료되면 사용할 수 있게 되며, 동결된 잔액은 동결 해제를 결정할 때까지 트랜잭션에 액세스할 수 없습니다.", + "unconfirmed": "미확정 잔액", + "understand": "이해합니다", + "unlock": "잠금 해제", + "unmatched_currencies": "현재 지갑 통화가 스캔된 QR의 통화와 일치하지 않습니다.", + "unrestricted_background_service": "제한 없는 백그라운드 서비스", + "unrestricted_background_service_notice": "백그라운드 동기화를 활성화하려면 제한 없는 백그라운드 서비스를 활성화해야 합니다.", + "unspent_change": "잔돈", + "unspent_coins_details_title": "미사용 코인 세부 정보", + "unspent_coins_title": "미사용 코인", + "unsupported_asset": "이 자산에 대해 이 작업을 지원하지 않습니다. 지원되는 자산 유형의 지갑을 생성하거나 전환하세요.", + "update_session": "세션 업데이트", "uptime": "가동 시간", - "upto": "최대 ${value}", + "upto": "${value}까지", "usb": "USB", - "use": "사용하다 ", - "use_card_info_three": "디지털 카드를 온라인 또는 비접촉식 결제 수단으로 사용하십시오.", - "use_card_info_two": "디지털 화폐가 아닌 선불 계정에 보유하면 자금이 USD로 변환됩니다.", + "use": "다음으로 전환 ", + "use_card_info_three": "디지털 카드를 온라인 또는 비접촉 결제 방법으로 사용하세요.", + "use_card_info_two": "자금은 디지털 통화가 아닌 선불 계정에 보관될 때 USD로 변환됩니다.", "use_ssl": "SSL 사용", - "use_suggested": "추천 사용", - "use_testnet": "TestNet을 사용하십시오", - "user_rejected_method": "사용자 거부 방법", + "use_suggested": "제안 사용", + "use_testnet": "테스트넷 사용", + "user_rejected_method": "사용자가 방법 거부", "value": "값", - "value_type": "가치 유형", - "variable_pair_not_supported": "이 변수 쌍은 선택한 교환에서 지원되지 않습니다.", + "value_type": "값 유형", + "variable_pair_not_supported": "선택한 교환소에서는 이 변동 쌍이 지원되지 않습니다.", "verification": "검증", - "verify_message": "메시지를 확인하십시오", - "verify_seed": "씨앗을 확인하십시오", - "verify_with_2fa": "케이크 2FA로 확인", + "verify_message": "메시지 검증", + "verify_seed": "시드 검증", + "verify_with_2fa": "Cake 2FA로 검증", "version": "버전 ${currentVersion}", "view_all": "모두 보기", - "view_in_block_explorer": "View in Block Explorer", - "view_key_private": "키보기(은밀한)", - "view_key_public": "키보기 (공공의)", - "view_transaction_on": "View Transaction on ", - "voting_weight": "투표 중량", - "waitFewSecondForTxUpdate": "거래 내역에 거래가 반영될 때까지 몇 초 정도 기다려 주세요.", + "view_in_block_explorer": "블록 탐색기에서 보기", + "view_key_private": "보기 키 (개인)", + "view_key_public": "보기 키 (공개)", + "view_transaction_on": "다음에서 트랜잭션 보기 ", + "voting_weight": "투표 가중치", + "waitFewSecondForTxUpdate": "트랜잭션이 트랜잭션 내역에 반영될 때까지 몇 초간 기다려 주십시오", "wallet": "지갑", "wallet_group": "지갑 그룹", - "wallet_group_description_existing_seed": "이 지갑에 기존 씨앗을 사용하기로 선택했습니다. 확인하거나 작성 해야하는 경우 씨앗을 다시 확인할 수 있습니다.", - "wallet_group_description_four": "완전히 새로운 씨앗으로 지갑을 만듭니다.", - "wallet_group_description_one": "케이크 지갑에서는 a를 만들 수 있습니다", - "wallet_group_description_open_wallet": "그렇지 않으면 지갑을 계속 열 수 있습니다", - "wallet_group_description_three": "사용 가능한 지갑 및/또는 지갑 그룹 스크린을 볼 수 있습니다. 또는 선택하십시오", - "wallet_group_description_two": "씨앗을 공유 할 기존 지갑을 선택함으로써. 각 지갑 그룹은 각 통화 유형의 단일 지갑을 포함 할 수 있습니다. \n\n", - "wallet_group_description_view_seed": "이 씨앗을 언제든지 다시 볼 수 있습니다", - "wallet_group_empty_state_text_one": "호환 지갑 그룹이없는 것 같습니다 !\n\n TAP", - "wallet_group_empty_state_text_two": "아래에서 새로운 것을 만들기 위해.", - "wallet_has_passphrase": "이 지갑에는 암호가 있습니다", - "wallet_keys": "지갑 시드 / 키", - "wallet_list_create_new_wallet": "새 월렛 만들기", + "wallet_group_description_existing_seed": "이 지갑에 기존 시드를 사용하도록 선택했습니다. 확인하거나 적어두어야 하는 경우 시드를 다시 확인할 수 있습니다.", + "wallet_group_description_four": "완전히 새로운 시드로 지갑을 생성합니다.", + "wallet_group_description_one": "Cake Wallet에서는 다음을 생성할 수 있습니다", + "wallet_group_description_open_wallet": "그렇지 않으면 계속해서 지갑을 열 수 있습니다", + "wallet_group_description_three": "사용 가능한 지갑 및/또는 지갑 그룹 화면을 봅니다. 또는 다음을 선택합니다", + "wallet_group_description_two": "시드를 공유할 기존 지갑을 선택하여. 각 지갑 그룹에는 각 통화 유형의 단일 지갑이 포함될 수 있습니다.\n\n다음을 선택할 수 있습니다", + "wallet_group_description_view_seed": "다음에서 이 시드를 항상 다시 볼 수 있습니다", + "wallet_group_empty_state_text_one": "호환되는 지갑 그룹이 없는 것 같습니다!\n\n탭", + "wallet_group_empty_state_text_two": "아래에서 새 그룹을 만듭니다.", + "wallet_has_passphrase": "이 지갑에는 암호 구문이 있습니다", + "wallet_keys": "지갑 시드/키", + "wallet_list_create_new_wallet": "새 지갑 생성", "wallet_list_edit_group_name": "그룹 이름 편집", - "wallet_list_edit_wallet": "지갑 수정", - "wallet_list_failed_to_load": "불러 오지 못했습니다 ${wallet_name} 지갑. ${error}", - "wallet_list_failed_to_remove": "제거하지 못했습니다 ${wallet_name} 지갑. ${error}", - "wallet_list_load_wallet": "지갑로드", - "wallet_list_loading_wallet": "로딩 ${wallet_name} 지갑", - "wallet_list_removing_wallet": "풀이 ${wallet_name} 지갑", - "wallet_list_restore_wallet": "월렛 복원", - "wallet_list_title": "모네로 월렛", + "wallet_list_edit_wallet": "지갑 편집", + "wallet_list_failed_to_load": "${wallet_name} 지갑 로드 실패. ${error}", + "wallet_list_failed_to_remove": "${wallet_name} 지갑 제거 실패. ${error}", + "wallet_list_load_wallet": "지갑 로드", + "wallet_list_loading_wallet": "${wallet_name} 지갑 로딩 중", + "wallet_list_removing_wallet": "${wallet_name} 지갑 제거 중", + "wallet_list_restore_wallet": "지갑 복구", + "wallet_list_title": "모네로 지갑", "wallet_list_wallet_name": "지갑 이름", - "wallet_menu": "월렛 메뉴", + "wallet_menu": "메뉴", "wallet_name": "지갑 이름", - "wallet_name_exists": "해당 이름의 지갑이 이미 존재합니다.", - "wallet_password_is_empty": "지갑 암호는 비어 있습니다. 지갑 암호는 비어 있지 않아야합니다", + "wallet_name_exists": "해당 이름의 지갑이 이미 존재합니다. 다른 이름을 선택하거나 먼저 다른 지갑의 이름을 변경하십시오.", + "wallet_password_is_empty": "지갑 비밀번호가 비어 있습니다. 지갑 비밀번호는 비워둘 수 없습니다.", "wallet_recovery_height": "복구 높이", - "wallet_restoration_store_incorrect_seed_length": "시드 길이가 잘못되었습니다", + "wallet_restoration_store_incorrect_seed_length": "잘못된 시드 길이", "wallet_seed": "지갑 시드", "wallet_seed_legacy": "레거시 지갑 시드", - "wallet_store_monero_wallet": "모네로 월렛", - "walletConnect": "월렛커넥트", + "wallet_store_monero_wallet": "모네로 지갑", + "walletConnect": "WalletConnect", "wallets": "지갑", "warning": "경고", - "welcome": "환영 에", - "welcome_subtitle_new_wallet": "신선하게 시작하려면 아래에서 새 지갑을 만들면 레이스로 떠날 것입니다.", - "welcome_subtitle_restore_wallet": "케이크에 가져 오려는 기존 지갑이 있다면 기존 지갑 복원을 선택하면 프로세스를 안내해 드리겠습니다.", + "welcome": "환영합니다", + "welcome_subtitle_new_wallet": "새로 시작하려면 아래의 새 지갑 생성을 탭하면 바로 시작할 수 있습니다.", + "welcome_subtitle_restore_wallet": "Cake로 가져오려는 기존 지갑이 있는 경우 기존 지갑 복구를 선택하면 과정을 안내해 드립니다.", "welcome_to_cakepay": "Cake Pay에 오신 것을 환영합니다!", - "what_is_silent_payments": "조용한 지불이란 무엇입니까?", + "what_is_silent_payments": "사일런트 페이먼트란 무엇인가요?", "widgets_address": "주소", "widgets_or": "또는", - "widgets_restore_from_blockheight": "블록 높이에서 복원", - "widgets_restore_from_date": "날짜에서 복원", - "widgets_seed": "씨", + "widgets_restore_from_blockheight": "블록 높이에서 복구", + "widgets_restore_from_date": "날짜에서 복구", + "widgets_seed": "시드", "wouoldLikeToConnect": "연결하고 싶습니다", - "write_down_backup_password": "백업 파일 가져 오기에 사용되는 백업 암호를 적어 두십시오.", - "xlm_extra_info": "교환을 위해 XLM 거래를 보낼 때 메모 ID를 지정하는 것을 잊지 마십시오", - "xmr_available_balance": "사용 가능한 잔액", - "xmr_full_balance": "풀 밸런스", + "write_down_backup_password": "백업 파일 가져오기에 사용되는 백업 비밀번호를 적어두세요.", + "xlm_extra_info": "교환을 위해 XLM 트랜잭션을 보낼 때 메모 ID를 지정하는 것을 잊지 마세요.", + "xmr_available_balance": "사용 가능 잔액", + "xmr_full_balance": "전체 잔액", "xmr_hidden": "숨김", "xmr_to_error": "XMR.TO 오류", - "xmr_to_error_description": "금액이 잘못되었습니다. 소수점 이하 최대 8 자리", - "xrp_extra_info": "교환을 위해 XRP 트랜잭션을 보내는 동안 대상 태그를 지정하는 것을 잊지 마십시오", + "xmr_to_error_description": "잘못된 금액입니다. 소수점 이하 최대 8자리", + "xrp_extra_info": "교환을 위해 XRP 트랜잭션을 보낼 때 목적지 태그를 지정하는 것을 잊지 마세요.", "yat": "Yat", "yat_address": "Yat 주소", - "yat_alert_content": "Cake Wallet 사용자는 이제 독특한 이모티콘 기반 사용자 이름으로 좋아하는 모든 통화를 보내고 받을 수 있습니다.", - "yat_alert_title": "Yat으로 더 쉽게 암호화폐를 보내고 받으십시오.", + "yat_alert_content": "Cake Wallet 사용자는 이제 고유한 이모지 기반 사용자 이름으로 좋아하는 모든 통화를 보내고 받을 수 있습니다.", + "yat_alert_title": "Yat으로 암호화폐를 더 쉽게 보내고 받으세요", "yat_error": "Yat 오류", - "yat_error_content": "이 Yat와 연결된 주소가 없습니다. 다른 Yat 시도", - "yat_popup_content": "이제 Yat(이모티콘 기반의 짧은 사용자 이름)으로 Cake Wallet에서 암호화폐를 보내고 받을 수 있습니다. 설정 화면에서 언제든지 Yats 관리", - "yat_popup_title": "지갑 주소를 이모티콘으로 만들 수 있습니다.", + "yat_error_content": "이 Yat과 연결된 주소가 없습니다. 다른 Yat을 시도하세요", + "yat_popup_content": "이제 짧은 이모지 기반 사용자 이름인 Yat으로 Cake Wallet에서 암호화폐를 보내고 받을 수 있습니다. 설정 화면에서 언제든지 Yat을 관리하세요.", + "yat_popup_title": "지갑 주소를 이모지로 만들 수 있습니다.", "yesterday": "어제", - "you_now_have_debit_card": "이제 직불카드가 있습니다.", - "you_pay": "당신이 지불합니다", - "you_will_get": "로 변환하다", - "you_will_receive_estimated_amount": "(추정 )을 받게됩니다.", - "you_will_send": "다음에서 변환", - "youCanGoBackToYourDapp": "이제 DAPP로 돌아갈 수 있습니다", - "YY": "YY", + "you_now_have_debit_card": "이제 직불 카드가 있습니다", + "you_pay": "지불 금액", + "you_will_get": "전환 대상", + "you_will_receive_estimated_amount": "받게 될 금액 (예상)", + "you_will_send": "전환 출처", + "youCanGoBackToYourDapp": "이제 dApp으로 돌아갈 수 있습니다", "yy": "YY" -} \ No newline at end of file +} From cab47923884111f59076643c3feb2e245fd8318d Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 7 May 2025 17:22:03 +0300 Subject: [PATCH 068/142] fix: possible context null check issue in exchange trade (#2253) --- lib/src/screens/exchange_trade/exchange_trade_page.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/src/screens/exchange_trade/exchange_trade_page.dart b/lib/src/screens/exchange_trade/exchange_trade_page.dart index 4b7b8ace9..d68a63f9c 100644 --- a/lib/src/screens/exchange_trade/exchange_trade_page.dart +++ b/lib/src/screens/exchange_trade/exchange_trade_page.dart @@ -307,9 +307,7 @@ class ExchangeTradeState extends State { if (state is TransactionCommitted) { WidgetsBinding.instance.addPostFrameCallback((_) async { - if (!context.mounted) { - return; - } + if (!mounted) return; await showModalBottomSheet( context: context, @@ -323,7 +321,7 @@ class ExchangeTradeState extends State { actionButtonKey: ValueKey('send_page_sent_dialog_ok_button_key'), actionButton: () { Navigator.of(bottomSheetContext).pop(); - if (context.mounted) { + if (mounted) { Navigator.of(context).pushNamedAndRemoveUntil( Routes.dashboard, (route) => false, From c6f9d12a8e58d08b22cc4c7b8d9e1c49f6303fb5 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Wed, 7 May 2025 16:46:17 +0200 Subject: [PATCH 069/142] fix: selection of the wordlist for seed validation on the restore from seed page (#2255) --- lib/src/screens/restore/wallet_restore_from_seed_form.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/screens/restore/wallet_restore_from_seed_form.dart b/lib/src/screens/restore/wallet_restore_from_seed_form.dart index af8261662..431574b38 100644 --- a/lib/src/screens/restore/wallet_restore_from_seed_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_seed_form.dart @@ -215,7 +215,7 @@ class WalletRestoreFromSeedFormState extends State { items: _getItems(), selectedAtIndex: isPolyseed ? 1 - : seedTypeController.value.text.contains("14") && widget.type == WalletType.wownero + : (seedTypeController.value.text.contains("14") && widget.type == WalletType.wownero) || isBip39 ? 2 : 0, mainAxisAlignment: MainAxisAlignment.start, @@ -325,9 +325,9 @@ class WalletRestoreFromSeedFormState extends State { '${language.replaceAll("POLYSEED_", "")} (Seed language)'; void _changeSeedType(MoneroSeedType item) { - _setSeedType(item); - _changeLanguage('English'); widget.seedSettingsViewModel.setMoneroSeedType(item); + _setSeedType(item); + _changeLanguage('English', isPolyseed || isBip39); } void _setSeedType(MoneroSeedType item) { From a9e05073a5995a9a18384656aae23f2dc2b18e25 Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 7 May 2025 17:48:13 +0300 Subject: [PATCH 070/142] fix: include destination parameter in payment method fetch request (#2254) --- lib/buy/onramper/onramper_buy_provider.dart | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/buy/onramper/onramper_buy_provider.dart b/lib/buy/onramper/onramper_buy_provider.dart index 6b903342f..dc9812d1d 100644 --- a/lib/buy/onramper/onramper_buy_provider.dart +++ b/lib/buy/onramper/onramper_buy_provider.dart @@ -60,9 +60,15 @@ class OnRamperBuyProvider extends BuyProvider { Future> getAvailablePaymentTypes( String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async { - final params = {'type': isBuyAction ? 'buy' : 'sell'}; + final normalizedCryptoCurrency = + cryptoCurrency.title + _getNormalizeNetwork(cryptoCurrency); - final url = Uri.https(_baseApiUrl, '$supported$paymentTypes/$fiatCurrency', params); + final sourceCurrency = (isBuyAction ? fiatCurrency : normalizedCryptoCurrency).toLowerCase(); + final destinationCurrency = (isBuyAction ? normalizedCryptoCurrency : fiatCurrency).toLowerCase(); + + final params = {'type': isBuyAction ? 'buy' : 'sell', 'destination' : destinationCurrency}; + + final url = Uri.https(_baseApiUrl, '$supported$paymentTypes/$sourceCurrency', params); try { final response = @@ -75,7 +81,9 @@ class OnRamperBuyProvider extends BuyProvider { .map((item) => PaymentMethod.fromOnramperJson(item as Map)) .toList(); } else { - printV('Failed to fetch available payment types'); + final responseBody = + jsonDecode(response.body) as Map; + printV('Failed to fetch available payment types: ${responseBody['message']}'); return []; } } catch (e) { From 5b43cb5373295bbae521117a90a1af59755a2ea6 Mon Sep 17 00:00:00 2001 From: Serhii Date: Thu, 8 May 2025 23:47:34 +0300 Subject: [PATCH 071/142] fix: hide balance for monero accounts (#2256) --- lib/di.dart | 2 +- .../monero_account_list_view_model.dart | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/di.dart b/lib/di.dart index 067fd7c4f..f6de95c4d 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -839,7 +839,7 @@ Future setup({ if (wallet.type == WalletType.monero || wallet.type == WalletType.wownero || wallet.type == WalletType.haven) { - return MoneroAccountListViewModel(wallet); + return MoneroAccountListViewModel(wallet,getIt.get()); } throw Exception( 'Unexpected wallet type: ${wallet.type} for generate Monero AccountListViewModel'); diff --git a/lib/view_model/monero_account_list/monero_account_list_view_model.dart b/lib/view_model/monero_account_list/monero_account_list_view_model.dart index 4ecffcfb3..37b28980a 100644 --- a/lib/view_model/monero_account_list/monero_account_list_view_model.dart +++ b/lib/view_model/monero_account_list/monero_account_list_view_model.dart @@ -1,3 +1,5 @@ +import 'package:cake_wallet/entities/balance_display_mode.dart'; +import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/wallet_type.dart'; @@ -12,7 +14,9 @@ class MoneroAccountListViewModel = MoneroAccountListViewModelBase with _$MoneroAccountListViewModel; abstract class MoneroAccountListViewModelBase with Store { - MoneroAccountListViewModelBase(this._wallet) : scrollOffsetFromTop = 0; + MoneroAccountListViewModelBase(this._wallet,this.settingsStore) : scrollOffsetFromTop = 0; + + final SettingsStore settingsStore; @observable double scrollOffsetFromTop; @@ -26,13 +30,14 @@ abstract class MoneroAccountListViewModelBase with Store { @computed List get accounts { + final hideBalance = settingsStore.balanceDisplayMode == BalanceDisplayMode.hiddenBalance; if (_wallet.type == WalletType.monero) { return monero !.getAccountList(_wallet) .accounts.map((acc) => AccountListItem( label: acc.label, id: acc.id, - balance: acc.balance, + balance: hideBalance ? '●●●●●●' : acc.balance, isSelected: acc.id == monero!.getCurrentAccount(_wallet).id)) .toList(); } @@ -43,7 +48,7 @@ abstract class MoneroAccountListViewModelBase with Store { .accounts.map((acc) => AccountListItem( label: acc.label, id: acc.id, - balance: acc.balance, + balance: hideBalance ? '●●●●●●' : acc.balance, isSelected: acc.id == wownero!.getCurrentAccount(_wallet).id)) .toList(); } From b28ea4199c0ac28b3a504ab4ad25253754e8434f Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Fri, 9 May 2025 15:01:09 +0300 Subject: [PATCH 072/142] Remove SwapTrade --- lib/exchange/provider/swaptrade_exchange_provider.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/exchange/provider/swaptrade_exchange_provider.dart b/lib/exchange/provider/swaptrade_exchange_provider.dart index d3f64b712..b86482335 100644 --- a/lib/exchange/provider/swaptrade_exchange_provider.dart +++ b/lib/exchange/provider/swaptrade_exchange_provider.dart @@ -47,10 +47,10 @@ class SwapTradeExchangeProvider extends ExchangeProvider { String get title => 'SwapTrade'; @override - bool get isAvailable => true; + bool get isAvailable => false; @override - bool get isEnabled => true; + bool get isEnabled => false; @override bool get supportsFixedRate => false; @@ -59,7 +59,7 @@ class SwapTradeExchangeProvider extends ExchangeProvider { ExchangeProviderDescription get description => ExchangeProviderDescription.swapTrade; @override - Future checkIsAvailable() async => true; + Future checkIsAvailable() async => false; @override Future fetchLimits({ From b1fc9ba2e538f03d44c33d6b2b9e980825c06712 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Sun, 11 May 2025 01:04:47 +0300 Subject: [PATCH 073/142] add words count next to seed type --- lib/entities/seed_type.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/entities/seed_type.dart b/lib/entities/seed_type.dart index 0548d9cee..0c5632875 100644 --- a/lib/entities/seed_type.dart +++ b/lib/entities/seed_type.dart @@ -8,10 +8,10 @@ class MoneroSeedType extends EnumerableItem with Serializable { static const defaultSeedType = polyseed; - static const legacy = MoneroSeedType(raw: 0, title: 'Legacy'); - static const polyseed = MoneroSeedType(raw: 1, title: 'Polyseed'); + static const legacy = MoneroSeedType(raw: 0, title: 'Legacy (25 words)'); + static const polyseed = MoneroSeedType(raw: 1, title: 'Polyseed (16 words)'); static const wowneroSeed = MoneroSeedType(raw: 2, title: 'Wownero'); - static const bip39 = MoneroSeedType(raw: 3, title: 'BIP39'); + static const bip39 = MoneroSeedType(raw: 3, title: 'BIP39 (12 words)'); static MoneroSeedType deserialize({required int raw}) { switch (raw) { From b4e16a05b1a7061044451aca942752a954dae33d Mon Sep 17 00:00:00 2001 From: eveneast <166489430+eveneast@users.noreply.github.com> Date: Sun, 11 May 2025 23:38:00 +0800 Subject: [PATCH 074/142] chore: fix some typos in comment (#2258) Signed-off-by: eveneast --- cw_tron/lib/tron_client.dart | 2 +- cw_zano/lib/api/model/destination.dart | 2 +- lib/core/node_address_validator.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cw_tron/lib/tron_client.dart b/cw_tron/lib/tron_client.dart index 8eca02af6..ee93fbd53 100644 --- a/cw_tron/lib/tron_client.dart +++ b/cw_tron/lib/tron_client.dart @@ -428,7 +428,7 @@ class TronClient { if (!request.isSuccess) { log("Tron TRC20 error: ${request.error} \n ${request.respose}"); throw Exception( - 'An error occured while creating the transfer request. Please try again.', + 'An error occurred while creating the transfer request. Please try again.', ); } diff --git a/cw_zano/lib/api/model/destination.dart b/cw_zano/lib/api/model/destination.dart index 3db4f6652..13f4673ab 100644 --- a/cw_zano/lib/api/model/destination.dart +++ b/cw_zano/lib/api/model/destination.dart @@ -1,5 +1,5 @@ class Destination { - final BigInt amount; // transfered as string + final BigInt amount; // transferred as string final String address; final String assetId; diff --git a/lib/core/node_address_validator.dart b/lib/core/node_address_validator.dart index 68478d501..ce8709a3c 100644 --- a/lib/core/node_address_validator.dart +++ b/lib/core/node_address_validator.dart @@ -19,7 +19,7 @@ class NodePathValidator extends TextValidator { } // NodeAddressValidatorDecredBlankException allows decred to send a blank ip -// address which effectively clears the current set persistant peer. +// address which effectively clears the current set persistent peer. class NodeAddressValidatorDecredBlankException extends TextValidator { NodeAddressValidatorDecredBlankException() : super( From e7fd1582a875c8c8a2222ae3320e8fe0da27c4b0 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Mon, 12 May 2025 18:08:43 +0300 Subject: [PATCH 075/142] enable macos deps in prebuilts download --- tool/download_moneroc_prebuilds.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tool/download_moneroc_prebuilds.dart b/tool/download_moneroc_prebuilds.dart index 8889a1bc1..fd9cd6dd6 100644 --- a/tool/download_moneroc_prebuilds.dart +++ b/tool/download_moneroc_prebuilds.dart @@ -22,6 +22,8 @@ final List triplets = [ // "aarch64-apple-darwin11", // apple silicon macbooks (contrib) - not used by cake // "host-apple-darwin", // not available on CI (yet) // "x86_64-host-apple-darwin", // not available on CI (yet) + "aarch64-apple-darwin", // apple silicon macbooks + "x86_64-apple-darwin", // intel macbooks "aarch64-host-apple-darwin", // apple silicon macbooks (local builds) "aarch64-apple-ios", "aarch64-apple-iossimulator", From 82e3ebf4fa9839e56da02ca53e55c64098c66394 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Mon, 12 May 2025 19:33:14 +0200 Subject: [PATCH 076/142] implement-payjoin (#1949) * Initial Payjoin * Initial Payjoin * More payjoin stuff * Minor fixes * Minor fixes * Minor cleanup * Minor cleanup * Minor cleanup * Minor cleanup * Minor cleanup * Minor cleanup * Fix minor bug causes by data inconsistency in the btc utxos * Minor cleanup * Minor cleanup * Minor cleanup * Minor cleanup * Initial Payjoin * Initial Payjoin * More payjoin stuff * Minor fixes * Minor fixes * Minor cleanup * Minor cleanup * Minor cleanup * Minor cleanup * Minor cleanup * Minor cleanup * Fix minor bug causes by data inconsistency in the btc utxos * Minor cleanup * Minor cleanup * Minor cleanup * Minor cleanup * Fix Rebase issues * Move PJ Receiver to isolate * Add Payjoin Setting * Payjoin Sender are now isolated * Added Payjoin sessions to tx overview. Fix Fee issue with payjoin * Clean up code * Fix taproot for payjoin * Fix CI Errors * Add Payjoin UI elements and details page * Add Payjoin UI elements and details page * Fix Translations * feat: Detect Payjoin URIs in pasted text and show to the User sending Payjoin * feat: rename pjUri to payjoinURI for more code clarity * Update res/values/strings_pl.arb Co-authored-by: cyan * Update cw_bitcoin/lib/payjoin/manager.dart Co-authored-by: cyan * Update cw_bitcoin/lib/payjoin/manager.dart Co-authored-by: cyan * feat: Disable Payjoin per default * feat: Disable Payjoin fully if disabled or no Inputs available * feat: Resume Payjoin if app comes back to foreground * chore: Revert overly aggressive code formats * feat: show correct Payjoin amount for receivers * feat: Improved payjoin status * feat: Show payjoin errors on payjoin details screen * deps: update flutter to 3.27.4 * feat: Revert localisations * bug: Remove duplicate transaction id on payjoin details * style: remove double await in payjoin sender * refactor(cw_bitcoin): Refactor method signatures and convert constructor to factory * refactor(cw_bitcoin): Refactor wallet service and PSBT signer for cleaner code Removed unnecessary `CakeHive` dependency and refactored `BitcoinWallet` initialization to use `payjoinSessionSource`. Improved code readability in `PsbtSigner` by reformatting lines and simplifying constructor methods for `UtxoWithPrivateKey`. * fix: Resume Payjoin Sessions and load PJUri after sleep * feat: Add "Copy Payjoin URL button" to receive screen * fix: Add "Payjoin enabled"-Box below QR Code on the receive screen * fix: Set payjoin_enabled color to black independent of the theme * refactor: Payjoin session management and cleanup unused code. --------- Co-authored-by: Omar Hatem Co-authored-by: cyan --- assets/images/payjoin.png | Bin 0 -> 30662 bytes cw_bitcoin/lib/address_from_output.dart | 44 ++- .../lib/bitcoin_transaction_credentials.dart | 10 +- cw_bitcoin/lib/bitcoin_wallet.dart | 227 ++++++++++--- cw_bitcoin/lib/bitcoin_wallet_addresses.dart | 26 ++ cw_bitcoin/lib/bitcoin_wallet_service.dart | 12 +- cw_bitcoin/lib/electrum_wallet.dart | 6 + cw_bitcoin/lib/payjoin/manager.dart | 298 ++++++++++++++++++ .../lib/payjoin/payjoin_receive_worker.dart | 219 +++++++++++++ .../lib/payjoin/payjoin_send_worker.dart | 119 +++++++ .../lib/payjoin/payjoin_session_errors.dart | 16 + cw_bitcoin/lib/payjoin/storage.dart | 95 ++++++ .../lib/pending_bitcoin_transaction.dart | 9 + cw_bitcoin/lib/psbt/signer.dart | 263 ++++++++++++++++ .../transaction_builder.dart} | 0 cw_bitcoin/lib/psbt/utils.dart | 41 +++ cw_bitcoin/lib/psbt/v0_deserialize.dart | 52 +++ cw_bitcoin/lib/psbt/v0_finalizer.dart | 143 +++++++++ cw_bitcoin/pubspec.lock | 53 +++- cw_bitcoin/pubspec.yaml | 5 + cw_core/lib/hive_type_ids.dart | 1 + cw_core/lib/payjoin_session.dart | 67 ++++ cw_core/pubspec.lock | 2 +- cw_decred/pubspec.lock | 2 +- cw_monero/pubspec.lock | 2 +- cw_nano/pubspec.lock | 2 +- cw_wownero/pubspec.lock | 2 +- cw_zano/pubspec.lock | 2 +- lib/bitcoin/cw_bitcoin.dart | 82 +++-- lib/di.dart | 25 ++ lib/entities/preferences_key.dart | 1 + lib/main.dart | 9 + lib/reactions/on_current_wallet_change.dart | 5 + lib/router.dart | 9 + lib/routes.dart | 1 + .../dashboard/pages/transactions_page.dart | 21 ++ .../widgets/payjoin_transaction_row.dart | 99 ++++++ .../payjoin_details/payjoin_details_page.dart | 77 +++++ .../screens/receive/widgets/qr_widget.dart | 86 +++-- lib/src/screens/root/root.dart | 10 +- lib/src/screens/send/send_page.dart | 2 +- lib/src/screens/send/widgets/send_card.dart | 30 +- lib/src/screens/settings/privacy_page.dart | 8 + .../trade_details_status_item.dart | 4 +- lib/src/widgets/address_text_field.dart | 34 +- lib/src/widgets/standard_list_status_row.dart | 6 +- .../dashboard/payjoin_transactions_store.dart | 47 +++ lib/store/settings_store.dart | 11 + lib/utils/payment_request.dart | 11 +- .../dashboard/dashboard_view_model.dart | 41 ++- .../payjoin_transaction_list_item.dart | 34 ++ .../payjoin_details_view_model.dart | 122 +++++++ lib/view_model/send/send_view_model.dart | 7 + .../settings/privacy_settings_view_model.dart | 38 ++- .../wallet_address_list_view_model.dart | 25 +- res/values/strings_ar.arb | 9 +- res/values/strings_bg.arb | 9 +- res/values/strings_cs.arb | 9 +- res/values/strings_de.arb | 7 + res/values/strings_en.arb | 7 + res/values/strings_es.arb | 7 + res/values/strings_fr.arb | 9 +- res/values/strings_ha.arb | 9 +- res/values/strings_hi.arb | 9 +- res/values/strings_hr.arb | 9 +- res/values/strings_hy.arb | 9 +- res/values/strings_id.arb | 9 +- res/values/strings_it.arb | 9 +- res/values/strings_ja.arb | 9 +- res/values/strings_ko.arb | 7 + res/values/strings_my.arb | 9 +- res/values/strings_nl.arb | 9 +- res/values/strings_pl.arb | 9 +- res/values/strings_pt.arb | 9 +- res/values/strings_ru.arb | 9 +- res/values/strings_th.arb | 9 +- res/values/strings_tl.arb | 9 +- res/values/strings_tr.arb | 9 +- res/values/strings_uk.arb | 9 +- res/values/strings_ur.arb | 9 +- res/values/strings_vi.arb | 9 +- res/values/strings_yo.arb | 9 +- res/values/strings_zh.arb | 9 +- tool/configure.dart | 13 +- 84 files changed, 2622 insertions(+), 198 deletions(-) create mode 100644 assets/images/payjoin.png create mode 100644 cw_bitcoin/lib/payjoin/manager.dart create mode 100644 cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart create mode 100644 cw_bitcoin/lib/payjoin/payjoin_send_worker.dart create mode 100644 cw_bitcoin/lib/payjoin/payjoin_session_errors.dart create mode 100644 cw_bitcoin/lib/payjoin/storage.dart create mode 100644 cw_bitcoin/lib/psbt/signer.dart rename cw_bitcoin/lib/{psbt_transaction_builder.dart => psbt/transaction_builder.dart} (100%) create mode 100644 cw_bitcoin/lib/psbt/utils.dart create mode 100644 cw_bitcoin/lib/psbt/v0_deserialize.dart create mode 100644 cw_bitcoin/lib/psbt/v0_finalizer.dart create mode 100644 cw_core/lib/payjoin_session.dart create mode 100644 lib/src/screens/dashboard/widgets/payjoin_transaction_row.dart create mode 100644 lib/src/screens/payjoin_details/payjoin_details_page.dart create mode 100644 lib/store/dashboard/payjoin_transactions_store.dart create mode 100644 lib/view_model/dashboard/payjoin_transaction_list_item.dart create mode 100644 lib/view_model/payjoin_details_view_model.dart diff --git a/assets/images/payjoin.png b/assets/images/payjoin.png new file mode 100644 index 0000000000000000000000000000000000000000..1ba3dccdbfa7799cd0cd78560ecb23048e0c1cd2 GIT binary patch literal 30662 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 outputs; final BitcoinTransactionPriority? priority; final int? feeRate; final UnspentCoinType coinTypeToSpendFrom; + final String? payjoinUri; } diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index 7135d0a7a..a23b72660 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -3,22 +3,33 @@ import 'dart:convert'; import 'package:bip39/bip39.dart' as bip39; import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:cw_bitcoin/address_from_output.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; -import 'package:cw_bitcoin/psbt_transaction_builder.dart'; -import 'package:cw_core/encryption_file_utils.dart'; -import 'package:cw_bitcoin/electrum_derivations.dart'; +import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_balance.dart'; +import 'package:cw_bitcoin/electrum_derivations.dart'; import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; +import 'package:cw_bitcoin/payjoin/manager.dart'; +import 'package:cw_bitcoin/payjoin/storage.dart'; +import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; +import 'package:cw_bitcoin/psbt/signer.dart'; +import 'package:cw_bitcoin/psbt/transaction_builder.dart'; +import 'package:cw_bitcoin/psbt/v0_deserialize.dart'; +import 'package:cw_bitcoin/psbt/v0_finalizer.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/encryption_file_utils.dart'; +import 'package:cw_core/payjoin_session.dart'; +import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_keys_file.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:ledger_bitcoin/ledger_bitcoin.dart'; +import 'package:ledger_bitcoin/psbt.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:mobx/mobx.dart'; @@ -31,6 +42,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { required String password, required WalletInfo walletInfo, required Box unspentCoinsInfo, + required Box payjoinBox, required EncryptionFileUtils encryptionFileUtils, Uint8List? seedBytes, String? mnemonic, @@ -71,20 +83,21 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { // String derivationPath = walletInfo.derivationInfo!.derivationPath!; // String sideDerivationPath = derivationPath.substring(0, derivationPath.length - 1) + "1"; // final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType); - walletAddresses = BitcoinWalletAddresses( - walletInfo, - initialAddresses: initialAddresses, - initialRegularAddressIndex: initialRegularAddressIndex, - initialChangeAddressIndex: initialChangeAddressIndex, - initialSilentAddresses: initialSilentAddresses, - initialSilentAddressIndex: initialSilentAddressIndex, - mainHd: hd, - sideHd: accountHD.childKey(Bip32KeyIndex(1)), - network: networkParam ?? network, - masterHd: - seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null, - isHardwareWallet: walletInfo.isHardwareWallet, - ); + + payjoinManager = PayjoinManager(PayjoinStorage(payjoinBox), this); + walletAddresses = BitcoinWalletAddresses(walletInfo, + initialAddresses: initialAddresses, + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex, + initialSilentAddresses: initialSilentAddresses, + initialSilentAddressIndex: initialSilentAddressIndex, + mainHd: hd, + sideHd: accountHD.childKey(Bip32KeyIndex(1)), + network: networkParam ?? network, + masterHd: + seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null, + isHardwareWallet: walletInfo.isHardwareWallet, + payjoinManager: payjoinManager); autorun((_) { this.walletAddresses.isEnabledAutoGenerateSubaddress = @@ -100,6 +113,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { required String password, required WalletInfo walletInfo, required Box unspentCoinsInfo, + required Box payjoinBox, required EncryptionFileUtils encryptionFileUtils, String? passphrase, String? addressPageType, @@ -122,9 +136,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { break; case DerivationType.electrum: default: - seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); + seedBytes = + await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); break; } + return BitcoinWallet( mnemonic: mnemonic, passphrase: passphrase ?? "", @@ -141,6 +157,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { initialChangeAddressIndex: initialChangeAddressIndex, addressPageType: addressPageType, networkParam: network, + payjoinBox: payjoinBox, ); } @@ -148,6 +165,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { required String name, required WalletInfo walletInfo, required Box unspentCoinsInfo, + required Box payjoinBox, required String password, required EncryptionFileUtils encryptionFileUtils, required bool alwaysScan, @@ -204,7 +222,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { if (mnemonic != null) { switch (walletInfo.derivationInfo!.derivationType) { case DerivationType.electrum: - seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); + seedBytes = + await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); break; case DerivationType.bip39: default: @@ -217,24 +236,24 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { } return BitcoinWallet( - mnemonic: mnemonic, - xpub: keysData.xPub, - password: password, - passphrase: passphrase, - walletInfo: walletInfo, - unspentCoinsInfo: unspentCoinsInfo, - initialAddresses: snp?.addresses, - initialSilentAddresses: snp?.silentAddresses, - initialSilentAddressIndex: snp?.silentAddressIndex ?? 0, - initialBalance: snp?.balance, - encryptionFileUtils: encryptionFileUtils, - seedBytes: seedBytes, - initialRegularAddressIndex: snp?.regularAddressIndex, - initialChangeAddressIndex: snp?.changeAddressIndex, - addressPageType: snp?.addressPageType, - networkParam: network, - alwaysScan: alwaysScan, - ); + mnemonic: mnemonic, + xpub: keysData.xPub, + password: password, + passphrase: passphrase, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + initialAddresses: snp?.addresses, + initialSilentAddresses: snp?.silentAddresses, + initialSilentAddressIndex: snp?.silentAddressIndex ?? 0, + initialBalance: snp?.balance, + encryptionFileUtils: encryptionFileUtils, + seedBytes: seedBytes, + initialRegularAddressIndex: snp?.regularAddressIndex, + initialChangeAddressIndex: snp?.changeAddressIndex, + addressPageType: snp?.addressPageType, + networkParam: network, + alwaysScan: alwaysScan, + payjoinBox: payjoinBox); } LedgerConnection? _ledgerConnection; @@ -247,20 +266,25 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { derivationPath: walletInfo.derivationInfo!.derivationPath!); } - @override - Future buildHardwareWalletTransaction({ + late final PayjoinManager payjoinManager; + + bool get isPayjoinAvailable => unspentCoinsInfo.values + .where((element) => + element.walletId == id && element.isSending && !element.isFrozen) + .isNotEmpty; + + Future buildPsbt({ required List outputs, required BigInt fee, required BasedUtxoNetwork network, required List utxos, required Map publicKeys, + required Uint8List masterFingerprint, String? memo, bool enableRBF = false, BitcoinOrdering inputOrdering = BitcoinOrdering.bip69, BitcoinOrdering outputOrdering = BitcoinOrdering.bip69, }) async { - final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint(); - final psbtReadyInputs = []; for (final utxo in utxos) { final rawTx = @@ -278,13 +302,128 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { )); } - final psbt = PSBTTransactionBuild( - inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF); + return PSBTTransactionBuild( + inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF) + .psbt; + } - final rawHex = await _bitcoinLedgerApp!.signPsbt(psbt: psbt.psbt); + @override + Future buildHardwareWalletTransaction({ + required List outputs, + required BigInt fee, + required BasedUtxoNetwork network, + required List utxos, + required Map publicKeys, + String? memo, + bool enableRBF = false, + BitcoinOrdering inputOrdering = BitcoinOrdering.bip69, + BitcoinOrdering outputOrdering = BitcoinOrdering.bip69, + }) async { + final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint(); + + final psbt = await buildPsbt( + outputs: outputs, + fee: fee, + network: network, + utxos: utxos, + publicKeys: publicKeys, + masterFingerprint: masterFingerprint, + memo: memo, + enableRBF: enableRBF, + inputOrdering: inputOrdering, + outputOrdering: outputOrdering, + ); + + final rawHex = await _bitcoinLedgerApp!.signPsbt(psbt: psbt); return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex)); } + @override + Future createTransaction(Object credentials) async { + credentials = credentials as BitcoinTransactionCredentials; + + final tx = (await super.createTransaction(credentials)) + as PendingBitcoinTransaction; + + final payjoinUri = credentials.payjoinUri; + if (payjoinUri == null) return tx; + + final transaction = await buildPsbt( + utxos: tx.utxos, + outputs: tx.outputs + .map((e) => BitcoinOutput( + address: addressFromScript(e.scriptPubKey), + value: e.amount, + isSilentPayment: e.isSilentPayment, + isChange: e.isChange, + )) + .toList(), + fee: BigInt.from(tx.fee), + network: network, + memo: credentials.outputs.first.memo, + outputOrdering: BitcoinOrdering.none, + enableRBF: true, + publicKeys: tx.publicKeys!, + masterFingerprint: Uint8List(0)); + + final originalPsbt = await signPsbt( + base64.encode(transaction.asPsbtV0()), getUtxoWithPrivateKeys()); + + tx.commitOverride = () async { + final sender = await payjoinManager.initSender( + payjoinUri, originalPsbt, int.parse(tx.feeRate)); + payjoinManager.spawnNewSender( + sender: sender, pjUrl: payjoinUri, amount: BigInt.from(tx.amount)); + }; + + return tx; + } + + List getUtxoWithPrivateKeys() => unspentCoins + .where((e) => (e.isSending && !e.isFrozen)) + .map((unspent) => UtxoWithPrivateKey.fromUnspent(unspent, this)) + .toList(); + + Future commitPsbt(String finalizedPsbt) { + final psbt = PsbtV2()..deserializeV0(base64.decode(finalizedPsbt)); + + final btcTx = + BtcTransaction.fromRaw(BytesUtils.toHexString(psbt.extract())); + + return PendingBitcoinTransaction( + btcTx, + type, + electrumClient: electrumClient, + amount: 0, + fee: 0, + feeRate: "", + network: network, + hasChange: true, + ).commit(); + } + + Future signPsbt( + String preProcessedPsbt, List utxos) async { + final psbt = PsbtV2()..deserializeV0(base64Decode(preProcessedPsbt)); + + await psbt.signWithUTXO(utxos, (txDigest, utxo, key, sighash) { + return utxo.utxo.isP2tr() + ? key.signTapRoot( + txDigest, + sighash: sighash, + tweak: utxo.utxo.isSilentPayment != true, + ) + : key.signInput(txDigest, sigHash: sighash); + }, (txId, vout) async { + final txHex = await electrumClient.getTransactionHex(hash: txId); + final output = BtcTransaction.fromRaw(txHex).outputs[vout]; + return TaprootAmountScriptPair(output.amount, output.scriptPubKey); + }); + + psbt.finalizeV0(); + return base64Encode(psbt.asPsbtV0()); + } + @override Future signMessage(String message, {String? address = null}) async { if (walletInfo.isHardwareWallet) { diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart index 1a122ef9e..0fefe4e57 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart @@ -1,10 +1,13 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/bip/bip/bip32/bip32.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; +import 'package:cw_bitcoin/payjoin/manager.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/unspent_coin_type.dart'; +import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:mobx/mobx.dart'; +import 'package:payjoin_flutter/receive.dart' as payjoin; part 'bitcoin_wallet_addresses.g.dart'; @@ -17,6 +20,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S required super.sideHd, required super.network, required super.isHardwareWallet, + required this.payjoinManager, super.initialAddresses, super.initialRegularAddressIndex, super.initialChangeAddressIndex, @@ -25,6 +29,15 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S super.masterHd, }) : super(walletInfo); + final PayjoinManager payjoinManager; + + @observable + payjoin.Receiver? currentPayjoinReceiver; + + @computed + String? get payjoinEndpoint => + currentPayjoinReceiver?.pjUriBuilder().build().pjEndpoint(); + @override String getAddress( {required int index, @@ -45,4 +58,17 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S return generateP2WPKHAddress(hd: hd, index: index, network: network); } + + Future initPayjoin() async { + currentPayjoinReceiver = await payjoinManager.initReceiver(primaryAddress); + + payjoinManager.resumeSessions(); + } + + Future newPayjoinReceiver() async { + currentPayjoinReceiver = await payjoinManager.initReceiver(primaryAddress); + + printV("Initializing new Payjoin Receiver"); + payjoinManager.spawnNewReceiver(receiver: currentPayjoinReceiver!); + } } diff --git a/cw_bitcoin/lib/bitcoin_wallet_service.dart b/cw_bitcoin/lib/bitcoin_wallet_service.dart index 7ee1534bf..317b25bcd 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_service.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_service.dart @@ -5,6 +5,7 @@ import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart'; import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart'; import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart'; import 'package:cw_core/encryption_file_utils.dart'; +import 'package:cw_core/payjoin_session.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_service.dart'; @@ -21,10 +22,12 @@ class BitcoinWalletService extends WalletService< BitcoinRestoreWalletFromSeedCredentials, BitcoinRestoreWalletFromWIFCredentials, BitcoinRestoreWalletFromHardware> { - BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan, this.isDirect); + BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, + this.payjoinSessionSource, this.alwaysScan, this.isDirect); final Box walletInfoSource; final Box unspentCoinsInfoSource; + final Box payjoinSessionSource; final bool alwaysScan; final bool isDirect; @@ -55,6 +58,7 @@ class BitcoinWalletService extends WalletService< passphrase: credentials.passphrase, walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource, + payjoinBox: payjoinSessionSource, network: network, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); @@ -79,6 +83,7 @@ class BitcoinWalletService extends WalletService< name: name, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource, + payjoinBox: payjoinSessionSource, alwaysScan: alwaysScan, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); @@ -92,6 +97,7 @@ class BitcoinWalletService extends WalletService< name: name, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource, + payjoinBox: payjoinSessionSource, alwaysScan: alwaysScan, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); @@ -126,6 +132,7 @@ class BitcoinWalletService extends WalletService< name: currentName, walletInfo: currentWalletInfo, unspentCoinsInfo: unspentCoinsInfoSource, + payjoinBox: payjoinSessionSource, alwaysScan: alwaysScan, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); @@ -147,7 +154,6 @@ class BitcoinWalletService extends WalletService< credentials.walletInfo?.network = network.value; credentials.walletInfo?.derivationInfo?.derivationPath = credentials.hwAccountData.derivationPath; - final wallet = await BitcoinWallet( password: credentials.password!, xpub: credentials.hwAccountData.xpub, @@ -155,6 +161,7 @@ class BitcoinWalletService extends WalletService< unspentCoinsInfo: unspentCoinsInfoSource, networkParam: network, encryptionFileUtils: encryptionFileUtilsFor(isDirect), + payjoinBox: payjoinSessionSource, ); await wallet.save(); await wallet.init(); @@ -182,6 +189,7 @@ class BitcoinWalletService extends WalletService< mnemonic: credentials.mnemonic, walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource, + payjoinBox: payjoinSessionSource, network: network, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 14fd1d3a9..35c15682c 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -1188,6 +1188,7 @@ abstract class ElectrumWalletBase isSendAll: estimatedTx.isSendAll, hasTaprootInputs: hasTaprootInputs, utxos: estimatedTx.utxos, + publicKeys: estimatedTx.publicKeys )..addListener((transaction) async { transactionHistory.addOne(transaction); if (estimatedTx.spendsSilentPayment) { @@ -1965,6 +1966,11 @@ abstract class ElectrumWalletBase } } + bool isMine(Script script) { + final derivedAddress = addressFromOutputScript(script, network); + return addressesSet.contains(derivedAddress); + } + @override Future> fetchTransactions() async { try { diff --git a/cw_bitcoin/lib/payjoin/manager.dart b/cw_bitcoin/lib/payjoin/manager.dart new file mode 100644 index 000000000..b80fa777c --- /dev/null +++ b/cw_bitcoin/lib/payjoin/manager.dart @@ -0,0 +1,298 @@ +import 'dart:async'; +import 'dart:isolate'; +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:cw_bitcoin/bitcoin_wallet.dart'; +import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart'; +import 'package:cw_bitcoin/payjoin/payjoin_receive_worker.dart'; +import 'package:cw_bitcoin/payjoin/payjoin_send_worker.dart'; +import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart'; +import 'package:cw_bitcoin/payjoin/storage.dart'; +import 'package:cw_bitcoin/psbt/signer.dart'; +import 'package:cw_bitcoin/psbt/utils.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:payjoin_flutter/common.dart'; +import 'package:payjoin_flutter/receive.dart'; +import 'package:payjoin_flutter/send.dart'; +import 'package:payjoin_flutter/uri.dart' as PayjoinUri; + +class PayjoinManager { + PayjoinManager(this._payjoinStorage, this._wallet); + + final PayjoinStorage _payjoinStorage; + final BitcoinWalletBase _wallet; + final Map _activePollers = {}; + + static const List ohttpRelayUrls = [ + 'https://pj.bobspacebkk.com', + 'https://ohttp.achow101.com', + ]; + + static Future randomOhttpRelayUrl() => PayjoinUri.Url.fromStr( + ohttpRelayUrls[Random.secure().nextInt(ohttpRelayUrls.length)]); + + static const payjoinDirectoryUrl = 'https://payjo.in'; + + Future resumeSessions() async { + final allSessions = _payjoinStorage.readAllOpenSessions(_wallet.id); + + final spawnedSessions = allSessions.map((session) { + if (session.isSenderSession) { + printV("Resuming Payjoin Sender Session ${session.pjUri!}"); + return _spawnSender( + sender: Sender.fromJson(session.sender!), + pjUri: session.pjUri!, + ); + } + final receiver = Receiver.fromJson(session.receiver!); + printV("Resuming Payjoin Receiver Session ${receiver.id()}"); + return _spawnReceiver(receiver: receiver); + }); + + printV("Resumed ${spawnedSessions.length} Payjoin Sessions"); + await Future.wait(spawnedSessions); + } + + Future initSender( + String pjUriString, String originalPsbt, int networkFeesSatPerVb) async { + try { + final pjUri = + (await PayjoinUri.Uri.fromStr(pjUriString)).checkPjSupported(); + final minFeeRateSatPerKwu = BigInt.from(networkFeesSatPerVb * 250); + final senderBuilder = await SenderBuilder.fromPsbtAndUri( + psbtBase64: originalPsbt, + pjUri: pjUri, + ); + return senderBuilder.buildRecommended(minFeeRate: minFeeRateSatPerKwu); + } catch (e) { + throw Exception('Error initializing Payjoin Sender: $e'); + } + } + + Future spawnNewSender({ + required Sender sender, + required String pjUrl, + required BigInt amount, + bool isTestnet = false, + }) async { + final pjUri = Uri.parse(pjUrl).queryParameters['pj']!; + await _payjoinStorage.insertSenderSession( + sender, pjUri, _wallet.id, amount); + + return _spawnSender(isTestnet: isTestnet, sender: sender, pjUri: pjUri); + } + + Future _spawnSender({ + required Sender sender, + required String pjUri, + bool isTestnet = false, + }) async { + final completer = Completer(); + final receivePort = ReceivePort(); + + receivePort.listen((message) async { + if (message is Map) { + try { + switch (message['type'] as PayjoinSenderRequestTypes) { + case PayjoinSenderRequestTypes.requestPosted: + return; + case PayjoinSenderRequestTypes.psbtToSign: + final proposalPsbt = message['psbt'] as String; + final utxos = _wallet.getUtxoWithPrivateKeys(); + final finalizedPsbt = await _wallet.signPsbt(proposalPsbt, utxos); + final txId = getTxIdFromPsbtV0(finalizedPsbt); + _wallet.commitPsbt(finalizedPsbt); + + _cleanupSession(pjUri); + await _payjoinStorage.markSenderSessionComplete(pjUri, txId); + completer.complete(); + } + } catch (e) { + _cleanupSession(pjUri); + printV(e); + await _payjoinStorage.markSenderSessionUnrecoverable(pjUri); + completer.completeError(e); + } + } else if (message is PayjoinSessionError) { + _cleanupSession(pjUri); + if (message is UnrecoverableError) { + printV(message.message); + await _payjoinStorage.markSenderSessionUnrecoverable(pjUri); + completer.complete(); + } else if (message is RecoverableError) { + completer.complete(); + } else { + completer.completeError(message); + } + } + }); + + final isolate = await Isolate.spawn( + PayjoinSenderWorker.run, + [receivePort.sendPort, sender.toJson(), pjUri], + ); + + _activePollers[pjUri] = PayjoinPollerSession(isolate, receivePort); + + return completer.future; + } + + Future initReceiver(String address, + [bool isTestnet = false]) async { + try { + final payjoinDirectory = + await PayjoinUri.Url.fromStr(payjoinDirectoryUrl); + + final ohttpKeys = await PayjoinUri.fetchOhttpKeys( + ohttpRelay: await randomOhttpRelayUrl(), + payjoinDirectory: payjoinDirectory, + ); + + final receiver = await Receiver.create( + address: address, + network: isTestnet ? Network.testnet : Network.bitcoin, + directory: payjoinDirectory, + ohttpKeys: ohttpKeys, + ohttpRelay: await randomOhttpRelayUrl(), + ); + + await _payjoinStorage.insertReceiverSession(receiver, _wallet.id); + + return receiver; + } catch (e) { + throw Exception('Error initializing Payjoin Receiver: $e'); + } + } + + Future spawnNewReceiver({ + required Receiver receiver, + bool isTestnet = false, + }) async { + await _payjoinStorage.insertReceiverSession(receiver, _wallet.id); + return _spawnReceiver(isTestnet: isTestnet, receiver: receiver); + } + + Future _spawnReceiver({ + required Receiver receiver, + bool isTestnet = false, + }) async { + final completer = Completer(); + final receivePort = ReceivePort(); + + SendPort? mainToIsolateSendPort; + List utxos = []; + String rawAmount = '0'; + + receivePort.listen((message) async { + if (message is Map) { + try { + switch (message['type'] as PayjoinReceiverRequestTypes) { + case PayjoinReceiverRequestTypes.processOriginalTx: + final tx = message['tx'] as String; + rawAmount = getOutputAmountFromTx(tx, _wallet); + break; + case PayjoinReceiverRequestTypes.checkIsOwned: + (_wallet.walletAddresses as BitcoinWalletAddresses).newPayjoinReceiver(); + _payjoinStorage.markReceiverSessionInProgress(receiver.id()); + + final inputScript = message['input_script'] as Uint8List; + final isOwned = + _wallet.isMine(Script.fromRaw(byteData: inputScript)); + mainToIsolateSendPort?.send({ + 'requestId': message['requestId'], + 'result': isOwned, + }); + break; + + case PayjoinReceiverRequestTypes.checkIsReceiverOutput: + final outputScript = message['output_script'] as Uint8List; + final isReceiverOutput = + _wallet.isMine(Script.fromRaw(byteData: outputScript)); + mainToIsolateSendPort?.send({ + 'requestId': message['requestId'], + 'result': isReceiverOutput, + }); + break; + + case PayjoinReceiverRequestTypes.getCandidateInputs: + utxos = _wallet.getUtxoWithPrivateKeys(); + mainToIsolateSendPort?.send({ + 'requestId': message['requestId'], + 'result': utxos, + }); + break; + + case PayjoinReceiverRequestTypes.processPsbt: + final psbt = message['psbt'] as String; + final signedPsbt = await _wallet.signPsbt(psbt, utxos); + mainToIsolateSendPort?.send({ + 'requestId': message['requestId'], + 'result': signedPsbt, + }); + break; + + case PayjoinReceiverRequestTypes.proposalSent: + _cleanupSession(receiver.id()); + final psbt = message['psbt'] as String; + await _payjoinStorage.markReceiverSessionComplete( + receiver.id(), getTxIdFromPsbtV0(psbt), rawAmount); + completer.complete(); + } + } catch (e) { + _cleanupSession(receiver.id()); + await _payjoinStorage.markReceiverSessionUnrecoverable( + receiver.id(), e.toString()); + completer.completeError(e); + } + } else if (message is PayjoinSessionError) { + _cleanupSession(receiver.id()); + if (message is UnrecoverableError) { + await _payjoinStorage.markReceiverSessionUnrecoverable( + receiver.id(), message.message); + completer.complete(); + } else if (message is RecoverableError) { + completer.complete(); + } else { + completer.completeError(message); + } + } else if (message is SendPort) { + mainToIsolateSendPort = message; + } + }); + + final isolate = await Isolate.spawn( + PayjoinReceiverWorker.run, + [receivePort.sendPort, receiver.toJson()], + ); + + _activePollers[receiver.id()] = PayjoinPollerSession(isolate, receivePort); + + return completer.future; + } + + void cleanupSessions() { + final sessionIds = _activePollers.keys.toList(); + for (final sessionId in sessionIds) { + _cleanupSession(sessionId); + } + } + + void _cleanupSession(String sessionId) { + _activePollers[sessionId]?.close(); + _activePollers.remove(sessionId); + } +} + +class PayjoinPollerSession { + final Isolate isolate; + final ReceivePort port; + + PayjoinPollerSession(this.isolate, this.port); + + void close() { + isolate.kill(); + port.close(); + } +} diff --git a/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart b/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart new file mode 100644 index 000000000..a499660b0 --- /dev/null +++ b/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart @@ -0,0 +1,219 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:isolate'; +import 'dart:typed_data'; + +import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart'; +import 'package:cw_bitcoin/psbt/signer.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:http/http.dart' as http; +import 'package:payjoin_flutter/bitcoin_ffi.dart'; +import 'package:payjoin_flutter/common.dart'; +import 'package:payjoin_flutter/receive.dart'; +import 'package:payjoin_flutter/src/generated/frb_generated.dart' as pj; + +enum PayjoinReceiverRequestTypes { + processOriginalTx, + proposalSent, + getCandidateInputs, + checkIsOwned, + checkIsReceiverOutput, + processPsbt; +} + +class PayjoinReceiverWorker { + final SendPort sendPort; + final pendingRequests = >{}; + + PayjoinReceiverWorker._(this.sendPort); + + static Future run(List args) async { + await pj.core.init(); + + final sendPort = args[0] as SendPort; + final receiverJson = args[1] as String; + + final worker = PayjoinReceiverWorker._(sendPort); + final receivePort = ReceivePort(); + + sendPort.send(receivePort.sendPort); + receivePort.listen(worker.handleMessage); + + try { + final httpClient = http.Client(); + final receiver = Receiver.fromJson(receiverJson); + + final uncheckedProposal = + await worker.receiveUncheckedProposal(httpClient, receiver); + + final originalTx = await uncheckedProposal.extractTxToScheduleBroadcast(); + sendPort.send({ + 'type': PayjoinReceiverRequestTypes.processOriginalTx, + 'tx': BytesUtils.toHexString(originalTx), + }); + + final payjoinProposal = await worker.processPayjoinProposal( + uncheckedProposal, + ); + final psbt = await worker.sendFinalProposal(httpClient, payjoinProposal); + sendPort.send({ + 'type': PayjoinReceiverRequestTypes.proposalSent, + 'psbt': psbt, + }); + } catch (e) { + if (e is HttpException || + (e is http.ClientException && + e.message.contains("Software caused connection abort"))) { + sendPort.send(PayjoinSessionError.recoverable(e.toString())); + } else { + sendPort.send(PayjoinSessionError.unrecoverable(e.toString())); + } + } + } + + void handleMessage(dynamic message) async { + if (message is Map) { + final requestId = message['requestId'] as String?; + if (requestId != null && pendingRequests.containsKey(requestId)) { + pendingRequests[requestId]!.complete(message['result']); + pendingRequests.remove(requestId); + } + } + } + + Future _sendRequest(PayjoinReceiverRequestTypes type, + [Map data = const {}]) async { + final completer = Completer(); + final requestId = DateTime.now().millisecondsSinceEpoch.toString(); + pendingRequests[requestId] = completer; + + sendPort.send({ + ...data, + 'type': type, + 'requestId': requestId, + }); + + return completer.future; + } + + Future receiveUncheckedProposal( + http.Client httpClient, Receiver session) async { + while (true) { + printV("Polling for Proposal (${session.id()})"); + final extractReq = await session.extractReq(); + final request = extractReq.$1; + + final url = Uri.parse(request.url.asString()); + final httpRequest = await httpClient.post(url, + headers: {'Content-Type': request.contentType}, body: request.body); + + final proposal = await session.processRes( + body: httpRequest.bodyBytes, ctx: extractReq.$2); + if (proposal != null) return proposal; + } + } + + Future sendFinalProposal( + http.Client httpClient, PayjoinProposal finalProposal) async { + final req = await finalProposal.extractV2Req(); + final proposalReq = req.$1; + final proposalCtx = req.$2; + + final request = await httpClient.post( + Uri.parse(proposalReq.url.asString()), + headers: {"Content-Type": proposalReq.contentType}, + body: proposalReq.body, + ); + + await finalProposal.processRes( + res: request.bodyBytes, + ohttpContext: proposalCtx, + ); + + return await finalProposal.psbt(); + } + + Future processPayjoinProposal( + UncheckedProposal proposal) async { + await proposal.extractTxToScheduleBroadcast(); + // TODO Handle this. send to the main port on a timer? + + try { + // Receive Check 1: can broadcast + final pj1 = await proposal.assumeInteractiveReceiver(); + + // Receive Check 2: original PSBT has no receiver-owned inputs + final pj2 = await pj1.checkInputsNotOwned( + isOwned: (inputScript) async { + final result = await _sendRequest( + PayjoinReceiverRequestTypes.checkIsOwned, + {'input_script': inputScript}, + ); + return result as bool; + }, + ); + // Receive Check 3: sender inputs have not been seen before (prevent probing attacks) + final pj3 = await pj2.checkNoInputsSeenBefore(isKnown: (input) => false); + + // Identify receiver outputs + final pj4 = await pj3.identifyReceiverOutputs( + isReceiverOutput: (outputScript) async { + final result = await _sendRequest( + PayjoinReceiverRequestTypes.checkIsReceiverOutput, + {'output_script': outputScript}, + ); + return result as bool; + }, + ); + final pj5 = await pj4.commitOutputs(); + + final listUnspent = + await _sendRequest(PayjoinReceiverRequestTypes.getCandidateInputs); + final unspent = listUnspent as List; + if (unspent.isEmpty) throw Exception('No unspent outputs available'); + + final selectedUtxo = await _inputPairFromUtxo(unspent[0]); + final pj6 = await pj5.contributeInputs(replacementInputs: [selectedUtxo]); + final pj7 = await pj6.commitInputs(); + + // Finalize proposal + final payjoinProposal = await pj7.finalizeProposal( + processPsbt: (String psbt) async { + final result = await _sendRequest( + PayjoinReceiverRequestTypes.processPsbt, {'psbt': psbt}); + return result as String; + }, + // TODO set maxFeeRateSatPerVb + maxFeeRateSatPerVb: BigInt.from(10000), + ); + return payjoinProposal; + } catch (e) { + printV('Error occurred while finalizing proposal: $e'); + rethrow; + } + } + + Future _inputPairFromUtxo(UtxoWithPrivateKey utxo) async { + final txout = TxOut( + value: utxo.utxo.value, + scriptPubkey: Uint8List.fromList( + utxo.ownerDetails.address.toScriptPubKey().toBytes()), + ); + + final psbtin = + PsbtInput(witnessUtxo: txout, redeemScript: null, witnessScript: null); + + final previousOutput = + OutPoint(txid: utxo.utxo.txHash, vout: utxo.utxo.vout); + + final txin = TxIn( + previousOutput: previousOutput, + scriptSig: await Script.newInstance(rawOutputScript: []), + witness: [], + sequence: 0, + ); + + return InputPair.newInstance(txin, psbtin); + } +} diff --git a/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart b/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart new file mode 100644 index 000000000..f720bac01 --- /dev/null +++ b/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart @@ -0,0 +1,119 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:isolate'; + +import 'package:cw_bitcoin/payjoin/manager.dart'; +import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:http/http.dart' as http; +import 'package:payjoin_flutter/common.dart'; +import 'package:payjoin_flutter/send.dart'; +import 'package:payjoin_flutter/src/generated/frb_generated.dart' as pj; + +enum PayjoinSenderRequestTypes { + requestPosted, + psbtToSign; +} + +class PayjoinSenderWorker { + final SendPort sendPort; + final pendingRequests = >{}; + final String pjUrl; + + PayjoinSenderWorker._(this.sendPort, this.pjUrl); + + static Future run(List args) async { + await pj.core.init(); + + final sendPort = args[0] as SendPort; + final senderJson = args[1] as String; + final pjUrl = args[2] as String; + + final sender = Sender.fromJson(senderJson); + final worker = PayjoinSenderWorker._(sendPort, pjUrl); + + try { + final proposalPsbt = await worker.runSender(sender); + sendPort.send({ + 'type': PayjoinSenderRequestTypes.psbtToSign, + 'psbt': proposalPsbt, + }); + } catch (e) { + sendPort.send(e); + } + } + + /// Run a payjoin sender (V2 protocol first, fallback to V1). + Future runSender(Sender sender) async { + final httpClient = http.Client(); + + try { + return await _runSenderV2(sender, httpClient); + } catch (e) { + printV(e); + if (e is PayjoinException && + // TODO condition on error type instead of message content + e.message?.contains('parse receiver public key') == true) { + return await _runSenderV1(sender, httpClient); + } else if (e is HttpException) { + printV(e); + throw Exception(PayjoinSessionError.recoverable(e.toString())); + } else { + throw Exception(PayjoinSessionError.unrecoverable(e.toString())); + } + } + } + + /// Attempt to send payjoin using the V2 of the protocol. + Future _runSenderV2(Sender sender, http.Client httpClient) async { + try { + final postRequest = await sender.extractV2( + ohttpProxyUrl: await PayjoinManager.randomOhttpRelayUrl(), + ); + + final postResult = await _postRequest(httpClient, postRequest.$1); + final getContext = + await postRequest.$2.processResponse(response: postResult); + + sendPort.send({'type': PayjoinSenderRequestTypes.requestPosted, "pj": pjUrl}); + + while (true) { + printV('Polling V2 Proposal Request (${pjUrl})'); + + final getRequest = await getContext.extractReq( + ohttpRelay: await PayjoinManager.randomOhttpRelayUrl(), + ); + final getRes = await _postRequest(httpClient, getRequest.$1); + final proposalPsbt = await getContext.processResponse( + response: getRes, + ohttpCtx: getRequest.$2, + ); + printV("$proposalPsbt"); + if (proposalPsbt != null) return proposalPsbt; + } + } catch (e) { + rethrow; + } + } + + /// Attempt to send payjoin using the V1 of the protocol. + Future _runSenderV1(Sender sender, http.Client httpClient) async { + try { + final postRequest = await sender.extractV1(); + final response = await _postRequest(httpClient, postRequest.$1); + + sendPort.send({'type': PayjoinSenderRequestTypes.requestPosted}); + + return await postRequest.$2.processResponse(response: response); + } catch (e) { + throw PayjoinSessionError.unrecoverable('Send V1 payjoin error: $e'); + } + } + + Future> _postRequest(http.Client client, Request req) async { + final httpRequest = await client.post(Uri.parse(req.url.asString()), + headers: {'Content-Type': req.contentType}, body: req.body); + + return httpRequest.bodyBytes; + } +} diff --git a/cw_bitcoin/lib/payjoin/payjoin_session_errors.dart b/cw_bitcoin/lib/payjoin/payjoin_session_errors.dart new file mode 100644 index 000000000..06e0a5431 --- /dev/null +++ b/cw_bitcoin/lib/payjoin/payjoin_session_errors.dart @@ -0,0 +1,16 @@ +class PayjoinSessionError { + final String message; + + const PayjoinSessionError._(this.message); + + factory PayjoinSessionError.recoverable(String message) = RecoverableError; + factory PayjoinSessionError.unrecoverable(String message) = UnrecoverableError; +} + +class RecoverableError extends PayjoinSessionError { + const RecoverableError(super.message) : super._(); +} + +class UnrecoverableError extends PayjoinSessionError { + const UnrecoverableError(super.message) : super._(); +} diff --git a/cw_bitcoin/lib/payjoin/storage.dart b/cw_bitcoin/lib/payjoin/storage.dart new file mode 100644 index 000000000..9c1c83253 --- /dev/null +++ b/cw_bitcoin/lib/payjoin/storage.dart @@ -0,0 +1,95 @@ +import 'package:cw_core/payjoin_session.dart'; +import 'package:hive/hive.dart'; +import 'package:payjoin_flutter/receive.dart'; +import 'package:payjoin_flutter/send.dart'; + +class PayjoinStorage { + PayjoinStorage(this._payjoinSessionSources); + + final Box _payjoinSessionSources; + + static const String _receiverPrefix = 'pj_recv_'; + static const String _senderPrefix = 'pj_send_'; + + Future insertReceiverSession( + Receiver receiver, + String walletId, + ) => + _payjoinSessionSources.put( + "$_receiverPrefix${receiver.id()}", + PayjoinSession( + walletId: walletId, + receiver: receiver.toJson(), + ), + ); + + Future markReceiverSessionComplete( + String sessionId, String txId, String amount) async { + final session = _payjoinSessionSources.get("$_receiverPrefix${sessionId}")!; + + session.status = PayjoinSessionStatus.success.name; + session.txId = txId; + session.rawAmount = amount; + await session.save(); + } + + Future markReceiverSessionUnrecoverable( + String sessionId, String reason) async { + final session = _payjoinSessionSources.get("$_receiverPrefix${sessionId}")!; + + session.status = PayjoinSessionStatus.unrecoverable.name; + session.error = reason; + await session.save(); + } + + Future markReceiverSessionInProgress(String sessionId) async { + final session = _payjoinSessionSources.get("$_receiverPrefix${sessionId}")!; + + session.status = PayjoinSessionStatus.inProgress.name; + session.inProgressSince = DateTime.now(); + await session.save(); + } + + Future insertSenderSession( + Sender sender, + String pjUrl, + String walletId, + BigInt amount, + ) => + _payjoinSessionSources.put( + "$_senderPrefix$pjUrl", + PayjoinSession( + walletId: walletId, + pjUri: pjUrl, + sender: sender.toJson(), + status: PayjoinSessionStatus.inProgress.name, + inProgressSince: DateTime.now(), + rawAmount: amount.toString(), + ), + ); + + Future markSenderSessionComplete(String pjUrl, String txId) async { + final session = _payjoinSessionSources.get("$_senderPrefix$pjUrl")!; + + session.status = PayjoinSessionStatus.success.name; + session.txId = txId; + await session.save(); + } + + Future markSenderSessionUnrecoverable(String pjUrl) async { + final session = _payjoinSessionSources.get("$_senderPrefix$pjUrl")!; + + session.status = PayjoinSessionStatus.unrecoverable.name; + await session.save(); + } + + List readAllOpenSessions(String walletId) => + _payjoinSessionSources.values + .where((session) => + session.walletId == walletId && + ![ + PayjoinSessionStatus.success.name, + PayjoinSessionStatus.unrecoverable.name + ].contains(session.status)) + .toList(); +} diff --git a/cw_bitcoin/lib/pending_bitcoin_transaction.dart b/cw_bitcoin/lib/pending_bitcoin_transaction.dart index 411c7de16..6930524eb 100644 --- a/cw_bitcoin/lib/pending_bitcoin_transaction.dart +++ b/cw_bitcoin/lib/pending_bitcoin_transaction.dart @@ -1,3 +1,4 @@ +import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:grpc/grpc.dart'; import 'package:cw_bitcoin/exceptions.dart'; import 'package:bitcoin_base/bitcoin_base.dart'; @@ -25,6 +26,8 @@ class PendingBitcoinTransaction with PendingTransaction { this.hasTaprootInputs = false, this.isMweb = false, this.utxos = const [], + this.publicKeys, + this.commitOverride, }) : _listeners = []; final WalletType type; @@ -43,6 +46,8 @@ class PendingBitcoinTransaction with PendingTransaction { String? idOverride; String? hexOverride; List? outputAddresses; + final Map? publicKeys; + Future Function()? commitOverride; @override String get id => idOverride ?? _tx.txId(); @@ -129,6 +134,10 @@ class PendingBitcoinTransaction with PendingTransaction { @override Future commit() async { + if (commitOverride != null) { + return commitOverride?.call(); + } + if (isMweb) { await _ltcCommit(); } else { diff --git a/cw_bitcoin/lib/psbt/signer.dart b/cw_bitcoin/lib/psbt/signer.dart new file mode 100644 index 000000000..1d0ceba8b --- /dev/null +++ b/cw_bitcoin/lib/psbt/signer.dart @@ -0,0 +1,263 @@ +import 'dart:typed_data'; + +import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:collection/collection.dart'; +import 'package:cw_bitcoin/bitcoin_address_record.dart'; +import 'package:cw_bitcoin/bitcoin_unspent.dart'; +import 'package:cw_bitcoin/bitcoin_wallet.dart'; +import 'package:cw_bitcoin/utils.dart'; +import 'package:ledger_bitcoin/psbt.dart'; +import 'package:ledger_bitcoin/src/utils/buffer_writer.dart'; + +extension PsbtSigner on PsbtV2 { + Uint8List extractUnsignedTX({bool getSegwit = true}) { + final tx = BufferWriter()..writeUInt32(getGlobalTxVersion()); + + final isSegwit = getInputWitnessUtxo(0) != null; + if (isSegwit && getSegwit) { + tx.writeSlice(Uint8List.fromList([0, 1])); + } + + final inputCount = getGlobalInputCount(); + tx.writeVarInt(inputCount); + + for (var i = 0; i < inputCount; i++) { + tx + ..writeSlice(getInputPreviousTxid(i)) + ..writeUInt32(getInputOutputIndex(i)) + ..writeVarSlice(Uint8List(0)) + ..writeUInt32(getInputSequence(i)); + } + + final outputCount = getGlobalOutputCount(); + tx.writeVarInt(outputCount); + for (var i = 0; i < outputCount; i++) { + tx.writeUInt64(getOutputAmount(i)); + tx.writeVarSlice(getOutputScript(i)); + } + tx.writeUInt32(getGlobalFallbackLocktime() ?? 0); + return tx.buffer(); + } + + Future signWithUTXO( + List utxos, UTXOSignerCallBack signer, + [UTXOGetterCallBack? getTaprootPair]) async { + final raw = BytesUtils.toHexString(extractUnsignedTX(getSegwit: false)); + final tx = BtcTransaction.fromRaw(raw); + + /// when the transaction is taproot and we must use getTaproot transaction + /// digest we need all of inputs amounts and owner script pub keys + List taprootAmounts = []; + List