mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Refactor token send/method confirmation flow (trimmed down) (#13788)
* make use of getTokenStandardAndDetails method exposed on assetsContractController to determine how to represent the contract being interacted with in token contract method calls
This commit is contained in:
parent
bc62e77cc3
commit
3747ace06b
7
app/_locales/en/messages.json
generated
7
app/_locales/en/messages.json
generated
@ -58,6 +58,10 @@
|
|||||||
"message": "$1 may access and spend up to this max amount",
|
"message": "$1 may access and spend up to this max amount",
|
||||||
"description": "$1 is the url of the site requesting ability to spend"
|
"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": {
|
"accessingYourCamera": {
|
||||||
"message": "Accessing your camera..."
|
"message": "Accessing your camera..."
|
||||||
},
|
},
|
||||||
@ -279,6 +283,9 @@
|
|||||||
"approvedAmountWithColon": {
|
"approvedAmountWithColon": {
|
||||||
"message": "Approved amount:"
|
"message": "Approved amount:"
|
||||||
},
|
},
|
||||||
|
"approvedAsset": {
|
||||||
|
"message": "Approved asset"
|
||||||
|
},
|
||||||
"areYouDeveloper": {
|
"areYouDeveloper": {
|
||||||
"message": "Are you a developer?"
|
"message": "Are you a developer?"
|
||||||
},
|
},
|
||||||
|
@ -1271,7 +1271,6 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
appStateController,
|
appStateController,
|
||||||
collectiblesController,
|
collectiblesController,
|
||||||
collectibleDetectionController,
|
collectibleDetectionController,
|
||||||
assetsContractController,
|
|
||||||
currencyRateController,
|
currencyRateController,
|
||||||
detectTokensController,
|
detectTokensController,
|
||||||
ensController,
|
ensController,
|
||||||
@ -1427,9 +1426,7 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
setTheme: preferencesController.setTheme.bind(preferencesController),
|
setTheme: preferencesController.setTheme.bind(preferencesController),
|
||||||
|
|
||||||
// AssetsContractController
|
// AssetsContractController
|
||||||
getTokenStandardAndDetails: assetsContractController.getTokenStandardAndDetails.bind(
|
getTokenStandardAndDetails: this.getTokenStandardAndDetails.bind(this),
|
||||||
assetsContractController,
|
|
||||||
),
|
|
||||||
|
|
||||||
// CollectiblesController
|
// CollectiblesController
|
||||||
addCollectible: collectiblesController.addCollectible.bind(
|
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
|
// VAULT / KEYRING RELATED METHODS
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
@ -16,6 +16,7 @@ const { ensureXServerIsRunning } = require('./x-server');
|
|||||||
const tinyDelayMs = 200;
|
const tinyDelayMs = 200;
|
||||||
const regularDelayMs = tinyDelayMs * 2;
|
const regularDelayMs = tinyDelayMs * 2;
|
||||||
const largeDelayMs = regularDelayMs * 2;
|
const largeDelayMs = regularDelayMs * 2;
|
||||||
|
const veryLargeDelayMs = largeDelayMs * 2;
|
||||||
const dappPort = 8080;
|
const dappPort = 8080;
|
||||||
|
|
||||||
const convertToHexValue = (val) => `0x${new BigNumber(val, 10).toString(16)}`;
|
const convertToHexValue = (val) => `0x${new BigNumber(val, 10).toString(16)}`;
|
||||||
@ -276,6 +277,7 @@ module.exports = {
|
|||||||
tinyDelayMs,
|
tinyDelayMs,
|
||||||
regularDelayMs,
|
regularDelayMs,
|
||||||
largeDelayMs,
|
largeDelayMs,
|
||||||
|
veryLargeDelayMs,
|
||||||
withFixtures,
|
withFixtures,
|
||||||
connectDappWithExtensionPopup,
|
connectDappWithExtensionPopup,
|
||||||
completeImportSRPOnboardingFlow,
|
completeImportSRPOnboardingFlow,
|
||||||
|
@ -3,7 +3,12 @@ const path = require('path');
|
|||||||
|
|
||||||
const enLocaleMessages = require('../../app/_locales/en/messages.json');
|
const enLocaleMessages = require('../../app/_locales/en/messages.json');
|
||||||
const createStaticServer = require('../../development/create-static-server');
|
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 { buildWebDriver } = require('./webdriver');
|
||||||
const Ganache = require('./ganache');
|
const Ganache = require('./ganache');
|
||||||
const { ensureXServerIsRunning } = require('./x-server');
|
const { ensureXServerIsRunning } = require('./x-server');
|
||||||
@ -272,7 +277,7 @@ describe('MetaMask', function () {
|
|||||||
const gasPriceInput = inputs[1];
|
const gasPriceInput = inputs[1];
|
||||||
await gasLimitInput.fill('4700000');
|
await gasLimitInput.fill('4700000');
|
||||||
await gasPriceInput.fill('20');
|
await gasPriceInput.fill('20');
|
||||||
await driver.delay(1000);
|
await driver.delay(veryLargeDelayMs);
|
||||||
await driver.clickElement({ text: 'Save', tag: 'button' });
|
await driver.clickElement({ text: 'Save', tag: 'button' });
|
||||||
await driver.clickElement({ text: 'Confirm', tag: 'button' });
|
await driver.clickElement({ text: 'Confirm', tag: 'button' });
|
||||||
|
|
||||||
@ -343,10 +348,11 @@ describe('MetaMask', function () {
|
|||||||
// Continue to next screen
|
// Continue to next screen
|
||||||
await driver.delay(largeDelayMs);
|
await driver.delay(largeDelayMs);
|
||||||
await driver.clickElement({ text: 'Next', tag: 'button' });
|
await driver.clickElement({ text: 'Next', tag: 'button' });
|
||||||
await driver.delay(regularDelayMs);
|
await driver.delay(largeDelayMs);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('displays the token transfer data', async function () {
|
it('displays the token transfer data', async function () {
|
||||||
|
await driver.delay(largeDelayMs);
|
||||||
await driver.clickElement({ text: 'Hex', tag: 'button' });
|
await driver.clickElement({ text: 'Hex', tag: 'button' });
|
||||||
await driver.delay(regularDelayMs);
|
await driver.delay(regularDelayMs);
|
||||||
|
|
||||||
@ -386,7 +392,7 @@ describe('MetaMask', function () {
|
|||||||
const gasPriceInput = inputs[1];
|
const gasPriceInput = inputs[1];
|
||||||
await gasLimitInput.fill('100000');
|
await gasLimitInput.fill('100000');
|
||||||
await gasPriceInput.fill('100');
|
await gasPriceInput.fill('100');
|
||||||
await driver.delay(1000);
|
await driver.delay(veryLargeDelayMs);
|
||||||
await driver.clickElement({ text: 'Save', tag: 'button' });
|
await driver.clickElement({ text: 'Save', tag: 'button' });
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -449,19 +455,20 @@ describe('MetaMask', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('customizes gas', async function () {
|
it('customizes gas', async function () {
|
||||||
|
await driver.delay(veryLargeDelayMs);
|
||||||
await driver.clickElement({ text: 'Edit', tag: 'button' });
|
await driver.clickElement({ text: 'Edit', tag: 'button' });
|
||||||
await driver.delay(largeDelayMs);
|
await driver.delay(veryLargeDelayMs);
|
||||||
await driver.clickElement(
|
await driver.clickElement(
|
||||||
{ text: 'Edit suggested gas fee', tag: 'button' },
|
{ text: 'Edit suggested gas fee', tag: 'button' },
|
||||||
10000,
|
10000,
|
||||||
);
|
);
|
||||||
await driver.delay(1000);
|
await driver.delay(veryLargeDelayMs);
|
||||||
const inputs = await driver.findElements('input[type="number"]');
|
const inputs = await driver.findElements('input[type="number"]');
|
||||||
const gasLimitInput = inputs[0];
|
const gasLimitInput = inputs[0];
|
||||||
const gasPriceInput = inputs[1];
|
const gasPriceInput = inputs[1];
|
||||||
await gasLimitInput.fill('60000');
|
await gasLimitInput.fill('60000');
|
||||||
await gasPriceInput.fill('10');
|
await gasPriceInput.fill('10');
|
||||||
await driver.delay(1000);
|
await driver.delay(veryLargeDelayMs);
|
||||||
await driver.clickElement({ text: 'Save', tag: 'button' });
|
await driver.clickElement({ text: 'Save', tag: 'button' });
|
||||||
await driver.findElement({ tag: 'span', text: '0.0006' });
|
await driver.findElement({ tag: 'span', text: '0.0006' });
|
||||||
});
|
});
|
||||||
@ -586,7 +593,7 @@ describe('MetaMask', function () {
|
|||||||
|
|
||||||
await gasLimitInput.fill('60001');
|
await gasLimitInput.fill('60001');
|
||||||
|
|
||||||
await driver.delay(1000);
|
await driver.delay(veryLargeDelayMs);
|
||||||
|
|
||||||
await driver.clickElement({ text: 'Save', tag: 'button' });
|
await driver.clickElement({ text: 'Save', tag: 'button' });
|
||||||
|
|
||||||
@ -752,7 +759,7 @@ describe('MetaMask', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('submits the transaction', async function () {
|
it('submits the transaction', async function () {
|
||||||
await driver.delay(1000);
|
await driver.delay(veryLargeDelayMs);
|
||||||
await driver.clickElement({ text: 'Confirm', tag: 'button' });
|
await driver.clickElement({ text: 'Confirm', tag: 'button' });
|
||||||
await driver.delay(regularDelayMs);
|
await driver.delay(regularDelayMs);
|
||||||
});
|
});
|
||||||
|
@ -11,6 +11,8 @@ import useAddressDetails from '../../../../../hooks/useAddressDetails';
|
|||||||
import Identicon from '../../../../ui/identicon';
|
import Identicon from '../../../../ui/identicon';
|
||||||
import InfoTooltip from '../../../../ui/info-tooltip';
|
import InfoTooltip from '../../../../ui/info-tooltip';
|
||||||
import NicknamePopovers from '../../../modals/nickname-popovers';
|
import NicknamePopovers from '../../../modals/nickname-popovers';
|
||||||
|
import Typography from '../../../../ui/typography';
|
||||||
|
import { TYPOGRAPHY } from '../../../../../helpers/constants/design-system';
|
||||||
|
|
||||||
const ConfirmPageContainerSummary = (props) => {
|
const ConfirmPageContainerSummary = (props) => {
|
||||||
const {
|
const {
|
||||||
@ -116,9 +118,15 @@ const ConfirmPageContainerSummary = (props) => {
|
|||||||
<div className="confirm-page-container-summary__title">
|
<div className="confirm-page-container-summary__title">
|
||||||
{renderImage()}
|
{renderImage()}
|
||||||
{!hideTitle ? (
|
{!hideTitle ? (
|
||||||
<div className="confirm-page-container-summary__title-text">
|
<Typography
|
||||||
|
className="confirm-page-container-summary__title-text"
|
||||||
|
variant={
|
||||||
|
title && title.length < 10 ? TYPOGRAPHY.H1 : TYPOGRAPHY.H3
|
||||||
|
}
|
||||||
|
title={title}
|
||||||
|
>
|
||||||
{titleComponent || title}
|
{titleComponent || title}
|
||||||
</div>
|
</Typography>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{hideSubtitle ? null : (
|
{hideSubtitle ? null : (
|
||||||
|
@ -77,6 +77,14 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__title-text-long {
|
||||||
|
@include H3;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
&__subtitle {
|
&__subtitle {
|
||||||
@include H5;
|
@include H5;
|
||||||
|
|
||||||
|
@ -82,6 +82,7 @@ export default function Typography({
|
|||||||
fontStyle = 'normal',
|
fontStyle = 'normal',
|
||||||
align,
|
align,
|
||||||
overflowWrap,
|
overflowWrap,
|
||||||
|
title,
|
||||||
tag,
|
tag,
|
||||||
margin = [1, 0],
|
margin = [1, 0],
|
||||||
boxProps = {},
|
boxProps = {},
|
||||||
@ -117,7 +118,10 @@ export default function Typography({
|
|||||||
return (
|
return (
|
||||||
<Box margin={margin} {...boxProps}>
|
<Box margin={margin} {...boxProps}>
|
||||||
{(boxClassName) => (
|
{(boxClassName) => (
|
||||||
<Tag className={classnames(boxClassName, computedClassName)}>
|
<Tag
|
||||||
|
className={classnames(boxClassName, computedClassName)}
|
||||||
|
title={title}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</Tag>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
@ -175,6 +179,10 @@ Typography.propTypes = {
|
|||||||
* Additional className to assign the Typography component
|
* Additional className to assign the Typography component
|
||||||
*/
|
*/
|
||||||
className: PropTypes.string,
|
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
|
* The text content of the Typography component
|
||||||
*/
|
*/
|
||||||
|
@ -4,8 +4,12 @@ import {
|
|||||||
conversionUtil,
|
conversionUtil,
|
||||||
multiplyCurrencies,
|
multiplyCurrencies,
|
||||||
} from '../../../shared/modules/conversion.utils';
|
} 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 * as util from './util';
|
||||||
import { formatCurrency } from './confirm-tx.util';
|
import { formatCurrency } from './confirm-tx.util';
|
||||||
|
import { getTransactionData } from './transactions.util';
|
||||||
|
|
||||||
const DEFAULT_SYMBOL = '';
|
const DEFAULT_SYMBOL = '';
|
||||||
|
|
||||||
@ -212,3 +216,48 @@ export function getTokenFiatAmount(
|
|||||||
}
|
}
|
||||||
return result;
|
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 {};
|
||||||
|
}
|
||||||
|
118
ui/hooks/useAssetDetails.js
Normal file
118
ui/hooks/useAssetDetails.js
Normal file
@ -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,
|
||||||
|
};
|
||||||
|
}
|
@ -28,6 +28,7 @@ import { SECOND } from '../../../../shared/constants/time';
|
|||||||
import { ConfirmPageContainerWarning } from '../../../components/app/confirm-page-container/confirm-page-container-content';
|
import { ConfirmPageContainerWarning } from '../../../components/app/confirm-page-container/confirm-page-container-content';
|
||||||
import GasDetailsItem from '../../../components/app/gas-details-item';
|
import GasDetailsItem from '../../../components/app/gas-details-item';
|
||||||
import LedgerInstructionField from '../../../components/app/ledger-instruction-field';
|
import LedgerInstructionField from '../../../components/app/ledger-instruction-field';
|
||||||
|
import { ERC1155, ERC20, ERC721 } from '../../../helpers/constants/common';
|
||||||
|
|
||||||
export default class ConfirmApproveContent extends Component {
|
export default class ConfirmApproveContent extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -60,13 +61,15 @@ export default class ConfirmApproveContent extends Component {
|
|||||||
warning: PropTypes.string,
|
warning: PropTypes.string,
|
||||||
txData: PropTypes.object,
|
txData: PropTypes.object,
|
||||||
fromAddressIsLedger: PropTypes.bool,
|
fromAddressIsLedger: PropTypes.bool,
|
||||||
tokenImage: PropTypes.string,
|
|
||||||
chainId: PropTypes.string,
|
chainId: PropTypes.string,
|
||||||
rpcPrefs: PropTypes.object,
|
rpcPrefs: PropTypes.object,
|
||||||
isContract: PropTypes.bool,
|
isContract: PropTypes.bool,
|
||||||
hexTransactionTotal: PropTypes.string,
|
hexTransactionTotal: PropTypes.string,
|
||||||
isMultiLayerFeeNetwork: PropTypes.bool,
|
isMultiLayerFeeNetwork: PropTypes.bool,
|
||||||
supportsEIP1559V2: PropTypes.bool,
|
supportsEIP1559V2: PropTypes.bool,
|
||||||
|
assetName: PropTypes.string,
|
||||||
|
tokenId: PropTypes.string,
|
||||||
|
assetStandard: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
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 (
|
||||||
|
<div className="flex-column">
|
||||||
|
<div className="confirm-approve-content__small-text">
|
||||||
|
{t('accessAndSpendNoticeNFT', [origin])}
|
||||||
|
</div>
|
||||||
|
<div className="flex-row">
|
||||||
|
<div className="confirm-approve-content__label">
|
||||||
|
{t('approvedAsset')}:
|
||||||
|
</div>
|
||||||
|
<div className="confirm-approve-content__medium-text">
|
||||||
|
{`${assetName} #${tokenId}`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-row">
|
||||||
|
<div className="confirm-approve-content__label">
|
||||||
|
{t('grantedToWithColon')}
|
||||||
|
</div>
|
||||||
|
<div className="confirm-approve-content__medium-text">
|
||||||
|
{displayedAddress}
|
||||||
|
</div>
|
||||||
|
<div className="confirm-approve-content__medium-text">
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
className="confirm-approve-content__copy-address"
|
||||||
|
onClick={() => {
|
||||||
|
this.setState({ copied: true });
|
||||||
|
this.copyTimeout = setTimeout(
|
||||||
|
() => this.setState({ copied: false }),
|
||||||
|
SECOND * 3,
|
||||||
|
);
|
||||||
|
copyToClipboard(toAddress);
|
||||||
|
}}
|
||||||
|
title={
|
||||||
|
this.state.copied
|
||||||
|
? t('copiedExclamation')
|
||||||
|
: t('copyToClipboard')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<CopyIcon size={14} color="#6a737d" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderERC20PermissionContent() {
|
||||||
const { t } = this.context;
|
const { t } = this.context;
|
||||||
const {
|
const {
|
||||||
customTokenAmount,
|
customTokenAmount,
|
||||||
@ -188,6 +244,7 @@ export default class ConfirmApproveContent extends Component {
|
|||||||
toAddress,
|
toAddress,
|
||||||
isContract,
|
isContract,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const displayedAddress = isContract
|
const displayedAddress = isContract
|
||||||
? `${t('contract')} (${addressSummary(toAddress)})`
|
? `${t('contract')} (${addressSummary(toAddress)})`
|
||||||
: 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 (
|
||||||
|
<div className="confirm-approve-content__full-tx-content">
|
||||||
|
<div className="confirm-approve-content__permission">
|
||||||
|
{this.renderApproveContentCard({
|
||||||
|
symbol: <img src="./images/user-check.svg" alt="" />,
|
||||||
|
title: t('permissionRequest'),
|
||||||
|
content: this.renderERC20PermissionContent(),
|
||||||
|
showEdit: true,
|
||||||
|
onEditClick: () =>
|
||||||
|
showEditApprovalPermissionModal({
|
||||||
|
customTokenAmount,
|
||||||
|
decimals,
|
||||||
|
origin,
|
||||||
|
setCustomAmount,
|
||||||
|
tokenAmount,
|
||||||
|
tokenSymbol,
|
||||||
|
tokenBalance,
|
||||||
|
}),
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="confirm-approve-content__data">
|
||||||
|
{this.renderApproveContentCard({
|
||||||
|
symbol: <i className="fa fa-file" />,
|
||||||
|
title: 'Data',
|
||||||
|
content: this.renderDataContent(),
|
||||||
|
noBorder: true,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (assetStandard === ERC721 || assetStandard === ERC1155) {
|
||||||
|
return (
|
||||||
|
<div className="confirm-approve-content__full-tx-content">
|
||||||
|
<div className="confirm-approve-content__permission">
|
||||||
|
{this.renderApproveContentCard({
|
||||||
|
symbol: <img src="./images/user-check.svg" alt="" />,
|
||||||
|
title: t('permissionRequest'),
|
||||||
|
content: this.renderERC721OrERC1155PermissionContent(),
|
||||||
|
showEdit: false,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="confirm-approve-content__data">
|
||||||
|
{this.renderApproveContentCard({
|
||||||
|
symbol: <i className="fa fa-file" />,
|
||||||
|
title: t('data'),
|
||||||
|
content: this.renderDataContent(),
|
||||||
|
noBorder: true,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
renderCustomNonceContent() {
|
renderCustomNonceContent() {
|
||||||
const { t } = this.context;
|
const { t } = this.context;
|
||||||
const {
|
const {
|
||||||
@ -321,11 +447,13 @@ export default class ConfirmApproveContent extends Component {
|
|||||||
warning,
|
warning,
|
||||||
txData,
|
txData,
|
||||||
fromAddressIsLedger,
|
fromAddressIsLedger,
|
||||||
tokenImage,
|
|
||||||
toAddress,
|
toAddress,
|
||||||
chainId,
|
chainId,
|
||||||
rpcPrefs,
|
rpcPrefs,
|
||||||
isContract,
|
isContract,
|
||||||
|
assetStandard,
|
||||||
|
tokenId,
|
||||||
|
assetName,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { showFullTxDetails } = this.state;
|
const { showFullTxDetails } = this.state;
|
||||||
|
|
||||||
@ -368,7 +496,11 @@ export default class ConfirmApproveContent extends Component {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<div className="confirm-approve-content__title">
|
<div className="confirm-approve-content__title">
|
||||||
{t('allowSpendToken', [tokenSymbol])}
|
{t('allowSpendToken', [
|
||||||
|
assetStandard === ERC20
|
||||||
|
? tokenSymbol
|
||||||
|
: `${assetName} (#${tokenId})`,
|
||||||
|
])}
|
||||||
</div>
|
</div>
|
||||||
<div className="confirm-approve-content__description">
|
<div className="confirm-approve-content__description">
|
||||||
{t('trustSiteApprovePermission', [
|
{t('trustSiteApprovePermission', [
|
||||||
@ -383,7 +515,6 @@ export default class ConfirmApproveContent extends Component {
|
|||||||
className="confirm-approve-content__address-identicon"
|
className="confirm-approve-content__address-identicon"
|
||||||
diameter={20}
|
diameter={20}
|
||||||
address={toAddress}
|
address={toAddress}
|
||||||
image={tokenImage}
|
|
||||||
/>
|
/>
|
||||||
<Typography
|
<Typography
|
||||||
variant={TYPOGRAPHY.H6}
|
variant={TYPOGRAPHY.H6}
|
||||||
@ -438,24 +569,26 @@ export default class ConfirmApproveContent extends Component {
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<div className="confirm-approve-content__edit-submission-button-container">
|
{assetStandard === ERC20 ? (
|
||||||
<div
|
<div className="confirm-approve-content__edit-submission-button-container">
|
||||||
className="confirm-approve-content__medium-link-text cursor-pointer"
|
<div
|
||||||
onClick={() =>
|
className="confirm-approve-content__medium-link-text cursor-pointer"
|
||||||
showEditApprovalPermissionModal({
|
onClick={() =>
|
||||||
customTokenAmount,
|
showEditApprovalPermissionModal({
|
||||||
decimals,
|
customTokenAmount,
|
||||||
origin,
|
decimals,
|
||||||
setCustomAmount,
|
origin,
|
||||||
tokenAmount,
|
setCustomAmount,
|
||||||
tokenSymbol,
|
tokenAmount,
|
||||||
tokenBalance,
|
tokenSymbol,
|
||||||
})
|
tokenBalance,
|
||||||
}
|
})
|
||||||
>
|
}
|
||||||
{t('editPermission')}
|
>
|
||||||
|
{t('editPermission')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : null}
|
||||||
<div className="confirm-approve-content__card-wrapper">
|
<div className="confirm-approve-content__card-wrapper">
|
||||||
{this.renderApproveContentCard({
|
{this.renderApproveContentCard({
|
||||||
symbol: <i className="fa fa-tag" />,
|
symbol: <i className="fa fa-tag" />,
|
||||||
@ -527,36 +660,7 @@ export default class ConfirmApproveContent extends Component {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{showFullTxDetails ? (
|
{showFullTxDetails ? this.renderFullDetails() : null}
|
||||||
<div className="confirm-approve-content__full-tx-content">
|
|
||||||
<div className="confirm-approve-content__permission">
|
|
||||||
{this.renderApproveContentCard({
|
|
||||||
symbol: <img src="./images/user-check.svg" alt="" />,
|
|
||||||
title: t('permissionRequest'),
|
|
||||||
content: this.renderPermissionContent(),
|
|
||||||
showEdit: true,
|
|
||||||
onEditClick: () =>
|
|
||||||
showEditApprovalPermissionModal({
|
|
||||||
customTokenAmount,
|
|
||||||
decimals,
|
|
||||||
origin,
|
|
||||||
setCustomAmount,
|
|
||||||
tokenAmount,
|
|
||||||
tokenSymbol,
|
|
||||||
tokenBalance,
|
|
||||||
}),
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<div className="confirm-approve-content__data">
|
|
||||||
{this.renderApproveContentCard({
|
|
||||||
symbol: <i className="fa fa-file" />,
|
|
||||||
title: 'Data',
|
|
||||||
content: this.renderDataContent(),
|
|
||||||
noBorder: true,
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import configureMockStore from 'redux-mock-store';
|
import configureMockStore from 'redux-mock-store';
|
||||||
import { fireEvent } from '@testing-library/react';
|
import { fireEvent } from '@testing-library/react';
|
||||||
import { renderWithProvider } from '../../../../test/jest/rendering';
|
import { renderWithProvider } from '../../../../test/jest/rendering';
|
||||||
|
import { ERC20 } from '../../../helpers/constants/common';
|
||||||
import ConfirmApproveContent from '.';
|
import ConfirmApproveContent from '.';
|
||||||
|
|
||||||
const renderComponent = (props) => {
|
const renderComponent = (props) => {
|
||||||
@ -16,6 +17,7 @@ const props = {
|
|||||||
tokenAmount: '10',
|
tokenAmount: '10',
|
||||||
origin: 'https://metamask.github.io/test-dapp/',
|
origin: 'https://metamask.github.io/test-dapp/',
|
||||||
tokenSymbol: 'TST',
|
tokenSymbol: 'TST',
|
||||||
|
assetStandard: ERC20,
|
||||||
tokenImage: 'https://metamask.github.io/test-dapp/metamask-fox.svg',
|
tokenImage: 'https://metamask.github.io/test-dapp/metamask-fox.svg',
|
||||||
tokenBalance: '15',
|
tokenBalance: '15',
|
||||||
showCustomizeGasModal: jest.fn(),
|
showCustomizeGasModal: jest.fn(),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useParams } from 'react-router-dom';
|
|
||||||
import ConfirmTransactionBase from '../confirm-transaction-base';
|
import ConfirmTransactionBase from '../confirm-transaction-base';
|
||||||
import { EDIT_GAS_MODES } from '../../../shared/constants/gas';
|
import { EDIT_GAS_MODES } from '../../../shared/constants/gas';
|
||||||
import {
|
import {
|
||||||
@ -8,24 +8,15 @@ import {
|
|||||||
updateCustomNonce,
|
updateCustomNonce,
|
||||||
getNextNonce,
|
getNextNonce,
|
||||||
} from '../../store/actions';
|
} from '../../store/actions';
|
||||||
import { getTransactionData } from '../../helpers/utils/transactions.util';
|
import { calcTokenAmount } from '../../helpers/utils/token-util';
|
||||||
import {
|
|
||||||
calcTokenAmount,
|
|
||||||
getTokenAddressParam,
|
|
||||||
getTokenValueParam,
|
|
||||||
} from '../../helpers/utils/token-util';
|
|
||||||
import { readAddressAsContract } from '../../../shared/modules/contract-utils';
|
import { readAddressAsContract } from '../../../shared/modules/contract-utils';
|
||||||
import { GasFeeContextProvider } from '../../contexts/gasFee';
|
import { GasFeeContextProvider } from '../../contexts/gasFee';
|
||||||
import { TransactionModalContextProvider } from '../../contexts/transaction-modal';
|
import { TransactionModalContextProvider } from '../../contexts/transaction-modal';
|
||||||
import { useTokenTracker } from '../../hooks/useTokenTracker';
|
|
||||||
import {
|
import {
|
||||||
getTokens,
|
|
||||||
getNativeCurrency,
|
getNativeCurrency,
|
||||||
isAddressLedger,
|
isAddressLedger,
|
||||||
} from '../../ducks/metamask/metamask';
|
} from '../../ducks/metamask/metamask';
|
||||||
import {
|
import {
|
||||||
transactionFeeSelector,
|
|
||||||
txDataSelector,
|
|
||||||
getCurrentCurrency,
|
getCurrentCurrency,
|
||||||
getSubjectMetadata,
|
getSubjectMetadata,
|
||||||
getUseNonceField,
|
getUseNonceField,
|
||||||
@ -38,12 +29,11 @@ import {
|
|||||||
getEIP1559V2Enabled,
|
getEIP1559V2Enabled,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
import { useApproveTransaction } from '../../hooks/useApproveTransaction';
|
import { useApproveTransaction } from '../../hooks/useApproveTransaction';
|
||||||
import { currentNetworkTxListSelector } from '../../selectors/transactions';
|
|
||||||
import AdvancedGasFeePopover from '../../components/app/advanced-gas-fee-popover';
|
import AdvancedGasFeePopover from '../../components/app/advanced-gas-fee-popover';
|
||||||
import EditGasFeePopover from '../../components/app/edit-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 EditGasPopover from '../../components/app/edit-gas-popover/edit-gas-popover.component';
|
||||||
import Loading from '../../components/ui/loading-screen';
|
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 { getCustomTxParamsData } from './confirm-approve.util';
|
||||||
import ConfirmApproveContent from './confirm-approve-content';
|
import ConfirmApproveContent from './confirm-approve-content';
|
||||||
|
|
||||||
@ -51,19 +41,28 @@ const isAddressLedgerByFromAddress = (address) => (state) => {
|
|||||||
return isAddressLedger(state, address);
|
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 dispatch = useDispatch();
|
||||||
const { id: paramsTransactionId } = useParams();
|
const { txParams: { data: transactionData } = {} } = transaction;
|
||||||
const {
|
|
||||||
id: transactionId,
|
|
||||||
txParams: { to: tokenAddress, data, from } = {},
|
|
||||||
} = useSelector(txDataSelector);
|
|
||||||
|
|
||||||
const currentCurrency = useSelector(getCurrentCurrency);
|
const currentCurrency = useSelector(getCurrentCurrency);
|
||||||
const nativeCurrency = useSelector(getNativeCurrency);
|
const nativeCurrency = useSelector(getNativeCurrency);
|
||||||
const currentNetworkTxList = useSelector(currentNetworkTxListSelector);
|
|
||||||
const subjectMetadata = useSelector(getSubjectMetadata);
|
const subjectMetadata = useSelector(getSubjectMetadata);
|
||||||
const tokens = useSelector(getTokens);
|
|
||||||
const useNonceField = useSelector(getUseNonceField);
|
const useNonceField = useSelector(getUseNonceField);
|
||||||
const nextNonce = useSelector(getNextSuggestedNonce);
|
const nextNonce = useSelector(getNextSuggestedNonce);
|
||||||
const customNonceValue = useSelector(getCustomNonceValue);
|
const customNonceValue = useSelector(getCustomNonceValue);
|
||||||
@ -73,45 +72,17 @@ export default function ConfirmApprove() {
|
|||||||
const networkAndAccountSupports1559 = useSelector(
|
const networkAndAccountSupports1559 = useSelector(
|
||||||
checkNetworkAndAccountSupports1559,
|
checkNetworkAndAccountSupports1559,
|
||||||
);
|
);
|
||||||
|
const fromAddressIsLedger = useSelector(
|
||||||
const fromAddressIsLedger = useSelector(isAddressLedgerByFromAddress(from));
|
isAddressLedgerByFromAddress(userAddress),
|
||||||
|
);
|
||||||
const transaction =
|
const [customPermissionAmount, setCustomPermissionAmount] = useState('');
|
||||||
currentNetworkTxList.find(
|
const [submitWarning, setSubmitWarning] = useState('');
|
||||||
({ id }) => id === (Number(paramsTransactionId) || transactionId),
|
const [isContract, setIsContract] = useState(false);
|
||||||
) || {};
|
|
||||||
const {
|
|
||||||
ethTransactionTotal,
|
|
||||||
fiatTransactionTotal,
|
|
||||||
hexTransactionTotal,
|
|
||||||
} = useSelector((state) => transactionFeeSelector(state, transaction));
|
|
||||||
|
|
||||||
const eip1559V2Enabled = useSelector(getEIP1559V2Enabled);
|
const eip1559V2Enabled = useSelector(getEIP1559V2Enabled);
|
||||||
const supportsEIP1559V2 = eip1559V2Enabled && networkAndAccountSupports1559;
|
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 previousTokenAmount = useRef(tokenAmount);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
approveTransaction,
|
approveTransaction,
|
||||||
showCustomizeGasPopover,
|
showCustomizeGasPopover,
|
||||||
@ -125,7 +96,6 @@ export default function ConfirmApprove() {
|
|||||||
previousTokenAmount.current = tokenAmount;
|
previousTokenAmount.current = tokenAmount;
|
||||||
}, [customPermissionAmount, tokenAmount]);
|
}, [customPermissionAmount, tokenAmount]);
|
||||||
|
|
||||||
const [submitWarning, setSubmitWarning] = useState('');
|
|
||||||
const prevNonce = useRef(nextNonce);
|
const prevNonce = useRef(nextNonce);
|
||||||
const prevCustomNonce = useRef(customNonceValue);
|
const prevCustomNonce = useRef(customNonceValue);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -145,7 +115,6 @@ export default function ConfirmApprove() {
|
|||||||
prevNonce.current = nextNonce;
|
prevNonce.current = nextNonce;
|
||||||
}, [customNonceValue, nextNonce]);
|
}, [customNonceValue, nextNonce]);
|
||||||
|
|
||||||
const [isContract, setIsContract] = useState(false);
|
|
||||||
const checkIfContract = useCallback(async () => {
|
const checkIfContract = useCallback(async () => {
|
||||||
const { isContractAddress } = await readAddressAsContract(
|
const { isContractAddress } = await readAddressAsContract(
|
||||||
global.eth,
|
global.eth,
|
||||||
@ -153,6 +122,7 @@ export default function ConfirmApprove() {
|
|||||||
);
|
);
|
||||||
setIsContract(isContractAddress);
|
setIsContract(isContractAddress);
|
||||||
}, [setIsContract, toAddress]);
|
}, [setIsContract, toAddress]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkIfContract();
|
checkIfContract();
|
||||||
}, [checkIfContract]);
|
}, [checkIfContract]);
|
||||||
@ -162,21 +132,30 @@ export default function ConfirmApprove() {
|
|||||||
|
|
||||||
const { iconUrl: siteImage = '' } = subjectMetadata[origin] || {};
|
const { iconUrl: siteImage = '' } = subjectMetadata[origin] || {};
|
||||||
|
|
||||||
const tokensText = `${Number(tokenAmount)} ${tokenSymbol}`;
|
let tokensText;
|
||||||
const tokenBalance = tokenTrackerBalance
|
if (assetStandard === ERC20) {
|
||||||
? calcTokenAmount(tokenTrackerBalance, decimals).toString(10)
|
tokensText = `${Number(tokenAmount)} ${tokenSymbol}`;
|
||||||
|
} else if (assetStandard === ERC721 || assetStandard === ERC1155) {
|
||||||
|
tokensText = assetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenBalance = userBalance
|
||||||
|
? calcTokenAmount(userBalance, decimals).toString(10)
|
||||||
: '';
|
: '';
|
||||||
const customData = customPermissionAmount
|
const customData = customPermissionAmount
|
||||||
? getCustomTxParamsData(data, { customPermissionAmount, decimals })
|
? getCustomTxParamsData(transactionData, {
|
||||||
|
customPermissionAmount,
|
||||||
|
decimals,
|
||||||
|
})
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return tokenSymbol === undefined ? (
|
return tokenSymbol === undefined && assetName === undefined ? (
|
||||||
<Loading />
|
<Loading />
|
||||||
) : (
|
) : (
|
||||||
<GasFeeContextProvider transaction={transaction}>
|
<GasFeeContextProvider transaction={transaction}>
|
||||||
<ConfirmTransactionBase
|
<ConfirmTransactionBase
|
||||||
toAddress={toAddress}
|
toAddress={toAddress}
|
||||||
identiconAddress={tokenAddress}
|
identiconAddress={toAddress}
|
||||||
showAccountInHeader
|
showAccountInHeader
|
||||||
title={tokensText}
|
title={tokensText}
|
||||||
contentComponent={
|
contentComponent={
|
||||||
@ -191,6 +170,9 @@ export default function ConfirmApprove() {
|
|||||||
tokenSymbol={tokenSymbol}
|
tokenSymbol={tokenSymbol}
|
||||||
tokenImage={tokenImage}
|
tokenImage={tokenImage}
|
||||||
tokenBalance={tokenBalance}
|
tokenBalance={tokenBalance}
|
||||||
|
tokenId={tokenId}
|
||||||
|
assetName={assetName}
|
||||||
|
assetStandard={assetStandard}
|
||||||
showCustomizeGasModal={approveTransaction}
|
showCustomizeGasModal={approveTransaction}
|
||||||
showEditApprovalPermissionModal={({
|
showEditApprovalPermissionModal={({
|
||||||
/* eslint-disable no-shadow */
|
/* eslint-disable no-shadow */
|
||||||
@ -213,10 +195,12 @@ export default function ConfirmApprove() {
|
|||||||
tokenAmount,
|
tokenAmount,
|
||||||
tokenBalance,
|
tokenBalance,
|
||||||
tokenSymbol,
|
tokenSymbol,
|
||||||
|
tokenId,
|
||||||
|
assetStandard,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
data={customData || data}
|
data={customData || transactionData}
|
||||||
toAddress={toAddress}
|
toAddress={toAddress}
|
||||||
currentCurrency={currentCurrency}
|
currentCurrency={currentCurrency}
|
||||||
nativeCurrency={nativeCurrency}
|
nativeCurrency={nativeCurrency}
|
||||||
@ -280,3 +264,27 @@ export default function ConfirmApprove() {
|
|||||||
</GasFeeContextProvider>
|
</GasFeeContextProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
118
ui/pages/confirm-send-token/confirm-send-token.js
Normal file
118
ui/pages/confirm-send-token/confirm-send-token.js
Normal file
@ -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 (
|
||||||
|
<ConfirmTokenTransactionBase
|
||||||
|
onEdit={handleEdit}
|
||||||
|
conversionRate={conversionRate}
|
||||||
|
currentCurrency={currentCurrency}
|
||||||
|
nativeCurrency={nativeCurrency}
|
||||||
|
contractExchangeRate={contractExchangeRate}
|
||||||
|
title={title}
|
||||||
|
subtitle={subtitle}
|
||||||
|
assetStandard={assetStandard}
|
||||||
|
assetName={assetName}
|
||||||
|
tokenSymbol={tokenSymbol}
|
||||||
|
tokenAmount={tokenAmount}
|
||||||
|
tokenId={tokenId}
|
||||||
|
transaction={transaction}
|
||||||
|
image={image}
|
||||||
|
toAddress={toAddress}
|
||||||
|
tokenAddress={tokenAddress}
|
||||||
|
ethTransactionTotal={ethTransactionTotal}
|
||||||
|
fiatTransactionTotal={fiatTransactionTotal}
|
||||||
|
hexMaximumTransactionFee={hexMaximumTransactionFee}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
@ -1 +1 @@
|
|||||||
export { default } from './confirm-send-token.container';
|
export { default } from './confirm-send-token';
|
||||||
|
@ -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);
|
|
@ -1,6 +1,7 @@
|
|||||||
import React, { useContext, useMemo } from 'react';
|
import React, { useContext, useMemo } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
import { I18nContext } from '../../contexts/i18n';
|
import { I18nContext } from '../../contexts/i18n';
|
||||||
import ConfirmTransactionBase from '../confirm-transaction-base';
|
import ConfirmTransactionBase from '../confirm-transaction-base';
|
||||||
import UserPreferencedCurrencyDisplay from '../../components/app/user-preferenced-currency-display';
|
import UserPreferencedCurrencyDisplay from '../../components/app/user-preferenced-currency-display';
|
||||||
@ -10,26 +11,57 @@ import {
|
|||||||
addFiat,
|
addFiat,
|
||||||
roundExponential,
|
roundExponential,
|
||||||
} from '../../helpers/utils/confirm-tx.util';
|
} from '../../helpers/utils/confirm-tx.util';
|
||||||
import { getWeiHexFromDecimalValue } from '../../helpers/utils/conversions.util';
|
import {
|
||||||
import { ETH, PRIMARY } from '../../helpers/constants/common';
|
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({
|
export default function ConfirmTokenTransactionBase({
|
||||||
image,
|
image = '',
|
||||||
title,
|
assetName,
|
||||||
subtitle,
|
|
||||||
toAddress,
|
toAddress,
|
||||||
tokenAddress,
|
tokenAddress,
|
||||||
tokenAmount = '0',
|
tokenAmount = '0',
|
||||||
fiatTransactionTotal,
|
tokenSymbol,
|
||||||
ethTransactionTotal,
|
tokenId,
|
||||||
ethTransactionTotalMaxAmount,
|
assetStandard,
|
||||||
contractExchangeRate,
|
|
||||||
conversionRate,
|
|
||||||
currentCurrency,
|
|
||||||
nativeCurrency,
|
|
||||||
onEdit,
|
onEdit,
|
||||||
|
ethTransactionTotal,
|
||||||
|
fiatTransactionTotal,
|
||||||
|
hexMaximumTransactionFee,
|
||||||
}) {
|
}) {
|
||||||
const t = useContext(I18nContext);
|
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(() => {
|
const hexWeiValue = useMemo(() => {
|
||||||
if (tokenAmount === '0' || !contractExchangeRate) {
|
if (tokenAmount === '0' || !contractExchangeRate) {
|
||||||
@ -105,17 +137,15 @@ export default function ConfirmTokenTransactionBase({
|
|||||||
|
|
||||||
ConfirmTokenTransactionBase.propTypes = {
|
ConfirmTokenTransactionBase.propTypes = {
|
||||||
image: PropTypes.string,
|
image: PropTypes.string,
|
||||||
title: PropTypes.string,
|
assetName: PropTypes.string,
|
||||||
subtitle: PropTypes.string,
|
|
||||||
tokenAddress: PropTypes.string,
|
|
||||||
toAddress: PropTypes.string,
|
toAddress: PropTypes.string,
|
||||||
|
tokenAddress: PropTypes.string,
|
||||||
tokenAmount: PropTypes.string,
|
tokenAmount: PropTypes.string,
|
||||||
fiatTransactionTotal: PropTypes.string,
|
tokenSymbol: PropTypes.string,
|
||||||
ethTransactionTotal: PropTypes.string,
|
tokenId: PropTypes.string,
|
||||||
contractExchangeRate: PropTypes.number,
|
assetStandard: PropTypes.string,
|
||||||
conversionRate: PropTypes.number,
|
|
||||||
currentCurrency: PropTypes.string,
|
|
||||||
onEdit: PropTypes.func,
|
onEdit: PropTypes.func,
|
||||||
nativeCurrency: PropTypes.string,
|
ethTransactionTotal: PropTypes.string,
|
||||||
ethTransactionTotalMaxAmount: PropTypes.string,
|
fiatTransactionTotal: PropTypes.string,
|
||||||
|
hexMaximumTransactionFee: PropTypes.string,
|
||||||
};
|
};
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { store } from '../../../.storybook/preview';
|
import { store } from '../../../.storybook/preview';
|
||||||
import ConfirmTokenTransactionBase from './confirm-token-transaction-base.component';
|
import ConfirmTokenTransactionBase from './confirm-token-transaction-base';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/ConfirmTokenTransactionBase',
|
title: 'Pages/ConfirmTokenTransactionBase',
|
||||||
|
@ -1,2 +1 @@
|
|||||||
export { default } from './confirm-token-transaction-base.container';
|
export { default } from './confirm-token-transaction-base';
|
||||||
export { default as ConfirmTokenTransactionBase } from './confirm-token-transaction-base.component';
|
|
||||||
|
@ -174,7 +174,7 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isCollectibleTransfer = Boolean(
|
const isCollectibleTransfer = Boolean(
|
||||||
allCollectibleContracts?.[selectedAddress]?.[chainId].find((contract) => {
|
allCollectibleContracts?.[selectedAddress]?.[chainId]?.find((contract) => {
|
||||||
return isEqualCaseInsensitive(contract.address, fullTxData.txParams.to);
|
return isEqualCaseInsensitive(contract.address, fullTxData.txParams.to);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
125
ui/pages/confirm-transaction/confirm-token-transaction-switch.js
Normal file
125
ui/pages/confirm-transaction/confirm-token-transaction-switch.js
Normal file
@ -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 (
|
||||||
|
<Switch>
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_APPROVE_PATH}`}
|
||||||
|
render={() => (
|
||||||
|
<ConfirmApprove
|
||||||
|
assetStandard={assetStandard}
|
||||||
|
assetName={assetName}
|
||||||
|
userBalance={userBalance}
|
||||||
|
tokenSymbol={tokenSymbol}
|
||||||
|
decimals={decimals}
|
||||||
|
tokenImage={tokenImage}
|
||||||
|
tokenAmount={tokenAmount}
|
||||||
|
tokenId={tokenId}
|
||||||
|
userAddress={userAddress}
|
||||||
|
tokenAddress={tokenAddress}
|
||||||
|
toAddress={toAddress}
|
||||||
|
transaction={transaction}
|
||||||
|
ethTransactionTotal={ethTransactionTotal}
|
||||||
|
fiatTransactionTotal={fiatTransactionTotal}
|
||||||
|
hexTransactionTotal={hexTransactionTotal}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_TRANSFER_FROM_PATH}`}
|
||||||
|
render={() => (
|
||||||
|
<ConfirmTokenTransactionBase
|
||||||
|
assetStandard={assetStandard}
|
||||||
|
assetName={assetName}
|
||||||
|
userBalance={userBalance}
|
||||||
|
tokenSymbol={tokenSymbol}
|
||||||
|
decimals={decimals}
|
||||||
|
image={tokenImage}
|
||||||
|
tokenAddress={tokenAddress}
|
||||||
|
toAddress={toAddress}
|
||||||
|
tokenAmount={tokenAmount}
|
||||||
|
tokenId={tokenId}
|
||||||
|
userAddress={userAddress}
|
||||||
|
transaction={transaction}
|
||||||
|
ethTransactionTotal={ethTransactionTotal}
|
||||||
|
fiatTransactionTotal={fiatTransactionTotal}
|
||||||
|
hexTransactionTotal={hexTransactionTotal}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_SEND_TOKEN_PATH}`}
|
||||||
|
render={() => (
|
||||||
|
<ConfirmSendToken
|
||||||
|
assetStandard={assetStandard}
|
||||||
|
assetName={assetName}
|
||||||
|
tokenSymbol={tokenSymbol}
|
||||||
|
image={tokenImage}
|
||||||
|
tokenAddress={tokenAddress}
|
||||||
|
toAddress={toAddress}
|
||||||
|
tokenAmount={tokenAmount}
|
||||||
|
tokenId={tokenId}
|
||||||
|
transaction={transaction}
|
||||||
|
ethTransactionTotal={ethTransactionTotal}
|
||||||
|
fiatTransactionTotal={fiatTransactionTotal}
|
||||||
|
hexMaximumTransactionFee={hexMaximumTransactionFee}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Route path="*" component={ConfirmTransactionSwitch} />
|
||||||
|
</Switch>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfirmTokenTransactionSwitch.propTypes = {
|
||||||
|
transaction: PropTypes.shape({
|
||||||
|
origin: PropTypes.string,
|
||||||
|
txParams: PropTypes.shape({
|
||||||
|
data: PropTypes.string,
|
||||||
|
to: PropTypes.string,
|
||||||
|
from: PropTypes.string,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
};
|
@ -5,10 +5,7 @@ import Loading from '../../components/ui/loading-screen';
|
|||||||
import ConfirmTransactionSwitch from '../confirm-transaction-switch';
|
import ConfirmTransactionSwitch from '../confirm-transaction-switch';
|
||||||
import ConfirmTransactionBase from '../confirm-transaction-base';
|
import ConfirmTransactionBase from '../confirm-transaction-base';
|
||||||
import ConfirmSendEther from '../confirm-send-ether';
|
import ConfirmSendEther from '../confirm-send-ether';
|
||||||
import ConfirmSendToken from '../confirm-send-token';
|
|
||||||
import ConfirmDeployContract from '../confirm-deploy-contract';
|
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 ConfirmDecryptMessage from '../confirm-decrypt-message';
|
||||||
import ConfirmEncryptionPublicKey from '../confirm-encryption-public-key';
|
import ConfirmEncryptionPublicKey from '../confirm-encryption-public-key';
|
||||||
|
|
||||||
@ -16,9 +13,6 @@ import {
|
|||||||
CONFIRM_TRANSACTION_ROUTE,
|
CONFIRM_TRANSACTION_ROUTE,
|
||||||
CONFIRM_DEPLOY_CONTRACT_PATH,
|
CONFIRM_DEPLOY_CONTRACT_PATH,
|
||||||
CONFIRM_SEND_ETHER_PATH,
|
CONFIRM_SEND_ETHER_PATH,
|
||||||
CONFIRM_SEND_TOKEN_PATH,
|
|
||||||
CONFIRM_APPROVE_PATH,
|
|
||||||
CONFIRM_TRANSFER_FROM_PATH,
|
|
||||||
CONFIRM_TOKEN_METHOD_PATH,
|
CONFIRM_TOKEN_METHOD_PATH,
|
||||||
SIGNATURE_REQUEST_PATH,
|
SIGNATURE_REQUEST_PATH,
|
||||||
DECRYPT_MESSAGE_REQUEST_PATH,
|
DECRYPT_MESSAGE_REQUEST_PATH,
|
||||||
@ -31,6 +25,7 @@ import {
|
|||||||
addPollingTokenToAppState,
|
addPollingTokenToAppState,
|
||||||
removePollingTokenFromAppState,
|
removePollingTokenFromAppState,
|
||||||
} from '../../store/actions';
|
} from '../../store/actions';
|
||||||
|
import ConfirmTokenTransactionSwitch from './confirm-token-transaction-switch';
|
||||||
import ConfTx from './conf-tx';
|
import ConfTx from './conf-tx';
|
||||||
|
|
||||||
export default class ConfirmTransaction extends Component {
|
export default class ConfirmTransaction extends Component {
|
||||||
@ -49,7 +44,6 @@ export default class ConfirmTransaction extends Component {
|
|||||||
getContractMethodData: PropTypes.func,
|
getContractMethodData: PropTypes.func,
|
||||||
transactionId: PropTypes.string,
|
transactionId: PropTypes.string,
|
||||||
paramsTransactionId: PropTypes.string,
|
paramsTransactionId: PropTypes.string,
|
||||||
getTokenParams: PropTypes.func,
|
|
||||||
isTokenMethodAction: PropTypes.bool,
|
isTokenMethodAction: PropTypes.bool,
|
||||||
setDefaultHomeActiveTabName: PropTypes.func,
|
setDefaultHomeActiveTabName: PropTypes.func,
|
||||||
};
|
};
|
||||||
@ -74,12 +68,10 @@ export default class ConfirmTransaction extends Component {
|
|||||||
sendTo,
|
sendTo,
|
||||||
history,
|
history,
|
||||||
mostRecentOverviewPage,
|
mostRecentOverviewPage,
|
||||||
transaction: { txParams: { data, to } = {} } = {},
|
transaction: { txParams: { data } = {} } = {},
|
||||||
getContractMethodData,
|
getContractMethodData,
|
||||||
transactionId,
|
transactionId,
|
||||||
paramsTransactionId,
|
paramsTransactionId,
|
||||||
getTokenParams,
|
|
||||||
isTokenMethodAction,
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
getGasFeeEstimatesAndStartPolling().then((pollingToken) => {
|
getGasFeeEstimatesAndStartPolling().then((pollingToken) => {
|
||||||
@ -100,9 +92,7 @@ export default class ConfirmTransaction extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getContractMethodData(data);
|
getContractMethodData(data);
|
||||||
if (isTokenMethodAction) {
|
|
||||||
getTokenParams(to);
|
|
||||||
}
|
|
||||||
const txId = transactionId || paramsTransactionId;
|
const txId = transactionId || paramsTransactionId;
|
||||||
if (txId) {
|
if (txId) {
|
||||||
this.props.setTransactionToConfirm(txId);
|
this.props.setTransactionToConfirm(txId);
|
||||||
@ -154,23 +144,30 @@ export default class ConfirmTransaction extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { transactionId, paramsTransactionId } = this.props;
|
const {
|
||||||
|
transactionId,
|
||||||
|
paramsTransactionId,
|
||||||
|
isTokenMethodAction,
|
||||||
|
transaction,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const validTransactionId =
|
||||||
|
transactionId &&
|
||||||
|
(!paramsTransactionId || paramsTransactionId === transactionId);
|
||||||
|
|
||||||
|
if (isTokenMethodAction && validTransactionId) {
|
||||||
|
return <ConfirmTokenTransactionSwitch transaction={transaction} />;
|
||||||
|
}
|
||||||
// Show routes when state.confirmTransaction has been set and when either the ID in the params
|
// 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
|
// isn't specified or is specified and matches the ID in state.confirmTransaction in order to
|
||||||
// support URLs of /confirm-transaction or /confirm-transaction/<transactionId>
|
// support URLs of /confirm-transaction or /confirm-transaction/<transactionId>
|
||||||
return transactionId &&
|
return validTransactionId ? (
|
||||||
(!paramsTransactionId || paramsTransactionId === transactionId) ? (
|
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_DEPLOY_CONTRACT_PATH}`}
|
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_DEPLOY_CONTRACT_PATH}`}
|
||||||
component={ConfirmDeployContract}
|
component={ConfirmDeployContract}
|
||||||
/>
|
/>
|
||||||
<Route
|
|
||||||
exact
|
|
||||||
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_TOKEN_METHOD_PATH}`}
|
|
||||||
component={ConfirmTransactionBase}
|
|
||||||
/>
|
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_SEND_ETHER_PATH}`}
|
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_SEND_ETHER_PATH}`}
|
||||||
@ -178,18 +175,8 @@ export default class ConfirmTransaction extends Component {
|
|||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_SEND_TOKEN_PATH}`}
|
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_TOKEN_METHOD_PATH}`}
|
||||||
component={ConfirmSendToken}
|
component={ConfirmTransactionBase}
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
exact
|
|
||||||
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_APPROVE_PATH}`}
|
|
||||||
component={ConfirmApprove}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
exact
|
|
||||||
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_TRANSFER_FROM_PATH}`}
|
|
||||||
component={ConfirmTokenTransactionBaseContainer}
|
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
|
@ -9,7 +9,6 @@ import { isTokenMethodAction } from '../../helpers/utils/transactions.util';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
getContractMethodData,
|
getContractMethodData,
|
||||||
getTokenParams,
|
|
||||||
setDefaultHomeActiveTabName,
|
setDefaultHomeActiveTabName,
|
||||||
} from '../../store/actions';
|
} from '../../store/actions';
|
||||||
import { unconfirmedTransactionsListSelector } from '../../selectors';
|
import { unconfirmedTransactionsListSelector } from '../../selectors';
|
||||||
@ -54,7 +53,6 @@ const mapDispatchToProps = (dispatch) => {
|
|||||||
},
|
},
|
||||||
clearConfirmTransaction: () => dispatch(clearConfirmTransaction()),
|
clearConfirmTransaction: () => dispatch(clearConfirmTransaction()),
|
||||||
getContractMethodData: (data) => dispatch(getContractMethodData(data)),
|
getContractMethodData: (data) => dispatch(getContractMethodData(data)),
|
||||||
getTokenParams: (tokenAddress) => dispatch(getTokenParams(tokenAddress)),
|
|
||||||
setDefaultHomeActiveTabName: (tabName) =>
|
setDefaultHomeActiveTabName: (tabName) =>
|
||||||
dispatch(setDefaultHomeActiveTabName(tabName)),
|
dispatch(setDefaultHomeActiveTabName(tabName)),
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,6 @@ import {
|
|||||||
loadRelativeTimeFormatLocaleData,
|
loadRelativeTimeFormatLocaleData,
|
||||||
} from '../helpers/utils/i18n-helper';
|
} from '../helpers/utils/i18n-helper';
|
||||||
import { getMethodDataAsync } from '../helpers/utils/transactions.util';
|
import { getMethodDataAsync } from '../helpers/utils/transactions.util';
|
||||||
import { getSymbolAndDecimals } from '../helpers/utils/token-util';
|
|
||||||
import switchDirection from '../helpers/utils/switch-direction';
|
import switchDirection from '../helpers/utils/switch-direction';
|
||||||
import {
|
import {
|
||||||
ENVIRONMENT_TYPE_NOTIFICATION,
|
ENVIRONMENT_TYPE_NOTIFICATION,
|
||||||
@ -22,7 +21,6 @@ import {
|
|||||||
getMetaMaskAccounts,
|
getMetaMaskAccounts,
|
||||||
getPermittedAccountsForCurrentTab,
|
getPermittedAccountsForCurrentTab,
|
||||||
getSelectedAddress,
|
getSelectedAddress,
|
||||||
getTokenList,
|
|
||||||
} from '../selectors';
|
} from '../selectors';
|
||||||
import { computeEstimatedGasLimit, resetSendState } from '../ducks/send';
|
import { computeEstimatedGasLimit, resetSendState } from '../ducks/send';
|
||||||
import { switchedToUnconnectedAccount } from '../ducks/alerts/unconnected-account';
|
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) {
|
export function setSeedPhraseBackedUp(seedPhraseBackupState) {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
log.debug(`background.setSeedPhraseBackedUp`);
|
log.debug(`background.setSeedPhraseBackedUp`);
|
||||||
|
Loading…
Reference in New Issue
Block a user