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": "1.4.6",
+    "version": "1.4.7",
     "lockfileVersion": 3,
     "requires": true,
     "packages": {
         "": {
             "name": "cashtab",
-            "version": "1.4.6",
+            "version": "1.4.7",
             "dependencies": {
                 "@ant-design/icons": "^5.3.0",
                 "@bitgo/utxo-lib": "^9.33.0",
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": "1.4.6",
+    "version": "1.4.7",
     "private": true,
     "scripts": {
         "start": "node scripts/start.js",
diff --git a/cashtab/src/components/Send/SendXec.js b/cashtab/src/components/Send/SendXec.js
--- a/cashtab/src/components/Send/SendXec.js
+++ b/cashtab/src/components/Send/SendXec.js
@@ -364,43 +364,52 @@
 
         if (validUrlParams) {
             // This is a tx request from the URL
+
+            // Save this flag in state var so it can be parsed in useEffect
+            txInfo.parseAllAsBip21 = parseAllAsBip21;
             setTxInfoFromUrl(txInfo);
-            if (parseAllAsBip21) {
-                handleAddressChange({
-                    target: {
-                        name: 'address',
-                        value: txInfo.bip21,
-                    },
-                });
-            } else {
-                // Enter address into input field and trigger handleAddressChange for validation
-                handleAddressChange({
+        }
+    }, []);
+
+    useEffect(() => {
+        if (txInfoFromUrl === false) {
+            return;
+        }
+        if (txInfoFromUrl.parseAllAsBip21) {
+            handleAddressChange({
+                target: {
+                    name: 'address',
+                    value: txInfoFromUrl.bip21,
+                },
+            });
+        } else {
+            // Enter address into input field and trigger handleAddressChange for validation
+            handleAddressChange({
+                target: {
+                    name: 'address',
+                    value: txInfoFromUrl.address,
+                },
+            });
+            if (
+                'value' in txInfoFromUrl &&
+                !Number.isNaN(parseFloat(txInfoFromUrl.value))
+            ) {
+                // Only update the amount field if txInfo.value is a good input
+                // Sometimes we want this field to be adjusted by the user, e.g. a donation amount
+
+                // Do not populate the field if the value param is not parseable as a number
+                // the strings 'undefined' and 'null', which PayButton passes to signify 'no amount', fail this test
+
+                // TODO deprecate this support once PayButton and cashtab-components do not require it
+                handleAmountChange({
                     target: {
-                        name: 'address',
-                        value: txInfo.address,
+                        name: 'value',
+                        value: txInfoFromUrl.value,
                     },
                 });
-                if (
-                    'value' in txInfo &&
-                    !Number.isNaN(parseFloat(txInfo.value))
-                ) {
-                    // Only update the amount field if txInfo.value is a good input
-                    // Sometimes we want this field to be adjusted by the user, e.g. a donation amount
-
-                    // Do not populate the field if the value param is not parseable as a number
-                    // the strings 'undefined' and 'null', which PayButton passes to signify 'no amount', fail this test
-
-                    // TODO deprecate this support once PayButton and cashtab-components do not require it
-                    handleAmountChange({
-                        target: {
-                            name: 'value',
-                            value: txInfo.value,
-                        },
-                    });
-                }
             }
         }
-    }, [cashtabCache]);
+    }, [txInfoFromUrl, balances.totalBalance]);
 
     function handleSendXecError(errorObj, oneToManyFlag) {
         // Set loading to false here as well, as balance may not change depending on where error occured in try loop
diff --git a/cashtab/src/components/Send/__tests__/SendByUrlParams.test.js b/cashtab/src/components/Send/__tests__/SendByUrlParams.test.js
--- a/cashtab/src/components/Send/__tests__/SendByUrlParams.test.js
+++ b/cashtab/src/components/Send/__tests__/SendByUrlParams.test.js
@@ -1,12 +1,20 @@
 import React from 'react';
 import { render, screen } from '@testing-library/react';
 import '@testing-library/jest-dom';
-import SendXec from '../SendXec';
-import { ThemeProvider } from 'styled-components';
-import { theme } from 'assets/styles/theme';
-import { mockWalletContext } from '../fixtures/mocks';
-import { WalletContext } from 'utils/context';
-import { BrowserRouter } from 'react-router-dom';
+import {
+    walletWithXecAndTokens,
+    SEND_ADDRESS_VALIDATION_ERRORS,
+    SEND_AMOUNT_VALIDATION_ERRORS,
+} from '../fixtures/mocks';
+import { when } from 'jest-when';
+import 'fake-indexeddb/auto';
+import localforage from 'localforage';
+import appConfig from 'config/app';
+import {
+    initializeCashtabStateForTests,
+    clearLocalForage,
+} from 'components/fixtures/helpers';
+import CashtabTestWrapper from 'components/fixtures/CashtabTestWrapper';
 
 // https://stackoverflow.com/questions/39830580/jest-test-fails-typeerror-window-matchmedia-is-not-a-function
 Object.defineProperty(window, 'matchMedia', {
@@ -35,20 +43,30 @@
     dispatchEvent: jest.fn(),
 });
 
-const TestSendXecScreen = (
-    <BrowserRouter>
-        <WalletContext.Provider value={mockWalletContext}>
-            <ThemeProvider theme={theme}>
-                <SendXec />
-            </ThemeProvider>
-        </WalletContext.Provider>
-    </BrowserRouter>
-);
-
-// Getting by class name is the only practical way to get some antd components
-/* eslint testing-library/no-container: 0 */
 describe('<SendXec /> rendered with params in URL', () => {
-    afterEach(() => {
+    beforeEach(() => {
+        // Mock the fetch call for Cashtab's price API
+        global.fetch = jest.fn();
+        const fiatCode = 'usd'; // Use usd until you mock getting settings from localforage
+        const cryptoId = appConfig.coingeckoId;
+        // Keep this in the code, because different URLs will have different outputs requiring different parsing
+        const priceApiUrl = `https://api.coingecko.com/api/v3/simple/price?ids=${cryptoId}&vs_currencies=${fiatCode}&include_last_updated_at=true`;
+        const xecPrice = 0.00003;
+        const priceResponse = {
+            ecash: {
+                usd: xecPrice,
+                last_updated_at: 1706644626,
+            },
+        };
+        when(fetch)
+            .calledWith(priceApiUrl)
+            .mockResolvedValue({
+                json: () => Promise.resolve(priceResponse),
+            });
+    });
+    afterEach(async () => {
+        jest.clearAllMocks();
+        await clearLocalForage(localforage);
         // Unset the window location so it does not impact other tests in this file
         Object.defineProperty(window, 'location', {
             value: {
@@ -60,24 +78,27 @@
     it('Legacy params. Address and value keys are set and valid.', async () => {
         const destinationAddress =
             'ecash:qp33mh3a7qq7p8yulhnvwty2uq5ynukqcvuxmvzfhm';
-        const hash = `#/send?address=${destinationAddress}&value=500`;
+        const value = 500;
+        const hash = `#/send?address=${destinationAddress}&value=${value}`;
+        // ?address=ecash:qp33mh3a7qq7p8yulhnvwty2uq5ynukqcvuxmvzfhm&value=500
         Object.defineProperty(window, 'location', {
             value: {
                 hash,
             },
             writable: true, // possibility to override
         });
-        const { container } = render(TestSendXecScreen);
-        const addressInputEl = screen.getByTestId('destination-address-single');
-        const amountInputEl = screen.getByTestId('send-xec-input');
-
-        // Input fields are rendered
-        expect(addressInputEl).toBeInTheDocument();
-        expect(amountInputEl).toBeInTheDocument();
+        // Mock the app with context at the Send screen
+        const mockedChronik = await initializeCashtabStateForTests(
+            walletWithXecAndTokens,
+            localforage,
+        );
+        render(<CashtabTestWrapper chronik={mockedChronik} route="/send" />);
+        const addressInputEl = screen.getByPlaceholderText('Address');
+        const amountInputEl = screen.getByPlaceholderText('Amount');
 
         // The multiple recipients switch is not rendered
         expect(
-            screen.queryByTestId('multiple-recipients-switch'),
+            screen.queryByText('Multiple Recipients:'),
         ).not.toBeInTheDocument();
 
         // The 'Send To' input field has this address as a value
@@ -85,48 +106,65 @@
         // The address input is disabled
         expect(addressInputEl).toHaveProperty('disabled', true);
 
-        // The amount input is empty
-        expect(amountInputEl).toHaveValue(500);
+        // The amount input is set to the expected value
+        expect(amountInputEl).toHaveValue(value);
         // The amount input is disabled
         expect(amountInputEl).toHaveProperty('disabled', true);
 
-        // The app-created-tx is rendered
-        expect(screen.getByTestId('app-created-tx')).toBeInTheDocument();
+        // The "Webapp Tx Request" notice is rendered
+        expect(screen.getByText('Webapp Tx Request')).toBeInTheDocument();
 
         // The Bip21Alert span is not rendered
-        const bip21Alert = screen.queryByTestId('bip-alert');
-        expect(bip21Alert).not.toBeInTheDocument();
+        expect(
+            screen.queryByText('(set by BIP21 query string)'),
+        ).not.toBeInTheDocument();
 
-        // The Send button is not disabled because we have a valid amount
-        expect(screen.queryByTestId('disabled-send')).not.toBeInTheDocument();
+        // Wait for balance to be loaded
+        expect(await screen.findByText('9,513.12 XEC')).toBeInTheDocument();
 
-        // No validation errors on load
-        const addressValidationErrorDiv = container.querySelector(
-            '[class="ant-form-item-explain-error"]',
-        );
-        expect(addressValidationErrorDiv).not.toBeInTheDocument();
+        // No addr validation errors on load
+        for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) {
+            expect(screen.queryByText(addrErr)).not.toBeInTheDocument();
+        }
+        // No amount validation errors on load
+        for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) {
+            expect(screen.queryByText(amountErr)).not.toBeInTheDocument();
+        }
+
+        // The Send button is not disabled because we have a valid amount
+        expect(
+            await screen.findByRole('button', { name: /Send/ }),
+        ).not.toHaveStyle('cursor: not-allowed');
     });
-    it('Legacy params. Address and value keys are set and valid. Unsupported legacy params are ignored.', async () => {
+    it('Legacy params. Address and value keys are set and valid. Invalid bip21 string is ignored.', async () => {
         const destinationAddress =
             'ecash:qp33mh3a7qq7p8yulhnvwty2uq5ynukqcvuxmvzfhm';
-        const hash = `#/send?address=${destinationAddress}&value=500&bip21=isthisgoingtodosomething&someotherparam=false&anotherstill=true`;
+        const legacyPassedAmount = 500;
+        const hash = `#/send?address=${destinationAddress}&value=${legacyPassedAmount}&bip21=isthisgoingtodosomething&someotherparam=false&anotherstill=true`;
+        // ?address=ecash:qp33mh3a7qq7p8yulhnvwty2uq5ynukqcvuxmvzfhm&value=500&bip21=isthisgoingtodosomething&someotherparam=false&anotherstill=true
         Object.defineProperty(window, 'location', {
             value: {
                 hash,
             },
             writable: true, // possibility to override
         });
-        const { container } = render(TestSendXecScreen);
-        const addressInputEl = screen.getByTestId('destination-address-single');
-        const amountInputEl = screen.getByTestId('send-xec-input');
 
-        // Input fields are rendered
-        expect(addressInputEl).toBeInTheDocument();
-        expect(amountInputEl).toBeInTheDocument();
+        // Mock the app with context at the Send screen
+        const mockedChronik = await initializeCashtabStateForTests(
+            walletWithXecAndTokens,
+            localforage,
+        );
+        render(<CashtabTestWrapper chronik={mockedChronik} route="/send" />);
+
+        // Wait for balance to be loaded
+        expect(await screen.findByText('9,513.12 XEC')).toBeInTheDocument();
+
+        const addressInputEl = screen.getByPlaceholderText('Address');
+        const amountInputEl = screen.getByPlaceholderText('Amount');
 
         // The multiple recipients switch is not rendered
         expect(
-            screen.queryByTestId('multiple-recipients-switch'),
+            screen.queryByText('Multiple Recipients:'),
         ).not.toBeInTheDocument();
 
         // The 'Send To' input field has this address as a value
@@ -134,48 +172,56 @@
         // The address input is disabled
         expect(addressInputEl).toHaveProperty('disabled', true);
 
-        // The amount input is empty
-        expect(amountInputEl).toHaveValue(500);
+        // The amount input is filled out per legacy passed amount
+        expect(amountInputEl).toHaveValue(legacyPassedAmount);
         // The amount input is disabled
         expect(amountInputEl).toHaveProperty('disabled', true);
 
-        // The app-created-tx is rendered
-        expect(screen.getByTestId('app-created-tx')).toBeInTheDocument();
+        // The "Webapp Tx Request" notice is rendered
+        expect(screen.getByText('Webapp Tx Request')).toBeInTheDocument();
 
         // The Bip21Alert span is not rendered
-        const bip21Alert = screen.queryByTestId('bip-alert');
-        expect(bip21Alert).not.toBeInTheDocument();
+        expect(
+            screen.queryByText('(set by BIP21 query string)'),
+        ).not.toBeInTheDocument();
 
         // The Send button is not disabled because we have a valid amount
-        expect(screen.queryByTestId('disabled-send')).not.toBeInTheDocument();
-
-        // No validation errors on load
-        const addressValidationErrorDiv = container.querySelector(
-            '[class="ant-form-item-explain-error"]',
-        );
-        expect(addressValidationErrorDiv).not.toBeInTheDocument();
+        expect(
+            await screen.findByRole('button', { name: /Send/ }),
+        ).not.toHaveStyle('cursor: not-allowed');
+
+        // No addr validation errors on load
+        for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) {
+            expect(screen.queryByText(addrErr)).not.toBeInTheDocument();
+        }
+        // No amount validation errors on load
+        for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) {
+            expect(screen.queryByText(amountErr)).not.toBeInTheDocument();
+        }
     });
     it('Legacy params. Address field is populated + disabled while value field is empty + enabled if legacy url params have address defined and value present as undefined', async () => {
         const destinationAddress =
             'ecash:qp33mh3a7qq7p8yulhnvwty2uq5ynukqcvuxmvzfhm';
         const hash = `#/send?address=${destinationAddress}&value=undefined`;
+        // ?address=ecash:qp33mh3a7qq7p8yulhnvwty2uq5ynukqcvuxmvzfhm&value=undefined
         Object.defineProperty(window, 'location', {
             value: {
                 hash,
             },
             writable: true,
         });
-        const { container } = render(TestSendXecScreen);
-        const addressInputEl = screen.getByTestId('destination-address-single');
-        const amountInputEl = screen.getByTestId('send-xec-input');
-
-        // Input fields are rendered
-        expect(addressInputEl).toBeInTheDocument();
-        expect(amountInputEl).toBeInTheDocument();
+        // Mock the app with context at the Send screen
+        const mockedChronik = await initializeCashtabStateForTests(
+            walletWithXecAndTokens,
+            localforage,
+        );
+        render(<CashtabTestWrapper chronik={mockedChronik} route="/send" />);
+        const addressInputEl = screen.getByPlaceholderText('Address');
+        const amountInputEl = screen.getByPlaceholderText('Amount');
 
         // The multiple recipients switch is not rendered
         expect(
-            screen.queryByTestId('multiple-recipients-switch'),
+            screen.queryByText('Multiple Recipients:'),
         ).not.toBeInTheDocument();
 
         // The 'Send To' input field has this address as a value
@@ -188,43 +234,51 @@
         // The amount input is not disabled
         expect(amountInputEl).toHaveProperty('disabled', false);
 
-        // The app-created-tx is rendered
-        expect(screen.getByTestId('app-created-tx')).toBeInTheDocument();
+        // The "Webapp Tx Request" notice is rendered
+        expect(screen.getByText('Webapp Tx Request')).toBeInTheDocument();
 
         // The Bip21Alert span is not rendered
-        const bip21Alert = screen.queryByTestId('bip-alert');
-        expect(bip21Alert).not.toBeInTheDocument();
+        expect(
+            screen.queryByText('(set by BIP21 query string)'),
+        ).not.toBeInTheDocument();
 
         // The Send button is disabled because no amount is entered
-        expect(screen.getByTestId('disabled-send')).toBeInTheDocument();
-
-        // No validation errors on load
-        const addressValidationErrorDiv = container.querySelector(
-            '[class="ant-form-item-explain-error"]',
+        expect(await screen.findByRole('button', { name: /Send/ })).toHaveStyle(
+            'cursor: not-allowed',
         );
-        expect(addressValidationErrorDiv).not.toBeInTheDocument();
+
+        // No addr validation errors on load
+        for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) {
+            expect(screen.queryByText(addrErr)).not.toBeInTheDocument();
+        }
+        // No amount validation errors on load
+        for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) {
+            expect(screen.queryByText(amountErr)).not.toBeInTheDocument();
+        }
     });
     it('Legacy params. Address field is populated + disabled while value field is empty + enabled if legacy url params have address defined and no value key present', async () => {
         const destinationAddress =
             'ecash:qp33mh3a7qq7p8yulhnvwty2uq5ynukqcvuxmvzfhm';
         const hash = `#/send?address=${destinationAddress}`;
+        // ?address=ecash:qp33mh3a7qq7p8yulhnvwty2uq5ynukqcvuxmvzfhm
         Object.defineProperty(window, 'location', {
             value: {
                 hash,
             },
             writable: true,
         });
-        const { container } = render(TestSendXecScreen);
-        const addressInputEl = screen.getByTestId('destination-address-single');
-        const amountInputEl = screen.getByTestId('send-xec-input');
-
-        // Input fields are rendered
-        expect(addressInputEl).toBeInTheDocument();
-        expect(amountInputEl).toBeInTheDocument();
+        // Mock the app with context at the Send screen
+        const mockedChronik = await initializeCashtabStateForTests(
+            walletWithXecAndTokens,
+            localforage,
+        );
+        render(<CashtabTestWrapper chronik={mockedChronik} route="/send" />);
+        const addressInputEl = screen.getByPlaceholderText('Address');
+        const amountInputEl = screen.getByPlaceholderText('Amount');
 
         // The multiple recipients switch is not rendered
         expect(
-            screen.queryByTestId('multiple-recipients-switch'),
+            screen.queryByText('Multiple Recipients:'),
         ).not.toBeInTheDocument();
 
         // The 'Send To' input field has this address as a value
@@ -237,42 +291,48 @@
         // The amount input is not disabled
         expect(amountInputEl).toHaveProperty('disabled', false);
 
-        // The app-created-tx is rendered
-        expect(screen.getByTestId('app-created-tx')).toBeInTheDocument();
+        // The "Webapp Tx Request" notice is rendered
+        expect(screen.getByText('Webapp Tx Request')).toBeInTheDocument();
 
         // The Bip21Alert span is not rendered
-        const bip21Alert = screen.queryByTestId('bip-alert');
-        expect(bip21Alert).not.toBeInTheDocument();
+        expect(
+            screen.queryByText('(set by BIP21 query string)'),
+        ).not.toBeInTheDocument();
 
         // The Send button is disabled because no amount is entered
-        expect(screen.getByTestId('disabled-send')).toBeInTheDocument();
-
-        // No validation errors on load
-        const addressValidationErrorDiv = container.querySelector(
-            '[class="ant-form-item-explain-error"]',
+        expect(await screen.findByRole('button', { name: /Send/ })).toHaveStyle(
+            'cursor: not-allowed',
         );
-        expect(addressValidationErrorDiv).not.toBeInTheDocument();
+
+        // No addr validation errors on load
+        for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) {
+            expect(screen.queryByText(addrErr)).not.toBeInTheDocument();
+        }
+        // No amount validation errors on load
+        for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) {
+            expect(screen.queryByText(amountErr)).not.toBeInTheDocument();
+        }
     });
     it('Legacy params. Params are ignored if only value param is present', async () => {
         const hash = `#/send?value=500`;
+        // ?value=500
         Object.defineProperty(window, 'location', {
             value: {
                 hash,
             },
             writable: true,
         });
-        const { container } = render(TestSendXecScreen);
-        const addressInputEl = screen.getByTestId('destination-address-single');
-        const amountInputEl = screen.getByTestId('send-xec-input');
-
-        // Input fields are rendered
-        expect(addressInputEl).toBeInTheDocument();
-        expect(amountInputEl).toBeInTheDocument();
+        // Mock the app with context at the Send screen
+        const mockedChronik = await initializeCashtabStateForTests(
+            walletWithXecAndTokens,
+            localforage,
+        );
+        render(<CashtabTestWrapper chronik={mockedChronik} route="/send" />);
+        const addressInputEl = screen.getByPlaceholderText('Address');
+        const amountInputEl = screen.getByPlaceholderText('Amount');
 
         // The multiple recipients switch is rendered
-        expect(
-            screen.getByTestId('multiple-recipients-switch'),
-        ).toBeInTheDocument();
+        expect(screen.getByText('Multiple Recipients:')).toBeInTheDocument();
 
         // The 'Send To' input field is untouched
         expect(addressInputEl).toHaveValue('');
@@ -284,44 +344,50 @@
         // The amount input is not disabled
         expect(amountInputEl).toHaveProperty('disabled', false);
 
-        // The app-created-tx is not rendered
-        expect(screen.queryByTestId('app-created-tx')).not.toBeInTheDocument();
+        // The "Webapp Tx Request" notice is NOT rendered
+        expect(screen.queryByText('Webapp Tx Request')).not.toBeInTheDocument();
 
         // The Bip21Alert span is not rendered
-        const bip21Alert = screen.queryByTestId('bip-alert');
-        expect(bip21Alert).not.toBeInTheDocument();
+        expect(
+            screen.queryByText('(set by BIP21 query string)'),
+        ).not.toBeInTheDocument();
 
         // The Send button is disabled because no amount is entered
-        expect(screen.getByTestId('disabled-send')).toBeInTheDocument();
-
-        // No validation errors on load
-        const addressValidationErrorDiv = container.querySelector(
-            '[class="ant-form-item-explain-error"]',
+        expect(await screen.findByRole('button', { name: /Send/ })).toHaveStyle(
+            'cursor: not-allowed',
         );
-        expect(addressValidationErrorDiv).not.toBeInTheDocument();
+
+        // No addr validation errors on load
+        for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) {
+            expect(screen.queryByText(addrErr)).not.toBeInTheDocument();
+        }
+        // No amount validation errors on load
+        for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) {
+            expect(screen.queryByText(amountErr)).not.toBeInTheDocument();
+        }
     });
     it('Legacy params. Params are ignored if param is duplicated', async () => {
         const destinationAddress =
             'ecash:qp33mh3a7qq7p8yulhnvwty2uq5ynukqcvuxmvzfhm';
         const hash = `#/send?address=${destinationAddress}&amount=500&amount=1000`;
+        // ?address=ecash:qp33mh3a7qq7p8yulhnvwty2uq5ynukqcvuxmvzfhm&amount=500&amount=1000
         Object.defineProperty(window, 'location', {
             value: {
                 hash,
             },
             writable: true,
         });
-        const { container } = render(TestSendXecScreen);
-        const addressInputEl = screen.getByTestId('destination-address-single');
-        const amountInputEl = screen.getByTestId('send-xec-input');
-
-        // Input fields are rendered
-        expect(addressInputEl).toBeInTheDocument();
-        expect(amountInputEl).toBeInTheDocument();
+        // Mock the app with context at the Send screen
+        const mockedChronik = await initializeCashtabStateForTests(
+            walletWithXecAndTokens,
+            localforage,
+        );
+        render(<CashtabTestWrapper chronik={mockedChronik} route="/send" />);
+        const addressInputEl = screen.getByPlaceholderText('Address');
+        const amountInputEl = screen.getByPlaceholderText('Amount');
 
         // The multiple recipients switch is rendered
-        expect(
-            screen.getByTestId('multiple-recipients-switch'),
-        ).toBeInTheDocument();
+        expect(screen.getByText('Multiple Recipients:')).toBeInTheDocument();
 
         // The 'Send To' input field is untouched
         expect(addressInputEl).toHaveValue('');
@@ -333,43 +399,56 @@
         // The amount input is not disabled
         expect(amountInputEl).toHaveProperty('disabled', false);
 
-        // The app-created-tx is not rendered
-        expect(screen.queryByTestId('app-created-tx')).not.toBeInTheDocument();
+        // The "Webapp Tx Request" notice is NOT rendered
+        expect(screen.queryByText('Webapp Tx Request')).not.toBeInTheDocument();
 
         // The Bip21Alert span is not rendered
-        const bip21Alert = screen.queryByTestId('bip-alert');
-        expect(bip21Alert).not.toBeInTheDocument();
+        expect(
+            screen.queryByText('(set by BIP21 query string)'),
+        ).not.toBeInTheDocument();
 
         // The Send button is disabled because no amount is entered
-        expect(screen.getByTestId('disabled-send')).toBeInTheDocument();
-
-        // No validation errors on load
-        const addressValidationErrorDiv = container.querySelector(
-            '[class="ant-form-item-explain-error"]',
+        expect(await screen.findByRole('button', { name: /Send/ })).toHaveStyle(
+            'cursor: not-allowed',
         );
-        expect(addressValidationErrorDiv).not.toBeInTheDocument();
+
+        // No addr validation errors on load
+        for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) {
+            expect(screen.queryByText(addrErr)).not.toBeInTheDocument();
+        }
+        // No amount validation errors on load
+        for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) {
+            expect(screen.queryByText(amountErr)).not.toBeInTheDocument();
+        }
     });
     it('Legacy params are not parsed as bip21 even if the bip21 param appears in the string', async () => {
         const destinationAddress =
             'ecash:qp33mh3a7qq7p8yulhnvwty2uq5ynukqcvuxmvzfhm';
-        const hash = `#/send?address=${destinationAddress}&value=500&bip21=ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6?amount=17&op_return_raw=04007461622263617368746162206D6573736167652077697468206F705F72657475726E5F726177`;
+        const legacyPassedAmount = 500;
+        const hash = `#/send?address=${destinationAddress}&value=${legacyPassedAmount}&bip21=ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6?amount=17&op_return_raw=04007461622263617368746162206D6573736167652077697468206F705F72657475726E5F726177`;
+        // ?address=ecash:qp33mh3a7qq7p8yulhnvwty2uq5ynukqcvuxmvzfhm&value=500&bip21=ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6?amount=17&op_return_raw=04007461622263617368746162206D6573736167652077697468206F705F72657475726E5F726177
         Object.defineProperty(window, 'location', {
             value: {
                 hash,
             },
             writable: true,
         });
-        const { container } = render(TestSendXecScreen);
-        const addressInputEl = screen.getByTestId('destination-address-single');
-        const amountInputEl = screen.getByTestId('send-xec-input');
+        // Mock the app with context at the Send screen
+        const mockedChronik = await initializeCashtabStateForTests(
+            walletWithXecAndTokens,
+            localforage,
+        );
+        render(<CashtabTestWrapper chronik={mockedChronik} route="/send" />);
 
-        // Input fields are rendered
-        expect(addressInputEl).toBeInTheDocument();
-        expect(amountInputEl).toBeInTheDocument();
+        // Wait for balance to be loaded
+        expect(await screen.findByText('9,513.12 XEC')).toBeInTheDocument();
+
+        const addressInputEl = screen.getByPlaceholderText('Address');
+        const amountInputEl = screen.getByPlaceholderText('Amount');
 
         // The multiple recipients switch is not rendered
         expect(
-            screen.queryByTestId('multiple-recipients-switch'),
+            screen.queryByText('Multiple Recipients:'),
         ).not.toBeInTheDocument();
 
         // The 'Send To' input field has this address as a value
@@ -377,26 +456,32 @@
         // The address input is disabled
         expect(addressInputEl).toHaveProperty('disabled', true);
 
-        // The amount input is empty
-        expect(amountInputEl).toHaveValue(500);
+        // The amount input has the expected value
+        expect(amountInputEl).toHaveValue(legacyPassedAmount);
         // The amount input is disabled
         expect(amountInputEl).toHaveProperty('disabled', true);
 
-        // The app-created-tx is rendered
-        expect(screen.getByTestId('app-created-tx')).toBeInTheDocument();
+        // The "Webapp Tx Request" notice is rendered
+        expect(screen.getByText('Webapp Tx Request')).toBeInTheDocument();
 
         // The Bip21Alert span is not rendered
-        const bip21Alert = screen.queryByTestId('bip-alert');
-        expect(bip21Alert).not.toBeInTheDocument();
+        expect(
+            screen.queryByText('(set by BIP21 query string)'),
+        ).not.toBeInTheDocument();
 
         // The Send button is not disabled because we have a valid amount
-        expect(screen.queryByTestId('disabled-send')).not.toBeInTheDocument();
-
-        // No validation errors on load
-        const addressValidationErrorDiv = container.querySelector(
-            '[class="ant-form-item-explain-error"]',
-        );
-        expect(addressValidationErrorDiv).not.toBeInTheDocument();
+        expect(
+            await screen.findByRole('button', { name: /Send/ }),
+        ).not.toHaveStyle('cursor: not-allowed');
+
+        // No addr validation errors on load
+        for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) {
+            expect(screen.queryByText(addrErr)).not.toBeInTheDocument();
+        }
+        // No amount validation errors on load
+        for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) {
+            expect(screen.queryByText(amountErr)).not.toBeInTheDocument();
+        }
     });
     it('bip21 param - valid bip21 param with amount and op_return_raw is parsed as expected', async () => {
         const destinationAddress =
@@ -406,23 +491,29 @@
             '04007461622263617368746162206D6573736167652077697468206F705F72657475726E5F726177';
         const bip21Str = `${destinationAddress}?amount=${amount}&op_return_raw=${op_return_raw}`;
         const hash = `#/send?bip21=${bip21Str}`;
+        // ?bip21=ecash:qp33mh3a7qq7p8yulhnvwty2uq5ynukqcvuxmvzfhm?amount=17&op_return_raw=04007461622263617368746162206D6573736167652077697468206F705F72657475726E5F726177
         Object.defineProperty(window, 'location', {
             value: {
                 hash,
             },
             writable: true,
         });
-        const { container } = render(TestSendXecScreen);
-        const addressInputEl = screen.getByTestId('destination-address-single');
-        const amountInputEl = screen.getByTestId('send-xec-input');
+        // Mock the app with context at the Send screen
+        const mockedChronik = await initializeCashtabStateForTests(
+            walletWithXecAndTokens,
+            localforage,
+        );
+        render(<CashtabTestWrapper chronik={mockedChronik} route="/send" />);
+
+        // Wait for balance to be loaded
+        expect(await screen.findByText('9,513.12 XEC')).toBeInTheDocument();
 
-        // Input fields are rendered
-        expect(addressInputEl).toBeInTheDocument();
-        expect(amountInputEl).toBeInTheDocument();
+        const addressInputEl = screen.getByPlaceholderText('Address');
+        const amountInputEl = screen.getByPlaceholderText('Amount');
 
         // The multiple recipients switch is not rendered
         expect(
-            screen.queryByTestId('multiple-recipients-switch'),
+            screen.queryByText('Multiple Recipients:'),
         ).not.toBeInTheDocument();
 
         // The 'Send To' input field has this address as a value
@@ -435,7 +526,7 @@
 
         // The multiple recipients switch is not rendered
         expect(
-            screen.queryByTestId('multiple-recipients-switch'),
+            screen.queryByText('Multiple Recipients:'),
         ).not.toBeInTheDocument();
 
         // Amount input is the valid amount param value
@@ -444,21 +535,27 @@
         // The amount input is disabled because it is set by a bip21 query string
         expect(amountInputEl).toHaveProperty('disabled', true);
 
-        // No validation errors
-        const addressValidationErrorDiv = container.querySelector(
-            '[class="ant-form-item-explain-error"]',
-        );
-        expect(addressValidationErrorDiv).not.toBeInTheDocument();
+        // No addr validation errors on load
+        for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) {
+            expect(screen.queryByText(addrErr)).not.toBeInTheDocument();
+        }
+        // No amount validation errors on load
+        for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) {
+            expect(screen.queryByText(amountErr)).not.toBeInTheDocument();
+        }
 
         // The Send button is enabled as we have valid address and amount params
-        expect(screen.queryByTestId('disabled-send')).not.toBeInTheDocument();
+        expect(
+            await screen.findByRole('button', { name: /Send/ }),
+        ).not.toHaveStyle('cursor: not-allowed');
 
-        // The app-created-tx is rendered
-        expect(screen.getByTestId('app-created-tx')).toBeInTheDocument();
+        // The "Webapp Tx Request" notice is rendered
+        expect(screen.getByText('Webapp Tx Request')).toBeInTheDocument();
 
-        // The Bip21Alert span is rendered
-        const bip21Alert = screen.getByTestId('bip-alert');
-        expect(bip21Alert).toBeInTheDocument();
+        // The Bip21Alert span is not rendered
+        expect(
+            screen.getByText('(set by BIP21 query string)'),
+        ).toBeInTheDocument();
 
         // The Cashtab Message collapse is not rendered
         expect(
@@ -483,23 +580,25 @@
         // Repeat the op_return_raw param
         const bip21Str = `${destinationAddress}?amount=${amount}&op_return_raw=${op_return_raw}&op_return_raw=${op_return_raw}`;
         const hash = `#/send?bip21=${bip21Str}`;
+        // ?bip21=ecash:qp33mh3a7qq7p8yulhnvwty2uq5ynukqcvuxmvzfhm?amount=17&op_return_raw=04007461622263617368746162206D6573736167652077697468206F705F72657475726E5F726177&op_return_raw=04007461622263617368746162206D6573736167652077697468206F705F72657475726E5F726177
         Object.defineProperty(window, 'location', {
             value: {
                 hash,
             },
             writable: true,
         });
-        const { container } = render(TestSendXecScreen);
-        const addressInputEl = screen.getByTestId('destination-address-single');
-        const amountInputEl = screen.getByTestId('send-xec-input');
-
-        // Input fields are rendered
-        expect(addressInputEl).toBeInTheDocument();
-        expect(amountInputEl).toBeInTheDocument();
+        // Mock the app with context at the Send screen
+        const mockedChronik = await initializeCashtabStateForTests(
+            walletWithXecAndTokens,
+            localforage,
+        );
+        render(<CashtabTestWrapper chronik={mockedChronik} route="/send" />);
+        const addressInputEl = screen.getByPlaceholderText('Address');
+        const amountInputEl = screen.getByPlaceholderText('Amount');
 
         // The multiple recipients switch is not rendered
         expect(
-            screen.queryByTestId('multiple-recipients-switch'),
+            screen.queryByText('Multiple Recipients:'),
         ).not.toBeInTheDocument();
 
         // The 'Send To' input field has this address as a value
@@ -512,7 +611,7 @@
 
         // The multiple recipients switch is not rendered
         expect(
-            screen.queryByTestId('multiple-recipients-switch'),
+            screen.queryByText('Multiple Recipients:'),
         ).not.toBeInTheDocument();
 
         // Amount input is not updated as the bip21 query is invalid
@@ -521,24 +620,26 @@
         // The amount input is not disabled because it is not set by the invalid bip21 query string
         expect(amountInputEl).toHaveProperty('disabled', false);
 
-        // Check for antd error div
-        const addressValidationErrorDiv = container.querySelector(
-            '[class="ant-form-item-explain-error"]',
-        );
-        expect(addressValidationErrorDiv).toBeInTheDocument();
-        expect(addressValidationErrorDiv).toHaveTextContent(
-            'bip21 parameters may not appear more than once',
-        );
+        // We get the expected validation error
+        expect(
+            // Note that, due to antd quirks, we get 2 of these
+            screen.getAllByText(
+                'bip21 parameters may not appear more than once',
+            )[0],
+        ).toBeInTheDocument();
 
         // The Send button is disabled
-        expect(screen.getByTestId('disabled-send')).toBeInTheDocument();
+        expect(await screen.findByRole('button', { name: /Send/ })).toHaveStyle(
+            'cursor: not-allowed',
+        );
 
-        // The app-created-tx is rendered
-        expect(screen.getByTestId('app-created-tx')).toBeInTheDocument();
+        // The "Webapp Tx Request" notice is rendered
+        expect(screen.getByText('Webapp Tx Request')).toBeInTheDocument();
 
         // The Bip21Alert span is not rendered as no info about the tx is set for invalid bip21
-        const bip21Alert = screen.queryByTestId('bip-alert');
-        expect(bip21Alert).not.toBeInTheDocument();
+        expect(
+            screen.queryByText('(set by BIP21 query string)'),
+        ).not.toBeInTheDocument();
 
         // The Cashtab Message collapse is not rendered
         expect(
@@ -559,18 +660,17 @@
             },
             writable: true, // possibility to override
         });
-        const { container } = render(TestSendXecScreen);
-        const addressInputEl = screen.getByTestId('destination-address-single');
-        const amountInputEl = screen.getByTestId('send-xec-input');
-
-        // Input fields are rendered
-        expect(addressInputEl).toBeInTheDocument();
-        expect(amountInputEl).toBeInTheDocument();
+        // Mock the app with context at the Send screen
+        const mockedChronik = await initializeCashtabStateForTests(
+            walletWithXecAndTokens,
+            localforage,
+        );
+        render(<CashtabTestWrapper chronik={mockedChronik} route="/send" />);
+        const addressInputEl = screen.getByPlaceholderText('Address');
+        const amountInputEl = screen.getByPlaceholderText('Amount');
 
         // The multiple recipients switch is rendered
-        expect(
-            screen.getByTestId('multiple-recipients-switch'),
-        ).toBeInTheDocument();
+        expect(screen.getByText('Multiple Recipients:')).toBeInTheDocument();
 
         // The 'Send To' input field has this address as a value
         expect(addressInputEl).toHaveValue('');
@@ -582,20 +682,26 @@
         // The amount input is not disabled
         expect(amountInputEl).toHaveProperty('disabled', false);
 
-        // The app-created-tx is not rendered
-        expect(screen.queryByTestId('app-created-tx')).not.toBeInTheDocument();
+        // The "Webapp Tx Request" notice is NOT rendered
+        expect(screen.queryByText('Webapp Tx Request')).not.toBeInTheDocument();
 
         // The Bip21Alert span is not rendered
-        const bip21Alert = screen.queryByTestId('bip-alert');
-        expect(bip21Alert).not.toBeInTheDocument();
-
-        // The Send button is disable
-        expect(screen.getByTestId('disabled-send')).toBeInTheDocument();
+        expect(
+            screen.queryByText('(set by BIP21 query string)'),
+        ).not.toBeInTheDocument();
 
-        // No validation errors on load
-        const addressValidationErrorDiv = container.querySelector(
-            '[class="ant-form-item-explain-error"]',
+        // The Send button is disabled
+        expect(await screen.findByRole('button', { name: /Send/ })).toHaveStyle(
+            'cursor: not-allowed',
         );
-        expect(addressValidationErrorDiv).not.toBeInTheDocument();
+
+        // No addr validation errors on load
+        for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) {
+            expect(screen.queryByText(addrErr)).not.toBeInTheDocument();
+        }
+        // No amount validation errors on load
+        for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) {
+            expect(screen.queryByText(amountErr)).not.toBeInTheDocument();
+        }
     });
 });
