diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 83de43779..4638e84dd 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -83,6 +83,12 @@ jobs:
           $secretFileNamecoinHash = Get-FileHash $secretFileNamecoin;
           Write-Output "Secret file $secretFileNamecoin has hash $($secretFileNamecoinHash.Hash)";
           
+          $secretFileParticl = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "test/services/coins/particl/particl_wallet_test_parameters.dart";
+          $encodedBytes = [System.Convert]::FromBase64String($env:PARTICL_TEST);
+          Set-Content $secretFileParticl -Value $encodedBytes -AsByteStream;
+          $secretFileParticlHash = Get-FileHash $secretFileParticl;
+          Write-Output "Secret file $secretFileParticl has hash $($secretFileParticlHash.Hash)";
+          
         shell: pwsh
         env:
           CHANGE_NOW: ${{ secrets.CHANGE_NOW }}
@@ -91,6 +97,7 @@ jobs:
           FIRO_TEST: ${{ secrets.FIRO_TEST }}
           BITCOINCASH_TEST: ${{ secrets.BITCOINCASH_TEST }}
           NAMECOIN_TEST: ${{ secrets.NAMECOIN_TEST }}
+          PARTICL_TEST: ${{ secrets.PARTICL_TEST }}
 #      - name: Analyze
 #        run: flutter analyze
       - name: Test
@@ -109,6 +116,7 @@ jobs:
           $secretFileFiro = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "test/services/coins/firo/firo_wallet_test_parameters.dart";
           $secretFileBitcoinCash = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart";
           $secretFileNamecoin = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "test/services/coins/namecoin/namecoin_wallet_test_parameters.dart";
+          $secretFileParticl = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "test/services/coins/particl/particl_wallet_test_parameters.dart";
 
           Remove-Item -Path $secretFileExchange;
           Remove-Item -Path $secretFileBitcoin;
@@ -116,5 +124,6 @@ jobs:
           Remove-Item -Path $secretFileFiro;
           Remove-Item -Path $secretFileBitcoinCash;
           Remove-Item -Path $secretFileNamecoin;
+          Remove-Item -Path $secretFileParticl;
         shell: pwsh
         if: always()
diff --git a/.gitignore b/.gitignore
index 323aac218..a36824135 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,7 +38,9 @@ test/services/coins/bitcoin/bitcoin_wallet_test_parameters.dart
 test/services/coins/firo/firo_wallet_test_parameters.dart
 test/services/coins/dogecoin/dogecoin_wallet_test_parameters.dart
 test/services/coins/namecoin/namecoin_wallet_test_parameters.dart
+test/services/coins/namecoin/namecoin_wallet_test_parameters.dart.txt # Legacy
 test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart
+test/services/coins/particl/particl_wallet_test_parameters.dart
 /integration_test/private.dart
 
 # Exceptions to above rules.
@@ -48,5 +50,3 @@ test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart
 coverage
 scripts/**/build
 /lib/external_api_keys.dart
-/test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart
-/test/services/coins/namecoin/namecoin_wallet_test_parameters.dart.txt
diff --git a/README.md b/README.md
index 37321c848..9d0f6ff6d 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ Highlights include:
 
 The following prerequisities can be installed with the setup script `scripts/setup.sh` or manually as described below:
 
