From 9e73ce24734d9288571be45835d5630c7e5a46b6 Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 26 Apr 2025 15:14:44 -0600 Subject: [PATCH 1/3] Use changenow v2 api only and update swap to be able to use coin+network rather than just coin ticker. A couple other bug fixes. And special shoutout to the autoformat of the flutter/dart android studio plugin for making a mess of this commit --- lib/models/exchange/aggregate_currency.dart | 5 +- .../change_now/cn_exchange_transaction.dart | 174 +++ .../cn_exchange_transaction_status.dart | 172 +++ .../change_now/estimated_exchange_amount.dart | 188 +-- .../change_now/exchange_transaction.dart | 20 +- .../exchange_transaction_status.dart | 63 +- lib/models/exchange/incomplete_exchange.dart | 7 +- .../exchange/response_objects/trade.dart | 44 +- .../sub_widgets/add_token_list_element.dart | 123 +- .../sub_widgets/coin_select_item.dart | 99 +- .../exchange_currency_selection_view.dart | 277 ++--- lib/pages/exchange_view/exchange_form.dart | 493 ++++---- .../exchange_step_views/step_3_view.dart | 119 +- .../exchange_provider_options.dart | 50 +- .../exchange_view/trade_details_view.dart | 1099 ++++++++--------- .../helpers/restore_create_backup.dart | 382 +++--- .../desktop_all_trades_view.dart | 409 +++--- .../exchange_steps/step_scaffold.dart | 180 ++- .../subwidgets/desktop_step_2.dart | 421 +++---- .../exchange/change_now/change_now_api.dart | 1017 ++++----------- .../change_now/change_now_exchange.dart | 227 ++-- lib/services/exchange/exchange.dart | 35 +- .../majestic_bank/majestic_bank_api.dart | 65 +- .../majestic_bank/majestic_bank_exchange.dart | 112 +- .../exchange/nanswap/nanswap_exchange.dart | 190 ++- .../simpleswap/simpleswap_exchange.dart | 72 +- .../response_objects/trocador_trade.dart | 55 +- .../exchange/trocador/trocador_exchange.dart | 206 +-- lib/services/trade_service.dart | 7 +- lib/utilities/assets.dart | 8 +- lib/widgets/trade_card.dart | 23 +- .../wallet_info_row_coin_icon.dart | 40 +- .../exchange/exchange_view_test.mocks.dart | 439 ++----- test/services/change_now/change_now_test.dart | 1005 +++++++-------- 34 files changed, 3555 insertions(+), 4271 deletions(-) create mode 100644 lib/models/exchange/change_now/cn_exchange_transaction.dart create mode 100644 lib/models/exchange/change_now/cn_exchange_transaction_status.dart diff --git a/lib/models/exchange/aggregate_currency.dart b/lib/models/exchange/aggregate_currency.dart index 841262c1d..ffaccd3c0 100644 --- a/lib/models/exchange/aggregate_currency.dart +++ b/lib/models/exchange/aggregate_currency.dart @@ -8,9 +8,10 @@ * */ +import 'package:tuple/tuple.dart'; + import '../isar/exchange_cache/currency.dart'; import '../isar/exchange_cache/pair.dart'; -import 'package:tuple/tuple.dart'; class AggregateCurrency { final Map _map = {}; @@ -29,6 +30,8 @@ class AggregateCurrency { return _map[exchangeName]; } + String? networkFor(String exchangeName) => forExchange(exchangeName)?.network; + String get ticker => _map.values.first!.ticker; String get name => _map.values.first!.name; diff --git a/lib/models/exchange/change_now/cn_exchange_transaction.dart b/lib/models/exchange/change_now/cn_exchange_transaction.dart new file mode 100644 index 000000000..5063df995 --- /dev/null +++ b/lib/models/exchange/change_now/cn_exchange_transaction.dart @@ -0,0 +1,174 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2025 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2025-04-26 + * + */ + +import 'package:decimal/decimal.dart'; +import 'package:uuid/uuid.dart'; + +import 'cn_exchange_transaction_status.dart'; + +class CNExchangeTransaction { + /// The amount being sent from the user. + final Decimal fromAmount; + + /// The amount the user will receive. + final Decimal toAmount; + + /// The type of exchange flow. Either "standard" or "fixed-rate". + final String flow; + + /// Direction of the exchange: "direct" or "reverse". + final String type; + + /// The address to which the user sends the input currency. + final String payinAddress; + + /// The address where the exchanged currency will be sent. + final String payoutAddress; + + /// Extra ID for payout address (e.g., memo, tag). Empty string if not used. + final String payoutExtraId; + + /// Currency ticker being exchanged from (e.g., "btc"). + final String fromCurrency; + + /// Currency ticker being exchanged to (e.g., "xmr"). + final String toCurrency; + + /// Refund address in case of failure or timeout. + final String refundAddress; + + /// Extra ID for the refund address (if needed). Empty string if not used. + final String refundExtraId; + + /// Deadline until which the estimated rate or transaction is valid. + final DateTime validUntil; + + /// Date when transaction was created. + final DateTime date; + + /// Unique transaction identifier. + final String id; + + /// The user-defined or system-determined amount in a "directed" flow. + final Decimal? directedAmount; + + /// Network of the currency being sent. + final String fromNetwork; + + /// Network of the currency being received. + final String toNetwork; + + final String uuid; + + final CNExchangeTransactionStatus? statusObject; + + const CNExchangeTransaction({ + required this.fromAmount, + required this.toAmount, + required this.flow, + required this.type, + required this.payinAddress, + required this.payoutAddress, + required this.payoutExtraId, + required this.fromCurrency, + required this.toCurrency, + required this.refundAddress, + required this.refundExtraId, + required this.validUntil, + required this.date, + required this.id, + required this.directedAmount, + required this.fromNetwork, + required this.toNetwork, + required this.uuid, + this.statusObject, + }); + + factory CNExchangeTransaction.fromJson(Map json) { + return CNExchangeTransaction( + fromAmount: Decimal.parse(json["fromAmount"].toString()), + toAmount: Decimal.parse(json["toAmount"].toString()), + flow: json["flow"] as String, + type: json["type"] as String, + payinAddress: json["payinAddress"] as String, + payoutAddress: json["payoutAddress"] as String, + payoutExtraId: json["payoutExtraId"] as String? ?? "", + fromCurrency: json["fromCurrency"] as String, + toCurrency: json["toCurrency"] as String, + refundAddress: json["refundAddress"] as String, + refundExtraId: json["refundExtraId"] as String, + validUntil: DateTime.parse(json["validUntil"] as String), + date: DateTime.parse(json["date"] as String), + id: json["id"] as String, + directedAmount: Decimal.tryParse(json["directedAmount"].toString()), + fromNetwork: json["fromNetwork"] as String? ?? "", + toNetwork: json["toNetwork"] as String? ?? "", + uuid: json["uuid"] as String? ?? const Uuid().v1(), + statusObject: + json["statusObject"] is Map + ? CNExchangeTransactionStatus.fromMap( + json["statusObject"] as Map, + ) + : null, + ); + } + + Map toMap() { + return { + "fromAmount": fromAmount, + "toAmount": toAmount, + "flow": flow, + "type": type, + "payinAddress": payinAddress, + "payoutAddress": payoutAddress, + "payoutExtraId": payoutExtraId, + "fromCurrency": fromCurrency, + "toCurrency": toCurrency, + "refundAddress": refundAddress, + "refundExtraId": refundExtraId, + "validUntil": validUntil.toIso8601String(), + "date": date.toIso8601String(), + "id": id, + "directedAmount": directedAmount, + "fromNetwork": fromNetwork, + "uuid": uuid, + "statusObject": statusObject?.toMap(), + }; + } + + CNExchangeTransaction copyWithStatus(CNExchangeTransactionStatus? status) { + return CNExchangeTransaction( + fromAmount: fromAmount, + toAmount: toAmount, + flow: flow, + type: type, + payinAddress: payinAddress, + payoutAddress: payoutAddress, + payoutExtraId: payoutExtraId, + fromCurrency: fromCurrency, + toCurrency: toCurrency, + refundAddress: refundAddress, + refundExtraId: refundExtraId, + validUntil: validUntil, + date: date, + id: id, + directedAmount: directedAmount, + fromNetwork: fromNetwork, + toNetwork: toNetwork, + uuid: uuid, + statusObject: status, + ); + } + + @override + String toString() { + return "CNExchangeTransaction: ${toMap()}"; + } +} diff --git a/lib/models/exchange/change_now/cn_exchange_transaction_status.dart b/lib/models/exchange/change_now/cn_exchange_transaction_status.dart new file mode 100644 index 000000000..14d7c03e5 --- /dev/null +++ b/lib/models/exchange/change_now/cn_exchange_transaction_status.dart @@ -0,0 +1,172 @@ +import 'package:decimal/decimal.dart'; + +enum ChangeNowTransactionStatus { + New, + Waiting, + Confirming, + Exchanging, + Sending, + Finished, + Failed, + Refunded, + Verifying, +} + +extension ChangeNowTransactionStatusExt on ChangeNowTransactionStatus { + String get lowerCaseName => name.toLowerCase(); +} + +ChangeNowTransactionStatus changeNowTransactionStatusFromStringIgnoreCase( + String string, +) { + for (final value in ChangeNowTransactionStatus.values) { + if (value.lowerCaseName == string.toLowerCase()) { + return value; + } + } + throw ArgumentError( + "String value does not match any known ChangeNowTransactionStatus", + ); +} + +class CNExchangeTransactionStatus { + final String id; + final ChangeNowTransactionStatus status; + final bool actionsAvailable; + final String fromCurrency; + final String fromNetwork; + final String toCurrency; + final String toNetwork; + final String? expectedAmountFrom; + final String? expectedAmountTo; + final String? amountFrom; + final String? amountTo; + final String payinAddress; + final String payoutAddress; + final String? payinExtraId; + final String? payoutExtraId; + final String? refundAddress; + final String? refundExtraId; + final String createdAt; + final String updatedAt; + final String? depositReceivedAt; + final String? payinHash; + final String? payoutHash; + final String fromLegacyTicker; + final String toLegacyTicker; + final String? refundHash; + final String? refundAmount; + final int? userId; + final String? validUntil; + + const CNExchangeTransactionStatus({ + required this.id, + required this.status, + required this.actionsAvailable, + required this.fromCurrency, + required this.fromNetwork, + required this.toCurrency, + required this.toNetwork, + this.expectedAmountFrom, + this.expectedAmountTo, + this.amountFrom, + this.amountTo, + required this.payinAddress, + required this.payoutAddress, + this.payinExtraId, + this.payoutExtraId, + this.refundAddress, + this.refundExtraId, + required this.createdAt, + required this.updatedAt, + this.depositReceivedAt, + this.payinHash, + this.payoutHash, + required this.fromLegacyTicker, + required this.toLegacyTicker, + this.refundHash, + this.refundAmount, + this.userId, + this.validUntil, + }); + + factory CNExchangeTransactionStatus.fromMap(Map map) { + return CNExchangeTransactionStatus( + id: map["id"] as String, + status: changeNowTransactionStatusFromStringIgnoreCase( + map["status"] as String, + ), + actionsAvailable: map["actionsAvailable"] as bool, + fromCurrency: map["fromCurrency"] as String? ?? "", + fromNetwork: map["fromNetwork"] as String? ?? "", + toCurrency: map["toCurrency"] as String? ?? "", + toNetwork: map["toNetwork"] as String? ?? "", + expectedAmountFrom: _get(map["expectedAmountFrom"]), + expectedAmountTo: _get(map["expectedAmountTo"]), + amountFrom: _get(map["amountFrom"]), + amountTo: _get(map["amountTo"]), + payinAddress: map["payinAddress"] as String? ?? "", + payoutAddress: map["payoutAddress"] as String? ?? "", + payinExtraId: map["payinExtraId"] as String?, + payoutExtraId: map["payoutExtraId"] as String?, + refundAddress: map["refundAddress"] as String?, + refundExtraId: map["refundExtraId"] as String?, + createdAt: map["createdAt"] as String? ?? "", + updatedAt: map["updatedAt"] as String? ?? "", + depositReceivedAt: map["depositReceivedAt"] as String?, + payinHash: map["payinHash"] as String?, + payoutHash: map["payoutHash"] as String?, + fromLegacyTicker: map["fromLegacyTicker"] as String? ?? "", + toLegacyTicker: map["toLegacyTicker"] as String? ?? "", + refundHash: map["refundHash"] as String?, + refundAmount: _get(map["refundAmount"]), + userId: + map["userId"] is int + ? map["userId"] as int + : int.tryParse(map["userId"].toString()), + validUntil: map["validUntil"] as String?, + ); + } + + Map toMap() { + return { + "id": id, + "status": status, + "actionsAvailable": actionsAvailable, + "fromCurrency": fromCurrency, + "fromNetwork": fromNetwork, + "toCurrency": toCurrency, + "toNetwork": toNetwork, + "expectedAmountFrom": expectedAmountFrom, + "expectedAmountTo": expectedAmountTo, + "amountFrom": amountFrom, + "amountTo": amountTo, + "payinAddress": payinAddress, + "payoutAddress": payoutAddress, + "payinExtraId": payinExtraId, + "payoutExtraId": payoutExtraId, + "refundAddress": refundAddress, + "refundExtraId": refundExtraId, + "createdAt": createdAt, + "updatedAt": updatedAt, + "depositReceivedAt": depositReceivedAt, + "payinHash": payinHash, + "payoutHash": payoutHash, + "fromLegacyTicker": fromLegacyTicker, + "toLegacyTicker": toLegacyTicker, + "refundHash": refundHash, + "refundAmount": refundAmount, + "userId": userId, + "validUntil": validUntil, + }; + } + + static String? _get(dynamic value) { + if (value is String) return value; + if (value is num) return Decimal.tryParse(value.toString())?.toString(); + return null; + } + + @override + String toString() => "CNExchangeTransactionStatus: ${toMap()}"; +} diff --git a/lib/models/exchange/change_now/estimated_exchange_amount.dart b/lib/models/exchange/change_now/estimated_exchange_amount.dart index 62eee2f60..7648cdacb 100644 --- a/lib/models/exchange/change_now/estimated_exchange_amount.dart +++ b/lib/models/exchange/change_now/estimated_exchange_amount.dart @@ -1,96 +1,142 @@ -/* - * This file is part of Stack Wallet. - * - * Copyright (c) 2023 Cypher Stack - * All Rights Reserved. - * The code is distributed under GPLv3 license, see LICENSE file for details. - * Generated by Cypher Stack on 2023-05-26 - * - */ +import "package:decimal/decimal.dart"; -import 'package:decimal/decimal.dart'; - -import '../../../utilities/logger.dart'; +import "../../../services/exchange/change_now/change_now_api.dart"; +/// Immutable model representing exchange rate information. class EstimatedExchangeAmount { - /// Estimated exchange amount - final Decimal estimatedAmount; + /// Ticker of the currency you want to exchange. + final String fromCurrency; - /// Dash-separated min and max estimated time in minutes - final String transactionSpeedForecast; + /// Network of the currency you want to exchange. + final String fromNetwork; - /// Some warnings like warnings that transactions on this network - /// take longer or that the currency has moved to another network - final String? warningMessage; + /// Ticker of the currency you want to receive. + final String toCurrency; + + /// Network of the currency you want to receive. + final String toNetwork; - /// (Optional) Use rateId for fixed-rate flow. If this field is true, you - /// could use returned field "rateId" in next method for creating transaction - /// to freeze estimated amount that you got in this method. Current estimated - /// amount would be valid until time in field "validUntil" + /// Type of exchange flow. Either `standard` or `fixed-rate`. + final CNFlow flow; + + /// Direction of exchange flow. Either `direct` or `reverse`. + /// + /// - `direct`: set amount for `fromCurrency`, get amount of `toCurrency`. + /// - `reverse`: set amount for `toCurrency`, get amount of `fromCurrency`. + final CNExchangeType type; + + /// RateId is needed for fixed-rate flow. Used to freeze estimated amount. final String? rateId; - /// ONLY for fixed rate. - /// Network fee for transferring funds between wallets, it should be deducted - /// from the result. Formula for calculating the estimated amount is given below - /// estimatedAmount = (rate * amount) - networkFee - final Decimal? networkFee; + /// Date and time before which the estimated amount is valid if using `rateId`. + final DateTime validUntil; + + /// Dash-separated min and max estimated time in minutes. + final String? transactionSpeedForecast; + + /// Some warnings, such as if a currency has moved to another network or transactions take longer. + final String? warningMessage; + + /// Deposit fee in the selected currency. + final Decimal depositFee; + + /// Withdrawal fee in the selected currency. + final Decimal withdrawalFee; + + /// A personal and permanent identifier under which information is stored in the database. + /// + /// Only enabled for special partners. + final String? userId; + + /// Exchange amount of `fromCurrency`. + /// + /// If `type=reverse`, this is an estimated value. + final Decimal fromAmount; - EstimatedExchangeAmount({ - required this.estimatedAmount, - required this.transactionSpeedForecast, - required this.warningMessage, + /// Exchange amount of `toCurrency`. + /// + /// If `type=direct`, this is an estimated value. + final Decimal toAmount; + + /// Creates an immutable [EstimatedExchangeAmount] instance. + const EstimatedExchangeAmount({ + required this.fromCurrency, + required this.fromNetwork, + required this.toCurrency, + required this.toNetwork, + required this.flow, + required this.type, required this.rateId, - this.networkFee, + required this.validUntil, + this.transactionSpeedForecast, + this.warningMessage, + required this.depositFee, + required this.withdrawalFee, + this.userId, + required this.fromAmount, + required this.toAmount, }); + /// Creates an instance of [EstimatedExchangeAmount] from a JSON map. factory EstimatedExchangeAmount.fromJson(Map json) { - try { - return EstimatedExchangeAmount( - estimatedAmount: Decimal.parse( - json["estimatedAmount"]?.toString() ?? - json["estimatedDeposit"].toString(), - ), - transactionSpeedForecast: - json["transactionSpeedForecast"] as String? ?? "", - warningMessage: json["warningMessage"] as String?, - rateId: json["rateId"] as String?, - networkFee: Decimal.tryParse(json["networkFee"].toString()), - ); - } catch (e, s) { - Logging.instance.e( - "Failed to parse: $json", - error: e, - stackTrace: s, - ); - rethrow; - } + return EstimatedExchangeAmount( + fromCurrency: json["fromCurrency"] as String, + fromNetwork: json["fromNetwork"] as String, + toCurrency: json["toCurrency"] as String, + toNetwork: json["toNetwork"] as String, + flow: _parseFlow(json["flow"] as String), + type: _parseType(json["type"] as String), + rateId: json["rateId"] as String?, + validUntil: DateTime.parse(json["validUntil"] as String), + transactionSpeedForecast: json["transactionSpeedForecast"] as String?, + warningMessage: json["warningMessage"] as String?, + depositFee: Decimal.parse(json["depositFee"].toString()), + withdrawalFee: Decimal.parse(json["withdrawalFee"].toString()), + userId: json["userId"]?.toString(), + fromAmount: Decimal.parse(json["fromAmount"].toString()), + toAmount: Decimal.parse(json["toAmount"].toString()), + ); } + /// Converts this [EstimatedExchangeAmount] instance to a JSON map. Map toJson() { return { - "estimatedAmount": estimatedAmount, + "fromCurrency": fromCurrency, + "fromNetwork": fromNetwork, + "toCurrency": toCurrency, + "toNetwork": toNetwork, + "flow": flow.name.replaceAll("fixedRate", "fixed-rate"), + "type": type.name, + "rateId": rateId, + "validUntil": validUntil.toIso8601String(), "transactionSpeedForecast": transactionSpeedForecast, "warningMessage": warningMessage, - "rateId": rateId, - "networkFee": networkFee, + "depositFee": depositFee.toString(), + "withdrawalFee": withdrawalFee.toString(), + "userId": userId, + "fromAmount": fromAmount.toString(), + "toAmount": toAmount.toString(), }; } - EstimatedExchangeAmount copyWith({ - Decimal? estimatedAmount, - String? transactionSpeedForecast, - String? warningMessage, - String? rateId, - Decimal? networkFee, - }) { - return EstimatedExchangeAmount( - estimatedAmount: estimatedAmount ?? this.estimatedAmount, - transactionSpeedForecast: - transactionSpeedForecast ?? this.transactionSpeedForecast, - warningMessage: warningMessage ?? this.warningMessage, - rateId: rateId ?? this.rateId, - networkFee: networkFee ?? this.networkFee, - ); + static CNFlow _parseFlow(String value) { + switch (value) { + case "fixed-rate": + return CNFlow.fixedRate; + case "standard": + default: + return CNFlow.standard; + } + } + + static CNExchangeType _parseType(String value) { + switch (value) { + case "reverse": + return CNExchangeType.reverse; + case "direct": + default: + return CNExchangeType.direct; + } } @override diff --git a/lib/models/exchange/change_now/exchange_transaction.dart b/lib/models/exchange/change_now/exchange_transaction.dart index 6bd514546..1de49bfd0 100644 --- a/lib/models/exchange/change_now/exchange_transaction.dart +++ b/lib/models/exchange/change_now/exchange_transaction.dart @@ -1,6 +1,6 @@ -/* +/* * This file is part of Stack Wallet. - * + * * Copyright (c) 2023 Cypher Stack * All Rights Reserved. * The code is distributed under GPLv3 license, see LICENSE file for details. @@ -77,6 +77,7 @@ class ExchangeTransaction { // @HiveField(14) final ExchangeTransactionStatus? statusObject; + @Deprecated("Only kept for legacy reasons") ExchangeTransaction({ required this.id, required this.payinAddress, @@ -96,6 +97,7 @@ class ExchangeTransaction { }); /// Important to pass a "date": DateTime in or it will default to 1970 + @Deprecated("Only kept for legacy reasons") factory ExchangeTransaction.fromJson(Map json) { try { return ExchangeTransaction( @@ -111,14 +113,16 @@ class ExchangeTransaction { refundExtraId: json["refundExtraId"] as String? ?? "", payoutExtraIdName: json["payoutExtraIdName"] as String? ?? "", uuid: json["uuid"] as String? ?? const Uuid().v1(), - date: DateTime.tryParse(json["date"] as String? ?? "") ?? + date: + DateTime.tryParse(json["date"] as String? ?? "") ?? DateTime.fromMillisecondsSinceEpoch(0), statusString: json["statusString"] as String? ?? "", - statusObject: json["statusObject"] is Map - ? ExchangeTransactionStatus.fromJson( - json["statusObject"] as Map, - ) - : null, + statusObject: + json["statusObject"] is Map + ? ExchangeTransactionStatus.fromJson( + json["statusObject"] as Map, + ) + : null, ); } catch (e) { rethrow; diff --git a/lib/models/exchange/change_now/exchange_transaction_status.dart b/lib/models/exchange/change_now/exchange_transaction_status.dart index 7874b8b80..5a904690f 100644 --- a/lib/models/exchange/change_now/exchange_transaction_status.dart +++ b/lib/models/exchange/change_now/exchange_transaction_status.dart @@ -1,6 +1,6 @@ -/* +/* * This file is part of Stack Wallet. - * + * * Copyright (c) 2023 Cypher Stack * All Rights Reserved. * The code is distributed under GPLv3 license, see LICENSE file for details. @@ -11,38 +11,14 @@ import 'package:hive/hive.dart'; import '../../../utilities/logger.dart'; +import 'cn_exchange_transaction_status.dart' + show + ChangeNowTransactionStatus, + changeNowTransactionStatusFromStringIgnoreCase; part '../../type_adaptors/exchange_transaction_status.g.dart'; -enum ChangeNowTransactionStatus { - New, - Waiting, - Confirming, - Exchanging, - Sending, - Finished, - Failed, - Refunded, - Verifying, -} - -extension ChangeNowTransactionStatusExt on ChangeNowTransactionStatus { - String get lowerCaseName => name.toLowerCase(); -} - -ChangeNowTransactionStatus changeNowTransactionStatusFromStringIgnoreCase( - String string, -) { - for (final value in ChangeNowTransactionStatus.values) { - if (value.lowerCaseName == string.toLowerCase()) { - return value; - } - } - throw ArgumentError( - "String value does not match any known ChangeNowTransactionStatus", - ); -} - +@Deprecated("Only kept for legacy reasons") @HiveType(typeId: 16) class ExchangeTransactionStatus { /// Transaction status @@ -156,6 +132,7 @@ class ExchangeTransactionStatus { @HiveField(26) final bool isPartner; + @Deprecated("Only kept for legacy reasons") ExchangeTransactionStatus({ required this.status, required this.payinAddress, @@ -186,6 +163,7 @@ class ExchangeTransactionStatus { required this.payload, }); + @Deprecated("Only kept for legacy reasons") factory ExchangeTransactionStatus.fromJson(Map json) { Logging.instance.d(json, stackTrace: StackTrace.current); try { @@ -199,12 +177,14 @@ class ExchangeTransactionStatus { toCurrency: json["toCurrency"] as String? ?? "", id: json["id"] as String, updatedAt: json["updatedAt"] as String? ?? "", - expectedSendAmountDecimal: json["expectedSendAmount"] == null - ? "" - : json["expectedSendAmount"].toString(), - expectedReceiveAmountDecimal: json["expectedReceiveAmount"] == null - ? "" - : json["expectedReceiveAmount"].toString(), + expectedSendAmountDecimal: + json["expectedSendAmount"] == null + ? "" + : json["expectedSendAmount"].toString(), + expectedReceiveAmountDecimal: + json["expectedReceiveAmount"] == null + ? "" + : json["expectedReceiveAmount"].toString(), createdAt: json["createdAt"] as String? ?? "", isPartner: json["isPartner"] as bool, depositReceivedAt: json["depositReceivedAt"] as String? ?? "", @@ -216,9 +196,10 @@ class ExchangeTransactionStatus { payoutExtraId: json["payoutExtraId"] as String? ?? "", amountSendDecimal: json["amountSend"] == null ? "" : json["amountSend"].toString(), - amountReceiveDecimal: json["amountReceive"] == null - ? "" - : json["amountReceive"].toString(), + amountReceiveDecimal: + json["amountReceive"] == null + ? "" + : json["amountReceive"].toString(), tokensDestination: json["tokensDestination"] as String? ?? "", refundAddress: json["refundAddress"] as String? ?? "", refundExtraId: json["refundExtraId"] as String? ?? "", @@ -233,6 +214,7 @@ class ExchangeTransactionStatus { } } + @Deprecated("Only kept for legacy reasons") Map toJson() { final map = { "status": status.name, @@ -267,6 +249,7 @@ class ExchangeTransactionStatus { return map; } + @Deprecated("Only kept for legacy reasons") ExchangeTransactionStatus copyWith({ ChangeNowTransactionStatus? status, String? payinAddress, diff --git a/lib/models/exchange/incomplete_exchange.dart b/lib/models/exchange/incomplete_exchange.dart index 1397afb1a..3608b8b05 100644 --- a/lib/models/exchange/incomplete_exchange.dart +++ b/lib/models/exchange/incomplete_exchange.dart @@ -10,13 +10,16 @@ import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; + +import '../../utilities/enums/exchange_rate_type_enum.dart'; import 'response_objects/estimate.dart'; import 'response_objects/trade.dart'; -import '../../utilities/enums/exchange_rate_type_enum.dart'; class IncompleteExchangeModel extends ChangeNotifier { final String sendTicker; + final String? sendNetwork; final String receiveTicker; + final String? receiveNetwork; final String rateInfo; @@ -74,7 +77,9 @@ class IncompleteExchangeModel extends ChangeNotifier { IncompleteExchangeModel({ required this.sendTicker, + required this.sendNetwork, required this.receiveTicker, + required this.receiveNetwork, required this.rateInfo, required this.sendAmount, required this.receiveAmount, diff --git a/lib/models/exchange/response_objects/trade.dart b/lib/models/exchange/response_objects/trade.dart index 03685a0ab..e6f996f9b 100644 --- a/lib/models/exchange/response_objects/trade.dart +++ b/lib/models/exchange/response_objects/trade.dart @@ -11,6 +11,7 @@ import 'package:hive/hive.dart'; import '../../../services/exchange/change_now/change_now_exchange.dart'; +import '../change_now/cn_exchange_transaction.dart'; import '../change_now/exchange_transaction.dart'; part 'trade.g.dart'; @@ -225,9 +226,10 @@ class Trade { timestamp: exTx.date, updatedAt: DateTime.tryParse(exTx.statusObject!.updatedAt) ?? exTx.date, payInCurrency: exTx.fromCurrency, - payInAmount: exTx.statusObject!.amountSendDecimal.isEmpty - ? exTx.statusObject!.expectedSendAmountDecimal - : exTx.statusObject!.amountSendDecimal, + payInAmount: + exTx.statusObject!.amountSendDecimal.isEmpty + ? exTx.statusObject!.expectedSendAmountDecimal + : exTx.statusObject!.amountSendDecimal, payInAddress: exTx.payinAddress, payInNetwork: "", payInExtraId: exTx.payinExtraId, @@ -245,6 +247,42 @@ class Trade { ); } + factory Trade.fromCNExchangeTransaction( + CNExchangeTransaction exTx, + bool reversed, + ) { + return Trade( + uuid: exTx.uuid, + tradeId: exTx.id, + rateType: "", + direction: reversed ? "reverse" : "direct", + timestamp: exTx.date, + updatedAt: DateTime.tryParse(exTx.statusObject!.updatedAt) ?? exTx.date, + payInCurrency: exTx.fromCurrency, + payInAmount: + exTx.statusObject!.amountFrom ?? + exTx.statusObject!.expectedAmountFrom ?? + exTx.fromAmount.toString(), + payInAddress: exTx.payinAddress, + payInNetwork: exTx.fromNetwork, + payInExtraId: "", + payInTxid: exTx.statusObject!.payinHash ?? "", + payOutCurrency: exTx.toCurrency, + payOutAmount: + exTx.statusObject!.amountTo ?? + exTx.statusObject!.expectedAmountTo ?? + exTx.toAmount.toString(), + payOutAddress: exTx.payoutAddress, + payOutNetwork: exTx.toNetwork, + payOutExtraId: exTx.payoutExtraId, + payOutTxid: exTx.statusObject!.payoutHash ?? "", + refundAddress: exTx.refundAddress, + refundExtraId: exTx.refundExtraId, + status: exTx.statusObject!.status.name, + exchangeName: ChangeNowExchange.exchangeName, + ); + } + @override String toString() { return toMap().toString(); diff --git a/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list_element.dart b/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list_element.dart index 89a818529..23035dc8e 100644 --- a/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list_element.dart +++ b/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list_element.dart @@ -48,26 +48,28 @@ class _AddTokenListElementState extends ConsumerState { @override Widget build(BuildContext context) { - final currency = ExchangeDataLoadingService.instance.isar.currencies - .where() - .exchangeNameEqualTo(ChangeNowExchange.exchangeName) - .filter() - .tokenContractEqualTo( - widget.data.token.address, - caseSensitive: false, - ) - .and() - .imageIsNotEmpty() - .findFirstSync(); + final currency = + ExchangeDataLoadingService.instance.isar.currencies + .where() + .exchangeNameEqualTo(ChangeNowExchange.exchangeName) + .filter() + .tokenContractEqualTo( + widget.data.token.address, + caseSensitive: false, + ) + .and() + .imageIsNotEmpty() + .findFirstSync(); final String mainLabel = widget.data.token.name; final double iconSize = isDesktop ? 32 : 24; return RoundedWhiteContainer( padding: EdgeInsets.all(isDesktop ? 16 : 12), - borderColor: isDesktop - ? Theme.of(context).extension()!.backgroundAppBar - : null, + borderColor: + isDesktop + ? Theme.of(context).extension()!.backgroundAppBar + : null, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -75,71 +77,68 @@ class _AddTokenListElementState extends ConsumerState { children: [ currency != null ? SvgPicture.network( - currency.image, - width: iconSize, - height: iconSize, - placeholderBuilder: (_) => AppIcon( - width: iconSize, - height: iconSize, - ), - ) + currency.image, + width: iconSize, + height: iconSize, + placeholderBuilder: + (_) => AppIcon(width: iconSize, height: iconSize), + ) : SvgPicture.asset( - widget.data.token.symbol == "BNB" - ? Assets.svg.bnbIcon - : Assets.svg.ethereum, - width: iconSize, - height: iconSize, - ), - const SizedBox( - width: 12, - ), + widget.data.token.symbol == "BNB" + ? Assets.svg.bnbIcon + : Assets.svg.ethereum, + width: iconSize, + height: iconSize, + ), + const SizedBox(width: 12), ConditionalParent( condition: isDesktop, - builder: (child) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - child, - const SizedBox( - height: 2, - ), - Text( - widget.data.token.symbol, - style: STextStyles.desktopTextExtraExtraSmall(context), - overflow: TextOverflow.ellipsis, + builder: + (child) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + child, + const SizedBox(height: 2), + Text( + widget.data.token.symbol, + style: STextStyles.desktopTextExtraExtraSmall( + context, + ), + overflow: TextOverflow.ellipsis, + ), + ], ), - ], - ), child: Text( isDesktop ? mainLabel : "$mainLabel (${widget.data.token.symbol})", - style: isDesktop - ? STextStyles.desktopTextSmall(context) - : STextStyles.w600_14(context), + style: + isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.w600_14(context), overflow: TextOverflow.ellipsis, ), ), ], ), - const SizedBox( - width: 4, - ), + const SizedBox(width: 4), isDesktop ? Checkbox( - value: widget.data.selected, - onChanged: (newValue) => - setState(() => widget.data.selected = newValue!), - ) + value: widget.data.selected, + onChanged: + (newValue) => + setState(() => widget.data.selected = newValue!), + ) : SizedBox( - height: 20, - width: 40, - child: DraggableSwitchButton( - isOn: widget.data.selected, - onValueChanged: (newValue) { - widget.data.selected = newValue; - }, - ), + height: 20, + width: 40, + child: DraggableSwitchButton( + isOn: widget.data.selected, + onValueChanged: (newValue) { + widget.data.selected = newValue; + }, ), + ), ], ), ); diff --git a/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/coin_select_item.dart b/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/coin_select_item.dart index 68527ccaf..80acac3ed 100644 --- a/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/coin_select_item.dart +++ b/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/coin_select_item.dart @@ -14,6 +14,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:isar/isar.dart'; + import '../../../../models/add_wallet_list_entity/add_wallet_list_entity.dart'; import '../../../../models/add_wallet_list_entity/sub_classes/eth_token_entity.dart'; import '../../../../models/isar/exchange_cache/currency.dart'; @@ -28,10 +29,7 @@ import '../../../../utilities/text_styles.dart'; import '../../../../utilities/util.dart'; class CoinSelectItem extends ConsumerWidget { - const CoinSelectItem({ - super.key, - required this.entity, - }); + const CoinSelectItem({super.key, required this.entity}); final AddWalletListEntity entity; @@ -44,82 +42,77 @@ class CoinSelectItem extends ConsumerWidget { String? tokenImageUri; if (entity is EthTokenEntity) { - final currency = ExchangeDataLoadingService.instance.isar.currencies - .where() - .exchangeNameEqualTo(ChangeNowExchange.exchangeName) - .filter() - .tokenContractEqualTo( - (entity as EthTokenEntity).token.address, - caseSensitive: false, - ) - .and() - .imageIsNotEmpty() - .findFirstSync(); + final currency = + ExchangeDataLoadingService.instance.isar.currencies + .where() + .exchangeNameEqualTo(ChangeNowExchange.exchangeName) + .filter() + .tokenContractEqualTo( + (entity as EthTokenEntity).token.address, + caseSensitive: false, + ) + .and() + .imageIsNotEmpty() + .findFirstSync(); tokenImageUri = currency?.image; } return Container( decoration: BoxDecoration( - color: selectedEntity == entity - ? Theme.of(context).extension()!.textFieldActiveBG - : Theme.of(context).extension()!.popupBG, - borderRadius: - BorderRadius.circular(Constants.size.circularBorderRadius), + color: + selectedEntity == entity + ? Theme.of(context).extension()!.textFieldActiveBG + : Theme.of(context).extension()!.popupBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), child: MaterialButton( key: Key("coinSelectItemButtonKey_${entity.name}${entity.ticker}"), - padding: isDesktop - ? const EdgeInsets.only(left: 24) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.only(left: 24) + : const EdgeInsets.all(12), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(Constants.size.circularBorderRadius), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: isDesktop ? 70 : 0, - ), + constraints: BoxConstraints(minHeight: isDesktop ? 70 : 0), child: Row( children: [ tokenImageUri != null - ? SvgPicture.network( - tokenImageUri, - width: 26, - height: 26, - ) + ? SvgPicture.network(tokenImageUri, width: 26, height: 26) : SvgPicture.file( - File( - ref.watch(coinIconProvider(entity.cryptoCurrency)), - ), - width: 26, - height: 26, - ), - SizedBox( - width: isDesktop ? 12 : 10, - ), + File(ref.watch(coinIconProvider(entity.cryptoCurrency))), + width: 26, + height: 26, + ), + SizedBox(width: isDesktop ? 12 : 10), Text( "${entity.name} (${entity.ticker})", - style: isDesktop - ? STextStyles.desktopTextMedium(context) - : STextStyles.subtitle600(context).copyWith( - fontSize: 14, - ), + style: + isDesktop + ? STextStyles.desktopTextMedium(context) + : STextStyles.subtitle600( + context, + ).copyWith(fontSize: 14), ), if (isDesktop && selectedEntity == entity) const Spacer(), if (isDesktop && selectedEntity == entity) Padding( - padding: const EdgeInsets.only( - right: 18, - ), + padding: const EdgeInsets.only(right: 18), child: SizedBox( width: 24, height: 24, child: SvgPicture.asset( Assets.svg.check, - color: Theme.of(context) - .extension()! - .accentColorDark, + color: + Theme.of( + context, + ).extension()!.accentColorDark, ), ), ), diff --git a/lib/pages/exchange_view/exchange_coin_selection/exchange_currency_selection_view.dart b/lib/pages/exchange_view/exchange_coin_selection/exchange_currency_selection_view.dart index e3ae98acd..5801cf869 100644 --- a/lib/pages/exchange_view/exchange_coin_selection/exchange_currency_selection_view.dart +++ b/lib/pages/exchange_view/exchange_coin_selection/exchange_currency_selection_view.dart @@ -15,10 +15,8 @@ import 'package:flutter_svg/svg.dart'; import 'package:isar/isar.dart'; import '../../../app_config.dart'; -import '../../../exceptions/exchange/unsupported_currency_exception.dart'; import '../../../models/isar/exchange_cache/currency.dart'; import '../../../models/isar/exchange_cache/pair.dart'; -import '../../../services/exchange/change_now/change_now_exchange.dart'; import '../../../services/exchange/exchange.dart'; import '../../../services/exchange/exchange_data_loading_service.dart'; import '../../../services/exchange/majestic_bank/majestic_bank_exchange.dart'; @@ -34,12 +32,9 @@ import '../../../widgets/background.dart'; import '../../../widgets/conditional_parent.dart'; import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../widgets/custom_loading_overlay.dart'; -import '../../../widgets/desktop/primary_button.dart'; -import '../../../widgets/desktop/secondary_button.dart'; import '../../../widgets/icon_widgets/x_icon.dart'; import '../../../widgets/loading_indicator.dart'; import '../../../widgets/rounded_white_container.dart'; -import '../../../widgets/stack_dialog.dart'; import '../../../widgets/stack_text_field.dart'; import '../../../widgets/textfield_icon_button.dart'; import '../../buy_view/sub_widgets/crypto_selection_view.dart'; @@ -74,26 +69,24 @@ class _ExchangeCurrencySelectionViewState bool _loaded = false; String _searchString = ""; - Future _showUpdatingCurrencies({ - required Future whileFuture, - }) async { + Future _showUpdatingCurrencies({required Future whileFuture}) async { unawaited( showDialog( context: context, barrierDismissible: false, - builder: (_) => WillPopScope( - onWillPop: () async => false, - child: Container( - color: Theme.of(context) - .extension()! - .overlay - .withOpacity(0.6), - child: const CustomLoadingOverlay( - message: "Loading currencies", - eventBus: null, + builder: + (_) => WillPopScope( + onWillPop: () async => false, + child: Container( + color: Theme.of( + context, + ).extension()!.overlay.withOpacity(0.6), + child: const CustomLoadingOverlay( + message: "Loading currencies", + eventBus: null, + ), + ), ), - ), - ), ), ); @@ -111,85 +104,52 @@ class _ExchangeCurrencySelectionViewState return await _getCurrencies(); } await ExchangeDataLoadingService.instance.initDB(); - final List currencies = await ExchangeDataLoadingService - .instance.isar.currencies - .where() - .filter() - .exchangeNameEqualTo(MajesticBankExchange.exchangeName) - .or() - .exchangeNameStartsWith(TrocadorExchange.exchangeName) - .or() - .exchangeNameStartsWith(NanswapExchange.exchangeName) - .findAll(); - - final cn = await ChangeNowExchange.instance.getPairedCurrencies( - widget.pairedTicker!, - widget.isFixedRate, - ); - - if (cn.value == null) { - if (cn.exception is UnsupportedCurrencyException) { - return _getDistinctCurrenciesFrom(currencies); - } - - if (mounted) { - await showDialog( - context: context, - builder: (context) => StackDialog( - title: "Exchange Error", - message: "Failed to load currency data: ${cn.exception}", - leftButton: SecondaryButton( - label: "Ok", - onPressed: Navigator.of(context, rootNavigator: isDesktop).pop, - ), - rightButton: PrimaryButton( - label: "Retry", - onPressed: () async { - Navigator.of(context, rootNavigator: isDesktop).pop(); - _currencies = await _showUpdatingCurrencies( - whileFuture: _loadCurrencies(), - ); - setState(() {}); - }, - ), - ), - ); - } - } else { - currencies.addAll(cn.value!); - } + final List currencies = + await ExchangeDataLoadingService.instance.isar.currencies + .where() + .filter() + .exchangeNameEqualTo(MajesticBankExchange.exchangeName) + .or() + .exchangeNameStartsWith(TrocadorExchange.exchangeName) + .or() + .exchangeNameStartsWith(NanswapExchange.exchangeName) + .findAll(); return _getDistinctCurrenciesFrom(currencies); } Future> _getCurrencies() async { await ExchangeDataLoadingService.instance.initDB(); - final currencies = await ExchangeDataLoadingService.instance.isar.currencies - .where() - .filter() - .isFiatEqualTo(false) - .and() - .group( - (q) => widget.isFixedRate - ? q - .rateTypeEqualTo(SupportedRateType.both) - .or() - .rateTypeEqualTo(SupportedRateType.fixed) - : q - .rateTypeEqualTo(SupportedRateType.both) - .or() - .rateTypeEqualTo(SupportedRateType.estimated), - ) - .sortByIsStackCoin() - .thenByName() - .findAll(); + final currencies = + await ExchangeDataLoadingService.instance.isar.currencies + .where() + .filter() + .isFiatEqualTo(false) + .and() + .group( + (q) => + widget.isFixedRate + ? q + .rateTypeEqualTo(SupportedRateType.both) + .or() + .rateTypeEqualTo(SupportedRateType.fixed) + : q + .rateTypeEqualTo(SupportedRateType.both) + .or() + .rateTypeEqualTo(SupportedRateType.estimated), + ) + .sortByIsStackCoin() + .thenByName() + .findAll(); // If using Tor, filter exchanges which do not support Tor. if (Prefs.instance.useTor) { if (Exchange.exchangeNamesWithTorSupport.isNotEmpty) { currencies.removeWhere( - (element) => !Exchange.exchangeNamesWithTorSupport - .contains(element.exchangeName), + (element) => + !Exchange.exchangeNamesWithTorSupport.contains( + element.exchangeName, + ), ); } } @@ -262,8 +222,9 @@ class _ExchangeCurrencySelectionViewState if (!_loaded) { _loaded = true; WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { - _currencies = - await _showUpdatingCurrencies(whileFuture: _loadCurrencies()); + _currencies = await _showUpdatingCurrencies( + whileFuture: _loadCurrencies(), + ); setState(() {}); }); } @@ -284,7 +245,7 @@ class _ExchangeCurrencySelectionViewState const Duration(milliseconds: 50), ); } - if (mounted) { + if (context.mounted) { Navigator.of(context).pop(); } }, @@ -295,9 +256,7 @@ class _ExchangeCurrencySelectionViewState ), ), body: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - ), + padding: const EdgeInsets.symmetric(horizontal: 16), child: child, ), ), @@ -307,10 +266,7 @@ class _ExchangeCurrencySelectionViewState crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: isDesktop ? MainAxisSize.min : MainAxisSize.max, children: [ - if (!isDesktop) - const SizedBox( - height: 16, - ), + if (!isDesktop) const SizedBox(height: 16), ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -340,32 +296,31 @@ class _ExchangeCurrencySelectionViewState height: 16, ), ), - suffixIcon: _searchController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - _searchController.text = ""; - _searchString = ""; - }); - }, - ), - ], + suffixIcon: + _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + _searchString = ""; + }); + }, + ), + ], + ), ), - ), - ) - : null, + ) + : null, ), ), ), - const SizedBox( - height: 20, - ), + const SizedBox(height: 20), Flexible( child: Builder( builder: (context) { @@ -377,17 +332,19 @@ class _ExchangeCurrencySelectionViewState final items = filter(_searchString); - final walletCoins = items - .where( - (currency) => coins - .where( - (coin) => - coin.ticker.toLowerCase() == - currency.ticker.toLowerCase(), - ) - .isNotEmpty, - ) - .toList(); + final walletCoins = + items + .where( + (currency) => + coins + .where( + (coin) => + coin.ticker.toLowerCase() == + currency.ticker.toLowerCase(), + ) + .isNotEmpty, + ) + .toList(); // sort alphabetically by name items.sort((a, b) => a.name.compareTo(b.name)); @@ -408,8 +365,9 @@ class _ExchangeCurrencySelectionViewState primary: isDesktop ? false : null, itemCount: items.length, itemBuilder: (builderContext, index) { - final bool hasImageUrl = - items[index].image.startsWith("http"); + final bool hasImageUrl = items[index].image.startsWith( + "http", + ); return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: GestureDetector( @@ -425,29 +383,27 @@ class _ExchangeCurrencySelectionViewState child: AppConfig.isStackCoin(items[index].ticker) ? CoinIconForTicker( - ticker: items[index].ticker, - size: 24, - ) + ticker: items[index].ticker, + size: 24, + ) // ? getIconForTicker( // items[index].ticker, // size: 24, // ) : hasImageUrl - ? SvgPicture.network( - items[index].image, - width: 24, - height: 24, - placeholderBuilder: (_) => - const LoadingIndicator(), - ) - : const SizedBox( - width: 24, - height: 24, - ), - ), - const SizedBox( - width: 10, + ? SvgPicture.network( + items[index].image, + width: 24, + height: 24, + placeholderBuilder: + (_) => const LoadingIndicator(), + ) + : const SizedBox( + width: 24, + height: 24, + ), ), + const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: @@ -455,19 +411,20 @@ class _ExchangeCurrencySelectionViewState children: [ Text( items[index].name, - style: - STextStyles.largeMedium14(context), - ), - const SizedBox( - height: 2, + style: STextStyles.largeMedium14( + context, + ), ), + const SizedBox(height: 2), Text( items[index].ticker.toUpperCase(), - style: STextStyles.smallMed12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, + style: STextStyles.smallMed12( + context, + ).copyWith( + color: + Theme.of(context) + .extension()! + .textSubtitle1, ), ), ], diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 111d38448..13f334d2a 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -40,6 +40,7 @@ import '../../utilities/amount/amount_unit.dart'; import '../../utilities/assets.dart'; import '../../utilities/constants.dart'; import '../../utilities/enums/exchange_rate_type_enum.dart'; +import '../../utilities/logger.dart'; import '../../utilities/text_styles.dart'; import '../../utilities/util.dart'; import '../../wallets/crypto_currency/crypto_currency.dart'; @@ -60,12 +61,7 @@ import 'sub_widgets/exchange_provider_options.dart'; import 'sub_widgets/rate_type_toggle.dart'; class ExchangeForm extends ConsumerStatefulWidget { - const ExchangeForm({ - super.key, - this.walletId, - this.coin, - this.contract, - }); + const ExchangeForm({super.key, this.walletId, this.coin, this.contract}); final String? walletId; final CryptoCurrency? coin; @@ -111,19 +107,19 @@ class _ExchangeFormState extends ConsumerState { showDialog( context: context, barrierDismissible: false, - builder: (_) => WillPopScope( - onWillPop: () async => false, - child: Container( - color: Theme.of(context) - .extension()! - .overlay - .withOpacity(0.6), - child: const CustomLoadingOverlay( - message: "Updating exchange rate", - eventBus: null, + builder: + (_) => WillPopScope( + onWillPop: () async => false, + child: Container( + color: Theme.of( + context, + ).extension()!.overlay.withOpacity(0.6), + child: const CustomLoadingOverlay( + message: "Updating exchange rate", + eventBus: null, + ), + ), ), - ), - ), ), ); @@ -185,27 +181,26 @@ class _ExchangeFormState extends ConsumerState { Future _getAggregateCurrency(Currency currency) async { final rateType = ref.read(efRateTypeProvider); - final currencies = await ExchangeDataLoadingService.instance.isar.currencies - .filter() - .group( - (q) => rateType == ExchangeRateType.fixed - ? q - .rateTypeEqualTo(SupportedRateType.both) - .or() - .rateTypeEqualTo(SupportedRateType.fixed) - : q - .rateTypeEqualTo(SupportedRateType.both) - .or() - .rateTypeEqualTo(SupportedRateType.estimated), - ) - .and() - .tickerEqualTo( - currency.ticker, - caseSensitive: false, - ) - .and() - .tokenContractEqualTo(currency.tokenContract) - .findAll(); + final currencies = + await ExchangeDataLoadingService.instance.isar.currencies + .filter() + .group( + (q) => + rateType == ExchangeRateType.fixed + ? q + .rateTypeEqualTo(SupportedRateType.both) + .or() + .rateTypeEqualTo(SupportedRateType.fixed) + : q + .rateTypeEqualTo(SupportedRateType.both) + .or() + .rateTypeEqualTo(SupportedRateType.estimated), + ) + .and() + .tickerEqualTo(currency.ticker, caseSensitive: false) + .and() + .tokenContractEqualTo(currency.tokenContract) + .findAll(); final items = [Tuple2(currency.exchangeName, currency)]; @@ -242,10 +237,9 @@ class _ExchangeFormState extends ConsumerState { if (selectedCurrency != null) { await showUpdatingExchangeRate( whileFuture: _getAggregateCurrency(selectedCurrency).then( - (aggregateSelected) => ref.read(efCurrencyPairProvider).setSend( - aggregateSelected, - notifyListeners: true, - ), + (aggregateSelected) => ref + .read(efCurrencyPairProvider) + .setSend(aggregateSelected, notifyListeners: true), ), ); } @@ -269,10 +263,9 @@ class _ExchangeFormState extends ConsumerState { if (selectedCurrency != null) { await showUpdatingExchangeRate( whileFuture: _getAggregateCurrency(selectedCurrency).then( - (aggregateSelected) => ref.read(efCurrencyPairProvider).setReceive( - aggregateSelected, - notifyListeners: true, - ), + (aggregateSelected) => ref + .read(efCurrencyPairProvider) + .setReceive(aggregateSelected, notifyListeners: true), ), ); } @@ -284,20 +277,20 @@ class _ExchangeFormState extends ConsumerState { _receiveFocusNode.unfocus(); final temp = ref.read(efCurrencyPairProvider).send; - ref.read(efCurrencyPairProvider).setSend( + ref + .read(efCurrencyPairProvider) + .setSend( ref.read(efCurrencyPairProvider).receive, notifyListeners: true, ); - ref.read(efCurrencyPairProvider).setReceive( - temp, - notifyListeners: true, - ); + ref.read(efCurrencyPairProvider).setReceive(temp, notifyListeners: true); // final reversed = ref.read(efReversedProvider); final amount = ref.read(efSendAmountProvider); - ref.read(efSendAmountProvider.notifier).state = - ref.read(efReceiveAmountProvider); + ref.read(efSendAmountProvider.notifier).state = ref.read( + efReceiveAmountProvider, + ); ref.read(efReceiveAmountProvider.notifier).state = amount; @@ -315,72 +308,73 @@ class _ExchangeFormState extends ConsumerState { _sendFocusNode.unfocus(); _receiveFocusNode.unfocus(); - final result = isDesktop - ? await showDialog( - context: context, - builder: (context) { - return DesktopDialog( - maxHeight: 700, - maxWidth: 580, - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( + final result = + isDesktop + ? await showDialog( + context: context, + builder: (context) { + return DesktopDialog( + maxHeight: 700, + maxWidth: 580, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Choose a coin to exchange", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( padding: const EdgeInsets.only( left: 32, + right: 32, + bottom: 32, ), - child: Text( - "Choose a coin to exchange", - style: STextStyles.desktopH3(context), - ), - ), - const DesktopDialogCloseButton(), - ], - ), - Expanded( - child: Padding( - padding: const EdgeInsets.only( - left: 32, - right: 32, - bottom: 32, - ), - child: Row( - children: [ - Expanded( - child: RoundedWhiteContainer( - padding: const EdgeInsets.all(16), - borderColor: Theme.of(context) - .extension()! - .background, - child: ExchangeCurrencySelectionView( - willChangeTicker: willChange, - pairedTicker: paired, - isFixedRate: isFixedRate, - willChangeIsSend: willChangeIsSend, + child: Row( + children: [ + Expanded( + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(16), + borderColor: + Theme.of( + context, + ).extension()!.background, + child: ExchangeCurrencySelectionView( + willChangeTicker: willChange, + pairedTicker: paired, + isFixedRate: isFixedRate, + willChangeIsSend: willChangeIsSend, + ), ), ), - ), - ], + ], + ), ), ), + ], + ), + ); + }, + ) + : await Navigator.of(context).push( + MaterialPageRoute( + builder: + (_) => ExchangeCurrencySelectionView( + willChangeTicker: willChange, + pairedTicker: paired, + isFixedRate: isFixedRate, + willChangeIsSend: willChangeIsSend, ), - ], - ), - ); - }, - ) - : await Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => ExchangeCurrencySelectionView( - willChangeTicker: willChange, - pairedTicker: paired, - isFixedRate: isFixedRate, - willChangeIsSend: willChangeIsSend, ), - ), - ); + ); if (mounted && result is Currency) { return result; @@ -398,20 +392,31 @@ class _ExchangeFormState extends ConsumerState { } void onExchangePressed() async { + final exchangeName = ref.read(efExchangeProvider).name; + final rateType = ref.read(efRateTypeProvider); final fromTicker = ref.read(efCurrencyPairProvider).send?.ticker ?? ""; + final fromNetwork = ref + .read(efCurrencyPairProvider) + .send + ?.networkFor(exchangeName); final toTicker = ref.read(efCurrencyPairProvider).receive?.ticker ?? ""; + final toNetwork = ref + .read(efCurrencyPairProvider) + .receive + ?.networkFor(exchangeName); final estimate = ref.read(efEstimateProvider)!; final sendAmount = ref.read(efSendAmountProvider)!; if (rateType == ExchangeRateType.fixed && toTicker.toUpperCase() == "WOW") { await showDialog( context: context, - builder: (context) => const StackOkDialog( - title: "WOW error", - message: - "Wownero is temporarily disabled as a receiving currency for fixed rate trades due to network issues", - ), + builder: + (context) => const StackOkDialog( + title: "WOW error", + message: + "Wownero is temporarily disabled as a receiving currency for fixed rate trades due to network issues", + ), ); return; @@ -421,9 +426,10 @@ class _ExchangeFormState extends ConsumerState { final amountToSend = estimate.reversed ? estimate.estimatedAmount : sendAmount; - final amountToReceive = estimate.reversed - ? ref.read(efReceiveAmountProvider)! - : estimate.estimatedAmount; + final amountToReceive = + estimate.reversed + ? ref.read(efReceiveAmountProvider)! + : estimate.estimatedAmount; switch (rateType) { case ExchangeRateType.estimated: @@ -466,32 +472,30 @@ class _ExchangeFormState extends ConsumerState { "Do you want to attempt trade anyways?", style: STextStyles.desktopTextSmall(context), ), - const Spacer( - flex: 2, - ), + const Spacer(flex: 2), Row( children: [ Expanded( child: SecondaryButton( label: "Cancel", buttonHeight: ButtonHeight.l, - onPressed: () => Navigator.of( - context, - rootNavigator: true, - ).pop(true), + onPressed: + () => Navigator.of( + context, + rootNavigator: true, + ).pop(true), ), ), - const SizedBox( - width: 16, - ), + const SizedBox(width: 16), Expanded( child: PrimaryButton( label: "Attempt", buttonHeight: ButtonHeight.l, - onPressed: () => Navigator.of( - context, - rootNavigator: true, - ).pop(false), + onPressed: + () => Navigator.of( + context, + rootNavigator: true, + ).pop(false), ), ), ], @@ -521,10 +525,7 @@ class _ExchangeFormState extends ConsumerState { style: Theme.of(context) .extension()! .getPrimaryEnabledButtonStyle(context), - child: Text( - "Attempt", - style: STextStyles.button(context), - ), + child: Text("Attempt", style: STextStyles.button(context)), onPressed: () { // continue and try to attempt trade Navigator.of(context).pop(false); @@ -540,15 +541,15 @@ class _ExchangeFormState extends ConsumerState { return; } rate = - "1 ${fromTicker.toUpperCase()} ~${(amountToReceive / amountToSend).toDecimal( - scaleOnInfinitePrecision: 12, - ).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; + "1 ${fromTicker.toUpperCase()} ~${(amountToReceive / amountToSend).toDecimal(scaleOnInfinitePrecision: 12).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; break; } final model = IncompleteExchangeModel( sendTicker: fromTicker.toUpperCase(), + sendNetwork: fromNetwork, receiveTicker: toTicker.toUpperCase(), + receiveNetwork: toNetwork, rateInfo: rate, sendAmount: amountToSend, receiveAmount: amountToReceive, @@ -560,8 +561,10 @@ class _ExchangeFormState extends ConsumerState { if (mounted) { if (walletInitiated) { - ref.read(exchangeSendFromWalletIdStateProvider.state).state = - Tuple2(walletId!, coin!); + ref.read(exchangeSendFromWalletIdStateProvider.state).state = Tuple2( + walletId!, + coin!, + ); if (isDesktop) { ref.read(ssss.state).state = model; await showDialog( @@ -571,18 +574,15 @@ class _ExchangeFormState extends ConsumerState { return const DesktopDialog( maxWidth: 720, maxHeight: double.infinity, - child: StepScaffold( - initialStep: 2, - ), + child: StepScaffold(initialStep: 2), ); }, ); } else { unawaited( - Navigator.of(context).pushNamed( - Step2View.routeName, - arguments: model, - ), + Navigator.of( + context, + ).pushNamed(Step2View.routeName, arguments: model), ); } } else { @@ -597,18 +597,15 @@ class _ExchangeFormState extends ConsumerState { return const DesktopDialog( maxWidth: 720, maxHeight: double.infinity, - child: StepScaffold( - initialStep: 1, - ), + child: StepScaffold(initialStep: 1), ); }, ); } else { unawaited( - Navigator.of(context).pushNamed( - Step1View.routeName, - arguments: model, - ), + Navigator.of( + context, + ).pushNamed(Step1View.routeName, arguments: model), ); } } @@ -620,9 +617,10 @@ class _ExchangeFormState extends ConsumerState { return false; } - final String? ticker = isSend - ? ref.read(efCurrencyPairProvider).send?.ticker - : ref.read(efCurrencyPairProvider).receive?.ticker; + final String? ticker = + isSend + ? ref.read(efCurrencyPairProvider).send?.ticker + : ref.read(efCurrencyPairProvider).receive?.ticker; if (ticker == null) { return false; @@ -640,9 +638,10 @@ class _ExchangeFormState extends ConsumerState { } final reversed = ref.read(efReversedProvider); - final amount = reversed - ? ref.read(efReceiveAmountProvider) - : ref.read(efSendAmountProvider); + final amount = + reversed + ? ref.read(efReceiveAmountProvider) + : ref.read(efSendAmountProvider); final pair = ref.read(efCurrencyPairProvider); if (amount == null || @@ -654,7 +653,7 @@ class _ExchangeFormState extends ConsumerState { } final rateType = ref.read(efRateTypeProvider); final Map>, Range?>> - results = {}; + results = {}; for (final exchange in usableExchanges) { final sendCurrency = pair.send?.forExchange(exchange.name); @@ -663,26 +662,29 @@ class _ExchangeFormState extends ConsumerState { if (sendCurrency != null && receiveCurrency != null) { final rangeResponse = await exchange.getRange( reversed ? receiveCurrency.ticker : sendCurrency.ticker, + reversed ? receiveCurrency.network : sendCurrency.network, reversed ? sendCurrency.ticker : receiveCurrency.ticker, + reversed ? sendCurrency.network : receiveCurrency.network, rateType == ExchangeRateType.fixed, ); + Logging.instance.d( + "${exchange.name}: fixedRate=$rateType, RANGE=$rangeResponse", + ); + final estimateResponse = await exchange.getEstimates( sendCurrency.ticker, + sendCurrency.network, receiveCurrency.ticker, + receiveCurrency.network, amount, rateType == ExchangeRateType.fixed, reversed, ); - results.addAll( - { - exchange.name: Tuple2( - estimateResponse, - rangeResponse.value, - ), - }, - ); + results.addAll({ + exchange.name: Tuple2(estimateResponse, rangeResponse.value), + }); } } @@ -767,18 +769,17 @@ class _ExchangeFormState extends ConsumerState { .setReceive(null, notifyListeners: true); ExchangeDataLoadingService.instance .getAggregateCurrency( - widget.contract == null ? coin!.ticker : widget.contract!.symbol, - ExchangeRateType.estimated, - widget.contract == null ? null : widget.contract!.address, - ) + widget.contract == null ? coin!.ticker : widget.contract!.symbol, + ExchangeRateType.estimated, + widget.contract?.address, + ) .then((value) { - if (value != null) { - ref.read(efCurrencyPairProvider).setSend( - value, - notifyListeners: true, - ); - } - }); + if (value != null) { + ref + .read(efCurrencyPairProvider) + .setSend(value, notifyListeners: true); + } + }); }); } else { WidgetsBinding.instance.addPostFrameCallback((timeStamp) { @@ -816,9 +817,7 @@ class _ExchangeFormState extends ConsumerState { if (_sendFocusNode.hasFocus) { _sendController.selection = TextSelection.fromPosition( - TextPosition( - offset: _sendController.text.length, - ), + TextPosition(offset: _sendController.text.length), ); } } @@ -835,9 +834,7 @@ class _ExchangeFormState extends ConsumerState { if (_receiveFocusNode.hasFocus) { _receiveController.selection = TextSelection.fromPosition( - TextPosition( - offset: _receiveController.text.length, - ), + TextPosition(offset: _receiveController.text.length), ); } } @@ -868,13 +865,13 @@ class _ExchangeFormState extends ConsumerState { color: Theme.of(context).extension()!.textDark3, ), ), - SizedBox( - height: isDesktop ? 10 : 4, - ), + SizedBox(height: isDesktop ? 10 : 4), ExchangeTextField( - key: Key("exchangeTextFieldKeyFor_" - "${Theme.of(context).extension()!.themeId}" - "${ref.watch(efCurrencyPairProvider.select((value) => value.send?.ticker))}"), + key: Key( + "exchangeTextFieldKeyFor_" + "${Theme.of(context).extension()!.themeId}" + "${ref.watch(efCurrencyPairProvider.select((value) => value.send?.ticker))}", + ), controller: _sendController, focusNode: _sendFocusNode, textStyle: STextStyles.smallMed14(context).copyWith( @@ -893,15 +890,12 @@ class _ExchangeFormState extends ConsumerState { onChanged: sendFieldOnChanged, onButtonTap: selectSendCurrency, isWalletCoin: isWalletCoin(coin, true), - currency: - ref.watch(efCurrencyPairProvider.select((value) => value.send)), - ), - SizedBox( - height: isDesktop ? 10 : 4, - ), - SizedBox( - height: isDesktop ? 10 : 4, + currency: ref.watch( + efCurrencyPairProvider.select((value) => value.send), + ), ), + SizedBox(height: isDesktop ? 10 : 4), + SizedBox(height: isDesktop ? 10 : 4), Row( crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -914,20 +908,23 @@ class _ExchangeFormState extends ConsumerState { ), ConditionalParent( condition: isDesktop, - builder: (child) => MouseRegion( - cursor: SystemMouseCursors.click, - child: child, - ), + builder: + (child) => MouseRegion( + cursor: SystemMouseCursors.click, + child: child, + ), child: Semantics( label: "Swap Button. Reverse The Exchange Currencies.", excludeSemantics: true, child: RoundedContainer( - padding: isDesktop - ? const EdgeInsets.all(6) - : const EdgeInsets.all(2), - color: Theme.of(context) - .extension()! - .buttonBackSecondary, + padding: + isDesktop + ? const EdgeInsets.all(6) + : const EdgeInsets.all(2), + color: + Theme.of( + context, + ).extension()!.buttonBackSecondary, radiusMultiplier: 0.75, child: GestureDetector( onTap: () async { @@ -939,9 +936,10 @@ class _ExchangeFormState extends ConsumerState { Assets.svg.swap, width: 20, height: 20, - color: Theme.of(context) - .extension()! - .accentColorDark, + color: + Theme.of( + context, + ).extension()!.accentColorDark, ), ), ), @@ -950,9 +948,7 @@ class _ExchangeFormState extends ConsumerState { ), ], ), - SizedBox( - height: isDesktop ? 10 : 7, - ), + SizedBox(height: isDesktop ? 10 : 7), ExchangeTextField( key: Key( "exchangeTextFieldKeyFor1_${Theme.of(context).extension()!.themeId}", @@ -967,52 +963,47 @@ class _ExchangeFormState extends ConsumerState { borderRadius: Constants.size.circularBorderRadius, background: Theme.of(context).extension()!.textFieldDefaultBG, - onTap: rateType == ExchangeRateType.estimated && - ref.watch(efExchangeProvider).name == - ChangeNowExchange.exchangeName - ? null - : () { - if (_sendController.text == "-") { - _sendController.text = ""; - } - }, + onTap: + rateType == ExchangeRateType.estimated && + ref.watch(efExchangeProvider).name == + ChangeNowExchange.exchangeName + ? null + : () { + if (_sendController.text == "-") { + _sendController.text = ""; + } + }, onChanged: receiveFieldOnChanged, onButtonTap: selectReceiveCurrency, isWalletCoin: isWalletCoin(coin, true), - currency: ref - .watch(efCurrencyPairProvider.select((value) => value.receive)), - readOnly: rateType == ExchangeRateType.estimated && + currency: ref.watch( + efCurrencyPairProvider.select((value) => value.receive), + ), + readOnly: + rateType == ExchangeRateType.estimated && ref.watch(efExchangeProvider).name == ChangeNowExchange.exchangeName, ), - SizedBox( - height: isDesktop ? 20 : 12, - ), + SizedBox(height: isDesktop ? 20 : 12), SizedBox( height: isDesktop ? 60 : 40, - child: RateTypeToggle( - key: UniqueKey(), - onChanged: onRateTypeChanged, - ), + child: RateTypeToggle(key: UniqueKey(), onChanged: onRateTypeChanged), ), AnimatedSize( duration: const Duration(milliseconds: 300), - child: ref.watch(efSendAmountProvider) == null && - ref.watch(efReceiveAmountProvider) == null - ? const SizedBox( - height: 0, - ) - : Padding( - padding: EdgeInsets.only(top: isDesktop ? 20 : 12), - child: ExchangeProviderOptions( - fixedRate: rateType == ExchangeRateType.fixed, - reversed: ref.watch(efReversedProvider), + child: + ref.watch(efSendAmountProvider) == null && + ref.watch(efReceiveAmountProvider) == null + ? const SizedBox(height: 0) + : Padding( + padding: EdgeInsets.only(top: isDesktop ? 20 : 12), + child: ExchangeProviderOptions( + fixedRate: rateType == ExchangeRateType.fixed, + reversed: ref.watch(efReversedProvider), + ), ), - ), - ), - SizedBox( - height: isDesktop ? 20 : 12, ), + SizedBox(height: isDesktop ? 20 : 12), PrimaryButton( buttonHeight: isDesktop ? ButtonHeight.l : null, enabled: ref.watch(efCanExchangeProvider), diff --git a/lib/pages/exchange_view/exchange_step_views/step_3_view.dart b/lib/pages/exchange_view/exchange_step_views/step_3_view.dart index 2cd014fc3..92c713acb 100644 --- a/lib/pages/exchange_view/exchange_step_views/step_3_view.dart +++ b/lib/pages/exchange_view/exchange_step_views/step_3_view.dart @@ -79,10 +79,7 @@ class _Step3ViewState extends ConsumerState { } }, ), - title: Text( - "Swap", - style: STextStyles.navBarTitle(context), - ), + title: Text("Swap", style: STextStyles.navBarTitle(context)), ), body: LayoutBuilder( builder: (context, constraints) { @@ -100,21 +97,13 @@ class _Step3ViewState extends ConsumerState { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - StepRow( - count: 4, - current: 2, - width: width, - ), - const SizedBox( - height: 14, - ), + StepRow(count: 4, current: 2, width: width), + const SizedBox(height: 14), Text( "Confirm exchange details", style: STextStyles.pageTitleH1(context), ), - const SizedBox( - height: 24, - ), + const SizedBox(height: 24), RoundedWhiteContainer( child: Row( children: [ @@ -130,9 +119,7 @@ class _Step3ViewState extends ConsumerState { ], ), ), - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), RoundedWhiteContainer( child: Row( children: [ @@ -148,9 +135,7 @@ class _Step3ViewState extends ConsumerState { ], ), ), - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), RoundedWhiteContainer( child: Row( children: [ @@ -166,9 +151,7 @@ class _Step3ViewState extends ConsumerState { ], ), ), - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), RoundedWhiteContainer( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -177,9 +160,7 @@ class _Step3ViewState extends ConsumerState { "Recipient ${model.receiveTicker.toUpperCase()} address", style: STextStyles.itemSubtitle(context), ), - const SizedBox( - height: 4, - ), + const SizedBox(height: 4), Text( model.recipientAddress!, style: STextStyles.itemSubtitle12(context), @@ -187,10 +168,7 @@ class _Step3ViewState extends ConsumerState { ], ), ), - if (supportsRefund) - const SizedBox( - height: 8, - ), + if (supportsRefund) const SizedBox(height: 8), if (supportsRefund) RoundedWhiteContainer( child: Column( @@ -200,9 +178,7 @@ class _Step3ViewState extends ConsumerState { "Refund ${model.sendTicker.toUpperCase()} address", style: STextStyles.itemSubtitle(context), ), - const SizedBox( - height: 4, - ), + const SizedBox(height: 4), Text( model.refundAddress!, style: STextStyles.itemSubtitle12(context), @@ -210,9 +186,7 @@ class _Step3ViewState extends ConsumerState { ], ), ), - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), const Spacer(), Row( children: [ @@ -227,16 +201,15 @@ class _Step3ViewState extends ConsumerState { child: Text( "Back", style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .buttonTextSecondary, + color: + Theme.of(context) + .extension()! + .buttonTextSecondary, ), ), ), ), - const SizedBox( - width: 16, - ), + const SizedBox(width: 16), Expanded( child: TextButton( onPressed: () async { @@ -244,19 +217,22 @@ class _Step3ViewState extends ConsumerState { showDialog( context: context, barrierDismissible: false, - builder: (_) => WillPopScope( - onWillPop: () async => false, - child: Container( - color: Theme.of(context) - .extension()! - .overlay - .withOpacity(0.6), - child: const CustomLoadingOverlay( - message: "Creating a trade", - eventBus: null, + builder: + (_) => WillPopScope( + onWillPop: () async => false, + child: Container( + color: Theme.of(context) + .extension()! + .overlay + .withOpacity(0.6), + child: + const CustomLoadingOverlay( + message: + "Creating a trade", + eventBus: null, + ), + ), ), - ), - ), ), ); @@ -265,18 +241,23 @@ class _Step3ViewState extends ConsumerState { .read(efExchangeProvider) .createTrade( from: model.sendTicker, + fromNetwork: model.sendNetwork, to: model.receiveTicker, - fixedRate: model.rateType != + toNetwork: model.receiveNetwork, + fixedRate: + model.rateType != ExchangeRateType.estimated, - amount: model.reversed - ? model.receiveAmount - : model.sendAmount, + amount: + model.reversed + ? model.receiveAmount + : model.sendAmount, addressTo: model.recipientAddress!, extraId: null, - addressRefund: supportsRefund - ? model.refundAddress! - : "", + addressRefund: + supportsRefund + ? model.refundAddress! + : "", refundExtraId: "", estimate: model.estimate, reversed: model.reversed, @@ -304,10 +285,12 @@ class _Step3ViewState extends ConsumerState { showDialog( context: context, barrierDismissible: true, - builder: (_) => StackDialog( - title: "Failed to create trade", - message: message ?? "", - ), + builder: + (_) => StackDialog( + title: + "Failed to create trade", + message: message ?? "", + ), ), ); } @@ -315,7 +298,9 @@ class _Step3ViewState extends ConsumerState { } // save trade to hive - await ref.read(tradesServiceProvider).add( + await ref + .read(tradesServiceProvider) + .add( trade: response.value!, shouldNotifyListeners: true, ); diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart index 1ffa12275..6bc4b5c04 100644 --- a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart @@ -70,10 +70,12 @@ class _ExchangeProviderOptionsState @override Widget build(BuildContext context) { - final sendCurrency = - ref.watch(efCurrencyPairProvider.select((value) => value.send)); - final receivingCurrency = - ref.watch(efCurrencyPairProvider.select((value) => value.receive)); + final sendCurrency = ref.watch( + efCurrencyPairProvider.select((value) => value.send), + ); + final receivingCurrency = ref.watch( + efCurrencyPairProvider.select((value) => value.receive), + ); final showChangeNow = exchangeSupported( exchangeName: ChangeNowExchange.exchangeName, @@ -98,9 +100,10 @@ class _ExchangeProviderOptionsState return RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(0) : const EdgeInsets.all(12), - borderColor: isDesktop - ? Theme.of(context).extension()!.background - : null, + borderColor: + isDesktop + ? Theme.of(context).extension()!.background + : null, child: Column( children: [ if (showChangeNow) @@ -112,13 +115,10 @@ class _ExchangeProviderOptionsState if (showChangeNow && showMajesticBank) isDesktop ? Container( - height: 1, - color: - Theme.of(context).extension()!.background, - ) - : const SizedBox( - height: 16, - ), + height: 1, + color: Theme.of(context).extension()!.background, + ) + : const SizedBox(height: 16), if (showMajesticBank) ExchangeOption( exchange: MajesticBankExchange.instance, @@ -128,13 +128,10 @@ class _ExchangeProviderOptionsState if ((showChangeNow || showMajesticBank) && showTrocador) isDesktop ? Container( - height: 1, - color: - Theme.of(context).extension()!.background, - ) - : const SizedBox( - height: 16, - ), + height: 1, + color: Theme.of(context).extension()!.background, + ) + : const SizedBox(height: 16), if (showTrocador) ExchangeOption( fixedRate: widget.fixedRate, @@ -145,13 +142,10 @@ class _ExchangeProviderOptionsState showNanswap) isDesktop ? Container( - height: 1, - color: - Theme.of(context).extension()!.background, - ) - : const SizedBox( - height: 16, - ), + height: 1, + color: Theme.of(context).extension()!.background, + ) + : const SizedBox(height: 16), if (showNanswap) ExchangeOption( fixedRate: widget.fixedRate, diff --git a/lib/pages/exchange_view/trade_details_view.dart b/lib/pages/exchange_view/trade_details_view.dart index 0416056a7..8bb6017c2 100644 --- a/lib/pages/exchange_view/trade_details_view.dart +++ b/lib/pages/exchange_view/trade_details_view.dart @@ -20,7 +20,7 @@ import 'package:tuple/tuple.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../app_config.dart'; -import '../../models/exchange/change_now/exchange_transaction_status.dart'; +import '../../models/exchange/change_now/cn_exchange_transaction_status.dart'; import '../../models/isar/models/blockchain_data/transaction.dart'; import '../../models/isar/stack_theme.dart'; import '../../notifications/show_flush_bar.dart'; @@ -164,7 +164,8 @@ class _TradeDetailsViewState extends ConsumerState { ), ); - final bool hasTx = sentFromStack || + final bool hasTx = + sentFromStack || !(trade.status == "New" || trade.status == "new" || trade.status == "Waiting" || @@ -176,7 +177,8 @@ class _TradeDetailsViewState extends ConsumerState { trade.status == "Expired" || trade.status == "expired" || trade.status == "Failed" || - trade.status == "failed"); + trade.status == "failed" || + trade.status.toLowerCase().startsWith("waiting")); //todo: check if print needed // debugPrint("walletId: $walletId"); @@ -190,9 +192,14 @@ class _TradeDetailsViewState extends ConsumerState { final isDesktop = Util.isDesktop; - final showSendFromStackButton = !hasTx && - !["xmr", "monero", "wow", "wownero"] - .contains(trade.payInCurrency.toLowerCase()) && + final showSendFromStackButton = + !hasTx && + ![ + "xmr", + "monero", + "wow", + "wownero", + ].contains(trade.payInCurrency.toLowerCase()) && AppConfig.isStackCoin(trade.payInCurrency) && (trade.status == "New" || trade.status == "new" || @@ -201,132 +208,128 @@ class _TradeDetailsViewState extends ConsumerState { return ConditionalParent( condition: !isDesktop, - builder: (child) => Background( - child: Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - backgroundColor: - Theme.of(context).extension()!.background, - leading: AppBarBackButton( - onPressed: () async { - Navigator.of(context).pop(); - }, - ), - title: Text( - "Trade details", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(12), - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(4), - child: child, + builder: + (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Trade details", + style: STextStyles.navBarTitle(context), + ), + ), + body: Padding( + padding: const EdgeInsets.all(12), + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, + ), + ), ), ), ), - ), - ), child: Padding( - padding: isDesktop - ? const EdgeInsets.only(left: 32) - : const EdgeInsets.all(0), + padding: + isDesktop + ? const EdgeInsets.only(left: 32) + : const EdgeInsets.all(0), child: BranchedParent( condition: isDesktop, - conditionBranchBuilder: (children) => Padding( - padding: const EdgeInsets.only( - right: 20, - ), - child: Padding( - padding: const EdgeInsets.only( - right: 12, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - RoundedWhiteContainer( - borderColor: Theme.of(context) - .extension()! - .backgroundAppBar, - padding: const EdgeInsets.all(0), - child: ListView( - primary: false, - shrinkWrap: true, - children: children, - ), - ), - if (showSendFromStackButton) - const SizedBox( - height: 32, - ), - if (showSendFromStackButton) - SecondaryButton( - label: "Send from ${AppConfig.prefix}", - buttonHeight: ButtonHeight.l, - onPressed: () { - CryptoCurrency coin; - try { - coin = AppConfig.getCryptoCurrencyForTicker( - trade.payInCurrency, - )!; - } catch (_) { - coin = AppConfig.getCryptoCurrencyByPrettyName( - trade.payInCurrency, - ); - } - final amount = Amount.fromDecimal( - sendAmount, - fractionDigits: coin.fractionDigits, - ); - final address = trade.payInAddress; + conditionBranchBuilder: + (children) => Padding( + padding: const EdgeInsets.only(right: 20), + child: Padding( + padding: const EdgeInsets.only(right: 12), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + RoundedWhiteContainer( + borderColor: + Theme.of( + context, + ).extension()!.backgroundAppBar, + padding: const EdgeInsets.all(0), + child: ListView( + primary: false, + shrinkWrap: true, + children: children, + ), + ), + if (showSendFromStackButton) const SizedBox(height: 32), + if (showSendFromStackButton) + SecondaryButton( + label: "Send from ${AppConfig.prefix}", + buttonHeight: ButtonHeight.l, + onPressed: () { + CryptoCurrency coin; + try { + coin = + AppConfig.getCryptoCurrencyForTicker( + trade.payInCurrency, + )!; + } catch (_) { + coin = AppConfig.getCryptoCurrencyByPrettyName( + trade.payInCurrency, + ); + } + final amount = Amount.fromDecimal( + sendAmount, + fractionDigits: coin.fractionDigits, + ); + final address = trade.payInAddress; - Navigator.of(context).pushNamed( - SendFromView.routeName, - arguments: Tuple4( - coin, - amount, - address, - trade, - ), - ); - }, - ), - const SizedBox( - height: 32, + Navigator.of(context).pushNamed( + SendFromView.routeName, + arguments: Tuple4(coin, amount, address, trade), + ); + }, + ), + const SizedBox(height: 32), + ], ), - ], + ), + ), + otherBranchBuilder: + (children) => Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: isDesktop ? MainAxisSize.min : MainAxisSize.max, + children: children, ), - ), - ), - otherBranchBuilder: (children) => Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: isDesktop ? MainAxisSize.min : MainAxisSize.max, - children: children, - ), children: [ RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(0) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(0) + : const EdgeInsets.all(12), child: Container( - decoration: isDesktop - ? BoxDecoration( - color: Theme.of(context) - .extension()! - .backgroundAppBar, - borderRadius: BorderRadius.vertical( - top: Radius.circular( - Constants.size.circularBorderRadius, + decoration: + isDesktop + ? BoxDecoration( + color: + Theme.of( + context, + ).extension()!.backgroundAppBar, + borderRadius: BorderRadius.vertical( + top: Radius.circular( + Constants.size.circularBorderRadius, + ), ), - ), - ) - : null, + ) + : null, child: Padding( - padding: isDesktop - ? const EdgeInsets.all(12) - : const EdgeInsets.all(0), + padding: + isDesktop + ? const EdgeInsets.all(12) + : const EdgeInsets.all(0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -343,9 +346,7 @@ class _TradeDetailsViewState extends ConsumerState { width: 32, height: 32, ), - const SizedBox( - width: 16, - ), + const SizedBox(width: 16), SelectableText( "Swap service", style: STextStyles.desktopTextMedium(context), @@ -353,25 +354,24 @@ class _TradeDetailsViewState extends ConsumerState { ], ), Column( - crossAxisAlignment: isDesktop - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, + crossAxisAlignment: + isDesktop + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, children: [ SelectableText( "${trade.payInCurrency.toUpperCase()} → ${trade.payOutCurrency.toUpperCase()}", style: STextStyles.titleBold12(context), ), - const SizedBox( - height: 4, - ), + const SizedBox(height: 4), Builder( builder: (context) { String text; try { final coin = AppConfig.getCryptoCurrencyForTicker( - trade.payInCurrency, - )!; + trade.payInCurrency, + )!; final amount = sendAmount.toAmount( fractionDigits: coin.fractionDigits, ); @@ -419,15 +419,12 @@ class _TradeDetailsViewState extends ConsumerState { ), ), ), - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), + isDesktop ? const _Divider() : const SizedBox(height: 12), RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -435,10 +432,7 @@ class _TradeDetailsViewState extends ConsumerState { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - "Status", - style: STextStyles.itemSubtitle(context), - ), + Text("Status", style: STextStyles.itemSubtitle(context)), if (trade.exchangeName == MajesticBankExchange.exchangeName && trade.status == "Completed") @@ -449,137 +443,136 @@ class _TradeDetailsViewState extends ConsumerState { onTap: () { showDialog( context: context, - builder: (context) => const StackOkDialog( - title: "Trade Info", - message: - "Majestic Bank does not store order data indefinitely", - ), + builder: + (context) => const StackOkDialog( + title: "Trade Info", + message: + "Majestic Bank does not store order data indefinitely", + ), ); }, child: SvgPicture.asset( Assets.svg.circleInfo, height: 20, width: 20, - color: Theme.of(context) - .extension()! - .infoItemIcons, + color: + Theme.of( + context, + ).extension()!.infoItemIcons, ), ), ], ), ], ), - const SizedBox( - height: 4, - ), + const SizedBox(height: 4), SelectableText( trade.status, style: STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context) - .extension()! - .colorForStatus(trade.status), + color: Theme.of( + context, + ).extension()!.colorForStatus(trade.status), ), ), ], ), ), if (!sentFromStack && !hasTx) - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), + isDesktop ? const _Divider() : const SizedBox(height: 12), if (!sentFromStack && !hasTx) RoundedContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - color: isDesktop - ? Theme.of(context).extension()!.popupBG - : Theme.of(context) - .extension()! - .warningBackground, + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: + isDesktop + ? Theme.of(context).extension()!.popupBG + : Theme.of( + context, + ).extension()!.warningBackground, child: ConditionalParent( condition: isDesktop, - builder: (child) => Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, + builder: + (child) => Column( + mainAxisSize: MainAxisSize.min, children: [ - Column( + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "Amount", - style: STextStyles.desktopTextExtraExtraSmall( - context, - ), - ), - const SizedBox( - height: 2, - ), - Text( - "${trade.payInAmount} ${trade.payInCurrency.toUpperCase()}", - style: STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Amount", + style: + STextStyles.desktopTextExtraExtraSmall( + context, + ), + ), + const SizedBox(height: 2), + Text( + "${trade.payInAmount} ${trade.payInCurrency.toUpperCase()}", + style: + STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension()! + .textDark, + ), + ), + ], ), + IconCopyButton(data: trade.payInAmount), ], ), - IconCopyButton( - data: trade.payInAmount, - ), + const SizedBox(height: 6), + child, ], ), - const SizedBox( - height: 6, - ), - child, - ], - ), child: RichText( text: TextSpan( text: - "You must send at least ${sendAmount.toStringAsFixed( - trade.payInCurrency.toLowerCase() == "xmr" ? 12 : 8, - )} ${trade.payInCurrency.toUpperCase()}. ", - style: isDesktop - ? STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .accentColorRed, - ) - : STextStyles.label(context).copyWith( - color: Theme.of(context) - .extension()! - .warningForeground, - ), + "You must send at least ${sendAmount.toStringAsFixed(trade.payInCurrency.toLowerCase() == "xmr" ? 12 : 8)} ${trade.payInCurrency.toUpperCase()}. ", + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension()!.accentColorRed, + ) + : STextStyles.label(context).copyWith( + color: + Theme.of(context) + .extension()! + .warningForeground, + ), children: [ TextSpan( text: - "If you send less than ${sendAmount.toStringAsFixed( - trade.payInCurrency.toLowerCase() == "xmr" ? 12 : 8, - )} ${trade.payInCurrency.toUpperCase()}, your transaction may not be converted and it may not be refunded.", - style: isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .accentColorRed, - ) - : STextStyles.label(context).copyWith( - color: Theme.of(context) - .extension()! - .warningForeground, - ), + "If you send less than ${sendAmount.toStringAsFixed(trade.payInCurrency.toLowerCase() == "xmr" ? 12 : 8)} ${trade.payInCurrency.toUpperCase()}, your transaction may not be converted and it may not be refunded.", + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension()! + .accentColorRed, + ) + : STextStyles.label(context).copyWith( + color: + Theme.of(context) + .extension()! + .warningForeground, + ), ), ], ), @@ -587,39 +580,30 @@ class _TradeDetailsViewState extends ConsumerState { ), ), if (sentFromStack) - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), + isDesktop ? const _Divider() : const SizedBox(height: 12), if (sentFromStack) RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "Sent from", - style: STextStyles.itemSubtitle(context), - ), - const SizedBox( - height: 4, - ), + Text("Sent from", style: STextStyles.itemSubtitle(context)), + const SizedBox(height: 4), SelectableText( widget.walletName!, style: STextStyles.itemSubtitle12(context), ), - const SizedBox( - height: 10, - ), + const SizedBox(height: 10), CustomTextButton( text: "View transaction", onTap: () { - final coin = AppConfig.getCryptoCurrencyForTicker( - trade.payInCurrency, - )!; + final coin = + AppConfig.getCryptoCurrencyForTicker( + trade.payInCurrency, + )!; if (isDesktop) { Navigator.of(context).push( @@ -655,16 +639,13 @@ class _TradeDetailsViewState extends ConsumerState { ), ), if (sentFromStack) - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), + isDesktop ? const _Divider() : const SizedBox(height: 12), if (sentFromStack) RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, @@ -677,9 +658,7 @@ class _TradeDetailsViewState extends ConsumerState { "${trade.exchangeName} address", style: STextStyles.itemSubtitle(context), ), - const SizedBox( - height: 4, - ), + const SizedBox(height: 4), Row( children: [ Flexible( @@ -693,24 +672,18 @@ class _TradeDetailsViewState extends ConsumerState { ], ), ), - if (isDesktop) - IconCopyButton( - data: trade.payInAddress, - ), + if (isDesktop) IconCopyButton(data: trade.payInAddress), ], ), ), if (!sentFromStack && !hasTx) - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), + isDesktop ? const _Divider() : const SizedBox(height: 12), if (!sentFromStack && !hasTx) RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -722,52 +695,45 @@ class _TradeDetailsViewState extends ConsumerState { style: STextStyles.itemSubtitle(context), ), isDesktop - ? IconCopyButton( - data: trade.payInAddress, - ) + ? IconCopyButton(data: trade.payInAddress) : GestureDetector( - onTap: () async { - final address = trade.payInAddress; - await Clipboard.setData( - ClipboardData( - text: address, + onTap: () async { + final address = trade.payInAddress; + await Clipboard.setData( + ClipboardData(text: address), + ); + if (context.mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + context: context, ), ); - if (context.mounted) { - unawaited( - showFloatingFlushBar( - type: FlushBarType.info, - message: "Copied to clipboard", - context: context, - ), - ); - } - }, - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.copy, - width: 12, - height: 12, - color: Theme.of(context) - .extension()! - .infoItemIcons, - ), - const SizedBox( - width: 4, - ), - Text( - "Copy", - style: STextStyles.link2(context), - ), - ], - ), + } + }, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.copy, + width: 12, + height: 12, + color: + Theme.of(context) + .extension()! + .infoItemIcons, + ), + const SizedBox(width: 4), + Text( + "Copy", + style: STextStyles.link2(context), + ), + ], ), + ), ], ), - const SizedBox( - height: 4, - ), + const SizedBox(height: 4), Row( children: [ Expanded( @@ -778,9 +744,7 @@ class _TradeDetailsViewState extends ConsumerState { ), ], ), - const SizedBox( - height: 10, - ), + const SizedBox(height: 10), GestureDetector( onTap: () { showDialog( @@ -799,9 +763,7 @@ class _TradeDetailsViewState extends ConsumerState { style: STextStyles.pageTitleH2(context), ), ), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), Center( child: RepaintBoundary( // key: _qrKey, @@ -815,9 +777,7 @@ class _TradeDetailsViewState extends ConsumerState { ), ), ), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), Center( child: SizedBox( width: width, @@ -833,11 +793,13 @@ class _TradeDetailsViewState extends ConsumerState { ), child: Text( "Cancel", - style: STextStyles.button(context) - .copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark, + style: STextStyles.button( + context, + ).copyWith( + color: + Theme.of(context) + .extension()! + .accentColorDark, ), ), ), @@ -855,13 +817,12 @@ class _TradeDetailsViewState extends ConsumerState { Assets.svg.qrcode, width: 12, height: 12, - color: Theme.of(context) - .extension()! - .infoItemIcons, - ), - const SizedBox( - width: 4, + color: + Theme.of( + context, + ).extension()!.infoItemIcons, ), + const SizedBox(width: 4), Text( "Show QR code", style: STextStyles.link2(context), @@ -872,73 +833,60 @@ class _TradeDetailsViewState extends ConsumerState { ], ), ), - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), + isDesktop ? const _Divider() : const SizedBox(height: 12), if (trade.payInExtraId.isNotEmpty && !sentFromStack && !hasTx) RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - "Memo", - style: STextStyles.itemSubtitle(context), - ), + Text("Memo", style: STextStyles.itemSubtitle(context)), isDesktop - ? IconCopyButton( - data: trade.payInExtraId, - ) + ? IconCopyButton(data: trade.payInExtraId) : GestureDetector( - onTap: () async { - final address = trade.payInExtraId; - await Clipboard.setData( - ClipboardData( - text: address, + onTap: () async { + final address = trade.payInExtraId; + await Clipboard.setData( + ClipboardData(text: address), + ); + if (context.mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + context: context, ), ); - if (context.mounted) { - unawaited( - showFloatingFlushBar( - type: FlushBarType.info, - message: "Copied to clipboard", - context: context, - ), - ); - } - }, - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.copy, - width: 12, - height: 12, - color: Theme.of(context) - .extension()! - .infoItemIcons, - ), - const SizedBox( - width: 4, - ), - Text( - "Copy", - style: STextStyles.link2(context), - ), - ], - ), + } + }, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.copy, + width: 12, + height: 12, + color: + Theme.of(context) + .extension()! + .infoItemIcons, + ), + const SizedBox(width: 4), + Text( + "Copy", + style: STextStyles.link2(context), + ), + ], ), + ), ], ), - const SizedBox( - height: 4, - ), + const SizedBox(height: 4), SelectableText( trade.payInExtraId, style: STextStyles.itemSubtitle12(context), @@ -947,15 +895,12 @@ class _TradeDetailsViewState extends ConsumerState { ), ), if (trade.payInExtraId.isNotEmpty && !sentFromStack && !hasTx) - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), + isDesktop ? const _Divider() : const SizedBox(height: 12), RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -968,6 +913,86 @@ class _TradeDetailsViewState extends ConsumerState { ), isDesktop ? IconPencilButton( + onPressed: () { + showDialog( + context: context, + builder: (context) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 360, + child: EditTradeNoteView( + tradeId: tradeId, + note: ref + .read(tradeNoteServiceProvider) + .getNote(tradeId: tradeId), + ), + ); + }, + ); + }, + ) + : GestureDetector( + onTap: () { + Navigator.of(context).pushNamed( + EditTradeNoteView.routeName, + arguments: Tuple2( + tradeId, + ref + .read(tradeNoteServiceProvider) + .getNote(tradeId: tradeId), + ), + ); + }, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.pencil, + width: 10, + height: 10, + color: + Theme.of( + context, + ).extension()!.infoItemIcons, + ), + const SizedBox(width: 4), + Text("Edit", style: STextStyles.link2(context)), + ], + ), + ), + ], + ), + const SizedBox(height: 4), + SelectableText( + ref.watch( + tradeNoteServiceProvider.select( + (value) => value.getNote(tradeId: tradeId), + ), + ), + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + if (sentFromStack) + isDesktop ? const _Divider() : const SizedBox(height: 12), + if (sentFromStack) + RoundedWhiteContainer( + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Transaction note", + style: STextStyles.itemSubtitle(context), + ), + isDesktop + ? IconPencilButton( onPressed: () { showDialog( context: context, @@ -975,26 +1000,22 @@ class _TradeDetailsViewState extends ConsumerState { return DesktopDialog( maxWidth: 580, maxHeight: 360, - child: EditTradeNoteView( - tradeId: tradeId, - note: ref - .read(tradeNoteServiceProvider) - .getNote(tradeId: tradeId), + child: EditNoteView( + txid: transactionIfSentFromStack!.txid, + walletId: walletId!, ), ); }, ); }, ) - : GestureDetector( + : GestureDetector( onTap: () { Navigator.of(context).pushNamed( - EditTradeNoteView.routeName, + EditNoteView.routeName, arguments: Tuple2( - tradeId, - ref - .read(tradeNoteServiceProvider) - .getNote(tradeId: tradeId), + transactionIfSentFromStack!.txid, + walletId, ), ); }, @@ -1004,13 +1025,12 @@ class _TradeDetailsViewState extends ConsumerState { Assets.svg.pencil, width: 10, height: 10, - color: Theme.of(context) - .extension()! - .infoItemIcons, - ), - const SizedBox( - width: 4, + color: + Theme.of(context) + .extension()! + .infoItemIcons, ), + const SizedBox(width: 4), Text( "Edit", style: STextStyles.link2(context), @@ -1018,105 +1038,16 @@ class _TradeDetailsViewState extends ConsumerState { ], ), ), - ], - ), - const SizedBox( - height: 4, - ), - SelectableText( - ref.watch( - tradeNoteServiceProvider - .select((value) => value.getNote(tradeId: tradeId)), - ), - style: STextStyles.itemSubtitle12(context), - ), - ], - ), - ), - if (sentFromStack) - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), - if (sentFromStack) - RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Transaction note", - style: STextStyles.itemSubtitle(context), - ), - isDesktop - ? IconPencilButton( - onPressed: () { - showDialog( - context: context, - builder: (context) { - return DesktopDialog( - maxWidth: 580, - maxHeight: 360, - child: EditNoteView( - txid: - transactionIfSentFromStack!.txid, - walletId: walletId!, - ), - ); - }, - ); - }, - ) - : GestureDetector( - onTap: () { - Navigator.of(context).pushNamed( - EditNoteView.routeName, - arguments: Tuple2( - transactionIfSentFromStack!.txid, - walletId, - ), - ); - }, - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.pencil, - width: 10, - height: 10, - color: Theme.of(context) - .extension()! - .infoItemIcons, - ), - const SizedBox( - width: 4, - ), - Text( - "Edit", - style: STextStyles.link2(context), - ), - ], - ), - ), ], ), - const SizedBox( - height: 4, - ), + const SizedBox(height: 4), SelectableText( ref .watch( - pTransactionNote( - ( - txid: transactionIfSentFromStack!.txid, - walletId: walletId!, - ), - ), + pTransactionNote(( + txid: transactionIfSentFromStack!.txid, + walletId: walletId!, + )), ) ?.value ?? "", @@ -1125,15 +1056,12 @@ class _TradeDetailsViewState extends ConsumerState { ], ), ), - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), + isDesktop ? const _Divider() : const SizedBox(height: 12), RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, @@ -1142,24 +1070,20 @@ class _TradeDetailsViewState extends ConsumerState { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "Date", - style: STextStyles.itemSubtitle(context), - ), - if (isDesktop) - const SizedBox( - height: 2, - ), + Text("Date", style: STextStyles.itemSubtitle(context)), + if (isDesktop) const SizedBox(height: 2), if (isDesktop) SelectableText( Format.extractDateFrom( trade.timestamp.millisecondsSinceEpoch ~/ 1000, ), - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension()!.textDark, ), ), ], @@ -1180,15 +1104,12 @@ class _TradeDetailsViewState extends ConsumerState { ], ), ), - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), + isDesktop ? const _Divider() : const SizedBox(height: 12), RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, @@ -1200,10 +1121,7 @@ class _TradeDetailsViewState extends ConsumerState { "Swap service", style: STextStyles.itemSubtitle(context), ), - if (isDesktop) - const SizedBox( - height: 2, - ), + if (isDesktop) const SizedBox(height: 2), if (isDesktop) SelectableText( trade.exchangeName, @@ -1211,10 +1129,7 @@ class _TradeDetailsViewState extends ConsumerState { ), ], ), - if (isDesktop) - IconCopyButton( - data: trade.exchangeName, - ), + if (isDesktop) IconCopyButton(data: trade.exchangeName), if (!isDesktop) SelectableText( trade.exchangeName, @@ -1223,15 +1138,12 @@ class _TradeDetailsViewState extends ConsumerState { ], ), ), - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), + isDesktop ? const _Divider() : const SizedBox(height: 12), RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, @@ -1243,10 +1155,7 @@ class _TradeDetailsViewState extends ConsumerState { "Trade ID", style: STextStyles.itemSubtitle(context), ), - if (isDesktop) - const SizedBox( - height: 2, - ), + if (isDesktop) const SizedBox(height: 2), if (isDesktop) Text( trade.tradeId, @@ -1254,10 +1163,7 @@ class _TradeDetailsViewState extends ConsumerState { ), ], ), - if (isDesktop) - IconCopyButton( - data: trade.tradeId, - ), + if (isDesktop) IconCopyButton(data: trade.tradeId), if (!isDesktop) Row( children: [ @@ -1265,9 +1171,7 @@ class _TradeDetailsViewState extends ConsumerState { trade.tradeId, style: STextStyles.itemSubtitle12(context), ), - const SizedBox( - width: 10, - ), + const SizedBox(width: 10), GestureDetector( onTap: () async { final data = ClipboardData(text: trade.tradeId); @@ -1284,9 +1188,10 @@ class _TradeDetailsViewState extends ConsumerState { }, child: SvgPicture.asset( Assets.svg.copy, - color: Theme.of(context) - .extension()! - .infoItemIcons, + color: + Theme.of( + context, + ).extension()!.infoItemIcons, width: 12, ), ), @@ -1295,25 +1200,17 @@ class _TradeDetailsViewState extends ConsumerState { ], ), ), - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), + isDesktop ? const _Divider() : const SizedBox(height: 12), RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "Tracking", - style: STextStyles.itemSubtitle(context), - ), - const SizedBox( - height: 4, - ), + Text("Tracking", style: STextStyles.itemSubtitle(context)), + const SizedBox(height: 4), Builder( builder: (context) { late final String url; @@ -1336,18 +1233,20 @@ class _TradeDetailsViewState extends ConsumerState { break; default: - if (trade.exchangeName - .startsWith(TrocadorExchange.exchangeName)) { + if (trade.exchangeName.startsWith( + TrocadorExchange.exchangeName, + )) { url = "https://trocador.app/en/checkout/${trade.tradeId}"; } } return ConditionalParent( condition: isDesktop, - builder: (child) => MouseRegion( - cursor: SystemMouseCursors.click, - child: child, - ), + builder: + (child) => MouseRegion( + cursor: SystemMouseCursors.click, + child: child, + ), child: GestureDetector( onTap: () { launchUrl( @@ -1355,10 +1254,7 @@ class _TradeDetailsViewState extends ConsumerState { mode: LaunchMode.externalApplication, ); }, - child: Text( - url, - style: STextStyles.link2(context), - ), + child: Text(url, style: STextStyles.link2(context)), ), ); }, @@ -1366,19 +1262,17 @@ class _TradeDetailsViewState extends ConsumerState { ], ), ), - if (!isDesktop) - const SizedBox( - height: 12, - ), + if (!isDesktop) const SizedBox(height: 12), if (!isDesktop && showSendFromStackButton) SecondaryButton( label: "Send from ${AppConfig.prefix}", onPressed: () { CryptoCurrency coin; try { - coin = AppConfig.getCryptoCurrencyForTicker( - trade.payInCurrency, - )!; + coin = + AppConfig.getCryptoCurrencyForTicker( + trade.payInCurrency, + )!; } catch (_) { coin = AppConfig.getCryptoCurrencyByPrettyName( trade.payInCurrency, @@ -1392,12 +1286,7 @@ class _TradeDetailsViewState extends ConsumerState { Navigator.of(context).pushNamed( SendFromView.routeName, - arguments: Tuple4( - coin, - amount, - address, - trade, - ), + arguments: Tuple4(coin, amount, address, trade), ); }, ), diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart index b6e872bd4..679ccc84c 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart @@ -141,10 +141,13 @@ abstract class SWB { if (!backupFile.existsSync()) { final String jsonBackup = plaintext; final Uint8List content = Uint8List.fromList(utf8.encode(jsonBackup)); - final Uint8List encryptedContent = - await encryptWithPassphrase(passphrase, content); - backupFile - .writeAsStringSync(Format.uint8listToString(encryptedContent)); + final Uint8List encryptedContent = await encryptWithPassphrase( + passphrase, + content, + ); + backupFile.writeAsStringSync( + Format.uint8listToString(encryptedContent), + ); } Logging.instance.d(backupFile.absolute); return true; @@ -170,8 +173,9 @@ abstract class SWB { content, version: adkVersion, ); - backupFile - .writeAsStringSync(Format.uint8listToString(encryptedContent)); + backupFile.writeAsStringSync( + Format.uint8listToString(encryptedContent), + ); } Logging.instance.d(backupFile.absolute); return true; @@ -207,8 +211,10 @@ abstract class SWB { final encryptedBytes = Format.stringToUint8List(encryptedText); - final decryptedContent = - await decryptWithPassphrase(passphrase, encryptedBytes); + final decryptedContent = await decryptWithPassphrase( + passphrase, + encryptedBytes, + ); final jsonBackup = utf8.decode(decryptedContent); return jsonBackup; @@ -225,50 +231,41 @@ abstract class SWB { Logging.instance.d("Starting createStackWalletJSON..."); final _wallets = Wallets.sharedInstance; final Map backupJson = {}; - final NodeService nodeService = - NodeService(secureStorageInterface: secureStorage); + final NodeService nodeService = NodeService( + secureStorageInterface: secureStorage, + ); final _secureStore = secureStorage; - Logging.instance.d( - "createStackWalletJSON awaiting DB.instance.mutex...", - ); + Logging.instance.d("createStackWalletJSON awaiting DB.instance.mutex..."); // prevent modification of data await DB.instance.mutex.protect(() async { - Logging.instance.i( - "...createStackWalletJSON DB.instance.mutex acquired", - ); - Logging.instance.i( - "SWB backing up nodes", - ); + Logging.instance.i("...createStackWalletJSON DB.instance.mutex acquired"); + Logging.instance.i("SWB backing up nodes"); try { - final primaryNodes = nodeService.primaryNodes.map((e) async { - final map = e.toMap(); - map["password"] = await e.getPassword(_secureStore); - return map; - }).toList(); + final primaryNodes = + nodeService.primaryNodes.map((e) async { + final map = e.toMap(); + map["password"] = await e.getPassword(_secureStore); + return map; + }).toList(); backupJson['primaryNodes'] = await Future.wait(primaryNodes); } catch (e, s) { - Logging.instance.e( - "", - error: e, - stackTrace: s, - ); + Logging.instance.e("", error: e, stackTrace: s); } try { - final nodesFuture = nodeService.nodes.map((e) async { - final map = e.toMap(); - map["password"] = await e.getPassword(_secureStore); - return map; - }).toList(); + final nodesFuture = + nodeService.nodes.map((e) async { + final map = e.toMap(); + map["password"] = await e.getPassword(_secureStore); + return map; + }).toList(); final nodes = await Future.wait(nodesFuture); backupJson['nodes'] = nodes; } catch (e, s) { Logging.instance.e("", error: e, stackTrace: s); } - Logging.instance.d( - "SWB backing up prefs", - ); + Logging.instance.d("SWB backing up prefs"); final Map prefs = {}; final _prefs = Prefs.instance; @@ -289,18 +286,14 @@ abstract class SWB { backupJson['prefs'] = prefs; - Logging.instance.d( - "SWB backing up addressbook", - ); + Logging.instance.d("SWB backing up addressbook"); final AddressBookService addressBookService = AddressBookService(); final addresses = addressBookService.contacts; backupJson['addressBookEntries'] = addresses.map((e) => e.toMap()).toList(); - Logging.instance.d( - "SWB backing up wallets", - ); + Logging.instance.d("SWB backing up wallets"); final List backupWallets = []; for (final wallet in _wallets.wallets) { @@ -349,14 +342,15 @@ abstract class SWB { backupWallet['restoreHeight'] = wallet.info.restoreHeight; - final isarNotes = await MainDB.instance.isar.transactionNotes - .where() - .walletIdEqualTo(wallet.walletId) - .findAll(); + final isarNotes = + await MainDB.instance.isar.transactionNotes + .where() + .walletIdEqualTo(wallet.walletId) + .findAll(); - final notes = isarNotes - .asMap() - .map((key, value) => MapEntry(value.txid, value.value)); + final notes = isarNotes.asMap().map( + (key, value) => MapEntry(value.txid, value.value), + ); backupWallet['notes'] = notes; @@ -364,14 +358,13 @@ abstract class SWB { } backupJson['wallets'] = backupWallets; - Logging.instance.d( - "SWB backing up trades", - ); + Logging.instance.d("SWB backing up trades"); // back up trade history final tradesService = TradesService(); - final trades = - tradesService.trades.map((e) => e.toMap()).toList(growable: false); + final trades = tradesService.trades + .map((e) => e.toMap()) + .toList(growable: false); backupJson["tradeHistory"] = trades; // back up trade history lookup data for trades send from stack wallet @@ -380,18 +373,14 @@ abstract class SWB { tradeTxidLookupDataService.all.map((e) => e.toMap()).toList(); backupJson["tradeTxidLookupData"] = lookupData; - Logging.instance.d( - "SWB backing up trade notes", - ); + Logging.instance.d("SWB backing up trade notes"); // back up trade notes final tradeNotesService = TradeNotesService(); final tradeNotes = tradeNotesService.all; backupJson["tradeNotes"] = tradeNotes; }); - Logging.instance.d( - "createStackWalletJSON DB.instance.mutex released", - ); + Logging.instance.d("createStackWalletJSON DB.instance.mutex released"); // // back up notifications data // final notificationsService = NotificationsService(); @@ -473,9 +462,7 @@ abstract class SWB { knownSalts: [], participants: participants, myName: myName, - threshold: frost.multisigThreshold( - multisigConfig: multisigConfig, - ), + threshold: frost.multisigThreshold(multisigConfig: multisigConfig), ); await MainDB.instance.isar.writeTxn(() async { @@ -507,7 +494,7 @@ abstract class SWB { case const (WowneroWallet): await (wallet as WowneroWallet).init(isRestore: true); break; - + case const (XelisWallet): await (wallet as XelisWallet).init(isRestore: true); break; @@ -547,8 +534,9 @@ abstract class SWB { } // restore notes - final notesMap = - Map.from(walletbackup["notes"] as Map? ?? {}); + final notesMap = Map.from( + walletbackup["notes"] as Map? ?? {}, + ); final List notes = []; for (final key in notesMap.keys) { @@ -601,11 +589,7 @@ abstract class SWB { mnemonicPassphrase: mnemonicPassphrase, ); } catch (e, s) { - Logging.instance.i( - "", - error: e, - stackTrace: s, - ); + Logging.instance.i("", error: e, stackTrace: s); uiState?.update( walletId: info.walletId, restoringStatus: StackRestoringStatus.failed, @@ -639,17 +623,13 @@ abstract class SWB { uiState?.preferences = StackRestoringStatus.restoring; - Logging.instance.d( - "SWB restoring prefs", - ); + Logging.instance.d("SWB restoring prefs"); await _restorePrefs(prefs); uiState?.preferences = StackRestoringStatus.success; uiState?.addressBook = StackRestoringStatus.restoring; - Logging.instance.d( - "SWB restoring addressbook", - ); + Logging.instance.d("SWB restoring addressbook"); if (addressBookEntries != null) { await _restoreAddressBook(addressBookEntries); } @@ -657,40 +637,28 @@ abstract class SWB { uiState?.addressBook = StackRestoringStatus.success; uiState?.nodes = StackRestoringStatus.restoring; - Logging.instance.d( - "SWB restoring nodes", - ); - await _restoreNodes( - nodes, - primaryNodes, - secureStorageInterface, - ); + Logging.instance.d("SWB restoring nodes"); + await _restoreNodes(nodes, primaryNodes, secureStorageInterface); uiState?.nodes = StackRestoringStatus.success; uiState?.trades = StackRestoringStatus.restoring; // restore trade history if (trades != null) { - Logging.instance.d( - "SWB restoring trades", - ); + Logging.instance.d("SWB restoring trades"); await _restoreTrades(trades); } // restore trade history lookup data for trades send from stack wallet if (tradeTxidLookupData != null) { - Logging.instance.d( - "SWB restoring trade look up data", - ); + Logging.instance.d("SWB restoring trade look up data"); await _restoreTradesLookUpData(tradeTxidLookupData, oldToNewWalletIdMap); } // restore trade notes if (tradeNotes != null) { - Logging.instance.d( - "SWB restoring trade notes", - ); + Logging.instance.d("SWB restoring trade notes"); await _restoreTradesNotes(tradeNotes); } @@ -722,22 +690,22 @@ abstract class SWB { ) async { if (!Platform.isLinux) await WakelockPlus.enable(); - Logging.instance.d( - "SWB creating temp backup", - ); - final preRestoreJSON = - await createStackWalletJSON(secureStorage: secureStorageInterface); - Logging.instance.d( - "SWB temp backup created", + Logging.instance.d("SWB creating temp backup"); + final preRestoreJSON = await createStackWalletJSON( + secureStorage: secureStorageInterface, ); + Logging.instance.d("SWB temp backup created"); - final List _currentWalletIds = await MainDB.instance.isar.walletInfo - .where() - .walletIdProperty() - .findAll(); + final List _currentWalletIds = + await MainDB.instance.isar.walletInfo + .where() + .walletIdProperty() + .findAll(); - final preRestoreState = - PreRestoreState(_currentWalletIds.toSet(), preRestoreJSON); + final preRestoreState = PreRestoreState( + _currentWalletIds.toSet(), + preRestoreJSON, + ); final Map oldToNewWalletIdMap = {}; @@ -759,10 +727,7 @@ abstract class SWB { // basic cancel check here // no reverting required yet as nothing has been written to store - if (_checkShouldCancel( - null, - secureStorageInterface, - )) { + if (_checkShouldCancel(null, secureStorageInterface)) { return false; } @@ -774,10 +739,7 @@ abstract class SWB { ); // check if cancel was requested and restore previous state - if (_checkShouldCancel( - preRestoreState, - secureStorageInterface, - )) { + if (_checkShouldCancel(preRestoreState, secureStorageInterface)) { return false; } @@ -794,10 +756,7 @@ abstract class SWB { for (final walletbackup in wallets) { // check if cancel was requested and restore previous state - if (_checkShouldCancel( - preRestoreState, - secureStorageInterface, - )) { + if (_checkShouldCancel(preRestoreState, secureStorageInterface)) { return false; } @@ -818,8 +777,9 @@ abstract class SWB { Map? otherData; try { if (walletbackup["otherDataJsonString"] is String) { - final data = - jsonDecode(walletbackup["otherDataJsonString"] as String); + final data = jsonDecode( + walletbackup["otherDataJsonString"] as String, + ); otherData = Map.from(data as Map); } } catch (e, s) { @@ -859,19 +819,13 @@ abstract class SWB { // final failovers = nodeService.failoverNodesFor(coin: coin); // check if cancel was requested and restore previous state - if (_checkShouldCancel( - preRestoreState, - secureStorageInterface, - )) { + if (_checkShouldCancel(preRestoreState, secureStorageInterface)) { return false; } managers.add(Tuple2(walletbackup, info)); // check if cancel was requested and restore previous state - if (_checkShouldCancel( - preRestoreState, - secureStorageInterface, - )) { + if (_checkShouldCancel(preRestoreState, secureStorageInterface)) { return false; } @@ -884,10 +838,7 @@ abstract class SWB { } // check if cancel was requested and restore previous state - if (_checkShouldCancel( - preRestoreState, - secureStorageInterface, - )) { + if (_checkShouldCancel(preRestoreState, secureStorageInterface)) { return false; } @@ -898,10 +849,7 @@ abstract class SWB { // start restoring wallets for (final tuple in managers) { // check if cancel was requested and restore previous state - if (_checkShouldCancel( - preRestoreState, - secureStorageInterface, - )) { + if (_checkShouldCancel(preRestoreState, secureStorageInterface)) { return false; } final bools = await _asyncRestore( @@ -915,19 +863,13 @@ abstract class SWB { } // check if cancel was requested and restore previous state - if (_checkShouldCancel( - preRestoreState, - secureStorageInterface, - )) { + if (_checkShouldCancel(preRestoreState, secureStorageInterface)) { return false; } for (final Future status in restoreStatuses) { // check if cancel was requested and restore previous state - if (_checkShouldCancel( - preRestoreState, - secureStorageInterface, - )) { + if (_checkShouldCancel(preRestoreState, secureStorageInterface)) { return false; } await status; @@ -935,19 +877,17 @@ abstract class SWB { if (!Platform.isLinux) await WakelockPlus.disable(); // check if cancel was requested and restore previous state - if (_checkShouldCancel( - preRestoreState, - secureStorageInterface, - )) { + if (_checkShouldCancel(preRestoreState, secureStorageInterface)) { return false; } - Logging.instance.d( - "done with SWB restore", - ); + Logging.instance.d("done with SWB restore"); - await Wallets.sharedInstance - .loadAfterStackRestore(_prefs, uiState?.wallets ?? [], Util.isDesktop); + await Wallets.sharedInstance.loadAfterStackRestore( + _prefs, + uiState?.wallets ?? [], + Util.isDesktop, + ); return true; } @@ -998,11 +938,12 @@ abstract class SWB { // ensure this contact's data matches the pre restore state final List addresses = []; for (final address in (contact['addresses'] as List)) { - final entry = ContactAddressEntry() - ..coinName = address['coin'] as String - ..address = address['address'] as String - ..label = address['label'] as String - ..other = address['other'] as String?; + final entry = + ContactAddressEntry() + ..coinName = address['coin'] as String + ..address = address['address'] as String + ..label = address['label'] as String + ..other = address['other'] as String?; try { entry.coin; @@ -1011,9 +952,7 @@ abstract class SWB { continue; } - addresses.add( - entry, - ); + addresses.add(entry); } await addressBookService.editContact( ContactEntry( @@ -1174,22 +1113,22 @@ abstract class SWB { } // finally remove any added wallets - final allWalletIds = (await MainDB.instance.isar.walletInfo - .where() - .walletIdProperty() - .findAll()) - .toSet(); + final allWalletIds = + (await MainDB.instance.isar.walletInfo + .where() + .walletIdProperty() + .findAll()) + .toSet(); final walletIdsToDelete = allWalletIds.difference(revertToState.walletIds); await MainDB.instance.isar.writeTxn(() async { - await MainDB.instance.isar.walletInfo - .deleteAllByWalletId(walletIdsToDelete.toList()); + await MainDB.instance.isar.walletInfo.deleteAllByWalletId( + walletIdsToDelete.toList(), + ); }); _cancelCompleter!.complete(); _shouldCancelRestore = false; - Logging.instance.d( - "Revert SWB complete", - ); + Logging.instance.d("Revert SWB complete"); } static Future _restorePrefs(Map prefs) async { @@ -1201,9 +1140,10 @@ abstract class SWB { _prefs.language = prefs['language'] as String; _prefs.showFavoriteWallets = prefs['showFavoriteWallets'] as bool; _prefs.wifiOnly = prefs['wifiOnly'] as bool; - _prefs.syncType = prefs['syncType'] == "currentWalletOnly" - ? SyncingType.currentWalletOnly - : prefs['syncType'] == "selectedWalletsAtStartup" + _prefs.syncType = + prefs['syncType'] == "currentWalletOnly" + ? SyncingType.currentWalletOnly + : prefs['syncType'] == "selectedWalletsAtStartup" ? SyncingType.currentWalletOnly : SyncingType.allWalletsOnStartup; // _prefs.walletIdsSyncOnStartup = @@ -1217,8 +1157,9 @@ abstract class SWB { (e) => e.name == (prefs['backupFrequencyType'] as String?), orElse: () => BackupFrequencyType.everyAppStart, ); - _prefs.lastAutoBackup = - DateTime.tryParse(prefs['lastAutoBackup'] as String? ?? ""); + _prefs.lastAutoBackup = DateTime.tryParse( + prefs['lastAutoBackup'] as String? ?? "", + ); } static Future _restoreAddressBook( @@ -1228,11 +1169,12 @@ abstract class SWB { for (final contact in addressBookEntries) { final List addresses = []; for (final address in (contact['addresses'] as List)) { - final entry = ContactAddressEntry() - ..coinName = address['coin'] as String - ..address = address['address'] as String - ..label = address['label'] as String - ..other = address['other'] as String?; + final entry = + ContactAddressEntry() + ..coinName = address['coin'] as String + ..address = address['address'] as String + ..label = address['label'] as String + ..other = address['other'] as String?; try { entry.coin; @@ -1241,9 +1183,7 @@ abstract class SWB { continue; } - addresses.add( - entry, - ); + addresses.add(entry); } if (addresses.isNotEmpty) { await addressBookService.addContact( @@ -1313,53 +1253,35 @@ abstract class SWB { await nodeService.updateDefaults(); } - static Future _restoreTrades( - List trades, - ) async { - final tradesService = TradesService(); - for (int i = 0; i < trades.length - 1; i++) { - ExchangeTransaction? exTx; - try { - exTx = ExchangeTransaction.fromJson(trades[i] as Map); - } catch (e) { - // unneeded log - // Logging.instance.log("$e\n$s", error: e, stackTrace: s,); - } - - Trade trade; - if (exTx != null) { - trade = Trade.fromExchangeTransaction(exTx, false); - } else { - trade = Trade.fromMap(trades[i] as Map); - } - - await tradesService.add( - trade: trade, - shouldNotifyListeners: false, - ); - } - // only call notifyListeners on last one added + static Future _restoreTrades(List trades) async { if (trades.isNotEmpty) { - ExchangeTransaction? exTx; - try { - exTx = - ExchangeTransaction.fromJson(trades.last as Map); - } catch (e) { - // unneeded log - // Logging.instance.log("$e\n$s", level: LogLevel.Warning); - } + final tradesService = TradesService(); + for (int i = 0; i < trades.length; i++) { + // First check for old old database entries + ExchangeTransaction? exTx; + try { + exTx = ExchangeTransaction.fromJson( + trades[i] as Map, + ); + } catch (e) { + // unneeded log + // Logging.instance.log("$e\n$s", error: e, stackTrace: s,); + } - Trade trade; - if (exTx != null) { - trade = Trade.fromExchangeTransaction(exTx, false); - } else { - trade = Trade.fromMap(trades.last as Map); - } + Trade trade; + if (exTx != null) { + trade = Trade.fromExchangeTransaction(exTx, false); + } else { + trade = Trade.fromMap(trades[i] as Map); + } - await tradesService.add( - trade: trade, - shouldNotifyListeners: true, - ); + await tradesService.add( + trade: trade, + shouldNotifyListeners: + i == + trades.length - 1, // only call notifyListeners on last one added + ); + } } } diff --git a/lib/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart b/lib/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart index c487ff991..d2a053d74 100644 --- a/lib/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart +++ b/lib/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart @@ -19,7 +19,7 @@ import 'package:isar/isar.dart'; import 'package:tuple/tuple.dart'; import '../../db/isar/main_db.dart'; -import '../../models/exchange/change_now/exchange_transaction_status.dart'; +import '../../models/exchange/change_now/cn_exchange_transaction_status.dart'; import '../../models/exchange/response_objects/trade.dart'; import '../../models/isar/models/isar_models.dart'; import '../../models/isar/stack_theme.dart'; @@ -104,41 +104,32 @@ class _DesktopAllTradesViewState extends ConsumerState { background: Theme.of(context).extension()!.popupBG, leading: Row( children: [ - const SizedBox( - width: 32, - ), + const SizedBox(width: 32), AppBarIconButton( size: 32, - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, + color: + Theme.of( + context, + ).extension()!.textFieldDefaultBG, shadows: const [], icon: SvgPicture.asset( Assets.svg.arrowLeft, width: 18, height: 18, - color: Theme.of(context) - .extension()! - .topNavIconPrimary, + color: + Theme.of( + context, + ).extension()!.topNavIconPrimary, ), onPressed: Navigator.of(context).pop, ), - const SizedBox( - width: 12, - ), - Text( - "Trades", - style: STextStyles.desktopH3(context), - ), + const SizedBox(width: 12), + Text("Trades", style: STextStyles.desktopH3(context)), ], ), ), body: Padding( - padding: const EdgeInsets.only( - left: 20, - top: 20, - right: 20, - ), + padding: const EdgeInsets.only(left: 20, top: 20, right: 20), child: Column( children: [ Row( @@ -159,11 +150,13 @@ class _DesktopAllTradesViewState extends ConsumerState { _searchString = value; }); }, - style: - STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveText, + style: STextStyles.desktopTextExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension()!.textFieldActiveText, height: 1.8, ), decoration: standardInputDecoration( @@ -183,35 +176,34 @@ class _DesktopAllTradesViewState extends ConsumerState { height: 20, ), ), - suffixIcon: _searchController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - _searchController.text = ""; - _searchString = ""; - }); - }, - ), - ], + suffixIcon: + _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + _searchString = ""; + }); + }, + ), + ], + ), ), - ), - ) - : null, + ) + : null, ), ), ), ), ], ), - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), Expanded( child: Consumer( builder: (_, ref, __) { @@ -237,38 +229,36 @@ class _DesktopAllTradesViewState extends ConsumerState { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (index != 0) - const SizedBox( - height: 12, - ), + if (index != 0) const SizedBox(height: 12), Text( month.item1, style: STextStyles.smallMed12(context), ), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), RoundedWhiteContainer( padding: const EdgeInsets.all(0), child: ListView.separated( shrinkWrap: true, primary: false, - separatorBuilder: (context, _) => Container( - height: 1, - color: Theme.of(context) - .extension()! - .background, - ), + separatorBuilder: + (context, _) => Container( + height: 1, + color: + Theme.of(context) + .extension()! + .background, + ), itemCount: month.item2.length, - itemBuilder: (context, index) => Padding( - padding: const EdgeInsets.all(4), - child: DesktopTradeRowCard( - key: Key( - "transactionCard_key_${month.item2[index].tradeId}", + itemBuilder: + (context, index) => Padding( + padding: const EdgeInsets.all(4), + child: DesktopTradeRowCard( + key: Key( + "transactionCard_key_${month.item2[index].tradeId}", + ), + tradeId: month.item2[index].tradeId, + ), ), - tradeId: month.item2[index].tradeId, - ), - ), ), ), ], @@ -287,10 +277,7 @@ class _DesktopAllTradesViewState extends ConsumerState { } class DesktopTradeRowCard extends ConsumerStatefulWidget { - const DesktopTradeRowCard({ - super.key, - required this.tradeId, - }); + const DesktopTradeRowCard({super.key, required this.tradeId}); final String tradeId; @@ -347,8 +334,9 @@ class _DesktopTradeRowCardState extends ConsumerState { @override Widget build(BuildContext context) { - final String? txid = - ref.read(tradeSentFromStackLookupProvider).getTxidForTradeId(tradeId); + final String? txid = ref + .read(tradeSentFromStackLookupProvider) + .getTxidForTradeId(tradeId); final List? walletIds = ref .read(tradeSentFromStackLookupProvider) .getWalletIdsForTradeId(tradeId); @@ -360,8 +348,9 @@ class _DesktopTradeRowCardState extends ConsumerState { color: Theme.of(context).extension()!.popupBG, elevation: 0, shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(Constants.size.circularBorderRadius), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), child: RawMaterialButton( shape: RoundedRectangleBorder( @@ -374,25 +363,27 @@ class _DesktopTradeRowCardState extends ConsumerState { //todo: check if print needed // debugPrint("name: ${manager.walletName}"); - final tx = await MainDB.instance - .getTransactions(walletIds.first) - .filter() - .txidEqualTo(txid) - .findFirst(); + final tx = + await MainDB.instance + .getTransactions(walletIds.first) + .filter() + .txidEqualTo(txid) + .findFirst(); if (mounted) { await showDialog( context: context, - builder: (context) => DesktopDialog( - maxHeight: MediaQuery.of(context).size.height - 64, - maxWidth: 580, - child: TradeDetailsView( - tradeId: tradeId, - transactionIfSentFromStack: tx, - walletName: ref.read(pWalletName(walletIds.first)), - walletId: walletIds.first, - ), - ), + builder: + (context) => DesktopDialog( + maxHeight: MediaQuery.of(context).size.height - 64, + maxWidth: 580, + child: TradeDetailsView( + tradeId: tradeId, + transactionIfSentFromStack: tx, + walletName: ref.read(pWalletName(walletIds.first)), + walletId: walletIds.first, + ), + ), ); } @@ -400,62 +391,67 @@ class _DesktopTradeRowCardState extends ConsumerState { unawaited( showDialog( context: context, - builder: (context) => Navigator( - initialRoute: TradeDetailsView.routeName, - onGenerateRoute: RouteGenerator.generateRoute, - onGenerateInitialRoutes: (_, __) { - return [ - FadePageRoute( - DesktopDialog( - maxHeight: null, - maxWidth: 580, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only( - left: 32, - bottom: 16, - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - "Trade details", - style: STextStyles.desktopH3(context), + builder: + (context) => Navigator( + initialRoute: TradeDetailsView.routeName, + onGenerateRoute: RouteGenerator.generateRoute, + onGenerateInitialRoutes: (_, __) { + return [ + FadePageRoute( + DesktopDialog( + maxHeight: null, + maxWidth: 580, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + bottom: 16, ), - DesktopDialogCloseButton( - onPressedOverride: Navigator.of( - context, - rootNavigator: true, - ).pop, + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Trade details", + style: STextStyles.desktopH3( + context, + ), + ), + DesktopDialogCloseButton( + onPressedOverride: + Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + ], ), - ], - ), - ), - Flexible( - child: SingleChildScrollView( - primary: false, - child: TradeDetailsView( - tradeId: tradeId, - transactionIfSentFromStack: tx, - walletName: ref - .read(pWalletName(walletIds.first)), - walletId: walletIds.first, ), - ), + Flexible( + child: SingleChildScrollView( + primary: false, + child: TradeDetailsView( + tradeId: tradeId, + transactionIfSentFromStack: tx, + walletName: ref.read( + pWalletName(walletIds.first), + ), + walletId: walletIds.first, + ), + ), + ), + ], ), - ], + ), + const RouteSettings( + name: TradeDetailsView.routeName, + ), ), - ), - const RouteSettings( - name: TradeDetailsView.routeName, - ), - ), - ]; - }, - ), + ]; + }, + ), ), ); } @@ -463,70 +459,69 @@ class _DesktopTradeRowCardState extends ConsumerState { unawaited( showDialog( context: context, - builder: (context) => Navigator( - initialRoute: TradeDetailsView.routeName, - onGenerateRoute: RouteGenerator.generateRoute, - onGenerateInitialRoutes: (_, __) { - return [ - FadePageRoute( - DesktopDialog( - maxHeight: null, - maxWidth: 580, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only( - left: 32, - bottom: 16, - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - "Trade details", - style: STextStyles.desktopH3(context), + builder: + (context) => Navigator( + initialRoute: TradeDetailsView.routeName, + onGenerateRoute: RouteGenerator.generateRoute, + onGenerateInitialRoutes: (_, __) { + return [ + FadePageRoute( + DesktopDialog( + maxHeight: null, + maxWidth: 580, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + bottom: 16, ), - DesktopDialogCloseButton( - onPressedOverride: Navigator.of( - context, - rootNavigator: true, - ).pop, + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Trade details", + style: STextStyles.desktopH3(context), + ), + DesktopDialogCloseButton( + onPressedOverride: + Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + ], ), - ], - ), - ), - Flexible( - child: SingleChildScrollView( - primary: false, - child: TradeDetailsView( - tradeId: tradeId, - transactionIfSentFromStack: null, - walletName: null, - walletId: walletIds?.first, ), - ), + Flexible( + child: SingleChildScrollView( + primary: false, + child: TradeDetailsView( + tradeId: tradeId, + transactionIfSentFromStack: null, + walletName: null, + walletId: walletIds?.first, + ), + ), + ), + ], ), - ], + ), + const RouteSettings( + name: TradeDetailsView.routeName, + ), ), - ), - const RouteSettings( - name: TradeDetailsView.routeName, - ), - ), - ]; - }, - ), + ]; + }, + ), ), ); } }, child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 10, - horizontal: 16, - ), + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), child: Row( children: [ Container( @@ -540,9 +535,7 @@ class _DesktopTradeRowCardState extends ConsumerState { File( _fetchIconAssetForStatus( trade.status, - ref.watch( - themeAssetsProvider, - ), + ref.watch(themeAssetsProvider), ), ), width: 32, @@ -550,15 +543,14 @@ class _DesktopTradeRowCardState extends ConsumerState { ), ), ), - const SizedBox( - width: 12, - ), + const SizedBox(width: 12), Expanded( flex: 3, child: Text( "${trade.payInCurrency.toUpperCase()} → ${trade.payOutCurrency.toUpperCase()}", - style: - STextStyles.desktopTextExtraExtraSmall(context).copyWith( + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( color: Theme.of(context).extension()!.textDark, ), ), @@ -576,8 +568,9 @@ class _DesktopTradeRowCardState extends ConsumerState { flex: 6, child: Text( "-${Decimal.tryParse(trade.payInAmount)?.toStringAsFixed(8) ?? "..."} ${trade.payInCurrency.toUpperCase()}", - style: - STextStyles.desktopTextExtraExtraSmall(context).copyWith( + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( color: Theme.of(context).extension()!.textDark, ), ), diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart index c6769e68a..3fe2b20d7 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart @@ -47,14 +47,11 @@ final ssss = StateProvider((_) => null); final desktopExchangeModelProvider = ChangeNotifierProvider( - (ref) => ref.watch(ssss.state).state, -); + (ref) => ref.watch(ssss.state).state, + ); class StepScaffold extends ConsumerStatefulWidget { - const StepScaffold({ - super.key, - required this.initialStep, - }); + const StepScaffold({super.key, required this.initialStep}); final int initialStep; @@ -79,19 +76,19 @@ class _StepScaffoldState extends ConsumerState { showDialog( context: context, barrierDismissible: false, - builder: (_) => WillPopScope( - onWillPop: () async => false, - child: Container( - color: Theme.of(context) - .extension()! - .overlay - .withOpacity(0.6), - child: const CustomLoadingOverlay( - message: "Creating a trade", - eventBus: null, + builder: + (_) => WillPopScope( + onWillPop: () async => false, + child: Container( + color: Theme.of( + context, + ).extension()!.overlay.withOpacity(0.6), + child: const CustomLoadingOverlay( + message: "Creating a trade", + eventBus: null, + ), + ), ), - ), - ), ), ); @@ -99,12 +96,16 @@ class _StepScaffoldState extends ConsumerState { .read(efExchangeProvider) .createTrade( from: ref.read(desktopExchangeModelProvider)!.sendTicker, + fromNetwork: ref.read(desktopExchangeModelProvider)!.sendNetwork, to: ref.read(desktopExchangeModelProvider)!.receiveTicker, - fixedRate: ref.read(desktopExchangeModelProvider)!.rateType != + toNetwork: ref.read(desktopExchangeModelProvider)!.receiveNetwork, + fixedRate: + ref.read(desktopExchangeModelProvider)!.rateType != ExchangeRateType.estimated, - amount: ref.read(desktopExchangeModelProvider)!.reversed - ? ref.read(desktopExchangeModelProvider)!.receiveAmount - : ref.read(desktopExchangeModelProvider)!.sendAmount, + amount: + ref.read(desktopExchangeModelProvider)!.reversed + ? ref.read(desktopExchangeModelProvider)!.receiveAmount + : ref.read(desktopExchangeModelProvider)!.sendAmount, addressTo: ref.read(desktopExchangeModelProvider)!.recipientAddress!, extraId: null, addressRefund: ref.read(desktopExchangeModelProvider)!.refundAddress!, @@ -131,10 +132,11 @@ class _StepScaffoldState extends ConsumerState { showDialog( context: context, barrierDismissible: true, - builder: (_) => SimpleDesktopDialog( - title: "Failed to create trade", - message: message ?? "", - ), + builder: + (_) => SimpleDesktopDialog( + title: "Failed to create trade", + message: message ?? "", + ), ), ); } @@ -142,10 +144,9 @@ class _StepScaffoldState extends ConsumerState { } // save trade to hive - await ref.read(tradesServiceProvider).add( - trade: response.value!, - shouldNotifyListeners: true, - ); + await ref + .read(tradesServiceProvider) + .add(trade: response.value!, shouldNotifyListeners: true); String status = response.value!.status; @@ -206,35 +207,35 @@ class _StepScaffoldState extends ConsumerState { void sendFromStack() { final trade = ref.read(desktopExchangeModelProvider)!.trade!; final address = trade.payInAddress; - final coin = AppConfig.getCryptoCurrencyForTicker(trade.payInCurrency) ?? + final coin = + AppConfig.getCryptoCurrencyForTicker(trade.payInCurrency) ?? AppConfig.getCryptoCurrencyByPrettyName(trade.payInCurrency); - final amount = Decimal.parse(trade.payInAmount).toAmount( - fractionDigits: coin.fractionDigits, - ); + final amount = Decimal.parse( + trade.payInAmount, + ).toAmount(fractionDigits: coin.fractionDigits); showDialog( context: context, - builder: (context) => Navigator( - initialRoute: SendFromView.routeName, - onGenerateRoute: RouteGenerator.generateRoute, - onGenerateInitialRoutes: (_, __) { - return [ - FadePageRoute( - SendFromView( - coin: coin, - trade: trade, - amount: amount, - address: address, - shouldPopRoot: true, - fromDesktopStep4: true, - ), - const RouteSettings( - name: SendFromView.routeName, - ), - ), - ]; - }, - ), + builder: + (context) => Navigator( + initialRoute: SendFromView.routeName, + onGenerateRoute: RouteGenerator.generateRoute, + onGenerateInitialRoutes: (_, __) { + return [ + FadePageRoute( + SendFromView( + coin: coin, + trade: trade, + amount: amount, + address: address, + shouldPopRoot: true, + fromDesktopStep4: true, + ), + const RouteSettings(name: SendFromView.routeName), + ), + ]; + }, + ), ); } @@ -258,13 +259,11 @@ class _StepScaffoldState extends ConsumerState { children: [ currentStep != 4 ? AppBarBackButton( - isCompact: true, - iconSize: 23, - onPressed: onBack, - ) - : const SizedBox( - width: 32, - ), + isCompact: true, + iconSize: 23, + onPressed: onBack, + ) + : const SizedBox(width: 32), Text( "Exchange ${model?.sendTicker.toUpperCase()} to ${model?.receiveTicker.toUpperCase()}", style: STextStyles.desktopH3(context), @@ -279,31 +278,19 @@ class _StepScaffoldState extends ConsumerState { ), ], ), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), Padding( - padding: const EdgeInsets.symmetric( - horizontal: 32, - ), - child: DesktopExchangeStepsIndicator( - currentStep: currentStep, - ), - ), - const SizedBox( - height: 32, + padding: const EdgeInsets.symmetric(horizontal: 32), + child: DesktopExchangeStepsIndicator(currentStep: currentStep), ), + const SizedBox(height: 32), Padding( - padding: const EdgeInsets.symmetric( - horizontal: 32, - ), + padding: const EdgeInsets.symmetric(horizontal: 32), child: FadeStack( index: currentStep - 1, children: [ const DesktopStep1(), - DesktopStep2( - enableNextChanged: updateEnableNext, - ), + DesktopStep2(enableNextChanged: updateEnableNext), const DesktopStep3(), const DesktopStep4(), ], @@ -321,9 +308,10 @@ class _StepScaffoldState extends ConsumerState { Expanded( child: AnimatedCrossFade( duration: const Duration(milliseconds: 250), - crossFadeState: currentStep == 4 - ? CrossFadeState.showSecond - : CrossFadeState.showFirst, + crossFadeState: + currentStep == 4 + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, firstChild: SecondaryButton( label: "Back", buttonHeight: ButtonHeight.l, @@ -336,20 +324,20 @@ class _StepScaffoldState extends ConsumerState { ), ), ), - const SizedBox( - width: 16, - ), + const SizedBox(width: 16), Expanded( child: AnimatedCrossFade( duration: const Duration(milliseconds: 250), - crossFadeState: currentStep == 4 - ? CrossFadeState.showSecond - : CrossFadeState.showFirst, + crossFadeState: + currentStep == 4 + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, firstChild: AnimatedCrossFade( duration: const Duration(milliseconds: 250), - crossFadeState: currentStep == 3 - ? CrossFadeState.showSecond - : CrossFadeState.showFirst, + crossFadeState: + currentStep == 3 + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, firstChild: PrimaryButton( label: "Next", enabled: currentStep != 2 ? true : enableNext, @@ -394,9 +382,7 @@ class _StepScaffoldState extends ConsumerState { "Send ${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendAmount.toStringAsFixed(8)))} ${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendTicker))} to this address", style: STextStyles.desktopH3(context), ), - const SizedBox( - height: 48, - ), + const SizedBox(height: 48), Center( child: QR( // TODO: grab coin uri scheme from somewhere @@ -409,9 +395,7 @@ class _StepScaffoldState extends ConsumerState { size: 290, ), ), - const SizedBox( - height: 48, - ), + const SizedBox(height: 48), SecondaryButton( label: "Cancel", width: 310, diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart index fd7c72a23..c7ba641ca 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart @@ -59,23 +59,23 @@ class _DesktopStep2State extends ConsumerState { void selectRecipientAddressFromStack() async { try { - final coin = AppConfig.getCryptoCurrencyForTicker( - ref.read(desktopExchangeModelProvider)!.receiveTicker, - )!; + final coin = + AppConfig.getCryptoCurrencyForTicker( + ref.read(desktopExchangeModelProvider)!.receiveTicker, + )!; final info = await showDialog?>( context: context, barrierColor: Colors.transparent, - builder: (context) => DesktopDialog( - maxWidth: 720, - maxHeight: 670, - child: Padding( - padding: const EdgeInsets.all(32), - child: DesktopChooseFromStack( - coin: coin, + builder: + (context) => DesktopDialog( + maxWidth: 720, + maxHeight: 670, + child: Padding( + padding: const EdgeInsets.all(32), + child: DesktopChooseFromStack(coin: coin), + ), ), - ), - ), ); if (info is Tuple2) { @@ -83,44 +83,40 @@ class _DesktopStep2State extends ConsumerState { ref.read(desktopExchangeModelProvider)!.recipientAddress = info.item2; } } catch (e, s) { - Logging.instance.i("$e\n$s", error: e, stackTrace: s,); + Logging.instance.i("$e\n$s", error: e, stackTrace: s); } - widget.enableNextChanged.call( - _next(), - ); + widget.enableNextChanged.call(_next()); } void selectRefundAddressFromStack() async { try { - final coin = AppConfig.getCryptoCurrencyForTicker( - ref.read(desktopExchangeModelProvider)!.sendTicker, - )!; + final coin = + AppConfig.getCryptoCurrencyForTicker( + ref.read(desktopExchangeModelProvider)!.sendTicker, + )!; final info = await showDialog?>( context: context, barrierColor: Colors.transparent, - builder: (context) => DesktopDialog( - maxWidth: 720, - maxHeight: 670, - child: Padding( - padding: const EdgeInsets.all(32), - child: DesktopChooseFromStack( - coin: coin, + builder: + (context) => DesktopDialog( + maxWidth: 720, + maxHeight: 670, + child: Padding( + padding: const EdgeInsets.all(32), + child: DesktopChooseFromStack(coin: coin), + ), ), - ), - ), ); if (info is Tuple2) { _refundController.text = info.item1; ref.read(desktopExchangeModelProvider)!.refundAddress = info.item2; } } catch (e, s) { - Logging.instance.i("$e\n$s", error: e, stackTrace: s,); + Logging.instance.i("$e\n$s", error: e, stackTrace: s); } - widget.enableNextChanged.call( - _next(), - ); + widget.enableNextChanged.call(_next()); } void selectRecipientFromAddressBook() async { @@ -131,43 +127,36 @@ class _DesktopStep2State extends ConsumerState { final entry = await showDialog( context: context, barrierColor: Colors.transparent, - builder: (context) => DesktopDialog( - maxWidth: 720, - maxHeight: 670, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + builder: + (context) => DesktopDialog( + maxWidth: 720, + maxHeight: 670, + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - Padding( - padding: const EdgeInsets.only( - left: 32, - ), - child: Text( - "Address book", - style: STextStyles.desktopH3(context), - ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Address book", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], ), - const DesktopDialogCloseButton(), + Expanded(child: AddressBookAddressChooser(coin: coin)), ], ), - Expanded( - child: AddressBookAddressChooser( - coin: coin, - ), - ), - ], - ), - ), + ), ); if (entry != null) { _toController.text = entry.address; ref.read(desktopExchangeModelProvider)!.recipientAddress = entry.address; - widget.enableNextChanged.call( - _next(), - ); + widget.enableNextChanged.call(_next()); } } @@ -179,43 +168,36 @@ class _DesktopStep2State extends ConsumerState { final entry = await showDialog( context: context, barrierColor: Colors.transparent, - builder: (context) => DesktopDialog( - maxWidth: 720, - maxHeight: 670, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + builder: + (context) => DesktopDialog( + maxWidth: 720, + maxHeight: 670, + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - Padding( - padding: const EdgeInsets.only( - left: 32, - ), - child: Text( - "Address book", - style: STextStyles.desktopH3(context), - ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Address book", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], ), - const DesktopDialogCloseButton(), + Expanded(child: AddressBookAddressChooser(coin: coin)), ], ), - Expanded( - child: AddressBookAddressChooser( - coin: coin, - ), - ), - ], - ), - ), + ), ); if (entry != null) { _refundController.text = entry.address; ref.read(desktopExchangeModelProvider)!.refundAddress = entry.address; - widget.enableNextChanged.call( - _next(), - ); + widget.enableNextChanged.call(_next()); } } @@ -242,33 +224,41 @@ class _DesktopStep2State extends ConsumerState { doesRefundAddress = ref.read(efExchangeProvider).supportsRefundAddress; if (!doesRefundAddress) { - // hack: set to empty to not throw null unwrap error later - ref.read(desktopExchangeModelProvider)!.refundAddress = ""; + WidgetsBinding.instance.addPostFrameCallback((_) { + // hack: set to empty to not throw null unwrap error later + ref.read(desktopExchangeModelProvider)!.refundAddress = ""; + }); } final tuple = ref.read(exchangeSendFromWalletIdStateProvider.state).state; if (tuple != null) { if (ref.read(desktopExchangeModelProvider)!.receiveTicker.toLowerCase() == tuple.item2.ticker.toLowerCase()) { - _toController.text = ref - .read(pWallets) - .getWallet(tuple.item1) - .info - .cachedReceivingAddress; - - ref.read(desktopExchangeModelProvider)!.recipientAddress = - _toController.text; + _toController.text = + ref + .read(pWallets) + .getWallet(tuple.item1) + .info + .cachedReceivingAddress; + + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(desktopExchangeModelProvider)!.recipientAddress = + _toController.text; + }); } else { if (doesRefundAddress && ref.read(desktopExchangeModelProvider)!.sendTicker.toUpperCase() == tuple.item2.ticker.toUpperCase()) { - _refundController.text = ref - .read(pWallets) - .getWallet(tuple.item1) - .info - .cachedReceivingAddress; - ref.read(desktopExchangeModelProvider)!.refundAddress = - _refundController.text; + _refundController.text = + ref + .read(pWallets) + .getWallet(tuple.item1) + .info + .cachedReceivingAddress; + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(desktopExchangeModelProvider)!.refundAddress = + _refundController.text; + }); } } } @@ -297,32 +287,30 @@ class _DesktopStep2State extends ConsumerState { style: STextStyles.desktopTextMedium(context), textAlign: TextAlign.center, ), - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), Text( "Enter your recipient and refund addresses", style: STextStyles.desktopTextExtraExtraSmall(context), textAlign: TextAlign.center, ), - const SizedBox( - height: 20, - ), + const SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Recipient Wallet", style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconRight, + color: + Theme.of( + context, + ).extension()!.textFieldActiveSearchIconRight, ), ), if (AppConfig.isStackCoin( ref.watch( - desktopExchangeModelProvider - .select((value) => value!.receiveTicker), + desktopExchangeModelProvider.select( + (value) => value!.receiveTicker, + ), ), )) CustomTextButton( @@ -331,9 +319,7 @@ class _DesktopStep2State extends ConsumerState { ), ], ), - const SizedBox( - height: 10, - ), + const SizedBox(height: 10), ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -359,9 +345,7 @@ class _DesktopStep2State extends ConsumerState { onChanged: (value) { ref.read(desktopExchangeModelProvider)!.recipientAddress = _toController.text; - widget.enableNextChanged.call( - _next(), - ); + widget.enableNextChanged.call(_next()); }, decoration: standardInputDecoration( "Enter the ${ref.watch(desktopExchangeModelProvider.select((value) => value!.receiveTicker.toUpperCase()))} payout address", @@ -376,57 +360,56 @@ class _DesktopStep2State extends ConsumerState { right: 5, ), suffixIcon: Padding( - padding: _toController.text.isEmpty - ? const EdgeInsets.only(right: 8) - : const EdgeInsets.only(right: 0), + padding: + _toController.text.isEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), child: UnconstrainedBox( child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _toController.text.isNotEmpty ? TextFieldIconButton( - key: const Key( - "sendViewClearAddressFieldButtonKey", - ), - onTap: () { - _toController.text = ""; + key: const Key( + "sendViewClearAddressFieldButtonKey", + ), + onTap: () { + _toController.text = ""; + ref + .read(desktopExchangeModelProvider)! + .recipientAddress = _toController.text; + widget.enableNextChanged.call(_next()); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + key: const Key( + "sendViewPasteAddressFieldButtonKey", + ), + onTap: () async { + final ClipboardData? data = await clipboard + .getData(Clipboard.kTextPlain); + if (data?.text != null && + data!.text!.isNotEmpty) { + final content = data.text!.trim(); + _toController.text = content; ref .read(desktopExchangeModelProvider)! .recipientAddress = _toController.text; - widget.enableNextChanged.call( - _next(), - ); - }, - child: const XIcon(), - ) - : TextFieldIconButton( - key: const Key( - "sendViewPasteAddressFieldButtonKey", - ), - onTap: () async { - final ClipboardData? data = await clipboard - .getData(Clipboard.kTextPlain); - if (data?.text != null && - data!.text!.isNotEmpty) { - final content = data.text!.trim(); - _toController.text = content; - ref - .read(desktopExchangeModelProvider)! - .recipientAddress = _toController.text; - widget.enableNextChanged.call( - _next(), - ); - } - }, - child: _toController.text.isEmpty - ? const ClipboardIcon() - : const XIcon(), - ), + widget.enableNextChanged.call(_next()); + } + }, + child: + _toController.text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), if (_toController.text.isEmpty && AppConfig.isStackCoin( ref.watch( - desktopExchangeModelProvider - .select((value) => value!.receiveTicker), + desktopExchangeModelProvider.select( + (value) => value!.receiveTicker, + ), ), )) TextFieldIconButton( @@ -441,9 +424,7 @@ class _DesktopStep2State extends ConsumerState { ), ), ), - const SizedBox( - height: 10, - ), + const SizedBox(height: 10), RoundedWhiteContainer( borderColor: Theme.of(context).extension()!.background, child: Text( @@ -451,10 +432,7 @@ class _DesktopStep2State extends ConsumerState { style: STextStyles.desktopTextExtraExtraSmall(context), ), ), - if (doesRefundAddress) - const SizedBox( - height: 24, - ), + if (doesRefundAddress) const SizedBox(height: 24), if (doesRefundAddress) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -462,15 +440,17 @@ class _DesktopStep2State extends ConsumerState { Text( "Refund Wallet (required)", style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconRight, + color: + Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, ), ), if (AppConfig.isStackCoin( ref.watch( - desktopExchangeModelProvider - .select((value) => value!.sendTicker), + desktopExchangeModelProvider.select( + (value) => value!.sendTicker, + ), ), )) CustomTextButton( @@ -479,10 +459,7 @@ class _DesktopStep2State extends ConsumerState { ), ], ), - if (doesRefundAddress) - const SizedBox( - height: 10, - ), + if (doesRefundAddress) const SizedBox(height: 10), if (doesRefundAddress) ClipRRect( borderRadius: BorderRadius.circular( @@ -508,9 +485,7 @@ class _DesktopStep2State extends ConsumerState { onChanged: (value) { ref.read(desktopExchangeModelProvider)!.refundAddress = _refundController.text; - widget.enableNextChanged.call( - _next(), - ); + widget.enableNextChanged.call(_next()); }, decoration: standardInputDecoration( "Enter ${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendTicker.toUpperCase()))} refund address", @@ -525,60 +500,59 @@ class _DesktopStep2State extends ConsumerState { right: 5, ), suffixIcon: Padding( - padding: _refundController.text.isEmpty - ? const EdgeInsets.only(right: 16) - : const EdgeInsets.only(right: 0), + padding: + _refundController.text.isEmpty + ? const EdgeInsets.only(right: 16) + : const EdgeInsets.only(right: 0), child: UnconstrainedBox( child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _refundController.text.isNotEmpty ? TextFieldIconButton( - key: const Key( - "sendViewClearAddressFieldButtonKey", - ), - onTap: () { - _refundController.text = ""; + key: const Key( + "sendViewClearAddressFieldButtonKey", + ), + onTap: () { + _refundController.text = ""; + ref + .read(desktopExchangeModelProvider)! + .refundAddress = _refundController.text; + + widget.enableNextChanged.call(_next()); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + key: const Key( + "sendViewPasteAddressFieldButtonKey", + ), + onTap: () async { + final ClipboardData? data = await clipboard + .getData(Clipboard.kTextPlain); + if (data?.text != null && + data!.text!.isNotEmpty) { + final content = data.text!.trim(); + + _refundController.text = content; ref .read(desktopExchangeModelProvider)! .refundAddress = _refundController.text; - widget.enableNextChanged.call( - _next(), - ); - }, - child: const XIcon(), - ) - : TextFieldIconButton( - key: const Key( - "sendViewPasteAddressFieldButtonKey", - ), - onTap: () async { - final ClipboardData? data = await clipboard - .getData(Clipboard.kTextPlain); - if (data?.text != null && - data!.text!.isNotEmpty) { - final content = data.text!.trim(); - - _refundController.text = content; - ref - .read(desktopExchangeModelProvider)! - .refundAddress = _refundController.text; - - widget.enableNextChanged.call( - _next(), - ); - } - }, - child: _refundController.text.isEmpty - ? const ClipboardIcon() - : const XIcon(), - ), + widget.enableNextChanged.call(_next()); + } + }, + child: + _refundController.text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), if (_refundController.text.isEmpty && AppConfig.isStackCoin( ref.watch( - desktopExchangeModelProvider - .select((value) => value!.sendTicker), + desktopExchangeModelProvider.select( + (value) => value!.sendTicker, + ), ), )) TextFieldIconButton( @@ -593,10 +567,7 @@ class _DesktopStep2State extends ConsumerState { ), ), ), - if (doesRefundAddress) - const SizedBox( - height: 10, - ), + if (doesRefundAddress) const SizedBox(height: 10), if (doesRefundAddress) RoundedWhiteContainer( borderColor: Theme.of(context).extension()!.background, diff --git a/lib/services/exchange/change_now/change_now_api.dart b/lib/services/exchange/change_now/change_now_api.dart index d7cf255d5..7b5cd0737 100644 --- a/lib/services/exchange/change_now/change_now_api.dart +++ b/lib/services/exchange/change_now/change_now_api.dart @@ -12,18 +12,14 @@ import 'dart:convert'; import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; -import 'package:tuple/tuple.dart'; import '../../../exceptions/exchange/exchange_exception.dart'; import '../../../exceptions/exchange/pair_unavailable_exception.dart'; -import '../../../exceptions/exchange/unsupported_currency_exception.dart'; import '../../../external_api_keys.dart'; -import '../../../models/exchange/change_now/cn_exchange_estimate.dart'; +import '../../../models/exchange/change_now/cn_exchange_transaction.dart'; +import '../../../models/exchange/change_now/cn_exchange_transaction_status.dart'; import '../../../models/exchange/change_now/estimated_exchange_amount.dart'; -import '../../../models/exchange/change_now/exchange_transaction.dart'; -import '../../../models/exchange/change_now/exchange_transaction_status.dart'; import '../../../models/exchange/response_objects/estimate.dart'; -import '../../../models/exchange/response_objects/fixed_rate_market.dart'; import '../../../models/exchange/response_objects/range.dart'; import '../../../models/isar/exchange_cache/currency.dart'; import '../../../models/isar/exchange_cache/pair.dart'; @@ -34,10 +30,25 @@ import '../../tor_service.dart'; import '../exchange_response.dart'; import 'change_now_exchange.dart'; +enum CNFlow { + standard("standard"), + fixedRate("fixed-rate"); + + const CNFlow(this.value); + final String value; +} + +enum CNExchangeType { + direct("direct"), + reverse("reverse"); + + const CNExchangeType(this.value); + final String value; +} + class ChangeNowAPI { static const String scheme = "https"; static const String authority = "api.changenow.io"; - static const String apiVersion = "/v1"; static const String apiVersionV2 = "/v2"; final HTTP client; @@ -48,56 +59,24 @@ class ChangeNowAPI { static final ChangeNowAPI _instance = ChangeNowAPI(); static ChangeNowAPI get instance => _instance; - Uri _buildUri(String path, Map? params) { - return Uri.https(authority, apiVersion + path, params); - } - Uri _buildUriV2(String path, Map? params) { return Uri.https(authority, apiVersionV2 + path, params); } - Future _makeGetRequest(Uri uri) async { - try { - final response = await client.get( - url: uri, - headers: {'Content-Type': 'application/json'}, - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, - ); - String? data; - try { - data = response.body; - final parsed = jsonDecode(data); - - return parsed; - } on FormatException catch (e) { - return { - "error": "Dart format exception", - "message": data, - }; - } - } catch (e, s) { - Logging.instance.e( - "_makeRequest($uri) threw", - error: e, - stackTrace: s, - ); - rethrow; - } - } - Future _makeGetRequestV2(Uri uri, String apiKey) async { + Logging.instance.t("ChangeNOW _makeGetRequestV2 to $uri"); + try { final response = await client.get( url: uri, headers: { - // 'Content-Type': 'application/json', - 'x-changenow-api-key': apiKey, + "Content-Type": "application/json", + "x-changenow-api-key": apiKey, }, - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, + proxyInfo: + Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, ); final data = response.body; @@ -105,27 +84,29 @@ class ChangeNowAPI { return parsed; } catch (e, s) { - Logging.instance.e( - "_makeRequestV2($uri) threw", - error: e, - stackTrace: s, - ); + Logging.instance.e("_makeRequestV2($uri) threw", error: e, stackTrace: s); rethrow; } } - Future _makePostRequest( + Future _makePostRequestV2( Uri uri, Map body, + String apiKey, ) async { + Logging.instance.t("ChangeNOW _makePostRequestV2 to $uri"); try { final response = await client.post( url: uri, - headers: {'Content-Type': 'application/json'}, + headers: { + "Content-Type": "application/json", + "x-changenow-api-key": apiKey, + }, body: jsonEncode(body), - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, + proxyInfo: + Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, ); String? data; @@ -144,7 +125,7 @@ class ChangeNowAPI { } } catch (e, s) { Logging.instance.e( - "_makePostRequest($uri) threw", + "_makePostRequestV2($uri) threw", error: e, stackTrace: s, ); @@ -152,129 +133,41 @@ class ChangeNowAPI { } } - /// This API endpoint returns the list of available currencies. + /// Retrieves the list of available currencies from the API. /// - /// Set [active] to true to return only active currencies. - /// Set [fixedRate] to true to return only currencies available on a fixed-rate flow. + /// - Set [active] to `true` to return only active currencies. + /// - Set [buy] to `true` to return only currencies available for buying. + /// - Set [sell] to `true` to return only currencies available for selling. + /// - Set [flow] to specify the type of exchange flow. + /// Options are [CNFlow.standard] (default) or [CNFlow.fixedRate]. Future>> getAvailableCurrencies({ - bool? fixedRate, bool? active, + bool? buy, + bool? sell, + CNFlow flow = CNFlow.standard, + String? apiKey, }) async { - Map? params; - - if (active != null || fixedRate != null) { - params = {}; - if (fixedRate != null) { - params.addAll({"fixedRate": fixedRate.toString()}); - } - if (active != null) { - params.addAll({"active": active.toString()}); - } - } - - final uri = _buildUri("/currencies", params); - - try { - // json array is expected here - final jsonArray = await _makeGetRequest(uri); - - try { - final result = await compute( - _parseAvailableCurrenciesJson, - Tuple2(jsonArray as List, fixedRate == true), - ); - return result; - } catch (e, s) { - Logging.instance.e( - "getAvailableCurrencies exception: ", - error: e, - stackTrace: s, - ); - return ExchangeResponse( - exception: ExchangeException( - "Error: $jsonArray", - ExchangeExceptionType.serializeResponseError, - ), - ); - } - } catch (e, s) { - Logging.instance.e( - "getAvailableCurrencies exception: ", - error: e, - stackTrace: s, - ); - return ExchangeResponse( - exception: ExchangeException( - e.toString(), - ExchangeExceptionType.generic, - ), - ); - } - } - - ExchangeResponse> _parseAvailableCurrenciesJson( - Tuple2, bool> args, - ) { - try { - final List currencies = []; - - for (final json in args.item1) { - try { - final map = Map.from(json as Map); - currencies.add( - Currency.fromJson( - map, - rateType: (map["supportsFixedRate"] as bool) - ? SupportedRateType.both - : SupportedRateType.estimated, - exchangeName: ChangeNowExchange.exchangeName, - ), - ); - } catch (_) { - return ExchangeResponse( - exception: ExchangeException( - "Failed to serialize $json", - ExchangeExceptionType.serializeResponseError, - ), - ); - } - } - - return ExchangeResponse(value: currencies); - } catch (_) { - rethrow; - } - } - - Future>> getCurrenciesV2( - // { - // bool? fixedRate, - // bool? active, - // } - ) async { - Map? params; - - // if (active != null || fixedRate != null) { - // params = {}; - // if (fixedRate != null) { - // params.addAll({"fixedRate": fixedRate.toString()}); - // } - // if (active != null) { - // params.addAll({"active": active.toString()}); - // } - // } + final params = { + "flow": flow.value, + if (active != null) "active": active.toString(), + if (buy != null) "buy": buy.toString(), + if (sell != null) "sell": sell.toString(), + }; final uri = _buildUriV2("/exchange/currencies", params); try { // json array is expected here - final jsonArray = await _makeGetRequest(uri); + final jsonArray = await _makeGetRequestV2( + uri, + apiKey ?? kChangeNowApiKey, + ); try { - final result = await compute( - _parseV2CurrenciesJson, - jsonArray as List, - ); + final result = await compute(_parseAvailableCurrenciesJson, ( + jsonList: jsonArray as List, + fixedRateFlow: flow == CNFlow.fixedRate, + )); return result; } catch (e, s) { Logging.instance.e( @@ -304,21 +197,22 @@ class ChangeNowAPI { } } - ExchangeResponse> _parseV2CurrenciesJson( - List args, + ExchangeResponse> _parseAvailableCurrenciesJson( + ({List jsonList, bool fixedRateFlow}) args, ) { try { final List currencies = []; - for (final json in args) { + for (final json in args.jsonList) { try { final map = Map.from(json as Map); currencies.add( Currency.fromJson( map, - rateType: (map["supportsFixedRate"] as bool) - ? SupportedRateType.both - : SupportedRateType.estimated, + rateType: + (map["supportsFixedRate"] as bool) + ? SupportedRateType.both + : SupportedRateType.estimated, exchangeName: ChangeNowExchange.exchangeName, ), ); @@ -338,110 +232,37 @@ class ChangeNowAPI { } } - /// This API endpoint returns the array of markets available for the specified currency be default. - /// The availability of a particular pair is determined by the 'isAvailable' field. + /// Retrieves the minimum amount required to exchange [fromCurrency] to [toCurrency]. /// - /// Required [ticker] to fetch paired currencies for. - /// Set [fixedRate] to true to return only currencies available on a fixed-rate flow. - Future>> getPairedCurrencies({ - required String ticker, - bool? fixedRate, - }) async { - Map? params; - - if (fixedRate != null) { - params = {}; - params.addAll({"fixedRate": fixedRate.toString()}); - } - - final uri = _buildUri("/currencies-to/$ticker", params); - - try { - // json array is expected here - - final response = await _makeGetRequest(uri); - - if (response is Map && response["error"] != null) { - return ExchangeResponse( - exception: UnsupportedCurrencyException( - response["message"] as String? ?? response["error"].toString(), - ExchangeExceptionType.generic, - ticker, - ), - ); - } - - final jsonArray = response as List; - - final List currencies = []; - try { - for (final json in jsonArray) { - try { - final map = Map.from(json as Map); - currencies.add( - Currency.fromJson( - map, - rateType: (map["supportsFixedRate"] as bool) - ? SupportedRateType.both - : SupportedRateType.estimated, - exchangeName: ChangeNowExchange.exchangeName, - ), - ); - } catch (_) { - return ExchangeResponse( - exception: ExchangeException( - "Failed to serialize $json", - ExchangeExceptionType.serializeResponseError, - ), - ); - } - } - } catch (e, s) { - Logging.instance.e( - "getPairedCurrencies exception: ", - error: e, - stackTrace: s, - ); - return ExchangeResponse( - exception: ExchangeException( - "Error: $jsonArray", - ExchangeExceptionType.serializeResponseError, - ), - ); - } - return ExchangeResponse(value: currencies); - } catch (e, s) { - Logging.instance.e( - "getPairedCurrencies exception", - error: e, - stackTrace: s, - ); - return ExchangeResponse( - exception: ExchangeException( - e.toString(), - ExchangeExceptionType.generic, - ), - ); - } - } - - /// The API endpoint returns minimal payment amount required to make - /// an exchange of [fromTicker] to [toTicker]. - /// If you try to exchange less, the transaction will most likely fail. + /// If you attempt to exchange less than this amount, the transaction may fail. + /// + /// - [fromCurrency]: Ticker of the currency you want to exchange (e.g., "btc"). + /// - [toCurrency]: Ticker of the currency you want to receive (e.g., "usdt"). + /// - [fromNetwork]: (Optional) Network of the currency you want to exchange (e.g., "btc"). + /// - [toNetwork]: (Optional) Network of the currency you want to receive (e.g., "eth"). + /// - [flow]: (Optional) Exchange flow type. Defaults to [CNFlow.standard]. + /// - [apiKey]: (Optional) API key if required. Future> getMinimalExchangeAmount({ - required String fromTicker, - required String toTicker, + required String fromCurrency, + required String toCurrency, + String? fromNetwork, + String? toNetwork, + CNFlow flow = CNFlow.standard, String? apiKey, }) async { - final Map params = { - "api_key": apiKey ?? kChangeNowApiKey, + final params = { + "fromCurrency": fromCurrency, + "toCurrency": toCurrency, + "flow": flow.value, + if (fromNetwork != null) "fromNetwork": fromNetwork, + if (toNetwork != null) "toNetwork": toNetwork, }; - final uri = _buildUri("/min-amount/${fromTicker}_$toTicker", params); + final uri = _buildUriV2("/exchange/min-amount", params); try { // simple json object is expected here - final json = await _makeGetRequest(uri); + final json = await _makeGetRequestV2(uri, apiKey ?? kChangeNowApiKey); try { final value = Decimal.parse(json["minAmount"].toString()); @@ -469,27 +290,40 @@ class ChangeNowAPI { } } - /// The API endpoint returns minimal payment amount and maximum payment amount - /// required to make an exchange. If you try to exchange less than minimum or - /// more than maximum, the transaction will most likely fail. Any pair of - /// assets has minimum amount and some of pairs have maximum amount. + /// Retrieves the minimum and maximum exchangeable amounts for a given currency pair. + /// + /// Attempting to exchange less than the minimum or more than the maximum may result in a failed transaction. + /// Every asset pair has a minimum exchange amount, and some also have a maximum. + /// + /// - [fromCurrency]: Ticker of the currency you want to exchange (e.g., "btc"). + /// - [toCurrency]: Ticker of the currency you want to receive (e.g., "eth"). + /// - [fromNetwork]: (Optional) Network of the currency you want to exchange (e.g., "btc"). + /// - [toNetwork]: (Optional) Network of the currency you want to receive (e.g., "eth"). + /// - [flow]: (Optional) Type of exchange flow. Defaults to [CNFlow.standard]. + /// - [apiKey]: (Optional) API key if required. Future> getRange({ - required String fromTicker, - required String toTicker, - required bool isFixedRate, + required String fromCurrency, + required String toCurrency, + String? fromNetwork, + String? toNetwork, + CNFlow flow = CNFlow.standard, String? apiKey, }) async { - final Map params = { - "api_key": apiKey ?? kChangeNowApiKey, + final params = { + "fromCurrency": fromCurrency, + "toCurrency": toCurrency, + "flow": flow.value, + if (fromNetwork != null) "fromNetwork": fromNetwork, + if (toNetwork != null) "toNetwork": toNetwork, }; - final uri = _buildUri( - "/exchange-range${isFixedRate ? "/fixed-rate" : ""}/${fromTicker}_$toTicker", - params, - ); + final uri = _buildUriV2("/exchange/range", params); try { - final jsonObject = await _makeGetRequest(uri); + final jsonObject = await _makeGetRequestV2( + uri, + apiKey ?? kChangeNowApiKey, + ); final json = Map.from(jsonObject as Map); return ExchangeResponse( @@ -499,11 +333,7 @@ class ChangeNowAPI { ), ); } catch (e, s) { - Logging.instance.e( - "getRange exception: ", - error: e, - stackTrace: s, - ); + Logging.instance.f("getRange exception: ", error: e, stackTrace: s); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -513,24 +343,50 @@ class ChangeNowAPI { } } - /// Get estimated amount of [toTicker] cryptocurrency to receive - /// for [fromAmount] of [fromTicker] + /// Retrieves an estimated amount of [toCurrency] you would receive for a given input amount of [fromCurrency]. + /// + /// - [fromCurrency]: Ticker of the currency you want to exchange (e.g., "btc"). + /// - [toCurrency]: Ticker of the currency you want to receive (e.g., "eth"). + /// - [fromAmount]: (Required if [type] is [CNExchangeType.direct]) Amount to exchange. Must be greater than 0. + /// - [toAmount]: (Required if [type] is [CNExchangeType.reverse]) Desired amount to receive. Must be greater than 0. + /// - [fromNetwork]: (Optional) Network of the currency you want to exchange (e.g., "btc"). + /// - [toNetwork]: (Optional) Network of the currency you want to receive (e.g., "eth"). + /// - [flow]: (Optional) Type of exchange flow. Defaults to [CNFlow.standard]. + /// - [type]: (Optional) Exchange direction. Either [CNExchangeType.direct] or [CNExchangeType.reverse]. Defaults to [CNExchangeType.direct]. + /// - [useRateId]: (Optional) For fixed-rate flow. When true, the response includes a [rateId] for locking the rate in the next request. + /// - [isTopUp]: (Optional) If true, gets an estimate for a balance top-up (no withdrawal fee). + /// - [apiKey]: (Optional) API key if required. Future> getEstimatedExchangeAmount({ - required String fromTicker, - required String toTicker, - required Decimal fromAmount, + required String fromCurrency, + required String toCurrency, + Decimal? fromAmount, + Decimal? toAmount, + String? fromNetwork, + String? toNetwork, + CNFlow flow = CNFlow.standard, + CNExchangeType type = CNExchangeType.direct, + bool? useRateId, + bool? isTopUp, String? apiKey, }) async { - final Map params = {"api_key": apiKey ?? kChangeNowApiKey}; + final params = { + "fromCurrency": fromCurrency, + "toCurrency": toCurrency, + if (fromAmount != null) "fromAmount": fromAmount.toString(), + if (toAmount != null) "toAmount": toAmount.toString(), + if (fromNetwork != null) "fromNetwork": fromNetwork, + if (toNetwork != null) "toNetwork": toNetwork, + "flow": flow.value, + "type": type.value, + if (useRateId != null) "useRateId": useRateId.toString(), + if (isTopUp != null) "isTopUp": isTopUp.toString(), + }; - final uri = _buildUri( - "/exchange-amount/${fromAmount.toString()}/${fromTicker}_$toTicker", - params, - ); + final uri = _buildUriV2("/exchange/estimated-amount", params); try { // simple json object is expected here - final json = await _makeGetRequest(uri); + final json = await _makeGetRequestV2(uri, apiKey ?? kChangeNowApiKey); try { final map = Map.from(json as Map); @@ -554,104 +410,19 @@ class ChangeNowAPI { } final value = EstimatedExchangeAmount.fromJson(map); + final reversed = value.type == CNExchangeType.reverse; return ExchangeResponse( value: Estimate( - estimatedAmount: value.estimatedAmount, - fixedRate: false, - reversed: false, - rateId: value.rateId, - warningMessage: value.warningMessage, - exchangeProvider: ChangeNowExchange.exchangeName, - ), - ); - } catch (_) { - return ExchangeResponse( - exception: ExchangeException( - "Failed to serialize $json", - ExchangeExceptionType.serializeResponseError, - ), - ); - } - } catch (e, s) { - Logging.instance.e( - "getEstimatedExchangeAmount exception: ", - error: e, - stackTrace: s, - ); - return ExchangeResponse( - exception: ExchangeException( - e.toString(), - ExchangeExceptionType.generic, - ), - ); - } - } - - /// Get estimated amount of [toTicker] cryptocurrency to receive - /// for [fromAmount] of [fromTicker] - Future> getEstimatedExchangeAmountFixedRate({ - required String fromTicker, - required String toTicker, - required Decimal fromAmount, - required bool reversed, - bool useRateId = true, - String? apiKey, - }) async { - final Map params = { - "api_key": apiKey ?? kChangeNowApiKey, - "useRateId": useRateId.toString(), - }; - - late final Uri uri; - if (reversed) { - uri = _buildUri( - "/exchange-deposit/fixed-rate/${fromAmount.toString()}/${fromTicker}_$toTicker", - params, - ); - } else { - uri = _buildUri( - "/exchange-amount/fixed-rate/${fromAmount.toString()}/${fromTicker}_$toTicker", - params, - ); - } - - try { - // simple json object is expected here - final json = await _makeGetRequest(uri); - - try { - final map = Map.from(json as Map); - - if (map["error"] != null) { - if (map["error"] == "not_valid_fixed_rate_pair") { - return ExchangeResponse( - exception: PairUnavailableException( - map["message"] as String? ?? "Unsupported fixed rate pair", - ExchangeExceptionType.generic, - ), - ); - } else { - return ExchangeResponse( - exception: ExchangeException( - map["message"] as String? ?? map["error"].toString(), - ExchangeExceptionType.generic, - ), - ); - } - } - - final value = EstimatedExchangeAmount.fromJson(map); - return ExchangeResponse( - value: Estimate( - estimatedAmount: value.estimatedAmount, - fixedRate: true, + estimatedAmount: reversed ? value.fromAmount : value.toAmount, + fixedRate: value.flow == CNFlow.fixedRate, reversed: reversed, rateId: value.rateId, warningMessage: value.warningMessage, exchangeProvider: ChangeNowExchange.exchangeName, ), ); - } catch (_) { + } catch (e, s) { + Logging.instance.f(json, error: e, stackTrace: s); return ExchangeResponse( exception: ExchangeException( "Failed to serialize $json", @@ -674,325 +445,100 @@ class ChangeNowAPI { } } - // old v1 version - /// This API endpoint returns fixed-rate estimated exchange amount of - /// [toTicker] cryptocurrency to receive for [fromAmount] of [fromTicker] - // Future> - // getEstimatedFixedRateExchangeAmount({ - // required String fromTicker, - // required String toTicker, - // required Decimal fromAmount, - // // (Optional) Use rateId for fixed-rate flow. If this field is true, you - // // could use returned field "rateId" in next method for creating transaction - // // to freeze estimated amount that you got in this method. Current estimated - // // amount would be valid until time in field "validUntil" - // bool useRateId = true, - // String? apiKey, - // }) async { - // Map params = { - // "api_key": apiKey ?? kChangeNowApiKey, - // "useRateId": useRateId.toString(), - // }; - // - // final uri = _buildUri( - // "/exchange-amount/fixed-rate/${fromAmount.toString()}/${fromTicker}_$toTicker", - // params, - // ); - // - // try { - // // simple json object is expected here - // final json = await _makeGetRequest(uri); - // - // try { - // final value = EstimatedExchangeAmount.fromJson( - // Map.from(json as Map)); - // return ExchangeResponse(value: value); - // } catch (_) { - // return ExchangeResponse( - // exception: ExchangeException( - // "Failed to serialize $json", - // ExchangeExceptionType.serializeResponseError, - // ), - // ); - // } - // } catch (e, s) { - // Logging.instance.log( - // "getEstimatedFixedRateExchangeAmount exception: $e\n$s", - // level: LogLevel.Error); - // return ExchangeResponse( - // exception: ExchangeException( - // e.toString(), - // ExchangeExceptionType.generic, - // ), - // ); - // } - // } - - /// Get estimated amount of [toTicker] cryptocurrency to receive - /// for [fromAmount] of [fromTicker] - Future> getEstimatedExchangeAmountV2({ - required String fromTicker, - required String toTicker, - required CNEstimateType fromOrTo, - required Decimal amount, - String? fromNetwork, - String? toNetwork, - CNFlowType flow = CNFlowType.standard, + /// Creates a new exchange transaction. + /// + /// This method initializes a currency exchange by specifying the source and destination + /// currencies, the exchange direction, recipient details, and optional metadata. + /// + /// If using a fixed-rate flow, you **must** provide a [rateId] obtained from a prior estimate + /// to lock in the rate. If using a standard flow, [rateId] can be left null. + /// + /// Parameters: + /// - [fromCurrency]: Ticker of the currency you want to exchange (e.g., "btc"). + /// - [fromNetwork]: Network of the currency you want to exchange (e.g., "btc"). + /// - [toCurrency]: Ticker of the currency you want to receive (e.g., "usdt"). + /// - [toNetwork]: Network of the currency you want to receive (e.g., "eth"). + /// - [fromAmount]: Amount of currency you want to exchange (used in "direct" flow). + /// - [toAmount]: Amount of currency you want to receive (used in "reverse" flow). + /// - [flow]: Type of exchange flow. Either [CNFlow.standard] or [CNFlow.fixedRate]. + /// - [type]: Direction of the exchange. Use [CNExchangeType.direct] to define the amount to send, + /// or [CNExchangeType.reverse] to define the amount to receive. + /// - [address]: Wallet address that will receive the exchanged funds. + /// - [extraId]: (Optional) Extra ID required by some currencies (e.g., memo, tag). + /// - [refundAddress]: (Optional) Address used to refund funds in case of timeout or failure. + /// - [refundExtraId]: (Optional) Extra ID for the refund address if required. + /// - [userId]: (Optional) Internal user identifier for partners with special access. + /// - [payload]: (Optional) Arbitrary string to store additional context for the transaction. + /// - [contactEmail]: (Optional) Email address to contact the user in case of issues. + /// - [rateId]: (Required for fixed-rate) The rate ID returned from the estimate step to freeze the exchange rate. + /// - [apiKey]: (Optional) Your API key, if authentication is required. + /// + /// Returns a [Future] resolving to [ExchangeResponse] containing the created [ExchangeTransaction]. + Future> createExchangeTransaction({ + required String fromCurrency, + required String fromNetwork, + required String toCurrency, + required String toNetwork, + Decimal? fromAmount, + Decimal? toAmount, + CNFlow flow = CNFlow.standard, + CNExchangeType type = CNExchangeType.direct, + required String address, + String? extraId, + String? refundAddress, + String? refundExtraId, + String? userId, + String? payload, + String? contactEmail, + required String? rateId, String? apiKey, }) async { - final Map params = { - "fromCurrency": fromTicker, - "toCurrency": toTicker, + final Map body = { + "fromCurrency": fromCurrency, + "fromNetwork": fromNetwork ?? "", + "toCurrency": toCurrency, + "toNetwork": toNetwork ?? "", + "fromAmount": fromAmount?.toString() ?? "", + "toAmount": toAmount?.toString() ?? "", "flow": flow.value, - "type": fromOrTo.name, + "type": type.value, + "address": address, + "extraId": extraId ?? "", + "refundAddress": refundAddress ?? "", + "refundExtraId": refundExtraId ?? "", + "userId": userId ?? "", + "payload": payload ?? "", + "contactEmail": contactEmail ?? "", + "rateId": rateId ?? "", }; - switch (fromOrTo) { - case CNEstimateType.direct: - params["fromAmount"] = amount.toString(); - break; - case CNEstimateType.reverse: - params["toAmount"] = amount.toString(); - break; - } - - if (fromNetwork != null) { - params["fromNetwork"] = fromNetwork; - } - - if (toNetwork != null) { - params["toNetwork"] = toNetwork; - } - - if (flow == CNFlowType.fixedRate) { - params["useRateId"] = "true"; - } - - final uri = _buildUriV2("/exchange/estimated-amount", params); + final uri = _buildUriV2("/exchange", null); try { // simple json object is expected here - final json = await _makeGetRequestV2(uri, apiKey ?? kChangeNowApiKey); - - try { - final value = - CNExchangeEstimate.fromJson(Map.from(json as Map)); - return ExchangeResponse(value: value); - } catch (_) { - return ExchangeResponse( - exception: ExchangeException( - "Failed to serialize $json", - ExchangeExceptionType.serializeResponseError, - ), - ); - } - } catch (e, s) { - Logging.instance.e( - "getEstimatedExchangeAmountV2 exception: ", - error: e, - stackTrace: s, - ); - return ExchangeResponse( - exception: ExchangeException( - e.toString(), - ExchangeExceptionType.generic, - ), + final json = await _makePostRequestV2( + uri, + body, + apiKey ?? kChangeNowApiKey, ); - } - } - - /// This API endpoint returns the list of all the pairs available on a - /// fixed-rate flow. Some currencies get enabled or disabled from time to - /// time and the market info gets updates, so make sure to refresh the list - /// occasionally. One time per minute is sufficient. - Future>> getAvailableFixedRateMarkets({ - String? apiKey, - }) async { - final uri = _buildUri( - "/market-info/fixed-rate/${apiKey ?? kChangeNowApiKey}", - null, - ); - try { - // json array is expected here - final jsonArray = await _makeGetRequest(uri); + json["date"] = DateTime.now().toIso8601String(); try { - final result = - await compute(_parseFixedRateMarketsJson, jsonArray as List); - return result; - } catch (e, s) { - Logging.instance.e( - "getAvailableFixedRateMarkets exception: ", - error: e, - stackTrace: s, - ); - return ExchangeResponse( - exception: ExchangeException( - "Error: $jsonArray", - ExchangeExceptionType.serializeResponseError, - ), + final value = CNExchangeTransaction.fromJson( + Map.from(json as Map), ); - } - } catch (e, s) { - Logging.instance.e( - "getAvailableFixedRateMarkets exception: ", - error: e, - stackTrace: s, - ); - return ExchangeResponse( - exception: ExchangeException( - e.toString(), - ExchangeExceptionType.generic, - ), - ); - } - } - - ExchangeResponse> _parseFixedRateMarketsJson( - List jsonArray, - ) { - try { - final List markets = []; - for (final json in jsonArray) { - try { - markets.add( - FixedRateMarket.fromMap(Map.from(json as Map)), - ); - } catch (_) { + return ExchangeResponse(value: value); + } catch (e, s) { + if (json["error"] == "rate_id_not_found_or_expired") { return ExchangeResponse( exception: ExchangeException( - "Failed to serialize $json", + "Rate ID not found or expired", ExchangeExceptionType.serializeResponseError, ), ); } - } - return ExchangeResponse(value: markets); - } catch (_) { - rethrow; - } - } - - /// The API endpoint creates a transaction, generates an address for - /// sending funds and returns transaction attributes. - Future> - createStandardExchangeTransaction({ - required String fromTicker, - required String toTicker, - required String receivingAddress, - required Decimal amount, - String extraId = "", - String userId = "", - String contactEmail = "", - String refundAddress = "", - String refundExtraId = "", - String? apiKey, - }) async { - final Map map = { - "from": fromTicker, - "to": toTicker, - "address": receivingAddress, - "amount": amount.toString(), - "flow": "standard", - "extraId": extraId, - "userId": userId, - "contactEmail": contactEmail, - "refundAddress": refundAddress, - "refundExtraId": refundExtraId, - }; - - final uri = _buildUri("/transactions/${apiKey ?? kChangeNowApiKey}", null); - - try { - // simple json object is expected here - final json = await _makePostRequest(uri, map); - - // pass in date to prevent using default 1970 date - json["date"] = DateTime.now().toString(); - - try { - final value = ExchangeTransaction.fromJson( - Map.from(json as Map), - ); - return ExchangeResponse(value: value); - } catch (_) { - return ExchangeResponse( - exception: ExchangeException( - "Failed to serialize $json", - ExchangeExceptionType.serializeResponseError, - ), - ); - } - } catch (e, s) { - Logging.instance.e( - "createStandardExchangeTransaction exception: ", - error: e, - stackTrace: s, - ); - return ExchangeResponse( - exception: ExchangeException( - e.toString(), - ExchangeExceptionType.generic, - ), - ); - } - } - - /// The API endpoint creates a transaction, generates an address for - /// sending funds and returns transaction attributes. - Future> - createFixedRateExchangeTransaction({ - required String fromTicker, - required String toTicker, - required String receivingAddress, - required Decimal amount, - required String rateId, - required bool reversed, - String extraId = "", - String userId = "", - String contactEmail = "", - String refundAddress = "", - String refundExtraId = "", - String? apiKey, - }) async { - final Map map = { - "from": fromTicker, - "to": toTicker, - "address": receivingAddress, - "flow": "fixed-rate", - "extraId": extraId, - "userId": userId, - "contactEmail": contactEmail, - "refundAddress": refundAddress, - "refundExtraId": refundExtraId, - "rateId": rateId, - }; - - if (reversed) { - map["result"] = amount.toString(); - } else { - map["amount"] = amount.toString(); - } - - final uri = _buildUri( - "/transactions/fixed-rate${reversed ? "/from-result" : ""}/${apiKey ?? kChangeNowApiKey}", - null, - ); - - try { - // simple json object is expected here - final json = await _makePostRequest(uri, map); - - // pass in date to prevent using default 1970 date - json["date"] = DateTime.now().toString(); - - try { - final value = ExchangeTransaction.fromJson( - Map.from(json as Map), - ); - return ExchangeResponse(value: value); - } catch (_) { + Logging.instance.f(json, error: e, stackTrace: s); return ExchangeResponse( exception: ExchangeException( "Failed to serialize $json", @@ -1015,23 +561,23 @@ class ChangeNowAPI { } } - Future> getTransactionStatus({ + Future> getTransactionStatus({ required String id, String? apiKey, }) async { - final uri = - _buildUri("/transactions/$id/${apiKey ?? kChangeNowApiKey}", null); + final uri = _buildUriV2("/exchange/by-id", {"id": id}); try { // simple json object is expected here - final json = await _makeGetRequest(uri); + final json = await _makeGetRequestV2(uri, apiKey ?? kChangeNowApiKey); try { - final value = ExchangeTransactionStatus.fromJson( + final value = CNExchangeTransactionStatus.fromMap( Map.from(json as Map), ); return ExchangeResponse(value: value); - } catch (_) { + } catch (e, s) { + Logging.instance.f(json, error: e, stackTrace: s); return ExchangeResponse( exception: ExchangeException( "Failed to serialize $json", @@ -1053,81 +599,4 @@ class ChangeNowAPI { ); } } - - Future>> getAvailableFloatingRatePairs({ - bool includePartners = false, - }) async { - final uri = _buildUri( - "/market-info/available-pairs", - {"includePartners": includePartners.toString()}, - ); - - try { - // json array is expected here - final jsonArray = await _makeGetRequest(uri); - - try { - final result = await compute( - _parseAvailableFloatingRatePairsJson, - jsonArray as List, - ); - return result; - } catch (e, s) { - Logging.instance.e( - "getAvailableFloatingRatePairs exception: ", - error: e, - stackTrace: s, - ); - return ExchangeResponse( - exception: ExchangeException( - "Error: $jsonArray", - ExchangeExceptionType.serializeResponseError, - ), - ); - } - } catch (e, s) { - Logging.instance.e( - "getAvailableFloatingRatePairs exception: ", - error: e, - stackTrace: s, - ); - return ExchangeResponse( - exception: ExchangeException( - e.toString(), - ExchangeExceptionType.generic, - ), - ); - } - } - - ExchangeResponse> _parseAvailableFloatingRatePairsJson( - List jsonArray, - ) { - try { - final List pairs = []; - for (final json in jsonArray) { - try { - final List stringPair = (json as String).split("_"); - pairs.add( - Pair( - exchangeName: ChangeNowExchange.exchangeName, - from: stringPair[0], - to: stringPair[1], - rateType: SupportedRateType.estimated, - ), - ); - } catch (_) { - return ExchangeResponse( - exception: ExchangeException( - "Failed to serialize $json", - ExchangeExceptionType.serializeResponseError, - ), - ); - } - } - return ExchangeResponse(value: pairs); - } catch (_) { - rethrow; - } - } } diff --git a/lib/services/exchange/change_now/change_now_exchange.dart b/lib/services/exchange/change_now/change_now_exchange.dart index 35e8e30b2..48389afeb 100644 --- a/lib/services/exchange/change_now/change_now_exchange.dart +++ b/lib/services/exchange/change_now/change_now_exchange.dart @@ -9,16 +9,16 @@ */ import 'package:decimal/decimal.dart'; -import '../../../models/exchange/change_now/exchange_transaction.dart'; +import 'package:uuid/uuid.dart'; + import '../../../models/exchange/response_objects/estimate.dart'; import '../../../models/exchange/response_objects/range.dart'; import '../../../models/exchange/response_objects/trade.dart'; import '../../../models/isar/exchange_cache/currency.dart'; import '../../../models/isar/exchange_cache/pair.dart'; -import 'change_now_api.dart'; import '../exchange.dart'; import '../exchange_response.dart'; -import 'package:uuid/uuid.dart'; +import 'change_now_api.dart'; class ChangeNowExchange extends Exchange { ChangeNowExchange._(); @@ -35,6 +35,8 @@ class ChangeNowExchange extends Exchange { Future> createTrade({ required String from, required String to, + String? fromNetwork, + String? toNetwork, required bool fixedRate, required Decimal amount, required String addressTo, @@ -44,45 +46,35 @@ class ChangeNowExchange extends Exchange { Estimate? estimate, required bool reversed, }) async { - late final ExchangeResponse response; - if (fixedRate) { - response = await ChangeNowAPI.instance.createFixedRateExchangeTransaction( - fromTicker: from, - toTicker: to, - receivingAddress: addressTo, - amount: amount, - rateId: estimate!.rateId!, - extraId: extraId ?? "", - refundAddress: addressRefund, - refundExtraId: refundExtraId, - reversed: reversed, - ); - } else { - response = await ChangeNowAPI.instance.createStandardExchangeTransaction( - fromTicker: from, - toTicker: to, - receivingAddress: addressTo, - amount: amount, - extraId: extraId ?? "", - refundAddress: addressRefund, - refundExtraId: refundExtraId, - ); - } + final response = await ChangeNowAPI.instance.createExchangeTransaction( + fromCurrency: from, + fromNetwork: fromNetwork ?? "", + toCurrency: to, + toNetwork: toNetwork ?? "", + address: addressTo, + rateId: estimate?.rateId, + refundAddress: addressRefund, + refundExtraId: refundExtraId, + fromAmount: reversed ? null : amount, + toAmount: reversed ? amount : null, + flow: fixedRate ? CNFlow.fixedRate : CNFlow.standard, + type: reversed ? CNExchangeType.reverse : CNExchangeType.direct, + ); + if (response.exception != null) { return ExchangeResponse(exception: response.exception); } - final statusResponse = await ChangeNowAPI.instance - .getTransactionStatus(id: response.value!.id); + final statusResponse = await ChangeNowAPI.instance.getTransactionStatus( + id: response.value!.id, + ); if (statusResponse.exception != null) { return ExchangeResponse(exception: statusResponse.exception); } return ExchangeResponse( - value: Trade.fromExchangeTransaction( - response.value!.copyWith( - statusObject: statusResponse.value!, - ), + value: Trade.fromCNExchangeTransaction( + response.value!.copyWithStatus(statusResponse.value!), reversed, ), ); @@ -92,75 +84,70 @@ class ChangeNowExchange extends Exchange { Future>> getAllCurrencies( bool fixedRate, ) async { - return await ChangeNowAPI.instance.getCurrenciesV2(); - // return await ChangeNowAPI.instance.getAvailableCurrencies( - // fixedRate: fixedRate ? true : null, - // active: true, - // ); - } - - @override - Future>> getPairedCurrencies( - String forCurrency, - bool fixedRate, - ) async { - return await ChangeNowAPI.instance.getPairedCurrencies( - ticker: forCurrency, - fixedRate: fixedRate, - ); - } - - @override - Future>> getAllPairs(bool fixedRate) async { - if (fixedRate) { - final markets = - await ChangeNowAPI.instance.getAvailableFixedRateMarkets(); - - if (markets.value == null) { - return ExchangeResponse(exception: markets.exception); - } - - final List pairs = []; - for (final market in markets.value!) { - pairs.add( - Pair( - exchangeName: ChangeNowExchange.exchangeName, - from: market.from, - to: market.to, - rateType: SupportedRateType.fixed, - ), - ); - } - return ExchangeResponse(value: pairs); - } else { - return await ChangeNowAPI.instance.getAvailableFloatingRatePairs(); - } + return await ChangeNowAPI.instance.getAvailableCurrencies(); } + // + // @override + // Future>> getPairedCurrencies( + // String forCurrency, + // bool fixedRate, + // ) async { + // return await ChangeNowAPI.instance.getPairedCurrencies( + // ticker: forCurrency, + // fixedRate: fixedRate, + // ); + // } + // + // @override + // Future>> getAllPairs(bool fixedRate) async { + // if (fixedRate) { + // final markets = + // await ChangeNowAPI.instance.getAvailableFixedRateMarkets(); + // + // if (markets.value == null) { + // return ExchangeResponse(exception: markets.exception); + // } + // + // final List pairs = []; + // for (final market in markets.value!) { + // pairs.add( + // Pair( + // exchangeName: ChangeNowExchange.exchangeName, + // from: market.from, + // to: market.to, + // rateType: SupportedRateType.fixed, + // ), + // ); + // } + // return ExchangeResponse(value: pairs); + // } else { + // return await ChangeNowAPI.instance.getAvailableFloatingRatePairs(); + // } + // } @override Future>> getEstimates( String from, + String? fromNetwork, String to, + String? toNetwork, Decimal amount, bool fixedRate, bool reversed, ) async { - late final ExchangeResponse response; - if (fixedRate) { - response = - await ChangeNowAPI.instance.getEstimatedExchangeAmountFixedRate( - fromTicker: from, - toTicker: to, - fromAmount: amount, - reversed: reversed, - ); - } else { - response = await ChangeNowAPI.instance.getEstimatedExchangeAmount( - fromTicker: from, - toTicker: to, - fromAmount: amount, - ); - } + final response = await ChangeNowAPI.instance.getEstimatedExchangeAmount( + fromCurrency: from, + fromNetwork: fromNetwork, + toCurrency: to, + toNetwork: toNetwork, + flow: fixedRate ? CNFlow.fixedRate : CNFlow.standard, + type: reversed ? CNExchangeType.reverse : CNExchangeType.direct, + fromAmount: reversed ? null : amount, + toAmount: reversed ? amount : null, + + useRateId: fixedRate ? true : null, + ); + return ExchangeResponse( value: response.value == null ? null : [response.value!], exception: response.exception, @@ -170,13 +157,17 @@ class ChangeNowExchange extends Exchange { @override Future> getRange( String from, + String? fromNetwork, String to, + String? toNetwork, bool fixedRate, ) async { return await ChangeNowAPI.instance.getRange( - fromTicker: from, - toTicker: to, - isFixedRate: fixedRate, + fromCurrency: from, + fromNetwork: fromNetwork, + toCurrency: to, + toNetwork: toNetwork, + flow: fixedRate ? CNFlow.fixedRate : CNFlow.standard, ); } @@ -191,8 +182,9 @@ class ChangeNowExchange extends Exchange { @override Future> getTrade(String tradeId) async { - final response = - await ChangeNowAPI.instance.getTransactionStatus(id: tradeId); + final response = await ChangeNowAPI.instance.getTransactionStatus( + id: tradeId, + ); if (response.exception != null) { return ExchangeResponse(exception: response.exception); } @@ -207,19 +199,19 @@ class ChangeNowExchange extends Exchange { timestamp: timestamp, updatedAt: DateTime.tryParse(t.updatedAt) ?? timestamp, payInCurrency: t.fromCurrency, - payInAmount: t.expectedSendAmountDecimal, + payInAmount: t.expectedAmountFrom ?? "", payInAddress: t.payinAddress, payInNetwork: "", - payInExtraId: t.payinExtraId, - payInTxid: t.payinHash, + payInExtraId: t.payinExtraId ?? "", + payInTxid: t.payinHash ?? "", payOutCurrency: t.toCurrency, - payOutAmount: t.expectedReceiveAmountDecimal, + payOutAmount: t.expectedAmountTo ?? "", payOutAddress: t.payoutAddress, payOutNetwork: "", - payOutExtraId: t.payoutExtraId, - payOutTxid: t.payoutHash, - refundAddress: t.refundAddress, - refundExtraId: t.refundExtraId, + payOutExtraId: t.payoutExtraId ?? "", + payOutTxid: t.payoutHash ?? "", + refundAddress: t.refundAddress ?? "", + refundExtraId: t.refundExtraId ?? "", status: t.status.name, exchangeName: ChangeNowExchange.exchangeName, ); @@ -229,8 +221,9 @@ class ChangeNowExchange extends Exchange { @override Future> updateTrade(Trade trade) async { - final response = - await ChangeNowAPI.instance.getTransactionStatus(id: trade.tradeId); + final response = await ChangeNowAPI.instance.getTransactionStatus( + id: trade.tradeId, + ); if (response.exception != null) { return ExchangeResponse(exception: response.exception); } @@ -245,23 +238,19 @@ class ChangeNowExchange extends Exchange { timestamp: timestamp, updatedAt: DateTime.tryParse(t.updatedAt) ?? timestamp, payInCurrency: t.fromCurrency, - payInAmount: t.amountSendDecimal.isEmpty - ? t.expectedSendAmountDecimal - : t.amountSendDecimal, + payInAmount: t.amountFrom ?? t.expectedAmountFrom ?? trade.payInAmount, payInAddress: t.payinAddress, payInNetwork: trade.payInNetwork, - payInExtraId: t.payinExtraId, - payInTxid: t.payinHash, + payInExtraId: t.payinExtraId ?? "", + payInTxid: t.payinHash ?? "", payOutCurrency: t.toCurrency, - payOutAmount: t.amountReceiveDecimal.isEmpty - ? t.expectedReceiveAmountDecimal - : t.amountReceiveDecimal, + payOutAmount: t.amountTo ?? t.expectedAmountTo ?? trade.payOutAmount, payOutAddress: t.payoutAddress, payOutNetwork: trade.payOutNetwork, - payOutExtraId: t.payoutExtraId, - payOutTxid: t.payoutHash, - refundAddress: t.refundAddress, - refundExtraId: t.refundExtraId, + payOutExtraId: t.payoutExtraId ?? "", + payOutTxid: t.payoutHash ?? "", + refundAddress: t.refundAddress ?? "", + refundExtraId: t.refundExtraId ?? "", status: t.status.name, exchangeName: ChangeNowExchange.exchangeName, ); diff --git a/lib/services/exchange/exchange.dart b/lib/services/exchange/exchange.dart index bf592fab3..f25bddc77 100644 --- a/lib/services/exchange/exchange.dart +++ b/lib/services/exchange/exchange.dart @@ -14,7 +14,6 @@ import '../../models/exchange/response_objects/estimate.dart'; import '../../models/exchange/response_objects/range.dart'; import '../../models/exchange/response_objects/trade.dart'; import '../../models/isar/exchange_cache/currency.dart'; -import '../../models/isar/exchange_cache/pair.dart'; import 'change_now/change_now_exchange.dart'; import 'exchange_response.dart'; import 'majestic_bank/majestic_bank_exchange.dart'; @@ -53,17 +52,17 @@ abstract class Exchange { Future>> getAllCurrencies(bool fixedRate); - Future>> getPairedCurrencies( - String forCurrency, - bool fixedRate, - ); - - Future>> getPairsFor( - String currency, - bool fixedRate, - ); + // Future>> getPairedCurrencies( + // String forCurrency, + // bool fixedRate, + // ); - Future>> getAllPairs(bool fixedRate); + // Future>> getPairsFor( + // String currency, + // bool fixedRate, + // ); + // + // Future>> getAllPairs(bool fixedRate); Future> getTrade(String tradeId); Future> updateTrade(Trade trade); @@ -72,13 +71,17 @@ abstract class Exchange { Future> getRange( String from, + String? fromNetwork, String to, + String? toNetwork, bool fixedRate, ); Future>> getEstimates( String from, + String? fromNetwork, String to, + String? toNetwork, Decimal amount, bool fixedRate, bool reversed, @@ -87,6 +90,8 @@ abstract class Exchange { Future> createTrade({ required String from, required String to, + required String? fromNetwork, + required String? toNetwork, required bool fixedRate, required Decimal amount, required String addressTo, @@ -101,10 +106,10 @@ abstract class Exchange { /// /// Add to this list when adding a new exchange which supports Tor. static List get exchangesWithTorSupport => [ - MajesticBankExchange.instance, - TrocadorExchange.instance, - NanswapExchange.instance, // Maybe?? - ]; + MajesticBankExchange.instance, + TrocadorExchange.instance, + NanswapExchange.instance, // Maybe?? + ]; /// List of exchange names which support Tor. /// diff --git a/lib/services/exchange/majestic_bank/majestic_bank_api.dart b/lib/services/exchange/majestic_bank/majestic_bank_api.dart index 210be8af4..ee87bf159 100644 --- a/lib/services/exchange/majestic_bank/majestic_bank_api.dart +++ b/lib/services/exchange/majestic_bank/majestic_bank_api.dart @@ -50,9 +50,10 @@ class MajesticBankAPI { try { final response = await client.get( url: uri, - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, + proxyInfo: + Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, ); code = response.code; @@ -61,16 +62,17 @@ class MajesticBankAPI { return parsed; } catch (e, s) { - Logging.instance - .e("_makeRequest($uri) HTTP:$code threw: ", error: e, stackTrace: s); + Logging.instance.e( + "_makeRequest($uri) HTTP:$code threw: ", + error: e, + stackTrace: s, + ); rethrow; } } Future>> getRates() async { - final uri = _buildUri( - endpoint: "rates", - ); + final uri = _buildUri(endpoint: "rates"); try { final jsonObject = await _makeGetRequest(uri); @@ -90,11 +92,7 @@ class MajesticBankAPI { } return ExchangeResponse(value: rates); } catch (e, s) { - Logging.instance.e( - "getRates exception", - error: e, - stackTrace: s, - ); + Logging.instance.e("getRates exception", error: e, stackTrace: s); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -109,9 +107,7 @@ class MajesticBankAPI { }) async { final uri = _buildUri( endpoint: "limits", - params: { - "from_currency": fromCurrency, - }, + params: {"from_currency": fromCurrency}, ); try { @@ -127,11 +123,7 @@ class MajesticBankAPI { return ExchangeResponse(value: limit); } catch (e, s) { - Logging.instance.e( - "getLimits exception", - error: e, - stackTrace: s, - ); + Logging.instance.e("getLimits exception", error: e, stackTrace: s); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -163,11 +155,7 @@ class MajesticBankAPI { return ExchangeResponse(value: limits); } catch (e, s) { - Logging.instance.e( - "getLimits exception", - error: e, - stackTrace: s, - ); + Logging.instance.e("getLimits exception", error: e, stackTrace: s); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -196,10 +184,7 @@ class MajesticBankAPI { params["from_amount"] = amount; } - final uri = _buildUri( - endpoint: "calculate", - params: params, - ); + final uri = _buildUri(endpoint: "calculate", params: params); try { final jsonObject = await _makeGetRequest(uri); @@ -284,11 +269,7 @@ class MajesticBankAPI { return ExchangeResponse(value: order); } catch (e, s) { - Logging.instance.e( - "createOrder exception", - error: e, - stackTrace: s, - ); + Logging.instance.e("createOrder exception", error: e, stackTrace: s); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -342,8 +323,11 @@ class MajesticBankAPI { return ExchangeResponse(value: order); } catch (e, s) { - Logging.instance - .e("createFixedRateOrder exception: ", error: e, stackTrace: s); + Logging.instance.e( + "createFixedRateOrder exception: ", + error: e, + stackTrace: s, + ); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -356,12 +340,7 @@ class MajesticBankAPI { Future> trackOrder({ required String orderId, }) async { - final uri = _buildUri( - endpoint: "track", - params: { - "trx": orderId, - }, - ); + final uri = _buildUri(endpoint: "track", params: {"trx": orderId}); try { final jsonObject = await _makeGetRequest(uri); diff --git a/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart b/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart index 2d283e69e..b1f8be150 100644 --- a/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart +++ b/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart @@ -53,6 +53,8 @@ class MajesticBankExchange extends Exchange { Future> createTrade({ required String from, required String to, + required String? fromNetwork, + required String? toNetwork, required bool fixedRate, required Decimal amount, required String addressTo, @@ -138,7 +140,8 @@ class MajesticBankExchange extends Exchange { final currency = Currency( exchangeName: MajesticBankExchange.exchangeName, ticker: limit.currency, - name: kMajesticBankCurrencyNames[limit.currency] ?? + name: + kMajesticBankCurrencyNames[limit.currency] ?? limit.currency, // todo: add more names if MB adds more network: "", image: "", @@ -154,42 +157,44 @@ class MajesticBankExchange extends Exchange { return ExchangeResponse(value: currencies); } - @override - Future>> getPairedCurrencies( - String forCurrency, - bool fixedRate, - ) { - // TODO: change this if the api changes to allow getting by paired currency - return getAllCurrencies(fixedRate); - } - - @override - Future>> getAllPairs(bool fixedRate) async { - final response = await MajesticBankAPI.instance.getRates(); - if (response.value == null) { - return ExchangeResponse(exception: response.exception); - } - - final List pairs = []; - final rates = response.value!; - - for (final rate in rates) { - final pair = Pair( - exchangeName: MajesticBankExchange.exchangeName, - from: rate.fromCurrency, - to: rate.toCurrency, - rateType: SupportedRateType.both, - ); - pairs.add(pair); - } - - return ExchangeResponse(value: pairs); - } + // @override + // Future>> getPairedCurrencies( + // String forCurrency, + // bool fixedRate, + // ) { + // // TODO: change this if the api changes to allow getting by paired currency + // return getAllCurrencies(fixedRate); + // } + // + // @override + // Future>> getAllPairs(bool fixedRate) async { + // final response = await MajesticBankAPI.instance.getRates(); + // if (response.value == null) { + // return ExchangeResponse(exception: response.exception); + // } + // + // final List pairs = []; + // final rates = response.value!; + // + // for (final rate in rates) { + // final pair = Pair( + // exchangeName: MajesticBankExchange.exchangeName, + // from: rate.fromCurrency, + // to: rate.toCurrency, + // rateType: SupportedRateType.both, + // ); + // pairs.add(pair); + // } + // + // return ExchangeResponse(value: pairs); + // } @override Future>> getEstimates( String from, + String? fromNetwork, String to, + String? toNetwork, Decimal amount, bool fixedRate, bool reversed, @@ -214,33 +219,36 @@ class MajesticBankExchange extends Exchange { return ExchangeResponse(value: [estimate]); } - @override - Future>> getPairsFor( - String currency, - bool fixedRate, - ) async { - final response = await getAllPairs(fixedRate); - if (response.value == null) { - return ExchangeResponse(exception: response.exception); - } - - final pairs = response.value!.where( - (e) => - e.from.toUpperCase() == currency.toUpperCase() || - e.to.toUpperCase() == currency.toUpperCase(), - ); - - return ExchangeResponse(value: pairs.toList()); - } + // @override + // Future>> getPairsFor( + // String currency, + // bool fixedRate, + // ) async { + // final response = await getAllPairs(fixedRate); + // if (response.value == null) { + // return ExchangeResponse(exception: response.exception); + // } + // + // final pairs = response.value!.where( + // (e) => + // e.from.toUpperCase() == currency.toUpperCase() || + // e.to.toUpperCase() == currency.toUpperCase(), + // ); + // + // return ExchangeResponse(value: pairs.toList()); + // } @override Future> getRange( String from, + String? fromNetwork, String to, + String? toNetwork, bool fixedRate, ) async { - final response = - await MajesticBankAPI.instance.getLimit(fromCurrency: from); + final response = await MajesticBankAPI.instance.getLimit( + fromCurrency: from, + ); if (response.value == null) { return ExchangeResponse(exception: response.exception); } diff --git a/lib/services/exchange/nanswap/nanswap_exchange.dart b/lib/services/exchange/nanswap/nanswap_exchange.dart index de89b886e..2392199e7 100644 --- a/lib/services/exchange/nanswap/nanswap_exchange.dart +++ b/lib/services/exchange/nanswap/nanswap_exchange.dart @@ -30,6 +30,8 @@ class NanswapExchange extends Exchange { Future> createTrade({ required String from, required String to, + required String? fromNetwork, + required String? toNetwork, required bool fixedRate, required Decimal amount, required String addressTo, @@ -74,9 +76,7 @@ class NanswapExchange extends Exchange { ); if (response.exception != null) { - return ExchangeResponse( - exception: response.exception, - ); + return ExchangeResponse(exception: response.exception); } final t = response.value!; @@ -108,9 +108,7 @@ class NanswapExchange extends Exchange { ), ); } on ExchangeException catch (e) { - return ExchangeResponse( - exception: e, - ); + return ExchangeResponse(exception: e); } catch (e) { return ExchangeResponse( exception: ExchangeException( @@ -136,34 +134,31 @@ class NanswapExchange extends Exchange { final response = await NanswapAPI.instance.getSupportedCurrencies(); if (response.exception != null) { - return ExchangeResponse( - exception: response.exception, - ); + return ExchangeResponse(exception: response.exception); } return ExchangeResponse( - value: response.value! - .where((e) => filter.contains(e.id)) - .map( - (e) => Currency( - exchangeName: exchangeName, - ticker: e.id, - name: e.name, - network: e.network, - image: e.image, - isFiat: false, - rateType: SupportedRateType.estimated, - isStackCoin: AppConfig.isStackCoin(e.id), - tokenContract: null, - isAvailable: true, - ), - ) - .toList(), + value: + response.value! + .where((e) => filter.contains(e.id)) + .map( + (e) => Currency( + exchangeName: exchangeName, + ticker: e.id, + name: e.name, + network: e.network, + image: e.image, + isFiat: false, + rateType: SupportedRateType.estimated, + isStackCoin: AppConfig.isStackCoin(e.id), + tokenContract: null, + isAvailable: true, + ), + ) + .toList(), ); } on ExchangeException catch (e) { - return ExchangeResponse( - exception: e, - ); + return ExchangeResponse(exception: e); } catch (e) { return ExchangeResponse( exception: ExchangeException( @@ -174,15 +169,12 @@ class NanswapExchange extends Exchange { } } - @override - Future>> getAllPairs(bool fixedRate) async { - throw UnimplementedError(); - } - @override Future>> getEstimates( String from, + String? fromNetwork, String to, + String? toNetwork, Decimal amount, bool fixedRate, bool reversed, @@ -211,9 +203,7 @@ class NanswapExchange extends Exchange { } if (response.exception != null) { - return ExchangeResponse( - exception: response.exception, - ); + return ExchangeResponse(exception: response.exception); } final t = response.value!; @@ -231,9 +221,7 @@ class NanswapExchange extends Exchange { ], ); } on ExchangeException catch (e) { - return ExchangeResponse( - exception: e, - ); + return ExchangeResponse(exception: e); } catch (e) { return ExchangeResponse( exception: ExchangeException( @@ -243,59 +231,53 @@ class NanswapExchange extends Exchange { ); } } - - @override - Future>> getPairedCurrencies( - String forCurrency, - bool fixedRate, - ) async { - try { - if (fixedRate) { - throw ExchangeException( - "Nanswap fixedRate not available", - ExchangeExceptionType.generic, - ); - } - - final response = await getAllCurrencies( - fixedRate, - ); - - if (response.exception != null) { - return ExchangeResponse( - exception: response.exception, - ); - } - - return ExchangeResponse( - value: response.value!..removeWhere((e) => e.ticker == forCurrency), - ); - } on ExchangeException catch (e) { - return ExchangeResponse( - exception: e, - ); - } catch (e) { - return ExchangeResponse( - exception: ExchangeException( - e.toString(), - ExchangeExceptionType.generic, - ), - ); - } - } - - @override - Future>> getPairsFor( - String currency, - bool fixedRate, - ) async { - throw UnsupportedError("Not used"); - } + // + // @override + // Future>> getPairedCurrencies( + // String forCurrency, + // bool fixedRate, + // ) async { + // try { + // if (fixedRate) { + // throw ExchangeException( + // "Nanswap fixedRate not available", + // ExchangeExceptionType.generic, + // ); + // } + // + // final response = await getAllCurrencies( + // fixedRate, + // ); + // + // if (response.exception != null) { + // return ExchangeResponse( + // exception: response.exception, + // ); + // } + // + // return ExchangeResponse( + // value: response.value!..removeWhere((e) => e.ticker == forCurrency), + // ); + // } on ExchangeException catch (e) { + // return ExchangeResponse( + // exception: e, + // ); + // } catch (e) { + // return ExchangeResponse( + // exception: ExchangeException( + // e.toString(), + // ExchangeExceptionType.generic, + // ), + // ); + // } + // } @override Future> getRange( String from, + String? fromNetwork, String to, + String? toNetwork, bool fixedRate, ) async { try { @@ -312,9 +294,7 @@ class NanswapExchange extends Exchange { ); if (response.exception != null) { - return ExchangeResponse( - exception: response.exception, - ); + return ExchangeResponse(exception: response.exception); } final t = response.value!; @@ -326,9 +306,7 @@ class NanswapExchange extends Exchange { ), ); } on ExchangeException catch (e) { - return ExchangeResponse( - exception: e, - ); + return ExchangeResponse(exception: e); } catch (e) { return ExchangeResponse( exception: ExchangeException( @@ -342,14 +320,10 @@ class NanswapExchange extends Exchange { @override Future> getTrade(String tradeId) async { try { - final response = await NanswapAPI.instance.getOrder( - id: tradeId, - ); + final response = await NanswapAPI.instance.getOrder(id: tradeId); if (response.exception != null) { - return ExchangeResponse( - exception: response.exception, - ); + return ExchangeResponse(exception: response.exception); } final t = response.value!; @@ -381,9 +355,7 @@ class NanswapExchange extends Exchange { ), ); } on ExchangeException catch (e) { - return ExchangeResponse( - exception: e, - ); + return ExchangeResponse(exception: e); } catch (e) { return ExchangeResponse( exception: ExchangeException( @@ -406,14 +378,10 @@ class NanswapExchange extends Exchange { @override Future> updateTrade(Trade trade) async { try { - final response = await NanswapAPI.instance.getOrder( - id: trade.tradeId, - ); + final response = await NanswapAPI.instance.getOrder(id: trade.tradeId); if (response.exception != null) { - return ExchangeResponse( - exception: response.exception, - ); + return ExchangeResponse(exception: response.exception); } final t = response.value!; @@ -445,9 +413,7 @@ class NanswapExchange extends Exchange { ), ); } on ExchangeException catch (e) { - return ExchangeResponse( - exception: e, - ); + return ExchangeResponse(exception: e); } catch (e) { return ExchangeResponse( exception: ExchangeException( diff --git a/lib/services/exchange/simpleswap/simpleswap_exchange.dart b/lib/services/exchange/simpleswap/simpleswap_exchange.dart index dae14cbf7..f477d182b 100644 --- a/lib/services/exchange/simpleswap/simpleswap_exchange.dart +++ b/lib/services/exchange/simpleswap/simpleswap_exchange.dart @@ -36,6 +36,8 @@ class SimpleSwapExchange extends Exchange { Future> createTrade({ required String from, required String to, + required String? fromNetwork, + required String? toNetwork, required bool fixedRate, required Decimal amount, required String addressTo, @@ -61,28 +63,31 @@ class SimpleSwapExchange extends Exchange { Future>> getAllCurrencies( bool fixedRate, ) async { - final response = - await SimpleSwapAPI.instance.getAllCurrencies(fixedRate: fixedRate); + final response = await SimpleSwapAPI.instance.getAllCurrencies( + fixedRate: fixedRate, + ); if (response.value != null) { - final List currencies = response.value! - .map( - (e) => Currency( - exchangeName: exchangeName, - ticker: e.symbol, - name: e.name, - network: e.network, - image: e.image, - externalId: e.extraId, - isFiat: false, - rateType: fixedRate - ? SupportedRateType.both - : SupportedRateType.estimated, - isAvailable: true, - isStackCoin: AppConfig.isStackCoin(e.symbol), - tokenContract: null, - ), - ) - .toList(); + final List currencies = + response.value! + .map( + (e) => Currency( + exchangeName: exchangeName, + ticker: e.symbol, + name: e.name, + network: e.network, + image: e.image, + externalId: e.extraId, + isFiat: false, + rateType: + fixedRate + ? SupportedRateType.both + : SupportedRateType.estimated, + isAvailable: true, + isStackCoin: AppConfig.isStackCoin(e.symbol), + tokenContract: null, + ), + ) + .toList(); return ExchangeResponse>( value: currencies, exception: response.exception, @@ -95,15 +100,17 @@ class SimpleSwapExchange extends Exchange { ); } - @override - Future>> getAllPairs(bool fixedRate) async { - return await SimpleSwapAPI.instance.getAllPairs(isFixedRate: fixedRate); - } + // @override + // Future>> getAllPairs(bool fixedRate) async { + // return await SimpleSwapAPI.instance.getAllPairs(isFixedRate: fixedRate); + // } @override Future>> getEstimates( String from, + String? fromNetwork, String to, + String? toNetwork, Decimal amount, bool fixedRate, bool reversed, @@ -115,9 +122,7 @@ class SimpleSwapExchange extends Exchange { amount: amount.toString(), ); if (response.exception != null) { - return ExchangeResponse( - exception: response.exception, - ); + return ExchangeResponse(exception: response.exception); } return ExchangeResponse( @@ -135,7 +140,9 @@ class SimpleSwapExchange extends Exchange { @override Future> getRange( String from, + String? fromNetwork, String to, + String? toNetwork, bool fixedRate, ) async { return await SimpleSwapAPI.instance.getRange( @@ -145,15 +152,6 @@ class SimpleSwapExchange extends Exchange { ); } - @override - Future>> getPairsFor( - String currency, - bool fixedRate, - ) async { - // return await SimpleSwapAPI.instance.ge - throw UnimplementedError(); - } - @override Future> getTrade(String tradeId) async { return await SimpleSwapAPI.instance.getExchange(exchangeId: tradeId); diff --git a/lib/services/exchange/trocador/response_objects/trocador_trade.dart b/lib/services/exchange/trocador/response_objects/trocador_trade.dart index 5d1f5d72a..781ef596a 100644 --- a/lib/services/exchange/trocador/response_objects/trocador_trade.dart +++ b/lib/services/exchange/trocador/response_objects/trocador_trade.dart @@ -92,32 +92,37 @@ class TrocadorTrade { ); } + Map toMap() { + return { + "tradeId": tradeId, + "date": date.toIso8601String(), + "tickerFrom": tickerFrom, + "tickerTo": tickerTo, + "coinFrom": coinFrom, + "coinTo": coinTo, + "networkFrom": networkFrom, + "networkTo": networkTo, + "amountFrom": amountFrom.toString(), + "amountTo": amountTo.toString(), + "provider": provider, + "fixed": fixed, + "status": status, + "addressProvider": addressProvider, + "addressProviderMemo": addressProviderMemo, + "addressUser": addressUser, + "addressUserMemo": addressUserMemo, + "refundAddress": refundAddress, + "refundAddressMemo": refundAddressMemo, + "password": password, + "idProvider": idProvider, + "quotes": quotes, + "payment": payment, + }; + } + + @override String toString() { - return 'TrocadorTrade( ' - 'tradeId: $tradeId, ' - 'date: $date, ' - 'tickerFrom: $tickerFrom, ' - 'tickerTo: $tickerTo, ' - 'coinFrom: $coinFrom, ' - 'coinTo: $coinTo, ' - 'networkFrom: $networkFrom, ' - 'networkTo: $networkTo, ' - 'amountFrom: $amountFrom, ' - 'amountTo: $amountTo, ' - 'provider: $provider, ' - 'fixed: $fixed, ' - 'status: $status, ' - 'addressProvider: $addressProvider, ' - 'addressProviderMemo: $addressProviderMemo, ' - 'addressUser: $addressUser, ' - 'addressUserMemo: $addressUserMemo, ' - 'refundAddress: $refundAddress, ' - 'refundAddressMemo: $refundAddressMemo, ' - 'password: $password, ' - 'idProvider: $idProvider, ' - 'quotes: $quotes, ' - 'payment: $payment ' - ')'; + return "TrocadorTrade: ${toMap()}"; } } diff --git a/lib/services/exchange/trocador/trocador_exchange.dart b/lib/services/exchange/trocador/trocador_exchange.dart index cf02cc206..3bae214b6 100644 --- a/lib/services/exchange/trocador/trocador_exchange.dart +++ b/lib/services/exchange/trocador/trocador_exchange.dart @@ -40,6 +40,8 @@ class TrocadorExchange extends Exchange { Future> createTrade({ required String from, required String to, + required String? fromNetwork, + required String? toNetwork, required bool fixedRate, required Decimal amount, required String addressTo, @@ -49,37 +51,38 @@ class TrocadorExchange extends Exchange { Estimate? estimate, required bool reversed, }) async { - final response = reversed - ? await TrocadorAPI.createNewPaymentRateTrade( - isOnion: false, - rateId: estimate?.rateId, - fromTicker: from.toLowerCase(), - fromNetwork: onlySupportedNetwork, - toTicker: to.toLowerCase(), - toNetwork: onlySupportedNetwork, - toAmount: amount.toString(), - receivingAddress: addressTo, - receivingMemo: null, - refundAddress: addressRefund, - refundMemo: null, - exchangeProvider: estimate!.exchangeProvider!, - isFixedRate: fixedRate, - ) - : await TrocadorAPI.createNewStandardRateTrade( - isOnion: false, - rateId: estimate?.rateId, - fromTicker: from.toLowerCase(), - fromNetwork: onlySupportedNetwork, - toTicker: to.toLowerCase(), - toNetwork: onlySupportedNetwork, - fromAmount: amount.toString(), - receivingAddress: addressTo, - receivingMemo: null, - refundAddress: addressRefund, - refundMemo: null, - exchangeProvider: estimate!.exchangeProvider!, - isFixedRate: fixedRate, - ); + final response = + reversed + ? await TrocadorAPI.createNewPaymentRateTrade( + isOnion: false, + rateId: estimate?.rateId, + fromTicker: from.toLowerCase(), + fromNetwork: onlySupportedNetwork, + toTicker: to.toLowerCase(), + toNetwork: onlySupportedNetwork, + toAmount: amount.toString(), + receivingAddress: addressTo, + receivingMemo: null, + refundAddress: addressRefund, + refundMemo: null, + exchangeProvider: estimate!.exchangeProvider!, + isFixedRate: fixedRate, + ) + : await TrocadorAPI.createNewStandardRateTrade( + isOnion: false, + rateId: estimate?.rateId, + fromTicker: from.toLowerCase(), + fromNetwork: onlySupportedNetwork, + toTicker: to.toLowerCase(), + toNetwork: onlySupportedNetwork, + fromAmount: amount.toString(), + receivingAddress: addressTo, + receivingMemo: null, + refundAddress: addressRefund, + refundMemo: null, + exchangeProvider: estimate!.exchangeProvider!, + isFixedRate: fixedRate, + ); if (response.value == null) { return ExchangeResponse(exception: response.exception); @@ -125,22 +128,23 @@ class TrocadorExchange extends Exchange { _cachedCurrencies?.removeWhere((e) => e.network != onlySupportedNetwork); - final value = _cachedCurrencies - ?.map( - (e) => Currency( - exchangeName: exchangeName, - ticker: e.ticker, - name: e.name, - network: e.network, - image: e.image, - isFiat: false, - rateType: SupportedRateType.both, - isStackCoin: AppConfig.isStackCoin(e.ticker), - tokenContract: null, - isAvailable: true, - ), - ) - .toList(); + final value = + _cachedCurrencies + ?.map( + (e) => Currency( + exchangeName: exchangeName, + ticker: e.ticker, + name: e.name, + network: e.network, + image: e.image, + isFiat: false, + rateType: SupportedRateType.both, + isStackCoin: AppConfig.isStackCoin(e.ticker), + tokenContract: null, + isAvailable: true, + ), + ) + .toList(); if (value == null) { return ExchangeResponse( @@ -195,28 +199,31 @@ class TrocadorExchange extends Exchange { @override Future>> getEstimates( String from, + String? fromNetwork, String to, + String? toNetwork, Decimal amount, bool fixedRate, bool reversed, ) async { - final response = reversed - ? await TrocadorAPI.getNewPaymentRate( - isOnion: false, - fromTicker: from, - fromNetwork: onlySupportedNetwork, - toTicker: to, - toNetwork: onlySupportedNetwork, - toAmount: amount.toString(), - ) - : await TrocadorAPI.getNewStandardRate( - isOnion: false, - fromTicker: from, - fromNetwork: onlySupportedNetwork, - toTicker: to, - toNetwork: onlySupportedNetwork, - fromAmount: amount.toString(), - ); + final response = + reversed + ? await TrocadorAPI.getNewPaymentRate( + isOnion: false, + fromTicker: from, + fromNetwork: onlySupportedNetwork, + toTicker: to, + toNetwork: onlySupportedNetwork, + toAmount: amount.toString(), + ) + : await TrocadorAPI.getNewStandardRate( + isOnion: false, + fromTicker: from, + fromNetwork: onlySupportedNetwork, + toTicker: to, + toNetwork: onlySupportedNetwork, + fromAmount: amount.toString(), + ); if (response.value == null) { return ExchangeResponse(exception: response.exception); @@ -265,43 +272,46 @@ class TrocadorExchange extends Exchange { } return ExchangeResponse( - value: estimates - ..sort((a, b) => b.estimatedAmount.compareTo(a.estimatedAmount)), + value: + estimates + ..sort((a, b) => b.estimatedAmount.compareTo(a.estimatedAmount)), ); } - @override - Future>> getPairedCurrencies( - String forCurrency, - bool fixedRate, - ) async { - // TODO: implement getPairedCurrencies - throw UnimplementedError(); - } - - @override - Future>> getPairsFor( - String currency, - bool fixedRate, - ) async { - final response = await getAllPairs(fixedRate); - if (response.value == null) { - return ExchangeResponse(exception: response.exception); - } - - final pairs = response.value!.where( - (e) => - e.from.toUpperCase() == currency.toUpperCase() || - e.to.toUpperCase() == currency.toUpperCase(), - ); - - return ExchangeResponse(value: pairs.toList()); - } + // @override + // Future>> getPairedCurrencies( + // String forCurrency, + // bool fixedRate, + // ) async { + // // TODO: implement getPairedCurrencies + // throw UnimplementedError(); + // } + // + // @override + // Future>> getPairsFor( + // String currency, + // bool fixedRate, + // ) async { + // final response = await getAllPairs(fixedRate); + // if (response.value == null) { + // return ExchangeResponse(exception: response.exception); + // } + // + // final pairs = response.value!.where( + // (e) => + // e.from.toUpperCase() == currency.toUpperCase() || + // e.to.toUpperCase() == currency.toUpperCase(), + // ); + // + // return ExchangeResponse(value: pairs.toList()); + // } @override Future> getRange( String from, + String? fromNetwork, String to, + String? toNetwork, bool fixedRate, ) async { if (_cachedCurrencies == null) { @@ -316,14 +326,12 @@ class TrocadorExchange extends Exchange { ); } - final fromCoin = _cachedCurrencies! - .firstWhere((e) => e.ticker.toLowerCase() == from.toLowerCase()); + final fromCoin = _cachedCurrencies!.firstWhere( + (e) => e.ticker.toLowerCase() == from.toLowerCase(), + ); return ExchangeResponse( - value: Range( - max: fromCoin.maximum, - min: fromCoin.minimum, - ), + value: Range(max: fromCoin.maximum, min: fromCoin.minimum), ); } diff --git a/lib/services/trade_service.dart b/lib/services/trade_service.dart index 6207b102e..e15216a33 100644 --- a/lib/services/trade_service.dart +++ b/lib/services/trade_service.dart @@ -38,8 +38,11 @@ class TradesService extends ChangeNotifier { required Trade trade, required bool shouldNotifyListeners, }) async { - await DB.instance - .put(boxName: DB.boxNameTradesV2, key: trade.uuid, value: trade); + await DB.instance.put( + boxName: DB.boxNameTradesV2, + key: trade.uuid, + value: trade, + ); if (shouldNotifyListeners) { notifyListeners(); diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index 73c520410..c12f215c0 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -62,8 +62,10 @@ class _EXCHANGE { case NanswapExchange.exchangeName: return nanswap; default: - throw ArgumentError("Invalid exchange name passed to " - "Assets.exchange.getIconFor()"); + throw ArgumentError( + "Invalid exchange name passed to " + "Assets.exchange.getIconFor()", + ); } } } @@ -232,7 +234,7 @@ class _SVG { String get trocadorRatingC => "assets/svg/trocador_rating_c.svg"; String get trocadorRatingD => "assets/svg/trocador_rating_d.svg"; -// TODO provide proper assets + // TODO provide proper assets String get bitcoinTestnet => "assets/svg/coin_icons/Bitcoin.svg"; String get bitcoincashTestnet => "assets/svg/coin_icons/Bitcoincash.svg"; String get firoTestnet => "assets/svg/coin_icons/Firo.svg"; diff --git a/lib/widgets/trade_card.dart b/lib/widgets/trade_card.dart index 28a05f9ae..35c8e4578 100644 --- a/lib/widgets/trade_card.dart +++ b/lib/widgets/trade_card.dart @@ -15,7 +15,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import '../models/exchange/change_now/exchange_transaction_status.dart'; +import '../models/exchange/change_now/cn_exchange_transaction_status.dart'; import '../models/exchange/response_objects/trade.dart'; import '../models/isar/stack_theme.dart'; import '../themes/theme_providers.dart'; @@ -26,11 +26,7 @@ import 'conditional_parent.dart'; import 'rounded_white_container.dart'; class TradeCard extends ConsumerWidget { - const TradeCard({ - super.key, - required this.trade, - required this.onTap, - }); + const TradeCard({super.key, required this.trade, required this.onTap}); final Trade trade; final VoidCallback onTap; @@ -78,10 +74,9 @@ class TradeCard extends ConsumerWidget { return ConditionalParent( condition: isDesktop, - builder: (child) => MouseRegion( - cursor: SystemMouseCursors.click, - child: child, - ), + builder: + (child) => + MouseRegion(cursor: SystemMouseCursors.click, child: child), child: GestureDetector( onTap: onTap, child: RoundedWhiteContainer( @@ -108,9 +103,7 @@ class TradeCard extends ConsumerWidget { ), ), ), - const SizedBox( - width: 12, - ), + const SizedBox(width: 12), Expanded( child: Column( children: [ @@ -127,9 +120,7 @@ class TradeCard extends ConsumerWidget { ), ], ), - const SizedBox( - height: 2, - ), + const SizedBox(height: 2), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/widgets/wallet_info_row/sub_widgets/wallet_info_row_coin_icon.dart b/lib/widgets/wallet_info_row/sub_widgets/wallet_info_row_coin_icon.dart index 0212d89d6..74d91b33f 100644 --- a/lib/widgets/wallet_info_row/sub_widgets/wallet_info_row_coin_icon.dart +++ b/lib/widgets/wallet_info_row/sub_widgets/wallet_info_row_coin_icon.dart @@ -14,6 +14,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:isar/isar.dart'; + import '../../../models/isar/exchange_cache/currency.dart'; import '../../../services/exchange/change_now/change_now_exchange.dart'; import '../../../services/exchange/exchange_data_loading_service.dart'; @@ -38,17 +39,15 @@ class WalletInfoCoinIcon extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { Currency? currency; if (contractAddress != null) { - currency = ExchangeDataLoadingService.instance.isar.currencies - .where() - .exchangeNameEqualTo(ChangeNowExchange.exchangeName) - .filter() - .tokenContractEqualTo( - contractAddress!, - caseSensitive: false, - ) - .and() - .imageIsNotEmpty() - .findFirstSync(); + currency = + ExchangeDataLoadingService.instance.isar.currencies + .where() + .exchangeNameEqualTo(ChangeNowExchange.exchangeName) + .filter() + .tokenContractEqualTo(contractAddress!, caseSensitive: false) + .and() + .imageIsNotEmpty() + .findFirstSync(); } return Container( @@ -62,19 +61,14 @@ class WalletInfoCoinIcon extends ConsumerWidget { ), child: Padding( padding: EdgeInsets.all(size / 5), - child: currency != null && currency.image.isNotEmpty - ? SvgPicture.network( - currency.image, - width: 20, - height: 20, - ) - : SvgPicture.file( - File( - ref.watch(coinIconProvider(coin)), + child: + currency != null && currency.image.isNotEmpty + ? SvgPicture.network(currency.image, width: 20, height: 20) + : SvgPicture.file( + File(ref.watch(coinIconProvider(coin))), + width: 20, + height: 20, ), - width: 20, - height: 20, - ), ), ); } diff --git a/test/screen_tests/exchange/exchange_view_test.mocks.dart b/test/screen_tests/exchange/exchange_view_test.mocks.dart index 565f05f82..1271b96b0 100644 --- a/test/screen_tests/exchange/exchange_view_test.mocks.dart +++ b/test/screen_tests/exchange/exchange_view_test.mocks.dart @@ -10,22 +10,17 @@ import 'package:decimal/decimal.dart' as _i19; import 'package:logger/logger.dart' as _i9; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i7; -import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart' +import 'package:stackwallet/models/exchange/change_now/cn_exchange_transaction.dart' as _i22; -import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart' - as _i24; -import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart' - as _i25; +import 'package:stackwallet/models/exchange/change_now/cn_exchange_transaction_status.dart' + as _i23; import 'package:stackwallet/models/exchange/response_objects/estimate.dart' as _i21; -import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart' - as _i23; import 'package:stackwallet/models/exchange/response_objects/range.dart' as _i20; import 'package:stackwallet/models/exchange/response_objects/trade.dart' as _i15; import 'package:stackwallet/models/isar/exchange_cache/currency.dart' as _i18; -import 'package:stackwallet/models/isar/exchange_cache/pair.dart' as _i26; import 'package:stackwallet/networking/http.dart' as _i3; import 'package:stackwallet/services/exchange/change_now/change_now_api.dart' as _i17; @@ -1031,16 +1026,22 @@ class MockChangeNowAPI extends _i1.Mock implements _i17.ChangeNowAPI { @override _i10.Future<_i4.ExchangeResponse>> getAvailableCurrencies({ - bool? fixedRate, bool? active, + bool? buy, + bool? sell, + _i17.CNFlow? flow = _i17.CNFlow.standard, + String? apiKey, }) => (super.noSuchMethod( Invocation.method( #getAvailableCurrencies, [], { - #fixedRate: fixedRate, #active: active, + #buy: buy, + #sell: sell, + #flow: flow, + #apiKey: apiKey, }, ), returnValue: @@ -1051,64 +1052,23 @@ class MockChangeNowAPI extends _i1.Mock implements _i17.ChangeNowAPI { #getAvailableCurrencies, [], { - #fixedRate: fixedRate, #active: active, + #buy: buy, + #sell: sell, + #flow: flow, + #apiKey: apiKey, }, ), )), ) as _i10.Future<_i4.ExchangeResponse>>); - @override - _i10.Future<_i4.ExchangeResponse>> getCurrenciesV2() => - (super.noSuchMethod( - Invocation.method( - #getCurrenciesV2, - [], - ), - returnValue: - _i10.Future<_i4.ExchangeResponse>>.value( - _FakeExchangeResponse_2>( - this, - Invocation.method( - #getCurrenciesV2, - [], - ), - )), - ) as _i10.Future<_i4.ExchangeResponse>>); - - @override - _i10.Future<_i4.ExchangeResponse>> getPairedCurrencies({ - required String? ticker, - bool? fixedRate, - }) => - (super.noSuchMethod( - Invocation.method( - #getPairedCurrencies, - [], - { - #ticker: ticker, - #fixedRate: fixedRate, - }, - ), - returnValue: - _i10.Future<_i4.ExchangeResponse>>.value( - _FakeExchangeResponse_2>( - this, - Invocation.method( - #getPairedCurrencies, - [], - { - #ticker: ticker, - #fixedRate: fixedRate, - }, - ), - )), - ) as _i10.Future<_i4.ExchangeResponse>>); - @override _i10.Future<_i4.ExchangeResponse<_i19.Decimal>> getMinimalExchangeAmount({ - required String? fromTicker, - required String? toTicker, + required String? fromCurrency, + required String? toCurrency, + String? fromNetwork, + String? toNetwork, + _i17.CNFlow? flow = _i17.CNFlow.standard, String? apiKey, }) => (super.noSuchMethod( @@ -1116,8 +1076,11 @@ class MockChangeNowAPI extends _i1.Mock implements _i17.ChangeNowAPI { #getMinimalExchangeAmount, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, + #fromCurrency: fromCurrency, + #toCurrency: toCurrency, + #fromNetwork: fromNetwork, + #toNetwork: toNetwork, + #flow: flow, #apiKey: apiKey, }, ), @@ -1128,8 +1091,11 @@ class MockChangeNowAPI extends _i1.Mock implements _i17.ChangeNowAPI { #getMinimalExchangeAmount, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, + #fromCurrency: fromCurrency, + #toCurrency: toCurrency, + #fromNetwork: fromNetwork, + #toNetwork: toNetwork, + #flow: flow, #apiKey: apiKey, }, ), @@ -1138,9 +1104,11 @@ class MockChangeNowAPI extends _i1.Mock implements _i17.ChangeNowAPI { @override _i10.Future<_i4.ExchangeResponse<_i20.Range>> getRange({ - required String? fromTicker, - required String? toTicker, - required bool? isFixedRate, + required String? fromCurrency, + required String? toCurrency, + String? fromNetwork, + String? toNetwork, + _i17.CNFlow? flow = _i17.CNFlow.standard, String? apiKey, }) => (super.noSuchMethod( @@ -1148,9 +1116,11 @@ class MockChangeNowAPI extends _i1.Mock implements _i17.ChangeNowAPI { #getRange, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, - #isFixedRate: isFixedRate, + #fromCurrency: fromCurrency, + #toCurrency: toCurrency, + #fromNetwork: fromNetwork, + #toNetwork: toNetwork, + #flow: flow, #apiKey: apiKey, }, ), @@ -1161,9 +1131,11 @@ class MockChangeNowAPI extends _i1.Mock implements _i17.ChangeNowAPI { #getRange, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, - #isFixedRate: isFixedRate, + #fromCurrency: fromCurrency, + #toCurrency: toCurrency, + #fromNetwork: fromNetwork, + #toNetwork: toNetwork, + #flow: flow, #apiKey: apiKey, }, ), @@ -1172,9 +1144,16 @@ class MockChangeNowAPI extends _i1.Mock implements _i17.ChangeNowAPI { @override _i10.Future<_i4.ExchangeResponse<_i21.Estimate>> getEstimatedExchangeAmount({ - required String? fromTicker, - required String? toTicker, - required _i19.Decimal? fromAmount, + required String? fromCurrency, + required String? toCurrency, + _i19.Decimal? fromAmount, + _i19.Decimal? toAmount, + String? fromNetwork, + String? toNetwork, + _i17.CNFlow? flow = _i17.CNFlow.standard, + _i17.CNExchangeType? type = _i17.CNExchangeType.direct, + bool? useRateId, + bool? isTopUp, String? apiKey, }) => (super.noSuchMethod( @@ -1182,9 +1161,16 @@ class MockChangeNowAPI extends _i1.Mock implements _i17.ChangeNowAPI { #getEstimatedExchangeAmount, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, + #fromCurrency: fromCurrency, + #toCurrency: toCurrency, #fromAmount: fromAmount, + #toAmount: toAmount, + #fromNetwork: fromNetwork, + #toNetwork: toNetwork, + #flow: flow, + #type: type, + #useRateId: useRateId, + #isTopUp: isTopUp, #apiKey: apiKey, }, ), @@ -1195,9 +1181,16 @@ class MockChangeNowAPI extends _i1.Mock implements _i17.ChangeNowAPI { #getEstimatedExchangeAmount, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, + #fromCurrency: fromCurrency, + #toCurrency: toCurrency, #fromAmount: fromAmount, + #toAmount: toAmount, + #fromNetwork: fromNetwork, + #toNetwork: toNetwork, + #flow: flow, + #type: type, + #useRateId: useRateId, + #isTopUp: isTopUp, #apiKey: apiKey, }, ), @@ -1205,277 +1198,109 @@ class MockChangeNowAPI extends _i1.Mock implements _i17.ChangeNowAPI { ) as _i10.Future<_i4.ExchangeResponse<_i21.Estimate>>); @override - _i10.Future<_i4.ExchangeResponse<_i21.Estimate>> - getEstimatedExchangeAmountFixedRate({ - required String? fromTicker, - required String? toTicker, - required _i19.Decimal? fromAmount, - required bool? reversed, - bool? useRateId = true, - String? apiKey, - }) => - (super.noSuchMethod( - Invocation.method( - #getEstimatedExchangeAmountFixedRate, - [], - { - #fromTicker: fromTicker, - #toTicker: toTicker, - #fromAmount: fromAmount, - #reversed: reversed, - #useRateId: useRateId, - #apiKey: apiKey, - }, - ), - returnValue: _i10.Future<_i4.ExchangeResponse<_i21.Estimate>>.value( - _FakeExchangeResponse_2<_i21.Estimate>( - this, - Invocation.method( - #getEstimatedExchangeAmountFixedRate, - [], - { - #fromTicker: fromTicker, - #toTicker: toTicker, - #fromAmount: fromAmount, - #reversed: reversed, - #useRateId: useRateId, - #apiKey: apiKey, - }, - ), - )), - ) as _i10.Future<_i4.ExchangeResponse<_i21.Estimate>>); - - @override - _i10.Future<_i4.ExchangeResponse<_i22.CNExchangeEstimate>> - getEstimatedExchangeAmountV2({ - required String? fromTicker, - required String? toTicker, - required _i22.CNEstimateType? fromOrTo, - required _i19.Decimal? amount, - String? fromNetwork, - String? toNetwork, - _i22.CNFlowType? flow = _i22.CNFlowType.standard, + _i10.Future<_i4.ExchangeResponse<_i22.CNExchangeTransaction>> + createExchangeTransaction({ + required String? fromCurrency, + required String? fromNetwork, + required String? toCurrency, + required String? toNetwork, + _i19.Decimal? fromAmount, + _i19.Decimal? toAmount, + _i17.CNFlow? flow = _i17.CNFlow.standard, + _i17.CNExchangeType? type = _i17.CNExchangeType.direct, + required String? address, + String? extraId, + String? refundAddress, + String? refundExtraId, + String? userId, + String? payload, + String? contactEmail, + required String? rateId, String? apiKey, }) => (super.noSuchMethod( Invocation.method( - #getEstimatedExchangeAmountV2, + #createExchangeTransaction, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, - #fromOrTo: fromOrTo, - #amount: amount, + #fromCurrency: fromCurrency, #fromNetwork: fromNetwork, + #toCurrency: toCurrency, #toNetwork: toNetwork, + #fromAmount: fromAmount, + #toAmount: toAmount, #flow: flow, - #apiKey: apiKey, - }, - ), - returnValue: _i10 - .Future<_i4.ExchangeResponse<_i22.CNExchangeEstimate>>.value( - _FakeExchangeResponse_2<_i22.CNExchangeEstimate>( - this, - Invocation.method( - #getEstimatedExchangeAmountV2, - [], - { - #fromTicker: fromTicker, - #toTicker: toTicker, - #fromOrTo: fromOrTo, - #amount: amount, - #fromNetwork: fromNetwork, - #toNetwork: toNetwork, - #flow: flow, - #apiKey: apiKey, - }, - ), - )), - ) as _i10.Future<_i4.ExchangeResponse<_i22.CNExchangeEstimate>>); - - @override - _i10.Future<_i4.ExchangeResponse>> - getAvailableFixedRateMarkets({String? apiKey}) => (super.noSuchMethod( - Invocation.method( - #getAvailableFixedRateMarkets, - [], - {#apiKey: apiKey}, - ), - returnValue: _i10 - .Future<_i4.ExchangeResponse>>.value( - _FakeExchangeResponse_2>( - this, - Invocation.method( - #getAvailableFixedRateMarkets, - [], - {#apiKey: apiKey}, - ), - )), - ) as _i10.Future<_i4.ExchangeResponse>>); - - @override - _i10.Future<_i4.ExchangeResponse<_i24.ExchangeTransaction>> - createStandardExchangeTransaction({ - required String? fromTicker, - required String? toTicker, - required String? receivingAddress, - required _i19.Decimal? amount, - String? extraId = r'', - String? userId = r'', - String? contactEmail = r'', - String? refundAddress = r'', - String? refundExtraId = r'', - String? apiKey, - }) => - (super.noSuchMethod( - Invocation.method( - #createStandardExchangeTransaction, - [], - { - #fromTicker: fromTicker, - #toTicker: toTicker, - #receivingAddress: receivingAddress, - #amount: amount, + #type: type, + #address: address, #extraId: extraId, - #userId: userId, - #contactEmail: contactEmail, #refundAddress: refundAddress, #refundExtraId: refundExtraId, + #userId: userId, + #payload: payload, + #contactEmail: contactEmail, + #rateId: rateId, #apiKey: apiKey, }, ), returnValue: _i10 - .Future<_i4.ExchangeResponse<_i24.ExchangeTransaction>>.value( - _FakeExchangeResponse_2<_i24.ExchangeTransaction>( + .Future<_i4.ExchangeResponse<_i22.CNExchangeTransaction>>.value( + _FakeExchangeResponse_2<_i22.CNExchangeTransaction>( this, Invocation.method( - #createStandardExchangeTransaction, + #createExchangeTransaction, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, - #receivingAddress: receivingAddress, - #amount: amount, + #fromCurrency: fromCurrency, + #fromNetwork: fromNetwork, + #toCurrency: toCurrency, + #toNetwork: toNetwork, + #fromAmount: fromAmount, + #toAmount: toAmount, + #flow: flow, + #type: type, + #address: address, #extraId: extraId, - #userId: userId, - #contactEmail: contactEmail, #refundAddress: refundAddress, #refundExtraId: refundExtraId, + #userId: userId, + #payload: payload, + #contactEmail: contactEmail, + #rateId: rateId, #apiKey: apiKey, }, ), )), - ) as _i10.Future<_i4.ExchangeResponse<_i24.ExchangeTransaction>>); + ) as _i10.Future<_i4.ExchangeResponse<_i22.CNExchangeTransaction>>); @override - _i10.Future<_i4.ExchangeResponse<_i24.ExchangeTransaction>> - createFixedRateExchangeTransaction({ - required String? fromTicker, - required String? toTicker, - required String? receivingAddress, - required _i19.Decimal? amount, - required String? rateId, - required bool? reversed, - String? extraId = r'', - String? userId = r'', - String? contactEmail = r'', - String? refundAddress = r'', - String? refundExtraId = r'', + _i10.Future<_i4.ExchangeResponse<_i23.CNExchangeTransactionStatus>> + getTransactionStatus({ + required String? id, String? apiKey, }) => (super.noSuchMethod( Invocation.method( - #createFixedRateExchangeTransaction, + #getTransactionStatus, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, - #receivingAddress: receivingAddress, - #amount: amount, - #rateId: rateId, - #reversed: reversed, - #extraId: extraId, - #userId: userId, - #contactEmail: contactEmail, - #refundAddress: refundAddress, - #refundExtraId: refundExtraId, + #id: id, #apiKey: apiKey, }, ), - returnValue: _i10 - .Future<_i4.ExchangeResponse<_i24.ExchangeTransaction>>.value( - _FakeExchangeResponse_2<_i24.ExchangeTransaction>( + returnValue: _i10.Future< + _i4 + .ExchangeResponse<_i23.CNExchangeTransactionStatus>>.value( + _FakeExchangeResponse_2<_i23.CNExchangeTransactionStatus>( this, Invocation.method( - #createFixedRateExchangeTransaction, + #getTransactionStatus, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, - #receivingAddress: receivingAddress, - #amount: amount, - #rateId: rateId, - #reversed: reversed, - #extraId: extraId, - #userId: userId, - #contactEmail: contactEmail, - #refundAddress: refundAddress, - #refundExtraId: refundExtraId, + #id: id, #apiKey: apiKey, }, ), )), - ) as _i10.Future<_i4.ExchangeResponse<_i24.ExchangeTransaction>>); - - @override - _i10.Future< - _i4 - .ExchangeResponse<_i25.ExchangeTransactionStatus>> getTransactionStatus({ - required String? id, - String? apiKey, - }) => - (super.noSuchMethod( - Invocation.method( - #getTransactionStatus, - [], - { - #id: id, - #apiKey: apiKey, - }, - ), - returnValue: _i10 - .Future<_i4.ExchangeResponse<_i25.ExchangeTransactionStatus>>.value( - _FakeExchangeResponse_2<_i25.ExchangeTransactionStatus>( - this, - Invocation.method( - #getTransactionStatus, - [], - { - #id: id, - #apiKey: apiKey, - }, - ), - )), - ) as _i10.Future<_i4.ExchangeResponse<_i25.ExchangeTransactionStatus>>); - - @override - _i10.Future<_i4.ExchangeResponse>> - getAvailableFloatingRatePairs({bool? includePartners = false}) => - (super.noSuchMethod( - Invocation.method( - #getAvailableFloatingRatePairs, - [], - {#includePartners: includePartners}, - ), - returnValue: - _i10.Future<_i4.ExchangeResponse>>.value( - _FakeExchangeResponse_2>( - this, - Invocation.method( - #getAvailableFloatingRatePairs, - [], - {#includePartners: includePartners}, - ), - )), - ) as _i10.Future<_i4.ExchangeResponse>>); + ) as _i10 + .Future<_i4.ExchangeResponse<_i23.CNExchangeTransactionStatus>>); } diff --git a/test/services/change_now/change_now_test.dart b/test/services/change_now/change_now_test.dart index f49c52064..f922bc3ed 100644 --- a/test/services/change_now/change_now_test.dart +++ b/test/services/change_now/change_now_test.dart @@ -8,7 +8,6 @@ import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; -import 'package:stackwallet/models/isar/exchange_cache/pair.dart'; import 'package:stackwallet/networking/http.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; @@ -23,12 +22,16 @@ void main() { final instance = ChangeNowAPI(http: client); - when(client.get( - url: Uri.parse("https://api.ChangeNow.io/v1/currencies"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => - Response(utf8.encode(jsonEncode(availableCurrenciesJSON)), 200)); + when( + client.get( + url: Uri.parse("https://api.ChangeNow.io/v1/currencies"), + headers: {'Content-Type': 'application/json'}, + proxyInfo: null, + ), + ).thenAnswer( + (realInvocation) async => + Response(utf8.encode(jsonEncode(availableCurrenciesJSON)), 200), + ); final result = await instance.getAvailableCurrencies(); @@ -41,12 +44,18 @@ void main() { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); - when(client.get( - url: Uri.parse("https://api.ChangeNow.io/v1/currencies?active=true"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => Response( - utf8.encode(jsonEncode(availableCurrenciesJSONActive)), 200)); + when( + client.get( + url: Uri.parse("https://api.ChangeNow.io/v1/currencies?active=true"), + headers: {'Content-Type': 'application/json'}, + proxyInfo: null, + ), + ).thenAnswer( + (realInvocation) async => Response( + utf8.encode(jsonEncode(availableCurrenciesJSONActive)), + 200, + ), + ); final result = await instance.getAvailableCurrencies(active: true); @@ -59,36 +68,24 @@ void main() { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); - when(client.get( - url: Uri.parse("https://api.ChangeNow.io/v1/currencies?fixedRate=true"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => Response( - utf8.encode(jsonEncode(availableCurrenciesJSONFixedRate)), 200)); - - final result = await instance.getAvailableCurrencies(fixedRate: true); - - expect(result.exception, null); - expect(result.value == null, false); - expect(result.value!.length, 410); - }); - - test("getAvailableCurrencies succeeds with fixedRate and active options", - () async { - final client = MockHTTP(); - final instance = ChangeNowAPI(http: client); - - when(client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/currencies?fixedRate=true&active=true"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => Response( - utf8.encode(jsonEncode(availableCurrenciesJSONActiveFixedRate)), - 200)); + when( + client.get( + url: Uri.parse( + "https://api.ChangeNow.io/v1/currencies?fixedRate=true", + ), + headers: {'Content-Type': 'application/json'}, + proxyInfo: null, + ), + ).thenAnswer( + (realInvocation) async => Response( + utf8.encode(jsonEncode(availableCurrenciesJSONFixedRate)), + 200, + ), + ); - final result = - await instance.getAvailableCurrencies(active: true, fixedRate: true); + final result = await instance.getAvailableCurrencies( + flow: CNFlow.fixedRate, + ); expect(result.exception, null); expect(result.value == null, false); @@ -96,116 +93,84 @@ void main() { }); test( - "getAvailableCurrencies fails with ChangeNowExceptionType.serializeResponseError", - () async { - final client = MockHTTP(); - final instance = ChangeNowAPI(http: client); - - when(client.get( - url: Uri.parse("https://api.ChangeNow.io/v1/currencies"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => Response( - utf8.encode('{"some unexpected": "but valid json data"}'), 200)); - - final result = await instance.getAvailableCurrencies(); + "getAvailableCurrencies succeeds with fixedRate and active options", + () async { + final client = MockHTTP(); + final instance = ChangeNowAPI(http: client); + + when( + client.get( + url: Uri.parse( + "https://api.ChangeNow.io/v1/currencies?fixedRate=true&active=true", + ), + headers: {'Content-Type': 'application/json'}, + proxyInfo: null, + ), + ).thenAnswer( + (realInvocation) async => Response( + utf8.encode(jsonEncode(availableCurrenciesJSONActiveFixedRate)), + 200, + ), + ); + + final result = await instance.getAvailableCurrencies( + active: true, + flow: CNFlow.fixedRate, + ); + + expect(result.exception, null); + expect(result.value == null, false); + expect(result.value!.length, 410); + }, + ); - expect( - result.exception!.type, ExchangeExceptionType.serializeResponseError); - expect(result.value == null, true); - }); + test( + "getAvailableCurrencies fails with ChangeNowExceptionType.serializeResponseError", + () async { + final client = MockHTTP(); + final instance = ChangeNowAPI(http: client); + + when( + client.get( + url: Uri.parse("https://api.ChangeNow.io/v1/currencies"), + headers: {'Content-Type': 'application/json'}, + proxyInfo: null, + ), + ).thenAnswer( + (realInvocation) async => Response( + utf8.encode('{"some unexpected": "but valid json data"}'), + 200, + ), + ); + + final result = await instance.getAvailableCurrencies(); + + expect( + result.exception!.type, + ExchangeExceptionType.serializeResponseError, + ); + expect(result.value == null, true); + }, + ); test("getAvailableCurrencies fails for any other reason", () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); - when(client.get( - url: Uri.parse("https://api.ChangeNow.io/v1/currencies"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => Response(utf8.encode(""), 400)); + when( + client.get( + url: Uri.parse("https://api.ChangeNow.io/v1/currencies"), + headers: {'Content-Type': 'application/json'}, + proxyInfo: null, + ), + ).thenAnswer((realInvocation) async => Response(utf8.encode(""), 400)); final result = await instance.getAvailableCurrencies(); expect( - result.exception!.type, ExchangeExceptionType.serializeResponseError); - expect(result.value == null, true); - }); - }); - - group("getPairedCurrencies", () { - test("getPairedCurrencies succeeds without fixedRate option", () async { - final client = MockHTTP(); - final instance = ChangeNowAPI(http: client); - - when(client.get( - url: Uri.parse("https://api.ChangeNow.io/v1/currencies-to/XMR"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => - Response(utf8.encode(jsonEncode(getPairedCurrenciesJSON)), 200)); - - final result = await instance.getPairedCurrencies(ticker: "XMR"); - - expect(result.exception, null); - expect(result.value == null, false); - expect(result.value!.length, 537); - }); - - test("getPairedCurrencies succeeds with fixedRate option", () async { - final client = MockHTTP(); - final instance = ChangeNowAPI(http: client); - - when(client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/currencies-to/XMR?fixedRate=true"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => Response( - utf8.encode(jsonEncode(getPairedCurrenciesJSONFixedRate)), 200)); - - final result = - await instance.getPairedCurrencies(ticker: "XMR", fixedRate: true); - - expect(result.exception, null); - expect(result.value == null, false); - expect(result.value!.length, 410); - }); - - test( - "getPairedCurrencies fails with ChangeNowExceptionType.serializeResponseError A", - () async { - final client = MockHTTP(); - final instance = ChangeNowAPI(http: client); - - when(client.get( - url: Uri.parse("https://api.ChangeNow.io/v1/currencies-to/XMR"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => Response( - utf8.encode('[{"some unexpected": "but valid json data"}]'), 200)); - - final result = await instance.getPairedCurrencies(ticker: "XMR"); - - expect( - result.exception!.type, ExchangeExceptionType.serializeResponseError); - expect(result.value == null, true); - }); - - test("getPairedCurrencies fails for any other reason", () async { - final client = MockHTTP(); - final instance = ChangeNowAPI(http: client); - - when(client.get( - url: Uri.parse("https://api.ChangeNow.io/v1/currencies"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => Response(utf8.encode(""), 400)); - - final result = - await instance.getPairedCurrencies(ticker: "XMR", fixedRate: true); - - expect(result.exception!.type, ExchangeExceptionType.generic); + result.exception!.type, + ExchangeExceptionType.serializeResponseError, + ); expect(result.value == null, true); }); }); @@ -215,17 +180,22 @@ void main() { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); - when(client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/min-amount/xmr_btc?api_key=testAPIKEY"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => - Response(utf8.encode('{"minAmount": 42}'), 200)); + when( + client.get( + url: Uri.parse( + "https://api.ChangeNow.io/v1/min-amount/xmr_btc?api_key=testAPIKEY", + ), + headers: {'Content-Type': 'application/json'}, + proxyInfo: null, + ), + ).thenAnswer( + (realInvocation) async => + Response(utf8.encode('{"minAmount": 42}'), 200), + ); final result = await instance.getMinimalExchangeAmount( - fromTicker: "xmr", - toTicker: "btc", + fromCurrency: "xmr", + toCurrency: "btc", apiKey: "testAPIKEY", ); @@ -235,49 +205,61 @@ void main() { }); test( - "getMinimalExchangeAmount fails with ChangeNowExceptionType.serializeResponseError", - () async { - final client = MockHTTP(); - final instance = ChangeNowAPI(http: client); - - when(client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/min-amount/xmr_btc?api_key=testAPIKEY"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => - Response(utf8.encode('{"error": 42}'), 200)); - - final result = await instance.getMinimalExchangeAmount( - fromTicker: "xmr", - toTicker: "btc", - apiKey: "testAPIKEY", - ); - - expect( - result.exception!.type, ExchangeExceptionType.serializeResponseError); - expect(result.value == null, true); - }); + "getMinimalExchangeAmount fails with ChangeNowExceptionType.serializeResponseError", + () async { + final client = MockHTTP(); + final instance = ChangeNowAPI(http: client); + + when( + client.get( + url: Uri.parse( + "https://api.ChangeNow.io/v1/min-amount/xmr_btc?api_key=testAPIKEY", + ), + headers: {'Content-Type': 'application/json'}, + proxyInfo: null, + ), + ).thenAnswer( + (realInvocation) async => Response(utf8.encode('{"error": 42}'), 200), + ); + + final result = await instance.getMinimalExchangeAmount( + fromCurrency: "xmr", + toCurrency: "btc", + apiKey: "testAPIKEY", + ); + + expect( + result.exception!.type, + ExchangeExceptionType.serializeResponseError, + ); + expect(result.value == null, true); + }, + ); test("getMinimalExchangeAmount fails for any other reason", () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); - when(client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/min-amount/xmr_btc?api_key=testAPIKEY"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => Response(utf8.encode(''), 400)); + when( + client.get( + url: Uri.parse( + "https://api.ChangeNow.io/v1/min-amount/xmr_btc?api_key=testAPIKEY", + ), + headers: {'Content-Type': 'application/json'}, + proxyInfo: null, + ), + ).thenAnswer((realInvocation) async => Response(utf8.encode(''), 400)); final result = await instance.getMinimalExchangeAmount( - fromTicker: "xmr", - toTicker: "btc", + fromCurrency: "xmr", + toCurrency: "btc", apiKey: "testAPIKEY", ); expect( - result.exception!.type, ExchangeExceptionType.serializeResponseError); + result.exception!.type, + ExchangeExceptionType.serializeResponseError, + ); expect(result.value == null, true); }); }); @@ -287,19 +269,26 @@ void main() { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); - when(client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/exchange-amount/42/xmr_btc?api_key=testAPIKEY"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => Response( + when( + client.get( + url: Uri.parse( + "https://api.ChangeNow.io/v1/exchange-amount/42/xmr_btc?api_key=testAPIKEY", + ), + headers: {'Content-Type': 'application/json'}, + proxyInfo: null, + ), + ).thenAnswer( + (realInvocation) async => Response( utf8.encode( - '{"estimatedAmount": 58.4142873, "transactionSpeedForecast": "10-60", "warningMessage": null}'), - 200)); + '{"estimatedAmount": 58.4142873, "transactionSpeedForecast": "10-60", "warningMessage": null}', + ), + 200, + ), + ); final result = await instance.getEstimatedExchangeAmount( - fromTicker: "xmr", - toTicker: "btc", + fromCurrency: "xmr", + toCurrency: "btc", fromAmount: Decimal.fromInt(42), apiKey: "testAPIKEY", ); @@ -310,45 +299,55 @@ void main() { }); test( - "getEstimatedExchangeAmount fails with ChangeNowExceptionType.serializeResponseError", - () async { - final client = MockHTTP(); - final instance = ChangeNowAPI(http: client); - - when(client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/exchange-amount/42/xmr_btc?api_key=testAPIKEY"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => - Response(utf8.encode('{"error": 42}'), 200)); - - final result = await instance.getEstimatedExchangeAmount( - fromTicker: "xmr", - toTicker: "btc", - fromAmount: Decimal.fromInt(42), - apiKey: "testAPIKEY", - ); - - expect( - result.exception!.type, ExchangeExceptionType.serializeResponseError); - expect(result.value == null, true); - }); + "getEstimatedExchangeAmount fails with ChangeNowExceptionType.serializeResponseError", + () async { + final client = MockHTTP(); + final instance = ChangeNowAPI(http: client); + + when( + client.get( + url: Uri.parse( + "https://api.ChangeNow.io/v1/exchange-amount/42/xmr_btc?api_key=testAPIKEY", + ), + headers: {'Content-Type': 'application/json'}, + proxyInfo: null, + ), + ).thenAnswer( + (realInvocation) async => Response(utf8.encode('{"error": 42}'), 200), + ); + + final result = await instance.getEstimatedExchangeAmount( + fromCurrency: "xmr", + toCurrency: "btc", + fromAmount: Decimal.fromInt(42), + apiKey: "testAPIKEY", + ); + + expect( + result.exception!.type, + ExchangeExceptionType.serializeResponseError, + ); + expect(result.value == null, true); + }, + ); test("getEstimatedExchangeAmount fails for any other reason", () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); - when(client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/exchange-amount/42/xmr_btc?api_key=testAPIKEY"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => Response(utf8.encode(''), 400)); + when( + client.get( + url: Uri.parse( + "https://api.ChangeNow.io/v1/exchange-amount/42/xmr_btc?api_key=testAPIKEY", + ), + headers: {'Content-Type': 'application/json'}, + proxyInfo: null, + ), + ).thenAnswer((realInvocation) async => Response(utf8.encode(''), 400)); final result = await instance.getEstimatedExchangeAmount( - fromTicker: "xmr", - toTicker: "btc", + fromCurrency: "xmr", + toCurrency: "btc", fromAmount: Decimal.fromInt(42), apiKey: "testAPIKEY", ); @@ -373,8 +372,8 @@ void main() { // // final result = // await ChangeNow.instance.getEstimatedFixedRateExchangeAmount( - // fromTicker: "xmr", - // toTicker: "btc", + // fromCurrency: "xmr", + // toCurrency: "btc", // fromAmount: Decimal.fromInt(10), // apiKey: "testAPIKEY", // ); @@ -400,8 +399,8 @@ void main() { // // final result = // await ChangeNow.instance.getEstimatedFixedRateExchangeAmount( - // fromTicker: "xmr", - // toTicker: "btc", + // fromCurrency: "xmr", + // toCurrency: "btc", // fromAmount: Decimal.fromInt(10), // apiKey: "testAPIKEY", // ); @@ -425,8 +424,8 @@ void main() { // // final result = // await ChangeNow.instance.getEstimatedFixedRateExchangeAmount( - // fromTicker: "xmr", - // toTicker: "btc", + // fromCurrency: "xmr", + // toCurrency: "btc", // fromAmount: Decimal.fromInt(10), // apiKey: "testAPIKEY", // ); @@ -436,95 +435,38 @@ void main() { // }); // }); - group("getAvailableFixedRateMarkets", () { - test("getAvailableFixedRateMarkets succeeds", () async { + group("createExchangeTransaction", () { + test("createExchangeTransaction succeeds", () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); - when(client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/market-info/fixed-rate/testAPIKEY"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => - Response(utf8.encode(jsonEncode(fixedRateMarketsJSON)), 200)); - - final result = await instance.getAvailableFixedRateMarkets( - apiKey: "testAPIKEY", + when( + client.post( + url: Uri.parse("https://api.ChangeNow.io/v1/transactions/testAPIKEY"), + headers: {'Content-Type': 'application/json'}, + proxyInfo: null, + body: + '{"from":"xmr","to":"btc","address":"bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5","amount":"0.3","flow":"standard","extraId":"","userId":"","contactEmail":"","refundAddress":"888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H","refundExtraId":""}', + encoding: null, + ), + ).thenAnswer( + (realInvocation) async => Response( + utf8.encode(jsonEncode(createStandardTransactionResponse)), + 200, + ), ); - expect(result.exception, null); - expect(result.value == null, false); - expect(result.value!.length, 237); - }); - - test( - "getAvailableFixedRateMarkets fails with ChangeNowExceptionType.serializeResponseError", - () async { - final client = MockHTTP(); - final instance = ChangeNowAPI(http: client); - - when(client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/market-info/fixed-rate/testAPIKEY"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => - Response(utf8.encode('{"error": 42}'), 200)); - - final result = await instance.getAvailableFixedRateMarkets( - apiKey: "testAPIKEY", - ); - - expect( - result.exception!.type, ExchangeExceptionType.serializeResponseError); - expect(result.value == null, true); - }); - - test("getAvailableFixedRateMarkets fails for any other reason", () async { - final client = MockHTTP(); - final instance = ChangeNowAPI(http: client); - - when(client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/market-info/fixed-rate/testAPIKEY"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => Response(utf8.encode(''), 400)); - - final result = await instance.getAvailableFixedRateMarkets( - apiKey: "testAPIKEY", - ); - - expect( - result.exception!.type, ExchangeExceptionType.serializeResponseError); - expect(result.value == null, true); - }); - }); - - group("createStandardExchangeTransaction", () { - test("createStandardExchangeTransaction succeeds", () async { - final client = MockHTTP(); - final instance = ChangeNowAPI(http: client); - - when(client.post( - url: Uri.parse("https://api.ChangeNow.io/v1/transactions/testAPIKEY"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - body: - '{"from":"xmr","to":"btc","address":"bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5","amount":"0.3","flow":"standard","extraId":"","userId":"","contactEmail":"","refundAddress":"888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H","refundExtraId":""}', - encoding: null, - )).thenAnswer((realInvocation) async => Response( - utf8.encode(jsonEncode(createStandardTransactionResponse)), 200)); - - final result = await instance.createStandardExchangeTransaction( - fromTicker: "xmr", - toTicker: "btc", - receivingAddress: "bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5", - amount: Decimal.parse("0.3"), + final result = await instance.createExchangeTransaction( + fromCurrency: "xmr", + toCurrency: "btc", + address: "bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5", + fromAmount: Decimal.parse("0.3"), refundAddress: "888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H", apiKey: "testAPIKEY", + fromNetwork: 'xmr', + toNetwork: '', + rateId: '', ); expect(result.exception, null); @@ -533,58 +475,73 @@ void main() { }); test( - "createStandardExchangeTransaction fails with ChangeNowExceptionType.serializeResponseError", - () async { - final client = MockHTTP(); - final instance = ChangeNowAPI(http: client); - - when(client.post( - url: Uri.parse("https://api.ChangeNow.io/v1/transactions/testAPIKEY"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - body: - '{"from":"xmr","to":"btc","address":"bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5","amount":"0.3","flow":"standard","extraId":"","userId":"","contactEmail":"","refundAddress":"888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H","refundExtraId":""}', - encoding: null, - )).thenAnswer((realInvocation) async => - Response(utf8.encode('{"error": 42}'), 200)); - - final result = await instance.createStandardExchangeTransaction( - fromTicker: "xmr", - toTicker: "btc", - receivingAddress: "bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5", - amount: Decimal.parse("0.3"), - refundAddress: - "888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H", - apiKey: "testAPIKEY", - ); - - expect( - result.exception!.type, ExchangeExceptionType.serializeResponseError); - expect(result.value == null, true); - }); - - test("createStandardExchangeTransaction fails for any other reason", - () async { + "createExchangeTransaction fails with ChangeNowExceptionType.serializeResponseError", + () async { + final client = MockHTTP(); + final instance = ChangeNowAPI(http: client); + + when( + client.post( + url: Uri.parse( + "https://api.ChangeNow.io/v1/transactions/testAPIKEY", + ), + headers: {'Content-Type': 'application/json'}, + proxyInfo: null, + body: + '{"from":"xmr","to":"btc","address":"bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5","amount":"0.3","flow":"standard","extraId":"","userId":"","contactEmail":"","refundAddress":"888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H","refundExtraId":""}', + encoding: null, + ), + ).thenAnswer( + (realInvocation) async => Response(utf8.encode('{"error": 42}'), 200), + ); + + final result = await instance.createExchangeTransaction( + fromCurrency: "xmr", + toCurrency: "btc", + address: "bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5", + fromAmount: Decimal.parse("0.3"), + refundAddress: + "888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H", + apiKey: "testAPIKEY", + fromNetwork: 'xmr', + toNetwork: '', + rateId: '', + ); + + expect( + result.exception!.type, + ExchangeExceptionType.serializeResponseError, + ); + expect(result.value == null, true); + }, + ); + + test("createExchangeTransaction fails for any other reason", () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); - when(client.post( - url: Uri.parse("https://api.ChangeNow.io/v1/transactions/testAPIKEY"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - body: - '{"from":"xmr","to":"btc","address":"bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5","amount":"0.3","flow":"standard","extraId":"","userId":"","contactEmail":"","refundAddress":"888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H","refundExtraId":""}', - encoding: null, - )).thenAnswer((realInvocation) async => Response(utf8.encode(''), 400)); - - final result = await instance.createStandardExchangeTransaction( - fromTicker: "xmr", - toTicker: "btc", - receivingAddress: "bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5", - amount: Decimal.parse("0.3"), + when( + client.post( + url: Uri.parse("https://api.ChangeNow.io/v1/transactions/testAPIKEY"), + headers: {'Content-Type': 'application/json'}, + proxyInfo: null, + body: + '{"from":"xmr","to":"btc","address":"bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5","amount":"0.3","flow":"standard","extraId":"","userId":"","contactEmail":"","refundAddress":"888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H","refundExtraId":""}', + encoding: null, + ), + ).thenAnswer((realInvocation) async => Response(utf8.encode(''), 400)); + + final result = await instance.createExchangeTransaction( + fromCurrency: "xmr", + toCurrency: "btc", + address: "bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5", + fromAmount: Decimal.parse("0.3"), refundAddress: "888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H", apiKey: "testAPIKEY", + fromNetwork: 'xmr', + toNetwork: '', + rateId: '', ); expect(result.exception!.type, ExchangeExceptionType.generic); @@ -592,37 +549,46 @@ void main() { }); }); - group("createFixedRateExchangeTransaction", () { - test("createFixedRateExchangeTransaction succeeds", () async { + group("createExchangeTransaction", () { + test("createExchangeTransaction succeeds", () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); - when(client.post( - url: Uri.parse( - "https://api.ChangeNow.io/v1/transactions/fixed-rate/testAPIKEY"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - body: - '{"from":"btc","to":"eth","address":"0x57f31ad4b64095347F87eDB1675566DAfF5EC886","flow":"fixed-rate","extraId":"","userId":"","contactEmail":"","refundAddress":"","refundExtraId":"","rateId":"","amount":"0.3"}', - encoding: null, - )).thenAnswer((realInvocation) async => Response( + when( + client.post( + url: Uri.parse( + "https://api.ChangeNow.io/v1/transactions/fixed-rate/testAPIKEY", + ), + headers: {'Content-Type': 'application/json'}, + proxyInfo: null, + body: + '{"from":"btc","to":"eth","address":"0x57f31ad4b64095347F87eDB1675566DAfF5EC886","flow":"fixed-rate","extraId":"","userId":"","contactEmail":"","refundAddress":"","refundExtraId":"","rateId":"","amount":"0.3"}', + encoding: null, + ), + ).thenAnswer( + (realInvocation) async => Response( utf8.encode( - '{"payinAddress": "33eFX2jfeWbXMSmRe9ewUUTrmSVSxZi5cj", "payoutAddress":' - ' "0x57f31ad4b64095347F87eDB1675566DAfF5EC886","payoutExtraId": "",' - ' "fromCurrency": "btc", "toCurrency": "eth", "refundAddress": "",' - '"refundExtraId": "","validUntil": "2019-09-09T14:01:04.921Z","id":' - ' "a5c73e2603f40d","amount": 62.9737711}'), - 200)); - - final result = await instance.createFixedRateExchangeTransaction( - fromTicker: "btc", - toTicker: "eth", - receivingAddress: "0x57f31ad4b64095347F87eDB1675566DAfF5EC886", - amount: Decimal.parse("0.3"), + '{"payinAddress": "33eFX2jfeWbXMSmRe9ewUUTrmSVSxZi5cj", "payoutAddress":' + ' "0x57f31ad4b64095347F87eDB1675566DAfF5EC886","payoutExtraId": "",' + ' "fromCurrency": "btc", "toCurrency": "eth", "refundAddress": "",' + '"refundExtraId": "","validUntil": "2019-09-09T14:01:04.921Z","id":' + ' "a5c73e2603f40d","amount": 62.9737711}', + ), + 200, + ), + ); + + final result = await instance.createExchangeTransaction( + fromCurrency: "btc", + toCurrency: "eth", + address: "0x57f31ad4b64095347F87eDB1675566DAfF5EC886", + fromAmount: Decimal.parse("0.3"), refundAddress: "", apiKey: "testAPIKEY", rateId: '', - reversed: false, + type: CNExchangeType.direct, + fromNetwork: 'xmr', + toNetwork: '', ); expect(result.exception, null); @@ -631,62 +597,76 @@ void main() { }); test( - "createFixedRateExchangeTransaction fails with ChangeNowExceptionType.serializeResponseError", - () async { - final client = MockHTTP(); - final instance = ChangeNowAPI(http: client); - - when(client.post( - url: Uri.parse( - "https://api.ChangeNow.io/v1/transactions/fixed-rate/testAPIKEY"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - body: - '{"from":"btc","to":"eth","address":"0x57f31ad4b64095347F87eDB1675566DAfF5EC886","amount":"0.3","flow":"fixed-rate","extraId":"","userId":"","contactEmail":"","refundAddress":"","refundExtraId":"","rateId":""}', - encoding: null, - )).thenAnswer((realInvocation) async => Response( - utf8.encode('{"id": "a5c73e2603f40d","amount": 62.9737711}'), 200)); - - final result = await instance.createFixedRateExchangeTransaction( - fromTicker: "btc", - toTicker: "eth", - receivingAddress: "0x57f31ad4b64095347F87eDB1675566DAfF5EC886", - amount: Decimal.parse("0.3"), - refundAddress: "", - apiKey: "testAPIKEY", - rateId: '', - reversed: false, - ); - - expect(result.exception!.type, ExchangeExceptionType.generic); - expect(result.value == null, true); - }); - - test("createFixedRateExchangeTransaction fails for any other reason", - () async { + "createExchangeTransaction fails with ChangeNowExceptionType.serializeResponseError", + () async { + final client = MockHTTP(); + final instance = ChangeNowAPI(http: client); + + when( + client.post( + url: Uri.parse( + "https://api.ChangeNow.io/v1/transactions/fixed-rate/testAPIKEY", + ), + headers: {'Content-Type': 'application/json'}, + proxyInfo: null, + body: + '{"from":"btc","to":"eth","address":"0x57f31ad4b64095347F87eDB1675566DAfF5EC886","amount":"0.3","flow":"fixed-rate","extraId":"","userId":"","contactEmail":"","refundAddress":"","refundExtraId":"","rateId":""}', + encoding: null, + ), + ).thenAnswer( + (realInvocation) async => Response( + utf8.encode('{"id": "a5c73e2603f40d","amount": 62.9737711}'), + 200, + ), + ); + + final result = await instance.createExchangeTransaction( + fromCurrency: "btc", + toCurrency: "eth", + address: "0x57f31ad4b64095347F87eDB1675566DAfF5EC886", + fromAmount: Decimal.parse("0.3"), + refundAddress: "", + apiKey: "testAPIKEY", + rateId: '', + type: CNExchangeType.direct, + fromNetwork: 'xmr', + toNetwork: '', + ); + + expect(result.exception!.type, ExchangeExceptionType.generic); + expect(result.value == null, true); + }, + ); + + test("createExchangeTransaction fails for any other reason", () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); - when(client.post( - url: Uri.parse( - "https://api.ChangeNow.io/v1/transactions/fixed-rate/testAPIKEY"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - body: - '{"from": "btc","to": "eth","address": "0x57f31ad4b64095347F87eDB1675566DAfF5EC886", "amount": "1.12345","extraId": "", "userId": "","contactEmail": "","refundAddress": "", "refundExtraId": "", "rateId": "" }', - encoding: null, - )).thenAnswer((realInvocation) async => Response(utf8.encode(''), 400)); - - final result = await instance.createFixedRateExchangeTransaction( - fromTicker: "xmr", - toTicker: "btc", - receivingAddress: "bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5", - amount: Decimal.parse("0.3"), + when( + client.post( + url: Uri.parse( + "https://api.ChangeNow.io/v1/transactions/fixed-rate/testAPIKEY", + ), + headers: {'Content-Type': 'application/json'}, + proxyInfo: null, + body: + '{"from": "btc","to": "eth","address": "0x57f31ad4b64095347F87eDB1675566DAfF5EC886", "amount": "1.12345","extraId": "", "userId": "","contactEmail": "","refundAddress": "", "refundExtraId": "", "rateId": "" }', + encoding: null, + ), + ).thenAnswer((realInvocation) async => Response(utf8.encode(''), 400)); + + final result = await instance.createExchangeTransaction( + fromCurrency: "xmr", + toCurrency: "btc", + address: "bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5", + fromAmount: Decimal.parse("0.3"), refundAddress: "888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H", apiKey: "testAPIKEY", rateId: '', - reversed: false, + type: CNExchangeType.direct, + fromNetwork: 'xmr', + toNetwork: '', ); expect(result.exception!.type, ExchangeExceptionType.generic); @@ -699,20 +679,27 @@ void main() { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); - when(client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/transactions/47F87eDB1675566DAfF5EC886/testAPIKEY"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => Response( + when( + client.get( + url: Uri.parse( + "https://api.ChangeNow.io/v1/transactions/47F87eDB1675566DAfF5EC886/testAPIKEY", + ), + headers: {'Content-Type': 'application/json'}, + proxyInfo: null, + ), + ).thenAnswer( + (realInvocation) async => Response( utf8.encode( - '{"status": "waiting", "payinAddress": "32Ge2ci26rj1sRGw2NjiQa9L7Xvxtgzhrj", ' - '"payoutAddress": "0x57f31ad4b64095347F87eDB1675566DAfF5EC886", ' - '"fromCurrency": "btc", "toCurrency": "eth", "id": "50727663e5d9a4", ' - '"updatedAt": "2019-08-22T14:47:49.943Z", "expectedSendAmount": 1, ' - '"expectedReceiveAmount": 52.31667, "createdAt": "2019-08-22T14:47:49.943Z",' - ' "isPartner": false}'), - 200)); + '{"status": "waiting", "payinAddress": "32Ge2ci26rj1sRGw2NjiQa9L7Xvxtgzhrj", ' + '"payoutAddress": "0x57f31ad4b64095347F87eDB1675566DAfF5EC886", ' + '"fromCurrency": "btc", "toCurrency": "eth", "id": "50727663e5d9a4", ' + '"updatedAt": "2019-08-22T14:47:49.943Z", "expectedSendAmount": 1, ' + '"expectedReceiveAmount": 52.31667, "createdAt": "2019-08-22T14:47:49.943Z",' + ' "isPartner": false}', + ), + 200, + ), + ); final result = await instance.getTransactionStatus( id: "47F87eDB1675566DAfF5EC886", @@ -725,39 +712,49 @@ void main() { }); test( - "getTransactionStatus fails with ChangeNowExceptionType.serializeResponseError", - () async { - final client = MockHTTP(); - final instance = ChangeNowAPI(http: client); - - when(client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/transactions/47F87eDB1675566DAfF5EC886/testAPIKEY"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => - Response(utf8.encode('{"error": 42}'), 200)); - - final result = await instance.getTransactionStatus( - id: "47F87eDB1675566DAfF5EC886", - apiKey: "testAPIKEY", - ); - - expect( - result.exception!.type, ExchangeExceptionType.serializeResponseError); - expect(result.value == null, true); - }); + "getTransactionStatus fails with ChangeNowExceptionType.serializeResponseError", + () async { + final client = MockHTTP(); + final instance = ChangeNowAPI(http: client); + + when( + client.get( + url: Uri.parse( + "https://api.ChangeNow.io/v1/transactions/47F87eDB1675566DAfF5EC886/testAPIKEY", + ), + headers: {'Content-Type': 'application/json'}, + proxyInfo: null, + ), + ).thenAnswer( + (realInvocation) async => Response(utf8.encode('{"error": 42}'), 200), + ); + + final result = await instance.getTransactionStatus( + id: "47F87eDB1675566DAfF5EC886", + apiKey: "testAPIKEY", + ); + + expect( + result.exception!.type, + ExchangeExceptionType.serializeResponseError, + ); + expect(result.value == null, true); + }, + ); test("getTransactionStatus fails for any other reason", () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); - when(client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/transactions/47F87eDB1675566DAfF5EC886/testAPIKEY"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => Response(utf8.encode(''), 400)); + when( + client.get( + url: Uri.parse( + "https://api.ChangeNow.io/v1/transactions/47F87eDB1675566DAfF5EC886/testAPIKEY", + ), + headers: {'Content-Type': 'application/json'}, + proxyInfo: null, + ), + ).thenAnswer((realInvocation) async => Response(utf8.encode(''), 400)); final result = await instance.getTransactionStatus( id: "47F87eDB1675566DAfF5EC886", @@ -765,67 +762,9 @@ void main() { ); expect( - result.exception!.type, ExchangeExceptionType.serializeResponseError); - expect(result.value == null, true); - }); - }); - - group("getAvailableFloatingRatePairs", () { - test("getAvailableFloatingRatePairs succeeds", () async { - final client = MockHTTP(); - final instance = ChangeNowAPI(http: client); - - when(client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/market-info/available-pairs?includePartners=false"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => Response( - utf8.encode('["btc_xmr","btc_firo","btc_doge","eth_ltc"]'), 200)); - - final result = await instance.getAvailableFloatingRatePairs(); - - expect(result.exception, null); - expect(result.value == null, false); - expect(result.value, isA>()); - }); - - test( - "getAvailableFloatingRatePairs fails with ChangeNowExceptionType.serializeResponseError", - () async { - final client = MockHTTP(); - final instance = ChangeNowAPI(http: client); - - when(client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/market-info/available-pairs?includePartners=false"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => - Response(utf8.encode('{"error": 42}'), 200)); - - final result = await instance.getAvailableFloatingRatePairs(); - - expect( - result.exception!.type, ExchangeExceptionType.serializeResponseError); - expect(result.value == null, true); - }); - - test("getAvailableFloatingRatePairs fails for any other reason", () async { - final client = MockHTTP(); - final instance = ChangeNowAPI(http: client); - - when(client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/market-info/available-pairs?includePartners=false"), - headers: {'Content-Type': 'application/json'}, - proxyInfo: null, - )).thenAnswer((realInvocation) async => Response(utf8.encode(''), 400)); - - final result = await instance.getAvailableFloatingRatePairs(); - - expect( - result.exception!.type, ExchangeExceptionType.serializeResponseError); + result.exception!.type, + ExchangeExceptionType.serializeResponseError, + ); expect(result.value == null, true); }); }); From a5554e068dd3258149a956a79dc913efafa5a363 Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 26 Apr 2025 16:14:48 -0600 Subject: [PATCH 2/3] possible fixed rate initial fetch fix --- lib/pages/exchange_view/exchange_form.dart | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 13f334d2a..6976d557b 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -745,8 +745,7 @@ class _ExchangeFormState extends ConsumerState { } }); _receiveFocusNode.addListener(() { - if (_receiveFocusNode.hasFocus && - ref.read(efExchangeProvider).name != ChangeNowExchange.exchangeName) { + if (_receiveFocusNode.hasFocus) { final reversed = ref.read(efReversedProvider); WidgetsBinding.instance.addPostFrameCallback((_) { ref.read(efReversedProvider.notifier).state = true; @@ -964,9 +963,7 @@ class _ExchangeFormState extends ConsumerState { background: Theme.of(context).extension()!.textFieldDefaultBG, onTap: - rateType == ExchangeRateType.estimated && - ref.watch(efExchangeProvider).name == - ChangeNowExchange.exchangeName + rateType == ExchangeRateType.estimated ? null : () { if (_sendController.text == "-") { @@ -979,10 +976,7 @@ class _ExchangeFormState extends ConsumerState { currency: ref.watch( efCurrencyPairProvider.select((value) => value.receive), ), - readOnly: - rateType == ExchangeRateType.estimated && - ref.watch(efExchangeProvider).name == - ChangeNowExchange.exchangeName, + readOnly: rateType == ExchangeRateType.estimated, ), SizedBox(height: isDesktop ? 20 : 12), SizedBox( From 68adcac9c284f9cea6b7d55e77908f1c3c7751de Mon Sep 17 00:00:00 2001 From: sneurlax Date: Wed, 30 Apr 2025 16:14:03 -0500 Subject: [PATCH 3/3] feat(monero): enable local monero nodes via a "Bypass TOR" node option chore: whitespace fix --- .../add_edit_node_view.dart | 48 ++++++++++++++++++- .../manage_nodes_views/node_details_view.dart | 4 +- .../intermediate/lib_monero_wallet.dart | 4 +- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index 4fdeec183..5703db754 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -241,6 +241,8 @@ class _AddEditNodeViewState extends ConsumerState { final plainEnabled = formData.netOption == TorPlainNetworkOption.clear || formData.netOption == TorPlainNetworkOption.both; + + final forceNoTor = formData.forceNoTor ?? false; switch (viewType) { case AddEditNodeViewType.add: @@ -258,6 +260,7 @@ class _AddEditNodeViewState extends ConsumerState { isDown: false, torEnabled: torEnabled, clearnetEnabled: plainEnabled, + forceNoTor: forceNoTor, ); await ref @@ -285,6 +288,7 @@ class _AddEditNodeViewState extends ConsumerState { isDown: false, torEnabled: torEnabled, clearnetEnabled: plainEnabled, + forceNoTor: forceNoTor, ); await ref @@ -744,7 +748,7 @@ class _AddEditNodeViewState extends ConsumerState { class NodeFormData { String? name, host, login, password; int? port; - bool? useSSL, isFailover, trusted; + bool? useSSL, isFailover, trusted, forceNoTor; TorPlainNetworkOption? netOption; @override @@ -793,6 +797,7 @@ class _NodeFormState extends ConsumerState { bool _useSSL = false; bool _isFailover = false; bool _trusted = false; + bool _forceNoTor = false; int? port; late bool enableSSLCheckbox; late TorPlainNetworkOption netOption; @@ -851,6 +856,7 @@ class _NodeFormState extends ConsumerState { ref.read(nodeFormDataProvider).isFailover = _isFailover; ref.read(nodeFormDataProvider).trusted = _trusted; ref.read(nodeFormDataProvider).netOption = netOption; + ref.read(nodeFormDataProvider).forceNoTor = _forceNoTor; } @override @@ -885,6 +891,7 @@ class _NodeFormState extends ConsumerState { _useSSL = node.useSSL; _isFailover = node.isFailover; _trusted = node.trusted ?? false; + _forceNoTor = node.forceNoTor ?? false; if (node.torEnabled && !node.clearnetEnabled) { netOption = TorPlainNetworkOption.tor; @@ -1445,9 +1452,48 @@ class _NodeFormState extends ConsumerState { ), ], ), + if (widget.coin is CryptonoteCurrency && _isLocalNode()) + const SizedBox(height: 8), + if (widget.coin is CryptonoteCurrency && _isLocalNode()) + Row( + children: [ + SizedBox( + width: 20, + height: 20, + child: Checkbox( + fillColor: + !widget.readOnly + ? null + : MaterialStateProperty.all( + Theme.of( + context, + ).extension()!.checkboxBGDisabled, + ), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: _forceNoTor, + onChanged: + !widget.readOnly + ? (newValue) { + setState(() { + _forceNoTor = newValue!; + }); + _updateState(); + } + : null, + ), + ), + const SizedBox(width: 12), + Text("Bypass TOR", style: STextStyles.itemSubtitle12(context)), + ], + ), ], ); } + + bool _isLocalNode() { + final host = _hostController.text.toLowerCase(); + return host.contains("127.0.0.1") || host.contains("localhost"); + } } class RadioTextButton extends StatelessWidget { diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart index 56c87e5c4..572c7d7a2 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart @@ -320,7 +320,8 @@ class _NodeDetailsViewState extends ConsumerState { ..login = node.loginName ..port = node.port ..isFailover = node.isFailover - ..netOption = netOption; + ..netOption = netOption + ..forceNoTor = node.forceNoTor; nodeFormData.password = await node.getPassword( ref.read(secureStoreProvider), ); @@ -396,6 +397,7 @@ class _NodeDetailsViewState extends ConsumerState { TorPlainNetworkOption.clear || ref.read(nodeFormDataProvider).netOption == TorPlainNetworkOption.both, + forceNoTor: ref.read(nodeFormDataProvider).forceNoTor, ); await ref diff --git a/lib/wallets/wallet/intermediate/lib_monero_wallet.dart b/lib/wallets/wallet/intermediate/lib_monero_wallet.dart index 55812fe19..b21f5884f 100644 --- a/lib/wallets/wallet/intermediate/lib_monero_wallet.dart +++ b/lib/wallets/wallet/intermediate/lib_monero_wallet.dart @@ -520,7 +520,7 @@ abstract class LibMoneroWallet trusted: node.trusted ?? false, useSSL: node.useSSL, socksProxyAddress: - proxy == null ? null : "${proxy.host.address}:${proxy.port}", + node.forceNoTor ? null : proxy == null ? null : "${proxy.host.address}:${proxy.port}", ); }); } else { @@ -531,7 +531,7 @@ abstract class LibMoneroWallet trusted: node.trusted ?? false, useSSL: node.useSSL, socksProxyAddress: - proxy == null ? null : "${proxy.host.address}:${proxy.port}", + node.forceNoTor ? null : proxy == null ? null : "${proxy.host.address}:${proxy.port}", ); } libMoneroWallet?.startSyncing();