diff --git a/cashtab/src/components/Send/__tests__/SendToken.test.js b/cashtab/src/components/Send/__tests__/SendToken.test.js
--- a/cashtab/src/components/Send/__tests__/SendToken.test.js
+++ b/cashtab/src/components/Send/__tests__/SendToken.test.js
@@ -54,13 +54,13 @@
 // See SendToken for some modified errors (SendToken does not support bip21)
 // These could change, which would break tests, which is expected behavior if we haven't
 // updated tests properly on changing the app
-const SEND_ADDRESS_VALIDATION_ERRORS = [
+const SEND_ADDRESS_VALIDATION_ERRORS_TOKEN = [
     `Aliases must end with '.xec'`,
     'eCash Alias does not exist or yet to receive 1 confirmation',
     'Invalid address',
     'eToken sends do not support bip21 query strings',
 ];
-const SEND_AMOUNT_VALIDATION_ERRORS = [
+const SEND_AMOUNT_VALIDATION_ERRORS_TOKEN = [
     `Amount must be a number`,
     'Amount must be greater than 0',
     `Amount cannot exceed your ${SEND_TOKEN_TICKER} balance of ${SEND_TOKEN_BALANCE}`,
@@ -151,11 +151,11 @@
         expect(amountInputEl).toHaveProperty('disabled', false);
 
         // No addr validation errors on load
-        for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) {
+        for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS_TOKEN) {
             expect(screen.queryByText(addrErr)).not.toBeInTheDocument();
         }
         // No amount validation errors on load
-        for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) {
+        for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS_TOKEN) {
             expect(screen.queryByText(amountErr)).not.toBeInTheDocument();
         }
     });
