diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 71ea87943..09fb496b4 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -58,6 +58,10 @@ "message": "$1 may access and spend up to this max amount", "description": "$1 is the url of the site requesting ability to spend" }, + "accessAndSpendNoticeNFT": { + "message": "$1 may access and spend this asset", + "description": "$1 is the url of the site requesting ability to spend" + }, "accessingYourCamera": { "message": "Accessing your camera..." }, @@ -279,6 +283,9 @@ "approvedAmountWithColon": { "message": "Approved amount:" }, + "approvedAsset": { + "message": "Approved asset" + }, "areYouDeveloper": { "message": "Are you a developer?" }, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 9b2fc8783..aa337c864 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1271,7 +1271,6 @@ export default class MetamaskController extends EventEmitter { appStateController, collectiblesController, collectibleDetectionController, - assetsContractController, currencyRateController, detectTokensController, ensController, @@ -1427,9 +1426,7 @@ export default class MetamaskController extends EventEmitter { setTheme: preferencesController.setTheme.bind(preferencesController), // AssetsContractController - getTokenStandardAndDetails: assetsContractController.getTokenStandardAndDetails.bind( - assetsContractController, - ), + getTokenStandardAndDetails: this.getTokenStandardAndDetails.bind(this), // CollectiblesController addCollectible: collectiblesController.addCollectible.bind( @@ -1758,6 +1755,19 @@ export default class MetamaskController extends EventEmitter { }; } + async getTokenStandardAndDetails(address, userAddress, tokenId) { + const details = await this.assetsContractController.getTokenStandardAndDetails( + address, + userAddress, + tokenId, + ); + return { + ...details, + decimals: details?.decimals?.toString(10), + balance: details?.balance?.toString(10), + }; + } + //============================================================================= // VAULT / KEYRING RELATED METHODS //============================================================================= diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index fea0e00b7..3d8e46f4a 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -16,6 +16,7 @@ const { ensureXServerIsRunning } = require('./x-server'); const tinyDelayMs = 200; const regularDelayMs = tinyDelayMs * 2; const largeDelayMs = regularDelayMs * 2; +const veryLargeDelayMs = largeDelayMs * 2; const dappPort = 8080; const convertToHexValue = (val) => `0x${new BigNumber(val, 10).toString(16)}`; @@ -276,6 +277,7 @@ module.exports = { tinyDelayMs, regularDelayMs, largeDelayMs, + veryLargeDelayMs, withFixtures, connectDappWithExtensionPopup, completeImportSRPOnboardingFlow, diff --git a/test/e2e/metamask-ui.spec.js b/test/e2e/metamask-ui.spec.js index 3600ec109..30ca8d43b 100644 --- a/test/e2e/metamask-ui.spec.js +++ b/test/e2e/metamask-ui.spec.js @@ -3,7 +3,12 @@ const path = require('path'); const enLocaleMessages = require('../../app/_locales/en/messages.json'); const createStaticServer = require('../../development/create-static-server'); -const { tinyDelayMs, regularDelayMs, largeDelayMs } = require('./helpers'); +const { + tinyDelayMs, + regularDelayMs, + largeDelayMs, + veryLargeDelayMs, +} = require('./helpers'); const { buildWebDriver } = require('./webdriver'); const Ganache = require('./ganache'); const { ensureXServerIsRunning } = require('./x-server'); @@ -272,7 +277,7 @@ describe('MetaMask', function () { const gasPriceInput = inputs[1]; await gasLimitInput.fill('4700000'); await gasPriceInput.fill('20'); - await driver.delay(1000); + await driver.delay(veryLargeDelayMs); await driver.clickElement({ text: 'Save', tag: 'button' }); await driver.clickElement({ text: 'Confirm', tag: 'button' }); @@ -343,10 +348,11 @@ describe('MetaMask', function () { // Continue to next screen await driver.delay(largeDelayMs); await driver.clickElement({ text: 'Next', tag: 'button' }); - await driver.delay(regularDelayMs); + await driver.delay(largeDelayMs); }); it('displays the token transfer data', async function () { + await driver.delay(largeDelayMs); await driver.clickElement({ text: 'Hex', tag: 'button' }); await driver.delay(regularDelayMs); @@ -386,7 +392,7 @@ describe('MetaMask', function () { const gasPriceInput = inputs[1]; await gasLimitInput.fill('100000'); await gasPriceInput.fill('100'); - await driver.delay(1000); + await driver.delay(veryLargeDelayMs); await driver.clickElement({ text: 'Save', tag: 'button' }); }); @@ -449,19 +455,20 @@ describe('MetaMask', function () { }); it('customizes gas', async function () { + await driver.delay(veryLargeDelayMs); await driver.clickElement({ text: 'Edit', tag: 'button' }); - await driver.delay(largeDelayMs); + await driver.delay(veryLargeDelayMs); await driver.clickElement( { text: 'Edit suggested gas fee', tag: 'button' }, 10000, ); - await driver.delay(1000); + await driver.delay(veryLargeDelayMs); const inputs = await driver.findElements('input[type="number"]'); const gasLimitInput = inputs[0]; const gasPriceInput = inputs[1]; await gasLimitInput.fill('60000'); await gasPriceInput.fill('10'); - await driver.delay(1000); + await driver.delay(veryLargeDelayMs); await driver.clickElement({ text: 'Save', tag: 'button' }); await driver.findElement({ tag: 'span', text: '0.0006' }); }); @@ -586,7 +593,7 @@ describe('MetaMask', function () { await gasLimitInput.fill('60001'); - await driver.delay(1000); + await driver.delay(veryLargeDelayMs); await driver.clickElement({ text: 'Save', tag: 'button' }); @@ -752,7 +759,7 @@ describe('MetaMask', function () { }); it('submits the transaction', async function () { - await driver.delay(1000); + await driver.delay(veryLargeDelayMs); await driver.clickElement({ text: 'Confirm', tag: 'button' }); await driver.delay(regularDelayMs); }); diff --git a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js index 089b359b8..7da4ddeba 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js +++ b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js @@ -11,6 +11,8 @@ import useAddressDetails from '../../../../../hooks/useAddressDetails'; import Identicon from '../../../../ui/identicon'; import InfoTooltip from '../../../../ui/info-tooltip'; import NicknamePopovers from '../../../modals/nickname-popovers'; +import Typography from '../../../../ui/typography'; +import { TYPOGRAPHY } from '../../../../../helpers/constants/design-system'; const ConfirmPageContainerSummary = (props) => { const { @@ -116,9 +118,15 @@ const ConfirmPageContainerSummary = (props) => {
{renderImage()} {!hideTitle ? ( -
+ {titleComponent || title} -
+ ) : null}
{hideSubtitle ? null : ( diff --git a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss index 0f4941976..53fb24a22 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss +++ b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss @@ -77,6 +77,14 @@ text-overflow: ellipsis; } + &__title-text-long { + @include H3; + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + &__subtitle { @include H5; diff --git a/ui/components/ui/typography/typography.js b/ui/components/ui/typography/typography.js index 9496533ba..166ad4ac1 100644 --- a/ui/components/ui/typography/typography.js +++ b/ui/components/ui/typography/typography.js @@ -82,6 +82,7 @@ export default function Typography({ fontStyle = 'normal', align, overflowWrap, + title, tag, margin = [1, 0], boxProps = {}, @@ -117,7 +118,10 @@ export default function Typography({ return ( {(boxClassName) => ( - + {children} )} @@ -175,6 +179,10 @@ Typography.propTypes = { * Additional className to assign the Typography component */ className: PropTypes.string, + /** + * Title attribute to include on the element. Will show as tooltip on hover. + */ + title: PropTypes.string, /** * The text content of the Typography component */ diff --git a/ui/helpers/utils/token-util.js b/ui/helpers/utils/token-util.js index 68431dde5..a75db6da6 100644 --- a/ui/helpers/utils/token-util.js +++ b/ui/helpers/utils/token-util.js @@ -4,8 +4,12 @@ import { conversionUtil, multiplyCurrencies, } from '../../../shared/modules/conversion.utils'; +import { getTokenStandardAndDetails } from '../../store/actions'; +import { ERC1155, ERC721 } from '../constants/common'; +import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils'; import * as util from './util'; import { formatCurrency } from './confirm-tx.util'; +import { getTransactionData } from './transactions.util'; const DEFAULT_SYMBOL = ''; @@ -212,3 +216,48 @@ export function getTokenFiatAmount( } return result; } + +export async function getAssetDetails( + tokenAddress, + currentUserAddress, + transactionData, + existingCollectibles, +) { + const tokenData = getTransactionData(transactionData); + if (!tokenData) { + throw new Error('Unable to detect valid token data'); + } + + const tokenId = getTokenValueParam(tokenData); + let tokenDetails; + try { + tokenDetails = await getTokenStandardAndDetails( + tokenAddress, + currentUserAddress, + tokenId, + ); + } catch (error) { + log.warn(error); + return {}; + } + + if (tokenDetails?.standard) { + const { standard } = tokenDetails; + if (standard === ERC721 || standard === ERC1155) { + const existingCollectible = existingCollectibles.find(({ address }) => + isEqualCaseInsensitive(tokenAddress, address), + ); + + if (existingCollectible) { + return { + ...existingCollectible, + standard, + }; + } + } + // else if not a collectible already in state or standard === ERC20 just return tokenDetails as it contains all required data + return tokenDetails; + } + + return {}; +} diff --git a/ui/hooks/useAssetDetails.js b/ui/hooks/useAssetDetails.js new file mode 100644 index 000000000..ac12ed494 --- /dev/null +++ b/ui/hooks/useAssetDetails.js @@ -0,0 +1,118 @@ +import { useState, useEffect } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { getCollectibles, getTokens } from '../ducks/metamask/metamask'; +import { ERC1155, ERC721, ERC20 } from '../helpers/constants/common'; +import { + calcTokenAmount, + getAssetDetails, + getTokenAddressParam, + getTokenValueParam, +} from '../helpers/utils/token-util'; +import { getTransactionData } from '../helpers/utils/transactions.util'; +import { getTokenList } from '../selectors'; +import { hideLoadingIndication, showLoadingIndication } from '../store/actions'; +import { usePrevious } from './usePrevious'; + +export function useAssetDetails(tokenAddress, userAddress, transactionData) { + const dispatch = useDispatch(); + + // state selectors + const tokens = useSelector(getTokens); + const collectibles = useSelector(getCollectibles); + const tokenList = useSelector(getTokenList); + + // in-hook state + const [currentAsset, setCurrentAsset] = useState(null); + + // previous state checkers + const prevTokenAddress = usePrevious(tokenAddress); + const prevUserAddress = usePrevious(userAddress); + const prevTransactionData = usePrevious(transactionData); + + useEffect(() => { + async function getAndSetAssetDetails() { + dispatch(showLoadingIndication()); + const assetDetails = await getAssetDetails( + tokenAddress, + userAddress, + transactionData, + collectibles, + tokens, + tokenList, + ); + setCurrentAsset(assetDetails); + dispatch(hideLoadingIndication()); + } + if ( + tokenAddress !== prevTokenAddress || + userAddress !== prevUserAddress || + transactionData !== prevTransactionData + ) { + getAndSetAssetDetails(); + } + }, [ + dispatch, + prevTokenAddress, + prevTransactionData, + prevUserAddress, + tokenAddress, + userAddress, + transactionData, + collectibles, + tokens, + tokenList, + ]); + + let assetStandard, + assetName, + assetAddress, + tokenSymbol, + decimals, + tokenImage, + userBalance, + tokenValue, + toAddress, + tokenAmount, + tokenId; + + if (currentAsset) { + const { + standard, + symbol, + image, + name, + balance, + decimals: currentAssetDecimals, + } = currentAsset; + const tokenData = getTransactionData(transactionData); + assetStandard = standard; + assetAddress = tokenAddress; + tokenSymbol = symbol; + tokenImage = image; + toAddress = getTokenAddressParam(tokenData); + if (assetStandard === ERC721 || assetStandard === ERC1155) { + assetName = name; + tokenId = getTokenValueParam(tokenData); + } + if (assetStandard === ERC20) { + userBalance = balance; + decimals = Number(currentAssetDecimals?.toString(10)); + tokenAmount = + tokenData && + calcTokenAmount(getTokenValueParam(tokenData), decimals).toString(10); + } + } + return { + assetStandard, + assetName, + assetAddress, + userBalance, + tokenSymbol, + decimals, + tokenImage, + tokenValue, + toAddress, + tokenAmount, + tokenId, + }; +} diff --git a/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js b/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js index e1ab67009..5fe8c59db 100644 --- a/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js +++ b/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js @@ -28,6 +28,7 @@ import { SECOND } from '../../../../shared/constants/time'; import { ConfirmPageContainerWarning } from '../../../components/app/confirm-page-container/confirm-page-container-content'; import GasDetailsItem from '../../../components/app/gas-details-item'; import LedgerInstructionField from '../../../components/app/ledger-instruction-field'; +import { ERC1155, ERC20, ERC721 } from '../../../helpers/constants/common'; export default class ConfirmApproveContent extends Component { static contextTypes = { @@ -60,13 +61,15 @@ export default class ConfirmApproveContent extends Component { warning: PropTypes.string, txData: PropTypes.object, fromAddressIsLedger: PropTypes.bool, - tokenImage: PropTypes.string, chainId: PropTypes.string, rpcPrefs: PropTypes.object, isContract: PropTypes.bool, hexTransactionTotal: PropTypes.string, isMultiLayerFeeNetwork: PropTypes.bool, supportsEIP1559V2: PropTypes.bool, + assetName: PropTypes.string, + tokenId: PropTypes.string, + assetStandard: PropTypes.string, }; state = { @@ -178,7 +181,60 @@ export default class ConfirmApproveContent extends Component { ); } - renderPermissionContent() { + renderERC721OrERC1155PermissionContent() { + const { t } = this.context; + const { origin, toAddress, isContract, assetName, tokenId } = this.props; + + const displayedAddress = isContract + ? `${t('contract')} (${addressSummary(toAddress)})` + : addressSummary(toAddress); + return ( +
+
+ {t('accessAndSpendNoticeNFT', [origin])} +
+
+
+ {t('approvedAsset')}: +
+
+ {`${assetName} #${tokenId}`} +
+
+
+
+ {t('grantedToWithColon')} +
+
+ {displayedAddress} +
+
+ +
+
+
+ ); + } + + renderERC20PermissionContent() { const { t } = this.context; const { customTokenAmount, @@ -188,6 +244,7 @@ export default class ConfirmApproveContent extends Component { toAddress, isContract, } = this.props; + const displayedAddress = isContract ? `${t('contract')} (${addressSummary(toAddress)})` : addressSummary(toAddress); @@ -252,6 +309,75 @@ export default class ConfirmApproveContent extends Component { ); } + renderFullDetails() { + const { t } = this.context; + const { + assetStandard, + showEditApprovalPermissionModal, + customTokenAmount, + tokenAmount, + decimals, + origin, + setCustomAmount, + tokenSymbol, + tokenBalance, + } = this.props; + if (assetStandard === ERC20) { + return ( +
+
+ {this.renderApproveContentCard({ + symbol: , + title: t('permissionRequest'), + content: this.renderERC20PermissionContent(), + showEdit: true, + onEditClick: () => + showEditApprovalPermissionModal({ + customTokenAmount, + decimals, + origin, + setCustomAmount, + tokenAmount, + tokenSymbol, + tokenBalance, + }), + })} +
+
+ {this.renderApproveContentCard({ + symbol: , + title: 'Data', + content: this.renderDataContent(), + noBorder: true, + })} +
+
+ ); + } else if (assetStandard === ERC721 || assetStandard === ERC1155) { + return ( +
+
+ {this.renderApproveContentCard({ + symbol: , + title: t('permissionRequest'), + content: this.renderERC721OrERC1155PermissionContent(), + showEdit: false, + })} +
+
+ {this.renderApproveContentCard({ + symbol: , + title: t('data'), + content: this.renderDataContent(), + noBorder: true, + })} +
+
+ ); + } + return null; + } + renderCustomNonceContent() { const { t } = this.context; const { @@ -321,11 +447,13 @@ export default class ConfirmApproveContent extends Component { warning, txData, fromAddressIsLedger, - tokenImage, toAddress, chainId, rpcPrefs, isContract, + assetStandard, + tokenId, + assetName, } = this.props; const { showFullTxDetails } = this.state; @@ -368,7 +496,11 @@ export default class ConfirmApproveContent extends Component {
- {t('allowSpendToken', [tokenSymbol])} + {t('allowSpendToken', [ + assetStandard === ERC20 + ? tokenSymbol + : `${assetName} (#${tokenId})`, + ])}
{t('trustSiteApprovePermission', [ @@ -383,7 +515,6 @@ export default class ConfirmApproveContent extends Component { className="confirm-approve-content__address-identicon" diameter={20} address={toAddress} - image={tokenImage} /> -
-
- showEditApprovalPermissionModal({ - customTokenAmount, - decimals, - origin, - setCustomAmount, - tokenAmount, - tokenSymbol, - tokenBalance, - }) - } - > - {t('editPermission')} + {assetStandard === ERC20 ? ( +
+
+ showEditApprovalPermissionModal({ + customTokenAmount, + decimals, + origin, + setCustomAmount, + tokenAmount, + tokenSymbol, + tokenBalance, + }) + } + > + {t('editPermission')} +
-
+ ) : null}
{this.renderApproveContentCard({ symbol: , @@ -527,36 +660,7 @@ export default class ConfirmApproveContent extends Component {
) : null} - {showFullTxDetails ? ( -
-
- {this.renderApproveContentCard({ - symbol: , - title: t('permissionRequest'), - content: this.renderPermissionContent(), - showEdit: true, - onEditClick: () => - showEditApprovalPermissionModal({ - customTokenAmount, - decimals, - origin, - setCustomAmount, - tokenAmount, - tokenSymbol, - tokenBalance, - }), - })} -
-
- {this.renderApproveContentCard({ - symbol: , - title: 'Data', - content: this.renderDataContent(), - noBorder: true, - })} -
-
- ) : null} + {showFullTxDetails ? this.renderFullDetails() : null}
); } diff --git a/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.test.js b/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.test.js index 162332853..52ecdeece 100644 --- a/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.test.js +++ b/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.test.js @@ -2,6 +2,7 @@ import React from 'react'; import configureMockStore from 'redux-mock-store'; import { fireEvent } from '@testing-library/react'; import { renderWithProvider } from '../../../../test/jest/rendering'; +import { ERC20 } from '../../../helpers/constants/common'; import ConfirmApproveContent from '.'; const renderComponent = (props) => { @@ -16,6 +17,7 @@ const props = { tokenAmount: '10', origin: 'https://metamask.github.io/test-dapp/', tokenSymbol: 'TST', + assetStandard: ERC20, tokenImage: 'https://metamask.github.io/test-dapp/metamask-fox.svg', tokenBalance: '15', showCustomizeGasModal: jest.fn(), diff --git a/ui/pages/confirm-approve/confirm-approve.js b/ui/pages/confirm-approve/confirm-approve.js index 1a2663367..246baca77 100644 --- a/ui/pages/confirm-approve/confirm-approve.js +++ b/ui/pages/confirm-approve/confirm-approve.js @@ -1,6 +1,6 @@ import React, { useEffect, useRef, useState, useCallback } from 'react'; +import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; -import { useParams } from 'react-router-dom'; import ConfirmTransactionBase from '../confirm-transaction-base'; import { EDIT_GAS_MODES } from '../../../shared/constants/gas'; import { @@ -8,24 +8,15 @@ import { updateCustomNonce, getNextNonce, } from '../../store/actions'; -import { getTransactionData } from '../../helpers/utils/transactions.util'; -import { - calcTokenAmount, - getTokenAddressParam, - getTokenValueParam, -} from '../../helpers/utils/token-util'; +import { calcTokenAmount } from '../../helpers/utils/token-util'; import { readAddressAsContract } from '../../../shared/modules/contract-utils'; import { GasFeeContextProvider } from '../../contexts/gasFee'; import { TransactionModalContextProvider } from '../../contexts/transaction-modal'; -import { useTokenTracker } from '../../hooks/useTokenTracker'; import { - getTokens, getNativeCurrency, isAddressLedger, } from '../../ducks/metamask/metamask'; import { - transactionFeeSelector, - txDataSelector, getCurrentCurrency, getSubjectMetadata, getUseNonceField, @@ -38,12 +29,11 @@ import { getEIP1559V2Enabled, } from '../../selectors'; import { useApproveTransaction } from '../../hooks/useApproveTransaction'; -import { currentNetworkTxListSelector } from '../../selectors/transactions'; import AdvancedGasFeePopover from '../../components/app/advanced-gas-fee-popover'; import EditGasFeePopover from '../../components/app/edit-gas-fee-popover'; import EditGasPopover from '../../components/app/edit-gas-popover/edit-gas-popover.component'; import Loading from '../../components/ui/loading-screen'; -import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils'; +import { ERC20, ERC1155, ERC721 } from '../../helpers/constants/common'; import { getCustomTxParamsData } from './confirm-approve.util'; import ConfirmApproveContent from './confirm-approve-content'; @@ -51,19 +41,28 @@ const isAddressLedgerByFromAddress = (address) => (state) => { return isAddressLedger(state, address); }; -export default function ConfirmApprove() { +export default function ConfirmApprove({ + assetStandard, + assetName, + userBalance, + tokenSymbol, + decimals, + tokenImage, + tokenAmount, + tokenId, + userAddress, + toAddress, + transaction, + ethTransactionTotal, + fiatTransactionTotal, + hexTransactionTotal, +}) { const dispatch = useDispatch(); - const { id: paramsTransactionId } = useParams(); - const { - id: transactionId, - txParams: { to: tokenAddress, data, from } = {}, - } = useSelector(txDataSelector); + const { txParams: { data: transactionData } = {} } = transaction; const currentCurrency = useSelector(getCurrentCurrency); const nativeCurrency = useSelector(getNativeCurrency); - const currentNetworkTxList = useSelector(currentNetworkTxListSelector); const subjectMetadata = useSelector(getSubjectMetadata); - const tokens = useSelector(getTokens); const useNonceField = useSelector(getUseNonceField); const nextNonce = useSelector(getNextSuggestedNonce); const customNonceValue = useSelector(getCustomNonceValue); @@ -73,45 +72,17 @@ export default function ConfirmApprove() { const networkAndAccountSupports1559 = useSelector( checkNetworkAndAccountSupports1559, ); - - const fromAddressIsLedger = useSelector(isAddressLedgerByFromAddress(from)); - - const transaction = - currentNetworkTxList.find( - ({ id }) => id === (Number(paramsTransactionId) || transactionId), - ) || {}; - const { - ethTransactionTotal, - fiatTransactionTotal, - hexTransactionTotal, - } = useSelector((state) => transactionFeeSelector(state, transaction)); + const fromAddressIsLedger = useSelector( + isAddressLedgerByFromAddress(userAddress), + ); + const [customPermissionAmount, setCustomPermissionAmount] = useState(''); + const [submitWarning, setSubmitWarning] = useState(''); + const [isContract, setIsContract] = useState(false); const eip1559V2Enabled = useSelector(getEIP1559V2Enabled); const supportsEIP1559V2 = eip1559V2Enabled && networkAndAccountSupports1559; - const currentToken = (tokens && - tokens.find(({ address }) => - isEqualCaseInsensitive(tokenAddress, address), - )) || { - address: tokenAddress, - }; - - const { tokensWithBalances } = useTokenTracker([currentToken]); - const tokenTrackerBalance = tokensWithBalances[0]?.balance || ''; - - const tokenSymbol = currentToken?.symbol; - const decimals = Number(currentToken?.decimals); - const tokenImage = currentToken?.image; - const tokenData = getTransactionData(data); - const tokenValue = getTokenValueParam(tokenData); - const toAddress = getTokenAddressParam(tokenData); - const tokenAmount = - tokenData && calcTokenAmount(tokenValue, decimals).toString(10); - - const [customPermissionAmount, setCustomPermissionAmount] = useState(''); - const previousTokenAmount = useRef(tokenAmount); - const { approveTransaction, showCustomizeGasPopover, @@ -125,7 +96,6 @@ export default function ConfirmApprove() { previousTokenAmount.current = tokenAmount; }, [customPermissionAmount, tokenAmount]); - const [submitWarning, setSubmitWarning] = useState(''); const prevNonce = useRef(nextNonce); const prevCustomNonce = useRef(customNonceValue); useEffect(() => { @@ -145,7 +115,6 @@ export default function ConfirmApprove() { prevNonce.current = nextNonce; }, [customNonceValue, nextNonce]); - const [isContract, setIsContract] = useState(false); const checkIfContract = useCallback(async () => { const { isContractAddress } = await readAddressAsContract( global.eth, @@ -153,6 +122,7 @@ export default function ConfirmApprove() { ); setIsContract(isContractAddress); }, [setIsContract, toAddress]); + useEffect(() => { checkIfContract(); }, [checkIfContract]); @@ -162,21 +132,30 @@ export default function ConfirmApprove() { const { iconUrl: siteImage = '' } = subjectMetadata[origin] || {}; - const tokensText = `${Number(tokenAmount)} ${tokenSymbol}`; - const tokenBalance = tokenTrackerBalance - ? calcTokenAmount(tokenTrackerBalance, decimals).toString(10) + let tokensText; + if (assetStandard === ERC20) { + tokensText = `${Number(tokenAmount)} ${tokenSymbol}`; + } else if (assetStandard === ERC721 || assetStandard === ERC1155) { + tokensText = assetName; + } + + const tokenBalance = userBalance + ? calcTokenAmount(userBalance, decimals).toString(10) : ''; const customData = customPermissionAmount - ? getCustomTxParamsData(data, { customPermissionAmount, decimals }) + ? getCustomTxParamsData(transactionData, { + customPermissionAmount, + decimals, + }) : null; - return tokenSymbol === undefined ? ( + return tokenSymbol === undefined && assetName === undefined ? ( ) : ( ); } + +ConfirmApprove.propTypes = { + assetStandard: PropTypes.string, + assetName: PropTypes.string, + userBalance: PropTypes.string, + tokenSymbol: PropTypes.string, + decimals: PropTypes.string, + tokenImage: PropTypes.string, + tokenAmount: PropTypes.string, + tokenId: PropTypes.string, + userAddress: PropTypes.string, + toAddress: PropTypes.string, + transaction: PropTypes.shape({ + origin: PropTypes.string, + txParams: PropTypes.shape({ + data: PropTypes.string, + to: PropTypes.string, + from: PropTypes.string, + }), + }), + ethTransactionTotal: PropTypes.string, + fiatTransactionTotal: PropTypes.string, + hexTransactionTotal: PropTypes.string, +}; diff --git a/ui/pages/confirm-send-token/confirm-send-token.js b/ui/pages/confirm-send-token/confirm-send-token.js new file mode 100644 index 000000000..a03fb9835 --- /dev/null +++ b/ui/pages/confirm-send-token/confirm-send-token.js @@ -0,0 +1,118 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { useDispatch, useSelector } from 'react-redux'; +import { useHistory } from 'react-router-dom'; +import ConfirmTokenTransactionBase from '../confirm-token-transaction-base/confirm-token-transaction-base'; +import { SEND_ROUTE } from '../../helpers/constants/routes'; +import { ASSET_TYPES, editTransaction } from '../../ducks/send'; +import { + contractExchangeRateSelector, + getCurrentCurrency, +} from '../../selectors'; +import { + getConversionRate, + getNativeCurrency, +} from '../../ducks/metamask/metamask'; +import { ERC20, ERC721 } from '../../helpers/constants/common'; +import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck'; +import { showSendTokenPage } from '../../store/actions'; + +export default function ConfirmSendToken({ + assetStandard, + toAddress, + tokenAddress, + assetName, + tokenSymbol, + tokenAmount, + tokenId, + transaction, + image, + ethTransactionTotal, + fiatTransactionTotal, + hexMaximumTransactionFee, +}) { + const dispatch = useDispatch(); + const history = useHistory(); + + const handleEditTransaction = ({ + txData, + tokenData, + tokenProps: assetDetails, + }) => { + const { id } = txData; + dispatch( + editTransaction( + ASSET_TYPES.TOKEN, + id.toString(), + tokenData, + assetDetails, + ), + ); + dispatch(clearConfirmTransaction()); + dispatch(showSendTokenPage()); + }; + + const handleEdit = (confirmTransactionData) => { + handleEditTransaction(confirmTransactionData); + history.push(SEND_ROUTE); + }; + const conversionRate = useSelector(getConversionRate); + const nativeCurrency = useSelector(getNativeCurrency); + const currentCurrency = useSelector(getCurrentCurrency); + const contractExchangeRate = useSelector(contractExchangeRateSelector); + + let title, subtitle; + + if (assetStandard === ERC721) { + title = assetName; + subtitle = `#${tokenId}`; + } else if (assetStandard === ERC20) { + title = `${tokenAmount} ${tokenSymbol}`; + } + + return ( + + ); +} + +ConfirmSendToken.propTypes = { + tokenAmount: PropTypes.string, + assetStandard: PropTypes.string, + assetName: PropTypes.string, + tokenSymbol: PropTypes.string, + image: PropTypes.string, + tokenId: PropTypes.string, + toAddress: PropTypes.string, + tokenAddress: PropTypes.string, + transaction: PropTypes.shape({ + origin: PropTypes.string, + txParams: PropTypes.shape({ + data: PropTypes.string, + to: PropTypes.string, + from: PropTypes.string, + }), + }), + ethTransactionTotal: PropTypes.string, + fiatTransactionTotal: PropTypes.string, + hexMaximumTransactionFee: PropTypes.string, +}; diff --git a/ui/pages/confirm-send-token/index.js b/ui/pages/confirm-send-token/index.js index 25067ca43..3bf86d319 100644 --- a/ui/pages/confirm-send-token/index.js +++ b/ui/pages/confirm-send-token/index.js @@ -1 +1 @@ -export { default } from './confirm-send-token.container'; +export { default } from './confirm-send-token'; diff --git a/ui/pages/confirm-token-transaction-base/confirm-token-transaction-base.container.js b/ui/pages/confirm-token-transaction-base/confirm-token-transaction-base.container.js deleted file mode 100644 index 5ab40cd65..000000000 --- a/ui/pages/confirm-token-transaction-base/confirm-token-transaction-base.container.js +++ /dev/null @@ -1,112 +0,0 @@ -import { connect } from 'react-redux'; -import { compose } from 'redux'; -import { withRouter } from 'react-router-dom'; -import { - contractExchangeRateSelector, - transactionFeeSelector, -} from '../../selectors'; -import { getCollectibles, getTokens } from '../../ducks/metamask/metamask'; -import { getTransactionData } from '../../helpers/utils/transactions.util'; -import { - calcTokenAmount, - getTokenAddressParam, - getTokenValueParam, -} from '../../helpers/utils/token-util'; -import { hexWEIToDecETH } from '../../helpers/utils/conversions.util'; -import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils'; -import ConfirmTokenTransactionBase from './confirm-token-transaction-base.component'; - -const mapStateToProps = (state, ownProps) => { - const { - match: { params = {} }, - } = ownProps; - const { id: paramsTransactionId } = params; - const { - confirmTransaction, - metamask: { - currentCurrency, - conversionRate, - currentNetworkTxList, - nativeCurrency, - }, - } = state; - - const { - txData: { - id: transactionId, - txParams: { to: tokenAddress, data } = {}, - } = {}, - } = confirmTransaction; - - const transaction = - currentNetworkTxList.find( - ({ id }) => id === (Number(paramsTransactionId) || transactionId), - ) || {}; - - const { - ethTransactionTotal, - fiatTransactionTotal, - hexMaximumTransactionFee, - } = transactionFeeSelector(state, transaction); - const tokens = getTokens(state); - const collectibles = getCollectibles(state); - - const transactionData = getTransactionData(data); - const toAddress = getTokenAddressParam(transactionData); - const tokenAmountOrTokenId = getTokenValueParam(transactionData); - const ethTransactionTotalMaxAmount = Number( - hexWEIToDecETH(hexMaximumTransactionFee), - ).toFixed(6); - - const currentToken = tokens?.find(({ address }) => - isEqualCaseInsensitive(tokenAddress, address), - ); - const currentCollectible = collectibles?.find( - ({ address, tokenId }) => - isEqualCaseInsensitive(tokenAddress, address) && - tokenId === tokenAmountOrTokenId, - ); - - let image, - tokenId, - collectibleName, - tokenAmount, - contractExchangeRate, - title, - subtitle; - - if (currentCollectible) { - ({ image, tokenId, name: collectibleName } = currentCollectible || {}); - - title = collectibleName; - subtitle = `#${tokenId}`; - } else if (currentToken) { - const { decimals, symbol: tokenSymbol } = currentToken || {}; - tokenAmount = - transactionData && - calcTokenAmount(tokenAmountOrTokenId, decimals).toFixed(); - contractExchangeRate = contractExchangeRateSelector(state); - title = `${tokenAmount} ${tokenSymbol}`; - } - - return { - title, - subtitle, - image, - toAddress, - tokenAddress, - tokenAmount, - currentCurrency, - conversionRate, - contractExchangeRate, - fiatTransactionTotal, - ethTransactionTotal, - ethTransactionTotalMaxAmount, - nativeCurrency, - }; -}; - -export default compose( - withRouter, - connect(mapStateToProps), -)(ConfirmTokenTransactionBase); diff --git a/ui/pages/confirm-token-transaction-base/confirm-token-transaction-base.component.js b/ui/pages/confirm-token-transaction-base/confirm-token-transaction-base.js similarity index 70% rename from ui/pages/confirm-token-transaction-base/confirm-token-transaction-base.component.js rename to ui/pages/confirm-token-transaction-base/confirm-token-transaction-base.js index 99f22d3c5..edacfaa8a 100644 --- a/ui/pages/confirm-token-transaction-base/confirm-token-transaction-base.component.js +++ b/ui/pages/confirm-token-transaction-base/confirm-token-transaction-base.js @@ -1,6 +1,7 @@ import React, { useContext, useMemo } from 'react'; import PropTypes from 'prop-types'; import BigNumber from 'bignumber.js'; +import { useSelector } from 'react-redux'; import { I18nContext } from '../../contexts/i18n'; import ConfirmTransactionBase from '../confirm-transaction-base'; import UserPreferencedCurrencyDisplay from '../../components/app/user-preferenced-currency-display'; @@ -10,26 +11,57 @@ import { addFiat, roundExponential, } from '../../helpers/utils/confirm-tx.util'; -import { getWeiHexFromDecimalValue } from '../../helpers/utils/conversions.util'; -import { ETH, PRIMARY } from '../../helpers/constants/common'; +import { + getWeiHexFromDecimalValue, + hexWEIToDecETH, +} from '../../helpers/utils/conversions.util'; +import { + ERC1155, + ERC20, + ERC721, + ETH, + PRIMARY, +} from '../../helpers/constants/common'; +import { + contractExchangeRateSelector, + getCurrentCurrency, +} from '../../selectors'; +import { + getConversionRate, + getNativeCurrency, +} from '../../ducks/metamask/metamask'; export default function ConfirmTokenTransactionBase({ - image, - title, - subtitle, + image = '', + assetName, toAddress, tokenAddress, tokenAmount = '0', - fiatTransactionTotal, - ethTransactionTotal, - ethTransactionTotalMaxAmount, - contractExchangeRate, - conversionRate, - currentCurrency, - nativeCurrency, + tokenSymbol, + tokenId, + assetStandard, onEdit, + ethTransactionTotal, + fiatTransactionTotal, + hexMaximumTransactionFee, }) { const t = useContext(I18nContext); + const contractExchangeRate = useSelector(contractExchangeRateSelector); + const nativeCurrency = useSelector(getNativeCurrency); + const currentCurrency = useSelector(getCurrentCurrency); + const conversionRate = useSelector(getConversionRate); + + const ethTransactionTotalMaxAmount = Number( + hexWEIToDecETH(hexMaximumTransactionFee), + ); + + let title, subtitle; + if (assetStandard === ERC721 || assetStandard === ERC1155) { + title = assetName; + subtitle = `#${tokenId}`; + } else if (assetStandard === ERC20) { + title = `${tokenAmount} ${tokenSymbol}`; + } const hexWeiValue = useMemo(() => { if (tokenAmount === '0' || !contractExchangeRate) { @@ -105,17 +137,15 @@ export default function ConfirmTokenTransactionBase({ ConfirmTokenTransactionBase.propTypes = { image: PropTypes.string, - title: PropTypes.string, - subtitle: PropTypes.string, - tokenAddress: PropTypes.string, + assetName: PropTypes.string, toAddress: PropTypes.string, + tokenAddress: PropTypes.string, tokenAmount: PropTypes.string, - fiatTransactionTotal: PropTypes.string, - ethTransactionTotal: PropTypes.string, - contractExchangeRate: PropTypes.number, - conversionRate: PropTypes.number, - currentCurrency: PropTypes.string, + tokenSymbol: PropTypes.string, + tokenId: PropTypes.string, + assetStandard: PropTypes.string, onEdit: PropTypes.func, - nativeCurrency: PropTypes.string, - ethTransactionTotalMaxAmount: PropTypes.string, + ethTransactionTotal: PropTypes.string, + fiatTransactionTotal: PropTypes.string, + hexMaximumTransactionFee: PropTypes.string, }; diff --git a/ui/pages/confirm-token-transaction-base/confirm-token-transaction-base.stories.js b/ui/pages/confirm-token-transaction-base/confirm-token-transaction-base.stories.js index ffe86e3ee..b1d5cf9f3 100644 --- a/ui/pages/confirm-token-transaction-base/confirm-token-transaction-base.stories.js +++ b/ui/pages/confirm-token-transaction-base/confirm-token-transaction-base.stories.js @@ -1,6 +1,6 @@ import React from 'react'; import { store } from '../../../.storybook/preview'; -import ConfirmTokenTransactionBase from './confirm-token-transaction-base.component'; +import ConfirmTokenTransactionBase from './confirm-token-transaction-base'; export default { title: 'Pages/ConfirmTokenTransactionBase', diff --git a/ui/pages/confirm-token-transaction-base/index.js b/ui/pages/confirm-token-transaction-base/index.js index e5b6df031..9c9fb78c4 100644 --- a/ui/pages/confirm-token-transaction-base/index.js +++ b/ui/pages/confirm-token-transaction-base/index.js @@ -1,2 +1 @@ -export { default } from './confirm-token-transaction-base.container'; -export { default as ConfirmTokenTransactionBase } from './confirm-token-transaction-base.component'; +export { default } from './confirm-token-transaction-base'; diff --git a/ui/pages/confirm-transaction-base/confirm-transaction-base.container.js b/ui/pages/confirm-transaction-base/confirm-transaction-base.container.js index 6a2313c97..e5c325242 100644 --- a/ui/pages/confirm-transaction-base/confirm-transaction-base.container.js +++ b/ui/pages/confirm-transaction-base/confirm-transaction-base.container.js @@ -174,7 +174,7 @@ const mapStateToProps = (state, ownProps) => { } const isCollectibleTransfer = Boolean( - allCollectibleContracts?.[selectedAddress]?.[chainId].find((contract) => { + allCollectibleContracts?.[selectedAddress]?.[chainId]?.find((contract) => { return isEqualCaseInsensitive(contract.address, fullTxData.txParams.to); }), ); diff --git a/ui/pages/confirm-transaction/confirm-token-transaction-switch.js b/ui/pages/confirm-transaction/confirm-token-transaction-switch.js new file mode 100644 index 000000000..e6a89996b --- /dev/null +++ b/ui/pages/confirm-transaction/confirm-token-transaction-switch.js @@ -0,0 +1,125 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { useSelector } from 'react-redux'; +import { Switch, Route } from 'react-router-dom'; +import { + CONFIRM_APPROVE_PATH, + CONFIRM_SEND_TOKEN_PATH, + CONFIRM_TRANSACTION_ROUTE, + CONFIRM_TRANSFER_FROM_PATH, +} from '../../helpers/constants/routes'; +import { transactionFeeSelector } from '../../selectors'; +import ConfirmApprove from '../confirm-approve'; +import ConfirmSendToken from '../confirm-send-token'; +import ConfirmTokenTransactionBase from '../confirm-token-transaction-base'; +import ConfirmTransactionSwitch from '../confirm-transaction-switch'; + +import { useAssetDetails } from '../../hooks/useAssetDetails'; + +export default function ConfirmTokenTransactionSwitch({ transaction }) { + const { + txParams: { data, to: tokenAddress, from: userAddress } = {}, + } = transaction; + + const { + assetStandard, + assetName, + userBalance, + tokenSymbol, + decimals, + tokenImage, + tokenAmount, + tokenId, + toAddress, + } = useAssetDetails(tokenAddress, userAddress, data); + + const { + ethTransactionTotal, + fiatTransactionTotal, + hexTransactionTotal, + hexMaximumTransactionFee, + } = useSelector((state) => transactionFeeSelector(state, transaction)); + + return ( + + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + + + ); +} + +ConfirmTokenTransactionSwitch.propTypes = { + transaction: PropTypes.shape({ + origin: PropTypes.string, + txParams: PropTypes.shape({ + data: PropTypes.string, + to: PropTypes.string, + from: PropTypes.string, + }), + }), +}; diff --git a/ui/pages/confirm-transaction/confirm-transaction.component.js b/ui/pages/confirm-transaction/confirm-transaction.component.js index d28904544..01e876fdb 100644 --- a/ui/pages/confirm-transaction/confirm-transaction.component.js +++ b/ui/pages/confirm-transaction/confirm-transaction.component.js @@ -5,10 +5,7 @@ import Loading from '../../components/ui/loading-screen'; import ConfirmTransactionSwitch from '../confirm-transaction-switch'; import ConfirmTransactionBase from '../confirm-transaction-base'; import ConfirmSendEther from '../confirm-send-ether'; -import ConfirmSendToken from '../confirm-send-token'; import ConfirmDeployContract from '../confirm-deploy-contract'; -import ConfirmApprove from '../confirm-approve'; -import ConfirmTokenTransactionBaseContainer from '../confirm-token-transaction-base'; import ConfirmDecryptMessage from '../confirm-decrypt-message'; import ConfirmEncryptionPublicKey from '../confirm-encryption-public-key'; @@ -16,9 +13,6 @@ import { CONFIRM_TRANSACTION_ROUTE, CONFIRM_DEPLOY_CONTRACT_PATH, CONFIRM_SEND_ETHER_PATH, - CONFIRM_SEND_TOKEN_PATH, - CONFIRM_APPROVE_PATH, - CONFIRM_TRANSFER_FROM_PATH, CONFIRM_TOKEN_METHOD_PATH, SIGNATURE_REQUEST_PATH, DECRYPT_MESSAGE_REQUEST_PATH, @@ -31,6 +25,7 @@ import { addPollingTokenToAppState, removePollingTokenFromAppState, } from '../../store/actions'; +import ConfirmTokenTransactionSwitch from './confirm-token-transaction-switch'; import ConfTx from './conf-tx'; export default class ConfirmTransaction extends Component { @@ -49,7 +44,6 @@ export default class ConfirmTransaction extends Component { getContractMethodData: PropTypes.func, transactionId: PropTypes.string, paramsTransactionId: PropTypes.string, - getTokenParams: PropTypes.func, isTokenMethodAction: PropTypes.bool, setDefaultHomeActiveTabName: PropTypes.func, }; @@ -74,12 +68,10 @@ export default class ConfirmTransaction extends Component { sendTo, history, mostRecentOverviewPage, - transaction: { txParams: { data, to } = {} } = {}, + transaction: { txParams: { data } = {} } = {}, getContractMethodData, transactionId, paramsTransactionId, - getTokenParams, - isTokenMethodAction, } = this.props; getGasFeeEstimatesAndStartPolling().then((pollingToken) => { @@ -100,9 +92,7 @@ export default class ConfirmTransaction extends Component { } getContractMethodData(data); - if (isTokenMethodAction) { - getTokenParams(to); - } + const txId = transactionId || paramsTransactionId; if (txId) { this.props.setTransactionToConfirm(txId); @@ -154,23 +144,30 @@ export default class ConfirmTransaction extends Component { } render() { - const { transactionId, paramsTransactionId } = this.props; + const { + transactionId, + paramsTransactionId, + isTokenMethodAction, + transaction, + } = this.props; + + const validTransactionId = + transactionId && + (!paramsTransactionId || paramsTransactionId === transactionId); + + if (isTokenMethodAction && validTransactionId) { + return ; + } // Show routes when state.confirmTransaction has been set and when either the ID in the params // isn't specified or is specified and matches the ID in state.confirmTransaction in order to // support URLs of /confirm-transaction or /confirm-transaction/ - return transactionId && - (!paramsTransactionId || paramsTransactionId === transactionId) ? ( + return validTransactionId ? ( - - - { }, clearConfirmTransaction: () => dispatch(clearConfirmTransaction()), getContractMethodData: (data) => dispatch(getContractMethodData(data)), - getTokenParams: (tokenAddress) => dispatch(getTokenParams(tokenAddress)), setDefaultHomeActiveTabName: (tabName) => dispatch(setDefaultHomeActiveTabName(tabName)), }; diff --git a/ui/store/actions.js b/ui/store/actions.js index 6d504bca6..698508883 100644 --- a/ui/store/actions.js +++ b/ui/store/actions.js @@ -8,7 +8,6 @@ import { loadRelativeTimeFormatLocaleData, } from '../helpers/utils/i18n-helper'; import { getMethodDataAsync } from '../helpers/utils/transactions.util'; -import { getSymbolAndDecimals } from '../helpers/utils/token-util'; import switchDirection from '../helpers/utils/switch-direction'; import { ENVIRONMENT_TYPE_NOTIFICATION, @@ -22,7 +21,6 @@ import { getMetaMaskAccounts, getPermittedAccountsForCurrentTab, getSelectedAddress, - getTokenList, } from '../selectors'; import { computeEstimatedGasLimit, resetSendState } from '../ducks/send'; import { switchedToUnconnectedAccount } from '../ducks/alerts/unconnected-account'; @@ -2871,46 +2869,6 @@ export function loadingTokenParamsFinished() { }; } -export function getTokenParams(address) { - return (dispatch, getState) => { - const tokenList = getTokenList(getState()); - const existingTokens = getState().metamask.tokens; - const { selectedAddress } = getState().metamask; - const { chainId } = getState().metamask.provider; - const existingCollectibles = getState().metamask?.allCollectibles?.[ - selectedAddress - ]?.[chainId]; - const existingToken = existingTokens.find(({ address: tokenAddress }) => - isEqualCaseInsensitive(address, tokenAddress), - ); - const existingCollectible = existingCollectibles?.find( - ({ address: collectibleAddress }) => - isEqualCaseInsensitive(address, collectibleAddress), - ); - - if (existingCollectible) { - return null; - } - - if (existingToken) { - return Promise.resolve({ - symbol: existingToken.symbol, - decimals: existingToken.decimals, - }); - } - - dispatch(loadingTokenParamsStarted()); - log.debug(`loadingTokenParams`); - - return getSymbolAndDecimals(address, tokenList).then( - ({ symbol, decimals }) => { - dispatch(addToken(address, symbol, Number(decimals))); - dispatch(loadingTokenParamsFinished()); - }, - ); - }; -} - export function setSeedPhraseBackedUp(seedPhraseBackupState) { return (dispatch) => { log.debug(`background.setSeedPhraseBackedUp`);