From 3025fc359ac1c44c411f117966cf8eabbee4dd68 Mon Sep 17 00:00:00 2001
From: sneurlax <sneurlax@gmail.com>
Date: Wed, 3 Jul 2024 17:01:47 -0500
Subject: [PATCH 01/12] add address type to desktop receive

---
 .../sub_widgets/desktop_receive.dart          | 143 +++++++++++++++---
 1 file changed, 121 insertions(+), 22 deletions(-)

diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart
index 0da2a6607..a4e4a16cb 100644
--- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart
+++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart
@@ -29,11 +29,15 @@ import '../../../../utilities/address_utils.dart';
 import '../../../../utilities/assets.dart';
 import '../../../../utilities/clipboard_interface.dart';
 import '../../../../utilities/constants.dart';
+import '../../../../utilities/enums/derive_path_type_enum.dart';
 import '../../../../utilities/text_styles.dart';
 import '../../../../utilities/util.dart';
 import '../../../../wallets/crypto_currency/crypto_currency.dart';
 import '../../../../wallets/isar/providers/eth/current_token_wallet_provider.dart';
 import '../../../../wallets/isar/providers/wallet_info_provider.dart';
+import '../../../../wallets/wallet/impl/bitcoin_wallet.dart';
+import '../../../../wallets/wallet/intermediate/bip39_hd_wallet.dart';
+import '../../../../wallets/wallet/wallet_mixin_interfaces/bcash_interface.dart';
 import '../../../../wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart';
 import '../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
 import '../../../../widgets/conditional_parent.dart';
@@ -52,6 +56,8 @@ class DesktopReceive extends ConsumerStatefulWidget {
     this.clipboard = const ClipboardWrapper(),
   });
 
+  static const String routeName = "/desktopReceive";
+
   final String walletId;
   final String? contractAddress;
   final ClipboardInterface clipboard;
@@ -65,11 +71,18 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
   late final String walletId;
   late final ClipboardInterface clipboard;
   late final bool supportsSpark;
+  late final bool showMultiType;
 
   String? _sparkAddress;
   String? _qrcodeContent;
   bool _showSparkAddress = true;
 
+  int _currentIndex = 0;
+
+  final List<AddressType> _walletAddressTypes = [];
+  final Map<AddressType, String> _addressMap = {};
+  final Map<AddressType, StreamSubscription<Address?>> _addressSubMap = {};
+
   Future<void> generateNewAddress() async {
     final wallet = ref.read(pWallets).getWallet(walletId);
     if (wallet is MultiAddressInterface) {
@@ -97,10 +110,32 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
 
       await wallet.generateNewReceivingAddress();
 
+      final Address? address;
+      if (wallet is Bip39HDWallet && wallet is! BCashInterface) {
+        final type = DerivePathType.values.firstWhere(
+          (e) => e.getAddressType() == _walletAddressTypes[_currentIndex],
+        );
+        address = await wallet.generateNextReceivingAddress(
+          derivePathType: type,
+        );
+        await ref.read(mainDBProvider).isar.writeTxn(() async {
+          await ref.read(mainDBProvider).isar.addresses.put(address!);
+        });
+      } else {
+        await wallet.generateNewReceivingAddress();
+        address = null;
+      }
+
       shouldPop = true;
 
       if (mounted) {
-        Navigator.of(context, rootNavigator: true).pop();
+        Navigator.of(context)
+            .popUntil(ModalRoute.withName(DesktopReceive.routeName));
+
+        setState(() {
+          _addressMap[_walletAddressTypes[_currentIndex]] =
+              address?.value ?? ref.read(pWalletReceivingAddress(walletId));
+        });
       }
     }
   }
@@ -155,7 +190,57 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
     walletId = widget.walletId;
     coin = ref.read(pWalletInfo(walletId)).coin;
     clipboard = widget.clipboard;
+    final wallet = ref.read(pWallets).getWallet(walletId);
     supportsSpark = ref.read(pWallets).getWallet(walletId) is SparkInterface;
+    showMultiType = supportsSpark ||
+        ref.read(pWallets).getWallet(walletId) is MultiAddressInterface;
+
+    _walletAddressTypes.add(wallet.info.mainAddressType);
+
+    if (showMultiType) {
+      if (supportsSpark) {
+        _walletAddressTypes.insert(0, AddressType.spark);
+      } else {
+        _walletAddressTypes.addAll(
+          (wallet as Bip39HDWallet)
+              .supportedAddressTypes
+              .where((e) => e != wallet.info.mainAddressType),
+        );
+      }
+    }
+
+    if (_walletAddressTypes.length > 1 && wallet is BitcoinWallet) {
+      _walletAddressTypes.removeWhere((e) => e == AddressType.p2pkh);
+    }
+
+    _addressMap[_walletAddressTypes[_currentIndex]] =
+        ref.read(pWalletReceivingAddress(walletId));
+
+    if (showMultiType) {
+      for (final type in _walletAddressTypes) {
+        _addressSubMap[type] = ref
+            .read(mainDBProvider)
+            .isar
+            .addresses
+            .where()
+            .walletIdEqualTo(walletId)
+            .filter()
+            .typeEqualTo(type)
+            .sortByDerivationIndexDesc()
+            .findFirst()
+            .asStream()
+            .listen((event) {
+          WidgetsBinding.instance.addPostFrameCallback((_) {
+            if (mounted) {
+              setState(() {
+                _addressMap[type] =
+                    event?.value ?? _addressMap[type] ?? "[No address yet]";
+              });
+            }
+          });
+        });
+      }
+    }
 
     if (supportsSpark) {
       _streamSub = ref
@@ -193,6 +278,13 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
   Widget build(BuildContext context) {
     debugPrint("BUILD: $runtimeType");
 
+    final String address;
+    if (showMultiType) {
+      address = _addressMap[_walletAddressTypes[_currentIndex]]!;
+    } else {
+      address = ref.watch(pWalletReceivingAddress(walletId));
+    }
+
     if (supportsSpark) {
       if (_showSparkAddress) {
         _qrcodeContent = _sparkAddress;
@@ -207,33 +299,30 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
       crossAxisAlignment: CrossAxisAlignment.stretch,
       children: [
         ConditionalParent(
-          condition: supportsSpark,
+          condition: showMultiType,
           builder: (child) => Column(
             crossAxisAlignment: CrossAxisAlignment.stretch,
             children: [
               DropdownButtonHideUnderline(
-                child: DropdownButton2<bool>(
-                  value: _showSparkAddress,
+                child: DropdownButton2<int>(
+                  value: _currentIndex,
                   items: [
-                    DropdownMenuItem(
-                      value: true,
-                      child: Text(
-                        "Spark address",
-                        style: STextStyles.desktopTextMedium(context),
+                    for (int i = 0; i < _walletAddressTypes.length; i++)
+                      DropdownMenuItem(
+                        value: i,
+                        child: Text(
+                          supportsSpark &&
+                                  _walletAddressTypes[i] == AddressType.p2pkh
+                              ? "Transparent address"
+                              : "${_walletAddressTypes[i].readableName} address",
+                          style: STextStyles.w500_14(context),
+                        ),
                       ),
-                    ),
-                    DropdownMenuItem(
-                      value: false,
-                      child: Text(
-                        "Transparent address",
-                        style: STextStyles.desktopTextMedium(context),
-                      ),
-                    ),
                   ],
                   onChanged: (value) {
-                    if (value is bool && value != _showSparkAddress) {
+                    if (value != null && value != _currentIndex) {
                       setState(() {
-                        _showSparkAddress = value;
+                        _currentIndex = value;
                       });
                     }
                   },
@@ -251,6 +340,16 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
                       ),
                     ),
                   ),
+                  buttonStyleData: ButtonStyleData(
+                    decoration: BoxDecoration(
+                      color: Theme.of(context)
+                          .extension<StackColors>()!
+                          .textFieldDefaultBG,
+                      borderRadius: BorderRadius.circular(
+                        Constants.size.circularBorderRadius,
+                      ),
+                    ),
+                  ),
                   dropdownStyleData: DropdownStyleData(
                     offset: const Offset(0, -10),
                     elevation: 0,
@@ -280,7 +379,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
                   child: GestureDetector(
                     onTap: () {
                       clipboard.setData(
-                        ClipboardData(text: _sparkAddress ?? "Error"),
+                        ClipboardData(text: address),
                       );
                       showFloatingFlushBar(
                         type: FlushBarType.info,
@@ -311,7 +410,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
                                       pCurrentTokenWallet.select(
                                         (value) => value!.tokenContract.symbol,
                                       ),
-                                    )} SPARK address",
+                                    )}${supportsSpark ? " SPARK" : ""} address",
                                   style: STextStyles.itemSubtitle(context),
                                 ),
                                 const Spacer(),
@@ -343,7 +442,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
                               children: [
                                 Expanded(
                                   child: Text(
-                                    _sparkAddress ?? "Error",
+                                    address,
                                     style:
                                         STextStyles.desktopTextExtraExtraSmall(
                                       context,

From 89718c6933d9d1a37ecba19ad5c1d78e5fb800f3 Mon Sep 17 00:00:00 2001
From: julian <julian@cypherstack.com>
Date: Thu, 4 Jul 2024 11:20:11 -0600
Subject: [PATCH 02/12] small tweaks and fixes

---
 .../sub_widgets/desktop_receive.dart          | 153 ++----------------
 1 file changed, 11 insertions(+), 142 deletions(-)

diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart
index a4e4a16cb..50c76ab7e 100644
--- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart
+++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart
@@ -56,8 +56,6 @@ class DesktopReceive extends ConsumerStatefulWidget {
     this.clipboard = const ClipboardWrapper(),
   });
 
-  static const String routeName = "/desktopReceive";
-
   final String walletId;
   final String? contractAddress;
   final ClipboardInterface clipboard;
@@ -73,10 +71,6 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
   late final bool supportsSpark;
   late final bool showMultiType;
 
-  String? _sparkAddress;
-  String? _qrcodeContent;
-  bool _showSparkAddress = true;
-
   int _currentIndex = 0;
 
   final List<AddressType> _walletAddressTypes = [];
@@ -108,8 +102,6 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
         ),
       );
 
-      await wallet.generateNewReceivingAddress();
-
       final Address? address;
       if (wallet is Bip39HDWallet && wallet is! BCashInterface) {
         final type = DerivePathType.values.firstWhere(
@@ -129,8 +121,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
       shouldPop = true;
 
       if (mounted) {
-        Navigator.of(context)
-            .popUntil(ModalRoute.withName(DesktopReceive.routeName));
+        Navigator.of(context, rootNavigator: true).pop();
 
         setState(() {
           _addressMap[_walletAddressTypes[_currentIndex]] =
@@ -174,11 +165,9 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
 
       if (mounted) {
         Navigator.of(context, rootNavigator: true).pop();
-        if (_sparkAddress != address.value) {
-          setState(() {
-            _sparkAddress = address.value;
-          });
-        }
+        setState(() {
+          _addressMap[AddressType.spark] = address.value;
+        });
       }
     }
   }
@@ -242,29 +231,6 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
       }
     }
 
-    if (supportsSpark) {
-      _streamSub = ref
-          .read(mainDBProvider)
-          .isar
-          .addresses
-          .where()
-          .walletIdEqualTo(walletId)
-          .filter()
-          .typeEqualTo(AddressType.spark)
-          .sortByDerivationIndexDesc()
-          .findFirst()
-          .asStream()
-          .listen((event) {
-        WidgetsBinding.instance.addPostFrameCallback((_) {
-          if (mounted) {
-            setState(() {
-              _sparkAddress = event?.value;
-            });
-          }
-        });
-      });
-    }
-
     super.initState();
   }
 
@@ -285,16 +251,6 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
       address = ref.watch(pWalletReceivingAddress(walletId));
     }
 
-    if (supportsSpark) {
-      if (_showSparkAddress) {
-        _qrcodeContent = _sparkAddress;
-      } else {
-        _qrcodeContent = ref.watch(pWalletReceivingAddress(walletId));
-      }
-    } else {
-      _qrcodeContent = ref.watch(pWalletReceivingAddress(walletId));
-    }
-
     return Column(
       crossAxisAlignment: CrossAxisAlignment.stretch,
       children: [
@@ -373,95 +329,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
               const SizedBox(
                 height: 12,
               ),
-              if (_showSparkAddress)
-                MouseRegion(
-                  cursor: SystemMouseCursors.click,
-                  child: GestureDetector(
-                    onTap: () {
-                      clipboard.setData(
-                        ClipboardData(text: address),
-                      );
-                      showFloatingFlushBar(
-                        type: FlushBarType.info,
-                        message: "Copied to clipboard",
-                        iconAsset: Assets.svg.copy,
-                        context: context,
-                      );
-                    },
-                    child: Container(
-                      decoration: BoxDecoration(
-                        border: Border.all(
-                          color: Theme.of(context)
-                              .extension<StackColors>()!
-                              .backgroundAppBar,
-                          width: 1,
-                        ),
-                        borderRadius: BorderRadius.circular(
-                          Constants.size.circularBorderRadius,
-                        ),
-                      ),
-                      child: RoundedWhiteContainer(
-                        child: Column(
-                          children: [
-                            Row(
-                              children: [
-                                Text(
-                                  "Your ${widget.contractAddress == null ? coin.ticker : ref.watch(
-                                      pCurrentTokenWallet.select(
-                                        (value) => value!.tokenContract.symbol,
-                                      ),
-                                    )}${supportsSpark ? " SPARK" : ""} address",
-                                  style: STextStyles.itemSubtitle(context),
-                                ),
-                                const Spacer(),
-                                Row(
-                                  children: [
-                                    SvgPicture.asset(
-                                      Assets.svg.copy,
-                                      width: 15,
-                                      height: 15,
-                                      color: Theme.of(context)
-                                          .extension<StackColors>()!
-                                          .infoItemIcons,
-                                    ),
-                                    const SizedBox(
-                                      width: 4,
-                                    ),
-                                    Text(
-                                      "Copy",
-                                      style: STextStyles.link2(context),
-                                    ),
-                                  ],
-                                ),
-                              ],
-                            ),
-                            const SizedBox(
-                              height: 8,
-                            ),
-                            Row(
-                              children: [
-                                Expanded(
-                                  child: Text(
-                                    address,
-                                    style:
-                                        STextStyles.desktopTextExtraExtraSmall(
-                                      context,
-                                    ).copyWith(
-                                      color: Theme.of(context)
-                                          .extension<StackColors>()!
-                                          .textDark,
-                                    ),
-                                  ),
-                                ),
-                              ],
-                            ),
-                          ],
-                        ),
-                      ),
-                    ),
-                  ),
-                ),
-              if (!_showSparkAddress) child,
+              child,
             ],
           ),
           child: MouseRegion(
@@ -534,7 +402,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
                         children: [
                           Expanded(
                             child: Text(
-                              ref.watch(pWalletReceivingAddress(walletId)),
+                              address,
                               style: STextStyles.desktopTextExtraExtraSmall(
                                 context,
                               ).copyWith(
@@ -566,7 +434,8 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
             supportsSpark)
           SecondaryButton(
             buttonHeight: ButtonHeight.l,
-            onPressed: supportsSpark && _showSparkAddress
+            onPressed: supportsSpark &&
+                    _walletAddressTypes[_currentIndex] == AddressType.spark
                 ? generateNewSparkAddress
                 : generateNewAddress,
             label: "Generate new address",
@@ -578,7 +447,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
           child: QR(
             data: AddressUtils.buildUriString(
               coin,
-              _qrcodeContent ?? "",
+              address,
               {},
             ),
             size: 200,
@@ -617,7 +486,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
                             RouteGenerator.generateRoute(
                               RouteSettings(
                                 name: GenerateUriQrCodeView.routeName,
-                                arguments: Tuple2(coin, _qrcodeContent ?? ""),
+                                arguments: Tuple2(coin, address),
                               ),
                             ),
                           ],
@@ -634,7 +503,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
                     shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute,
                     builder: (_) => GenerateUriQrCodeView(
                       coin: coin,
-                      receivingAddress: _qrcodeContent ?? "",
+                      receivingAddress: address,
                     ),
                     settings: const RouteSettings(
                       name: GenerateUriQrCodeView.routeName,

From f22f34904a0444bdacfad5ea2ae5e4d0bd1e13fd Mon Sep 17 00:00:00 2001
From: julian <julian@cypherstack.com>
Date: Mon, 1 Jul 2024 13:02:08 -0600
Subject: [PATCH 03/12] nanswap

---
 .../svg/campfire/exchange_icons/nanswap.svg   |  69 +++
 .../svg/stack_duo/exchange_icons/nanswap.svg  |  69 +++
 .../stack_wallet/exchange_icons/nanswap.svg   |  69 +++
 .../exchange_currency_selection_view.dart     |   3 +
 lib/pages/exchange_view/exchange_form.dart    |   2 +
 .../exchange_step_views/step_2_view.dart      |   4 +-
 .../exchange_step_views/step_3_view.dart      |   4 +-
 .../exchange_provider_options.dart            |  26 +-
 .../exchange_view/trade_details_view.dart     |   5 +
 .../subwidgets/desktop_step_2.dart            | 315 ++++++-----
 .../subwidgets/desktop_step_3.dart            |  35 +-
 lib/services/exchange/exchange.dart           |   7 +
 .../exchange_data_loading_service.dart        |  27 +
 .../majestic_bank/majestic_bank_exchange.dart |   3 +
 .../api_response_models/n_currency.dart       |  43 ++
 .../api_response_models/n_estimate.dart       |  32 ++
 .../nanswap/api_response_models/n_trade.dart  |  81 +++
 .../exchange/nanswap/nanswap_api.dart         | 517 ++++++++++++++++++
 .../exchange/nanswap/nanswap_exchange.dart    | 461 ++++++++++++++++
 lib/utilities/assets.dart                     |   5 +
 20 files changed, 1605 insertions(+), 172 deletions(-)
 create mode 100644 asset_sources/svg/campfire/exchange_icons/nanswap.svg
 create mode 100644 asset_sources/svg/stack_duo/exchange_icons/nanswap.svg
 create mode 100644 asset_sources/svg/stack_wallet/exchange_icons/nanswap.svg
 create mode 100644 lib/services/exchange/nanswap/api_response_models/n_currency.dart
 create mode 100644 lib/services/exchange/nanswap/api_response_models/n_estimate.dart
 create mode 100644 lib/services/exchange/nanswap/api_response_models/n_trade.dart
 create mode 100644 lib/services/exchange/nanswap/nanswap_api.dart
 create mode 100644 lib/services/exchange/nanswap/nanswap_exchange.dart

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 @@
+<?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>
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 @@
+<?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>
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 @@
+<?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>
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<ExchangeForm> {
         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<Step2View> {
 
   @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<Step3View> {
 
   @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<StackColors>()!.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<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
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<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),
+            ),
           ),
-        ),
       ],
     );
   }
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<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",
+                ),
             ],
           ),
         ),
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<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.
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<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;
   //
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<ExchangeResponse<Trade>> 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<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'
+        '}';
+  }
+}
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<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 '
+        '}';
+  }
+}
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<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, '
+        '}';
+  }
+}
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<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,
+        ),
+      );
+    }
+  }
+}
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<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,
+        ),
+      );
+    }
+  }
+}
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()");