@@ -182,11 +182,11 @@
         expect(addressInputEl).toHaveValue(addressInput);
 
         // No addr validation errors on load
-        for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) {
+        for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS_TOKEN) {
             expect(screen.queryByText(addrErr)).not.toBeInTheDocument();
         }
         // No amount validation errors on load
-        for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) {
+        for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS_TOKEN) {
             expect(screen.queryByText(amountErr)).not.toBeInTheDocument();
         }
     });
@@ -214,11 +214,11 @@
         expect(addressInputEl).toHaveValue(addressInput);
 
         // No addr validation errors on load
-        for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) {
+        for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS_TOKEN) {
             expect(screen.queryByText(addrErr)).not.toBeInTheDocument();
         }
         // No amount validation errors on load
-        for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) {
+        for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS_TOKEN) {
             expect(screen.queryByText(amountErr)).not.toBeInTheDocument();
         }
     });
@@ -264,11 +264,11 @@
         expect(addressInputEl).toHaveValue(addressInput);
 
         // No addr validation errors on load
-        for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) {
+        for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS_TOKEN) {
             expect(screen.queryByText(addrErr)).not.toBeInTheDocument();
         }
         // No amount validation errors on load
-        for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) {
+        for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS_TOKEN) {
             expect(screen.queryByText(amountErr)).not.toBeInTheDocument();
         }
 
