Skip to content

Temp TokenHero hook #15262

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
@@ -1,63 +1,91 @@
import React, { useState } from 'react';
import { StyleSheet, TouchableOpacity, View } from 'react-native';
import { strings } from '../../../../../../../../locales/i18n';
import Badge, {
BadgeVariant,
} from '../../../../../../../component-library/components/Badges/Badge';
import React from 'react';
import {
StyleSheet,
TouchableOpacity,
View,
ImageSourcePropType,
} from 'react-native';
import { BigNumber } from 'bignumber.js';
import { Hex } from '@metamask/utils';
import { Collection } from '@metamask/assets-controllers';
import { TransactionType } from '@metamask/transaction-controller';
import { toChecksumAddress } from 'ethereumjs-util';

import BadgeWrapper, {
BadgePosition,
} from '../../../../../../../component-library/components/Badges/BadgeWrapper';
import Text, {
TextVariant,
} from '../../../../../../../component-library/components/Texts/Text';
import { useStyles } from '../../../../../../../component-library/hooks';
import images from '../../../../../../../images/image-icons';
import TokenIcon from '../../../../../../UI/Swaps/components/TokenIcon';
import { useTokenValues } from '../../../../hooks/useTokenValues';
import { useSelector } from 'react-redux';
import { useFlatConfirmation } from '../../../../hooks/ui/useFlatConfirmation';
import { TooltipModal } from '../../../UI/Tooltip/Tooltip';
import styleSheet from './token-hero.styles';
import { useTransactionMetadataRequest } from '../../../../hooks/transactions/useTransactionMetadataRequest';
import { parseStandardTokenTransactionData } from '../../../../utils/transaction';
import { selectERC20TokensByChain } from '../../../../../../../selectors/tokenListController';
import { useAsyncResult } from '../../../../../../hooks/useAsyncResult';
import Engine from '../../../../../../../core/Engine';
import { getTokenDetails } from '../../../../../../../util/address';
import { hexWEIToDecETH } from '../../../../../../../util/conversions';
import useFiatFormatter from '../../../../../../UI/SimulationDetails/FiatDisplay/useFiatFormatter';
import { RootState } from '../../../../../../../reducers';
import { selectConversionRateByChainId } from '../../../../../../../selectors/currencyRateController';
import { selectNetworkConfigurationByChainId } from '../../../../../../../selectors/networkController';
import { selectContractExchangeRatesByChainId } from '../../../../../../../selectors/tokenRatesController';
import { getNetworkImageSource } from '../../../../../../../util/networks';
import AvatarNetwork from '../../../../../../../component-library/components/Avatars/Avatar/variants/AvatarNetwork';
import { AvatarSize } from '../../../../../../../component-library/components/Avatars/Avatar';

const NetworkAndTokenImage = ({
tokenSymbol,
styles,
image,
networkImage,
}: {
tokenSymbol: string;
styles: StyleSheet.NamedStyles<Record<string, unknown>>;
image: string;
networkImage: ImageSourcePropType;
}) => (
<View style={styles.networkAndTokenContainer}>
<BadgeWrapper
badgePosition={BadgePosition.BottomRight}
badgeElement={
<Badge imageSource={images.ETHEREUM} variant={BadgeVariant.Network} />
image ? (
<AvatarNetwork size={AvatarSize.Xs} imageSource={networkImage} />
) : null
}
>
<TokenIcon big symbol={tokenSymbol} />
{image ? (
<TokenIcon big icon={image} />
) : (
<AvatarNetwork size={AvatarSize.Xl} imageSource={networkImage} />
)}
</BadgeWrapper>
</View>
);