-- Flutter 3.0.5 [(install manually or with git, do not install with snap)](https://docs.flutter.dev/get-started/install)
+- Flutter 3.3.4 [(install manually or with git, do not install with snap)](https://docs.flutter.dev/get-started/install)
 - Dart SDK Requirement (>=2.17.0, up until <3.0.0)
 - Android setup ([Android Studio](https://developer.android.com/studio) and subsequent dependencies)
 
diff --git a/assets/images/particl.png b/assets/images/particl.png
new file mode 100644
index 000000000..ef5939f47
Binary files /dev/null and b/assets/images/particl.png differ
diff --git a/assets/svg/coin_icons/Particl.svg b/assets/svg/coin_icons/Particl.svg
new file mode 100644
index 000000000..3f8a920ab
--- /dev/null
+++ b/assets/svg/coin_icons/Particl.svg
@@ -0,0 +1 @@
+<svg id="Particl_-_logo_white_text" data-name="Particl - logo white text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 228.62 228.33"><title>particl-part-logo</title><path d="M227.64,47.53a57.36,57.36,0,0,0-20.55-34.84A56.67,56.67,0,0,0,171.31,0Q113.76,0,56.16,0c-20,.23-39,11.82-48.7,29.24C2,38.62,0,49.13,0,59.85q0,52.43,0,104.88c-.07,8.13.29,15.94,2.83,23.74A58,58,0,0,0,42,226.26c-5.51-4.39-7.95-10.5-9.74-17.11-3.71-14.5-4-29.56-3.4-44.42-.06-35,0-69.93,0-104.88.05-6,.5-11.67,3.58-17a27.31,27.31,0,0,1,23.79-14q54.71,0,109.45,0c5.51,0,10.52-.4,15.78,1.59A27.2,27.2,0,0,1,199,50.14c1.08,5.29.79,11.16.81,16.55v102.6c0,5-.56,10-2.73,14.57-4.47,9.35-14.14,15.84-24.61,15.65-28.87,0-57.77,0-86.63,0-.06-9.41,0-18.82,0-28.24q35.36,0,70.69,0c5.07.28,10.91-2.29,13.24-7a15.68,15.68,0,0,0,1.6-7.58q0-42.78,0-85.53a13.21,13.21,0,0,0-7-12.5c-3.9-2.26-8.07-1.68-12.42-1.76-27,.07-54,0-80.94.05A13.87,13.87,0,0,0,57.08,71.24c-.11,41,0,82.15,0,123.14.21,8.14-.05,15.85,4.67,22.9,3.58,5.82,9.63,8.78,16.14,10.07,7.7,1.24,15.06.94,22.79,1q35.36,0,70.68,0A57.17,57.17,0,0,0,204.07,218c10.32-7.61,18.32-18.09,22-30.45,2.54-8.36,2.5-16.48,2.54-25.11q0-49,0-98A114.68,114.68,0,0,0,227.64,47.53Zm-85.28,95H85.83V85.77h56.53Z" style="fill:#45d492"/></svg>
\ No newline at end of file
diff --git a/dockerfile.linux b/dockerfile.linux
new file mode 100644
index 000000000..0853741d9
--- /dev/null
+++ b/dockerfile.linux
@@ -0,0 +1,17 @@
+FROM ubuntu:20.04 as base
+COPY . /stack_wallet
+WORKDIR /stack_wallet/scripts/linux
+RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y git=1:2.25.1-1ubuntu3.6 make=4.2.1-1.2 curl=7.68.0-1ubuntu2.14 cargo=0.62.0ubuntu0libgit2-0ubuntu0.20.04.1 \
+    file=1:5.38-4 ca-certificates=20211016~20.04.1 cmake=3.16.3-1ubuntu1.20.04.1 cmake-data=3.16.3-1ubuntu1.20.04.1 g++=4:9.3.0-1ubuntu2 libgmp-dev=2:6.2.0+dfsg-4ubuntu0.1 libssl-dev=1.1.1f-1ubuntu2.16 libclang-dev=1:10.0-50~exp1 \
+    unzip=6.0-25ubuntu1.1 python3=3.8.2-0ubuntu2 pkg-config=0.29.1-0ubuntu4 libglib2.0-dev=2.64.6-1~ubuntu20.04.4 libgcrypt20-dev=1.8.5-5ubuntu1.1 gettext-base=0.19.8.1-10build1 libgirepository1.0-dev=1.64.1-1~ubuntu20.04.1 \
+    valac=0.48.6-0ubuntu1 xsltproc=1.1.34-4ubuntu0.20.04.1 docbook-xsl=1.79.1+dfsg-2 python3-pip=20.0.2-5ubuntu1.6 ninja-build=1.10.0-1build1 clang=1:10.0-50~exp1 libgtk-3-dev=3.24.20-0ubuntu1.1 \
+    libunbound-dev=1.9.4-2ubuntu1.4 libzmq3-dev=4.3.2-2ubuntu1 libtool=2.4.6-14 autoconf=2.69-11.1 automake=1:1.16.1-4ubuntu6 bison=2:3.5.1+dfsg-1 \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/* \
+ && pip3 install --upgrade meson==0.64.1 markdown==3.4.1 markupsafe==2.1.1 jinja2==3.1.2 pygments==2.13.0 toml==0.10.2 typogrify==2.0.7 tomli==2.0.1 && cd .. && ./prebuild.sh && cd linux && ./build_all.sh
+WORKDIR /
+RUN git clone https://github.com/flutter/flutter.git -b 3.3.4
+ENV PATH "$PATH:/flutter/bin"
+WORKDIR /stack_wallet
+RUN flutter pub get Linux && flutter build linux
+ENTRYPOINT ["/bin/bash"]
diff --git a/lib/models/paymint/transactions_model.dart b/lib/models/paymint/transactions_model.dart
index 382459922..06a21e16a 100644
--- a/lib/models/paymint/transactions_model.dart
+++ b/lib/models/paymint/transactions_model.dart
@@ -380,19 +380,45 @@ class Output {
 
   factory Output.fromJson(Map<String, dynamic> json) {
     // TODO determine if any of this code is needed.
-    final address = json["scriptPubKey"]["addresses"] == null
-        ? json['scriptPubKey']['type'] as String
-        : json["scriptPubKey"]["addresses"][0] as String;
-    return Output(
-      scriptpubkey: json['scriptPubKey']['hex'] as String?,
-      scriptpubkeyAsm: json['scriptPubKey']['asm'] as String?,
-      scriptpubkeyType: json['scriptPubKey']['type'] as String?,
-      scriptpubkeyAddress: address,
-      value: (Decimal.parse(json["value"].toString()) *
-              Decimal.fromInt(Constants.satsPerCoin(Coin
-                  .firo))) // dirty hack but we need 8 decimal places here to keep consistent data structure
-          .toBigInt()
-          .toInt(),
-    );
+    // Particl has different tx types that need to be detected and handled here
+    if (json.containsKey('scriptPubKey') as bool) {
+      // output is transparent
+      final address = json["scriptPubKey"]["addresses"] == null
+          ? json['scriptPubKey']['type'] as String
+          : json["scriptPubKey"]["addresses"][0] as String;
+      return Output(
+        scriptpubkey: json['scriptPubKey']['hex'] as String?,
+        scriptpubkeyAsm: json['scriptPubKey']['asm'] as String?,
+        scriptpubkeyType: json['scriptPubKey']['type'] as String?,
+        scriptpubkeyAddress: address,
+        value: (Decimal.parse(json["value"].toString()) *
+                Decimal.fromInt(Constants.satsPerCoin(Coin
+                    .firo))) // dirty hack but we need 8 decimal places here to keep consistent data structure
+            .toBigInt()
+            .toInt(),
+      );
+    } /* else if (json.containsKey('ct_fee') as bool) {
+      // or type: data
+      // output is blinded (CT)
+    } else if (json.containsKey('rangeproof') as bool) {
+      // or valueCommitment or type: anon
+      // output is private (RingCT)
+    } */
+    else {
+      // TODO detect staking
+      // TODO handle CT, RingCT, and staking accordingly
+      // print("transaction not supported: ${json}");
+      return Output(
+          // Return output object with null values; allows wallet history to be built
+          scriptpubkey: "",
+          scriptpubkeyAsm: "",
+          scriptpubkeyType: "",
+          scriptpubkeyAddress: "",
+          value: (Decimal.parse(0.toString()) *
+                  Decimal.fromInt(Constants.satsPerCoin(Coin
+                      .firo))) // dirty hack but we need 8 decimal places here to keep consistent data structure
+              .toBigInt()
+              .toInt());
+    }
   }
 }
diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart
index 7c18fffcc..e29b0888f 100644
--- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart
+++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart
@@ -135,6 +135,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
       case Coin.dogecoin:
       case Coin.firo:
       case Coin.namecoin:
+      case Coin.particl:
       case Coin.bitcoinTestNet:
       case Coin.litecoinTestNet:
       case Coin.bitcoincashTestnet:
@@ -681,6 +682,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
       case Coin.firo:
       case Coin.namecoin:
       case Coin.bitcoincash:
+      case Coin.particl:
       case Coin.bitcoinTestNet:
       case Coin.litecoinTestNet:
       case Coin.bitcoincashTestnet:
diff --git a/lib/services/coins/coin_service.dart b/lib/services/coins/coin_service.dart
index 16015ea0c..41dbed42b 100644
--- a/lib/services/coins/coin_service.dart
+++ b/lib/services/coins/coin_service.dart
@@ -10,6 +10,7 @@ import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
 import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
 import 'package:stackwallet/services/coins/monero/monero_wallet.dart';
 import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart';
+import 'package:stackwallet/services/coins/particl/particl_wallet.dart';
 import 'package:stackwallet/services/coins/wownero/wownero_wallet.dart';
 import 'package:stackwallet/services/transaction_notification_tracker.dart';
 import 'package:stackwallet/utilities/enums/coin_enum.dart';
@@ -181,6 +182,16 @@ abstract class CoinServiceAPI {
           // tracker: tracker,
         );
 
+      case Coin.particl:
+        return ParticlWallet(
+            walletId: walletId,
+            walletName: walletName,
+            coin: coin,
+            secureStore: secureStorageInterface,
+            client: client,
+            cachedClient: cachedClient,
+            tracker: tracker);
+
       case Coin.wownero:
         return WowneroWallet(
           walletId: walletId,
diff --git a/lib/services/coins/particl/particl_wallet.dart b/lib/services/coins/particl/particl_wallet.dart
new file mode 100644
index 000000000..0249fb205
--- /dev/null
+++ b/lib/services/coins/particl/particl_wallet.dart
@@ -0,0 +1,3538 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'package:bech32/bech32.dart';
+import 'package:bip32/bip32.dart' as bip32;
+import 'package:bip39/bip39.dart' as bip39;
+import 'package:bitcoindart/bitcoindart.dart';
+import 'package:bs58check/bs58check.dart' as bs58check;
+import 'package:crypto/crypto.dart';
+import 'package:decimal/decimal.dart';
+import 'package:devicelocale/devicelocale.dart';
+import 'package:flutter/foundation.dart';
+import 'package:http/http.dart';
+import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart';
+import 'package:stackwallet/electrumx_rpc/electrumx.dart';
+import 'package:stackwallet/hive/db.dart';
+import 'package:stackwallet/models/models.dart' as models;
+import 'package:stackwallet/models/paymint/fee_object_model.dart';
+import 'package:stackwallet/models/paymint/transactions_model.dart';
+import 'package:stackwallet/models/paymint/utxo_model.dart';
+import 'package:stackwallet/services/coins/coin_service.dart';
+import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
+import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart';
+import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
+import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
+import 'package:stackwallet/services/event_bus/global_event_bus.dart';
+import 'package:stackwallet/services/node_service.dart';
+import 'package:stackwallet/services/notifications_api.dart';
+import 'package:stackwallet/services/price.dart';
+import 'package:stackwallet/services/transaction_notification_tracker.dart';
+import 'package:stackwallet/utilities/assets.dart';
+import 'package:stackwallet/utilities/constants.dart';
+import 'package:stackwallet/utilities/default_nodes.dart';
+import 'package:stackwallet/utilities/enums/coin_enum.dart';
+import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
+import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
+import 'package:stackwallet/utilities/format.dart';
+import 'package:stackwallet/utilities/logger.dart';
+import 'package:stackwallet/utilities/prefs.dart';
+import 'package:tuple/tuple.dart';
+import 'package:uuid/uuid.dart';
+
+const int MINIMUM_CONFIRMATIONS = 1;
+const int DUST_LIMIT = 294;
+
+const String GENESIS_HASH_MAINNET =
+    "0000ee0784c195317ac95623e22fddb8c7b8825dc3998e0bb924d66866eccf4c";
+const String GENESIS_HASH_TESTNET =
+    "0000594ada5310b367443ee0afd4fa3d0bbd5850ea4e33cdc7d6a904a7ec7c90";
+
+enum DerivePathType { bip44, bip84 }
+
+bip32.BIP32 getBip32Node(
+  int chain,
+  int index,
+  String mnemonic,
+  NetworkType network,
+  DerivePathType derivePathType,
+) {
+  final root = getBip32Root(mnemonic, network);
+
+  final node = getBip32NodeFromRoot(chain, index, root, derivePathType);
+  return node;
+}
+
+/// wrapper for compute()
+bip32.BIP32 getBip32NodeWrapper(
+  Tuple5<int, int, String, NetworkType, DerivePathType> args,
+) {
+  return getBip32Node(
+    args.item1,
+    args.item2,
+    args.item3,
+    args.item4,
+    args.item5,
+  );
+}
+
+bip32.BIP32 getBip32NodeFromRoot(
+  int chain,
+  int index,
+  bip32.BIP32 root,
+  DerivePathType derivePathType,
+) {
+  String coinType;
+  switch (root.network.wif) {
+    case 0x6c: // PART mainnet wif
+      coinType = "44"; // PART mainnet
+      break;
+    default:
+      throw Exception("Invalid Particl network type used!");
+  }
+  switch (derivePathType) {
+    case DerivePathType.bip44:
+      return root.derivePath("m/44'/$coinType'/0'/$chain/$index");
+    case DerivePathType.bip84:
+      return root.derivePath("m/84'/$coinType'/0'/$chain/$index");
+    default:
+      throw Exception("DerivePathType must not be null.");
+  }
+}
+
+/// wrapper for compute()
+bip32.BIP32 getBip32NodeFromRootWrapper(
+  Tuple4<int, int, bip32.BIP32, DerivePathType> args,
+) {
+  return getBip32NodeFromRoot(
+    args.item1,
+    args.item2,
+    args.item3,
+    args.item4,
+  );
+}
+
+bip32.BIP32 getBip32Root(String mnemonic, NetworkType network) {
+  final seed = bip39.mnemonicToSeed(mnemonic);
+  final networkType = bip32.NetworkType(
+    wif: network.wif,
+    bip32: bip32.Bip32Type(
+      public: network.bip32.public,
+      private: network.bip32.private,
+    ),
+  );
+
+  final root = bip32.BIP32.fromSeed(seed, networkType);
+  return root;
+}
+
+/// wrapper for compute()
+bip32.BIP32 getBip32RootWrapper(Tuple2<String, NetworkType> args) {
+  return getBip32Root(args.item1, args.item2);
+}
+
+class ParticlWallet extends CoinServiceAPI {
+  static const integrationTestFlag =
+      bool.fromEnvironment("IS_INTEGRATION_TEST");
+
+  final _prefs = Prefs.instance;
+
+  Timer? timer;
+  late Coin _coin;
+
+  late final TransactionNotificationTracker txTracker;
+
+  NetworkType get _network {
+    switch (coin) {
+      case Coin.particl:
+        return particl;
+      default:
+        throw Exception("Invalid network type!");
+    }
+  }
+
+  List<UtxoObject> outputsList = [];
+
+  @override
+  set isFavorite(bool markFavorite) {
+    DB.instance.put<dynamic>(
+        boxName: walletId, key: "isFavorite", value: markFavorite);
+  }
+
+  @override
+  bool get isFavorite {
+    try {
+      return DB.instance.get<dynamic>(boxName: walletId, key: "isFavorite")
+          as bool;
+    } catch (e, s) {
+      Logging.instance
+          .log("isFavorite fetch failed: $e\n$s", level: LogLevel.Error);
+      rethrow;
+    }
+  }
+
+  @override
+  Coin get coin => _coin;
+
+  @override
+  Future<List<String>> get allOwnAddresses =>
+      _allOwnAddresses ??= _fetchAllOwnAddresses();
+  Future<List<String>>? _allOwnAddresses;
+
+  Future<UtxoData>? _utxoData;
+  Future<UtxoData> get utxoData => _utxoData ??= _fetchUtxoData();
+
+  @override
+  Future<List<UtxoObject>> get unspentOutputs async =>
+      (await utxoData).unspentOutputArray;
+
+  @override
+  Future<Decimal> get availableBalance async {
+    final data = await utxoData;
+    return Format.satoshisToAmount(
+        data.satoshiBalance - data.satoshiBalanceUnconfirmed,
+        coin: coin);
+  }
+
+  @override
+  Future<Decimal> get pendingBalance async {
+    final data = await utxoData;
+    return Format.satoshisToAmount(data.satoshiBalanceUnconfirmed, coin: coin);
+  }
+
+  @override
+  Future<Decimal> get balanceMinusMaxFee async =>
+      (await availableBalance) -
+      (Decimal.fromInt((await maxFee)) /
+              Decimal.fromInt(Constants.satsPerCoin(coin)))
+          .toDecimal();
+
+  @override
+  Future<Decimal> get totalBalance async {
+    if (!isActive) {
+      final totalBalance = DB.instance
+          .get<dynamic>(boxName: walletId, key: 'totalBalance') as int?;
+      if (totalBalance == null) {
+        final data = await utxoData;
+        return Format.satoshisToAmount(data.satoshiBalance, coin: coin);
+      } else {
+        return Format.satoshisToAmount(totalBalance, coin: coin);
+      }
+    }
+    final data = await utxoData;
+    return Format.satoshisToAmount(data.satoshiBalance, coin: coin);
+  }
+
+  @override
+  Future<String> get currentReceivingAddress => _currentReceivingAddress ??=
+      _getCurrentAddressForChain(0, DerivePathType.bip84);
+  Future<String>? _currentReceivingAddress;
+
+  Future<String> get currentLegacyReceivingAddress =>
+      _currentReceivingAddressP2PKH ??=
+          _getCurrentAddressForChain(0, DerivePathType.bip44);
+  Future<String>? _currentReceivingAddressP2PKH;
+
+  @override
+  Future<void> exit() async {
+    _hasCalledExit = true;
+    timer?.cancel();
+    timer = null;
+    stopNetworkAlivePinging();
+  }
+
+  bool _hasCalledExit = false;
+
+  @override
+  bool get hasCalledExit => _hasCalledExit;
+
+  @override
+  Future<FeeObject> get fees => _feeObject ??= _getFees();
+  Future<FeeObject>? _feeObject;
+
+  @override
+  Future<int> get maxFee async {
+    final fee = (await fees).fast as String;
+    final satsFee =
+        Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin(coin));
+    return satsFee.floor().toBigInt().toInt();
+  }
+
+  @override
+  Future<List<String>> get mnemonic => _getMnemonicList();
+
+  Future<int> get chainHeight async {
+    try {
+      final result = await _electrumXClient.getBlockHeadTip();
+      return result["height"] as int;
+    } catch (e, s) {
+      Logging.instance.log("Exception caught in chainHeight: $e\n$s",
+          level: LogLevel.Error);
+      return -1;
+    }
+  }
+
+  int get storedChainHeight {
+    final storedHeight = DB.instance
+        .get<dynamic>(boxName: walletId, key: "storedChainHeight") as int?;
+    return storedHeight ?? 0;
+  }
+
+  Future<void> updateStoredChainHeight({required int newHeight}) async {
+    await DB.instance.put<dynamic>(
+        boxName: walletId, key: "storedChainHeight", value: newHeight);
+  }
+
+  DerivePathType addressType({required String address}) {
+    Uint8List? decodeBase58;
+    Segwit? decodeBech32;
+    try {
+      decodeBase58 = bs58check.decode(address);
+    } catch (err) {
+      // Base58check decode fail
+    }
+
+    // return DerivePathType.bip84;
+    if (decodeBase58 != null) {
+      if (decodeBase58[0] == _network.pubKeyHash) {
+        // P2PKH
+        return DerivePathType.bip44;
+      }
+      throw ArgumentError('Invalid version or Network mismatch');
+    } else {
+      try {
+        decodeBech32 = segwit.decode(address, particl.bech32!);
+      } catch (err) {
+        // Bech32 decode fail
+      }
+      if (_network.bech32 != decodeBech32!.hrp) {
+        throw ArgumentError('Invalid prefix or Network mismatch');
+      }
+      if (decodeBech32.version != 0) {
+        throw ArgumentError('Invalid address version');
+      }
+      // P2WPKH
+      return DerivePathType.bip84;
+    }
+  }
+
+  bool longMutex = false;
+
+  @override
+  Future<void> recoverFromMnemonic({
+    required String mnemonic,
+    required int maxUnusedAddressGap,
+    required int maxNumberOfIndexesToCheck,
+    required int height,
+  }) async {
+    longMutex = true;
+    final start = DateTime.now();
+    try {
+      Logging.instance.log("IS_INTEGRATION_TEST: $integrationTestFlag",
+          level: LogLevel.Info);
+      if (!integrationTestFlag) {
+        final features = await electrumXClient.getServerFeatures();
+        Logging.instance.log("features: $features", level: LogLevel.Info);
+        switch (coin) {
+          case Coin.particl:
+            if (features['genesis_hash'] != GENESIS_HASH_MAINNET) {
+              throw Exception("genesis hash does not match main net!");
+            }
+            break;
+          default:
+            throw Exception(
+                "Attempted to generate a ParticlWallet using a non particl coin type: ${coin.name}");
+        }
+        // if (_networkType == BasicNetworkType.main) {
+        //   if (features['genesis_hash'] != GENESIS_HASH_MAINNET) {
+        //     throw Exception("genesis hash does not match main net!");
+        //   }
+        // } else if (_networkType == BasicNetworkType.test) {
+        //   if (features['genesis_hash'] != GENESIS_HASH_TESTNET) {
+        //     throw Exception("genesis hash does not match test net!");
+        //   }
+        // }
+      }
+      // check to make sure we aren't overwriting a mnemonic
+      // this should never fail
+      if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) {
+        longMutex = false;
+        throw Exception("Attempted to overwrite mnemonic on restore!");
+      }
+      await _secureStore.write(
+          key: '${_walletId}_mnemonic', value: mnemonic.trim());
+      await _recoverWalletFromBIP32SeedPhrase(
+        mnemonic: mnemonic.trim(),
+        maxUnusedAddressGap: maxUnusedAddressGap,
+        maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck,
+      );
+    } catch (e, s) {
+      Logging.instance.log(
+          "Exception rethrown from recoverFromMnemonic(): $e\n$s",
+          level: LogLevel.Error);
+      longMutex = false;
+      rethrow;
+    }
+    longMutex = false;
+
+    final end = DateTime.now();
+    Logging.instance.log(
+        "$walletName recovery time: ${end.difference(start).inMilliseconds} millis",
+        level: LogLevel.Info);
+  }
+
+  Future<Map<String, dynamic>> _checkGaps(
+      int maxNumberOfIndexesToCheck,
+      int maxUnusedAddressGap,
+      int txCountBatchSize,
+      bip32.BIP32 root,
+      DerivePathType type,
+      int account) async {
+    List<String> addressArray = [];
+    int returningIndex = -1;
+    Map<String, Map<String, String>> derivations = {};
+    int gapCounter = 0;
+    for (int index = 0;
+        index < maxNumberOfIndexesToCheck && gapCounter < maxUnusedAddressGap;
+        index += txCountBatchSize) {
+      List<String> iterationsAddressArray = [];
+      Logging.instance.log(
+          "index: $index, \t GapCounter $account ${type.name}: $gapCounter",
+          level: LogLevel.Info);
+
+      final _id = "k_$index";
+      Map<String, String> txCountCallArgs = {};
+      final Map<String, dynamic> receivingNodes = {};
+
+      for (int j = 0; j < txCountBatchSize; j++) {
+        final node = await compute(
+          getBip32NodeFromRootWrapper,
+          Tuple4(
+            account,
+            index + j,
+            root,
+            type,
+          ),
+        );
+        String? address;
+        switch (type) {
+          case DerivePathType.bip44:
+            address = P2PKH(
+                    data: PaymentData(pubkey: node.publicKey),
+                    network: _network)
+                .data
+                .address!;
+            break;
+          case DerivePathType.bip84:
+            address = P2WPKH(
+                    network: _network,
+                    data: PaymentData(pubkey: node.publicKey))
+                .data
+                .address!;
+            break;
+          default:
+            throw Exception("No Path type $type exists");
+        }
+        receivingNodes.addAll({
+          "${_id}_$j": {
+            "node": node,
+            "address": address,
+          }
+        });
+        txCountCallArgs.addAll({
+          "${_id}_$j": address,
+        });
+      }
+
+      // get address tx counts
+      final counts = await _getBatchTxCount(addresses: txCountCallArgs);
+
+      // check and add appropriate addresses
+      for (int k = 0; k < txCountBatchSize; k++) {
+        int count = counts["${_id}_$k"]!;
+        if (count > 0) {
+          final node = receivingNodes["${_id}_$k"];
+          // add address to array
+          addressArray.add(node["address"] as String);
+          iterationsAddressArray.add(node["address"] as String);
+          // set current index
+          returningIndex = index + k;
+          // reset counter
+          gapCounter = 0;
+          // add info to derivations
+          derivations[node["address"] as String] = {
+            "pubKey": Format.uint8listToString(
+                (node["node"] as bip32.BIP32).publicKey),
+            "wif": (node["node"] as bip32.BIP32).toWIF(),
+          };
+        }
+
+        // increase counter when no tx history found
+        if (count == 0) {
+          gapCounter++;
+        }
+      }
+      // cache all the transactions while waiting for the current function to finish.
+      unawaited(getTransactionCacheEarly(iterationsAddressArray));
+    }
+    return {
+      "addressArray": addressArray,
+      "index": returningIndex,
+      "derivations": derivations
+    };
+  }
+
+  Future<void> getTransactionCacheEarly(List<String> allAddresses) async {
+    try {
+      final List<Map<String, dynamic>> allTxHashes =
+          await _fetchHistory(allAddresses);
+      for (final txHash in allTxHashes) {
+        try {
+          unawaited(cachedElectrumXClient.getTransaction(
+            txHash: txHash["tx_hash"] as String,
+            verbose: true,
+            coin: coin,
+          ));
+        } catch (e) {
+          continue;
+        }
+      }
+    } catch (e) {
+      //
+    }
+  }
+
+  Future<void> _recoverWalletFromBIP32SeedPhrase({
+    required String mnemonic,
+    int maxUnusedAddressGap = 20,
+    int maxNumberOfIndexesToCheck = 1000,
+  }) async {
+    longMutex = true;
+
+    Map<String, Map<String, String>> p2pkhReceiveDerivations = {};
+    Map<String, Map<String, String>> p2wpkhReceiveDerivations = {};
+    Map<String, Map<String, String>> p2pkhChangeDerivations = {};
+    Map<String, Map<String, String>> p2wpkhChangeDerivations = {};
+
+    final root = await compute(getBip32RootWrapper, Tuple2(mnemonic, _network));
+
+    List<String> p2pkhReceiveAddressArray = [];
+    List<String> p2wpkhReceiveAddressArray = [];
+    int p2pkhReceiveIndex = -1;
+    int p2wpkhReceiveIndex = -1;
+
+    List<String> p2pkhChangeAddressArray = [];
+    List<String> p2wpkhChangeAddressArray = [];
+    int p2pkhChangeIndex = -1;
+    int p2wpkhChangeIndex = -1;
+
+    // actual size is 24 due to p2pkh, and p2wpkh so 12x2
+    const txCountBatchSize = 12;
+
+    try {
+      // receiving addresses
+      Logging.instance
+          .log("checking receiving addresses...", level: LogLevel.Info);
+      final resultReceive44 = _checkGaps(maxNumberOfIndexesToCheck,
+          maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 0);
+
+      final resultReceive84 = _checkGaps(maxNumberOfIndexesToCheck,
+          maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip84, 0);
+
+      Logging.instance
+          .log("checking change addresses...", level: LogLevel.Info);
+      // change addresses
+      final resultChange44 = _checkGaps(maxNumberOfIndexesToCheck,
+          maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 1);
+
+      final resultChange84 = _checkGaps(maxNumberOfIndexesToCheck,
+          maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip84, 1);
+
+      await Future.wait(
+          [resultReceive44, resultReceive84, resultChange44, resultChange84]);
+
+      p2pkhReceiveAddressArray =
+          (await resultReceive44)['addressArray'] as List<String>;
+      p2pkhReceiveIndex = (await resultReceive44)['index'] as int;
+      p2pkhReceiveDerivations = (await resultReceive44)['derivations']
+          as Map<String, Map<String, String>>;
+
+      p2wpkhReceiveAddressArray =
+          (await resultReceive84)['addressArray'] as List<String>;
+      p2wpkhReceiveIndex = (await resultReceive84)['index'] as int;
+      p2wpkhReceiveDerivations = (await resultReceive84)['derivations']
+          as Map<String, Map<String, String>>;
+
+      p2pkhChangeAddressArray =
+          (await resultChange44)['addressArray'] as List<String>;
+      p2pkhChangeIndex = (await resultChange44)['index'] as int;
+      p2pkhChangeDerivations = (await resultChange44)['derivations']
+          as Map<String, Map<String, String>>;
+
+      p2wpkhChangeAddressArray =
+          (await resultChange84)['addressArray'] as List<String>;
+      p2wpkhChangeIndex = (await resultChange84)['index'] as int;
+      p2wpkhChangeDerivations = (await resultChange84)['derivations']
+          as Map<String, Map<String, String>>;
+
+      // save the derivations (if any)
+      if (p2pkhReceiveDerivations.isNotEmpty) {
+        await addDerivations(
+            chain: 0,
+            derivePathType: DerivePathType.bip44,
+            derivationsToAdd: p2pkhReceiveDerivations);
+      }
+
+      if (p2wpkhReceiveDerivations.isNotEmpty) {
+        await addDerivations(
+            chain: 0,
+            derivePathType: DerivePathType.bip84,
+            derivationsToAdd: p2wpkhReceiveDerivations);
+      }
+      if (p2pkhChangeDerivations.isNotEmpty) {
+        await addDerivations(
+            chain: 1,
+            derivePathType: DerivePathType.bip44,
+            derivationsToAdd: p2pkhChangeDerivations);
+      }
+
+      if (p2wpkhChangeDerivations.isNotEmpty) {
+        await addDerivations(
+            chain: 1,
+            derivePathType: DerivePathType.bip84,
+            derivationsToAdd: p2wpkhChangeDerivations);
+      }
+
+      // If restoring a wallet that never received any funds, then set receivingArray manually
+      // If we didn't do this, it'd store an empty array
+      if (p2pkhReceiveIndex == -1) {
+        final address =
+            await _generateAddressForChain(0, 0, DerivePathType.bip44);
+        p2pkhReceiveAddressArray.add(address);
+        p2pkhReceiveIndex = 0;
+      }
+
+      if (p2wpkhReceiveIndex == -1) {
+        final address =
+            await _generateAddressForChain(0, 0, DerivePathType.bip84);
+        p2wpkhReceiveAddressArray.add(address);
+        p2wpkhReceiveIndex = 0;
+      }
+
+      // If restoring a wallet that never sent any funds with change, then set changeArray
+      // manually. If we didn't do this, it'd store an empty array.
+      if (p2pkhChangeIndex == -1) {
+        final address =
+            await _generateAddressForChain(1, 0, DerivePathType.bip44);
+        p2pkhChangeAddressArray.add(address);
+        p2pkhChangeIndex = 0;
+      }
+
+      if (p2wpkhChangeIndex == -1) {
+        final address =
+            await _generateAddressForChain(1, 0, DerivePathType.bip84);
+        p2wpkhChangeAddressArray.add(address);
+        p2wpkhChangeIndex = 0;
+      }
+
+      await DB.instance.put<dynamic>(
+          boxName: walletId,
+          key: 'receivingAddressesP2WPKH',
+          value: p2wpkhReceiveAddressArray);
+      await DB.instance.put<dynamic>(
+          boxName: walletId,
+          key: 'changeAddressesP2WPKH',
+          value: p2wpkhChangeAddressArray);
+      await DB.instance.put<dynamic>(
+          boxName: walletId,
+          key: 'receivingAddressesP2PKH',
+          value: p2pkhReceiveAddressArray);
+      await DB.instance.put<dynamic>(
+          boxName: walletId,
+          key: 'changeAddressesP2PKH',
+          value: p2pkhChangeAddressArray);
+      await DB.instance.put<dynamic>(
+          boxName: walletId,
+          key: 'receivingIndexP2WPKH',
+          value: p2wpkhReceiveIndex);
+      await DB.instance.put<dynamic>(
+          boxName: walletId,
+          key: 'changeIndexP2WPKH',
+          value: p2wpkhChangeIndex);
+      await DB.instance.put<dynamic>(
+          boxName: walletId, key: 'changeIndexP2PKH', value: p2pkhChangeIndex);
+      await DB.instance.put<dynamic>(
+          boxName: walletId,
+          key: 'receivingIndexP2PKH',
+          value: p2pkhReceiveIndex);
+      await DB.instance
+          .put<dynamic>(boxName: walletId, key: "id", value: _walletId);
+      await DB.instance
+          .put<dynamic>(boxName: walletId, key: "isFavorite", value: false);
+
+      longMutex = false;
+    } catch (e, s) {
+      Logging.instance.log(
+          "Exception rethrown from _recoverWalletFromBIP32SeedPhrase(): $e\n$s",
+          level: LogLevel.Error);
+
+      longMutex = false;
+      rethrow;
+    }
+  }
+
+  Future<bool> refreshIfThereIsNewData() async {
+    if (longMutex) return false;
+    if (_hasCalledExit) return false;
+    Logging.instance.log("refreshIfThereIsNewData", level: LogLevel.Info);
+
+    try {
+      bool needsRefresh = false;
+      Set<String> txnsToCheck = {};
+
+      for (final String txid in txTracker.pendings) {
+        if (!txTracker.wasNotifiedConfirmed(txid)) {
+          txnsToCheck.add(txid);
+        }
+      }
+
+      for (String txid in txnsToCheck) {
+        final txn = await electrumXClient.getTransaction(txHash: txid);
+        int confirmations = txn["confirmations"] as int? ?? 0;
+        bool isUnconfirmed = confirmations < MINIMUM_CONFIRMATIONS;
+        if (!isUnconfirmed) {
+          // unconfirmedTxs = {};
+          needsRefresh = true;
+          break;
+        }
+      }
+      if (!needsRefresh) {
+        var allOwnAddresses = await _fetchAllOwnAddresses();
+        List<Map<String, dynamic>> allTxs =
+            await _fetchHistory(allOwnAddresses);
+        final txData = await transactionData;
+        for (Map<String, dynamic> transaction in allTxs) {
+          if (txData.findTransaction(transaction['tx_hash'] as String) ==
+              null) {
+            Logging.instance.log(
+                " txid not found in address history already ${transaction['tx_hash']}",
+                level: LogLevel.Info);
+            needsRefresh = true;
+            break;
+          }
+        }
+      }
+      return needsRefresh;
+    } catch (e, s) {
+      Logging.instance.log(
+          "Exception caught in refreshIfThereIsNewData: $e\n$s",
+          level: LogLevel.Error);
+      rethrow;
+    }
+  }
+
+  Future<void> getAllTxsToWatch(
+    TransactionData txData,
+  ) async {
+    if (_hasCalledExit) return;
+    List<models.Transaction> unconfirmedTxnsToNotifyPending = [];
+    List<models.Transaction> unconfirmedTxnsToNotifyConfirmed = [];
+
+    for (final chunk in txData.txChunks) {
+      for (final tx in chunk.transactions) {
+        if (tx.confirmedStatus) {
+          // get all transactions that were notified as pending but not as confirmed
+          if (txTracker.wasNotifiedPending(tx.txid) &&
+              !txTracker.wasNotifiedConfirmed(tx.txid)) {
+            unconfirmedTxnsToNotifyConfirmed.add(tx);
+          }
+        } else {
+          // get all transactions that were not notified as pending yet
+          if (!txTracker.wasNotifiedPending(tx.txid)) {
+            unconfirmedTxnsToNotifyPending.add(tx);
+          }
+        }
+      }
+    }
+
+    // notify on unconfirmed transactions
+    for (final tx in unconfirmedTxnsToNotifyPending) {
+      if (tx.txType == "Received") {
+        unawaited(NotificationApi.showNotification(
+          title: "Incoming transaction",
+          body: walletName,
+          walletId: walletId,
+          iconAssetName: Assets.svg.iconFor(coin: coin),
+          date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000),
+          shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS,
+          coinName: coin.name,
+          txid: tx.txid,
+          confirmations: tx.confirmations,
+          requiredConfirmations: MINIMUM_CONFIRMATIONS,
+        ));
+        await txTracker.addNotifiedPending(tx.txid);
+      } else if (tx.txType == "Sent") {
+        unawaited(NotificationApi.showNotification(
+          title: "Sending transaction",
+          body: walletName,
+          walletId: walletId,
+          iconAssetName: Assets.svg.iconFor(coin: coin),
+          date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000),
+          shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS,
+          coinName: coin.name,
+          txid: tx.txid,
+          confirmations: tx.confirmations,
+          requiredConfirmations: MINIMUM_CONFIRMATIONS,
+        ));
+        await txTracker.addNotifiedPending(tx.txid);
+      }
+    }
+
+    // notify on confirmed
+    for (final tx in unconfirmedTxnsToNotifyConfirmed) {
+      if (tx.txType == "Received") {
+        unawaited(NotificationApi.showNotification(
+          title: "Incoming transaction confirmed",
+          body: walletName,
+          walletId: walletId,
+          iconAssetName: Assets.svg.iconFor(coin: coin),
+          date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000),
+          shouldWatchForUpdates: false,
+          coinName: coin.name,
+        ));
+        await txTracker.addNotifiedConfirmed(tx.txid);
+      } else if (tx.txType == "Sent") {
+        unawaited(NotificationApi.showNotification(
+          title: "Outgoing transaction confirmed",
+          body: walletName,
+          walletId: walletId,
+          iconAssetName: Assets.svg.iconFor(coin: coin),
+          date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000),
+          shouldWatchForUpdates: false,
+          coinName: coin.name,
+        ));
+        await txTracker.addNotifiedConfirmed(tx.txid);
+      }
+    }
+  }
+
+  bool _shouldAutoSync = false;
+
+  @override
+  bool get shouldAutoSync => _shouldAutoSync;
+
+  @override
+  set shouldAutoSync(bool shouldAutoSync) {
+    if (_shouldAutoSync != shouldAutoSync) {
+      _shouldAutoSync = shouldAutoSync;
+      if (!shouldAutoSync) {
+        timer?.cancel();
+        timer = null;
+        stopNetworkAlivePinging();
+      } else {
+        startNetworkAlivePinging();
+        refresh();
+      }
+    }
+  }
+
+  @override
+  bool get isRefreshing => refreshMutex;
+
+  bool refreshMutex = false;
+
+  //TODO Show percentages properly/more consistently
+  /// Refreshes display data for the wallet
+  @override
+  Future<void> refresh() async {
+    if (refreshMutex) {
+      Logging.instance.log("$walletId $walletName refreshMutex denied",
+          level: LogLevel.Info);
+      return;
+    } else {
+      refreshMutex = true;
+    }
+
+    try {
+      GlobalEventBus.instance.fire(
+        WalletSyncStatusChangedEvent(
+          WalletSyncStatus.syncing,
+          walletId,
+          coin,
+        ),
+      );
+
+      GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId));
+
+      GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId));
+
+      final currentHeight = await chainHeight;
+      const storedHeight = 1; //await storedChainHeight;
+
+      Logging.instance
+          .log("chain height: $currentHeight", level: LogLevel.Info);
+      Logging.instance
+          .log("cached height: $storedHeight", level: LogLevel.Info);
+
+      if (currentHeight != storedHeight) {
+        if (currentHeight != -1) {
+          // -1 failed to fetch current height
+          unawaited(updateStoredChainHeight(newHeight: currentHeight));
+        }
+
+        GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId));
+        final changeAddressForTransactions =
+            _checkChangeAddressForTransactions(DerivePathType.bip84);
+
+        GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId));
+        final currentReceivingAddressesForTransactions =
+            _checkCurrentReceivingAddressesForTransactions();
+
+        final newTxData = _fetchTransactionData();
+        GlobalEventBus.instance
+            .fire(RefreshPercentChangedEvent(0.50, walletId));
+
+        final newUtxoData = _fetchUtxoData();
+        final feeObj = _getFees();
+        GlobalEventBus.instance
+            .fire(RefreshPercentChangedEvent(0.60, walletId));
+
+        _transactionData = Future(() => newTxData);
+
+        GlobalEventBus.instance
+            .fire(RefreshPercentChangedEvent(0.70, walletId));
+        _feeObject = Future(() => feeObj);
+        _utxoData = Future(() => newUtxoData);
+        GlobalEventBus.instance
+            .fire(RefreshPercentChangedEvent(0.80, walletId));
+
+        final allTxsToWatch = getAllTxsToWatch(await newTxData);
+        await Future.wait([
+          newTxData,
+          changeAddressForTransactions,
+          currentReceivingAddressesForTransactions,
+          newUtxoData,
+          feeObj,
+          allTxsToWatch,
+        ]);
+        GlobalEventBus.instance
+            .fire(RefreshPercentChangedEvent(0.90, walletId));
+      }
+
+      refreshMutex = false;
+      GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId));
+      GlobalEventBus.instance.fire(
+        WalletSyncStatusChangedEvent(
+          WalletSyncStatus.synced,
+          walletId,
+          coin,
+        ),
+      );
+
+      if (shouldAutoSync) {
+        timer ??= Timer.periodic(const Duration(seconds: 30), (timer) async {
+          Logging.instance.log(
+              "Periodic refresh check for $walletId $walletName in object instance: $hashCode",
+              level: LogLevel.Info);
+          if (await refreshIfThereIsNewData()) {
+            await refresh();
+            GlobalEventBus.instance.fire(UpdatedInBackgroundEvent(
+                "New data found in $walletId $walletName in background!",
+                walletId));
+          }
+        });
+      }
+    } catch (error, strace) {
+      refreshMutex = false;
+      GlobalEventBus.instance.fire(
+        NodeConnectionStatusChangedEvent(
+          NodeConnectionStatus.disconnected,
+          walletId,
+          coin,
+        ),
+      );
+      GlobalEventBus.instance.fire(
+        WalletSyncStatusChangedEvent(
+          WalletSyncStatus.unableToSync,
+          walletId,
+          coin,
+        ),
+      );
+      Logging.instance.log(
+          "Caught exception in refreshWalletData(): $error\n$strace",
+          level: LogLevel.Error);
+    }
+  }
+
+  @override
+  Future<Map<String, dynamic>> prepareSend({
+    required String address,
+    required int satoshiAmount,
+    Map<String, dynamic>? args,
+  }) async {
+    try {
+      final feeRateType = args?["feeRate"];
+      final feeRateAmount = args?["feeRateAmount"];
+      if (feeRateType is FeeRateType || feeRateAmount is int) {
+        late final int rate;
+        if (feeRateType is FeeRateType) {
+          int fee = 0;
+          final feeObject = await fees;
+          switch (feeRateType) {
+            case FeeRateType.fast:
+              fee = feeObject.fast;
+              break;
+            case FeeRateType.average:
+              fee = feeObject.medium;
+              break;
+            case FeeRateType.slow:
+              fee = feeObject.slow;
+              break;
+          }
+          rate = fee;
+        } else {
+          rate = feeRateAmount as int;
+        }
+
+        // check for send all
+        bool isSendAll = false;
+        final balance =
+            Format.decimalAmountToSatoshis(await availableBalance, coin);
+        if (satoshiAmount == balance) {
+          isSendAll = true;
+        }
+
+        final txData =
+            await coinSelection(satoshiAmount, rate, address, isSendAll);
+
+        Logging.instance.log("prepare send: $txData", level: LogLevel.Info);
+        try {
+          if (txData is int) {
+            switch (txData) {
+              case 1:
+                throw Exception("Insufficient balance!");
+              case 2:
+                throw Exception(
+                    "Insufficient funds to pay for transaction fee!");
+              default:
+                throw Exception("Transaction failed with error code $txData");
+            }
+          } else {
+            final hex = txData["hex"];
+
+            if (hex is String) {
+              final fee = txData["fee"] as int;
+              final vSize = txData["vSize"] as int;
+
+              Logging.instance
+                  .log("prepared txHex: $hex", level: LogLevel.Info);
+              Logging.instance.log("prepared fee: $fee", level: LogLevel.Info);
+              Logging.instance
+                  .log("prepared vSize: $vSize", level: LogLevel.Info);
+
+              // fee should never be less than vSize sanity check
+              if (fee < vSize) {
+                throw Exception(
+                    "Error in fee calculation: Transaction fee cannot be less than vSize");
+              }
+
+              return txData as Map<String, dynamic>;
+            } else {
+              throw Exception("prepared hex is not a String!!!");
+            }
+          }
+        } catch (e, s) {
+          Logging.instance.log("Exception rethrown from prepareSend(): $e\n$s",
+              level: LogLevel.Error);
+          rethrow;
+        }
+      } else {
+        throw ArgumentError("Invalid fee rate argument provided!");
+      }
+    } catch (e, s) {
+      Logging.instance.log("Exception rethrown from prepareSend(): $e\n$s",
+          level: LogLevel.Error);
+      rethrow;
+    }
+  }
+
+  @override
+  Future<String> confirmSend({required Map<String, dynamic> txData}) async {
+    try {
+      Logging.instance.log("confirmSend txData: $txData", level: LogLevel.Info);
+
+      final hex = txData["hex"] as String;
+
+      final txHash = await _electrumXClient.broadcastTransaction(rawTx: hex);
+      Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info);
+
+      return txHash;
+    } catch (e, s) {
+      Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s",
+          level: LogLevel.Error);
+      rethrow;
+    }
+  }
+
+  @override
+  Future<String> send({
+    required String toAddress,
+    required int amount,
+    Map<String, String> args = const {},
+  }) async {
+    try {
+      final txData = await prepareSend(
+          address: toAddress, satoshiAmount: amount, args: args);
+      final txHash = await confirmSend(txData: txData);
+      return txHash;
+    } catch (e, s) {
+      Logging.instance
+          .log("Exception rethrown from send(): $e\n$s", level: LogLevel.Error);
+      rethrow;
+    }
+  }
+
+  @override
+  Future<bool> testNetworkConnection() async {
+    try {
+      final result = await _electrumXClient.ping();
+      return result;
+    } catch (_) {
+      return false;
+    }
+  }
+
+  Timer? _networkAliveTimer;
+
+  void startNetworkAlivePinging() {
+    // call once on start right away
+    _periodicPingCheck();
+
+    // then periodically check
+    _networkAliveTimer = Timer.periodic(
+      Constants.networkAliveTimerDuration,
+      (_) async {
+        _periodicPingCheck();
+      },
+    );
+  }
+
+  void _periodicPingCheck() async {
+    bool hasNetwork = await testNetworkConnection();
+    _isConnected = hasNetwork;
+    if (_isConnected != hasNetwork) {
+      NodeConnectionStatus status = hasNetwork
+          ? NodeConnectionStatus.connected
+          : NodeConnectionStatus.disconnected;
+      GlobalEventBus.instance
+          .fire(NodeConnectionStatusChangedEvent(status, walletId, coin));
+    }
+  }
+
+  void stopNetworkAlivePinging() {
+    _networkAliveTimer?.cancel();
+    _networkAliveTimer = null;
+  }
+
+  bool _isConnected = false;
+
+  @override
+  bool get isConnected => _isConnected;
+
+  @override
+  Future<void> initializeNew() async {
+    Logging.instance
+        .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info);
+
+    if ((DB.instance.get<dynamic>(boxName: walletId, key: "id")) != null) {
+      throw Exception(
+          "Attempted to initialize a new wallet using an existing wallet ID!");
+    }
+
+    await _prefs.init();
+    try {
+      await _generateNewWallet();
+    } catch (e, s) {
+      Logging.instance.log("Exception rethrown from initializeNew(): $e\n$s",
+          level: LogLevel.Fatal);
+      rethrow;
+    }
+    await Future.wait([
+      DB.instance.put<dynamic>(boxName: walletId, key: "id", value: walletId),
+      DB.instance
+          .put<dynamic>(boxName: walletId, key: "isFavorite", value: false),
+    ]);
+  }
+
+  @override
+  Future<void> initializeExisting() async {
+    Logging.instance.log("Opening existing ${coin.prettyName} wallet.",
+        level: LogLevel.Info);
+
+    if ((DB.instance.get<dynamic>(boxName: walletId, key: "id")) == null) {
+      throw Exception(
+          "Attempted to initialize an existing wallet using an unknown wallet ID!");
+    }
+    await _prefs.init();
+    final data =
+        DB.instance.get<dynamic>(boxName: walletId, key: "latest_tx_model")
+            as TransactionData?;
+    if (data != null) {
+      _transactionData = Future(() => data);
+    }
+  }
+
+  @override
+  Future<TransactionData> get transactionData =>
+      _transactionData ??= _fetchTransactionData();
+  Future<TransactionData>? _transactionData;
+
+  TransactionData? cachedTxData;
+
+  // TODO make sure this copied implementation from bitcoin_wallet.dart applies for particl just as well--or import it
+  // hack to add tx to txData before refresh completes
+  // required based on current app architecture where we don't properly store
+  // transactions locally in a good way
+  @override
+  Future<void> updateSentCachedTxData(Map<String, dynamic> txData) async {
+    final priceData =
+        await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency);
+    Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
+    final locale = await Devicelocale.currentLocale;
+    final String worthNow = Format.localizedStringAsFixed(
+        value:
+            ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) /
+                    Decimal.fromInt(Constants.satsPerCoin(coin)))
+                .toDecimal(scaleOnInfinitePrecision: 2),
+        decimalPlaces: 2,
+        locale: locale!);
+
+    final tx = models.Transaction(
+      txid: txData["txid"] as String,
+      confirmedStatus: false,
+      timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
+      txType: "Sent",
+      amount: txData["recipientAmt"] as int,
+      worthNow: worthNow,
+      worthAtBlockTimestamp: worthNow,
+      fees: txData["fee"] as int,
+      inputSize: 0,
+      outputSize: 0,
+      inputs: [],
+      outputs: [],
+      address: txData["address"] as String,
+      height: -1,
+      confirmations: 0,
+    );
+
+    if (cachedTxData == null) {
+      final data = await _fetchTransactionData();
+      _transactionData = Future(() => data);
+    } else {
+      final transactions = cachedTxData!.getAllTransactions();
+      transactions[tx.txid] = tx;
+      cachedTxData = models.TransactionData.fromMap(transactions);
+      _transactionData = Future(() => cachedTxData!);
+    }
+  }
+
+  @override
+  bool validateAddress(String address) {
+    return Address.validateAddress(address, _network, particl.bech32!);
+  }
+
+  @override
+  String get walletId => _walletId;
+  late String _walletId;
+
+  @override
+  String get walletName => _walletName;
+  late String _walletName;
+
+  // setter for updating on rename
+  @override
+  set walletName(String newName) => _walletName = newName;
+
+  late ElectrumX _electrumXClient;
+
+  ElectrumX get electrumXClient => _electrumXClient;
+
+  late CachedElectrumX _cachedElectrumXClient;
+
+  CachedElectrumX get cachedElectrumXClient => _cachedElectrumXClient;
+
+  late SecureStorageInterface _secureStore;
+
+  late PriceAPI _priceAPI;
+
+  ParticlWallet({
+    required String walletId,
+    required String walletName,
+    required Coin coin,
+    required ElectrumX client,
+    required CachedElectrumX cachedClient,
+    required TransactionNotificationTracker tracker,
+    PriceAPI? priceAPI,
+    required SecureStorageInterface secureStore,
+  }) {
+    txTracker = tracker;
+    _walletId = walletId;
+    _walletName = walletName;
+    _coin = coin;
+    _electrumXClient = client;
+    _cachedElectrumXClient = cachedClient;
+
+    _priceAPI = priceAPI ?? PriceAPI(Client());
+    _secureStore = secureStore;
+  }
+
+  @override
+  Future<void> updateNode(bool shouldRefresh) async {
+    final failovers = NodeService(secureStorageInterface: _secureStore)
+        .failoverNodesFor(coin: coin)
+        .map((e) => ElectrumXNode(
+              address: e.host,
+              port: e.port,
+              name: e.name,
+              id: e.id,
+              useSSL: e.useSSL,
+            ))
+        .toList();
+    final newNode = await getCurrentNode();
+    _cachedElectrumXClient = CachedElectrumX.from(
+      node: newNode,
+      prefs: _prefs,
+      failovers: failovers,
+    );
+    _electrumXClient = ElectrumX.from(
+      node: newNode,
+      prefs: _prefs,
+      failovers: failovers,
+    );
+
+    if (shouldRefresh) {
+      unawaited(refresh());
+    }
+  }
+
+  Future<List<String>> _getMnemonicList() async {
+    final mnemonicString =
+        await _secureStore.read(key: '${_walletId}_mnemonic');
+    if (mnemonicString == null) {
+      return [];
+    }
+    final List<String> data = mnemonicString.split(' ');
+    return data;
+  }
+
+  Future<ElectrumXNode> getCurrentNode() async {
+    final node = NodeService(secureStorageInterface: _secureStore)
+            .getPrimaryNodeFor(coin: coin) ??
+        DefaultNodes.getNodeFor(coin);
+
+    return ElectrumXNode(
+      address: node.host,
+      port: node.port,
+      name: node.name,
+      useSSL: node.useSSL,
+      id: node.id,
+    );
+  }
+
+  Future<List<String>> _fetchAllOwnAddresses() async {
+    final List<String> allAddresses = [];
+    final receivingAddresses = DB.instance.get<dynamic>(
+        boxName: walletId, key: 'receivingAddressesP2WPKH') as List<dynamic>;
+    final changeAddresses = DB.instance.get<dynamic>(
+        boxName: walletId, key: 'changeAddressesP2WPKH') as List<dynamic>;
+    final receivingAddressesP2PKH = DB.instance.get<dynamic>(
+        boxName: walletId, key: 'receivingAddressesP2PKH') as List<dynamic>;
+    final changeAddressesP2PKH =
+        DB.instance.get<dynamic>(boxName: walletId, key: 'changeAddressesP2PKH')
+            as List<dynamic>;
+
+    for (var i = 0; i < receivingAddresses.length; i++) {
+      if (!allAddresses.contains(receivingAddresses[i])) {
+        allAddresses.add(receivingAddresses[i] as String);
+      }
+    }
+    for (var i = 0; i < changeAddresses.length; i++) {
+      if (!allAddresses.contains(changeAddresses[i])) {
+        allAddresses.add(changeAddresses[i] as String);
+      }
+    }
+    for (var i = 0; i < receivingAddressesP2PKH.length; i++) {
+      if (!allAddresses.contains(receivingAddressesP2PKH[i])) {
+        allAddresses.add(receivingAddressesP2PKH[i] as String);
+      }
+    }
+    for (var i = 0; i < changeAddressesP2PKH.length; i++) {
+      if (!allAddresses.contains(changeAddressesP2PKH[i])) {
+        allAddresses.add(changeAddressesP2PKH[i] as String);
+      }
+    }
+
+    return allAddresses;
+  }
+
+  Future<FeeObject> _getFees() async {
+    try {
+      //TODO adjust numbers for different speeds?
+      const int f = 1, m = 5, s = 20;
+
+      final fast = await electrumXClient.estimateFee(blocks: f);
+      final medium = await electrumXClient.estimateFee(blocks: m);
+      final slow = await electrumXClient.estimateFee(blocks: s);
+
+      final feeObject = FeeObject(
+        numberOfBlocksFast: f,
+        numberOfBlocksAverage: m,
+        numberOfBlocksSlow: s,
+        fast: Format.decimalAmountToSatoshis(fast, coin),
+        medium: Format.decimalAmountToSatoshis(medium, coin),
+        slow: Format.decimalAmountToSatoshis(slow, coin),
+      );
+
+      Logging.instance.log("fetched fees: $feeObject", level: LogLevel.Info);
+      return feeObject;
+    } catch (e) {
+      Logging.instance
+          .log("Exception rethrown from _getFees(): $e", level: LogLevel.Error);
+      rethrow;
+    }
+  }
+
+  Future<void> _generateNewWallet() async {
+    Logging.instance
+        .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info);
+    if (!integrationTestFlag) {
+      final features = await electrumXClient.getServerFeatures();
+      Logging.instance.log("features: $features", level: LogLevel.Info);
+      switch (coin) {
+        case Coin.particl:
+          if (features['genesis_hash'] != GENESIS_HASH_MAINNET) {
+            throw Exception("genesis hash does not match main net!");
+          }
+          break;
+        default:
+          throw Exception(
+              "Attempted to generate a ParticlWallet using a non particl coin type: ${coin.name}");
+      }
+    }
+
+    // this should never fail
+    if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) {
+      throw Exception(
+          "Attempted to overwrite mnemonic on generate new wallet!");
+    }
+    await _secureStore.write(
+        key: '${_walletId}_mnemonic',
+        value: bip39.generateMnemonic(strength: 256));
+
+    // Set relevant indexes
+    await DB.instance
+        .put<dynamic>(boxName: walletId, key: "receivingIndexP2WPKH", value: 0);
+    await DB.instance
+        .put<dynamic>(boxName: walletId, key: "changeIndexP2WPKH", value: 0);
+    await DB.instance
+        .put<dynamic>(boxName: walletId, key: "receivingIndexP2PKH", value: 0);
+    await DB.instance
+        .put<dynamic>(boxName: walletId, key: "changeIndexP2PKH", value: 0);
+    await DB.instance.put<dynamic>(
+      boxName: walletId,
+      key: 'blocked_tx_hashes',
+      value: ["0xdefault"],
+    ); // A list of transaction hashes to represent frozen utxos in wallet
+    // initialize address book entries
+    await DB.instance.put<dynamic>(
+        boxName: walletId,
+        key: 'addressBookEntries',
+        value: <String, String>{});
+
+    // Generate and add addresses to relevant arrays
+    await Future.wait([
+      // P2WPKH
+      _generateAddressForChain(0, 0, DerivePathType.bip84).then(
+        (initialReceivingAddressP2WPKH) {
+          _addToAddressesArrayForChain(
+              initialReceivingAddressP2WPKH, 0, DerivePathType.bip84);
+          _currentReceivingAddress =
+              Future(() => initialReceivingAddressP2WPKH);
+        },
+      ),
+      _generateAddressForChain(1, 0, DerivePathType.bip84).then(
+        (initialChangeAddressP2WPKH) => _addToAddressesArrayForChain(
+          initialChangeAddressP2WPKH,
+          1,
+          DerivePathType.bip84,
+        ),
+      ),
+
+      // P2PKH
+      _generateAddressForChain(0, 0, DerivePathType.bip44).then(
+        (initialReceivingAddressP2PKH) {
+          _addToAddressesArrayForChain(
+              initialReceivingAddressP2PKH, 0, DerivePathType.bip44);
+          _currentReceivingAddressP2PKH =
+              Future(() => initialReceivingAddressP2PKH);
+        },
+      ),
+      _generateAddressForChain(1, 0, DerivePathType.bip44).then(
+        (initialChangeAddressP2PKH) => _addToAddressesArrayForChain(
+          initialChangeAddressP2PKH,
+          1,
+          DerivePathType.bip44,
+        ),
+      ),
+    ]);
+
+    Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info);
+  }
+
+  /// Generates a new internal or external chain address for the wallet using a BIP84, BIP44, or BIP49 derivation path.
+  /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value!
+  /// [index] - This can be any integer >= 0
+  Future<String> _generateAddressForChain(
+    int chain,
+    int index,
+    DerivePathType derivePathType,
+  ) async {
+    final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic');
+    final node = await compute(
+      getBip32NodeWrapper,
+      Tuple5(
+        chain,
+        index,
+        mnemonic!,
+        _network,
+        derivePathType,
+      ),
+    );
+    final data = PaymentData(pubkey: node.publicKey);
+    String address;
+
+    switch (derivePathType) {
+      case DerivePathType.bip44:
+        address = P2PKH(data: data, network: _network).data.address!;
+        break;
+      case DerivePathType.bip84:
+        address = P2WPKH(network: _network, data: data).data.address!;
+        break;
+    }
+
+    // add generated address & info to derivations
+    await addDerivation(
+      chain: chain,
+      address: address,
+      pubKey: Format.uint8listToString(node.publicKey),
+      wif: node.toWIF(),
+      derivePathType: derivePathType,
+    );
+
+    return address;
+  }
+
+  /// Increases the index for either the internal or external chain, depending on [chain].
+  /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value!
+  Future<void> _incrementAddressIndexForChain(
+      int chain, DerivePathType derivePathType) async {
+    // Here we assume chain == 1 if it isn't 0
+    String indexKey = chain == 0 ? "receivingIndex" : "changeIndex";
+    switch (derivePathType) {
+      case DerivePathType.bip44:
+        indexKey += "P2PKH";
+        break;
+      case DerivePathType.bip84:
+        indexKey += "P2WPKH";
+        break;
+    }
+    final newIndex =
+        (DB.instance.get<dynamic>(boxName: walletId, key: indexKey)) + 1;
+    await DB.instance
+        .put<dynamic>(boxName: walletId, key: indexKey, value: newIndex);
+  }
+
+  /// Adds [address] to the relevant chain's address array, which is determined by [chain].
+  /// [address] - Expects a standard native segwit address
+  /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value!
+  Future<void> _addToAddressesArrayForChain(
+      String address, int chain, DerivePathType derivePathType) async {
+    String chainArray = '';
+    if (chain == 0) {
+      chainArray = 'receivingAddresses';
+    } else {
+      chainArray = 'changeAddresses';
+    }
+    switch (derivePathType) {
+      case DerivePathType.bip44:
+        chainArray += "P2PKH";
+        break;
+      case DerivePathType.bip84:
+        chainArray += "P2WPKH";
+        break;
+    }
+
+    final addressArray =
+        DB.instance.get<dynamic>(boxName: walletId, key: chainArray);
+    if (addressArray == null) {
+      Logging.instance.log(
+          'Attempting to add the following to $chainArray array for chain $chain:${[
+            address
+          ]}',
+          level: LogLevel.Info);
+      await DB.instance
+          .put<dynamic>(boxName: walletId, key: chainArray, value: [address]);
+    } else {
+      // Make a deep copy of the existing list
+      final List<String> newArray = [];
+      addressArray
+          .forEach((dynamic _address) => newArray.add(_address as String));
+      newArray.add(address); // Add the address passed into the method
+      await DB.instance
+          .put<dynamic>(boxName: walletId, key: chainArray, value: newArray);
+    }
+  }
+
+  /// Returns the latest receiving/change (external/internal) address for the wallet depending on [chain]
+  /// and
+  /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value!
+  Future<String> _getCurrentAddressForChain(
+      int chain, DerivePathType derivePathType) async {
+    // Here, we assume that chain == 1 if it isn't 0
+    String arrayKey = chain == 0 ? "receivingAddresses" : "changeAddresses";
+    switch (derivePathType) {
+      case DerivePathType.bip44:
+        arrayKey += "P2PKH";
+        break;
+      case DerivePathType.bip84:
+        arrayKey += "P2WPKH";
+        break;
+    }
+    final internalChainArray =
+        DB.instance.get<dynamic>(boxName: walletId, key: arrayKey);
+    return internalChainArray.last as String;
+  }
+
+  String _buildDerivationStorageKey({
+    required int chain,
+    required DerivePathType derivePathType,
+  }) {
+    String key;
+    String chainId = chain == 0 ? "receive" : "change";
+
+    switch (derivePathType) {
+      case DerivePathType.bip44:
+        key = "${walletId}_${chainId}DerivationsP2PKH";
+        break;
+      case DerivePathType.bip84:
+        key = "${walletId}_${chainId}DerivationsP2WPKH";
+        break;
+    }
+    return key;
+  }
+
+  Future<Map<String, dynamic>> _fetchDerivations({
+    required int chain,
+    required DerivePathType derivePathType,
+  }) async {
+    // build lookup key
+    final key = _buildDerivationStorageKey(
+        chain: chain, derivePathType: derivePathType);
+
+    // fetch current derivations
+    final derivationsString = await _secureStore.read(key: key);
+    return Map<String, dynamic>.from(
+        jsonDecode(derivationsString ?? "{}") as Map);
+  }
+
+  /// Add a single derivation to the local secure storage for [chain] and
+  /// [derivePathType] where [chain] must either be 1 for change or 0 for receive.
+  /// This will overwrite a previous entry where the address of the new derivation
+  /// matches a derivation currently stored.
+  Future<void> addDerivation({
+    required int chain,
+    required String address,
+    required String pubKey,
+    required String wif,
+    required DerivePathType derivePathType,
+  }) async {
+    // build lookup key
+    final key = _buildDerivationStorageKey(
+        chain: chain, derivePathType: derivePathType);
+
+    // fetch current derivations
+    final derivationsString = await _secureStore.read(key: key);
+    final derivations =
+        Map<String, dynamic>.from(jsonDecode(derivationsString ?? "{}") as Map);
+
+    // add derivation
+    derivations[address] = {
+      "pubKey": pubKey,
+      "wif": wif,
+    };
+
+    // save derivations
+    final newReceiveDerivationsString = jsonEncode(derivations);
+    await _secureStore.write(key: key, value: newReceiveDerivationsString);
+  }
+
+  /// Add multiple derivations to the local secure storage for [chain] and
+  /// [derivePathType] where [chain] must either be 1 for change or 0 for receive.
+  /// This will overwrite any previous entries where the address of the new derivation
+  /// matches a derivation currently stored.
+  /// The [derivationsToAdd] must be in the format of:
+  /// {
+  ///   addressA : {
+  ///     "pubKey": <the pubKey string>,
+  ///     "wif": <the wif string>,
+  ///   },
+  ///   addressB : {
+  ///     "pubKey": <the pubKey string>,
+  ///     "wif": <the wif string>,
+  ///   },
+  /// }
+  Future<void> addDerivations({
+    required int chain,
+    required DerivePathType derivePathType,
+    required Map<String, dynamic> derivationsToAdd,
+  }) async {
+    // build lookup key
+    final key = _buildDerivationStorageKey(
+        chain: chain, derivePathType: derivePathType);
+
+    // fetch current derivations
+    final derivationsString = await _secureStore.read(key: key);
+    final derivations =
+        Map<String, dynamic>.from(jsonDecode(derivationsString ?? "{}") as Map);
+
+    // add derivation
+    derivations.addAll(derivationsToAdd);
+
+    // save derivations
+    final newReceiveDerivationsString = jsonEncode(derivations);
+    await _secureStore.write(key: key, value: newReceiveDerivationsString);
+  }
+
+  Future<UtxoData> _fetchUtxoData() async {
+    final List<String> allAddresses = await _fetchAllOwnAddresses();
+
+    try {
+      final fetchedUtxoList = <List<Map<String, dynamic>>>[];
+
+      final Map<int, Map<String, List<dynamic>>> batches = {};
+      const batchSizeMax = 100;
+      int batchNumber = 0;
+      for (int i = 0; i < allAddresses.length; i++) {
+        if (batches[batchNumber] == null) {
+          batches[batchNumber] = {};
+        }
+        final scripthash = _convertToScriptHash(allAddresses[i], _network);
+        batches[batchNumber]!.addAll({
+          scripthash: [scripthash]
+        });
+        if (i % batchSizeMax == batchSizeMax - 1) {
+          batchNumber++;
+        }
+      }
+
+      for (int i = 0; i < batches.length; i++) {
+        final response =
+            await _electrumXClient.getBatchUTXOs(args: batches[i]!);
+        for (final entry in response.entries) {
+          if (entry.value.isNotEmpty) {
+            fetchedUtxoList.add(entry.value);
+          }
+        }
+      }
+      final priceData =
+          await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency);
+      Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
+      final List<Map<String, dynamic>> outputArray = [];
+      int satoshiBalance = 0;
+      int satoshiBalancePending = 0;
+
+      for (int i = 0; i < fetchedUtxoList.length; i++) {
+        for (int j = 0; j < fetchedUtxoList[i].length; j++) {
+          int value = fetchedUtxoList[i][j]["value"] as int;
+          satoshiBalance += value;
+
+          final txn = await cachedElectrumXClient.getTransaction(
+            txHash: fetchedUtxoList[i][j]["tx_hash"] as String,
+            verbose: true,
+            coin: coin,
+          );
+
+          final Map<String, dynamic> utxo = {};
+          final int confirmations = txn["confirmations"] as int? ?? 0;
+          final bool confirmed = confirmations >= MINIMUM_CONFIRMATIONS;
+          if (!confirmed) {
+            satoshiBalancePending += value;
+          }
+
+          utxo["txid"] = txn["txid"];
+          utxo["vout"] = fetchedUtxoList[i][j]["tx_pos"];
+          utxo["value"] = value;
+
+          utxo["status"] = <String, dynamic>{};
+          utxo["status"]["confirmed"] = confirmed;
+          utxo["status"]["confirmations"] = confirmations;
+          utxo["status"]["block_height"] = fetchedUtxoList[i][j]["height"];
+          utxo["status"]["block_hash"] = txn["blockhash"];
+          utxo["status"]["block_time"] = txn["blocktime"];
+
+          final fiatValue = ((Decimal.fromInt(value) * currentPrice) /
+                  Decimal.fromInt(Constants.satsPerCoin(coin)))
+              .toDecimal(scaleOnInfinitePrecision: 2);
+          utxo["rawWorth"] = fiatValue;
+          utxo["fiatWorth"] = fiatValue.toString();
+          outputArray.add(utxo);
+        }
+      }
+
+      Decimal currencyBalanceRaw =
+          ((Decimal.fromInt(satoshiBalance) * currentPrice) /
+                  Decimal.fromInt(Constants.satsPerCoin(coin)))
+              .toDecimal(scaleOnInfinitePrecision: 2);
+
+      final Map<String, dynamic> result = {
+        "total_user_currency": currencyBalanceRaw.toString(),
+        "total_sats": satoshiBalance,
+        "total_btc": (Decimal.fromInt(satoshiBalance) /
+                Decimal.fromInt(Constants.satsPerCoin(coin)))
+            .toDecimal(
+                scaleOnInfinitePrecision: Constants.decimalPlacesForCoin(coin))
+            .toString(),
+        "outputArray": outputArray,
+        "unconfirmed": satoshiBalancePending,
+      };
+
+      final dataModel = UtxoData.fromJson(result);
+
+      final List<UtxoObject> allOutputs = dataModel.unspentOutputArray;
+      Logging.instance
+          .log('Outputs fetched: $allOutputs', level: LogLevel.Info);
+      await _sortOutputs(allOutputs);
+      await DB.instance.put<dynamic>(
+          boxName: walletId, key: 'latest_utxo_model', value: dataModel);
+      await DB.instance.put<dynamic>(
+          boxName: walletId,
+          key: 'totalBalance',
+          value: dataModel.satoshiBalance);
+      return dataModel;
+    } catch (e, s) {
+      Logging.instance
+          .log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error);
+      final latestTxModel =
+          DB.instance.get<dynamic>(boxName: walletId, key: 'latest_utxo_model')
+              as models.UtxoData?;
+
+      if (latestTxModel == null) {
+        final emptyModel = {
+          "total_user_currency": "0.00",
+          "total_sats": 0,
+          "total_btc": "0",
+          "outputArray": <dynamic>[]
+        };
+        return UtxoData.fromJson(emptyModel);
+      } else {
+        Logging.instance
+            .log("Old output model located", level: LogLevel.Warning);
+        return latestTxModel;
+      }
+    }
+  }
+
+  /// Takes in a list of UtxoObjects and adds a name (dependent on object index within list)
+  /// and checks for the txid associated with the utxo being blocked and marks it accordingly.
+  /// Now also checks for output labeling.
+  Future<void> _sortOutputs(List<UtxoObject> utxos) async {
+    final blockedHashArray =
+        DB.instance.get<dynamic>(boxName: walletId, key: 'blocked_tx_hashes')
+            as List<dynamic>?;
+    final List<String> lst = [];
+    if (blockedHashArray != null) {
+      for (var hash in blockedHashArray) {
+        lst.add(hash as String);
+      }
+    }
+    final labels =
+        DB.instance.get<dynamic>(boxName: walletId, key: 'labels') as Map? ??
+            {};
+
+    outputsList = [];
+
+    for (var i = 0; i < utxos.length; i++) {
+      if (labels[utxos[i].txid] != null) {
+        utxos[i].txName = labels[utxos[i].txid] as String? ?? "";
+      } else {
+        utxos[i].txName = 'Output #$i';
+      }
+
+      if (utxos[i].status.confirmed == false) {
+        outputsList.add(utxos[i]);
+      } else {
+        if (lst.contains(utxos[i].txid)) {
+          utxos[i].blocked = true;
+          outputsList.add(utxos[i]);
+        } else if (!lst.contains(utxos[i].txid)) {
+          outputsList.add(utxos[i]);
+        }
+      }
+    }
+  }
+
+  Future<int> getTxCount({required String address}) async {
+    String? scripthash;
+    try {
+      scripthash = _convertToScriptHash(address, _network);
+      final transactions =
+          await electrumXClient.getHistory(scripthash: scripthash);
+      return transactions.length;
+    } catch (e) {
+      Logging.instance.log(
+          "Exception rethrown in _getTxCount(address: $address, scripthash: $scripthash): $e",
+          level: LogLevel.Error);
+      rethrow;
+    }
+  }
+
+  Future<Map<String, int>> _getBatchTxCount({
+    required Map<String, String> addresses,
+  }) async {
+    try {
+      final Map<String, List<dynamic>> args = {};
+      for (final entry in addresses.entries) {
+        args[entry.key] = [_convertToScriptHash(entry.value, _network)];
+      }
+      final response = await electrumXClient.getBatchHistory(args: args);
+
+      final Map<String, int> result = {};
+      for (final entry in response.entries) {
+        result[entry.key] = entry.value.length;
+      }
+      return result;
+    } catch (e, s) {
+      Logging.instance.log(
+          "Exception rethrown in _getBatchTxCount(address: $addresses: $e\n$s",
+          level: LogLevel.Error);
+      rethrow;
+    }
+  }
+
+  Future<void> _checkReceivingAddressForTransactions(
+      DerivePathType derivePathType) async {
+    try {
+      final String currentExternalAddr =
+          await _getCurrentAddressForChain(0, derivePathType);
+      final int txCount = await getTxCount(address: currentExternalAddr);
+      Logging.instance.log(
+          'Number of txs for current receiving address $currentExternalAddr: $txCount',
+          level: LogLevel.Info);
+
+      if (txCount >= 1) {
+        // First increment the receiving index
+        await _incrementAddressIndexForChain(0, derivePathType);
+
+        // Check the new receiving index
+        String indexKey = "receivingIndex";
+        switch (derivePathType) {
+          case DerivePathType.bip44:
+            indexKey += "P2PKH";
+            break;
+          case DerivePathType.bip84:
+            indexKey += "P2WPKH";
+            break;
+        }
+        final newReceivingIndex =
+            DB.instance.get<dynamic>(boxName: walletId, key: indexKey) as int;
+
+        // Use new index to derive a new receiving address
+        final newReceivingAddress = await _generateAddressForChain(
+            0, newReceivingIndex, derivePathType);
+
+        // Add that new receiving address to the array of receiving addresses
+        await _addToAddressesArrayForChain(
+            newReceivingAddress, 0, derivePathType);
+
+        // Set the new receiving address that the service
+
+        switch (derivePathType) {
+          case DerivePathType.bip44:
+            _currentReceivingAddressP2PKH = Future(() => newReceivingAddress);
+            break;
+          case DerivePathType.bip84:
+            _currentReceivingAddress = Future(() => newReceivingAddress);
+            break;
+        }
+      }
+    } catch (e, s) {
+      Logging.instance.log(
+          "Exception rethrown from _checkReceivingAddressForTransactions($derivePathType): $e\n$s",
+          level: LogLevel.Error);
+      rethrow;
+    }
+  }
+
+  Future<void> _checkChangeAddressForTransactions(
+      DerivePathType derivePathType) async {
+    try {
+      final String currentExternalAddr =
+          await _getCurrentAddressForChain(1, derivePathType);
+      final int txCount = await getTxCount(address: currentExternalAddr);
+      Logging.instance.log(
+          'Number of txs for current change address $currentExternalAddr: $txCount',
+          level: LogLevel.Info);
+
+      if (txCount >= 1) {
+        // First increment the change index
+        await _incrementAddressIndexForChain(1, derivePathType);
+
+        // Check the new change index
+        String indexKey = "changeIndex";
+        switch (derivePathType) {
+          case DerivePathType.bip44:
+            indexKey += "P2PKH";
+            break;
+          case DerivePathType.bip84:
+            indexKey += "P2WPKH";
+            break;
+        }
+        final newChangeIndex =
+            DB.instance.get<dynamic>(boxName: walletId, key: indexKey) as int;
+
+        // Use new index to derive a new change address
+        final newChangeAddress =
+            await _generateAddressForChain(1, newChangeIndex, derivePathType);
+
+        // Add that new receiving address to the array of change addresses
+        await _addToAddressesArrayForChain(newChangeAddress, 1, derivePathType);
+      }
+    } on SocketException catch (se, s) {
+      Logging.instance.log(
+          "SocketException caught in _checkReceivingAddressForTransactions($derivePathType): $se\n$s",
+          level: LogLevel.Error);
+      return;
+    } catch (e, s) {
+      Logging.instance.log(
+          "Exception rethrown from _checkReceivingAddressForTransactions($derivePathType): $e\n$s",
+          level: LogLevel.Error);
+      rethrow;
+    }
+  }
+
+  Future<void> _checkCurrentReceivingAddressesForTransactions() async {
+    try {
+      for (final type in DerivePathType.values) {
+        await _checkReceivingAddressForTransactions(type);
+      }
+    } catch (e, s) {
+      Logging.instance.log(
+          "Exception rethrown from _checkCurrentReceivingAddressesForTransactions(): $e\n$s",
+          level: LogLevel.Error);
+      rethrow;
+    }
+  }
+
+  /// public wrapper because dart can't test private...
+  Future<void> checkCurrentReceivingAddressesForTransactions() async {
+    if (Platform.environment["FLUTTER_TEST"] == "true") {
+      try {
+        return _checkCurrentReceivingAddressesForTransactions();
+      } catch (_) {
+        rethrow;
+      }
+    }
+  }
+
+  Future<void> _checkCurrentChangeAddressesForTransactions() async {
+    try {
+      for (final type in DerivePathType.values) {
+        await _checkChangeAddressForTransactions(type);
+      }
+    } catch (e, s) {
+      Logging.instance.log(
+          "Exception rethrown from _checkCurrentChangeAddressesForTransactions(): $e\n$s",
+          level: LogLevel.Error);
+      rethrow;
+    }
+  }
+
+  /// public wrapper because dart can't test private...
+  Future<void> checkCurrentChangeAddressesForTransactions() async {
+    if (Platform.environment["FLUTTER_TEST"] == "true") {
+      try {
+        return _checkCurrentChangeAddressesForTransactions();
+      } catch (_) {
+        rethrow;
+      }
+    }
+  }
+
+  /// attempts to convert a string to a valid scripthash
+  ///
+  /// Returns the scripthash or throws an exception on invalid particl address
+  String _convertToScriptHash(String particlAddress, NetworkType network) {
+    try {
+      final output = Address.addressToOutputScript(
+          particlAddress, network, particl.bech32!);
+      final hash = sha256.convert(output.toList(growable: false)).toString();
+
+      final chars = hash.split("");
+      final reversedPairs = <String>[];
+      var i = chars.length - 1;
+      while (i > 0) {
+        reversedPairs.add(chars[i - 1]);
+        reversedPairs.add(chars[i]);
+        i -= 2;
+      }
+      return reversedPairs.join("");
+    } catch (e) {
+      rethrow;
+    }
+  }
+
+  Future<List<Map<String, dynamic>>> _fetchHistory(
+      List<String> allAddresses) async {
+    try {
+      List<Map<String, dynamic>> allTxHashes = [];
+
+      final Map<int, Map<String, List<dynamic>>> batches = {};
+      final Map<String, String> requestIdToAddressMap = {};
+      const batchSizeMax = 100;
+      int batchNumber = 0;
+      for (int i = 0; i < allAddresses.length; i++) {
+        if (batches[batchNumber] == null) {
+          batches[batchNumber] = {};
+        }
+        final scripthash = _convertToScriptHash(allAddresses[i], _network);
+        final id = Logger.isTestEnv ? "$i" : const Uuid().v1();
+        requestIdToAddressMap[id] = allAddresses[i];
+        batches[batchNumber]!.addAll({
+          id: [scripthash]
+        });
+        if (i % batchSizeMax == batchSizeMax - 1) {
+          batchNumber++;
+        }
+      }
+
+      for (int i = 0; i < batches.length; i++) {
+        final response =
+            await _electrumXClient.getBatchHistory(args: batches[i]!);
+        for (final entry in response.entries) {
+          for (int j = 0; j < entry.value.length; j++) {
+            entry.value[j]["address"] = requestIdToAddressMap[entry.key];
+            if (!allTxHashes.contains(entry.value[j])) {
+              allTxHashes.add(entry.value[j]);
+            }
+          }
+        }
+      }
+
+      return allTxHashes;
+    } catch (e, s) {
+      Logging.instance.log("_fetchHistory: $e\n$s", level: LogLevel.Error);
+      rethrow;
+    }
+  }
+
+  bool _duplicateTxCheck(
+      List<Map<String, dynamic>> allTransactions, String txid) {
+    for (int i = 0; i < allTransactions.length; i++) {
+      if (allTransactions[i]["txid"] == txid) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  Future<List<Map<String, dynamic>>> fastFetch(List<String> allTxHashes) async {
+    List<Map<String, dynamic>> allTransactions = [];
+
+    const futureLimit = 30;
+    List<Future<Map<String, dynamic>>> transactionFutures = [];
+    int currentFutureCount = 0;
+    for (final txHash in allTxHashes) {
+      Future<Map<String, dynamic>> transactionFuture =
+          cachedElectrumXClient.getTransaction(
+        txHash: txHash,
+        verbose: true,
+        coin: coin,
+      );
+      transactionFutures.add(transactionFuture);
+      currentFutureCount++;
+      if (currentFutureCount > futureLimit) {
+        currentFutureCount = 0;
+        await Future.wait(transactionFutures);
+        for (final fTx in transactionFutures) {
+          final tx = await fTx;
+
+          allTransactions.add(tx);
+        }
+      }
+    }
+    if (currentFutureCount != 0) {
+      currentFutureCount = 0;
+      await Future.wait(transactionFutures);
+      for (final fTx in transactionFutures) {
+        final tx = await fTx;
+
+        allTransactions.add(tx);
+      }
+    }
+    return allTransactions;
+  }
+
+  Future<TransactionData> _fetchTransactionData() async {
+    final List<String> allAddresses = await _fetchAllOwnAddresses();
+
+    final changeAddresses = DB.instance.get<dynamic>(
+        boxName: walletId, key: 'changeAddressesP2WPKH') as List<dynamic>;
+
+    final List<Map<String, dynamic>> allTxHashes =
+        await _fetchHistory(allAddresses);
+
+    final cachedTransactions =
+        DB.instance.get<dynamic>(boxName: walletId, key: 'latest_tx_model')
+            as TransactionData?;
+    int latestTxnBlockHeight =
+        DB.instance.get<dynamic>(boxName: walletId, key: "storedTxnDataHeight")
+                as int? ??
+            0;
+
+    final unconfirmedCachedTransactions =
+        cachedTransactions?.getAllTransactions() ?? {};
+    unconfirmedCachedTransactions
+        .removeWhere((key, value) => value.confirmedStatus);
+
+    if (cachedTransactions != null) {
+      for (final tx in allTxHashes.toList(growable: false)) {
+        final txHeight = tx["height"] as int;
+        if (txHeight > 0 &&
+            txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) {
+          if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) {
+            allTxHashes.remove(tx);
+          }
+        }
+      }
+    }
+
+    Set<String> hashes = {};
+    for (var element in allTxHashes) {
+      hashes.add(element['tx_hash'] as String);
+    }
+    await fastFetch(hashes.toList());
+    List<Map<String, dynamic>> allTransactions = [];
+
+    for (final txHash in allTxHashes) {
+      final tx = await cachedElectrumXClient.getTransaction(
+        txHash: txHash["tx_hash"] as String,
+        verbose: true,
+        coin: coin,
+      );
+
+      // Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}");
+      // TODO fix this for sent to self transactions?
+      if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) {
+        tx["address"] = txHash["address"];
+        tx["height"] = txHash["height"];
+        allTransactions.add(tx);
+      }
+    }
+
+    Logging.instance.log("addAddresses: $allAddresses", level: LogLevel.Info);
+    Logging.instance.log("allTxHashes: $allTxHashes", level: LogLevel.Info);
+
+    Logging.instance.log("allTransactions length: ${allTransactions.length}",
+        level: LogLevel.Info);
+
+    final priceData =
+        await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency);
+    Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
+    final List<Map<String, dynamic>> midSortedArray = [];
+
+    Set<String> vHashes = {};
+    for (final txObject in allTransactions) {
+      for (int i = 0; i < (txObject["vin"] as List).length; i++) {
+        final input = txObject["vin"]![i] as Map;
+        final prevTxid = input["txid"] as String;
+        vHashes.add(prevTxid);
+      }
+    }
+    await fastFetch(vHashes.toList());
+
+    for (final txObject in allTransactions) {
+      List<String> sendersArray = [];
+      List<String> recipientsArray = [];
+
+      // Usually only has value when txType = 'Send'
+      int inputAmtSentFromWallet = 0;
+      // Usually has value regardless of txType due to change addresses
+      int outputAmtAddressedToWallet = 0;
+      int fee = 0;
+
+      Map<String, dynamic> midSortedTx = {};
+
+      for (int i = 0; i < (txObject["vin"] as List).length; i++) {
+        final input = txObject["vin"]![i] as Map;
+        final prevTxid = input["txid"] as String;
+        final prevOut = input["vout"] as int;
+
+        final tx = await _cachedElectrumXClient.getTransaction(
+          txHash: prevTxid,
+          coin: coin,
+        );
+
+        for (final out in tx["vout"] as List) {
+          if (prevOut == out["n"]) {
+            final address = out["scriptPubKey"]["address"] as String?;
+            if (address != null) {
+              sendersArray.add(address);
+            }
+          }
+        }
+      }
+
+      Logging.instance.log("sendersArray: $sendersArray", level: LogLevel.Info);
+
+      for (final output in txObject["vout"] as List) {
+        // Particl has different tx types that need to be detected and handled here
+        if (output.containsKey('scriptPubKey') as bool) {
+          // Logging.instance.log("output is transparent", level: LogLevel.Info);
+          final address = output["scriptPubKey"]["address"] as String?;
+          if (address != null) {
+            recipientsArray.add(address);
+          }
+        } else if (output.containsKey('ct_fee') as bool) {
+          // or type: data
+          Logging.instance.log("output is blinded (CT)", level: LogLevel.Info);
+        } else if (output.containsKey('rangeproof') as bool) {
+          // or valueCommitment or type: anon
+          Logging.instance
+              .log("output is private (RingCT)", level: LogLevel.Info);
+        } else {
+          // TODO detect staking
+          Logging.instance.log("output type not detected; output: ${output}",
+              level: LogLevel.Info);
+        }
+      }
+
+      Logging.instance
+          .log("recipientsArray: $recipientsArray", level: LogLevel.Info);
+
+      final foundInSenders =
+          allAddresses.any((element) => sendersArray.contains(element));
+      Logging.instance
+          .log("foundInSenders: $foundInSenders", level: LogLevel.Info);
+
+      // If txType = Sent, then calculate inputAmtSentFromWallet
+      if (foundInSenders) {
+        int totalInput = 0;
+        for (int i = 0; i < (txObject["vin"] as List).length; i++) {
+          final input = txObject["vin"]![i] as Map;
+          final prevTxid = input["txid"] as String;
+          final prevOut = input["vout"] as int;
+          final tx = await _cachedElectrumXClient.getTransaction(
+            txHash: prevTxid,
+            coin: coin,
+          );
+
+          for (final out in tx["vout"] as List) {
+            if (prevOut == out["n"]) {
+              inputAmtSentFromWallet +=
+                  (Decimal.parse(out["value"]!.toString()) *
+                          Decimal.fromInt(Constants.satsPerCoin(coin)))
+                      .toBigInt()
+                      .toInt();
+            }
+          }
+        }
+        totalInput = inputAmtSentFromWallet;
+        int totalOutput = 0;
+
+        Logging.instance.log("txObject: ${txObject}", level: LogLevel.Info);
+
+        for (final output in txObject["vout"] as List) {
+          // Particl has different tx types that need to be detected and handled here
+          if (output.containsKey('scriptPubKey') as bool) {
+            // Logging.instance.log("output is transparent", level: LogLevel.Info);
+            final String address = output["scriptPubKey"]!["address"] as String;
+            final value = output["value"]!;
+            final _value = (Decimal.parse(value.toString()) *
+                    Decimal.fromInt(Constants.satsPerCoin(coin)))
+                .toBigInt()
+                .toInt();
+            totalOutput += _value;
+            if (changeAddresses.contains(address)) {
+              inputAmtSentFromWallet -= _value;
+            } else {
+              // change address from 'sent from' to the 'sent to' address
+              txObject["address"] = address;
+            }
+          } else if (output.containsKey('ct_fee') as bool) {
+            // or type: data
+            // TODO handle CT tx
+            Logging.instance.log(
+                "output is blinded (CT); cannot parse output values",
+                level: LogLevel.Info);
+            final ct_fee = output["ct_fee"]!;
+            final fee_value = (Decimal.parse(ct_fee.toString()) *
+                    Decimal.fromInt(Constants.satsPerCoin(coin)))
+                .toBigInt()
+                .toInt();
+            Logging.instance.log(
+                "ct_fee ${ct_fee} subtracted from inputAmtSentFromWallet ${inputAmtSentFromWallet}",
+                level: LogLevel.Info);
+            inputAmtSentFromWallet += fee_value;
+          } else if (output.containsKey('rangeproof') as bool) {
+            // or valueCommitment or type: anon
+            // TODO handle RingCT tx
+            Logging.instance.log(
+                "output is private (RingCT); cannot parse output values",
+                level: LogLevel.Info);
+          } else {
+            // TODO detect staking
+            Logging.instance.log("output type not detected; output: ${output}",
+                level: LogLevel.Info);
+          }
+        }
+        // calculate transaction fee
+        fee = totalInput - totalOutput;
+        // subtract fee from sent to calculate correct value of sent tx
+        inputAmtSentFromWallet -= fee;
+      } else {
+        // counters for fee calculation
+        int totalOut = 0;
+        int totalIn = 0;
+
+        // add up received tx value
+        for (final output in txObject["vout"] as List) {
+          final address = output["scriptPubKey"]["address"];
+          if (address != null) {
+            final value = (Decimal.parse(output["value"].toString()) *
+                    Decimal.fromInt(Constants.satsPerCoin(coin)))
+                .toBigInt()
+                .toInt();
+            totalOut += value;
+            if (allAddresses.contains(address)) {
+              outputAmtAddressedToWallet += value;
+            }
+          }
+        }
+
+        // calculate fee for received tx
+        for (int i = 0; i < (txObject["vin"] as List).length; i++) {
+          final input = txObject["vin"][i] as Map;
+          final prevTxid = input["txid"] as String;
+          final prevOut = input["vout"] as int;
+          final tx = await _cachedElectrumXClient.getTransaction(
+            txHash: prevTxid,
+            coin: coin,
+          );
+
+          for (final out in tx["vout"] as List) {
+            if (prevOut == out["n"]) {
+              totalIn += (Decimal.parse(out["value"].toString()) *
+                      Decimal.fromInt(Constants.satsPerCoin(coin)))
+                  .toBigInt()
+                  .toInt();
+            }
+          }
+        }
+        fee = totalIn - totalOut;
+      }
+
+      // create final tx map
+      midSortedTx["txid"] = txObject["txid"];
+      midSortedTx["confirmed_status"] = (txObject["confirmations"] != null) &&
+          (txObject["confirmations"] as int >= MINIMUM_CONFIRMATIONS);
+      midSortedTx["confirmations"] = txObject["confirmations"] ?? 0;
+      midSortedTx["timestamp"] = txObject["blocktime"] ??
+          (DateTime.now().millisecondsSinceEpoch ~/ 1000);
+
+      if (foundInSenders) {
+        midSortedTx["txType"] = "Sent";
+        midSortedTx["amount"] = inputAmtSentFromWallet;
+        final String worthNow =
+            ((currentPrice * Decimal.fromInt(inputAmtSentFromWallet)) /
+                    Decimal.fromInt(Constants.satsPerCoin(coin)))
+                .toDecimal(scaleOnInfinitePrecision: 2)
+                .toStringAsFixed(2);
+        midSortedTx["worthNow"] = worthNow;
+        midSortedTx["worthAtBlockTimestamp"] = worthNow;
+      } else {
+        midSortedTx["txType"] = "Received";
+        midSortedTx["amount"] = outputAmtAddressedToWallet;
+        final worthNow =
+            ((currentPrice * Decimal.fromInt(outputAmtAddressedToWallet)) /
+                    Decimal.fromInt(Constants.satsPerCoin(coin)))
+                .toDecimal(scaleOnInfinitePrecision: 2)
+                .toStringAsFixed(2);
+        midSortedTx["worthNow"] = worthNow;
+      }
+      midSortedTx["aliens"] = <dynamic>[];
+      midSortedTx["fees"] = fee;
+      midSortedTx["address"] = txObject["address"];
+      midSortedTx["inputSize"] = txObject["vin"].length;
+      midSortedTx["outputSize"] = txObject["vout"].length;
+      midSortedTx["inputs"] = txObject["vin"];
+      midSortedTx["outputs"] = txObject["vout"];
+
+      final int height = txObject["height"] as int;
+      midSortedTx["height"] = height;
+
+      if (height >= latestTxnBlockHeight) {
+        latestTxnBlockHeight = height;
+      }
+
+      midSortedArray.add(midSortedTx);
+    }
+
+    // sort by date  ----  //TODO not sure if needed
+    // shouldn't be any issues with a null timestamp but I got one at some point?
+    midSortedArray
+        .sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int));
+    // {
+    //   final aT = a["timestamp"];
+    //   final bT = b["timestamp"];
+    //
+    //   if (aT == null && bT == null) {
+    //     return 0;
+    //   } else if (aT == null) {
+    //     return -1;
+    //   } else if (bT == null) {
+    //     return 1;
+    //   } else {
+    //     return bT - aT;
+    //   }
+    // });
+
+    // buildDateTimeChunks
+    final Map<String, dynamic> result = {"dateTimeChunks": <dynamic>[]};
+    final dateArray = <dynamic>[];
+
+    for (int i = 0; i < midSortedArray.length; i++) {
+      final txObject = midSortedArray[i];
+      final date = extractDateFromTimestamp(txObject["timestamp"] as int);
+      final txTimeArray = [txObject["timestamp"], date];
+
+      if (dateArray.contains(txTimeArray[1])) {
+        result["dateTimeChunks"].forEach((dynamic chunk) {
+          if (extractDateFromTimestamp(chunk["timestamp"] as int) ==
+              txTimeArray[1]) {
+            if (chunk["transactions"] == null) {
+              chunk["transactions"] = <Map<String, dynamic>>[];
+            }
+            chunk["transactions"].add(txObject);
+          }
+        });
+      } else {
+        dateArray.add(txTimeArray[1]);
+        final chunk = {
+          "timestamp": txTimeArray[0],
+          "transactions": [txObject],
+        };
+        result["dateTimeChunks"].add(chunk);
+      }
+    }
+
+    final transactionsMap = cachedTransactions?.getAllTransactions() ?? {};
+    transactionsMap
+        .addAll(TransactionData.fromJson(result).getAllTransactions());
+
+    final txModel = TransactionData.fromMap(transactionsMap);
+
+    await DB.instance.put<dynamic>(
+        boxName: walletId,
+        key: 'storedTxnDataHeight',
+        value: latestTxnBlockHeight);
+    await DB.instance.put<dynamic>(
+        boxName: walletId, key: 'latest_tx_model', value: txModel);
+
+    return txModel;
+  }
+
+  int estimateTxFee({required int vSize, required int feeRatePerKB}) {
+    return vSize * (feeRatePerKB / 1000).ceil();
+  }
+
+  /// The coinselection algorithm decides whether or not the user is eligible to make the transaction
+  /// with [satoshiAmountToSend] and [selectedTxFeeRate]. If so, it will call buildTrasaction() and return
+  /// a map containing the tx hex along with other important information. If not, then it will return
+  /// an integer (1 or 2)
+  dynamic coinSelection(
+    int satoshiAmountToSend,
+    int selectedTxFeeRate,
+    String _recipientAddress,
+    bool isSendAll, {
+    int additionalOutputs = 0,
+    List<UtxoObject>? utxos,
+  }) async {
+    Logging.instance
+        .log("Starting coinSelection ----------", level: LogLevel.Info);
+    final List<UtxoObject> availableOutputs = utxos ?? outputsList;
+    final List<UtxoObject> spendableOutputs = [];
+    int spendableSatoshiValue = 0;
+    print("AVAILABLE UTXOS IS ::::: ${availableOutputs}");
+    // Build list of spendable outputs and totaling their satoshi amount
+    for (var i = 0; i < availableOutputs.length; i++) {
+      if (availableOutputs[i].blocked == false &&
+          availableOutputs[i].status.confirmed == true) {
+        spendableOutputs.add(availableOutputs[i]);
+        spendableSatoshiValue += availableOutputs[i].value;
+      }
+    }
+
+    // sort spendable by age (oldest first)
+    spendableOutputs.sort(
+        (a, b) => b.status.confirmations.compareTo(a.status.confirmations));
+
+    Logging.instance.log("spendableOutputs.length: ${spendableOutputs.length}",
+        level: LogLevel.Info);
+    Logging.instance
+        .log("spendableOutputs: $spendableOutputs", level: LogLevel.Info);
+    Logging.instance.log("spendableSatoshiValue: $spendableSatoshiValue",
+        level: LogLevel.Info);
+    Logging.instance
+        .log("satoshiAmountToSend: $satoshiAmountToSend", level: LogLevel.Info);
+    // If the amount the user is trying to send is smaller than the amount that they have spendable,
+    // then return 1, which indicates that they have an insufficient balance.
+    if (spendableSatoshiValue < satoshiAmountToSend) {
+      return 1;
+      // If the amount the user wants to send is exactly equal to the amount they can spend, then return
+      // 2, which indicates that they are not leaving enough over to pay the transaction fee
+    } else if (spendableSatoshiValue == satoshiAmountToSend && !isSendAll) {
+      return 2;
+    }
+    // If neither of these statements pass, we assume that the user has a spendable balance greater
+    // than the amount they're attempting to send. Note that this value still does not account for
+    // the added transaction fee, which may require an extra input and will need to be checked for
+    // later on.
+
+    // Possible situation right here
+    int satoshisBeingUsed = 0;
+    int inputsBeingConsumed = 0;
+    List<UtxoObject> utxoObjectsToUse = [];
+
+    for (var i = 0;
+        satoshisBeingUsed < satoshiAmountToSend && i < spendableOutputs.length;
+        i++) {
+      utxoObjectsToUse.add(spendableOutputs[i]);
+      satoshisBeingUsed += spendableOutputs[i].value;
+      inputsBeingConsumed += 1;
+    }
+    for (int i = 0;
+        i < additionalOutputs && inputsBeingConsumed < spendableOutputs.length;
+        i++) {
+      utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]);
+      satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value;
+      inputsBeingConsumed += 1;
+    }
+
+    Logging.instance
+        .log("satoshisBeingUsed: $satoshisBeingUsed", level: LogLevel.Info);
+    Logging.instance
+        .log("inputsBeingConsumed: $inputsBeingConsumed", level: LogLevel.Info);
+    Logging.instance
+        .log('utxoObjectsToUse: $utxoObjectsToUse', level: LogLevel.Info);
+
+    // numberOfOutputs' length must always be equal to that of recipientsArray and recipientsAmtArray
+    List<String> recipientsArray = [_recipientAddress];
+    List<int> recipientsAmtArray = [satoshiAmountToSend];
+
+    // gather required signing data
+    final utxoSigningData = await fetchBuildTxData(utxoObjectsToUse);
+
+    if (isSendAll) {
+      Logging.instance
+          .log("Attempting to send all $coin", level: LogLevel.Info);
+
+      final int vSizeForOneOutput = (await buildTransaction(
+        utxosToUse: utxoObjectsToUse,
+        utxoSigningData: utxoSigningData,
+        recipients: [_recipientAddress],
+        satoshiAmounts: [satoshisBeingUsed - 1],
+      ))["vSize"] as int;
+      int feeForOneOutput = estimateTxFee(
+        vSize: vSizeForOneOutput,
+        feeRatePerKB: selectedTxFeeRate,
+      );
+
+      final int roughEstimate =
+          roughFeeEstimate(spendableOutputs.length, 1, selectedTxFeeRate);
+      if (feeForOneOutput < roughEstimate) {
+        feeForOneOutput = roughEstimate;
+      }
+
+      final int amount = satoshiAmountToSend - feeForOneOutput;
+      dynamic txn = await buildTransaction(
+        utxosToUse: utxoObjectsToUse,
+        utxoSigningData: utxoSigningData,
+        recipients: recipientsArray,
+        satoshiAmounts: [amount],
+      );
+      Map<String, dynamic> transactionObject = {
+        "hex": txn["hex"],
+        "recipient": recipientsArray[0],
+        "recipientAmt": amount,
+        "fee": feeForOneOutput,
+        "vSize": txn["vSize"],
+      };
+      return transactionObject;
+    }
+
+    final int vSizeForOneOutput = (await buildTransaction(
+      utxosToUse: utxoObjectsToUse,
+      utxoSigningData: utxoSigningData,
+      recipients: [_recipientAddress],
+      satoshiAmounts: [satoshisBeingUsed - 1],
+    ))["vSize"] as int;
+    final int vSizeForTwoOutPuts = (await buildTransaction(
+      utxosToUse: utxoObjectsToUse,
+      utxoSigningData: utxoSigningData,
+      recipients: [
+        _recipientAddress,
+        await _getCurrentAddressForChain(1, DerivePathType.bip84),
+      ],
+      satoshiAmounts: [
+        satoshiAmountToSend,
+        satoshisBeingUsed - satoshiAmountToSend - 1
+      ], // dust limit is the minimum amount a change output should be
+    ))["vSize"] as int;
+
+    // Assume 1 output, only for recipient and no change
+    final feeForOneOutput = estimateTxFee(
+      vSize: vSizeForOneOutput,
+      feeRatePerKB: selectedTxFeeRate,
+    );
+    // Assume 2 outputs, one for recipient and one for change
+    final feeForTwoOutputs = estimateTxFee(
+      vSize: vSizeForTwoOutPuts,
+      feeRatePerKB: selectedTxFeeRate,
+    );
+
+    Logging.instance
+        .log("feeForTwoOutputs: $feeForTwoOutputs", level: LogLevel.Info);
+    Logging.instance
+        .log("feeForOneOutput: $feeForOneOutput", level: LogLevel.Info);
+
+    if (satoshisBeingUsed - satoshiAmountToSend > feeForOneOutput) {
+      if (satoshisBeingUsed - satoshiAmountToSend >
+          feeForOneOutput + DUST_LIMIT) {
+        // Here, we know that theoretically, we may be able to include another output(change) but we first need to
+        // factor in the value of this output in satoshis.
+        int changeOutputSize =
+            satoshisBeingUsed - satoshiAmountToSend - feeForTwoOutputs;
+        // We check to see if the user can pay for the new transaction with 2 outputs instead of one. If they can and
+        // the second output's size > DUST_LIMIT satoshis, we perform the mechanics required to properly generate and use a new
+        // change address.
+        if (changeOutputSize > DUST_LIMIT &&
+            satoshisBeingUsed - satoshiAmountToSend - changeOutputSize ==
+                feeForTwoOutputs) {
+          // generate new change address if current change address has been used
+          await _checkChangeAddressForTransactions(DerivePathType.bip84);
+          final String newChangeAddress =
+              await _getCurrentAddressForChain(1, DerivePathType.bip84);
+
+          int feeBeingPaid =
+              satoshisBeingUsed - satoshiAmountToSend - changeOutputSize;
+
+          recipientsArray.add(newChangeAddress);
+          recipientsAmtArray.add(changeOutputSize);
+          // At this point, we have the outputs we're going to use, the amounts to send along with which addresses
+          // we intend to send these amounts to. We have enough to send instructions to build the transaction.
+          Logging.instance.log('2 outputs in tx', level: LogLevel.Info);
+          Logging.instance
+              .log('Input size: $satoshisBeingUsed', level: LogLevel.Info);
+          Logging.instance.log('Recipient output size: $satoshiAmountToSend',
+              level: LogLevel.Info);
+          Logging.instance.log('Change Output Size: $changeOutputSize',
+              level: LogLevel.Info);
+          Logging.instance.log(
+              'Difference (fee being paid): $feeBeingPaid sats',
+              level: LogLevel.Info);
+          Logging.instance
+              .log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info);
+          dynamic txn = await buildTransaction(
+            utxosToUse: utxoObjectsToUse,
+            utxoSigningData: utxoSigningData,
+            recipients: recipientsArray,
+            satoshiAmounts: recipientsAmtArray,
+          );
+
+          // make sure minimum fee is accurate if that is being used
+          if (txn["vSize"] - feeBeingPaid == 1) {
+            int changeOutputSize =
+                satoshisBeingUsed - satoshiAmountToSend - (txn["vSize"] as int);
+            feeBeingPaid =
+                satoshisBeingUsed - satoshiAmountToSend - changeOutputSize;
+            recipientsAmtArray.removeLast();
+            recipientsAmtArray.add(changeOutputSize);
+            Logging.instance.log('Adjusted Input size: $satoshisBeingUsed',
+                level: LogLevel.Info);
+            Logging.instance.log(
+                'Adjusted Recipient output size: $satoshiAmountToSend',
+                level: LogLevel.Info);
+            Logging.instance.log(
+                'Adjusted Change Output Size: $changeOutputSize',
+                level: LogLevel.Info);
+            Logging.instance.log(
+                'Adjusted Difference (fee being paid): $feeBeingPaid sats',
+                level: LogLevel.Info);
+            Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs',
+                level: LogLevel.Info);
+            txn = await buildTransaction(
+              utxosToUse: utxoObjectsToUse,
+              utxoSigningData: utxoSigningData,
+              recipients: recipientsArray,
+              satoshiAmounts: recipientsAmtArray,
+            );
+          }
+
+          Map<String, dynamic> transactionObject = {
+            "hex": txn["hex"],
+            "recipient": recipientsArray[0],
+            "recipientAmt": recipientsAmtArray[0],
+            "fee": feeBeingPaid,
+            "vSize": txn["vSize"],
+          };
+          return transactionObject;
+        } else {
+          // Something went wrong here. It either overshot or undershot the estimated fee amount or the changeOutputSize
+          // is smaller than or equal to DUST_LIMIT. Revert to single output transaction.
+          Logging.instance.log('1 output in tx', level: LogLevel.Info);
+          Logging.instance
+              .log('Input size: $satoshisBeingUsed', level: LogLevel.Info);
+          Logging.instance.log('Recipient output size: $satoshiAmountToSend',
+              level: LogLevel.Info);
+          Logging.instance.log(
+              'Difference (fee being paid): ${satoshisBeingUsed - satoshiAmountToSend} sats',
+              level: LogLevel.Info);
+          Logging.instance
+              .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
+          dynamic txn = await buildTransaction(
+            utxosToUse: utxoObjectsToUse,
+            utxoSigningData: utxoSigningData,
+            recipients: recipientsArray,
+            satoshiAmounts: recipientsAmtArray,
+          );
+          Map<String, dynamic> transactionObject = {
+            "hex": txn["hex"],
+            "recipient": recipientsArray[0],
+            "recipientAmt": recipientsAmtArray[0],
+            "fee": satoshisBeingUsed - satoshiAmountToSend,
+            "vSize": txn["vSize"],
+          };
+          return transactionObject;
+        }
+      } else {
+        // No additional outputs needed since adding one would mean that it'd be smaller than DUST_LIMIT sats
+        // which makes it uneconomical to add to the transaction. Here, we pass data directly to instruct
+        // the wallet to begin crafting the transaction that the user requested.
+        Logging.instance.log('1 output in tx', level: LogLevel.Info);
+        Logging.instance
+            .log('Input size: $satoshisBeingUsed', level: LogLevel.Info);
+        Logging.instance.log('Recipient output size: $satoshiAmountToSend',
+            level: LogLevel.Info);
+        Logging.instance.log(
+            'Difference (fee being paid): ${satoshisBeingUsed - satoshiAmountToSend} sats',
+            level: LogLevel.Info);
+        Logging.instance
+            .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
+        dynamic txn = await buildTransaction(
+          utxosToUse: utxoObjectsToUse,
+          utxoSigningData: utxoSigningData,
+          recipients: recipientsArray,
+          satoshiAmounts: recipientsAmtArray,
+        );
+        Map<String, dynamic> transactionObject = {
+          "hex": txn["hex"],
+          "recipient": recipientsArray[0],
+          "recipientAmt": recipientsAmtArray[0],
+          "fee": satoshisBeingUsed - satoshiAmountToSend,
+          "vSize": txn["vSize"],
+        };
+        return transactionObject;
+      }
+    } else if (satoshisBeingUsed - satoshiAmountToSend == feeForOneOutput) {
+      // In this scenario, no additional change output is needed since inputs - outputs equal exactly
+      // what we need to pay for fees. Here, we pass data directly to instruct the wallet to begin
+      // crafting the transaction that the user requested.
+      Logging.instance.log('1 output in tx', level: LogLevel.Info);
+      Logging.instance
+          .log('Input size: $satoshisBeingUsed', level: LogLevel.Info);
+      Logging.instance.log('Recipient output size: $satoshiAmountToSend',
+          level: LogLevel.Info);
+      Logging.instance.log(
+          'Fee being paid: ${satoshisBeingUsed - satoshiAmountToSend} sats',
+          level: LogLevel.Info);
+      Logging.instance
+          .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
+      dynamic txn = await buildTransaction(
+        utxosToUse: utxoObjectsToUse,
+        utxoSigningData: utxoSigningData,
+        recipients: recipientsArray,
+        satoshiAmounts: recipientsAmtArray,
+      );
+      Map<String, dynamic> transactionObject = {
+        "hex": txn["hex"],
+        "recipient": recipientsArray[0],
+        "recipientAmt": recipientsAmtArray[0],
+        "fee": feeForOneOutput,
+        "vSize": txn["vSize"],
+      };
+      return transactionObject;
+    } else {
+      // Remember that returning 2 indicates that the user does not have a sufficient balance to
+      // pay for the transaction fee. Ideally, at this stage, we should check if the user has any
+      // additional outputs they're able to spend and then recalculate fees.
+      Logging.instance.log(
+          'Cannot pay tx fee - checking for more outputs and trying again',
+          level: LogLevel.Warning);
+      // try adding more outputs
+      if (spendableOutputs.length > inputsBeingConsumed) {
+        return coinSelection(satoshiAmountToSend, selectedTxFeeRate,
+            _recipientAddress, isSendAll,
+            additionalOutputs: additionalOutputs + 1, utxos: utxos);
+      }
+      return 2;
+    }
+  }
+
+  Future<Map<String, dynamic>> fetchBuildTxData(
+    List<UtxoObject> utxosToUse,
+  ) async {
+    // return data
+    Map<String, dynamic> results = {};
+    Map<String, List<String>> addressTxid = {};
+
+    print("CALLING FETCH BUILD TX DATA");
+
+    // addresses to check
+    List<String> addressesP2PKH = [];
+    List<String> addressesP2WPKH = [];
+
+    try {
+      // Populating the addresses to check
+      for (var i = 0; i < utxosToUse.length; i++) {
+        final txid = utxosToUse[i].txid;
+        final tx = await _cachedElectrumXClient.getTransaction(
+          txHash: txid,
+          coin: coin,
+        );
+
+        for (final output in tx["vout"] as List) {
+          final n = output["n"];
+          if (n != null && n == utxosToUse[i].vout) {
+            print("SCRIPT PUB KEY IS ${output["scriptPubKey"]}");
+            final address = output["scriptPubKey"]["address"] as String;
+            if (!addressTxid.containsKey(address)) {
+              addressTxid[address] = <String>[];
+            }
+            (addressTxid[address] as List).add(txid);
+            switch (addressType(address: address)) {
+              case DerivePathType.bip44:
+                addressesP2PKH.add(address);
+                break;
+              case DerivePathType.bip84:
+                addressesP2WPKH.add(address);
+                break;
+            }
+          }
+        }
+      }
+
+      // p2pkh / bip44
+      final p2pkhLength = addressesP2PKH.length;
+      if (p2pkhLength > 0) {
+        final receiveDerivations = await _fetchDerivations(
+          chain: 0,
+          derivePathType: DerivePathType.bip44,
+        );
+        final changeDerivations = await _fetchDerivations(
+          chain: 1,
+          derivePathType: DerivePathType.bip44,
+        );
+        for (int i = 0; i < p2pkhLength; i++) {
+          // receives
+          final receiveDerivation = receiveDerivations[addressesP2PKH[i]];
+          // if a match exists it will not be null
+          if (receiveDerivation != null) {
+            final data = P2PKH(
+              data: PaymentData(
+                  pubkey: Format.stringToUint8List(
+                      receiveDerivation["pubKey"] as String)),
+              network: _network,
+            ).data;
+
+            for (String tx in addressTxid[addressesP2PKH[i]]!) {
+              results[tx] = {
+                "output": data.output,
+                "keyPair": ECPair.fromWIF(
+                  receiveDerivation["wif"] as String,
+                  network: _network,
+                ),
+              };
+            }
+          } else {
+            // if its not a receive, check change
+            final changeDerivation = changeDerivations[addressesP2PKH[i]];
+            // if a match exists it will not be null
+            if (changeDerivation != null) {
+              final data = P2PKH(
+                data: PaymentData(
+                    pubkey: Format.stringToUint8List(
+                        changeDerivation["pubKey"] as String)),
+                network: _network,
+              ).data;
+
+              for (String tx in addressTxid[addressesP2PKH[i]]!) {
+                results[tx] = {
+                  "output": data.output,
+                  "keyPair": ECPair.fromWIF(
+                    changeDerivation["wif"] as String,
+                    network: _network,
+                  ),
+                };
+              }
+            }
+          }
+        }
+      }
+
+      // p2wpkh / bip84
+      final p2wpkhLength = addressesP2WPKH.length;
+      if (p2wpkhLength > 0) {
+        final receiveDerivations = await _fetchDerivations(
+          chain: 0,
+          derivePathType: DerivePathType.bip84,
+        );
+        final changeDerivations = await _fetchDerivations(
+          chain: 1,
+          derivePathType: DerivePathType.bip84,
+        );
+
+        for (int i = 0; i < p2wpkhLength; i++) {
+          // receives
+          final receiveDerivation = receiveDerivations[addressesP2WPKH[i]];
+          // if a match exists it will not be null
+          if (receiveDerivation != null) {
+            final data = P2WPKH(
+              data: PaymentData(
+                  pubkey: Format.stringToUint8List(
+                      receiveDerivation["pubKey"] as String)),
+              network: _network,
+            ).data;
+
+            for (String tx in addressTxid[addressesP2WPKH[i]]!) {
+              results[tx] = {
+                "output": data.output,
+                "keyPair": ECPair.fromWIF(
+                  receiveDerivation["wif"] as String,
+                  network: _network,
+                ),
+              };
+            }
+          } else {
+            // if its not a receive, check change
+            final changeDerivation = changeDerivations[addressesP2WPKH[i]];
+            // if a match exists it will not be null
+            if (changeDerivation != null) {
+              final data = P2WPKH(
+                data: PaymentData(
+                    pubkey: Format.stringToUint8List(
+                        changeDerivation["pubKey"] as String)),
+                network: _network,
+              ).data;
+
+              for (String tx in addressTxid[addressesP2WPKH[i]]!) {
+                results[tx] = {
+                  "output": data.output,
+                  "keyPair": ECPair.fromWIF(
+                    changeDerivation["wif"] as String,
+                    network: _network,
+                  ),
+                };
+              }
+            }
+          }
+        }
+      }
+      Logging.instance.log("FETCHED TX BUILD DATA IS -----$results",
+          level: LogLevel.Info, printFullLength: true);
+      return results;
+    } catch (e, s) {
+      Logging.instance
+          .log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error);
+      rethrow;
+    }
+  }
+
+  /// Builds and signs a transaction
+  Future<Map<String, dynamic>> buildTransaction({
+    required List<UtxoObject> utxosToUse,
+    required Map<String, dynamic> utxoSigningData,
+    required List<String> recipients,
+    required List<int> satoshiAmounts,
+  }) async {
+    Logging.instance
+        .log("Starting buildTransaction ----------", level: LogLevel.Info);
+
+    Logging.instance.log("UTXOs SIGNING DATA IS -----$utxoSigningData",
+        level: LogLevel.Info, printFullLength: true);
+
+    final txb = TransactionBuilder(network: _network);
+    txb.setVersion(160);
+
+    // Add transaction inputs
+    for (var i = 0; i < utxosToUse.length; i++) {
+      final txid = utxosToUse[i].txid;
+      txb.addInput(txid, utxosToUse[i].vout, null,
+          utxoSigningData[txid]["output"] as Uint8List, '');
+    }
+
+    // Add transaction output
+    for (var i = 0; i < recipients.length; i++) {
+      txb.addOutput(recipients[i], satoshiAmounts[i], particl.bech32!);
+    }
+
+    try {
+      // Sign the transaction accordingly
+      for (var i = 0; i < utxosToUse.length; i++) {
+        final txid = utxosToUse[i].txid;
+        txb.sign(
+            vin: i,
+            keyPair: utxoSigningData[txid]["keyPair"] as ECPair,
+            witnessValue: utxosToUse[i].value,
+            redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?);
+      }
+    } catch (e, s) {
+      Logging.instance.log("Caught exception while signing transaction: $e\n$s",
+          level: LogLevel.Error);
+      rethrow;
+    }
+
+    final builtTx = txb.build();
+    final vSize = builtTx.virtualSize();
+
+    String hexBefore = builtTx.toHex(isParticl: true).toString();
+    if (hexBefore.endsWith('000000')) {
+      String stripped = hexBefore.substring(0, hexBefore.length - 6);
+      return {"hex": stripped, "vSize": vSize};
+    } else if (hexBefore.endsWith('0000')) {
+      String stripped = hexBefore.substring(0, hexBefore.length - 4);
+      return {"hex": stripped, "vSize": vSize};
+    } else if (hexBefore.endsWith('00')) {
+      String stripped = hexBefore.substring(0, hexBefore.length - 2);
+      return {"hex": stripped, "vSize": vSize};
+    } else {
+      return {"hex": hexBefore, "vSize": vSize};
+    }
+  }
+
+  @override
+  Future<void> fullRescan(
+    int maxUnusedAddressGap,
+    int maxNumberOfIndexesToCheck,
+  ) async {
+    Logging.instance.log("Starting full rescan!", level: LogLevel.Info);
+    longMutex = true;
+    GlobalEventBus.instance.fire(
+      WalletSyncStatusChangedEvent(
+        WalletSyncStatus.syncing,
+        walletId,
+        coin,
+      ),
+    );
+
+    // clear cache
+    await _cachedElectrumXClient.clearSharedTransactionCache(coin: coin);
+
+    // back up data
+    await _rescanBackup();
+
+    try {
+      final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic');
+      await _recoverWalletFromBIP32SeedPhrase(
+        mnemonic: mnemonic!,
+        maxUnusedAddressGap: maxUnusedAddressGap,
+        maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck,
+      );
+
+      longMutex = false;
+      Logging.instance.log("Full rescan complete!", level: LogLevel.Info);
+      GlobalEventBus.instance.fire(
+        WalletSyncStatusChangedEvent(
+          WalletSyncStatus.synced,
+          walletId,
+          coin,
+        ),
+      );
+    } catch (e, s) {
+      GlobalEventBus.instance.fire(
+        WalletSyncStatusChangedEvent(
+          WalletSyncStatus.unableToSync,
+          walletId,
+          coin,
+        ),
+      );
+
+      // restore from backup
+      await _rescanRestore();
+
+      longMutex = false;
+      Logging.instance.log("Exception rethrown from fullRescan(): $e\n$s",
+          level: LogLevel.Error);
+      rethrow;
+    }
+  }
+
+  Future<void> _rescanRestore() async {
+    Logging.instance.log("starting rescan restore", level: LogLevel.Info);
+
+    // restore from backup
+    // p2pkh
+    final tempReceivingAddressesP2PKH = DB.instance
+        .get<dynamic>(boxName: walletId, key: 'receivingAddressesP2PKH_BACKUP');
+    final tempChangeAddressesP2PKH = DB.instance
+        .get<dynamic>(boxName: walletId, key: 'changeAddressesP2PKH_BACKUP');
+    final tempReceivingIndexP2PKH = DB.instance
+        .get<dynamic>(boxName: walletId, key: 'receivingIndexP2PKH_BACKUP');
+    final tempChangeIndexP2PKH = DB.instance
+        .get<dynamic>(boxName: walletId, key: 'changeIndexP2PKH_BACKUP');
+    await DB.instance.put<dynamic>(
+        boxName: walletId,
+        key: 'receivingAddressesP2PKH',
+        value: tempReceivingAddressesP2PKH);
+    await DB.instance.put<dynamic>(
+        boxName: walletId,
+        key: 'changeAddressesP2PKH',
+        value: tempChangeAddressesP2PKH);
+    await DB.instance.put<dynamic>(
+        boxName: walletId,
+        key: 'receivingIndexP2PKH',
+        value: tempReceivingIndexP2PKH);
+    await DB.instance.put<dynamic>(
+        boxName: walletId,
+        key: 'changeIndexP2PKH',
+        value: tempChangeIndexP2PKH);
+    await DB.instance.delete<dynamic>(
+        key: 'receivingAddressesP2PKH_BACKUP', boxName: walletId);
+    await DB.instance
+        .delete<dynamic>(key: 'changeAddressesP2PKH_BACKUP', boxName: walletId);
+    await DB.instance
+        .delete<dynamic>(key: 'receivingIndexP2PKH_BACKUP', boxName: walletId);
+    await DB.instance
+        .delete<dynamic>(key: 'changeIndexP2PKH_BACKUP', boxName: walletId);
+
+    // p2wpkh
+    final tempReceivingAddressesP2WPKH = DB.instance.get<dynamic>(
+        boxName: walletId, key: 'receivingAddressesP2WPKH_BACKUP');
+    final tempChangeAddressesP2WPKH = DB.instance
+        .get<dynamic>(boxName: walletId, key: 'changeAddressesP2WPKH_BACKUP');
+    final tempReceivingIndexP2WPKH = DB.instance
+        .get<dynamic>(boxName: walletId, key: 'receivingIndexP2WPKH_BACKUP');
+    final tempChangeIndexP2WPKH = DB.instance
+        .get<dynamic>(boxName: walletId, key: 'changeIndexP2WPKH_BACKUP');
+    await DB.instance.put<dynamic>(
+        boxName: walletId,
+        key: 'receivingAddressesP2WPKH',
+        value: tempReceivingAddressesP2WPKH);
+    await DB.instance.put<dynamic>(
+        boxName: walletId,
+        key: 'changeAddressesP2WPKH',
+        value: tempChangeAddressesP2WPKH);
+    await DB.instance.put<dynamic>(
+        boxName: walletId,
+        key: 'receivingIndexP2WPKH',
+        value: tempReceivingIndexP2WPKH);
+    await DB.instance.put<dynamic>(
+        boxName: walletId,
+        key: 'changeIndexP2WPKH',
+        value: tempChangeIndexP2WPKH);
+    await DB.instance.delete<dynamic>(
+        key: 'receivingAddressesP2WPKH_BACKUP', boxName: walletId);
+    await DB.instance.delete<dynamic>(
+        key: 'changeAddressesP2WPKH_BACKUP', boxName: walletId);
+    await DB.instance
+        .delete<dynamic>(key: 'receivingIndexP2WPKH_BACKUP', boxName: walletId);
+    await DB.instance
+        .delete<dynamic>(key: 'changeIndexP2WPKH_BACKUP', boxName: walletId);
+
+    // P2PKH derivations
+    final p2pkhReceiveDerivationsString = await _secureStore.read(
+        key: "${walletId}_receiveDerivationsP2PKH_BACKUP");
+    final p2pkhChangeDerivationsString = await _secureStore.read(
+        key: "${walletId}_changeDerivationsP2PKH_BACKUP");
+
+    await _secureStore.write(
+        key: "${walletId}_receiveDerivationsP2PKH",
+        value: p2pkhReceiveDerivationsString);
+    await _secureStore.write(
+        key: "${walletId}_changeDerivationsP2PKH",
+        value: p2pkhChangeDerivationsString);
+
+    await _secureStore.delete(
+        key: "${walletId}_receiveDerivationsP2PKH_BACKUP");
+    await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH_BACKUP");
+
+    // P2WPKH derivations
+    final p2wpkhReceiveDerivationsString = await _secureStore.read(
+        key: "${walletId}_receiveDerivationsP2WPKH_BACKUP");
+    final p2wpkhChangeDerivationsString = await _secureStore.read(
+        key: "${walletId}_changeDerivationsP2WPKH_BACKUP");
+
+    await _secureStore.write(
+        key: "${walletId}_receiveDerivationsP2WPKH",
+        value: p2wpkhReceiveDerivationsString);
+    await _secureStore.write(
+        key: "${walletId}_changeDerivationsP2WPKH",
+        value: p2wpkhChangeDerivationsString);
+
+    await _secureStore.delete(
+        key: "${walletId}_receiveDerivationsP2WPKH_BACKUP");
+    await _secureStore.delete(
+        key: "${walletId}_changeDerivationsP2WPKH_BACKUP");
+
+    // UTXOs
+    final utxoData = DB.instance
+        .get<dynamic>(boxName: walletId, key: 'latest_utxo_model_BACKUP');
+    await DB.instance.put<dynamic>(
+        boxName: walletId, key: 'latest_utxo_model', value: utxoData);
+    await DB.instance
+        .delete<dynamic>(key: 'latest_utxo_model_BACKUP', boxName: walletId);
+
+    Logging.instance.log("rescan restore  complete", level: LogLevel.Info);
+  }
+
+  Future<void> _rescanBackup() async {
+    Logging.instance.log("starting rescan backup", level: LogLevel.Info);
+
+    // backup current and clear data
+    // p2pkh
+    final tempReceivingAddressesP2PKH = DB.instance
+        .get<dynamic>(boxName: walletId, key: 'receivingAddressesP2PKH');
+    await DB.instance.put<dynamic>(
+        boxName: walletId,
+        key: 'receivingAddressesP2PKH_BACKUP',
+        value: tempReceivingAddressesP2PKH);
+    await DB.instance
+        .delete<dynamic>(key: 'receivingAddressesP2PKH', boxName: walletId);
+
+    final tempChangeAddressesP2PKH = DB.instance
+        .get<dynamic>(boxName: walletId, key: 'changeAddressesP2PKH');
+    await DB.instance.put<dynamic>(
+        boxName: walletId,
+        key: 'changeAddressesP2PKH_BACKUP',
+        value: tempChangeAddressesP2PKH);
+    await DB.instance
+        .delete<dynamic>(key: 'changeAddressesP2PKH', boxName: walletId);
+
+    final tempReceivingIndexP2PKH =
+        DB.instance.get<dynamic>(boxName: walletId, key: 'receivingIndexP2PKH');
+    await DB.instance.put<dynamic>(
+        boxName: walletId,
+        key: 'receivingIndexP2PKH_BACKUP',
+        value: tempReceivingIndexP2PKH);
+    await DB.instance
+        .delete<dynamic>(key: 'receivingIndexP2PKH', boxName: walletId);
+
+    final tempChangeIndexP2PKH =
+        DB.instance.get<dynamic>(boxName: walletId, key: 'changeIndexP2PKH');
+    await DB.instance.put<dynamic>(
+        boxName: walletId,
+        key: 'changeIndexP2PKH_BACKUP',
+        value: tempChangeIndexP2PKH);
+    await DB.instance
+        .delete<dynamic>(key: 'changeIndexP2PKH', boxName: walletId);
+
+    // p2wpkh
+    final tempReceivingAddressesP2WPKH = DB.instance
+        .get<dynamic>(boxName: walletId, key: 'receivingAddressesP2WPKH');
+    await DB.instance.put<dynamic>(
+        boxName: walletId,
+        key: 'receivingAddressesP2WPKH_BACKUP',
+        value: tempReceivingAddressesP2WPKH);
+    await DB.instance
+        .delete<dynamic>(key: 'receivingAddressesP2WPKH', boxName: walletId);
+
+    final tempChangeAddressesP2WPKH = DB.instance
+        .get<dynamic>(boxName: walletId, key: 'changeAddressesP2WPKH');
+    await DB.instance.put<dynamic>(
+        boxName: walletId,
+        key: 'changeAddressesP2WPKH_BACKUP',
+        value: tempChangeAddressesP2WPKH);
+    await DB.instance
+        .delete<dynamic>(key: 'changeAddressesP2WPKH', boxName: walletId);
+
+    final tempReceivingIndexP2WPKH = DB.instance
+        .get<dynamic>(boxName: walletId, key: 'receivingIndexP2WPKH');
+    await DB.instance.put<dynamic>(
+        boxName: walletId,
+        key: 'receivingIndexP2WPKH_BACKUP',
+        value: tempReceivingIndexP2WPKH);
+    await DB.instance
+        .delete<dynamic>(key: 'receivingIndexP2WPKH', boxName: walletId);
+
+    final tempChangeIndexP2WPKH =
+        DB.instance.get<dynamic>(boxName: walletId, key: 'changeIndexP2WPKH');
+    await DB.instance.put<dynamic>(
+        boxName: walletId,
+        key: 'changeIndexP2WPKH_BACKUP',
+        value: tempChangeIndexP2WPKH);
+    await DB.instance
+        .delete<dynamic>(key: 'changeIndexP2WPKH', boxName: walletId);
+
+    // P2PKH derivations
+    final p2pkhReceiveDerivationsString =
+        await _secureStore.read(key: "${walletId}_receiveDerivationsP2PKH");
+    final p2pkhChangeDerivationsString =
+        await _secureStore.read(key: "${walletId}_changeDerivationsP2PKH");
+
+    await _secureStore.write(
+        key: "${walletId}_receiveDerivationsP2PKH_BACKUP",
+        value: p2pkhReceiveDerivationsString);
+    await _secureStore.write(
+        key: "${walletId}_changeDerivationsP2PKH_BACKUP",
+        value: p2pkhChangeDerivationsString);
+
+    await _secureStore.delete(key: "${walletId}_receiveDerivationsP2PKH");
+    await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH");
+
+    // P2WPKH derivations
+    final p2wpkhReceiveDerivationsString =
+        await _secureStore.read(key: "${walletId}_receiveDerivationsP2WPKH");
+    final p2wpkhChangeDerivationsString =
+        await _secureStore.read(key: "${walletId}_changeDerivationsP2WPKH");
+
+    await _secureStore.write(
+        key: "${walletId}_receiveDerivationsP2WPKH_BACKUP",
+        value: p2wpkhReceiveDerivationsString);
+    await _secureStore.write(
+        key: "${walletId}_changeDerivationsP2WPKH_BACKUP",
+        value: p2wpkhChangeDerivationsString);
+
+    await _secureStore.delete(key: "${walletId}_receiveDerivationsP2WPKH");
+    await _secureStore.delete(key: "${walletId}_changeDerivationsP2WPKH");
+
+    // UTXOs
+    final utxoData =
+        DB.instance.get<dynamic>(boxName: walletId, key: 'latest_utxo_model');
+    await DB.instance.put<dynamic>(
+        boxName: walletId, key: 'latest_utxo_model_BACKUP', value: utxoData);
+    await DB.instance
+        .delete<dynamic>(key: 'latest_utxo_model', boxName: walletId);
+
+    Logging.instance.log("rescan backup complete", level: LogLevel.Info);
+  }
+
+  bool isActive = false;
+
+  @override
+  void Function(bool)? get onIsActiveWalletChanged =>
+      (isActive) => this.isActive = isActive;
+
+  @override
+  Future<int> estimateFeeFor(int satoshiAmount, int feeRate) async {
+    final available =
+        Format.decimalAmountToSatoshis(await availableBalance, coin);
+
+    if (available == satoshiAmount) {
+      return satoshiAmount - sweepAllEstimate(feeRate);
+    } else if (satoshiAmount <= 0 || satoshiAmount > available) {
+      return roughFeeEstimate(1, 2, feeRate);
+    }
+
+    int runningBalance = 0;
+    int inputCount = 0;
+    for (final output in outputsList) {
+      runningBalance += output.value;
+      inputCount++;
+      if (runningBalance > satoshiAmount) {
+        break;
+      }
+    }
+
+    final oneOutPutFee = roughFeeEstimate(inputCount, 1, feeRate);
+    final twoOutPutFee = roughFeeEstimate(inputCount, 2, feeRate);
+
+    if (runningBalance - satoshiAmount > oneOutPutFee) {
+      if (runningBalance - satoshiAmount > oneOutPutFee + DUST_LIMIT) {
+        final change = runningBalance - satoshiAmount - twoOutPutFee;
+        if (change > DUST_LIMIT &&
+            runningBalance - satoshiAmount - change == twoOutPutFee) {
+          return runningBalance - satoshiAmount - change;
+        } else {
+          return runningBalance - satoshiAmount;
+        }
+      } else {
+        return runningBalance - satoshiAmount;
+      }
+    } else if (runningBalance - satoshiAmount == oneOutPutFee) {
+      return oneOutPutFee;
+    } else {
+      return twoOutPutFee;
+    }
+  }
+
+  int roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) {
+    return ((42 + (272 * inputCount) + (128 * outputCount)) / 4).ceil() *
+        (feeRatePerKB / 1000).ceil();
+  }
+
+  int sweepAllEstimate(int feeRate) {
+    int available = 0;
+    int inputCount = 0;
+    for (final output in outputsList) {
+      if (output.status.confirmed) {
+        available += output.value;
+        inputCount++;
+      }
+    }
+
+    // transaction will only have 1 output minus the fee
+    final estimatedFee = roughFeeEstimate(inputCount, 1, feeRate);
+
+    return available - estimatedFee;
+  }
+
+  @override
+  Future<bool> generateNewAddress() async {
+    try {
+      await _incrementAddressIndexForChain(
+          0, DerivePathType.bip84); // First increment the receiving index
+      final newReceivingIndex = DB.instance.get<dynamic>(
+          boxName: walletId,
+          key: 'receivingIndexP2WPKH') as int; // Check the new receiving index
+      final newReceivingAddress = await _generateAddressForChain(
+          0,
+          newReceivingIndex,
+          DerivePathType
+              .bip84); // Use new index to derive a new receiving address
+      await _addToAddressesArrayForChain(
+          newReceivingAddress,
+          0,
+          DerivePathType
+              .bip84); // Add that new receiving address to the array of receiving addresses
+      _currentReceivingAddress = Future(() =>
+          newReceivingAddress); // Set the new receiving address that the service
+
+      return true;
+    } catch (e, s) {
+      Logging.instance.log(
+          "Exception rethrown from generateNewAddress(): $e\n$s",
+          level: LogLevel.Error);
+      return false;
+    }
+  }
+}
+
+// Particl Network
+final particl = NetworkType(
+    messagePrefix: '\x18Bitcoin Signed Message:\n',
+    bech32: 'pw',
+    bip32: Bip32Type(public: 0x696e82d1, private: 0x8f1daeb8),
+    pubKeyHash: 0x38,
+    scriptHash: 0x3c,
+    wif: 0x6c);
diff --git a/lib/services/price.dart b/lib/services/price.dart
index 2514cc12a..43751c141 100644
--- a/lib/services/price.dart
+++ b/lib/services/price.dart
@@ -87,7 +87,7 @@ class PriceAPI {
     Map<Coin, Tuple2<Decimal, double>> result = {};
     try {
       final uri = Uri.parse(
-          "https://api.coingecko.com/api/v3/coins/markets?vs_currency=${baseCurrency.toLowerCase()}&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero&order=market_cap_desc&per_page=10&page=1&sparkline=false");
+          "https://api.coingecko.com/api/v3/coins/markets?vs_currency=${baseCurrency.toLowerCase()}&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero,particl&order=market_cap_desc&per_page=10&page=1&sparkline=false");
       // final uri = Uri.parse(
       //     "https://api.coingecko.com/api/v3/coins/markets?vs_currency=${baseCurrency.toLowerCase()}&ids=monero%2Cbitcoin%2Cepic-cash%2Czcoin%2Cdogecoin&order=market_cap_desc&per_page=10&page=1&sparkline=false");
 
diff --git a/lib/utilities/address_utils.dart b/lib/utilities/address_utils.dart
index 65303b24d..a6cbb8b58 100644
--- a/lib/utilities/address_utils.dart
+++ b/lib/utilities/address_utils.dart
@@ -8,6 +8,7 @@ import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
 import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
 import 'package:stackwallet/services/coins/litecoin/litecoin_wallet.dart';
 import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart';
+import 'package:stackwallet/services/coins/particl/particl_wallet.dart';
 import 'package:stackwallet/utilities/enums/coin_enum.dart';
 import 'package:stackwallet/utilities/logger.dart';
 
@@ -61,6 +62,8 @@ class AddressUtils {
             RegExp("[a-zA-Z0-9]{106}").hasMatch(address);
       case Coin.namecoin:
         return Address.validateAddress(address, namecoin, namecoin.bech32!);
+      case Coin.particl:
+        return Address.validateAddress(address, particl);
       case Coin.bitcoinTestNet:
         return Address.validateAddress(address, testnet);
       case Coin.litecoinTestNet:
diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart
index 27c8fe3b4..b02c6584e 100644
--- a/lib/utilities/assets.dart
+++ b/lib/utilities/assets.dart
@@ -180,6 +180,7 @@ class _SVG {
   String get monero => "assets/svg/coin_icons/Monero.svg";
   String get wownero => "assets/svg/coin_icons/Wownero.svg";
   String get namecoin => "assets/svg/coin_icons/Namecoin.svg";
+  String get particl => "assets/svg/coin_icons/Particl.svg";
 
   String get chevronRight => "assets/svg/chevron-right.svg";
   String get minimize => "assets/svg/minimize.svg";
@@ -192,6 +193,8 @@ class _SVG {
   String get bitcoincashTestnet => "assets/svg/coin_icons/Bitcoincash.svg";
   String get firoTestnet => "assets/svg/coin_icons/Firo.svg";
   String get dogecoinTestnet => "assets/svg/coin_icons/Dogecoin.svg";
+  String get particlTestnet =>
+      "assets/svg/coin_icons/Dogecoin.svg"; //TODO - Update icon to particl
 
   String iconFor({required Coin coin}) {
     switch (coin) {
@@ -214,6 +217,8 @@ class _SVG {
         return wownero;
       case Coin.namecoin:
         return namecoin;
+      case Coin.particl:
+        return particl;
       case Coin.bitcoinTestNet:
         return bitcoinTestnet;
       case Coin.bitcoincashTestnet:
@@ -241,6 +246,7 @@ class _PNG {
   String get epicCash => "assets/images/epic-cash.png";
   String get bitcoincash => "assets/images/bitcoincash.png";
   String get namecoin => "assets/images/namecoin.png";
+  String get particl => "assets/images/particl.png";
 
   String get glasses => "assets/images/glasses.png";
   String get glassesHidden => "assets/images/glasses-hidden.png";
@@ -271,6 +277,8 @@ class _PNG {
         return wownero;
       case Coin.namecoin:
         return namecoin;
+      case Coin.particl:
+        return particl;
     }
   }
 }
diff --git a/lib/utilities/block_explorers.dart b/lib/utilities/block_explorers.dart
index 69afb4a83..4b406b704 100644
--- a/lib/utilities/block_explorers.dart
+++ b/lib/utilities/block_explorers.dart
@@ -35,5 +35,7 @@ Uri getBlockExplorerTransactionUrlFor({
           "https://blockexplorer.one/bitcoin-cash/testnet/tx/$txid");
     case Coin.namecoin:
       return Uri.parse("https://chainz.cryptoid.info/nmc/tx.dws?$txid.htm");
+    case Coin.particl:
+      return Uri.parse("https://chainz.cryptoid.info/part/tx.dws?$txid.htm");
   }
 }
diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart
index 3263d526e..c6fe81d74 100644
--- a/lib/utilities/constants.dart
+++ b/lib/utilities/constants.dart
@@ -54,6 +54,7 @@ abstract class Constants {
       case Coin.firoTestNet:
       case Coin.epicCash:
       case Coin.namecoin:
+      case Coin.particl:
         return _satsPerCoin;
 
       case Coin.wownero:
@@ -78,6 +79,7 @@ abstract class Constants {
       case Coin.firoTestNet:
       case Coin.epicCash:
       case Coin.namecoin:
+      case Coin.particl:
         return _decimalPlaces;
 
       case Coin.wownero:
@@ -103,6 +105,7 @@ abstract class Constants {
       case Coin.firoTestNet:
       case Coin.epicCash:
       case Coin.namecoin:
+      case Coin.particl:
         values.addAll([24, 21, 18, 15, 12]);
         break;
 
@@ -150,6 +153,9 @@ abstract class Constants {
 
       case Coin.namecoin:
         return 600;
+
+      case Coin.particl:
+        return 600;
     }
   }
 
diff --git a/lib/utilities/default_nodes.dart b/lib/utilities/default_nodes.dart
index c9e96fbac..f4eb41b7c 100644
--- a/lib/utilities/default_nodes.dart
+++ b/lib/utilities/default_nodes.dart
@@ -18,6 +18,7 @@ abstract class DefaultNodes {
         bitcoincash,
         namecoin,
         wownero,
+        particl,
         bitcoinTestnet,
         litecoinTestNet,
         bitcoincashTestnet,
@@ -145,6 +146,17 @@ abstract class DefaultNodes {
         isDown: false,
       );
 
+  static NodeModel get particl => NodeModel(
+      host: "particl.stackwallet.com",
+      port: 58002,
+      name: defaultName,
+      id: _nodeId(Coin.particl),
+      useSSL: true,
+      enabled: true,
+      coinName: Coin.particl.name,
+      isFailover: true,
+      isDown: false);
+
   static NodeModel get bitcoinTestnet => NodeModel(
         host: "electrumx-testnet.cypherstack.com",
         port: 51002,
@@ -222,6 +234,9 @@ abstract class DefaultNodes {
       case Coin.namecoin:
         return namecoin;
 
+      case Coin.particl:
+        return particl;
+
       case Coin.bitcoinTestNet:
         return bitcoinTestnet;
 
diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart
index 543a193ee..e90509305 100644
--- a/lib/utilities/enums/coin_enum.dart
+++ b/lib/utilities/enums/coin_enum.dart
@@ -12,7 +12,11 @@ import 'package:stackwallet/services/coins/monero/monero_wallet.dart' as xmr;
 import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'
     as nmc;
 import 'package:stackwallet/services/coins/wownero/wownero_wallet.dart' as wow;
+import 'package:stackwallet/services/coins/particl/particl_wallet.dart'
+    as particl;
 import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/services/coins/particl/particl_wallet.dart'
+    as particl;
 
 enum Coin {
   bitcoin,
@@ -23,6 +27,7 @@ enum Coin {
   litecoin,
   monero,
   namecoin,
+  particl,
   wownero,
 
   ///
@@ -56,6 +61,8 @@ extension CoinExt on Coin {
         return "Firo";
       case Coin.monero:
         return "Monero";
+      case Coin.particl:
+        return "Particl";
       case Coin.wownero:
         return "Wownero";
       case Coin.namecoin:
@@ -89,6 +96,8 @@ extension CoinExt on Coin {
         return "FIRO";
       case Coin.monero:
         return "XMR";
+      case Coin.particl:
+        return "PART";
       case Coin.wownero:
         return "WOW";
       case Coin.namecoin:
@@ -123,6 +132,8 @@ extension CoinExt on Coin {
         return "firo";
       case Coin.monero:
         return "monero";
+      case Coin.particl:
+        return "particl";
       case Coin.wownero:
         return "wownero";
       case Coin.namecoin:
@@ -148,6 +159,7 @@ extension CoinExt on Coin {
       case Coin.dogecoin:
       case Coin.firo:
       case Coin.namecoin:
+      case Coin.particl:
       case Coin.bitcoinTestNet:
       case Coin.litecoinTestNet:
       case Coin.bitcoincashTestnet:
@@ -190,6 +202,9 @@ extension CoinExt on Coin {
       case Coin.monero:
         return xmr.MINIMUM_CONFIRMATIONS;
 
+      case Coin.particl:
+        return particl.MINIMUM_CONFIRMATIONS;
+
       case Coin.wownero:
         return wow.MINIMUM_CONFIRMATIONS;
 
@@ -230,6 +245,10 @@ Coin coinFromPrettyName(String name) {
     case "monero":
       return Coin.monero;
 
+    case "Particl":
+    case "particl":
+      return Coin.particl;
+
     case "Namecoin":
     case "namecoin":
       return Coin.namecoin;
@@ -293,8 +312,12 @@ Coin coinFromTickerCaseInsensitive(String ticker) {
       return Coin.monero;
     case "nmc":
       return Coin.namecoin;
+    case "part":
+      return Coin.particl;
     case "tltc":
       return Coin.litecoinTestNet;
+    case "part":
+      return Coin.particl;
     case "tbtc":
       return Coin.bitcoinTestNet;
     case "tbch":
diff --git a/lib/utilities/theme/color_theme.dart b/lib/utilities/theme/color_theme.dart
index e8b491e71..43012ffe6 100644
--- a/lib/utilities/theme/color_theme.dart
+++ b/lib/utilities/theme/color_theme.dart
@@ -220,6 +220,7 @@ class CoinThemeColor {
   Color get monero => const Color(0xFFFF9E6B);
   Color get namecoin => const Color(0xFF91B1E1);
   Color get wownero => const Color(0xFFED80C1);
+  Color get particl => const Color(0xFF8175BD);
 
   Color forCoin(Coin coin) {
     switch (coin) {
@@ -246,6 +247,8 @@ class CoinThemeColor {
         return namecoin;
       case Coin.wownero:
         return wownero;
+      case Coin.particl:
+        return particl;
     }
   }
 }
diff --git a/lib/utilities/theme/stack_colors.dart b/lib/utilities/theme/stack_colors.dart
index 935fa03ae..9764128e4 100644
--- a/lib/utilities/theme/stack_colors.dart
+++ b/lib/utilities/theme/stack_colors.dart
@@ -1443,6 +1443,8 @@ class StackColors extends ThemeExtension<StackColors> {
         return _coin.namecoin;
       case Coin.wownero:
         return _coin.wownero;
+      case Coin.particl:
+        return _coin.particl;
     }
   }
 
diff --git a/pubspec.lock b/pubspec.lock
index ffd644d05..ecc0bdf8d 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -56,7 +56,7 @@ packages:
       name: asn1lib
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.2.0"
+    version: "1.4.0"
   async:
     dependency: transitive
     description:
@@ -109,8 +109,8 @@ packages:
     dependency: "direct main"
     description:
       path: "."
-      ref: "65eb920719c8f7895c5402a07497647e7fc4b346"
-      resolved-ref: "65eb920719c8f7895c5402a07497647e7fc4b346"
+      ref: "004d6f82dff7389b561e5078b4649adcd2d9c10f"
+      resolved-ref: "004d6f82dff7389b561e5078b4649adcd2d9c10f"
       url: "https://github.com/cypherstack/bitcoindart.git"
     source: git
     version: "3.0.1"
@@ -169,7 +169,7 @@ packages:
       name: build_runner_core
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "7.2.6"
+    version: "7.2.7"
   built_collection:
     dependency: transitive
     description:
@@ -183,7 +183,7 @@ packages:
       name: built_value
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "8.4.1"
+    version: "8.4.2"
   characters:
     dependency: transitive
     description:
@@ -253,7 +253,7 @@ packages:
       name: connectivity_plus_platform_interface
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.2.2"
+    version: "1.2.3"
   connectivity_plus_web:
     dependency: transitive
     description:
@@ -372,7 +372,7 @@ packages:
       name: decimal
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.3.0"
+    version: "2.3.2"
   dependency_validator:
     dependency: "direct dev"
     description:
@@ -456,7 +456,7 @@ packages:
       name: file_picker
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "5.2.1"
+    version: "5.2.3"
   fixnum:
     dependency: transitive
     description:
@@ -543,7 +543,7 @@ packages:
       name: flutter_mobx
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.0.6+4"
+    version: "2.0.6+5"
   flutter_native_splash:
     dependency: "direct main"
     description:
@@ -585,35 +585,35 @@ packages:
       name: flutter_secure_storage_linux
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.1.1"
+    version: "1.1.2"
   flutter_secure_storage_macos:
     dependency: transitive
     description:
       name: flutter_secure_storage_macos
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.1.1"
+    version: "1.1.2"
   flutter_secure_storage_platform_interface:
     dependency: transitive
     description:
       name: flutter_secure_storage_platform_interface
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.0.0"
+    version: "1.0.1"
   flutter_secure_storage_web:
     dependency: transitive
     description:
       name: flutter_secure_storage_web
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.0.2"
+    version: "1.1.1"
   flutter_secure_storage_windows:
     dependency: transitive
     description:
       name: flutter_secure_storage_windows
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.1.2"
+    version: "1.1.3"
   flutter_spinkit:
     dependency: "direct main"
     description:
@@ -627,7 +627,7 @@ packages:
       name: flutter_svg
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.1.5"
+    version: "1.1.6"
   flutter_test:
     dependency: "direct dev"
     description: flutter
@@ -670,7 +670,7 @@ packages:
       name: graphs
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.1.0"
+    version: "2.2.0"
   hex:
     dependency: transitive
     description:
@@ -712,7 +712,7 @@ packages:
       name: html
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.15.0"
+    version: "0.15.1"
   http:
     dependency: "direct main"
     description:
@@ -836,7 +836,7 @@ packages:
       name: lints
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.0.0"
+    version: "2.0.1"
   local_auth:
     dependency: "direct main"
     description:
@@ -885,14 +885,14 @@ packages:
       name: mime
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.0.2"
+    version: "1.0.3"
   mobx:
     dependency: transitive
     description:
       name: mobx
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.1.1"
+    version: "2.1.3"
   mockingjay:
     dependency: "direct dev"
     description:
@@ -920,7 +920,7 @@ packages:
       name: mutex
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "3.0.0"
+    version: "3.0.1"
   nm:
     dependency: transitive
     description:
@@ -1018,7 +1018,7 @@ packages:
       name: path_provider_android
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.0.20"
+    version: "2.0.22"
   path_provider_ios:
     dependency: transitive
     description:
@@ -1060,7 +1060,7 @@ packages:
       name: permission_handler
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "10.1.0"
+    version: "10.2.0"
   permission_handler_android:
     dependency: transitive
     description:
@@ -1144,7 +1144,7 @@ packages:
       name: pub_semver
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.1.2"
+    version: "2.1.3"
   pubspec_parse:
     dependency: transitive
     description:
@@ -1172,7 +1172,7 @@ packages:
       name: rational
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.2.0"
+    version: "2.2.2"
   riverpod:
     dependency: transitive
     description:
@@ -1200,7 +1200,7 @@ packages:
       name: rxdart
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.27.5"
+    version: "0.27.7"
   share_plus:
     dependency: "direct main"
     description:
@@ -1228,7 +1228,7 @@ packages:
       name: share_plus_platform_interface
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "3.1.1"
+    version: "3.2.0"
   share_plus_web:
     dependency: transitive
     description:
@@ -1326,7 +1326,7 @@ packages:
       name: shelf_web_socket
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.0.2"
+    version: "1.0.3"
   sky_engine:
     dependency: transitive
     description: flutter
@@ -1403,7 +1403,7 @@ packages:
       name: stream_transform
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.0.1"
+    version: "2.1.0"
   string_scanner:
     dependency: transitive
     description:
@@ -1515,14 +1515,14 @@ packages:
       name: url_launcher
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "6.1.6"
+    version: "6.1.7"
   url_launcher_android:
     dependency: transitive
     description:
       name: url_launcher_android
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "6.0.19"
+    version: "6.0.22"
   url_launcher_ios:
     dependency: transitive
     description:
@@ -1571,7 +1571,7 @@ packages:
       name: uuid
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "3.0.6"
+    version: "3.0.7"
   vector_math:
     dependency: transitive
     description:
@@ -1655,7 +1655,7 @@ packages:
       name: win32
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "3.0.1"
+    version: "3.1.2"
   window_size:
     dependency: "direct main"
     description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 2397fbfbc..81f1a9e9a 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -11,7 +11,7 @@ description: Stack Wallet
 # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
 # Read more about iOS versioning at
 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
-version: 1.5.19+91
+version: 1.5.24+97
 
 environment:
   sdk: ">=2.17.0 <3.0.0"
@@ -49,7 +49,7 @@ dependencies:
   bitcoindart:
     git:
       url: https://github.com/cypherstack/bitcoindart.git
-      ref: 65eb920719c8f7895c5402a07497647e7fc4b346
+      ref: 004d6f82dff7389b561e5078b4649adcd2d9c10f
 
   stack_wallet_backup:
     git:
@@ -209,6 +209,7 @@ flutter:
     - assets/images/epic-cash.png
     - assets/images/bitcoincash.png
     - assets/images/namecoin.png
+    - assets/images/particl.png
     - assets/images/glasses.png
     - assets/images/glasses-hidden.png
     - assets/svg/plus.svg
@@ -310,6 +311,7 @@ flutter:
     - assets/svg/coin_icons/Monero.svg
     - assets/svg/coin_icons/Wownero.svg
     - assets/svg/coin_icons/Namecoin.svg
+    - assets/svg/coin_icons/Particl.svg
     # lottie animations
     - assets/lottie/test.json
     - assets/lottie/test2.json
diff --git a/scripts/linux/build_all.sh b/scripts/linux/build_all.sh
index 830198bcd..31edfb872 100755
--- a/scripts/linux/build_all.sh
+++ b/scripts/linux/build_all.sh
@@ -4,7 +4,7 @@
 # flutter-elinux clean
 # flutter-elinux pub get
 # flutter-elinux build linux --dart-define="IS_ARM=true"
-mkdir build
+mkdir -p build
 ./build_secure_storage_deps.sh &
 (cd ../../crypto_plugins/flutter_liblelantus/scripts/linux && ./build_all.sh ) &
 (cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./build_all.sh )  &
diff --git a/scripts/linux/build_secure_storage_deps.sh b/scripts/linux/build_secure_storage_deps.sh
index 7a725d65c..e63e38665 100755
--- a/scripts/linux/build_secure_storage_deps.sh
+++ b/scripts/linux/build_secure_storage_deps.sh
@@ -1,30 +1,31 @@
 #!/bin/bash
 LINUX_DIRECTORY=$(pwd)
+JSONCPP_TAG=1.7.4
 mkdir -p build
 
 # Build JsonCPP
-cd build || exit
+cd build || exit 1
 if ! [ -x "$(command -v git)" ]; then
   echo 'Error: git is not installed.' >&2
   exit 1
 fi
-git -C jsoncpp pull || git clone https://github.com/open-source-parsers/jsoncpp.git jsoncpp
-cd jsoncpp || exit
-git checkout 1.7.4
+git -C jsoncpp pull origin $JSONCPP_TAG || git clone https://github.com/open-source-parsers/jsoncpp.git jsoncpp
+cd jsoncpp || exit 1
+git checkout $JSONCPP_TAG
 mkdir -p build
-cd build || exit
+cd build || exit 1
 cmake -DCMAKE_BUILD_TYPE=release -DBUILD_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=ON -DARCHIVE_INSTALL_DIR=. -G "Unix Makefiles" ..
 make -j"$(nproc)"
 
-cd "$LINUX_DIRECTORY" || exit
+cd "$LINUX_DIRECTORY" || exit 1
 # Build libSecret
 # sudo apt install meson libgirepository1.0-dev valac xsltproc gi-docgen docbook-xsl
 # sudo apt install python3-pip
-#pip3 install --user meson markdown --upgrade
+#pip3 install --user meson markdown tomli --upgrade
 # pip3 install --user gi-docgen
-cd build || exit
+cd build || exit 1
 git -C libsecret pull || git clone https://gitlab.gnome.org/GNOME/libsecret.git libsecret
-cd libsecret || exit
+cd libsecret || exit 1
 if ! [ -x "$(command -v meson)" ]; then
   echo 'Error: meson is not installed.' >&2
   exit 1
diff --git a/scripts/prebuild.sh b/scripts/prebuild.sh
index d24076666..b9ab666a9 100755
--- a/scripts/prebuild.sh
+++ b/scripts/prebuild.sh
@@ -2,5 +2,17 @@
 KEYS=../lib/external_api_keys.dart
 if ! test -f "$KEYS"; then
     echo 'prebuild.sh: creating template lib/external_api_keys.dart file'
-    printf 'const kChangeNowApiKey = "";\nconst kSimpleSwapApiKey = "";' > $KEYS
+    printf 'const kChangeNowApiKey = "";\nconst kSimpleSwapApiKey = "";\n' > $KEYS
 fi
+
+# Create template wallet test parameter files if they don't already exist
+declare -a coins=("bitcoin" "bitcoincash" "dogecoin" "namecoin" "firo" "particl") # TODO add monero and wownero when those tests are updated to use the .gitignored test wallet setup: when doing that, make sure to update the test vectors for a new, private development seed
+
+for coin in "${coins[@]}"
+do
+    WALLETTESTPARAMFILE="../test/services/coins/${coin}/${coin}_wallet_test_parameters.dart"
+    if ! test -f "$WALLETTESTPARAMFILE"; then
+        echo "prebuild.sh: creating template test/services/coins/${coin}/${coin}_wallet_test_parameters.dart file"
+        printf 'const TEST_MNEMONIC = "";\nconst ROOT_WIF = "";\nconst NODE_WIF_84 = "";\n' > $WALLETTESTPARAMFILE
+    fi
+done
diff --git a/scripts/setup.sh b/scripts/setup.sh
index 5525d7814..d9c716546 100644
--- a/scripts/setup.sh
+++ b/scripts/setup.sh
@@ -12,7 +12,7 @@ sudo apt install -y unzip pkg-config clang cmake ninja-build libgtk-3-dev
 cd $DEVELOPMENT
 git clone https://github.com/flutter/flutter.git
 cd flutter 
-git checkout 3.0.3
+git checkout 3.3.4
 export FLUTTER_DIR=$(pwd)/bin
 echo 'export PATH="$PATH:'${FLUTTER_DIR}'"' >> ~/.bashrc
 source ~/.bashrc
diff --git a/test/cached_electrumx_test.mocks.dart b/test/cached_electrumx_test.mocks.dart
index a45cdd402..68d3b695f 100644
--- a/test/cached_electrumx_test.mocks.dart
+++ b/test/cached_electrumx_test.mocks.dart
@@ -551,6 +551,19 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
         returnValueForMissingStub: null,
       );
   @override
+  int get familiarity => (super.noSuchMethod(
+        Invocation.getter(#familiarity),
+        returnValue: 0,
+      ) as int);
+  @override
+  set familiarity(int? familiarity) => super.noSuchMethod(
+        Invocation.setter(
+          #familiarity,
+          familiarity,
+        ),
+        returnValueForMissingStub: null,
+      );
+  @override
   bool get showTestNetCoins => (super.noSuchMethod(
         Invocation.getter(#showTestNetCoins),
         returnValue: false,
diff --git a/test/electrumx_test.mocks.dart b/test/electrumx_test.mocks.dart
index 9f29ae1e5..c4a9ae9b2 100644
--- a/test/electrumx_test.mocks.dart
+++ b/test/electrumx_test.mocks.dart
@@ -272,6 +272,19 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs {
         returnValueForMissingStub: null,
       );
   @override
+  int get familiarity => (super.noSuchMethod(
+        Invocation.getter(#familiarity),
+        returnValue: 0,
+      ) as int);
+  @override
+  set familiarity(int? familiarity) => super.noSuchMethod(
+        Invocation.setter(
+          #familiarity,
+          familiarity,
+        ),
+        returnValueForMissingStub: null,
+      );
+  @override
   bool get showTestNetCoins => (super.noSuchMethod(
         Invocation.getter(#showTestNetCoins),
         returnValue: false,
diff --git a/test/pages/send_view/send_view_test.mocks.dart b/test/pages/send_view/send_view_test.mocks.dart
index d63dafb04..b5a3e4e63 100644
--- a/test/pages/send_view/send_view_test.mocks.dart
+++ b/test/pages/send_view/send_view_test.mocks.dart
@@ -85,9 +85,9 @@ class _FakeManager_3 extends _i1.SmartFake implements _i6.Manager {
         );
 }
 
-class _FakeFlutterSecureStorageInterface_4 extends _i1.SmartFake
+class _FakeSecureStorageInterface_4 extends _i1.SmartFake
     implements _i7.SecureStorageInterface {
-  _FakeFlutterSecureStorageInterface_4(
+  _FakeSecureStorageInterface_4(
     Object parent,
     Invocation parentInvocation,
   ) : super(
@@ -623,7 +623,7 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService {
   @override
   _i7.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod(
         Invocation.getter(#secureStorageInterface),
-        returnValue: _FakeFlutterSecureStorageInterface_4(
+        returnValue: _FakeSecureStorageInterface_4(
           this,
           Invocation.getter(#secureStorageInterface),
         ),
@@ -1765,6 +1765,19 @@ class MockPrefs extends _i1.Mock implements _i17.Prefs {
         returnValueForMissingStub: null,
       );
   @override
+  int get familiarity => (super.noSuchMethod(
+        Invocation.getter(#familiarity),
+        returnValue: 0,
+      ) as int);
+  @override
+  set familiarity(int? familiarity) => super.noSuchMethod(
+        Invocation.setter(
+          #familiarity,
+          familiarity,
+        ),
+        returnValueForMissingStub: null,
+      );
+  @override
   bool get showTestNetCoins => (super.noSuchMethod(
         Invocation.getter(#showTestNetCoins),
         returnValue: false,
diff --git a/test/price_test.dart b/test/price_test.dart
index 89300122e..6b98b67d1 100644
--- a/test/price_test.dart
+++ b/test/price_test.dart
@@ -26,7 +26,7 @@ void main() {
 
     when(client.get(
         Uri.parse(
-            "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
+            "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero,particl&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
         headers: {
           'Content-Type': 'application/json'
         })).thenAnswer((_) async => Response(
@@ -39,10 +39,10 @@ void main() {
     final price = await priceAPI.getPricesAnd24hChange(baseCurrency: "btc");
 
     expect(price.toString(),
-        '{Coin.bitcoin: [1, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: [0.00000315, -2.68533], Coin.epicCash: [0.00002803, 7.27524], Coin.firo: [0.0001096, -0.89304], Coin.litecoin: [0, 0.0], Coin.monero: [0.00717236, -0.77656], Coin.namecoin: [0, 0.0], Coin.wownero: [0, 0.0], Coin.bitcoinTestNet: [0, 0.0], Coin.litecoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}');
+        '{Coin.bitcoin: [1, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: [0.00000315, -2.68533], Coin.epicCash: [0.00002803, 7.27524], Coin.firo: [0.0001096, -0.89304], Coin.litecoin: [0, 0.0], Coin.monero: [0.00717236, -0.77656], Coin.namecoin: [0, 0.0], Coin.particl: [0, 0.0], Coin.wownero: [0, 0.0], Coin.bitcoinTestNet: [0, 0.0], Coin.litecoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}');
     verify(client.get(
         Uri.parse(
-            "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
+            "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero,particl&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
         headers: {'Content-Type': 'application/json'})).called(1);
 
     verifyNoMoreInteractions(client);
@@ -53,7 +53,7 @@ void main() {
 
     when(client.get(
         Uri.parse(
-            "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
+            "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero,particl&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
         headers: {
           'Content-Type': 'application/json'
         })).thenAnswer((_) async => Response(
@@ -71,12 +71,12 @@ void main() {
         await priceAPI.getPricesAnd24hChange(baseCurrency: "btc");
 
     expect(cachedPrice.toString(),
-        '{Coin.bitcoin: [1, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: [0.00000315, -2.68533], Coin.epicCash: [0.00002803, 7.27524], Coin.firo: [0.0001096, -0.89304], Coin.litecoin: [0, 0.0], Coin.monero: [0.00717236, -0.77656], Coin.namecoin: [0, 0.0], Coin.wownero: [0, 0.0], Coin.bitcoinTestNet: [0, 0.0], Coin.litecoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}');
+        '{Coin.bitcoin: [1, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: [0.00000315, -2.68533], Coin.epicCash: [0.00002803, 7.27524], Coin.firo: [0.0001096, -0.89304], Coin.litecoin: [0, 0.0], Coin.monero: [0.00717236, -0.77656], Coin.namecoin: [0, 0.0], Coin.particl: [0, 0.0], Coin.wownero: [0, 0.0], Coin.bitcoinTestNet: [0, 0.0], Coin.litecoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}');
 
     // verify only called once during filling of cache
     verify(client.get(
         Uri.parse(
-            "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
+            "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero,particl&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
         headers: {'Content-Type': 'application/json'})).called(1);
 
     verifyNoMoreInteractions(client);
@@ -87,7 +87,7 @@ void main() {
 
     when(client.get(
         Uri.parse(
-            "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
+            "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero,particl&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
         headers: {
           'Content-Type': 'application/json'
         })).thenAnswer((_) async => Response(
@@ -100,7 +100,7 @@ void main() {
     final price = await priceAPI.getPricesAnd24hChange(baseCurrency: "btc");
 
     expect(price.toString(),
-        '{Coin.bitcoin: [0, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: [0, 0.0], Coin.epicCash: [0, 0.0], Coin.firo: [0, 0.0], Coin.litecoin: [0, 0.0], Coin.monero: [0, 0.0], Coin.namecoin: [0, 0.0], Coin.wownero: [0, 0.0], Coin.bitcoinTestNet: [0, 0.0], Coin.litecoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}');
+        '{Coin.bitcoin: [0, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: [0, 0.0], Coin.epicCash: [0, 0.0], Coin.firo: [0, 0.0], Coin.litecoin: [0, 0.0], Coin.monero: [0, 0.0], Coin.namecoin: [0, 0.0], Coin.particl: [0, 0.0], Coin.wownero: [0, 0.0], Coin.bitcoinTestNet: [0, 0.0], Coin.litecoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}');
   });
 
   test("no internet available", () async {
@@ -108,7 +108,7 @@ void main() {
 
     when(client.get(
         Uri.parse(
-            "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
+            "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero,particl&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
         headers: {
           'Content-Type': 'application/json'
         })).thenThrow(const SocketException(
@@ -120,7 +120,7 @@ void main() {
     final price = await priceAPI.getPricesAnd24hChange(baseCurrency: "btc");
 
     expect(price.toString(),
-        '{Coin.bitcoin: [0, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: [0, 0.0], Coin.epicCash: [0, 0.0], Coin.firo: [0, 0.0], Coin.litecoin: [0, 0.0], Coin.monero: [0, 0.0], Coin.namecoin: [0, 0.0], Coin.wownero: [0, 0.0], Coin.bitcoinTestNet: [0, 0.0], Coin.litecoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}');
+        '{Coin.bitcoin: [0, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: [0, 0.0], Coin.epicCash: [0, 0.0], Coin.firo: [0, 0.0], Coin.litecoin: [0, 0.0], Coin.monero: [0, 0.0], Coin.namecoin: [0, 0.0], Coin.particl: [0, 0.0], Coin.wownero: [0, 0.0], Coin.bitcoinTestNet: [0, 0.0], Coin.litecoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}');
   });
 
   tearDown(() async {
diff --git a/test/screen_tests/exchange/exchange_view_test.mocks.dart b/test/screen_tests/exchange/exchange_view_test.mocks.dart
index 0da12dbd0..8c0d72f55 100644
--- a/test/screen_tests/exchange/exchange_view_test.mocks.dart
+++ b/test/screen_tests/exchange/exchange_view_test.mocks.dart
@@ -223,6 +223,19 @@ class MockPrefs extends _i1.Mock implements _i3.Prefs {
         returnValueForMissingStub: null,
       );
   @override
+  int get familiarity => (super.noSuchMethod(
+        Invocation.getter(#familiarity),
+        returnValue: 0,
+      ) as int);
+  @override
+  set familiarity(int? familiarity) => super.noSuchMethod(
+        Invocation.setter(
+          #familiarity,
+          familiarity,
+        ),
+        returnValueForMissingStub: null,
+      );
+  @override
   bool get showTestNetCoins => (super.noSuchMethod(
         Invocation.getter(#showTestNetCoins),
         returnValue: false,
diff --git a/test/screen_tests/lockscreen_view_screen_test.mocks.dart b/test/screen_tests/lockscreen_view_screen_test.mocks.dart
index a33c4ef28..2ea9822b6 100644
--- a/test/screen_tests/lockscreen_view_screen_test.mocks.dart
+++ b/test/screen_tests/lockscreen_view_screen_test.mocks.dart
@@ -29,9 +29,9 @@ import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'
 // ignore_for_file: camel_case_types
 // ignore_for_file: subtype_of_sealed_class
 
-class _FakeFlutterSecureStorageInterface_0 extends _i1.SmartFake
+class _FakeSecureStorageInterface_0 extends _i1.SmartFake
     implements _i2.SecureStorageInterface {
-  _FakeFlutterSecureStorageInterface_0(
+  _FakeSecureStorageInterface_0(
     Object parent,
     Invocation parentInvocation,
   ) : super(
@@ -311,7 +311,7 @@ class MockNodeService extends _i1.Mock implements _i10.NodeService {
   @override
   _i2.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod(
         Invocation.getter(#secureStorageInterface),
-        returnValue: _FakeFlutterSecureStorageInterface_0(
+        returnValue: _FakeSecureStorageInterface_0(
           this,
           Invocation.getter(#secureStorageInterface),
         ),
diff --git a/test/screen_tests/onboarding/create_pin_view_screen_test.mocks.dart b/test/screen_tests/onboarding/create_pin_view_screen_test.mocks.dart
index 3aed1dcb8..c891911ae 100644
--- a/test/screen_tests/onboarding/create_pin_view_screen_test.mocks.dart
+++ b/test/screen_tests/onboarding/create_pin_view_screen_test.mocks.dart
@@ -29,9 +29,9 @@ import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'
 // ignore_for_file: camel_case_types
 // ignore_for_file: subtype_of_sealed_class
 
-class _FakeFlutterSecureStorageInterface_0 extends _i1.SmartFake
+class _FakeSecureStorageInterface_0 extends _i1.SmartFake
     implements _i2.SecureStorageInterface {
-  _FakeFlutterSecureStorageInterface_0(
+  _FakeSecureStorageInterface_0(
     Object parent,
     Invocation parentInvocation,
   ) : super(
@@ -311,7 +311,7 @@ class MockNodeService extends _i1.Mock implements _i10.NodeService {
   @override
   _i2.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod(
         Invocation.getter(#secureStorageInterface),
-        returnValue: _FakeFlutterSecureStorageInterface_0(
+        returnValue: _FakeSecureStorageInterface_0(
           this,
           Invocation.getter(#secureStorageInterface),
         ),
diff --git a/test/screen_tests/onboarding/restore_wallet_view_screen_test.mocks.dart b/test/screen_tests/onboarding/restore_wallet_view_screen_test.mocks.dart
index cd4986e13..ee326b1d8 100644
--- a/test/screen_tests/onboarding/restore_wallet_view_screen_test.mocks.dart
+++ b/test/screen_tests/onboarding/restore_wallet_view_screen_test.mocks.dart
@@ -83,9 +83,9 @@ class _FakeTransactionData_4 extends _i1.SmartFake
         );
 }
 
-class _FakeFlutterSecureStorageInterface_5 extends _i1.SmartFake
+class _FakeSecureStorageInterface_5 extends _i1.SmartFake
     implements _i6.SecureStorageInterface {
-  _FakeFlutterSecureStorageInterface_5(
+  _FakeSecureStorageInterface_5(
     Object parent,
     Invocation parentInvocation,
   ) : super(
@@ -744,10 +744,9 @@ class MockManager extends _i1.Mock implements _i12.Manager {
 /// See the documentation for Mockito's code generation for more information.
 class MockNodeService extends _i1.Mock implements _i13.NodeService {
   @override
-  _i6.SecureStorageInterface get secureStorageInterface =>
-      (super.noSuchMethod(
+  _i6.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod(
         Invocation.getter(#secureStorageInterface),
-        returnValue: _FakeFlutterSecureStorageInterface_5(
+        returnValue: _FakeSecureStorageInterface_5(
           this,
           Invocation.getter(#secureStorageInterface),
         ),
diff --git a/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/add_custom_node_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/add_custom_node_view_screen_test.mocks.dart
index 3ac5afcc7..9cdd45a2a 100644
--- a/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/add_custom_node_view_screen_test.mocks.dart
+++ b/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/add_custom_node_view_screen_test.mocks.dart
@@ -28,9 +28,9 @@ import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'
 // ignore_for_file: camel_case_types
 // ignore_for_file: subtype_of_sealed_class
 
-class _FakeFlutterSecureStorageInterface_0 extends _i1.SmartFake
+class _FakeSecureStorageInterface_0 extends _i1.SmartFake
     implements _i2.SecureStorageInterface {
-  _FakeFlutterSecureStorageInterface_0(
+  _FakeSecureStorageInterface_0(
     Object parent,
     Invocation parentInvocation,
   ) : super(
@@ -88,7 +88,7 @@ class MockNodeService extends _i1.Mock implements _i6.NodeService {
   @override
   _i2.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod(
         Invocation.getter(#secureStorageInterface),
-        returnValue: _FakeFlutterSecureStorageInterface_0(
+        returnValue: _FakeSecureStorageInterface_0(
           this,
           Invocation.getter(#secureStorageInterface),
         ),
diff --git a/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/node_details_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/node_details_view_screen_test.mocks.dart
index 0f6447a9e..984287655 100644
--- a/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/node_details_view_screen_test.mocks.dart
+++ b/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/node_details_view_screen_test.mocks.dart
@@ -28,9 +28,9 @@ import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'
 // ignore_for_file: camel_case_types
 // ignore_for_file: subtype_of_sealed_class
 
-class _FakeFlutterSecureStorageInterface_0 extends _i1.SmartFake
+class _FakeSecureStorageInterface_0 extends _i1.SmartFake
     implements _i2.SecureStorageInterface {
-  _FakeFlutterSecureStorageInterface_0(
+  _FakeSecureStorageInterface_0(
     Object parent,
     Invocation parentInvocation,
   ) : super(
@@ -88,7 +88,7 @@ class MockNodeService extends _i1.Mock implements _i6.NodeService {
   @override
   _i2.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod(
         Invocation.getter(#secureStorageInterface),
-        returnValue: _FakeFlutterSecureStorageInterface_0(
+        returnValue: _FakeSecureStorageInterface_0(
           this,
           Invocation.getter(#secureStorageInterface),
         ),
diff --git a/test/screen_tests/settings_view/settings_subviews/network_settings_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/network_settings_view_screen_test.mocks.dart
index 707da7345..cf3ed4e9a 100644
--- a/test/screen_tests/settings_view/settings_subviews/network_settings_view_screen_test.mocks.dart
+++ b/test/screen_tests/settings_view/settings_subviews/network_settings_view_screen_test.mocks.dart
@@ -24,9 +24,9 @@ import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'
 // ignore_for_file: camel_case_types
 // ignore_for_file: subtype_of_sealed_class
 
-class _FakeFlutterSecureStorageInterface_0 extends _i1.SmartFake
+class _FakeSecureStorageInterface_0 extends _i1.SmartFake
     implements _i2.SecureStorageInterface {
-  _FakeFlutterSecureStorageInterface_0(
+  _FakeSecureStorageInterface_0(
     Object parent,
     Invocation parentInvocation,
   ) : super(
@@ -42,7 +42,7 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService {
   @override
   _i2.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod(
         Invocation.getter(#secureStorageInterface),
-        returnValue: _FakeFlutterSecureStorageInterface_0(
+        returnValue: _FakeSecureStorageInterface_0(
           this,
           Invocation.getter(#secureStorageInterface),
         ),
diff --git a/test/services/coins/particl/particl_history_sample_data.dart b/test/services/coins/particl/particl_history_sample_data.dart
new file mode 100644
index 000000000..048dc0b1f
--- /dev/null
+++ b/test/services/coins/particl/particl_history_sample_data.dart
@@ -0,0 +1,152 @@
+final Map<String, List<dynamic>> historyBatchArgs0 = {
+  "k_0_0": ["a48f8ee5dc6ff58b29eddeac1afd808b2edff10d736bdede3a2e6a95e588911c"],
+  "k_0_1": ["b6fce6c41154ccf70676c5c91acd9b6899ef0195e34b4c05c4920daa827c19a3"],
+  "k_0_2": ["aabdda195909a16141e8a3ab548fc5e8e8ba483762f94e1571cf93572bc6767e"],
+  "k_0_3": ["17ffed6bf6a9960b696139b6c40a1e4f2ee214a68442abdf57e9040079e62765"],
+  "k_0_4": ["25b58fdb4a8ea949730e138bddf6ff90c13d09123ba935efefc6f5d0e085885e"],
+  "k_0_5": ["952c7c54ad41bca032fa752d00a8d07e8ea5ae3e850266b45110bbc2a8969c43"],
+  "k_0_6": ["6b9a3a156ca83f20533ddc29c84cd1872fce4b612f738f022028ad680b77aaa3"],
+  "k_0_7": ["b6f0f12cc91bbb21584668146c2bfa7d07a786b8772fdd43e6daba3ff43aadff"],
+  "k_0_8": ["d478d5ca5e92e3a98c36136bf9712f981e7b1cb93ebe65e25f1e11151047a753"],
+  "k_0_9": ["f3bfe232ca898d1cb44c23586323b0fedef477208c8b4f203eebdf9ea8a2ceef"],
+  "k_0_10": [
+    "aedac6f5e8f0e96c7a53d9b0460ba9e9397efbd9d15c46a28d7b0be70ffc6dac"
+  ],
+  "k_0_11": ["f66b687065339e2d4d567f9ea473947b8aab74c066bf00cdfdb5f918bbd159dc"]
+};
+
+final Map<String, List<dynamic>> historyBatchArgs1 = {
+  "k_0_0": ["0664a4e19dd852c7d6fb53824198070e911dae9049aa9a6a940413cb868bbb27"],
+  "k_0_1": ["c4b1d9cd4edb7c13eae863b1e4f8fd5acff29f1fe153c4f859906cbea26a3f2f"],
+  "k_0_2": ["aa34a5cd34908ed90f41ce31058552bee864b8894eec3b5b3f2583eb56eca759"],
+  "k_0_3": ["996cca35eb2c30699c8b28d3fff12a8fb7fbbacfbfe4aafc4e59833134cb37bb"],
+  "k_0_4": ["f581a49f492a0d2bc18b5c82ef999f03ff795bf5101646bed871b3efa1f34578"],
+  "k_0_5": ["ce5fe035e2ce47a7d5d1dc65ad4fc2d40d621f0f2fa25eb233e6d717e0b1743a"],
+  "k_0_6": ["51031f3710836824b48df2f33d1daa2c63b397c3c604577f09c8b4bef19302fb"],
+  "k_0_7": ["901f355de67d762c5a768ef19624359c8f95bc9f70d381507727a885cb46964b"],
+  "k_0_8": ["8ac9526d63526f498fc7c609adcb72c23a403cc271c91408288c19318357f059"],
+  "k_0_9": ["ca7b62c4b069ff2d4fcbc0faac32447b92d519dd726039eb7381ca5fde176e97"],
+  "k_0_10": [
+    "f07285fe3a8eac625b2c5339cf9f068fccb2278f923a38a46a60c94c7179d4aa"
+  ],
+  "k_0_11": ["f8f09b8fe23da8435409c3e688002dcaa87c2b9f3707e17bc668db7392039dab"]
+};
+
+final Map<String, List<dynamic>> historyBatchArgs2 = {
+  "k_0_0": ["4cff1590918be5d24d130f10627aaacc6d1e3f03872643c3afc742e6c77e3e72"],
+  "k_0_1": ["3fedd8a2d5fc355727afe353413dc1a0ef861ba768744d5b8193c33cbc829339"],
+  "k_0_2": ["68668ef2d53d4cb5bda66ce3adae25dbe7a8466eb3eca64ed816a59cf8362288"],
+  "k_0_3": ["8bb32cbd5de862d6305813e312b0caec7249692c6f41154b4855003450c58f6d"],
+  "k_0_4": ["d66592642c8dcbde165c04fd394ed92bcef89bbadbfd8cbe213cea0e811708ce"],
+  "k_0_5": ["72ac3ef3b722777e5e7cf75eaf8324cb7db0c575a6a8640609757c99a71bca91"],
+  "k_0_6": ["be8f9884d1655f84993572924729f52ec66b56582adf44b841cebdf42d3dcd5b"],
+  "k_0_7": ["c5a53feb8be5ea226da3e72bfcb522569f7956d137266e3da16ada99d0c4817b"],
+  "k_0_8": ["f50124f4371374e623db18f24bf01644018b0a47351dfa5624df9706c5409dca"],
+  "k_0_9": ["83f0334c6c57164ac6fd9c83b89f1977e2e4bf9144dd25c992e3def16242ae8c"],
+  "k_0_10": [
+    "e04e7d94a880ccbd8ce473ce5e780fd86003137cef1e879e38971e4216a282e1"
+  ],
+  "k_0_11": ["f6a7b80c32f2568bebe37d6615ebfa602ec04207cd9edf304ff7f835b03c27d2"]
+};
+
+final Map<String, List<dynamic>> historyBatchArgs3 = {
+  "k_0_0": ["f2547dcbe38adc0fee943dc0b0a543f96b90af587850c9df172c69134a49f4c9"],
+  "k_0_1": ["0e8b6756b404db5a381fd71ad79cb595a6c36c938cf9913c5a0494b667c2151a"],
+  "k_0_2": ["099bdff41fbbfc3d90ea5a8510d5588e71a27509592447025ee6dee4278e13ff"],
+  "k_0_3": ["c1ae51351f1267bf6747c888760f25cc199747a3cc2be7dd6a899223d748508c"],
+  "k_0_4": ["7eba642b2889d562edc75dc2653caf1d2b864a9db8a0e64e1b6d62e014c6ed5b"],
+  "k_0_5": ["be2b6216b6effadfa12f4588612396daa781a40b50a7bd73c1bf722b7855d4c3"],
+  "k_0_6": ["ddf040fca6a4609fcb1045216fc17772b821cf560802be267bd433596c2aa897"],
+  "k_0_7": ["5c9c6a409240e59d731c7d87c58d701e2d99cc87d073ff07114ebf5db602e87d"],
+  "k_0_8": ["03b3c0dae8d561f1ab38199b5dfa4930a18fe702b14332b996c93364819aef56"],
+  "k_0_9": ["98bf1f26ff3e8db88a4506d476122c2b2ff7f8e9e217b351e532fb95d6c9e308"],
+  "k_0_10": [
+    "0c6c028ede10b0c3180e9541675c16b72f4443663dd7dbe9b45037b230d55917"
+  ],
+  "k_0_11": ["eb1ebeefa4bc5f754daabb0f783f3685bd839429ee0a287bd26d8717265c3d27"]
+};
+
+final Map<String, List<Map<String, dynamic>>> historyBatchResponse = {
+  "k_0_0": [],
+  "s_0_0": [{}, {}],
+  "w_0_0": [],
+  "k_0_1": [{}],
+  "s_0_1": [],
+  "w_0_1": [{}, {}, {}],
+  "k_0_2": [],
+  "s_0_2": [],
+  "w_0_2": [],
+  "k_0_3": [],
+  "s_0_3": [],
+  "w_0_3": [],
+  "k_0_4": [],
+  "s_0_4": [],
+  "w_0_4": [],
+  "k_0_5": [],
+  "s_0_5": [],
+  "w_0_5": [],
+  "k_0_6": [],
+  "s_0_6": [],
+  "w_0_6": [],
+  "k_0_7": [],
+  "s_0_7": [],
+  "w_0_7": [],
+  "k_0_8": [],
+  "s_0_8": [],
+  "w_0_8": [],
+  "k_0_9": [],
+  "s_0_9": [],
+  "w_0_9": [],
+  "k_0_10": [],
+  "s_0_10": [],
+  "w_0_10": [],
+  "k_0_11": [],
+  "s_0_11": [],
+  "w_0_11": []
+};
+
+final Map<String, List<Map<String, dynamic>>> emptyHistoryBatchResponse = {
+  "k_0_0": [],
+  "s_0_0": [],
+  "w_0_0": [],
+  "k_0_1": [],
+  "s_0_1": [],
+  "w_0_1": [],
+  "k_0_2": [],
+  "s_0_2": [],
+  "w_0_2": [],
+  "k_0_3": [],
+  "s_0_3": [],
+  "w_0_3": [],
+  "k_0_4": [],
+  "s_0_4": [],
+  "w_0_4": [],
+  "k_0_5": [],
+  "s_0_5": [],
+  "w_0_5": [],
+  "k_0_6": [],
+  "s_0_6": [],
+  "w_0_6": [],
+  "k_0_7": [],
+  "s_0_7": [],
+  "w_0_7": [],
+  "k_0_8": [],
+  "s_0_8": [],
+  "w_0_8": [],
+  "k_0_9": [],
+  "s_0_9": [],
+  "w_0_9": [],
+  "k_0_10": [],
+  "s_0_10": [],
+  "w_0_10": [],
+  "k_0_11": [],
+  "s_0_11": [],
+  "w_0_11": []
+};
+
+final List<String> activeScriptHashes = [
+  "3fedd8a2d5fc355727afe353413dc1a0ef861ba768744d5b8193c33cbc829339",
+  "b6fce6c41154ccf70676c5c91acd9b6899ef0195e34b4c05c4920daa827c19a3",
+  "0e8b6756b404db5a381fd71ad79cb595a6c36c938cf9913c5a0494b667c2151a",
+  "c4b1d9cd4edb7c13eae863b1e4f8fd5acff29f1fe153c4f859906cbea26a3f2f"
+];
diff --git a/test/services/coins/particl/particl_transaction_data_samples.dart b/test/services/coins/particl/particl_transaction_data_samples.dart
new file mode 100644
index 000000000..e90a90263
--- /dev/null
+++ b/test/services/coins/particl/particl_transaction_data_samples.dart
@@ -0,0 +1,372 @@
+import 'package:stackwallet/models/paymint/transactions_model.dart';
+
+final transactionData = TransactionData.fromMap({
+  "a51831f09072dc9edb3130f677a484ca03bced8f6d803e8df83a1ed84bc06c0a": tx1,
+  "39a9c37d54d04f9ac6ed45aaa1a02b058391b5d1fc0e2e1d67e50f36b1d82896": tx2,
+  "e53ef367a5f9d8493825400a291136870ea24a750f63897f559851ab80ea1020": tx3,
+  "10e14b1d34c18a563b476c4c36688eb7caebf6658e25753074471d2adef460ba": tx4,
+});
+
+final tx1 = Transaction(
+  txid: "a51831f09072dc9edb3130f677a484ca03bced8f6d803e8df83a1ed84bc06c0a",
+  confirmedStatus: true,
+  confirmations: 15447,
+  txType: "Received",
+  amount: 10000000,
+  fees: 53600,
+  height: 1299909,
+  address: "PtQCgwUx9mLmRDWxB3J7MPnNsWDcce7a5g",
+  timestamp: 1667814832,
+  worthNow: "0.00",
+  worthAtBlockTimestamp: "0.00",
+  inputSize: 2,
+  outputSize: 2,
+  inputs: [
+    Input(
+      txid: "e53ef367a5f9d8493825400a291136870ea24a750f63897f559851ab80ea1020",
+      vout: 1,
+    ),
+    Input(
+      txid: "b255bf1b4b2f1a76eab45fd69e589b655261b049f238807b0acbf304d1b8195b",
+      vout: 0,
+    ),
+  ],
+  outputs: [
+    Output(
+      scriptpubkeyAddress: "PtQCgwUx9mLmRDWxB3J7MPnNsWDcce7a5g",
+      value: 10000000,
+    ),
+    Output(
+      scriptpubkeyAddress: "PsHtVuRCybcTpJQN6ckLFptPB7k9ZkqztA",
+      value: 9946400,
+    )
+  ],
+);
+
+final tx2 = Transaction(
+  txid: "39a9c37d54d04f9ac6ed45aaa1a02b058391b5d1fc0e2e1d67e50f36b1d82896",
+  confirmedStatus: true,
+  confirmations: 13927,
+  txType: "Sent",
+  amount: 50000000,
+  fees: 49500,
+  height: 1301433,
+  address: "PcKLXor8hqb3qSjtoHQThapJSbPapSDt4C",
+  timestamp: 1668010880,
+  worthNow: "0.00",
+  worthAtBlockTimestamp: "0.00",
+  inputSize: 1,
+  outputSize: 2,
+  inputs: [
+    Input(
+      txid: "909bdf555736c272df0e1df52ca5fcce4f1090b74c0e5d9319bb40e02f4b3add",
+      vout: 1,
+    ),
+  ],
+  outputs: [
+    Output(
+      scriptpubkeyAddress: "PcKLXor8hqb3qSjtoHQThapJSbPapSDt4C",
+      value: 50000000,
+    ),
+    Output(
+      scriptpubkeyAddress: "PjDq9kwadvgKNtQLTdGqcDsFzPmk9LMjT7",
+      value: 1749802000,
+    ),
+  ],
+);
+
+final tx3 = Transaction(
+  txid: "e53ef367a5f9d8493825400a291136870ea24a750f63897f559851ab80ea1020",
+  confirmedStatus: true,
+  confirmations: 23103,
+  txType: "Received",
+  amount: 10000000,
+  fees: 34623,
+  height: 1292263,
+  address: "PhDSyHLt7ejdPXGve3HFr93pSdFLHUBwdr",
+  timestamp: 1666827392,
+  worthNow: "0.00",
+  worthAtBlockTimestamp: "0.00",
+  inputSize: 1,
+  outputSize: 2,
+  inputs: [
+    Input(
+      txid: "8a2c6a4c0797d057f20f93b5e3b6e5f306493c67b2341626e0375f30f35a2d47",
+      vout: 0,
+    )
+  ],
+  outputs: [
+    Output(
+      scriptpubkeyAddress: "PYv7kk7TKQsSosWLuLveMJqAYxTiDiK5kp",
+      value: 39915877,
+    ),
+    Output(
+      scriptpubkeyAddress: "PhDSyHLt7ejdPXGve3HFr93pSdFLHUBwdr",
+      value: 10000000,
+    ),
+  ],
+);
+
+final tx4 = Transaction(
+  txid: "10e14b1d34c18a563b476c4c36688eb7caebf6658e25753074471d2adef460ba",
+  confirmedStatus: true,
+  confirmations: 493,
+  txType: "Sent",
+  amount: 9945773,
+  fees: 27414,
+  height: 1314873,
+  address: "PpqgMahyfqfasunUKfkmVfdpyhhrHa2ibY",
+  timestamp: 1669740960,
+  worthNow: "0.00",
+  worthAtBlockTimestamp: "0.00",
+  inputSize: 1,
+  outputSize: 1,
+  inputs: [
+    Input(
+      txid: "aab01876c4db40b35ba00bbfb7c58aaec32cad7dc136214b7344a944606cbe73",
+      vout: 0,
+    ),
+  ],
+  outputs: [
+    Output(
+      scriptpubkeyAddress: "PpqgMahyfqfasunUKfkmVfdpyhhrHa2ibY",
+      value: 9945773,
+    ),
+  ],
+);
+
+final tx1Raw = {
+  "txid": "a51831f09072dc9edb3130f677a484ca03bced8f6d803e8df83a1ed84bc06c0a",
+  "hash": "46b7358ccbc018da4e144188f311657e8b694f056211d7511726c4259dca86b4",
+  "size": 374,
+  "vsize": 267,
+  "version": 160,
+  "locktime": 1299908,
+  "vin": [
+    {
+      "txid":
+          "e53ef367a5f9d8493825400a291136870ea24a750f63897f559851ab80ea1020",
+      "vout": 1,
+      "scriptSig": {"asm": "", "hex": ""},
+      "txinwitness": [
+        "30440220336bf0952b543314ba37b1bb8866a65b2482b499c715d778e92e90d7d59c6a39022072cae4341ca8825bee8043ae91f18de5776edd069ed228142eca55a16c887d6b01",
+        "026b4ca62de9e8f63abd0a6cf176536fe8e6a64d6343b6396aa9fb35232520e4a7"
+      ],
+      "sequence": 4294967293
+    },
+    {
+      "txid":
+          "b255bf1b4b2f1a76eab45fd69e589b655261b049f238807b0acbf304d1b8195b",
+      "vout": 0,
+      "scriptSig": {"asm": "", "hex": ""},
+      "txinwitness": [
+        "304402205b914f31952958d54f0290d47eef6d9042259387c9493993882e24bd9acefe00022066b16f2f41885a85051c9bff4c119ecddc0209520e9a93d75866624f11b4e82d01",
+        "026b4ca62de9e8f63abd0a6cf176536fe8e6a64d6343b6396aa9fb35232520e4a7"
+      ],
+      "sequence": 4294967293
+    }
+  ],
+  "vout": [
+    {
+      "n": 0,
+      "type": "standard",
+      "value": 0.1,
+      "valueSat": 10000000,
+      "scriptPubKey": {
+        "asm":
+            "OP_DUP OP_HASH160 e0923d464a2c30438f0808e4af94868253b63ca0 OP_EQUALVERIFY OP_CHECKSIG",
+        "hex": "76a914e0923d464a2c30438f0808e4af94868253b63ca088ac",
+        "reqSigs": 1,
+        "type": "pubkeyhash",
+        "addresses": ["PtQCgwUx9mLmRDWxB3J7MPnNsWDcce7a5g"]
+      }
+    },
+    {
+      "n": 1,
+      "type": "standard",
+      "value": 0.099464,
+      "valueSat": 9946400,
+      "scriptPubKey": {
+        "asm":
+            "OP_DUP OP_HASH160 d4686eee8cd127b50d28869627d61b38cc63fe4a OP_EQUALVERIFY OP_CHECKSIG",
+        "hex": "76a914d4686eee8cd127b50d28869627d61b38cc63fe4a88ac",
+        "reqSigs": 1,
+        "type": "pubkeyhash",
+        "addresses": ["PsHtVuRCybcTpJQN6ckLFptPB7k9ZkqztA"]
+      }
+    }
+  ],
+  "blockhash":
+      "b7cb29eb9cb4fa73c4da32f5cf8dfd90194eb6b689d4e547fa9b3176a698a741",
+  "height": 1299909,
+  "confirmations": 15447,
+  "time": 1667814832,
+  "blocktime": 1667814832
+};
+
+final tx2Raw = {
+  "txid": "39a9c37d54d04f9ac6ed45aaa1a02b058391b5d1fc0e2e1d67e50f36b1d82896",
+  "hash": "85130125ec9e37a48670fb5eb0a2780b94ea958cd700a1237ff75775d8a0edb0",
+  "size": 226,
+  "vsize": 173,
+  "version": 160,
+  "locktime": 1301432,
+  "vin": [
+    {
+      "txid":
+          "909bdf555736c272df0e1df52ca5fcce4f1090b74c0e5d9319bb40e02f4b3add",
+      "vout": 1,
+      "scriptSig": {"asm": "", "hex": ""},
+      "txinwitness": [
+        "30440220486c87376122e2d3ca7154f41a45fdafa2865412ec90e4b3db791915eee1d13002204cca8520a655b43c3cddc216725cc8508cd9b326a39ed99ed893be59167289af01",
+        "03acc7ad6e2e9560db73f7ec7ef2f55a6115d85069cf0eacfe3ab663f33415573c"
+      ],
+      "sequence": 4294967293
+    }
+  ],
+  "vout": [
+    {
+      "n": 0,
+      "type": "standard",
+      "value": 0.5,
+      "valueSat": 50000000,
+      "scriptPubKey": {
+        "asm":
+            "OP_DUP OP_HASH160 3024b192883be45b197b548f71155829af980724 OP_EQUALVERIFY OP_CHECKSIG",
+        "hex": "76a9143024b192883be45b197b548f71155829af98072488ac",
+        "reqSigs": 1,
+        "type": "pubkeyhash",
+        "addresses": ["PcKLXor8hqb3qSjtoHQThapJSbPapSDt4C"]
+      }
+    },
+    {
+      "n": 1,
+      "type": "standard",
+      "value": 17.49802,
+      "valueSat": 1749802000,
+      "scriptPubKey": {
+        "asm":
+            "OP_DUP OP_HASH160 7be2f80f6b9f6df740142fb34668c25c4e5c8bd5 OP_EQUALVERIFY OP_CHECKSIG",
+        "hex": "76a9147be2f80f6b9f6df740142fb34668c25c4e5c8bd588ac",
+        "reqSigs": 1,
+        "type": "pubkeyhash",
+        "addresses": ["PjDq9kwadvgKNtQLTdGqcDsFzPmk9LMjT7"]
+      }
+    }
+  ],
+  "blockhash":
+      "065c7328f1a768f3005ab7bfb322806bcc0cf88a96e89830b44991cc434c9955",
+  "height": 1301433,
+  "confirmations": 13927,
+  "time": 1668010880,
+  "blocktime": 1668010880
+};
+
+final tx3Raw = {
+  "txid": "71b56532e9e7321bd8c30d0f8b14530743049d2f3edd5623065c46eee1dda04d",
+  "hash": "bb25567e1ffb2fd6ec9aa3925a7a8dd3055a29521f7811b2b2bc01ce7d8a216e",
+  "version": 2,
+  "size": 370,
+  "vsize": 208,
+  "weight": 832,
+  "locktime": 0,
+  "vin": [
+    {
+      "txid":
+          "dffa9543852197f9fb90f8adafaab8a0b9b4925e9ada8c6bdcaf00bf2e9f60d7",
+      "vout": 0,
+      "scriptSig": {"asm": "", "hex": ""},
+      "txinwitness": [
+        "304402203535cf570aca7c1acfa6e8d2f43e0b188b76d0b7a75ffca448e6af953ffe8b6302202ea52b312aaaf6d615d722bd92535d1e8b25fa9584a8dbe34dfa1ea9c18105ca01",
+        "038b68078a95f73f8710e8464dec52c61f9e21675ddf69d4f61b93cc417cf73d74"
+      ],
+      "sequence": 4294967295
+    },
+    {
+      "txid":
+          "80f8c6de5be2243013348219bbb7043a6d8d00ddc716baf6a69eab517f9a6fc1",
+      "vout": 1,
+      "scriptSig": {"asm": "", "hex": ""},
+      "txinwitness": [
+        "3044022045268613674326251c46caeaf435081ca753e4ee2018d79480c4930ad7d5e19f022050090a9add82e7272b8206b9d369675e7e9a5f1396fc93490143f0053666102901",
+        "028e2ede901e69887cb80603c8e207839f61a477d59beff17705162a2045dd974e"
+      ],
+      "sequence": 4294967295
+    }
+  ],
+  "vout": [
+    {
+      "value": 0.01,
+      "n": 0,
+      "scriptPubKey": {
+        "asm": "0 756037000a8676334b35368581a29143fc078471",
+        "hex": "0014756037000a8676334b35368581a29143fc078471",
+        "reqSigs": 1,
+        "type": "witness_v0_keyhash",
+        "addresses": ["nc1qw4srwqq2semrxje4x6zcrg53g07q0pr3yqv5kr"]
+      }
+    },
+    {
+      "value": 0.2880577,
+      "n": 1,
+      "scriptPubKey": {
+        "asm": "0 8207ee56ed52878d546567f29d17332b85f66e4b",
+        "hex": "00148207ee56ed52878d546567f29d17332b85f66e4b",
+        "reqSigs": 1,
+        "type": "witness_v0_keyhash",
+        "addresses": ["nc1qsgr7u4hd22rc64r9vlef69en9wzlvmjt8dzyrm"]
+      }
+    }
+  ],
+  "hex":
+      "02000000000102d7609f2ebf00afdc6b8cda9a5e92b4b9a0b8aaafadf890fbf99721854395fadf0000000000ffffffffc16f9a7f51ab9ea6f6ba16c7dd008d6d3a04b7bb198234133024e25bdec6f8800100000000ffffffff0240420f0000000000160014756037000a8676334b35368581a29143fc0784718a8ab701000000001600148207ee56ed52878d546567f29d17332b85f66e4b0247304402203535cf570aca7c1acfa6e8d2f43e0b188b76d0b7a75ffca448e6af953ffe8b6302202ea52b312aaaf6d615d722bd92535d1e8b25fa9584a8dbe34dfa1ea9c18105ca0121038b68078a95f73f8710e8464dec52c61f9e21675ddf69d4f61b93cc417cf73d7402473044022045268613674326251c46caeaf435081ca753e4ee2018d79480c4930ad7d5e19f022050090a9add82e7272b8206b9d369675e7e9a5f1396fc93490143f005366610290121028e2ede901e69887cb80603c8e207839f61a477d59beff17705162a2045dd974e00000000",
+  "blockhash":
+      "98f388ba99e3b6fc421c23edf3c699ada082b01e5a5d130af7550b7fa6184f2f",
+  "confirmations": 147,
+  "time": 1663145287,
+  "blocktime": 1663145287
+};
+
+final tx4Raw = {
+  "txid": "10e14b1d34c18a563b476c4c36688eb7caebf6658e25753074471d2adef460ba",
+  "hash": "cb0d83958db55c91fb9cd9cab65ee516e63aea68ae5650a692918779ceb46576",
+  "size": 191,
+  "vsize": 138,
+  "version": 160,
+  "locktime": 1314871,
+  "vin": [
+    {
+      "txid":
+          "aab01876c4db40b35ba00bbfb7c58aaec32cad7dc136214b7344a944606cbe73",
+      "vout": 0,
+      "scriptSig": {"asm": "", "hex": ""},
+      "txinwitness": [
+        "304402202e33ab9c5bb6a50c24de9ebfd1b2f398b4c9027787fb9620fda515a25b62ffcf02205e8371aeeda3b3765fa1e2a5c7ebce5dffbf18932012670c1f5266992f9ed9c901",
+        "039ca6c697fed4daf1697f137e7d5b113ff7b6c48ea48d707addd9cfa51889a42a"
+      ],
+      "sequence": 4294967293
+    }
+  ],
+  "vout": [
+    {
+      "n": 0,
+      "type": "standard",
+      "value": 0.09945773,
+      "valueSat": 9945773,
+      "scriptPubKey": {
+        "asm":
+            "OP_DUP OP_HASH160 b9833ad924ab05567ea2b679a5c523c66a1da6d7 OP_EQUALVERIFY OP_CHECKSIG",
+        "hex": "76a914b9833ad924ab05567ea2b679a5c523c66a1da6d788ac",
+        "reqSigs": 1,
+        "type": "pubkeyhash",
+        "addresses": ["PpqgMahyfqfasunUKfkmVfdpyhhrHa2ibY"]
+      }
+    }
+  ],
+  "blockhash":
+      "74e2d8acec688645120925c8a10d2fdf9ec61278534c0788d749162a6899ddaf",
+  "height": 1314873,
+  "confirmations": 493,
+  "time": 1669740960,
+  "blocktime": 1669740960
+};
diff --git a/test/services/coins/particl/particl_utxo_sample_data.dart b/test/services/coins/particl/particl_utxo_sample_data.dart
new file mode 100644
index 000000000..57adb0357
--- /dev/null
+++ b/test/services/coins/particl/particl_utxo_sample_data.dart
@@ -0,0 +1,58 @@
+import 'package:stackwallet/models/paymint/utxo_model.dart';
+
+final Map<String, List<Map<String, dynamic>>> batchGetUTXOResponse0 = {
+  "some id 0": [
+    {
+      "tx_pos": 0,
+      "value": 9973187,
+      "tx_hash":
+          "7b932948c95cf483798011da3fc77b6d53ee26d3d2ba4d90748cd007bdce48e8",
+      "height": 1314869
+    },
+    {
+      "tx_pos": 0,
+      "value": 50000000,
+      "tx_hash":
+          "aae9e712e26e5ff77ac2258c47a845ad6e952d580c2ad805e2b5d7667f3d4e42",
+      "height": 1297229
+    },
+  ],
+  "some id 1": [],
+};
+
+final utxoList = [
+  UtxoObject(
+    txid: "aab01876c4db40b35ba00bbfb7c58aaec32cad7dc136214b7344a944606cbe73",
+    vout: 0,
+    status: Status(
+      confirmed: true,
+      confirmations: 516,
+      blockHeight: 1314869,
+      blockTime: 1669740688,
+      blockHash:
+          "6146005e4b21b72d0e2afe5b0cce3abd6e9e9e71c6cf6a1e1150d33e33ba81d4",
+    ),
+    value: 9973187,
+    fiatWorth: "\$0",
+    txName: "pw1qj6t0kvsmx8qd95pdh4rwxaz5qp5qtfz0xq2rja",
+    blocked: false,
+    isCoinbase: false,
+  ),
+  UtxoObject(
+    txid: "909bdf555736c272df0e1df52ca5fcce4f1090b74c0e5d9319bb40e02f4b3add",
+    vout: 0,
+    status: Status(
+      confirmed: true,
+      confirmations: 18173,
+      blockHeight: 1297229,
+      blockTime: 1667469296,
+      blockHash:
+          "5c5c1a4e2d9cc77a1df4337359f901c92bb4907cff85312599b06141fd1d96d9",
+    ),
+    value: 50000000,
+    fiatWorth: "\$0",
+    txName: "PhDSyHLt7ejdPXGve3HFr93pSdFLHUBwdr",
+    blocked: false,
+    isCoinbase: false,
+  ),
+];
diff --git a/test/services/coins/particl/particl_wallet_test.dart b/test/services/coins/particl/particl_wallet_test.dart
new file mode 100644
index 000000000..bbe34a5ec
--- /dev/null
+++ b/test/services/coins/particl/particl_wallet_test.dart
@@ -0,0 +1,1664 @@
+import 'package:decimal/decimal.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:hive/hive.dart';
+import 'package:hive_test/hive_test.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart';
+import 'package:stackwallet/electrumx_rpc/electrumx.dart';
+import 'package:stackwallet/models/models.dart';
+import 'package:stackwallet/services/coins/particl/particl_wallet.dart';
+import 'package:stackwallet/services/price.dart';
+import 'package:stackwallet/services/transaction_notification_tracker.dart';
+import 'package:stackwallet/utilities/enums/coin_enum.dart';
+import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
+import 'package:tuple/tuple.dart';
+
+import 'particl_history_sample_data.dart';
+import 'particl_transaction_data_samples.dart';
+import 'particl_utxo_sample_data.dart';
+import 'particl_wallet_test.mocks.dart';
+import 'particl_wallet_test_parameters.dart';
+
+@GenerateMocks(
+    [ElectrumX, CachedElectrumX, PriceAPI, TransactionNotificationTracker])
+void main() {
+  group("particl constants", () {
+    test("particl minimum confirmations", () async {
+      expect(MINIMUM_CONFIRMATIONS,
+          1); // TODO confirm particl minimum confirmations
+    });
+    test("particl dust limit", () async {
+      expect(DUST_LIMIT, 294); // TODO confirm particl dust limit
+    });
+    test("particl mainnet genesis block hash", () async {
+      expect(GENESIS_HASH_MAINNET,
+          "0000ee0784c195317ac95623e22fddb8c7b8825dc3998e0bb924d66866eccf4c");
+    });
+    test("particl testnet genesis block hash", () async {
+      expect(GENESIS_HASH_TESTNET,
+          "0000594ada5310b367443ee0afd4fa3d0bbd5850ea4e33cdc7d6a904a7ec7c90");
+    });
+  });
+
+  test("particl DerivePathType enum", () {
+    expect(DerivePathType.values.length, 2);
+    expect(DerivePathType.values.toString(),
+        "[DerivePathType.bip44, DerivePathType.bip84]");
+  });
+
+  group("bip32 node/root", () {
+    test("getBip32Root", () {
+      final root = getBip32Root(TEST_MNEMONIC, particl);
+      expect(root.toWIF(), ROOT_WIF);
+    });
+
+    // test("getBip32NodeFromRoot", () {
+    //   final root = getBip32Root(TEST_MNEMONIC, particl);
+    //   // two mainnet
+    //   final node44 = getBip32NodeFromRoot(0, 0, root, DerivePathType.bip44);
+    //   expect(node44.toWIF(), NODE_WIF_44);
+    //   final node49 = getBip32NodeFromRoot(0, 0, root, DerivePathType.bip49);
+    //   expect(node49.toWIF(), NODE_WIF_49);
+    //   // and one on testnet
+    //   final node84 = getBip32NodeFromRoot(
+    //       0, 0, getBip32Root(TEST_MNEMONIC, testnet), DerivePathType.bip84);
+    //   expect(node84.toWIF(), NODE_WIF_84);
+    //   // a bad derive path
+    //   bool didThrow = false;
+    //   try {
+    //     getBip32NodeFromRoot(0, 0, root, null);
+    //   } catch (_) {
+    //     didThrow = true;
+    //   }
+    //   expect(didThrow, true);
+    //   // finally an invalid network
+    //   didThrow = false;
+    //   final invalidNetwork = NetworkType(
+    //       messagePrefix: '\x18hello world\n',
+    //       bech32: 'gg',
+    //       bip32: Bip32Type(public: 0x055521e, private: 0x055555),
+    //       pubKeyHash: 0x55,
+    //       scriptHash: 0x55,
+    //       wif: 0x00);
+    //   try {
+    //     getBip32NodeFromRoot(0, 0, getBip32Root(TEST_MNEMONIC, invalidNetwork),
+    //         DerivePathType.bip44);
+    //   } catch (_) {
+    //     didThrow = true;
+    //   }
+    //   expect(didThrow, true);
+    // });
+    //TODO Testnet not setup
+    // test("basic getBip32Node", () {
+    //   final node =
+    //       getBip32Node(0, 0, TEST_MNEMONIC, testnet, DerivePathType.bip84);
+    //   expect(node.toWIF(), NODE_WIF_84);
+    // });
+  });
+
+  group("validate mainnet particl addresses", () {
+    MockElectrumX? client;
+    MockCachedElectrumX? cachedClient;
+    MockPriceAPI? priceAPI;
+    late FakeSecureStorage secureStore;
+    MockTransactionNotificationTracker? tracker;
+
+    ParticlWallet?
+        mainnetWallet; // TODO reimplement testnet, see 9baa30c1a40b422bb5f4746efc1220b52691ace6 and sneurlax/stack_wallet#ec399ade0aef1d9ab2dd78876a2d20819dae4ba0
+
+    setUp(() {
+      client = MockElectrumX();
+      cachedClient = MockCachedElectrumX();
+      priceAPI = MockPriceAPI();
+      secureStore = FakeSecureStorage();
+      tracker = MockTransactionNotificationTracker();
+
+      mainnetWallet = ParticlWallet(
+        walletId: "validateAddressMainNet",
+        walletName: "validateAddressMainNet",
+        coin: Coin.particl,
+        client: client!,
+        cachedClient: cachedClient!,
+        tracker: tracker!,
+        priceAPI: priceAPI,
+        secureStore: secureStore,
+      );
+    });
+
+    test("valid mainnet particl legacy/p2pkh address", () {
+      expect(
+          mainnetWallet?.validateAddress("Pi9W46PhXkNRusar2KVMbXftYpGzEYGcSa"),
+          true);
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("valid mainnet particl legacy/p2pkh address type", () {
+      expect(
+          mainnetWallet?.addressType(
+              address: "Pi9W46PhXkNRusar2KVMbXftYpGzEYGcSa"),
+          DerivePathType.bip44);
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(tracker);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("valid mainnet particl p2wpkh address", () {
+      expect(
+          mainnetWallet
+              ?.validateAddress("pw1qj6t0kvsmx8qd95pdh4rwxaz5qp5qtfz0xq2rja"),
+          true);
+      expect(
+          mainnetWallet
+              ?.validateAddress("bc1qc5ymmsay89r6gr4fy2kklvrkuvzyln4shdvjhf"),
+          false);
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("valid mainnet particl legacy/p2pkh address", () {
+      expect(
+          mainnetWallet?.validateAddress("PputQYxNxMiYh3sg7vSh25wg3XxHiPHag7"),
+          true);
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("invalid mainnet particl legacy/p2pkh address", () {
+      expect(
+          mainnetWallet?.validateAddress("PputQYxNxMiYh3sg7vSh25wg3XxHiP0000"),
+          false);
+      expect(
+          mainnetWallet?.validateAddress("16YB85zQHjro7fqjR2hMcwdQWCX8jNVtr5"),
+          false);
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("invalid mainnet particl p2wpkh address", () {
+      expect(
+          mainnetWallet
+              ?.validateAddress("pw1qce3dhmmle4e0833mssj7ptta3ehydjf0tsa3ju"),
+          false);
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(tracker);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("invalid bech32 address type", () {
+      expect(
+          () => mainnetWallet?.addressType(
+              address: "tb1qzzlm6mnc8k54mx6akehl8p9ray8r439va5ndyq"),
+          throwsArgumentError);
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(tracker);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("address has no matching script", () {
+      expect(
+          () => mainnetWallet?.addressType(
+              address: "mpMk94ETazqonHutyC1v6ajshgtP8oiFKU"),
+          throwsArgumentError);
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(tracker);
+      verifyNoMoreInteractions(priceAPI);
+    });
+  });
+
+  group("testNetworkConnection", () {
+    MockElectrumX? client;
+    MockCachedElectrumX? cachedClient;
+    MockPriceAPI? priceAPI;
+    late FakeSecureStorage secureStore;
+    MockTransactionNotificationTracker? tracker;
+
+    ParticlWallet? part;
+
+    setUp(() {
+      client = MockElectrumX();
+      cachedClient = MockCachedElectrumX();
+      priceAPI = MockPriceAPI();
+      secureStore = FakeSecureStorage();
+      tracker = MockTransactionNotificationTracker();
+
+      part = ParticlWallet(
+        walletId: "testNetworkConnection",
+        walletName: "testNetworkConnection",
+        coin: Coin.particl,
+        client: client!,
+        cachedClient: cachedClient!,
+        tracker: tracker!,
+        priceAPI: priceAPI,
+        secureStore: secureStore,
+      );
+    });
+
+    test("attempted connection fails due to server error", () async {
+      when(client?.ping()).thenAnswer((_) async => false);
+      final bool? result = await part?.testNetworkConnection();
+      expect(result, false);
+      expect(secureStore.interactions, 0);
+      verify(client?.ping()).called(1);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("attempted connection fails due to exception", () async {
+      when(client?.ping()).thenThrow(Exception);
+      final bool? result = await part?.testNetworkConnection();
+      expect(result, false);
+      expect(secureStore.interactions, 0);
+      verify(client?.ping()).called(1);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("attempted connection test success", () async {
+      when(client?.ping()).thenAnswer((_) async => true);
+      final bool? result = await part?.testNetworkConnection();
+      expect(result, true);
+      expect(secureStore.interactions, 0);
+      verify(client?.ping()).called(1);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+  });
+
+  group("basic getters, setters, and functions", () {
+    final testWalletId = "ParticltestWalletID";
+    final testWalletName = "ParticlWallet";
+
+    MockElectrumX? client;
+    MockCachedElectrumX? cachedClient;
+    MockPriceAPI? priceAPI;
+    late FakeSecureStorage secureStore;
+    MockTransactionNotificationTracker? tracker;
+
+    ParticlWallet? part;
+
+    setUp(() async {
+      client = MockElectrumX();
+      cachedClient = MockCachedElectrumX();
+      priceAPI = MockPriceAPI();
+      secureStore = FakeSecureStorage();
+      tracker = MockTransactionNotificationTracker();
+
+      part = ParticlWallet(
+        walletId: testWalletId,
+        walletName: testWalletName,
+        coin: Coin.particl,
+        client: client!,
+        cachedClient: cachedClient!,
+        tracker: tracker!,
+        priceAPI: priceAPI,
+        secureStore: secureStore,
+      );
+    });
+
+    test("get networkType main", () async {
+      expect(Coin.particl, Coin.particl);
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("get networkType test", () async {
+      part = ParticlWallet(
+        walletId: testWalletId,
+        walletName: testWalletName,
+        coin: Coin.particl,
+        client: client!,
+        cachedClient: cachedClient!,
+        tracker: tracker!,
+        priceAPI: priceAPI,
+        secureStore: secureStore,
+      );
+      expect(Coin.particl, Coin.particl);
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("get cryptoCurrency", () async {
+      expect(Coin.particl, Coin.particl);
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("get coinName", () async {
+      expect(Coin.particl, Coin.particl);
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("get coinTicker", () async {
+      expect(Coin.particl, Coin.particl);
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("get and set walletName", () async {
+      expect(Coin.particl, Coin.particl);
+      part?.walletName = "new name";
+      expect(part?.walletName, "new name");
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("estimateTxFee", () async {
+      expect(part?.estimateTxFee(vSize: 356, feeRatePerKB: 1), 356);
+      expect(part?.estimateTxFee(vSize: 356, feeRatePerKB: 900), 356);
+      expect(part?.estimateTxFee(vSize: 356, feeRatePerKB: 999), 356);
+      expect(part?.estimateTxFee(vSize: 356, feeRatePerKB: 1000), 356);
+      expect(part?.estimateTxFee(vSize: 356, feeRatePerKB: 1001), 712);
+      expect(part?.estimateTxFee(vSize: 356, feeRatePerKB: 1699), 712);
+      expect(part?.estimateTxFee(vSize: 356, feeRatePerKB: 2000), 712);
+      expect(part?.estimateTxFee(vSize: 356, feeRatePerKB: 12345), 4628);
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("get fees succeeds", () async {
+      when(client?.ping()).thenAnswer((_) async => true);
+      when(client?.getServerFeatures()).thenAnswer((_) async => {
+            "hosts": {},
+            "pruning": null,
+            "server_version": "Unit tests",
+            "protocol_min": "1.4",
+            "protocol_max": "1.4.2",
+            "genesis_hash": GENESIS_HASH_MAINNET,
+            "hash_function": "sha256",
+            "services": []
+          });
+      when(client?.estimateFee(blocks: 1))
+          .thenAnswer((realInvocation) async => Decimal.zero);
+      when(client?.estimateFee(blocks: 5))
+          .thenAnswer((realInvocation) async => Decimal.one);
+      when(client?.estimateFee(blocks: 20))
+          .thenAnswer((realInvocation) async => Decimal.ten);
+
+      final fees = await part?.fees;
+      expect(fees, isA<FeeObject>());
+      expect(fees?.slow, 1000000000);
+      expect(fees?.medium, 100000000);
+      expect(fees?.fast, 0);
+
+      verify(client?.estimateFee(blocks: 1)).called(1);
+      verify(client?.estimateFee(blocks: 5)).called(1);
+      verify(client?.estimateFee(blocks: 20)).called(1);
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("get fees fails", () async {
+      when(client?.ping()).thenAnswer((_) async => true);
+      when(client?.getServerFeatures()).thenAnswer((_) async => {
+            "hosts": {},
+            "pruning": null,
+            "server_version": "Unit tests",
+            "protocol_min": "1.4",
+            "protocol_max": "1.4.2",
+            "genesis_hash": GENESIS_HASH_MAINNET,
+            "hash_function": "sha256",
+            "services": []
+          });
+      when(client?.estimateFee(blocks: 1))
+          .thenAnswer((realInvocation) async => Decimal.zero);
+      when(client?.estimateFee(blocks: 5))
+          .thenAnswer((realInvocation) async => Decimal.one);
+      when(client?.estimateFee(blocks: 20))
+          .thenThrow(Exception("some exception"));
+
+      bool didThrow = false;
+      try {
+        await part?.fees;
+      } catch (_) {
+        didThrow = true;
+      }
+
+      expect(didThrow, true);
+
+      verify(client?.estimateFee(blocks: 1)).called(1);
+      verify(client?.estimateFee(blocks: 5)).called(1);
+      verify(client?.estimateFee(blocks: 20)).called(1);
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    // test("get maxFee", () async {
+    //   when(client?.ping()).thenAnswer((_) async => true);
+    //   when(client?.getServerFeatures()).thenAnswer((_) async => {
+    //         "hosts": {},
+    //         "pruning": null,
+    //         "server_version": "Unit tests",
+    //         "protocol_min": "1.4",
+    //         "protocol_max": "1.4.2",
+    //         "genesis_hash": GENESIS_HASH_TESTNET,
+    //         "hash_function": "sha256",
+    //         "services": []
+    //       });
+    //   when(client?.estimateFee(blocks: 20))
+    //       .thenAnswer((realInvocation) async => Decimal.zero);
+    //   when(client?.estimateFee(blocks: 5))
+    //       .thenAnswer((realInvocation) async => Decimal.one);
+    //   when(client?.estimateFee(blocks: 1))
+    //       .thenAnswer((realInvocation) async => Decimal.ten);
+    //
+    //   final maxFee = await part?.maxFee;
+    //   expect(maxFee, 1000000000);
+    //
+    //   verify(client?.estimateFee(blocks: 1)).called(1);
+    //   verify(client?.estimateFee(blocks: 5)).called(1);
+    //   verify(client?.estimateFee(blocks: 20)).called(1);
+    //   expect(secureStore.interactions, 0);
+    //   verifyNoMoreInteractions(client);
+    //   verifyNoMoreInteractions(cachedClient);
+    //   verifyNoMoreInteractions(tracker);
+    //   verifyNoMoreInteractions(priceAPI);
+    // });
+  });
+
+  group("Particl service class functions that depend on shared storage", () {
+    const testWalletId = "ParticltestWalletID";
+    const testWalletName = "ParticlWallet";
+
+    bool hiveAdaptersRegistered = false;
+
+    MockElectrumX? client;
+    MockCachedElectrumX? cachedClient;
+    MockPriceAPI? priceAPI;
+    late FakeSecureStorage secureStore;
+    MockTransactionNotificationTracker? tracker;
+
+    ParticlWallet? part;
+
+    setUp(() async {
+      await setUpTestHive();
+      if (!hiveAdaptersRegistered) {
+        hiveAdaptersRegistered = true;
+
+        // Registering Transaction Model Adapters
+        Hive.registerAdapter(TransactionDataAdapter());
+        Hive.registerAdapter(TransactionChunkAdapter());
+        Hive.registerAdapter(TransactionAdapter());
+        Hive.registerAdapter(InputAdapter());
+        Hive.registerAdapter(OutputAdapter());
+
+        // Registering Utxo Model Adapters
+        Hive.registerAdapter(UtxoDataAdapter());
+        Hive.registerAdapter(UtxoObjectAdapter());
+        Hive.registerAdapter(StatusAdapter());
+
+        final wallets = await Hive.openBox('wallets');
+        await wallets.put('currentWalletName', testWalletName);
+      }
+
+      client = MockElectrumX();
+      cachedClient = MockCachedElectrumX();
+      priceAPI = MockPriceAPI();
+      secureStore = FakeSecureStorage();
+      tracker = MockTransactionNotificationTracker();
+
+      part = ParticlWallet(
+        walletId: testWalletId,
+        walletName: testWalletName,
+        coin: Coin.particl,
+        client: client!,
+        cachedClient: cachedClient!,
+        tracker: tracker!,
+        priceAPI: priceAPI,
+        secureStore: secureStore,
+      );
+    });
+    //TODO - THis function definition has changed, possibly remove
+    // test("initializeWallet no network", () async {
+    //   when(client?.ping()).thenAnswer((_) async => false);
+    //   expect(await part?.initializeNew(), false);
+    //   expect(secureStore.interactions, 0);
+    //   verify(client?.ping()).called(1);
+    //   verifyNoMoreInteractions(client);
+    //   verifyNoMoreInteractions(cachedClient);
+    //   verifyNoMoreInteractions(priceAPI);
+    // });
+
+    // test("initializeWallet no network exception", () async {
+    //   when(client?.ping()).thenThrow(Exception("Network connection failed"));
+    //   final wallets = await Hive.openBox(testWalletId);
+    //   expect(await nmc?.initializeExisting(), false);
+    //   expect(secureStore.interactions, 0);
+    //   verify(client?.ping()).called(1);
+    //   verifyNoMoreInteractions(client);
+    //   verifyNoMoreInteractions(cachedClient);
+    //   verifyNoMoreInteractions(priceAPI);
+    // });
+
+    test("initializeWallet mainnet throws bad network", () async {
+      when(client?.ping()).thenAnswer((_) async => true);
+      when(client?.getServerFeatures()).thenAnswer((_) async => {
+            "hosts": {},
+            "pruning": null,
+            "server_version": "Unit tests",
+            "protocol_min": "1.4",
+            "protocol_max": "1.4.2",
+            "genesis_hash": GENESIS_HASH_MAINNET,
+            "hash_function": "sha256",
+            "services": []
+          });
+      await Hive.openBox(testWalletId);
+
+      await expectLater(
+              () => part?.initializeExisting(), throwsA(isA<Exception>()))
+          .then((_) {
+        expect(secureStore.interactions, 0);
+        verifyNoMoreInteractions(client);
+        verifyNoMoreInteractions(cachedClient);
+        verifyNoMoreInteractions(priceAPI);
+      });
+    });
+
+    test("initializeWallet throws mnemonic overwrite exception", () async {
+      when(client?.ping()).thenAnswer((_) async => true);
+      when(client?.getServerFeatures()).thenAnswer((_) async => {
+            "hosts": {},
+            "pruning": null,
+            "server_version": "Unit tests",
+            "protocol_min": "1.4",
+            "protocol_max": "1.4.2",
+            "genesis_hash": GENESIS_HASH_MAINNET,
+            "hash_function": "sha256",
+            "services": []
+          });
+      await secureStore.write(
+          key: "${testWalletId}_mnemonic", value: "some mnemonic");
+
+      await Hive.openBox(testWalletId);
+      await expectLater(
+              () => part?.initializeExisting(), throwsA(isA<Exception>()))
+          .then((_) {
+        expect(secureStore.interactions, 1);
+        verifyNoMoreInteractions(client);
+        verifyNoMoreInteractions(cachedClient);
+        verifyNoMoreInteractions(priceAPI);
+      });
+    });
+
+    test(
+        "recoverFromMnemonic using empty seed on mainnet fails due to bad genesis hash match",
+        () async {
+      when(client?.getServerFeatures()).thenAnswer((_) async => {
+            "hosts": {},
+            "pruning": null,
+            "server_version": "Unit tests",
+            "protocol_min": "1.4",
+            "protocol_max": "1.4.2",
+            "genesis_hash": GENESIS_HASH_TESTNET,
+            "hash_function": "sha256",
+            "services": []
+          });
+
+      bool hasThrown = false;
+      try {
+        await part?.recoverFromMnemonic(
+            mnemonic: TEST_MNEMONIC,
+            maxUnusedAddressGap: 2,
+            maxNumberOfIndexesToCheck: 1000,
+            height: 4000);
+      } catch (_) {
+        hasThrown = true;
+      }
+      expect(hasThrown, true);
+
+      verify(client?.getServerFeatures()).called(1);
+
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test(
+        "recoverFromMnemonic using empty seed on mainnet fails due to attempted overwrite of mnemonic",
+        () async {
+      when(client?.getServerFeatures()).thenAnswer((_) async => {
+            "hosts": {},
+            "pruning": null,
+            "server_version": "Unit tests",
+            "protocol_min": "1.4",
+            "protocol_max": "1.4.2",
+            "genesis_hash": GENESIS_HASH_MAINNET,
+            "hash_function": "sha256",
+            "services": []
+          });
+
+      await secureStore.write(
+          key: "${testWalletId}_mnemonic", value: "some mnemonic words");
+
+      bool hasThrown = false;
+      try {
+        await part?.recoverFromMnemonic(
+            mnemonic: TEST_MNEMONIC,
+            maxUnusedAddressGap: 2,
+            maxNumberOfIndexesToCheck: 1000,
+            height: 4000);
+      } catch (_) {
+        hasThrown = true;
+      }
+      expect(hasThrown, true);
+
+      verify(client?.getServerFeatures()).called(1);
+
+      expect(secureStore.interactions, 2);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("recoverFromMnemonic using empty seed on mainnet succeeds", () async {
+      when(client?.getServerFeatures()).thenAnswer((_) async => {
+            "hosts": {},
+            "pruning": null,
+            "server_version": "Unit tests",
+            "protocol_min": "1.4",
+            "protocol_max": "1.4.2",
+            "genesis_hash": GENESIS_HASH_MAINNET,
+            "hash_function": "sha256",
+            "services": []
+          });
+      when(client?.getBatchHistory(args: historyBatchArgs0))
+          .thenAnswer((_) async => emptyHistoryBatchResponse);
+      when(client?.getBatchHistory(args: historyBatchArgs1))
+          .thenAnswer((_) async => emptyHistoryBatchResponse);
+      when(client?.getBatchHistory(args: historyBatchArgs2))
+          .thenAnswer((_) async => emptyHistoryBatchResponse);
+      when(client?.getBatchHistory(args: historyBatchArgs3))
+          .thenAnswer((_) async => emptyHistoryBatchResponse);
+
+      // await DB.instance.init();
+      final wallet = await Hive.openBox(testWalletId);
+      bool hasThrown = false;
+      try {
+        await part?.recoverFromMnemonic(
+            mnemonic: TEST_MNEMONIC,
+            maxUnusedAddressGap: 2,
+            maxNumberOfIndexesToCheck: 1000,
+            height: 4000);
+      } catch (_) {
+        hasThrown = true;
+      }
+      expect(hasThrown, false);
+
+      verify(client?.getServerFeatures()).called(1);
+      verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1);
+      verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1);
+      verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1);
+      verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1);
+
+      expect(secureStore.interactions, 14);
+      expect(secureStore.writes, 5);
+      expect(secureStore.reads, 9);
+      expect(secureStore.deletes, 0);
+
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("get mnemonic list", () async {
+      when(client?.getServerFeatures()).thenAnswer((_) async => {
+            "hosts": {},
+            "pruning": null,
+            "server_version": "Unit tests",
+            "protocol_min": "1.4",
+            "protocol_max": "1.4.2",
+            "genesis_hash": GENESIS_HASH_MAINNET,
+            "hash_function": "sha256",
+            "services": []
+          });
+      when(client?.getBatchHistory(args: historyBatchArgs0))
+          .thenAnswer((_) async => emptyHistoryBatchResponse);
+      when(client?.getBatchHistory(args: historyBatchArgs1))
+          .thenAnswer((_) async => emptyHistoryBatchResponse);
+      when(client?.getBatchHistory(args: historyBatchArgs2))
+          .thenAnswer((_) async => emptyHistoryBatchResponse);
+      when(client?.getBatchHistory(args: historyBatchArgs3))
+          .thenAnswer((_) async => emptyHistoryBatchResponse);
+
+      await Hive.openBox(testWalletId);
+
+      await part?.recoverFromMnemonic(
+          mnemonic: TEST_MNEMONIC,
+          maxUnusedAddressGap: 2,
+          maxNumberOfIndexesToCheck: 1000,
+          height: 4000);
+
+      expect(await part?.mnemonic, TEST_MNEMONIC.split(" "));
+
+      verify(client?.getServerFeatures()).called(1);
+      verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1);
+      verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1);
+      verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1);
+      verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1);
+
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("recoverFromMnemonic using non empty seed on mainnet succeeds",
+        () async {
+      when(client?.getServerFeatures()).thenAnswer((_) async => {
+            "hosts": {},
+            "pruning": null,
+            "server_version": "Unit tests",
+            "protocol_min": "1.4",
+            "protocol_max": "1.4.2",
+            "genesis_hash": GENESIS_HASH_MAINNET,
+            "hash_function": "sha256",
+            "services": []
+          });
+      when(client?.getBatchHistory(args: historyBatchArgs0))
+          .thenAnswer((_) async => historyBatchResponse);
+      when(client?.getBatchHistory(args: historyBatchArgs1))
+          .thenAnswer((_) async => historyBatchResponse);
+      when(client?.getBatchHistory(args: historyBatchArgs2))
+          .thenAnswer((_) async => historyBatchResponse);
+      when(client?.getBatchHistory(args: historyBatchArgs3))
+          .thenAnswer((_) async => historyBatchResponse);
+
+      List<dynamic> dynamicArgValues = [];
+
+      when(client?.getBatchHistory(args: anyNamed("args")))
+          .thenAnswer((realInvocation) async {
+        if (realInvocation.namedArguments.values.first.length == 1) {
+          dynamicArgValues.add(realInvocation.namedArguments.values.first);
+        }
+
+        return historyBatchResponse;
+      });
+
+      await Hive.openBox<dynamic>(testWalletId);
+
+      bool hasThrown = false;
+      try {
+        await part?.recoverFromMnemonic(
+            mnemonic: TEST_MNEMONIC,
+            maxUnusedAddressGap: 2,
+            maxNumberOfIndexesToCheck: 1000,
+            height: 4000);
+      } catch (_) {
+        hasThrown = true;
+      }
+      expect(hasThrown, false);
+
+      verify(client?.getServerFeatures()).called(1);
+      verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1);
+      verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1);
+      verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1);
+      verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1);
+
+      for (final arg in dynamicArgValues) {
+        final map = Map<String, List<dynamic>>.from(arg as Map);
+        verify(client?.getBatchHistory(args: map)).called(1);
+        expect(activeScriptHashes.contains(map.values.first.first as String),
+            true);
+      }
+
+      expect(secureStore.interactions, 10);
+      expect(secureStore.writes, 5);
+      expect(secureStore.reads, 5);
+      expect(secureStore.deletes, 0);
+
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("fullRescan succeeds", () async {
+      when(client?.getServerFeatures()).thenAnswer((_) async => {
+            "hosts": {},
+            "pruning": null,
+            "server_version": "Unit tests",
+            "protocol_min": "1.4",
+            "protocol_max": "1.4.2",
+            "genesis_hash": GENESIS_HASH_MAINNET,
+            "hash_function": "sha256",
+            "services": []
+          });
+      when(client?.getBatchHistory(args: historyBatchArgs0))
+          .thenAnswer((_) async => historyBatchResponse);
+      when(client?.getBatchHistory(args: historyBatchArgs1))
+          .thenAnswer((_) async => historyBatchResponse);
+      when(client?.getBatchHistory(args: historyBatchArgs2))
+          .thenAnswer((_) async => historyBatchResponse);
+      when(client?.getBatchHistory(args: historyBatchArgs3))
+          .thenAnswer((_) async => historyBatchResponse);
+      when(cachedClient?.clearSharedTransactionCache(coin: Coin.particl))
+          .thenAnswer((realInvocation) async {});
+
+      when(client?.getBatchHistory(args: {
+        "0": [
+          "8ba03c2c46ed4980fa1e4c84cbceeb2d5e1371a7ccbaf5f3d69c5114161a2247"
+        ]
+      })).thenAnswer((realInvocation) async => {"0": []});
+
+      when(client?.getBatchHistory(args: {
+        "0": [
+          "3fedd8a2d5fc355727afe353413dc1a0ef861ba768744d5b8193c33cbc829339"
+        ]
+      })).thenAnswer((realInvocation) async => {"0": []});
+
+      when(client?.getBatchHistory(args: {
+        "0": [
+          "b6fce6c41154ccf70676c5c91acd9b6899ef0195e34b4c05c4920daa827c19a3"
+        ]
+      })).thenAnswer((realInvocation) async => {"0": []});
+
+      when(client?.getBatchHistory(args: {
+        "0": [
+          "0e8b6756b404db5a381fd71ad79cb595a6c36c938cf9913c5a0494b667c2151a"
+        ]
+      })).thenAnswer((realInvocation) async => {"0": []});
+      when(client?.getBatchHistory(args: {
+        "0": [
+          "9b56ab30c7bef0e1eaa10a632c8e2dcdd11b2158d7a917c03d62936afd0015fc"
+        ]
+      })).thenAnswer((realInvocation) async => {"0": []});
+      when(client?.getBatchHistory(args: {
+        "0": [
+          "c4b1d9cd4edb7c13eae863b1e4f8fd5acff29f1fe153c4f859906cbea26a3f2f"
+        ]
+      })).thenAnswer((realInvocation) async => {"0": []});
+
+      final wallet = await Hive.openBox<dynamic>(testWalletId);
+
+      // restore so we have something to rescan
+      await part?.recoverFromMnemonic(
+          mnemonic: TEST_MNEMONIC,
+          maxUnusedAddressGap: 2,
+          maxNumberOfIndexesToCheck: 1000,
+          height: 4000);
+
+      // fetch valid wallet data
+      final preReceivingAddressesP2PKH =
+          await wallet.get('receivingAddressesP2PKH');
+      final preReceivingAddressesP2WPKH =
+          await wallet.get('receivingAddressesP2WPKH');
+      final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH');
+      final preChangeAddressesP2WPKH =
+          await wallet.get('changeAddressesP2WPKH');
+      final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH');
+      final preReceivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH');
+      final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH');
+      final preChangeIndexP2WPKH = await wallet.get('changeIndexP2WPKH');
+      final preUtxoData = await wallet.get('latest_utxo_model');
+      final preReceiveDerivationsStringP2PKH = await secureStore.read(
+          key: "${testWalletId}_receiveDerivationsP2PKH");
+      final preChangeDerivationsStringP2PKH =
+          await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH");
+      final preReceiveDerivationsStringP2WPKH = await secureStore.read(
+          key: "${testWalletId}_receiveDerivationsP2WPKH");
+      final preChangeDerivationsStringP2WPKH = await secureStore.read(
+          key: "${testWalletId}_changeDerivationsP2WPKH");
+
+      // destroy the data that the rescan will fix
+      await wallet.put(
+          'receivingAddressesP2PKH', ["some address", "some other address"]);
+      await wallet.put(
+          'receivingAddressesP2WPKH', ["some address", "some other address"]);
+      await wallet
+          .put('changeAddressesP2PKH', ["some address", "some other address"]);
+      await wallet
+          .put('changeAddressesP2WPKH', ["some address", "some other address"]);
+      await wallet.put('receivingIndexP2PKH', 123);
+      await wallet.put('receivingIndexP2WPKH', 123);
+      await wallet.put('changeIndexP2PKH', 123);
+      await wallet.put('changeIndexP2WPKH', 123);
+      await secureStore.write(
+          key: "${testWalletId}_receiveDerivationsP2PKH", value: "{}");
+      await secureStore.write(
+          key: "${testWalletId}_changeDerivationsP2PKH", value: "{}");
+      await secureStore.write(
+          key: "${testWalletId}_receiveDerivationsP2WPKH", value: "{}");
+      await secureStore.write(
+          key: "${testWalletId}_changeDerivationsP2WPKH", value: "{}");
+
+      bool hasThrown = false;
+      try {
+        await part?.fullRescan(2, 1000);
+      } catch (_) {
+        hasThrown = true;
+      }
+      expect(hasThrown, false);
+
+      // fetch wallet data again
+      final receivingAddressesP2PKH =
+          await wallet.get('receivingAddressesP2PKH');
+      final receivingAddressesP2WPKH =
+          await wallet.get('receivingAddressesP2WPKH');
+      final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH');
+      final changeAddressesP2WPKH = await wallet.get('changeAddressesP2WPKH');
+      final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH');
+      final receivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH');
+      final changeIndexP2PKH = await wallet.get('changeIndexP2PKH');
+      final changeIndexP2WPKH = await wallet.get('changeIndexP2WPKH');
+      final utxoData = await wallet.get('latest_utxo_model');
+      final receiveDerivationsStringP2PKH = await secureStore.read(
+          key: "${testWalletId}_receiveDerivationsP2PKH");
+      final changeDerivationsStringP2PKH =
+          await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH");
+      final receiveDerivationsStringP2WPKH = await secureStore.read(
+          key: "${testWalletId}_receiveDerivationsP2WPKH");
+      final changeDerivationsStringP2WPKH = await secureStore.read(
+          key: "${testWalletId}_changeDerivationsP2WPKH");
+
+      expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH);
+      expect(preReceivingAddressesP2WPKH, receivingAddressesP2WPKH);
+      expect(preChangeAddressesP2PKH, changeAddressesP2PKH);
+      expect(preChangeAddressesP2WPKH, changeAddressesP2WPKH);
+      expect(preReceivingIndexP2PKH, receivingIndexP2PKH);
+      expect(preReceivingIndexP2WPKH, receivingIndexP2WPKH);
+      expect(preChangeIndexP2PKH, changeIndexP2PKH);
+      expect(preChangeIndexP2WPKH, changeIndexP2WPKH);
+      expect(preUtxoData, utxoData);
+      expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH);
+      expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH);
+      expect(preReceiveDerivationsStringP2WPKH, receiveDerivationsStringP2WPKH);
+      expect(preChangeDerivationsStringP2WPKH, changeDerivationsStringP2WPKH);
+
+      verify(client?.getServerFeatures()).called(1);
+      verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2);
+      verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2);
+      verify(client?.getBatchHistory(args: historyBatchArgs2)).called(2);
+      verify(client?.getBatchHistory(args: historyBatchArgs3)).called(2);
+
+      verify(client?.getBatchHistory(args: {
+        "0": [
+          "3fedd8a2d5fc355727afe353413dc1a0ef861ba768744d5b8193c33cbc829339"
+        ]
+      })).called(2);
+
+      verify(client?.getBatchHistory(args: {
+        "0": [
+          "b6fce6c41154ccf70676c5c91acd9b6899ef0195e34b4c05c4920daa827c19a3"
+        ]
+      })).called(2);
+
+      verify(client?.getBatchHistory(args: {
+        "0": [
+          "0e8b6756b404db5a381fd71ad79cb595a6c36c938cf9913c5a0494b667c2151a"
+        ]
+      })).called(2);
+
+      verify(client?.getBatchHistory(args: {
+        "0": [
+          "c4b1d9cd4edb7c13eae863b1e4f8fd5acff29f1fe153c4f859906cbea26a3f2f"
+        ]
+      })).called(2);
+      verify(cachedClient?.clearSharedTransactionCache(coin: Coin.particl))
+          .called(1);
+
+      expect(secureStore.writes, 17);
+      expect(secureStore.reads, 22);
+      expect(secureStore.deletes, 4);
+
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("fullRescan fails", () async {
+      when(client?.getServerFeatures()).thenAnswer((_) async => {
+            "hosts": {},
+            "pruning": null,
+            "server_version": "Unit tests",
+            "protocol_min": "1.4",
+            "protocol_max": "1.4.2",
+            "genesis_hash": GENESIS_HASH_MAINNET,
+            "hash_function": "sha256",
+            "services": []
+          });
+
+      when(client?.getBatchHistory(args: historyBatchArgs0))
+          .thenAnswer((_) async => historyBatchResponse);
+      when(client?.getBatchHistory(args: historyBatchArgs1))
+          .thenAnswer((_) async => historyBatchResponse);
+      when(client?.getBatchHistory(args: historyBatchArgs2))
+          .thenAnswer((_) async => historyBatchResponse);
+      when(client?.getBatchHistory(args: historyBatchArgs3))
+          .thenAnswer((_) async => historyBatchResponse);
+
+      when(client?.getBatchHistory(args: {
+        "0": [
+          "8ba03c2c46ed4980fa1e4c84cbceeb2d5e1371a7ccbaf5f3d69c5114161a2247"
+        ]
+      })).thenAnswer((realInvocation) async => {"0": []});
+
+      when(client?.getBatchHistory(args: {
+        "0": [
+          "3fedd8a2d5fc355727afe353413dc1a0ef861ba768744d5b8193c33cbc829339"
+        ]
+      })).thenAnswer((realInvocation) async => {"0": []});
+
+      when(client?.getBatchHistory(args: {
+        "0": [
+          "b6fce6c41154ccf70676c5c91acd9b6899ef0195e34b4c05c4920daa827c19a3"
+        ]
+      })).thenAnswer((realInvocation) async => {"0": []});
+
+      when(client?.getBatchHistory(args: {
+        "0": [
+          "0e8b6756b404db5a381fd71ad79cb595a6c36c938cf9913c5a0494b667c2151a"
+        ]
+      })).thenAnswer((realInvocation) async => {"0": []});
+
+      when(client?.getBatchHistory(args: {
+        "0": [
+          "9b56ab30c7bef0e1eaa10a632c8e2dcdd11b2158d7a917c03d62936afd0015fc"
+        ]
+      })).thenAnswer((realInvocation) async => {"0": []});
+
+      when(client?.getBatchHistory(args: {
+        "0": [
+          "c4b1d9cd4edb7c13eae863b1e4f8fd5acff29f1fe153c4f859906cbea26a3f2f"
+        ]
+      })).thenAnswer((realInvocation) async => {"0": []});
+
+      when(cachedClient?.clearSharedTransactionCache(coin: Coin.particl))
+          .thenAnswer((realInvocation) async {});
+
+      final wallet = await Hive.openBox<dynamic>(testWalletId);
+
+      // restore so we have something to rescan
+      await part?.recoverFromMnemonic(
+          mnemonic: TEST_MNEMONIC,
+          maxUnusedAddressGap: 2,
+          maxNumberOfIndexesToCheck: 1000,
+          height: 4000);
+
+      // fetch wallet data
+      final preReceivingAddressesP2PKH =
+          await wallet.get('receivingAddressesP2PKH');
+      final preReceivingAddressesP2SH =
+          await wallet.get('receivingAddressesP2SH');
+      final preReceivingAddressesP2WPKH =
+          await wallet.get('receivingAddressesP2WPKH');
+      final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH');
+      final preChangeAddressesP2SH = await wallet.get('changeAddressesP2SH');
+      final preChangeAddressesP2WPKH =
+          await wallet.get('changeAddressesP2WPKH');
+      final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH');
+      final preReceivingIndexP2SH = await wallet.get('receivingIndexP2SH');
+      final preReceivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH');
+      final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH');
+      final preChangeIndexP2SH = await wallet.get('changeIndexP2SH');
+      final preChangeIndexP2WPKH = await wallet.get('changeIndexP2WPKH');
+      final preUtxoData = await wallet.get('latest_utxo_model');
+      final preReceiveDerivationsStringP2PKH = await secureStore.read(
+          key: "${testWalletId}_receiveDerivationsP2PKH");
+      final preChangeDerivationsStringP2PKH =
+          await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH");
+      final preReceiveDerivationsStringP2SH =
+          await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH");
+      final preChangeDerivationsStringP2SH =
+          await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH");
+      final preReceiveDerivationsStringP2WPKH = await secureStore.read(
+          key: "${testWalletId}_receiveDerivationsP2WPKH");
+      final preChangeDerivationsStringP2WPKH = await secureStore.read(
+          key: "${testWalletId}_changeDerivationsP2WPKH");
+
+      when(client?.getBatchHistory(args: historyBatchArgs0))
+          .thenThrow(Exception("fake exception"));
+      when(client?.getBatchHistory(args: historyBatchArgs1))
+          .thenThrow(Exception("fake exception"));
+      when(client?.getBatchHistory(args: historyBatchArgs2))
+          .thenThrow(Exception("fake exception"));
+      when(client?.getBatchHistory(args: historyBatchArgs3))
+          .thenThrow(Exception("fake exception"));
+
+      bool hasThrown = false;
+      try {
+        await part?.fullRescan(2, 1000);
+      } catch (_) {
+        hasThrown = true;
+      }
+      expect(hasThrown, true);
+
+      // fetch wallet data again
+      final receivingAddressesP2PKH =
+          await wallet.get('receivingAddressesP2PKH');
+      final receivingAddressesP2SH = await wallet.get('receivingAddressesP2SH');
+      final receivingAddressesP2WPKH =
+          await wallet.get('receivingAddressesP2WPKH');
+      final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH');
+      final changeAddressesP2SH = await wallet.get('changeAddressesP2SH');
+      final changeAddressesP2WPKH = await wallet.get('changeAddressesP2WPKH');
+      final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH');
+      final receivingIndexP2SH = await wallet.get('receivingIndexP2SH');
+      final receivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH');
+      final changeIndexP2PKH = await wallet.get('changeIndexP2PKH');
+      final changeIndexP2SH = await wallet.get('changeIndexP2SH');
+      final changeIndexP2WPKH = await wallet.get('changeIndexP2WPKH');
+      final utxoData = await wallet.get('latest_utxo_model');
+      final receiveDerivationsStringP2PKH = await secureStore.read(
+          key: "${testWalletId}_receiveDerivationsP2PKH");
+      final changeDerivationsStringP2PKH =
+          await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH");
+      final receiveDerivationsStringP2SH =
+          await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH");
+      final changeDerivationsStringP2SH =
+          await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH");
+      final receiveDerivationsStringP2WPKH = await secureStore.read(
+          key: "${testWalletId}_receiveDerivationsP2WPKH");
+      final changeDerivationsStringP2WPKH = await secureStore.read(
+          key: "${testWalletId}_changeDerivationsP2WPKH");
+
+      expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH);
+      expect(preReceivingAddressesP2SH, receivingAddressesP2SH);
+      expect(preReceivingAddressesP2WPKH, receivingAddressesP2WPKH);
+      expect(preChangeAddressesP2PKH, changeAddressesP2PKH);
+      expect(preChangeAddressesP2SH, changeAddressesP2SH);
+      expect(preChangeAddressesP2WPKH, changeAddressesP2WPKH);
+      expect(preReceivingIndexP2PKH, receivingIndexP2PKH);
+      expect(preReceivingIndexP2SH, receivingIndexP2SH);
+      expect(preReceivingIndexP2WPKH, receivingIndexP2WPKH);
+      expect(preChangeIndexP2PKH, changeIndexP2PKH);
+      expect(preChangeIndexP2SH, changeIndexP2SH);
+      expect(preChangeIndexP2WPKH, changeIndexP2WPKH);
+      expect(preUtxoData, utxoData);
+      expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH);
+      expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH);
+      expect(preReceiveDerivationsStringP2SH, receiveDerivationsStringP2SH);
+      expect(preChangeDerivationsStringP2SH, changeDerivationsStringP2SH);
+      expect(preReceiveDerivationsStringP2WPKH, receiveDerivationsStringP2WPKH);
+      expect(preChangeDerivationsStringP2WPKH, changeDerivationsStringP2WPKH);
+
+      verify(client?.getServerFeatures()).called(1);
+      verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2);
+      verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2);
+      verify(client?.getBatchHistory(args: historyBatchArgs2)).called(2);
+      verify(client?.getBatchHistory(args: historyBatchArgs3)).called(2);
+      verify(client?.getBatchHistory(args: {
+        "0": [
+          "3fedd8a2d5fc355727afe353413dc1a0ef861ba768744d5b8193c33cbc829339"
+        ]
+      })).called(1);
+      verify(client?.getBatchHistory(args: {
+        "0": [
+          "b6fce6c41154ccf70676c5c91acd9b6899ef0195e34b4c05c4920daa827c19a3"
+        ]
+      })).called(1);
+      verify(client?.getBatchHistory(args: {
+        "0": [
+          "0e8b6756b404db5a381fd71ad79cb595a6c36c938cf9913c5a0494b667c2151a"
+        ]
+      })).called(1);
+      verify(client?.getBatchHistory(args: {
+        "0": [
+          "c4b1d9cd4edb7c13eae863b1e4f8fd5acff29f1fe153c4f859906cbea26a3f2f"
+        ]
+      })).called(1);
+      verify(cachedClient?.clearSharedTransactionCache(coin: Coin.particl))
+          .called(1);
+
+      expect(secureStore.writes, 13);
+      expect(secureStore.reads, 26);
+      expect(secureStore.deletes, 8);
+
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("prepareSend fails", () async {
+      when(client?.getServerFeatures()).thenAnswer((_) async => {
+            "hosts": {},
+            "pruning": null,
+            "server_version": "Unit tests",
+            "protocol_min": "1.4",
+            "protocol_max": "1.4.2",
+            "genesis_hash": GENESIS_HASH_MAINNET,
+            "hash_function": "sha256",
+            "services": []
+          });
+      when(client?.getBatchHistory(args: historyBatchArgs0))
+          .thenAnswer((_) async => historyBatchResponse);
+      when(client?.getBatchHistory(args: historyBatchArgs1))
+          .thenAnswer((_) async => historyBatchResponse);
+      when(client?.getBatchHistory(args: historyBatchArgs2))
+          .thenAnswer((_) async => historyBatchResponse);
+      when(client?.getBatchHistory(args: historyBatchArgs3))
+          .thenAnswer((_) async => historyBatchResponse);
+
+      List<dynamic> dynamicArgValues = [];
+
+      when(client?.getBatchHistory(args: anyNamed("args")))
+          .thenAnswer((realInvocation) async {
+        if (realInvocation.namedArguments.values.first.length == 1) {
+          dynamicArgValues.add(realInvocation.namedArguments.values.first);
+        }
+
+        return historyBatchResponse;
+      });
+
+      await Hive.openBox<dynamic>(testWalletId);
+
+      when(cachedClient?.getTransaction(
+              txHash:
+                  "85130125ec9e37a48670fb5eb0a2780b94ea958cd700a1237ff75775d8a0edb0",
+              coin: Coin.particl))
+          .thenAnswer((_) async => tx2Raw);
+      when(cachedClient?.getTransaction(
+              txHash:
+                  "bb25567e1ffb2fd6ec9aa3925a7a8dd3055a29521f7811b2b2bc01ce7d8a216e",
+              coin: Coin.particl))
+          .thenAnswer((_) async => tx3Raw);
+      when(cachedClient?.getTransaction(
+        txHash:
+            "bb25567e1ffb2fd6ec9aa3925a7a8dd3055a29521f7811b2b2bc01ce7d8a216e",
+        coin: Coin.particl,
+      )).thenAnswer((_) async => tx4Raw);
+
+      // recover to fill data
+      await part?.recoverFromMnemonic(
+          mnemonic: TEST_MNEMONIC,
+          maxUnusedAddressGap: 2,
+          maxNumberOfIndexesToCheck: 1000,
+          height: 4000);
+
+      // modify addresses to properly mock data to build a tx
+      final rcv44 = await secureStore.read(
+          key: testWalletId + "_receiveDerivationsP2PKH");
+      await secureStore.write(
+          key: testWalletId + "_receiveDerivationsP2PKH",
+          value: rcv44?.replaceFirst("1RMSPixoLPuaXuhR2v4HsUMcRjLncKDaw",
+              "16FuTPaeRSPVxxCnwQmdyx2PQWxX6HWzhQ"));
+      final rcv84 = await secureStore.read(
+          key: testWalletId + "_receiveDerivationsP2WPKH");
+      await secureStore.write(
+          key: testWalletId + "_receiveDerivationsP2WPKH",
+          value: rcv84?.replaceFirst(
+              "pw1qvr6ehcm44vvqe96mxy9zw9aa5sa5yezvr2r94s",
+              "pw1q66xtkhqzcue808nlg8tp48uq7fshmaddljtkpy"));
+
+      part?.outputsList = utxoList;
+
+      bool didThrow = false;
+      try {
+        await part?.prepareSend(
+            address: "pw1q66xtkhqzcue808nlg8tp48uq7fshmaddljtkpy",
+            satoshiAmount: 15000);
+      } catch (_) {
+        didThrow = true;
+      }
+
+      expect(didThrow, true);
+
+      verify(client?.getServerFeatures()).called(1);
+
+      /// verify transaction no matching calls
+
+      // verify(cachedClient?.getTransaction(
+      //         txHash:
+      //             "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703",
+      //         coin: Coin.particl,
+      //         callOutSideMainIsolate: false))
+      //     .called(1);
+      // verify(cachedClient?.getTransaction(
+      //         txHash:
+      //             "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7",
+      //         coin: Coin.particl,
+      //         callOutSideMainIsolate: false))
+      //     .called(1);
+      // verify(cachedClient?.getTransaction(
+      //         txHash:
+      //             "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4",
+      //         coin: Coin.particl,
+      //         callOutSideMainIsolate: false))
+      //     .called(1);
+      verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1);
+      verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1);
+      verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1);
+      verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1);
+
+      for (final arg in dynamicArgValues) {
+        final map = Map<String, List<dynamic>>.from(arg as Map);
+
+        verify(client?.getBatchHistory(args: map)).called(1);
+        expect(activeScriptHashes.contains(map.values.first.first as String),
+            true);
+      }
+
+      expect(secureStore.interactions, 14);
+      expect(secureStore.writes, 7);
+      expect(secureStore.reads, 7);
+      expect(secureStore.deletes, 0);
+
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("confirmSend no hex", () async {
+      bool didThrow = false;
+      try {
+        await part?.confirmSend(txData: {"some": "strange map"});
+      } catch (_) {
+        didThrow = true;
+      }
+
+      expect(didThrow, true);
+
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("confirmSend hex is not string", () async {
+      bool didThrow = false;
+      try {
+        await part?.confirmSend(txData: {"hex": true});
+      } catch (_) {
+        didThrow = true;
+      }
+
+      expect(didThrow, true);
+
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("confirmSend hex is string but missing other data", () async {
+      bool didThrow = false;
+      try {
+        await part?.confirmSend(txData: {"hex": "a string"});
+      } catch (_) {
+        didThrow = true;
+      }
+
+      expect(didThrow, true);
+
+      verify(client?.broadcastTransaction(
+              rawTx: "a string", requestID: anyNamed("requestID")))
+          .called(1);
+
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("confirmSend fails due to vSize being greater than fee", () async {
+      bool didThrow = false;
+      try {
+        await part
+            ?.confirmSend(txData: {"hex": "a string", "fee": 1, "vSize": 10});
+      } catch (_) {
+        didThrow = true;
+      }
+
+      expect(didThrow, true);
+
+      verify(client?.broadcastTransaction(
+              rawTx: "a string", requestID: anyNamed("requestID")))
+          .called(1);
+
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("confirmSend fails when broadcast transactions throws", () async {
+      when(client?.broadcastTransaction(
+              rawTx: "a string", requestID: anyNamed("requestID")))
+          .thenThrow(Exception("some exception"));
+
+      bool didThrow = false;
+      try {
+        await part
+            ?.confirmSend(txData: {"hex": "a string", "fee": 10, "vSize": 10});
+      } catch (_) {
+        didThrow = true;
+      }
+
+      expect(didThrow, true);
+
+      verify(client?.broadcastTransaction(
+              rawTx: "a string", requestID: anyNamed("requestID")))
+          .called(1);
+
+      expect(secureStore.interactions, 0);
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(tracker);
+      verifyNoMoreInteractions(priceAPI);
+    });
+    //
+    // // this test will create a non mocked electrumx client that will try to connect
+    // // to the provided ipAddress below. This will throw a bunch of errors
+    // // which what we want here as actually calling electrumx calls here is unwanted.
+    // // test("listen to NodesChangedEvent", () async {
+    // //   nmc = ParticlWallet(
+    // //     walletId: testWalletId,
+    // //     walletName: testWalletName,
+    // //     networkType: BasicNetworkType.test,
+    // //     client: client,
+    // //     cachedClient: cachedClient,
+    // //     priceAPI: priceAPI,
+    // //     secureStore: secureStore,
+    // //   );
+    // //
+    // //   // set node
+    // //   final wallet = await Hive.openBox(testWalletId);
+    // //   await wallet.put("nodes", {
+    // //     "default": {
+    // //       "id": "some nodeID",
+    // //       "ipAddress": "some address",
+    // //       "port": "9000",
+    // //       "useSSL": true,
+    // //     }
+    // //   });
+    // //   await wallet.put("activeNodeID_Bitcoin", "default");
+    // //
+    // //   final a = nmc.cachedElectrumXClient;
+    // //
+    // //   // return when refresh is called on node changed trigger
+    // //   nmc.longMutex = true;
+    // //
+    // //   GlobalEventBus.instance
+    // //       .fire(NodesChangedEvent(NodesChangedEventType.updatedCurrentNode));
+    // //
+    // //   // make sure event has processed before continuing
+    // //   await Future.delayed(Duration(seconds: 5));
+    // //
+    // //   final b = nmc.cachedElectrumXClient;
+    // //
+    // //   expect(identical(a, b), false);
+    // //
+    // //   await nmc.exit();
+    // //
+    // //   expect(secureStore.interactions, 0);
+    // //   verifyNoMoreInteractions(client);
+    // //   verifyNoMoreInteractions(cachedClient);
+    // //   verifyNoMoreInteractions(priceAPI);
+    // // });
+
+    test("refresh wallet mutex locked", () async {
+      when(client?.getServerFeatures()).thenAnswer((_) async => {
+            "hosts": {},
+            "pruning": null,
+            "server_version": "Unit tests",
+            "protocol_min": "1.4",
+            "protocol_max": "1.4.2",
+            "genesis_hash": GENESIS_HASH_MAINNET,
+            "hash_function": "sha256",
+            "services": []
+          });
+      when(client?.getBatchHistory(args: historyBatchArgs0))
+          .thenAnswer((_) async => historyBatchResponse);
+      when(client?.getBatchHistory(args: historyBatchArgs1))
+          .thenAnswer((_) async => historyBatchResponse);
+      when(client?.getBatchHistory(args: historyBatchArgs2))
+          .thenAnswer((_) async => historyBatchResponse);
+      when(client?.getBatchHistory(args: historyBatchArgs3))
+          .thenAnswer((_) async => historyBatchResponse);
+      List<dynamic> dynamicArgValues = [];
+
+      when(client?.getBatchHistory(args: anyNamed("args")))
+          .thenAnswer((realInvocation) async {
+        if (realInvocation.namedArguments.values.first.length == 1) {
+          dynamicArgValues.add(realInvocation.namedArguments.values.first);
+        }
+
+        return historyBatchResponse;
+      });
+
+      await Hive.openBox<dynamic>(testWalletId);
+
+      // recover to fill data
+      await part?.recoverFromMnemonic(
+          mnemonic: TEST_MNEMONIC,
+          maxUnusedAddressGap: 2,
+          maxNumberOfIndexesToCheck: 1000,
+          height: 4000);
+
+      part?.refreshMutex = true;
+
+      await part?.refresh();
+
+      verify(client?.getServerFeatures()).called(1);
+      verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1);
+      verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1);
+      verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1);
+      verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1);
+
+      for (final arg in dynamicArgValues) {
+        final map = Map<String, List<dynamic>>.from(arg as Map);
+
+        verify(client?.getBatchHistory(args: map)).called(1);
+        expect(activeScriptHashes.contains(map.values.first.first as String),
+            true);
+      }
+
+      expect(secureStore.interactions, 10);
+      expect(secureStore.writes, 5);
+      expect(secureStore.reads, 5);
+      expect(secureStore.deletes, 0);
+
+      verifyNoMoreInteractions(client);
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(tracker);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    test("refresh wallet normally", () async {
+      when(client?.getBlockHeadTip()).thenAnswer((realInvocation) async =>
+          {"height": 520481, "hex": "some block hex"});
+      when(client?.getServerFeatures()).thenAnswer((_) async => {
+            "hosts": {},
+            "pruning": null,
+            "server_version": "Unit tests",
+            "protocol_min": "1.4",
+            "protocol_max": "1.4.2",
+            "genesis_hash": GENESIS_HASH_MAINNET,
+            "hash_function": "sha256",
+            "services": []
+          });
+      when(client?.getHistory(scripthash: anyNamed("scripthash")))
+          .thenAnswer((_) async => []);
+      when(client?.estimateFee(blocks: anyNamed("blocks")))
+          .thenAnswer((_) async => Decimal.one);
+
+      when(priceAPI?.getPricesAnd24hChange(baseCurrency: "USD"))
+          .thenAnswer((_) async => {Coin.particl: Tuple2(Decimal.one, 0.3)});
+
+      final List<dynamic> dynamicArgValues = [];
+
+      when(client?.getBatchHistory(args: anyNamed("args")))
+          .thenAnswer((realInvocation) async {
+        dynamicArgValues.add(realInvocation.namedArguments.values.first);
+        return historyBatchResponse;
+      });
+
+      await Hive.openBox<dynamic>(testWalletId);
+
+      // recover to fill data
+      await part?.recoverFromMnemonic(
+          mnemonic: TEST_MNEMONIC,
+          maxUnusedAddressGap: 2,
+          maxNumberOfIndexesToCheck: 1000,
+          height: 4000);
+
+      when(client?.getBatchHistory(args: anyNamed("args")))
+          .thenAnswer((_) async => {});
+      when(client?.getBatchUTXOs(args: anyNamed("args")))
+          .thenAnswer((_) async => emptyHistoryBatchResponse);
+
+      await part?.refresh();
+
+      verify(client?.getServerFeatures()).called(1);
+      verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(3);
+      verify(client?.estimateFee(blocks: anyNamed("blocks"))).called(3);
+      verify(client?.getBlockHeadTip()).called(1);
+      verify(priceAPI?.getPricesAnd24hChange(baseCurrency: "USD")).called(2);
+
+      for (final arg in dynamicArgValues) {
+        final map = Map<String, List<dynamic>>.from(arg as Map);
+
+        verify(client?.getBatchHistory(args: map)).called(1);
+      }
+
+      expect(secureStore.interactions, 10);
+      expect(secureStore.writes, 5);
+      expect(secureStore.reads, 5);
+      expect(secureStore.deletes, 0);
+
+      verifyNoMoreInteractions(cachedClient);
+      verifyNoMoreInteractions(priceAPI);
+    });
+
+    tearDown(() async {
+      await tearDownTestHive();
+    });
+  });
+}
diff --git a/test/services/coins/particl/particl_wallet_test.mocks.dart b/test/services/coins/particl/particl_wallet_test.mocks.dart
new file mode 100644
index 000000000..fb0f50d79
--- /dev/null
+++ b/test/services/coins/particl/particl_wallet_test.mocks.dart
@@ -0,0 +1,629 @@
+// Mocks generated by Mockito 5.3.2 from annotations
+// in stackwallet/test/services/coins/particl/particl_wallet_test.dart.
+// Do not manually edit this file.
+
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'dart:async' as _i6;
+
+import 'package:decimal/decimal.dart' as _i2;
+import 'package:http/http.dart' as _i4;
+import 'package:mockito/mockito.dart' as _i1;
+import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i7;
+import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i5;
+import 'package:stackwallet/services/price.dart' as _i9;
+import 'package:stackwallet/services/transaction_notification_tracker.dart'
+    as _i11;
+import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i8;
+import 'package:stackwallet/utilities/prefs.dart' as _i3;
+import 'package:tuple/tuple.dart' as _i10;
+
+// ignore_for_file: type=lint
+// ignore_for_file: avoid_redundant_argument_values
+// ignore_for_file: avoid_setters_without_getters
+// ignore_for_file: comment_references
+// ignore_for_file: implementation_imports
+// ignore_for_file: invalid_use_of_visible_for_testing_member
+// ignore_for_file: prefer_const_constructors
+// ignore_for_file: unnecessary_parenthesis
+// ignore_for_file: camel_case_types
+// ignore_for_file: subtype_of_sealed_class
+
+class _FakeDecimal_0 extends _i1.SmartFake implements _i2.Decimal {
+  _FakeDecimal_0(
+    Object parent,
+    Invocation parentInvocation,
+  ) : super(
+          parent,
+          parentInvocation,
+        );
+}
+
+class _FakePrefs_1 extends _i1.SmartFake implements _i3.Prefs {
+  _FakePrefs_1(
+    Object parent,
+    Invocation parentInvocation,
+  ) : super(
+          parent,
+          parentInvocation,
+        );
+}
+
+class _FakeClient_2 extends _i1.SmartFake implements _i4.Client {
+  _FakeClient_2(
+    Object parent,
+    Invocation parentInvocation,
+  ) : super(
+          parent,
+          parentInvocation,
+        );
+}
+
+/// A class which mocks [ElectrumX].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockElectrumX extends _i1.Mock implements _i5.ElectrumX {
+  MockElectrumX() {
+    _i1.throwOnMissingStub(this);
+  }
+
+  @override
+  set failovers(List<_i5.ElectrumXNode>? _failovers) => super.noSuchMethod(
+        Invocation.setter(
+          #failovers,
+          _failovers,
+        ),
+        returnValueForMissingStub: null,
+      );
+  @override
+  int get currentFailoverIndex => (super.noSuchMethod(
+        Invocation.getter(#currentFailoverIndex),
+        returnValue: 0,
+      ) as int);
+  @override
+  set currentFailoverIndex(int? _currentFailoverIndex) => super.noSuchMethod(
+        Invocation.setter(
+          #currentFailoverIndex,
+          _currentFailoverIndex,
+        ),
+        returnValueForMissingStub: null,
+      );
+  @override
+  String get host => (super.noSuchMethod(
+        Invocation.getter(#host),
+        returnValue: '',
+      ) as String);
+  @override
+  int get port => (super.noSuchMethod(
+        Invocation.getter(#port),
+        returnValue: 0,
+      ) as int);
+  @override
+  bool get useSSL => (super.noSuchMethod(
+        Invocation.getter(#useSSL),
+        returnValue: false,
+      ) as bool);
+  @override
+  _i6.Future<dynamic> request({
+    required String? command,
+    List<dynamic>? args = const [],
+    Duration? connectionTimeout = const Duration(seconds: 60),
+    String? requestID,
+    int? retries = 2,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #request,
+          [],
+          {
+            #command: command,
+            #args: args,
+            #connectionTimeout: connectionTimeout,
+            #requestID: requestID,
+            #retries: retries,
+          },
+        ),
+        returnValue: _i6.Future<dynamic>.value(),
+      ) as _i6.Future<dynamic>);
+  @override
+  _i6.Future<List<Map<String, dynamic>>> batchRequest({
+    required String? command,
+    required Map<String, List<dynamic>>? args,
+    Duration? connectionTimeout = const Duration(seconds: 60),
+    int? retries = 2,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #batchRequest,
+          [],
+          {
+            #command: command,
+            #args: args,
+            #connectionTimeout: connectionTimeout,
+            #retries: retries,
+          },
+        ),
+        returnValue: _i6.Future<List<Map<String, dynamic>>>.value(
+            <Map<String, dynamic>>[]),
+      ) as _i6.Future<List<Map<String, dynamic>>>);
+  @override
+  _i6.Future<bool> ping({
+    String? requestID,
+    int? retryCount = 1,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #ping,
+          [],
+          {
+            #requestID: requestID,
+            #retryCount: retryCount,
+          },
+        ),
+        returnValue: _i6.Future<bool>.value(false),
+      ) as _i6.Future<bool>);
+  @override
+  _i6.Future<Map<String, dynamic>> getBlockHeadTip({String? requestID}) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #getBlockHeadTip,
+          [],
+          {#requestID: requestID},
+        ),
+        returnValue:
+            _i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
+      ) as _i6.Future<Map<String, dynamic>>);
+  @override
+  _i6.Future<Map<String, dynamic>> getServerFeatures({String? requestID}) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #getServerFeatures,
+          [],
+          {#requestID: requestID},
+        ),
+        returnValue:
+            _i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
+      ) as _i6.Future<Map<String, dynamic>>);
+  @override
+  _i6.Future<String> broadcastTransaction({
+    required String? rawTx,
+    String? requestID,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #broadcastTransaction,
+          [],
+          {
+            #rawTx: rawTx,
+            #requestID: requestID,
+          },
+        ),
+        returnValue: _i6.Future<String>.value(''),
+      ) as _i6.Future<String>);
+  @override
+  _i6.Future<Map<String, dynamic>> getBalance({
+    required String? scripthash,
+    String? requestID,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #getBalance,
+          [],
+          {
+            #scripthash: scripthash,
+            #requestID: requestID,
+          },
+        ),
+        returnValue:
+            _i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
+      ) as _i6.Future<Map<String, dynamic>>);
+  @override
+  _i6.Future<List<Map<String, dynamic>>> getHistory({
+    required String? scripthash,
+    String? requestID,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #getHistory,
+          [],
+          {
+            #scripthash: scripthash,
+            #requestID: requestID,
+          },
+        ),
+        returnValue: _i6.Future<List<Map<String, dynamic>>>.value(
+            <Map<String, dynamic>>[]),
+      ) as _i6.Future<List<Map<String, dynamic>>>);
+  @override
+  _i6.Future<Map<String, List<Map<String, dynamic>>>> getBatchHistory(
+          {required Map<String, List<dynamic>>? args}) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #getBatchHistory,
+          [],
+          {#args: args},
+        ),
+        returnValue: _i6.Future<Map<String, List<Map<String, dynamic>>>>.value(
+            <String, List<Map<String, dynamic>>>{}),
+      ) as _i6.Future<Map<String, List<Map<String, dynamic>>>>);
+  @override
+  _i6.Future<List<Map<String, dynamic>>> getUTXOs({
+    required String? scripthash,
+    String? requestID,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #getUTXOs,
+          [],
+          {
+            #scripthash: scripthash,
+            #requestID: requestID,
+          },
+        ),
+        returnValue: _i6.Future<List<Map<String, dynamic>>>.value(
+            <Map<String, dynamic>>[]),
+      ) as _i6.Future<List<Map<String, dynamic>>>);
+  @override
+  _i6.Future<Map<String, List<Map<String, dynamic>>>> getBatchUTXOs(
+          {required Map<String, List<dynamic>>? args}) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #getBatchUTXOs,
+          [],
+          {#args: args},
+        ),
+        returnValue: _i6.Future<Map<String, List<Map<String, dynamic>>>>.value(
+            <String, List<Map<String, dynamic>>>{}),
+      ) as _i6.Future<Map<String, List<Map<String, dynamic>>>>);
+  @override
+  _i6.Future<Map<String, dynamic>> getTransaction({
+    required String? txHash,
+    bool? verbose = true,
+    String? requestID,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #getTransaction,
+          [],
+          {
+            #txHash: txHash,
+            #verbose: verbose,
+            #requestID: requestID,
+          },
+        ),
+        returnValue:
+            _i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
+      ) as _i6.Future<Map<String, dynamic>>);
+  @override
+  _i6.Future<Map<String, dynamic>> getAnonymitySet({
+    String? groupId = r'1',
+    String? blockhash = r'',
+    String? requestID,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #getAnonymitySet,
+          [],
+          {
+            #groupId: groupId,
+            #blockhash: blockhash,
+            #requestID: requestID,
+          },
+        ),
+        returnValue:
+            _i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
+      ) as _i6.Future<Map<String, dynamic>>);
+  @override
+  _i6.Future<dynamic> getMintData({
+    dynamic mints,
+    String? requestID,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #getMintData,
+          [],
+          {
+            #mints: mints,
+            #requestID: requestID,
+          },
+        ),
+        returnValue: _i6.Future<dynamic>.value(),
+      ) as _i6.Future<dynamic>);
+  @override
+  _i6.Future<Map<String, dynamic>> getUsedCoinSerials({
+    String? requestID,
+    required int? startNumber,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #getUsedCoinSerials,
+          [],
+          {
+            #requestID: requestID,
+            #startNumber: startNumber,
+          },
+        ),
+        returnValue:
+            _i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
+      ) as _i6.Future<Map<String, dynamic>>);
+  @override
+  _i6.Future<int> getLatestCoinId({String? requestID}) => (super.noSuchMethod(
+        Invocation.method(
+          #getLatestCoinId,
+          [],
+          {#requestID: requestID},
+        ),
+        returnValue: _i6.Future<int>.value(0),
+      ) as _i6.Future<int>);
+  @override
+  _i6.Future<Map<String, dynamic>> getFeeRate({String? requestID}) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #getFeeRate,
+          [],
+          {#requestID: requestID},
+        ),
+        returnValue:
+            _i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
+      ) as _i6.Future<Map<String, dynamic>>);
+  @override
+  _i6.Future<_i2.Decimal> estimateFee({
+    String? requestID,
+    required int? blocks,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #estimateFee,
+          [],
+          {
+            #requestID: requestID,
+            #blocks: blocks,
+          },
+        ),
+        returnValue: _i6.Future<_i2.Decimal>.value(_FakeDecimal_0(
+          this,
+          Invocation.method(
+            #estimateFee,
+            [],
+            {
+              #requestID: requestID,
+              #blocks: blocks,
+            },
+          ),
+        )),
+      ) as _i6.Future<_i2.Decimal>);
+  @override
+  _i6.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod(
+        Invocation.method(
+          #relayFee,
+          [],
+          {#requestID: requestID},
+        ),
+        returnValue: _i6.Future<_i2.Decimal>.value(_FakeDecimal_0(
+          this,
+          Invocation.method(
+            #relayFee,
+            [],
+            {#requestID: requestID},
+          ),
+        )),
+      ) as _i6.Future<_i2.Decimal>);
+}
+
+/// A class which mocks [CachedElectrumX].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX {
+  MockCachedElectrumX() {
+    _i1.throwOnMissingStub(this);
+  }
+
+  @override
+  String get server => (super.noSuchMethod(
+        Invocation.getter(#server),
+        returnValue: '',
+      ) as String);
+  @override
+  int get port => (super.noSuchMethod(
+        Invocation.getter(#port),
+        returnValue: 0,
+      ) as int);
+  @override
+  bool get useSSL => (super.noSuchMethod(
+        Invocation.getter(#useSSL),
+        returnValue: false,
+      ) as bool);
+  @override
+  _i3.Prefs get prefs => (super.noSuchMethod(
+        Invocation.getter(#prefs),
+        returnValue: _FakePrefs_1(
+          this,
+          Invocation.getter(#prefs),
+        ),
+      ) as _i3.Prefs);
+  @override
+  List<_i5.ElectrumXNode> get failovers => (super.noSuchMethod(
+        Invocation.getter(#failovers),
+        returnValue: <_i5.ElectrumXNode>[],
+      ) as List<_i5.ElectrumXNode>);
+  @override
+  _i6.Future<Map<String, dynamic>> getAnonymitySet({
+    required String? groupId,
+    String? blockhash = r'',
+    required _i8.Coin? coin,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #getAnonymitySet,
+          [],
+          {
+            #groupId: groupId,
+            #blockhash: blockhash,
+            #coin: coin,
+          },
+        ),
+        returnValue:
+            _i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
+      ) as _i6.Future<Map<String, dynamic>>);
+  @override
+  String base64ToHex(String? source) => (super.noSuchMethod(
+        Invocation.method(
+          #base64ToHex,
+          [source],
+        ),
+        returnValue: '',
+      ) as String);
+  @override
+  String base64ToReverseHex(String? source) => (super.noSuchMethod(
+        Invocation.method(
+          #base64ToReverseHex,
+          [source],
+        ),
+        returnValue: '',
+      ) as String);
+  @override
+  _i6.Future<Map<String, dynamic>> getTransaction({
+    required String? txHash,
+    required _i8.Coin? coin,
+    bool? verbose = true,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #getTransaction,
+          [],
+          {
+            #txHash: txHash,
+            #coin: coin,
+            #verbose: verbose,
+          },
+        ),
+        returnValue:
+            _i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
+      ) as _i6.Future<Map<String, dynamic>>);
+  @override
+  _i6.Future<List<dynamic>> getUsedCoinSerials({
+    required _i8.Coin? coin,
+    int? startNumber = 0,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #getUsedCoinSerials,
+          [],
+          {
+            #coin: coin,
+            #startNumber: startNumber,
+          },
+        ),
+        returnValue: _i6.Future<List<dynamic>>.value(<dynamic>[]),
+      ) as _i6.Future<List<dynamic>>);
+  @override
+  _i6.Future<void> clearSharedTransactionCache({required _i8.Coin? coin}) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #clearSharedTransactionCache,
+          [],
+          {#coin: coin},
+        ),
+        returnValue: _i6.Future<void>.value(),
+        returnValueForMissingStub: _i6.Future<void>.value(),
+      ) as _i6.Future<void>);
+}
+
+/// A class which mocks [PriceAPI].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockPriceAPI extends _i1.Mock implements _i9.PriceAPI {
+  MockPriceAPI() {
+    _i1.throwOnMissingStub(this);
+  }
+
+  @override
+  _i4.Client get client => (super.noSuchMethod(
+        Invocation.getter(#client),
+        returnValue: _FakeClient_2(
+          this,
+          Invocation.getter(#client),
+        ),
+      ) as _i4.Client);
+  @override
+  void resetLastCalledToForceNextCallToUpdateCache() => super.noSuchMethod(
+        Invocation.method(
+          #resetLastCalledToForceNextCallToUpdateCache,
+          [],
+        ),
+        returnValueForMissingStub: null,
+      );
+  @override
+  _i6.Future<
+      Map<_i8.Coin, _i10.Tuple2<_i2.Decimal, double>>> getPricesAnd24hChange(
+          {required String? baseCurrency}) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #getPricesAnd24hChange,
+          [],
+          {#baseCurrency: baseCurrency},
+        ),
+        returnValue:
+            _i6.Future<Map<_i8.Coin, _i10.Tuple2<_i2.Decimal, double>>>.value(
+                <_i8.Coin, _i10.Tuple2<_i2.Decimal, double>>{}),
+      ) as _i6.Future<Map<_i8.Coin, _i10.Tuple2<_i2.Decimal, double>>>);
+}
+
+/// A class which mocks [TransactionNotificationTracker].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockTransactionNotificationTracker extends _i1.Mock
+    implements _i11.TransactionNotificationTracker {
+  MockTransactionNotificationTracker() {
+    _i1.throwOnMissingStub(this);
+  }
+
+  @override
+  String get walletId => (super.noSuchMethod(
+        Invocation.getter(#walletId),
+        returnValue: '',
+      ) as String);
+  @override
+  List<String> get pendings => (super.noSuchMethod(
+        Invocation.getter(#pendings),
+        returnValue: <String>[],
+      ) as List<String>);
+  @override
+  List<String> get confirmeds => (super.noSuchMethod(
+        Invocation.getter(#confirmeds),
+        returnValue: <String>[],
+      ) as List<String>);
+  @override
+  bool wasNotifiedPending(String? txid) => (super.noSuchMethod(
+        Invocation.method(
+          #wasNotifiedPending,
+          [txid],
+        ),
+        returnValue: false,
+      ) as bool);
+  @override
+  _i6.Future<void> addNotifiedPending(String? txid) => (super.noSuchMethod(
+        Invocation.method(
+          #addNotifiedPending,
+          [txid],
+        ),
+        returnValue: _i6.Future<void>.value(),
+        returnValueForMissingStub: _i6.Future<void>.value(),
+      ) as _i6.Future<void>);
+  @override
+  bool wasNotifiedConfirmed(String? txid) => (super.noSuchMethod(
+        Invocation.method(
+          #wasNotifiedConfirmed,
+          [txid],
+        ),
+        returnValue: false,
+      ) as bool);
+  @override
+  _i6.Future<void> addNotifiedConfirmed(String? txid) => (super.noSuchMethod(
+        Invocation.method(
+          #addNotifiedConfirmed,
+          [txid],
+        ),
+        returnValue: _i6.Future<void>.value(),
+        returnValueForMissingStub: _i6.Future<void>.value(),
+      ) as _i6.Future<void>);
+}
diff --git a/test/widget_tests/managed_favorite_test.mocks.dart b/test/widget_tests/managed_favorite_test.mocks.dart
index 50f906c3c..117a810cf 100644
--- a/test/widget_tests/managed_favorite_test.mocks.dart
+++ b/test/widget_tests/managed_favorite_test.mocks.dart
@@ -165,9 +165,9 @@ class _FakeElectrumXNode_11 extends _i1.SmartFake
         );
 }
 
-class _FakeFlutterSecureStorageInterface_12 extends _i1.SmartFake
+class _FakeSecureStorageInterface_12 extends _i1.SmartFake
     implements _i12.SecureStorageInterface {
-  _FakeFlutterSecureStorageInterface_12(
+  _FakeSecureStorageInterface_12(
     Object parent,
     Invocation parentInvocation,
   ) : super(
@@ -1400,7 +1400,7 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService {
   @override
   _i12.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod(
         Invocation.getter(#secureStorageInterface),
-        returnValue: _FakeFlutterSecureStorageInterface_12(
+        returnValue: _FakeSecureStorageInterface_12(
           this,
           Invocation.getter(#secureStorageInterface),
         ),
diff --git a/test/widget_tests/node_card_test.mocks.dart b/test/widget_tests/node_card_test.mocks.dart
index 2bb32b58d..6b85a4f5d 100644
--- a/test/widget_tests/node_card_test.mocks.dart
+++ b/test/widget_tests/node_card_test.mocks.dart
@@ -24,9 +24,9 @@ import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'
 // ignore_for_file: camel_case_types
 // ignore_for_file: subtype_of_sealed_class
 
-class _FakeFlutterSecureStorageInterface_0 extends _i1.SmartFake
+class _FakeSecureStorageInterface_0 extends _i1.SmartFake
     implements _i2.SecureStorageInterface {
-  _FakeFlutterSecureStorageInterface_0(
+  _FakeSecureStorageInterface_0(
     Object parent,
     Invocation parentInvocation,
   ) : super(
@@ -46,7 +46,7 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService {
   @override
   _i2.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod(
         Invocation.getter(#secureStorageInterface),
-        returnValue: _FakeFlutterSecureStorageInterface_0(
+        returnValue: _FakeSecureStorageInterface_0(
           this,
           Invocation.getter(#secureStorageInterface),
         ),
diff --git a/test/widget_tests/node_options_sheet_test.mocks.dart b/test/widget_tests/node_options_sheet_test.mocks.dart
index c9e4e2bb8..7e3e3a92d 100644
--- a/test/widget_tests/node_options_sheet_test.mocks.dart
+++ b/test/widget_tests/node_options_sheet_test.mocks.dart
@@ -76,9 +76,9 @@ class _FakeManager_3 extends _i1.SmartFake implements _i6.Manager {
         );
 }
 
-class _FakeFlutterSecureStorageInterface_4 extends _i1.SmartFake
+class _FakeSecureStorageInterface_4 extends _i1.SmartFake
     implements _i7.SecureStorageInterface {
-  _FakeFlutterSecureStorageInterface_4(
+  _FakeSecureStorageInterface_4(
     Object parent,
     Invocation parentInvocation,
   ) : super(
@@ -446,6 +446,19 @@ class MockPrefs extends _i1.Mock implements _i11.Prefs {
         returnValueForMissingStub: null,
       );
   @override
+  int get familiarity => (super.noSuchMethod(
+        Invocation.getter(#familiarity),
+        returnValue: 0,
+      ) as int);
+  @override
+  set familiarity(int? familiarity) => super.noSuchMethod(
+        Invocation.setter(
+          #familiarity,
+          familiarity,
+        ),
+        returnValueForMissingStub: null,
+      );
+  @override
   bool get showTestNetCoins => (super.noSuchMethod(
         Invocation.getter(#showTestNetCoins),
         returnValue: false,
@@ -623,10 +636,9 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService {
   }
 
   @override
-  _i7.SecureStorageInterface get secureStorageInterface =>
-      (super.noSuchMethod(
+  _i7.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod(
         Invocation.getter(#secureStorageInterface),
-        returnValue: _FakeFlutterSecureStorageInterface_4(
+        returnValue: _FakeSecureStorageInterface_4(
           this,
           Invocation.getter(#secureStorageInterface),
         ),
diff --git a/test/widget_tests/transaction_card_test.mocks.dart b/test/widget_tests/transaction_card_test.mocks.dart
index f258a402b..2aa2e1dcb 100644
--- a/test/widget_tests/transaction_card_test.mocks.dart
+++ b/test/widget_tests/transaction_card_test.mocks.dart
@@ -2213,6 +2213,19 @@ class MockPrefs extends _i1.Mock implements _i17.Prefs {
         returnValueForMissingStub: null,
       );
   @override
+  int get familiarity => (super.noSuchMethod(
+        Invocation.getter(#familiarity),
+        returnValue: 0,
+      ) as int);
+  @override
+  set familiarity(int? familiarity) => super.noSuchMethod(
+        Invocation.setter(
+          #familiarity,
+          familiarity,
+        ),
+        returnValueForMissingStub: null,
+      );
+  @override
   bool get showTestNetCoins => (super.noSuchMethod(
         Invocation.getter(#showTestNetCoins),
         returnValue: false,
diff --git a/test/widget_tests/wallet_info_row/sub_widgets/wallet_info_row_balance_future_test.mocks.dart b/test/widget_tests/wallet_info_row/sub_widgets/wallet_info_row_balance_future_test.mocks.dart
index a249c997d..fe5e0e8a2 100644
--- a/test/widget_tests/wallet_info_row/sub_widgets/wallet_info_row_balance_future_test.mocks.dart
+++ b/test/widget_tests/wallet_info_row/sub_widgets/wallet_info_row_balance_future_test.mocks.dart
@@ -164,9 +164,9 @@ class _FakeElectrumXNode_11 extends _i1.SmartFake
         );
 }
 
-class _FakeFlutterSecureStorageInterface_12 extends _i1.SmartFake
+class _FakeSecureStorageInterface_12 extends _i1.SmartFake
     implements _i12.SecureStorageInterface {
-  _FakeFlutterSecureStorageInterface_12(
+  _FakeSecureStorageInterface_12(
     Object parent,
     Invocation parentInvocation,
   ) : super(
@@ -1337,7 +1337,7 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService {
   @override
   _i12.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod(
         Invocation.getter(#secureStorageInterface),
-        returnValue: _FakeFlutterSecureStorageInterface_12(
+        returnValue: _FakeSecureStorageInterface_12(
           this,
           Invocation.getter(#secureStorageInterface),
         ),
diff --git a/test/widget_tests/wallet_info_row/wallet_info_row_test.mocks.dart b/test/widget_tests/wallet_info_row/wallet_info_row_test.mocks.dart
index 820fbd96d..7f370eb9a 100644
--- a/test/widget_tests/wallet_info_row/wallet_info_row_test.mocks.dart
+++ b/test/widget_tests/wallet_info_row/wallet_info_row_test.mocks.dart
@@ -164,9 +164,9 @@ class _FakeElectrumXNode_11 extends _i1.SmartFake
         );
 }
 
-class _FakeFlutterSecureStorageInterface_12 extends _i1.SmartFake
+class _FakeSecureStorageInterface_12 extends _i1.SmartFake
     implements _i12.SecureStorageInterface {
-  _FakeFlutterSecureStorageInterface_12(
+  _FakeSecureStorageInterface_12(
     Object parent,
     Invocation parentInvocation,
   ) : super(
@@ -1337,7 +1337,7 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService {
   @override
   _i12.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod(
         Invocation.getter(#secureStorageInterface),
-        returnValue: _FakeFlutterSecureStorageInterface_12(
+        returnValue: _FakeSecureStorageInterface_12(
           this,
           Invocation.getter(#secureStorageInterface),
         ),