Skip to content

Commit

Permalink
feat(suite): nft support
Browse files Browse the repository at this point in the history
  • Loading branch information
tomasklim committed Mar 22, 2023
1 parent 1b669c8 commit 8297a73
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 28 deletions.
4 changes: 4 additions & 0 deletions packages/blockchain-link-types/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ export interface TokenTransfer {
from?: string;
to?: string;
standard?: 'ERC20' | 'ERC1155' | 'ERC721';
multiTokenValues?: Array<{
id: string;
value: string;
}>;
}

export interface InternalTransfer {
Expand Down
66 changes: 66 additions & 0 deletions packages/suite/src/components/suite/FormattedNftAmount.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<HiddenPlaceholder>
<Container className={className}>
{!!signValue && <Sign value={signValue} />}

<span>ID&nbsp;</span>

{useLink ? (
<StyledTrezorLink href={explorerUrl}>{id}</StyledTrezorLink>
) : (
<span>{id}</span>
)}

<Symbol>&nbsp;{transfer.symbol}</Symbol>
</Container>
</HiddenPlaceholder>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -212,11 +214,19 @@ export const AmountDetails = ({ tx, isTestnet }: AmountDetailsProps) => {
) : undefined
}
secondColumn={
<FormattedCryptoAmount
value={formatAmount(t.amount, t.decimals)}
symbol={t.symbol as NetworkSymbol}
signValue={getTxOperation(t, true)}
/>
isNftTokenTransfer(t) ? (
<FormattedNftAmount
transfer={t}
useLink
signValue={getTxOperation(t, true)}
/>
) : (
<FormattedCryptoAmount
value={formatAmount(t.amount, t.decimals)}
symbol={t.symbol as NetworkSymbol}
signValue={getTxOperation(t, true)}
/>
)
}
// no history rates available for tokens
thirdColumn={null}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
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';
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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -189,29 +196,21 @@ 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 (
<RowGrid>
<RowGridItem>
<TxAddressOverflow txAddress={from} />
<br />
{formattedInValue && (
<StyledFormattedCryptoAmount value={formattedInValue} symbol={symbol} />
)}
{formattedOutValue && (
<StyledFormattedCryptoAmount value={formattedOutValue} symbol={symbol} />
{typeof amount === 'string' ? (
<StyledFormattedCryptoAmount value={amount} symbol={symbol} />
) : (
amount
)}
</RowGridItem>
<RowGridItem>
Expand Down Expand Up @@ -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}
/>
))}
Expand All @@ -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) ? (
<StyledFormattedNftAmount transfer={t} useLink />
) : (
formatAmount(t.amount, t.decimals)
)
}
symbol={t.symbol}
/>
))}
Expand All @@ -303,15 +308,15 @@ 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] ? (
<GridGroup>
<IOGridGroupWrapper inputsLength={-1} outputsLength={-1}>
<GridRowGroupComponent
from={vin.addresses[0]}
to={vout.addresses[0]}
formattedInValue={vin.value && formatNetworkAmount(vin.value, tx.symbol)}
formattedOutValue={vout.value && formatNetworkAmount(vout.value, tx.symbol)}
amount={value}
symbol={tx.symbol}
/>
</IOGridGroupWrapper>
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand All @@ -30,15 +31,24 @@ 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};
font-weight: ${variables.FONT_WEIGHT.MEDIUM};
white-space: nowrap;
`;

export const StyledFormattedCryptoAmount = styled(FormattedCryptoAmount)`
${amountStyle}
`;

const StyledFormattedNftAmount = styled(FormattedNftAmount)`
${amountStyle}
`;

interface BaseTransfer {
singleRowLayout?: boolean;
useAnimation?: boolean;
Expand All @@ -57,6 +67,7 @@ export const TokenTransfer = ({
...baseLayoutProps
}: TokenTransferProps) => {
const operation = getTxOperation(transfer);
const isNft = isNftTokenTransfer(transfer);
const isZeroValuePhishing = getIsZeroValuePhishing(transaction);

return (
Expand All @@ -70,7 +81,9 @@ export const TokenTransfer = ({
/>
}
amount={
!baseLayoutProps.singleRowLayout && (
isNft ? (
<StyledFormattedNftAmount transfer={transfer} signValue={operation} />
) : (
<StyledFormattedCryptoAmount
value={formatAmount(transfer.amount, transfer.decimals)}
symbol={transfer.symbol}
Expand Down
3 changes: 3 additions & 0 deletions suite-common/wallet-utils/src/transactionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,9 @@ export const getTxOperation = (
return null;
};

export const isNftTokenTransfer = (transfer: WalletAccountTransaction['tokens'][number]) =>
['ERC1155', 'ERC721'].includes(transfer.standard || '');

export const getTxIcon = (txType: WalletAccountTransaction['type']) => {
switch (txType) {
case 'recv':
Expand Down

0 comments on commit 8297a73

Please sign in to comment.