diff --git a/assets/flutter_i18n/de.json b/assets/flutter_i18n/de.json index a53b7169..d10ef672 100644 --- a/assets/flutter_i18n/de.json +++ b/assets/flutter_i18n/de.json @@ -31,7 +31,7 @@ "wallet": "Wallet", "active": "Aktiv", "date": "Datum", - "tx": "Tx = Transaktion", + "tx": "Transaktion", "manage_portfolio": "Token senden/Portfolio verwalten", "fiat_options": "FIAT Optionen", "show/hide_balance": "Balance anzeigen/verbergen", @@ -300,5 +300,12 @@ "removing_account_warning": "Sind Sie sicher, dass Sie dieses Konto entfernen möchten?", "remove": "Entfernen", "duplicate_account_import_notice": "Das Konto, das Sie versuchen zu importieren, ist ein Duplikat.", - "unable_to_launch_email_app": "Nicht in der Lage, E-Mail-App zu starten" + "unable_to_launch_email_app": "Nicht in der Lage, E-Mail-App zu starten", + "max_fee": "Maximale Gebühr", + "deposit_from_exchanges_notice": "Kaufen Sie von {0}, {1} Börsen und hinterlegen Sie zkEVM MXC direkt in die AXS Wallet.", + "deposit_with_l3_bridge_notice": "Kaufen Sie von anderen Börsen und verwenden Sie {0}, um ERC20 MXC in die AXS Wallet einzuzahlen.", + "buy_some_x_for_fee_notice": "Kaufen Sie etwas {0}, um die Gasgebühr für die Transaktion zu bezahlen, normalerweise weniger als 2 USD, wenn das Netzwerk nicht ausgelastet ist.", + "insufficient_funds_notice": "Unzureichendes Guthaben für diese Transaktion. Bitte fügen Sie Geldmittel hinzu.", + "contractCall": "Vertragsaufruf", + "contract_call_transactions": "Vertragsaufruf-Transaktionen" } \ No newline at end of file diff --git a/assets/flutter_i18n/en.json b/assets/flutter_i18n/en.json index 0add92cf..a5a4fc16 100644 --- a/assets/flutter_i18n/en.json +++ b/assets/flutter_i18n/en.json @@ -42,6 +42,7 @@ "send": "Send", "received": "Received", "sent": "Sent", + "contractCall": "Contract call", "email_secured_body": "For the security and privacy of your AXS wallet it's essential that you keep all information contained in this email strictly confidential. \nPlease follow these next steps carefully: \n1. Ensure you're the sole recipient of this email by replacing the recipient's address with your own. \n2. After confirming the correct email address, proceed to click the 'Send' button to forward this vital information to yourself securely. By adhering to these measures, you'll ensure the utmost safety for your AXS wallet. Thank you for your diligence in this matter.", "email_secured_subject": "Secure Your AXS Wallet Key - Important Instructions", "face_id": "Face ID", @@ -141,6 +142,7 @@ "from": "From", "to": "To", "estimated_fee": "Estimated Fee", + "max_fee": "Max Fee", "sending": "Sending", "confirm_transaction": "Confirm transaction", "add_nft": "Add NFT", @@ -171,6 +173,7 @@ "all_transactions": "All transactions", "send_transactions": "Send transactions", "receive_transactions": "Receive transactions", + "contract_call_transactions": "Contract call transactions", "sort": "Sort", "new_to_old": "New to Old", "old_to_new": "Old to New", @@ -300,5 +303,9 @@ "removing_account_warning": "Are you sure you want to remove this account?", "remove": "Remove", "duplicate_account_import_notice": "The account you are trying to import is a duplicate", - "unable_to_launch_email_app": "Unable to launch email app" + "unable_to_launch_email_app": "Unable to launch email app", + "deposit_from_exchanges_notice": "Buy from {0}, {1} exchanges and deposit zkEVM MXC directly to AXS wallet", + "deposit_with_l3_bridge_notice": "Buy from other exchanges and use {0} to deposit ERC20 MXC to AXS wallet", + "buy_some_x_for_fee_notice": "Buy some {0} to pay for gas fee for the transaction, normally less than 2 USD when the network is not busy", + "insufficient_funds_notice": "Insufficient balance for this transaction.\n Please add funds." } \ No newline at end of file diff --git a/assets/flutter_i18n/es.json b/assets/flutter_i18n/es.json index 6a2dba61..c9dafb59 100644 --- a/assets/flutter_i18n/es.json +++ b/assets/flutter_i18n/es.json @@ -300,5 +300,12 @@ "removing_account_warning": "¿Estás seguro de que quieres eliminar esta cuenta?", "remove": "Eliminar", "duplicate_account_import_notice": "La cuenta que intentas importar es un duplicado.", - "unable_to_launch_email_app": "Incapaz de iniciar la aplicación de correo electrónico" + "unable_to_launch_email_app": "Incapaz de iniciar la aplicación de correo electrónico", + "max_fee": "Tarifa Máxima", + "deposit_from_exchanges_notice": "Compra desde {0}, {1} intercambios y deposita zkEVM MXC directamente en la cartera AXS", + "deposit_with_l3_bridge_notice": "Compra en otros intercambios y usa {0} para depositar ERC20 MXC en la billetera AXS.", + "buy_some_x_for_fee_notice": "Compra algo de {0} para pagar la tarifa de gas para la transacción, normalmente menos de 2 USD cuando la red no está ocupada.", + "insufficient_funds_notice": "Saldo insuficiente para esta transacción. Por favor, añade fondos.", + "contractCall": "Llamada de contrato", + "contract_call_transactions": "Transacciones de llamada de contrato" } \ No newline at end of file diff --git a/assets/flutter_i18n/fr.json b/assets/flutter_i18n/fr.json index a0ceb788..7a6361cb 100644 --- a/assets/flutter_i18n/fr.json +++ b/assets/flutter_i18n/fr.json @@ -300,5 +300,12 @@ "removing_account_warning": "Êtes-vous sûr de vouloir supprimer ce compte?", "remove": "Supprimer", "duplicate_account_import_notice": "Le compte que vous essayez d'importer est un doublon.", - "unable_to_launch_email_app": "Impossible de lancer l'application email" + "unable_to_launch_email_app": "Impossible de lancer l'application email", + "max_fee": "Frais Maximal", + "deposit_from_exchanges_notice": "Achetez auprès de {0}, {1} échanges et déposez directement zkEVM MXC dans le portefeuille AXS", + "deposit_with_l3_bridge_notice": "Achetez sur d'autres échanges et utilisez {0} pour déposer ERC20 MXC dans le portefeuille AXS", + "buy_some_x_for_fee_notice": "Achetez un peu de {0} pour payer les frais de gaz pour la transaction, normalement moins de 2 USD lorsque le réseau n'est pas occupé", + "insufficient_funds_notice": "Solde insuffisant pour cette transaction. Veuillez ajouter des fonds.", + "contractCall": "Appel de contrat", + "contract_call_transactions": "Transactions d'appel de contrat" } \ No newline at end of file diff --git a/assets/flutter_i18n/id.json b/assets/flutter_i18n/id.json index ee9a8827..ee48d292 100644 --- a/assets/flutter_i18n/id.json +++ b/assets/flutter_i18n/id.json @@ -300,5 +300,12 @@ "removing_account_warning": "Apakah Anda yakin ingin menghapus akun ini?", "remove": "Hapus", "duplicate_account_import_notice": "Akun yang Anda coba impor adalah duplikat.", - "unable_to_launch_email_app": "Tidak dapat meluncurkan aplikasi email" + "unable_to_launch_email_app": "Tidak dapat meluncurkan aplikasi email", + "max_fee": "Biaya Maksimal", + "deposit_from_exchanges_notice": "Beli dari {0}, {1} bursa dan setor langsung zkEVM MXC ke dompet AXS", + "deposit_with_l3_bridge_notice": "Beli dari bursa lain dan gunakan {0} untuk menyetor MXC ERC20 ke dompet AXS", + "buy_some_x_for_fee_notice": "Beli beberapa {0} untuk membayar biaya gas untuk transaksi, biasanya kurang dari 2 USD saat jaringan tidak sibuk", + "insufficient_funds_notice": "Saldo tidak cukup untuk transaksi ini. \nSilakan tambahkan dana.", + "contractCall": "Panggilan kontrak", + "contract_call_transactions": "Transaksi panggilan kontrak" } \ No newline at end of file diff --git a/assets/flutter_i18n/it.json b/assets/flutter_i18n/it.json index f19629a8..6c026863 100644 --- a/assets/flutter_i18n/it.json +++ b/assets/flutter_i18n/it.json @@ -300,5 +300,12 @@ "removing_account_warning": "Sei sicuro di voler rimuovere questo account?", "remove": "Rimuovi", "duplicate_account_import_notice": "L'account che stai cercando di importare è un duplicato.", - "unable_to_launch_email_app": "Impossibile avviare l'app di posta elettronica" + "unable_to_launch_email_app": "Impossibile avviare l'app di posta elettronica", + "max_fee": "Commissione Massima", + "deposit_from_exchanges_notice": "Acquista da {0}, {1} scambi e deposita direttamente zkEVM MXC nel portafoglio AXS", + "deposit_with_l3_bridge_notice": "Acquista da altri exchange e usa {0} per depositare MXC ERC20 nel portafoglio AXS", + "buy_some_x_for_fee_notice": "Acquista un po' di {0} per pagare la commissione di gas per la transazione, normalmente meno di 2 USD quando la rete non è occupata.", + "insufficient_funds_notice": "Saldo insufficiente per questa transazione. Per favore, aggiungi fondi.", + "contractCall": "Chiamata del contratto", + "contract_call_transactions": "Transazioni di chiamata del contratto" } \ No newline at end of file diff --git a/assets/flutter_i18n/ja.json b/assets/flutter_i18n/ja.json index ae8e762c..c7016c71 100644 --- a/assets/flutter_i18n/ja.json +++ b/assets/flutter_i18n/ja.json @@ -300,5 +300,12 @@ "removing_account_warning": "このアカウントを削除してもよろしいですか?", "remove": "削除する", "duplicate_account_import_notice": "あなたがインポートしようとしているアカウントは重複しています。", - "unable_to_launch_email_app": "メールアプリを起動できません" + "unable_to_launch_email_app": "メールアプリを起動できません", + "max_fee": "最大手数料", + "deposit_from_exchanges_notice": "{0}から購入し、{1}の取引所でzkEVM MXCを直接AXSウォレットに預けてください。", + "deposit_with_l3_bridge_notice": "他の取引所から購入し、{0}を使用してERC20 MXCをAXSウォレットに預ける", + "buy_some_x_for_fee_notice": "取引のガス料を支払うために{0}をいくつか購入してください。通常、ネットワークが混雑していないときは2ドル未満です。", + "insufficient_funds_notice": "このトランザクションには残高が不足しています。資金を追加してください。", + "contractCall": "契約の呼び出し", + "contract_call_transactions": "契約呼び出しトランザクション" } \ No newline at end of file diff --git a/assets/flutter_i18n/ko.json b/assets/flutter_i18n/ko.json index d225478c..128f0e0c 100644 --- a/assets/flutter_i18n/ko.json +++ b/assets/flutter_i18n/ko.json @@ -300,5 +300,12 @@ "removing_account_warning": "이 계정을 삭제하시겠습니까?", "remove": "제거하다", "duplicate_account_import_notice": "당신이 가져오려고 하는 계정은 중복된 계정입니다.", - "unable_to_launch_email_app": "이메일 앱을 실행할 수 없습니다." + "unable_to_launch_email_app": "이메일 앱을 실행할 수 없습니다.", + "max_fee": "최대 수수료", + "deposit_from_exchanges_notice": "{0}에서 구매하고, {1} 거래소에서 zkEVM MXC를 직접 AXS 지갑에 입금하세요.", + "deposit_with_l3_bridge_notice": "다른 거래소에서 구매하고 {0}을 사용하여 ERC20 MXC를 AXS 지갑에 입금하십시오.", + "buy_some_x_for_fee_notice": "거래 수수료를 위해 {0}을 구매하세요, 네트워크가 바쁘지 않을 때는 일반적으로 2 달러 미만입니다.", + "insufficient_funds_notice": "이 거래를 위한 잔액이 부족합니다. 자금을 추가해 주세요.", + "contractCall": "계약 호출", + "contract_call_transactions": "계약 호출 트랜잭션" } \ No newline at end of file diff --git a/assets/flutter_i18n/nl.json b/assets/flutter_i18n/nl.json index 001a4032..420cef90 100644 --- a/assets/flutter_i18n/nl.json +++ b/assets/flutter_i18n/nl.json @@ -300,5 +300,12 @@ "removing_account_warning": "Weet je zeker dat je dit account wilt verwijderen?", "remove": "Verwijderen", "duplicate_account_import_notice": "Het account dat u probeert te importeren is een duplicaat.", - "unable_to_launch_email_app": "Kan e-mail app niet starten" + "unable_to_launch_email_app": "Kan e-mail app niet starten", + "max_fee": "Maximale Vergoeding", + "deposit_from_exchanges_notice": "Koop van {0}, {1} beurzen en stort zkEVM MXC direct in de AXS portemonnee", + "deposit_with_l3_bridge_notice": "Koop van andere beurzen en gebruik {0} om ERC20 MXC te storten in de AXS portemonnee", + "buy_some_x_for_fee_notice": "Koop wat {0} om de gasprijs voor de transactie te betalen, normaal gesproken minder dan 2 USD wanneer het netwerk niet druk is.", + "insufficient_funds_notice": "Onvoldoende saldo voor deze transactie. Voeg alsjeblieft fondsen toe.", + "contractCall": "Contractoproep", + "contract_call_transactions": "Contractoproep transacties" } \ No newline at end of file diff --git a/assets/flutter_i18n/pt.json b/assets/flutter_i18n/pt.json index c16b2939..9651b0ab 100644 --- a/assets/flutter_i18n/pt.json +++ b/assets/flutter_i18n/pt.json @@ -300,5 +300,12 @@ "removing_account_warning": "Tem certeza de que deseja remover esta conta?", "remove": "Remover", "duplicate_account_import_notice": "A conta que você está tentando importar é uma duplicata.", - "unable_to_launch_email_app": "Incapaz de iniciar o aplicativo de email" + "unable_to_launch_email_app": "Incapaz de iniciar o aplicativo de email", + "max_fee": "Taxa Máxima", + "deposit_from_exchanges_notice": "Compre de {0}, {1} bolsas e deposite zkEVM MXC diretamente na carteira AXS", + "deposit_with_l3_bridge_notice": "Compre de outras bolsas e use {0} para depositar ERC20 MXC na carteira AXS", + "buy_some_x_for_fee_notice": "Compre algum {0} para pagar a taxa de gás para a transação, normalmente menos de 2 USD quando a rede não está ocupada.", + "insufficient_funds_notice": "Saldo insuficiente para esta transação. Por favor, adicione fundos.", + "contractCall": "Chamada de contrato", + "contract_call_transactions": "Transações de chamada de contrato" } \ No newline at end of file diff --git a/assets/flutter_i18n/ro.json b/assets/flutter_i18n/ro.json index 6ea9674d..27f8cf69 100644 --- a/assets/flutter_i18n/ro.json +++ b/assets/flutter_i18n/ro.json @@ -300,5 +300,12 @@ "removing_account_warning": "Ești sigur că vrei să ștergi acest cont?", "remove": "Eliminați", "duplicate_account_import_notice": "Contul pe care încercați să-l importați este un duplicat.", - "unable_to_launch_email_app": "Imposibil de lansat aplicația de email" + "unable_to_launch_email_app": "Imposibil de lansat aplicația de email", + "max_fee": "Taxă Maximă", + "deposit_from_exchanges_notice": "Cumpărați de la {0}, {1} burse și depuneți direct zkEVM MXC în portofelul AXS", + "deposit_with_l3_bridge_notice": "Cumpărați de la alte burse și utilizați {0} pentru a depune MXC ERC20 în portofelul AXS.", + "buy_some_x_for_fee_notice": "Cumpărați ceva {0} pentru a plăti taxa de gaz pentru tranzacție, de obicei mai puțin de 2 USD când rețeaua nu este ocupată.", + "insufficient_funds_notice": "Sold insuficient pentru această tranzacție. Vă rugăm să adăugați fonduri.", + "contractCall": "Apel de contract", + "contract_call_transactions": "Tranzacții de apel contractuale" } \ No newline at end of file diff --git a/assets/flutter_i18n/ru.json b/assets/flutter_i18n/ru.json index b5039656..f7b1dd29 100644 --- a/assets/flutter_i18n/ru.json +++ b/assets/flutter_i18n/ru.json @@ -300,5 +300,12 @@ "removing_account_warning": "Вы уверены, что хотите удалить этот аккаунт?", "remove": "Удалить", "duplicate_account_import_notice": "Учетная запись, которую вы пытаетесь импортировать, является дубликатом.", - "unable_to_launch_email_app": "Не удается запустить приложение для электронной почты" + "unable_to_launch_email_app": "Не удается запустить приложение для электронной почты", + "max_fee": "Максимальная комиссия", + "deposit_from_exchanges_notice": "Купите от {0}, {1} бирж и напрямую внесите zkEVM MXC в кошелек AXS", + "deposit_with_l3_bridge_notice": "Покупайте на других биржах и используйте {0} для депозита ERC20 MXC в кошелек AXS", + "buy_some_x_for_fee_notice": "Купите немного {0} для оплаты комиссии за транзакцию, обычно меньше 2 долларов США, когда сеть не загружена.", + "insufficient_funds_notice": "Недостаточный баланс для этой транзакции. Пожалуйста, пополните счет.", + "contractCall": "Вызов контракта", + "contract_call_transactions": "Транзакции вызова контракта" } \ No newline at end of file diff --git a/assets/flutter_i18n/tr.json b/assets/flutter_i18n/tr.json index 1d992998..280ad25b 100644 --- a/assets/flutter_i18n/tr.json +++ b/assets/flutter_i18n/tr.json @@ -300,5 +300,12 @@ "removing_account_warning": "Bu hesabı kaldırmak istediğinize emin misiniz?", "remove": "Kaldır", "duplicate_account_import_notice": "İçe aktarmaya çalıştığınız hesap bir kopyadır.", - "unable_to_launch_email_app": "E-posta uygulaması başlatılamıyor" + "unable_to_launch_email_app": "E-posta uygulaması başlatılamıyor", + "max_fee": "Maksimum Ücret", + "deposit_from_exchanges_notice": "{0}, {1} borsalarından satın alın ve zkEVM MXC'yi doğrudan AXS cüzdanına yatırın.", + "deposit_with_l3_bridge_notice": "Diğer borsalardan satın alın ve ERC20 MXC'yi AXS cüzdanına yatırmak için {0} kullanın.", + "buy_some_x_for_fee_notice": "İşlem için gaz ücretini ödemek üzere biraz {0} satın alın, ağ meşgul olmadığında genellikle 2 USD'den azdır.", + "insufficient_funds_notice": "Bu işlem için yetersiz bakiye. Lütfen fon ekleyin.", + "contractCall": "Sözleşme çağrısı", + "contract_call_transactions": "Sözleşme çağrı işlemleri" } \ No newline at end of file diff --git a/assets/flutter_i18n/vi.json b/assets/flutter_i18n/vi.json index 5830d31d..d14833f5 100644 --- a/assets/flutter_i18n/vi.json +++ b/assets/flutter_i18n/vi.json @@ -300,5 +300,12 @@ "removing_account_warning": "Bạn có chắc chắn muốn xóa tài khoản này không?", "remove": "Xóa bỏ", "duplicate_account_import_notice": "Tài khoản bạn đang cố gắng nhập vào là một bản sao.", - "unable_to_launch_email_app": "Không thể khởi chạy ứng dụng email" + "unable_to_launch_email_app": "Không thể khởi chạy ứng dụng email", + "max_fee": "Phí Tối Đa", + "deposit_from_exchanges_notice": "Mua từ {0}, {1} sàn giao dịch và nạp zkEVM MXC trực tiếp vào ví AXS", + "deposit_with_l3_bridge_notice": "Mua từ các sàn giao dịch khác và sử dụng {0} để nạp MXC ERC20 vào ví AXS", + "buy_some_x_for_fee_notice": "Mua một số {0} để trả phí gas cho giao dịch, thường ít hơn 2 USD khi mạng không bận rộn", + "insufficient_funds_notice": "Số dư không đủ cho giao dịch này. Vui lòng nạp thêm tiền.", + "contractCall": "Gọi hợp đồng", + "contract_call_transactions": "Giao dịch gọi hợp đồng" } \ No newline at end of file diff --git a/assets/flutter_i18n/zh_CN.json b/assets/flutter_i18n/zh_CN.json index 2ca064aa..719054f3 100644 --- a/assets/flutter_i18n/zh_CN.json +++ b/assets/flutter_i18n/zh_CN.json @@ -300,5 +300,12 @@ "removing_account_warning": "您确定要删除这个账户吗?", "remove": "删除", "duplicate_account_import_notice": "您试图导入的账户是重复的。", - "unable_to_launch_email_app": "无法启动电子邮件应用程序" + "unable_to_launch_email_app": "无法启动电子邮件应用程序", + "max_fee": "最大费用", + "deposit_from_exchanges_notice": "从{0},{1}交易所购买并直接将zkEVM MXC存入AXS钱包", + "deposit_with_l3_bridge_notice": "从其他交易所购买并使用{0}将ERC20 MXC存入AXS钱包", + "buy_some_x_for_fee_notice": "购买一些{0}来支付交易的燃气费,通常在网络不忙的时候少于2美元。", + "insufficient_funds_notice": "此交易余额不足。请添加资金。", + "contractCall": "合约调用", + "contract_call_transactions": "合约调用交易" } \ No newline at end of file diff --git a/assets/flutter_i18n/zh_TW.json b/assets/flutter_i18n/zh_TW.json index c5e4e069..76ee86e3 100644 --- a/assets/flutter_i18n/zh_TW.json +++ b/assets/flutter_i18n/zh_TW.json @@ -300,5 +300,12 @@ "removing_account_warning": "您確定要刪除此帳戶嗎?", "remove": "移除", "duplicate_account_import_notice": "您試圖導入的帳戶是重複的。", - "unable_to_launch_email_app": "無法啟動電子郵件應用程式" + "unable_to_launch_email_app": "無法啟動電子郵件應用程式", + "max_fee": "最高費用", + "deposit_from_exchanges_notice": "從{0},{1}交易所購買並直接將zkEVM MXC存入AXS錢包", + "deposit_with_l3_bridge_notice": "從其他交易所購買,並使用 {0} 存款 ERC20 MXC 到 AXS 錢包", + "buy_some_x_for_fee_notice": "購買一些{0}來支付交易的燃氣費,通常在網路不忙碌時少於2美元。", + "insufficient_funds_notice": "此交易餘額不足。請增加資金。", + "contractCall": "合約呼叫", + "contract_call_transactions": "合約呼叫交易" } \ No newline at end of file diff --git a/lib/common/components/recent_transactions/domain/mxc_transaction_use_case.dart b/lib/common/components/recent_transactions/domain/mxc_transaction_use_case.dart new file mode 100644 index 00000000..44bb44eb --- /dev/null +++ b/lib/common/components/recent_transactions/domain/mxc_transaction_use_case.dart @@ -0,0 +1,80 @@ +import 'package:datadashwallet/common/common.dart'; +import 'package:datadashwallet/core/core.dart'; +import 'package:mxc_logic/mxc_logic.dart'; +import 'package:web3dart/web3dart.dart'; + +class MXCTransactionsUseCase extends ReactiveUseCase { + MXCTransactionsUseCase(this._web3Repository); + + final Web3Repository _web3Repository; + + /// Will remove token transfer (tx that are in general transaction) from general transaction + List removeTokenTransfersFromTxList( + List txList, + List tokenTransferList) { + return txList.where((element) { + if (element.hash != null) { + // 1. Delete if txHash is null + // 2. Delete if tx is token transfer + return tokenTransferList.indexWhere( + (e) => e.txHash == null ? true : e.txHash == element.hash) == + -1; + } else { + return false; + } + }).toList(); + } + + void addTokenTransfersToTxList(List txList, + List tokenTransferList) { + for (int i = 0; i < tokenTransferList.length; i++) { + final item = tokenTransferList[i]; + txList.add(WannseeTransactionModel(tokenTransfers: [item])); + } + } + + List keepOnlySixTransactions( + List txList, + ) { + if (txList.length > 6) { + return txList.sublist(0, 6); + } + return txList; + } + + void sortByDate(List txList) { + if (txList.isNotEmpty) { + txList.sort((a, b) { + final item1 = a.timestamp ?? a.tokenTransfers![0].timestamp; + final item2 = b.timestamp ?? b.tokenTransfers![0].timestamp; + + return item2!.compareTo(item1!); + }); + } + } + + List axsTxListFromMxcTxList( + List mxcTxList, String walletAddress) { + return mxcTxList + .map((e) => TransactionModel.fromMXCTransaction(e, walletAddress)) + .toList(); + } + + void removeInvalidTx(List txList) { + txList.removeWhere( + (element) => element.hash == "Unknown", + ); + } + + List applyTxDateLimit( + List txList) { + final sevenDays = DateTime.now() + .subtract(Duration(days: Config.transactionsHistoryLimit)); + return txList.where((element) { + if (element.timestamp != null) { + return element.timestamp!.isAfter(sevenDays); + } + return element.tokenTransfers![0].timestamp!.isAfter(sevenDays); + }).toList(); + } +} diff --git a/lib/common/components/recent_transactions/domain/transactions_repository.dart b/lib/common/components/recent_transactions/domain/transactions_history_repository.dart similarity index 62% rename from lib/common/components/recent_transactions/domain/transactions_repository.dart rename to lib/common/components/recent_transactions/domain/transactions_history_repository.dart index 2d497e4c..ff39cda5 100644 --- a/lib/common/components/recent_transactions/domain/transactions_repository.dart +++ b/lib/common/components/recent_transactions/domain/transactions_history_repository.dart @@ -7,25 +7,22 @@ class TransactionsHistoryRepository extends ControlledCacheRepository { late final Field> transactionsHistory = fieldWithDefault>('items', [], - serializer: (b) => b - .map((e) => e.toMap()) - .toList(), - deserializer: (b) => (b as List) - .map((e) => TransactionModel.fromMap(e) - ) - .toList()); + serializer: (b) => b.map((e) => e.toMap()).toList(), + deserializer: (b) => + (b as List).map((e) => TransactionModel.fromMap(e)).toList()); List get items => transactionsHistory.value; - - void addItem(TransactionModel item, int index) { final newList = transactionsHistory.value; newList.insert(0, item); transactionsHistory.value = newList; } - void updateItem(TransactionModel item, int index,) { + void updateItem( + TransactionModel item, + int index, + ) { final newList = transactionsHistory.value; newList[index] = item; @@ -33,10 +30,8 @@ class TransactionsHistoryRepository extends ControlledCacheRepository { transactionsHistory.value = newList; } - void removeItem(TransactionModel item) => - transactionsHistory.value = transactionsHistory.value - .where((e) => e.hash != item.hash) - .toList(); + void removeItem(TransactionModel item) => transactionsHistory.value = + transactionsHistory.value.where((e) => e.hash != item.hash).toList(); void removeAll() => transactionsHistory.value = []; } diff --git a/lib/common/components/recent_transactions/domain/transactions_use_case.dart b/lib/common/components/recent_transactions/domain/transactions_history_use_case.dart similarity index 79% rename from lib/common/components/recent_transactions/domain/transactions_use_case.dart rename to lib/common/components/recent_transactions/domain/transactions_history_use_case.dart index 09e2c2df..0ab28540 100644 --- a/lib/common/components/recent_transactions/domain/transactions_use_case.dart +++ b/lib/common/components/recent_transactions/domain/transactions_history_use_case.dart @@ -15,6 +15,8 @@ class TransactionsHistoryUseCase extends ReactiveUseCase { List getTransactionsHistory() => _repository.items; + late final ValueStream shouldUpdateBalances = reactive(false); + List updatingTxList = []; void updateItem( @@ -46,25 +48,37 @@ class TransactionsHistoryUseCase extends ReactiveUseCase { update(transactionsHistory, _repository.items); } + /// This function will spy on the given transaction void spyOnTransaction( TransactionModel item, ) { if (!updatingTxList.contains(item.hash)) { updatingTxList.add(item.hash); final stream = _web3Repository.tokenContract.spyTransaction(item.hash); - stream.onData((succeeded) { - if (succeeded) { - final updatedItem = item.copyWith(status: TransactionStatus.done); + + stream.onData((receipt) { + if (receipt?.status ?? false) { + // success + final itemValue = item.value ?? + (receipt!.gasUsed! * receipt.effectiveGasPrice!.getInWei) + .toString(); + + final updatedItem = + item.copyWith(status: TransactionStatus.done, value: itemValue); updateItem( updatedItem, ); updatingTxList.remove(item.hash); + update(shouldUpdateBalances, true); + stream.cancel(); } }); } } + /// This function will run through all the transactions and will start spying on + /// pending transactions void checkForPendingTransactions(int chainId) { if (!Config.isMxcChains(chainId)) { final txList = transactionsHistory.value; diff --git a/lib/common/components/recent_transactions/recent_transactions.dart b/lib/common/components/recent_transactions/recent_transactions.dart index b0980d56..8a1e89f3 100644 --- a/lib/common/components/recent_transactions/recent_transactions.dart +++ b/lib/common/components/recent_transactions/recent_transactions.dart @@ -9,8 +9,8 @@ import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mxc_ui/mxc_ui.dart'; -export 'domain/transactions_use_case.dart'; -export 'domain/transactions_repository.dart'; +export 'domain/transactions_history_use_case.dart'; +export 'domain/transactions_history_repository.dart'; class RecentTransactions extends HookConsumerWidget { const RecentTransactions({ diff --git a/lib/common/components/recent_transactions/utils.dart b/lib/common/components/recent_transactions/utils.dart index 18d54aa2..9b2a09a5 100644 --- a/lib/common/components/recent_transactions/utils.dart +++ b/lib/common/components/recent_transactions/utils.dart @@ -37,6 +37,9 @@ class RecentTransactionsUtils { case TransactionType.received: txColor = ColorsTheme.of(context).greenMain; break; + case TransactionType.contractCall: + txColor = ColorsTheme.of(context).textGrey1; + break; default: txColor = ColorsTheme.of(context).mainRed; } @@ -52,6 +55,9 @@ class RecentTransactionsUtils { case TransactionType.received: txIcon = MxcIcons.receive; break; + case TransactionType.contractCall: + txIcon = Icons.article_rounded; + break; default: txIcon = Icons.question_mark; } @@ -85,11 +91,13 @@ class RecentTransactionsUtils { 'assets/svg/networks/unknown.svg'; final decimal = foundToken.decimals ?? e.token.decimals ?? Config.ethDecimals; - final symbol = foundToken.symbol ?? e.token.symbol ?? 'Unknown'; + final symbol = foundToken.symbol ?? e.token.symbol; return RecentTrxListItem( logoUrl: logoUrl, - amount: Formatter.convertWeiToEth(e.value, decimal), + amount: e.value == null + ? null + : Formatter.convertWeiToEth(e.value!, decimal), symbol: symbol, timestamp: e.timeStamp == null ? "Unknown" : Formatter.localTime(e.timeStamp!), diff --git a/lib/common/components/recent_transactions/widgets/recent_transaction_item.dart b/lib/common/components/recent_transactions/widgets/recent_transaction_item.dart index f5116b86..ac016c0b 100644 --- a/lib/common/components/recent_transactions/widgets/recent_transaction_item.dart +++ b/lib/common/components/recent_transactions/widgets/recent_transaction_item.dart @@ -11,8 +11,8 @@ import '../../../common.dart'; import '../utils.dart'; class RecentTrxListItem extends HookConsumerWidget { - final String amount; - final String symbol; + final String? amount; + final String? symbol; final String txHash; final String timestamp; final TransactionType transactionType; @@ -86,40 +86,42 @@ class RecentTrxListItem extends HookConsumerWidget { ), Row( children: [ - Text( - amount, - style: FontTheme.of(context) - .body1 - .primary() - .copyWith( - fontWeight: FontWeight.w500, - foreground: state.hideBalance == true - ? (Paint() - ..style = PaintingStyle.fill - ..color = Colors.white - ..maskFilter = const MaskFilter.blur( - BlurStyle.normal, 6)) - : null, - ), - softWrap: true, - ), + if (amount != null) + Text( + amount!, + style: FontTheme.of(context) + .body1 + .primary() + .copyWith( + fontWeight: FontWeight.w500, + foreground: state.hideBalance == true + ? (Paint() + ..style = PaintingStyle.fill + ..color = Colors.white + ..maskFilter = const MaskFilter.blur( + BlurStyle.normal, 6)) + : null, + ), + softWrap: true, + ), const SizedBox( width: 4, ), - Expanded( - child: Text( - symbol, - style: FontTheme.of(context).h7().copyWith( - fontSize: 16, - fontWeight: FontWeight.w400, - color: - ColorsTheme.of(context).textSecondary, - ), - textAlign: TextAlign.start, - overflow: TextOverflow.ellipsis, - softWrap: false, + if (symbol != null) + Expanded( + child: Text( + symbol!, + style: FontTheme.of(context).h7().copyWith( + fontSize: 16, + fontWeight: FontWeight.w400, + color: + ColorsTheme.of(context).textSecondary, + ), + textAlign: TextAlign.start, + overflow: TextOverflow.ellipsis, + softWrap: false, + ), ), - ), ], ), ], diff --git a/lib/common/config.dart b/lib/common/config.dart index 09558729..a85359c2 100644 --- a/lib/common/config.dart +++ b/lib/common/config.dart @@ -5,6 +5,7 @@ class Config { static const int ethDecimals = 18; static const String mxcSymbol = 'MXC'; static const String mxcName = 'MXC Token'; + static const priority = 1.5; static const String zeroAddress = '0x0000000000000000000000000000000000000000'; static const String mxcAddressSepolia = @@ -21,10 +22,17 @@ class Config { static int decimalShowFixed = 3; static int decimalWriteFixed = 8; + /// It's in days + static int transactionsHistoryLimit = 7; + static bool isMxcChains(int chainId) { return chainId == mxcMainnetChainId || chainId == mxcTestnetChainId; } + static bool isMXCMainnet(int chainId) { + return chainId == mxcMainnetChainId; + } + static bool isEthereumMainnet(int chainId) { return chainId == ethereumMainnetChainId; } @@ -41,4 +49,15 @@ class Config { static String addressExplorer(String address) { return 'address/$address'; } + + /// If error happens with these messages then we will need to show receive bottom sheet + static List fundErrors = [ + // User doesn't have enough to pay for native token transfer + // Zero native token balance or not enough for fee + 'gas required exceeds allowance', + // Sending more than tokens balance + 'execution reverted: ERC20: transfer amount exceeds balance', + // Sending more than native token balance + 'insufficient funds for gas * price + value' + ]; } diff --git a/lib/common/dialogs/dialogs.dart b/lib/common/dialogs/dialogs.dart index e57c5215..ac22c940 100644 --- a/lib/common/dialogs/dialogs.dart +++ b/lib/common/dialogs/dialogs.dart @@ -1,3 +1,4 @@ export 'alert_dialog.dart'; export 'bottom_sheet.dart'; export 'warning_dialog.dart'; +export 'receive_bottom_sheet/receive_bottom_sheet.dart'; diff --git a/lib/common/dialogs/receive_bottom_sheet/components/black_box.dart b/lib/common/dialogs/receive_bottom_sheet/components/black_box.dart new file mode 100644 index 00000000..cc1883f3 --- /dev/null +++ b/lib/common/dialogs/receive_bottom_sheet/components/black_box.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:mxc_ui/mxc_ui.dart'; + +class BlackBox extends StatelessWidget { + const BlackBox({super.key, required this.child}); + + final Widget child; + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(top: 16), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: ColorsTheme.of(context).grey6, + borderRadius: const BorderRadius.all(Radius.circular(35)), + ), + child: child, + ); + } +} diff --git a/lib/common/dialogs/receive_bottom_sheet/components/mxc_notices.dart b/lib/common/dialogs/receive_bottom_sheet/components/mxc_notices.dart new file mode 100644 index 00000000..29d9f709 --- /dev/null +++ b/lib/common/dialogs/receive_bottom_sheet/components/mxc_notices.dart @@ -0,0 +1,68 @@ +import 'package:datadashwallet/common/common.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_i18n/flutter_i18n.dart'; +import 'package:mxc_ui/mxc_ui.dart'; + +List depositFromExchangesNotice( + BuildContext context, void Function(String) launchUrl) { + final text = FlutterI18n.translate(context, 'deposit_from_exchanges_notice'); + final firstSplit = text.split('{0}'); + final firstPart = firstSplit[0]; + final secondSplit = firstSplit[1].split('{1}'); + final secondPart = secondSplit[0]; + final thirdPart = secondSplit[1]; + return [ + TextSpan( + text: firstPart, + ), + TextSpan( + text: 'OKX', + style: TextStyle( + color: ColorsTheme.of(context, listen: false).textSecondary, + decoration: TextDecoration.underline, + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + launchUrl(Urls.okx); + }, + ), + TextSpan(text: secondPart), + TextSpan( + text: 'Gate.io', + style: TextStyle( + color: ColorsTheme.of(context, listen: false).textSecondary, + decoration: TextDecoration.underline, + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + launchUrl(Urls.gateio); + }, + ), + TextSpan(text: thirdPart), + ]; +} + +List depositWithL3BridgeNotice( + BuildContext context, + VoidCallback onL3Tap, +) { + final text = FlutterI18n.translate(context, 'deposit_with_l3_bridge_notice'); + final firstSplit = text.split('{0}'); + final firstPart = firstSplit[0]; + final secondPart = firstSplit[1]; + return [ + TextSpan( + text: firstPart, + ), + TextSpan( + text: 'L3 bridge', + style: TextStyle( + color: ColorsTheme.of(context, listen: false).textSecondary, + decoration: TextDecoration.underline, + ), + recognizer: TapGestureRecognizer()..onTap = onL3Tap, + ), + TextSpan(text: secondPart), + ]; +} diff --git a/lib/common/dialogs/receive_bottom_sheet/components/others_notice.dart b/lib/common/dialogs/receive_bottom_sheet/components/others_notice.dart new file mode 100644 index 00000000..604b92f6 --- /dev/null +++ b/lib/common/dialogs/receive_bottom_sheet/components/others_notice.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_i18n/flutter_i18n.dart'; + +List buySomeXForFeeNotice( + BuildContext context, String networkSymbol) { + String text = FlutterI18n.translate(context, 'buy_some_x_for_fee_notice'); + text = text.replaceFirst('{0}', networkSymbol); + return [ + TextSpan( + text: text, + ), + ]; +} diff --git a/lib/features/portfolio/presentation/widgets/show_wallet_address_dialog.dart b/lib/common/dialogs/receive_bottom_sheet/components/receive_bottom_sheet.dart similarity index 81% rename from lib/features/portfolio/presentation/widgets/show_wallet_address_dialog.dart rename to lib/common/dialogs/receive_bottom_sheet/components/receive_bottom_sheet.dart index 12c27bd4..9f97e8f0 100644 --- a/lib/features/portfolio/presentation/widgets/show_wallet_address_dialog.dart +++ b/lib/common/dialogs/receive_bottom_sheet/components/receive_bottom_sheet.dart @@ -1,37 +1,23 @@ import 'dart:ui'; import 'package:clipboard/clipboard.dart'; -import 'package:datadashwallet/common/utils/utils.dart'; +import 'package:datadashwallet/common/common.dart'; import 'package:flutter/material.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:mxc_ui/mxc_ui.dart'; import 'package:qr_flutter/qr_flutter.dart'; -void showWalletAddressDialog({ - required BuildContext context, - String? walletAddress, -}) { - showModalBottomSheet( - context: context, - useRootNavigator: true, - isScrollControlled: true, - useSafeArea: true, - backgroundColor: Colors.transparent, - builder: (BuildContext context) => WalletAddress( - walletAddress: walletAddress, - ), - ); -} - -class WalletAddress extends StatelessWidget { - const WalletAddress({ - Key? key, - this.walletAddress, - this.onTap, - }) : super(key: key); +class ReceiveBottomSheet extends StatelessWidget { + const ReceiveBottomSheet( + {Key? key, + this.walletAddress, + required this.noticeComponents, + required this.showError}) + : super(key: key); final String? walletAddress; - final VoidCallback? onTap; + final List noticeComponents; + final bool showError; @override Widget build(BuildContext context) { @@ -66,6 +52,12 @@ class WalletAddress extends StatelessWidget { ), ), ), + if (showError) + Text( + FlutterI18n.translate(context, 'insufficient_funds_notice'), + style: FontTheme.of(context).body1.error(), + textAlign: TextAlign.center, + ), QrImageView( data: walletAddress ?? '', size: 215, @@ -83,13 +75,7 @@ class WalletAddress extends StatelessWidget { style: FontTheme.of(context).body1.secondary(), textAlign: TextAlign.center, ), - const SizedBox(height: 16), - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: ColorsTheme.of(context).grey6, - borderRadius: const BorderRadius.all(Radius.circular(35)), - ), + BlackBox( child: StatefulBuilder(builder: (_, setState) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -123,7 +109,8 @@ class WalletAddress extends StatelessWidget { ) ]); }), - ) + ), + ...noticeComponents ], ), ), diff --git a/lib/common/dialogs/receive_bottom_sheet/components/receive_bottom_sheet_components.dart b/lib/common/dialogs/receive_bottom_sheet/components/receive_bottom_sheet_components.dart new file mode 100644 index 00000000..45b07b73 --- /dev/null +++ b/lib/common/dialogs/receive_bottom_sheet/components/receive_bottom_sheet_components.dart @@ -0,0 +1,4 @@ +export 'black_box.dart'; +export 'receive_bottom_sheet.dart'; +export 'mxc_notices.dart'; +export 'others_notice.dart'; diff --git a/lib/common/dialogs/receive_bottom_sheet/receive_bottom_sheet.dart b/lib/common/dialogs/receive_bottom_sheet/receive_bottom_sheet.dart new file mode 100644 index 00000000..2ad1aa75 --- /dev/null +++ b/lib/common/dialogs/receive_bottom_sheet/receive_bottom_sheet.dart @@ -0,0 +1,2 @@ +export 'receive_bottom_sheet_utils.dart'; +export 'components/receive_bottom_sheet_components.dart'; diff --git a/lib/common/dialogs/receive_bottom_sheet/receive_bottom_sheet_utils.dart b/lib/common/dialogs/receive_bottom_sheet/receive_bottom_sheet_utils.dart new file mode 100644 index 00000000..be9c4149 --- /dev/null +++ b/lib/common/dialogs/receive_bottom_sheet/receive_bottom_sheet_utils.dart @@ -0,0 +1,103 @@ +import 'package:datadashwallet/common/common.dart'; +import 'package:datadashwallet/core/core.dart'; +import 'package:datadashwallet/features/dapps/dapps.dart'; +import 'package:flutter/material.dart'; +import 'package:mxc_ui/mxc_ui.dart'; + +void showReceiveBottomSheet( + BuildContext context, + String walletAddress, + int chainId, + String networkSymbol, + VoidCallback onL3Tap, + void Function(String url) launchUrlInPlatformDefault, + bool showError) { + if (Config.isMxcChains(chainId)) { + showWalletAddressDialogMXCChains( + context: context, + walletAddress: walletAddress, + onL3Tap: () => onL3Tap(), + launchUrlInPlatformDefault: launchUrlInPlatformDefault, + showError: showError); + } else { + showWalletAddressDialogOtherChains( + context: context, + walletAddress: walletAddress, + networkSymbol: networkSymbol, + showError: showError); + } +} + +void showWalletAddressDialogMXCChains( + {required BuildContext context, + required String walletAddress, + required VoidCallback onL3Tap, + required Function(String) launchUrlInPlatformDefault, + required bool showError}) => + showWalletAddressDialog( + context: context, + walletAddress: walletAddress, + noticeComponents: [ + BlackBox( + child: applyTextStyle( + context, + depositFromExchangesNotice( + context, launchUrlInPlatformDefault))), + BlackBox( + child: applyTextStyle( + context, depositWithL3BridgeNotice(context, onL3Tap))), + ], + showError: showError); + +void showWalletAddressDialogOtherChains( + {required BuildContext context, + required String walletAddress, + required String networkSymbol, + required bool showError}) => + showWalletAddressDialog( + context: context, + walletAddress: walletAddress, + noticeComponents: [ + BlackBox( + child: applyTextStyle( + context, buySomeXForFeeNotice(context, networkSymbol))) + ], + showError: showError); + +void showWalletAddressDialogSimple( + {required BuildContext context, + required String walletAddress, + required bool showError}) => + showWalletAddressDialog( + context: context, + walletAddress: walletAddress, + noticeComponents: [], + showError: showError); + +void showWalletAddressDialog( + {required BuildContext context, + required String walletAddress, + required List noticeComponents, + required bool showError}) { + showModalBottomSheet( + context: context, + useRootNavigator: true, + isScrollControlled: true, + useSafeArea: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) => ReceiveBottomSheet( + walletAddress: walletAddress, + noticeComponents: noticeComponents, + showError: showError, + ), + ); +} + +Widget applyTextStyle(BuildContext context, List children) { + return RichText( + text: TextSpan( + style: FontTheme.of(context, listen: false).body1.primary(), + children: [...children], + ), + ); +} diff --git a/lib/common/urls.dart b/lib/common/urls.dart index ef9d7ea2..cb06777c 100644 --- a/lib/common/urls.dart +++ b/lib/common/urls.dart @@ -1,3 +1,5 @@ +import 'package:datadashwallet/common/common.dart'; + class Urls { static const String mxcMainnetNftMarketPlace = 'https://nft.mxc.com/'; static const String mxcTestnetNftMarketPlace = 'https://wannsee-nft.mxc.com/'; @@ -10,4 +12,12 @@ class Urls { 'https://apps.apple.com/us/app/axs-decentralized-wallet/id6460891587'; static const String emailApp = 'mailto:'; + + static const String gateio = 'https://gate.io/'; + static const String okx = 'https://www.okx.com/'; + static const String mainnetL3Bridge = 'https://erc20.mxc.com/'; + static const String testnetL3Bridge = 'https://wannsee-erc20.mxc.com/'; + + static String networkL3Bridge(int chainId) => + Config.isMXCMainnet(chainId) ? mainnetL3Bridge : testnetL3Bridge; } diff --git a/lib/common/utils/formatter.dart b/lib/common/utils/formatter.dart index 4d8d3f24..9049d63d 100644 --- a/lib/common/utils/formatter.dart +++ b/lib/common/utils/formatter.dart @@ -125,4 +125,8 @@ class Formatter { return words.join(' '); // Join words back with a single space between each } + + static String removeZeroX(String value) { + return value.startsWith('0x') ? value.substring(2) : value; + } } diff --git a/lib/common/utils/validation.dart b/lib/common/utils/validation.dart index ef9f1db0..57ea6833 100644 --- a/lib/common/utils/validation.dart +++ b/lib/common/utils/validation.dart @@ -1,9 +1,9 @@ import 'package:datadashwallet/common/config.dart'; +import 'package:datadashwallet/common/utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:web3dart/web3dart.dart'; - class Validation { static String? notEmpty(BuildContext context, String? value, [String? errorText]) { @@ -56,13 +56,9 @@ class Validation { } static String? checkEthereumPrivateKey(BuildContext context, String value) { - String ethereumPrivateKeyPattern = r'^[0-9a-fA-F]{64}$'; - - if (!RegExp(ethereumPrivateKeyPattern, caseSensitive: false) - .hasMatch(value)) { + if (!isPrivateKey(value)) { return FlutterI18n.translate(context, 'invalid_format'); } - return null; } @@ -132,7 +128,6 @@ class Validation { return regex.hasMatch(value); } - static bool isAddress(String address) { try { EthereumAddress.fromHex(address); @@ -144,7 +139,7 @@ class Validation { static bool isPrivateKey(String privateKey) { try { - EthPrivateKey.fromHex(privateKey); + EthPrivateKey.fromHex(Formatter.removeZeroX(privateKey)); return true; } catch (e) { return false; diff --git a/lib/core/src/providers/providers_use_cases.dart b/lib/core/src/providers/providers_use_cases.dart index 52789cbe..a109c45e 100644 --- a/lib/core/src/providers/providers_use_cases.dart +++ b/lib/core/src/providers/providers_use_cases.dart @@ -1,4 +1,5 @@ import 'package:datadashwallet/common/common.dart'; +import 'package:datadashwallet/common/components/recent_transactions/domain/mxc_transaction_use_case.dart'; import 'package:datadashwallet/features/common/account/log_out_use_case.dart'; import 'package:datadashwallet/features/common/common.dart'; import 'package:datadashwallet/features/common/contract/chains_use_case.dart'; @@ -114,6 +115,13 @@ final Provider chainConfigurationUseCaseProvider = ), ); +final Provider mxcTransactionsUseCaseProvider = + Provider( + (ref) => MXCTransactionsUseCase( + ref.watch(web3RepositoryProvider), + ), +); + final Provider transactionHistoryUseCaseProvider = Provider( (ref) => TransactionsHistoryUseCase( @@ -155,3 +163,11 @@ final Provider chainsUseCaseProvider = Provider( ref.watch(authUseCaseProvider), ), ); + +final Provider errorUseCaseProvider = Provider( + (ref) => ErrorUseCase( + ref.watch(web3RepositoryProvider), + ref.watch(accountUseCaseProvider), + ref.watch(chainConfigurationUseCaseProvider), + ), +); diff --git a/lib/features/common/app/app.dart b/lib/features/common/app/app.dart new file mode 100644 index 00000000..098b3f2e --- /dev/null +++ b/lib/features/common/app/app.dart @@ -0,0 +1,2 @@ +export 'error_use_case.dart'; +export 'launcher_use_case.dart'; \ No newline at end of file diff --git a/lib/features/common/app/error_use_case.dart b/lib/features/common/app/error_use_case.dart new file mode 100644 index 00000000..e0753eb2 --- /dev/null +++ b/lib/features/common/app/error_use_case.dart @@ -0,0 +1,71 @@ +import 'dart:async'; + +import 'package:datadashwallet/common/common.dart'; +import 'package:datadashwallet/core/core.dart'; +import 'package:datadashwallet/features/common/common.dart'; +import 'package:datadashwallet/features/settings/subfeatures/chain_configuration/domain/chain_configuration_use_case.dart'; +import 'package:flutter/material.dart'; +import 'package:mxc_logic/mxc_logic.dart'; +import 'package:web3dart/web3dart.dart'; +import 'package:web3dart/json_rpc.dart'; + +class ErrorUseCase extends ReactiveUseCase { + ErrorUseCase( + this._repository, + this._accountUseCase, + this._chainConfigurationUseCase, + ); + + final Web3Repository _repository; + final AccountUseCase _accountUseCase; + final ChainConfigurationUseCase _chainConfigurationUseCase; + + /// If error is known & handled will return true, otherwise return false. + handleError(BuildContext context, dynamic e, {VoidCallback? onL3Tap}) { + if (e is RPCError) { + return handlerRPCError(context, e.message, onL3Tap!); + } else { + return false; + } + } + + bool handlerRPCError( + BuildContext context, String message, VoidCallback onL3Tap) { + final isInsufficientFundError = isFundError(message); + if (isInsufficientFundError) { + final network = _chainConfigurationUseCase.selectedNetwork.value!; + final walletAddress = _accountUseCase.account.value!.address; + showReceiveBottomSheet( + context, + walletAddress, + network.chainId, + network.symbol, + onL3Tap, + _chainConfigurationUseCase.launchUrlInPlatformDefault, + true); + } + + return isInsufficientFundError; + // String errorMessage = message; + // errorMessage = changeErrorMessage(errorMessage); + // addError(errorMessage); + } + + bool isFundError(String message) { + bool isError = false; + for (String error in Config.fundErrors) { + if (message.contains(error)) { + isError = true; + break; + } + } + return isError; + } + + // String _changeErrorMessage(String message) { + // if (message.contains('gas required exceeds allowance')) { + // return translate('insufficient_balance_for_fee') ?? message; + // } + // return message; + // } +} diff --git a/lib/features/common/app/launcher_use_case.dart b/lib/features/common/app/launcher_use_case.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/features/common/common.dart b/lib/features/common/common.dart index a68688c1..187e5eaa 100644 --- a/lib/features/common/common.dart +++ b/lib/features/common/common.dart @@ -1,3 +1,4 @@ export 'contract/token_contract_use_case.dart'; export 'account/account_use_case.dart'; export 'app_nav_bar/app_nav_bar.dart'; +export 'app/app.dart'; diff --git a/lib/features/common/contract/token_contract_use_case.dart b/lib/features/common/contract/token_contract_use_case.dart index 3bfe4977..942c0283 100644 --- a/lib/features/common/contract/token_contract_use_case.dart +++ b/lib/features/common/contract/token_contract_use_case.dart @@ -38,12 +38,16 @@ class TokenContractUseCase extends ReactiveUseCase { balance.getInWei.toString(), Config.ethDecimals); } - Future?> subscribeToBalance(String event) async { - return await _repository.tokenContract.subscribeToBalanceEvent( + Future?> subscribeEvent(String event) async { + return await _repository.tokenContract.subscribeEvent( event, ); } + Future connectToWebsSocket() async { + return await _repository.tokenContract.connectToWebSocket(); + } + Future getTransactionsByAddress( String address) async { return _repository.tokenContract.getTransactionsByAddress(address); @@ -67,7 +71,7 @@ class TokenContractUseCase extends ReactiveUseCase { final cNetwork = _repository.tokenContract.getCurrentNetwork(); final chainNativeToken = Token( - logoUri: result?.logoUri ?? 'assets/svg/networks/unknown.svg', + logoUri: result?.logoUri ?? cNetwork.logo, symbol: cNetwork.symbol, name: '${cNetwork.symbol} Token', decimals: Config.ethDecimals); @@ -236,7 +240,7 @@ class TokenContractUseCase extends ReactiveUseCase { update(totalBalanceInXsd, totalPrice); } - StreamSubscription spyOnTransaction(String hash) { + StreamSubscription spyOnTransaction(String hash) { return _repository.tokenContract.spyTransaction(hash); } } diff --git a/lib/features/dapps/subfeatures/open_dapp/open_dapp_presenter.dart b/lib/features/dapps/subfeatures/open_dapp/open_dapp_presenter.dart index 41a75575..925c2983 100644 --- a/lib/features/dapps/subfeatures/open_dapp/open_dapp_presenter.dart +++ b/lib/features/dapps/subfeatures/open_dapp/open_dapp_presenter.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'package:datadashwallet/common/common.dart'; + import 'package:datadashwallet/core/core.dart'; import 'package:datadashwallet/features/dapps/subfeatures/open_dapp/widgets/add_asset_dialog.dart'; import 'package:datadashwallet/features/dapps/subfeatures/open_dapp/widgets/swtich_network_dialog.dart'; @@ -10,6 +11,7 @@ import 'package:url_launcher/url_launcher.dart'; import 'package:web3_provider/web3_provider.dart'; import 'package:web3dart/web3dart.dart'; import 'package:eth_sig_util/util/utils.dart'; +import 'package:web3dart/json_rpc.dart'; import 'open_dapp_state.dart'; import 'widgets/bridge_params.dart'; @@ -30,6 +32,7 @@ class OpenDAppPresenter extends CompletePresenter { late final _accountUseCase = ref.read(accountUseCaseProvider); late final _authUseCase = ref.read(authUseCaseProvider); late final _customTokensUseCase = ref.read(customTokensUseCaseProvider); + late final _errorUseCase = ref.read(errorUseCaseProvider); @override void initState() { @@ -86,26 +89,18 @@ class OpenDAppPresenter extends CompletePresenter { Future _sendTransaction(String to, EtherAmount amount, Uint8List? data, EstimatedGasFee? estimatedGasFee, String url, {String? from}) async { - loading = true; - try { - final res = await _tokenContractUseCase.sendTransaction( - privateKey: state.account!.privateKey, - to: to, - from: from, - amount: amount, - data: data, - estimatedGasFee: estimatedGasFee); - if (!Config.isMxcChains(state.network!.chainId) && - Config.isL3Bridge(url)) { - recordTransaction(res); - } - - return res; - } catch (e, s) { - addError(e, s); - } finally { - loading = false; + final res = await _tokenContractUseCase.sendTransaction( + privateKey: state.account!.privateKey, + to: to, + from: from, + amount: amount, + data: data, + estimatedGasFee: estimatedGasFee); + if (!Config.isMxcChains(state.network!.chainId)) { + recordTransaction(res); } + + return res; } String? _signTypedMessage( @@ -139,23 +134,21 @@ class OpenDAppPresenter extends CompletePresenter { void recordTransaction(String hash) { final timeStamp = DateTime.now(); const txStatus = TransactionStatus.pending; - const txType = TransactionType.sent; - final chainId = state.network!.chainId; + const txType = TransactionType.contractCall; + final currentNetwork = state.network!; + final chainId = currentNetwork.chainId; final token = Token( - chainId: state.network!.chainId, - logoUri: Assets.mxcLogoUri, - name: Config.mxcName, - symbol: Config.mxcSymbol, - // can separate Sepolia & Ethereum - address: Config.isEthereumMainnet(chainId) - ? Config.mxcAddressEthereum - : Config.mxcAddressSepolia); + chainId: currentNetwork.chainId, + logoUri: currentNetwork.logo, + name: currentNetwork.label ?? currentNetwork.web3RpcHttpUrl, + symbol: currentNetwork.symbol, + address: null); final tx = TransactionModel( hash: hash, timeStamp: timeStamp, status: txStatus, type: txType, - value: '0', + value: null, token: token, ); @@ -205,6 +198,10 @@ class OpenDAppPresenter extends CompletePresenter { } String finalFee = estimatedGasFee.gasFee.toString(); + final maxFeeDouble = estimatedGasFee.gasFee * Config.priority; + final maxFeeString = maxFeeDouble.toString(); + final maxFee = + Validation.isExpoNumber(maxFeeString) ? '0.000' : maxFeeString; if (Validation.isExpoNumber(finalFee)) { finalFee = '0.000'; @@ -219,9 +216,12 @@ class OpenDAppPresenter extends CompletePresenter { from: bridge.from!, to: bridge.to!, estimatedFee: finalFee, + maxFee: maxFee, symbol: symbol); if (result != null && result) { + loading = true; + final hash = await _sendTransaction( bridge.to!, amountEther, bridgeData, estimatedGasFee, url, from: bridge.from); @@ -231,6 +231,25 @@ class OpenDAppPresenter extends CompletePresenter { } } catch (e, s) { cancel.call(); + callErrorHandler(e, s); + } finally { + loading = false; + } + } + + void callErrorHandler(dynamic e, StackTrace s) { + final isHandled = _errorUseCase.handleError( + context!, + e, + onL3Tap: () { + navigator!.pop(); + final chainId = state.network!.chainId; + final l3BridgeUri = Uri.parse(Urls.networkL3Bridge(chainId)); + state.webviewController! + .loadUrl(urlRequest: URLRequest(url: l3BridgeUri)); + }, + ); + if (!isHandled) { addError(e, s); } } diff --git a/lib/features/dapps/subfeatures/open_dapp/widgets/transaction_dialog.dart b/lib/features/dapps/subfeatures/open_dapp/widgets/transaction_dialog.dart index 53f5dadc..7420731f 100644 --- a/lib/features/dapps/subfeatures/open_dapp/widgets/transaction_dialog.dart +++ b/lib/features/dapps/subfeatures/open_dapp/widgets/transaction_dialog.dart @@ -8,7 +8,8 @@ Future showTransactionDialog(BuildContext context, required String amount, required String from, required String to, - String? estimatedFee, + required String estimatedFee, + required String maxFee, VoidCallback? onTap, required String symbol}) { return showModalBottomSheet( @@ -45,6 +46,7 @@ Future showTransactionDialog(BuildContext context, from: from, to: to, estimatedFee: estimatedFee, + maxFee: maxFee, onTap: onTap, symbol: symbol, ), diff --git a/lib/features/dapps/subfeatures/open_dapp/widgets/transaction_info.dart b/lib/features/dapps/subfeatures/open_dapp/widgets/transaction_info.dart index e119c9ee..f606dfc9 100644 --- a/lib/features/dapps/subfeatures/open_dapp/widgets/transaction_info.dart +++ b/lib/features/dapps/subfeatures/open_dapp/widgets/transaction_info.dart @@ -15,7 +15,8 @@ class TransactionInfo extends ConsumerWidget { required this.amount, required this.from, required this.to, - this.estimatedFee, + required this.estimatedFee, + required this.maxFee, this.onTap, required this.symbol}) : super(key: key); @@ -23,7 +24,8 @@ class TransactionInfo extends ConsumerWidget { final String amount; final String from; final String to; - final String? estimatedFee; + final String estimatedFee; + final String maxFee; final VoidCallback? onTap; final String symbol; @@ -44,16 +46,20 @@ class TransactionInfo extends ConsumerWidget { title: 'to', value: to, ), - if (estimatedFee != null) - SingleLineInfoItem( - title: 'estimated_fee', - value: estimatedFee != null - ? Formatter.formatNumberForUI( - estimatedFee!, - ) - : '--', - hint: symbol, + SingleLineInfoItem( + title: 'estimated_fee', + value: Formatter.formatNumberForUI( + estimatedFee, ), + hint: symbol, + ), + SingleLineInfoItem( + title: 'max_fee', + value: Formatter.formatNumberForUI( + maxFee, + ), + hint: symbol, + ), ], ), ), diff --git a/lib/features/portfolio/presentation/portfolio_page.dart b/lib/features/portfolio/presentation/portfolio_page.dart index 9f7eee5c..edb5306f 100644 --- a/lib/features/portfolio/presentation/portfolio_page.dart +++ b/lib/features/portfolio/presentation/portfolio_page.dart @@ -14,7 +14,6 @@ import 'package:datadashwallet/common/common.dart'; import 'package:mxc_ui/mxc_ui.dart'; import 'portfolio_page_presenter.dart'; -import 'widgets/show_wallet_address_dialog.dart'; class PortfolioPage extends HookConsumerWidget { const PortfolioPage({Key? key}) : super(key: key); @@ -97,10 +96,7 @@ class PortfolioPage extends HookConsumerWidget { .iconButtonBackgroundActive, color: ColorsTheme.of(context).iconButtonInvertActive, icon: MxcIcons.receive, - onTap: () => showWalletAddressDialog( - context: context, - walletAddress: state.walletAddress, - ), + onTap: () => presenter.showReceiveSheet(), titleStyle: FontTheme.of(context).subtitle1.primary(), iconSize: 24, filled: false, diff --git a/lib/features/portfolio/presentation/portfolio_page_presenter.dart b/lib/features/portfolio/presentation/portfolio_page_presenter.dart index 0bc9beb5..3266713f 100644 --- a/lib/features/portfolio/presentation/portfolio_page_presenter.dart +++ b/lib/features/portfolio/presentation/portfolio_page_presenter.dart @@ -1,5 +1,7 @@ import 'package:datadashwallet/common/common.dart'; import 'package:datadashwallet/core/core.dart'; +import 'package:datadashwallet/features/dapps/dapps.dart'; +import 'package:flutter/material.dart'; import 'portfolio_page_state.dart'; final portfolioContainer = @@ -30,6 +32,12 @@ class PortfolioPresenter extends CompletePresenter { getBuyEnabled(); }); + listen(_chainConfigurationUseCase.selectedNetwork, (value) { + if (value != null) { + state.network = value; + } + }); + listen(_accountUserCase.account, (value) { if (value != null) { notify(() => state.walletAddress = value.address); @@ -91,4 +99,19 @@ class PortfolioPresenter extends CompletePresenter { return null; } } + + void showReceiveSheet() { + final walletAddress = state.walletAddress!; + final chainId = state.network!.chainId; + final networkSymbol = state.network!.symbol; + showReceiveBottomSheet(context!, walletAddress, chainId, networkSymbol, () { + final l3BridgeUri = Urls.networkL3Bridge(chainId); + Navigator.of(context!).push(route.featureDialog( + maintainState: false, + OpenAppPage( + url: l3BridgeUri, + ), + )); + }, _chainConfigurationUseCase.launchUrlInPlatformDefault, false); + } } diff --git a/lib/features/portfolio/presentation/portfolio_page_state.dart b/lib/features/portfolio/presentation/portfolio_page_state.dart index 2e9cd483..b44446cd 100644 --- a/lib/features/portfolio/presentation/portfolio_page_state.dart +++ b/lib/features/portfolio/presentation/portfolio_page_state.dart @@ -10,6 +10,8 @@ class PortfolioState with EquatableMixin { String? walletAddress; + Network? network; + bool switchTokensOrNFTs = true; String? ipfsGateway; diff --git a/lib/features/portfolio/subfeatures/nft/send_nft/send_nft_presenter.dart b/lib/features/portfolio/subfeatures/nft/send_nft/send_nft_presenter.dart index 232ffa82..1d6b2641 100644 --- a/lib/features/portfolio/subfeatures/nft/send_nft/send_nft_presenter.dart +++ b/lib/features/portfolio/subfeatures/nft/send_nft/send_nft_presenter.dart @@ -104,7 +104,7 @@ class SendNftPresenter extends CompletePresenter { BottomFlowDialog.of(context!).close(); ref.read(chooseCryptoPageContainer.actions).loadPage(); - ref.read(walletContainer.actions).initializeWalletPage(); + // ref.read(walletContainer.actions).initializeWalletPage(); } } diff --git a/lib/features/portfolio/subfeatures/token/send_token/choose_crypto/choose_crypto_page.dart b/lib/features/portfolio/subfeatures/token/send_token/choose_crypto/choose_crypto_page.dart index 2aeca5a0..bb6b760f 100644 --- a/lib/features/portfolio/subfeatures/token/send_token/choose_crypto/choose_crypto_page.dart +++ b/lib/features/portfolio/subfeatures/token/send_token/choose_crypto/choose_crypto_page.dart @@ -92,6 +92,11 @@ class ChooseCryptoPage extends HookConsumerWidget { SendCryptoPage( token: token, qrCode: qrCode, + isBalanceZero: + ref.watch(state).tokens?[0].balance == null + ? false + : ref.watch(state).tokens![0].balance! <= + 0.0, ), ), ), diff --git a/lib/features/portfolio/subfeatures/token/send_token/choose_crypto/choose_crypto_presenter.dart b/lib/features/portfolio/subfeatures/token/send_token/choose_crypto/choose_crypto_presenter.dart index 0bb9810e..d7eee08b 100644 --- a/lib/features/portfolio/subfeatures/token/send_token/choose_crypto/choose_crypto_presenter.dart +++ b/lib/features/portfolio/subfeatures/token/send_token/choose_crypto/choose_crypto_presenter.dart @@ -24,7 +24,6 @@ class ChooseCryptoPresenter extends CompletePresenter { listen(_accountUserCase.account, (value) { if (value != null) { notify(() => state.account = value); - loadPage(); } }); diff --git a/lib/features/portfolio/subfeatures/token/send_token/send_crypto/send_crypto_page.dart b/lib/features/portfolio/subfeatures/token/send_token/send_crypto/send_crypto_page.dart index 4326f3a8..0f255228 100644 --- a/lib/features/portfolio/subfeatures/token/send_token/send_crypto/send_crypto_page.dart +++ b/lib/features/portfolio/subfeatures/token/send_token/send_crypto/send_crypto_page.dart @@ -16,10 +16,12 @@ class SendCryptoPage extends HookConsumerWidget { Key? key, required this.token, this.qrCode, + required this.isBalanceZero, }) : super(key: key); final Token token; final String? qrCode; + final bool isBalanceZero; @override ProviderBase get presenter => diff --git a/lib/features/portfolio/subfeatures/token/send_token/send_crypto/send_crypto_presenter.dart b/lib/features/portfolio/subfeatures/token/send_token/send_crypto/send_crypto_presenter.dart index 93b07d4d..bbf3790a 100644 --- a/lib/features/portfolio/subfeatures/token/send_token/send_crypto/send_crypto_presenter.dart +++ b/lib/features/portfolio/subfeatures/token/send_token/send_crypto/send_crypto_presenter.dart @@ -1,8 +1,10 @@ +import 'package:datadashwallet/common/common.dart'; import 'package:datadashwallet/common/config.dart'; import 'package:datadashwallet/common/utils/utils.dart'; import 'package:datadashwallet/core/core.dart'; import 'package:datadashwallet/features/common/common.dart'; import 'package:datadashwallet/features/common/app_nav_bar/app_nav_bar_presenter.dart'; +import 'package:datadashwallet/features/dapps/dapps.dart'; import 'package:web3dart/web3dart.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; @@ -24,7 +26,10 @@ class SendCryptoArguments with EquatableMixin { final String? qrCode; @override - List get props => [token, qrCode]; + List get props => [ + token, + qrCode, + ]; } final sendTokenPageContainer = PresenterContainerWithParameter< @@ -35,8 +40,10 @@ final sendTokenPageContainer = PresenterContainerWithParameter< )); class SendCryptoPresenter extends CompletePresenter { - SendCryptoPresenter(this.token, String? qrCode) - : super(SendCryptoState()..qrCode = qrCode); + SendCryptoPresenter( + this.token, + String? qrCode, + ) : super(SendCryptoState()..qrCode = qrCode); final Token token; @@ -48,6 +55,10 @@ class SendCryptoPresenter extends CompletePresenter { late final _chainConfigurationUserCase = ref.read(chainConfigurationUseCaseProvider); late final accountInfo = ref.read(appNavBarContainer.state); + late final _chainConfigurationUseCase = + ref.read(chainConfigurationUseCaseProvider); + late final _errorUseCase = ref.read(errorUseCaseProvider); + late final TextEditingController amountController = TextEditingController(); late final TextEditingController recipientController = TextEditingController(); @@ -146,10 +157,8 @@ class SendCryptoPresenter extends CompletePresenter { return; } - EstimatedGasFee? estimatedGasFee; - double sumBalance = token.balance! - double.parse(amount); - estimatedGasFee = await _estimatedFee(recipientAddress); + EstimatedGasFee? estimatedGasFee = await _estimatedFee(recipientAddress); if (estimatedGasFee != null) { sumBalance -= estimatedGasFee.gasFee; final estimatedFee = @@ -157,6 +166,11 @@ class SendCryptoPresenter extends CompletePresenter { ? '0.000' : estimatedGasFee.gasFee.toString(); + final maxFeeDouble = estimatedGasFee.gasFee * Config.priority; + final maxFeeString = maxFeeDouble.toString(); + final maxFee = + Validation.isExpoNumber(maxFeeString) ? '0.000' : maxFeeString; + final result = await showTransactionDialog(context!, amount: amount, balance: sumBalance.toString(), @@ -165,8 +179,9 @@ class SendCryptoPresenter extends CompletePresenter { from: state.account!.address, to: recipient, estimatedFee: estimatedFee, + maxFee: maxFee, onTap: (transactionType) => - _nextTransactionStep(transactionType, estimatedGasFee!), + _nextTransactionStep(transactionType, estimatedGasFee), networkSymbol: state.network?.symbol ?? '--'); } } @@ -191,7 +206,12 @@ class SendCryptoPresenter extends CompletePresenter { } return res; } else if (TransactionProcessType.done == type) { - BottomFlowDialog.of(context!).close(); + navigator!.pop(); + Future.delayed(const Duration(milliseconds: 200), () { + navigator?.popUntil((route) { + return route.settings.name?.contains('WalletPage') ?? false; + }); + }); } } @@ -206,11 +226,7 @@ class SendCryptoPresenter extends CompletePresenter { return gasFee; } catch (e, s) { - if (e is RPCError) { - String errorMessage = e.message; - errorMessage = changeErrorMessage(errorMessage); - addError(errorMessage); - } + callErrorHandler(e, s); } finally { loading = false; } @@ -252,24 +268,29 @@ class SendCryptoPresenter extends CompletePresenter { return res; } catch (e, s) { - if (e is RPCError) { - if (BottomFlowDialog.maybeOf(context!) != null) { - BottomFlowDialog.of(context!).close(); - } - String errorMessage = e.message; - errorMessage = changeErrorMessage(errorMessage); - addError(errorMessage); + if (BottomFlowDialog.maybeOf(context!) != null) { + BottomFlowDialog.of(context!).close(); } + callErrorHandler(e, s); } finally { loading = false; } } - String changeErrorMessage(String message) { - if (message.contains('gas required exceeds allowance')) { - return translate('insufficient_balance_for_fee') ?? message; + void callErrorHandler(dynamic e, StackTrace s) { + final isHandled = _errorUseCase.handleError(context!, e, onL3Tap: () { + final chainId = state.network!.chainId; + final l3BridgeUri = Urls.networkL3Bridge(chainId); + Navigator.of(context!).push(route.featureDialog( + maintainState: false, + OpenAppPage( + url: l3BridgeUri, + ), + )); + }); + if (!isHandled) { + addError(e, s); } - return message; } @override diff --git a/lib/features/portfolio/subfeatures/token/send_token/send_crypto/widgets/transaction_dialog.dart b/lib/features/portfolio/subfeatures/token/send_token/send_crypto/widgets/transaction_dialog.dart index 8ab1b2d7..d31abe7b 100644 --- a/lib/features/portfolio/subfeatures/token/send_token/send_crypto/widgets/transaction_dialog.dart +++ b/lib/features/portfolio/subfeatures/token/send_token/send_crypto/widgets/transaction_dialog.dart @@ -6,17 +6,20 @@ import 'transaction_info.dart'; enum TransactionProcessType { confirm, send, sending, done } -Future showTransactionDialog(BuildContext context, - {required String amount, - required String balance, - required Token token, - required String network, - required String networkSymbol, - required String from, - required String to, - String? estimatedFee, - TransactionProcessType? processType, - required Function(TransactionProcessType) onTap,}) { +Future showTransactionDialog( + BuildContext context, { + required String amount, + required String balance, + required Token token, + required String network, + required String networkSymbol, + required String from, + required String to, + required String estimatedFee, + required String maxFee, + TransactionProcessType? processType, + required Function(TransactionProcessType) onTap, +}) { return showModalBottomSheet( context: context, useRootNavigator: true, @@ -41,6 +44,7 @@ Future showTransactionDialog(BuildContext context, from: from, to: to, estimatedFee: estimatedFee, + maxFee: maxFee, processType: processType, onTap: onTap, ), diff --git a/lib/features/portfolio/subfeatures/token/send_token/send_crypto/widgets/transaction_info.dart b/lib/features/portfolio/subfeatures/token/send_token/send_crypto/widgets/transaction_info.dart index f6bedb3f..608cf437 100644 --- a/lib/features/portfolio/subfeatures/token/send_token/send_crypto/widgets/transaction_info.dart +++ b/lib/features/portfolio/subfeatures/token/send_token/send_crypto/widgets/transaction_info.dart @@ -16,7 +16,8 @@ class TransactionInfo extends StatefulWidget { required this.networkSymbol, required this.from, required this.to, - this.estimatedFee, + required this.estimatedFee, + required this.maxFee, this.processType = TransactionProcessType.confirm, required this.onTap, }) : super(key: key); @@ -28,7 +29,8 @@ class TransactionInfo extends StatefulWidget { final String networkSymbol; final String from; final String to; - final String? estimatedFee; + final String estimatedFee; + final String maxFee; final TransactionProcessType? processType; final Function(TransactionProcessType) onTap; @@ -77,16 +79,22 @@ class _TransactionInfoState extends State { title: 'to', value: widget.to, ), - if (TransactionProcessType.confirm != processType) + if (TransactionProcessType.confirm != processType) ...[ SingleLineInfoItem( title: 'estimated_fee', - value: widget.estimatedFee != null - ? Formatter.formatNumberForUI( - widget.estimatedFee!, - ) - : '--', + value: Formatter.formatNumberForUI( + widget.estimatedFee, + ), hint: widget.networkSymbol, ), + SingleLineInfoItem( + title: 'max_fee', + value: Formatter.formatNumberForUI( + widget.maxFee, + ), + hint: widget.networkSymbol, + ), + ] ], ), ), @@ -148,7 +156,6 @@ class _TransactionInfoState extends State { } } else { widget.onTap(processType); - Navigator.of(context).pop(true); } }, ); diff --git a/lib/features/portfolio/subfeatures/tokens_balance_list/tokens_balance_list.dart b/lib/features/portfolio/subfeatures/tokens_balance_list/tokens_balance_list.dart index ee47cec4..439e5ced 100644 --- a/lib/features/portfolio/subfeatures/tokens_balance_list/tokens_balance_list.dart +++ b/lib/features/portfolio/subfeatures/tokens_balance_list/tokens_balance_list.dart @@ -2,6 +2,7 @@ import 'package:datadashwallet/common/common.dart'; import 'package:datadashwallet/core/core.dart'; import 'package:datadashwallet/features/portfolio/portfolio.dart'; import 'package:datadashwallet/features/portfolio/subfeatures/token/add_token/add_token_page.dart'; +import '../token/send_token/send_crypto/send_crypto_page.dart'; import './utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; @@ -43,6 +44,21 @@ class TokensBalanceList extends HookConsumerWidget { ...TokensBalanceListUtils .generateTokensBalanceList( portfolioState.tokensList!, + onSelected: (token) => + Navigator.of(context).push( + route.featureDialog( + SendCryptoPage( + token: token, + isBalanceZero: portfolioState + .tokensList?[0].balance == + null + ? false + : portfolioState + .tokensList![0].balance! <= + 0.0, + ), + ), + ), ) ], )), diff --git a/lib/features/portfolio/subfeatures/transaction_history/transaction_history_presenter.dart b/lib/features/portfolio/subfeatures/transaction_history/transaction_history_presenter.dart index 5e0e0601..3a318192 100644 --- a/lib/features/portfolio/subfeatures/transaction_history/transaction_history_presenter.dart +++ b/lib/features/portfolio/subfeatures/transaction_history/transaction_history_presenter.dart @@ -20,6 +20,7 @@ class TransactionHistoryPresenter late final _accountUserCase = ref.read(accountUseCaseProvider); late final _transactionHistoryUseCase = ref.read(transactionHistoryUseCaseProvider); + late final _mxcTransactionsUseCase = ref.read(mxcTransactionsUseCaseProvider); @override void initState() { @@ -92,61 +93,30 @@ class TransactionHistoryPresenter if (newTokenTransfersList != null && newTransactionsList != null) { // loading over and we have the data // merge - if (newTransactionsList.items != null) { - newTransactionsList = newTransactionsList.copyWith( - items: newTransactionsList.items!.where((element) { - if (element.txTypes != null) { - return element.txTypes! - .any((element) => element == 'coin_transfer'); - } else { - return false; - } - }).toList()); - } + newTransactionsList = newTransactionsList.copyWith( + items: _mxcTransactionsUseCase.removeTokenTransfersFromTxList( + newTransactionsList.items!, newTokenTransfersList.items!)); if (newTokenTransfersList.items != null) { - for (int i = 0; i < newTokenTransfersList.items!.length; i++) { - final item = newTokenTransfersList.items![i]; - newTransactionsList.items! - .add(WannseeTransactionModel(tokenTransfers: [item])); - } - if (newTransactionsList.items!.isNotEmpty) { - newTransactionsList.items!.sort((a, b) { - final item1 = a.timestamp ?? a.tokenTransfers![0].timestamp; - final item2 = b.timestamp ?? b.tokenTransfers![0].timestamp; + _mxcTransactionsUseCase.addTokenTransfersToTxList( + newTransactionsList.items!, newTokenTransfersList.items!); - return item2!.compareTo(item1!); - }); - } + _mxcTransactionsUseCase.sortByDate(newTransactionsList.items!); - final sevenDays = DateTime.now().subtract(const Duration(days: 7)); + final sevenDays = DateTime.now() + .subtract(Duration(days: Config.transactionsHistoryLimit)); newTransactionsList = newTransactionsList.copyWith( - items: newTransactionsList.items!.where((element) { - if (element.timestamp != null) { - return element.timestamp!.isAfter(sevenDays); - } - return element.tokenTransfers![0].timestamp!.isAfter(sevenDays); - }).toList()); + items: _mxcTransactionsUseCase + .applyTxDateLimit(newTransactionsList.items!)); - newTransactionsList = newTransactionsList.copyWith( - items: newTransactionsList.items!.where((element) { - if (element.timestamp != null) { - return element.timestamp!.isAfter(sevenDays); - } - return element.tokenTransfers![0].timestamp!.isAfter(sevenDays); - }).toList()); + final finalTxList = _mxcTransactionsUseCase.axsTxListFromMxcTxList( + newTransactionsList.items!, state.account!.address); - final newTxList = newTransactionsList.items! - .map((e) => TransactionModel.fromMXCTransaction( - e, state.account!.address)) - .toList(); - newTxList.removeWhere( - (element) => element.hash == "Unknown", - ); + _mxcTransactionsUseCase.removeInvalidTx(finalTxList); notify(() { - state.transactions = newTxList; - state.filterTransactions = newTxList; + state.transactions = finalTxList; + state.filterTransactions = finalTxList; }); } } @@ -181,6 +151,12 @@ class TransactionHistoryPresenter return transactionType == item.type; }).toList(); + if (SortOption.amount == sortOption) { + result = state.transactions!.where((item) { + return TransactionType.contractCall != item.type; + }).toList(); + } + result.sort((a, b) { if (SortOption.date == sortOption) { final item1 = a.timeStamp; @@ -194,8 +170,8 @@ class TransactionHistoryPresenter return item2.compareTo(item1); } } else { - final item1 = double.parse(a.value); - final item2 = double.parse(b.value); + final item1 = double.parse(a.value!); + final item2 = double.parse(b.value!); if (SortType.increase == amountSort) { return item1.compareTo(item2); diff --git a/lib/features/portfolio/subfeatures/transaction_history/widgets/filter_and_sort_items.dart b/lib/features/portfolio/subfeatures/transaction_history/widgets/filter_and_sort_items.dart index db2ed3c4..8bb524d7 100644 --- a/lib/features/portfolio/subfeatures/transaction_history/widgets/filter_and_sort_items.dart +++ b/lib/features/portfolio/subfeatures/transaction_history/widgets/filter_and_sort_items.dart @@ -53,7 +53,8 @@ class _FilterAndSortItemsState extends State { ...{ 'all_transactions': TransactionType.all, 'send_transactions': TransactionType.sent, - 'receive_transactions': TransactionType.received + 'receive_transactions': TransactionType.received, + 'contract_call_transactions': TransactionType.contractCall } .entries .map( diff --git a/lib/features/settings/subfeatures/accounts/subfeatures/import_account/import_account_presenter.dart b/lib/features/settings/subfeatures/accounts/subfeatures/import_account/import_account_presenter.dart index 7854ee7d..c5d8f5d4 100644 --- a/lib/features/settings/subfeatures/accounts/subfeatures/import_account/import_account_presenter.dart +++ b/lib/features/settings/subfeatures/accounts/subfeatures/import_account/import_account_presenter.dart @@ -1,3 +1,4 @@ +import 'package:datadashwallet/common/utils/utils.dart'; import 'package:datadashwallet/core/core.dart'; import 'package:datadashwallet/features/settings/domain/webview_use_case.dart'; import 'package:flutter/material.dart'; @@ -33,8 +34,8 @@ class ImportAccountPresenter extends CompletePresenter { final index = state.accounts.length; final privateKey = privateKeyController.text; - final newAccount = - await _authUseCase.addCustomAccount('${index + 1}', privateKey); + final newAccount = await _authUseCase.addCustomAccount( + '${index + 1}', Formatter.removeZeroX(privateKey)); _accountUserCase.addAccount(newAccount); loadCache(); @@ -53,8 +54,9 @@ class ImportAccountPresenter extends CompletePresenter { String? checkDuplicate(String privateKey) { if (privateKey.isEmpty) return translate('invalid_format'); - final foundIndex = state.accounts - .indexWhere((element) => element.privateKey == privateKey); + final foundIndex = state.accounts.indexWhere( + (element) => element.privateKey == Formatter.removeZeroX(privateKey)); + if (foundIndex != -1) { return translate('duplicate_account_import_notice')!; diff --git a/lib/features/settings/subfeatures/chain_configuration/domain/chain_configuration_use_case.dart b/lib/features/settings/subfeatures/chain_configuration/domain/chain_configuration_use_case.dart index 05c78cfc..d2b415bc 100644 --- a/lib/features/settings/subfeatures/chain_configuration/domain/chain_configuration_use_case.dart +++ b/lib/features/settings/subfeatures/chain_configuration/domain/chain_configuration_use_case.dart @@ -148,6 +148,7 @@ class ChainConfigurationUseCase extends ReactiveUseCase { update(selectedNetworkForDetails, network); } + /// TODO: Make a ur launcher use case void launchAddress(String address) async { final chainExplorerUrl = selectedNetwork.value!.explorerUrl!; final addressExplorer = Config.addressExplorer(address); @@ -157,4 +158,11 @@ class ChainConfigurationUseCase extends ReactiveUseCase { await launchUrl(launchUri, mode: LaunchMode.platformDefault); } } + + void launchUrlInPlatformDefault(String url) async { + final launchUri = Uri.parse(url); + if ((await canLaunchUrl(launchUri))) { + await launchUrl(launchUri, mode: LaunchMode.platformDefault); + } + } } diff --git a/lib/features/splash/mns_process/presentation/mns_query/mns_query_presenter.dart b/lib/features/splash/mns_process/presentation/mns_query/mns_query_presenter.dart index d37f3110..d54f3314 100644 --- a/lib/features/splash/mns_process/presentation/mns_query/mns_query_presenter.dart +++ b/lib/features/splash/mns_process/presentation/mns_query/mns_query_presenter.dart @@ -1,9 +1,9 @@ +import 'package:datadashwallet/common/common.dart'; import 'package:datadashwallet/common/config.dart'; import 'package:datadashwallet/features/dapps/dapps.dart'; import 'package:datadashwallet/core/core.dart'; -import 'package:datadashwallet/features/portfolio/presentation/widgets/show_wallet_address_dialog.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/src/consumer.dart'; +import 'mns_query_state.dart'; import '../widgets/no_balance_dialog.dart'; import 'mns_query_state.dart'; @@ -76,9 +76,25 @@ class SplashMNSQueryPresenter extends CompletePresenter { final result = await showNoBalanceDialog(context!); if (result != null) { if (result) { - showWalletAddressDialog( - context: context!, - walletAddress: state.walletAddress, + final network = state.network!; + final walletAddress = state.walletAddress!; + final chainId = network.chainId; + showReceiveBottomSheet( + context!, + walletAddress, + network.chainId, + network.symbol, + () { + final l3BridgeUri = Urls.networkL3Bridge(chainId); + Navigator.of(context!).push(route.featureDialog( + maintainState: false, + OpenAppPage( + url: l3BridgeUri, + ), + )); + }, + _chainConfigurationUseCase.launchUrlInPlatformDefault, + true, ); } else { navigator?.replaceAll(route(const DAppsPage())); diff --git a/lib/features/wallet/presentation/wallet_page.dart b/lib/features/wallet/presentation/wallet_page.dart index f03eb692..42840fc9 100644 --- a/lib/features/wallet/presentation/wallet_page.dart +++ b/lib/features/wallet/presentation/wallet_page.dart @@ -87,7 +87,7 @@ class WalletPage extends HookConsumerWidget { height: 12, ), RecentTransactions( - walletAddress: state.walletAddress, + walletAddress: state.account?.address, transactions: txList, tokens: state.tokensList, networkType: state.network?.networkType, diff --git a/lib/features/wallet/presentation/wallet_page_presenter.dart b/lib/features/wallet/presentation/wallet_page_presenter.dart index a63f8367..cb3bcb62 100644 --- a/lib/features/wallet/presentation/wallet_page_presenter.dart +++ b/lib/features/wallet/presentation/wallet_page_presenter.dart @@ -24,36 +24,43 @@ class WalletPresenter extends CompletePresenter { late final _balanceUseCase = ref.read(balanceHistoryUseCaseProvider); late final _transactionHistoryUseCase = ref.read(transactionHistoryUseCaseProvider); + late final _mxcTransactionsUseCase = ref.read(mxcTransactionsUseCaseProvider); @override void initState() { super.initState(); getMXCTweets(); - _transactionHistoryUseCase.checkForPendingTransactions( - _chainConfigurationUseCase.getCurrentNetworkWithoutRefresh().chainId); + checkForPendingTx(); - listen(_chainConfigurationUseCase.selectedNetwork, (value) { + listen(_accountUserCase.account, (value) { if (value != null) { - state.network = value; - if (state.walletAddress != null) { - initializeWalletPage(); + final cAccount = state.account; + notify(() => state.account = value); + if (cAccount != null && cAccount.address != value.address) { + /// Not first time & there is a change + createSubscriptions(); + } + if (state.network != null) { + getTransactions(); } } }); - listen(_accountUserCase.account, (value) { + listen(_chainConfigurationUseCase.selectedNetwork, (value) { if (value != null) { - notify(() => state.walletAddress = value.address); - initializeWalletPage(); + state.network = value; + connectAndSubscribe(); + getTransactions(); + resetBalanceUpdateStream(); } }); listen(_transactionHistoryUseCase.transactionsHistory, (value) { - if (state.network != null) { - if (!Config.isMxcChains(state.network!.chainId)) { - getCustomChainsTransactions(value); - } + if (state.network != null && + !Config.isMxcChains(state.network!.chainId)) { + getCustomChainsTransactions(value); + initBalanceUpdateStream(); } }); @@ -83,7 +90,7 @@ class WalletPresenter extends CompletePresenter { listen(_customTokenUseCase.tokens, (customTokens) { _tokenContractUseCase.addCustomTokens( customTokens, - state.walletAddress ?? _accountUserCase.account.value!.address, + state.account?.address ?? _accountUserCase.account.value!.address, Config.isMxcChains(state.network!.chainId) || Config.isEthereumMainnet(state.network!.chainId)); initializeBalancePanelAndTokens(); @@ -100,107 +107,116 @@ class WalletPresenter extends CompletePresenter { notify(() => state.currentIndex = newIndex); } - Future initializeWalletPage() async { - createSubscriptions(); - getTransactions(); + void connectAndSubscribe() async { + try { + if (state.network?.web3WebSocketUrl?.isNotEmpty ?? false) { + final isConnected = await connectToWebsocket(); + if (isConnected) { + createSubscriptions(); + } else { + connectAndSubscribe(); + } + } + } catch (e) { + connectAndSubscribe(); + } + } + + Future connectToWebsocket() async { + return await _tokenContractUseCase.connectToWebsSocket(); + } + + Future?> subscribeToBalance() async { + return await _tokenContractUseCase.subscribeEvent( + "addresses:${state.account!.address}".toLowerCase(), + ); } void createSubscriptions() async { - if (state.subscription == null && state.network!.web3WebSocketUrl != null) { - if (state.network!.web3WebSocketUrl!.isNotEmpty) { - final subscription = await _tokenContractUseCase.subscribeToBalance( - "addresses:${state.walletAddress}".toLowerCase(), - ); + final subscription = await subscribeToBalance(); - if (subscription == null) { - createSubscriptions(); - } + if (subscription == null) { + createSubscriptions(); + } + + if (state.subscription != null) state.subscription!.cancel(); + state.subscription = subscription!.listen(handleWebSocketEvents); + } - subscription!.doOnError( - (object, trace) { - createSubscriptions(); - }, - ); - - state.subscription = subscription.listen((event) { - if (!mounted) return; - switch (event.event.value as String) { - // coin transfer pending tx token transfer - coin transfer - case 'pending_transaction': - final newTx = WannseeTransactionModel.fromJson( - json.encode(event.payload['transactions'][0])); - if (newTx.value != null) { - notify(() => state.txList!.insert( - 0, + handleWebSocketEvents(dynamic event) { + if (!mounted) return; + switch (event.event.value as String) { + // coin transfer pending tx token transfer - coin transfer + case 'pending_transaction': + final newTx = WannseeTransactionModel.fromJson( + json.encode(event.payload['transactions'][0])); + if (newTx.value != null) { + notify(() => state.txList!.insert( + 0, + TransactionModel.fromMXCTransaction( + newTx, state.account!.address))); + } + break; + // coin transfer done + case 'transaction': + final newTx = WannseeTransactionModel.fromJson( + json.encode(event.payload['transactions'][0])); + if (newTx.value != null) { + // We will filter token_transfer tx because It is also received from token_transfer event + if (newTx.txTypes != null && + !(newTx.txTypes!.contains('token_transfer'))) { + final itemIndex = + state.txList!.indexWhere((txItem) => txItem.hash == newTx.hash); + // checking for if the transaction is found. + if (itemIndex != -1) { + notify(() => state.txList!.replaceRange( + itemIndex, itemIndex + 1, [ TransactionModel.fromMXCTransaction( - newTx, state.walletAddress!))); - } - break; - // coin transfer done - case 'transaction': - final newTx = WannseeTransactionModel.fromJson( - json.encode(event.payload['transactions'][0])); - if (newTx.value != null) { - // We will filter token_transfer tx because It is also received from token_transfer event - if (newTx.txTypes != null && - !(newTx.txTypes!.contains('token_transfer'))) { - final itemIndex = state.txList! - .indexWhere((txItem) => txItem.hash == newTx.hash); - // checking for if the transaction is found. - if (itemIndex != -1) { - notify(() => state.txList!.replaceRange( - itemIndex, itemIndex + 1, [ - TransactionModel.fromMXCTransaction( - newTx, state.walletAddress!) - ])); - } else { - // we must have missed the pending tx - notify(() => state.txList!.insert( - 0, - TransactionModel.fromMXCTransaction( - newTx, state.walletAddress!))); - } - } - } - break; - // token transfer pending - case 'token_transfer': - final newTx = TokenTransfer.fromJson( - json.encode(event.payload['token_transfers'][0])); - if (newTx.txHash != null) { - // Sender will get pending tx - // Receiver won't get pending tx - final itemIndex = state.txList! - .indexWhere((txItem) => txItem.hash == newTx.txHash); - // checking for if the transaction is found. - if (itemIndex != -1) { - notify(() => - state.txList!.replaceRange(itemIndex, itemIndex + 1, [ - TransactionModel.fromMXCTransaction( - WannseeTransactionModel(tokenTransfers: [newTx]), - state.walletAddress!) - ])); - } else { - // we must have missed the token transfer pending tx - notify(() => state.txList!.insert( - 0, - TransactionModel.fromMXCTransaction( - WannseeTransactionModel(tokenTransfers: [newTx]), - state.walletAddress!), - )); - } - } - break; - // new balance - case 'balance': - final wannseeBalanceEvent = - WannseeBalanceModel.fromJson(event.payload); - getWalletTokensBalance(null, true); - break; - default: + newTx, state.account!.address) + ])); + } else { + // we must have missed the pending tx + notify(() => state.txList!.insert( + 0, + TransactionModel.fromMXCTransaction( + newTx, state.account!.address))); + } } - }); - } + } + break; + // token transfer pending + case 'token_transfer': + final newTx = TokenTransfer.fromJson( + json.encode(event.payload['token_transfers'][0])); + if (newTx.txHash != null) { + // Sender will get pending tx + // Receiver won't get pending tx + final itemIndex = + state.txList!.indexWhere((txItem) => txItem.hash == newTx.txHash); + // checking for if the transaction is found. + if (itemIndex != -1) { + notify(() => state.txList!.replaceRange(itemIndex, itemIndex + 1, [ + TransactionModel.fromMXCTransaction( + WannseeTransactionModel(tokenTransfers: [newTx]), + state.account!.address) + ])); + } else { + // we must have missed the token transfer pending tx + notify(() => state.txList!.insert( + 0, + TransactionModel.fromMXCTransaction( + WannseeTransactionModel(tokenTransfers: [newTx]), + state.account!.address), + )); + } + } + break; + // new balance + case 'balance': + final wannseeBalanceEvent = WannseeBalanceModel.fromJson(event.payload); + getWalletTokensBalance(null, true); + break; + default: } } @@ -215,79 +231,47 @@ class WalletPresenter extends CompletePresenter { void getCustomChainsTransactions(List? txHistory) { txHistory = txHistory ?? _transactionHistoryUseCase.getTransactionsHistory(); + final chainTxHistory = txHistory; - if (state.network != null) { - final chainTxHistory = txHistory; - - notify(() => state.txList = chainTxHistory); - } + notify(() => state.txList = chainTxHistory); } void getMXCTransactions() async { - // final walletAddress = await _walletUserCase.getPublicAddress(); // transactions list contains all the kind of transactions // It's going to be filtered to only have native coin transfer await _tokenContractUseCase - .getTransactionsByAddress(state.walletAddress!) + .getTransactionsByAddress(state.account!.address) .then((newTransactionsList) async { // token transfer list contains only one kind transaction which is token transfer final newTokenTransfersList = await _tokenContractUseCase - .getTokenTransfersByAddress(state.walletAddress!); + .getTokenTransfersByAddress(state.account!.address); if (newTokenTransfersList != null && newTransactionsList != null) { // loading over and we have the data state.isTxListLoading = false; // merge - if (newTransactionsList.items != null) { + if (newTransactionsList.items != null && + newTokenTransfersList.items != null) { + // Separating token transfer from all transaction since they have different structure newTransactionsList = newTransactionsList.copyWith( - items: newTransactionsList.items!.where((element) { - if (element.txTypes != null) { - return element.txTypes! - .any((element) => element == 'coin_transfer'); - } else { - return false; - } - }).toList()); + items: _mxcTransactionsUseCase.removeTokenTransfersFromTxList( + newTransactionsList.items!, newTokenTransfersList.items!)); } if (newTokenTransfersList.items != null) { - for (int i = 0; i < newTokenTransfersList.items!.length; i++) { - final item = newTokenTransfersList.items![i]; - newTransactionsList.items! - .add(WannseeTransactionModel(tokenTransfers: [item])); - } - if (newTransactionsList.items!.isNotEmpty) { - newTransactionsList.items!.sort((a, b) { - if (b.timestamp == null && a.timestamp == null) { - // both token transfer - return b.tokenTransfers![0].timestamp! - .compareTo(a.tokenTransfers![0].timestamp!); - } else if (b.timestamp != null && a.timestamp != null) { - // both coin transfer - return b.timestamp!.compareTo(a.timestamp!); - } else if (b.timestamp == null) { - // b is token transfer - return b.tokenTransfers![0].timestamp!.compareTo(a.timestamp!); - } else { - // a is token transfer - return b.timestamp!.compareTo(a.tokenTransfers![0].timestamp!); - } - }); - } + _mxcTransactionsUseCase.addTokenTransfersToTxList( + newTransactionsList.items!, newTokenTransfersList.items!); - if (newTransactionsList.items!.length > 6) { - newTransactionsList = newTransactionsList.copyWith( - items: newTransactionsList.items!.sublist(0, 6)); - } + _mxcTransactionsUseCase.sortByDate(newTransactionsList.items!); + + newTransactionsList = newTransactionsList.copyWith( + items: _mxcTransactionsUseCase + .keepOnlySixTransactions(newTransactionsList.items!)); - final finalTxList = newTransactionsList.items! - .map((e) => - TransactionModel.fromMXCTransaction(e, state.walletAddress!)) - .toList(); + final finalTxList = _mxcTransactionsUseCase.axsTxListFromMxcTxList( + newTransactionsList.items!, state.account!.address); - finalTxList.removeWhere( - (element) => element.hash == "Unknown", - ); + _mxcTransactionsUseCase.removeInvalidTx(finalTxList); notify(() => state.txList = finalTxList); } @@ -306,7 +290,7 @@ class WalletPresenter extends CompletePresenter { } Future> getDefaultTokens() async { - return await _tokenContractUseCase.getDefaultTokens(state.walletAddress!); + return await _tokenContractUseCase.getDefaultTokens(state.account!.address); } void changeHideBalanceState() { @@ -325,10 +309,9 @@ class WalletPresenter extends CompletePresenter { void getViewOtherTransactionsLink() async { final chainExplorerUrl = state.network!.explorerUrl!; - final address = state.walletAddress!; + final address = state.account!.address; final addressExplorer = Config.addressExplorer(address); - final launchUri = - Formatter.mergeUrl(chainExplorerUrl, addressExplorer); + final launchUri = Formatter.mergeUrl(chainExplorerUrl, addressExplorer); if ((await canLaunchUrl(launchUri))) { await launchUrl(launchUri, mode: LaunchMode.platformDefault); @@ -439,7 +422,7 @@ class WalletPresenter extends CompletePresenter { void getWalletTokensBalance( List? tokenList, bool shouldGetPrice) async { _tokenContractUseCase.getTokensBalance( - tokenList, state.walletAddress!, shouldGetPrice); + tokenList, state.account!.address, shouldGetPrice); } void checkMaxTweetHeight(double height) { @@ -447,4 +430,24 @@ class WalletPresenter extends CompletePresenter { notify(() => state.maxTweetViewHeight = height + 120); } } + + void checkForPendingTx() { + _transactionHistoryUseCase.checkForPendingTransactions( + _chainConfigurationUseCase.getCurrentNetworkWithoutRefresh().chainId); + } + + void initBalanceUpdateStream() { + state.balancesUpdateSubscription ??= + listen(_transactionHistoryUseCase.shouldUpdateBalances, (value) { + if (value) initializeBalancePanelAndTokens(); + }); + } + + void resetBalanceUpdateStream() { + if (Config.isMxcChains(state.network!.chainId) && + state.balancesUpdateSubscription != null) { + state.balancesUpdateSubscription!.cancel(); + state.balancesUpdateSubscription = null; + } + } } diff --git a/lib/features/wallet/presentation/wallet_page_state.dart b/lib/features/wallet/presentation/wallet_page_state.dart index 41c22088..a0564495 100644 --- a/lib/features/wallet/presentation/wallet_page_state.dart +++ b/lib/features/wallet/presentation/wallet_page_state.dart @@ -14,7 +14,7 @@ class WalletState with EquatableMixin { List tokensList = []; - String? walletAddress; + Account? account; bool hideBalance = false; @@ -32,6 +32,9 @@ class WalletState with EquatableMixin { StreamSubscription? subscription; + /// This stream is only used for chains other than MXC + StreamSubscription? balancesUpdateSubscription; + Network? network; double maxTweetViewHeight = 620; @@ -43,7 +46,7 @@ class WalletState with EquatableMixin { txList, isTxListLoading, tokensList, - walletAddress, + account, hideBalance, chartMaxAmount, chartMinAmount, diff --git a/packages/shared b/packages/shared index 73336956..aa777733 160000 --- a/packages/shared +++ b/packages/shared @@ -1 +1 @@ -Subproject commit 733369560b552ca65b70ae47893166343126daaa +Subproject commit aa77773379d04b87f4e46d5777e5517bddd79622 diff --git a/pubspec.yaml b/pubspec.yaml index 876f8cc8..b238e944 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.6.0 +version: 1.7.0 environment: