diff --git a/cashtab/extension/public/manifest.json b/cashtab/extension/public/manifest.json
--- a/cashtab/extension/public/manifest.json
+++ b/cashtab/extension/public/manifest.json
@@ -2,7 +2,7 @@
     "manifest_version": 3,
     "name": "Cashtab",
     "description": "A browser-integrated eCash wallet from Bitcoin ABC",
-    "version": "3.42.5",
+    "version": "3.43.0",
     "content_scripts": [
         {
             "matches": ["file://*/*", "http://*/*", "https://*/*"],
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": "2.42.7",
+    "version": "2.43.0",
     "lockfileVersion": 3,
     "requires": true,
     "packages": {
         "": {
             "name": "cashtab",
-            "version": "2.42.7",
+            "version": "2.43.0",
             "dependencies": {
                 "@bitgo/utxo-lib": "^9.33.0",
                 "@zxing/browser": "^0.1.4",
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": "2.42.7",
+    "version": "2.43.0",
     "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
@@ -18,7 +18,7 @@
     isValidXecSendAmount,
     getOpReturnRawError,
 } from 'validation';
-import { ConvertAmount, AlertMsg, TxLink } from 'components/Common/Atoms';
+import { ConvertAmount, AlertMsg, TxLink, Info } from 'components/Common/Atoms';
 import { getWalletState } from 'utils/cashMethods';
 import {
     sendXec,
@@ -96,17 +96,17 @@
     flex-direction: column;
     justify-content: center;
 `;
-const ParsedOpReturnRawRow = styled.div`
+const ParsedBip21InfoRow = styled.div`
     display: flex;
     flex-direction: column;
     word-break: break-word;
 `;
-const ParsedOpReturnRawLabel = styled.div`
+const ParsedBip21InfoLabel = styled.div`
     color: ${props => props.theme.contrast};
     text-align: left;
     width: 100%;
 `;
-const ParsedOpReturnRaw = styled.div`
+const ParsedBip21Info = styled.div`
     background-color: #fff2f0;
     border-radius: 12px;
     color: ${props => props.theme.eCashBlue};
@@ -225,6 +225,21 @@
 
     const [airdropFlag, setAirdropFlag] = useState(false);
 
+    // Shorthand variable for bip21 multiple outputs
+    const isBip21MultipleOutputs =
+        typeof parsedAddressInput.parsedAdditionalXecOutputs !== 'undefined' &&
+        parsedAddressInput.parsedAdditionalXecOutputs.error === false &&
+        parsedAddressInput.parsedAdditionalXecOutputs.value !== null;
+
+    // Shorthand this calc as well as it is used in multiple spots
+    const bip21MultipleOutputsFormattedTotalSendXec = isBip21MultipleOutputs
+        ? parsedAddressInput.parsedAdditionalXecOutputs.value.reduce(
+              (accumulator, addressAmountArray) =>
+                  accumulator + parseFloat(addressAmountArray[1]),
+              parseFloat(parsedAddressInput.amount.value),
+          )
+        : 0;
+
     const userLocale = getUserLocale(navigator);
     const clearInputForms = () => {
         setFormData(emptyFormData);
@@ -479,7 +494,19 @@
                 value: satoshisToSend,
             });
 
-            Event('Send.js', 'Send', selectedCurrency);
+            if (isBip21MultipleOutputs) {
+                parsedAddressInput.parsedAdditionalXecOutputs.value.forEach(
+                    ([addr, amount]) => {
+                        targetOutputs.push({
+                            script: Script.fromAddress(addr),
+                            value: toSatoshis(amount),
+                        });
+                    },
+                );
+                Event('Send.js', 'SendToMany', selectedCurrency);
+            } else {
+                Event('Send.js', 'Send', selectedCurrency);
+            }
         }
 
         // Send and notify
@@ -586,9 +613,19 @@
             renderedSendToError = parsedAddressInput.op_return_raw.error;
         }
 
+        // Handle errors in secondary addr&amount params
+        if (
+            renderedSendToError === false &&
+            'parsedAdditionalXecOutputs' in parsedAddressInput &&
+            typeof parsedAddressInput.parsedAdditionalXecOutputs.error ===
+                'string'
+        ) {
+            renderedSendToError =
+                parsedAddressInput.parsedAdditionalXecOutputs.error;
+        }
+
         setSendAddressError(renderedSendToError);
 
-        // Set amount if it's in the query string
         if ('amount' in parsedAddressInput) {
             // Set currency to non-fiat
             setSelectedCurrency(appConfig.ticker);
@@ -605,15 +642,21 @@
 
         // Set op_return_raw if it's in the query string
         if ('op_return_raw' in parsedAddressInput) {
-            // Turn on sendWithOpReturnRaw
-            setSendWithOpReturnRaw(true);
-            // Update the op_return_raw field and trigger its validation
-            handleOpReturnRawInput({
-                target: {
-                    name: 'opReturnRaw',
-                    value: parsedAddressInput.op_return_raw.value,
-                },
-            });
+            // In general, we want to show the op_return_raw value even if there is an error,
+            // so the user can see what it is
+            // However in some cases, like duplicate op_return_raw, we do not even have a value to show
+            // So, only render if we have a renderable value
+            if (typeof parsedAddressInput.op_return_raw.value === 'string') {
+                // Turn on sendWithOpReturnRaw
+                setSendWithOpReturnRaw(true);
+                // Update the op_return_raw field and trigger its validation
+                handleOpReturnRawInput({
+                    target: {
+                        name: 'opReturnRaw',
+                        value: parsedAddressInput.op_return_raw.value,
+                    },
+                });
+            }
         }
 
         // Set address field to user input
@@ -799,10 +842,25 @@
                                     .symbol
                             } `
                           : '$ '
-                  } ${(fiatPrice * formData.amount).toLocaleString(userLocale, {
-                      minimumFractionDigits: appConfig.cashDecimals,
-                      maximumFractionDigits: appConfig.cashDecimals,
-                  })} ${
+                  } ${
+                      isBip21MultipleOutputs
+                          ? `${(
+                                fiatPrice *
+                                bip21MultipleOutputsFormattedTotalSendXec
+                            ).toLocaleString(userLocale, {
+                                minimumFractionDigits: appConfig.cashDecimals,
+                                maximumFractionDigits: appConfig.cashDecimals,
+                            })}`
+                          : `${(fiatPrice * formData.amount).toLocaleString(
+                                userLocale,
+                                {
+                                    minimumFractionDigits:
+                                        appConfig.cashDecimals,
+                                    maximumFractionDigits:
+                                        appConfig.cashDecimals,
+                                },
+                            )}`
+                  } ${
                       settings && settings.fiatCurrency
                           ? settings.fiatCurrency.toUpperCase()
                           : 'USD'
@@ -905,27 +963,48 @@
                                 </TxLink>
                             </AliasAddressPreviewLabel>
                         </InputAndAliasPreviewHolder>
-                        <SendXecInput
-                            name="amount"
-                            value={formData.amount}
-                            selectValue={selectedCurrency}
-                            selectDisabled={
-                                'amount' in parsedAddressInput || txInfoFromUrl
-                            }
-                            inputDisabled={
-                                priceApiError ||
-                                (txInfoFromUrl !== false &&
-                                    'value' in txInfoFromUrl &&
-                                    txInfoFromUrl.value !== 'null' &&
-                                    txInfoFromUrl.value !== 'undefined') ||
-                                'amount' in parsedAddressInput
-                            }
-                            fiatCode={settings.fiatCurrency.toUpperCase()}
-                            error={sendAmountError}
-                            handleInput={handleAmountChange}
-                            handleSelect={handleSelectedCurrencyChange}
-                            handleOnMax={onMax}
-                        />
+                        {isBip21MultipleOutputs ? (
+                            <Info>
+                                <b>
+                                    BIP21: Sending{' '}
+                                    {bip21MultipleOutputsFormattedTotalSendXec.toLocaleString(
+                                        userLocale,
+                                        {
+                                            maximumFractionDigits: 2,
+                                            minimumFractionDigits: 2,
+                                        },
+                                    )}{' '}
+                                    XEC to{' '}
+                                    {parsedAddressInput
+                                        .parsedAdditionalXecOutputs.value
+                                        .length + 1}{' '}
+                                    outputs
+                                </b>
+                            </Info>
+                        ) : (
+                            <SendXecInput
+                                name="amount"
+                                value={formData.amount}
+                                selectValue={selectedCurrency}
+                                selectDisabled={
+                                    'amount' in parsedAddressInput ||
+                                    txInfoFromUrl
+                                }
+                                inputDisabled={
+                                    priceApiError ||
+                                    (txInfoFromUrl !== false &&
+                                        'value' in txInfoFromUrl &&
+                                        txInfoFromUrl.value !== 'null' &&
+                                        txInfoFromUrl.value !== 'undefined') ||
+                                    'amount' in parsedAddressInput
+                                }
+                                fiatCode={settings.fiatCurrency.toUpperCase()}
+                                error={sendAmountError}
+                                handleInput={handleAmountChange}
+                                handleSelect={handleSelectedCurrencyChange}
+                                handleOnMax={onMax}
+                            />
+                        )}
                     </SendToOneInputForm>
                 </SendToOneHolder>
                 {priceApiError && (
@@ -1053,20 +1132,67 @@
                         {opReturnRawError === false &&
                             formData.opReturnRaw !== '' && (
                                 <SendXecRow>
-                                    <ParsedOpReturnRawRow>
-                                        <ParsedOpReturnRawLabel>
+                                    <ParsedBip21InfoRow>
+                                        <ParsedBip21InfoLabel>
                                             Parsed op_return_raw
-                                        </ParsedOpReturnRawLabel>
-                                        <ParsedOpReturnRaw>
+                                        </ParsedBip21InfoLabel>
+                                        <ParsedBip21Info>
                                             <b>{parsedOpReturnRaw.protocol}</b>
                                             <br />
                                             {parsedOpReturnRaw.data}
-                                        </ParsedOpReturnRaw>
-                                    </ParsedOpReturnRawRow>
+                                        </ParsedBip21Info>
+                                    </ParsedBip21InfoRow>
                                 </SendXecRow>
                             )}
                     </>
                 )}
+                {isBip21MultipleOutputs && (
+                    <SendXecRow>
+                        <ParsedBip21InfoRow>
+                            <ParsedBip21InfoLabel>
+                                Parsed BIP21 outputs
+                            </ParsedBip21InfoLabel>
+                            <ParsedBip21Info>
+                                <ol>
+                                    <li
+                                        title={parsedAddressInput.address.value}
+                                    >{`${parsedAddressInput.address.value.slice(
+                                        6,
+                                        12,
+                                    )}...${parsedAddressInput.address.value.slice(
+                                        -6,
+                                    )}, ${parseFloat(
+                                        parsedAddressInput.amount.value,
+                                    ).toLocaleString(userLocale, {
+                                        minimumFractionDigits: 2,
+                                        maximumFractionDigits: 2,
+                                    })} XEC`}</li>
+                                    {Array.from(
+                                        parsedAddressInput
+                                            .parsedAdditionalXecOutputs.value,
+                                    ).map(([addr, amount], index) => {
+                                        return (
+                                            <li
+                                                key={index}
+                                                title={addr}
+                                            >{`${addr.slice(
+                                                6,
+                                                12,
+                                            )}...${addr.slice(
+                                                -6,
+                                            )}, ${parseFloat(
+                                                amount,
+                                            ).toLocaleString(userLocale, {
+                                                minimumFractionDigits: 2,
+                                                maximumFractionDigits: 2,
+                                            })} XEC`}</li>
+                                        );
+                                    })}
+                                </ol>
+                            </ParsedBip21Info>
+                        </ParsedBip21InfoRow>
+                    </SendXecRow>
+                )}
             </SendXecForm>
 
             <AmountPreviewCtn>
@@ -1078,6 +1204,17 @@
                                     ' ' +
                                     selectedCurrency}
                             </LocaleFormattedValue>
+                        ) : isBip21MultipleOutputs ? (
+                            <LocaleFormattedValue>
+                                {bip21MultipleOutputsFormattedTotalSendXec.toLocaleString(
+                                    userLocale,
+                                    {
+                                        maximumFractionDigits: 2,
+                                        minimumFractionDigits: 2,
+                                    },
+                                )}{' '}
+                                XEC
+                            </LocaleFormattedValue>
                         ) : (
                             <LocaleFormattedValue>
                                 {!isNaN(formData.amount)
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
@@ -679,15 +679,17 @@
             true,
         );
 
-        // Amount input is not updated as the bip21 query is invalid
-        expect(amountInputEl).toHaveValue(null);
+        // Amount input is updated despite invalid bip21 query, so the user can see the amount
+        expect(amountInputEl).toHaveValue(amount);
 
-        // The amount input is not disabled because it is not set by the invalid bip21 query string
-        expect(amountInputEl).toHaveProperty('disabled', false);
+        // The amount input is disabled because it was set by a bip21 query string and URL routing
+        expect(amountInputEl).toHaveProperty('disabled', true);
 
         // We get the expected validation error
         expect(
-            screen.getByText('bip21 parameters may not appear more than once'),
+            screen.getByText(
+                'The op_return_raw param may not appear more than once',
+            ),
         ).toBeInTheDocument();
 
         // The Send button is disabled
@@ -756,4 +758,123 @@
             expect(screen.queryByText(amountErr)).not.toBeInTheDocument();
         }
     });
+    it('bip21 param - valid bip21 param with amount, op_return_raw, and additional output with amount is parsed as expected', async () => {
+        const destinationAddress =
+            'ecash:qr6lws9uwmjkkaau4w956lugs9nlg9hudqs26lyxkv';
+        const amount = 110;
+        const secondOutputAddr =
+            'ecash:qp4dxtmjlkc6upn29hh9pr2u8rlznwxeqqy0qkrjp5';
+        const secondOutputAmount = 5.5;
+        const op_return_raw =
+            '0470617977202562dd05deda1c101b10562527bcd6bec20268fb94eed01843ba049cd774bec1';
+        const bip21Str = `${destinationAddress}?amount=${amount}&op_return_raw=${op_return_raw}&addr=${secondOutputAddr}&amount=${secondOutputAmount}`;
+        const hash = `#/send?bip21=${bip21Str}`;
+        // ?bip21=ecash:qr6lws9uwmjkkaau4w956lugs9nlg9hudqs26lyxkv?amount=110&op_return_raw=0470617977202562dd05deda1c101b10562527bcd6bec20268fb94eed01843ba049cd774bec1&addr=ecash:qp4dxtmjlkc6upn29hh9pr2u8rlznwxeqqy0qkrjp5&amount=5.50
+        Object.defineProperty(window, 'location', {
+            value: {
+                hash,
+            },
+            writable: true,
+        });
+        // Mock the app with context at the Send screen
+        const mockedChronik = await initializeCashtabStateForTests(
+            walletWithXecAndTokens,
+            localforage,
+        );
+        render(<CashtabTestWrapper chronik={mockedChronik} route="/send" />);
+
+        // Wait for the app to load
+        await waitFor(() =>
+            expect(
+                screen.queryByTitle('Cashtab Loading'),
+            ).not.toBeInTheDocument(),
+        );
+
+        // Wait for balance to be loaded
+        expect(await screen.findByText('9,513.12 XEC')).toBeInTheDocument();
+
+        const addressInputEl = screen.getByPlaceholderText('Address');
+
+        // The "Send to Many" switch is disabled
+        expect(screen.getByTitle('Toggle Multisend')).toHaveProperty(
+            'disabled',
+            true,
+        );
+
+        // The 'Send To' input field has this address as a value
+        await waitFor(() => expect(addressInputEl).toHaveValue(bip21Str));
+
+        // The address input is disabled for app txs with bip21 strings
+        // Note it is NOT disabled for txs where the user inputs the bip21 string
+        // This is covered in SendXec.test.js
+        expect(addressInputEl).toHaveProperty('disabled', true);
+
+        // The "Send to Many" switch is disabled
+        expect(screen.getByTitle('Toggle Multisend')).toHaveProperty(
+            'disabled',
+            true,
+        );
+
+        // Amount input is not displayed
+        expect(screen.queryByPlaceholderText('Amount')).not.toBeInTheDocument();
+
+        // Instead, we see a bip21 output summary
+        expect(
+            screen.getByText('BIP21: Sending 115.50 XEC to 2 outputs'),
+        ).toBeInTheDocument();
+
+        const opReturnRawInput = screen.getByPlaceholderText(
+            `(Advanced) Enter raw hex to be included with this transaction's OP_RETURN`,
+        );
+
+        // The op_return_raw input is populated with this op_return_raw
+        expect(opReturnRawInput).toHaveValue(op_return_raw);
+
+        // The op_return_raw input is disabled
+        expect(opReturnRawInput).toHaveProperty('disabled', true);
+
+        // 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 op_return_raw switch is disabled because we have txInfoFromUrl
+        expect(screen.getByTitle('Toggle op_return_raw')).toHaveProperty(
+            'disabled',
+            true,
+        );
+
+        // The op_return_raw switch is checked because it is set by txInfoFromUrl
+        expect(screen.getByTitle('Toggle op_return_raw')).toHaveProperty(
+            'checked',
+            true,
+        );
+
+        // We see the preview of this op_return_raw
+        expect(screen.getByText('Parsed op_return_raw')).toBeInTheDocument();
+
+        // The Send button is enabled as we have valid address and amount params
+        expect(
+            await screen.findByRole('button', { name: 'Send' }),
+        ).not.toHaveStyle('cursor: not-allowed');
+
+        // The Cashtab Msg switch is disabled because we have txInfoFromUrl
+        expect(screen.getByTitle('Toggle Cashtab Msg')).toHaveProperty(
+            'disabled',
+            true,
+        );
+
+        // We see expected summary of additional bip21 outputs
+        expect(screen.getByText('Parsed BIP21 outputs')).toBeInTheDocument();
+        expect(
+            screen.getByText(`qr6lws...6lyxkv, 110.00 XEC`),
+        ).toBeInTheDocument();
+        expect(
+            screen.getByText(`qp4dxt...qkrjp5, 5.50 XEC`),
+        ).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
@@ -1772,4 +1772,152 @@
             ),
         );
     });