const AssetAmount = ({
tokenAmountDisplayValue,
tokenSymbol,
amount,
name,
styles,
setIsModalVisible,
}: {
tokenAmountDisplayValue: string;
tokenSymbol: string;
amount: string;
name: string;
styles: StyleSheet.NamedStyles<Record<string, unknown>>;
setIsModalVisible: ((isModalVisible: boolean) => void) | null;
}) => (
<View style={styles.assetAmountContainer}>
{setIsModalVisible ? (
<TouchableOpacity onPress={() => setIsModalVisible(true)}>
<Text style={styles.assetAmountText} variant={TextVariant.HeadingLG}>
{tokenAmountDisplayValue} {tokenSymbol}
{amount} {name}
</Text>
</TouchableOpacity>
) : (
<Text style={styles.assetAmountText} variant={TextVariant.HeadingLG}>
{tokenAmountDisplayValue} {tokenSymbol}
{amount} {name}
</Text>
)}
</View>
Expand All @@ -75,44 +103,195 @@ const AssetFiatConversion = ({
</Text>
);

const TokenHero = ({ amountWei }: { amountWei?: string }) => {
const useTokenTransferValues = () => {
const { NftController } = Engine.context;
const fiatFormatter = useFiatFormatter();
const transactionMetadata = useTransactionMetadataRequest();
const transactionType = transactionMetadata?.type;
const networkClientId = transactionMetadata?.networkClientId;
const txParams = transactionMetadata?.txParams;
const chainId = transactionMetadata?.chainId;
const to = txParams?.to as string;
const transferData = parseStandardTokenTransactionData(txParams?.data);
const contractExchangeRates = useSelector((state: RootState) =>
selectContractExchangeRatesByChainId(state, chainId as Hex),
);
const erc20TokensByChain = useSelector(selectERC20TokensByChain);
const nativeConversionRate = useSelector((state: RootState) =>
selectConversionRateByChainId(state, chainId as Hex),
);
const { nativeCurrency } = useSelector((state: RootState) =>
selectNetworkConfigurationByChainId(state, chainId as Hex),
);
const { value: tokenDetails } = useAsyncResult(async () => {
const tokenDetails = await getTokenDetails(
to,
undefined,
undefined,
networkClientId,
);
return tokenDetails;
}, []);
const { value: collectionsMetadata } = useAsyncResult(async () => {
const collectionsResult = await NftController.getNFTContractInfo(
[to],
chainId as Hex,
);

const collectionsData = collectionsResult.collections.reduce<
Record<string, Collection>
>((acc, collection) => {
acc[to] = {
name: collection?.name,
image: collection?.image,
isSpam: collection?.isSpam,
};
return acc;
}, {});

return collectionsData;
}, []);

let amount = null;
let tokenAddress = null; // If no tokenAddress, it's a native transaction
let tokenFormattedFiatValue = null;
let tokenId = null;
let tokenImage = null;
let tokenName = null;
let networkImage = getNetworkImageSource({ chainId: chainId as Hex });

switch (transactionType) {
case TransactionType.simpleSend: {
// Native
const decimalAmount = hexWEIToDecETH(txParams?.value as Hex);
const amountBN = new BigNumber(decimalAmount);
const nativeConversionRateInBN = new BigNumber(
nativeConversionRate as number,
);
const preciseFiatValue = amountBN.times(nativeConversionRateInBN);

amount = decimalAmount.toString();
tokenFormattedFiatValue = fiatFormatter(preciseFiatValue);
tokenName = nativeCurrency;
break;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

somewhere there is a bug here.

CleanShot 2025-05-09 at 17 12 44

#15259 appears to cover this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merging fiat logic

case TransactionType.tokenMethodTransfer: {
// ERC20
const amountBN = new BigNumber(transferData?.args[1].toString());

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think transferData?.args[1] should equate to transactionData?.args?._value

const { name: erc20TokenName, iconUrl: image } =
erc20TokensByChain[chainId as Hex]?.data?.[txParams?.to as string] ??
{};

const { decimals } = tokenDetails ?? {};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than using tokenDetails to fetch decimals, I think fetchErc20Decimals is a nice option since it will return the default of 18 decimals if not found. This keeps the type consistent and avoids decimals ?? 18 below.

tokenDetails may only be used for nft logic

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit of tradeoff I would say - to use TokenListController or make component to fetch the details itself. In ideal world we should use the same token state which is from TokenListController so that we don't have to make all components to fetch their own details.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it would be better to resolve this issue with a single source of truth.

I'll look into this more. Planning to examine if AssetsContractController is a candidate as well

const divisor = new BigNumber(10).pow(decimals ?? 18);
const formattedAmountBN = new BigNumber(amountBN.toString()).dividedBy(
divisor,
);

const formattedAmount = formattedAmountBN.toString();

const nativeConversionRateInBN = new BigNumber(
nativeConversionRate as number,
);

const contractExchangeRateInNative =
contractExchangeRates?.[
toChecksumAddress(txParams?.to as string) as Hex
].price ?? 0;

const contractExchangeRateInNativeBN = new BigNumber(
contractExchangeRateInNative,
);

const tokenValueInNative = formattedAmountBN.multipliedBy(
contractExchangeRateInNativeBN,
);

const preciseFiatValue = tokenValueInNative.times(
nativeConversionRateInBN,
);

amount = formattedAmount;
tokenAddress = txParams?.to;
tokenImage = image;
tokenName = erc20TokenName;
tokenFormattedFiatValue = fiatFormatter(preciseFiatValue);
break;
}
case TransactionType.tokenMethodTransferFrom: {
// ERC721 - ERC1155
tokenAddress = txParams?.to;
tokenId = transferData?.args[transferData?.args.length - 1].toString();
tokenImage = collectionsMetadata?.[to]?.image;
tokenName = collectionsMetadata?.[to]?.name;
break;
}
case TransactionType.contractInteraction: {
// TODO: Staking use case
break;
}
default: {
// TODO: We should be show unknown token image and name
break;
}
}

console.log('---------------------------');
console.log('amount', amount);
console.log('networkImage', networkImage);
console.log('transactionType', transactionType);
console.log('tokenAddress', tokenAddress);
console.log('tokenId', tokenId);
console.log('tokenName', tokenName);
console.log('tokenImage', tokenImage);
console.log('tokenFormattedFiatValue', tokenFormattedFiatValue);
console.log('---------------------------');

return {
amount,
networkImage,
tokenAddress,
tokenFormattedFiatValue,
tokenId,
tokenImage,
tokenName,
};
};

const TokenHero = () => {
const { isFlatConfirmation } = useFlatConfirmation();
const { styles } = useStyles(styleSheet, {
isFlatConfirmation,
});
const { tokenAmountValue, tokenAmountDisplayValue, fiatDisplayValue } =
useTokenValues({ amountWei });
const [isModalVisible, setIsModalVisible] = useState(false);

const displayTokenAmountIsRounded =
tokenAmountValue !== tokenAmountDisplayValue;

const tokenSymbol = 'ETH';
const {
amount,
networkImage,
tokenFormattedFiatValue,
tokenId, // TODO: Will be displayed for NFTs instead of fiat
tokenImage,
tokenName,
} = useTokenTransferValues();

return (
<View style={styles.container}>
<NetworkAndTokenImage tokenSymbol={tokenSymbol} styles={styles} />
<NetworkAndTokenImage
image={tokenImage ?? ''}
networkImage={networkImage}
styles={styles}
/>
<AssetAmount
tokenAmountDisplayValue={tokenAmountDisplayValue}
tokenSymbol={tokenSymbol}
amount={amount ?? ''}
name={tokenName ?? ''}
styles={styles}
setIsModalVisible={
displayTokenAmountIsRounded ? setIsModalVisible : null
}
setIsModalVisible={() => {}}
/>
<AssetFiatConversion
fiatDisplayValue={fiatDisplayValue}
fiatDisplayValue={tokenFormattedFiatValue ?? ''}
styles={styles}
/>
{displayTokenAmountIsRounded && (
<TooltipModal
open={isModalVisible}
setOpen={setIsModalVisible}
content={tokenAmountValue}
title={strings('send.amount')}
tooltipTestId="token-hero-amount"
/>
)}
</View>
);
};
Expand Down
Loading