mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-16 09:17:37 +00:00
nanswap
This commit is contained in:
parent
40f8767b11
commit
df3935d5d4
20 changed files with 1605 additions and 172 deletions
69
asset_sources/svg/campfire/exchange_icons/nanswap.svg
Normal file
69
asset_sources/svg/campfire/exchange_icons/nanswap.svg
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="75.209999mm"
|
||||
height="75.209999mm"
|
||||
viewBox="0 0 213.19692 213.2"
|
||||
version="1.1"
|
||||
id="svg30"
|
||||
sodipodi:docname="nanswap2.svg"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
|
||||
<metadata
|
||||
id="metadata36">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>nanswap</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs34" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="2635"
|
||||
inkscape:window-height="1461"
|
||||
id="namedview32"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.29390619"
|
||||
inkscape:cx="-79.957486"
|
||||
inkscape:cy="142.12913"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg30" />
|
||||
<title
|
||||
id="title2">nanswap</title>
|
||||
<circle
|
||||
cx="106.6"
|
||||
cy="106.6"
|
||||
r="106.6"
|
||||
id="circle4"
|
||||
style="fill:#4a90e2" />
|
||||
<path
|
||||
d="m 166.7,66.500006 a 20,20 0 0 1 -20,20 c -15,0 -20,5 -20,19.999994 a 20,20 0 0 1 -20,20 c -15,0 -20,5 -20,20 a 20,20 0 1 1 -20,-20 c 15,0 20,-5 20,-20 a 20,20 0 0 1 20,-19.999994 c 15,0 20,-5 20,-20 a 20,20 0 0 1 40,0 z"
|
||||
id="path6"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000034" />
|
||||
<path
|
||||
d="m 66.5,46.500006 a 20,20 0 0 1 20,20 c 0,15 5,20 20,20 A 20,20 0 0 1 126.5,106.5 c 0,15 5,20 20,20 a 20,20 0 1 1 -20,20 c 0,-15 -5,-20 -20,-20 a 20,20 0 0 1 -20,-20 c 0,-14.999994 -5,-19.999994 -20,-19.999994 a 20,20 0 0 1 0,-40 z"
|
||||
id="path8"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000034" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
69
asset_sources/svg/stack_duo/exchange_icons/nanswap.svg
Normal file
69
asset_sources/svg/stack_duo/exchange_icons/nanswap.svg
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="75.209999mm"
|
||||
height="75.209999mm"
|
||||
viewBox="0 0 213.19692 213.2"
|
||||
version="1.1"
|
||||
id="svg30"
|
||||
sodipodi:docname="nanswap2.svg"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
|
||||
<metadata
|
||||
id="metadata36">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>nanswap</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs34" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="2635"
|
||||
inkscape:window-height="1461"
|
||||
id="namedview32"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.29390619"
|
||||
inkscape:cx="-79.957486"
|
||||
inkscape:cy="142.12913"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg30" />
|
||||
<title
|
||||
id="title2">nanswap</title>
|
||||
<circle
|
||||
cx="106.6"
|
||||
cy="106.6"
|
||||
r="106.6"
|
||||
id="circle4"
|
||||
style="fill:#4a90e2" />
|
||||
<path
|
||||
d="m 166.7,66.500006 a 20,20 0 0 1 -20,20 c -15,0 -20,5 -20,19.999994 a 20,20 0 0 1 -20,20 c -15,0 -20,5 -20,20 a 20,20 0 1 1 -20,-20 c 15,0 20,-5 20,-20 a 20,20 0 0 1 20,-19.999994 c 15,0 20,-5 20,-20 a 20,20 0 0 1 40,0 z"
|
||||
id="path6"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000034" />
|
||||
<path
|
||||
d="m 66.5,46.500006 a 20,20 0 0 1 20,20 c 0,15 5,20 20,20 A 20,20 0 0 1 126.5,106.5 c 0,15 5,20 20,20 a 20,20 0 1 1 -20,20 c 0,-15 -5,-20 -20,-20 a 20,20 0 0 1 -20,-20 c 0,-14.999994 -5,-19.999994 -20,-19.999994 a 20,20 0 0 1 0,-40 z"
|
||||
id="path8"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000034" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
69
asset_sources/svg/stack_wallet/exchange_icons/nanswap.svg
Normal file
69
asset_sources/svg/stack_wallet/exchange_icons/nanswap.svg
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="75.209999mm"
|
||||
height="75.209999mm"
|
||||
viewBox="0 0 213.19692 213.2"
|
||||
version="1.1"
|
||||
id="svg30"
|
||||
sodipodi:docname="nanswap2.svg"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
|
||||
<metadata
|
||||
id="metadata36">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>nanswap</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs34" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="2635"
|
||||
inkscape:window-height="1461"
|
||||
id="namedview32"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.29390619"
|
||||
inkscape:cx="-79.957486"
|
||||
inkscape:cy="142.12913"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg30" />
|
||||
<title
|
||||
id="title2">nanswap</title>
|
||||
<circle
|
||||
cx="106.6"
|
||||
cy="106.6"
|
||||
r="106.6"
|
||||
id="circle4"
|
||||
style="fill:#4a90e2" />
|
||||
<path
|
||||
d="m 166.7,66.500006 a 20,20 0 0 1 -20,20 c -15,0 -20,5 -20,19.999994 a 20,20 0 0 1 -20,20 c -15,0 -20,5 -20,20 a 20,20 0 1 1 -20,-20 c 15,0 20,-5 20,-20 a 20,20 0 0 1 20,-19.999994 c 15,0 20,-5 20,-20 a 20,20 0 0 1 40,0 z"
|
||||
id="path6"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000034" />
|
||||
<path
|
||||
d="m 66.5,46.500006 a 20,20 0 0 1 20,20 c 0,15 5,20 20,20 A 20,20 0 0 1 126.5,106.5 c 0,15 5,20 20,20 a 20,20 0 1 1 -20,20 c 0,-15 -5,-20 -20,-20 a 20,20 0 0 1 -20,-20 c 0,-14.999994 -5,-19.999994 -20,-19.999994 a 20,20 0 0 1 0,-40 z"
|
||||
id="path8"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000034" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
|
@ -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(
|
||||
|
|
|
@ -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<ExchangeForm> {
|
|||
MajesticBankExchange.instance,
|
||||
ChangeNowExchange.instance,
|
||||
TrocadorExchange.instance,
|
||||
NanswapExchange.instance,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Step2View> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final supportsRefund =
|
||||
ref.watch(efExchangeProvider).name != MajesticBankExchange.exchangeName;
|
||||
final supportsRefund = ref.watch(efExchangeProvider).supportsRefundAddress;
|
||||
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
|
|
|
@ -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<Step3View> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final supportsRefund =
|
||||
ref.watch(efExchangeProvider).name != MajesticBankExchange.exchangeName;
|
||||
final supportsRefund = ref.watch(efExchangeProvider).supportsRefundAddress;
|
||||
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
|
|
|
@ -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<StackColors>()!.background,
|
||||
)
|
||||
: const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
if (showNanswap)
|
||||
ExchangeOption(
|
||||
fixedRate: widget.fixedRate,
|
||||
reversed: widget.reversed,
|
||||
exchange: NanswapExchange.instance,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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<TradeDetailsView> {
|
|||
url =
|
||||
"https://majesticbank.sc/track?trx=${trade.tradeId}";
|
||||
break;
|
||||
case NanswapExchange.exchangeName:
|
||||
url =
|
||||
"https://nanswap.com/transaction/${trade.tradeId}";
|
||||
break;
|
||||
|
||||
default:
|
||||
if (trade.exchangeName
|
||||
|
|
|
@ -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<DesktopStep2> {
|
|||
}
|
||||
|
||||
widget.enableNextChanged.call(
|
||||
_toController.text.isNotEmpty && _refundController.text.isNotEmpty,
|
||||
_next(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -120,7 +119,7 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
|
|||
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<DesktopStep2> {
|
|||
_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<DesktopStep2> {
|
|||
_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<DesktopStep2> {
|
|||
_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<DesktopStep2> {
|
|||
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<DesktopStep2> {
|
|||
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<DesktopStep2> {
|
|||
.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<DesktopStep2> {
|
|||
.read(desktopExchangeModelProvider)!
|
||||
.recipientAddress = _toController.text;
|
||||
widget.enableNextChanged.call(
|
||||
_toController.text.isNotEmpty &&
|
||||
_refundController.text.isNotEmpty,
|
||||
_next(),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -435,155 +449,158 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
|
|||
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<StackColors>()!
|
||||
.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: <TextInputFormatter>[
|
||||
// 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<StackColors>()!
|
||||
.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: <TextInputFormatter>[
|
||||
// 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<StackColors>()!.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<StackColors>()!.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),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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<DesktopStep3> {
|
|||
) ??
|
||||
"Error",
|
||||
),
|
||||
Container(
|
||||
height: 1,
|
||||
color: Theme.of(context).extension<StackColors>()!.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<StackColors>()!.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",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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<ExchangeResponse<List<Currency>>> getAllCurrencies(bool fixedRate);
|
||||
|
||||
Future<ExchangeResponse<List<Currency>>> getPairedCurrencies(
|
||||
|
@ -97,6 +103,7 @@ abstract class Exchange {
|
|||
static List<Exchange> get exchangesWithTorSupport => [
|
||||
MajesticBankExchange.instance,
|
||||
TrocadorExchange.instance,
|
||||
NanswapExchange.instance, // Maybe??
|
||||
];
|
||||
|
||||
/// List of exchange names which support Tor.
|
||||
|
|
|
@ -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<void> 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<void> loadMajesticBankPairs() async {
|
||||
// final exchange = MajesticBankExchange.instance;
|
||||
//
|
||||
|
|
|
@ -46,6 +46,9 @@ class MajesticBankExchange extends Exchange {
|
|||
"XMR": "Monero",
|
||||
};
|
||||
|
||||
@override
|
||||
bool get supportsRefundAddress => false;
|
||||
|
||||
@override
|
||||
Future<ExchangeResponse<Trade>> createTrade({
|
||||
required String from,
|
||||
|
|
|
@ -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<String, dynamic> 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'
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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<String, dynamic> 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 '
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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<String, dynamic> 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, '
|
||||
'}';
|
||||
}
|
||||
}
|
517
lib/services/exchange/nanswap/nanswap_api.dart
Normal file
517
lib/services/exchange/nanswap/nanswap_api.dart
Normal file
|
@ -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<String, String>? params}) {
|
||||
return Uri.https(authority, "/$version/$endpoint", params);
|
||||
}
|
||||
|
||||
Future<dynamic> _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<dynamic> _makePostRequest(
|
||||
Uri uri,
|
||||
Map<String, dynamic> 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<ExchangeResponse<List<NCurrency>>> getSupportedCurrencies() async {
|
||||
final uri = _buildUri(
|
||||
endpoint: "all-currencies",
|
||||
);
|
||||
|
||||
try {
|
||||
final json = await _makeGetRequest(uri);
|
||||
|
||||
final List<NCurrency> result = [];
|
||||
for (final key in (json as Map).keys) {
|
||||
final _map = json[key] as Map;
|
||||
_map["id"] = key;
|
||||
result.add(
|
||||
NCurrency.fromJson(
|
||||
Map<String, dynamic>.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<ExchangeResponse<NEstimate>> 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<String, dynamic>.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<ExchangeResponse<NEstimate>> 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<String, dynamic>.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<ExchangeResponse<({num minFrom, num maxFrom})>> 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
|
||||
//
|
||||
// <value>
|
||||
// HEADERS
|
||||
// nanswap-api-key
|
||||
//
|
||||
// API_KEY
|
||||
//
|
||||
// (Required)
|
||||
// Content-Type
|
||||
//
|
||||
// application/json
|
||||
// Accept
|
||||
//
|
||||
// application/json
|
||||
Future<ExchangeResponse<NTrade>> 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<String, dynamic>.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<ExchangeResponse<NTrade>> 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<String, dynamic>.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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
461
lib/services/exchange/nanswap/nanswap_exchange.dart
Normal file
461
lib/services/exchange/nanswap/nanswap_exchange.dart
Normal file
|
@ -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<ExchangeResponse<Trade>> 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<ExchangeResponse<List<Currency>>> 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<ExchangeResponse<List<Pair>>> getAllPairs(bool fixedRate) async {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ExchangeResponse<List<Estimate>>> 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<NEstimate> 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<ExchangeResponse<List<Currency>>> 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<ExchangeResponse<List<Pair>>> getPairsFor(
|
||||
String currency,
|
||||
bool fixedRate,
|
||||
) async {
|
||||
throw UnsupportedError("Not used");
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ExchangeResponse<Range>> 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<ExchangeResponse<Trade>> 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<ExchangeResponse<List<Trade>>> getTrades() async {
|
||||
// TODO: implement getTrades
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
String get name => exchangeName;
|
||||
|
||||
@override
|
||||
Future<ExchangeResponse<Trade>> 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()");
|
||||
|
|
Loading…
Reference in a new issue