diff --git a/asset_sources/svg/campfire/exchange_icons/nanswap.svg b/asset_sources/svg/campfire/exchange_icons/nanswap.svg
new file mode 100644
index 000000000..caceb9a29
--- /dev/null
+++ b/asset_sources/svg/campfire/exchange_icons/nanswap.svg
@@ -0,0 +1,69 @@
+
+
diff --git a/asset_sources/svg/stack_duo/exchange_icons/nanswap.svg b/asset_sources/svg/stack_duo/exchange_icons/nanswap.svg
new file mode 100644
index 000000000..caceb9a29
--- /dev/null
+++ b/asset_sources/svg/stack_duo/exchange_icons/nanswap.svg
@@ -0,0 +1,69 @@
+
+
diff --git a/asset_sources/svg/stack_wallet/exchange_icons/nanswap.svg b/asset_sources/svg/stack_wallet/exchange_icons/nanswap.svg
new file mode 100644
index 000000000..caceb9a29
--- /dev/null
+++ b/asset_sources/svg/stack_wallet/exchange_icons/nanswap.svg
@@ -0,0 +1,69 @@
+
+
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 109bee3ba..e3ae98acd 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
@@ -22,6 +22,7 @@ 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';
+import '../../../services/exchange/nanswap/nanswap_exchange.dart';
import '../../../services/exchange/trocador/trocador_exchange.dart';
import '../../../themes/stack_colors.dart';
import '../../../utilities/assets.dart';
@@ -117,6 +118,8 @@ class _ExchangeCurrencySelectionViewState
.exchangeNameEqualTo(MajesticBankExchange.exchangeName)
.or()
.exchangeNameStartsWith(TrocadorExchange.exchangeName)
+ .or()
+ .exchangeNameStartsWith(NanswapExchange.exchangeName)
.findAll();
final cn = await ChangeNowExchange.instance.getPairedCurrencies(
diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart
index 9d227f231..111d38448 100644
--- a/lib/pages/exchange_view/exchange_form.dart
+++ b/lib/pages/exchange_view/exchange_form.dart
@@ -33,6 +33,7 @@ import '../../services/exchange/exchange.dart';
import '../../services/exchange/exchange_data_loading_service.dart';
import '../../services/exchange/exchange_response.dart';
import '../../services/exchange/majestic_bank/majestic_bank_exchange.dart';
+import '../../services/exchange/nanswap/nanswap_exchange.dart';
import '../../services/exchange/trocador/trocador_exchange.dart';
import '../../themes/stack_colors.dart';
import '../../utilities/amount/amount_unit.dart';
@@ -87,6 +88,7 @@ class _ExchangeFormState extends ConsumerState {
MajesticBankExchange.instance,
ChangeNowExchange.instance,
TrocadorExchange.instance,
+ NanswapExchange.instance,
];
}
}
diff --git a/lib/pages/exchange_view/exchange_step_views/step_2_view.dart b/lib/pages/exchange_view/exchange_step_views/step_2_view.dart
index 433984627..111c93240 100644
--- a/lib/pages/exchange_view/exchange_step_views/step_2_view.dart
+++ b/lib/pages/exchange_view/exchange_step_views/step_2_view.dart
@@ -15,7 +15,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../app_config.dart';
import '../../../models/exchange/incomplete_exchange.dart';
import '../../../providers/providers.dart';
-import '../../../services/exchange/majestic_bank/majestic_bank_exchange.dart';
import '../../../themes/stack_colors.dart';
import '../../../utilities/address_utils.dart';
import '../../../utilities/barcode_scanner_interface.dart';
@@ -126,8 +125,7 @@ class _Step2ViewState extends ConsumerState {
@override
Widget build(BuildContext context) {
- final supportsRefund =
- ref.watch(efExchangeProvider).name != MajesticBankExchange.exchangeName;
+ final supportsRefund = ref.watch(efExchangeProvider).supportsRefundAddress;
return Background(
child: Scaffold(
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 545e2d41a..2cd014fc3 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
@@ -18,7 +18,6 @@ import '../../../models/exchange/response_objects/trade.dart';
import '../../../providers/global/trades_service_provider.dart';
import '../../../providers/providers.dart';
import '../../../services/exchange/exchange_response.dart';
-import '../../../services/exchange/majestic_bank/majestic_bank_exchange.dart';
import '../../../services/notifications_api.dart';
import '../../../themes/stack_colors.dart';
import '../../../utilities/assets.dart';
@@ -63,8 +62,7 @@ class _Step3ViewState extends ConsumerState {
@override
Widget build(BuildContext context) {
- final supportsRefund =
- ref.watch(efExchangeProvider).name != MajesticBankExchange.exchangeName;
+ final supportsRefund = ref.watch(efExchangeProvider).supportsRefundAddress;
return Background(
child: Scaffold(
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 436e724ea..1ffa12275 100644
--- a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart
+++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart
@@ -10,17 +10,19 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
+
import '../../../models/exchange/aggregate_currency.dart';
-import 'exchange_provider_option.dart';
import '../../../providers/providers.dart';
import '../../../services/exchange/change_now/change_now_exchange.dart';
import '../../../services/exchange/exchange.dart';
import '../../../services/exchange/majestic_bank/majestic_bank_exchange.dart';
+import '../../../services/exchange/nanswap/nanswap_exchange.dart';
import '../../../services/exchange/trocador/trocador_exchange.dart';
import '../../../themes/stack_colors.dart';
import '../../../utilities/prefs.dart';
import '../../../utilities/util.dart';
import '../../../widgets/rounded_white_container.dart';
+import 'exchange_provider_option.dart';
class ExchangeProviderOptions extends ConsumerStatefulWidget {
const ExchangeProviderOptions({
@@ -88,6 +90,11 @@ class _ExchangeProviderOptionsState
sendCurrency: sendCurrency,
receiveCurrency: receivingCurrency,
);
+ final showNanswap = exchangeSupported(
+ exchangeName: NanswapExchange.exchangeName,
+ sendCurrency: sendCurrency,
+ receiveCurrency: receivingCurrency,
+ );
return RoundedWhiteContainer(
padding: isDesktop ? const EdgeInsets.all(0) : const EdgeInsets.all(12),
@@ -134,6 +141,23 @@ class _ExchangeProviderOptionsState
reversed: widget.reversed,
exchange: TrocadorExchange.instance,
),
+ if ((showChangeNow || showMajesticBank || showTrocador) &&
+ showNanswap)
+ isDesktop
+ ? Container(
+ height: 1,
+ color:
+ Theme.of(context).extension()!.background,
+ )
+ : const SizedBox(
+ height: 16,
+ ),
+ if (showNanswap)
+ ExchangeOption(
+ fixedRate: widget.fixedRate,
+ reversed: widget.reversed,
+ exchange: NanswapExchange.instance,
+ ),
],
),
);
diff --git a/lib/pages/exchange_view/trade_details_view.dart b/lib/pages/exchange_view/trade_details_view.dart
index fd56c4a59..7c2317768 100644
--- a/lib/pages/exchange_view/trade_details_view.dart
+++ b/lib/pages/exchange_view/trade_details_view.dart
@@ -30,6 +30,7 @@ import '../../route_generator.dart';
import '../../services/exchange/change_now/change_now_exchange.dart';
import '../../services/exchange/exchange.dart';
import '../../services/exchange/majestic_bank/majestic_bank_exchange.dart';
+import '../../services/exchange/nanswap/nanswap_exchange.dart';
import '../../services/exchange/simpleswap/simpleswap_exchange.dart';
import '../../services/exchange/trocador/trocador_exchange.dart';
import '../../themes/stack_colors.dart';
@@ -1330,6 +1331,10 @@ class _TradeDetailsViewState extends ConsumerState {
url =
"https://majesticbank.sc/track?trx=${trade.tradeId}";
break;
+ case NanswapExchange.exchangeName:
+ url =
+ "https://nanswap.com/transaction/${trade.tradeId}";
+ break;
default:
if (trade.exchangeName
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 64f4e4471..6c84996da 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
@@ -15,8 +15,7 @@ import 'package:tuple/tuple.dart';
import '../../../../app_config.dart';
import '../../../../models/contact_address_entry.dart';
-import '../../../../providers/exchange/exchange_send_from_wallet_id_provider.dart';
-import '../../../../providers/global/wallets_provider.dart';
+import '../../../../providers/providers.dart';
import '../../../../themes/stack_colors.dart';
import '../../../../utilities/clipboard_interface.dart';
import '../../../../utilities/constants.dart';
@@ -88,7 +87,7 @@ class _DesktopStep2State extends ConsumerState {
}
widget.enableNextChanged.call(
- _toController.text.isNotEmpty && _refundController.text.isNotEmpty,
+ _next(),
);
}
@@ -120,7 +119,7 @@ class _DesktopStep2State extends ConsumerState {
Logging.instance.log("$e\n$s", level: LogLevel.Info);
}
widget.enableNextChanged.call(
- _toController.text.isNotEmpty && _refundController.text.isNotEmpty,
+ _next(),
);
}
@@ -167,7 +166,7 @@ class _DesktopStep2State extends ConsumerState {
_toController.text = entry.address;
ref.read(desktopExchangeModelProvider)!.recipientAddress = entry.address;
widget.enableNextChanged.call(
- _toController.text.isNotEmpty && _refundController.text.isNotEmpty,
+ _next(),
);
}
}
@@ -215,11 +214,21 @@ class _DesktopStep2State extends ConsumerState {
_refundController.text = entry.address;
ref.read(desktopExchangeModelProvider)!.refundAddress = entry.address;
widget.enableNextChanged.call(
- _toController.text.isNotEmpty && _refundController.text.isNotEmpty,
+ _next(),
);
}
}
+ bool _next() {
+ if (doesRefundAddress) {
+ return _toController.text.isNotEmpty && _refundController.text.isNotEmpty;
+ } else {
+ return _toController.text.isNotEmpty;
+ }
+ }
+
+ late final bool doesRefundAddress;
+
@override
void initState() {
clipboard = widget.clipboard;
@@ -230,6 +239,13 @@ class _DesktopStep2State extends ConsumerState {
_toFocusNode = FocusNode();
_refundFocusNode = FocusNode();
+ doesRefundAddress = ref.read(efExchangeProvider).supportsRefundAddress;
+
+ if (!doesRefundAddress) {
+ // 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() ==
@@ -243,8 +259,9 @@ class _DesktopStep2State extends ConsumerState {
ref.read(desktopExchangeModelProvider)!.recipientAddress =
_toController.text;
} else {
- if (ref.read(desktopExchangeModelProvider)!.sendTicker.toUpperCase() ==
- tuple.item2.ticker.toUpperCase()) {
+ if (doesRefundAddress &&
+ ref.read(desktopExchangeModelProvider)!.sendTicker.toUpperCase() ==
+ tuple.item2.ticker.toUpperCase()) {
_refundController.text = ref
.read(pWallets)
.getWallet(tuple.item1)
@@ -341,8 +358,7 @@ class _DesktopStep2State extends ConsumerState {
style: STextStyles.field(context),
onChanged: (value) {
widget.enableNextChanged.call(
- _toController.text.isNotEmpty &&
- _refundController.text.isNotEmpty,
+ _next(),
);
},
decoration: standardInputDecoration(
@@ -376,8 +392,7 @@ class _DesktopStep2State extends ConsumerState {
.read(desktopExchangeModelProvider)!
.recipientAddress = _toController.text;
widget.enableNextChanged.call(
- _toController.text.isNotEmpty &&
- _refundController.text.isNotEmpty,
+ _next(),
);
},
child: const XIcon(),
@@ -397,8 +412,7 @@ class _DesktopStep2State extends ConsumerState {
.read(desktopExchangeModelProvider)!
.recipientAddress = _toController.text;
widget.enableNextChanged.call(
- _toController.text.isNotEmpty &&
- _refundController.text.isNotEmpty,
+ _next(),
);
}
},
@@ -435,155 +449,158 @@ class _DesktopStep2State extends ConsumerState {
style: STextStyles.desktopTextExtraExtraSmall(context),
),
),
- const SizedBox(
- height: 24,
- ),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Text(
- "Refund Wallet (required)",
- style: STextStyles.desktopTextExtraExtraSmall(context).copyWith(
- color: Theme.of(context)
- .extension()!
- .textFieldActiveSearchIconRight,
- ),
- ),
- if (AppConfig.isStackCoin(
- ref.watch(
- desktopExchangeModelProvider
- .select((value) => value!.sendTicker),
- ),
- ))
- CustomTextButton(
- text: "Choose from Stack",
- onTap: selectRefundAddressFromStack,
- ),
- ],
- ),
- const SizedBox(
- height: 10,
- ),
- ClipRRect(
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius,
+ if (doesRefundAddress)
+ const SizedBox(
+ height: 24,
),
- child: TextField(
- key: const Key("refundExchangeStep2ViewAddressFieldKey"),
- controller: _refundController,
- readOnly: false,
- autocorrect: false,
- enableSuggestions: false,
- // inputFormatters: [
- // FilteringTextInputFormatter.allow(RegExp("[a-zA-Z0-9]{34}")),
- // ],
- toolbarOptions: const ToolbarOptions(
- copy: false,
- cut: false,
- paste: true,
- selectAll: false,
- ),
- focusNode: _refundFocusNode,
- style: STextStyles.field(context),
- onChanged: (value) {
- widget.enableNextChanged.call(
- _toController.text.isNotEmpty &&
- _refundController.text.isNotEmpty,
- );
- },
- decoration: standardInputDecoration(
- "Enter ${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendTicker.toUpperCase()))} refund address",
- _refundFocusNode,
- context,
- desktopMed: true,
- ).copyWith(
- contentPadding: const EdgeInsets.only(
- left: 16,
- top: 6,
- bottom: 8,
- right: 5,
+ if (doesRefundAddress)
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ "Refund Wallet (required)",
+ style: STextStyles.desktopTextExtraExtraSmall(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textFieldActiveSearchIconRight,
+ ),
),
- suffixIcon: Padding(
- 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 = "";
- ref
- .read(desktopExchangeModelProvider)!
- .refundAddress = _refundController.text;
-
- widget.enableNextChanged.call(
- _toController.text.isNotEmpty &&
- _refundController.text.isNotEmpty,
- );
- },
- 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;
+ if (AppConfig.isStackCoin(
+ ref.watch(
+ desktopExchangeModelProvider
+ .select((value) => value!.sendTicker),
+ ),
+ ))
+ CustomTextButton(
+ text: "Choose from Stack",
+ onTap: selectRefundAddressFromStack,
+ ),
+ ],
+ ),
+ if (doesRefundAddress)
+ const SizedBox(
+ height: 10,
+ ),
+ if (doesRefundAddress)
+ ClipRRect(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ child: TextField(
+ key: const Key("refundExchangeStep2ViewAddressFieldKey"),
+ controller: _refundController,
+ readOnly: false,
+ autocorrect: false,
+ enableSuggestions: false,
+ // inputFormatters: [
+ // FilteringTextInputFormatter.allow(RegExp("[a-zA-Z0-9]{34}")),
+ // ],
+ toolbarOptions: const ToolbarOptions(
+ copy: false,
+ cut: false,
+ paste: true,
+ selectAll: false,
+ ),
+ focusNode: _refundFocusNode,
+ style: STextStyles.field(context),
+ onChanged: (value) {
+ widget.enableNextChanged.call(
+ _next(),
+ );
+ },
+ decoration: standardInputDecoration(
+ "Enter ${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendTicker.toUpperCase()))} refund address",
+ _refundFocusNode,
+ context,
+ desktopMed: true,
+ ).copyWith(
+ contentPadding: const EdgeInsets.only(
+ left: 16,
+ top: 6,
+ bottom: 8,
+ right: 5,
+ ),
+ suffixIcon: Padding(
+ 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 = "";
ref
.read(desktopExchangeModelProvider)!
.refundAddress = _refundController.text;
widget.enableNextChanged.call(
- _toController.text.isNotEmpty &&
- _refundController.text.isNotEmpty,
+ _next(),
);
- }
- },
- child: _refundController.text.isEmpty
- ? const ClipboardIcon()
- : const XIcon(),
- ),
- if (_refundController.text.isEmpty &&
- AppConfig.isStackCoin(
- ref.watch(
- desktopExchangeModelProvider
- .select((value) => value!.sendTicker),
- ),
- ))
- TextFieldIconButton(
- key: const Key("sendViewAddressBookButtonKey"),
- onTap: selectRefundFromAddressBook,
- child: const AddressBookIcon(),
- ),
- ],
+ },
+ 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(),
+ ),
+ if (_refundController.text.isEmpty &&
+ AppConfig.isStackCoin(
+ ref.watch(
+ desktopExchangeModelProvider
+ .select((value) => value!.sendTicker),
+ ),
+ ))
+ TextFieldIconButton(
+ key: const Key("sendViewAddressBookButtonKey"),
+ onTap: selectRefundFromAddressBook,
+ child: const AddressBookIcon(),
+ ),
+ ],
+ ),
),
),
),
),
),
- ),
- const SizedBox(
- height: 10,
- ),
- RoundedWhiteContainer(
- borderColor: Theme.of(context).extension()!.background,
- child: Text(
- "In case something goes wrong during the exchange, we might need a refund address so we can return your coins back to you.",
- style: STextStyles.desktopTextExtraExtraSmall(context),
+ if (doesRefundAddress)
+ const SizedBox(
+ height: 10,
+ ),
+ if (doesRefundAddress)
+ RoundedWhiteContainer(
+ borderColor: Theme.of(context).extension()!.background,
+ child: Text(
+ "In case something goes wrong during the exchange, we might need a refund address so we can return your coins back to you.",
+ style: STextStyles.desktopTextExtraExtraSmall(context),
+ ),
),
- ),
],
);
}
diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart
index 8dfb82734..98c4daaf5 100644
--- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart
+++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart
@@ -10,13 +10,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import '../step_scaffold.dart';
-import 'desktop_step_item.dart';
+
import '../../../../providers/providers.dart';
import '../../../../themes/stack_colors.dart';
import '../../../../utilities/enums/exchange_rate_type_enum.dart';
import '../../../../utilities/text_styles.dart';
import '../../../../widgets/rounded_white_container.dart';
+import '../step_scaffold.dart';
+import 'desktop_step_item.dart';
class DesktopStep3 extends ConsumerStatefulWidget {
const DesktopStep3({
@@ -97,20 +98,22 @@ class _DesktopStep3State extends ConsumerState {
) ??
"Error",
),
- Container(
- height: 1,
- color: Theme.of(context).extension()!.background,
- ),
- DesktopStepItem(
- vertical: true,
- label:
- "Refund ${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendTicker.toUpperCase()))} address",
- value: ref.watch(
- desktopExchangeModelProvider
- .select((value) => value!.refundAddress),
- ) ??
- "Error",
- ),
+ if (ref.watch(efExchangeProvider).supportsRefundAddress)
+ Container(
+ height: 1,
+ color: Theme.of(context).extension()!.background,
+ ),
+ if (ref.watch(efExchangeProvider).supportsRefundAddress)
+ DesktopStepItem(
+ vertical: true,
+ label:
+ "Refund ${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendTicker.toUpperCase()))} address",
+ value: ref.watch(
+ desktopExchangeModelProvider
+ .select((value) => value!.refundAddress),
+ ) ??
+ "Error",
+ ),
],
),
),
diff --git a/lib/services/exchange/exchange.dart b/lib/services/exchange/exchange.dart
index b5a2c4180..bf592fab3 100644
--- a/lib/services/exchange/exchange.dart
+++ b/lib/services/exchange/exchange.dart
@@ -9,6 +9,7 @@
*/
import 'package:decimal/decimal.dart';
+
import '../../models/exchange/response_objects/estimate.dart';
import '../../models/exchange/response_objects/range.dart';
import '../../models/exchange/response_objects/trade.dart';
@@ -17,6 +18,7 @@ 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';
+import 'nanswap/nanswap_exchange.dart';
import 'simpleswap/simpleswap_exchange.dart';
import 'trocador/trocador_exchange.dart';
@@ -33,6 +35,8 @@ abstract class Exchange {
return MajesticBankExchange.instance;
case TrocadorExchange.exchangeName:
return TrocadorExchange.instance;
+ case NanswapExchange.exchangeName:
+ return NanswapExchange.instance;
default:
final split = name.split(" ");
if (split.length >= 2) {
@@ -45,6 +49,8 @@ abstract class Exchange {
String get name;
+ bool get supportsRefundAddress => true;
+
Future>> getAllCurrencies(bool fixedRate);
Future>> getPairedCurrencies(
@@ -97,6 +103,7 @@ abstract class Exchange {
static List get exchangesWithTorSupport => [
MajesticBankExchange.instance,
TrocadorExchange.instance,
+ NanswapExchange.instance, // Maybe??
];
/// List of exchange names which support Tor.
diff --git a/lib/services/exchange/exchange_data_loading_service.dart b/lib/services/exchange/exchange_data_loading_service.dart
index e500ed5cf..cfb01e150 100644
--- a/lib/services/exchange/exchange_data_loading_service.dart
+++ b/lib/services/exchange/exchange_data_loading_service.dart
@@ -23,6 +23,7 @@ import '../../utilities/prefs.dart';
import '../../utilities/stack_file_system.dart';
import 'change_now/change_now_exchange.dart';
import 'majestic_bank/majestic_bank_exchange.dart';
+import 'nanswap/nanswap_exchange.dart';
import 'trocador/trocador_exchange.dart';
class ExchangeDataLoadingService {
@@ -170,6 +171,7 @@ class ExchangeDataLoadingService {
final futures = [
loadMajesticBankCurrencies(),
loadTrocadorCurrencies(),
+ loadNanswapCurrencies(),
];
// If using Tor, don't load data for exchanges which don't support Tor.
@@ -382,6 +384,31 @@ class ExchangeDataLoadingService {
}
}
+ Future loadNanswapCurrencies() async {
+ if (_isar == null) {
+ await initDB();
+ }
+ final responseCurrencies =
+ await NanswapExchange.instance.getAllCurrencies(false);
+
+ if (responseCurrencies.value != null) {
+ await isar.writeTxn(() async {
+ final idsToDelete = await isar.currencies
+ .where()
+ .exchangeNameEqualTo(NanswapExchange.exchangeName)
+ .idProperty()
+ .findAll();
+ await isar.currencies.deleteAll(idsToDelete);
+ await isar.currencies.putAll(responseCurrencies.value!);
+ });
+ } else {
+ Logging.instance.log(
+ "loadNanswapCurrencies: $responseCurrencies",
+ level: LogLevel.Warning,
+ );
+ }
+ }
+
// Future loadMajesticBankPairs() async {
// final exchange = MajesticBankExchange.instance;
//
diff --git a/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart b/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart
index e23490e0a..2d283e69e 100644
--- a/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart
+++ b/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart
@@ -46,6 +46,9 @@ class MajesticBankExchange extends Exchange {
"XMR": "Monero",
};
+ @override
+ bool get supportsRefundAddress => false;
+
@override
Future> createTrade({
required String from,
diff --git a/lib/services/exchange/nanswap/api_response_models/n_currency.dart b/lib/services/exchange/nanswap/api_response_models/n_currency.dart
new file mode 100644
index 000000000..87f42de12
--- /dev/null
+++ b/lib/services/exchange/nanswap/api_response_models/n_currency.dart
@@ -0,0 +1,43 @@
+class NCurrency {
+ final String id;
+ final String ticker;
+ final String name;
+ final String image;
+ final String network;
+ final bool hasExternalId;
+ final bool feeLess;
+
+ NCurrency({
+ required this.id,
+ required this.ticker,
+ required this.name,
+ required this.image,
+ required this.network,
+ required this.hasExternalId,
+ required this.feeLess,
+ });
+
+ factory NCurrency.fromJson(Map json) {
+ return NCurrency(
+ id: json["id"] as String,
+ ticker: json['ticker'] as String,
+ name: json['name'] as String,
+ image: json['image'] as String,
+ network: json['network'] as String,
+ hasExternalId: json['hasExternalId'] as bool,
+ feeLess: json['feeless'] as bool,
+ );
+ }
+
+ @override
+ String toString() {
+ return 'NCurrency {'
+ 'ticker: $ticker, '
+ 'name: $name, '
+ 'image: $image, '
+ 'network: $network, '
+ 'hasExternalId: $hasExternalId, '
+ 'feeless: $feeLess'
+ '}';
+ }
+}
diff --git a/lib/services/exchange/nanswap/api_response_models/n_estimate.dart b/lib/services/exchange/nanswap/api_response_models/n_estimate.dart
new file mode 100644
index 000000000..4ee2f51f8
--- /dev/null
+++ b/lib/services/exchange/nanswap/api_response_models/n_estimate.dart
@@ -0,0 +1,32 @@
+class NEstimate {
+ final String from;
+ final String to;
+ final num amountFrom;
+ final num amountTo;
+
+ NEstimate({
+ required this.from,
+ required this.to,
+ required this.amountFrom,
+ required this.amountTo,
+ });
+
+ factory NEstimate.fromJson(Map json) {
+ return NEstimate(
+ from: json['from'] as String,
+ to: json['to'] as String,
+ amountFrom: json['amountFrom'] as num,
+ amountTo: json['amountTo'] as num,
+ );
+ }
+
+ @override
+ String toString() {
+ return 'NEstimate {'
+ 'from: $from, '
+ 'to: $to, '
+ 'amountFrom: $amountFrom, '
+ 'amountTo: $amountTo '
+ '}';
+ }
+}
diff --git a/lib/services/exchange/nanswap/api_response_models/n_trade.dart b/lib/services/exchange/nanswap/api_response_models/n_trade.dart
new file mode 100644
index 000000000..f26e19f3f
--- /dev/null
+++ b/lib/services/exchange/nanswap/api_response_models/n_trade.dart
@@ -0,0 +1,81 @@
+class NTrade {
+ final String id;
+ final String from;
+ final String to;
+ final num expectedAmountFrom;
+ final num expectedAmountTo;
+ final String payinAddress;
+ final String payoutAddress;
+
+ final String? payinExtraId;
+ final String? fullLink;
+ final String? status;
+ final String? payinHash;
+ final String? payoutHash;
+ final num? fromAmount;
+ final num? toAmount;
+ final String? fromNetwork;
+ final String? toNetwork;
+
+ NTrade({
+ required this.id,
+ required this.from,
+ required this.to,
+ required this.expectedAmountFrom,
+ required this.expectedAmountTo,
+ required this.payinAddress,
+ required this.payoutAddress,
+ this.payinExtraId,
+ this.fullLink,
+ this.status,
+ this.payinHash,
+ this.payoutHash,
+ this.fromAmount,
+ this.toAmount,
+ this.fromNetwork,
+ this.toNetwork,
+ });
+
+ factory NTrade.fromJson(Map json) {
+ return NTrade(
+ id: json['id'] as String,
+ from: json['from'] as String,
+ to: json['to'] as String,
+ expectedAmountFrom: num.parse(json['expectedAmountFrom'].toString()),
+ expectedAmountTo: json['expectedAmountTo'] as num,
+ payinAddress: json['payinAddress'] as String,
+ payoutAddress: json['payoutAddress'] as String,
+ fullLink: json['fullLink'] as String?,
+ payinExtraId: json['payinExtraId'] as String?,
+ status: json['status'] as String?,
+ payinHash: json['payinHash'] as String?,
+ payoutHash: json['payoutHash'] as String?,
+ fromAmount: json['fromAmount'] as num?,
+ toAmount: json['toAmount'] as num?,
+ fromNetwork: json['fromNetwork'] as String?,
+ toNetwork: json['toNetwork'] as String?,
+ );
+ }
+
+ @override
+ String toString() {
+ return 'NTrade {'
+ ' id: $id, '
+ ' from: $from, '
+ ' to: $to, '
+ ' expectedAmountFrom: $expectedAmountFrom, '
+ ' expectedAmountTo: $expectedAmountTo, '
+ ' payinAddress: $payinAddress, '
+ ' payoutAddress: $payoutAddress, '
+ ' fullLink: $fullLink, '
+ ' payinExtraId: $payinExtraId, '
+ ' status: $status, '
+ ' payinHash: $payinHash, '
+ ' payoutHash: $payoutHash '
+ ' fromAmount: $fromAmount, '
+ ' toAmount: $toAmount, '
+ ' fromNetwork: $fromNetwork, '
+ ' toNetwork: $toNetwork, '
+ '}';
+ }
+}
diff --git a/lib/services/exchange/nanswap/nanswap_api.dart b/lib/services/exchange/nanswap/nanswap_api.dart
new file mode 100644
index 000000000..41f06f19a
--- /dev/null
+++ b/lib/services/exchange/nanswap/nanswap_api.dart
@@ -0,0 +1,517 @@
+import 'dart:convert';
+
+import 'package:flutter/foundation.dart';
+
+import '../../../exceptions/exchange/exchange_exception.dart';
+import '../../../external_api_keys.dart';
+import '../../../networking/http.dart';
+import '../../../utilities/logger.dart';
+import '../../../utilities/prefs.dart';
+import '../../tor_service.dart';
+import '../exchange_response.dart';
+import 'api_response_models/n_currency.dart';
+import 'api_response_models/n_estimate.dart';
+import 'api_response_models/n_trade.dart';
+
+class NanswapAPI {
+ NanswapAPI._();
+
+ static const authority = "api.nanswap.com";
+ static const version = "v1";
+
+ static NanswapAPI? _instance;
+ static NanswapAPI get instance => _instance ??= NanswapAPI._();
+
+ final _client = HTTP();
+
+ Uri _buildUri({required String endpoint, Map? params}) {
+ return Uri.https(authority, "/$version/$endpoint", params);
+ }
+
+ Future _makeGetRequest(Uri uri) async {
+ int code = -1;
+ try {
+ final response = await _client.get(
+ url: uri,
+ headers: {
+ 'Accept': 'application/json',
+ },
+ proxyInfo: Prefs.instance.useTor
+ ? TorService.sharedInstance.getProxyInfo()
+ : null,
+ );
+
+ code = response.code;
+
+ final parsed = jsonDecode(response.body);
+
+ return parsed;
+ } catch (e, s) {
+ Logging.instance.log(
+ "NanswapAPI._makeRequest($uri) HTTP:$code threw: $e\n$s",
+ level: LogLevel.Error,
+ );
+ rethrow;
+ }
+ }
+
+ Future _makePostRequest(
+ Uri uri,
+ Map body,
+ ) async {
+ int code = -1;
+ try {
+ final response = await _client.post(
+ url: uri,
+ headers: {
+ 'nanswap-api-key': kNanswapApiKey,
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json',
+ },
+ body: jsonEncode(body),
+ proxyInfo: Prefs.instance.useTor
+ ? TorService.sharedInstance.getProxyInfo()
+ : null,
+ );
+
+ code = response.code;
+
+ final data = response.body;
+ final parsed = jsonDecode(data);
+
+ return parsed;
+ } catch (e, s) {
+ Logging.instance.log(
+ "NanswapAPI._makePostRequest($uri) HTTP:$code threw: $e\n$s",
+ level: LogLevel.Error,
+ );
+ rethrow;
+ }
+ }
+
+ // ============= API ===================================================
+
+ // GET List of supported currencies
+ // https://api.nanswap.com/v1/all-currencies
+ //
+ // Returns a Key => Value map of available currencies.
+ //
+ // The Key is the ticker, that can be used in the from and to params of the /get-estimate, /get-limit, /create-order.
+ //
+ // The Value is the currency info:
+ //
+ // name
+ //
+ // logo
+ //
+ // network Network of the crypto.
+ //
+ // hasExternalId Boolean. If the crypto require a memo/id.
+ //
+ // feeless Boolean. If crypto has 0 network fees.
+ //
+ // HEADERS
+ // Accept
+ //
+ // application/json
+ Future>> getSupportedCurrencies() async {
+ final uri = _buildUri(
+ endpoint: "all-currencies",
+ );
+
+ try {
+ final json = await _makeGetRequest(uri);
+
+ final List result = [];
+ for (final key in (json as Map).keys) {
+ final _map = json[key] as Map;
+ _map["id"] = key;
+ result.add(
+ NCurrency.fromJson(
+ Map.from(_map),
+ ),
+ );
+ }
+
+ return ExchangeResponse(value: result);
+ } catch (e, s) {
+ Logging.instance.log(
+ "Nanswap.getSupportedCurrencies() exception: $e\n$s",
+ level: LogLevel.Error,
+ );
+ return ExchangeResponse(
+ exception: ExchangeException(
+ e.toString(),
+ ExchangeExceptionType.generic,
+ ),
+ );
+ }
+ }
+
+ // GET Get estimate
+ // https://api.nanswap.com/v1/get-estimate?from=XNO&to=BAN&amount=10
+ //
+ // Get estimated exchange amount.
+ // HEADERS
+ // Accept
+ //
+ // application/json
+ // PARAMS
+ //
+ // from
+ // XNO
+ // Ticker from
+ //
+ // to
+ // BAN
+ // Ticker to
+ //
+ // amount
+ // 10
+ // Amount from
+ Future> getEstimate({
+ required String amountFrom,
+ required String from,
+ required String to,
+ }) async {
+ final uri = _buildUri(
+ endpoint: "get-estimate",
+ params: {
+ "to": to.toUpperCase(),
+ "from": from.toUpperCase(),
+ "amount": amountFrom,
+ },
+ );
+
+ try {
+ final json = await _makeGetRequest(uri);
+
+ try {
+ final map = Map.from(json as Map);
+
+ // not sure why the api responds without these sometimes...
+ map["to"] ??= to.toUpperCase();
+ map["from"] ??= from.toUpperCase();
+
+ return ExchangeResponse(
+ value: NEstimate.fromJson(
+ map,
+ ),
+ );
+ } catch (_) {
+ Logging.instance.log(
+ "Nanswap.getEstimate() response was: $json",
+ level: LogLevel.Error,
+ );
+ rethrow;
+ }
+ } catch (e, s) {
+ Logging.instance.log(
+ "Nanswap.getEstimate() exception: $e\n$s",
+ level: LogLevel.Error,
+ );
+ return ExchangeResponse(
+ exception: ExchangeException(
+ e.toString(),
+ ExchangeExceptionType.generic,
+ ),
+ );
+ }
+ }
+
+ // GET Get estimate reverse
+ // https://api.nanswap.com/v1/get-estimate-reverse?from=XNO&to=BAN&amount=1650
+ //
+ // (Only available for feeless crypto)
+ //
+ // Get estimate but reversed, it takes toAmount and returns the fromAmount
+ // estimation. Allows to let user input directly their toAmount wanted.
+ // HEADERS
+ // Accept
+ //
+ // application/json
+ // PARAMS
+ // from
+ // XNO
+ // Ticker from
+ //
+ // to
+ // BAN
+ // Ticker to
+ //
+ // amount
+ // 1650
+ // Amount to
+ Future> getEstimateReversed({
+ required String amountTo,
+ required String from,
+ required String to,
+ }) async {
+ final uri = _buildUri(
+ endpoint: "get-estimate-reverse",
+ params: {
+ "to": to.toUpperCase(),
+ "from": from.toUpperCase(),
+ "amount": amountTo,
+ },
+ );
+
+ try {
+ final json = await _makeGetRequest(uri);
+
+ final map = Map.from(json as Map);
+
+ // not sure why the api responds without these sometimes...
+ map["to"] ??= to.toUpperCase();
+ map["from"] ??= from.toUpperCase();
+
+ return ExchangeResponse(
+ value: NEstimate.fromJson(
+ map,
+ ),
+ );
+ } catch (e, s) {
+ Logging.instance.log(
+ "Nanswap.getEstimateReverse() exception: $e\n$s",
+ level: LogLevel.Error,
+ );
+ return ExchangeResponse(
+ exception: ExchangeException(
+ e.toString(),
+ ExchangeExceptionType.generic,
+ ),
+ );
+ }
+ }
+
+ // GET Get order limit amount
+ // https://api.nanswap.com/v1/get-limits?from=XNO&to=BAN
+ //
+ // Returns minimum and maximum from amount for a given pair. Maximum amount depends of current liquidity.
+ // HEADERS
+ // Accept
+ //
+ // application/json
+ // PARAMS
+ // from
+ // XNO
+ // Ticker from
+ //
+ // to
+ // BAN
+ // Ticker to
+ Future> getOrderLimits({
+ required String from,
+ required String to,
+ }) async {
+ final uri = _buildUri(
+ endpoint: "get-limits",
+ params: {
+ "to": to.toUpperCase(),
+ "from": from.toUpperCase(),
+ },
+ );
+
+ try {
+ final json = await _makeGetRequest(uri);
+
+ return ExchangeResponse(
+ value: (
+ minFrom: json["min"] as num,
+ maxFrom: json["max"] as num,
+ ),
+ );
+ } catch (e, s) {
+ Logging.instance.log(
+ "Nanswap.getOrderLimits() exception: $e\n$s",
+ level: LogLevel.Error,
+ );
+ return ExchangeResponse(
+ exception: ExchangeException(
+ e.toString(),
+ ExchangeExceptionType.generic,
+ ),
+ );
+ }
+ }
+
+ // POST Create a new order
+ // https://api.nanswap.com/v1/create-order
+ //
+ // Create a new order and returns order data. You need to send the request body as JSON.
+ // A valid API key is required in nanswap-api-key header for this request.
+ // You can get one at https://nanswap.com/API
+ // Request:
+ //
+ // * from ticker of currency you want to exchange
+ // * to ticker of currency you want to receive
+ // * amount The amount you want to send
+ // * toAddress The address that will recieve the exchanged funds
+ // * extraId (optional) Memo/Id of the toAddress
+ //
+ // * itemName (optional) An item name that will be displayed on transaction
+ // page. Can be used by merchant to provide a better UX to users. Max 128 char.
+ // * maxDurationSeconds (optional) Maximum seconds after what transaction
+ // expires. Min: 30s Max: 259200s. Default to 72h or 5min if itemName is set
+ // Reponse:
+ //
+ // * id Order id.
+ // * from ticker of currency you want to exchange
+ // * to ticker of currency you want to receive
+ // * expectedAmountFrom The amount you want to send
+ // * expectedAmountTo Estimated value that you will get based on the field expectedAmountFrom
+ // * payinAddress Nanswap's address you need to send the funds to
+ // * payinExtraId If present, the extra/memo id required for the payinAddress
+ // * payoutAddress The address that will recieve the exchanged funds
+ // * fullLink URL of the transaction
+ // AUTHORIZATIONAPI Key
+ // Key
+ //
+ // nanswap-api-key
+ // Value
+ //
+ //
+ // HEADERS
+ // nanswap-api-key
+ //
+ // API_KEY
+ //
+ // (Required)
+ // Content-Type
+ //
+ // application/json
+ // Accept
+ //
+ // application/json
+ Future> createOrder({
+ required String from,
+ required String to,
+ required num fromAmount,
+ required String toAddress,
+ String? extraIdOrMemo,
+ }) async {
+ final uri = _buildUri(
+ endpoint: "create-order",
+ );
+
+ final body = {
+ "from": from.toUpperCase(),
+ "to": to.toUpperCase(),
+ "amount": fromAmount,
+ "toAddress": toAddress,
+ };
+
+ if (extraIdOrMemo != null) {
+ body["extraId"] = extraIdOrMemo;
+ }
+
+ try {
+ final json = await _makePostRequest(uri, body);
+
+ try {
+ return ExchangeResponse(
+ value: NTrade.fromJson(
+ Map.from(json as Map),
+ ),
+ );
+ } catch (_) {
+ debugPrint(json.toString());
+ rethrow;
+ }
+ } catch (e, s) {
+ Logging.instance.log(
+ "Nanswap.createOrder() exception: $e\n$s",
+ level: LogLevel.Error,
+ );
+ return ExchangeResponse(
+ exception: ExchangeException(
+ e.toString(),
+ ExchangeExceptionType.generic,
+ ),
+ );
+ }
+ }
+
+ // GET Get order id data
+ // https://api.nanswap.com/v1/get-order?id=zYkxDxfmYRM
+ //
+ // Returns data of an order id.
+ // Response:
+ //
+ // id Order id.
+ //
+ // status Order status, can be one of the following : [waiting, exchanging, sending, completed, error]
+ //
+ // from ticker of currency you want to exchange
+ //
+ // fromNetwork network of the currency you want to exchange.
+ //
+ // to ticker of currency you want to receive
+ //
+ // toNetwork network of the currency you want to receive.
+ //
+ // expectedAmountFrom The amount you want to send
+ //
+ // expectedAmountTo Estimated value that you will get based on the field expectedAmountFrom
+ //
+ // amountFrom From Amount Exchanged
+ //
+ // amountTo To Amount Exchanged
+ //
+ // payinAddress Nanswap's address you need to send the funds to
+ //
+ // payinExtraId If present, the extra/memo id required for the payinAddress
+ //
+ // payoutAddress The address that will recieve the exchanged funds
+ //
+ // payinHash Hash of the transaction you sent us
+ //
+ // senderAddress Address which sent us the funds
+ //
+ // payoutHash Hash of the transaction we sent to you
+ //
+ // HEADERS
+ // Accept
+ //
+ // application/json
+ // PARAMS
+ // id
+ //
+ // zYkxDxfmYRM
+ //
+ // The order id
+ Future> getOrder({required String id}) async {
+ final uri = _buildUri(
+ endpoint: "get-order",
+ params: {
+ "id": id,
+ },
+ );
+
+ try {
+ final json = await _makeGetRequest(uri);
+
+ try {
+ return ExchangeResponse(
+ value: NTrade.fromJson(
+ Map.from(json as Map),
+ ),
+ );
+ } catch (_) {
+ debugPrint(json.toString());
+ rethrow;
+ }
+ } catch (e, s) {
+ Logging.instance.log(
+ "Nanswap.getOrder($id) exception: $e\n$s",
+ level: LogLevel.Error,
+ );
+ return ExchangeResponse(
+ exception: ExchangeException(
+ e.toString(),
+ ExchangeExceptionType.generic,
+ ),
+ );
+ }
+ }
+}
diff --git a/lib/services/exchange/nanswap/nanswap_exchange.dart b/lib/services/exchange/nanswap/nanswap_exchange.dart
new file mode 100644
index 000000000..a2de87c33
--- /dev/null
+++ b/lib/services/exchange/nanswap/nanswap_exchange.dart
@@ -0,0 +1,461 @@
+import 'package:decimal/decimal.dart';
+import 'package:uuid/uuid.dart';
+
+import '../../../app_config.dart';
+import '../../../exceptions/exchange/exchange_exception.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 '../exchange.dart';
+import '../exchange_response.dart';
+import 'api_response_models/n_estimate.dart';
+import 'nanswap_api.dart';
+
+class NanswapExchange extends Exchange {
+ NanswapExchange._();
+
+ static NanswapExchange? _instance;
+ static NanswapExchange get instance => _instance ??= NanswapExchange._();
+
+ static const exchangeName = "Nanswap";
+
+ static const filter = ["BTC", "BAN", "XNO"];
+
+ @override
+ bool get supportsRefundAddress => false;
+
+ @override
+ Future> createTrade({
+ required String from,
+ required String to,
+ required bool fixedRate,
+ required Decimal amount,
+ required String addressTo,
+ String? extraId,
+ required String addressRefund,
+ required String refundExtraId,
+ Estimate? estimate,
+ required bool reversed,
+ }) async {
+ try {
+ if (fixedRate) {
+ throw ExchangeException(
+ "Nanswap fixedRate not available",
+ ExchangeExceptionType.generic,
+ );
+ }
+ if (refundExtraId.isNotEmpty) {
+ throw ExchangeException(
+ "Nanswap refundExtraId not available",
+ ExchangeExceptionType.generic,
+ );
+ }
+ if (addressRefund.isNotEmpty) {
+ throw ExchangeException(
+ "Nanswap addressRefund not available",
+ ExchangeExceptionType.generic,
+ );
+ }
+ if (reversed) {
+ throw ExchangeException(
+ "Nanswap reversed not available",
+ ExchangeExceptionType.generic,
+ );
+ }
+
+ final response = await NanswapAPI.instance.createOrder(
+ from: from,
+ to: to,
+ fromAmount: amount.toDouble(),
+ toAddress: addressTo,
+ extraIdOrMemo: extraId,
+ );
+
+ if (response.exception != null) {
+ return ExchangeResponse(
+ exception: response.exception,
+ );
+ }
+
+ final t = response.value!;
+ print(t);
+
+ return ExchangeResponse(
+ value: Trade(
+ uuid: const Uuid().v1(),
+ tradeId: t.id,
+ rateType: "estimated",
+ direction: "normal",
+ timestamp: DateTime.now(),
+ updatedAt: DateTime.now(),
+ payInCurrency: from,
+ payInAmount: t.expectedAmountFrom.toString(),
+ payInAddress: t.payinAddress,
+ payInNetwork: t.toNetwork ?? t.to,
+ payInExtraId: t.payinExtraId ?? "",
+ payInTxid: t.payinHash ?? "",
+ payOutCurrency: to,
+ payOutAmount: t.expectedAmountTo.toString(),
+ payOutAddress: t.payoutAddress,
+ payOutNetwork: t.fromNetwork ?? t.from,
+ payOutExtraId: "",
+ payOutTxid: t.payoutHash ?? "",
+ refundAddress: "",
+ refundExtraId: "",
+ status: "waiting",
+ exchangeName: exchangeName,
+ ),
+ );
+ } on ExchangeException catch (e) {
+ return ExchangeResponse(
+ exception: e,
+ );
+ } catch (e) {
+ return ExchangeResponse(
+ exception: ExchangeException(
+ e.toString(),
+ ExchangeExceptionType.generic,
+ ),
+ );
+ }
+ }
+
+ @override
+ Future>> getAllCurrencies(
+ bool fixedRate,
+ ) async {
+ try {
+ if (fixedRate) {
+ throw ExchangeException(
+ "Nanswap fixedRate not available",
+ ExchangeExceptionType.generic,
+ );
+ }
+
+ final response = await NanswapAPI.instance.getSupportedCurrencies();
+
+ if (response.exception != null) {
+ 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(),
+ );
+ } on ExchangeException catch (e) {
+ return ExchangeResponse(
+ exception: e,
+ );
+ } catch (e) {
+ return ExchangeResponse(
+ exception: ExchangeException(
+ e.toString(),
+ ExchangeExceptionType.generic,
+ ),
+ );
+ }
+ }
+
+ @override
+ Future>> getAllPairs(bool fixedRate) async {
+ throw UnimplementedError();
+ }
+
+ @override
+ Future>> getEstimates(
+ String from,
+ String to,
+ Decimal amount,
+ bool fixedRate,
+ bool reversed,
+ ) async {
+ try {
+ if (fixedRate) {
+ throw ExchangeException(
+ "Nanswap fixedRate not available",
+ ExchangeExceptionType.generic,
+ );
+ }
+
+ final ExchangeResponse response;
+ if (reversed) {
+ response = await NanswapAPI.instance.getEstimateReversed(
+ from: from,
+ to: to,
+ amountTo: amount.toString(),
+ );
+ } else {
+ response = await NanswapAPI.instance.getEstimate(
+ from: from,
+ to: to,
+ amountFrom: amount.toString(),
+ );
+ }
+
+ if (response.exception != null) {
+ return ExchangeResponse(
+ exception: response.exception,
+ );
+ }
+
+ final t = response.value!;
+
+ return ExchangeResponse(
+ value: [
+ Estimate(
+ estimatedAmount: Decimal.parse(
+ (reversed ? t.amountFrom : t.amountTo).toString(),
+ ),
+ fixedRate: fixedRate,
+ reversed: reversed,
+ exchangeProvider: exchangeName,
+ ),
+ ],
+ );
+ } on ExchangeException catch (e) {
+ return ExchangeResponse(
+ exception: e,
+ );
+ } catch (e) {
+ return ExchangeResponse(
+ exception: ExchangeException(
+ e.toString(),
+ ExchangeExceptionType.generic,
+ ),
+ );
+ }
+ }
+
+ @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> getRange(
+ String from,
+ String to,
+ bool fixedRate,
+ ) async {
+ try {
+ if (fixedRate) {
+ throw ExchangeException(
+ "Nanswap fixedRate not available",
+ ExchangeExceptionType.generic,
+ );
+ }
+
+ final response = await NanswapAPI.instance.getOrderLimits(
+ from: from,
+ to: to,
+ );
+
+ if (response.exception != null) {
+ return ExchangeResponse(
+ exception: response.exception,
+ );
+ }
+
+ final t = response.value!;
+
+ return ExchangeResponse(
+ value: Range(
+ min: Decimal.parse(t.minFrom.toString()),
+ max: Decimal.parse(t.maxFrom.toString()),
+ ),
+ );
+ } on ExchangeException catch (e) {
+ return ExchangeResponse(
+ exception: e,
+ );
+ } catch (e) {
+ return ExchangeResponse(
+ exception: ExchangeException(
+ e.toString(),
+ ExchangeExceptionType.generic,
+ ),
+ );
+ }
+ }
+
+ @override
+ Future> getTrade(String tradeId) async {
+ try {
+ final response = await NanswapAPI.instance.getOrder(
+ id: tradeId,
+ );
+
+ if (response.exception != null) {
+ return ExchangeResponse(
+ exception: response.exception,
+ );
+ }
+
+ final t = response.value!;
+
+ return ExchangeResponse(
+ value: Trade(
+ uuid: const Uuid().v1(),
+ tradeId: t.id,
+ rateType: "estimated",
+ direction: "normal",
+ timestamp: DateTime.now(),
+ updatedAt: DateTime.now(),
+ payInCurrency: t.from,
+ payInAmount: t.expectedAmountFrom.toString(),
+ payInAddress: t.payinAddress,
+ payInNetwork: t.toNetwork ?? t.to,
+ payInExtraId: t.payinExtraId ?? "",
+ payInTxid: t.payinHash ?? "",
+ payOutCurrency: t.to,
+ payOutAmount: t.expectedAmountTo.toString(),
+ payOutAddress: t.payoutAddress,
+ payOutNetwork: t.fromNetwork ?? t.from,
+ payOutExtraId: "",
+ payOutTxid: t.payoutHash ?? "",
+ refundAddress: "",
+ refundExtraId: "",
+ status: t.status ?? "unknown",
+ exchangeName: exchangeName,
+ ),
+ );
+ } on ExchangeException catch (e) {
+ return ExchangeResponse(
+ exception: e,
+ );
+ } catch (e) {
+ return ExchangeResponse(
+ exception: ExchangeException(
+ e.toString(),
+ ExchangeExceptionType.generic,
+ ),
+ );
+ }
+ }
+
+ @override
+ Future>> getTrades() async {
+ // TODO: implement getTrades
+ throw UnimplementedError();
+ }
+
+ @override
+ String get name => exchangeName;
+
+ @override
+ Future> updateTrade(Trade trade) async {
+ try {
+ final response = await NanswapAPI.instance.getOrder(
+ id: trade.tradeId,
+ );
+
+ if (response.exception != null) {
+ return ExchangeResponse(
+ exception: response.exception,
+ );
+ }
+
+ final t = response.value!;
+
+ return ExchangeResponse(
+ value: Trade(
+ uuid: trade.uuid,
+ tradeId: t.id,
+ rateType: trade.rateType,
+ direction: trade.rateType,
+ timestamp: trade.timestamp,
+ updatedAt: DateTime.now(),
+ payInCurrency: t.from,
+ payInAmount: t.expectedAmountFrom.toString(),
+ payInAddress: t.payinAddress,
+ payInNetwork: t.toNetwork ?? trade.payInNetwork,
+ payInExtraId: t.payinExtraId ?? trade.payInExtraId,
+ payInTxid: t.payinHash ?? trade.payInTxid,
+ payOutCurrency: t.to,
+ payOutAmount: t.expectedAmountTo.toString(),
+ payOutAddress: t.payoutAddress,
+ payOutNetwork: t.fromNetwork ?? trade.payOutNetwork,
+ payOutExtraId: trade.payOutExtraId,
+ payOutTxid: t.payoutHash ?? trade.payOutTxid,
+ refundAddress: trade.refundAddress,
+ refundExtraId: trade.refundExtraId,
+ status: t.status ?? "unknown",
+ exchangeName: exchangeName,
+ ),
+ );
+ } on ExchangeException catch (e) {
+ return ExchangeResponse(
+ exception: e,
+ );
+ } catch (e) {
+ return ExchangeResponse(
+ exception: ExchangeException(
+ e.toString(),
+ ExchangeExceptionType.generic,
+ ),
+ );
+ }
+ }
+}
diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart
index 84dad9ae6..c8bfaf1fb 100644
--- a/lib/utilities/assets.dart
+++ b/lib/utilities/assets.dart
@@ -9,8 +9,10 @@
*/
import 'package:flutter/material.dart';
+
import '../services/exchange/change_now/change_now_exchange.dart';
import '../services/exchange/majestic_bank/majestic_bank_exchange.dart';
+import '../services/exchange/nanswap/nanswap_exchange.dart';
import '../services/exchange/simpleswap/simpleswap_exchange.dart';
import '../services/exchange/trocador/trocador_exchange.dart';
@@ -45,6 +47,7 @@ class _EXCHANGE {
String get majesticBankBlue => "${_path}mb_blue.svg";
String get majesticBankGreen => "${_path}mb_green.svg";
String get trocador => "${_path}trocador.svg";
+ String get nanswap => "${_path}nanswap.svg";
String getIconFor({required String exchangeName}) {
switch (exchangeName) {
@@ -56,6 +59,8 @@ class _EXCHANGE {
return majesticBankBlue;
case TrocadorExchange.exchangeName:
return trocador;
+ case NanswapExchange.exchangeName:
+ return nanswap;
default:
throw ArgumentError("Invalid exchange name passed to "
"Assets.exchange.getIconFor()");