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

EVM NFT section #15467

Merged
merged 17 commits into from
Dec 20, 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
2 changes: 0 additions & 2 deletions packages/blockchain-link-types/src/blockbook-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,6 @@ export interface Token {
secondaryValue?: number;
ids?: string[];
multiTokenValues?: MultiTokenValue[];
totalReceived?: string;
totalSent?: string;
}
export interface Address {
page?: number;
Expand Down
4 changes: 1 addition & 3 deletions packages/blockchain-link-types/src/blockbook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,7 @@ type BlockFiltersBatch = `${string}:${string}:${string}`[];
// XPUBAddress, ERC20, ERC721, ERC1155 - blockbook generated type (Token) is not strict enough
export type XPUBAddress = {
type: 'XPUBAddress';
} & Required<
Pick<BlockbookToken, 'path' | 'decimals' | 'balance' | 'totalSent' | 'totalReceived'>
> &
} & Required<Pick<BlockbookToken, 'path' | 'decimals' | 'balance'>> &
Pick<BlockbookToken, 'name' | 'transfers'>;

type BaseERC = Required<Pick<BlockbookToken, 'contract'>> &
Expand Down
3 changes: 2 additions & 1 deletion packages/blockchain-link-types/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
TokenTransfer as BlockbookTokenTransfer,
ContractInfo,
StakingPool,
Token,
} from './blockbook-api';
import type { SolanaStakingAccount } from './solana';

Expand Down Expand Up @@ -179,7 +180,7 @@ export interface TokenAccount {
balance: string;
}

export interface TokenInfo {
export interface TokenInfo extends Partial<Pick<Token, 'multiTokenValues' | 'ids'>> {
type: string; // token type: ERC20...
contract: string; // token address, token unit for ADA
balance?: string; // token balance
Expand Down
2 changes: 0 additions & 2 deletions packages/blockchain-link-utils/src/blockbook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,6 @@ export const transformAddresses = (
path: t.path,
transfers: t.transfers,
balance: t.balance,
sent: t.totalSent,
received: t.totalReceived,
},
]);
}, [] as Address[]);
Expand Down
2 changes: 1 addition & 1 deletion packages/components/src/components/Table/TableRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const Row = styled.tr<{
$isCollapsed &&
css`
visibility: collapse;
border-top: 0;
border-top: 1;
opacity: 0;
`}

Expand Down
2 changes: 2 additions & 0 deletions packages/suite-desktop-ui/src/support/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { SettingsCoins } from 'src/views/settings/SettingsCoins/SettingsCoins';
import { SettingsDebug } from 'src/views/settings/SettingsDebug/SettingsDebug';
import { SettingsDevice } from 'src/views/settings/SettingsDevice/SettingsDevice';
import { Tokens } from 'src/views/wallet/tokens';
import { Nfts } from 'src/views/wallet/nfts';
import PasswordManager from 'src/views/password-manager';