From cae27b3835374879284a98b33c4c864cb6954f75 Mon Sep 17 00:00:00 2001
From: julian <julian@cypherstack.com>
Date: Wed, 3 Jul 2024 10:32:20 -0600
Subject: [PATCH 04/12] display xprivs along side mnemonic on backup screen

---
 .../unlock_wallet_keys_desktop.dart           |   2 +
 .../wallet_keys_desktop_popup.dart            | 304 +++++++++++++-----
 lib/route_generator.dart                      |   2 +
 lib/wallets/wallet/impl/bitcoin_wallet.dart   |   2 +
 .../wallet/impl/bitcoincash_wallet.dart       |   2 +
 lib/wallets/wallet/impl/dash_wallet.dart      |   3 +-
 lib/wallets/wallet/impl/dogecoin_wallet.dart  |   4 +-
 lib/wallets/wallet/impl/ecash_wallet.dart     |   2 +
 lib/wallets/wallet/impl/firo_wallet.dart      |   2 +
 lib/wallets/wallet/impl/litecoin_wallet.dart  |   2 +
 lib/wallets/wallet/impl/particl_wallet.dart   |   6 +-
 lib/wallets/wallet/impl/peercoin_wallet.dart  |   7 +-
 .../extended_keys_interface.dart              |  79 +++++
 13 files changed, 326 insertions(+), 91 deletions(-)
 create mode 100644 lib/wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart

diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart
index 24dbe3150..267a4e6b6 100644
--- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart
+++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart
@@ -105,6 +105,7 @@ class _UnlockWalletKeysDesktopState
           WalletKeysDesktopPopup.routeName,
           arguments: (
             mnemonic: words ?? [],
+            walletId: widget.walletId,
             frostData: frostData,
           ),
         );
@@ -344,6 +345,7 @@ class _UnlockWalletKeysDesktopState
                                   WalletKeysDesktopPopup.routeName,
                                   arguments: (
                                     mnemonic: words ?? [],
+                                    walletId: widget.walletId,
                                     frostData: frostData,
                                   ),
                                 );
diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart
index df9252fe5..aabba6c58 100644
--- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart
+++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart
@@ -12,38 +12,46 @@ import 'dart:async';
 
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
 
 import '../../../../notifications/show_flush_bar.dart';
 import '../../../../pages/add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart';
 import '../../../../pages/wallet_view/transaction_views/transaction_details_view.dart';
+import '../../../../providers/providers.dart';
 import '../../../../themes/stack_colors.dart';
 import '../../../../utilities/address_utils.dart';
 import '../../../../utilities/assets.dart';
 import '../../../../utilities/clipboard_interface.dart';
 import '../../../../utilities/text_styles.dart';
+import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
+import '../../../../widgets/custom_tab_view.dart';
 import '../../../../widgets/desktop/desktop_dialog.dart';
 import '../../../../widgets/desktop/desktop_dialog_close_button.dart';
 import '../../../../widgets/desktop/primary_button.dart';
 import '../../../../widgets/desktop/secondary_button.dart';
+import '../../../../widgets/detail_item.dart';
+import '../../../../widgets/loading_indicator.dart';
 import '../../../../widgets/rounded_white_container.dart';
 import 'qr_code_desktop_popup_content.dart';
 
