diff --git a/cashtab/package-lock.json b/cashtab/package-lock.json
--- a/cashtab/package-lock.json
+++ b/cashtab/package-lock.json
@@ -1,12 +1,12 @@
 {
     "name": "cashtab",
-    "version": "3.2.6",
+    "version": "3.2.7",
     "lockfileVersion": 3,
     "requires": true,
     "packages": {
         "": {
             "name": "cashtab",
-            "version": "3.2.6",
+            "version": "3.2.7",
             "dependencies": {
                 "@bitgo/utxo-lib": "^11.0.0",
                 "@zxing/browser": "^0.1.4",
@@ -45,7 +45,9 @@
                 "@types/lodash.debounce": "^4.0.9",
                 "@types/randombytes": "^2.0.3",
                 "@types/react": "^18.3.12",
+                "@types/react-dom": "^18.3.1",
                 "@types/styled-components": "^5.1.34",
+                "@types/webpack-env": "^1.18.5",
                 "@types/wif": "^2.0.5",
                 "assert": "^2.0.0",
                 "babel-jest": "^29.7.0",
@@ -110,7 +112,7 @@
             }
         },
         "../modules/chronik-client": {
-            "version": "1.4.0",
+            "version": "2.0.0",
             "license": "MIT",
             "dependencies": {
                 "@types/ws": "^8.2.1",
@@ -2858,7 +2860,7 @@
             }
         },
         "../modules/ecash-agora": {
-            "version": "0.1.1",
+            "version": "0.2.0",
             "license": "MIT",
             "dependencies": {
                 "chronik-client": "file:../chronik-client",
@@ -6890,7 +6892,7 @@
             }
         },
         "../modules/ecash-lib": {
-            "version": "0.2.1",
+            "version": "1.1.0",
             "license": "MIT",
             "dependencies": {
                 "ecashaddrjs": "file:../ecashaddrjs"
@@ -24077,6 +24079,16 @@
                 "csstype": "^3.0.2"
             }
         },
+        "node_modules/@types/react-dom": {
+            "version": "18.3.1",
+            "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz",
+            "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@types/react": "*"
+            }
+        },
         "node_modules/@types/resolve": {
             "version": "1.17.1",
             "dev": true,
@@ -24155,6 +24167,13 @@
             "dev": true,
             "license": "MIT"
         },
+        "node_modules/@types/webpack-env": {
+            "version": "1.18.5",
+            "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.18.5.tgz",
+            "integrity": "sha512-wz7kjjRRj8/Lty4B+Kr0LN6Ypc/3SymeCCGSbaXp2leH0ZVg/PriNiOwNj4bD4uphI7A8NXS4b6Gl373sfO5mA==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/@types/wif": {
             "version": "2.0.5",
             "resolved": "https://registry.npmjs.org/@types/wif/-/wif-2.0.5.tgz",
diff --git a/cashtab/package.json b/cashtab/package.json
--- a/cashtab/package.json
+++ b/cashtab/package.json
@@ -1,6 +1,6 @@
 {
     "name": "cashtab",
-    "version": "3.2.6",
+    "version": "3.2.7",
     "private": true,
     "scripts": {
         "start": "node scripts/start.js",
@@ -62,7 +62,9 @@
         "@types/lodash.debounce": "^4.0.9",
         "@types/randombytes": "^2.0.3",
         "@types/react": "^18.3.12",
+        "@types/react-dom": "^18.3.1",
         "@types/styled-components": "^5.1.34",
+        "@types/webpack-env": "^1.18.5",
         "@types/wif": "^2.0.5",
         "assert": "^2.0.0",
         "babel-jest": "^29.7.0",
diff --git a/cashtab/src/components/Agora/OrderBook/index.tsx b/cashtab/src/components/Agora/OrderBook/index.tsx
--- a/cashtab/src/components/Agora/OrderBook/index.tsx
+++ b/cashtab/src/components/Agora/OrderBook/index.tsx
@@ -90,7 +90,7 @@
 
 interface OrderBookProps {
     tokenId: string;
-    cachedTokenInfo: CashtabCachedTokenInfo;
+    cachedTokenInfo: CashtabCachedTokenInfo | undefined;
     settings: CashtabSettings;
     userLocale: string;
     fiatPrice: null | number;
diff --git a/cashtab/src/components/Agora/index.tsx b/cashtab/src/components/Agora/index.tsx
--- a/cashtab/src/components/Agora/index.tsx
+++ b/cashtab/src/components/Agora/index.tsx
@@ -2,8 +2,8 @@
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
-import React, { useState, useEffect } from 'react';
-import { WalletContext } from 'wallet/context';
+import React, { useState, useEffect, useContext } from 'react';
+import { WalletContext, isWalletContextLoaded } from 'wallet/context';
 import { SwitchLabel, Alert, PageHeader } from 'components/Common/Atoms';
 import Spinner from 'components/Common/Spinner';
 import { getTokenGenesisInfo } from 'chronik';
@@ -17,6 +17,7 @@
 import { token as tokenConfig } from 'config/token';
 import CashtabCache, { CashtabCachedTokenInfo } from 'config/CashtabCache';
 import { DogeIcon } from 'components/Common/CustomIcons';
+import { CashtabPathInfo } from 'wallet';
 
 interface CashtabActiveOffers {
     offeredFungibleTokenIds: string[];
@@ -30,7 +31,11 @@
 
 const Agora: React.FC = () => {
     const userLocale = getUserLocale(navigator);
-    const ContextValue = React.useContext(WalletContext);
+    const ContextValue = useContext(WalletContext);
+    if (!isWalletContextLoaded(ContextValue)) {
+        // Confirm we have all context required to load the page
+        return null;
+    }
     const {
         ecc,
         fiatPrice,
@@ -41,9 +46,11 @@
         chaintipBlockheight,
     } = ContextValue;
     const { wallets, settings, cashtabCache } = cashtabState;
-    const wallet = wallets.length > 0 ? wallets[0] : false;
-    const pk =
-        wallet === false ? null : wallet.paths.get(appConfig.derivationPath).pk;
+    // Note that wallets must be a non-empty array of CashtabWallet[] here, because
+    // context is loaded, and App component only renders Onboarding screen if user has no wallet
+    const wallet = wallets[0];
+    const pk = (wallet.paths.get(appConfig.derivationPath) as CashtabPathInfo)
+        .pk;
 
     // active agora partial offers organized for rendering this screen
     const [activeOffersCashtab, setActiveOffersCashtab] =
diff --git a/cashtab/src/components/Common/GoogleAnalytics.js b/cashtab/src/components/Common/GoogleAnalytics.js
deleted file mode 100644
--- a/cashtab/src/components/Common/GoogleAnalytics.js
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright (c) 2024 The Bitcoin developers
-// Distributed under the MIT software license, see the accompanying
-// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-
-import { useEffect } from 'react';
-import { useLocation } from 'react-router-dom';
-
-let ReactGA;
-if (process.env.REACT_APP_BUILD_ENV !== 'extension') {
-    ReactGA = require('react-ga');
-}
-
-const RouteTracker = () => {
-    const location = useLocation();
-    useEffect(() => {
-        ReactGA.pageview(location.pathname + location.search);
-    }, [location]);
-};
-
-const init =
-    process.env.REACT_APP_BUILD_ENV !== 'extension'
-        ? () => {
-              const isGAEnabled = process.env.NODE_ENV === 'production';
-              if (isGAEnabled) {
-                  ReactGA.initialize(process.env.REACT_APP_GOOGLE_ANALYTICS);
-              }
-
-              return isGAEnabled;
-          }
-        : // We return a new function if we are building the extension, because
-          // in this case ReactGA is undefined and will not have an initialize method
-          () => {
-              return false;
-          };
-
-export const Event =
-    process.env.REACT_APP_BUILD_ENV !== 'extension'
-        ? // If you are not building the extension, export GA event tracking function
-          (category, action, label) => {
-              ReactGA.event({
-                  category: category,
-                  action: action,
-                  label: label,
-              });
-          }
-        : // If you are building the extension, export function that does nothing
-          // Note: it's not practical to conditionally remove calls to this function from all screens
-          // So, more practical to just define it as a do-nothing function for the extension
-          () => undefined;
-
-export default process.env.REACT_APP_BUILD_ENV !== 'extension'
-    ? {
-          RouteTracker,
-          init,
-      }
-    : {
-          RouteTracker: () => undefined,
-          init,
-      };
diff --git a/cashtab/src/components/Common/GoogleAnalytics.ts b/cashtab/src/components/Common/GoogleAnalytics.ts
new file mode 100644
--- /dev/null
+++ b/cashtab/src/components/Common/GoogleAnalytics.ts
@@ -0,0 +1,73 @@
+// Copyright (c) 2024 The Bitcoin developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+import React, { useEffect } from 'react';
+import { useLocation } from 'react-router-dom';
+
+interface ReactGA {
+    pageview: (path: string) => void;
+    initialize: (trackingID: string) => void;
+    event: (event: {
+        category: string;
+        action: string;
+        label?: string;
+    }) => void;
+}
+
+let ReactGA: ReactGA | undefined;
+if (process.env.REACT_APP_BUILD_ENV !== 'extension') {
+    ReactGA = require('react-ga');
+}
+
+const RouteTracker: React.FC | (() => null) =
+    typeof ReactGA === 'undefined'
+        ? () => null
+        : () => {
+              const location = useLocation();
+              useEffect(() => {
+                  (ReactGA as ReactGA).pageview(
+                      location.pathname + location.search,
+                  );
+              }, [location]);
+              return null;
+          };
+
+const init =
+    typeof ReactGA === 'undefined'
+        ? // We return false here to prevent rendering route tracker in non-prod and extension
+          // see top level index.tsx
+          // in this case ReactGA is undefined and will not have an initialize method
+          () => {
+              return false;
+          }
+        : () => {
+              const isGAEnabled = process.env.NODE_ENV === 'production';
+              if (isGAEnabled) {
+                  (ReactGA as ReactGA).initialize(
+                      process.env.REACT_APP_GOOGLE_ANALYTICS as string,
+                  );
+              }
+
+              return isGAEnabled;
+          };
+
+export const Event =
+    typeof ReactGA === 'undefined'
+        ? // If you are building the extension, export function that does nothing
+          // Note: it's not practical to conditionally remove calls to this function from all screens
+          // So, more practical to just define it as a do-nothing function for the extension
+          () => undefined
+        : // If you are not building the extension, export GA event tracking function
+          (category: string, action: string, label: string) => {
+              (ReactGA as ReactGA).event({
+                  category,
+                  action,
+                  label,
+              });
+          };
+
+export default {
+    RouteTracker,
+    init,
+};
diff --git a/cashtab/src/components/Etokens/CreateToken.tsx b/cashtab/src/components/Etokens/CreateToken.tsx
--- a/cashtab/src/components/Etokens/CreateToken.tsx
+++ b/cashtab/src/components/Etokens/CreateToken.tsx
@@ -2,9 +2,8 @@
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
-import React from 'react';
-import { WalletContext } from 'wallet/context';
-import { getWalletState } from 'utils/cashMethods';
+import React, { useContext } from 'react';
+import { WalletContext, isWalletContextLoaded } from 'wallet/context';
 import { toXec } from 'wallet';
 import CreateTokenForm from 'components/Etokens/CreateTokenForm';
 import { AlertMsg } from 'components/Common/Atoms';
@@ -13,10 +12,15 @@
 import appConfig from 'config/app';
 
 const CreateToken: React.FC = () => {
-    const { apiError, fiatPrice, cashtabState } =
-        React.useContext(WalletContext);
-
+    const ContextValue = useContext(WalletContext);
+    if (!isWalletContextLoaded(ContextValue)) {
+        // Confirm we have all context required to load the page
+        return null;
+    }
+    const { apiError, fiatPrice, cashtabState } = ContextValue;
     const { settings, wallets } = cashtabState;
+    const wallet = wallets[0];
+    const { balanceSats } = wallet.state;
 
     const minTokenCreationFiatPriceString =
         fiatPrice !== null
@@ -27,10 +31,6 @@
               ].slug.toUpperCase()})`
             : '';
 
-    const wallet = wallets.length > 0 ? wallets[0] : false;
-    const walletState = getWalletState(wallet);
-    const { balanceSats } = walletState;
-
     return (
         <>
             {apiError && <ApiError />}
diff --git a/cashtab/src/components/Etokens/CreateTokenForm/index.tsx b/cashtab/src/components/Etokens/CreateTokenForm/index.tsx
--- a/cashtab/src/components/Etokens/CreateTokenForm/index.tsx
+++ b/cashtab/src/components/Etokens/CreateTokenForm/index.tsx
@@ -2,9 +2,9 @@
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
-import React, { useState, useCallback, useEffect } from 'react';
+import React, { useState, useCallback, useEffect, useContext } from 'react';
 import Modal from 'components/Common/Modal';
-import { WalletContext } from 'wallet/context';
+import { WalletContext, isWalletContextLoaded } from 'wallet/context';
 import {
     isValidTokenName,
     isValidTokenTicker,
@@ -41,12 +41,12 @@
 import { sendXec } from 'transactions';
 import { TokenNotificationIcon } from 'components/Common/CustomIcons';
 import { explorer } from 'config/explorer';
-import { getWalletState } from 'utils/cashMethods';
 import {
     hasEnoughToken,
     undecimalizeTokenAmount,
     TokenUtxo,
     SlpDecimals,
+    CashtabPathInfo,
 } from 'wallet';
 import { toast } from 'react-toastify';
 import Switch from 'components/Common/Switch';
@@ -82,6 +82,16 @@
 const CreateTokenForm: React.FC<CreateTokenFormProps> = ({
     nftChildGenesisInput,
 }) => {
+    const ContextValue = useContext(WalletContext);
+    if (!isWalletContextLoaded(ContextValue)) {
+        // Confirm we have all context required to load the page
+        return null;
+    }
+    const { chronik, ecc, chaintipBlockheight, cashtabState } = ContextValue;
+    const { settings, wallets } = cashtabState;
+    const wallet = wallets[0];
+    const { tokens } = wallet.state;
+
     // Constant to handle rendering of CreateTokenForm for NFT Minting
     const isNftMint = Array.isArray(nftChildGenesisInput);
 
@@ -91,14 +101,6 @@
     const navigate = useNavigate();
     const location = useLocation();
     const userLocale = getUserLocale(navigator);
-    const { chronik, ecc, chaintipBlockheight, cashtabState } =
-        React.useContext(WalletContext);
-    const { settings, wallets } = cashtabState;
-
-    const wallet = wallets.length > 0 ? wallets[0] : false;
-
-    const walletState = getWalletState(wallet);
-    const { tokens } = walletState;
 
     // eToken icon adds
     const [tokenIcon, setTokenIcon] = useState<null | File>(null);
@@ -645,7 +647,11 @@
                           ...genesisInfo,
                           // Set as Cashtab active wallet public key
                           authPubkey: toHex(
-                              wallet.paths.get(appConfig.derivationPath).pk,
+                              (
+                                  wallet.paths.get(
+                                      appConfig.derivationPath,
+                                  ) as CashtabPathInfo
+                              ).pk,
                           ),
                           // Note we are omitting the "data" key for now
                       },
diff --git a/cashtab/src/components/Etokens/Token/index.tsx b/cashtab/src/components/Etokens/Token/index.tsx
--- a/cashtab/src/components/Etokens/Token/index.tsx
+++ b/cashtab/src/components/Etokens/Token/index.tsx
@@ -4,7 +4,7 @@
 
 import React, { useState, useEffect, useContext } from 'react';
 import { Link, useParams } from 'react-router-dom';
-import { WalletContext } from 'wallet/context';
+import { WalletContext, isWalletContextLoaded } from 'wallet/context';
 import PrimaryButton, {
     SecondaryButton,
     IconButton,
@@ -14,7 +14,6 @@
 import Spinner from 'components/Common/Spinner';
 import BalanceHeaderToken from 'components/Common/BalanceHeaderToken';
 import { Event } from 'components/Common/GoogleAnalytics';
-import { getWalletState } from 'utils/cashMethods';
 import ApiError from 'components/Common/ApiError';
 import {
     isValidTokenSendOrBurnAmount,
@@ -70,6 +69,8 @@
     xecToNanoSatoshis,
     TokenUtxo,
     SlpDecimals,
+    CashtabPathInfo,
+    ScriptUtxoWithToken,
 } from 'wallet';
 import Modal from 'components/Common/Modal';
 import { toast } from 'react-toastify';
@@ -152,6 +153,11 @@
 import { CashtabCachedTokenInfo } from 'config/CashtabCache';
 
 const Token: React.FC = () => {
+    const ContextValue = useContext(WalletContext);
+    if (!isWalletContextLoaded(ContextValue)) {
+        // Confirm we have all context required to load the page
+        return null;
+    }
     const {
         apiError,
         cashtabState,
@@ -161,29 +167,23 @@
         ecc,
         chaintipBlockheight,
         fiatPrice,
-    } = useContext(WalletContext);
+    } = ContextValue;
     const { settings, wallets, cashtabCache } = cashtabState;
-    const wallet = wallets.length > 0 ? wallets[0] : false;
+    const wallet = wallets[0];
     // We get sk/pk/hash when wallet changes
-    const sk =
-        wallet === false
-            ? false
-            : wallet.paths.get(appConfig.derivationPath).sk;
-    const pk =
-        wallet === false
-            ? false
-            : wallet.paths.get(appConfig.derivationPath).pk;
-    const changeScript =
-        wallet === false
-            ? false
-            : Script.p2pkh(
-                  fromHex(wallet.paths.get(appConfig.derivationPath).hash),
-              );
-    const walletState = getWalletState(wallet);
-    const { tokens, balanceSats } = walletState;
+    const { sk, pk, address } = wallet.paths.get(
+        appConfig.derivationPath,
+    ) as CashtabPathInfo;
+    const changeScript = Script.fromAddress(address);
+    const { tokens, balanceSats } = wallet.state;
 
     const { tokenId } = useParams();
 
+    if (typeof tokenId === 'undefined') {
+        // We can't render this component without tokenId, any tokenId
+        return null;
+    }
+
     const validTokenId = isValidTokenId(tokenId);
 
     const tokenBalance = tokens.get(tokenId);
@@ -470,7 +470,7 @@
             : getFormattedFiatPrice(
                   settings,
                   userLocale,
-                  parseFloat(formData.tokenListPrice) / fiatPrice,
+                  parseFloat(formData.tokenListPrice) / (fiatPrice as number), // NB for selectedCurrency to be fiat fiatPrice is not null
                   null,
               );
     };
@@ -513,7 +513,7 @@
               } ${selectedCurrency.toUpperCase()} (${getFormattedFiatPrice(
                   settings,
                   userLocale,
-                  parseFloat(inputPrice) / fiatPrice,
+                  parseFloat(inputPrice) / (fiatPrice as number),
                   null,
               )}) per token`;
     };
@@ -547,7 +547,8 @@
             let undecimalizedBigIntCirculatingSupply = 0n;
             let mintBatons = 0;
             for (const utxo of tokenUtxos.utxos) {
-                const { token } = utxo;
+                // getting utxos by tokenId returns only token utxos
+                const { token } = utxo as ScriptUtxoWithToken;
                 const { amount, isMintBaton } = token;
                 undecimalizedBigIntCirculatingSupply += BigInt(amount);
                 if (isMintBaton) {
@@ -631,7 +632,8 @@
         try {
             const thisNftOffer = await agora.activeOffersByTokenId(tokenId);
             // Note we only expect an array of length 0 or 1 here
-            setNftActiveOffer(thisNftOffer);
+            // We only call this function on NFTs so we only expect OneshotOffer[]
+            setNftActiveOffer(thisNftOffer as OneshotOffer[]);
         } catch (err) {
             console.error(
                 `Error querying agora.activeOffersByTokenId(${tokenId})`,
@@ -755,7 +757,7 @@
 
     async function sendToken() {
         // GA event
-        Event('SendToken.js', 'Send', tokenId);
+        Event('SendToken.js', 'Send', tokenId as string);
 
         const { address, amount } = formData;
 
@@ -784,14 +786,14 @@
             const tokenSendTargetOutputs = isNftChild
                 ? getNftChildSendTargetOutputs(tokenId as string, cleanAddress)
                 : isAlp
-                  ? getAlpSendTargetOutputs(
-                        tokenInputInfo as TokenInputInfo,
-                        cleanAddress,
-                    )
-                  : getSlpSendTargetOutputs(
-                        tokenInputInfo as TokenInputInfo,
-                        cleanAddress,
-                    );
+                ? getAlpSendTargetOutputs(
+                      tokenInputInfo as TokenInputInfo,
+                      cleanAddress,
+                  )
+                : getSlpSendTargetOutputs(
+                      tokenInputInfo as TokenInputInfo,
+                      cleanAddress,
+                  );
             // Build and broadcast the tx
             const { response } = await sendXec(
                 chronik,
@@ -896,7 +898,7 @@
 
         const isValidAmountOrErrorMsg = isValidTokenSendOrBurnAmount(
             amount,
-            tokenBalance,
+            tokenBalance as string, // we do not render the slide without tokenBalance
             decimals as SlpDecimals,
             // Component does not render until token info is defined
             protocol as 'ALP' | 'SLP',
@@ -914,7 +916,7 @@
 
         const isValidAmountOrErrorMsg = isValidTokenSendOrBurnAmount(
             amount,
-            tokenBalance,
+            tokenBalance as string, // we do not render the slide without tokenBalance
             decimals as SlpDecimals,
             // Component does not render until token info is defined
             protocol as 'ALP' | 'SLP',
@@ -947,7 +949,7 @@
         const { value, name } = e.target;
         const isValidAmountOrErrorMsg = isValidTokenSendOrBurnAmount(
             value,
-            tokenBalance,
+            tokenBalance as string, // we do not render token actions without tokenBalance
             decimals as SlpDecimals,
             // Component does not render until token info is defined
             protocol as 'ALP' | 'SLP',
@@ -1036,11 +1038,9 @@
         // Clear this error before updating field
         setSendTokenAmountError(false);
         try {
-            const amount = tokenBalance;
-
             setFormData({
                 ...formData,
-                amount,
+                amount: tokenBalance as string, // we do not render token actions without tokenBalance
             });
         } catch (err) {
             console.error(`Error in onMax:`);
@@ -1086,7 +1086,7 @@
         const { name, value } = e.target;
         const isValidBurnAmountOrErrorMsg = isValidTokenSendOrBurnAmount(
             value,
-            tokenBalance,
+            tokenBalance as string, // we do not render token actions without tokenBalance
             decimals as SlpDecimals,
             // Component does not render until token info is defined
             protocol as 'ALP' | 'SLP',
@@ -1136,7 +1136,7 @@
             return;
         }
 
-        Event('SendToken.js', 'Burn eToken', tokenId);
+        Event('SendToken.js', 'Burn eToken', tokenId as string);
 
         try {
             // Get input utxos for slpv1 burn tx
@@ -1200,7 +1200,7 @@
     }
 
     async function handleMint() {
-        Event('SendToken.js', 'Mint eToken', tokenId);
+        Event('SendToken.js', 'Mint eToken', tokenId as string);
 
         // We only use 1 mint baton
         const mintBaton = mintBatons[0];
@@ -1348,7 +1348,7 @@
                       parseFloat(
                           (
                               parseFloat(formData.nftListPrice as string) /
-                              fiatPrice
+                              (fiatPrice as number)
                           ).toFixed(2),
                       ),
                   );
@@ -1378,7 +1378,13 @@
             {
                 value: listPriceSatoshis,
                 script: Script.p2pkh(
-                    fromHex(wallet.paths.get(appConfig.derivationPath).hash),
+                    fromHex(
+                        (
+                            wallet.paths.get(
+                                appConfig.derivationPath,
+                            ) as CashtabPathInfo
+                        ).hash,
+                    ),
                 ),
             },
         ];
@@ -1554,7 +1560,7 @@
                 : new BN(
                       new BN(
                           parseFloat(formData.tokenListPrice as string) /
-                              fiatPrice,
+                              (fiatPrice as number),
                       ).toFixed(NANOSAT_DECIMALS),
                   );
         const priceNanoSatsPerDecimalizedToken = xecToNanoSatoshis(priceInXec);
@@ -1631,11 +1637,6 @@
             toast.error(`Error listing ALP partial: tokenId is undefined`);
             return;
         }
-        if (changeScript === false) {
-            // Should never happen
-            toast.error(`Error listing ALP partial: wallet is not loaded`);
-            return;
-        }
 
         // offeredTokens is in units of token satoshis
         const offeredTokens = previewedAgoraPartial.offeredTokens();
@@ -2072,42 +2073,16 @@
                                         ? `${parseFloat(
                                               formData.nftListPrice,
                                           ).toLocaleString(userLocale)}
-                                                        XEC (${
-                                                            settings
-                                                                ? `${
-                                                                      supportedFiatCurrencies[
-                                                                          settings
-                                                                              .fiatCurrency
-                                                                      ].symbol
-                                                                  } `
-                                                                : '$ '
-                                                        }${(
-                                                            parseFloat(
-                                                                formData.nftListPrice,
-                                                            ) * fiatPrice
-                                                        ).toLocaleString(
+                                                        XEC ${getFormattedFiatPrice(
+                                                            settings,
                                                             userLocale,
-                                                            {
-                                                                minimumFractionDigits:
-                                                                    appConfig.cashDecimals,
-                                                                maximumFractionDigits:
-                                                                    appConfig.cashDecimals,
-                                                            },
-                                                        )} ${
-                                                            settings &&
-                                                            settings.fiatCurrency
-                                                                ? settings.fiatCurrency.toUpperCase()
-                                                                : 'USD'
-                                                        })?`
+                                                            formData.nftListPrice,
+                                                            fiatPrice,
+                                                        )}?`
                                         : `${
-                                              settings
-                                                  ? `${
-                                                        supportedFiatCurrencies[
-                                                            settings
-                                                                .fiatCurrency
-                                                        ].symbol
-                                                    } `
-                                                  : '$ '
+                                              supportedFiatCurrencies[
+                                                  settings.fiatCurrency
+                                              ].symbol
                                           }${parseFloat(
                                               formData.nftListPrice,
                                           ).toLocaleString(userLocale)} ${
@@ -2117,7 +2092,7 @@
                                           } (${(
                                               parseFloat(
                                                   formData.nftListPrice,
-                                              ) / fiatPrice
+                                              ) / (fiatPrice as number)
                                           ).toLocaleString(userLocale, {
                                               minimumFractionDigits:
                                                   appConfig.cashDecimals,
@@ -2224,8 +2199,10 @@
                                             to={`/token/${cachedInfo.groupTokenId}`}
                                         >
                                             {
-                                                cashtabCache.tokens.get(
-                                                    cachedInfo.groupTokenId,
+                                                (
+                                                    cashtabCache.tokens.get(
+                                                        cachedInfo.groupTokenId,
+                                                    ) as CashtabCachedTokenInfo
                                                 ).genesisInfo.tokenName
                                             }
                                         </Link>
@@ -2406,7 +2383,6 @@
                         </Alert>
                     )}
                     {isSupportedToken &&
-                        pk !== false &&
                         isBlacklisted !== null &&
                         !isBlacklisted &&
                         isNftChild && (
@@ -2446,7 +2422,6 @@
                             </>
                         )}
                     {isSupportedToken &&
-                        pk !== false &&
                         isBlacklisted !== null &&
                         !isBlacklisted &&
                         !isNftParent &&
@@ -2538,29 +2513,21 @@
                                     );
                                 })}
                             </NftTable>
-                            {pk !== false && (
-                                <>
-                                    <NftTitle>
-                                        Listings in this Collection
-                                    </NftTitle>
-                                    <Collection
-                                        groupTokenId={tokenId as string}
-                                        agora={agora}
-                                        chronik={chronik}
-                                        cashtabCache={cashtabCache}
-                                        settings={settings}
-                                        fiatPrice={fiatPrice}
-                                        userLocale={userLocale}
-                                        wallet={wallet}
-                                        activePk={pk}
-                                        chaintipBlockheight={
-                                            chaintipBlockheight
-                                        }
-                                        ecc={ecc}
-                                        noCollectionInfo
-                                    />
-                                </>
-                            )}
+                            <NftTitle>Listings in this Collection</NftTitle>
+                            <Collection
+                                groupTokenId={tokenId as string}
+                                agora={agora}
+                                chronik={chronik}
+                                cashtabCache={cashtabCache}
+                                settings={settings}
+                                fiatPrice={fiatPrice}
+                                userLocale={userLocale}
+                                wallet={wallet}
+                                activePk={pk}
+                                chaintipBlockheight={chaintipBlockheight}
+                                ecc={ecc}
+                                noCollectionInfo
+                            />
                         </>
                     )}
                     {apiError && <ApiError />}
@@ -2648,23 +2615,24 @@
                                                                   } `
                                                                 : '$ '
                                                         }${(
-                                                            parseFloat(
-                                                                formData.nftListPrice,
-                                                            ) * fiatPrice
-                                                        ).toLocaleString(
-                                                            userLocale,
-                                                            {
-                                                                minimumFractionDigits:
-                                                                    appConfig.cashDecimals,
-                                                                maximumFractionDigits:
-                                                                    appConfig.cashDecimals,
-                                                            },
-                                                        )} ${
-                                                            settings &&
-                                                            settings.fiatCurrency
-                                                                ? settings.fiatCurrency.toUpperCase()
-                                                                : 'USD'
-                                                        }`
+                                                                          parseFloat(
+                                                                              formData.nftListPrice,
+                                                                          ) *
+                                                                          fiatPrice
+                                                                      ).toLocaleString(
+                                                                          userLocale,
+                                                                          {
+                                                                              minimumFractionDigits:
+                                                                                  appConfig.cashDecimals,
+                                                                              maximumFractionDigits:
+                                                                                  appConfig.cashDecimals,
+                                                                          },
+                                                                      )} ${
+                                                                          settings &&
+                                                                          settings.fiatCurrency
+                                                                              ? settings.fiatCurrency.toUpperCase()
+                                                                              : 'USD'
+                                                                      }`
                                                                     : `${
                                                                           settings
                                                                               ? `${
@@ -2709,7 +2677,8 @@
                                                             }}
                                                             disabled={
                                                                 apiError ||
-                                                                nftListPriceError ||
+                                                                nftListPriceError !==
+                                                                    false ||
                                                                 formData.nftListPrice ===
                                                                     ''
                                                             }
@@ -2872,7 +2841,8 @@
                                                                 }}
                                                                 disabled={
                                                                     apiError ||
-                                                                    tokenListPriceError ||
+                                                                    tokenListPriceError !==
+                                                                        false ||
                                                                     formData.tokenListPrice ===
                                                                         '' ||
                                                                     formData.tokenListPrice ===
@@ -2988,8 +2958,10 @@
                                                             }}
                                                             disabled={
                                                                 apiError ||
-                                                                sendTokenAmountError ||
-                                                                sendTokenAddressError ||
+                                                                sendTokenAmountError !==
+                                                                    false ||
+                                                                sendTokenAddressError !==
+                                                                    false ||
                                                                 formData.address ===
                                                                     '' ||
                                                                 (!isNftChild &&
diff --git a/cashtab/src/components/Etokens/__tests__/TokenActions.test.js b/cashtab/src/components/Etokens/__tests__/TokenActions.test.js
--- a/cashtab/src/components/Etokens/__tests__/TokenActions.test.js
+++ b/cashtab/src/components/Etokens/__tests__/TokenActions.test.js
@@ -972,7 +972,7 @@
         await userEvent.click(listButton);
 
         // We see expected confirmation modal to list the NFT
-        expect(screen.getByText(/List GC for \$ 5 USD/)).toBeInTheDocument();
+        expect(screen.getByText(/List GC for \$5 USD/)).toBeInTheDocument();
 
         // We can cancel and not list the NFT
         await userEvent.click(screen.getByText('Cancel'));
diff --git a/cashtab/src/components/Nfts/index.tsx b/cashtab/src/components/Nfts/index.tsx
--- a/cashtab/src/components/Nfts/index.tsx
+++ b/cashtab/src/components/Nfts/index.tsx
@@ -2,8 +2,8 @@
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
-import React, { useState, useEffect } from 'react';
-import { WalletContext } from 'wallet/context';
+import React, { useState, useEffect, useContext } from 'react';
+import { WalletContext, isWalletContextLoaded } from 'wallet/context';
 import { SwitchLabel, Alert, PageHeader } from 'components/Common/Atoms';
 import Spinner from 'components/Common/Spinner';
 import { toHex } from 'ecash-lib';
@@ -18,10 +18,14 @@
     OneshotOffer,
 } from 'components/Agora/Collection';
 import { NftIcon } from 'components/Common/CustomIcons';
+import { CashtabPathInfo } from 'wallet';
 
 const Nfts: React.FC = () => {
-    const userLocale = getUserLocale(navigator);
-    const ContextValue = React.useContext(WalletContext);
+    const ContextValue = useContext(WalletContext);
+    if (!isWalletContextLoaded(ContextValue)) {
+        // Confirm we have all context required to load the page
+        return null;
+    }
     const {
         ecc,
         fiatPrice,
@@ -31,12 +35,12 @@
         chaintipBlockheight,
     } = ContextValue;
     const { wallets, settings, cashtabCache } = cashtabState;
-    const wallet = wallets.length > 0 ? wallets[0] : false;
+    const wallet = wallets[0];
     // We get public key when wallet changes
-    const pk =
-        wallet === false
-            ? false
-            : wallet.paths.get(appConfig.derivationPath).pk;
+    const pk = (wallet.paths.get(appConfig.derivationPath) as CashtabPathInfo)
+        .pk;
+
+    const userLocale = getUserLocale(navigator);
 
     const [chronikQueryError, setChronikQueryError] = useState<null | boolean>(
         null,
@@ -91,9 +95,6 @@
     }, []);
 
     useEffect(() => {
-        if (pk === false) {
-            return;
-        }
         getMyNfts();
     }, [wallet.name]);
 
@@ -110,7 +111,7 @@
                     Error querying listed NFTs. Please try again later.
                 </Alert>
             )}
-            {pk !== false && !chronikQueryError && (
+            {!chronikQueryError && (
                 <>
                     <SwitchHolder>
                         <Switch
diff --git a/cashtab/src/components/Send/SendXec.tsx b/cashtab/src/components/Send/SendXec.tsx
--- a/cashtab/src/components/Send/SendXec.tsx
+++ b/cashtab/src/components/Send/SendXec.tsx
@@ -2,9 +2,9 @@
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useContext } from 'react';
 import { useLocation } from 'react-router-dom';
-import { WalletContext } from 'wallet/context';
+import { WalletContext, isWalletContextLoaded } from 'wallet/context';
 import { CashReceivedNotificationIcon } from 'components/Common/CustomIcons';
 import Modal from 'components/Common/Modal';
 import PrimaryButton from 'components/Common/Buttons';
@@ -34,7 +34,6 @@
     TxLink,
     Info,
 } from 'components/Common/Atoms';
-import { getWalletState } from 'utils/cashMethods';
 import {
     sendXec,
     getMultisendTargetOutputs,
@@ -235,7 +234,11 @@
     parseAllAsBip21?: boolean;
 }
 const SendXec: React.FC = () => {
-    const ContextValue = React.useContext(WalletContext);
+    const ContextValue = useContext(WalletContext);
+    if (!isWalletContextLoaded(ContextValue)) {
+        // Confirm we have all context required to load the page
+        return null;
+    }
     const location = useLocation();
     const {
         chaintipBlockheight,
@@ -247,9 +250,8 @@
         ecc,
     } = ContextValue;
     const { settings, wallets, cashtabCache } = cashtabState;
-    const wallet = wallets.length > 0 ? wallets[0] : false;
-    const walletState = getWalletState(wallet);
-    const { balanceSats, tokens } = walletState;
+    const wallet = wallets[0];
+    const { balanceSats, tokens } = wallet.state;
 
     const [isOneToManyXECSend, setIsOneToManyXECSend] =
         useState<boolean>(false);
@@ -782,7 +784,7 @@
             const satoshisToSend =
                 selectedCurrency === 'XEC'
                     ? toSatoshis(parseFloat(formData.amount))
-                    : fiatToSatoshis(formData.amount, fiatPrice);
+                    : fiatToSatoshis(formData.amount, fiatPrice as number);
 
             targetOutputs.push({
                 script: Script.fromAddress(cleanAddress),
@@ -1043,7 +1045,7 @@
             balanceSats,
             userLocale,
             selectedCurrency,
-            fiatPrice,
+            fiatPrice as number,
         );
 
         setSendAmountError(
diff --git a/cashtab/src/index.js b/cashtab/src/index.js
deleted file mode 100644
--- a/cashtab/src/index.js
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (c) 2024 The Bitcoin developers
-// Distributed under the MIT software license, see the accompanying
-// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-
-import React from 'react';
-import { createRoot } from 'react-dom/client';
-import App from 'components/App/App';
-import { WalletProvider } from 'wallet/context';
-import { HashRouter as Router } from 'react-router-dom';
-import GA from 'components/Common/GoogleAnalytics';
-import { ChronikClient } from 'chronik-client';
-import { chronik as chronikConfig } from 'config/chronik';
-import { Ecc, initWasm } from 'ecash-lib';
-import { Agora } from 'ecash-agora';
-
-// Initialize wasm (activate ecash-lib) at app startup
-initWasm()
-    .then(() => {
-        // Initialize Ecc (used for signing txs) at app startup
-        const ecc = new Ecc();
-        // Initialize chronik-client at app startup
-        const chronik = new ChronikClient(chronikConfig.urls);
-        // Initialize new Agora chronik wrapper at app startup
-        const agora = new Agora(chronik);
-
-        const container = document.getElementById('root');
-        const root = createRoot(container);
-        root.render(
-            <WalletProvider chronik={chronik} agora={agora} ecc={ecc}>
-                <Router>
-                    {GA.init() && <GA.RouteTracker />}
-                    <App />
-                </Router>
-            </WalletProvider>,
-        );
-    })
-    .catch(console.error);
-
-if (module.hot) {
-    module.hot.accept();
-}
diff --git a/cashtab/src/index.tsx b/cashtab/src/index.tsx
new file mode 100644
--- /dev/null
+++ b/cashtab/src/index.tsx
@@ -0,0 +1,49 @@
+// Copyright (c) 2024 The Bitcoin developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+import React, { useEffect } from 'react';
+import { createRoot } from 'react-dom/client';
+import App from 'components/App/App';
+import { WalletProvider } from 'wallet/context';
+import { HashRouter as Router } from 'react-router-dom';
+import GA from 'components/Common/GoogleAnalytics';
+import { ChronikClient } from 'chronik-client';
+import { chronik as chronikConfig } from 'config/chronik';
+import { Ecc, initWasm } from 'ecash-lib';
+import { Agora } from 'ecash-agora';
+
+const AppWrapper: React.FC = () => {
+    useEffect(() => {
+        const initializeApp = async () => {
+            await initWasm();
+        };
+
+        initializeApp().catch(console.error);
+    }, []);
+
+    return (
+        <WalletProvider
+            chronik={new ChronikClient(chronikConfig.urls)}
+            agora={new Agora(new ChronikClient(chronikConfig.urls))}
+            ecc={new Ecc()}
+        >
+            <Router>
+                {GA.init() && ((<GA.RouteTracker />) as React.ReactNode)}
+                <App />
+            </Router>
+        </WalletProvider>
+    );
+};
+
+const container = document.getElementById('root');
+if (container) {
+    const root = createRoot(container);
+    root.render(<AppWrapper />);
+} else {
+    console.error('Root container not found');
+}
+
+if (module.hot) {
+    module.hot.accept();
+}
diff --git a/cashtab/src/wallet/context.js b/cashtab/src/wallet/context.js
deleted file mode 100644
--- a/cashtab/src/wallet/context.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2024 The Bitcoin developers
-// Distributed under the MIT software license, see the accompanying
-// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import useWallet from 'wallet/useWallet';
-
-export const WalletContext = React.createContext();
-
-export const WalletProvider = ({ chronik, agora, ecc, children }) => {
-    const wallet = useWallet(chronik, agora, ecc);
-    return (
-        <WalletContext.Provider value={wallet}>
-            {children}
-        </WalletContext.Provider>
-    );
-};
-
-WalletProvider.propTypes = {
-    chronik: PropTypes.object,
-    agora: PropTypes.object,
-    ecc: PropTypes.object,
-    children: PropTypes.node,
-};
diff --git a/cashtab/src/wallet/context.tsx b/cashtab/src/wallet/context.tsx
new file mode 100644
--- /dev/null
+++ b/cashtab/src/wallet/context.tsx
@@ -0,0 +1,100 @@
+// Copyright (c) 2024 The Bitcoin developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+import React from 'react';
+import useWallet, { UseWalletReturnType } from 'wallet/useWallet';
+import { ChronikClient } from 'chronik-client';
+import { Agora } from 'ecash-agora';
+import { Ecc } from 'ecash-lib';
+
+export function isWalletContextLoaded(
+    context: UseWalletReturnType | NullDefaultUseWalletReturnType,
+): context is UseWalletReturnType {
+    return (
+        context !== undefined &&
+        'chronik' in context &&
+        'agora' in context &&
+        'ecc' in context &&
+        'chaintipBlockheight' in context &&
+        'fiatPrice' in context &&
+        'cashtabLoaded' in context &&
+        'loading' in context &&
+        'apiError' in context &&
+        'refreshAliases' in context &&
+        'aliases' in context &&
+        'setAliases' in context &&
+        'aliasServerError' in context &&
+        'setAliasServerError' in context &&
+        'aliasPrices' in context &&
+        'setAliasPrices' in context &&
+        'updateCashtabState' in context &&
+        'processChronikWsMsg' in context &&
+        'cashtabState' in context
+    );
+}
+
+interface NullDefaultUseWalletReturnType {
+    chronik: undefined;
+    agora: undefined;
+    ecc: undefined;
+    chaintipBlockheight: undefined;
+    fiatPrice: undefined;
+    cashtabLoaded: undefined;
+    loading: undefined;
+    apiError: undefined;
+    refreshAliases: undefined;
+    aliases: undefined;
+    setAliases: undefined;
+    aliasServerError: undefined;
+    setAliasServerError: undefined;
+    aliasPrices: undefined;
+    setAliasPrices: undefined;
+    updateCashtabState: undefined;
+    processChronikWsMsg: undefined;
+    cashtabState: undefined;
+}
+
+const nullDefaultUseWalletReturnType: NullDefaultUseWalletReturnType = {
+    chronik: undefined,
+    agora: undefined,
+    ecc: undefined,
+    chaintipBlockheight: undefined,
+    fiatPrice: undefined,
+    cashtabLoaded: undefined,
+    loading: undefined,
+    apiError: undefined,
+    refreshAliases: undefined,
+    aliases: undefined,
+    setAliases: undefined,
+    aliasServerError: undefined,
+    setAliasServerError: undefined,
+    aliasPrices: undefined,
+    setAliasPrices: undefined,
+    updateCashtabState: undefined,
+    processChronikWsMsg: undefined,
+    cashtabState: undefined,
+};
+
+export const WalletContext = React.createContext<
+    UseWalletReturnType | NullDefaultUseWalletReturnType
+>(nullDefaultUseWalletReturnType);
+
+interface WalletProviderProps {
+    chronik: ChronikClient;
+    agora: Agora;
+    ecc: Ecc;
+    children: React.ReactNode;
+}
+export const WalletProvider: React.FC<WalletProviderProps> = ({
+    chronik,
+    agora,
+    ecc,
+    children,
+}) => {
+    return (
+        <WalletContext.Provider value={useWallet(chronik, agora, ecc)}>
+            {children}
+        </WalletContext.Provider>
+    );
+};
diff --git a/cashtab/src/wallet/index.ts b/cashtab/src/wallet/index.ts
--- a/cashtab/src/wallet/index.ts
+++ b/cashtab/src/wallet/index.ts
@@ -59,6 +59,9 @@
      */
     sk: number[];
 }
+export interface ScriptUtxoWithToken extends ScriptUtxo {
+    token: Token;
+}
 export interface NonTokenUtxo extends Omit<ScriptUtxo, 'token'> {
     path: number;
 }
diff --git a/cashtab/src/wallet/useWallet.ts b/cashtab/src/wallet/useWallet.ts
--- a/cashtab/src/wallet/useWallet.ts
+++ b/cashtab/src/wallet/useWallet.ts
@@ -61,6 +61,42 @@
 import CashtabCache from 'config/CashtabCache';
 import { ToastIcon } from 'react-toastify/dist/types';
 
+export interface UseWalletReturnType {
+    chronik: ChronikClient;
+    agora: Agora;
+    ecc: Ecc;
+    chaintipBlockheight: number;
+    fiatPrice: number | null;
+    cashtabLoaded: boolean;
+    loading: boolean;
+    apiError: boolean;
+    refreshAliases: (address: string) => Promise<void>;
+    aliases: AddressAliasStatus;
+    setAliases: React.Dispatch<React.SetStateAction<AddressAliasStatus>>;
+    aliasServerError: false | string;
+    setAliasServerError: React.Dispatch<React.SetStateAction<false | string>>;
+    aliasPrices: null | AliasPrices;
+    setAliasPrices: React.Dispatch<React.SetStateAction<null | AliasPrices>>;
+    updateCashtabState: (
+        key: string,
+        value:
+            | CashtabWallet[]
+            | CashtabCache
+            | CashtabContact[]
+            | CashtabSettings
+            | CashtabCacheJson
+            | StoredCashtabWallet[]
+            | (LegacyCashtabWallet | StoredCashtabWallet)[],
+    ) => Promise<boolean>;
+    processChronikWsMsg: (
+        msg: WsMsgClient,
+        cashtabState: CashtabState,
+        fiatPrice: null | number,
+        aliasesEnabled: boolean,
+    ) => Promise<boolean>;
+    cashtabState: CashtabState;
+}
+
 const useWallet = (chronik: ChronikClient, agora: Agora, ecc: Ecc) => {
     const [cashtabLoaded, setCashtabLoaded] = useState<boolean>(false);
     const [ws, setWs] = useState<null | WsEndpoint>(null);
@@ -1145,7 +1181,7 @@
         updateCashtabState,
         processChronikWsMsg,
         cashtabState,
-    };
+    } as UseWalletReturnType;
 };
 
 export default useWallet;