+    it('Entering a valid bip21 query string with multiple outputs and op_return_raw will correctly populate UI fields, and the tx can be sent', async () => {
+        // Mock the app with context at the Send screen
+        const mockedChronik = await initializeCashtabStateForTests(
+            walletWithXecAndTokens,
+            localforage,
+        );
+
+        // Can check in electrum for opreturn and multiple outputs
+        const hex =
+            '0200000001fe667fba52a1aa603a892126e492717eed3dad43bfea7365a7fdd08e051e8a21020000006441d95dfbf01e233d19684fd525d1cc39eb82a53ebfc97b8f2f9160f418ce863f4360f9fd1d6c182abde1d582ed39c6998ec5e4cdbde1b09736f6abe390a6ab8d8f4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff040000000000000000296a04007461622263617368746162206d6573736167652077697468206f705f72657475726e5f726177a4060000000000001976a9144e532257c01b310b3b5c1fd947c79a72addf852388ac40e20100000000001976a91495e79f51d4260bc0dc3ba7fb77c7be92d0fbdd1d88acca980c00000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188ac00000000';
+        const txid =
+            'f153119862f52dbe765ed5d66a5ff848d0386c5d987af9bef5e49a7e62a2c889';
+        mockedChronik.setMock('broadcastTx', {
+            input: hex,
+            output: { txid },
+        });
+
+        render(
+            <CashtabTestWrapper
+                chronik={mockedChronik}
+                ecc={ecc}
+                route="/send"
+            />,
+        );
+
+        // Wait for the app to load
+        await waitFor(() =>
+            expect(
+                screen.queryByTitle('Cashtab Loading'),
+            ).not.toBeInTheDocument(),
+        );
+
+        const addressInputEl = screen.getByPlaceholderText('Address');
+        // The user enters a valid BIP21 query string with a valid amount param
+        const op_return_raw =
+            '04007461622263617368746162206d6573736167652077697468206f705f72657475726e5f726177';
+        const addressInput = `ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6?amount=17&op_return_raw=${op_return_raw}&addr=ecash:qz2708636snqhsxu8wnlka78h6fdp77ar59jrf5035&amount=1234.56`;
+        await user.type(addressInputEl, addressInput);
+
+        // The 'Send To' input field has this bip21 query string as a value
+        expect(addressInputEl).toHaveValue(addressInput);
+
+        // The 'Send To' input field is not disabled
+        expect(addressInputEl).toHaveProperty('disabled', false);
+
+        // The "Send to Many" switch is disabled
+        expect(screen.getByTitle('Toggle Multisend')).toHaveProperty(
+            'disabled',
+            true,
+        );
+
+        // Because we have multiple outputs, the amount input is not displayed
+        expect(screen.queryByPlaceholderText('Amount')).not.toBeInTheDocument();
+
+        // Instead, we see a summary of total outputs and XEC to be sent
+        expect(
+            screen.getByText('BIP21: Sending 1,251.56 XEC to 2 outputs'),
+        ).toBeInTheDocument();
+
+        const opReturnRawInput = screen.getByPlaceholderText(
+            `(Advanced) Enter raw hex to be included with this transaction's OP_RETURN`,
+        );
+
+        // The op_return_raw input is populated with this op_return_raw
+        expect(opReturnRawInput).toHaveValue(op_return_raw);
+
+        // The op_return_raw input is disabled
+        expect(opReturnRawInput).toHaveProperty('disabled', true);
+
+        // We see expected data in the op_return_raw preview
+        expect(
+            screen.getByText('cashtab message with op_return_raw'),
+        ).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.getByRole('button', { name: 'Send' })).not.toHaveStyle(
+            'cursor: not-allowed',
+        );
+
+        // The Cashtab Msg switch is disabled because op_return_raw is set
+        expect(screen.getByTitle('Toggle Cashtab Msg')).toHaveProperty(
+            'disabled',
+            true,
+        );
+
+        // We see a summary table of addresses and amounts
+        expect(screen.getByText('Parsed BIP21 outputs')).toBeInTheDocument();
+        expect(
+            screen.getByText('qp89xg...9nhgg6, 17.00 XEC'),
+        ).toBeInTheDocument();
+        expect(
+            screen.getByText('qz2708...rf5035, 1,234.56 XEC'),
+        ).toBeInTheDocument();
+
+        // Click Send
+        await user.click(
+            screen.getByRole('button', { name: 'Send' }),
+            addressInput,
+        );
+
+        // Notification is rendered with expected txid?;
+        const txSuccessNotification = await screen.findByText('eCash sent');
+        await waitFor(() =>
+            expect(txSuccessNotification).toHaveAttribute(
+                'href',
+                `${explorer.blockExplorerUrl}/tx/${txid}`,
+            ),
+        );
+        await waitFor(() =>
+            // The op_return_raw set alert is now removed
+            expect(
+                screen.queryByText(
+                    `Hex OP_RETURN "04007461622263617368746162206D6573736167652077697468206F705F72657475726E5F726177" set by BIP21`,
+                ),
+            ).not.toBeInTheDocument(),
+        );
+        // The amount input is no longer hidden
+        expect(
+            await screen.findByPlaceholderText('Amount'),
+        ).toBeInTheDocument(),
+            // Amount input is reset
+            expect(await screen.findByPlaceholderText('Amount')).toHaveValue(
+                null,
+            ),
+            // The "Send to Many" switch is not disabled
+            expect(screen.getByTitle('Toggle Multisend')).toHaveProperty(
+                'disabled',
+                false,
+            );
+
+        // The 'Send To' input field has been cleared
+        expect(addressInputEl).toHaveValue('');
+
+        // The Cashtab Msg switch is not disabled because op_return_raw is not set
+        expect(screen.getByTitle('Toggle Cashtab Msg')).toHaveProperty(
+            'disabled',
+            false,
+        );
+    });
 });
