diff --git a/src/app/common/validation/forms/recipient-validators.ts b/src/app/common/validation/forms/recipient-validators.ts
index 0f24ddb3..d8f04b0e 100644
--- a/src/app/common/validation/forms/recipient-validators.ts
+++ b/src/app/common/validation/forms/recipient-validators.ts
@@ -1,13 +1,8 @@
-import * as yup from 'yup';
-
import { NetworkConfiguration } from '@shared/constants';
import { FormErrorMessages } from '@app/common/error-messages';
-import { fetchBtcNameOwner } from '@app/query/stacks/bns/bns.utils';
-import { StacksClient } from '@app/query/stacks/stacks-client';
import {
- btcAddressValidator,
notCurrentAddressValidator,
stxAddressNetworkValidator,
stxAddressValidator,
@@ -21,23 +16,3 @@ export function stxRecipientValidator(
.concat(stxAddressNetworkValidator(currentNetwork))
.concat(notCurrentAddressValidator(currentAddress || ''));
}
-
-export function btcRecipientAddressOrBnsNameValidator({ client }: { client: StacksClient }) {
- return yup.string().test({
- name: 'btcRecipientOrBnsName',
- message: FormErrorMessages.InvalidAddress,
- test: async value => {
- try {
- await btcAddressValidator().validate(value);
- return true;
- } catch (e) {}
- try {
- const btcAddress = await fetchBtcNameOwner(client, value ?? '');
- await btcAddressValidator().validate(btcAddress);
- return true;
- } catch (error) {
- return false;
- }
- },
- });
-}
diff --git a/src/app/components/fees-row/components/fee-estimate-select.layout.tsx b/src/app/components/fees-row/components/fee-estimate-select.layout.tsx
index c38f8d82..50239dcc 100644
--- a/src/app/components/fees-row/components/fee-estimate-select.layout.tsx
+++ b/src/app/components/fees-row/components/fee-estimate-select.layout.tsx
@@ -50,7 +50,7 @@ export function FeeEstimateSelectLayout(props: FeeEstimateSelectLayoutProps) {
position="absolute"
ref={ref}
style={styles}
- top="-100px"
+ top="-35px"
zIndex={9999}
>
{children}
diff --git a/src/app/pages/send/ordinal-inscription/components/collectible-asset.tsx b/src/app/pages/send/ordinal-inscription/components/collectible-asset.tsx
index 62f6c032..c6e33714 100644
--- a/src/app/pages/send/ordinal-inscription/components/collectible-asset.tsx
+++ b/src/app/pages/send/ordinal-inscription/components/collectible-asset.tsx
@@ -10,14 +10,25 @@ interface CollectibleAssetProps {
}
export function CollectibleAsset({ icon, name, symbol }: CollectibleAssetProps) {
return (
-
-
- {icon}
-
- {name}
-
- {symbol && ({symbol.toUpperCase()})}
-
-
+
+
+
+ {icon}
+
+ {name}
+
+ {symbol && ({symbol.toUpperCase()})}
+
+
+
);
}
diff --git a/src/app/pages/send/ordinal-inscription/send-inscription-form.tsx b/src/app/pages/send/ordinal-inscription/send-inscription-form.tsx
index 747f7bc2..19c3bdfe 100644
--- a/src/app/pages/send/ordinal-inscription/send-inscription-form.tsx
+++ b/src/app/pages/send/ordinal-inscription/send-inscription-form.tsx
@@ -1,7 +1,7 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
-import { Box, Button } from '@stacks/ui';
+import { Box, Button, Flex } from '@stacks/ui';
import BigNumber from 'bignumber.js';
import { Form, Formik } from 'formik';
@@ -16,8 +16,6 @@ import { OrdinalIcon } from '@app/components/icons/ordinal-icon';
import { getNumberOfInscriptionOnUtxo } from '@app/query/bitcoin/ordinals/utils';
import { BtcSizeFeeEstimator } from '../../../common/transactions/bitcoin/fees/btc-size-fee-estimator';
-import { FormErrors } from '../send-crypto-asset-form/components/form-errors';
-import { FormFieldsLayout } from '../send-crypto-asset-form/components/form-fields.layout';
import { RecipientField } from '../send-crypto-asset-form/components/recipient-field';
import { CollectibleAsset } from './components/collectible-asset';
import { CollectiblePreviewCard } from './components/collectible-preview-card';
@@ -107,12 +105,15 @@ export function SendInscriptionForm() {
-
+
} name="Ordinal inscription" />
-
-
+
+
-
{currentError && (
{currentError}
@@ -120,6 +121,7 @@ export function SendInscriptionForm() {
)}
);
}
diff --git a/src/app/pages/send/send-crypto-asset-form/components/recipient-field.tsx b/src/app/pages/send/send-crypto-asset-form/components/recipient-field.tsx
index 0c6fda5b..4cdfb069 100644
--- a/src/app/pages/send/send-crypto-asset-form/components/recipient-field.tsx
+++ b/src/app/pages/send/send-crypto-asset-form/components/recipient-field.tsx
@@ -4,6 +4,7 @@ import { TextInputField } from './text-input-field';
interface RecipientFieldProps {
isDisabled?: boolean;
+ label?: string;
labelAction?: string;
name: string;
onBlur?(): void;
@@ -13,6 +14,7 @@ interface RecipientFieldProps {
}
export function RecipientField({
isDisabled,
+ label,
labelAction,
name,
onBlur,
@@ -24,6 +26,7 @@ export function RecipientField({
-
+
-
-
+ {address}
+
-
+
-
+
);
}
diff --git a/src/app/pages/send/send-crypto-asset-form/components/recipient-select-fields/hooks/use-recipient-bns-name.tsx b/src/app/pages/send/send-crypto-asset-form/components/recipient-select-fields/hooks/use-recipient-bns-name.tsx
new file mode 100644
index 00000000..f2d34629
--- /dev/null
+++ b/src/app/pages/send/send-crypto-asset-form/components/recipient-select-fields/hooks/use-recipient-bns-name.tsx
@@ -0,0 +1,47 @@
+import { useCallback, useState } from 'react';
+
+import { useFormikContext } from 'formik';
+
+import { logger } from '@shared/logger';
+import { BitcoinSendFormValues, StacksSendFormValues } from '@shared/models/form.model';
+
+import { FormErrorMessages } from '@app/common/error-messages';
+import { StacksClient } from '@app/query/stacks/stacks-client';
+import { useStacksClientUnanchored } from '@app/store/common/api-clients.hooks';
+import { useCurrentNetworkState } from '@app/store/networks/networks.hooks';
+
+// Handles validating the BNS name lookup
+export function useRecipientBnsName() {
+ const { setFieldError, setFieldValue, values } = useFormikContext<
+ BitcoinSendFormValues | StacksSendFormValues
+ >();
+ const [bnsAddress, setBnsAddress] = useState('');
+ const currentNetwork = useCurrentNetworkState();
+ const client = useStacksClientUnanchored();
+
+ const getBnsAddressAndValidate = useCallback(
+ async (
+ fetchFn: (client: StacksClient, name: string, isTestnet?: boolean) => Promise
+ ) => {
+ setBnsAddress('');
+ if (!values.recipientBnsName) return;
+
+ try {
+ const owner = await fetchFn(client, values.recipientBnsName, currentNetwork.isTestnet);
+ if (owner) {
+ setBnsAddress(owner);
+ setFieldError('recipient', undefined);
+ setFieldValue('recipient', owner);
+ } else {
+ setFieldError('recipientBnsName', FormErrorMessages.BnsAddressNotFound);
+ }
+ } catch (e) {
+ setFieldError('recipientBnsName', FormErrorMessages.BnsAddressNotFound);
+ logger.error('Error fetching bns address', e);
+ }
+ },
+ [client, currentNetwork.isTestnet, setFieldError, setFieldValue, values.recipientBnsName]
+ );
+
+ return { bnsAddress, getBnsAddressAndValidate, setBnsAddress };
+}
diff --git a/src/app/pages/send/send-crypto-asset-form/family/stacks/components/stacks-recipient-field/hooks/use-stacks-recipient-field.tsx b/src/app/pages/send/send-crypto-asset-form/components/recipient-select-fields/hooks/use-recipient-select-fields.tsx
similarity index 81%
rename from src/app/pages/send/send-crypto-asset-form/family/stacks/components/stacks-recipient-field/hooks/use-stacks-recipient-field.tsx
rename to src/app/pages/send/send-crypto-asset-form/components/recipient-select-fields/hooks/use-recipient-select-fields.tsx
index f121dd98..6b6032e0 100644
--- a/src/app/pages/send/send-crypto-asset-form/family/stacks/components/stacks-recipient-field/hooks/use-stacks-recipient-field.tsx
+++ b/src/app/pages/send/send-crypto-asset-form/components/recipient-select-fields/hooks/use-recipient-select-fields.tsx
@@ -3,19 +3,20 @@ import { useNavigate } from 'react-router-dom';
import { useFormikContext } from 'formik';
-import { StacksSendFormValues } from '@shared/models/form.model';
+import { BitcoinSendFormValues, StacksSendFormValues } from '@shared/models/form.model';
import { RouteUrls } from '@shared/route-urls';
import { RecipientFieldType } from '@app/pages/send/send-crypto-asset-form/components/recipient-select/recipient-select';
-import { useStacksRecipientBnsName } from './use-stacks-recipient-bns-name';
+import { useRecipientBnsName } from './use-recipient-bns-name';
-export function useStacksRecipientField() {
- const { setFieldError, setFieldTouched, setFieldValue } =
- useFormikContext();
+export function useRecipientSelectFields() {
+ const { setFieldError, setFieldTouched, setFieldValue } = useFormikContext<
+ BitcoinSendFormValues | StacksSendFormValues
+ >();
const [selectedRecipientField, setSelectedRecipientField] = useState(RecipientFieldType.Address);
const [isSelectVisible, setIsSelectVisible] = useState(false);
- const { setBnsAddress } = useStacksRecipientBnsName();
+ const { setBnsAddress } = useRecipientBnsName();
const navigate = useNavigate();
const onClickLabelAction = useCallback(() => {
diff --git a/src/app/pages/send/send-crypto-asset-form/family/stacks/components/stacks-recipient-field/components/recipient-field-address.tsx b/src/app/pages/send/send-crypto-asset-form/components/recipient-select-fields/recipient-field-address.tsx
similarity index 100%
rename from src/app/pages/send/send-crypto-asset-form/family/stacks/components/stacks-recipient-field/components/recipient-field-address.tsx
rename to src/app/pages/send/send-crypto-asset-form/components/recipient-select-fields/recipient-field-address.tsx
diff --git a/src/app/pages/send/send-crypto-asset-form/family/stacks/components/stacks-recipient-field/components/recipient-field-bns-name.tsx b/src/app/pages/send/send-crypto-asset-form/components/recipient-select-fields/recipient-field-bns-name.tsx
similarity index 67%
rename from src/app/pages/send/send-crypto-asset-form/family/stacks/components/stacks-recipient-field/components/recipient-field-bns-name.tsx
rename to src/app/pages/send/send-crypto-asset-form/components/recipient-select-fields/recipient-field-bns-name.tsx
index 768101e7..9df76ea5 100644
--- a/src/app/pages/send/send-crypto-asset-form/family/stacks/components/stacks-recipient-field/components/recipient-field-bns-name.tsx
+++ b/src/app/pages/send/send-crypto-asset-form/components/recipient-select-fields/recipient-field-bns-name.tsx
@@ -2,27 +2,32 @@ import { useEffect, useState } from 'react';
import { useFormikContext } from 'formik';
-import { StacksSendFormValues } from '@shared/models/form.model';
+import { BitcoinSendFormValues, StacksSendFormValues } from '@shared/models/form.model';
import { RecipientField } from '@app/pages/send/send-crypto-asset-form/components/recipient-field';
+import { StacksClient } from '@app/query/stacks/stacks-client';
-import { useStacksRecipientBnsName } from '../hooks/use-stacks-recipient-bns-name';
-import { RecipientBnsAddressDisplayer } from './recipient-bns-address-displayer';
+import { RecipientAddressDisplayer } from './components/recipient-address-displayer';
+import { useRecipientBnsName } from './hooks/use-recipient-bns-name';
interface RecipientFieldBnsNameProps {
+ fetchFn(client: StacksClient, name: string, isTestnet?: boolean): Promise;
isSelectVisible: boolean;
onClickLabelAction(): void;
selectedRecipientField: number;
topInputOverlay: JSX.Element;
}
export function RecipientFieldBnsName({
+ fetchFn,
isSelectVisible,
onClickLabelAction,
topInputOverlay,
}: RecipientFieldBnsNameProps) {
const [showBnsAddress, setShowBnsAddress] = useState(false);
- const { errors, setFieldError, values } = useFormikContext();
- const { bnsAddress, getBnsAddressAndValidate } = useStacksRecipientBnsName();
+ const { errors, setFieldError, values } = useFormikContext<
+ BitcoinSendFormValues | StacksSendFormValues
+ >();
+ const { bnsAddress, getBnsAddressAndValidate } = useRecipientBnsName();
// Apply the recipient field validation to the bns name field
// here so we don't need to validate the bns name on blur.
@@ -46,12 +51,12 @@ export function RecipientFieldBnsName({
isDisabled={isSelectVisible}
labelAction="Select account"
name="recipientBnsName"
- onBlur={getBnsAddressAndValidate}
+ onBlur={() => getBnsAddressAndValidate(fetchFn)}
onClickLabelAction={onClickLabelAction}
placeholder="Enter recipient BNS name"
topInputOverlay={topInputOverlay}
/>
- {showBnsAddress ? : null}
+ {showBnsAddress ? : null}
>
);
}
diff --git a/src/app/pages/send/send-crypto-asset-form/components/recipient-select/recipient-select-item.tsx b/src/app/pages/send/send-crypto-asset-form/components/recipient-select/components/recipient-select-item.tsx
similarity index 68%
rename from src/app/pages/send/send-crypto-asset-form/components/recipient-select/recipient-select-item.tsx
rename to src/app/pages/send/send-crypto-asset-form/components/recipient-select/components/recipient-select-item.tsx
index 20f8f774..78fb64c6 100644
--- a/src/app/pages/send/send-crypto-asset-form/components/recipient-select/recipient-select-item.tsx
+++ b/src/app/pages/send/send-crypto-asset-form/components/recipient-select/components/recipient-select-item.tsx
@@ -3,6 +3,7 @@ import { FiChevronDown } from 'react-icons/fi';
import { Box, Text, color } from '@stacks/ui';
const labels = ['Address', 'BNS Name'];
+const testLabels = ['address', 'bns-name'];
interface RecipientSelectItemProps {
index: number;
@@ -18,6 +19,7 @@ export function RecipientSelectItem(props: RecipientSelectItemProps) {
alignItems="center"
as="button"
bg={color('bg')}
+ data-testid={`recipient-select-field-${testLabels[index]}`}
display="flex"
height="32px"
mb="0px !important"
@@ -26,10 +28,16 @@ export function RecipientSelectItem(props: RecipientSelectItemProps) {
pl={isVisible ? 'tight' : 'unset'}
type="button"
>
-
+
{labels[index]}
- {isVisible ? <>> : }
+ {isVisible ? <>> : }
);
}
diff --git a/src/app/pages/send/send-crypto-asset-form/family/stacks/components/stacks-recipient-field/components/recipient-select-overlay.tsx b/src/app/pages/send/send-crypto-asset-form/components/recipient-select/components/recipient-select-overlay.tsx
similarity index 100%
rename from src/app/pages/send/send-crypto-asset-form/family/stacks/components/stacks-recipient-field/components/recipient-select-overlay.tsx
rename to src/app/pages/send/send-crypto-asset-form/components/recipient-select/components/recipient-select-overlay.tsx
diff --git a/src/app/pages/send/send-crypto-asset-form/components/recipient-select/recipient-select.layout.tsx b/src/app/pages/send/send-crypto-asset-form/components/recipient-select/components/recipient-select.layout.tsx
similarity index 100%
rename from src/app/pages/send/send-crypto-asset-form/components/recipient-select/recipient-select.layout.tsx
rename to src/app/pages/send/send-crypto-asset-form/components/recipient-select/components/recipient-select.layout.tsx
diff --git a/src/app/pages/send/send-crypto-asset-form/components/recipient-select/recipient-select.tsx b/src/app/pages/send/send-crypto-asset-form/components/recipient-select/recipient-select.tsx
index 4f627680..f312941d 100644
--- a/src/app/pages/send/send-crypto-asset-form/components/recipient-select/recipient-select.tsx
+++ b/src/app/pages/send/send-crypto-asset-form/components/recipient-select/recipient-select.tsx
@@ -1,5 +1,5 @@
-import { RecipientSelectItem } from './recipient-select-item';
-import { RecipientSelectLayout } from './recipient-select.layout';
+import { RecipientSelectItem } from './components/recipient-select-item';
+import { RecipientSelectLayout } from './components/recipient-select.layout';
export enum RecipientFieldType {
Address,
diff --git a/src/app/pages/send/send-crypto-asset-form/components/send-crypto-asset-form.layout.tsx b/src/app/pages/send/send-crypto-asset-form/components/send-crypto-asset-form.layout.tsx
index f684a39a..77bbd460 100644
--- a/src/app/pages/send/send-crypto-asset-form/components/send-crypto-asset-form.layout.tsx
+++ b/src/app/pages/send/send-crypto-asset-form/components/send-crypto-asset-form.layout.tsx
@@ -1,13 +1,23 @@
-import { Box } from '@stacks/ui';
+import { Flex } from '@stacks/ui';
import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors';
interface SendCryptoAssetFormLayoutProps {
- children: JSX.Element;
+ children: React.ReactNode;
}
export function SendCryptoAssetFormLayout({ children }: SendCryptoAssetFormLayoutProps) {
return (
-
+
{children}
-
+
);
}
diff --git a/src/app/pages/send/send-crypto-asset-form/components/text-input-field.tsx b/src/app/pages/send/send-crypto-asset-form/components/text-input-field.tsx
index 53071bd4..02488b09 100644
--- a/src/app/pages/send/send-crypto-asset-form/components/text-input-field.tsx
+++ b/src/app/pages/send/send-crypto-asset-form/components/text-input-field.tsx
@@ -88,6 +88,7 @@ export function TextInputField({
as="button"
color={color('accent')}
fontSize={1}
+ fontWeight={500}
onClick={onClickLabelAction}
type="button"
zIndex={999}
diff --git a/src/app/pages/send/send-crypto-asset-form/family/bitcoin/components/bitcoin-recipient-field.tsx b/src/app/pages/send/send-crypto-asset-form/family/bitcoin/components/bitcoin-recipient-field.tsx
new file mode 100644
index 00000000..9704d2ff
--- /dev/null
+++ b/src/app/pages/send/send-crypto-asset-form/family/bitcoin/components/bitcoin-recipient-field.tsx
@@ -0,0 +1,52 @@
+import { RecipientFieldType } from '@app/pages/send/send-crypto-asset-form/components/recipient-select/recipient-select';
+import { fetchBtcNameOwner } from '@app/query/stacks/bns/bns.utils';
+
+import { useRecipientSelectFields } from '../../../components/recipient-select-fields/hooks/use-recipient-select-fields';
+import { RecipientFieldAddress } from '../../../components/recipient-select-fields/recipient-field-address';
+import { RecipientFieldBnsName } from '../../../components/recipient-select-fields/recipient-field-bns-name';
+import { RecipientSelectOverlay } from '../../../components/recipient-select/components/recipient-select-overlay';
+
+export function BitcoinRecipientField() {
+ const {
+ isSelectVisible,
+ onClickLabelAction,
+ onSelectRecipientFieldType,
+ onSetIsSelectVisible,
+ selectedRecipientField,
+ } = useRecipientSelectFields();
+
+ const topInputOverlay = (
+
+ );
+
+ const recipientFieldAddress = (
+
+ );
+
+ switch (selectedRecipientField) {
+ case RecipientFieldType.Address:
+ return recipientFieldAddress;
+ case RecipientFieldType.BnsName:
+ return (
+
+ );
+ default:
+ return recipientFieldAddress;
+ }
+}
diff --git a/src/app/pages/send/send-crypto-asset-form/form/btc/components/testnet-btc-message.tsx b/src/app/pages/send/send-crypto-asset-form/family/bitcoin/components/testnet-btc-message.tsx
similarity index 91%
rename from src/app/pages/send/send-crypto-asset-form/form/btc/components/testnet-btc-message.tsx
rename to src/app/pages/send/send-crypto-asset-form/family/bitcoin/components/testnet-btc-message.tsx
index b78d4db1..71cac9dd 100644
--- a/src/app/pages/send/send-crypto-asset-form/form/btc/components/testnet-btc-message.tsx
+++ b/src/app/pages/send/send-crypto-asset-form/family/bitcoin/components/testnet-btc-message.tsx
@@ -5,7 +5,7 @@ import { WarningLabel } from '@app/components/warning-label';
export function TestnetBtcMessage() {
return (
-
+
This is a Bitcoin testnet transaction. Funds have no value.{' '}
{
- void analytics.track('copy_resolved_address_to_clipboard');
- onCopy();
- }, [analytics, onCopy]);
-
- const onHover = useCallback(
- () => analytics.track('view_resolved_recipient_address'),
- [analytics]
- );
-
- return (
-
-
- {truncateMiddle(bnsAddress, 4)}
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/app/pages/send/send-crypto-asset-form/family/stacks/components/stacks-recipient-field/hooks/use-stacks-recipient-bns-name.tsx b/src/app/pages/send/send-crypto-asset-form/family/stacks/components/stacks-recipient-field/hooks/use-stacks-recipient-bns-name.tsx
deleted file mode 100644
index a60c5280..00000000
--- a/src/app/pages/send/send-crypto-asset-form/family/stacks/components/stacks-recipient-field/hooks/use-stacks-recipient-bns-name.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { useCallback, useState } from 'react';
-
-import { useFormikContext } from 'formik';
-
-import { logger } from '@shared/logger';
-import { StacksSendFormValues } from '@shared/models/form.model';
-
-import { FormErrorMessages } from '@app/common/error-messages';
-import { fetchNameOwner } from '@app/query/stacks/bns/bns.utils';
-import { useStacksClientUnanchored } from '@app/store/common/api-clients.hooks';
-import { useCurrentNetworkState } from '@app/store/networks/networks.hooks';
-
-// Handles validating the BNS name lookup
-export function useStacksRecipientBnsName() {
- const { setFieldError, setFieldValue, values } = useFormikContext();
- const [bnsAddress, setBnsAddress] = useState('');
- const currentNetwork = useCurrentNetworkState();
- const client = useStacksClientUnanchored();
-
- const getBnsAddressAndValidate = useCallback(async () => {
- setBnsAddress('');
- if (!values.recipientBnsName) return;
-
- try {
- const owner = await fetchNameOwner(client, values.recipientBnsName, currentNetwork.isTestnet);
- if (owner) {
- setBnsAddress(owner);
- setFieldError('recipient', undefined);
- setFieldValue('recipient', owner);
- } else {
- setFieldError('recipientBnsName', FormErrorMessages.BnsAddressNotFound);
- }
- } catch (e) {
- setFieldError('recipientBnsName', FormErrorMessages.BnsAddressNotFound);
- logger.error('Error fetching bns address', e);
- }
- }, [client, currentNetwork.isTestnet, setFieldError, setFieldValue, values.recipientBnsName]);
-
- return { bnsAddress, getBnsAddressAndValidate, setBnsAddress };
-}
diff --git a/src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/use-stacks-ft-params.ts b/src/app/pages/send/send-crypto-asset-form/family/stacks/hooks/use-stacks-ft-params.ts
similarity index 100%
rename from src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/use-stacks-ft-params.ts
rename to src/app/pages/send/send-crypto-asset-form/family/stacks/hooks/use-stacks-ft-params.ts
diff --git a/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form.tsx
index 92344314..bb860c0d 100644
--- a/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form.tsx
+++ b/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form.tsx
@@ -12,19 +12,15 @@ import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/marke
import { useCurrentBtcNativeSegwitAccountAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
import { AmountField } from '../../components/amount-field';
-import { AvailableBalance } from '../../components/available-balance';
-import { FormErrors } from '../../components/form-errors';
-import { FormFieldsLayout } from '../../components/form-fields.layout';
-import { PreviewButton } from '../../components/preview-button';
+import { FormFooter } from '../../components/form-footer';
import { SelectedAssetField } from '../../components/selected-asset-field';
import { SendCryptoAssetFormLayout } from '../../components/send-crypto-asset-form.layout';
import { SendFiatValue } from '../../components/send-fiat-value';
import { SendMaxButton } from '../../components/send-max-button';
-import { useCalculateMaxBitcoinSpend } from '../../family/bitcoin/hooks/use-calculate-max-spend';
+import { BitcoinRecipientField } from '../../family/bitcoin/components/bitcoin-recipient-field';
+import { TestnetBtcMessage } from '../../family/bitcoin/components/testnet-btc-message';
import { useSendFormRouteState } from '../../hooks/use-send-form-route-state';
import { createDefaultInitialFormValues, defaultSendFormFormikProps } from '../../send-form.utils';
-import { BtcRecipientField } from './components/btc-recipient-field';
-import { TestnetBtcMessage } from './components/testnet-btc-message';
import { useBtcSendForm } from './use-btc-send-form';
export function BtcSendForm() {
@@ -34,17 +30,21 @@ export function BtcSendForm() {
const currentAccountBtcAddress = useCurrentBtcNativeSegwitAccountAddressIndexZero();
const btcBalance = useNativeSegwitBalance(currentAccountBtcAddress);
- const calcMaxSpend = useCalculateMaxBitcoinSpend();
-
- const { validationSchema, currentNetwork, formRef, previewTransaction, onFormStateChange } =
- useBtcSendForm();
+ const {
+ calcMaxSpend,
+ currentNetwork,
+ formRef,
+ onFormStateChange,
+ previewTransaction,
+ validationSchema,
+ } = useBtcSendForm();
return (
-
+
- }
- bottomInputOverlay={
-
- }
- autoComplete="off"
- />
-
+
+
+ }
+ bottomInputOverlay={
+
+ }
+ autoComplete="off"
+ />
} name={btcBalance.asset.name} symbol="BTC" />
- {/* TODO: Implement new recipient field here */}
-
-
- {currentNetwork.chain.bitcoin.network === 'testnet' && }
-
-
-
-
-
+
+ {currentNetwork.chain.bitcoin.network === 'testnet' && }
+
+
);
}}
-
+
);
}
diff --git a/src/app/pages/send/send-crypto-asset-form/form/btc/components/btc-recipient-field.tsx b/src/app/pages/send/send-crypto-asset-form/form/btc/components/btc-recipient-field.tsx
deleted file mode 100644
index 3d232e0b..00000000
--- a/src/app/pages/send/send-crypto-asset-form/form/btc/components/btc-recipient-field.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { useCallback, useState } from 'react';
-import { useNavigate } from 'react-router-dom';
-
-import { useField } from 'formik';
-
-import { RouteUrls } from '@shared/route-urls';
-
-import { fetchBtcNameOwner } from '@app/query/stacks/bns/bns.utils';
-import { useStacksClientUnanchored } from '@app/store/common/api-clients.hooks';
-
-import { RecipientField } from '../../../components/recipient-field';
-import { RecipientFieldBnsAddress } from '../../../family/stacks/components/stacks-recipient-field/components/recipient-bns-address';
-
-export function BtcRecipientField() {
- const client = useStacksClientUnanchored();
- const [recipientAddressOrBnsNameField] = useField('recipientAddressOrBnsName');
- const [, _, recipientFieldHelpers] = useField('recipient');
- const navigate = useNavigate();
- const [bnsAddress, setBnsAddress] = useState('');
- const [lastValidatedInput, setLastValidatedInput] = useState('');
-
- const getBtcAddressFromBns = useCallback(async () => {
- // Skip if this input was already handled
- if (lastValidatedInput === recipientAddressOrBnsNameField.value) return;
-
- setBnsAddress('');
- setLastValidatedInput(recipientAddressOrBnsNameField.value);
- try {
- const btcFromBns = await fetchBtcNameOwner(client, recipientAddressOrBnsNameField.value);
- if (btcFromBns) {
- recipientFieldHelpers.setValue(btcFromBns);
- setBnsAddress(btcFromBns);
- } else {
- recipientFieldHelpers.setValue(recipientAddressOrBnsNameField.value);
- }
- } catch (error) {
- recipientFieldHelpers.setValue(recipientAddressOrBnsNameField.value);
- }
- }, [
- client,
- recipientAddressOrBnsNameField,
- recipientFieldHelpers,
- lastValidatedInput,
- setLastValidatedInput,
- ]);
-
- const onClickLabel = () => {
- setBnsAddress('');
- navigate(RouteUrls.SendCryptoAssetFormRecipientAccounts);
- };
-
- return (
- : undefined
- }
- />
- );
-}
diff --git a/src/app/pages/send/send-crypto-asset-form/form/btc/use-btc-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/btc/use-btc-send-form.tsx
index 0512f113..94084817 100644
--- a/src/app/pages/send/send-crypto-asset-form/form/btc/use-btc-send-form.tsx
+++ b/src/app/pages/send/send-crypto-asset-form/form/btc/use-btc-send-form.tsx
@@ -21,20 +21,17 @@ import {
btcMinimumSpendValidator,
} from '@app/common/validation/forms/amount-validators';
import { btcAmountPrecisionValidator } from '@app/common/validation/forms/currency-validators';
-import { btcRecipientAddressOrBnsNameValidator } from '@app/common/validation/forms/recipient-validators';
import { useUpdatePersistedSendFormValues } from '@app/features/popup-send-form-restoration/use-update-persisted-send-form-values';
import { useNativeSegwitBalance } from '@app/query/bitcoin/balance/bitcoin-balances.query';
import { useCurrentBtcNativeSegwitAccountAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
-import { useStacksClientUnanchored } from '@app/store/common/api-clients.hooks';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
import { useCalculateMaxBitcoinSpend } from '../../family/bitcoin/hooks/use-calculate-max-spend';
+import { useGenerateSignedBitcoinTx } from '../../family/bitcoin/hooks/use-generate-bitcoin-tx';
import { useSendFormNavigate } from '../../hooks/use-send-form-navigate';
-import { useGenerateSignedBitcoinTx } from './use-generate-bitcoin-tx';
export function useBtcSendForm() {
const formRef = useRef>(null);
-
const currentNetwork = useCurrentNetwork();
const currentAccountBtcAddress = useCurrentBtcNativeSegwitAccountAddressIndexZero();
const btcCryptoCurrencyAssetBalance = useNativeSegwitBalance(currentAccountBtcAddress);
@@ -44,14 +41,12 @@ export function useBtcSendForm() {
const generateTx = useGenerateSignedBitcoinTx();
const calcMaxSpend = useCalculateMaxBitcoinSpend();
const { onFormStateChange } = useUpdatePersistedSendFormValues();
- const client = useStacksClientUnanchored();
return {
- formRef,
-
- onFormStateChange,
-
+ calcMaxSpend,
currentNetwork,
+ formRef,
+ onFormStateChange,
validationSchema: yup.object({
amount: yup
@@ -68,10 +63,6 @@ export function useBtcSendForm() {
})
)
.concat(btcMinimumSpendValidator()),
- // TODO: Implement new recipient field here
- recipientAddressOrBnsName: btcRecipientAddressOrBnsNameValidator({
- client,
- }),
recipient: yup
.string()
.concat(btcAddressValidator())
diff --git a/src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/stacks-sip10-fungible-token-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/stacks-sip10-fungible-token-send-form.tsx
index 8555742f..dadf0177 100644
--- a/src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/stacks-sip10-fungible-token-send-form.tsx
+++ b/src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/stacks-sip10-fungible-token-send-form.tsx
@@ -1,5 +1,6 @@
-import { Navigate, useNavigate } from 'react-router-dom';
+import { Navigate, Outlet, useNavigate } from 'react-router-dom';
+import { Box } from '@stacks/ui';
import { Form, Formik } from 'formik';
import { HIGH_FEE_WARNING_LEARN_MORE_URL_STX } from '@shared/constants';
@@ -10,16 +11,16 @@ import { StxAvatar } from '@app/components/crypto-assets/stacks/components/stx-a
import { EditNonceButton } from '@app/components/edit-nonce-button';
import { FeesRow } from '@app/components/fees-row/fees-row';
import { NonceSetter } from '@app/components/nonce-setter';
+import { HighFeeDrawer } from '@app/features/high-fee-drawer/high-fee-drawer';
import { useUpdatePersistedSendFormValues } from '@app/features/popup-send-form-restoration/use-update-persisted-send-form-values';
import { AmountField } from '../../components/amount-field';
-import { Footer } from '../../components/footer';
-import { FormFieldsLayout } from '../../components/form-fields.layout';
+import { FormFooter } from '../../components/form-footer';
import { MemoField } from '../../components/memo-field';
import { SelectedAssetField } from '../../components/selected-asset-field';
import { SendCryptoAssetFormLayout } from '../../components/send-crypto-asset-form.layout';
import { SendMaxButton } from '../../components/send-max-button';
-import { StacksRecipientField } from '../../family/stacks/components/stacks-recipient-field/stacks-recipient-field';
+import { StacksRecipientField } from '../../family/stacks/components/stacks-recipient-field';
import { defaultSendFormFormikProps } from '../../send-form.utils';
import { useSip10SendForm } from './use-sip10-send-form';
@@ -42,7 +43,7 @@ export function StacksSip10FungibleTokenSendForm({}) {
}
return (
-
+
await previewTransaction(values, formikHelpers)}
@@ -54,7 +55,7 @@ export function StacksSip10FungibleTokenSendForm({}) {
return (
+
+
+
);
}}
-
+
);
}
diff --git a/src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/use-sip10-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/use-sip10-send-form.tsx
index 16ede6f3..b79ac6fc 100644
--- a/src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/use-sip10-send-form.tsx
+++ b/src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/use-sip10-send-form.tsx
@@ -31,10 +31,10 @@ import {
useGenerateFtTokenTransferUnsignedTx,
} from '@app/store/transactions/token-transfer.hooks';
+import { useStacksFtRouteState } from '../../family/stacks/hooks/use-stacks-ft-params';
import { useSendFormNavigate } from '../../hooks/use-send-form-navigate';
import { useSendFormRouteState } from '../../hooks/use-send-form-route-state';
import { createDefaultInitialFormValues } from '../../send-form.utils';
-import { useStacksFtRouteState } from './use-stacks-ft-params';
export function useSip10SendForm() {
const [contractId, setContractId] = useState('');
diff --git a/src/app/pages/send/send-crypto-asset-form/form/stx/stx-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/stx/stx-send-form.tsx
index cc18e6b3..efe9ecb3 100644
--- a/src/app/pages/send/send-crypto-asset-form/form/stx/stx-send-form.tsx
+++ b/src/app/pages/send/send-crypto-asset-form/form/stx/stx-send-form.tsx
@@ -1,5 +1,6 @@
-import { useNavigate } from 'react-router-dom';
+import { Outlet, useNavigate } from 'react-router-dom';
+import { Box } from '@stacks/ui';
import { Form, Formik } from 'formik';
import { HIGH_FEE_WARNING_LEARN_MORE_URL_STX } from '@shared/constants';
@@ -9,17 +10,17 @@ import { StxAvatar } from '@app/components/crypto-assets/stacks/components/stx-a
import { EditNonceButton } from '@app/components/edit-nonce-button';
import { FeesRow } from '@app/components/fees-row/fees-row';
import { NonceSetter } from '@app/components/nonce-setter';
+import { HighFeeDrawer } from '@app/features/high-fee-drawer/high-fee-drawer';
import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks';
import { AmountField } from '../../components/amount-field';
-import { Footer } from '../../components/footer';
-import { FormFieldsLayout } from '../../components/form-fields.layout';
+import { FormFooter } from '../../components/form-footer';
import { MemoField } from '../../components/memo-field';
import { SelectedAssetField } from '../../components/selected-asset-field';
import { SendCryptoAssetFormLayout } from '../../components/send-crypto-asset-form.layout';
import { SendFiatValue } from '../../components/send-fiat-value';
import { SendMaxButton } from '../../components/send-max-button';
-import { StacksRecipientField } from '../../family/stacks/components/stacks-recipient-field/stacks-recipient-field';
+import { StacksRecipientField } from '../../family/stacks/components/stacks-recipient-field';
import { defaultSendFormFormikProps } from '../../send-form.utils';
import { useStxSendForm } from './use-stx-send-form';
@@ -38,7 +39,7 @@ export function StxSendForm() {
} = useStxSendForm();
return (
-
+
+
+
+
);
}}
-
+
);
}
diff --git a/src/shared/models/form.model.ts b/src/shared/models/form.model.ts
index b6ee3933..9cb26f66 100644
--- a/src/shared/models/form.model.ts
+++ b/src/shared/models/form.model.ts
@@ -5,6 +5,7 @@ export interface BitcoinSendFormValues {
feeType: string;
memo: string;
recipient: string;
+ recipientBnsName: string;
symbol: string;
}
diff --git a/tests/page-object-models/send.page.ts b/tests/page-object-models/send.page.ts
index 10a58a28..e7af7552 100644
--- a/tests/page-object-models/send.page.ts
+++ b/tests/page-object-models/send.page.ts
@@ -1,8 +1,6 @@
import { Locator, Page } from '@playwright/test';
import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors';
import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors';
-import { SharedComponentsSelectors } from '@tests/selectors/shared-component.selectors';
-import { createTestSelector } from '@tests/utils';
import { RouteUrls } from '@shared/route-urls';
@@ -16,13 +14,13 @@ export class SendPage {
readonly memoInput: Locator;
readonly previewSendTxButton: Locator;
readonly recipientChooseAccountButton: Locator;
+ readonly recipientSelectFieldAddress: Locator;
+ readonly recipientSelectFieldBnsName: Locator;
readonly recipientInput: Locator;
readonly recipientBnsAddressLabel: Locator;
- readonly recipientBnsAddressInfoIcon: Locator;
readonly sendMaxButton: Locator;
readonly feesRow: Locator;
readonly memoRow: Locator;
- readonly feesSelector: string = createTestSelector(SharedComponentsSelectors.FeeRow);
constructor(page: Page) {
this.page = page;
@@ -40,13 +38,16 @@ export class SendPage {
this.recipientChooseAccountButton = page.getByTestId(
SendCryptoAssetSelectors.RecipientChooseAccountButton
);
+ this.recipientSelectFieldAddress = this.page.getByTestId(
+ SendCryptoAssetSelectors.RecipientSelectFieldAddress
+ );
+ this.recipientSelectFieldBnsName = this.page.getByTestId(
+ SendCryptoAssetSelectors.RecipientSelectFieldBnsName
+ );
this.recipientInput = this.page.getByTestId(SendCryptoAssetSelectors.RecipientFieldInput);
this.recipientBnsAddressLabel = this.page.getByTestId(
SendCryptoAssetSelectors.RecipientBnsAddressLabel
);
- this.recipientBnsAddressInfoIcon = page.getByTestId(
- SendCryptoAssetSelectors.RecipientBnsAddressInfoIcon
- );
this.feesRow = page.getByTestId(SendCryptoAssetSelectors.ConfirmationDetailsFee);
this.memoRow = page.getByTestId(SendCryptoAssetSelectors.ConfirmationDetailsMemo);
@@ -70,8 +71,4 @@ export class SendPage {
await this.page.waitForURL('**' + `${RouteUrls.SendCryptoAsset}/stx`);
await this.page.getByTestId(SendCryptoAssetSelectors.SendForm).waitFor();
}
-
- async waitForFeesSelector() {
- await this.page.waitForSelector(this.feesSelector, { timeout: 30000 });
- }
}
diff --git a/tests/selectors/send.selectors.ts b/tests/selectors/send.selectors.ts
index 42d9f794..7f886ad2 100644
--- a/tests/selectors/send.selectors.ts
+++ b/tests/selectors/send.selectors.ts
@@ -13,9 +13,10 @@ export enum SendCryptoAssetSelectors {
MemoFieldInput = 'memo-field-input',
PreviewSendTxBtn = 'preview-send-tx-btn',
RecipientChooseAccountButton = 'recipient-choose-account-button',
+ RecipientSelectFieldAddress = 'recipient-select-field-address',
+ RecipientSelectFieldBnsName = 'recipient-select-field-bns-name',
RecipientFieldInput = 'recipient-field-input',
RecipientBnsAddressLabel = 'recipient-bns-address-label',
- RecipientBnsAddressInfoIcon = 'recipient-bns-address-info-icon',
RecipientBnsAddressCopyToClipboard = 'recipient-bns-address-copy-to-clipboard',
SendForm = 'send-form',
SendMaxBtn = 'send-max-btn',
diff --git a/tests/specs/send/send-btc.spec.ts b/tests/specs/send/send-btc.spec.ts
index c240f302..3df5a840 100644
--- a/tests/specs/send/send-btc.spec.ts
+++ b/tests/specs/send/send-btc.spec.ts
@@ -7,7 +7,6 @@ import { FormErrorMessages } from '@app/common/error-messages';
import { test } from '../../fixtures/fixtures';
test.describe('send btc', () => {
- // TODO: Don't run these if we disable bitcoin sending?
test.beforeEach(async ({ extensionId, globalPage, homePage, onboardingPage, sendPage }) => {
await globalPage.setupAndUseApiCalls(extensionId);
await onboardingPage.signInExistingUser();
diff --git a/tests/specs/send/send-stx.spec.ts b/tests/specs/send/send-stx.spec.ts
index 4707f255..270de429 100644
--- a/tests/specs/send/send-stx.spec.ts
+++ b/tests/specs/send/send-stx.spec.ts
@@ -31,26 +31,29 @@ test.describe('send stx', () => {
test('that recipient address matches bns name', async ({ page, sendPage }) => {
await sendPage.amountInput.fill('.0001');
+ await sendPage.recipientSelectFieldAddress.click();
+ await sendPage.recipientSelectFieldBnsName.click();
await sendPage.recipientInput.fill(TEST_BNS_NAME);
await sendPage.recipientInput.blur();
await sendPage.recipientBnsAddressLabel.waitFor();
- await sendPage.recipientBnsAddressInfoIcon.hover();
const bnsResolvedAddress = await page.getByText(TEST_BNS_RESOLVED_ADDRESS).innerText();
test.expect(bnsResolvedAddress).toBeTruthy();
});
test('that fee row defaults to middle fee estimation', async ({ page }) => {
+ await page.getByTestId(SharedComponentsSelectors.FeeToBePaidLabel).scrollIntoViewIfNeeded();
const feeToBePaid = await page
.getByTestId(SharedComponentsSelectors.FeeToBePaidLabel)
.innerText();
const fee = Number(feeToBePaid.split(' ')[0]);
// Using min/max fee caps
- const isMiddleFee = fee >= 0.003 && fee < 0.75;
+ const isMiddleFee = fee >= 0.003 && fee <= 0.75;
test.expect(isMiddleFee).toBeTruthy();
});
test('that low fee estimate can be selected', async ({ page }) => {
+ await page.getByTestId(SharedComponentsSelectors.FeeToBePaidLabel).scrollIntoViewIfNeeded();
await page.getByTestId(SharedComponentsSelectors.MiddleFeeEstimateItem).click();
await page.getByTestId(SharedComponentsSelectors.LowFeeEstimateItem).click();
const feeToBePaid = await page
@@ -58,7 +61,7 @@ test.describe('send stx', () => {
.innerText();
const fee = Number(feeToBePaid.split(' ')[0]);
// Using min/max fee caps
- const isLowFee = fee >= 0.0025 && fee < 0.5;
+ const isLowFee = fee >= 0.0025 && fee <= 0.5;
test.expect(isLowFee).toBeTruthy();
});
});
@@ -117,21 +120,22 @@ test.describe('send stx', () => {
});
test.describe('send form preview', () => {
- test('that it shows preview of tx details to be confirmed', async ({ sendPage }) => {
+ test('that it shows preview of tx details to be confirmed', async ({ page, sendPage }) => {
await sendPage.amountInput.fill('0.000001');
await sendPage.recipientInput.fill(TEST_ACCOUNT_2_STX_ADDRESS);
- await sendPage.waitForFeesSelector();
+ await page.getByTestId(SharedComponentsSelectors.FeeToBePaidLabel).scrollIntoViewIfNeeded();
await sendPage.previewSendTxButton.click();
const details = await sendPage.confirmationDetails.allInnerTexts();
test.expect(details).toBeTruthy();
});
test('that it shows preview of tx details after validation error is resolved', async ({
+ page,
sendPage,
}) => {
await sendPage.amountInput.fill('0.0000001');
await sendPage.recipientInput.fill(TEST_ACCOUNT_2_STX_ADDRESS);
- await sendPage.waitForFeesSelector();
+ await page.getByTestId(SharedComponentsSelectors.FeeToBePaidLabel).scrollIntoViewIfNeeded();
await sendPage.previewSendTxButton.click();
const errorMsg = await sendPage.amountInputErrorLabel.innerText();
test.expect(errorMsg).toEqual(FormErrorMessages.MustBePositive);
@@ -143,6 +147,7 @@ test.describe('send stx', () => {
});
test('that asset value, recipient, memo and fees on preview match input', async ({
+ page,
sendPage,
}) => {
const amount = '0.000001';
@@ -151,7 +156,7 @@ test.describe('send stx', () => {
await sendPage.amountInput.fill(amount);
await sendPage.recipientInput.fill(TEST_ACCOUNT_2_STX_ADDRESS);
await sendPage.memoInput.fill(memo);
- await sendPage.waitForFeesSelector();
+ await page.getByTestId(SharedComponentsSelectors.FeeToBePaidLabel).scrollIntoViewIfNeeded();
const fees = await sendPage.page
.getByTestId(SharedComponentsSelectors.FeeToBePaidLabel)
.innerText();
@@ -177,13 +182,13 @@ test.describe('send stx', () => {
test.expect(confirmationMemo).toEqual(memo);
});
- test('that empty memo on preview matches default empty value', async ({ sendPage }) => {
+ test('that empty memo on preview matches default empty value', async ({ page, sendPage }) => {
const amount = '0.000001';
const emptyMemoPreviewValue = 'No memo';
await sendPage.amountInput.fill(amount);
await sendPage.recipientInput.fill(TEST_ACCOUNT_2_STX_ADDRESS);
- await sendPage.waitForFeesSelector();
+ await page.getByTestId(SharedComponentsSelectors.FeeToBePaidLabel).scrollIntoViewIfNeeded();
await sendPage.previewSendTxButton.click();
const confirmationMemo = await sendPage.memoRow