1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 18:00:18 +01:00

Fix issue where we show contract address as recipient when calling safe transfer method on erc721 or erc1155 contracts (#13535)

* fix issue where we show contract address as recipient when calling safe transfer method on erc721 or erc1155 contracts

* updates function name getTransactionData -> parseStandardTokenTransactionData, and adds documentation
This commit is contained in:
Alex Donesky 2022-03-17 13:35:40 -05:00 committed by GitHub
parent 41974cec3b
commit e3ea4f2cd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 142 additions and 66 deletions

View File

@ -2594,6 +2594,9 @@
"rpcUrl": { "rpcUrl": {
"message": "New RPC URL" "message": "New RPC URL"
}, },
"safeTransferFrom": {
"message": "Safe Transfer From"
},
"save": { "save": {
"message": "Save" "message": "Save"
}, },

View File

@ -85,8 +85,8 @@ import {
import { hexToDecimal } from '../../ui/helpers/utils/conversions.util'; import { hexToDecimal } from '../../ui/helpers/utils/conversions.util';
import { getTokenValueParam } from '../../ui/helpers/utils/token-util'; import { getTokenValueParam } from '../../ui/helpers/utils/token-util';
import { getTransactionData } from '../../ui/helpers/utils/transactions.util';
import { isEqualCaseInsensitive } from '../../shared/modules/string-utils'; import { isEqualCaseInsensitive } from '../../shared/modules/string-utils';
import { parseStandardTokenTransactionData } from '../../shared/modules/transaction.utils';
import ComposableObservableStore from './lib/ComposableObservableStore'; import ComposableObservableStore from './lib/ComposableObservableStore';
import AccountTracker from './lib/account-tracker'; import AccountTracker from './lib/account-tracker';
import createLoggerMiddleware from './lib/createLoggerMiddleware'; import createLoggerMiddleware from './lib/createLoggerMiddleware';
@ -777,7 +777,7 @@ export default class MetamaskController extends EventEmitter {
from: userAddress, from: userAddress,
} = txMeta.txParams; } = txMeta.txParams;
const { chainId } = txMeta; const { chainId } = txMeta;
const transactionData = getTransactionData(data); const transactionData = parseStandardTokenTransactionData(data);
const tokenAmountOrTokenId = getTokenValueParam(transactionData); const tokenAmountOrTokenId = getTokenValueParam(transactionData);
const { allCollectibles } = this.collectiblesController.state; const { allCollectibles } = this.collectiblesController.state;

View File

@ -117,6 +117,7 @@
"@metamask/iframe-execution-environment-service": "^0.10.2", "@metamask/iframe-execution-environment-service": "^0.10.2",
"@metamask/jazzicon": "^2.0.0", "@metamask/jazzicon": "^2.0.0",
"@metamask/logo": "^3.1.1", "@metamask/logo": "^3.1.1",
"@metamask/metamask-eth-abis": "^3.0.0",
"@metamask/obs-store": "^5.0.0", "@metamask/obs-store": "^5.0.0",
"@metamask/post-message-stream": "^4.0.0", "@metamask/post-message-stream": "^4.0.0",
"@metamask/providers": "^8.1.1", "@metamask/providers": "^8.1.1",

View File

@ -48,6 +48,7 @@ export const TRANSACTION_TYPES = {
RETRY: 'retry', RETRY: 'retry',
TOKEN_METHOD_TRANSFER: 'transfer', TOKEN_METHOD_TRANSFER: 'transfer',
TOKEN_METHOD_TRANSFER_FROM: 'transferfrom', TOKEN_METHOD_TRANSFER_FROM: 'transferfrom',
TOKEN_METHOD_SAFE_TRANSFER_FROM: 'safetransferfrom',
TOKEN_METHOD_APPROVE: 'approve', TOKEN_METHOD_APPROVE: 'approve',
INCOMING: 'incoming', INCOMING: 'incoming',
SIMPLE_SEND: 'simpleSend', SIMPLE_SEND: 'simpleSend',

View File

@ -1,6 +1,6 @@
import { isHexString } from 'ethereumjs-util'; import { isHexString } from 'ethereumjs-util';
import { ethers } from 'ethers'; import { ethers } from 'ethers';
import abi from 'human-standard-token-abi'; import { abiERC721, abiERC20, abiERC1155 } from '@metamask/metamask-eth-abis';
import log from 'loglevel'; import log from 'loglevel';
import { TOKEN_STANDARDS } from '../../ui/helpers/constants/common'; import { TOKEN_STANDARDS } from '../../ui/helpers/constants/common';
import { ASSET_TYPES, TRANSACTION_TYPES } from '../constants/transaction'; import { ASSET_TYPES, TRANSACTION_TYPES } from '../constants/transaction';
@ -19,7 +19,22 @@ import { isEqualCaseInsensitive } from './string-utils';
* code * code
*/ */
const hstInterface = new ethers.utils.Interface(abi); /**
* @typedef EthersContractCall
* @type object
* @property {any[]} args - The args/params to the function call.
* An array-like object with numerical and string indices.
* @property {string} name - The name of the function.
* @property {string} signature - The function signature.
* @property {string} sighash - The function signature hash.
* @property {EthersBigNumber} value - The ETH value associated with the call.
* @property {FunctionFragment} functionFragment - The Ethers function fragment
* representation of the function.
*/
const erc20Interface = new ethers.utils.Interface(abiERC20);
const erc721Interface = new ethers.utils.Interface(abiERC721);
const erc1155Interface = new ethers.utils.Interface(abiERC1155);
export function transactionMatchesNetwork(transaction, chainId, networkId) { export function transactionMatchesNetwork(transaction, chainId, networkId) {
if (typeof transaction.chainId !== 'undefined') { if (typeof transaction.chainId !== 'undefined') {
@ -83,6 +98,36 @@ export function txParamsAreDappSuggested(transaction) {
); );
} }
/**
* Attempts to decode transaction data using ABIs for three different token standards: ERC20, ERC721, ERC1155.
* The data will decode correctly if the transaction is an interaction with a contract that matches one of these
* contract standards
*
* @param data - encoded transaction data
* @returns {EthersContractCall | undefined}
*/
export function parseStandardTokenTransactionData(data) {
try {
return erc20Interface.parseTransaction({ data });
} catch {
// ignore and next try to parse with erc721 ABI
}
try {
return erc721Interface.parseTransaction({ data });
} catch {
// ignore and next try to parse with erc1155 ABI
}
try {
return erc1155Interface.parseTransaction({ data });
} catch {
// ignore and return undefined
}
return undefined;
}
/** /**
* Determines the type of the transaction by analyzing the txParams. * Determines the type of the transaction by analyzing the txParams.
* This method will return one of the types defined in shared/constants/transactions * This method will return one of the types defined in shared/constants/transactions
@ -98,7 +143,7 @@ export async function determineTransactionType(txParams, query) {
const { data, to } = txParams; const { data, to } = txParams;
let name; let name;
try { try {
name = data && hstInterface.parseTransaction({ data }).name; ({ name } = data && parseStandardTokenTransactionData(data));
} catch (error) { } catch (error) {
log.debug('Failed to parse transaction data.', error, data); log.debug('Failed to parse transaction data.', error, data);
} }
@ -107,6 +152,7 @@ export async function determineTransactionType(txParams, query) {
TRANSACTION_TYPES.TOKEN_METHOD_APPROVE, TRANSACTION_TYPES.TOKEN_METHOD_APPROVE,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER, TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM, TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM,
TRANSACTION_TYPES.TOKEN_METHOD_SAFE_TRANSFER_FROM,
].find((methodName) => isEqualCaseInsensitive(methodName, name)); ].find((methodName) => isEqualCaseInsensitive(methodName, name));
let result; let result;

View File

@ -5,9 +5,28 @@ import {
determineTransactionType, determineTransactionType,
isEIP1559Transaction, isEIP1559Transaction,
isLegacyTransaction, isLegacyTransaction,
parseStandardTokenTransactionData,
} from './transaction.utils'; } from './transaction.utils';
describe('Transaction.utils', function () { describe('Transaction.utils', function () {
describe('parseStandardTokenTransactionData', () => {
it('should return token data', () => {
const tokenData = parseStandardTokenTransactionData(
'0xa9059cbb00000000000000000000000050a9d56c2b8ba9a5c7f2c08c3d26e0499f23a7060000000000000000000000000000000000000000000000000000000000004e20',
);
expect(tokenData).toStrictEqual(expect.anything());
const { name, args } = tokenData;
expect(name).toStrictEqual(TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER);
const to = args._to;
const value = args._value.toString();
expect(to).toStrictEqual('0x50A9D56C2B8BA9A5c7f2C08C3d26E0499F23a706');
expect(value).toStrictEqual('20000');
});
it('should not throw errors when called without arguments', () => {
expect(() => parseStandardTokenTransactionData()).not.toThrow();
});
});
describe('isEIP1559Transaction', function () { describe('isEIP1559Transaction', function () {
it('should return true if both maxFeePerGas and maxPriorityFeePerGas are hex strings', () => { it('should return true if both maxFeePerGas and maxPriorityFeePerGas are hex strings', () => {
expect( expect(

View File

@ -38,6 +38,7 @@ const ConfirmPageContainerSummary = (props) => {
TRANSACTION_TYPES.CONTRACT_INTERACTION, TRANSACTION_TYPES.CONTRACT_INTERACTION,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER, TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM, TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM,
TRANSACTION_TYPES.TOKEN_METHOD_SAFE_TRANSFER_FROM,
]; ];
const isContractTypeTransaction = contractInitiatedTransactionType.includes( const isContractTypeTransaction = contractInitiatedTransactionType.includes(
transactionType, transactionType,
@ -49,7 +50,8 @@ const ConfirmPageContainerSummary = (props) => {
// type of contract interaction it is passed as toAddress // type of contract interaction it is passed as toAddress
contractAddress = contractAddress =
transactionType === TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER || transactionType === TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER ||
transactionType === TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM transactionType === TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM ||
transactionType === TRANSACTION_TYPES.TOKEN_METHOD_SAFE_TRANSFER_FROM
? tokenAddress ? tokenAddress
: toAddress; : toAddress;
} }

View File

@ -13,14 +13,12 @@ import {
addEth, addEth,
} from '../../helpers/utils/confirm-tx.util'; } from '../../helpers/utils/confirm-tx.util';
import { import { sumHexes } from '../../helpers/utils/transactions.util';
getTransactionData,
sumHexes,
} from '../../helpers/utils/transactions.util';
import { conversionUtil } from '../../../shared/modules/conversion.utils'; import { conversionUtil } from '../../../shared/modules/conversion.utils';
import { getAveragePriceEstimateInHexWEI } from '../../selectors/custom-gas'; import { getAveragePriceEstimateInHexWEI } from '../../selectors/custom-gas';
import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils'; import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils';
import { parseStandardTokenTransactionData } from '../../../shared/modules/transaction.utils';
// Actions // Actions
const createActionType = (action) => `metamask/confirm-transaction/${action}`; const createActionType = (action) => `metamask/confirm-transaction/${action}`;
@ -285,7 +283,7 @@ export function setTransactionToConfirm(transactionId) {
if (txParams.data) { if (txParams.data) {
const { to: tokenAddress, data } = txParams; const { to: tokenAddress, data } = txParams;
const tokenData = getTransactionData(data); const tokenData = parseStandardTokenTransactionData(data);
const tokens = getTokens(state); const tokens = getTokens(state);
const currentToken = tokens?.find(({ address }) => const currentToken = tokens?.find(({ address }) =>
isEqualCaseInsensitive(tokenAddress, address), isEqualCaseInsensitive(tokenAddress, address),

View File

@ -88,6 +88,7 @@ const CONFIRM_SEND_TOKEN_PATH = '/send-token';
const CONFIRM_DEPLOY_CONTRACT_PATH = '/deploy-contract'; const CONFIRM_DEPLOY_CONTRACT_PATH = '/deploy-contract';
const CONFIRM_APPROVE_PATH = '/approve'; const CONFIRM_APPROVE_PATH = '/approve';
const CONFIRM_TRANSFER_FROM_PATH = '/transfer-from'; const CONFIRM_TRANSFER_FROM_PATH = '/transfer-from';
const CONFIRM_SAFE_TRANSFER_FROM_PATH = '/safe-transfer-from';
const CONFIRM_TOKEN_METHOD_PATH = '/token-method'; const CONFIRM_TOKEN_METHOD_PATH = '/token-method';
const SIGNATURE_REQUEST_PATH = '/signature-request'; const SIGNATURE_REQUEST_PATH = '/signature-request';
const DECRYPT_MESSAGE_REQUEST_PATH = '/decrypt-message-request'; const DECRYPT_MESSAGE_REQUEST_PATH = '/decrypt-message-request';
@ -140,6 +141,7 @@ const PATH_NAME_MAP = {
[`${CONFIRM_TRANSACTION_ROUTE}/:id${CONFIRM_DEPLOY_CONTRACT_PATH}`]: 'Confirm Deploy Contract Transaction Page', [`${CONFIRM_TRANSACTION_ROUTE}/:id${CONFIRM_DEPLOY_CONTRACT_PATH}`]: 'Confirm Deploy Contract Transaction Page',
[`${CONFIRM_TRANSACTION_ROUTE}/:id${CONFIRM_APPROVE_PATH}`]: 'Confirm Approve Transaction Page', [`${CONFIRM_TRANSACTION_ROUTE}/:id${CONFIRM_APPROVE_PATH}`]: 'Confirm Approve Transaction Page',
[`${CONFIRM_TRANSACTION_ROUTE}/:id${CONFIRM_TRANSFER_FROM_PATH}`]: 'Confirm Transfer From Transaction Page', [`${CONFIRM_TRANSACTION_ROUTE}/:id${CONFIRM_TRANSFER_FROM_PATH}`]: 'Confirm Transfer From Transaction Page',
[`${CONFIRM_TRANSACTION_ROUTE}/:id${CONFIRM_SAFE_TRANSFER_FROM_PATH}`]: 'Confirm Safe Transfer From Transaction Page',
[`${CONFIRM_TRANSACTION_ROUTE}/:id${SIGNATURE_REQUEST_PATH}`]: 'Signature Request Page', [`${CONFIRM_TRANSACTION_ROUTE}/:id${SIGNATURE_REQUEST_PATH}`]: 'Signature Request Page',
[`${CONFIRM_TRANSACTION_ROUTE}/:id${DECRYPT_MESSAGE_REQUEST_PATH}`]: 'Decrypt Message Request Page', [`${CONFIRM_TRANSACTION_ROUTE}/:id${DECRYPT_MESSAGE_REQUEST_PATH}`]: 'Decrypt Message Request Page',
[`${CONFIRM_TRANSACTION_ROUTE}/:id${ENCRYPTION_PUBLIC_KEY_REQUEST_PATH}`]: 'Encryption Public Key Request Page', [`${CONFIRM_TRANSACTION_ROUTE}/:id${ENCRYPTION_PUBLIC_KEY_REQUEST_PATH}`]: 'Encryption Public Key Request Page',
@ -200,6 +202,7 @@ export {
CONFIRM_DEPLOY_CONTRACT_PATH, CONFIRM_DEPLOY_CONTRACT_PATH,
CONFIRM_APPROVE_PATH, CONFIRM_APPROVE_PATH,
CONFIRM_TRANSFER_FROM_PATH, CONFIRM_TRANSFER_FROM_PATH,
CONFIRM_SAFE_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,

View File

@ -7,15 +7,14 @@ import {
import { getTokenStandardAndDetails } from '../../store/actions'; import { getTokenStandardAndDetails } from '../../store/actions';
import { ERC1155, ERC721 } from '../constants/common'; import { ERC1155, ERC721 } from '../constants/common';
import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils'; import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils';
import { parseStandardTokenTransactionData } from '../../../shared/modules/transaction.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 = '';
async function getSymbolFromContract(tokenAddress) { async function getSymbolFromContract(tokenAddress) {
const token = util.getContractAtAddress(tokenAddress); const token = util.getContractAtAddress(tokenAddress);
try { try {
const result = await token.symbol(); const result = await token.symbol();
return result[0]; return result[0];
@ -136,7 +135,8 @@ export function calcTokenValue(value, decimals) {
* @returns {string | undefined} A lowercase address string. * @returns {string | undefined} A lowercase address string.
*/ */
export function getTokenAddressParam(tokenData = {}) { export function getTokenAddressParam(tokenData = {}) {
const value = tokenData?.args?._to || tokenData?.args?.[0]; const value =
tokenData?.args?._to || tokenData?.args?.to || tokenData?.args?.[0];
return value?.toString().toLowerCase(); return value?.toString().toLowerCase();
} }
@ -223,7 +223,7 @@ export async function getAssetDetails(
transactionData, transactionData,
existingCollectibles, existingCollectibles,
) { ) {
const tokenData = getTransactionData(transactionData); const tokenData = parseStandardTokenTransactionData(transactionData);
if (!tokenData) { if (!tokenData) {
throw new Error('Unable to detect valid token data'); throw new Error('Unable to detect valid token data');
} }

View File

@ -1,6 +1,4 @@
import { MethodRegistry } from 'eth-method-registry'; import { MethodRegistry } from 'eth-method-registry';
import abi from 'human-standard-token-abi';
import { ethers } from 'ethers';
import log from 'loglevel'; import log from 'loglevel';
import { addHexPrefix } from '../../../app/scripts/lib/util'; import { addHexPrefix } from '../../../app/scripts/lib/util';
@ -14,8 +12,6 @@ import { addCurrencies } from '../../../shared/modules/conversion.utils';
import { readAddressAsContract } from '../../../shared/modules/contract-utils'; import { readAddressAsContract } from '../../../shared/modules/contract-utils';
import fetchWithCache from './fetch-with-cache'; import fetchWithCache from './fetch-with-cache';
const hstInterface = new ethers.utils.Interface(abi);
/** /**
* @typedef EthersContractCall * @typedef EthersContractCall
* @type object * @type object
@ -29,19 +25,6 @@ const hstInterface = new ethers.utils.Interface(abi);
* representation of the function. * representation of the function.
*/ */
/**
* @param data
* @returns {EthersContractCall | undefined}
*/
export function getTransactionData(data) {
try {
return hstInterface.parseTransaction({ data });
} catch (error) {
log.debug('Failed to parse transaction data.', error, data);
return undefined;
}
}
async function getMethodFrom4Byte(fourBytePrefix) { async function getMethodFrom4Byte(fourBytePrefix) {
const fourByteResponse = await fetchWithCache( const fourByteResponse = await fetchWithCache(
`https://www.4byte.directory/api/v1/signatures/?hex_signature=${fourBytePrefix}`, `https://www.4byte.directory/api/v1/signatures/?hex_signature=${fourBytePrefix}`,
@ -122,6 +105,7 @@ export function isTokenMethodAction(type) {
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER, TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
TRANSACTION_TYPES.TOKEN_METHOD_APPROVE, TRANSACTION_TYPES.TOKEN_METHOD_APPROVE,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM, TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM,
TRANSACTION_TYPES.TOKEN_METHOD_SAFE_TRANSFER_FROM,
].includes(type); ].includes(type);
} }
@ -215,6 +199,9 @@ export function getTransactionTypeTitle(t, type, nativeCurrency = 'ETH') {
case TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM: { case TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM: {
return t('transferFrom'); return t('transferFrom');
} }
case TRANSACTION_TYPES.TOKEN_METHOD_SAFE_TRANSFER_FROM: {
return t('safeTransferFrom');
}
case TRANSACTION_TYPES.TOKEN_METHOD_APPROVE: { case TRANSACTION_TYPES.TOKEN_METHOD_APPROVE: {
return t('approve'); return t('approve');
} }

View File

@ -1,5 +1,4 @@
import { import {
TRANSACTION_TYPES,
TRANSACTION_GROUP_STATUSES, TRANSACTION_GROUP_STATUSES,
TRANSACTION_STATUSES, TRANSACTION_STATUSES,
TRANSACTION_ENVELOPE_TYPES, TRANSACTION_ENVELOPE_TYPES,
@ -7,25 +6,6 @@ import {
import * as utils from './transactions.util'; import * as utils from './transactions.util';
describe('Transactions utils', () => { describe('Transactions utils', () => {
describe('getTransactionData', () => {
it('should return token data', () => {
const tokenData = utils.getTransactionData(
'0xa9059cbb00000000000000000000000050a9d56c2b8ba9a5c7f2c08c3d26e0499f23a7060000000000000000000000000000000000000000000000000000000000004e20',
);
expect(tokenData).toStrictEqual(expect.anything());
const { name, args } = tokenData;
expect(name).toStrictEqual(TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER);
const to = args._to;
const value = args._value.toString();
expect(to).toStrictEqual('0x50A9D56C2B8BA9A5c7f2C08C3d26E0499F23a706');
expect(value).toStrictEqual('20000');
});
it('should not throw errors when called without arguments', () => {
expect(() => utils.getTransactionData()).not.toThrow();
});
});
describe('getStatusKey', () => { describe('getStatusKey', () => {
it('should return the correct status', () => { it('should return the correct status', () => {
const tests = [ const tests = [

View File

@ -1,5 +1,6 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import { parseStandardTokenTransactionData } from '../../shared/modules/transaction.utils';
import { getCollectibles, getTokens } from '../ducks/metamask/metamask'; import { getCollectibles, getTokens } from '../ducks/metamask/metamask';
import { ERC1155, ERC721, ERC20 } from '../helpers/constants/common'; import { ERC1155, ERC721, ERC20 } from '../helpers/constants/common';
import { import {
@ -8,14 +9,12 @@ import {
getTokenAddressParam, getTokenAddressParam,
getTokenValueParam, getTokenValueParam,
} from '../helpers/utils/token-util'; } from '../helpers/utils/token-util';
import { getTransactionData } from '../helpers/utils/transactions.util';
import { getTokenList } from '../selectors'; import { getTokenList } from '../selectors';
import { hideLoadingIndication, showLoadingIndication } from '../store/actions'; import { hideLoadingIndication, showLoadingIndication } from '../store/actions';
import { usePrevious } from './usePrevious'; import { usePrevious } from './usePrevious';
export function useAssetDetails(tokenAddress, userAddress, transactionData) { export function useAssetDetails(tokenAddress, userAddress, transactionData) {
const dispatch = useDispatch(); const dispatch = useDispatch();
// state selectors // state selectors
const tokens = useSelector(getTokens); const tokens = useSelector(getTokens);
const collectibles = useSelector(getCollectibles); const collectibles = useSelector(getCollectibles);
@ -84,7 +83,7 @@ export function useAssetDetails(tokenAddress, userAddress, transactionData) {
balance, balance,
decimals: currentAssetDecimals, decimals: currentAssetDecimals,
} = currentAsset; } = currentAsset;
const tokenData = getTransactionData(transactionData); const tokenData = parseStandardTokenTransactionData(transactionData);
assetStandard = standard; assetStandard = standard;
assetAddress = tokenAddress; assetAddress = tokenAddress;
tokenSymbol = symbol; tokenSymbol = symbol;

View File

@ -1,5 +1,5 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { getTransactionData } from '../helpers/utils/transactions.util'; import { parseStandardTokenTransactionData } from '../../shared/modules/transaction.utils';
/** /**
* useTokenData * useTokenData
@ -19,6 +19,6 @@ export function useTokenData(transactionData, isTokenTransaction = true) {
if (!isTokenTransaction || !transactionData) { if (!isTokenTransaction || !transactionData) {
return null; return null;
} }
return getTransactionData(transactionData); return parseStandardTokenTransactionData(transactionData);
}, [isTokenTransaction, transactionData]); }, [isTokenTransaction, transactionData]);
} }

View File

@ -1,7 +1,7 @@
import { renderHook } from '@testing-library/react-hooks'; import { renderHook } from '@testing-library/react-hooks';
import sinon from 'sinon'; import sinon from 'sinon';
import * as tokenUtil from '../helpers/utils/token-util'; import * as tokenUtil from '../helpers/utils/token-util';
import * as txUtil from '../helpers/utils/transactions.util'; import * as txUtil from '../../shared/modules/transaction.utils';
import { useTokenDisplayValue } from './useTokenDisplayValue'; import { useTokenDisplayValue } from './useTokenDisplayValue';
const tests = [ const tests = [
@ -122,9 +122,12 @@ describe('useTokenDisplayValue', () => {
describe(`when input is decimals: ${token.decimals} and value: ${tokenValue}`, () => { describe(`when input is decimals: ${token.decimals} and value: ${tokenValue}`, () => {
it(`should return ${displayValue} as displayValue`, () => { it(`should return ${displayValue} as displayValue`, () => {
const getTokenValueStub = sinon.stub(tokenUtil, 'getTokenValueParam'); const getTokenValueStub = sinon.stub(tokenUtil, 'getTokenValueParam');
const getTokenDataStub = sinon.stub(txUtil, 'getTransactionData'); const parseStandardTokenTransactionDataStub = sinon.stub(
txUtil,
'parseStandardTokenTransactionData',
);
getTokenDataStub.callsFake(() => tokenData); parseStandardTokenTransactionDataStub.callsFake(() => tokenData);
getTokenValueStub.callsFake(() => tokenValue); getTokenValueStub.callsFake(() => tokenValue);
const { result } = renderHook(() => const { result } = renderHook(() =>

View File

@ -1,16 +1,16 @@
import { TRANSACTION_TYPES } from '../../../shared/constants/transaction'; import { TRANSACTION_TYPES } from '../../../shared/constants/transaction';
import { parseStandardTokenTransactionData } from '../../../shared/modules/transaction.utils';
import { decimalToHex } from '../../helpers/utils/conversions.util'; import { decimalToHex } from '../../helpers/utils/conversions.util';
import { import {
calcTokenValue, calcTokenValue,
getTokenAddressParam, getTokenAddressParam,
} from '../../helpers/utils/token-util'; } from '../../helpers/utils/token-util';
import { getTransactionData } from '../../helpers/utils/transactions.util';
export function getCustomTxParamsData( export function getCustomTxParamsData(
data, data,
{ customPermissionAmount, decimals }, { customPermissionAmount, decimals },
) { ) {
const tokenData = getTransactionData(data); const tokenData = parseStandardTokenTransactionData(data);
if (!tokenData) { if (!tokenData) {
throw new Error('Invalid data'); throw new Error('Invalid data');

View File

@ -43,6 +43,7 @@ import {
} from '../../ducks/metamask/metamask'; } from '../../ducks/metamask/metamask';
import { import {
parseStandardTokenTransactionData,
transactionMatchesNetwork, transactionMatchesNetwork,
txParamsAreDappSuggested, txParamsAreDappSuggested,
} from '../../../shared/modules/transaction.utils'; } from '../../../shared/modules/transaction.utils';
@ -52,6 +53,7 @@ import { getGasLoadingAnimationIsShowing } from '../../ducks/app/app';
import { isLegacyTransaction } from '../../helpers/utils/transactions.util'; import { isLegacyTransaction } from '../../helpers/utils/transactions.util';
import { CUSTOM_GAS_ESTIMATE } from '../../../shared/constants/gas'; import { CUSTOM_GAS_ESTIMATE } from '../../../shared/constants/gas';
import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils'; import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils';
import { getTokenAddressParam } from '../../helpers/utils/token-util';
import ConfirmTransactionBase from './confirm-transaction-base.component'; import ConfirmTransactionBase from './confirm-transaction-base.component';
let customNonceValue = ''; let customNonceValue = '';
@ -104,9 +106,12 @@ const mapStateToProps = (state, ownProps) => {
} = (transaction && transaction.txParams) || txParams; } = (transaction && transaction.txParams) || txParams;
const accounts = getMetaMaskAccounts(state); const accounts = getMetaMaskAccounts(state);
const transactionData = parseStandardTokenTransactionData(data);
const tokenToAddress = getTokenAddressParam(transactionData);
const { balance } = accounts[fromAddress]; const { balance } = accounts[fromAddress];
const { name: fromName } = identities[fromAddress]; const { name: fromName } = identities[fromAddress];
const toAddress = propsToAddress || txParamsToAddress; const toAddress = propsToAddress || tokenToAddress || txParamsToAddress;
const tokenList = getTokenList(state); const tokenList = getTokenList(state);
const useTokenDetection = getUseTokenDetection(state); const useTokenDetection = getUseTokenDetection(state);

View File

@ -13,6 +13,7 @@ import {
SIGNATURE_REQUEST_PATH, SIGNATURE_REQUEST_PATH,
DECRYPT_MESSAGE_REQUEST_PATH, DECRYPT_MESSAGE_REQUEST_PATH,
ENCRYPTION_PUBLIC_KEY_REQUEST_PATH, ENCRYPTION_PUBLIC_KEY_REQUEST_PATH,
CONFIRM_SAFE_TRANSFER_FROM_PATH,
} from '../../helpers/constants/routes'; } from '../../helpers/constants/routes';
import { MESSAGE_TYPE } from '../../../shared/constants/app'; import { MESSAGE_TYPE } from '../../../shared/constants/app';
import { TRANSACTION_TYPES } from '../../../shared/constants/transaction'; import { TRANSACTION_TYPES } from '../../../shared/constants/transaction';
@ -50,6 +51,10 @@ export default class ConfirmTransactionSwitch extends Component {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_TRANSFER_FROM_PATH}`; const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_TRANSFER_FROM_PATH}`;
return <Redirect to={{ pathname }} />; return <Redirect to={{ pathname }} />;
} }
case TRANSACTION_TYPES.TOKEN_METHOD_SAFE_TRANSFER_FROM: {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SAFE_TRANSFER_FROM_PATH}`;
return <Redirect to={{ pathname }} />;
}
default: { default: {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_TOKEN_METHOD_PATH}`; const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_TOKEN_METHOD_PATH}`;
return <Redirect to={{ pathname }} />; return <Redirect to={{ pathname }} />;

View File

@ -4,6 +4,7 @@ import { useSelector } from 'react-redux';
import { Switch, Route } from 'react-router-dom'; import { Switch, Route } from 'react-router-dom';
import { import {
CONFIRM_APPROVE_PATH, CONFIRM_APPROVE_PATH,
CONFIRM_SAFE_TRANSFER_FROM_PATH,
CONFIRM_SEND_TOKEN_PATH, CONFIRM_SEND_TOKEN_PATH,
CONFIRM_TRANSACTION_ROUTE, CONFIRM_TRANSACTION_ROUTE,
CONFIRM_TRANSFER_FROM_PATH, CONFIRM_TRANSFER_FROM_PATH,
@ -88,6 +89,29 @@ export default function ConfirmTokenTransactionSwitch({ transaction }) {
/> />
)} )}
/> />
<Route
exact
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_SAFE_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}
hexMaximumTransactionFee={hexMaximumTransactionFee}
/>
)}
/>
<Route <Route
exact exact
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_SEND_TOKEN_PATH}`} path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_SEND_TOKEN_PATH}`}

View File

@ -75,7 +75,6 @@ import {
SWAPS_ERROR_ROUTE, SWAPS_ERROR_ROUTE,
AWAITING_SWAP_ROUTE, AWAITING_SWAP_ROUTE,
} from '../../../helpers/constants/routes'; } from '../../../helpers/constants/routes';
import { getTransactionData } from '../../../helpers/utils/transactions.util';
import { import {
calcTokenAmount, calcTokenAmount,
calcTokenValue, calcTokenValue,
@ -113,6 +112,7 @@ import SwapsFooter from '../swaps-footer';
import PulseLoader from '../../../components/ui/pulse-loader'; // TODO: Replace this with a different loading component. import PulseLoader from '../../../components/ui/pulse-loader'; // TODO: Replace this with a different loading component.
import Box from '../../../components/ui/box'; import Box from '../../../components/ui/box';
import { isEqualCaseInsensitive } from '../../../../shared/modules/string-utils'; import { isEqualCaseInsensitive } from '../../../../shared/modules/string-utils';
import { parseStandardTokenTransactionData } from '../../../../shared/modules/transaction.utils';
import ViewQuotePriceDifference from './view-quote-price-difference'; import ViewQuotePriceDifference from './view-quote-price-difference';
let intervalId; let intervalId;
@ -320,7 +320,7 @@ export default function ViewQuote() {
const tokenBalanceUnavailable = const tokenBalanceUnavailable =
tokensWithBalances && balanceToken === undefined; tokensWithBalances && balanceToken === undefined;
const approveData = getTransactionData(approveTxParams?.data); const approveData = parseStandardTokenTransactionData(approveTxParams?.data);
const approveValue = approveData && getTokenValueParam(approveData); const approveValue = approveData && getTokenValueParam(approveData);
const approveAmount = const approveAmount =
approveValue && approveValue &&

View File

@ -2914,7 +2914,7 @@
gl-mat4 "1.1.4" gl-mat4 "1.1.4"
gl-vec3 "1.0.3" gl-vec3 "1.0.3"
"@metamask/metamask-eth-abis@3.0.0": "@metamask/metamask-eth-abis@3.0.0", "@metamask/metamask-eth-abis@^3.0.0":
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/@metamask/metamask-eth-abis/-/metamask-eth-abis-3.0.0.tgz#eccc0746b3ab1ab63000444403819c16e88b5272" resolved "https://registry.yarnpkg.com/@metamask/metamask-eth-abis/-/metamask-eth-abis-3.0.0.tgz#eccc0746b3ab1ab63000444403819c16e88b5272"
integrity sha512-YtIl4e1VzqwwHGafuLIVPqbcWWWqQ0Ezo8/Ci5m5OGllqE2oTTx9iVHdUmXNkgCVD37SBfwn/fm/S1IGkM8BQA== integrity sha512-YtIl4e1VzqwwHGafuLIVPqbcWWWqQ0Ezo8/Ci5m5OGllqE2oTTx9iVHdUmXNkgCVD37SBfwn/fm/S1IGkM8BQA==