diff --git a/docs/building.md b/docs/building.md
index 79e1bfb64..3d35acea2 100644
--- a/docs/building.md
+++ b/docs/building.md
@@ -116,6 +116,22 @@ cd ..
 or manually by creating the files referenced in that script with the specified content.
 
 ### Build plugins
+#### Build script: `build_app.sh`
+The `build_app.sh` script is use to build applications Stack Wallet.  View the script's help message with `./build_app.sh -h` for more information on its usage.
+
+Options:
+
+ - `a <app>`: Specify the application ID (required).  Valid options are `stack_wallet` or `stack_duo`.
+ - `b <build_number>`: Specify the build number in 123 (required).
+ - `p <platform>`: Specify the platform to build for (required).  Valid options are `android`, `ios`, `macos`, `linux`, or `windows`.
+ - `v <version>`: Specify the version of the application in 1.2.3 format (required).
+ - `i`: Optional flag to skip building crypto plugins.  Useful for updating `pubspec.yaml` and white-labelling different apps with the same plugins.
+
+For example,
+```
+./build_app.sh -a stack_wallet -p linux -v 2.1.0 -b 210
+```
+
 #### Building plugins for Android 
 > Warning: This will take a long time, please be patient
 ```
diff --git a/lib/pages/buy_view/buy_order_details.dart b/lib/pages/buy_view/buy_order_details.dart
index 4144a85ab..7021309a2 100644
--- a/lib/pages/buy_view/buy_order_details.dart
+++ b/lib/pages/buy_view/buy_order_details.dart
@@ -8,11 +8,15 @@
  *
  */
 
+import 'dart:async';
+
 import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:flutter_svg/svg.dart';
 
 import '../../models/buy/response_objects/order.dart';
+import '../../notifications/show_flush_bar.dart';
 import '../../themes/stack_colors.dart';
 import '../../themes/theme_providers.dart';
 import '../../utilities/assets.dart';
@@ -44,6 +48,16 @@ class _BuyOrderDetailsViewState extends ConsumerState<BuyOrderDetailsView> {
 
   @override
   Widget build(BuildContext context) {
+    final orderDetails = '''
+Purchase ID: ${widget.order.paymentId}
+User ID: ${widget.order.userId}
+Quote ID: ${widget.order.quote.id}
+Quoted cost: ${widget.order.quote.youPayFiatPrice.toStringAsFixed(2)} ${widget.order.quote.fiat.ticker.toUpperCase()}
+Quoted amount: ${widget.order.quote.youReceiveCryptoAmount} ${widget.order.quote.crypto.ticker.toUpperCase()}
+Receiving ${widget.order.quote.crypto.ticker.toUpperCase()} address: ${widget.order.quote.receivingAddress}
+Provider: Simplex
+''';
+
     return ConditionalParent(
       condition: !isDesktop,
       builder: (child) {
@@ -272,6 +286,43 @@ class _BuyOrderDetailsViewState extends ConsumerState<BuyOrderDetailsView> {
               ),
             ],
           ),
+          const SizedBox(height: 8),
+          TextButton(
+            onPressed: () async {
+              await Clipboard.setData(ClipboardData(text: orderDetails));
+              if (context.mounted) {
+                unawaited(
+                  showFloatingFlushBar(
+                    type: FlushBarType.info,
+                    message: "Copied to clipboard",
+                    context: context,
+                  ),
+                );
+              }
+            },
+            child: Row(
+              mainAxisAlignment: MainAxisAlignment.center,
+              children: [
+                SvgPicture.asset(
+                  Assets.svg.copy,
+                  width: 20,
+                  height: 20,
+                  color: Theme.of(context)
+                      .extension<StackColors>()!
+                      .buttonTextSecondary,
+                ),
+                const SizedBox(
+                  width: 10,
+                ),
+                Text(
+                  "Copy to clipboard",
+                  style: STextStyles.desktopButtonSecondaryEnabled(
+                    context,
+                  ),
+                ),
+              ],
+            ),
+          ),
           const Spacer(),
           PrimaryButton(
             label: "Dismiss",
diff --git a/lib/pages/pinpad_views/lock_screen_view.dart b/lib/pages/pinpad_views/lock_screen_view.dart
index afb7c7a1d..a2b61c404 100644
--- a/lib/pages/pinpad_views/lock_screen_view.dart
+++ b/lib/pages/pinpad_views/lock_screen_view.dart
@@ -188,12 +188,14 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
     _timeout = Duration.zero;
 
     _checkUseBiometrics();
+    _pinTextController.addListener(_onPinChanged);
     super.initState();
   }
 
   @override
   dispose() {
     // _shakeController.dispose();
+    _pinTextController.removeListener(_onPinChanged);
     super.dispose();
   }
 
@@ -208,13 +210,27 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
     );
   }
 
-  final _pinTextController = TextEditingController();
   final FocusNode _pinFocusNode = FocusNode();
 
   late SecureStorageInterface _secureStore;
   late Biometrics biometrics;
   int pinCount = 1;
 
+  final _pinTextController = TextEditingController();
+
+  void _onPinChanged() async {
+    String enteredPin = _pinTextController.text;
+    final storedPin = await _secureStore.read(key: 'stack_pin');
+    final autoPin = ref.read(prefsChangeNotifierProvider).autoPin;
+
+    if (enteredPin.length >= 4 && autoPin && enteredPin == storedPin) {
+      await Future<void>.delayed(
+        const Duration(milliseconds: 200),
+      );
+      unawaited(_onUnlock());
+    }
+  }
+
   Widget get _body => Background(
         child: SafeArea(
           child: Scaffold(
diff --git a/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart b/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart
index 57fa710ad..4c4bfa66c 100644
--- a/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart
+++ b/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart
@@ -61,9 +61,12 @@ class _ChangePinViewState extends ConsumerState<ChangePinView> {
 
   int pinCount = 1;
 
+  final TextEditingController _pinTextController = TextEditingController();
+
   @override
   void initState() {
     _secureStore = ref.read(secureStoreProvider);
+    _pinTextController.addListener(_onPinChanged);
     super.initState();
   }
 
@@ -74,9 +77,23 @@ class _ChangePinViewState extends ConsumerState<ChangePinView> {
     _pinPutController2.dispose();
     _pinPutFocusNode1.dispose();
     _pinPutFocusNode2.dispose();
+    _pinTextController.removeListener(_onPinChanged);
     super.dispose();
   }
 
+  void _onPinChanged() async {
+    String enteredPin = _pinTextController.text;
+    final storedPin = await _secureStore.read(key: 'stack_pin');
+    final autoPin = ref.read(prefsChangeNotifierProvider).autoPin;
+
+    if (enteredPin.length >= 4 && autoPin && enteredPin == storedPin) {
+      await _pageController.nextPage(
+        duration: const Duration(milliseconds: 300),
+        curve: Curves.linear,
+      );
+    }
+  }
+
   @override
   Widget build(BuildContext context) {
     return Background(
diff --git a/lib/pages/settings_views/global_settings_view/security_views/security_view.dart b/lib/pages/settings_views/global_settings_view/security_views/security_view.dart
index ff35130fe..e80374320 100644
--- a/lib/pages/settings_views/global_settings_view/security_views/security_view.dart
+++ b/lib/pages/settings_views/global_settings_view/security_views/security_view.dart
@@ -10,8 +10,7 @@
 
 import 'package:flutter/material.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
-import '../../../pinpad_views/lock_screen_view.dart';
-import 'change_pin_view/change_pin_view.dart';
+
 import '../../../../providers/global/prefs_provider.dart';
 import '../../../../route_generator.dart';
 import '../../../../themes/stack_colors.dart';
@@ -21,6 +20,8 @@ import '../../../../widgets/background.dart';
 import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
 import '../../../../widgets/custom_buttons/draggable_switch_button.dart';
 import '../../../../widgets/rounded_white_container.dart';
+import '../../../pinpad_views/lock_screen_view.dart';
+import 'change_pin_view/change_pin_view.dart';
 
 class SecurityView extends StatelessWidget {
   const SecurityView({
@@ -203,6 +204,54 @@ class SecurityView extends StatelessWidget {
                   },
                 ),
               ),
+              // The "autoPin" preference (whether to automatically accept a correct PIN).
+              const SizedBox(
+                height: 8,
+              ),
+              RoundedWhiteContainer(
+                child: Consumer(
+                  builder: (_, ref, __) {
+                    return RawMaterialButton(
+                      // splashColor: Theme.of(context).extension<StackColors>()!.highlight,
+                      materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+                      shape: RoundedRectangleBorder(
+                        borderRadius: BorderRadius.circular(
+                          Constants.size.circularBorderRadius,
+                        ),
+                      ),
+                      onPressed: null,
+                      child: Padding(
+                        padding: const EdgeInsets.symmetric(vertical: 8),
+                        child: Row(
+                          mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                          children: [
+                            Text(
+                              "Auto-accept correct PIN",
+                              style: STextStyles.titleBold12(context),
+                              textAlign: TextAlign.left,
+                            ),
+                            SizedBox(
+                              height: 20,
+                              width: 40,
+                              child: DraggableSwitchButton(
+                                isOn: ref.watch(
+                                  prefsChangeNotifierProvider
+                                      .select((value) => value.autoPin),
+                                ),
+                                onValueChanged: (newValue) {
+                                  ref
+                                      .read(prefsChangeNotifierProvider)
+                                      .autoPin = newValue;
+                                },
+                              ),
+                            ),
+                          ],
+                        ),
+                      ),
+                    );
+                  },
+                ),
+              ),
             ],
           ),
         ),
diff --git a/lib/utilities/prefs.dart b/lib/utilities/prefs.dart
index abd236416..535f1310e 100644
--- a/lib/utilities/prefs.dart
+++ b/lib/utilities/prefs.dart
@@ -11,18 +11,19 @@
 import 'dart:async';
 
 import 'package:flutter/cupertino.dart';
+import 'package:uuid/uuid.dart';
+
+import '../app_config.dart';
 import '../db/hive/db.dart';
 import '../services/event_bus/events/global/tor_status_changed_event.dart';
 import '../services/event_bus/global_event_bus.dart';
-import '../app_config.dart';
+import '../wallets/crypto_currency/crypto_currency.dart';
+import '../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart';
 import 'amount/amount_unit.dart';
 import 'constants.dart';
 import 'enums/backup_frequency_type.dart';
 import 'enums/languages_enum.dart';
 import 'enums/sync_type_enum.dart';
-import '../wallets/crypto_currency/crypto_currency.dart';
-import '../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart';
-import 'package:uuid/uuid.dart';
 
 class Prefs extends ChangeNotifier {
   Prefs._();
@@ -1103,4 +1104,30 @@ class Prefs extends ChangeNotifier {
 
     return actualMap;
   }
+
+  // Automatic PIN entry.
+
+  bool _autoPin = false;
+
+  bool get autoPin => _autoPin;
+
+  set autoPin(bool autoPin) {
+    if (_autoPin != autoPin) {
+      DB.instance.put<dynamic>(
+        boxName: DB.boxNamePrefs,
+        key: "autoPin",
+        value: autoPin,
+      );
+      _autoPin = autoPin;
+      notifyListeners();
+    }
+  }
+
+  Future<bool> _getAutoPin() async {
+    return await DB.instance.get<dynamic>(
+          boxName: DB.boxNamePrefs,
+          key: "autoPin",
+        ) as bool? ??
+        false;
+  }
 }
diff --git a/lib/utilities/test_node_connection.dart b/lib/utilities/test_node_connection.dart
index 5578d5fd0..2160671db 100644
--- a/lib/utilities/test_node_connection.dart
+++ b/lib/utilities/test_node_connection.dart
@@ -4,7 +4,6 @@ import 'dart:io';
 
 import 'package:flutter/material.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:solana/solana.dart';
 
 import '../networking/http.dart';
 import '../pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
@@ -15,6 +14,7 @@ import '../wallets/crypto_currency/crypto_currency.dart';
 import '../wallets/crypto_currency/interfaces/electrumx_currency_interface.dart';
 import '../wallets/crypto_currency/intermediate/cryptonote_currency.dart';
 import '../wallets/crypto_currency/intermediate/nano_currency.dart';
+import '../wallets/wallet/impl/solana_wallet.dart';
 import 'connection_check/electrum_connection_check.dart';
 import 'logger.dart';
 import 'test_epic_box_connection.dart';
@@ -210,14 +210,20 @@ Future<bool> testNodeConnection({
 
     case Solana():
       try {
-        RpcClient rpcClient;
-        if (formData.host!.startsWith("http") ||
-            formData.host!.startsWith("https")) {
-          rpcClient = RpcClient("${formData.host}:${formData.port}");
-        } else {
-          rpcClient = RpcClient("http://${formData.host}:${formData.port}");
-        }
-        await rpcClient.getEpochInfo().then((value) => testPassed = true);
+        final rpcClient = SolanaWallet.createRpcClient(
+          formData.host!,
+          formData.port!,
+          formData.useSSL ?? false,
+          ref.read(prefsChangeNotifierProvider),
+          ref.read(pTorService),
+        );
+
+        final health = await rpcClient.getHealth();
+        Logging.instance.log(
+          "Solana testNodeConnection \"health=$health\"",
+          level: LogLevel.Info,
+        );
+        return true;
       } catch (_) {
         testPassed = false;
       }
diff --git a/lib/wallets/crypto_currency/coins/bitcoincash.dart b/lib/wallets/crypto_currency/coins/bitcoincash.dart
index 5950fc913..9d9a291a8 100644
--- a/lib/wallets/crypto_currency/coins/bitcoincash.dart
+++ b/lib/wallets/crypto_currency/coins/bitcoincash.dart
@@ -55,8 +55,6 @@ class Bitcoincash extends Bip39HDCurrency with ElectrumXCurrencyInterface {
 
   @override
   int get maxUnusedAddressGap => 50;
-  @override
-  int get maxNumberOfIndexesToCheck => 10000000;
 
   @override
   // change this to change the number of confirms a tx needs in order to show as confirmed
diff --git a/lib/wallets/crypto_currency/coins/ecash.dart b/lib/wallets/crypto_currency/coins/ecash.dart
index 102f509c8..ad1ec4f6c 100644
--- a/lib/wallets/crypto_currency/coins/ecash.dart
+++ b/lib/wallets/crypto_currency/coins/ecash.dart
@@ -50,8 +50,6 @@ class Ecash extends Bip39HDCurrency with ElectrumXCurrencyInterface {
 
   @override
   int get maxUnusedAddressGap => 50;
-  @override
-  int get maxNumberOfIndexesToCheck => 10000000;
 
   @override
   // change this to change the number of confirms a tx needs in order to show as confirmed
diff --git a/lib/wallets/crypto_currency/coins/solana.dart b/lib/wallets/crypto_currency/coins/solana.dart
index 1505abead..6cfc185e1 100644
--- a/lib/wallets/crypto_currency/coins/solana.dart
+++ b/lib/wallets/crypto_currency/coins/solana.dart
@@ -46,8 +46,7 @@ class Solana extends Bip39Currency {
     switch (network) {
       case CryptoCurrencyNetwork.main:
         return NodeModel(
-          host:
-              "https://api.mainnet-beta.solana.com/", // TODO: Change this to stack wallet one
+          host: "https://solana.stackwallet.com",
           port: 443,
           name: DefaultNodes.defaultName,
           id: DefaultNodes.buildId(this),
@@ -70,9 +69,13 @@ class Solana extends Bip39Currency {
 
   @override
   bool validateAddress(String address) {
-    return isPointOnEd25519Curve(
-      Ed25519HDPublicKey.fromBase58(address).toByteArray(),
-    );
+    try {
+      return isPointOnEd25519Curve(
+        Ed25519HDPublicKey.fromBase58(address).toByteArray(),
+      );
+    } catch (_) {
+      return false;
+    }
   }
 
   @override
diff --git a/lib/wallets/crypto_currency/intermediate/bip39_hd_currency.dart b/lib/wallets/crypto_currency/intermediate/bip39_hd_currency.dart
index bbe5bd2ab..bb97b6754 100644
--- a/lib/wallets/crypto_currency/intermediate/bip39_hd_currency.dart
+++ b/lib/wallets/crypto_currency/intermediate/bip39_hd_currency.dart
@@ -1,6 +1,7 @@
 import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib;
 import 'package:crypto/crypto.dart';
 import 'package:flutter/foundation.dart';
+
 import '../../../models/isar/models/blockchain_data/address.dart';
 import '../../../utilities/amount/amount.dart';
 import '../../../utilities/enums/derive_path_type_enum.dart';
@@ -16,7 +17,6 @@ abstract class Bip39HDCurrency extends Bip39Currency {
   List<DerivePathType> get supportedDerivationPathTypes;
 
   int get maxUnusedAddressGap => 50;
-  int get maxNumberOfIndexesToCheck => 10000;
 
   String constructDerivePath({
     required DerivePathType derivePathType,
diff --git a/lib/wallets/wallet/impl/solana_wallet.dart b/lib/wallets/wallet/impl/solana_wallet.dart
index 50ff0b31a..5cbfda157 100644
--- a/lib/wallets/wallet/impl/solana_wallet.dart
+++ b/lib/wallets/wallet/impl/solana_wallet.dart
@@ -18,6 +18,7 @@ import '../../../services/node_service.dart';
 import '../../../services/tor_service.dart';
 import '../../../utilities/amount/amount.dart';
 import '../../../utilities/logger.dart';
+import '../../../utilities/prefs.dart';
 import '../../crypto_currency/crypto_currency.dart';
 import '../../models/tx_data.dart';
 import '../intermediate/bip39_wallet.dart';
@@ -245,14 +246,15 @@ class SolanaWallet extends Bip39Wallet<Solana> {
   }
 
   @override
-  Future<bool> pingCheck() {
+  Future<bool> pingCheck() async {
+    String? health;
     try {
       _checkClient();
-      _rpcClient?.getHealth();
-      return Future.value(true);
+      health = await _rpcClient?.getHealth();
+      return health != null;
     } catch (e, s) {
       Logging.instance.log(
-        "$runtimeType Solana pingCheck failed: $e\n$s",
+        "$runtimeType Solana pingCheck failed \"health=$health\": $e\n$s",
         level: LogLevel.Error,
       );
       return Future.value(false);
@@ -453,32 +455,67 @@ class SolanaWallet extends Bip39Wallet<Solana> {
   }
 
   @override
-  Future<bool> updateUTXOs() {
+  Future<bool> updateUTXOs() async {
     // No UTXOs in Solana
-    return Future.value(false);
+    return false;
   }
 
   /// Make sure the Solana RpcClient uses Tor if it's enabled.
   ///
-  void _checkClient() async {
+  void _checkClient() {
+    final node = getCurrentNode();
+    _rpcClient = createRpcClient(
+      node.host,
+      node.port,
+      node.useSSL,
+      prefs,
+      TorService.sharedInstance,
+    );
+  }
+
+  // static helper function for building a sol rpc client
+  static RpcClient createRpcClient(
+    final String host,
+    final int port,
+    final bool useSSL,
+    final Prefs prefs,
+    final TorService torService,
+  ) {
     HttpClient? httpClient;
 
     if (prefs.useTor) {
       // Make proxied HttpClient.
-      final ({InternetAddress host, int port}) proxyInfo =
-          TorService.sharedInstance.getProxyInfo();
+      final proxyInfo = torService.getProxyInfo();
 
       final proxySettings = ProxySettings(proxyInfo.host, proxyInfo.port);
       httpClient = HttpClient();
       SocksTCPClient.assignToHttpClient(httpClient, [proxySettings]);
     }
 
-    _rpcClient = RpcClient(
-      "${getCurrentNode().host}:${getCurrentNode().port}",
+    final regex = RegExp("^(http|https)://");
+
+    String editedHost;
+    if (host.startsWith(regex)) {
+      editedHost = host.replaceFirst(regex, "");
+    } else {
+      editedHost = host;
+    }
+
+    while (editedHost.endsWith("/")) {
+      editedHost = editedHost.substring(0, editedHost.length - 1);
+    }
+
+    final uri = Uri(
+      scheme: useSSL ? "https" : "http",
+      host: editedHost,
+      port: port,
+    );
+
+    return RpcClient(
+      uri.toString(),
       timeout: const Duration(seconds: 30),
       customHeaders: {},
       httpClient: httpClient,
     );
-    return;
   }
 }
diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart
index c7b8aa259..035ae649f 100644
--- a/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart
+++ b/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart
@@ -937,8 +937,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
     int highestIndexWithHistory = 0;
 
     for (int index = 0;
-        index < cryptoCurrency.maxNumberOfIndexesToCheck &&
-            gapCounter < cryptoCurrency.maxUnusedAddressGap;
+        gapCounter < cryptoCurrency.maxUnusedAddressGap;
         index += txCountBatchSize) {
       Logging.instance.log(
         "index: $index, \t GapCounter $chain ${type.name}: $gapCounter",
@@ -1017,10 +1016,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
     final List<Address> addressArray = [];
     int gapCounter = 0;
     int index = 0;
-    for (;
-        index < cryptoCurrency.maxNumberOfIndexesToCheck &&
-            gapCounter < cryptoCurrency.maxUnusedAddressGap;
-        index++) {
+    for (; gapCounter < cryptoCurrency.maxUnusedAddressGap; index++) {
       Logging.instance.log(
         "index: $index, \t GapCounter chain=$chain ${type.name}: $gapCounter",
         level: LogLevel.Info,