diff --git a/cashtab/src/components/Send/__tests__/SendXec.test.js b/cashtab/src/components/Send/__tests__/SendXec.test.js
--- a/cashtab/src/components/Send/__tests__/SendXec.test.js
+++ b/cashtab/src/components/Send/__tests__/SendXec.test.js
@@ -4,7 +4,11 @@
 import userEvent, {
     PointerEventsCheckLevel,
 } from '@testing-library/user-event';
-import { walletWithXecAndTokens } from '../fixtures/mocks';
+import {
+    walletWithXecAndTokens,
+    SEND_ADDRESS_VALIDATION_ERRORS,
+    SEND_AMOUNT_VALIDATION_ERRORS,
+} from '../fixtures/mocks';
 import { when } from 'jest-when';
 import aliasSettings from 'config/alias';
 import { explorer } from 'config/explorer';
@@ -44,18 +48,6 @@
     dispatchEvent: jest.fn(),
 });
 
-// See src/validation, ref parseAddressInput
-// These could change, which would break tests, which is expected behavior if we haven't
-// updated tests properly on changing the app
-const SEND_ADDRESS_VALIDATION_ERRORS = [
-    `Aliases must end with '.xec'`,
-    'eToken addresses are not supported for ${appConfig.ticker} sends',
-    'Invalid address',
-    'bip21 parameters may not appear more than once',
-    `Unsupported param`,
-    `Invalid op_return_raw param`,
-];
-const SEND_AMOUNT_VALIDATION_ERRORS = [`Invalid XEC send amount`];
 describe('<SendXec />', () => {
     let user;
     beforeEach(() => {
diff --git a/cashtab/src/components/Send/fixtures/mocks.js b/cashtab/src/components/Send/fixtures/mocks.js
--- a/cashtab/src/components/Send/fixtures/mocks.js
+++ b/cashtab/src/components/Send/fixtures/mocks.js
@@ -1,6 +1,3 @@
-import { cashtabSettings } from 'config/cashtabSettings';
-import cashtabCache from 'config/cashtabCache';
-
 export const walletWithXecAndTokens = {
     mnemonic:
         'beauty shoe decline spend still weird slot snack coach flee between paper',
@@ -797,8 +794,24 @@
     },
 };
 
-export const mockWalletContext = {
-    wallet: walletWithXecAndTokens,
-    cashtabState: { settings: cashtabSettings },
-    cashtabCache,
-};
+// See src/validation, ref parseAddressInput
+// These could change, which would break tests, which is expected behavior if we haven't
+// updated tests properly on changing the app
+export const SEND_ADDRESS_VALIDATION_ERRORS = [
+    `Aliases must end with '.xec'`,
+    'eToken addresses are not supported for ${appConfig.ticker} sends',
+    'Invalid address',
+    'bip21 parameters may not appear more than once',
+    `Unsupported param`,
+    `Invalid op_return_raw param`,
+];
+
+// See function shouldRejectAmountInput in validation/index.js
+export const SEND_AMOUNT_VALIDATION_ERRORS = [
+    `Invalid XEC send amount`,
+    'Amount must be a number',
+    'Amount must be greater than 0',
+    `Send amount must be at least 5.50 XEC`,
+    `Amount cannot exceed your XEC balance`,
+    `XEC transactions do not support more than 2 decimal places`,
+];
diff --git a/cashtab/src/validation/index.js b/cashtab/src/validation/index.js
--- a/cashtab/src/validation/index.js
+++ b/cashtab/src/validation/index.js
@@ -124,6 +124,10 @@
     fiatPrice,
     totalCashBalance,
 ) => {
+    console.log(`cashAmount`, cashAmount);
+    console.log(`selectedCurrency`, selectedCurrency);
+    console.log(`fiatPrice`, fiatPrice);
+    console.log(`totalCashBalance`, totalCashBalance);
     // Take cashAmount as input, a string from form input
     let error = false;
     let testedAmount = new BN(cashAmount);