diff --git a/cashtab/src/validation/fixtures/vectors.js b/cashtab/src/validation/fixtures/vectors.js
--- a/cashtab/src/validation/fixtures/vectors.js
+++ b/cashtab/src/validation/fixtures/vectors.js
@@ -709,6 +709,132 @@
                     queryString: { value: 'amount=125', error: false },
                 },
             },
+            // no op_return_raw, additional outputs
+            {
+                description:
+                    'Valid primary address & amount, valid secondary addr & amount',
+                addressInput:
+                    'ecash:qr6lws9uwmjkkaau4w956lugs9nlg9hudqs26lyxkv?amount=110&addr=ecash:qp4dxtmjlkc6upn29hh9pr2u8rlznwxeqqy0qkrjp5&amount=5.50',
+                balanceSats: 50000000,
+                userLocale: appConfig.defaultLocale,
+                parsedAddressInput: {
+                    address: {
+                        value: 'ecash:qr6lws9uwmjkkaau4w956lugs9nlg9hudqs26lyxkv',
+                        error: false,
+                        isAlias: false,
+                    },
+                    amount: { value: '110', error: false },
+                    parsedAdditionalXecOutputs: {
+                        error: false,
+                        value: [
+                            [
+                                'ecash:qp4dxtmjlkc6upn29hh9pr2u8rlznwxeqqy0qkrjp5',
+                                '5.50',
+                            ],
+                        ],
+                    },
+                    queryString: {
+                        value: 'amount=110&addr=ecash:qp4dxtmjlkc6upn29hh9pr2u8rlznwxeqqy0qkrjp5&amount=5.50',
+                        error: false,
+                    },
+                },
+            },
+            {
+                description:
+                    'Valid primary address & amount, invalid secondary addr',
+                addressInput:
+                    'ecash:qr6lws9uwmjkkaau4w956lugs9nlg9hudqs26lyxkv?amount=110&addr=someinvalidaddress&amount=5.50',
+                balanceSats: 50000000,
+                userLocale: appConfig.defaultLocale,
+                parsedAddressInput: {
+                    address: {
+                        value: 'ecash:qr6lws9uwmjkkaau4w956lugs9nlg9hudqs26lyxkv',
+                        error: false,
+                        isAlias: false,
+                    },
+                    amount: { value: '110', error: false },
+                    parsedAdditionalXecOutputs: {
+                        error: `Invalid address "someinvalidaddress"`,
+                        value: null,
+                    },
+                    queryString: {
+                        value: 'amount=110&addr=someinvalidaddress&amount=5.50',
+                        error: false,
+                    },
+                },
+            },
+            {
+                description:
+                    'Valid primary address & amount, invalid secondary amount',
+                addressInput:
+                    'ecash:qr6lws9uwmjkkaau4w956lugs9nlg9hudqs26lyxkv?amount=110&addr=ecash:qp4dxtmjlkc6upn29hh9pr2u8rlznwxeqqy0qkrjp5&amount=5.123',
+                balanceSats: 50000000,
+                userLocale: appConfig.defaultLocale,
+                parsedAddressInput: {
+                    address: {
+                        value: 'ecash:qr6lws9uwmjkkaau4w956lugs9nlg9hudqs26lyxkv',
+                        error: false,
+                        isAlias: false,
+                    },
+                    amount: { value: '110', error: false },
+                    parsedAdditionalXecOutputs: {
+                        error: `Invalid amount 5.123 for address ecash:qp4dxtmjlkc6upn29hh9pr2u8rlznwxeqqy0qkrjp5: XEC transactions do not support more than 2 decimal places`,
+                        value: null,
+                    },
+                    queryString: {
+                        value: 'amount=110&addr=ecash:qp4dxtmjlkc6upn29hh9pr2u8rlznwxeqqy0qkrjp5&amount=5.123',
+                        error: false,
+                    },
+                },
+            },
+            {
+                description:
+                    'Valid primary address & amount, valid secondary addr & amount, but the secondary amount param does not directly follow the secondary addr param',
+                addressInput:
+                    'ecash:qr6lws9uwmjkkaau4w956lugs9nlg9hudqs26lyxkv?amount=110&addr=ecash:qp4dxtmjlkc6upn29hh9pr2u8rlznwxeqqy0qkrjp5&op_return_raw=0401020304&amount=5.50',
+                balanceSats: 50000000,
+                userLocale: appConfig.defaultLocale,
+                parsedAddressInput: {
+                    address: {
+                        value: 'ecash:qr6lws9uwmjkkaau4w956lugs9nlg9hudqs26lyxkv',
+                        error: false,
+                        isAlias: false,
+                    },
+                    amount: { value: '110', error: false },
+                    parsedAdditionalXecOutputs: {
+                        error: `No amount key for addr ecash:qp4dxtmjlkc6upn29hh9pr2u8rlznwxeqqy0qkrjp5`,
+                        value: null,
+                    },
+                    queryString: {
+                        value: 'amount=110&addr=ecash:qp4dxtmjlkc6upn29hh9pr2u8rlznwxeqqy0qkrjp5&op_return_raw=0401020304&amount=5.50',
+                        error: false,
+                    },
+                },
+            },
+            {
+                description:
+                    'Valid primary address & amount, valid secondary addr, but no corresponding amount param',
+                addressInput:
+                    'ecash:qr6lws9uwmjkkaau4w956lugs9nlg9hudqs26lyxkv?amount=110&addr=ecash:qp4dxtmjlkc6upn29hh9pr2u8rlznwxeqqy0qkrjp5',
+                balanceSats: 50000000,
+                userLocale: appConfig.defaultLocale,
+                parsedAddressInput: {
+                    address: {
+                        value: 'ecash:qr6lws9uwmjkkaau4w956lugs9nlg9hudqs26lyxkv',
+                        error: false,
+                        isAlias: false,
+                    },
+                    amount: { value: '110', error: false },
+                    parsedAdditionalXecOutputs: {
+                        error: `No amount key for addr ecash:qp4dxtmjlkc6upn29hh9pr2u8rlznwxeqqy0qkrjp5`,
+                        value: null,
+                    },
+                    queryString: {
+                        value: 'amount=110&addr=ecash:qp4dxtmjlkc6upn29hh9pr2u8rlznwxeqqy0qkrjp5',
+                        error: false,
+                    },
+                },
+            },
 
             // opreturn param only
             {
@@ -801,7 +927,40 @@
                     },
                 },
             },