-class WalletKeysDesktopPopup extends StatelessWidget {
+class WalletKeysDesktopPopup extends ConsumerWidget {
   const WalletKeysDesktopPopup({
     super.key,
     required this.words,
+    required this.walletId,
     this.frostData,
     this.clipboardInterface = const ClipboardWrapper(),
   });
 
   final List<String> words;
+  final String walletId;
   final ({String keys, String config})? frostData;
   final ClipboardInterface clipboardInterface;
 
   static const String routeName = "walletKeysDesktopPopup";
 
   @override
-  Widget build(BuildContext context) {
+  Widget build(BuildContext context, WidgetRef ref) {
     return DesktopDialog(
       maxWidth: 614,
       maxHeight: double.infinity,
@@ -168,94 +176,26 @@ class WalletKeysDesktopPopup extends StatelessWidget {
                     ),
                   ],
                 )
-              : Column(
-                  children: [
-                    Text(
-                      "Recovery phrase",
-                      style: STextStyles.desktopTextMedium(context),
-                    ),
-                    const SizedBox(
-                      height: 8,
-                    ),
-                    Center(
-                      child: Padding(
-                        padding: const EdgeInsets.symmetric(
-                          horizontal: 32,
+              : (ref.watch(pWallets).getWallet(walletId)
+                      is ExtendedKeysInterface)
+                  ? CustomTabView(
+                      titles: const ["Mnemonic", "XPriv(s)"],
+                      children: [
+                        Padding(
+                          padding: const EdgeInsets.only(top: 16),
+                          child: _Mnemonic(
+                            words: words,
+                          ),
                         ),
-                        child: Text(
-                          "Please write down your recovery phrase in the correct order and save it to keep your funds secure. You will also be asked to verify the words on the next screen.",
-                          style:
-                              STextStyles.desktopTextExtraExtraSmall(context),
-                          textAlign: TextAlign.center,
+                        _MasterSeedPrivateKey(
+                          words: words,
+                          walletId: walletId,
                         ),
-                      ),
+                      ],
+                    )
+                  : _Mnemonic(
+                      words: words,
                     ),
-                    const SizedBox(
-                      height: 24,
-                    ),
-                    Padding(
-                      padding: const EdgeInsets.symmetric(
-                        horizontal: 32,
-                      ),
-                      child: MnemonicTable(
-                        words: words,
-                        isDesktop: true,
-                        itemBorderColor: Theme.of(context)
-                            .extension<StackColors>()!
-                            .buttonBackSecondary,
-                      ),
-                    ),
-                    const SizedBox(
-                      height: 24,
-                    ),
-                    Padding(
-                      padding: const EdgeInsets.symmetric(
-                        horizontal: 32,
-                      ),
-                      child: Row(
-                        children: [
-                          Expanded(
-                            child: SecondaryButton(
-                              label: "Show QR code",
-                              onPressed: () {
-                                // TODO: address utils
-                                final String value =
-                                    AddressUtils.encodeQRSeedData(words);
-                                Navigator.of(context).pushNamed(
-                                  QRCodeDesktopPopupContent.routeName,
-                                  arguments: value,
-                                );
-                              },
-                            ),
-                          ),
-                          const SizedBox(
-                            width: 16,
-                          ),
-                          Expanded(
-                            child: PrimaryButton(
-                              label: "Copy",
-                              onPressed: () async {
-                                await clipboardInterface.setData(
-                                  ClipboardData(text: words.join(" ")),
-                                );
-                                if (context.mounted) {
-                                  unawaited(
-                                    showFloatingFlushBar(
-                                      type: FlushBarType.info,
-                                      message: "Copied to clipboard",
-                                      iconAsset: Assets.svg.copy,
-                                      context: context,
-                                    ),
-                                  );
-                                }
-                              },
-                            ),
-                          ),
-                        ],
-                      ),
-                    ),
-                  ],
-                ),
           const SizedBox(
             height: 32,
           ),
@@ -264,3 +204,193 @@ class WalletKeysDesktopPopup extends StatelessWidget {
     );
   }
 }
+
+class _Mnemonic extends StatelessWidget {
+  const _Mnemonic({
+    super.key,
+    required this.words,
+    this.clipboardInterface = const ClipboardWrapper(),
+  });
+
+  final List<String> words;
+  final ClipboardInterface clipboardInterface;
+
+  @override
+  Widget build(BuildContext context) {
+    return Column(
+      children: [
+        Text(
+          "Recovery phrase",
+          style: STextStyles.desktopTextMedium(context),
+        ),
+        const SizedBox(
+          height: 8,
+        ),
+        Center(
+          child: Padding(
+            padding: const EdgeInsets.symmetric(
+              horizontal: 32,
+            ),
+            child: Text(
+              "Please write down your recovery phrase in the correct order and "
+              "save it to keep your funds secure. You will also be asked to"
+              " verify the words on the next screen.",
+              style: STextStyles.desktopTextExtraExtraSmall(context),
+              textAlign: TextAlign.center,
+            ),
+          ),
+        ),
+        const SizedBox(
+          height: 24,
+        ),
+        Padding(
+          padding: const EdgeInsets.symmetric(
+            horizontal: 32,
+          ),
+          child: MnemonicTable(
+            words: words,
+            isDesktop: true,
+            itemBorderColor:
+                Theme.of(context).extension<StackColors>()!.buttonBackSecondary,
+          ),
+        ),
+        const SizedBox(
+          height: 24,
+        ),
+        Padding(
+          padding: const EdgeInsets.symmetric(
+            horizontal: 32,
+          ),
+          child: Row(
+            children: [
+              Expanded(
+                child: SecondaryButton(
+                  label: "Show QR code",
+                  onPressed: () {
+                    // TODO: address utils
+                    final String value = AddressUtils.encodeQRSeedData(words);
+                    Navigator.of(context).pushNamed(
+                      QRCodeDesktopPopupContent.routeName,
+                      arguments: value,
+                    );
+                  },
+                ),
+              ),
+              const SizedBox(
+                width: 16,
+              ),
+              Expanded(
+                child: PrimaryButton(
+                  label: "Copy",
+                  onPressed: () async {
+                    await clipboardInterface.setData(
+                      ClipboardData(text: words.join(" ")),
+                    );
+                    if (context.mounted) {
+                      unawaited(
+                        showFloatingFlushBar(
+                          type: FlushBarType.info,
+                          message: "Copied to clipboard",
+                          iconAsset: Assets.svg.copy,
+                          context: context,
+                        ),
+                      );
+                    }
+                  },
+                ),
+              ),
+            ],
+          ),
+        ),
+      ],
+    );
+  }
+}
+
+class _MasterSeedPrivateKey extends ConsumerStatefulWidget {
+  const _MasterSeedPrivateKey({
+    super.key,
+    required this.words,
+    required this.walletId,
+    this.clipboardInterface = const ClipboardWrapper(),
+  });
+
+  final List<String> words;
+  final String walletId;
+  final ClipboardInterface clipboardInterface;
+
+  @override
+  ConsumerState<_MasterSeedPrivateKey> createState() =>
+      _MasterSeedPrivateKeyState();
+}
+
+class _MasterSeedPrivateKeyState extends ConsumerState<_MasterSeedPrivateKey> {
+  final controller = TextEditingController();
+
+  @override
+  void initState() {
+    super.initState();
+  }
+
+  @override
+  void dispose() {
+    controller.dispose();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Padding(
+      padding: const EdgeInsets.symmetric(horizontal: 20),
+      child: Column(
+        children: [
+          const SizedBox(
+            height: 12,
+          ),
+          FutureBuilder(
+            future: (ref.read(pWallets).getWallet(widget.walletId)
+                    as ExtendedKeysInterface)
+                .getXPrivs(),
+            builder: (context, snapshot) {
+              if (snapshot.connectionState == ConnectionState.done &&
+                  snapshot.hasData) {
+                return Column(
+                  mainAxisSize: MainAxisSize.min,
+                  children: [
+                    DetailItem(
+                      title: "Master fingerprint",
+                      detail: snapshot.data!.fingerprint,
+                    ),
+                    ...snapshot.data!.xprivs.map(
+                      (e) => Padding(
+                        padding: const EdgeInsets.symmetric(vertical: 8),
+                        child: Column(
+                          mainAxisSize: MainAxisSize.min,
+                          children: [
+                            DetailItem(
+                              title: "Derivation",
+                              detail: e.path,
+                            ),
+                            DetailItem(
+                              title: "xpriv",
+                              detail: e.xpriv,
+                            ),
+                          ],
+                        ),
+                      ),
+                    ),
+                  ],
+                );
+              } else {
+                return const LoadingIndicator(
+                  width: 100,
+                  height: 100,
+                );
+              }
+            },
+          ),
+        ],
+      ),
+    );
+  }
+}
diff --git a/lib/route_generator.dart b/lib/route_generator.dart
index be011c90f..9e70573bb 100644
--- a/lib/route_generator.dart
+++ b/lib/route_generator.dart
@@ -2293,11 +2293,13 @@ class RouteGenerator {
       case WalletKeysDesktopPopup.routeName:
         if (args is ({
           List<String> mnemonic,
+          String walletId,
           ({String keys, String config})? frostData
         })) {
           return FadePageRoute(
             WalletKeysDesktopPopup(
               words: args.mnemonic,
+              walletId: args.walletId,
               frostData: args.frostData,
             ),
             RouteSettings(
diff --git a/lib/wallets/wallet/impl/bitcoin_wallet.dart b/lib/wallets/wallet/impl/bitcoin_wallet.dart
index cbe49e180..5c7f3ed75 100644
--- a/lib/wallets/wallet/impl/bitcoin_wallet.dart
+++ b/lib/wallets/wallet/impl/bitcoin_wallet.dart
@@ -8,12 +8,14 @@ import '../intermediate/bip39_hd_wallet.dart';
 import '../wallet_mixin_interfaces/coin_control_interface.dart';
 import '../wallet_mixin_interfaces/cpfp_interface.dart';
 import '../wallet_mixin_interfaces/electrumx_interface.dart';
+import '../wallet_mixin_interfaces/extended_keys_interface.dart';
 import '../wallet_mixin_interfaces/paynym_interface.dart';
 import '../wallet_mixin_interfaces/rbf_interface.dart';
 
 class BitcoinWallet<T extends PaynymCurrencyInterface> extends Bip39HDWallet<T>
     with
         ElectrumXInterface<T>,
+        ExtendedKeysInterface<T>,
         CoinControlInterface,
         PaynymInterface<T>,
         RbfInterface<T>,
diff --git a/lib/wallets/wallet/impl/bitcoincash_wallet.dart b/lib/wallets/wallet/impl/bitcoincash_wallet.dart
index b00f650f2..3c8fb58bc 100644
--- a/lib/wallets/wallet/impl/bitcoincash_wallet.dart
+++ b/lib/wallets/wallet/impl/bitcoincash_wallet.dart
@@ -19,11 +19,13 @@ import '../wallet_mixin_interfaces/bcash_interface.dart';
 import '../wallet_mixin_interfaces/cash_fusion_interface.dart';
 import '../wallet_mixin_interfaces/coin_control_interface.dart';
 import '../wallet_mixin_interfaces/electrumx_interface.dart';
+import '../wallet_mixin_interfaces/extended_keys_interface.dart';
 
 class BitcoincashWallet<T extends ElectrumXCurrencyInterface>
     extends Bip39HDWallet<T>
     with
         ElectrumXInterface<T>,
+        ExtendedKeysInterface<T>,
         BCashInterface<T>,
         CoinControlInterface<T>,
         CashFusionInterface<T> {
diff --git a/lib/wallets/wallet/impl/dash_wallet.dart b/lib/wallets/wallet/impl/dash_wallet.dart
index 8138b684b..bf1e91b9a 100644
--- a/lib/wallets/wallet/impl/dash_wallet.dart
+++ b/lib/wallets/wallet/impl/dash_wallet.dart
@@ -12,9 +12,10 @@ import '../../crypto_currency/interfaces/electrumx_currency_interface.dart';
 import '../intermediate/bip39_hd_wallet.dart';
 import '../wallet_mixin_interfaces/coin_control_interface.dart';
 import '../wallet_mixin_interfaces/electrumx_interface.dart';
+import '../wallet_mixin_interfaces/extended_keys_interface.dart';
 
 class DashWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
-    with ElectrumXInterface<T>, CoinControlInterface {
+    with ElectrumXInterface<T>, ExtendedKeysInterface<T>, CoinControlInterface {
   DashWallet(CryptoCurrencyNetwork network) : super(Dash(network) as T);
 
   @override
diff --git a/lib/wallets/wallet/impl/dogecoin_wallet.dart b/lib/wallets/wallet/impl/dogecoin_wallet.dart
index 2c2dcd309..8d2d2f029 100644
--- a/lib/wallets/wallet/impl/dogecoin_wallet.dart
+++ b/lib/wallets/wallet/impl/dogecoin_wallet.dart
@@ -13,9 +13,11 @@ import '../../crypto_currency/interfaces/electrumx_currency_interface.dart';
 import '../intermediate/bip39_hd_wallet.dart';
 import '../wallet_mixin_interfaces/coin_control_interface.dart';
 import '../wallet_mixin_interfaces/electrumx_interface.dart';
+import '../wallet_mixin_interfaces/extended_keys_interface.dart';
 
 class DogecoinWallet<T extends ElectrumXCurrencyInterface>
-    extends Bip39HDWallet<T> with ElectrumXInterface<T>, CoinControlInterface {
+    extends Bip39HDWallet<T>
+    with ElectrumXInterface<T>, ExtendedKeysInterface<T>, CoinControlInterface {
   DogecoinWallet(CryptoCurrencyNetwork network) : super(Dogecoin(network) as T);
 
   @override
diff --git a/lib/wallets/wallet/impl/ecash_wallet.dart b/lib/wallets/wallet/impl/ecash_wallet.dart
index 53c0f895b..968ea72cc 100644
--- a/lib/wallets/wallet/impl/ecash_wallet.dart
+++ b/lib/wallets/wallet/impl/ecash_wallet.dart
@@ -19,10 +19,12 @@ import '../wallet_mixin_interfaces/bcash_interface.dart';
 import '../wallet_mixin_interfaces/cash_fusion_interface.dart';
 import '../wallet_mixin_interfaces/coin_control_interface.dart';
 import '../wallet_mixin_interfaces/electrumx_interface.dart';
+import '../wallet_mixin_interfaces/extended_keys_interface.dart';
 
 class EcashWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
     with
         ElectrumXInterface<T>,
+        ExtendedKeysInterface<T>,
         BCashInterface<T>,
         CoinControlInterface<T>,
         CashFusionInterface<T> {
diff --git a/lib/wallets/wallet/impl/firo_wallet.dart b/lib/wallets/wallet/impl/firo_wallet.dart
index 61c9346f7..33ae3fb24 100644
--- a/lib/wallets/wallet/impl/firo_wallet.dart
+++ b/lib/wallets/wallet/impl/firo_wallet.dart
@@ -22,6 +22,7 @@ import '../../models/tx_data.dart';
 import '../intermediate/bip39_hd_wallet.dart';
 import '../wallet_mixin_interfaces/coin_control_interface.dart';
 import '../wallet_mixin_interfaces/electrumx_interface.dart';
+import '../wallet_mixin_interfaces/extended_keys_interface.dart';
 import '../wallet_mixin_interfaces/lelantus_interface.dart';
 import '../wallet_mixin_interfaces/spark_interface.dart';
 
@@ -30,6 +31,7 @@ const sparkStartBlock = 819300; // (approx 18 Jan 2024)
 class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
     with
         ElectrumXInterface<T>,
+        ExtendedKeysInterface<T>,
         LelantusInterface<T>,
         SparkInterface<T>,
         CoinControlInterface<T> {
diff --git a/lib/wallets/wallet/impl/litecoin_wallet.dart b/lib/wallets/wallet/impl/litecoin_wallet.dart
index 63c9d7420..d301d0608 100644
--- a/lib/wallets/wallet/impl/litecoin_wallet.dart
+++ b/lib/wallets/wallet/impl/litecoin_wallet.dart
@@ -15,6 +15,7 @@ import '../../crypto_currency/interfaces/electrumx_currency_interface.dart';
 import '../intermediate/bip39_hd_wallet.dart';
 import '../wallet_mixin_interfaces/coin_control_interface.dart';
 import '../wallet_mixin_interfaces/electrumx_interface.dart';
+import '../wallet_mixin_interfaces/extended_keys_interface.dart';
 import '../wallet_mixin_interfaces/ordinals_interface.dart';
 import '../wallet_mixin_interfaces/rbf_interface.dart';
 
@@ -22,6 +23,7 @@ class LitecoinWallet<T extends ElectrumXCurrencyInterface>
     extends Bip39HDWallet<T>
     with
         ElectrumXInterface<T>,
+        ExtendedKeysInterface<T>,
         CoinControlInterface<T>,
         RbfInterface<T>,
         OrdinalsInterface<T> {
diff --git a/lib/wallets/wallet/impl/particl_wallet.dart b/lib/wallets/wallet/impl/particl_wallet.dart
index 0ce72b393..567700e6f 100644
--- a/lib/wallets/wallet/impl/particl_wallet.dart
+++ b/lib/wallets/wallet/impl/particl_wallet.dart
@@ -19,10 +19,14 @@ import '../../models/tx_data.dart';
 import '../intermediate/bip39_hd_wallet.dart';
 import '../wallet_mixin_interfaces/coin_control_interface.dart';
 import '../wallet_mixin_interfaces/electrumx_interface.dart';
+import '../wallet_mixin_interfaces/extended_keys_interface.dart';
 
 class ParticlWallet<T extends ElectrumXCurrencyInterface>
     extends Bip39HDWallet<T>
-    with ElectrumXInterface<T>, CoinControlInterface<T> {
+    with
+        ElectrumXInterface<T>,
+        ExtendedKeysInterface<T>,
+        CoinControlInterface<T> {
   @override
   int get isarTransactionVersion => 2;
 
diff --git a/lib/wallets/wallet/impl/peercoin_wallet.dart b/lib/wallets/wallet/impl/peercoin_wallet.dart
index 2567e841d..e1d993584 100644
--- a/lib/wallets/wallet/impl/peercoin_wallet.dart
+++ b/lib/wallets/wallet/impl/peercoin_wallet.dart
@@ -1,4 +1,5 @@
 import 'package:isar/isar.dart';
+
 import '../../../models/isar/models/blockchain_data/address.dart';
 import '../../../models/isar/models/blockchain_data/transaction.dart';
 import '../../../models/isar/models/blockchain_data/v2/input_v2.dart';
@@ -11,10 +12,14 @@ import '../../crypto_currency/interfaces/electrumx_currency_interface.dart';
 import '../intermediate/bip39_hd_wallet.dart';
 import '../wallet_mixin_interfaces/coin_control_interface.dart';
 import '../wallet_mixin_interfaces/electrumx_interface.dart';
+import '../wallet_mixin_interfaces/extended_keys_interface.dart';
 
 class PeercoinWallet<T extends ElectrumXCurrencyInterface>
     extends Bip39HDWallet<T>
-    with ElectrumXInterface<T>, CoinControlInterface<T> {
+    with
+        ElectrumXInterface<T>,
+        ExtendedKeysInterface<T>,
+        CoinControlInterface<T> {
   @override
   int get isarTransactionVersion => 2;
 
diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart
new file mode 100644
index 000000000..5101b6a45
--- /dev/null
+++ b/lib/wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart
@@ -0,0 +1,79 @@
+import '../../crypto_currency/interfaces/electrumx_currency_interface.dart';
+import 'electrumx_interface.dart';
+
+typedef XPub = ({String path, String xpub});
+typedef XPriv = ({String path, String xpriv});
+
+mixin ExtendedKeysInterface<T extends ElectrumXCurrencyInterface>
+    on ElectrumXInterface<T> {
+  Future<({List<XPub> xpubs, String fingerprint})> getXPubs() async {
+    final paths = cryptoCurrency.supportedDerivationPathTypes.map(
+      (e) => (
+        path: e,
+        addressType: e.getAddressType(),
+      ),
+    );
+
+    final master = await getRootHDNode();
+    final fingerprint = master.fingerprint.toRadixString(16);
+
+    final futures = paths.map((e) async {
+      String path = cryptoCurrency.constructDerivePath(
+        derivePathType: e.path,
+        chain: 0,
+        index: 0,
+      );
+      // trim chain and address index
+      path = path.substring(0, path.lastIndexOf("'") + 1);
+      final node = master.derivePath(path);
+
+      return (
+        path: path,
+        xpub: node.hdPublicKey.encode(
+          cryptoCurrency.networkParams.pubHDPrefix,
+          // 0x04b24746,
+        ),
+      );
+    });
+
+    return (
+      fingerprint: fingerprint,
+      xpubs: await Future.wait(futures),
+    );
+  }
+
+  Future<({List<XPriv> xprivs, String fingerprint})> getXPrivs() async {
+    final paths = cryptoCurrency.supportedDerivationPathTypes.map(
+      (e) => (
+        path: e,
+        addressType: e.getAddressType(),
+      ),
+    );
+
+    final master = await getRootHDNode();
+    final fingerprint = master.fingerprint.toRadixString(16);
+
+    final futures = paths.map((e) async {
+      String path = cryptoCurrency.constructDerivePath(
+        derivePathType: e.path,
+        chain: 0,
+        index: 0,
+      );
+      // trim chain and address index
+      path = path.substring(0, path.lastIndexOf("'") + 1);
+      final node = master.derivePath(path);
+
+      return (
+        path: path,
+        xpriv: node.encode(
+          cryptoCurrency.networkParams.privHDPrefix,
+        ),
+      );
+    });
+
+    return (
+      fingerprint: fingerprint,
+      xprivs: await Future.wait(futures),
+    );
+  }
+}

From d66e0580ec958d991112f07273d3fdb4e5643b56 Mon Sep 17 00:00:00 2001
From: julian <julian@cypherstack.com>
Date: Wed, 3 Jul 2024 12:29:47 -0600
Subject: [PATCH 05/12] add minimum wait time option

---
 lib/utilities/show_loading.dart | 24 ++++++++++++++++++++++--
 1 file changed, 22 insertions(+), 2 deletions(-)

diff --git a/lib/utilities/show_loading.dart b/lib/utilities/show_loading.dart
index 1730df83d..371d67d1d 100644
--- a/lib/utilities/show_loading.dart
+++ b/lib/utilities/show_loading.dart
@@ -11,9 +11,24 @@
 import 'dart:async';
 
 import 'package:flutter/material.dart';
+
 import '../themes/stack_colors.dart';
-import 'logger.dart';
 import '../widgets/custom_loading_overlay.dart';
+import 'logger.dart';
+
+Future<T> minWaitFuture<T>(
+  Future<T> future, {
+  required Duration delay,
+}) async {
+  final results = await Future.wait(
+    [
+      future,
+      Future<dynamic>.delayed(delay),
+    ],
+  );
+
+  return results.first as T;
+}
 
 Future<T?> showLoading<T>({
   required Future<T> whileFuture,
@@ -23,6 +38,7 @@ Future<T?> showLoading<T>({
   bool rootNavigator = false,
   bool opaqueBG = false,
   void Function(Exception)? onException,
+  Duration? delay,
 }) async {
   unawaited(
     showDialog<void>(
@@ -49,7 +65,11 @@ Future<T?> showLoading<T>({
   T? result;
 
   try {
-    result = await whileFuture;
+    if (delay != null) {
+      result = await minWaitFuture(whileFuture, delay: delay);
+    } else {
+      result = await whileFuture;
+    }
   } catch (e, s) {
     Logging.instance.log(
       "showLoading caught: $e\n$s",

From 64b61779cdd249cfa6446358fef7559c75019804 Mon Sep 17 00:00:00 2001
From: julian <julian@cypherstack.com>
Date: Wed, 3 Jul 2024 12:30:30 -0600
Subject: [PATCH 06/12] show xpubs

---
 .../wallet_settings_view.dart                 |  34 ++-
 .../xpub_view.dart                            | 281 +++++++-----------
 .../sub_widgets/wallet_options_button.dart    |  63 ++--
 lib/route_generator.dart                      |   6 +-
 4 files changed, 170 insertions(+), 214 deletions(-)

diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart
index 098c1c285..aca76d7f8 100644
--- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart
+++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart
@@ -35,6 +35,7 @@ import '../../../wallets/crypto_currency/intermediate/frost_currency.dart';
 import '../../../wallets/crypto_currency/intermediate/nano_currency.dart';
 import '../../../wallets/wallet/impl/bitcoin_frost_wallet.dart';
 import '../../../wallets/wallet/impl/epiccash_wallet.dart';
+import '../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
 import '../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart';
 import '../../../widgets/background.dart';
 import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
@@ -95,9 +96,9 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
   void initState() {
     walletId = widget.walletId;
     coin = widget.coin;
-    // TODO: [prio=low] xpubs
-    // xPubEnabled = ref.read(pWallets).getWallet(walletId).hasXPub;
-    xPubEnabled = false;
+    xPubEnabled =
+        ref.read(pWallets).getWallet(walletId) is ExtendedKeysInterface;
+
     xpub = "";
 
     _currentSyncStatus = widget.initialSyncStatus;
@@ -373,11 +374,30 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
                                       return SettingsListButton(
                                         iconAssetName: Assets.svg.eye,
                                         title: "Wallet xPub",
-                                        onPressed: () {
-                                          Navigator.of(context).pushNamed(
-                                            XPubView.routeName,
-                                            arguments: widget.walletId,
+                                        onPressed: () async {
+                                          final xpubData = await showLoading(
+                                            delay: const Duration(
+                                              milliseconds: 800,
+                                            ),
+                                            whileFuture: (ref
+                                                        .read(pWallets)
+                                                        .getWallet(walletId)
+                                                    as ExtendedKeysInterface)
+                                                .getXPubs(),
+                                            context: context,
+                                            message: "Loading xpubs",
+                                            rootNavigator: Util.isDesktop,
                                           );
+                                          if (context.mounted) {
+                                            await Navigator.of(context)
+                                                .pushNamed(
+                                              XPubView.routeName,
+                                              arguments: (
+                                                widget.walletId,
+                                                xpubData
+                                              ),
+                                            );
+                                          }
                                         },
                                       );
                                     },
diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/xpub_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/xpub_view.dart
index c8fa245f0..a256b5aea 100644
--- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/xpub_view.dart
+++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/xpub_view.dart
@@ -13,80 +13,45 @@ import 'dart:async';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:flutter_svg/svg.dart';
 
 import '../../../../notifications/show_flush_bar.dart';
-import '../../../../providers/global/wallets_provider.dart';
 import '../../../../themes/stack_colors.dart';
 import '../../../../utilities/assets.dart';
 import '../../../../utilities/clipboard_interface.dart';
 import '../../../../utilities/text_styles.dart';
 import '../../../../utilities/util.dart';
-import '../../../../wallets/wallet/wallet.dart';
+import '../../../../wallets/isar/providers/wallet_info_provider.dart';
+import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
 import '../../../../widgets/background.dart';
 import '../../../../widgets/conditional_parent.dart';
 import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
+import '../../../../widgets/custom_tab_view.dart';
 import '../../../../widgets/desktop/desktop_dialog.dart';
 import '../../../../widgets/desktop/desktop_dialog_close_button.dart';
 import '../../../../widgets/desktop/primary_button.dart';
 import '../../../../widgets/desktop/secondary_button.dart';
-import '../../../../widgets/loading_indicator.dart';
+import '../../../../widgets/detail_item.dart';
 import '../../../../widgets/qr.dart';
 import '../../../../widgets/rounded_white_container.dart';
 
-class XPubView extends ConsumerStatefulWidget {
+class XPubView extends ConsumerWidget {
   const XPubView({
     super.key,
     required this.walletId,
+    required this.xpubData,
     this.clipboardInterface = const ClipboardWrapper(),
   });
 
   final String walletId;
   final ClipboardInterface clipboardInterface;
+  final ({List<XPub> xpubs, String fingerprint}) xpubData;
 
   static const String routeName = "/xpub";
 
   @override
-  ConsumerState<XPubView> createState() => _XPubViewState();
-}
+  Widget build(BuildContext context, WidgetRef ref) {
+    final bool isDesktop = Util.isDesktop;
 
-class _XPubViewState extends ConsumerState<XPubView> {
-  final bool isDesktop = Util.isDesktop;
-
-  late ClipboardInterface _clipboardInterface;
-  late final Wallet wallet;
-
-  String? xpub;
-
-  @override
-  void initState() {
-    _clipboardInterface = widget.clipboardInterface;
-    wallet = ref.read(pWallets).getWallet(widget.walletId);
-
-    super.initState();
-  }
-
-  @override
-  void dispose() {
-    super.dispose();
-  }
-
-  Future<void> _copy() async {
-    await _clipboardInterface.setData(ClipboardData(text: xpub!));
-    if (mounted) {
-      unawaited(
-        showFloatingFlushBar(
-          type: FlushBarType.info,
-          message: "Copied to clipboard",
-          iconAsset: Assets.svg.copy,
-          context: context,
-        ),
-      );
-    }
-  }
-
-  @override
-  Widget build(BuildContext context) {
     return ConditionalParent(
       condition: !isDesktop,
       builder: (child) => Background(
@@ -100,35 +65,9 @@ class _XPubViewState extends ConsumerState<XPubView> {
               },
             ),
             title: Text(
-              "Wallet xPub",
+              "Wallet xpub(s)",
               style: STextStyles.navBarTitle(context),
             ),
-            actions: [
-              Padding(
-                padding: const EdgeInsets.all(10),
-                child: AspectRatio(
-                  aspectRatio: 1,
-                  child: AppBarIconButton(
-                    color:
-                        Theme.of(context).extension<StackColors>()!.background,
-                    shadows: const [],
-                    icon: SvgPicture.asset(
-                      Assets.svg.copy,
-                      width: 24,
-                      height: 24,
-                      color: Theme.of(context)
-                          .extension<StackColors>()!
-                          .topNavIconPrimary,
-                    ),
-                    onPressed: () {
-                      if (xpub != null) {
-                        _copy();
-                      }
-                    },
-                  ),
-                ),
-              ),
-            ],
           ),
           body: Padding(
             padding: const EdgeInsets.only(
@@ -136,7 +75,7 @@ class _XPubViewState extends ConsumerState<XPubView> {
               left: 16,
               right: 16,
             ),
-            child: child,
+            child: SingleChildScrollView(child: child),
           ),
         ),
       ),
@@ -146,6 +85,7 @@ class _XPubViewState extends ConsumerState<XPubView> {
           maxWidth: 600,
           maxHeight: double.infinity,
           child: Column(
+            mainAxisSize: MainAxisSize.min,
             children: [
               Row(
                 mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -155,7 +95,7 @@ class _XPubViewState extends ConsumerState<XPubView> {
                       left: 32,
                     ),
                     child: Text(
-                      "${wallet.info.name} xPub",
+                      "${ref.watch(pWalletName(walletId))} xpub(s)",
                       style: STextStyles.desktopH2(context),
                     ),
                   ),
@@ -167,62 +107,40 @@ class _XPubViewState extends ConsumerState<XPubView> {
                   ),
                 ],
               ),
-              AnimatedSize(
-                duration: const Duration(
-                  milliseconds: 150,
-                ),
+              Flexible(
                 child: Padding(
                   padding: const EdgeInsets.fromLTRB(32, 0, 32, 32),
-                  child: child,
+                  child: SingleChildScrollView(
+                    child: child,
+                  ),
                 ),
               ),
             ],
           ),
         ),
         child: Column(
+          mainAxisSize: MainAxisSize.min,
           children: [
-            if (isDesktop) const SizedBox(height: 44),
-            ConditionalParent(
-              condition: !isDesktop,
-              builder: (child) => Expanded(
-                child: child,
-              ),
-              child: FutureBuilder(
-                future: Future(() => "fixme"),
-                // future: wallet.xpub,
-                builder: (context, AsyncSnapshot<String> snapshot) {
-                  if (snapshot.connectionState == ConnectionState.done &&
-                      snapshot.hasData) {
-                    xpub = snapshot.data!;
-                  }
-
-                  const height = 600.0;
-                  Widget child;
-                  if (xpub == null) {
-                    child = const SizedBox(
-                      key: Key("loadingXPUB"),
-                      height: height,
-                      child: Center(
-                        child: LoadingIndicator(
-                          width: 100,
-                        ),
+            if (isDesktop) const SizedBox(height: 16),
+            DetailItem(
+              title: "Master fingerprint",
+              detail: xpubData.fingerprint,
+              horizontal: true,
+            ),
+            if (isDesktop) const SizedBox(height: 16),
+            CustomTabView(
+              titles: xpubData.xpubs.map((e) => e.path).toList(),
+              children: xpubData.xpubs
+                  .map(
+                    (e) => Padding(
+                      padding: const EdgeInsets.only(top: 16),
+                      child: _XPub(
+                        xpub: e.xpub,
+                        derivation: e.path,
                       ),
-                    );
-                  } else {
-                    child = _XPub(
-                      xpub: xpub!,
-                      height: height,
-                    );
-                  }
-
-                  return AnimatedSwitcher(
-                    duration: const Duration(
-                      milliseconds: 200,
                     ),
-                    child: child,
-                  );
-                },
-              ),
+                  )
+                  .toList(),
             ),
           ],
         ),
@@ -235,85 +153,86 @@ class _XPub extends StatelessWidget {
   const _XPub({
     super.key,
     required this.xpub,
-    required this.height,
+    required this.derivation,
     this.clipboardInterface = const ClipboardWrapper(),
   });
 
   final String xpub;
-  final double height;
+  final String derivation;
+
   final ClipboardInterface clipboardInterface;
 
   @override
   Widget build(BuildContext context) {
     final bool isDesktop = Util.isDesktop;
 
-    return SizedBox(
-      height: isDesktop ? height : double.infinity,
-      child: Column(
-        children: [
-          ConditionalParent(
-            condition: !isDesktop,
-            builder: (child) => RoundedWhiteContainer(
-              child: child,
-            ),
-            child: QR(
-              data: xpub,
-              size: isDesktop ? 280 : MediaQuery.of(context).size.width / 1.5,
-            ),
+    return Column(
+      mainAxisSize: MainAxisSize.min,
+      children: [
+        const SizedBox(
+          height: 25,
+        ),
+        ConditionalParent(
+          condition: !isDesktop,
+          builder: (child) => RoundedWhiteContainer(
+            child: child,
           ),
-          const SizedBox(height: 25),
-          RoundedWhiteContainer(
-            padding: const EdgeInsets.all(16),
-            borderColor:
-                Theme.of(context).extension<StackColors>()!.backgroundAppBar,
-            child: SelectableText(
-              xpub,
-              style: STextStyles.largeMedium14(context),
-            ),
+          child: QR(
+            data: xpub,
+            size: isDesktop ? 280 : MediaQuery.of(context).size.width / 1.5,
           ),
-          const SizedBox(height: 32),
-          Row(
-            children: [
-              if (isDesktop)
-                Expanded(
-                  child: SecondaryButton(
-                    buttonHeight: ButtonHeight.xl,
-                    label: "Cancel",
-                    onPressed: Navigator.of(
-                      context,
-                      rootNavigator: true,
-                    ).pop,
-                  ),
-                ),
-              if (isDesktop) const SizedBox(width: 16),
+        ),
+        const SizedBox(height: 25),
+        RoundedWhiteContainer(
+          padding: const EdgeInsets.all(16),
+          borderColor:
+              Theme.of(context).extension<StackColors>()!.backgroundAppBar,
+          child: SelectableText(
+            xpub,
+            style: STextStyles.largeMedium14(context),
+          ),
+        ),
+        const SizedBox(height: 32),
+        Row(
+          children: [
+            if (isDesktop)
               Expanded(
-                child: PrimaryButton(
+                child: SecondaryButton(
                   buttonHeight: ButtonHeight.xl,
-                  label: "Copy",
-                  onPressed: () async {
-                    await clipboardInterface.setData(
-                      ClipboardData(
-                        text: xpub,
-                      ),
-                    );
-                    if (context.mounted) {
-                      unawaited(
-                        showFloatingFlushBar(
-                          type: FlushBarType.info,
-                          message: "Copied to clipboard",
-                          iconAsset: Assets.svg.copy,
-                          context: context,
-                        ),
-                      );
-                    }
-                  },
+                  label: "Cancel",
+                  onPressed: Navigator.of(
+                    context,
+                    rootNavigator: true,
+                  ).pop,
                 ),
               ),
-            ],
-          ),
-          if (!isDesktop) const Spacer(),
-        ],
-      ),
+            if (isDesktop) const SizedBox(width: 16),
+            Expanded(
+              child: PrimaryButton(
+                buttonHeight: ButtonHeight.xl,
+                label: "Copy",
+                onPressed: () async {
+                  await clipboardInterface.setData(
+                    ClipboardData(
+                      text: xpub,
+                    ),
+                  );
+                  if (context.mounted) {
+                    unawaited(
+                      showFloatingFlushBar(
+                        type: FlushBarType.info,
+                        message: "Copied to clipboard",
+                        iconAsset: Assets.svg.copy,
+                        context: context,
+                      ),
+                    );
+                  }
+                },
+              ),
+            ),
+          ],
+        ),
+      ],
     );
   }
 }
diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart
index 93a084c0e..4f955c00f 100644
--- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart
+++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart
@@ -18,15 +18,19 @@ import 'package:flutter_svg/svg.dart';
 import '../../../../pages/settings_views/wallet_settings_view/frost_ms/frost_ms_options_view.dart';
 import '../../../../pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/change_representative_view.dart';
 import '../../../../pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/xpub_view.dart';
+import '../../../../providers/global/wallets_provider.dart';
 import '../../../../route_generator.dart';
 import '../../../../themes/stack_colors.dart';
 import '../../../../utilities/assets.dart';
 import '../../../../utilities/constants.dart';
+import '../../../../utilities/show_loading.dart';
 import '../../../../utilities/text_styles.dart';
+import '../../../../utilities/util.dart';
 import '../../../../wallets/crypto_currency/coins/firo.dart';
 import '../../../../wallets/crypto_currency/intermediate/frost_currency.dart';
 import '../../../../wallets/crypto_currency/intermediate/nano_currency.dart';
 import '../../../../wallets/isar/providers/wallet_info_provider.dart';
+import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
 import '../../../addresses/desktop_wallet_addresses_view.dart';
 import '../../../lelantus_coins/lelantus_coins_view.dart';
 import '../../../spark_coins/spark_coins_view.dart';
@@ -61,7 +65,7 @@ enum _WalletOptions {
   }
 }
 
-class WalletOptionsButton extends StatelessWidget {
+class WalletOptionsButton extends ConsumerWidget {
   const WalletOptionsButton({
     super.key,
     required this.walletId,
@@ -70,7 +74,7 @@ class WalletOptionsButton extends StatelessWidget {
   final String walletId;
 
   @override
-  Widget build(BuildContext context) {
+  Widget build(BuildContext context, WidgetRef ref) {
     return RawMaterialButton(
       constraints: const BoxConstraints(
         minHeight: 32,
@@ -148,28 +152,40 @@ class WalletOptionsButton extends StatelessWidget {
               }
               break;
             case _WalletOptions.showXpub:
-              final result = await showDialog<bool?>(
+              final xpubData = await showLoading(
+                delay: const Duration(milliseconds: 800),
+                whileFuture: (ref.read(pWallets).getWallet(walletId)
+                        as ExtendedKeysInterface)
+                    .getXPubs(),
                 context: context,
-                barrierDismissible: false,
-                builder: (context) => Navigator(
-                  initialRoute: XPubView.routeName,
-                  onGenerateRoute: RouteGenerator.generateRoute,
-                  onGenerateInitialRoutes: (_, __) {
-                    return [
-                      RouteGenerator.generateRoute(
-                        RouteSettings(
-                          name: XPubView.routeName,
-                          arguments: walletId,
-                        ),
-                      ),
-                    ];
-                  },
-                ),
+                message: "Loading xpubs",
+                rootNavigator: Util.isDesktop,
               );
 
-              if (result == true) {
-                if (context.mounted) {
-                  Navigator.of(context).pop();
+              if (context.mounted) {
+                final result = await showDialog<bool?>(
+                  context: context,
+                  barrierDismissible: false,
+                  builder: (context) => Navigator(
+                    initialRoute: XPubView.routeName,
+                    onGenerateRoute: RouteGenerator.generateRoute,
+                    onGenerateInitialRoutes: (_, __) {
+                      return [
+                        RouteGenerator.generateRoute(
+                          RouteSettings(
+                            name: XPubView.routeName,
+                            arguments: (walletId, xpubData),
+                          ),
+                        ),
+                      ];
+                    },
+                  ),
+                );
+
+                if (result == true) {
+                  if (context.mounted) {
+                    Navigator.of(context).pop();
+                  }
                 }
               }
               break;
@@ -279,9 +295,8 @@ class WalletOptionsPopupMenu extends ConsumerWidget {
 
     final firoDebug = kDebugMode && (coin is Firo);
 
-    // TODO: [prio=low]
-    // final bool xpubEnabled = manager.hasXPub;
-    final bool xpubEnabled = false;
+    final bool xpubEnabled =
+        ref.watch(pWallets).getWallet(walletId) is ExtendedKeysInterface;
 
     final bool canChangeRep = coin is NanoCurrency;
 
diff --git a/lib/route_generator.dart b/lib/route_generator.dart
index 9e70573bb..d7b8a1563 100644
--- a/lib/route_generator.dart
+++ b/lib/route_generator.dart
@@ -198,6 +198,7 @@ import 'wallets/crypto_currency/crypto_currency.dart';
 import 'wallets/crypto_currency/intermediate/frost_currency.dart';
 import 'wallets/models/tx_data.dart';
 import 'wallets/wallet/wallet.dart';
+import 'wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
 import 'widgets/choose_coin_view.dart';
 import 'widgets/frost_scaffold.dart';
 
@@ -908,11 +909,12 @@ class RouteGenerator {
         );
 
       case XPubView.routeName:
-        if (args is String) {
+        if (args is (String, ({List<XPub> xpubs, String fingerprint}))) {
           return getRoute(
             shouldUseMaterialRoute: useMaterialPageRoute,
             builder: (_) => XPubView(
-              walletId: args,
+              walletId: args.$1,
+              xpubData: args.$2,
             ),
             settings: RouteSettings(
               name: settings.name,

From fe5458928fe1889ffaf9a9a9c9f5ab1e7653ad2d Mon Sep 17 00:00:00 2001
From: julian <julian@cypherstack.com>
Date: Wed, 3 Jul 2024 13:55:11 -0600
Subject: [PATCH 07/12] mobile xprivs

---
 .../wallet_backup_view.dart                   | 725 +++++++++++-------
 .../wallet_settings_view.dart                 |   9 +
 .../firo_rescan_recovery_error_dialog.dart    |   9 +-
 lib/route_generator.dart                      |  57 ++
 4 files changed, 521 insertions(+), 279 deletions(-)

diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart
index 70bbdd294..d2ec44ca6 100644
--- a/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart
+++ b/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart
@@ -13,7 +13,7 @@ import 'dart:async';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:flutter_svg/svg.dart';
+import 'package:flutter_svg/flutter_svg.dart';
 
 import '../../../../app_config.dart';
 import '../../../../notifications/show_flush_bar.dart';
@@ -25,8 +25,10 @@ import '../../../../utilities/constants.dart';
 import '../../../../utilities/text_styles.dart';
 import '../../../../utilities/util.dart';
 import '../../../../wallets/isar/providers/wallet_info_provider.dart';
+import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
 import '../../../../widgets/background.dart';
 import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
+import '../../../../widgets/custom_buttons/blue_text_button.dart';
 import '../../../../widgets/custom_buttons/simple_copy_button.dart';
 import '../../../../widgets/detail_item.dart';
 import '../../../../widgets/qr.dart';
@@ -42,6 +44,7 @@ class WalletBackupView extends ConsumerWidget {
     required this.mnemonic,
     this.frostWalletData,
     this.clipboardInterface = const ClipboardWrapper(),
+    this.xprivData,
   });
 
   static const String routeName = "/walletBackup";
@@ -55,13 +58,13 @@ class WalletBackupView extends ConsumerWidget {
     ({String config, String keys})? prevGen,
   })? frostWalletData;
   final ClipboardInterface clipboardInterface;
+  final ({List<XPriv> xprivs, String fingerprint})? xprivData;
 
   @override
   Widget build(BuildContext context, WidgetRef ref) {
     debugPrint("BUILD: $runtimeType");
 
     final bool frost = frostWalletData != null;
-    final prevGen = frostWalletData?.prevGen != null;
 
     return Background(
       child: Scaffold(
@@ -77,296 +80,462 @@ class WalletBackupView extends ConsumerWidget {
             style: STextStyles.navBarTitle(context),
           ),
           actions: [
-            Padding(
-              padding: const EdgeInsets.all(10),
-              child: AspectRatio(
-                aspectRatio: 1,
-                child: AppBarIconButton(
-                  color: Theme.of(context).extension<StackColors>()!.background,
-                  shadows: const [],
-                  icon: SvgPicture.asset(
-                    Assets.svg.copy,
-                    width: 20,
-                    height: 20,
-                    color: Theme.of(context)
-                        .extension<StackColors>()!
-                        .topNavIconPrimary,
-                  ),
-                  onPressed: () async {
-                    await clipboardInterface
-                        .setData(ClipboardData(text: mnemonic.join(" ")));
-                    unawaited(
-                      showFloatingFlushBar(
-                        type: FlushBarType.info,
-                        message: "Copied to clipboard",
-                        iconAsset: Assets.svg.copy,
-                        context: context,
+            if (xprivData != null)
+              Padding(
+                padding: const EdgeInsets.all(10),
+                child: CustomTextButton(
+                  text: "xpriv(s)",
+                  onTap: () {
+                    Navigator.pushNamed(
+                      context,
+                      MobileXPrivsView.routeName,
+                      arguments: (
+                        walletId: walletId,
+                        xprivData: xprivData!,
                       ),
                     );
                   },
                 ),
+
+                // child: AspectRatio(
+                //   aspectRatio: 1,
+                //   child: AppBarIconButton(
+                //     color:
+                //         Theme.of(context).extension<StackColors>()!.background,
+                //     shadows: const [],
+                //     icon: SvgPicture.asset(
+                //       Assets.svg.copy,
+                //       width: 20,
+                //       height: 20,
+                //       color: Theme.of(context)
+                //           .extension<StackColors>()!
+                //           .topNavIconPrimary,
+                //     ),
+                //     onPressed: () async {
+                //       await clipboardInterface
+                //           .setData(ClipboardData(text: mnemonic.join(" ")));
+                //       unawaited(
+                //         showFloatingFlushBar(
+                //           type: FlushBarType.info,
+                //           message: "Copied to clipboard",
+                //           iconAsset: Assets.svg.copy,
+                //           context: context,
+                //         ),
+                //       );
+                //     },
+                //   ),
+                // ),
+              ),
+            if (!frost && xprivData == null)
+              Padding(
+                padding: const EdgeInsets.all(10),
+                child: AspectRatio(
+                  aspectRatio: 1,
+                  child: AppBarIconButton(
+                    color:
+                        Theme.of(context).extension<StackColors>()!.background,
+                    shadows: const [],
+                    icon: SvgPicture.asset(
+                      Assets.svg.copy,
+                      width: 20,
+                      height: 20,
+                      color: Theme.of(context)
+                          .extension<StackColors>()!
+                          .topNavIconPrimary,
+                    ),
+                    onPressed: () async {
+                      await clipboardInterface
+                          .setData(ClipboardData(text: mnemonic.join(" ")));
+                      unawaited(
+                        showFloatingFlushBar(
+                          type: FlushBarType.info,
+                          message: "Copied to clipboard",
+                          iconAsset: Assets.svg.copy,
+                          context: context,
+                        ),
+                      );
+                    },
+                  ),
+                ),
               ),
-            ),
           ],
         ),
         body: Padding(
           padding: const EdgeInsets.all(16),
           child: frost
-              ? LayoutBuilder(
-                  builder: (builderContext, constraints) {
-                    return SingleChildScrollView(
-                      child: ConstrainedBox(
-                        constraints: BoxConstraints(
-                          minHeight: constraints.maxHeight - 24,
-                        ),
-                        child: IntrinsicHeight(
-                          child: Column(
-                            crossAxisAlignment: CrossAxisAlignment.stretch,
-                            children: [
-                              RoundedWhiteContainer(
-                                child: Text(
-                                  "Please write down your backup data. Keep it safe and "
-                                  "never share it with anyone. "
-                                  "Your backup data is the only way you can access your "
-                                  "funds if you forget your PIN, lose your phone, etc."
-                                  "\n\n"
-                                  "${AppConfig.appName} does not keep nor is able to restore "
-                                  "your backup data. "
-                                  "Only you have access to your wallet.",
-                                  style: STextStyles.label(context),
-                                ),
-                              ),
-                              const SizedBox(
-                                height: 24,
-                              ),
-                              // DetailItem(
-                              //   title: "My name",
-                              //   detail: frostWalletData!.myName,
-                              //   button: Util.isDesktop
-                              //       ? IconCopyButton(
-                              //           data: frostWalletData!.myName,
-                              //         )
-                              //       : SimpleCopyButton(
-                              //           data: frostWalletData!.myName,
-                              //         ),
-                              // ),
-                              // const SizedBox(
-                              //   height: 16,
-                              // ),
-                              DetailItem(
-                                title: "Multisig config",
-                                detail: frostWalletData!.config,
-                                button: Util.isDesktop
-                                    ? IconCopyButton(
-                                        data: frostWalletData!.config,
-                                      )
-                                    : SimpleCopyButton(
-                                        data: frostWalletData!.config,
-                                      ),
-                              ),
-                              const SizedBox(
-                                height: 16,
-                              ),
-                              DetailItem(
-                                title: "Keys",
-                                detail: frostWalletData!.keys,
-                                button: Util.isDesktop
-                                    ? IconCopyButton(
-                                        data: frostWalletData!.keys,
-                                      )
-                                    : SimpleCopyButton(
-                                        data: frostWalletData!.keys,
-                                      ),
-                              ),
-                              if (prevGen)
-                                const SizedBox(
-                                  height: 24,
-                                ),
-                              if (prevGen)
-                                RoundedWhiteContainer(
-                                  child: Text(
-                                    "Previous generation info",
-                                    style: STextStyles.label(context),
-                                  ),
-                                ),
-                              if (prevGen)
-                                const SizedBox(
-                                  height: 12,
-                                ),
-                              if (prevGen)
-                                DetailItem(
-                                  title: "Previous multisig config",
-                                  detail: frostWalletData!.prevGen!.config,
-                                  button: Util.isDesktop
-                                      ? IconCopyButton(
-                                          data:
-                                              frostWalletData!.prevGen!.config,
-                                        )
-                                      : SimpleCopyButton(
-                                          data:
-                                              frostWalletData!.prevGen!.config,
-                                        ),
-                                ),
-                              if (prevGen)
-                                const SizedBox(
-                                  height: 16,
-                                ),
-                              if (prevGen)
-                                DetailItem(
-                                  title: "Previous keys",
-                                  detail: frostWalletData!.prevGen!.keys,
-                                  button: Util.isDesktop
-                                      ? IconCopyButton(
-                                          data: frostWalletData!.prevGen!.keys,
-                                        )
-                                      : SimpleCopyButton(
-                                          data: frostWalletData!.prevGen!.keys,
-                                        ),
-                                ),
-                            ],
-                          ),
-                        ),
-                      ),
-                    );
-                  },
+              ? _FrostKeys(
+                  frostWalletData: frostWalletData,
+                  walletId: walletId,
                 )
-              : Column(
-                  crossAxisAlignment: CrossAxisAlignment.stretch,
-                  children: [
-                    const SizedBox(
-                      height: 4,
-                    ),
-                    Text(
-                      ref.watch(pWalletName(walletId)),
-                      textAlign: TextAlign.center,
-                      style: STextStyles.label(context).copyWith(
-                        fontSize: 12,
-                      ),
-                    ),
-                    const SizedBox(
-                      height: 4,
-                    ),
-                    Text(
-                      "Recovery Phrase",
-                      textAlign: TextAlign.center,
-                      style: STextStyles.pageTitleH1(context),
-                    ),
-                    const SizedBox(
-                      height: 16,
-                    ),
-                    Container(
-                      decoration: BoxDecoration(
-                        color:
-                            Theme.of(context).extension<StackColors>()!.popupBG,
-                        borderRadius: BorderRadius.circular(
-                          Constants.size.circularBorderRadius,
-                        ),
-                      ),
-                      child: Padding(
-                        padding: const EdgeInsets.all(12),
-                        child: Text(
-                          "Please write down your backup key. Keep it safe and never share it with anyone. Your backup key is the only way you can access your funds if you forget your PIN, lose your phone, etc.\n\nStack Wallet does not keep nor is able to restore your backup key. Only you have access to your wallet.",
-                          style: STextStyles.label(context),
-                        ),
-                      ),
-                    ),
-                    const SizedBox(
-                      height: 8,
-                    ),
-                    Expanded(
-                      child: SingleChildScrollView(
-                        child: MnemonicTable(
-                          words: mnemonic,
-                          isDesktop: false,
-                        ),
-                      ),
-                    ),
-                    const SizedBox(
-                      height: 12,
-                    ),
-                    TextButton(
-                      style: Theme.of(context)
-                          .extension<StackColors>()!
-                          .getPrimaryEnabledButtonStyle(context),
-                      onPressed: () {
-                        final String data =
-                            AddressUtils.encodeQRSeedData(mnemonic);
-
-                        showDialog<dynamic>(
-                          context: context,
-                          useSafeArea: false,
-                          barrierDismissible: true,
-                          builder: (_) {
-                            final width = MediaQuery.of(context).size.width / 2;
-                            return StackDialogBase(
-                              child: Column(
-                                crossAxisAlignment: CrossAxisAlignment.stretch,
-                                children: [
-                                  Center(
-                                    child: Text(
-                                      "Recovery phrase QR code",
-                                      style: STextStyles.pageTitleH2(context),
-                                    ),
-                                  ),
-                                  const SizedBox(
-                                    height: 12,
-                                  ),
-                                  Center(
-                                    child: RepaintBoundary(
-                                      // key: _qrKey,
-                                      child: SizedBox(
-                                        width: width + 20,
-                                        height: width + 20,
-                                        child: QR(
-                                          data: data,
-                                          size: width,
-                                        ),
-                                      ),
-                                    ),
-                                  ),
-                                  const SizedBox(
-                                    height: 12,
-                                  ),
-                                  Center(
-                                    child: SizedBox(
-                                      width: width,
-                                      child: TextButton(
-                                        onPressed: () async {
-                                          // await _capturePng(true);
-                                          Navigator.of(context).pop();
-                                        },
-                                        style: Theme.of(context)
-                                            .extension<StackColors>()!
-                                            .getSecondaryEnabledButtonStyle(
-                                              context,
-                                            ),
-                                        child: Text(
-                                          "Cancel",
-                                          style: STextStyles.button(context)
-                                              .copyWith(
-                                            color: Theme.of(context)
-                                                .extension<StackColors>()!
-                                                .accentColorDark,
-                                          ),
-                                        ),
-                                      ),
-                                    ),
-                                  ),
-                                ],
-                              ),
-                            );
-                          },
-                        );
-                      },
-                      child: Text(
-                        "Show QR Code",
-                        style: STextStyles.button(context),
-                      ),
-                    ),
-                  ],
+              : _Mnemonic(
+                  walletId: walletId,
+                  mnemonic: mnemonic,
                 ),
         ),
       ),
     );
   }
 }
+
+class _XPrivs extends StatelessWidget {
+  const _XPrivs({super.key, required this.walletId, required this.xprivData});
+
+  final String walletId;
+  final ({List<XPriv> xprivs, String fingerprint}) xprivData;
+
+  @override
+  Widget build(BuildContext context) {
+    return Column(
+      mainAxisSize: MainAxisSize.min,
+      children: [
+        DetailItem(
+          title: "Master fingerprint",
+          detail: xprivData.fingerprint,
+          horizontal: true,
+        ),
+        const SizedBox(
+          height: 16,
+        ),
+        ...xprivData.xprivs.map(
+          (e) => Padding(
+            padding: const EdgeInsets.only(
+              bottom: 16,
+            ),
+            child: Column(
+              children: [
+                DetailItem(
+                  title: e.path,
+                  detail: e.xpriv,
+                ),
+              ],
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+}
+
+class _Mnemonic extends ConsumerWidget {
+  const _Mnemonic({super.key, required this.walletId, required this.mnemonic});
+
+  final String walletId;
+  final List<String> mnemonic;
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: [
+        const SizedBox(
+          height: 4,
+        ),
+        Text(
+          ref.watch(pWalletName(walletId)),
+          textAlign: TextAlign.center,
+          style: STextStyles.label(context).copyWith(
+            fontSize: 12,
+          ),
+        ),
+        const SizedBox(
+          height: 4,
+        ),
+        Text(
+          "Recovery Phrase",
+          textAlign: TextAlign.center,
+          style: STextStyles.pageTitleH1(context),
+        ),
+        const SizedBox(
+          height: 16,
+        ),
+        Container(
+          decoration: BoxDecoration(
+            color: Theme.of(context).extension<StackColors>()!.popupBG,
+            borderRadius: BorderRadius.circular(
+              Constants.size.circularBorderRadius,
+            ),
+          ),
+          child: Padding(
+            padding: const EdgeInsets.all(12),
+            child: Text(
+              "Please write down your backup key. Keep it safe and never share it with anyone. Your backup key is the only way you can access your funds if you forget your PIN, lose your phone, etc.\n\nStack Wallet does not keep nor is able to restore your backup key. Only you have access to your wallet.",
+              style: STextStyles.label(context),
+            ),
+          ),
+        ),
+        const SizedBox(
+          height: 8,
+        ),
+        Expanded(
+          child: SingleChildScrollView(
+            child: MnemonicTable(
+              words: mnemonic,
+              isDesktop: false,
+            ),
+          ),
+        ),
+        const SizedBox(
+          height: 12,
+        ),
+        TextButton(
+          style: Theme.of(context)
+              .extension<StackColors>()!
+              .getPrimaryEnabledButtonStyle(context),
+          onPressed: () {
+            final String data = AddressUtils.encodeQRSeedData(mnemonic);
+
+            showDialog<dynamic>(
+              context: context,
+              useSafeArea: false,
+              barrierDismissible: true,
+              builder: (_) {
+                final width = MediaQuery.of(context).size.width / 2;
+                return StackDialogBase(
+                  child: Column(
+                    crossAxisAlignment: CrossAxisAlignment.stretch,
+                    children: [
+                      Center(
+                        child: Text(
+                          "Recovery phrase QR code",
+                          style: STextStyles.pageTitleH2(context),
+                        ),
+                      ),
+                      const SizedBox(
+                        height: 12,
+                      ),
+                      Center(
+                        child: RepaintBoundary(
+                          // key: _qrKey,
+                          child: SizedBox(
+                            width: width + 20,
+                            height: width + 20,
+                            child: QR(
+                              data: data,
+                              size: width,
+                            ),
+                          ),
+                        ),
+                      ),
+                      const SizedBox(
+                        height: 12,
+                      ),
+                      Center(
+                        child: SizedBox(
+                          width: width,
+                          child: TextButton(
+                            onPressed: () async {
+                              // await _capturePng(true);
+                              Navigator.of(context).pop();
+                            },
+                            style: Theme.of(context)
+                                .extension<StackColors>()!
+                                .getSecondaryEnabledButtonStyle(
+                                  context,
+                                ),
+                            child: Text(
+                              "Cancel",
+                              style: STextStyles.button(context).copyWith(
+                                color: Theme.of(context)
+                                    .extension<StackColors>()!
+                                    .accentColorDark,
+                              ),
+                            ),
+                          ),
+                        ),
+                      ),
+                    ],
+                  ),
+                );
+              },
+            );
+          },
+          child: Text(
+            "Show QR Code",
+            style: STextStyles.button(context),
+          ),
+        ),
+      ],
+    );
+  }
+}
+
+class _FrostKeys extends StatelessWidget {
+  const _FrostKeys({
+    super.key,
+    required this.walletId,
+    this.frostWalletData,
+  });
+
+  static const String routeName = "/walletBackup";
+
+  final String walletId;
+  final ({
+    String myName,
+    String config,
+    String keys,
+    ({String config, String keys})? prevGen,
+  })? frostWalletData;
+
+  @override
+  Widget build(BuildContext context) {
+    final prevGen = frostWalletData?.prevGen != null;
+    return LayoutBuilder(
+      builder: (builderContext, constraints) {
+        return SingleChildScrollView(
+          child: ConstrainedBox(
+            constraints: BoxConstraints(
+              minHeight: constraints.maxHeight - 24,
+            ),
+            child: IntrinsicHeight(
+              child: Column(
+                crossAxisAlignment: CrossAxisAlignment.stretch,
+                children: [
+                  RoundedWhiteContainer(
+                    child: Text(
+                      "Please write down your backup data. Keep it safe and "
+                      "never share it with anyone. "
+                      "Your backup data is the only way you can access your "
+                      "funds if you forget your PIN, lose your phone, etc."
+                      "\n\n"
+                      "${AppConfig.appName} does not keep nor is able to restore "
+                      "your backup data. "
+                      "Only you have access to your wallet.",
+                      style: STextStyles.label(context),
+                    ),
+                  ),
+                  const SizedBox(
+                    height: 24,
+                  ),
+                  // DetailItem(
+                  //   title: "My name",
+                  //   detail: frostWalletData!.myName,
+                  //   button: Util.isDesktop
+                  //       ? IconCopyButton(
+                  //           data: frostWalletData!.myName,
+                  //         )
+                  //       : SimpleCopyButton(
+                  //           data: frostWalletData!.myName,
+                  //         ),
+                  // ),
+                  // const SizedBox(
+                  //   height: 16,
+                  // ),
+                  DetailItem(
+                    title: "Multisig config",
+                    detail: frostWalletData!.config,
+                    button: Util.isDesktop
+                        ? IconCopyButton(
+                            data: frostWalletData!.config,
+                          )
+                        : SimpleCopyButton(
+                            data: frostWalletData!.config,
+                          ),
+                  ),
+                  const SizedBox(
+                    height: 16,
+                  ),
+                  DetailItem(
+                    title: "Keys",
+                    detail: frostWalletData!.keys,
+                    button: Util.isDesktop
+                        ? IconCopyButton(
+                            data: frostWalletData!.keys,
+                          )
+                        : SimpleCopyButton(
+                            data: frostWalletData!.keys,
+                          ),
+                  ),
+                  if (prevGen)
+                    const SizedBox(
+                      height: 24,
+                    ),
+                  if (prevGen)
+                    RoundedWhiteContainer(
+                      child: Text(
+                        "Previous generation info",
+                        style: STextStyles.label(context),
+                      ),
+                    ),
+                  if (prevGen)
+                    const SizedBox(
+                      height: 12,
+                    ),
+                  if (prevGen)
+                    DetailItem(
+                      title: "Previous multisig config",
+                      detail: frostWalletData!.prevGen!.config,
+                      button: Util.isDesktop
+                          ? IconCopyButton(
+                              data: frostWalletData!.prevGen!.config,
+                            )
+                          : SimpleCopyButton(
+                              data: frostWalletData!.prevGen!.config,
+                            ),
+                    ),
+                  if (prevGen)
+                    const SizedBox(
+                      height: 16,
+                    ),
+                  if (prevGen)
+                    DetailItem(
+                      title: "Previous keys",
+                      detail: frostWalletData!.prevGen!.keys,
+                      button: Util.isDesktop
+                          ? IconCopyButton(
+                              data: frostWalletData!.prevGen!.keys,
+                            )
+                          : SimpleCopyButton(
+                              data: frostWalletData!.prevGen!.keys,
+                            ),
+                    ),
+                ],
+              ),
+            ),
+          ),
+        );
+      },
+    );
+  }
+}
+
+class MobileXPrivsView extends StatelessWidget {
+  const MobileXPrivsView({
+    super.key,
+    required this.walletId,
+    this.clipboardInterface = const ClipboardWrapper(),
+    required this.xprivData,
+  });
+
+  static const String routeName = "/mobileXPrivView";
+
+  final String walletId;
+  final ClipboardInterface clipboardInterface;
+  final ({List<XPriv> xprivs, String fingerprint}) xprivData;
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      backgroundColor: Theme.of(context).extension<StackColors>()!.background,
+      appBar: AppBar(
+        leading: AppBarBackButton(
+          onPressed: () {
+            Navigator.of(context).pop();
+          },
+        ),
+        title: Text(
+          "Wallet xpriv(s)",
+          style: STextStyles.navBarTitle(context),
+        ),
+      ),
+      body: Padding(
+        padding: const EdgeInsets.symmetric(horizontal: 16),
+        child: SingleChildScrollView(
+          child: _XPrivs(
+            walletId: walletId,
+            xprivData: xprivData,
+          ),
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart
index aca76d7f8..a9fdd52f0 100644
--- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart
+++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart
@@ -261,6 +261,10 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
 
                                         // TODO: [prio=med] take wallets that don't have a mnemonic into account
 
+                                        ({
+                                          List<XPriv> xprivs,
+                                          String fingerprint
+                                        })? xprivData;
                                         List<String>? mnemonic;
                                         ({
                                           String myName,
@@ -302,6 +306,10 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
                                               await wallet.getMnemonicAsWords();
                                         }
 
+                                        if (wallet is ExtendedKeysInterface) {
+                                          xprivData = await wallet.getXPrivs();
+                                        }
+
                                         if (context.mounted) {
                                           await Navigator.push(
                                             context,
@@ -315,6 +323,7 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
                                                   mnemonic: mnemonic ?? [],
                                                   frostWalletData:
                                                       frostWalletData,
+                                                  xprivData: xprivData,
                                                 ),
                                                 showBackButton: true,
                                                 routeOnSuccess:
diff --git a/lib/pages/special/firo_rescan_recovery_error_dialog.dart b/lib/pages/special/firo_rescan_recovery_error_dialog.dart
index 3a80b0b85..6409646c6 100644
--- a/lib/pages/special/firo_rescan_recovery_error_dialog.dart
+++ b/lib/pages/special/firo_rescan_recovery_error_dialog.dart
@@ -11,6 +11,7 @@ import '../../utilities/assets.dart';
 import '../../utilities/text_styles.dart';
 import '../../utilities/util.dart';
 import '../../wallets/isar/providers/wallet_info_provider.dart';
+import '../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
 import '../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart';
 import '../../widgets/background.dart';
 import '../../widgets/conditional_parent.dart';
@@ -264,7 +265,12 @@ class _FiroRescanRecoveryErrorViewState
                         if (wallet is MnemonicInterface) {
                           final mnemonic = await wallet.getMnemonicAsWords();
 
-                          if (mounted) {
+                          ({List<XPriv> xprivs, String fingerprint})? xprivData;
+                          if (wallet is ExtendedKeysInterface) {
+                            xprivData = await wallet.getXPrivs();
+                          }
+
+                          if (context.mounted) {
                             await Navigator.push(
                               context,
                               RouteGenerator.getRoute(
@@ -274,6 +280,7 @@ class _FiroRescanRecoveryErrorViewState
                                   routeOnSuccessArguments: (
                                     walletId: widget.walletId,
                                     mnemonic: mnemonic,
+                                    xprivData: xprivData,
                                   ),
                                   showBackButton: true,
                                   routeOnSuccess: WalletBackupView.routeName,
diff --git a/lib/route_generator.dart b/lib/route_generator.dart
index d7b8a1563..3d07a766b 100644
--- a/lib/route_generator.dart
+++ b/lib/route_generator.dart
@@ -1277,6 +1277,63 @@ class RouteGenerator {
               name: settings.name,
             ),
           );
+        } else if (args is ({
+          String walletId,
+          List<String> mnemonic,
+          ({List<XPriv> xprivs, String fingerprint})? xprivData,
+        })) {
+          return getRoute(
+            shouldUseMaterialRoute: useMaterialPageRoute,
+            builder: (_) => WalletBackupView(
+              walletId: args.walletId,
+              mnemonic: args.mnemonic,
+              xprivData: args.xprivData,
+            ),
+            settings: RouteSettings(
+              name: settings.name,
+            ),
+          );
+        } else if (args is ({
+          String walletId,
+          List<String> mnemonic,
+          ({List<XPriv> xprivs, String fingerprint})? xprivData,
+          ({
+            String myName,
+            String config,
+            String keys,
+            ({String config, String keys})? prevGen,
+          })? frostWalletData,
+        })) {
+          return getRoute(
+            shouldUseMaterialRoute: useMaterialPageRoute,
+            builder: (_) => WalletBackupView(
+              walletId: args.walletId,
+              mnemonic: args.mnemonic,
+              frostWalletData: args.frostWalletData,
+              xprivData: args.xprivData,
+            ),
+            settings: RouteSettings(
+              name: settings.name,
+            ),
+          );
+        }
+        return _routeError("${settings.name} invalid args: ${args.toString()}");
+
+      case MobileXPrivsView.routeName:
+        if (args is ({
+          String walletId,
+          ({List<XPriv> xprivs, String fingerprint}) xprivData,
+        })) {
+          return getRoute(
+            shouldUseMaterialRoute: useMaterialPageRoute,
+            builder: (_) => MobileXPrivsView(
+              walletId: args.walletId,
+              xprivData: args.xprivData,
+            ),
+            settings: RouteSettings(
+              name: settings.name,
+            ),
+          );
         }
         return _routeError("${settings.name} invalid args: ${args.toString()}");
 

From f0b62aed92743052a7df5fdb95405d9095ab70ff Mon Sep 17 00:00:00 2001
From: julian <julian@cypherstack.com>
Date: Wed, 3 Jul 2024 15:04:19 -0600
Subject: [PATCH 08/12] address key wif

---
 .../addresses/address_details_view.dart       |  94 ++++-----------
 .../wallet/intermediate/bip39_hd_wallet.dart  |  19 +++
 lib/widgets/address_private_key.dart          |  83 +++++++++++++
 lib/widgets/detail_item.dart                  | 111 +++++++++---------
 4 files changed, 177 insertions(+), 130 deletions(-)
 create mode 100644 lib/widgets/address_private_key.dart

diff --git a/lib/pages/receive_view/addresses/address_details_view.dart b/lib/pages/receive_view/addresses/address_details_view.dart
index 103a5729d..eb102bfe7 100644
--- a/lib/pages/receive_view/addresses/address_details_view.dart
+++ b/lib/pages/receive_view/addresses/address_details_view.dart
@@ -22,6 +22,7 @@ import '../../../utilities/address_utils.dart';
 import '../../../utilities/text_styles.dart';
 import '../../../utilities/util.dart';
 import '../../../wallets/isar/providers/wallet_info_provider.dart';
+import '../../../widgets/address_private_key.dart';
 import '../../../widgets/background.dart';
 import '../../../widgets/conditional_parent.dart';
 import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
@@ -30,6 +31,7 @@ import '../../../widgets/custom_buttons/simple_copy_button.dart';
 import '../../../widgets/custom_buttons/simple_edit_button.dart';
 import '../../../widgets/desktop/desktop_dialog.dart';
 import '../../../widgets/desktop/desktop_dialog_close_button.dart';
+import '../../../widgets/detail_item.dart';
 import '../../../widgets/qr.dart';
 import '../../../widgets/rounded_white_container.dart';
 import '../../../widgets/transaction_card.dart';
@@ -298,9 +300,9 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
                   const SizedBox(
                     height: 16,
                   ),
-                _Item(
+                DetailItem(
                   title: "Address",
-                  data: address.value,
+                  detail: address.value,
                   button: isDesktop
                       ? IconCopyButton(
                           data: address.value,
@@ -312,9 +314,9 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
                 const _Div(
                   height: 12,
                 ),
-                _Item(
+                DetailItem(
                   title: "Label",
-                  data: label!.value,
+                  detail: label!.value,
                   button: SimpleEditButton(
                     editValue: label!.value,
                     editLabel: 'label',
@@ -338,9 +340,9 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
                     height: 12,
                   ),
                 if (address.derivationPath != null)
-                  _Item(
+                  DetailItem(
                     title: "Derivation path",
-                    data: address.derivationPath!.value,
+                    detail: address.derivationPath!.value,
                     button: Container(),
                   ),
                 if (address.type == AddressType.spark)
@@ -348,27 +350,34 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
                     height: 12,
                   ),
                 if (address.type == AddressType.spark)
-                  _Item(
+                  DetailItem(
                     title: "Diversifier",
-                    data: address.derivationIndex.toString(),
+                    detail: address.derivationIndex.toString(),
                     button: Container(),
                   ),
                 const _Div(
                   height: 12,
                 ),
-                _Item(
+                DetailItem(
                   title: "Type",
-                  data: address.type.readableName,
+                  detail: address.type.readableName,
                   button: Container(),
                 ),
                 const _Div(
                   height: 12,
                 ),
-                _Item(
+                DetailItem(
                   title: "Sub type",
-                  data: address.subType.prettyName,
+                  detail: address.subType.prettyName,
                   button: Container(),
                 ),
+                const _Div(
+                  height: 12,
+                ),
+                AddressPrivateKey(
+                  walletId: widget.walletId,
+                  address: address,
+                ),
                 if (!isDesktop)
                   const SizedBox(
                     height: 20,
@@ -631,64 +640,3 @@ class _Tags extends StatelessWidget {
     );
   }
 }
-
-class _Item extends StatelessWidget {
-  const _Item({
-    super.key,
-    required this.title,
-    required this.data,
-    required this.button,
-  });
-
-  final String title;
-  final String data;
-  final Widget button;
-
-  @override
-  Widget build(BuildContext context) {
-    return ConditionalParent(
-      condition: !Util.isDesktop,
-      builder: (child) => RoundedWhiteContainer(
-        child: child,
-      ),
-      child: ConditionalParent(
-        condition: Util.isDesktop,
-        builder: (child) => Padding(
-          padding: const EdgeInsets.all(16),
-          child: child,
-        ),
-        child: Column(
-          crossAxisAlignment: CrossAxisAlignment.start,
-          children: [
-            Row(
-              mainAxisAlignment: MainAxisAlignment.spaceBetween,
-              children: [
-                Text(
-                  title,
-                  style: STextStyles.itemSubtitle(context),
-                ),
-                button,
-              ],
-            ),
-            const SizedBox(
-              height: 5,
-            ),
-            data.isNotEmpty
-                ? SelectableText(
-                    data,
-                    style: STextStyles.w500_14(context),
-                  )
-                : Text(
-                    "$title will appear here",
-                    style: STextStyles.w500_14(context).copyWith(
-                      color: Theme.of(context)
-                          .extension<StackColors>()!
-                          .textSubtitle3,
-                    ),
-                  ),
-          ],
-        ),
-      ),
-    );
-  }
-}
diff --git a/lib/wallets/wallet/intermediate/bip39_hd_wallet.dart b/lib/wallets/wallet/intermediate/bip39_hd_wallet.dart
index 6d87b7ace..3d603fc03 100644
--- a/lib/wallets/wallet/intermediate/bip39_hd_wallet.dart
+++ b/lib/wallets/wallet/intermediate/bip39_hd_wallet.dart
@@ -1,3 +1,5 @@
+import 'dart:typed_data';
+
 import 'package:bip39/bip39.dart' as bip39;
 import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib;
 import 'package:isar/isar.dart';
@@ -6,6 +8,7 @@ import '../../../models/balance.dart';
 import '../../../models/isar/models/blockchain_data/address.dart';
 import '../../../utilities/amount/amount.dart';
 import '../../../utilities/enums/derive_path_type_enum.dart';
+import '../../../utilities/extensions/extensions.dart';
 import '../../crypto_currency/intermediate/bip39_hd_currency.dart';
 import '../wallet_mixin_interfaces/multi_address_interface.dart';
 import 'bip39_wallet.dart';
@@ -28,6 +31,22 @@ abstract class Bip39HDWallet<T extends Bip39HDCurrency> extends Bip39Wallet<T>
     return coinlib.HDPrivateKey.fromSeed(seed);
   }
 
+  Future<String> getPrivateKeyWIF(Address address) async {
+    final keys =
+        (await getRootHDNode()).derivePath(address.derivationPath!.value);
+
+    final List<int> data = [
+      cryptoCurrency.networkParams.wifPrefix,
+      ...keys.privateKey.data,
+      if (keys.privateKey.compressed) 1,
+    ];
+    final checksum =
+        coinlib.sha256DoubleHash(Uint8List.fromList(data)).sublist(0, 4);
+    data.addAll(checksum);
+
+    return Uint8List.fromList(data).toBase58Encoded;
+  }
+
   Future<Address> generateNextReceivingAddress({
     required DerivePathType derivePathType,
   }) async {
diff --git a/lib/widgets/address_private_key.dart b/lib/widgets/address_private_key.dart
new file mode 100644
index 000000000..8bcbd404d
--- /dev/null
+++ b/lib/widgets/address_private_key.dart
@@ -0,0 +1,83 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+import '../models/isar/models/isar_models.dart';
+import '../providers/global/wallets_provider.dart';
+import '../utilities/show_loading.dart';
+import '../utilities/text_styles.dart';
+import '../utilities/util.dart';
+import '../wallets/wallet/intermediate/bip39_hd_wallet.dart';
+import 'custom_buttons/blue_text_button.dart';
+import 'detail_item.dart';
+
+class AddressPrivateKey extends ConsumerStatefulWidget {
+  /// The [walletId] MUST be the id of a [Bip39HDWallet]!
+  const AddressPrivateKey({
+    super.key,
+    required this.walletId,
+    required this.address,
+  });
+
+  final String walletId;
+  final Address address;
+
+  @override
+  ConsumerState<AddressPrivateKey> createState() => _AddressPrivateKeyState();
+}
+
+class _AddressPrivateKeyState extends ConsumerState<AddressPrivateKey> {
+  String? _private;
+
+  bool _lock = false;
+
+  Future<void> _loadPrivKey() async {
+    // sanity check that should never actually fail in practice.
+    // Big problems if it actually does though so we check and crash if it fails.
+    assert(widget.walletId == widget.address.walletId);
+
+    if (_lock) {
+      return;
+    }
+    _lock = true;
+
+    try {
+      final wallet =
+          ref.read(pWallets).getWallet(widget.walletId) as Bip39HDWallet;
+
+      _private = await showLoading(
+        whileFuture: wallet.getPrivateKeyWIF(widget.address),
+        context: context,
+        message: "Loading...",
+        delay: const Duration(milliseconds: 800),
+        rootNavigator: Util.isDesktop,
+      );
+
+      if (context.mounted) {
+        setState(() {});
+      } else {
+        _private == null;
+      }
+    } finally {
+      _lock = false;
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return DetailItemBase(
+      button: CustomTextButton(
+        text: "Show",
+        onTap: _loadPrivKey,
+        enabled: _private == null,
+      ),
+      title: Text(
+        "Private key",
+        style: STextStyles.itemSubtitle(context),
+      ),
+      detail: SelectableText(
+        _private ?? "*" * 52, // 52 is approx length
+        style: STextStyles.w500_14(context),
+      ),
+    );
+  }
+}
diff --git a/lib/widgets/detail_item.dart b/lib/widgets/detail_item.dart
index b3b1552a2..c619c4192 100644
--- a/lib/widgets/detail_item.dart
+++ b/lib/widgets/detail_item.dart
@@ -1,4 +1,5 @@
 import 'package:flutter/material.dart';
+
 import '../themes/stack_colors.dart';
 import '../utilities/text_styles.dart';
 import '../utilities/util.dart';
@@ -27,15 +28,61 @@ class DetailItem extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    final TextStyle detailStyle;
+    TextStyle detailStyle = STextStyles.w500_14(context);
+    String _detail = detail;
     if (overrideDetailTextColor != null) {
       detailStyle = STextStyles.w500_14(context).copyWith(
         color: overrideDetailTextColor,
       );
-    } else {
-      detailStyle = STextStyles.w500_14(context);
     }
 
+    if (detail.isEmpty && showEmptyDetail) {
+      _detail = "$title will appear here";
+      detailStyle = detailStyle.copyWith(
+        color: Theme.of(context).extension<StackColors>()!.textSubtitle3,
+      );
+    }
+
+    return DetailItemBase(
+      horizontal: horizontal,
+      title: disableSelectableText
+          ? Text(
+              title,
+              style: STextStyles.itemSubtitle(context),
+            )
+          : SelectableText(
+              title,
+              style: STextStyles.itemSubtitle(context),
+            ),
+      detail: disableSelectableText
+          ? Text(
+              _detail,
+              style: detailStyle,
+            )
+          : SelectableText(
+              _detail,
+              style: detailStyle,
+            ),
+    );
+  }
+}
+
+class DetailItemBase extends StatelessWidget {
+  const DetailItemBase({
+    super.key,
+    required this.title,
+    required this.detail,
+    this.button,
+    this.horizontal = false,
+  });
+
+  final Widget title;
+  final Widget detail;
+  final Widget? button;
+  final bool horizontal;
+
+  @override
+  Widget build(BuildContext context) {
     return ConditionalParent(
       condition: !Util.isDesktop,
       builder: (child) => RoundedWhiteContainer(
@@ -51,24 +98,8 @@ class DetailItem extends StatelessWidget {
             ? Row(
                 mainAxisAlignment: MainAxisAlignment.spaceBetween,
                 children: [
-                  disableSelectableText
-                      ? Text(
-                          title,
-                          style: STextStyles.itemSubtitle(context),
-                        )
-                      : SelectableText(
-                          title,
-                          style: STextStyles.itemSubtitle(context),
-                        ),
-                  disableSelectableText
-                      ? Text(
-                          detail,
-                          style: detailStyle,
-                        )
-                      : SelectableText(
-                          detail,
-                          style: detailStyle,
-                        ),
+                  title,
+                  detail,
                 ],
               )
             : Column(
@@ -77,48 +108,14 @@ class DetailItem extends StatelessWidget {
                   Row(
                     mainAxisAlignment: MainAxisAlignment.spaceBetween,
                     children: [
-                      disableSelectableText
-                          ? Text(
-                              title,
-                              style: STextStyles.itemSubtitle(context),
-                            )
-                          : SelectableText(
-                              title,
-                              style: STextStyles.itemSubtitle(context),
-                            ),
+                      title,
                       button ?? Container(),
                     ],
                   ),
                   const SizedBox(
                     height: 5,
                   ),
-                  detail.isEmpty && showEmptyDetail
-                      ? disableSelectableText
-                          ? Text(
-                              "$title will appear here",
-                              style: STextStyles.w500_14(context).copyWith(
-                                color: Theme.of(context)
-                                    .extension<StackColors>()!
-                                    .textSubtitle3,
-                              ),
-                            )
-                          : SelectableText(
-                              "$title will appear here",
-                              style: STextStyles.w500_14(context).copyWith(
-                                color: Theme.of(context)
-                                    .extension<StackColors>()!
-                                    .textSubtitle3,
-                              ),
-                            )
-                      : disableSelectableText
-                          ? Text(
-                              detail,
-                              style: detailStyle,
-                            )
-                          : SelectableText(
-                              detail,
-                              style: detailStyle,
-                            ),
+                  detail,
                 ],
               ),
       ),

From 830a7ab24f3eb60e7dbbc61787c0c1c9c368be23 Mon Sep 17 00:00:00 2001
From: julian <julian@cypherstack.com>
Date: Wed, 3 Jul 2024 16:34:57 -0600
Subject: [PATCH 09/12] lelantus scan option fix

---
 .../restore_options_view.dart                 | 24 ++++++++++---------
 1 file changed, 13 insertions(+), 11 deletions(-)

diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart
index 8f88e8710..5e4a3d76a 100644
--- a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart
+++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart
@@ -405,17 +405,19 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
                     color: Colors.transparent,
                     child: Column(
                       children: [
-                        CheckboxTextButton(
-                          label: "Scan for Lelantus transactions",
-                          onChanged: (newValue) {
-                            setState(() {
-                              enableLelantusScanning = newValue ?? true;
-                            });
-                          },
-                        ),
-                        const SizedBox(
-                          height: 8,
-                        ),
+                        if (coin is Firo)
+                          CheckboxTextButton(
+                            label: "Scan for Lelantus transactions",
+                            onChanged: (newValue) {
+                              setState(() {
+                                enableLelantusScanning = newValue ?? true;
+                              });
+                            },
+                          ),
+                        if (coin is Firo)
+                          const SizedBox(
+                            height: 8,
+                          ),
                         ClipRRect(
                           borderRadius: BorderRadius.circular(
                             Constants.size.circularBorderRadius,

From 6af6456c9c0841e09033372d35e7e195440011f0 Mon Sep 17 00:00:00 2001
From: julian <julian@cypherstack.com>
Date: Wed, 3 Jul 2024 17:32:32 -0600
Subject: [PATCH 10/12] request auth to show address private key

---
 .../restore_options_view.dart                 |   3 +
 lib/pages/pinpad_views/pinpad_dialog.dart     | 292 ++++++++++++++++++
 .../password/request_desktop_auth_dialog.dart | 264 ++++++++++++++++
 lib/widgets/address_private_key.dart          |  43 ++-
 4 files changed, 589 insertions(+), 13 deletions(-)
 create mode 100644 lib/pages/pinpad_views/pinpad_dialog.dart
 create mode 100644 lib/pages_desktop_specific/password/request_desktop_auth_dialog.dart

diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart
index 5e4a3d76a..0b7fcc37b 100644
--- a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart
+++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart
@@ -502,6 +502,9 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
                             ),
                           ),
                         ),
+                        const SizedBox(
+                          height: 16,
+                        ),
                       ],
                     ),
                   ),
diff --git a/lib/pages/pinpad_views/pinpad_dialog.dart b/lib/pages/pinpad_views/pinpad_dialog.dart
new file mode 100644
index 000000000..35d60aa32
--- /dev/null
+++ b/lib/pages/pinpad_views/pinpad_dialog.dart
@@ -0,0 +1,292 @@
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+import '../../notifications/show_flush_bar.dart';
+import '../../providers/global/prefs_provider.dart';
+import '../../providers/global/secure_store_provider.dart';
+import '../../themes/stack_colors.dart';
+import '../../utilities/assets.dart';
+import '../../utilities/biometrics.dart';
+import '../../utilities/flutter_secure_storage_interface.dart';
+import '../../utilities/text_styles.dart';
+import '../../widgets/custom_pin_put/custom_pin_put.dart';
+import '../../widgets/shake/shake.dart';
+import '../../widgets/stack_dialog.dart';
+
+class PinpadDialog extends ConsumerStatefulWidget {
+  const PinpadDialog({
+    super.key,
+    required this.biometricsAuthenticationTitle,
+    required this.biometricsLocalizedReason,
+    required this.biometricsCancelButtonString,
+    this.biometrics = const Biometrics(),
+    this.customKeyLabel = "Button",
+  });
+
+  final String biometricsAuthenticationTitle;
+  final String biometricsLocalizedReason;
+  final String biometricsCancelButtonString;
+  final Biometrics biometrics;
+  final String customKeyLabel;
+
+  @override
+  ConsumerState<PinpadDialog> createState() => _PinpadDialogState();
+}
+
+class _PinpadDialogState extends ConsumerState<PinpadDialog> {
+  late final ShakeController _shakeController;
+
+  late int _attempts;
+  bool _attemptLock = false;
+  late Duration _timeout;
+  static const maxAttemptsBeforeThrottling = 3;
+  Timer? _timer;
+
+  final FocusNode _pinFocusNode = FocusNode();
+
+  late SecureStorageInterface _secureStore;
+  late Biometrics biometrics;
+  int pinCount = 1;
+
+  final _pinTextController = TextEditingController();
+
+  BoxDecoration get _pinPutDecoration {
+    return BoxDecoration(
+      color: Theme.of(context).extension<StackColors>()!.infoItemIcons,
+      border: Border.all(
+        width: 1,
+        color: Theme.of(context).extension<StackColors>()!.infoItemIcons,
+      ),
+      borderRadius: BorderRadius.circular(6),
+    );
+  }
+
+  Future<void> _onPinChanged() async {
+    final enteredPin = _pinTextController.text;
+    final storedPin = await _secureStore.read(key: 'stack_pin');
+    final autoPin = ref.read(prefsChangeNotifierProvider).autoPin;
+
+    if (enteredPin.length >= 4 && autoPin && enteredPin == storedPin) {
+      await Future<void>.delayed(
+        const Duration(milliseconds: 200),
+      );
+      unawaited(_onUnlock());
+    }
+  }
+
+  Future<void> _onUnlock() async {
+    final now = DateTime.now().toUtc();
+    ref.read(prefsChangeNotifierProvider).lastUnlocked =
+        now.millisecondsSinceEpoch ~/ 1000;
+
+    Navigator.of(context).pop("verified success");
+  }
+
+  Future<void> _checkUseBiometrics() async {
+    if (!ref.read(prefsChangeNotifierProvider).isInitialized) {
+      await ref.read(prefsChangeNotifierProvider).init();
+    }
+
+    final bool useBiometrics =
+        ref.read(prefsChangeNotifierProvider).useBiometrics;
+
+    final title = widget.biometricsAuthenticationTitle;
+    final localizedReason = widget.biometricsLocalizedReason;
+    final cancelButtonText = widget.biometricsCancelButtonString;
+
+    if (useBiometrics) {
+      if (await biometrics.authenticate(
+        title: title,
+        localizedReason: localizedReason,
+        cancelButtonText: cancelButtonText,
+      )) {
+        unawaited(_onUnlock());
+      }
+      // leave this commented to enable pin fall back should biometrics not work properly
+      // else {
+      //   Navigator.pop(context);
+      // }
+    }
+  }
+
+  Future<void> _onSubmit(String pin) async {
+    _attempts++;
+
+    if (_attempts > maxAttemptsBeforeThrottling) {
+      _attemptLock = true;
+      switch (_attempts) {
+        case 4:
+          _timeout = const Duration(seconds: 30);
+          break;
+
+        case 5:
+          _timeout = const Duration(seconds: 60);
+          break;
+
+        case 6:
+          _timeout = const Duration(minutes: 5);
+          break;
+
+        case 7:
+          _timeout = const Duration(minutes: 10);
+          break;
+
+        case 8:
+          _timeout = const Duration(minutes: 20);
+          break;
+
+        case 9:
+          _timeout = const Duration(minutes: 30);
+          break;
+
+        default:
+          _timeout = const Duration(minutes: 60);
+      }
+
+      _timer?.cancel();
+      _timer = Timer(_timeout, () {
+        _attemptLock = false;
+        _attempts = 0;
+      });
+    }
+
+    if (_attemptLock) {
+      String prettyTime = "";
+      if (_timeout.inSeconds >= 60) {
+        prettyTime += "${_timeout.inMinutes} minutes";
+      } else {
+        prettyTime += "${_timeout.inSeconds} seconds";
+      }
+
+      unawaited(
+        showFloatingFlushBar(
+          type: FlushBarType.warning,
+          message:
+              "Incorrect PIN entered too many times. Please wait $prettyTime",
+          context: context,
+          iconAsset: Assets.svg.alertCircle,
+        ),
+      );
+
+      await Future<void>.delayed(
+        const Duration(milliseconds: 100),
+      );
+
+      _pinTextController.text = '';
+
+      return;
+    }
+
+    final storedPin = await _secureStore.read(key: 'stack_pin');
+
+    if (mounted) {
+      if (storedPin == pin) {
+        await Future<void>.delayed(
+          const Duration(milliseconds: 200),
+        );
+        unawaited(_onUnlock());
+      } else {
+        unawaited(_shakeController.shake());
+        unawaited(
+          showFloatingFlushBar(
+            type: FlushBarType.warning,
+            message: "Incorrect PIN. Please try again",
+            context: context,
+            iconAsset: Assets.svg.alertCircle,
+          ),
+        );
+
+        await Future<void>.delayed(
+          const Duration(milliseconds: 100),
+        );
+
+        _pinTextController.text = '';
+      }
+    }
+  }
+
+  @override
+  void initState() {
+    _shakeController = ShakeController();
+
+    _secureStore = ref.read(secureStoreProvider);
+    biometrics = widget.biometrics;
+    _attempts = 0;
+    _timeout = Duration.zero;
+
+    _checkUseBiometrics();
+    _pinTextController.addListener(_onPinChanged);
+    super.initState();
+  }
+
+  @override
+  dispose() {
+    // _shakeController.dispose();
+    _pinTextController.removeListener(_onPinChanged);
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return StackDialogBase(
+      child: Column(
+        mainAxisAlignment: MainAxisAlignment.center,
+        children: [
+          Shake(
+            animationDuration: const Duration(milliseconds: 700),
+            animationRange: 12,
+            controller: _shakeController,
+            child: Center(
+              child: Column(
+                crossAxisAlignment: CrossAxisAlignment.stretch,
+                children: [
+                  Center(
+                    child: Text(
+                      "Enter PIN",
+                      style: STextStyles.pageTitleH1(context),
+                    ),
+                  ),
+                  const SizedBox(
+                    height: 40,
+                  ),
+                  CustomPinPut(
+                    fieldsCount: pinCount,
+                    eachFieldHeight: 12,
+                    eachFieldWidth: 12,
+                    textStyle: STextStyles.label(context).copyWith(
+                      fontSize: 1,
+                    ),
+                    focusNode: _pinFocusNode,
+                    controller: _pinTextController,
+                    useNativeKeyboard: false,
+                    obscureText: "",
+                    inputDecoration: InputDecoration(
+                      border: InputBorder.none,
+                      enabledBorder: InputBorder.none,
+                      focusedBorder: InputBorder.none,
+                      disabledBorder: InputBorder.none,
+                      errorBorder: InputBorder.none,
+                      focusedErrorBorder: InputBorder.none,
+                      fillColor:
+                          Theme.of(context).extension<StackColors>()!.popupBG,
+                      counterText: "",
+                    ),
+                    submittedFieldDecoration: _pinPutDecoration,
+                    isRandom:
+                        ref.read(prefsChangeNotifierProvider).randomizePIN,
+                    onSubmit: _onSubmit,
+                  ),
+                  const SizedBox(
+                    height: 32,
+                  ),
+                ],
+              ),
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}
diff --git a/lib/pages_desktop_specific/password/request_desktop_auth_dialog.dart b/lib/pages_desktop_specific/password/request_desktop_auth_dialog.dart
new file mode 100644
index 000000000..c991513cb
--- /dev/null
+++ b/lib/pages_desktop_specific/password/request_desktop_auth_dialog.dart
@@ -0,0 +1,264 @@
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_svg/svg.dart';
+
+import '../../notifications/show_flush_bar.dart';
+import '../../providers/desktop/storage_crypto_handler_provider.dart';
+import '../../themes/stack_colors.dart';
+import '../../utilities/assets.dart';
+import '../../utilities/constants.dart';
+import '../../utilities/show_loading.dart';
+import '../../utilities/text_styles.dart';
+import '../../widgets/desktop/desktop_dialog.dart';
+import '../../widgets/desktop/desktop_dialog_close_button.dart';
+import '../../widgets/desktop/primary_button.dart';
+import '../../widgets/desktop/secondary_button.dart';
+import '../../widgets/stack_text_field.dart';
+
+class RequestDesktopAuthDialog extends ConsumerStatefulWidget {
+  const RequestDesktopAuthDialog({
+    super.key,
+    this.title,
+  });
+
+  final String? title;
+
+  @override
+  ConsumerState<RequestDesktopAuthDialog> createState() =>
+      _RequestDesktopAuthDialogState();
+}
+
+class _RequestDesktopAuthDialogState
+    extends ConsumerState<RequestDesktopAuthDialog> {
+  late final TextEditingController passwordController;
+  late final FocusNode passwordFocusNode;
+
+  bool continueEnabled = false;
+  bool hidePassword = true;
+
+  bool _lock = false;
+  Future<void> _auth() async {
+    if (_lock) {
+      return;
+    }
+    _lock = true;
+
+    try {
+      final verified = await showLoading(
+        whileFuture: ref
+            .read(storageCryptoHandlerProvider)
+            .verifyPassphrase(passwordController.text),
+        context: context,
+        message: "Checking...",
+        rootNavigator: true,
+        delay: const Duration(milliseconds: 1000),
+      );
+
+      if (verified == true) {
+        if (mounted) {
+          Navigator.of(context, rootNavigator: true).pop("verified success");
+        }
+      } else {
+        if (mounted) {
+          Navigator.of(context, rootNavigator: true).pop();
+
+          await Future<void>.delayed(const Duration(milliseconds: 300));
+
+          if (mounted) {
+            unawaited(
+              showFloatingFlushBar(
+                type: FlushBarType.warning,
+                message: "Invalid passphrase!",
+                context: context,
+              ),
+            );
+          }
+        }
+      }
+    } finally {
+      _lock = false;
+    }
+  }
+
+  @override
+  void initState() {
+    passwordController = TextEditingController();
+    passwordFocusNode = FocusNode();
+    super.initState();
+  }
+
+  @override
+  void dispose() {
+    passwordController.dispose();
+    passwordFocusNode.dispose();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return DesktopDialog(
+      maxWidth: 579,
+      maxHeight: double.infinity,
+      child: Column(
+        children: [
+          Row(
+            mainAxisAlignment: MainAxisAlignment.end,
+            children: [
+              DesktopDialogCloseButton(
+                onPressedOverride: Navigator.of(
+                  context,
+                  rootNavigator: true,
+                ).pop,
+              ),
+            ],
+          ),
+          const SizedBox(
+            height: 12,
+          ),
+          SvgPicture.asset(
+            Assets.svg.keys,
+            width: 100,
+            height: 58,
+          ),
+          const SizedBox(
+            height: 55,
+          ),
+          if (widget.title != null)
+            Text(
+              widget.title!,
+              style: STextStyles.desktopH2(context),
+            ),
+          if (widget.title != null)
+            const SizedBox(
+              height: 16,
+            ),
+          Text(
+            "Enter your password",
+            style: STextStyles.desktopTextMedium(context).copyWith(
+              color: Theme.of(context).extension<StackColors>()!.textDark3,
+            ),
+          ),
+          const SizedBox(
+            height: 24,
+          ),
+          Padding(
+            padding: const EdgeInsets.symmetric(
+              horizontal: 32,
+            ),
+            child: ClipRRect(
+              borderRadius: BorderRadius.circular(
+                Constants.size.circularBorderRadius,
+              ),
+              child: TextField(
+                key: const Key("enterPasswordUnlockWalletKeysDesktopFieldKey"),
+                focusNode: passwordFocusNode,
+                controller: passwordController,
+                style: STextStyles.desktopTextMedium(context).copyWith(
+                  height: 2,
+                ),
+                obscureText: hidePassword,
+                enableSuggestions: false,
+                autocorrect: false,
+                autofocus: true,
+                onSubmitted: (_) {
+                  if (continueEnabled) {
+                    _auth();
+                  }
+                },
+                decoration: standardInputDecoration(
+                  "Enter password",
+                  passwordFocusNode,
+                  context,
+                ).copyWith(
+                  suffixIcon: UnconstrainedBox(
+                    child: SizedBox(
+                      height: 70,
+                      child: Row(
+                        children: [
+                          GestureDetector(
+                            key: const Key(
+                              "enterUnlockWalletKeysDesktopFieldShowPasswordButtonKey",
+                            ),
+                            onTap: () async {
+                              setState(() {
+                                hidePassword = !hidePassword;
+                              });
+                            },
+                            child: Container(
+                              decoration: BoxDecoration(
+                                color: Colors.transparent,
+                                borderRadius: BorderRadius.circular(1000),
+                              ),
+                              height: 32,
+                              width: 32,
+                              child: Center(
+                                child: SvgPicture.asset(
+                                  hidePassword
+                                      ? Assets.svg.eye
+                                      : Assets.svg.eyeSlash,
+                                  color: Theme.of(context)
+                                      .extension<StackColors>()!
+                                      .textDark3,
+                                  width: 24,
+                                  height: 19,
+                                ),
+                              ),
+                            ),
+                          ),
+                          const SizedBox(
+                            width: 10,
+                          ),
+                        ],
+                      ),
+                    ),
+                  ),
+                ),
+                onChanged: (newValue) {
+                  setState(() {
+                    continueEnabled = newValue.isNotEmpty;
+                  });
+                },
+              ),
+            ),
+          ),
+          const SizedBox(
+            height: 55,
+          ),
+          Padding(
+            padding: const EdgeInsets.symmetric(
+              horizontal: 32,
+            ),
+            child: Row(
+              children: [
+                Expanded(
+                  child: SecondaryButton(
+                    label: "Cancel",
+                    onPressed: Navigator.of(
+                      context,
+                      rootNavigator: true,
+                    ).pop,
+                  ),
+                ),
+                const SizedBox(
+                  width: 16,
+                ),
+                Expanded(
+                  child: PrimaryButton(
+                    label: "Continue",
+                    enabled: continueEnabled,
+                    onPressed: continueEnabled ? _auth : null,
+                  ),
+                ),
+              ],
+            ),
+          ),
+          const SizedBox(
+            height: 32,
+          ),
+        ],
+      ),
+    );
+  }
+}
diff --git a/lib/widgets/address_private_key.dart b/lib/widgets/address_private_key.dart
index 8bcbd404d..324af585e 100644
--- a/lib/widgets/address_private_key.dart
+++ b/lib/widgets/address_private_key.dart
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 
 import '../models/isar/models/isar_models.dart';
+import '../pages/pinpad_views/pinpad_dialog.dart';
+import '../pages_desktop_specific/password/request_desktop_auth_dialog.dart';
 import '../providers/global/wallets_provider.dart';
 import '../utilities/show_loading.dart';
 import '../utilities/text_styles.dart';
@@ -41,21 +43,36 @@ class _AddressPrivateKeyState extends ConsumerState<AddressPrivateKey> {
     _lock = true;
 
     try {
-      final wallet =
-          ref.read(pWallets).getWallet(widget.walletId) as Bip39HDWallet;
-
-      _private = await showLoading(
-        whileFuture: wallet.getPrivateKeyWIF(widget.address),
+      final verified = await showDialog<String?>(
         context: context,
-        message: "Loading...",
-        delay: const Duration(milliseconds: 800),
-        rootNavigator: Util.isDesktop,
+        builder: (context) => Util.isDesktop
+            ? const RequestDesktopAuthDialog(title: "Show WIF private key")
+            : const PinpadDialog(
+                biometricsAuthenticationTitle: "Show WIF private key",
+                biometricsLocalizedReason:
+                    "Authenticate to show WIF private key",
+                biometricsCancelButtonString: "CANCEL",
+              ),
+        barrierDismissible: !Util.isDesktop,
       );
 
-      if (context.mounted) {
-        setState(() {});
-      } else {
-        _private == null;
+      if (verified == "verified success" && mounted) {
+        final wallet =
+            ref.read(pWallets).getWallet(widget.walletId) as Bip39HDWallet;
+
+        _private = await showLoading(
+          whileFuture: wallet.getPrivateKeyWIF(widget.address),
+          context: context,
+          message: "Loading...",
+          delay: const Duration(milliseconds: 800),
+          rootNavigator: Util.isDesktop,
+        );
+
+        if (context.mounted) {
+          setState(() {});
+        } else {
+          _private == null;
+        }
       }
     } finally {
       _lock = false;
@@ -71,7 +88,7 @@ class _AddressPrivateKeyState extends ConsumerState<AddressPrivateKey> {
         enabled: _private == null,
       ),
       title: Text(
-        "Private key",
+        "Private key (WIF)",
         style: STextStyles.itemSubtitle(context),
       ),
       detail: SelectableText(

From c4a765086d74546efe5c1e8b19c4c33260f47940 Mon Sep 17 00:00:00 2001
From: julian <julian@cypherstack.com>
Date: Thu, 4 Jul 2024 09:16:40 -0600
Subject: [PATCH 11/12] fix autopin pref load on app start

---
 lib/utilities/prefs.dart | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lib/utilities/prefs.dart b/lib/utilities/prefs.dart
index 535f1310e..78a461ec7 100644
--- a/lib/utilities/prefs.dart
+++ b/lib/utilities/prefs.dart
@@ -70,6 +70,7 @@ class Prefs extends ChangeNotifier {
       await _setMaxDecimals();
       _useTor = await _getUseTor();
       _fusionServerInfo = await _getFusionServerInfo();
+      _autoPin = await _getAutoPin();
 
       _initialized = true;
     }

From afab9e591884d57bb10b22078b0b8641bc9ee04c Mon Sep 17 00:00:00 2001
From: julian <julian@cypherstack.com>
Date: Thu, 4 Jul 2024 09:31:41 -0600
Subject: [PATCH 12/12] xpriv gui clean up

---
 .../wallet_backup_view.dart                   | 144 ++++--------
 .../wallet_backup_views/wallet_xprivs.dart    | 221 ++++++++++++++++++
 .../unlock_wallet_keys_desktop.dart           |  16 ++
 .../wallet_keys_desktop_popup.dart            | 103 +-------
 lib/route_generator.dart                      |  41 +++-
 .../extended_keys_interface.dart              |  10 +-
 lib/widgets/detail_item.dart                  |  34 ++-
 lib/widgets/qr.dart                           |  26 ++-
 8 files changed, 380 insertions(+), 215 deletions(-)
 create mode 100644 lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_xprivs.dart

diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart
index d2ec44ca6..9c1488fcc 100644
--- a/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart
+++ b/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart
@@ -36,6 +36,7 @@ import '../../../../widgets/rounded_white_container.dart';
 import '../../../../widgets/stack_dialog.dart';
 import '../../../add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart';
 import '../../../wallet_view/transaction_views/transaction_details_view.dart';
+import 'wallet_xprivs.dart';
 
 class WalletBackupView extends ConsumerWidget {
   const WalletBackupView({
@@ -96,35 +97,6 @@ class WalletBackupView extends ConsumerWidget {
                     );
                   },
                 ),
-
-                // child: AspectRatio(
-                //   aspectRatio: 1,
-                //   child: AppBarIconButton(
-                //     color:
-                //         Theme.of(context).extension<StackColors>()!.background,
-                //     shadows: const [],
-                //     icon: SvgPicture.asset(
-                //       Assets.svg.copy,
-                //       width: 20,
-                //       height: 20,
-                //       color: Theme.of(context)
-                //           .extension<StackColors>()!
-                //           .topNavIconPrimary,
-                //     ),
-                //     onPressed: () async {
-                //       await clipboardInterface
-                //           .setData(ClipboardData(text: mnemonic.join(" ")));
-                //       unawaited(
-                //         showFloatingFlushBar(
-                //           type: FlushBarType.info,
-                //           message: "Copied to clipboard",
-                //           iconAsset: Assets.svg.copy,
-                //           context: context,
-                //         ),
-                //       );
-                //     },
-                //   ),
-                // ),
               ),
             if (!frost && xprivData == null)
               Padding(
@@ -146,14 +118,16 @@ class WalletBackupView extends ConsumerWidget {
                     onPressed: () async {
                       await clipboardInterface
                           .setData(ClipboardData(text: mnemonic.join(" ")));
-                      unawaited(
-                        showFloatingFlushBar(
-                          type: FlushBarType.info,
-                          message: "Copied to clipboard",
-                          iconAsset: Assets.svg.copy,
-                          context: context,
-                        ),
-                      );
+                      if (context.mounted) {
+                        unawaited(
+                          showFloatingFlushBar(
+                            type: FlushBarType.info,
+                            message: "Copied to clipboard",
+                            iconAsset: Assets.svg.copy,
+                            context: context,
+                          ),
+                        );
+                      }
                     },
                   ),
                 ),
@@ -177,45 +151,6 @@ class WalletBackupView extends ConsumerWidget {
   }
 }
 
-class _XPrivs extends StatelessWidget {
-  const _XPrivs({super.key, required this.walletId, required this.xprivData});
-
-  final String walletId;
-  final ({List<XPriv> xprivs, String fingerprint}) xprivData;
-
-  @override
-  Widget build(BuildContext context) {
-    return Column(
-      mainAxisSize: MainAxisSize.min,
-      children: [
-        DetailItem(
-          title: "Master fingerprint",
-          detail: xprivData.fingerprint,
-          horizontal: true,
-        ),
-        const SizedBox(
-          height: 16,
-        ),
-        ...xprivData.xprivs.map(
-          (e) => Padding(
-            padding: const EdgeInsets.only(
-              bottom: 16,
-            ),
-            child: Column(
-              children: [
-                DetailItem(
-                  title: e.path,
-                  detail: e.xpriv,
-                ),
-              ],
-            ),
-          ),
-        ),
-      ],
-    );
-  }
-}
-
 class _Mnemonic extends ConsumerWidget {
   const _Mnemonic({super.key, required this.walletId, required this.mnemonic});
 
@@ -514,25 +449,46 @@ class MobileXPrivsView extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return Scaffold(
-      backgroundColor: Theme.of(context).extension<StackColors>()!.background,
-      appBar: AppBar(
-        leading: AppBarBackButton(
-          onPressed: () {
-            Navigator.of(context).pop();
-          },
+    return Background(
+      child: Scaffold(
+        backgroundColor: Theme.of(context).extension<StackColors>()!.background,
+        appBar: AppBar(
+          leading: AppBarBackButton(
+            onPressed: () {
+              Navigator.of(context).pop();
+            },
+          ),
+          title: Text(
+            "Wallet xpriv(s)",
+            style: STextStyles.navBarTitle(context),
+          ),
         ),
-        title: Text(
-          "Wallet xpriv(s)",
-          style: STextStyles.navBarTitle(context),
-        ),
-      ),
-      body: Padding(
-        padding: const EdgeInsets.symmetric(horizontal: 16),
-        child: SingleChildScrollView(
-          child: _XPrivs(
-            walletId: walletId,
-            xprivData: xprivData,
+        body: SafeArea(
+          child: Padding(
+            padding: const EdgeInsets.symmetric(horizontal: 16),
+            child: LayoutBuilder(
+              builder: (context, constraints) => SingleChildScrollView(
+                child: ConstrainedBox(
+                  constraints: BoxConstraints(minHeight: constraints.maxHeight),
+                  child: IntrinsicHeight(
+                    child: Column(
+                      mainAxisSize: MainAxisSize.min,
+                      children: [
+                        Expanded(
+                          child: WalletXPrivs(
+                            walletId: walletId,
+                            xprivData: xprivData,
+                          ),
+                        ),
+                        const SizedBox(
+                          height: 16,
+                        ),
+                      ],
+                    ),
+                  ),
+                ),
+              ),
+            ),
           ),
         ),
       ),
diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_xprivs.dart b/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_xprivs.dart
new file mode 100644
index 000000000..133dd95a1
--- /dev/null
+++ b/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_xprivs.dart
@@ -0,0 +1,221 @@
+/*
+ * This file is part of Stack Wallet.
+ *
+ * Copyright (c) 2023 Cypher Stack
+ * All Rights Reserved.
+ * The code is distributed under GPLv3 license, see LICENSE file for details.
+ * Generated by Cypher Stack on 2023-05-26
+ *
+ */
+
+import 'dart:async';
+
+import 'package:dropdown_button2/dropdown_button2.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_svg/svg.dart';
+
+import '../../../../notifications/show_flush_bar.dart';
+import '../../../../themes/stack_colors.dart';
+import '../../../../utilities/assets.dart';
+import '../../../../utilities/clipboard_interface.dart';
+import '../../../../utilities/constants.dart';
+import '../../../../utilities/text_styles.dart';
+import '../../../../utilities/util.dart';
+import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
+import '../../../../widgets/desktop/primary_button.dart';
+import '../../../../widgets/detail_item.dart';
+import '../../../../widgets/qr.dart';
+import '../../../../widgets/rounded_white_container.dart';
+
+class WalletXPrivs extends ConsumerStatefulWidget {
+  const WalletXPrivs({
+    super.key,
+    required this.xprivData,
+    required this.walletId,
+    this.clipboardInterface = const ClipboardWrapper(),
+  });
+
+  final ({List<XPriv> xprivs, String fingerprint}) xprivData;
+  final String walletId;
+  final ClipboardInterface clipboardInterface;
+
+  @override
+  ConsumerState<WalletXPrivs> createState() => WalletXPrivsState();
+}
+
+class WalletXPrivsState extends ConsumerState<WalletXPrivs> {
+  late String _currentDropDownValue;
+
+  String _current(String key) =>
+      widget.xprivData.xprivs.firstWhere((e) => e.path == key).xpriv;
+
+  Future<void> _copy() async {
+    await widget.clipboardInterface.setData(
+      ClipboardData(text: _current(_currentDropDownValue)),
+    );
+    if (mounted) {
+      unawaited(
+        showFloatingFlushBar(
+          type: FlushBarType.info,
+          message: "Copied to clipboard",
+          iconAsset: Assets.svg.copy,
+          context: context,
+        ),
+      );
+    }
+  }
+
+  @override
+  void initState() {
+    _currentDropDownValue = widget.xprivData.xprivs.first.path;
+    super.initState();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Padding(
+      padding: Util.isDesktop
+          ? const EdgeInsets.symmetric(horizontal: 20)
+          : EdgeInsets.zero,
+      child: Column(
+        mainAxisSize: Util.isDesktop ? MainAxisSize.min : MainAxisSize.max,
+        crossAxisAlignment: CrossAxisAlignment.center,
+        children: [
+          SizedBox(
+            height: Util.isDesktop ? 12 : 16,
+          ),
+          DetailItem(
+            title: "Master fingerprint",
+            detail: widget.xprivData.fingerprint,
+            horizontal: true,
+            borderColor: Util.isDesktop
+                ? Theme.of(context).extension<StackColors>()!.textFieldDefaultBG
+                : null,
+          ),
+          SizedBox(
+            height: Util.isDesktop ? 12 : 16,
+          ),
+          DetailItemBase(
+            horizontal: true,
+            borderColor: Util.isDesktop
+                ? Theme.of(context).extension<StackColors>()!.textFieldDefaultBG
+                : null,
+            title: Text(
+              "Derivation",
+              style: STextStyles.itemSubtitle(context),
+            ),
+            detail: SizedBox(
+              width: Util.isDesktop ? 200 : 170,
+              child: DropdownButtonHideUnderline(
+                child: DropdownButton2<String>(
+                  value: _currentDropDownValue,
+                  items: [
+                    ...widget.xprivData.xprivs.map(
+                      (e) => DropdownMenuItem(
+                        value: e.path,
+                        child: Text(
+                          e.path,
+                          style: STextStyles.w500_14(context),
+                        ),
+                      ),
+                    ),
+                  ],
+                  onChanged: (value) {
+                    if (value is String) {
+                      setState(() {
+                        _currentDropDownValue = value;
+                      });
+                    }
+                  },
+                  isExpanded: true,
+                  buttonStyleData: ButtonStyleData(
+                    decoration: BoxDecoration(
+                      color: Theme.of(context)
+                          .extension<StackColors>()!
+                          .textFieldDefaultBG,
+                      borderRadius: BorderRadius.circular(
+                        Constants.size.circularBorderRadius,
+                      ),
+                    ),
+                  ),
+                  iconStyleData: IconStyleData(
+                    icon: Padding(
+                      padding: const EdgeInsets.only(right: 10),
+                      child: SvgPicture.asset(
+                        Assets.svg.chevronDown,
+                        width: 12,
+                        height: 6,
+                        color: Theme.of(context)
+                            .extension<StackColors>()!
+                            .textFieldActiveSearchIconRight,
+                      ),
+                    ),
+                  ),
+                  dropdownStyleData: DropdownStyleData(
+                    offset: const Offset(0, -10),
+                    elevation: 0,
+                    decoration: BoxDecoration(
+                      color: Theme.of(context)
+                          .extension<StackColors>()!
+                          .textFieldDefaultBG,
+                      borderRadius: BorderRadius.circular(
+                        Constants.size.circularBorderRadius,
+                      ),
+                    ),
+                  ),
+                  menuItemStyleData: const MenuItemStyleData(
+                    padding: EdgeInsets.symmetric(
+                      horizontal: 16,
+                      vertical: 8,
+                    ),
+                  ),
+                ),
+              ),
+            ),
+          ),
+          SizedBox(
+            height: Util.isDesktop ? 12 : 16,
+          ),
+          QR(
+            data: _current(_currentDropDownValue),
+            size:
+                Util.isDesktop ? 256 : MediaQuery.of(context).size.width / 1.5,
+          ),
+          SizedBox(
+            height: Util.isDesktop ? 12 : 16,
+          ),
+          RoundedWhiteContainer(
+            borderColor: Util.isDesktop
+                ? Theme.of(context).extension<StackColors>()!.textFieldDefaultBG
+                : null,
+            child: SelectableText(
+              _current(_currentDropDownValue),
+              style: STextStyles.w500_14(context),
+            ),
+          ),
+          SizedBox(
+            height: Util.isDesktop ? 12 : 16,
+          ),
+          if (!Util.isDesktop) const Spacer(),
+          Row(
+            children: [
+              if (Util.isDesktop) const Spacer(),
+              if (Util.isDesktop)
+                const SizedBox(
+                  width: 16,
+                ),
+              Expanded(
+                child: PrimaryButton(
+                  label: "Copy",
+                  onPressed: _copy,
+                ),
+              ),
+            ],
+          ),
+        ],
+      ),
+    );
+  }
+}
diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart
index 267a4e6b6..6500b669e 100644
--- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart
+++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart
@@ -22,6 +22,7 @@ import '../../../../utilities/assets.dart';
 import '../../../../utilities/constants.dart';
 import '../../../../utilities/text_styles.dart';
 import '../../../../wallets/wallet/impl/bitcoin_frost_wallet.dart';
+import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
 import '../../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart';
 import '../../../../widgets/desktop/desktop_dialog.dart';
 import '../../../../widgets/desktop/desktop_dialog_close_button.dart';
@@ -84,6 +85,7 @@ class _UnlockWalletKeysDesktopState
       final wallet = ref.read(pWallets).getWallet(widget.walletId);
       ({String keys, String config})? frostData;
       List<String>? words;
+      ({List<XPriv> xprivs, String fingerprint})? xprivData;
 
       // TODO: [prio=low] handle wallets that don't have a mnemonic
       // All wallets currently are mnemonic based
@@ -100,6 +102,10 @@ class _UnlockWalletKeysDesktopState
         words = await wallet.getMnemonicAsWords();
       }
 
+      if (wallet is ExtendedKeysInterface) {
+        xprivData = await wallet.getXPrivs();
+      }
+
       if (mounted) {
         await Navigator.of(context).pushReplacementNamed(
           WalletKeysDesktopPopup.routeName,
@@ -107,6 +113,7 @@ class _UnlockWalletKeysDesktopState
             mnemonic: words ?? [],
             walletId: widget.walletId,
             frostData: frostData,
+            xprivData: xprivData,
           ),
         );
       }
@@ -320,6 +327,10 @@ class _UnlockWalletKeysDesktopState
 
                               ({String keys, String config})? frostData;
                               List<String>? words;
+                              ({
+                                List<XPriv> xprivs,
+                                String fingerprint
+                              })? xprivData;
 
                               final wallet =
                                   ref.read(pWallets).getWallet(widget.walletId);
@@ -339,6 +350,10 @@ class _UnlockWalletKeysDesktopState
                                 words = await wallet.getMnemonicAsWords();
                               }
 
+                              if (wallet is ExtendedKeysInterface) {
+                                xprivData = await wallet.getXPrivs();
+                              }
+
                               if (mounted) {
                                 await Navigator.of(context)
                                     .pushReplacementNamed(
@@ -347,6 +362,7 @@ class _UnlockWalletKeysDesktopState
                                     mnemonic: words ?? [],
                                     walletId: widget.walletId,
                                     frostData: frostData,
+                                    xprivData: xprivData,
                                   ),
                                 );
                               }
diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart
index aabba6c58..a3ec985da 100644
--- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart
+++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart
@@ -16,8 +16,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
 
 import '../../../../notifications/show_flush_bar.dart';
 import '../../../../pages/add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart';
+import '../../../../pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_xprivs.dart';
 import '../../../../pages/wallet_view/transaction_views/transaction_details_view.dart';
-import '../../../../providers/providers.dart';
 import '../../../../themes/stack_colors.dart';
 import '../../../../utilities/address_utils.dart';
 import '../../../../utilities/assets.dart';
@@ -29,8 +29,6 @@ import '../../../../widgets/desktop/desktop_dialog.dart';
 import '../../../../widgets/desktop/desktop_dialog_close_button.dart';
 import '../../../../widgets/desktop/primary_button.dart';
 import '../../../../widgets/desktop/secondary_button.dart';
-import '../../../../widgets/detail_item.dart';
-import '../../../../widgets/loading_indicator.dart';
 import '../../../../widgets/rounded_white_container.dart';
 import 'qr_code_desktop_popup_content.dart';
 
@@ -41,12 +39,14 @@ class WalletKeysDesktopPopup extends ConsumerWidget {
     required this.walletId,
     this.frostData,
     this.clipboardInterface = const ClipboardWrapper(),
+    this.xprivData,
   });
 
   final List<String> words;
   final String walletId;
   final ({String keys, String config})? frostData;
   final ClipboardInterface clipboardInterface;
+  final ({List<XPriv> xprivs, String fingerprint})? xprivData;
 
   static const String routeName = "walletKeysDesktopPopup";
 
@@ -77,7 +77,7 @@ class WalletKeysDesktopPopup extends ConsumerWidget {
             ],
           ),
           const SizedBox(
-            height: 28,
+            height: 6,
           ),
           frostData != null
               ? Column(
@@ -176,8 +176,7 @@ class WalletKeysDesktopPopup extends ConsumerWidget {
                     ),
                   ],
                 )
-              : (ref.watch(pWallets).getWallet(walletId)
-                      is ExtendedKeysInterface)
+              : xprivData != null
                   ? CustomTabView(
                       titles: const ["Mnemonic", "XPriv(s)"],
                       children: [
@@ -187,8 +186,8 @@ class WalletKeysDesktopPopup extends ConsumerWidget {
                             words: words,
                           ),
                         ),
-                        _MasterSeedPrivateKey(
-                          words: words,
+                        WalletXPrivs(
+                          xprivData: xprivData!,
                           walletId: walletId,
                         ),
                       ],
@@ -306,91 +305,3 @@ class _Mnemonic extends StatelessWidget {
     );
   }
 }
-
-class _MasterSeedPrivateKey extends ConsumerStatefulWidget {
-  const _MasterSeedPrivateKey({
-    super.key,
-    required this.words,
-    required this.walletId,
-    this.clipboardInterface = const ClipboardWrapper(),
-  });
-
-  final List<String> words;
-  final String walletId;
-  final ClipboardInterface clipboardInterface;
-
-  @override
-  ConsumerState<_MasterSeedPrivateKey> createState() =>
-      _MasterSeedPrivateKeyState();
-}
-
-class _MasterSeedPrivateKeyState extends ConsumerState<_MasterSeedPrivateKey> {
-  final controller = TextEditingController();
-
-  @override
-  void initState() {
-    super.initState();
-  }
-
-  @override
-  void dispose() {
-    controller.dispose();
-    super.dispose();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return Padding(
-      padding: const EdgeInsets.symmetric(horizontal: 20),
-      child: Column(
-        children: [
-          const SizedBox(
-            height: 12,
-          ),
-          FutureBuilder(
-            future: (ref.read(pWallets).getWallet(widget.walletId)
-                    as ExtendedKeysInterface)
-                .getXPrivs(),
-            builder: (context, snapshot) {
-              if (snapshot.connectionState == ConnectionState.done &&
-                  snapshot.hasData) {
-                return Column(
-                  mainAxisSize: MainAxisSize.min,
-                  children: [
-                    DetailItem(
-                      title: "Master fingerprint",
-                      detail: snapshot.data!.fingerprint,
-                    ),
-                    ...snapshot.data!.xprivs.map(
-                      (e) => Padding(
-                        padding: const EdgeInsets.symmetric(vertical: 8),
-                        child: Column(
-                          mainAxisSize: MainAxisSize.min,
-                          children: [
-                            DetailItem(
-                              title: "Derivation",
-                              detail: e.path,
-                            ),
-                            DetailItem(
-                              title: "xpriv",
-                              detail: e.xpriv,
-                            ),
-                          ],
-                        ),
-                      ),
-                    ),
-                  ],
-                );
-              } else {
-                return const LoadingIndicator(
-                  width: 100,
-                  height: 100,
-                );
-              }
-            },
-          ),
-        ],
-      ),
-    );
-  }
-}
diff --git a/lib/route_generator.dart b/lib/route_generator.dart
index 3d07a766b..fe7b36872 100644
--- a/lib/route_generator.dart
+++ b/lib/route_generator.dart
@@ -2365,15 +2365,38 @@ class RouteGenerator {
               name: settings.name,
             ),
           );
-          // return getRoute(
-          //   shouldUseMaterialRoute: useMaterialPageRoute,
-          //   builder: (_) => WalletKeysDesktopPopup(
-          //     words: args,
-          //   ),
-          //   settings: RouteSettings(
-          //     name: settings.name,
-          //   ),
-          // );
+        } else if (args is ({
+          List<String> mnemonic,
+          String walletId,
+          ({String keys, String config})? frostData,
+          ({List<XPriv> xprivs, String fingerprint})? xprivData,
+        })) {
+          return FadePageRoute(
+            WalletKeysDesktopPopup(
+              words: args.mnemonic,
+              walletId: args.walletId,
+              frostData: args.frostData,
+              xprivData: args.xprivData,
+            ),
+            RouteSettings(
+              name: settings.name,
+            ),
+          );
+        } else if (args is ({
+          List<String> mnemonic,
+          String walletId,
+          ({List<XPriv> xprivs, String fingerprint})? xprivData,
+        })) {
+          return FadePageRoute(
+            WalletKeysDesktopPopup(
+              words: args.mnemonic,
+              walletId: args.walletId,
+              xprivData: args.xprivData,
+            ),
+            RouteSettings(
+              name: settings.name,
+            ),
+          );
         }
         return _routeError("${settings.name} invalid args: ${args.toString()}");
 
diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart
index 5101b6a45..2e2836964 100644
--- a/lib/wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart
+++ b/lib/wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart
@@ -73,7 +73,15 @@ mixin ExtendedKeysInterface<T extends ElectrumXCurrencyInterface>
 
     return (
       fingerprint: fingerprint,
-      xprivs: await Future.wait(futures),
+      xprivs: [
+        (
+          path: "Master",
+          xpriv: master.encode(
+            cryptoCurrency.networkParams.privHDPrefix,
+          ),
+        ),
+        ...(await Future.wait(futures)),
+      ],
     );
   }
 }
diff --git a/lib/widgets/detail_item.dart b/lib/widgets/detail_item.dart
index c619c4192..13e8975d3 100644
--- a/lib/widgets/detail_item.dart
+++ b/lib/widgets/detail_item.dart
@@ -16,6 +16,8 @@ class DetailItem extends StatelessWidget {
     this.showEmptyDetail = true,
     this.horizontal = false,
     this.disableSelectableText = false,
+    this.borderColor,
+    this.expandDetail = false,
   });
 
   final String title;
@@ -25,6 +27,8 @@ class DetailItem extends StatelessWidget {
   final bool horizontal;
   final bool disableSelectableText;
   final Color? overrideDetailTextColor;
+  final Color? borderColor;
+  final bool expandDetail;
 
   @override
   Widget build(BuildContext context) {
@@ -45,6 +49,8 @@ class DetailItem extends StatelessWidget {
 
     return DetailItemBase(
       horizontal: horizontal,
+      borderColor: borderColor,
+      expandDetail: expandDetail,
       title: disableSelectableText
           ? Text(
               title,
@@ -74,22 +80,30 @@ class DetailItemBase extends StatelessWidget {
     required this.detail,
     this.button,
     this.horizontal = false,
+    this.borderColor,
+    this.expandDetail = false,
   });
 
   final Widget title;
   final Widget detail;
   final Widget? button;
   final bool horizontal;
+  final Color? borderColor;
+  final bool expandDetail;
 
   @override
   Widget build(BuildContext context) {
     return ConditionalParent(
-      condition: !Util.isDesktop,
+      condition: !Util.isDesktop || borderColor != null,
       builder: (child) => RoundedWhiteContainer(
+        padding: Util.isDesktop
+            ? const EdgeInsets.all(16)
+            : const EdgeInsets.all(12),
+        borderColor: borderColor,
         child: child,
       ),
       child: ConditionalParent(
-        condition: Util.isDesktop,
+        condition: Util.isDesktop && borderColor == null,
         builder: (child) => Padding(
           padding: const EdgeInsets.all(16),
           child: child,
@@ -99,7 +113,15 @@ class DetailItemBase extends StatelessWidget {
                 mainAxisAlignment: MainAxisAlignment.spaceBetween,
                 children: [
                   title,
-                  detail,
+                  if (expandDetail)
+                    const SizedBox(
+                      width: 16,
+                    ),
+                  ConditionalParent(
+                    condition: expandDetail,
+                    builder: (child) => Expanded(child: child),
+                    child: detail,
+                  ),
                 ],
               )
             : Column(
@@ -115,7 +137,11 @@ class DetailItemBase extends StatelessWidget {
                   const SizedBox(
                     height: 5,
                   ),
-                  detail,
+                  ConditionalParent(
+                    condition: expandDetail,
+                    builder: (child) => Expanded(child: child),
+                    child: detail,
+                  ),
                 ],
               ),
       ),
diff --git a/lib/widgets/qr.dart b/lib/widgets/qr.dart
index 39e874474..3b63538b1 100644
--- a/lib/widgets/qr.dart
+++ b/lib/widgets/qr.dart
@@ -11,17 +11,21 @@ class QR extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return QrImageView(
-      data: data,
-      size: size,
-      padding: padding ?? const EdgeInsets.all(10),
-      backgroundColor: Colors.white,
-      foregroundColor: Colors.black,
-      // backgroundColor:
-      // Theme.of(context).extension<StackColors>()!.background,
-      // foregroundColor: Theme.of(context)
-      //     .extension<StackColors>()!
-      //     .accentColorDark,
+    return SizedBox(
+      width: size,
+      height: size,
+      child: QrImageView(
+        data: data,
+        size: size,
+        padding: padding ?? const EdgeInsets.all(10),
+        backgroundColor: Colors.white,
+        foregroundColor: Colors.black,
+        // backgroundColor:
+        // Theme.of(context).extension<StackColors>()!.background,
+        // foregroundColor: Theme.of(context)
+        //     .extension<StackColors>()!
+        //     .accentColorDark,
+      ),
     );
   }
 }