Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(coinmarket): possibility to proceed with unverified address #14320

Merged
merged 1 commit into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/suite-data/files/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1700,6 +1700,7 @@
"TR_SHOW_MORE_ADDRESSES": "Show more ({count})",
"TR_SHOW_UNVERIFIED_ADDRESS": "Show unverified address",
"TR_SHOW_UNVERIFIED_XPUB": "Show unverified public key",
"TR_PROCEED_UNVERIFIED_ADDRESS": "Proceed with unverified address",
"TR_SIDEBAR_ADD_COIN": "Add a coin",
"TR_SIGN": "Sign",
"TR_SIGNATURE": "Signature",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,8 @@ export const VERIFY_BUY_ADDRESS_FIXTURES = [
action: {
type: MODAL.OPEN_USER_CONTEXT,
payload: {
type: 'unverified-address',
type: 'unverified-address-proceed',
value: XRP_ACCOUNT.descriptor,
addressPath: XRP_ACCOUNT.path,
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,8 @@ export const verifyAddress =
if (!connected || !available) {
dispatch(
modalActions.openModal({
type: 'unverified-address',
type: 'unverified-address-proceed',
value: address,
addressPath: path,
}),
);

Expand Down
2 changes: 1 addition & 1 deletion packages/suite/src/actions/wallet/coinmarketBuyActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export type CoinmarketBuyAction =
| { type: typeof COINMARKET_BUY.SET_IS_FROM_REDIRECT; isFromRedirect: boolean }
| { type: typeof COINMARKET_BUY.SAVE_TRANSACTION_DETAIL_ID; transactionId: string }
| { type: typeof COINMARKET_BUY.SAVE_QUOTE_REQUEST; request: BuyTradeQuoteRequest }
| { type: typeof COINMARKET_BUY.VERIFY_ADDRESS; addressVerified: string }
| { type: typeof COINMARKET_BUY.VERIFY_ADDRESS; addressVerified: string | undefined }
| {
type: typeof COINMARKET_BUY.SAVE_CACHED_ACCOUNT_INFO;
symbol: Account['symbol'];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React, { useCallback } from 'react';
import { useCallback } from 'react';

import { ModalProps } from '@trezor/components';
import { openAddressModal, showAddress } from 'src/actions/wallet/receiveActions';
import { ConfirmUnverifiedModal } from './ConfirmUnverifiedModal';

interface ConfirmUnverifiedAddressModalProps extends Required<Pick<ModalProps, 'onCancel'>> {
interface ConfirmUnverifiedAddressModalProps {
addressPath: string;
value: string;
}
Expand All @@ -13,15 +12,17 @@ export const ConfirmUnverifiedAddressModal = ({
addressPath,
value,
}: ConfirmUnverifiedAddressModalProps) => {
const verifyAddress = useCallback(() => showAddress(addressPath, value), [addressPath, value]);
const verifyProcess = useCallback(() => showAddress(addressPath, value), [addressPath, value]);
const showUnverifiedAddress = () => openAddressModal({ addressPath, value });

return (
<ConfirmUnverifiedModal
showUnverifiedButtonText="TR_SHOW_UNVERIFIED_ADDRESS"
action={{
event: showUnverifiedAddress,
title: 'TR_SHOW_UNVERIFIED_ADDRESS',
}}
verifyProcess={verifyProcess}
warningText="TR_ADDRESS_PHISHING_WARNING"
verify={verifyAddress}
showUnverified={showUnverifiedAddress}
/>
);
};
Original file line number Diff line number Diff line change
@@ -1,101 +1,100 @@
import { useEffect } from 'react';
import styled from 'styled-components';

import { applySettings } from 'src/actions/settings/deviceSettingsActions';
import { Translation, Modal } from 'src/components/suite';
import { Translation } from 'src/components/suite';
import { TranslationKey } from 'src/components/suite/Translation';
import { useDevice, useDispatch, useSelector } from 'src/hooks/suite';
import { ThunkAction } from 'src/types/suite';
import { Button } from '@trezor/components';
import { Dispatch, GetState } from 'src/types/suite';
import { Button, H3, NewModal, Paragraph } from '@trezor/components';
import { onCancel } from 'src/actions/suite/modalActions';
import { selectDeviceLabelOrName } from '@suite-common/wallet-core';

const StyledModal = styled(Modal)`
width: 520px;
`;

// eslint-disable-next-line local-rules/no-override-ds-component
const StyledButton = styled(Button)`
flex-grow: 1;
`;
import { applySettings } from 'src/actions/settings/deviceSettingsActions';
import { useEffect } from 'react';

interface ConfirmUnverifiedModalProps {
showUnverifiedButtonText: TranslationKey;
showUnverified: () => ThunkAction;
verify: () => ThunkAction;
action: {
event: () => (dispatch: Dispatch) => void;
title: TranslationKey;
closeAfterEventTriggered?: boolean;
};
verifyProcess?: () => (dispatch: Dispatch, getState: GetState) => Promise<void>;
warningText: TranslationKey;
}

export const ConfirmUnverifiedModal = ({
showUnverifiedButtonText,
showUnverified,
verify,
action,
warningText,
verifyProcess,
}: ConfirmUnverifiedModalProps) => {
const deviceLabel = useSelector(selectDeviceLabelOrName);
const { device } = useDevice();
const dispatch = useDispatch();
const { device, isLocked } = useDevice();

// Device connected while the modal is open -> switch to verification modal.
useEffect(() => {
if (device?.connected) {
dispatch(verify());
}
}, [device?.connected, dispatch, verify]);

// just to make TS happy
if (!device) return null;
const { isLocked } = useDevice();

const isDeviceLocked = isLocked();
const isPassphraseRequired = device.connected && !device.available;
const isPassphraseRequired = device?.connected && !device.available;
const deviceStatus = isPassphraseRequired
? 'TR_DEVICE_LABEL_IS_UNAVAILABLE'
: 'TR_DEVICE_LABEL_IS_NOT_CONNECTED';
const description = isPassphraseRequired
? 'TR_PLEASE_ENABLE_PASSPHRASE'
: 'TR_PLEASE_CONNECT_YOUR_DEVICE';

const handleClose = () => dispatch(onCancel());
const handleEvent = () => {
dispatch(action.event());

if (action.closeAfterEventTriggered) {
handleClose();
}
};

const enablePassphraseAndContinue = async () => {
if (!device.available) {
if (!device?.available) {
const result = await dispatch(applySettings({ use_passphrase: true }));
if (!result || !result.success) return;
}
dispatch(verify());
};
const continueUnverified = () => dispatch(showUnverified());
const close = () => dispatch(onCancel());

// Device connected while the modal is open -> switch to verification modal.
useEffect(() => {
if (device?.connected && verifyProcess) {
dispatch(verifyProcess());
}
}, [device?.connected, dispatch, verifyProcess]);

return (
<StyledModal
heading={<Translation id={deviceStatus} values={{ deviceLabel }} />}
isCancelable
onCancel={close}
description={
<Translation
id={warningText}
values={{ claim: <Translation id={description} /> }}
/>
}
bottomBarComponents={
<NewModal
variant="warning"
size="small"
icon="shieldWarning"
onCancel={handleClose}
bottomContent={
<>
<Button variant="warning" onClick={continueUnverified}>
<Translation id={showUnverifiedButtonText} />
<Button variant="warning" onClick={handleEvent}>
<Translation id={action.title} />
</Button>

{isPassphraseRequired && (
<StyledButton
<Button
variant="primary"
onClick={enablePassphraseAndContinue}
isDisabled={isDeviceLocked}
>
<Translation id="TR_ACCOUNT_ENABLE_PASSPHRASE" />
</StyledButton>
</Button>
)}
<Button onClick={close} variant="tertiary">
<Button onClick={handleClose} variant="tertiary">
<Translation id="TR_BACK" />
</Button>
</>
}
/>
>
<H3>
<Translation id={deviceStatus} values={{ deviceLabel }} />
</H3>
<Paragraph>
<Translation
id={warningText}
values={{ claim: <Translation id={description} /> }}
/>
</Paragraph>
</NewModal>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ConfirmUnverifiedModal } from './ConfirmUnverifiedModal';
import { COINMARKET_BUY } from 'src/actions/wallet/constants';
import { Dispatch } from 'src/types/suite';

interface ConfirmUnverifiedProceedModalProps {
value: string;
}

export const ConfirmUnverifiedProceedModal = ({ value }: ConfirmUnverifiedProceedModalProps) => {
const proceedWithUnverifiedAddress = () => (dispatch: Dispatch) => {
dispatch({
type: COINMARKET_BUY.VERIFY_ADDRESS,
addressVerified: value,
});
};

return (
<ConfirmUnverifiedModal
action={{
event: proceedWithUnverifiedAddress,
title: 'TR_PROCEED_UNVERIFIED_ADDRESS',
closeAfterEventTriggered: true,
}}
warningText="TR_ADDRESS_PHISHING_WARNING"
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useCallback } from 'react';

import { ConfirmUnverifiedModal } from './ConfirmUnverifiedModal';
import { openXpubModal, showXpub } from 'src/actions/wallet/publicKeyActions';

export const ConfirmUnverifiedXpubModal = () => {
const event = useCallback(() => openXpubModal(), []);
const verifyProcess = useCallback(() => showXpub(), []);

return (
<ConfirmUnverifiedModal
action={{
event,
title: 'TR_SHOW_UNVERIFIED_XPUB',
}}
verifyProcess={verifyProcess}
warningText="TR_XPUB_PHISHING_WARNING"
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ import {
CriticalCoinjoinPhaseModal,
CoinjoinSuccessModal,
MoreRoundsNeededModal,
ConfirmUnverifiedModal,
ConfirmUnverifiedAddressModal,
UnecoCoinjoinModal,
AuthenticateDeviceModal,
AuthenticateDeviceFailModal,
Expand All @@ -39,9 +37,11 @@ import {
ClaimModal,
CopyAddressModal,
UnhideTokenModal,
ConfirmUnverifiedAddressModal,
ConfirmUnverifiedXpubModal,
ConfirmUnverifiedProceedModal,
} from 'src/components/suite/modals';
import type { AcquiredDevice } from 'src/types/suite';
import { openXpubModal, showXpub } from 'src/actions/wallet/publicKeyActions';
import type { ReduxModalProps } from '../ReduxModal';
import { CryptoId } from 'invity-api';
import { EverstakeModal } from './UnstakeModal/EverstakeModal';
Expand Down Expand Up @@ -74,18 +74,12 @@ export const UserContextModal = ({
<ConfirmUnverifiedAddressModal
addressPath={payload.addressPath}
value={payload.value}
onCancel={onCancel}
/>
);
case 'unverified-xpub':
return (
<ConfirmUnverifiedModal
showUnverifiedButtonText="TR_SHOW_UNVERIFIED_XPUB"
warningText="TR_XPUB_PHISHING_WARNING"
verify={showXpub}
showUnverified={openXpubModal}
/>
);
return <ConfirmUnverifiedXpubModal />;
case 'unverified-address-proceed':
return <ConfirmUnverifiedProceedModal value={payload.value} />;
case 'address':
return <ConfirmAddressModal {...payload} onCancel={onCancel} />;
case 'xpub':
Expand Down
2 changes: 2 additions & 0 deletions packages/suite/src/components/suite/modals/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export { ImportTransactionModal } from './ReduxModal/UserContextModal/ImportTran
export { ConfirmEvmExplanationModal } from './ConfirmEvmExplanationModal';
export { ConfirmUnverifiedModal } from './ReduxModal/UserContextModal/ConfirmUnverifiedModal';
export { ConfirmUnverifiedAddressModal } from './ReduxModal/UserContextModal/ConfirmUnverifiedAddressModal';
export { ConfirmUnverifiedXpubModal } from './ReduxModal/UserContextModal/ConfirmUnverifiedXpubModal';
export { ConfirmUnverifiedProceedModal } from './ReduxModal/UserContextModal/ConfirmUnverifiedProceedModal';
export { AddAccountModal } from './ReduxModal/UserContextModal/AddAccountModal/AddAccountModal';
export { QrScannerModal } from './ReduxModal/UserContextModal/QrScannerModal';
export { BackgroundGalleryModal } from './ReduxModal/UserContextModal/BackgroundGalleryModal';
Expand Down
4 changes: 2 additions & 2 deletions packages/suite/src/reducers/wallet/coinmarketReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,14 @@ interface Buy extends CoinmarketTradeCommonProps {
symbol?: Account['symbol'];
shouldSubmit?: boolean;
};
addressVerified?: string;
addressVerified: string | undefined;
}

interface Exchange extends CoinmarketTradeCommonProps {
exchangeInfo?: ExchangeInfo;
quotesRequest?: ExchangeTradeQuoteRequest;
quotes: ExchangeTrade[] | undefined;
addressVerified?: string;
addressVerified: string | undefined;
coinmarketAccount?: Account;
selectedQuote: ExchangeTrade | undefined;
}
Expand Down
8 changes: 8 additions & 0 deletions packages/suite/src/support/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,10 @@ export default defineMessages({
id: 'TR_COINMARKET_SWAP_DEX_MODAL_TERMS_6',
dynamic: true,
},
TR_CONFIRM_ADDRESS: {
defaultMessage: 'Confirm address',
id: 'TR_CONFIRM_ADDRESS',
},
TR_EXCHANGE_STATUS_ERROR: {
defaultMessage: 'Rejected',
id: 'TR_EXCHANGE_STATUS_ERROR',
Expand Down Expand Up @@ -3291,6 +3295,10 @@ export default defineMessages({
defaultMessage: 'Show unverified public key',
id: 'TR_SHOW_UNVERIFIED_XPUB',
},
TR_PROCEED_UNVERIFIED_ADDRESS: {
defaultMessage: 'Proceed with unverified address',
id: 'TR_PROCEED_UNVERIFIED_ADDRESS',
},
TR_SIGN: {
defaultMessage: 'Sign',
description: 'Sign button in Sign and Verify form',
Expand Down
Loading
Loading