-
+            // Both op_return_raw and amount params, with an additional output
+            {
+                description:
+                    'Valid amount and op_return_raw params and valid second output',
+                addressInput:
+                    'ecash:qr6lws9uwmjkkaau4w956lugs9nlg9hudqs26lyxkv?amount=110&op_return_raw=0470617977202562dd05deda1c101b10562527bcd6bec20268fb94eed01843ba049cd774bec1&addr=ecash:qp4dxtmjlkc6upn29hh9pr2u8rlznwxeqqy0qkrjp5&amount=5.50',
+                balanceSats: 50000000,
+                userLocale: appConfig.defaultLocale,
+                parsedAddressInput: {
+                    address: {
+                        value: 'ecash:qr6lws9uwmjkkaau4w956lugs9nlg9hudqs26lyxkv',
+                        error: false,
+                        isAlias: false,
+                    },
+                    amount: { value: '110', error: false },
+                    op_return_raw: {
+                        value: '0470617977202562dd05deda1c101b10562527bcd6bec20268fb94eed01843ba049cd774bec1',
+                        error: false,
+                    },
+                    parsedAdditionalXecOutputs: {
+                        error: false,
+                        value: [
+                            [
+                                'ecash:qp4dxtmjlkc6upn29hh9pr2u8rlznwxeqqy0qkrjp5',
+                                '5.50',
+                            ],
+                        ],
+                    },
+                    queryString: {
+                        value: 'amount=110&op_return_raw=0470617977202562dd05deda1c101b10562527bcd6bec20268fb94eed01843ba049cd774bec1&addr=ecash:qp4dxtmjlkc6upn29hh9pr2u8rlznwxeqqy0qkrjp5&amount=5.50',
+                        error: false,
+                    },
+                },
+            },
             {
                 description: 'invalid querystring (unsupported params)',
                 addressInput:
@@ -822,7 +981,8 @@
             },
             // Querystring errors where no params can be returned
             {
-                description: 'Invalid queryString, repeated param',
+                description:
+                    'Invalid queryString, repeated amount param without corresponding address',
                 addressInput:
                     'ecash:qq9h6d0a5q65fgywv4ry64x04ep906mdku8f0gxfgx?amount=123.45&amount=678.9',
                 balanceSats: 50000000,
@@ -833,9 +993,13 @@
                         error: false,
                         isAlias: false,
                     },