const components: { [key: string]: ComponentType<any> } = {
Expand All @@ -47,6 +48,7 @@ const components: { [key: string]: ComponentType<any> } = {
'wallet-sign-verify': WalletSignVerify,
'wallet-anonymize': WalletAnonymize,
'wallet-tokens': Tokens,
'wallet-nfts': Nfts,
'wallet-coinmarket-buy': CoinmarketBuyForm,
'wallet-coinmarket-buy-detail': CoinmarketBuyDetail,
'wallet-coinmarket-buy-offers': CoinmarketBuyOffers,
Expand Down
1 change: 1 addition & 0 deletions packages/suite-web/src/support/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const components: Record<PageName, LazyExoticComponent<ComponentType<any>>> = {
() => import(/* webpackChunkName: "wallet" */ 'src/views/wallet/details'),
),
'wallet-tokens': lazy(() => import(/* webpackChunkName: "wallet" */ 'src/views/wallet/tokens')),
'wallet-nfts': lazy(() => import(/* webpackChunkName: "wallet" */ 'src/views/wallet/nfts')),
'wallet-send': lazy(() => import(/* webpackChunkName: "wallet" */ 'src/views/wallet/send')),
'wallet-staking': lazy(() =>
import(/* webpackChunkName: "wallet" */ 'src/views/wallet/staking/WalletStaking').then(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface AppNavigationTooltipProps {
}

export const AppNavigationTooltip = ({ children, isActiveTab }: AppNavigationTooltipProps) => {
const { selectedAccount } = useSelector(state => state.wallet);
const selectedAccount = useSelector(state => state.wallet.selectedAccount);

const isAccountLoading = selectedAccount.status === 'loading';

Expand Down
6 changes: 3 additions & 3 deletions packages/suite/src/components/suite/FormattedNftAmount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const FormattedNftAmount = ({
}: FormattedNftAmountProps) => {
const theme = useTheme();
const { translationString } = useTranslation();
const { selectedAccount } = useSelector(state => state.wallet);
const selectedAccount = useSelector(state => state.wallet.selectedAccount);
const { network } = selectedAccount;

const symbolComponent = transfer.symbol ? (
Expand Down Expand Up @@ -63,7 +63,7 @@ export const FormattedNftAmount = ({
) : (
<Row gap={spacings.xxs}>
<Row>{token.value}x</Row>
<Translation id="TR_TOKEN_ID" />
<Translation id="TR_TOKEN_ID_COLON" />
</Row>
)}
</Row>
Expand Down Expand Up @@ -95,7 +95,7 @@ export const FormattedNftAmount = ({
<Row className={className}>
{signValue ? <Sign value={signValue} /> : null}
<Box margin={{ right: spacings.xxs }}>
<Translation id="TR_TOKEN_ID" />
<Translation id="TR_TOKEN_ID_COLON" />
</Box>
{isWithLink ? (
<TrezorLink
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const AmountDetails = ({ tx, isTestnet }: AmountDetailsProps) => {
const fee = formatNetworkAmount(tx.fee, tx.symbol);
const cardanoWithdrawal = formatCardanoWithdrawal(tx);
const cardanoDeposit = formatCardanoDeposit(tx);
const { selectedAccount } = useSelector(state => state.wallet);
const selectedAccount = useSelector(state => state.wallet.selectedAccount);

const txSignature = tx.ethereumSpecific?.parsedData?.methodId;
const isStakeTypeTxNoAmount = isStakeTypeTx(txSignature) && amount.eq(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ const IOGroup = ({
hasHeadings = true,
isUtxoBased = false,
}: IOGroupProps) => {
const { selectedAccount } = useSelector(state => state.wallet);
const selectedAccount = useSelector(state => state.wallet.selectedAccount);

const anonymitySet = selectedAccount?.account?.addresses?.anonymitySet;
const hasInputs = !!inputs?.length;
Expand Down Expand Up @@ -316,8 +316,7 @@ type IODetailsProps = {

// Not ready for Cardano tokens, they will not be visible, probably
export const IODetails = ({ tx, isPhishingTransaction }: IODetailsProps) => {
const { selectedAccount } = useSelector(state => state.wallet);
const { network } = selectedAccount;
const network = useSelector(state => state.wallet.selectedAccount.network);

const getContent = () => {
if (network?.networkType === 'ethereum') {
Expand Down
7 changes: 5 additions & 2 deletions packages/suite/src/components/wallet/TokenIconSetWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ export const TokenIconSetWrapper = ({ accounts, symbol }: TokenIconSetWrapperPro

if (!allTokensWithRates.length) return null;

const tokens = getTokens(allTokensWithRates, symbol, coinDefinitions)
.shownWithBalance as TokensWithRates[];
const tokens = getTokens({
tokens: allTokensWithRates,
symbol,
tokenDefinitions: coinDefinitions,
})?.shownWithBalance as TokensWithRates[];

const aggregatedTokens = Object.values(
tokens.reduce((acc: Record<string, TokensWithRates>, token) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@ import { useDispatch, useSelector } from 'src/hooks/suite';
import { goto } from 'src/actions/suite/routerActions';
import { selectSelectedAccount } from 'src/reducers/wallet/selectedAccountReducer';
import { NavigationItem, SubpageNavigation } from 'src/components/suite/layouts/SuiteLayout';
import { selectIsDebugModeActive } from 'src/reducers/suite/suiteReducer';
import {
selectIsDebugModeActive,
selectHasExperimentalFeature,
} from 'src/reducers/suite/suiteReducer';

export const ACCOUNT_TABS = [
'wallet-index',
'wallet-details',
'wallet-tokens',
'wallet-nfts',
'wallet-nfts-hidden',
'wallet-tokens-hidden',
'wallet-staking',
];
Expand All @@ -24,7 +29,7 @@ export const AccountNavigation = () => {
const account = useSelector(selectSelectedAccount);
const routerParams = useSelector(state => state.router.params) as WalletParams;
const dispatch = useDispatch();

const enabledNftSection = useSelector(selectHasExperimentalFeature('nft-section'));
const network = getNetworkOptional(routerParams?.symbol);
const networkType = account?.networkType || network?.networkType || '';

Expand Down Expand Up @@ -57,6 +62,16 @@ export const AccountNavigation = () => {
activeRoutes: ['wallet-tokens', 'wallet-tokens-hidden'],
'data-testid': '@wallet/menu/wallet-tokens',
},
{
id: 'wallet-nfts',
callback: () => {
goToWithAnalytics('wallet-nfts', { preserveParams: true });
},
title: <Translation id="TR_NAV_NFTS" />,
isHidden: !hasNetworkFeatures(account, 'nfts') || !enabledNftSection,
activeRoutes: ['wallet-nfts', 'wallet-nfts-hidden'],
'data-testid': '@wallet/menu/wallet-nfts',
},
{
id: 'wallet-staking',
callback: () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ export const AccountSection = ({

const showGroup = ['ethereum', 'solana', 'cardano'].includes(networkType);

const tokens = getTokens(accountTokens, account.symbol, coinDefinitions);
const tokens = getTokens({
tokens: accountTokens,
symbol: account.symbol,
tokenDefinitions: coinDefinitions,
});

const dataTestKey = `@account-menu/${symbol}/${accountType}/${index}`;

Expand Down
6 changes: 5 additions & 1 deletion packages/suite/src/constants/suite/experimental.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { isDesktop } from '@trezor/env-utils';

import { Dispatch } from '../../types/suite';

export type ExperimentalFeature = 'password-manager' | 'tor-external';
export type ExperimentalFeature = 'password-manager' | 'tor-external' | 'nft-section';

export type ExperimentalFeatureConfig = {
title: TranslationKey;
Expand Down Expand Up @@ -40,4 +40,8 @@ export const EXPERIMENTAL_FEATURES: Record<ExperimentalFeature, ExperimentalFeat
}
},
},
'nft-section': {
title: 'TR_EXPERIMENTAL_NFT_SECTION',
description: 'TR_EXPERIMENTAL_NFT_SECTION_DESCRIPTION',
},
};
66 changes: 66 additions & 0 deletions packages/suite/src/support/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2603,6 +2603,14 @@ export default defineMessages({
defaultMessage: 'Unrecognized tokens pose potential risks. Use caution.',
id: 'TR_TOKEN_UNRECOGNIZED_BY_TREZOR_TOOLTIP',
},
TR_COLLECTIONS_UNRECOGNIZED_BY_TREZOR: {
defaultMessage: 'Unrecognized collections',
id: 'TR_COLLECTIONS_UNRECOGNIZED_BY_TREZOR',
},
TR_NFT_UNRECOGNIZED_BY_TREZOR_TOOLTIP: {
defaultMessage: 'Unrecognized NFTs pose potential risks. Use caution.',
id: 'TR_NFT_UNRECOGNIZED_BY_TREZOR_TOOLTIP',
},
TR_LEARN: {
defaultMessage: 'Learn',
description: 'Link to Suite Guide.',
Expand Down Expand Up @@ -2726,6 +2734,14 @@ export default defineMessages({
defaultMessage: 'Tokens',
id: 'TR_NAV_TOKENS',
},
TR_NAV_COLLECTIONS: {
defaultMessage: 'Collections',
id: 'TR_NAV_COLLECTIONS',
},
TR_NAV_NFTS: {
defaultMessage: 'NFTs',
id: 'TR_NAV_NFTS',
},
TR_NAV_SIGN_AND_VERIFY: {
defaultMessage: 'Sign & verify',
description:
Expand Down Expand Up @@ -3315,6 +3331,11 @@ export default defineMessages({
defaultMessage: 'Details',
id: 'TR_TRANSACTION_DETAILS',
},
TR_TOKEN_ID_COLON: {
defaultMessage: 'Token ID:',
id: 'TR_TOKEN_ID_COLON',
},

TR_TOKEN_ID: {
defaultMessage: 'Token ID',
id: 'TR_TOKEN_ID',
Expand Down Expand Up @@ -4966,6 +4987,15 @@ export default defineMessages({
defaultMessage: 'Experimental',
description: 'Section title for Early Access program so far',
},
TR_EXPERIMENTAL_NFT_SECTION: {
id: 'TR_EXPERIMENTAL_NFT_SECTION',
defaultMessage: 'NFTs (non-fungible tokens)',
},
TR_EXPERIMENTAL_NFT_SECTION_DESCRIPTION: {
id: 'TR_EXPERIMENTAL_NFT_SECTION_DESCRIPTION',
defaultMessage:
'Access the NFTs stored in your wallet. Currently available for EVM-based chains only.',
},
TR_EXPERIMENTAL_FEATURES_ALLOW: {
id: 'TR_EXPERIMENTAL_FEATURES_ALLOW',
defaultMessage: 'Experimental features',
Expand Down Expand Up @@ -5281,18 +5311,34 @@ export default defineMessages({
id: 'TR_TOKENS',
defaultMessage: 'Tokens',
},
TR_COLLECTIONS: {
id: 'TR_COLLECTIONS',
defaultMessage: 'Collections',
},
TR_TOKENS_EMPTY: {
id: 'TR_TOKENS_EMPTY',
defaultMessage: 'No tokens... yet.',
},
TR_NFT_EMPTY: {
id: 'TR_NFT_EMPTY',
defaultMessage: 'No NFT collections... yet.',
},
TR_TOKENS_EMPTY_CHECK_HIDDEN: {
id: 'TR_TOKENS_EMPTY_CHECK_HIDDEN',
defaultMessage: 'No tokens. They may be hidden.',
},
TR_NFT_EMPTY_CHECK_HIDDEN: {
id: 'TR_NFT_EMPTY_CHECK_HIDDEN',
defaultMessage: 'No NFT collections. They may be hidden.',
},
TR_HIDDEN_TOKENS_EMPTY: {
id: 'TR_HIDDEN_TOKENS_EMPTY',
defaultMessage: 'You have no hidden tokens.',
},
TR_HIDDEN_NFT_EMPTY: {
id: 'TR_HIDDEN_NFT_EMPTY',
defaultMessage: 'You have no hidden NFT collections.',
},
TR_ADD_TOKEN_TITLE: {
id: 'TR_ADD_TOKEN_TITLE',
defaultMessage: 'Add ERC20 token',
Expand Down Expand Up @@ -5382,6 +5428,10 @@ export default defineMessages({
defaultMessage: 'Amount',
id: 'AMOUNT',
},
TR_QUANTITY: {
defaultMessage: 'Quantity',
id: 'TR_QUANTITY',
},
AMOUNT_SEND_MAX: {
id: 'AMOUNT_SEND_MAX',
defaultMessage: 'Send max',
Expand Down Expand Up @@ -6372,6 +6422,10 @@ export default defineMessages({
id: 'TR_UNHIDE_TOKEN',
defaultMessage: 'Unhide token',
},
TR_HIDE_COLLECTION: {
id: 'TR_HIDE_COLLECTION',
defaultMessage: 'Hide collection',
},
TR_UNHIDE: {
id: 'TR_UNHIDE',
defaultMessage: 'Unhide',
Expand Down Expand Up @@ -6456,10 +6510,18 @@ export default defineMessages({
id: 'TR_SEARCH_TOKENS',
defaultMessage: 'Search tokens',
},
TR_SEARCH_COLLECTIONS: {
id: 'TR_SEARCH_COLLECTIONS',
defaultMessage: 'Search collections',
},
TR_TOKENS_SEARCH_TOOLTIP: {
id: 'TR_TOKENS_SEARCH_TOOLTIP',
defaultMessage: 'Search by token, symbol, or contract address.',
},
TR_COLLECTIONS_SEARCH_TOOLTIP: {
id: 'TR_COLLECTIONS_SEARCH_TOOLTIP',
defaultMessage: 'Search by collection name, symbol, or contract address.',
},
TR_SEARCH_TRANSACTIONS: {
id: 'TR_SEARCH_TRANSACTIONS',
defaultMessage: 'Search transactions',
Expand Down Expand Up @@ -8839,6 +8901,10 @@ export default defineMessages({
id: 'ZERO_BALANCE_TOKENS',
defaultMessage: 'Zero-balance tokens',
},
EMPTY_NFT_COLLECTIONS: {
id: 'EMPTY_NFT_COLLECTIONS',
defaultMessage: 'Empty collections',
},
TR_STAKE_ADDING_TO_POOL: {
id: 'TR_STAKE_ADDING_TO_POOL',
defaultMessage: 'Adding to staking pool',
Expand Down
11 changes: 8 additions & 3 deletions packages/suite/src/utils/wallet/__tests__/tokenUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ describe('getTokens', () => {
getTokensFixtures.forEach(
({ testName, tokens, symbol, coinDefinitions, searchQuery, result }) => {
test(testName, () => {
expect(getTokens(tokens, symbol, coinDefinitions, searchQuery)).toStrictEqual(
result,
);
expect(
getTokens({
tokens,
symbol,
tokenDefinitions: coinDefinitions,
searchQuery,
}),
).toStrictEqual(result);
});
},
);
Expand Down
Loading
Loading