diff --git a/packages/suite-desktop-core/e2e/tests/wallet/send-doge.test.ts b/packages/suite-desktop-core/e2e/tests/wallet/send-doge.test.ts index d951cfce484..26dce245dcd 100644 --- a/packages/suite-desktop-core/e2e/tests/wallet/send-doge.test.ts +++ b/packages/suite-desktop-core/e2e/tests/wallet/send-doge.test.ts @@ -54,6 +54,7 @@ test.describe('Doge Send', { tag: ['@group=wallet', '@snapshot'] }, () => { }); await test.step('Verify info on modals and confirm', async () => { + await trezorUserEnvLink.pressYes(); await expect(devicePrompt.outputValueOf('amount')).toContainText( `${localizeNumber(sendAmount)} DOGE`, ); diff --git a/packages/suite/src/components/suite/modals/ReduxModal/ConfirmValueModal/ConfirmValueModal.tsx b/packages/suite/src/components/suite/modals/ReduxModal/ConfirmValueModal/ConfirmValueModal.tsx index a76de4bf0e8..23064eead06 100644 --- a/packages/suite/src/components/suite/modals/ReduxModal/ConfirmValueModal/ConfirmValueModal.tsx +++ b/packages/suite/src/components/suite/modals/ReduxModal/ConfirmValueModal/ConfirmValueModal.tsx @@ -61,12 +61,12 @@ export const ConfirmValueModal = ({ const canConfirmOnDevice = !!(device?.connected && device?.available); const addressConfirmed = isConfirmed || !canConfirmOnDevice; const isCancelable = isActionAbortable || addressConfirmed; - const state = addressConfirmed ? 'done' : 'default'; + const state = addressConfirmed ? 'confirmed' : 'active'; const outputLines: OutputElementLine[] = [ { id: 'address', value, - type: 'address', + type: 'safe-address', }, ]; @@ -109,7 +109,7 @@ export const ConfirmValueModal = ({ heading={heading} description={description} onCancel={isCancelable ? onCancel : undefined} - size="large" + size="huge" > {!device?.connected && ( @@ -151,7 +151,6 @@ export const ConfirmValueModal = ({ isDisabled={!addressConfirmed} onClick={copy} data-testid={copyButtonDataTest} - isFullWidth > {copyButtonText} diff --git a/packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutput.tsx b/packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutput.tsx index d38f43eb8cf..e0a82b627bc 100644 --- a/packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutput.tsx +++ b/packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutput.tsx @@ -4,11 +4,11 @@ import { TranslationKey } from '@suite-common/intl-types'; import { NetworkSymbol, NetworkType, getNetworkDisplaySymbol } from '@suite-common/wallet-config'; import { BTC_LOCKTIME_VALUE } from '@suite-common/wallet-constants'; import { ReviewOutput, StakeType } from '@suite-common/wallet-types'; -import { isTestnet } from '@suite-common/wallet-utils'; +import { findAccountsByAddress, isTestnet } from '@suite-common/wallet-utils'; import { BigNumber } from '@trezor/utils/src/bigNumber'; import { Translation } from 'src/components/suite'; -import { useTranslation } from 'src/hooks/suite'; +import { useSelector, useTranslation } from 'src/hooks/suite'; import type { Account } from 'src/types/wallet'; import { @@ -116,7 +116,7 @@ const getOutputLines = ( return [ { id: type, - type: 'fee', + type: 'amount', value, }, ]; @@ -124,13 +124,13 @@ const getOutputLines = ( return [ { id: 'increase-fee-by', - type: 'fee', + type: 'amount', label: , value, }, { id: 'increased-fee', - type: 'fee', + type: 'amount', label: , value: value2, }, @@ -232,6 +232,7 @@ export const TransactionReviewOutput = ({ isRbf, }: TransactionReviewOutputProps) => { const { networkType, symbol } = account; + const accounts = useSelector(state => state.wallet.accounts); const { translationString } = useTranslation(); const isFiatVisible = ['fee', 'amount', 'gas', 'fee-replace', 'reduce-output'].includes(type) && @@ -247,7 +248,21 @@ export const TransactionReviewOutput = ({ symbol, stakeType, translationString, - ); + ).map(line => { + if (line.type === 'address') { + const relevantAccounts = findAccountsByAddress(symbol, line.value, accounts); + + return { + ...line, + type: + relevantAccounts.length > 0 + ? ('safe-address' as OutputElementLine['type']) + : line.type, + }; + } + + return line; + }); // prevents double label when bumping stake type txs if (type === 'address' && isRbf && stakeType) { diff --git a/packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutputElement.tsx b/packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutputElement.tsx index a62e9be8c9e..b0ecbc7e737 100644 --- a/packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutputElement.tsx +++ b/packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewOutputElement.tsx @@ -13,6 +13,7 @@ import { H4, Icon, InfoItem, + Note, Row, Text, } from '@trezor/components'; @@ -43,9 +44,9 @@ const DataWrapper = styled.p` const Status = ({ state }: { state: TransactionReviewOutputElementProps['state'] }) => { switch (state) { - case 'done': + case 'confirmed': return ; - case 'pending': + case 'unconfirmed': return ; default: return ; @@ -58,17 +59,24 @@ type ValueProps = { symbol: NetworkSymbol; isFiatVisible: boolean; isFee: boolean; + state: TransactionReviewOutputElementProps['state']; token?: TokenInfo; }; -const Value = ({ value, type, symbol, token, isFee, isFiatVisible }: ValueProps) => { +const Value = ({ value, type, symbol, token, isFee, isFiatVisible, state }: ValueProps) => { switch (type) { case 'address': + return state !== 'confirmed' ? ( + + + + ) : ( +
+ ); + case 'safe-address': return
; case 'data': return {value}; - case 'total': - case 'fee': case 'amount': { const isTokenAmount = !isFee && token; const formattedValue = isTokenAmount @@ -114,7 +122,7 @@ const Value = ({ value, type, symbol, token, isFee, isFiatVisible }: ValueProps) export type OutputElementLine = { id: string; value: string; - type: 'default' | 'address' | 'data' | 'amount' | 'fee' | 'total'; + type: 'default' | 'address' | 'safe-address' | 'data' | 'amount'; label?: ReactNode; }; @@ -122,7 +130,7 @@ export type TransactionReviewOutputElementProps = { title: ReactNode; lines: OutputElementLine[]; account: Account; - state: 'default' | 'done' | 'pending'; + state: 'active' | 'confirmed' | 'unconfirmed'; fiatVisible?: boolean; token?: TokenInfo; }; @@ -138,12 +146,12 @@ export const TransactionReviewOutputElement = ({ const { networkType, symbol } = account; return ( - +

{title}

@@ -166,15 +174,12 @@ export const TransactionReviewOutputElement = ({ token={token} isFiatVisible={fiatVisible} isFee={line.id === 'fee'} + state={state} /> ); return ( - + {line.label ? ( { if (hasSignedTx || index < buttonRequestsCount - 1) { - return 'done'; + return 'confirmed'; } if (index === buttonRequestsCount - 1) { - return 'default'; + return 'active'; } - return 'pending'; + return 'unconfirmed'; }; const Wrapper = styled.div` @@ -72,8 +74,15 @@ export const TransactionReviewOutputList = ({ }: TransactionReviewOutputListProps) => { const outputRefs = useRef<(HTMLDivElement | null)[]>([]); const totalOutputRef = useRef(null); - const { networkType } = account; + const accounts = useSelector(state => state.wallet.accounts); + const { networkType, symbol } = account; const isMultirecipient = outputs.filter(({ type }) => type === 'address').length > 1; + const isFirstOutputAddress = outputs[0].type === 'address'; + const isFirstStep = buttonRequestsCount === 1; + const isNotStaking = !stakeType; + const isInternalTransfer = + isFirstOutputAddress && + findAccountsByAddress(symbol, outputs[0].value, accounts).length > 0; const summaryIndex = outputs.findIndex( ({ type }) => !['address', 'amount', 'opreturn'].includes(type), @@ -88,6 +97,47 @@ export const TransactionReviewOutputList = ({ } }, [buttonRequestsCount, outputs.length, signedTx]); + if (isFirstOutputAddress && isFirstStep && isNotStaking && !isInternalTransfer) { + return ( + + +

+ +

+ + + + + } + /> + + + + } + /> + + + + } + /> + +
+
+ ); + } + return ( {outputs.map((output, index) => { diff --git a/packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewTotalOutput.tsx b/packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewTotalOutput.tsx index eb52948d726..66cf5b618f4 100644 --- a/packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewTotalOutput.tsx +++ b/packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewOutputList/TransactionReviewTotalOutput.tsx @@ -6,7 +6,6 @@ import { getIsUpdatedSendFlow, isTestnet, } from '@suite-common/wallet-utils'; -import { BulletListItemState } from '@trezor/components'; import { BigNumber } from '@trezor/utils/src/bigNumber'; import { Translation } from 'src/components/suite/Translation'; @@ -16,6 +15,7 @@ import { TrezorDevice } from 'src/types/suite'; import { OutputElementLine, TransactionReviewOutputElement, + TransactionReviewOutputElementProps, } from './TransactionReviewOutputElement'; const getLines = ( @@ -59,7 +59,7 @@ const getLines = ( id: 'fee', label: , value: precomposedTx.fee, - type: 'fee', + type: 'amount', }; return isUnknownStakingClaimValue ? [feeLine] : [amountLine, feeLine]; @@ -72,13 +72,13 @@ const getLines = ( id: 'total', label: , value: tokenInfo ? precomposedTx.totalSpent : amount, - type: 'total', + type: 'amount', }, { id: 'fee', label: , value: precomposedTx.fee, - type: 'fee', + type: 'amount', }, ]; } @@ -88,13 +88,13 @@ const getLines = ( id: 'total', label: , value: precomposedTx.totalSpent, - type: 'total', + type: 'amount', }, ]; }; export type TransactionReviewTotalOutputProps = { - state: BulletListItemState; + state: TransactionReviewOutputElementProps['state']; precomposedTx: GeneralPrecomposedTransactionFinal; account: Account; isRbf: boolean; diff --git a/packages/suite/src/support/messages.ts b/packages/suite/src/support/messages.ts index c160ca3280e..abcdb44e004 100644 --- a/packages/suite/src/support/messages.ts +++ b/packages/suite/src/support/messages.ts @@ -8535,6 +8535,27 @@ export default defineMessages({ defaultMessage: '{index, selectordinal, one {#st} two {#nd} few {#rd} other {#th} } Recipient', }, + TR_SEND_ADDRESS_CONFIRMATION_HEADING: { + id: 'TR_SEND_ADDRESS_CONFIRMATION_HEADING', + defaultMessage: 'Verify the address to avoid risking your funds', + }, + TR_SEND_ADDRESS_CONFIRMATION_ITEM_1_HEADING: { + id: 'TR_SEND_ADDRESS_CONFIRMATION_ITEM_1_HEADING', + defaultMessage: 'Go to the app or place where you originally got the address.', + }, + TR_SEND_ADDRESS_CONFIRMATION_ITEM_2_HEADING: { + id: 'TR_SEND_ADDRESS_CONFIRMATION_ITEM_2_HEADING', + defaultMessage: 'Compare that address with what is on your Trezor.', + }, + TR_SEND_ADDRESS_CONFIRMATION_ITEM_3_HEADING: { + id: 'TR_SEND_ADDRESS_CONFIRMATION_ITEM_3_HEADING', + defaultMessage: 'Confirm on Trezor if they match exactly.', + }, + TR_SEND_ADDRESS_CONFIRMATION_NOTE: { + id: 'TR_SEND_ADDRESS_CONFIRMATION_NOTE', + defaultMessage: + 'Verify that the original address matches the address on your Trezor exactly.', + }, TR_DISCOVERY_NEW_COINS: { id: 'TR_DISCOVERY_NEW_COINS', defaultMessage: 'Activate coins',