+                    amount: {
+                        value: null,
+                        error: 'Duplicated amount param without matching address',
+                    },
                     queryString: {
                         value: 'amount=123.45&amount=678.9',
-                        error: 'bip21 parameters may not appear more than once',
+                        error: 'The amount param appears without a corresponding addr param',
                     },
                 },
             },
@@ -851,9 +1015,13 @@
                         error: false,
                         isAlias: false,
                     },
+                    op_return_raw: {
+                        error: 'Duplicated op_return_raw param',
+                        value: null,
+                    },
                     queryString: {
                         value: 'op_return_raw=042e786563000474657374150095e79f51d4260bc0dc3ba7fb77c7be92d0fbdd1d&op_return_raw=042e786563000474657374150095e79f51d4260bc0dc3ba7fb77c7be92d0fbdd1d',
-                        error: `bip21 parameters may not appear more than once`,
+                        error: `The op_return_raw param may not appear more than once`,
                     },
                 },
             },
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
@@ -651,6 +651,7 @@
  * For now, Cashtab supports only
  * amount - amount to be sent in XEC
  * opreturn - raw hex for opreturn output
+ * additional addr & amount - multiple outputs for XEC txs
  * @param {number} balanceSats user wallet balance in satoshis
  * @param {string} userLocale navigator.language if available, or default value if not
  * @returns {object} addressInfo. Object with parsed params designed for use in Send.js
