diff --git a/packages/blockchain-link-types/src/common.ts b/packages/blockchain-link-types/src/common.ts index 1c1a682ee2e5..b653f9016383 100644 --- a/packages/blockchain-link-types/src/common.ts +++ b/packages/blockchain-link-types/src/common.ts @@ -42,6 +42,10 @@ export interface TokenTransfer { from?: string; to?: string; standard?: 'ERC20' | 'ERC1155' | 'ERC721'; + multiTokenValues?: Array<{ + id: string; + value: string; + }>; } export interface InternalTransfer { diff --git a/packages/suite/src/components/suite/FormattedNftAmount.tsx b/packages/suite/src/components/suite/FormattedNftAmount.tsx new file mode 100644 index 000000000000..490026e90f80 --- /dev/null +++ b/packages/suite/src/components/suite/FormattedNftAmount.tsx @@ -0,0 +1,66 @@ +import { WalletAccountTransaction } from '@wallet-types/index'; +import React from 'react'; + +import styled from 'styled-components'; +import { SignValue } from '@suite-common/suite-types'; +import { HiddenPlaceholder } from './HiddenPlaceholder'; +import { Sign } from './Sign'; +import { TrezorLink } from '@suite-components'; +import { useSelector } from '@suite-hooks/useSelector'; + +const Container = styled.span` + max-width: 100%; + display: flex; +`; + +const Symbol = styled.span` + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`; + +const StyledTrezorLink = styled(TrezorLink)` + color: ${({ theme }) => theme.TYPE_GREEN}; + text-decoration: underline; +`; + +interface FormattedNftAmountProps { + transfer: WalletAccountTransaction['tokens'][number]; + signValue?: SignValue; + className?: string; + useLink?: boolean; +} + +export const FormattedNftAmount = ({ + transfer, + signValue, + className, + useLink, +}: FormattedNftAmountProps) => { + const id = // use 0 index, haven't found an example where multiTokenValues.length > 1 + transfer.standard === 'ERC1155' && transfer.multiTokenValues?.length + ? transfer.multiTokenValues[0].id + : transfer.amount; + + const { selectedAccount } = useSelector(state => state.wallet); + const { network } = selectedAccount; + const explorerUrl = `${network?.explorer.tx.replace('tx', 'nft')}/${transfer.address}/${id}`; + + return ( + + + {!!signValue && } + + ID  + + {useLink ? ( + {id} + ) : ( + {id} + )} + +  {transfer.symbol} + + + ); +}; diff --git a/packages/suite/src/components/suite/modals/TransactionDetail/components/AmountDetails.tsx b/packages/suite/src/components/suite/modals/TransactionDetail/components/AmountDetails.tsx index 61970ff6dab8..dc1832bd1baf 100644 --- a/packages/suite/src/components/suite/modals/TransactionDetail/components/AmountDetails.tsx +++ b/packages/suite/src/components/suite/modals/TransactionDetail/components/AmountDetails.tsx @@ -10,8 +10,10 @@ import { formatCardanoWithdrawal, formatNetworkAmount, getTxOperation, + isNftTokenTransfer, } from '@suite-common/wallet-utils'; import BigNumber from 'bignumber.js'; +import { FormattedNftAmount } from '@suite-components/FormattedNftAmount'; const MainContainer = styled.div` display: flex; @@ -212,11 +214,19 @@ export const AmountDetails = ({ tx, isTestnet }: AmountDetailsProps) => { ) : undefined } secondColumn={ - + isNftTokenTransfer(t) ? ( + + ) : ( + + ) } // no history rates available for tokens thirdColumn={null} diff --git a/packages/suite/src/components/suite/modals/TransactionDetail/components/IODetails.tsx b/packages/suite/src/components/suite/modals/TransactionDetail/components/IODetails.tsx index 47788b49ff4a..63a2ad6b39b0 100644 --- a/packages/suite/src/components/suite/modals/TransactionDetail/components/IODetails.tsx +++ b/packages/suite/src/components/suite/modals/TransactionDetail/components/IODetails.tsx @@ -1,8 +1,8 @@ -import React, { ReactElement } from 'react'; +import React, { ReactElement, ReactNode } from 'react'; import styled, { css } from 'styled-components'; import { WalletAccountTransaction } from '@suite-common/wallet-types'; -import { formatAmount, formatNetworkAmount } from '@suite-common/wallet-utils'; +import { formatAmount, formatNetworkAmount, isNftTokenTransfer } from '@suite-common/wallet-utils'; import { FormattedCryptoAmount, Translation } from '@suite-components'; import { useSelector } from '@suite-hooks/useSelector'; import { AnonymitySet, TokenTransfer } from '@trezor/blockchain-link'; @@ -10,6 +10,7 @@ import { Icon, useTheme, variables, CollapsibleBox } from '@trezor/components'; import { UtxoAnonymity } from '@wallet-components'; import { TxAddressOverflow } from './TxAddressOverflow'; import { AnalyzeInBlockbookBanner } from './AnalyzeInBlockbookBanner'; +import { FormattedNftAmount } from '@suite-components/FormattedNftAmount'; export const blurFix = css` margin-left: -10px; @@ -105,6 +106,12 @@ const StyledFormattedCryptoAmount = styled(FormattedCryptoAmount)` display: block; `; +const StyledFormattedNftAmount = styled(FormattedNftAmount)` + color: ${({ theme }) => theme.BG_GREEN}; + margin-top: 4px; + display: block; +`; + const GridGroup = styled.div` &:not(:last-of-type) { margin-bottom: 8px; @@ -189,17 +196,10 @@ interface GridRowGroupComponentProps { from?: string; to?: string; symbol: string; - formattedInValue?: string; - formattedOutValue?: string; + amount?: string | ReactNode; } -const GridRowGroupComponent = ({ - from, - to, - symbol, - formattedInValue, - formattedOutValue, -}: GridRowGroupComponentProps) => { +const GridRowGroupComponent = ({ from, to, symbol, amount }: GridRowGroupComponentProps) => { const theme = useTheme(); return ( @@ -207,11 +207,10 @@ const GridRowGroupComponent = ({
- {formattedInValue && ( - - )} - {formattedOutValue && ( - + {typeof amount === 'string' ? ( + + ) : ( + amount )}
@@ -261,7 +260,7 @@ const EthereumSpecificBalanceDetailsRow = ({ tx }: EthereumSpecificBalanceDetail key={index} from={t.from} to={t.to} - formattedInValue={formatNetworkAmount(t.amount, tx.symbol)} + amount={formatNetworkAmount(t.amount, tx.symbol)} symbol={tx.symbol} /> ))} @@ -285,7 +284,13 @@ const EthereumSpecificBalanceDetailsRow = ({ tx }: EthereumSpecificBalanceDetail key={index} from={t.from} to={t.to} - formattedInValue={formatAmount(t.amount, t.decimals)} + amount={ + isNftTokenTransfer(t) ? ( + + ) : ( + formatAmount(t.amount, t.decimals) + ) + } symbol={t.symbol} /> ))} @@ -303,6 +308,7 @@ interface BalanceDetailsRowProps { const BalanceDetailsRow = ({ tx }: BalanceDetailsRowProps) => { const vout = tx?.details?.vout[0]; const vin = tx?.details?.vin[0]; + const value = formatNetworkAmount(vin.value || vout.value || '', tx.symbol); return vout.addresses?.[0] && vin.addresses?.[0] ? ( @@ -310,8 +316,7 @@ const BalanceDetailsRow = ({ tx }: BalanceDetailsRowProps) => { diff --git a/packages/suite/src/components/wallet/TransactionItem/components/Target.tsx b/packages/suite/src/components/wallet/TransactionItem/components/Target.tsx index 5c2997d5be8d..cdf2c3967926 100644 --- a/packages/suite/src/components/wallet/TransactionItem/components/Target.tsx +++ b/packages/suite/src/components/wallet/TransactionItem/components/Target.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import styled from 'styled-components'; +import styled, { css } from 'styled-components'; import BigNumber from 'bignumber.js'; import { variables } from '@trezor/components'; import { getIsZeroValuePhishing } from '@suite-common/suite-utils'; @@ -19,6 +19,7 @@ import { formatCardanoWithdrawal, formatCardanoDeposit, formatNetworkAmount, + isNftTokenTransfer, } from '@suite-common/wallet-utils'; import { WalletAccountTransaction } from '@wallet-types'; import { notificationsActions } from '@suite-common/toast-notifications'; @@ -30,8 +31,9 @@ import { copyToClipboard } from '@trezor/dom-utils'; import { AccountMetadata } from '@suite-types/metadata'; import { ExtendedMessageDescriptor } from '@suite-types'; import { SignOperator } from '@suite-common/suite-types'; +import { FormattedNftAmount } from '@suite-components/FormattedNftAmount'; -export const StyledFormattedCryptoAmount = styled(FormattedCryptoAmount)` +const amountStyle = css` width: 100%; color: ${({ theme }) => theme.TYPE_DARK_GREY}; font-size: ${variables.FONT_SIZE.NORMAL}; @@ -39,6 +41,14 @@ export const StyledFormattedCryptoAmount = styled(FormattedCryptoAmount)` white-space: nowrap; `; +export const StyledFormattedCryptoAmount = styled(FormattedCryptoAmount)` + ${amountStyle} +`; + +const StyledFormattedNftAmount = styled(FormattedNftAmount)` + ${amountStyle} +`; + interface BaseTransfer { singleRowLayout?: boolean; useAnimation?: boolean; @@ -57,6 +67,7 @@ export const TokenTransfer = ({ ...baseLayoutProps }: TokenTransferProps) => { const operation = getTxOperation(transfer); + const isNft = isNftTokenTransfer(transfer); const isZeroValuePhishing = getIsZeroValuePhishing(transaction); return ( @@ -70,7 +81,9 @@ export const TokenTransfer = ({ /> } amount={ - !baseLayoutProps.singleRowLayout && ( + isNft ? ( + + ) : ( + ['ERC1155', 'ERC721'].includes(transfer.standard || ''); + export const getTxIcon = (txType: WalletAccountTransaction['type']) => { switch (txType) { case 'recv':