@@ -714,31 +715,92 @@
         // https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
         const addrParams = new URLSearchParams(queryString);
 
-        // Check for duplicated params
-        const duplicatedParams =
-            new Set(addrParams.keys()).size !==
-            Array.from(addrParams.keys()).length;
+        const supportedParams = ['amount', 'op_return_raw', 'addr'];
 
-        if (duplicatedParams) {
-            // In this case, we can't pass any values back for supported params,
-            // without changing the shape of addressInfo
-            parsedAddressInput.queryString.error = `bip21 parameters may not appear more than once`;
-            return parsedAddressInput;
-        }
+        // Iterate over params to check for valid and/or invalid params
+        // Set a flag -- the first time we see the 'amount' param, it is for the bip21 starting address
+        let firstAmount = true;
+        // Set a flag -- per spec, nth outputs must be addr=<address> followed immediately by amount= for that address
+        let precedingParamAddr = false;
+
+        // Tempting to make additionalXecOutputs a map of address => amount
+        // However, we want to support the use case of multiple outputs of different amounts going to the same address
+        const additionalXecOutputs = [];
+        // Flag as any duplication of this param is off spec
+        let opReturnRawOccurred = false;
+        for (const [key, value] of addrParams) {
+            if (precedingParamAddr !== false) {
+                // If the preceding param was addr, then this has to be amount, otherwise off spec
+                if (key !== 'amount') {
+                    parsedAddressInput.parsedAdditionalXecOutputs.error = `No amount key for addr ${precedingParamAddr}`;
+                    return parsedAddressInput;
+                }
+                // Validate the amount
+                const isValidXecSendAmountOrErrorMsg = isValidXecSendAmount(
+                    value,
+                    balanceSats,
+                    userLocale,
+                );
+                if (isValidXecSendAmountOrErrorMsg !== true) {
+                    parsedAddressInput.parsedAdditionalXecOutputs.error = `Invalid amount ${value} for address ${precedingParamAddr}: ${isValidXecSendAmountOrErrorMsg}`;
+                    return parsedAddressInput;
+                } else {
+                    additionalXecOutputs.push([precedingParamAddr, value]);
+                }
 
-        const supportedParams = ['amount', 'op_return_raw'];
+                // Reset the flag
+                precedingParamAddr = false;
 
-        // Iterate over params to check for valid and/or invalid params
-        for (const paramKeyValue of addrParams) {
-            const paramKey = paramKeyValue[0];
-            if (!supportedParams.includes(paramKey)) {
+                // Go to the next param
+                continue;
+            }
+
+            if (!supportedParams.includes(key)) {
                 // queryString error
                 // Keep parsing for other params though
-                parsedAddressInput.queryString.error = `Unsupported param "${paramKey}"`;
+                parsedAddressInput.queryString.error = `Unsupported param "${key}"`;
+            }
+            if (key === 'addr') {
+                // nth output address param
+                // So, we will return a parsedAdditionalXecOutputs key
+                parsedAddressInput.parsedAdditionalXecOutputs = {
+                    value: null,
+                    error: false,
+                };
+
+                const nthAddress = value;
+                // address validation
+                // Note: for now, Cashtab only supports valid cash addresses for secondary outputs
+                // TODO support aliases
+                const isValidNthAddress = cashaddr.isValidCashAddress(
+                    nthAddress,
+                    'ecash',
+                );
+                if (!isValidNthAddress) {
+                    //
+                    // If your address is not a valid address and not a valid alias format
+                    parsedAddressInput.parsedAdditionalXecOutputs.error = `Invalid address "${nthAddress}"`;
+                    // We do not return a value for parsedAdditionalXecOutputs if there is a validation error
+                    return parsedAddressInput;
+                }
+                // set precedingParamAddr flag to the address
+                // In this way we can validate bip21 spec and set the amount for this address by the next param
+                precedingParamAddr = nthAddress;
             }
-            if (paramKey === 'amount') {
+            if (key === 'amount') {
+                if (!firstAmount) {
+                    // We should only get to this block for the first amount
+                    // If we get here otherwise, it means we are missing the corresponding 'addr' param for this amount
+                    // Set a query string error
+                    parsedAddressInput.queryString.error = `The amount param appears without a corresponding addr param`;
+                    // Do not return an amount value, since it is ambiguous
+                    parsedAddressInput.amount.value = null;
+                    parsedAddressInput.amount.error = `Duplicated amount param without matching address`;
+                    // Stop parsing
+                    return parsedAddressInput;
+                }
                 // Handle Cashtab-supported bip21 param 'amount'
-                const amount = paramKeyValue[1];
+                const amount = value;
                 parsedAddressInput.amount = { value: amount, error: false };
 
                 const validXecSendAmount = isValidXecSendAmount(
@@ -750,10 +812,25 @@
                     // If the result of isValidXecSendAmount is not true, it is an error msg explaining wy
                     parsedAddressInput.amount.error = validXecSendAmount;
                 }
+
+                if (firstAmount) {
+                    // Unset the firstAmount flag so that we correctly parse nth outputs
+                    firstAmount = false;
+                }
             }
-            if (paramKey === 'op_return_raw') {
+            if (key === 'op_return_raw') {
+                if (opReturnRawOccurred) {
+                    // Set a query string error
+                    parsedAddressInput.queryString.error = `The op_return_raw param may not appear more than once`;
+                    // Do not return an op_return_raw value, since it is ambiguous
+                    parsedAddressInput.op_return_raw.value = null;
+                    parsedAddressInput.op_return_raw.error = `Duplicated op_return_raw param`;
+                    // Stop parsing
+                    return parsedAddressInput;
+                }
+                opReturnRawOccurred = true;
                 // Handle Cashtab-supported bip21 param 'op_return_raw'
-                const opreturnParam = paramKeyValue[1];
+                const opreturnParam = value;
                 parsedAddressInput.op_return_raw = {
                     value: opreturnParam,
                     error: false,
@@ -765,6 +842,17 @@
                 }
             }
         }
+
+        // Catch a bip21 syntax error where the LAST param was addr
+        if (precedingParamAddr !== false) {
+            parsedAddressInput.parsedAdditionalXecOutputs.error = `No amount key for addr ${precedingParamAddr}`;
+            return parsedAddressInput;
+        }
+        if (additionalXecOutputs.length > 0) {
+            // If we have secondary outputs, include them
+            parsedAddressInput.parsedAdditionalXecOutputs.value =
+                additionalXecOutputs;
+        }
     }
 
     return parsedAddressInput;