Merge remote-tracking branch 'origin/develop' into sync-master

* origin/develop:
  Position the 3dot menu in the same spot on asset screen and home screen (#10642)
  Move swaps constants to the shared constants directory (#10614)
  prefer chainId over networkId in most cases (#10594)
  no more node:console (#10640)
  fix: speedup cancellation (#10579)
  Setting balance to 0x0 when the original value is undefined (#10634)
  Hide zero balance tokens at useTokenTracker layer (#10630)
  Removing double click bug from delete custom network modal (#10628)
  remove transactionCategory in favor of more types (#10615)
  Ensure permission log will only store JSON-able data (#10524)
  Replace logic for eth swap token in fetchQuotesAndSetQuoteState with getSwapsEthToken call (#10624)
  add trezor HD path for ledger wallets (#10616)
feature/default_network_editable
Mark Stacey 4 years ago
commit 9ce7b31719
  1. 4
      app/scripts/controllers/incoming-transactions.js
  2. 6
      app/scripts/controllers/permissions/permissionsLog.js
  3. 2
      app/scripts/controllers/swaps.js
  4. 62
      app/scripts/controllers/transactions/index.js
  5. 2
      app/scripts/lib/account-tracker.js
  6. 36
      app/scripts/lib/buy-eth-url.js
  7. 57
      app/scripts/metamask-controller.js
  8. 46
      app/scripts/migrations/053.js
  9. 1
      app/scripts/migrations/index.js
  10. 3
      jsconfig.json
  11. 5
      package.json
  12. 2
      shared/constants/network.js
  13. 0
      shared/constants/swaps.js
  14. 71
      shared/constants/transaction.js
  15. 34
      test/data/transaction-data.json
  16. 14
      test/unit/app/buy-eth-url.test.js
  17. 12
      test/unit/app/controllers/incoming-transactions.test.js
  18. 7
      test/unit/app/controllers/network/stubs.js
  19. 7
      test/unit/app/controllers/permissions/helpers.js
  20. 2
      test/unit/app/controllers/swaps.test.js
  21. 51
      test/unit/app/controllers/transactions/tx-controller.test.js
  22. 136
      test/unit/migrations/053.test.js
  23. 1
      ui/app/components/app/dropdowns/network-dropdown.js
  24. 12
      ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js
  25. 224
      ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js
  26. 6
      ui/app/components/app/loading-network-screen/loading-network-screen.component.js
  27. 6
      ui/app/components/app/loading-network-screen/loading-network-screen.container.js
  28. 40
      ui/app/components/app/modals/cancel-transaction/cancel-transaction.container.js
  29. 16
      ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.component.js
  30. 13
      ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.container.js
  31. 7
      ui/app/components/app/network-display/network-display.js
  32. 4
      ui/app/components/app/sidebars/sidebar.component.js
  33. 5
      ui/app/components/app/transaction-breakdown/transaction-breakdown.container.js
  34. 5
      ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js
  35. 16
      ui/app/components/app/transaction-list-item/transaction-list-item.component.js
  36. 14
      ui/app/components/app/transaction-list/transaction-list.component.js
  37. 21
      ui/app/ducks/swaps/swaps.js
  38. 8
      ui/app/helpers/constants/transactions.js
  39. 41
      ui/app/helpers/utils/transactions.util.js
  40. 4
      ui/app/helpers/utils/transactions.util.test.js
  41. 27
      ui/app/helpers/utils/util.js
  42. 51
      ui/app/hooks/tests/useCancelTransaction.test.js
  43. 44
      ui/app/hooks/tests/useRetryTransaction.test.js
  44. 8
      ui/app/hooks/tests/useTokenData.test.js
  45. 4
      ui/app/hooks/tests/useTransactionDisplayData.test.js
  46. 58
      ui/app/hooks/useCancelTransaction.js
  47. 2
      ui/app/hooks/useCurrentAsset.js
  48. 6
      ui/app/hooks/useRetryTransaction.js
  49. 12
      ui/app/hooks/useSwappedTokenValue.js
  50. 10
      ui/app/hooks/useTokenTracker.js
  51. 44
      ui/app/hooks/useTransactionDisplayData.js
  52. 2
      ui/app/pages/asset/asset.scss
  53. 4
      ui/app/pages/confirm-approve/confirm-approve.util.js
  54. 20
      ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js
  55. 9
      ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js
  56. 16
      ui/app/pages/confirm-transaction-switch/confirm-transaction-switch.component.js
  57. 4
      ui/app/pages/confirm-transaction/confirm-transaction.container.js
  58. 2
      ui/app/pages/create-account/connect-hardware/index.js
  59. 9
      ui/app/pages/routes/routes.component.js
  60. 3
      ui/app/pages/routes/routes.container.js
  61. 6
      ui/app/pages/send/send-content/add-recipient/add-recipient.js
  62. 12
      ui/app/pages/send/send.component.js
  63. 4
      ui/app/pages/send/send.container.js
  64. 20
      ui/app/pages/send/tests/send-component.test.js
  65. 2
      ui/app/pages/swaps/awaiting-swap/awaiting-swap.js
  66. 2
      ui/app/pages/swaps/build-quote/build-quote.js
  67. 2
      ui/app/pages/swaps/index.js
  68. 2
      ui/app/pages/swaps/swaps-util-test-constants.js
  69. 2
      ui/app/pages/swaps/swaps.util.js
  70. 2
      ui/app/pages/swaps/view-quote/view-quote.js
  71. 8
      ui/app/selectors/confirm-transaction.js
  72. 38
      ui/app/selectors/selectors.js
  73. 4
      ui/app/selectors/send.js
  74. 4
      ui/app/selectors/tests/confirm-transaction.test.js
  75. 7
      ui/app/selectors/tests/send.test.js
  76. 13
      ui/app/selectors/transactions.js
  77. 3
      ui/app/store/actions.js
  78. 5
      yarn.lock

@ -6,7 +6,7 @@ import { bnToHex } from '../lib/util';
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout'; import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout';
import { import {
TRANSACTION_CATEGORIES, TRANSACTION_TYPES,
TRANSACTION_STATUSES, TRANSACTION_STATUSES,
} from '../../../shared/constants/transaction'; } from '../../../shared/constants/transaction';
import { import {
@ -296,7 +296,7 @@ export default class IncomingTransactionsController {
value: bnToHex(new BN(txMeta.value)), value: bnToHex(new BN(txMeta.value)),
}, },
hash: txMeta.hash, hash: txMeta.hash,
transactionCategory: TRANSACTION_CATEGORIES.INCOMING, type: TRANSACTION_TYPES.INCOMING,
}; };
} }
} }

@ -1,4 +1,4 @@
import { cloneDeep } from 'lodash'; import stringify from 'fast-safe-stringify';
import { CAVEAT_NAMES } from '../../../../shared/constants/permissions'; import { CAVEAT_NAMES } from '../../../../shared/constants/permissions';
import { import {
HISTORY_STORE_KEY, HISTORY_STORE_KEY,
@ -151,7 +151,7 @@ export default class PermissionsLogController {
? LOG_METHOD_TYPES.internal ? LOG_METHOD_TYPES.internal
: LOG_METHOD_TYPES.restricted, : LOG_METHOD_TYPES.restricted,
origin: request.origin, origin: request.origin,
request: cloneDeep(request), request: stringify(request, null, 2),
requestTime: Date.now(), requestTime: Date.now(),
response: null, response: null,
responseTime: null, responseTime: null,
@ -174,7 +174,7 @@ export default class PermissionsLogController {
return; return;
} }
entry.response = cloneDeep(response); entry.response = stringify(response, null, 2);
entry.responseTime = time; entry.responseTime = time;
entry.success = !response.error; entry.success = !response.error;
} }

@ -13,7 +13,7 @@ import {
QUOTES_EXPIRED_ERROR, QUOTES_EXPIRED_ERROR,
QUOTES_NOT_AVAILABLE_ERROR, QUOTES_NOT_AVAILABLE_ERROR,
SWAPS_FETCH_ORDER_CONFLICT, SWAPS_FETCH_ORDER_CONFLICT,
} from '../../../ui/app/helpers/constants/swaps'; } from '../../../shared/constants/swaps';
import { import {
fetchTradesInfo as defaultFetchTradesInfo, fetchTradesInfo as defaultFetchTradesInfo,
fetchSwapsFeatureLiveness as defaultFetchSwapsFeatureLiveness, fetchSwapsFeatureLiveness as defaultFetchSwapsFeatureLiveness,

@ -19,7 +19,6 @@ import {
import { TRANSACTION_NO_CONTRACT_ERROR_KEY } from '../../../../ui/app/helpers/constants/error-keys'; import { TRANSACTION_NO_CONTRACT_ERROR_KEY } from '../../../../ui/app/helpers/constants/error-keys';
import { getSwapsTokensReceivedFromTxMeta } from '../../../../ui/app/pages/swaps/swaps.util'; import { getSwapsTokensReceivedFromTxMeta } from '../../../../ui/app/pages/swaps/swaps.util';
import { import {
TRANSACTION_CATEGORIES,
TRANSACTION_STATUSES, TRANSACTION_STATUSES,
TRANSACTION_TYPES, TRANSACTION_TYPES,
} from '../../../../shared/constants/transaction'; } from '../../../../shared/constants/transaction';
@ -235,11 +234,10 @@ export default class TransactionController extends EventEmitter {
`generateTxMeta` adds the default txMeta properties to the passed object. `generateTxMeta` adds the default txMeta properties to the passed object.
These include the tx's `id`. As we use the id for determining order of These include the tx's `id`. As we use the id for determining order of
txes in the tx-state-manager, it is necessary to call the asynchronous txes in the tx-state-manager, it is necessary to call the asynchronous
method `this._determineTransactionCategory` after `generateTxMeta`. method `this._determineTransactionType` after `generateTxMeta`.
*/ */
let txMeta = this.txStateManager.generateTxMeta({ let txMeta = this.txStateManager.generateTxMeta({
txParams: normalizedTxParams, txParams: normalizedTxParams,
type: TRANSACTION_TYPES.STANDARD,
}); });
if (origin === 'metamask') { if (origin === 'metamask') {
@ -265,11 +263,10 @@ export default class TransactionController extends EventEmitter {
txMeta.origin = origin; txMeta.origin = origin;
const { const { type, getCodeResponse } = await this._determineTransactionType(
transactionCategory, txParams,
getCodeResponse, );
} = await this._determineTransactionCategory(txParams); txMeta.type = type;
txMeta.transactionCategory = transactionCategory;
// ensure value // ensure value
txMeta.txParams.value = txMeta.txParams.value txMeta.txParams.value = txMeta.txParams.value
@ -347,7 +344,7 @@ export default class TransactionController extends EventEmitter {
return {}; return {};
} else if ( } else if (
txMeta.txParams.to && txMeta.txParams.to &&
txMeta.transactionCategory === TRANSACTION_CATEGORIES.SENT_ETHER txMeta.type === TRANSACTION_TYPES.SENT_ETHER
) { ) {
// if there's data in the params, but there's no contract code, it's not a valid transaction // if there's data in the params, but there's no contract code, it's not a valid transaction
if (txMeta.txParams.data) { if (txMeta.txParams.data) {
@ -388,7 +385,7 @@ export default class TransactionController extends EventEmitter {
* @param {string} [customGasPrice] - the hex value to use for the cancel transaction * @param {string} [customGasPrice] - the hex value to use for the cancel transaction
* @returns {txMeta} * @returns {txMeta}
*/ */
async createCancelTransaction(originalTxId, customGasPrice) { async createCancelTransaction(originalTxId, customGasPrice, customGasLimit) {
const originalTxMeta = this.txStateManager.getTx(originalTxId); const originalTxMeta = this.txStateManager.getTx(originalTxId);
const { txParams } = originalTxMeta; const { txParams } = originalTxMeta;
const { gasPrice: lastGasPrice, from, nonce } = txParams; const { gasPrice: lastGasPrice, from, nonce } = txParams;
@ -401,7 +398,7 @@ export default class TransactionController extends EventEmitter {
from, from,
to: from, to: from,
nonce, nonce,
gas: '0x5208', gas: customGasLimit || '0x5208',
value: '0x0', value: '0x0',
gasPrice: newGasPrice, gasPrice: newGasPrice,
}, },
@ -581,7 +578,7 @@ export default class TransactionController extends EventEmitter {
async publishTransaction(txId, rawTx) { async publishTransaction(txId, rawTx) {
const txMeta = this.txStateManager.getTx(txId); const txMeta = this.txStateManager.getTx(txId);
txMeta.rawTx = rawTx; txMeta.rawTx = rawTx;
if (txMeta.transactionCategory === TRANSACTION_CATEGORIES.SWAP) { if (txMeta.type === TRANSACTION_TYPES.SWAP) {
const preTxBalance = await this.query.getBalance(txMeta.txParams.from); const preTxBalance = await this.query.getBalance(txMeta.txParams.from);
txMeta.preTxBalance = preTxBalance.toString(16); txMeta.preTxBalance = preTxBalance.toString(16);
} }
@ -637,7 +634,7 @@ export default class TransactionController extends EventEmitter {
'transactions#confirmTransaction - add txReceipt', 'transactions#confirmTransaction - add txReceipt',
); );
if (txMeta.transactionCategory === TRANSACTION_CATEGORIES.SWAP) { if (txMeta.type === TRANSACTION_TYPES.SWAP) {
const postTxBalance = await this.query.getBalance(txMeta.txParams.from); const postTxBalance = await this.query.getBalance(txMeta.txParams.from);
const latestTxMeta = this.txStateManager.getTx(txId); const latestTxMeta = this.txStateManager.getTx(txId);
@ -812,10 +809,27 @@ export default class TransactionController extends EventEmitter {
} }
/** /**
Returns a "type" for a transaction out of the following list: simpleSend, tokenTransfer, tokenApprove, * @typedef { 'transfer' | 'approve' | 'transferfrom' | 'contractInteraction'| 'sentEther' } InferrableTransactionTypes
contractDeployment, contractMethodCall */
*/
async _determineTransactionCategory(txParams) { /**
* @typedef {Object} InferTransactionTypeResult
* @property {InferrableTransactionTypes} type - The type of transaction
* @property {string} getCodeResponse - The contract code, in hex format if
* it exists. '0x0' or '0x' are also indicators of non-existent contract
* code
*/
/**
* Determines the type of the transaction by analyzing the txParams.
* This method will return one of the types defined in shared/constants/transactions
* It will never return TRANSACTION_TYPE_CANCEL or TRANSACTION_TYPE_RETRY as these
* represent specific events that we control from the extension and are added manually
* at transaction creation.
* @param {Object} txParams - Parameters for the transaction
* @returns {InferTransactionTypeResult}
*/
async _determineTransactionType(txParams) {
const { data, to } = txParams; const { data, to } = txParams;
let name; let name;
try { try {
@ -825,16 +839,16 @@ export default class TransactionController extends EventEmitter {
} }
const tokenMethodName = [ const tokenMethodName = [
TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE, TRANSACTION_TYPES.TOKEN_METHOD_APPROVE,
TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER, TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER_FROM, TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM,
].find((methodName) => methodName === name && name.toLowerCase()); ].find((methodName) => methodName === name && name.toLowerCase());
let result; let result;
if (data && tokenMethodName) { if (data && tokenMethodName) {
result = tokenMethodName; result = tokenMethodName;
} else if (data && !to) { } else if (data && !to) {
result = TRANSACTION_CATEGORIES.DEPLOY_CONTRACT; result = TRANSACTION_TYPES.DEPLOY_CONTRACT;
} }
let code; let code;
@ -849,11 +863,11 @@ export default class TransactionController extends EventEmitter {
const codeIsEmpty = !code || code === '0x' || code === '0x0'; const codeIsEmpty = !code || code === '0x' || code === '0x0';
result = codeIsEmpty result = codeIsEmpty
? TRANSACTION_CATEGORIES.SENT_ETHER ? TRANSACTION_TYPES.SENT_ETHER
: TRANSACTION_CATEGORIES.CONTRACT_INTERACTION; : TRANSACTION_TYPES.CONTRACT_INTERACTION;
} }
return { transactionCategory: result, getCodeResponse: code }; return { type: result, getCodeResponse: code };
} }
/** /**

@ -286,7 +286,7 @@ export default class AccountTracker {
return; return;
} }
addresses.forEach((address, index) => { addresses.forEach((address, index) => {
const balance = bnToHex(result[index]); const balance = result[index] ? bnToHex(result[index]) : '0x0';
accounts[address] = { address, balance }; accounts[address] = { address, balance };
}); });
this.store.updateState({ accounts }); this.store.updateState({ accounts });

@ -1,18 +1,26 @@
import {
GOERLI_CHAIN_ID,
KOVAN_CHAIN_ID,
MAINNET_CHAIN_ID,
RINKEBY_CHAIN_ID,
ROPSTEN_CHAIN_ID,
} from '../../../shared/constants/network';
/** /**
* Gives the caller a url at which the user can acquire eth, depending on the network they are in * Gives the caller a url at which the user can acquire eth, depending on the network they are in
* *
* @param {Object} opts - Options required to determine the correct url * @param {Object} opts - Options required to determine the correct url
* @param {string} opts.network - The network for which to return a url * @param {string} opts.chainId - The chainId for which to return a url
* @param {string} opts.address - The address the bought ETH should be sent to. Only relevant if network === '1'. * @param {string} opts.address - The address the bought ETH should be sent to. Only relevant if chainId === '0x1'.
* @returns {string|undefined} The url at which the user can access ETH, while in the given network. If the passed * @returns {string|undefined} The url at which the user can access ETH, while in the given chain. If the passed
* network does not match any of the specified cases, or if no network is given, returns undefined. * chainId does not match any of the specified cases, or if no chainId is given, returns undefined.
* *
*/ */
export default function getBuyEthUrl({ network, address, service }) { export default function getBuyEthUrl({ chainId, address, service }) {
// default service by network if not specified // default service by network if not specified
if (!service) { if (!service) {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
service = getDefaultServiceForNetwork(network); service = getDefaultServiceForChain(chainId);
} }
switch (service) { switch (service) {
@ -33,21 +41,21 @@ export default function getBuyEthUrl({ network, address, service }) {
} }
} }
function getDefaultServiceForNetwork(network) { function getDefaultServiceForChain(chainId) {
switch (network) { switch (chainId) {
case '1': case MAINNET_CHAIN_ID:
return 'wyre'; return 'wyre';
case '3': case ROPSTEN_CHAIN_ID:
return 'metamask-faucet'; return 'metamask-faucet';
case '4': case RINKEBY_CHAIN_ID:
return 'rinkeby-faucet'; return 'rinkeby-faucet';
case '42': case KOVAN_CHAIN_ID:
return 'kovan-faucet'; return 'kovan-faucet';
case '5': case GOERLI_CHAIN_ID:
return 'goerli-faucet'; return 'goerli-faucet';
default: default:
throw new Error( throw new Error(
`No default cryptocurrency exchange or faucet for networkId: "${network}"`, `No default cryptocurrency exchange or faucet for chainId: "${chainId}"`,
); );
} }
} }

@ -24,7 +24,6 @@ import {
CurrencyRateController, CurrencyRateController,
PhishingController, PhishingController,
} from '@metamask/controllers'; } from '@metamask/controllers';
import { getBackgroundMetaMetricState } from '../../ui/app/selectors';
import { TRANSACTION_STATUSES } from '../../shared/constants/transaction'; import { TRANSACTION_STATUSES } from '../../shared/constants/transaction';
import { MAINNET_CHAIN_ID } from '../../shared/constants/network'; import { MAINNET_CHAIN_ID } from '../../shared/constants/network';
import ComposableObservableStore from './lib/ComposableObservableStore'; import ComposableObservableStore from './lib/ComposableObservableStore';
@ -330,12 +329,23 @@ export default class MetamaskController extends EventEmitter {
this.platform.showTransactionNotification(txMeta, rpcPrefs); this.platform.showTransactionNotification(txMeta, rpcPrefs);
const { txReceipt } = txMeta; const { txReceipt } = txMeta;
const metamaskState = await this.getState();
if (txReceipt && txReceipt.status === '0x0') { if (txReceipt && txReceipt.status === '0x0') {
this.sendBackgroundMetaMetrics({ this.metaMetricsController.trackEvent(
action: 'Transactions', {
name: 'On Chain Failure', category: 'Background',
customVariables: { errorMessage: txMeta.simulationFails?.reason }, properties: {
}); action: 'Transactions',
errorMessage: txMeta.simulationFails?.reason,
numberOfTokens: metamaskState.tokens.length,
numberOfAccounts: Object.keys(metamaskState.accounts).length,
},
},
{
matomoEvent: true,
},
);
} }
} }
}); });
@ -577,7 +587,7 @@ export default class MetamaskController extends EventEmitter {
const isInitialized = Boolean(vault); const isInitialized = Boolean(vault);
return { return {
...{ isInitialized }, isInitialized,
...this.memStore.getFlatState(), ...this.memStore.getFlatState(),
}; };
} }
@ -1058,10 +1068,6 @@ export default class MetamaskController extends EventEmitter {
}); });
} }
getCurrentNetwork = () => {
return this.networkController.store.getState().network;
};
/** /**
* Collects all the information that we want to share * Collects all the information that we want to share
* with the mobile client for syncing purposes * with the mobile client for syncing purposes
@ -1858,10 +1864,11 @@ export default class MetamaskController extends EventEmitter {
* @param {string} [customGasPrice] - the hex value to use for the cancel transaction * @param {string} [customGasPrice] - the hex value to use for the cancel transaction
* @returns {Object} MetaMask state * @returns {Object} MetaMask state
*/ */
async createCancelTransaction(originalTxId, customGasPrice) { async createCancelTransaction(originalTxId, customGasPrice, customGasLimit) {
await this.txController.createCancelTransaction( await this.txController.createCancelTransaction(
originalTxId, originalTxId,
customGasPrice, customGasPrice,
customGasLimit,
); );
const state = await this.getState(); const state = await this.getState();
return state; return state;
@ -2413,32 +2420,6 @@ export default class MetamaskController extends EventEmitter {
return nonceLock.nextNonce; return nonceLock.nextNonce;
} }
async sendBackgroundMetaMetrics({ action, name, customVariables } = {}) {
if (!action || !name) {
throw new Error('Must provide action and name.');
}
const metamaskState = await this.getState();
const additionalProperties = getBackgroundMetaMetricState({
metamask: metamaskState,
});
this.metaMetricsController.trackEvent(
{
event: name,
category: 'Background',
properties: {
action,
...additionalProperties,
...customVariables,
},
},
{
matomoEvent: true,
},
);
}
/** /**
* Migrate address book state from old to new chainId. * Migrate address book state from old to new chainId.
* *

@ -0,0 +1,46 @@
import { cloneDeep } from 'lodash';
import { TRANSACTION_TYPES } from '../../../shared/constants/transaction';
const version = 53;
/**
* Deprecate transactionCategory and consolidate on 'type'
*/
export default {
version,
async migrate(originalVersionedData) {
const versionedData = cloneDeep(originalVersionedData);
versionedData.meta.version = version;
const state = versionedData.data;
versionedData.data = transformState(state);
return versionedData;
},
};
function transformState(state) {
const transactions = state?.TransactionController?.transactions;
const incomingTransactions =
state?.IncomingTransactionsController?.incomingTransactions;
if (Array.isArray(transactions)) {
transactions.forEach((transaction) => {
if (
transaction.type !== TRANSACTION_TYPES.RETRY &&
transaction.type !== TRANSACTION_TYPES.CANCEL
) {
transaction.type = transaction.transactionCategory;
}
delete transaction.transactionCategory;
});
}
if (incomingTransactions) {
const incomingTransactionsEntries = Object.entries(incomingTransactions);
incomingTransactionsEntries.forEach(([key, transaction]) => {
delete transaction.transactionCategory;
state.IncomingTransactionsController.incomingTransactions[key] = {
...transaction,
type: TRANSACTION_TYPES.INCOMING,
};
});
}
return state;
}

@ -57,6 +57,7 @@ const migrations = [
require('./050').default, require('./050').default,
require('./051').default, require('./051').default,
require('./052').default, require('./052').default,
require('./053').default,
]; ];
export default migrations; export default migrations;

@ -0,0 +1,3 @@
{
"exclude": ["node:console"]
}

@ -134,6 +134,7 @@
"extension-port-stream": "^2.0.0", "extension-port-stream": "^2.0.0",
"extensionizer": "^1.0.1", "extensionizer": "^1.0.1",
"fast-json-patch": "^2.0.4", "fast-json-patch": "^2.0.4",
"fast-safe-stringify": "^2.0.7",
"fuse.js": "^3.2.0", "fuse.js": "^3.2.0",
"globalthis": "^1.0.1", "globalthis": "^1.0.1",
"human-standard-token-abi": "^2.0.0", "human-standard-token-abi": "^2.0.0",
@ -175,7 +176,6 @@
"reselect": "^3.0.1", "reselect": "^3.0.1",
"rpc-cap": "^3.2.1", "rpc-cap": "^3.2.1",
"safe-event-emitter": "^1.0.1", "safe-event-emitter": "^1.0.1",
"safe-json-stringify": "^1.2.0",
"single-call-balance-checker-abi": "^1.0.0", "single-call-balance-checker-abi": "^1.0.0",
"swappable-obj-proxy": "^1.1.0", "swappable-obj-proxy": "^1.1.0",
"textarea-caret": "^3.0.1", "textarea-caret": "^3.0.1",
@ -317,7 +317,8 @@
"gc-stats": false, "gc-stats": false,
"github:assemblyscript/assemblyscript": false, "github:assemblyscript/assemblyscript": false,
"tiny-secp256k1": false, "tiny-secp256k1": false,
"@lavamoat/preinstall-always-fail": false "@lavamoat/preinstall-always-fail": false,
"fsevents": false
} }
} }
} }

@ -10,12 +10,14 @@ export const ROPSTEN_NETWORK_ID = '3';
export const RINKEBY_NETWORK_ID = '4'; export const RINKEBY_NETWORK_ID = '4';
export const GOERLI_NETWORK_ID = '5'; export const GOERLI_NETWORK_ID = '5';
export const KOVAN_NETWORK_ID = '42'; export const KOVAN_NETWORK_ID = '42';
export const LOCALHOST_NETWORK_ID = '1337';
export const MAINNET_CHAIN_ID = '0x1'; export const MAINNET_CHAIN_ID = '0x1';
export const ROPSTEN_CHAIN_ID = '0x3'; export const ROPSTEN_CHAIN_ID = '0x3';
export const RINKEBY_CHAIN_ID = '0x4'; export const RINKEBY_CHAIN_ID = '0x4';
export const GOERLI_CHAIN_ID = '0x5'; export const GOERLI_CHAIN_ID = '0x5';
export const KOVAN_CHAIN_ID = '0x2a'; export const KOVAN_CHAIN_ID = '0x2a';
export const LOCALHOST_CHAIN_ID = '0x539';
/** /**
* The largest possible chain ID we can handle. * The largest possible chain ID we can handle.

@ -1,8 +1,26 @@
/** /**
* Transaction Type is a MetaMask construct used internally * Transaction Type is a MetaMask construct used internally
* @typedef {Object} TransactionTypes * @typedef {Object} TransactionTypes
* @property {'standard'} STANDARD - A standard transaction, usually the first with * @property {'transfer'} TOKEN_METHOD_TRANSFER - A token transaction where the user
* a given nonce * is sending tokens that they own to another address
* @property {'transferfrom'} TOKEN_METHOD_TRANSFER_FROM - A token transaction
* transferring tokens from an account that the sender has an allowance of.
* For more information on allowances, see the approve type.
* @property {'approve'} TOKEN_METHOD_APPROVE - A token transaction requesting an
* allowance of the token to spend on behalf of the user
* @property {'incoming'} INCOMING - An incoming (deposit) transaction
* @property {'sentEther'} SENT_ETHER - A transaction sending ether to a recipient
* @property {'contractInteraction'} CONTRACT_INTERACTION - A transaction that is
* interacting with a smart contract's methods that we have not treated as a special
* case, such as approve, transfer, and transferfrom
* @property {'contractDeployment'} DEPLOY_CONTRACT - A transaction that deployed
* a smart contract
* @property {'swap'} SWAP - A transaction swapping one token for another through
* MetaMask Swaps
* @property {'swapApproval'} SWAP_APPROVAL - Similar to the approve type, a swap
* approval is a special case of ERC20 approve method that requests an allowance of
* the token to spend on behalf of the user for the MetaMask Swaps contract. The first
* swap for any token will have an accompanying swapApproval transaction.
* @property {'cancel'} CANCEL - A transaction submitted with the same nonce as a * @property {'cancel'} CANCEL - A transaction submitted with the same nonce as a
* previous transaction, a higher gas price and a zeroed out send amount. Useful * previous transaction, a higher gas price and a zeroed out send amount. Useful
* for users who accidentally send to erroneous addresses or if they send too much. * for users who accidentally send to erroneous addresses or if they send too much.
@ -16,9 +34,17 @@
* @type {TransactionTypes} * @type {TransactionTypes}
*/ */
export const TRANSACTION_TYPES = { export const TRANSACTION_TYPES = {
STANDARD: 'standard',
CANCEL: 'cancel', CANCEL: 'cancel',
RETRY: 'retry', RETRY: 'retry',
TOKEN_METHOD_TRANSFER: 'transfer',
TOKEN_METHOD_TRANSFER_FROM: 'transferfrom',
TOKEN_METHOD_APPROVE: 'approve',
INCOMING: 'incoming',
SENT_ETHER: 'sentEther',
CONTRACT_INTERACTION: 'contractInteraction',
DEPLOY_CONTRACT: 'contractDeployment',
SWAP: 'swap',
SWAP_APPROVAL: 'swapApproval',
}; };
/** /**
@ -53,45 +79,6 @@ export const TRANSACTION_STATUSES = {
CONFIRMED: 'confirmed', CONFIRMED: 'confirmed',
}; };
/**
* @typedef {Object} TransactionCategories
* @property {'transfer'} TOKEN_METHOD_TRANSFER - A token transaction where the user
* is sending tokens that they own to another address
* @property {'transferfrom'} TOKEN_METHOD_TRANSFER_FROM - A token transaction
* transferring tokens from an account that the sender has an allowance of.
* For more information on allowances, see the approve category.
* @property {'approve'} TOKEN_METHOD_APPROVE - A token transaction requesting an
* allowance of the token to spend on behalf of the user
* @property {'incoming'} INCOMING - An incoming (deposit) transaction
* @property {'sentEther'} SENT_ETHER - A transaction sending ether to a recipient
* @property {'contractInteraction'} CONTRACT_INTERACTION - A transaction that is
* interacting with a smart contract's methods that we have not treated as a special
* case, such as approve, transfer, and transferfrom
* @property {'contractDeployment'} DEPLOY_CONTRACT - A transaction that deployed
* a smart contract
* @property {'swap'} SWAP - A transaction swapping one token for another through
* MetaMask Swaps
* @property {'swapApproval'} SWAP_APPROVAL - Similar to the approve category, a swap
* approval is a special case of ERC20 approve method that requests an allowance of
* the token to spend on behalf of the user for the MetaMask Swaps contract. The first
* swap for any token will have an accompanying swapApproval transaction.
*/
/**
* @type {TransactionCategories}
*/
export const TRANSACTION_CATEGORIES = {
TOKEN_METHOD_TRANSFER: 'transfer',
TOKEN_METHOD_TRANSFER_FROM: 'transferfrom',
TOKEN_METHOD_APPROVE: 'approve',
INCOMING: 'incoming',
SENT_ETHER: 'sentEther',
CONTRACT_INTERACTION: 'contractInteraction',
DEPLOY_CONTRACT: 'contractDeployment',
SWAP: 'swap',
SWAP_APPROVAL: 'swapApproval',
};
/** /**
* Transaction Group Status is a MetaMask construct to track the status of groups * Transaction Group Status is a MetaMask construct to track the status of groups
* of transactions. * of transactions.

@ -15,9 +15,8 @@
"gas": "0x5208", "gas": "0x5208",
"gasPrice": "0x2540be400" "gasPrice": "0x2540be400"
}, },
"type": "standard",
"origin": "metamask", "origin": "metamask",
"transactionCategory": "sentEther", "type": "sentEther",
"nonceDetails": { "nonceDetails": {
"params": { "params": {
"highestLocallyConfirmed": 12, "highestLocallyConfirmed": 12,
@ -76,9 +75,8 @@
"gas": "0x5208", "gas": "0x5208",
"gasPrice": "0x2540be400" "gasPrice": "0x2540be400"
}, },
"type": "standard",
"origin": "metamask", "origin": "metamask",
"transactionCategory": "sentEther", "type": "sentEther",
"nonceDetails": { "nonceDetails": {
"params": { "params": {
"highestLocallyConfirmed": 12, "highestLocallyConfirmed": 12,
@ -142,9 +140,8 @@
"gas": "0x5208", "gas": "0x5208",
"gasPrice": "0x2540be400" "gasPrice": "0x2540be400"
}, },
"type": "standard",
"origin": "metamask", "origin": "metamask",
"transactionCategory": "sentEther", "type": "sentEther",
"nonceDetails": { "nonceDetails": {
"params": { "params": {
"highestLocallyConfirmed": 0, "highestLocallyConfirmed": 0,
@ -203,9 +200,8 @@
"gas": "0x5208", "gas": "0x5208",
"gasPrice": "0x2540be400" "gasPrice": "0x2540be400"
}, },
"type": "standard",
"origin": "metamask", "origin": "metamask",
"transactionCategory": "sentEther", "type": "sentEther",
"nonceDetails": { "nonceDetails": {
"params": { "params": {
"highestLocallyConfirmed": 0, "highestLocallyConfirmed": 0,
@ -269,9 +265,8 @@
"gas": "0x5208", "gas": "0x5208",
"gasPrice": "0x306dc4200" "gasPrice": "0x306dc4200"
}, },
"type": "standard",
"origin": "metamask", "origin": "metamask",
"transactionCategory": "sentEther", "type": "sentEther",
"nonceDetails": { "nonceDetails": {
"params": { "params": {
"highestLocallyConfirmed": 0, "highestLocallyConfirmed": 0,
@ -331,9 +326,8 @@
"gas": "0x5208", "gas": "0x5208",
"gasPrice": "0x306dc4200" "gasPrice": "0x306dc4200"
}, },
"type": "standard",
"origin": "metamask", "origin": "metamask",
"transactionCategory": "sentEther", "type": "sentEther",
"nonceDetails": { "nonceDetails": {
"params": { "params": {
"highestLocallyConfirmed": 0, "highestLocallyConfirmed": 0,
@ -398,7 +392,7 @@
"value": "0x1043561a882930000" "value": "0x1043561a882930000"
}, },
"hash": "0x5ca26d1cdcabef1ac2ad5b2b38604c9ced65d143efc7525f848c46f28e0e4116", "hash": "0x5ca26d1cdcabef1ac2ad5b2b38604c9ced65d143efc7525f848c46f28e0e4116",
"transactionCategory": "incoming" "type": "incoming"
}, },
"primaryTransaction": { "primaryTransaction": {
"blockNumber": "6477257", "blockNumber": "6477257",
@ -415,7 +409,7 @@
"value": "0x1043561a882930000" "value": "0x1043561a882930000"
}, },
"hash": "0x5ca26d1cdcabef1ac2ad5b2b38604c9ced65d143efc7525f848c46f28e0e4116", "hash": "0x5ca26d1cdcabef1ac2ad5b2b38604c9ced65d143efc7525f848c46f28e0e4116",
"transactionCategory": "incoming" "type": "incoming"
}, },
"hasRetried": false, "hasRetried": false,
"hasCancelled": false "hasCancelled": false
@ -436,7 +430,7 @@
"value": "0x0" "value": "0x0"
}, },
"hash": "0xa42b2b433e5bd2616b52e30792aedb6a3c374a752a95d43d99e2a8b143937889", "hash": "0xa42b2b433e5bd2616b52e30792aedb6a3c374a752a95d43d99e2a8b143937889",
"transactionCategory": "incoming" "type": "incoming"
}, },
"primaryTransaction": { "primaryTransaction": {
"blockNumber": "6454493", "blockNumber": "6454493",
@ -453,7 +447,7 @@
"value": "0x0" "value": "0x0"
}, },
"hash": "0xa42b2b433e5bd2616b52e30792aedb6a3c374a752a95d43d99e2a8b143937889", "hash": "0xa42b2b433e5bd2616b52e30792aedb6a3c374a752a95d43d99e2a8b143937889",
"transactionCategory": "incoming" "type": "incoming"
}, },
"hasRetried": false, "hasRetried": false,
"hasCancelled": false "hasCancelled": false
@ -474,7 +468,7 @@
"value": "0xde0b6b3a7640000" "value": "0xde0b6b3a7640000"
}, },
"hash": "0xbcb195f393f4468945b4045cd41bcdbc2f19ad75ae92a32cf153a3004e42009a", "hash": "0xbcb195f393f4468945b4045cd41bcdbc2f19ad75ae92a32cf153a3004e42009a",
"transactionCategory": "incoming" "type": "incoming"
}, },
"primaryTransaction": { "primaryTransaction": {
"blockNumber": "6195526", "blockNumber": "6195526",
@ -491,7 +485,7 @@
"value": "0xde0b6b3a7640000" "value": "0xde0b6b3a7640000"
}, },
"hash": "0xbcb195f393f4468945b4045cd41bcdbc2f19ad75ae92a32cf153a3004e42009a", "hash": "0xbcb195f393f4468945b4045cd41bcdbc2f19ad75ae92a32cf153a3004e42009a",
"transactionCategory": "incoming" "type": "incoming"
}, },
"hasRetried": false, "hasRetried": false,
"hasCancelled": false "hasCancelled": false
@ -514,7 +508,7 @@
"hash": "0xbcb195f393f4468945b4045cd41bcdbc2f19ad75ae92a32cf153a3004e42009a", "hash": "0xbcb195f393f4468945b4045cd41bcdbc2f19ad75ae92a32cf153a3004e42009a",
"destinationTokenSymbol": "ABC", "destinationTokenSymbol": "ABC",
"sourceTokenSymbol": "ETH", "sourceTokenSymbol": "ETH",
"transactionCategory": "swap" "type": "swap"
}, },
"primaryTransaction": { "primaryTransaction": {
"blockNumber": "6195527", "blockNumber": "6195527",
@ -531,7 +525,7 @@
"value": "0xde0b6b3a7640000" "value": "0xde0b6b3a7640000"
}, },
"hash": "0xbcb195f393f4468945b4045cd41bcdbc2f19ad75ae92a32cf153a3004e42009a", "hash": "0xbcb195f393f4468945b4045cd41bcdbc2f19ad75ae92a32cf153a3004e42009a",
"transactionCategory": "swap", "type": "swap",
"destinationTokenSymbol": "ABC", "destinationTokenSymbol": "ABC",
"destinationTokenAddress": "0xabca64466f257793eaa52fcfff5066894b76a149", "destinationTokenAddress": "0xabca64466f257793eaa52fcfff5066894b76a149",
"sourceTokenSymbol": "ETH" "sourceTokenSymbol": "ETH"

@ -1,20 +1,26 @@
import assert from 'assert'; import assert from 'assert';
import getBuyEthUrl from '../../../app/scripts/lib/buy-eth-url'; import getBuyEthUrl from '../../../app/scripts/lib/buy-eth-url';
import {
KOVAN_CHAIN_ID,
MAINNET_CHAIN_ID,
RINKEBY_CHAIN_ID,
ROPSTEN_CHAIN_ID,
} from '../../../shared/constants/network';
describe('buy-eth-url', function () { describe('buy-eth-url', function () {
const mainnet = { const mainnet = {
network: '1', chainId: MAINNET_CHAIN_ID,
amount: 5, amount: 5,
address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
}; };
const ropsten = { const ropsten = {
network: '3', chainId: ROPSTEN_CHAIN_ID,
}; };
const rinkeby = { const rinkeby = {
network: '4', chainId: RINKEBY_CHAIN_ID,
}; };
const kovan = { const kovan = {
network: '42', chainId: KOVAN_CHAIN_ID,
}; };
it('returns wyre url with address for network 1', function () { it('returns wyre url with address for network 1', function () {

@ -16,7 +16,7 @@ import {
ROPSTEN_NETWORK_ID, ROPSTEN_NETWORK_ID,
} from '../../../../shared/constants/network'; } from '../../../../shared/constants/network';
import { import {
TRANSACTION_CATEGORIES, TRANSACTION_TYPES,
TRANSACTION_STATUSES, TRANSACTION_STATUSES,
} from '../../../../shared/constants/transaction'; } from '../../../../shared/constants/transaction';
import { NETWORK_EVENTS } from '../../../../app/scripts/controllers/network'; import { NETWORK_EVENTS } from '../../../../app/scripts/controllers/network';
@ -276,7 +276,7 @@ describe('IncomingTransactionsController', function () {
chainId: ROPSTEN_CHAIN_ID, chainId: ROPSTEN_CHAIN_ID,
status: TRANSACTION_STATUSES.CONFIRMED, status: TRANSACTION_STATUSES.CONFIRMED,
time: 16000000000000000, time: 16000000000000000,
transactionCategory: TRANSACTION_CATEGORIES.INCOMING, type: TRANSACTION_TYPES.INCOMING,
txParams: { txParams: {
from: '0xfake', from: '0xfake',
gas: '0x0', gas: '0x0',
@ -620,7 +620,7 @@ describe('IncomingTransactionsController', function () {
chainId: ROPSTEN_CHAIN_ID, chainId: ROPSTEN_CHAIN_ID,
status: TRANSACTION_STATUSES.CONFIRMED, status: TRANSACTION_STATUSES.CONFIRMED,
time: 16000000000000000, time: 16000000000000000,
transactionCategory: TRANSACTION_CATEGORIES.INCOMING, type: TRANSACTION_TYPES.INCOMING,
txParams: { txParams: {
from: '0xfake', from: '0xfake',
gas: '0x0', gas: '0x0',
@ -775,7 +775,7 @@ describe('IncomingTransactionsController', function () {
chainId: ROPSTEN_CHAIN_ID, chainId: ROPSTEN_CHAIN_ID,
status: TRANSACTION_STATUSES.CONFIRMED, status: TRANSACTION_STATUSES.CONFIRMED,
time: 16000000000000000, time: 16000000000000000,
transactionCategory: TRANSACTION_CATEGORIES.INCOMING, type: TRANSACTION_TYPES.INCOMING,
txParams: { txParams: {
from: '0xfake', from: '0xfake',
gas: '0x0', gas: '0x0',
@ -1362,7 +1362,7 @@ describe('IncomingTransactionsController', function () {
value: '0xf', value: '0xf',
}, },
hash: '0xg', hash: '0xg',
transactionCategory: TRANSACTION_CATEGORIES.INCOMING, type: TRANSACTION_TYPES.INCOMING,
}); });
}); });
@ -1408,7 +1408,7 @@ describe('IncomingTransactionsController', function () {
value: '0xf', value: '0xf',
}, },
hash: '0xg', hash: '0xg',
transactionCategory: TRANSACTION_CATEGORIES.INCOMING, type: TRANSACTION_TYPES.INCOMING,
}); });
}); });
}); });

@ -1,5 +1,4 @@
import { import {
TRANSACTION_CATEGORIES,
TRANSACTION_STATUSES, TRANSACTION_STATUSES,
TRANSACTION_TYPES, TRANSACTION_TYPES,
} from '../../../../../shared/constants/transaction'; } from '../../../../../shared/constants/transaction';
@ -14,7 +13,7 @@ export const txMetaStub = {
metamaskNetworkId: '4', metamaskNetworkId: '4',
status: TRANSACTION_STATUSES.UNAPPROVED, status: TRANSACTION_STATUSES.UNAPPROVED,
time: 1572395156620, time: 1572395156620,
transactionCategory: TRANSACTION_CATEGORIES.SENT_ETHER, type: TRANSACTION_TYPES.SENT_ETHER,
txParams: { txParams: {
from: '0xf231d46dd78806e1dd93442cf33c7671f8538748', from: '0xf231d46dd78806e1dd93442cf33c7671f8538748',
gas: '0x5208', gas: '0x5208',
@ -22,7 +21,6 @@ export const txMetaStub = {
to: '0xf231d46dd78806e1dd93442cf33c7671f8538748', to: '0xf231d46dd78806e1dd93442cf33c7671f8538748',
value: '0x0', value: '0x0',
}, },
type: TRANSACTION_TYPES.STANDARD,
}, },
[ [
{ {
@ -196,7 +194,7 @@ export const txMetaStub = {
status: TRANSACTION_STATUSES.SUBMITTED, status: TRANSACTION_STATUSES.SUBMITTED,
submittedTime: 1572395158570, submittedTime: 1572395158570,
time: 1572395156620, time: 1572395156620,
transactionCategory: TRANSACTION_CATEGORIES.SENT_ETHER, type: TRANSACTION_TYPES.SENT_ETHER,
txParams: { txParams: {
from: '0xf231d46dd78806e1dd93442cf33c7671f8538748', from: '0xf231d46dd78806e1dd93442cf33c7671f8538748',
gas: '0x5208', gas: '0x5208',
@ -205,6 +203,5 @@ export const txMetaStub = {
to: '0xf231d46dd78806e1dd93442cf33c7671f8538748', to: '0xf231d46dd78806e1dd93442cf33c7671f8538748',
value: '0x0', value: '0x0',
}, },
type: TRANSACTION_TYPES.STANDARD,
v: '0x2c', v: '0x2c',
}; };

@ -1,4 +1,5 @@
import { strict as assert } from 'assert'; import { strict as assert } from 'assert';
import stringify from 'fast-safe-stringify';
import { noop } from './mocks'; import { noop } from './mocks';
@ -84,9 +85,9 @@ function _validateActivityEntry(entry, req, res, methodType, success) {
assert.equal(entry.method, req.method); assert.equal(entry.method, req.method);
assert.equal(entry.origin, req.origin); assert.equal(entry.origin, req.origin);
assert.equal(entry.methodType, methodType); assert.equal(entry.methodType, methodType);
assert.deepEqual( assert.equal(
entry.request, entry.request,
req, stringify(req, null, 2),
'entry.request should equal the request', 'entry.request should equal the request',
); );
@ -104,7 +105,7 @@ function _validateActivityEntry(entry, req, res, methodType, success) {
assert.equal(entry.success, success); assert.equal(entry.success, success);
assert.deepEqual( assert.deepEqual(
entry.response, entry.response,
res, stringify(res, null, 2),
'entry.response should equal the response', 'entry.response should equal the response',
); );
} else { } else {

@ -9,7 +9,7 @@ import {
ROPSTEN_NETWORK_ID, ROPSTEN_NETWORK_ID,
MAINNET_NETWORK_ID, MAINNET_NETWORK_ID,
} from '../../../../shared/constants/network'; } from '../../../../shared/constants/network';
import { ETH_SWAPS_TOKEN_OBJECT } from '../../../../ui/app/helpers/constants/swaps'; import { ETH_SWAPS_TOKEN_OBJECT } from '../../../../shared/constants/swaps';
import { createTestProviderTools } from '../../../stub/provider'; import { createTestProviderTools } from '../../../stub/provider';
import SwapsController, { import SwapsController, {
utils, utils,

@ -11,7 +11,6 @@ import {
getTestAccounts, getTestAccounts,
} from '../../../../stub/provider'; } from '../../../../stub/provider';
import { import {
TRANSACTION_CATEGORIES,
TRANSACTION_STATUSES, TRANSACTION_STATUSES,
TRANSACTION_TYPES, TRANSACTION_TYPES,
} from '../../../../../shared/constants/transaction'; } from '../../../../../shared/constants/transaction';
@ -776,76 +775,76 @@ describe('Transaction Controller', function () {
}); });
}); });
describe('#_determineTransactionCategory', function () { describe('#_determineTransactionType', function () {
it('should return a simple send transactionCategory when to is truthy but data is falsy', async function () { it('should return a simple send type when to is truthy but data is falsy', async function () {
const result = await txController._determineTransactionCategory({ const result = await txController._determineTransactionType({
to: '0xabc', to: '0xabc',
data: '', data: '',
}); });
assert.deepEqual(result, { assert.deepEqual(result, {
transactionCategory: TRANSACTION_CATEGORIES.SENT_ETHER, type: TRANSACTION_TYPES.SENT_ETHER,
getCodeResponse: null, getCodeResponse: null,
}); });
}); });
it('should return a token transfer transactionCategory when data is for the respective method call', async function () { it('should return a token transfer type when data is for the respective method call', async function () {
const result = await txController._determineTransactionCategory({ const result = await txController._determineTransactionType({
to: '0xabc', to: '0xabc',
data: data:
'0xa9059cbb0000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C970000000000000000000000000000000000000000000000000000000000000000a', '0xa9059cbb0000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C970000000000000000000000000000000000000000000000000000000000000000a',
}); });
assert.deepEqual(result, { assert.deepEqual(result, {
transactionCategory: TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER, type: TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
getCodeResponse: undefined, getCodeResponse: undefined,
}); });
}); });
it('should return a token approve transactionCategory when data is for the respective method call', async function () { it('should return a token approve type when data is for the respective method call', async function () {
const result = await txController._determineTransactionCategory({ const result = await txController._determineTransactionType({
to: '0xabc', to: '0xabc',
data: data:
'0x095ea7b30000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C9700000000000000000000000000000000000000000000000000000000000000005', '0x095ea7b30000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C9700000000000000000000000000000000000000000000000000000000000000005',
}); });
assert.deepEqual(result, { assert.deepEqual(result, {
transactionCategory: TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE, type: TRANSACTION_TYPES.TOKEN_METHOD_APPROVE,
getCodeResponse: undefined, getCodeResponse: undefined,
}); });
}); });
it('should return a contract deployment transactionCategory when to is falsy and there is data', async function () { it('should return a contract deployment type when to is falsy and there is data', async function () {
const result = await txController._determineTransactionCategory({ const result = await txController._determineTransactionType({
to: '', to: '',
data: '0xabd', data: '0xabd',
}); });
assert.deepEqual(result, { assert.deepEqual(result, {
transactionCategory: TRANSACTION_CATEGORIES.DEPLOY_CONTRACT, type: TRANSACTION_TYPES.DEPLOY_CONTRACT,
getCodeResponse: undefined, getCodeResponse: undefined,
}); });
}); });
it('should return a simple send transactionCategory with a 0x getCodeResponse when there is data and but the to address is not a contract address', async function () { it('should return a simple send type with a 0x getCodeResponse when there is data and but the to address is not a contract address', async function () {
const result = await txController._determineTransactionCategory({ const result = await txController._determineTransactionType({
to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9', to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9',
data: '0xabd', data: '0xabd',
}); });
assert.deepEqual(result, { assert.deepEqual(result, {
transactionCategory: TRANSACTION_CATEGORIES.SENT_ETHER, type: TRANSACTION_TYPES.SENT_ETHER,
getCodeResponse: '0x', getCodeResponse: '0x',
}); });
}); });
it('should return a simple send transactionCategory with a null getCodeResponse when to is truthy and there is data and but getCode returns an error', async function () { it('should return a simple send type with a null getCodeResponse when to is truthy and there is data and but getCode returns an error', async function () {
const result = await txController._determineTransactionCategory({ const result = await txController._determineTransactionType({
to: '0xabc', to: '0xabc',
data: '0xabd', data: '0xabd',
}); });
assert.deepEqual(result, { assert.deepEqual(result, {
transactionCategory: TRANSACTION_CATEGORIES.SENT_ETHER, type: TRANSACTION_TYPES.SENT_ETHER,
getCodeResponse: null, getCodeResponse: null,
}); });
}); });
it('should return a contract interaction transactionCategory with the correct getCodeResponse when to is truthy and there is data and it is not a token transaction', async function () { it('should return a contract interaction type with the correct getCodeResponse when to is truthy and there is data and it is not a token transaction', async function () {
const _providerResultStub = { const _providerResultStub = {
// 1 gwei // 1 gwei
eth_gasPrice: '0x0de0b6b3a7640000', eth_gasPrice: '0x0de0b6b3a7640000',
@ -875,17 +874,17 @@ describe('Transaction Controller', function () {
}), }),
getParticipateInMetrics: () => false, getParticipateInMetrics: () => false,
}); });
const result = await _txController._determineTransactionCategory({ const result = await _txController._determineTransactionType({
to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9', to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9',
data: 'abd', data: 'abd',
}); });
assert.deepEqual(result, { assert.deepEqual(result, {
transactionCategory: TRANSACTION_CATEGORIES.CONTRACT_INTERACTION, type: TRANSACTION_TYPES.CONTRACT_INTERACTION,
getCodeResponse: '0x0a', getCodeResponse: '0x0a',
}); });
}); });
it('should return a contract interaction transactionCategory with the correct getCodeResponse when to is a contract address and data is falsy', async function () { it('should return a contract interaction type with the correct getCodeResponse when to is a contract address and data is falsy', async function () {
const _providerResultStub = { const _providerResultStub = {
// 1 gwei // 1 gwei
eth_gasPrice: '0x0de0b6b3a7640000', eth_gasPrice: '0x0de0b6b3a7640000',
@ -915,12 +914,12 @@ describe('Transaction Controller', function () {
}), }),
getParticipateInMetrics: () => false, getParticipateInMetrics: () => false,
}); });
const result = await _txController._determineTransactionCategory({ const result = await _txController._determineTransactionType({
to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9', to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9',
data: '', data: '',
}); });
assert.deepEqual(result, { assert.deepEqual(result, {
transactionCategory: TRANSACTION_CATEGORIES.CONTRACT_INTERACTION, type: TRANSACTION_TYPES.CONTRACT_INTERACTION,
getCodeResponse: '0x0a', getCodeResponse: '0x0a',
}); });
}); });

@ -0,0 +1,136 @@
import { strict as assert } from 'assert';
import migration53 from '../../../app/scripts/migrations/053';
import { TRANSACTION_TYPES } from '../../../shared/constants/transaction';
describe('migration #53', function () {
it('should update the version metadata', async function () {
const oldStorage = {
meta: {
version: 52,
},
data: {},
};
const newStorage = await migration53.migrate(oldStorage);
assert.deepEqual(newStorage.meta, {
version: 53,
});
});
it('should update type of standard transactions', async function () {
const oldStorage = {
meta: {},
data: {
TransactionController: {
transactions: [
{
type: TRANSACTION_TYPES.CANCEL,
transactionCategory: TRANSACTION_TYPES.SENT_ETHER,
txParams: { foo: 'bar' },
},
{
type: 'standard',
transactionCategory: TRANSACTION_TYPES.SENT_ETHER,
txParams: { foo: 'bar' },
},
{
type: 'standard',
transactionCategory: TRANSACTION_TYPES.CONTRACT_INTERACTION,
txParams: { foo: 'bar' },
},
{
type: TRANSACTION_TYPES.RETRY,
transactionCategory: TRANSACTION_TYPES.SENT_ETHER,
txParams: { foo: 'bar' },
},
],
},
IncomingTransactionsController: {
incomingTransactions: {
test: {
transactionCategory: 'incoming',
txParams: {
foo: 'bar',
},
},
},
},
foo: 'bar',
},
};
const newStorage = await migration53.migrate(oldStorage);
assert.deepEqual(newStorage.data, {
TransactionController: {
transactions: [
{ type: TRANSACTION_TYPES.CANCEL, txParams: { foo: 'bar' } },
{ type: TRANSACTION_TYPES.SENT_ETHER, txParams: { foo: 'bar' } },
{
type: TRANSACTION_TYPES.CONTRACT_INTERACTION,
txParams: { foo: 'bar' },
},
{ type: TRANSACTION_TYPES.RETRY, txParams: { foo: 'bar' } },
],
},
IncomingTransactionsController: {
incomingTransactions: {
test: {
type: 'incoming',
txParams: {
foo: 'bar',
},
},
},
},
foo: 'bar',
});
});
it('should do nothing if transactions state does not exist', async function () {
const oldStorage = {
meta: {},
data: {
TransactionController: {
bar: 'baz',
},
IncomingTransactionsController: {
foo: 'baz',
},
foo: 'bar',
},
};
const newStorage = await migration53.migrate(oldStorage);
assert.deepEqual(oldStorage.data, newStorage.data);
});
it('should do nothing if transactions state is empty', async function () {
const oldStorage = {
meta: {},
data: {
TransactionController: {
transactions: [],
bar: 'baz',
},
IncomingTransactionsController: {
incomingTransactions: {},
baz: 'bar',
},
foo: 'bar',
},
};
const newStorage = await migration53.migrate(oldStorage);
assert.deepEqual(oldStorage.data, newStorage.data);
});
it('should do nothing if state is empty', async function () {
const oldStorage = {
meta: {},
data: {},
};
const newStorage = await migration53.migrate(oldStorage);
assert.deepEqual(oldStorage.data, newStorage.data);
});
});

@ -25,6 +25,7 @@ const notToggleElementClassnames = [
'network-indicator', 'network-indicator',
'network-caret', 'network-caret',
'network-component', 'network-component',
'modal-container__footer-button',
]; ];
const DROP_DOWN_MENU_ITEM_STYLE = { const DROP_DOWN_MENU_ITEM_STYLE = {

@ -61,7 +61,7 @@ const mapStateToProps = (state, ownProps) => {
const { currentNetworkTxList, send } = state.metamask; const { currentNetworkTxList, send } = state.metamask;
const { modalState: { props: modalProps } = {} } = state.appState.modal || {}; const { modalState: { props: modalProps } = {} } = state.appState.modal || {};
const { txData = {} } = modalProps || {}; const { txData = {} } = modalProps || {};
const { transaction = {} } = ownProps; const { transaction = {}, onSubmit } = ownProps;
const selectedTransaction = currentNetworkTxList.find( const selectedTransaction = currentNetworkTxList.find(
({ id }) => id === (transaction.id || txData.id), ({ id }) => id === (transaction.id || txData.id),
); );
@ -77,7 +77,8 @@ const mapStateToProps = (state, ownProps) => {
value: sendToken ? '0x0' : send.amount, value: sendToken ? '0x0' : send.amount,
}; };
const { gasPrice: currentGasPrice, gas: currentGasLimit, value } = txParams; const { gasPrice: currentGasPrice, gas: currentGasLimit } = txParams;
const value = ownProps.transaction?.txParams?.value || txParams.value;
const customModalGasPriceInHex = getCustomGasPrice(state) || currentGasPrice; const customModalGasPriceInHex = getCustomGasPrice(state) || currentGasPrice;
const customModalGasLimitInHex = const customModalGasLimitInHex =
getCustomGasLimit(state) || currentGasLimit || '0x5208'; getCustomGasLimit(state) || currentGasLimit || '0x5208';
@ -175,6 +176,7 @@ const mapStateToProps = (state, ownProps) => {
tokenBalance: getTokenBalance(state), tokenBalance: getTokenBalance(state),
conversionRate, conversionRate,
value, value,
onSubmit,
}; };
}; };
@ -253,6 +255,12 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
...otherDispatchProps, ...otherDispatchProps,
...ownProps, ...ownProps,
onSubmit: (gasLimit, gasPrice) => { onSubmit: (gasLimit, gasPrice) => {
if (ownProps.onSubmit) {
dispatchHideSidebar();
dispatchCancelAndClose();
ownProps.onSubmit(gasLimit, gasPrice);
return;
}
if (isConfirm) { if (isConfirm) {
const updatedTx = { const updatedTx = {
...transaction, ...transaction,

@ -1,9 +1,7 @@
import assert from 'assert'; import assert from 'assert';
import proxyquire from 'proxyquire'; import proxyquire from 'proxyquire';
import sinon from 'sinon'; import sinon from 'sinon';
import { TRANSACTION_STATUSES } from '../../../../../../../shared/constants/transaction';
let mapStateToProps;
let mapDispatchToProps; let mapDispatchToProps;
let mergeProps; let mergeProps;
@ -25,8 +23,7 @@ const sendActionSpies = {
proxyquire('../gas-modal-page-container.container.js', { proxyquire('../gas-modal-page-container.container.js', {
'react-redux': { 'react-redux': {
connect: (ms, md, mp) => { connect: (_, md, mp) => {
mapStateToProps = ms;
mapDispatchToProps = md; mapDispatchToProps = md;
mergeProps = mp; mergeProps = mp;
return () => ({}); return () => ({});
@ -48,225 +45,6 @@ proxyquire('../gas-modal-page-container.container.js', {
}); });
describe('gas-modal-page-container container', function () { describe('gas-modal-page-container container', function () {
describe('mapStateToProps()', function () {
it('should map the correct properties to props', function () {
const baseMockState = {
appState: {
modal: {
modalState: {
props: {
hideBasic: true,
txData: {
id: 34,
},
},
},
},
},
metamask: {
send: {
gasLimit: '16',
gasPrice: '32',
amount: '64',
maxModeOn: false,
},
currentCurrency: 'abc',
conversionRate: 50,
usdConversionRate: 123,
preferences: {
showFiatInTestnets: false,
},
provider: {
type: 'mainnet',
chainId: '0x1',
},
currentNetworkTxList: [
{
id: 34,
txParams: {
gas: '0x1600000',
gasPrice: '0x3200000',
value: '0x640000000000000',
},
},
],
},
gas: {
basicEstimates: {
blockTime: 12,
safeLow: 2,
},
customData: {
limit: 'aaaaaaaa',
price: 'ffffffff',
},
priceAndTimeEstimates: [
{ gasprice: 3, expectedTime: 31 },
{ gasprice: 4, expectedTime: 62 },
{ gasprice: 5, expectedTime: 93 },
{ gasprice: 6, expectedTime: 124 },
],
},
confirmTransaction: {
txData: {
txParams: {
gas: '0x1600000',
gasPrice: '0x3200000',
value: '0x640000000000000',
},
},
},
};
const baseExpectedResult = {
balance: '0x0',
isConfirm: true,
customGasPrice: 4.294967295,
customGasLimit: 2863311530,
newTotalFiat: '637.41',
conversionRate: 50,
customModalGasLimitInHex: 'aaaaaaaa',
customModalGasPriceInHex: 'ffffffff',
customPriceIsExcessive: false,
customGasTotal: 'aaaaaaa955555556',
customPriceIsSafe: true,
gasPriceButtonGroupProps: {
buttonDataLoading: 'mockBasicGasEstimateLoadingStatus:4',
defaultActiveButtonIndex: 'mockRenderableBasicEstimateData:4ffffffff',
gasButtonInfo: 'mockRenderableBasicEstimateData:4',
},
hideBasic: true,
infoRowProps: {
originalTotalFiat: '637.41',
originalTotalEth: '12.748189 ETH',
newTotalFiat: '637.41',
newTotalEth: '12.748189 ETH',
sendAmount: '0.45036 ETH',
transactionFee: '12.297829 ETH',
},
insufficientBalance: true,
isSpeedUp: false,
isRetry: false,
txId: 34,
isMainnet: true,
maxModeOn: false,
sendToken: null,
tokenBalance: '0x0',
transaction: {
id: 34,
},
value: '0x640000000000000',
};
const baseMockOwnProps = { transaction: { id: 34 } };
const tests = [
{
mockState: baseMockState,
expectedResult: baseExpectedResult,
mockOwnProps: baseMockOwnProps,
},
{
mockState: {
...baseMockState,
metamask: {
...baseMockState.metamask,
balance: '0xfffffffffffffffffffff',
},
},
expectedResult: {
...baseExpectedResult,
balance: '0xfffffffffffffffffffff',
insufficientBalance: false,
},
mockOwnProps: baseMockOwnProps,
},
{
mockState: baseMockState,
mockOwnProps: {
...baseMockOwnProps,
transaction: { id: 34, status: TRANSACTION_STATUSES.SUBMITTED },
},
expectedResult: {
...baseExpectedResult,
isSpeedUp: true,
transaction: { id: 34 },
},
},
{
mockState: {
...baseMockState,
metamask: {
...baseMockState.metamask,
preferences: {
...baseMockState.metamask.preferences,
showFiatInTestnets: false,
},
provider: {
...baseMockState.metamask.provider,
type: 'rinkeby',
chainId: '0x4',
},
},
},
mockOwnProps: baseMockOwnProps,
expectedResult: {
...baseExpectedResult,
infoRowProps: {
...baseExpectedResult.infoRowProps,
newTotalFiat: '',
},
isMainnet: false,
},
},
{
mockState: {
...baseMockState,
metamask: {
...baseMockState.metamask,
preferences: {
...baseMockState.metamask.preferences,
showFiatInTestnets: true,
},
provider: {
...baseMockState.metamask.provider,
type: 'rinkeby',
chainId: '0x4',
},
},
},
mockOwnProps: baseMockOwnProps,
expectedResult: {
...baseExpectedResult,
isMainnet: false,
},
},
{
mockState: {
...baseMockState,
metamask: {
...baseMockState.metamask,
preferences: {
...baseMockState.metamask.preferences,
showFiatInTestnets: true,
},
provider: {
...baseMockState.metamask.provider,
type: 'mainnet',
chainId: '0x1',
},
},
},
mockOwnProps: baseMockOwnProps,
expectedResult: baseExpectedResult,
},
];
let result;
tests.forEach(({ mockState, mockOwnProps, expectedResult }) => {
result = mapStateToProps(mockState, mockOwnProps);
assert.deepStrictEqual(result, expectedResult);
});
});
});
describe('mapDispatchToProps()', function () { describe('mapDispatchToProps()', function () {
let dispatchSpy; let dispatchSpy;
let mapDispatchToPropsObject; let mapDispatchToPropsObject;

@ -21,7 +21,7 @@ export default class LoadingNetworkScreen extends PureComponent {
setProviderArgs: PropTypes.array, setProviderArgs: PropTypes.array,
setProviderType: PropTypes.func, setProviderType: PropTypes.func,
rollbackToPreviousProvider: PropTypes.func, rollbackToPreviousProvider: PropTypes.func,
isLoadingNetwork: PropTypes.bool, isNetworkLoading: PropTypes.bool,
}; };
componentDidMount = () => { componentDidMount = () => {
@ -99,9 +99,9 @@ export default class LoadingNetworkScreen extends PureComponent {
}; };
cancelCall = () => { cancelCall = () => {
const { isLoadingNetwork } = this.props; const { isNetworkLoading } = this.props;
if (isLoadingNetwork) { if (isNetworkLoading) {
this.setState({ showErrorScreen: true }); this.setState({ showErrorScreen: true });
} }
}; };

@ -1,12 +1,12 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { NETWORK_TYPE_RPC } from '../../../../../shared/constants/network'; import { NETWORK_TYPE_RPC } from '../../../../../shared/constants/network';
import * as actions from '../../../store/actions'; import * as actions from '../../../store/actions';
import { getNetworkIdentifier } from '../../../selectors'; import { getNetworkIdentifier, isNetworkLoading } from '../../../selectors';
import LoadingNetworkScreen from './loading-network-screen.component'; import LoadingNetworkScreen from './loading-network-screen.component';
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
const { loadingMessage } = state.appState; const { loadingMessage } = state.appState;
const { provider, network } = state.metamask; const { provider } = state.metamask;
const { rpcUrl, chainId, ticker, nickname, type } = provider; const { rpcUrl, chainId, ticker, nickname, type } = provider;
const setProviderArgs = const setProviderArgs =
@ -15,7 +15,7 @@ const mapStateToProps = (state) => {
: [provider.type]; : [provider.type];
return { return {
isLoadingNetwork: network === 'loading', isNetworkLoading: isNetworkLoading(state),
loadingMessage, loadingMessage,
setProviderArgs, setProviderArgs,
provider, provider,

@ -1,47 +1,40 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { compose } from 'redux'; import { compose } from 'redux';
import { multiplyCurrencies } from '../../../../helpers/utils/conversion-util';
import withModalProps from '../../../../helpers/higher-order-components/with-modal-props'; import withModalProps from '../../../../helpers/higher-order-components/with-modal-props';
import { showModal, createCancelTransaction } from '../../../../store/actions'; import { showModal, createCancelTransaction } from '../../../../store/actions';
import { getHexGasTotal } from '../../../../helpers/utils/confirm-tx.util';
import { addHexPrefix } from '../../../../../../app/scripts/lib/util';
import CancelTransaction from './cancel-transaction.component'; import CancelTransaction from './cancel-transaction.component';
const mapStateToProps = (state, ownProps) => { const mapStateToProps = (state, ownProps) => {
const { metamask } = state; const { metamask } = state;
const { transactionId, originalGasPrice } = ownProps; const {
transactionId,
originalGasPrice,
newGasFee,
defaultNewGasPrice,
gasLimit,
} = ownProps;
const { currentNetworkTxList } = metamask; const { currentNetworkTxList } = metamask;
const transaction = currentNetworkTxList.find( const transaction = currentNetworkTxList.find(
({ id }) => id === transactionId, ({ id }) => id === transactionId,
); );
const transactionStatus = transaction ? transaction.status : ''; const transactionStatus = transaction ? transaction.status : '';
const defaultNewGasPrice = addHexPrefix(
multiplyCurrencies(originalGasPrice, 1.1, {
toNumericBase: 'hex',
multiplicandBase: 16,
multiplierBase: 10,
}),
);
const newGasFee = getHexGasTotal({
gasPrice: defaultNewGasPrice,
gasLimit: '0x5208',
});
return { return {
transactionId, transactionId,
transactionStatus, transactionStatus,
originalGasPrice, originalGasPrice,
defaultNewGasPrice, defaultNewGasPrice,
newGasFee, newGasFee,
gasLimit,
}; };
}; };
const mapDispatchToProps = (dispatch) => { const mapDispatchToProps = (dispatch) => {
return { return {
createCancelTransaction: (txId, customGasPrice) => { createCancelTransaction: (txId, customGasPrice, customGasLimit) => {
return dispatch(createCancelTransaction(txId, customGasPrice)); return dispatch(
createCancelTransaction(txId, customGasPrice, customGasLimit),
);
}, },
showTransactionConfirmedModal: () => showTransactionConfirmedModal: () =>
dispatch(showModal({ name: 'TRANSACTION_CONFIRMED' })), dispatch(showModal({ name: 'TRANSACTION_CONFIRMED' })),
@ -49,7 +42,12 @@ const mapDispatchToProps = (dispatch) => {
}; };
const mergeProps = (stateProps, dispatchProps, ownProps) => { const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { transactionId, defaultNewGasPrice, ...restStateProps } = stateProps; const {
transactionId,
defaultNewGasPrice,
gasLimit,
...restStateProps
} = stateProps;
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow
const { createCancelTransaction, ...restDispatchProps } = dispatchProps; const { createCancelTransaction, ...restDispatchProps } = dispatchProps;
@ -58,7 +56,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
...restDispatchProps, ...restDispatchProps,
...ownProps, ...ownProps,
createCancelTransaction: () => createCancelTransaction: () =>
createCancelTransaction(transactionId, defaultNewGasPrice), createCancelTransaction(transactionId, defaultNewGasPrice, gasLimit),
}; };
}; };

@ -1,6 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { getNetworkDisplayName } from '../../../../../../app/scripts/controllers/network/util'; import { NETWORK_TO_NAME_MAP } from '../../../../../../shared/constants/network';
import Button from '../../../ui/button'; import Button from '../../../ui/button';
export default class DepositEtherModal extends Component { export default class DepositEtherModal extends Component {
@ -10,7 +10,7 @@ export default class DepositEtherModal extends Component {
}; };
static propTypes = { static propTypes = {
network: PropTypes.string.isRequired, chainId: PropTypes.string.isRequired,
isTestnet: PropTypes.bool.isRequired, isTestnet: PropTypes.bool.isRequired,
isMainnet: PropTypes.bool.isRequired, isMainnet: PropTypes.bool.isRequired,
toWyre: PropTypes.func.isRequired, toWyre: PropTypes.func.isRequired,
@ -21,10 +21,6 @@ export default class DepositEtherModal extends Component {
showAccountDetailModal: PropTypes.func.isRequired, showAccountDetailModal: PropTypes.func.isRequired,
}; };
faucetRowText = (networkName) => {
return this.context.t('getEtherFromFaucet', [networkName]);
};
goToAccountDetailsModal = () => { goToAccountDetailsModal = () => {
this.props.hideWarning(); this.props.hideWarning();
this.props.hideModal(); this.props.hideModal();
@ -89,14 +85,14 @@ export default class DepositEtherModal extends Component {
render() { render() {
const { const {
network, chainId,
toWyre, toWyre,
address, address,
toFaucet, toFaucet,
isTestnet, isTestnet,
isMainnet, isMainnet,
} = this.props; } = this.props;
const networkName = getNetworkDisplayName(network); const networkName = NETWORK_TO_NAME_MAP[chainId];
return ( return (
<div className="page-container page-container--full-width page-container--full-height"> <div className="page-container page-container--full-width page-container--full-height">
@ -162,9 +158,9 @@ export default class DepositEtherModal extends Component {
{this.renderRow({ {this.renderRow({
logo: <i className="fa fa-tint fa-2x" />, logo: <i className="fa fa-tint fa-2x" />,
title: this.context.t('testFaucet'), title: this.context.t('testFaucet'),
text: this.faucetRowText(networkName), text: this.context.t('getEtherFromFaucet', [networkName]),
buttonLabel: this.context.t('getEther'), buttonLabel: this.context.t('getEther'),
onButtonClick: () => toFaucet(network), onButtonClick: () => toFaucet(chainId),
hide: !isTestnet, hide: !isTestnet,
})} })}
</div> </div>

@ -5,15 +5,20 @@ import {
showModal, showModal,
hideWarning, hideWarning,
} from '../../../../store/actions'; } from '../../../../store/actions';
import { getIsTestnet, getIsMainnet } from '../../../../selectors/selectors'; import {
getIsTestnet,
getIsMainnet,
getCurrentChainId,
getSelectedAddress,
} from '../../../../selectors/selectors';
import DepositEtherModal from './deposit-ether-modal.component'; import DepositEtherModal from './deposit-ether-modal.component';
function mapStateToProps(state) { function mapStateToProps(state) {
return { return {
network: state.metamask.network, chainId: getCurrentChainId(state),
isTestnet: getIsTestnet(state), isTestnet: getIsTestnet(state),
isMainnet: getIsMainnet(state), isMainnet: getIsMainnet(state),
address: state.metamask.selectedAddress, address: getSelectedAddress(state),
}; };
} }
@ -31,7 +36,7 @@ function mapDispatchToProps(dispatch) {
showAccountDetailModal: () => { showAccountDetailModal: () => {
dispatch(showModal({ name: 'ACCOUNT_DETAILS' })); dispatch(showModal({ name: 'ACCOUNT_DETAILS' }));
}, },
toFaucet: (network) => dispatch(buyEth({ network })), toFaucet: (chainId) => dispatch(buyEth({ chainId })),
}; };
} }

@ -16,6 +16,7 @@ import {
} from '../../../helpers/constants/design-system'; } from '../../../helpers/constants/design-system';
import Chip from '../../ui/chip/chip'; import Chip from '../../ui/chip/chip';
import { useI18nContext } from '../../../hooks/useI18nContext'; import { useI18nContext } from '../../../hooks/useI18nContext';
import { isNetworkLoading } from '../../../selectors';
export default function NetworkDisplay({ export default function NetworkDisplay({
colored, colored,
@ -27,14 +28,14 @@ export default function NetworkDisplay({
targetNetwork, targetNetwork,
onClick, onClick,
}) { }) {
const networkIsLoading = useSelector(isNetworkLoading);
const currentNetwork = useSelector((state) => ({ const currentNetwork = useSelector((state) => ({
network: state.metamask.network,
nickname: state.metamask.provider.nickname, nickname: state.metamask.provider.nickname,
type: state.metamask.provider.type, type: state.metamask.provider.type,
})); }));
const t = useI18nContext(); const t = useI18nContext();
const { network = '', nickname: networkNickname, type: networkType } = const { nickname: networkNickname, type: networkType } =
targetNetwork ?? currentNetwork; targetNetwork ?? currentNetwork;
return ( return (
@ -45,7 +46,7 @@ export default function NetworkDisplay({
<LoadingIndicator <LoadingIndicator
alt={t('attemptingConnect')} alt={t('attemptingConnect')}
title={t('attemptingConnect')} title={t('attemptingConnect')}
isLoading={network === 'loading'} isLoading={networkIsLoading}
> >
<ColorIndicator <ColorIndicator
color={networkType === NETWORK_TYPE_RPC ? COLORS.UI4 : networkType} color={networkType === NETWORK_TYPE_RPC ? COLORS.UI4 : networkType}

@ -30,12 +30,12 @@ export default class Sidebar extends Component {
renderSidebarContent() { renderSidebarContent() {
const { type, sidebarProps = {} } = this.props; const { type, sidebarProps = {} } = this.props;
const { transaction = {} } = sidebarProps; const { transaction = {}, onSubmit } = sidebarProps;
switch (type) { switch (type) {
case 'customize-gas': case 'customize-gas':
return ( return (
<div className="sidebar-left"> <div className="sidebar-left">
<CustomizeGas transaction={transaction} /> <CustomizeGas transaction={transaction} onSubmit={onSubmit} />
</div> </div>
); );
default: default:

@ -6,19 +6,16 @@ import {
} from '../../../selectors'; } from '../../../selectors';
import { getHexGasTotal } from '../../../helpers/utils/confirm-tx.util'; import { getHexGasTotal } from '../../../helpers/utils/confirm-tx.util';
import { sumHexes } from '../../../helpers/utils/transactions.util'; import { sumHexes } from '../../../helpers/utils/transactions.util';
import { TRANSACTION_CATEGORIES } from '../../../../../shared/constants/transaction';
import TransactionBreakdown from './transaction-breakdown.component'; import TransactionBreakdown from './transaction-breakdown.component';
const mapStateToProps = (state, ownProps) => { const mapStateToProps = (state, ownProps) => {
const { transaction, transactionCategory } = ownProps; const { transaction, isTokenApprove } = ownProps;
const { const {
txParams: { gas, gasPrice, value } = {}, txParams: { gas, gasPrice, value } = {},
txReceipt: { gasUsed } = {}, txReceipt: { gasUsed } = {},
} = transaction; } = transaction;
const { showFiatInTestnets } = getPreferences(state); const { showFiatInTestnets } = getPreferences(state);
const isMainnet = getIsMainnet(state); const isMainnet = getIsMainnet(state);
const isTokenApprove =
transactionCategory === TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE;
const gasLimit = typeof gasUsed === 'string' ? gasUsed : gas; const gasLimit = typeof gasUsed === 'string' ? gasUsed : gas;

@ -10,6 +10,7 @@ import Tooltip from '../../ui/tooltip';
import Copy from '../../ui/icon/copy-icon.component'; import Copy from '../../ui/icon/copy-icon.component';
import Popover from '../../ui/popover'; import Popover from '../../ui/popover';
import { getBlockExplorerUrlForTx } from '../../../../../shared/modules/transaction.utils'; import { getBlockExplorerUrlForTx } from '../../../../../shared/modules/transaction.utils';
import { TRANSACTION_TYPES } from '../../../../../shared/constants/transaction';
export default class TransactionListItemDetails extends PureComponent { export default class TransactionListItemDetails extends PureComponent {
static contextTypes = { static contextTypes = {
@ -156,7 +157,7 @@ export default class TransactionListItemDetails extends PureComponent {
} = this.props; } = this.props;
const { const {
primaryTransaction: transaction, primaryTransaction: transaction,
initialTransaction: { transactionCategory }, initialTransaction: { type },
} = transactionGroup; } = transactionGroup;
const { hash } = transaction; const { hash } = transaction;
@ -255,7 +256,7 @@ export default class TransactionListItemDetails extends PureComponent {
<div className="transaction-list-item-details__cards-container"> <div className="transaction-list-item-details__cards-container">
<TransactionBreakdown <TransactionBreakdown
nonce={transactionGroup.initialTransaction.txParams.nonce} nonce={transactionGroup.initialTransaction.txParams.nonce}
transactionCategory={transactionCategory} isTokenApprove={type === TRANSACTION_TYPES.TOKEN_METHOD_APPROVE}
transaction={transaction} transaction={transaction}
primaryCurrency={primaryCurrency} primaryCurrency={primaryCurrency}
className="transaction-list-item-details__transaction-breakdown" className="transaction-list-item-details__transaction-breakdown"

@ -118,13 +118,21 @@ export default function TransactionListItem({
<Button <Button
type="secondary" type="secondary"
rounded rounded
onClick={retryTransaction} onClick={hasCancelled ? cancelTransaction : retryTransaction}
className="transaction-list-item-details__header-button" style={hasCancelled ? { width: 'auto' } : null}
> >
{t('speedUp')} {hasCancelled ? t('speedUpCancellation') : t('speedUp')}
</Button> </Button>
); );
}, [shouldShowSpeedUp, isUnapproved, t, isPending, retryTransaction]); }, [
shouldShowSpeedUp,
isUnapproved,
t,
isPending,
retryTransaction,
hasCancelled,
cancelTransaction,
]);
return ( return (
<> <>

@ -9,8 +9,8 @@ import { useI18nContext } from '../../../hooks/useI18nContext';
import TransactionListItem from '../transaction-list-item'; import TransactionListItem from '../transaction-list-item';
import Button from '../../ui/button'; import Button from '../../ui/button';
import { TOKEN_CATEGORY_HASH } from '../../../helpers/constants/transactions'; import { TOKEN_CATEGORY_HASH } from '../../../helpers/constants/transactions';
import { SWAPS_CONTRACT_ADDRESS } from '../../../helpers/constants/swaps'; import { SWAPS_CONTRACT_ADDRESS } from '../../../../../shared/constants/swaps';
import { TRANSACTION_CATEGORIES } from '../../../../../shared/constants/transaction'; import { TRANSACTION_TYPES } from '../../../../../shared/constants/transaction';
const PAGE_INCREMENT = 10; const PAGE_INCREMENT = 10;
@ -25,15 +25,11 @@ const getTransactionGroupRecipientAddressFilter = (recipientAddress) => {
}; };
const tokenTransactionFilter = ({ const tokenTransactionFilter = ({
initialTransaction: { initialTransaction: { type, destinationTokenSymbol, sourceTokenSymbol },
transactionCategory,
destinationTokenSymbol,
sourceTokenSymbol,
},
}) => { }) => {
if (TOKEN_CATEGORY_HASH[transactionCategory]) { if (TOKEN_CATEGORY_HASH[type]) {
return false; return false;
} else if (transactionCategory === TRANSACTION_CATEGORIES.SWAP) { } else if (type === TRANSACTION_TYPES.SWAP) {
return destinationTokenSymbol === 'ETH' || sourceTokenSymbol === 'ETH'; return destinationTokenSymbol === 'ETH' || sourceTokenSymbol === 'ETH';
} }
return true; return true;

@ -41,7 +41,6 @@ import {
decimalToHex, decimalToHex,
getValueFromWeiHex, getValueFromWeiHex,
decGWEIToHexWEI, decGWEIToHexWEI,
hexToDecimal,
hexWEIToDecGWEI, hexWEIToDecGWEI,
} from '../../helpers/utils/conversions.util'; } from '../../helpers/utils/conversions.util';
import { conversionLessThan } from '../../helpers/utils/conversion-util'; import { conversionLessThan } from '../../helpers/utils/conversion-util';
@ -50,15 +49,15 @@ import {
getSelectedAccount, getSelectedAccount,
getTokenExchangeRates, getTokenExchangeRates,
getUSDConversionRate, getUSDConversionRate,
getSwapsEthToken,
} from '../../selectors'; } from '../../selectors';
import { import {
ERROR_FETCHING_QUOTES, ERROR_FETCHING_QUOTES,
QUOTES_NOT_AVAILABLE_ERROR, QUOTES_NOT_AVAILABLE_ERROR,
ETH_SWAPS_TOKEN_OBJECT,
SWAP_FAILED_ERROR, SWAP_FAILED_ERROR,
SWAPS_FETCH_ORDER_CONFLICT, SWAPS_FETCH_ORDER_CONFLICT,
} from '../../helpers/constants/swaps'; } from '../../../../shared/constants/swaps';
import { TRANSACTION_CATEGORIES } from '../../../../shared/constants/transaction'; import { TRANSACTION_TYPES } from '../../../../shared/constants/transaction';
const GAS_PRICES_LOADING_STATES = { const GAS_PRICES_LOADING_STATES = {
INITIAL: 'INITIAL', INITIAL: 'INITIAL',
@ -396,15 +395,7 @@ export const fetchQuotesAndSetQuoteState = (
const balanceError = getBalanceError(state); const balanceError = getBalanceError(state);
const fetchParamsFromToken = const fetchParamsFromToken =
fetchParams?.metaData?.sourceTokenInfo?.symbol === 'ETH' fetchParams?.metaData?.sourceTokenInfo?.symbol === 'ETH'
? { ? getSwapsEthToken(state)
...ETH_SWAPS_TOKEN_OBJECT,
string: getValueFromWeiHex({
value: selectedAccount.balance,
numberOfDecimals: 4,
toDenomination: 'ETH',
}),
balance: hexToDecimal(selectedAccount.balance),
}
: fetchParams?.metaData?.sourceTokenInfo; : fetchParams?.metaData?.sourceTokenInfo;
const selectedFromToken = getFromToken(state) || fetchParamsFromToken || {}; const selectedFromToken = getFromToken(state) || fetchParamsFromToken || {};
const selectedToToken = const selectedToToken =
@ -680,7 +671,7 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => {
updateTransaction( updateTransaction(
{ {
...approveTxMeta, ...approveTxMeta,
transactionCategory: TRANSACTION_CATEGORIES.SWAP_APPROVAL, type: TRANSACTION_TYPES.SWAP_APPROVAL,
sourceTokenSymbol: sourceTokenInfo.symbol, sourceTokenSymbol: sourceTokenInfo.symbol,
}, },
true, true,
@ -721,7 +712,7 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => {
...tradeTxMeta, ...tradeTxMeta,
sourceTokenSymbol: sourceTokenInfo.symbol, sourceTokenSymbol: sourceTokenInfo.symbol,
destinationTokenSymbol: destinationTokenInfo.symbol, destinationTokenSymbol: destinationTokenInfo.symbol,
transactionCategory: TRANSACTION_CATEGORIES.SWAP, type: TRANSACTION_TYPES.SWAP,
destinationTokenDecimals: destinationTokenInfo.decimals, destinationTokenDecimals: destinationTokenInfo.decimals,
destinationTokenAddress: destinationTokenInfo.address, destinationTokenAddress: destinationTokenInfo.address,
swapMetaData, swapMetaData,

@ -1,5 +1,5 @@
import { import {
TRANSACTION_CATEGORIES, TRANSACTION_TYPES,
TRANSACTION_STATUSES, TRANSACTION_STATUSES,
} from '../../../../shared/constants/transaction'; } from '../../../../shared/constants/transaction';
@ -15,7 +15,7 @@ export const PRIORITY_STATUS_HASH = {
}; };
export const TOKEN_CATEGORY_HASH = { export const TOKEN_CATEGORY_HASH = {
[TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE]: true, [TRANSACTION_TYPES.TOKEN_METHOD_APPROVE]: true,
[TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER]: true, [TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER]: true,
[TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER_FROM]: true, [TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM]: true,
}; };

@ -5,10 +5,9 @@ import log from 'loglevel';
import { addHexPrefix } from '../../../../app/scripts/lib/util'; import { addHexPrefix } from '../../../../app/scripts/lib/util';
import { import {
TRANSACTION_CATEGORIES, TRANSACTION_TYPES,
TRANSACTION_GROUP_STATUSES, TRANSACTION_GROUP_STATUSES,
TRANSACTION_STATUSES, TRANSACTION_STATUSES,
TRANSACTION_TYPES,
} from '../../../../shared/constants/transaction'; } from '../../../../shared/constants/transaction';
import fetchWithCache from './fetch-with-cache'; import fetchWithCache from './fetch-with-cache';
@ -112,15 +111,15 @@ export function getFourBytePrefix(data = '') {
/** /**
* Given an transaction category, returns a boolean which indicates whether the transaction is calling an erc20 token method * Given an transaction category, returns a boolean which indicates whether the transaction is calling an erc20 token method
* *
* @param {string} transactionCategory - The category of transaction being evaluated * @param {TRANSACTION_TYPES[keyof TRANSACTION_TYPES]} type - The type of transaction being evaluated
* @returns {boolean} whether the transaction is calling an erc20 token method * @returns {boolean} whether the transaction is calling an erc20 token method
*/ */
export function isTokenMethodAction(transactionCategory) { export function isTokenMethodAction(type) {
return [ return [
TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER, TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE, TRANSACTION_TYPES.TOKEN_METHOD_APPROVE,
TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER_FROM, TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM,
].includes(transactionCategory); ].includes(type);
} }
export function getLatestSubmittedTxWithNonce( export function getLatestSubmittedTxWithNonce(
@ -197,39 +196,37 @@ export function getStatusKey(transaction) {
* *
* This will throw an error if the transaction category is unrecognized and no default is provided. * This will throw an error if the transaction category is unrecognized and no default is provided.
* @param {function} t - The translation function * @param {function} t - The translation function
* @param {TRANSACTION_CATEGORIES[keyof TRANSACTION_CATEGORIES]} transactionCategory - The transaction category constant * @param {TRANSACTION_TYPES[keyof TRANSACTION_TYPES]} type - The transaction type constant
* @returns {string} The transaction category title * @returns {string} The transaction category title
*/ */
export function getTransactionCategoryTitle(t, transactionCategory) { export function getTransactionTypeTitle(t, type) {
switch (transactionCategory) { switch (type) {
case TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER: { case TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER: {
return t('transfer'); return t('transfer');
} }
case TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER_FROM: { case TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM: {
return t('transferFrom'); return t('transferFrom');
} }
case TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE: { case TRANSACTION_TYPES.TOKEN_METHOD_APPROVE: {
return t('approve'); return t('approve');
} }
case TRANSACTION_CATEGORIES.SENT_ETHER: { case TRANSACTION_TYPES.SENT_ETHER: {
return t('sentEther'); return t('sentEther');
} }
case TRANSACTION_CATEGORIES.CONTRACT_INTERACTION: { case TRANSACTION_TYPES.CONTRACT_INTERACTION: {
return t('contractInteraction'); return t('contractInteraction');
} }
case TRANSACTION_CATEGORIES.DEPLOY_CONTRACT: { case TRANSACTION_TYPES.DEPLOY_CONTRACT: {
return t('contractDeployment'); return t('contractDeployment');
} }
case TRANSACTION_CATEGORIES.SWAP: { case TRANSACTION_TYPES.SWAP: {
return t('swap'); return t('swap');
} }
case TRANSACTION_CATEGORIES.SWAP_APPROVAL: { case TRANSACTION_TYPES.SWAP_APPROVAL: {
return t('swapApproval'); return t('swapApproval');
} }
default: { default: {
throw new Error( throw new Error(`Unrecognized transaction type: ${type}`);
`Unrecognized transaction category: ${transactionCategory}`,
);
} }
} }
} }

@ -1,6 +1,6 @@
import assert from 'assert'; import assert from 'assert';
import { import {
TRANSACTION_CATEGORIES, TRANSACTION_TYPES,
TRANSACTION_GROUP_STATUSES, TRANSACTION_GROUP_STATUSES,
TRANSACTION_STATUSES, TRANSACTION_STATUSES,
} from '../../../../shared/constants/transaction'; } from '../../../../shared/constants/transaction';
@ -14,7 +14,7 @@ describe('Transactions utils', function () {
); );
assert.ok(tokenData); assert.ok(tokenData);
const { name, args } = tokenData; const { name, args } = tokenData;
assert.strictEqual(name, TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER); assert.strictEqual(name, TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER);
const to = args._to; const to = args._to;
const value = args._value.toString(); const value = args._value.toString();
assert.strictEqual(to, '0x50A9D56C2B8BA9A5c7f2C08C3d26E0499F23a706'); assert.strictEqual(to, '0x50A9D56C2B8BA9A5c7f2C08C3d26E0499F23a706');

@ -4,6 +4,14 @@ import BigNumber from 'bignumber.js';
import ethUtil from 'ethereumjs-util'; import ethUtil from 'ethereumjs-util';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { addHexPrefix } from '../../../../app/scripts/lib/util'; import { addHexPrefix } from '../../../../app/scripts/lib/util';
import {
GOERLI_CHAIN_ID,
KOVAN_CHAIN_ID,
LOCALHOST_CHAIN_ID,
MAINNET_CHAIN_ID,
RINKEBY_CHAIN_ID,
ROPSTEN_CHAIN_ID,
} from '../../../../shared/constants/network';
// formatData :: ( date: <Unix Timestamp> ) -> String // formatData :: ( date: <Unix Timestamp> ) -> String
export function formatDate(date, format = "M/d/y 'at' T") { export function formatDate(date, format = "M/d/y 'at' T") {
@ -40,14 +48,19 @@ Object.keys(valueTable).forEach((currency) => {
bnTable[currency] = new ethUtil.BN(valueTable[currency], 10); bnTable[currency] = new ethUtil.BN(valueTable[currency], 10);
}); });
export function isEthNetwork(netId) { /**
* Determines if the provided chainId is a default MetaMask chain
* @param {string} chainId - chainId to check
*/
export function isDefaultMetaMaskChain(chainId) {
if ( if (
!netId || !chainId ||
netId === '1' || chainId === MAINNET_CHAIN_ID ||
netId === '3' || chainId === ROPSTEN_CHAIN_ID ||
netId === '4' || chainId === RINKEBY_CHAIN_ID ||
netId === '42' || chainId === KOVAN_CHAIN_ID ||
netId === '1337' chainId === GOERLI_CHAIN_ID ||
chainId === LOCALHOST_CHAIN_ID
) { ) {
return true; return true;
} }

@ -7,6 +7,7 @@ import { getConversionRate, getSelectedAccount } from '../../selectors';
import { useCancelTransaction } from '../useCancelTransaction'; import { useCancelTransaction } from '../useCancelTransaction';
import { showModal } from '../../store/actions'; import { showModal } from '../../store/actions';
import { increaseLastGasPrice } from '../../helpers/utils/confirm-tx.util'; import { increaseLastGasPrice } from '../../helpers/utils/confirm-tx.util';
import * as actionConstants from '../../store/actionConstants';
describe('useCancelTransaction', function () { describe('useCancelTransaction', function () {
let useSelector; let useSelector;
@ -46,7 +47,7 @@ describe('useCancelTransaction', function () {
); );
assert.strictEqual(result.current[0], false); assert.strictEqual(result.current[0], false);
}); });
it(`should return a function that kicks off cancellation for id ${transactionId}`, function () { it(`should return a function that opens the gas sidebar onsubmit kicks off cancellation for id ${transactionId}`, function () {
const { result } = renderHook(() => const { result } = renderHook(() =>
useCancelTransaction(transactionGroup), useCancelTransaction(transactionGroup),
); );
@ -55,12 +56,35 @@ describe('useCancelTransaction', function () {
preventDefault: () => undefined, preventDefault: () => undefined,
stopPropagation: () => undefined, stopPropagation: () => undefined,
}); });
const dispatchAction = dispatch.args;
// calls customize-gas sidebar
// also check type= customize-gas
assert.strictEqual(
dispatchAction[dispatchAction.length - 1][0].type,
actionConstants.SIDEBAR_OPEN,
);
assert.strictEqual(
dispatchAction[dispatchAction.length - 1][0].value.props.transaction
.id,
transactionId,
);
// call onSubmit myself
dispatchAction[dispatchAction.length - 1][0].value.props.onSubmit(
'0x5208',
'0x1',
);
assert.strictEqual( assert.strictEqual(
dispatch.calledWith( dispatch.calledWith(
showModal({ showModal({
name: 'CANCEL_TRANSACTION', name: 'CANCEL_TRANSACTION',
transactionId, transactionId,
originalGasPrice, newGasFee: '0x5208',
defaultNewGasPrice: '0x1',
gasLimit: '0x5208',
}), }),
), ),
true, true,
@ -98,7 +122,7 @@ describe('useCancelTransaction', function () {
); );
assert.strictEqual(result.current[0], true); assert.strictEqual(result.current[0], true);
}); });
it(`should return a function that kicks off cancellation for id ${transactionId}`, function () { it(`should return a function that opens the gas sidebar onsubmit kicks off cancellation for id ${transactionId}`, function () {
const { result } = renderHook(() => const { result } = renderHook(() =>
useCancelTransaction(transactionGroup), useCancelTransaction(transactionGroup),
); );
@ -107,12 +131,31 @@ describe('useCancelTransaction', function () {
preventDefault: () => undefined, preventDefault: () => undefined,
stopPropagation: () => undefined, stopPropagation: () => undefined,
}); });
const dispatchAction = dispatch.args;
assert.strictEqual(
dispatchAction[dispatchAction.length - 1][0].type,
actionConstants.SIDEBAR_OPEN,
);
assert.strictEqual(
dispatchAction[dispatchAction.length - 1][0].value.props.transaction
.id,
transactionId,
);
dispatchAction[dispatchAction.length - 1][0].value.props.onSubmit(
'0x5208',
'0x1',
);
assert.strictEqual( assert.strictEqual(
dispatch.calledWith( dispatch.calledWith(
showModal({ showModal({
name: 'CANCEL_TRANSACTION', name: 'CANCEL_TRANSACTION',
transactionId, transactionId,
originalGasPrice, newGasFee: '0x5208',
defaultNewGasPrice: '0x1',
gasLimit: '0x5208',
}), }),
), ),
true, true,

@ -64,6 +64,50 @@ describe('useRetryTransaction', function () {
); );
}); });
it('should handle cancelled or multiple speedup transactions', async function () {
const cancelledTransaction = {
initialTransaction: {
...transactions[0].initialTransaction,
txParams: {
...transactions[0].initialTransaction.txParams,
},
},
primaryTransaction: {
...transactions[0].primaryTransaction,
txParams: {
from: '0xee014609ef9e09776ac5fe00bdbfef57bcdefebb',
gas: '0x5308',
gasPrice: '0x77359400',
nonce: '0x3',
to: '0xabca64466f257793eaa52fcfff5066894b76a149',
value: '0x0',
},
},
transactions: [
{
submittedTime: new Date() - 5001,
},
],
hasRetried: false,
};
const { result } = renderHook(() =>
useRetryTransaction(cancelledTransaction, true),
);
const retry = result.current;
await retry(event);
assert.strictEqual(
dispatch.calledWith(
showSidebar({
transitionName: 'sidebar-left',
type: 'customize-gas',
props: { transaction: cancelledTransaction.primaryTransaction },
}),
),
true,
);
});
after(function () { after(function () {
sinon.restore(); sinon.restore();
}); });

@ -2,14 +2,14 @@ import assert from 'assert';
import { ethers } from 'ethers'; import { ethers } from 'ethers';
import { renderHook } from '@testing-library/react-hooks'; import { renderHook } from '@testing-library/react-hooks';
import { useTokenData } from '../useTokenData'; import { useTokenData } from '../useTokenData';
import { TRANSACTION_CATEGORIES } from '../../../../shared/constants/transaction'; import { TRANSACTION_TYPES } from '../../../../shared/constants/transaction';
const tests = [ const tests = [
{ {
data: data:
'0xa9059cbb000000000000000000000000ffe5bc4e8f1f969934d773fa67da095d2e491a970000000000000000000000000000000000000000000000000000000000003a98', '0xa9059cbb000000000000000000000000ffe5bc4e8f1f969934d773fa67da095d2e491a970000000000000000000000000000000000000000000000000000000000003a98',
tokenData: { tokenData: {
name: TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER, name: TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
args: [ args: [
'0xffe5bc4e8f1f969934d773fa67da095d2e491a97', '0xffe5bc4e8f1f969934d773fa67da095d2e491a97',
ethers.BigNumber.from(15000), ethers.BigNumber.from(15000),
@ -20,7 +20,7 @@ const tests = [
data: data:
'0xa9059cbb000000000000000000000000ffe5bc4e8f1f969934d773fa67da095d2e491a9700000000000000000000000000000000000000000000000000000000000061a8', '0xa9059cbb000000000000000000000000ffe5bc4e8f1f969934d773fa67da095d2e491a9700000000000000000000000000000000000000000000000000000000000061a8',
tokenData: { tokenData: {
name: TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER, name: TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
args: [ args: [
'0xffe5bc4e8f1f969934d773fa67da095d2e491a97', '0xffe5bc4e8f1f969934d773fa67da095d2e491a97',
ethers.BigNumber.from(25000), ethers.BigNumber.from(25000),
@ -31,7 +31,7 @@ const tests = [
data: data:
'0xa9059cbb000000000000000000000000ffe5bc4e8f1f969934d773fa67da095d2e491a970000000000000000000000000000000000000000000000000000000000002710', '0xa9059cbb000000000000000000000000ffe5bc4e8f1f969934d773fa67da095d2e491a970000000000000000000000000000000000000000000000000000000000002710',
tokenData: { tokenData: {
name: TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER, name: TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
args: [ args: [
'0xffe5bc4e8f1f969934d773fa67da095d2e491a97', '0xffe5bc4e8f1f969934d773fa67da095d2e491a97',
ethers.BigNumber.from(10000), ethers.BigNumber.from(10000),

@ -19,7 +19,7 @@ import { getMessage } from '../../helpers/utils/i18n-helper';
import messages from '../../../../app/_locales/en/messages.json'; import messages from '../../../../app/_locales/en/messages.json';
import { ASSET_ROUTE, DEFAULT_ROUTE } from '../../helpers/constants/routes'; import { ASSET_ROUTE, DEFAULT_ROUTE } from '../../helpers/constants/routes';
import { import {
TRANSACTION_CATEGORIES, TRANSACTION_TYPES,
TRANSACTION_GROUP_CATEGORIES, TRANSACTION_GROUP_CATEGORIES,
TRANSACTION_STATUSES, TRANSACTION_STATUSES,
} from '../../../../shared/constants/transaction'; } from '../../../../shared/constants/transaction';
@ -106,7 +106,7 @@ const expectedResults = [
}, },
{ {
title: 'Swap ETH to ABC', title: 'Swap ETH to ABC',
category: TRANSACTION_CATEGORIES.SWAP, category: TRANSACTION_TYPES.SWAP,
subtitle: '', subtitle: '',
subtitleContainsOrigin: false, subtitleContainsOrigin: false,
date: 'May 12, 2020', date: 'May 12, 2020',

@ -1,12 +1,18 @@
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { showModal } from '../store/actions'; import { addHexPrefix } from 'ethereumjs-util';
import { showModal, showSidebar } from '../store/actions';
import { isBalanceSufficient } from '../pages/send/send.utils'; import { isBalanceSufficient } from '../pages/send/send.utils';
import { import {
getHexGasTotal, getHexGasTotal,
increaseLastGasPrice, increaseLastGasPrice,
} from '../helpers/utils/confirm-tx.util'; } from '../helpers/utils/confirm-tx.util';
import { getConversionRate, getSelectedAccount } from '../selectors'; import { getConversionRate, getSelectedAccount } from '../selectors';
import {
setCustomGasLimit,
setCustomGasPriceForRetry,
} from '../ducks/gas/gas.duck';
import { multiplyCurrencies } from '../helpers/utils/conversion-util';
/** /**
* Determine whether a transaction can be cancelled and provide a method to * Determine whether a transaction can be cancelled and provide a method to
@ -19,27 +25,61 @@ import { getConversionRate, getSelectedAccount } from '../selectors';
* @return {[boolean, Function]} * @return {[boolean, Function]}
*/ */
export function useCancelTransaction(transactionGroup) { export function useCancelTransaction(transactionGroup) {
const { primaryTransaction, initialTransaction } = transactionGroup; const { primaryTransaction } = transactionGroup;
const gasPrice = primaryTransaction.txParams?.gasPrice?.startsWith('-') const gasPrice = primaryTransaction.txParams?.gasPrice?.startsWith('-')
? '0x0' ? '0x0'
: primaryTransaction.txParams?.gasPrice; : primaryTransaction.txParams?.gasPrice;
const { id } = initialTransaction; const transaction = primaryTransaction;
const dispatch = useDispatch(); const dispatch = useDispatch();
const selectedAccount = useSelector(getSelectedAccount); const selectedAccount = useSelector(getSelectedAccount);
const conversionRate = useSelector(getConversionRate); const conversionRate = useSelector(getConversionRate);
const defaultNewGasPrice = addHexPrefix(
multiplyCurrencies(gasPrice, 1.1, {
toNumericBase: 'hex',
multiplicandBase: 16,
multiplierBase: 10,
}),
);
const cancelTransaction = useCallback( const cancelTransaction = useCallback(
(event) => { (event) => {
event.stopPropagation(); event.stopPropagation();
dispatch(setCustomGasLimit('0x5208'));
dispatch(setCustomGasPriceForRetry(defaultNewGasPrice));
const tx = {
...transaction,
txParams: {
...transaction.txParams,
gas: '0x5208',
value: '0x0',
},
};
return dispatch( return dispatch(
showModal({ showSidebar({
name: 'CANCEL_TRANSACTION', transitionName: 'sidebar-left',
transactionId: id, type: 'customize-gas',
originalGasPrice: gasPrice, props: {
transaction: tx,
onSubmit: (newGasLimit, newGasPrice) => {
const userCustomizedGasTotal = getHexGasTotal({
gasPrice: newGasPrice,
gasLimit: newGasLimit,
});
dispatch(
showModal({
name: 'CANCEL_TRANSACTION',
newGasFee: userCustomizedGasTotal,
transactionId: transaction.id,
defaultNewGasPrice: newGasPrice,
gasLimit: newGasLimit,
}),
);
},
},
}), }),
); );
}, },
[dispatch, id, gasPrice], [dispatch, transaction, defaultNewGasPrice],
); );
const hasEnoughCancelGas = const hasEnoughCancelGas =

@ -2,7 +2,7 @@ import { useSelector } from 'react-redux';
import { useRouteMatch } from 'react-router-dom'; import { useRouteMatch } from 'react-router-dom';
import { getTokens } from '../ducks/metamask/metamask'; import { getTokens } from '../ducks/metamask/metamask';
import { ASSET_ROUTE } from '../helpers/constants/routes'; import { ASSET_ROUTE } from '../helpers/constants/routes';
import { ETH_SWAPS_TOKEN_OBJECT } from '../helpers/constants/swaps'; import { ETH_SWAPS_TOKEN_OBJECT } from '../../../shared/constants/swaps';
/** /**
* Returns a token object for the asset that is currently being viewed. * Returns a token object for the asset that is currently being viewed.

@ -16,7 +16,7 @@ import { useMetricEvent } from './useMetricEvent';
* @return {Function} * @return {Function}
*/ */
export function useRetryTransaction(transactionGroup) { export function useRetryTransaction(transactionGroup) {
const { primaryTransaction, initialTransaction } = transactionGroup; const { primaryTransaction } = transactionGroup;
// Signature requests do not have a txParams, but this hook is called indiscriminately // Signature requests do not have a txParams, but this hook is called indiscriminately
const gasPrice = primaryTransaction.txParams?.gasPrice; const gasPrice = primaryTransaction.txParams?.gasPrice;
const trackMetricsEvent = useMetricEvent({ const trackMetricsEvent = useMetricEvent({
@ -34,7 +34,7 @@ export function useRetryTransaction(transactionGroup) {
trackMetricsEvent(); trackMetricsEvent();
await dispatch(fetchBasicGasEstimates); await dispatch(fetchBasicGasEstimates);
const transaction = initialTransaction; const transaction = primaryTransaction;
const increasedGasPrice = increaseLastGasPrice(gasPrice); const increasedGasPrice = increaseLastGasPrice(gasPrice);
await dispatch( await dispatch(
setCustomGasPriceForRetry( setCustomGasPriceForRetry(
@ -50,7 +50,7 @@ export function useRetryTransaction(transactionGroup) {
}), }),
); );
}, },
[dispatch, trackMetricsEvent, initialTransaction, gasPrice], [dispatch, trackMetricsEvent, gasPrice, primaryTransaction],
); );
return retryTransaction; return retryTransaction;

@ -1,5 +1,5 @@
import { TRANSACTION_CATEGORIES } from '../../../shared/constants/transaction'; import { TRANSACTION_TYPES } from '../../../shared/constants/transaction';
import { ETH_SWAPS_TOKEN_OBJECT } from '../helpers/constants/swaps'; import { ETH_SWAPS_TOKEN_OBJECT } from '../../../shared/constants/swaps';
import { getSwapsTokensReceivedFromTxMeta } from '../pages/swaps/swaps.util'; import { getSwapsTokensReceivedFromTxMeta } from '../pages/swaps/swaps.util';
import { useTokenFiatAmount } from './useTokenFiatAmount'; import { useTokenFiatAmount } from './useTokenFiatAmount';
@ -25,7 +25,7 @@ import { useTokenFiatAmount } from './useTokenFiatAmount';
export function useSwappedTokenValue(transactionGroup, currentAsset) { export function useSwappedTokenValue(transactionGroup, currentAsset) {
const { symbol, decimals, address } = currentAsset; const { symbol, decimals, address } = currentAsset;
const { primaryTransaction, initialTransaction } = transactionGroup; const { primaryTransaction, initialTransaction } = transactionGroup;
const { transactionCategory } = initialTransaction; const { type } = initialTransaction;
const { from: senderAddress } = initialTransaction.txParams || {}; const { from: senderAddress } = initialTransaction.txParams || {};
const isViewingReceivedTokenFromSwap = const isViewingReceivedTokenFromSwap =
@ -34,8 +34,7 @@ export function useSwappedTokenValue(transactionGroup, currentAsset) {
primaryTransaction.destinationTokenSymbol === 'ETH'); primaryTransaction.destinationTokenSymbol === 'ETH');
const swapTokenValue = const swapTokenValue =
transactionCategory === TRANSACTION_CATEGORIES.SWAP && type === TRANSACTION_TYPES.SWAP && isViewingReceivedTokenFromSwap
isViewingReceivedTokenFromSwap
? getSwapsTokensReceivedFromTxMeta( ? getSwapsTokensReceivedFromTxMeta(
primaryTransaction.destinationTokenSymbol, primaryTransaction.destinationTokenSymbol,
initialTransaction, initialTransaction,
@ -43,8 +42,7 @@ export function useSwappedTokenValue(transactionGroup, currentAsset) {
senderAddress, senderAddress,
decimals, decimals,
) )
: transactionCategory === TRANSACTION_CATEGORIES.SWAP && : type === TRANSACTION_TYPES.SWAP && primaryTransaction.swapTokenValue;
primaryTransaction.swapTokenValue;
const isNegative = const isNegative =
typeof swapTokenValue === 'string' typeof swapTokenValue === 'string'

@ -1,7 +1,7 @@
import { useState, useEffect, useRef, useCallback } from 'react'; import { useState, useEffect, useRef, useCallback } from 'react';
import TokenTracker from '@metamask/eth-token-tracker'; import TokenTracker from '@metamask/eth-token-tracker';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { getCurrentNetwork, getSelectedAddress } from '../selectors'; import { getCurrentChainId, getSelectedAddress } from '../selectors';
import { useEqualityCheck } from './useEqualityCheck'; import { useEqualityCheck } from './useEqualityCheck';
export function useTokenTracker( export function useTokenTracker(
@ -9,7 +9,7 @@ export function useTokenTracker(
includeFailedTokens = false, includeFailedTokens = false,
hideZeroBalanceTokens = false, hideZeroBalanceTokens = false,
) { ) {
const network = useSelector(getCurrentNetwork); const chainId = useSelector(getCurrentChainId);
const userAddress = useSelector(getSelectedAddress); const userAddress = useSelector(getSelectedAddress);
const [loading, setLoading] = useState(() => tokens?.length >= 0); const [loading, setLoading] = useState(() => tokens?.length >= 0);
const [tokensWithBalances, setTokensWithBalances] = useState([]); const [tokensWithBalances, setTokensWithBalances] = useState([]);
@ -74,14 +74,14 @@ export function useTokenTracker(
// Effect to set loading state and initialize tracker when values change // Effect to set loading state and initialize tracker when values change
useEffect(() => { useEffect(() => {
// This effect will only run initially and when: // This effect will only run initially and when:
// 1. network is updated, // 1. chainId is updated,
// 2. userAddress is changed, // 2. userAddress is changed,
// 3. token list is updated and not equal to previous list // 3. token list is updated and not equal to previous list
// in any of these scenarios, we should indicate to the user that their token // in any of these scenarios, we should indicate to the user that their token
// values are in the process of updating by setting loading state. // values are in the process of updating by setting loading state.
setLoading(true); setLoading(true);
if (!userAddress || network === 'loading' || !global.ethereumProvider) { if (!userAddress || chainId === undefined || !global.ethereumProvider) {
// If we do not have enough information to build a TokenTracker, we exit early // If we do not have enough information to build a TokenTracker, we exit early
// When the values above change, the effect will be restarted. We also teardown // When the values above change, the effect will be restarted. We also teardown
// tracker because inevitably this effect will run again momentarily. // tracker because inevitably this effect will run again momentarily.
@ -98,7 +98,7 @@ export function useTokenTracker(
}, [ }, [
userAddress, userAddress,
teardownTracker, teardownTracker,
network, chainId,
memoizedTokens, memoizedTokens,
updateBalances, updateBalances,
buildTracker, buildTracker,

@ -2,7 +2,7 @@ import { useSelector } from 'react-redux';
import { getKnownMethodData } from '../selectors/selectors'; import { getKnownMethodData } from '../selectors/selectors';
import { import {
getStatusKey, getStatusKey,
getTransactionCategoryTitle, getTransactionTypeTitle,
} from '../helpers/utils/transactions.util'; } from '../helpers/utils/transactions.util';
import { camelCaseToCapitalize } from '../helpers/utils/common.util'; import { camelCaseToCapitalize } from '../helpers/utils/common.util';
import { PRIMARY, SECONDARY } from '../helpers/constants/common'; import { PRIMARY, SECONDARY } from '../helpers/constants/common';
@ -18,7 +18,7 @@ import {
} from '../helpers/constants/transactions'; } from '../helpers/constants/transactions';
import { getTokens } from '../ducks/metamask/metamask'; import { getTokens } from '../ducks/metamask/metamask';
import { import {
TRANSACTION_CATEGORIES, TRANSACTION_TYPES,
TRANSACTION_GROUP_CATEGORIES, TRANSACTION_GROUP_CATEGORIES,
TRANSACTION_STATUSES, TRANSACTION_STATUSES,
} from '../../../shared/constants/transaction'; } from '../../../shared/constants/transaction';
@ -62,7 +62,7 @@ export function useTransactionDisplayData(transactionGroup) {
const t = useI18nContext(); const t = useI18nContext();
const { initialTransaction, primaryTransaction } = transactionGroup; const { initialTransaction, primaryTransaction } = transactionGroup;
// initialTransaction contains the data we need to derive the primary purpose of this transaction group // initialTransaction contains the data we need to derive the primary purpose of this transaction group
const { transactionCategory } = initialTransaction; const { type } = initialTransaction;
const { from: senderAddress, to } = initialTransaction.txParams || {}; const { from: senderAddress, to } = initialTransaction.txParams || {};
@ -85,7 +85,7 @@ export function useTransactionDisplayData(transactionGroup) {
// This value is used to determine whether we should look inside txParams.data // This value is used to determine whether we should look inside txParams.data
// to pull out and render token related information // to pull out and render token related information
const isTokenCategory = TOKEN_CATEGORY_HASH[transactionCategory]; const isTokenCategory = TOKEN_CATEGORY_HASH[type];
// these values are always instantiated because they are either // these values are always instantiated because they are either
// used by or returned from hooks. Hooks must be called at the top level, // used by or returned from hooks. Hooks must be called at the top level,
@ -145,12 +145,12 @@ export function useTransactionDisplayData(transactionGroup) {
// 6. Swap // 6. Swap
// 7. Swap Approval // 7. Swap Approval
if (transactionCategory === null || transactionCategory === undefined) { if (type === null || type === undefined) {
category = TRANSACTION_GROUP_CATEGORIES.SIGNATURE_REQUEST; category = TRANSACTION_GROUP_CATEGORIES.SIGNATURE_REQUEST;
title = t('signatureRequest'); title = t('signatureRequest');
subtitle = origin; subtitle = origin;
subtitleContainsOrigin = true; subtitleContainsOrigin = true;
} else if (transactionCategory === TRANSACTION_CATEGORIES.SWAP) { } else if (type === TRANSACTION_TYPES.SWAP) {
category = TRANSACTION_GROUP_CATEGORIES.SWAP; category = TRANSACTION_GROUP_CATEGORIES.SWAP;
title = t('swapTokenToToken', [ title = t('swapTokenToToken', [
initialTransaction.sourceTokenSymbol, initialTransaction.sourceTokenSymbol,
@ -170,48 +170,43 @@ export function useTransactionDisplayData(transactionGroup) {
} else { } else {
prefix = '-'; prefix = '-';
} }
} else if (transactionCategory === TRANSACTION_CATEGORIES.SWAP_APPROVAL) { } else if (type === TRANSACTION_TYPES.SWAP_APPROVAL) {
category = TRANSACTION_GROUP_CATEGORIES.APPROVAL; category = TRANSACTION_GROUP_CATEGORIES.APPROVAL;
title = t('swapApproval', [primaryTransaction.sourceTokenSymbol]); title = t('swapApproval', [primaryTransaction.sourceTokenSymbol]);
subtitle = origin; subtitle = origin;
subtitleContainsOrigin = true; subtitleContainsOrigin = true;
primarySuffix = primaryTransaction.sourceTokenSymbol; primarySuffix = primaryTransaction.sourceTokenSymbol;
} else if ( } else if (type === TRANSACTION_TYPES.TOKEN_METHOD_APPROVE) {
transactionCategory === TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE
) {
category = TRANSACTION_GROUP_CATEGORIES.APPROVAL; category = TRANSACTION_GROUP_CATEGORIES.APPROVAL;
prefix = ''; prefix = '';
title = t('approveSpendLimit', [token?.symbol || t('token')]); title = t('approveSpendLimit', [token?.symbol || t('token')]);
subtitle = origin; subtitle = origin;
subtitleContainsOrigin = true; subtitleContainsOrigin = true;
} else if ( } else if (
transactionCategory === TRANSACTION_CATEGORIES.DEPLOY_CONTRACT || type === TRANSACTION_TYPES.DEPLOY_CONTRACT ||
transactionCategory === TRANSACTION_CATEGORIES.CONTRACT_INTERACTION type === TRANSACTION_TYPES.CONTRACT_INTERACTION
) { ) {
category = TRANSACTION_GROUP_CATEGORIES.INTERACTION; category = TRANSACTION_GROUP_CATEGORIES.INTERACTION;
const transactionCategoryTitle = getTransactionCategoryTitle( const transactionTypeTitle = getTransactionTypeTitle(t, type);
t,
transactionCategory,
);
title = title =
(methodData?.name && camelCaseToCapitalize(methodData.name)) || (methodData?.name && camelCaseToCapitalize(methodData.name)) ||
transactionCategoryTitle; transactionTypeTitle;
subtitle = origin; subtitle = origin;
subtitleContainsOrigin = true; subtitleContainsOrigin = true;
} else if (transactionCategory === TRANSACTION_CATEGORIES.INCOMING) { } else if (type === TRANSACTION_TYPES.INCOMING) {
category = TRANSACTION_GROUP_CATEGORIES.RECEIVE; category = TRANSACTION_GROUP_CATEGORIES.RECEIVE;
title = t('receive'); title = t('receive');
prefix = ''; prefix = '';
subtitle = t('fromAddress', [shortenAddress(senderAddress)]); subtitle = t('fromAddress', [shortenAddress(senderAddress)]);
} else if ( } else if (
transactionCategory === TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER_FROM || type === TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM ||
transactionCategory === TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER type === TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER
) { ) {
category = TRANSACTION_GROUP_CATEGORIES.SEND; category = TRANSACTION_GROUP_CATEGORIES.SEND;
title = t('sendSpecifiedTokens', [token?.symbol || t('token')]); title = t('sendSpecifiedTokens', [token?.symbol || t('token')]);
recipientAddress = getTokenAddressParam(tokenData); recipientAddress = getTokenAddressParam(tokenData);
subtitle = t('toAddress', [shortenAddress(recipientAddress)]); subtitle = t('toAddress', [shortenAddress(recipientAddress)]);
} else if (transactionCategory === TRANSACTION_CATEGORIES.SENT_ETHER) { } else if (type === TRANSACTION_TYPES.SENT_ETHER) {
category = TRANSACTION_GROUP_CATEGORIES.SEND; category = TRANSACTION_GROUP_CATEGORIES.SEND;
title = t('sendETH'); title = t('sendETH');
subtitle = t('toAddress', [shortenAddress(recipientAddress)]); subtitle = t('toAddress', [shortenAddress(recipientAddress)]);
@ -241,15 +236,12 @@ export function useTransactionDisplayData(transactionGroup) {
subtitle, subtitle,
subtitleContainsOrigin, subtitleContainsOrigin,
primaryCurrency: primaryCurrency:
transactionCategory === TRANSACTION_CATEGORIES.SWAP && isPending type === TRANSACTION_TYPES.SWAP && isPending ? '' : primaryCurrency,
? ''
: primaryCurrency,
senderAddress, senderAddress,
recipientAddress, recipientAddress,
secondaryCurrency: secondaryCurrency:
(isTokenCategory && !tokenFiatAmount) || (isTokenCategory && !tokenFiatAmount) ||
(transactionCategory === TRANSACTION_CATEGORIES.SWAP && (type === TRANSACTION_TYPES.SWAP && !swapTokenFiatAmount)
!swapTokenFiatAmount)
? undefined ? undefined
: secondaryCurrency, : secondaryCurrency,
displayedStatusKey, displayedStatusKey,

@ -37,7 +37,7 @@
font-size: $font-size-paragraph; font-size: $font-size-paragraph;
color: $Black-100; color: $Black-100;
background-color: inherit; background-color: inherit;
padding: 2px 8px; padding: 2px 0 2px 8px;
} }
&__icon { &__icon {

@ -1,4 +1,4 @@
import { TRANSACTION_CATEGORIES } from '../../../../shared/constants/transaction'; import { TRANSACTION_TYPES } from '../../../../shared/constants/transaction';
import { decimalToHex } from '../../helpers/utils/conversions.util'; import { decimalToHex } from '../../helpers/utils/conversions.util';
import { import {
calcTokenValue, calcTokenValue,
@ -14,7 +14,7 @@ export function getCustomTxParamsData(
if (!tokenData) { if (!tokenData) {
throw new Error('Invalid data'); throw new Error('Invalid data');
} else if (tokenData.name !== TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE) { } else if (tokenData.name !== TRANSACTION_TYPES.TOKEN_METHOD_APPROVE) {
throw new Error( throw new Error(
`Invalid data; should be 'approve' method, but instead is '${tokenData.name}'`, `Invalid data; should be 'approve' method, but instead is '${tokenData.name}'`,
); );

@ -19,10 +19,10 @@ import { hexToDecimal } from '../../helpers/utils/conversions.util';
import AdvancedGasInputs from '../../components/app/gas-customization/advanced-gas-inputs'; import AdvancedGasInputs from '../../components/app/gas-customization/advanced-gas-inputs';
import TextField from '../../components/ui/text-field'; import TextField from '../../components/ui/text-field';
import { import {
TRANSACTION_CATEGORIES, TRANSACTION_TYPES,
TRANSACTION_STATUSES, TRANSACTION_STATUSES,
} from '../../../../shared/constants/transaction'; } from '../../../../shared/constants/transaction';
import { getTransactionCategoryTitle } from '../../helpers/utils/transactions.util'; import { getTransactionTypeTitle } from '../../helpers/utils/transactions.util';
export default class ConfirmTransactionBase extends Component { export default class ConfirmTransactionBase extends Component {
static contextTypes = { static contextTypes = {
@ -87,7 +87,7 @@ export default class ConfirmTransactionBase extends Component {
advancedInlineGasShown: PropTypes.bool, advancedInlineGasShown: PropTypes.bool,
insufficientBalance: PropTypes.bool, insufficientBalance: PropTypes.bool,
hideFiatConversion: PropTypes.bool, hideFiatConversion: PropTypes.bool,
transactionCategory: PropTypes.string, type: PropTypes.string,
getNextNonce: PropTypes.func, getNextNonce: PropTypes.func,
nextNonce: PropTypes.number, nextNonce: PropTypes.number,
tryReverseResolveAddress: PropTypes.func.isRequired, tryReverseResolveAddress: PropTypes.func.isRequired,
@ -218,7 +218,7 @@ export default class ConfirmTransactionBase extends Component {
functionType: functionType:
actionKey || actionKey ||
getMethodName(methodData.name) || getMethodName(methodData.name) ||
TRANSACTION_CATEGORIES.CONTRACT_INTERACTION, TRANSACTION_TYPES.CONTRACT_INTERACTION,
origin, origin,
}, },
}); });
@ -395,7 +395,7 @@ export default class ConfirmTransactionBase extends Component {
functionType: functionType:
actionKey || actionKey ||
getMethodName(methodData.name) || getMethodName(methodData.name) ||
TRANSACTION_CATEGORIES.CONTRACT_INTERACTION, TRANSACTION_TYPES.CONTRACT_INTERACTION,
origin, origin,
}, },
}); });
@ -450,7 +450,7 @@ export default class ConfirmTransactionBase extends Component {
functionType: functionType:
actionKey || actionKey ||
getMethodName(methodData.name) || getMethodName(methodData.name) ||
TRANSACTION_CATEGORIES.CONTRACT_INTERACTION, TRANSACTION_TYPES.CONTRACT_INTERACTION,
origin, origin,
}, },
}); });
@ -500,7 +500,7 @@ export default class ConfirmTransactionBase extends Component {
functionType: functionType:
actionKey || actionKey ||
getMethodName(methodData.name) || getMethodName(methodData.name) ||
TRANSACTION_CATEGORIES.CONTRACT_INTERACTION, TRANSACTION_TYPES.CONTRACT_INTERACTION,
origin, origin,
}, },
}); });
@ -667,7 +667,7 @@ export default class ConfirmTransactionBase extends Component {
customNonceValue, customNonceValue,
assetImage, assetImage,
unapprovedTxCount, unapprovedTxCount,
transactionCategory, type,
hideSenderToRecipient, hideSenderToRecipient,
showAccountInHeader, showAccountInHeader,
txData, txData,
@ -690,8 +690,8 @@ export default class ConfirmTransactionBase extends Component {
let functionType = getMethodName(name); let functionType = getMethodName(name);
if (!functionType) { if (!functionType) {
if (transactionCategory) { if (type) {
functionType = getTransactionCategoryTitle(t, transactionCategory); functionType = getTransactionTypeTitle(t, type);
} else { } else {
functionType = t('contractInteraction'); functionType = t('contractInteraction');
} }

@ -81,12 +81,7 @@ const mapStateToProps = (state, ownProps) => {
provider: { chainId }, provider: { chainId },
} = metamask; } = metamask;
const { tokenData, txData, tokenProps, nonce } = confirmTransaction; const { tokenData, txData, tokenProps, nonce } = confirmTransaction;
const { const { txParams = {}, lastGasPrice, id: transactionId, type } = txData;
txParams = {},
lastGasPrice,
id: transactionId,
transactionCategory,
} = txData;
const transaction = const transaction =
Object.values(unapprovedTxs).find( Object.values(unapprovedTxs).find(
({ id }) => id === (transactionId || Number(paramsTransactionId)), ({ id }) => id === (transactionId || Number(paramsTransactionId)),
@ -189,7 +184,7 @@ const mapStateToProps = (state, ownProps) => {
hideSubtitle: !isMainnet && !showFiatInTestnets, hideSubtitle: !isMainnet && !showFiatInTestnets,
hideFiatConversion: !isMainnet && !showFiatInTestnets, hideFiatConversion: !isMainnet && !showFiatInTestnets,
metaMetricsSendCount, metaMetricsSendCount,
transactionCategory, type,
nextNonce, nextNonce,
mostRecentOverviewPage: getMostRecentOverviewPage(state), mostRecentOverviewPage: getMostRecentOverviewPage(state),
isMainnet, isMainnet,

@ -15,7 +15,7 @@ import {
ENCRYPTION_PUBLIC_KEY_REQUEST_PATH, ENCRYPTION_PUBLIC_KEY_REQUEST_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_CATEGORIES } from '../../../../shared/constants/transaction'; import { TRANSACTION_TYPES } from '../../../../shared/constants/transaction';
export default class ConfirmTransactionSwitch extends Component { export default class ConfirmTransactionSwitch extends Component {
static propTypes = { static propTypes = {
@ -24,29 +24,29 @@ export default class ConfirmTransactionSwitch extends Component {
redirectToTransaction() { redirectToTransaction() {
const { txData } = this.props; const { txData } = this.props;
const { id, txParams: { data } = {}, transactionCategory } = txData; const { id, txParams: { data } = {}, type } = txData;
if (transactionCategory === TRANSACTION_CATEGORIES.DEPLOY_CONTRACT) { if (type === TRANSACTION_TYPES.DEPLOY_CONTRACT) {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_DEPLOY_CONTRACT_PATH}`; const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_DEPLOY_CONTRACT_PATH}`;
return <Redirect to={{ pathname }} />; return <Redirect to={{ pathname }} />;
} }
if (transactionCategory === TRANSACTION_CATEGORIES.SENT_ETHER) { if (type === TRANSACTION_TYPES.SENT_ETHER) {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_ETHER_PATH}`; const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_ETHER_PATH}`;
return <Redirect to={{ pathname }} />; return <Redirect to={{ pathname }} />;
} }
if (data) { if (data) {
switch (transactionCategory) { switch (type) {
case TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER: { case TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER: {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_TOKEN_PATH}`; const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_TOKEN_PATH}`;
return <Redirect to={{ pathname }} />; return <Redirect to={{ pathname }} />;
} }
case TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE: { case TRANSACTION_TYPES.TOKEN_METHOD_APPROVE: {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_APPROVE_PATH}`; const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_APPROVE_PATH}`;
return <Redirect to={{ pathname }} />; return <Redirect to={{ pathname }} />;
} }
case TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER_FROM: { case TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM: {
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 }} />;
} }

@ -27,7 +27,7 @@ const mapStateToProps = (state, ownProps) => {
const transaction = totalUnconfirmed const transaction = totalUnconfirmed
? unapprovedTxs[id] || unconfirmedTransactions[0] ? unapprovedTxs[id] || unconfirmedTransactions[0]
: {}; : {};
const { id: transactionId, transactionCategory } = transaction; const { id: transactionId, type } = transaction;
return { return {
totalUnapprovedCount: totalUnconfirmed, totalUnapprovedCount: totalUnconfirmed,
@ -38,7 +38,7 @@ const mapStateToProps = (state, ownProps) => {
paramsTransactionId: id && String(id), paramsTransactionId: id && String(id),
transactionId: transactionId && String(transactionId), transactionId: transactionId && String(transactionId),
transaction, transaction,
isTokenMethodAction: isTokenMethodAction(transactionCategory), isTokenMethodAction: isTokenMethodAction(type),
}; };
}; };

@ -17,9 +17,11 @@ const U2F_ERROR = 'U2F';
const LEDGER_LIVE_PATH = `m/44'/60'/0'/0/0`; const LEDGER_LIVE_PATH = `m/44'/60'/0'/0/0`;
const MEW_PATH = `m/44'/60'/0'`; const MEW_PATH = `m/44'/60'/0'`;
const BIP44_PATH = `m/44'/60'/0'/0`;
const HD_PATHS = [ const HD_PATHS = [
{ name: 'Ledger Live', value: LEDGER_LIVE_PATH }, { name: 'Ledger Live', value: LEDGER_LIVE_PATH },
{ name: 'Legacy (MEW / MyCrypto)', value: MEW_PATH }, { name: 'Legacy (MEW / MyCrypto)', value: MEW_PATH },
{ name: `BIP44 Standard (e.g. Trezor)`, value: BIP44_PATH },
]; ];
class ConnectHardwareForm extends Component { class ConnectHardwareForm extends Component {

@ -72,7 +72,7 @@ export default class Routes extends Component {
loadingMessage: PropTypes.string, loadingMessage: PropTypes.string,
alertMessage: PropTypes.string, alertMessage: PropTypes.string,
textDirection: PropTypes.string, textDirection: PropTypes.string,
network: PropTypes.string, isNetworkLoading: PropTypes.bool,
provider: PropTypes.object, provider: PropTypes.object,
frequentRpcListDetail: PropTypes.array, frequentRpcListDetail: PropTypes.array,
sidebar: PropTypes.object, sidebar: PropTypes.object,
@ -267,7 +267,7 @@ export default class Routes extends Component {
alertMessage, alertMessage,
textDirection, textDirection,
loadingMessage, loadingMessage,
network, isNetworkLoading,
provider, provider,
frequentRpcListDetail, frequentRpcListDetail,
setMouseUserState, setMouseUserState,
@ -276,9 +276,8 @@ export default class Routes extends Component {
isMouseUser, isMouseUser,
prepareToLeaveSwaps, prepareToLeaveSwaps,
} = this.props; } = this.props;
const isLoadingNetwork = network === 'loading';
const loadMessage = const loadMessage =
loadingMessage || isLoadingNetwork loadingMessage || isNetworkLoading
? this.getConnectingLabel(loadingMessage) ? this.getConnectingLabel(loadingMessage)
: null; : null;
@ -340,7 +339,7 @@ export default class Routes extends Component {
<AccountMenu /> <AccountMenu />
<div className="main-container-wrapper"> <div className="main-container-wrapper">
{isLoading && <Loading loadingMessage={loadMessage} />} {isLoading && <Loading loadingMessage={loadMessage} />}
{!isLoading && isLoadingNetwork && <LoadingNetwork />} {!isLoading && isNetworkLoading && <LoadingNetwork />}
{this.renderRoutes()} {this.renderRoutes()}
</div> </div>
{isUnlocked ? <Alerts history={this.props.history} /> : null} {isUnlocked ? <Alerts history={this.props.history} /> : null}

@ -4,6 +4,7 @@ import { compose } from 'redux';
import { import {
getNetworkIdentifier, getNetworkIdentifier,
getPreferences, getPreferences,
isNetworkLoading,
submittedPendingTransactionsSelector, submittedPendingTransactionsSelector,
} from '../../selectors'; } from '../../selectors';
import { import {
@ -37,7 +38,7 @@ function mapStateToProps(state) {
loadingMessage, loadingMessage,
isUnlocked: state.metamask.isUnlocked, isUnlocked: state.metamask.isUnlocked,
submittedPendingTransactions: submittedPendingTransactionsSelector(state), submittedPendingTransactions: submittedPendingTransactionsSelector(state),
network: state.metamask.network, isNetworkLoading: isNetworkLoading(state),
provider: state.metamask.provider, provider: state.metamask.provider,
frequentRpcListDetail: state.metamask.frequentRpcListDetail || [], frequentRpcListDetail: state.metamask.frequentRpcListDetail || [],
currentCurrency: state.metamask.currentCurrency, currentCurrency: state.metamask.currentCurrency,

@ -12,18 +12,18 @@ import {
import { import {
isValidAddress, isValidAddress,
isEthNetwork,
checkExistingAddresses, checkExistingAddresses,
isValidDomainName, isValidDomainName,
isOriginContractAddress, isOriginContractAddress,
isDefaultMetaMaskChain,
} from '../../../../helpers/utils/util'; } from '../../../../helpers/utils/util';
export function getToErrorObject(to, sendTokenAddress, network) { export function getToErrorObject(to, sendTokenAddress, chainId) {
let toError = null; let toError = null;
if (!to) { if (!to) {
toError = REQUIRED_ERROR; toError = REQUIRED_ERROR;
} else if (!isValidAddress(to)) { } else if (!isValidAddress(to)) {
toError = isEthNetwork(network) toError = isDefaultMetaMaskChain(chainId)
? INVALID_RECIPIENT_ADDRESS_ERROR ? INVALID_RECIPIENT_ADDRESS_ERROR
: INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR; : INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR;
} else if (isOriginContractAddress(to, sendTokenAddress)) { } else if (isOriginContractAddress(to, sendTokenAddress)) {

@ -36,7 +36,7 @@ export default class SendTransactionScreen extends Component {
gasPrice: PropTypes.string, gasPrice: PropTypes.string,
gasTotal: PropTypes.string, gasTotal: PropTypes.string,
history: PropTypes.object, history: PropTypes.object,
network: PropTypes.string, chainId: PropTypes.string,
primaryCurrency: PropTypes.string, primaryCurrency: PropTypes.string,
resetSendState: PropTypes.func.isRequired, resetSendState: PropTypes.func.isRequired,
selectedAddress: PropTypes.string, selectedAddress: PropTypes.string,
@ -84,7 +84,7 @@ export default class SendTransactionScreen extends Component {
conversionRate, conversionRate,
from: { address, balance }, from: { address, balance },
gasTotal, gasTotal,
network, chainId,
primaryCurrency, primaryCurrency,
sendToken, sendToken,
tokenBalance, tokenBalance,
@ -106,7 +106,7 @@ export default class SendTransactionScreen extends Component {
from: { balance: prevBalance }, from: { balance: prevBalance },
gasTotal: prevGasTotal, gasTotal: prevGasTotal,
tokenBalance: prevTokenBalance, tokenBalance: prevTokenBalance,
network: prevNetwork, chainId: prevChainId,
sendToken: prevSendToken, sendToken: prevSendToken,
to: prevTo, to: prevTo,
} = prevProps; } = prevProps;
@ -146,7 +146,7 @@ export default class SendTransactionScreen extends Component {
} }
if (!uninitialized) { if (!uninitialized) {
if (network !== prevNetwork && network !== 'loading') { if (chainId !== prevChainId && chainId !== undefined) {
updateSendTokenBalance({ updateSendTokenBalance({
sendToken, sendToken,
tokenContract, tokenContract,
@ -259,7 +259,7 @@ export default class SendTransactionScreen extends Component {
} }
validate(query) { validate(query) {
const { tokens, sendToken, network, sendTokenAddress } = this.props; const { tokens, sendToken, chainId, sendTokenAddress } = this.props;
const { internalSearch } = this.state; const { internalSearch } = this.state;
@ -268,7 +268,7 @@ export default class SendTransactionScreen extends Component {
return; return;
} }
const toErrorObject = getToErrorObject(query, sendTokenAddress, network); const toErrorObject = getToErrorObject(query, sendTokenAddress, chainId);
const toWarningObject = getToWarningObject(query, tokens, sendToken); const toWarningObject = getToWarningObject(query, tokens, sendToken);
this.setState({ this.setState({

@ -5,7 +5,6 @@ import { compose } from 'redux';
import { import {
getBlockGasLimit, getBlockGasLimit,
getConversionRate, getConversionRate,
getCurrentNetwork,
getGasLimit, getGasLimit,
getGasPrice, getGasPrice,
getGasTotal, getGasTotal,
@ -24,6 +23,7 @@ import {
getAddressBook, getAddressBook,
getSendTokenAddress, getSendTokenAddress,
isCustomPriceExcessive, isCustomPriceExcessive,
getCurrentChainId,
} from '../../selectors'; } from '../../selectors';
import { import {
@ -56,7 +56,7 @@ function mapStateToProps(state) {
gasLimit: getGasLimit(state), gasLimit: getGasLimit(state),
gasPrice: getGasPrice(state), gasPrice: getGasPrice(state),
gasTotal: getGasTotal(state), gasTotal: getGasTotal(state),
network: getCurrentNetwork(state), chainId: getCurrentChainId(state),
primaryCurrency: getPrimaryCurrency(state), primaryCurrency: getPrimaryCurrency(state),
qrCodeData: getQrCodeData(state), qrCodeData: getQrCodeData(state),
selectedAddress: getSelectedAddress(state), selectedAddress: getSelectedAddress(state),

@ -9,6 +9,10 @@ import AddRecipient from '../send-content/add-recipient/add-recipient.container'
import SendHeader from '../send-header/send-header.container'; import SendHeader from '../send-header/send-header.container';
import SendContent from '../send-content/send-content.container'; import SendContent from '../send-content/send-content.container';
import SendFooter from '../send-footer/send-footer.container'; import SendFooter from '../send-footer/send-footer.container';
import {
RINKEBY_CHAIN_ID,
ROPSTEN_CHAIN_ID,
} from '../../../../../shared/constants/network';
describe('Send Component', function () { describe('Send Component', function () {
let wrapper; let wrapper;
@ -59,7 +63,7 @@ describe('Send Component', function () {
gasPrice="mockGasPrice" gasPrice="mockGasPrice"
gasTotal="mockGasTotal" gasTotal="mockGasTotal"
history={{ mockProp: 'history-abc' }} history={{ mockProp: 'history-abc' }}
network="3" chainId={ROPSTEN_CHAIN_ID}
primaryCurrency="mockPrimaryCurrency" primaryCurrency="mockPrimaryCurrency"
selectedAddress="mockSelectedAddress" selectedAddress="mockSelectedAddress"
sendToken={{ address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }} sendToken={{ address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }}
@ -287,7 +291,7 @@ describe('Send Component', function () {
from: { from: {
balance: 'balanceChanged', balance: 'balanceChanged',
}, },
network: '3', chainId: ROPSTEN_CHAIN_ID,
sendToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, // Make sure not to hit updateGas when changing asset sendToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, // Make sure not to hit updateGas when changing asset
}); });
assert.strictEqual(propsMethodSpies.updateSendTokenBalance.callCount, 0); assert.strictEqual(propsMethodSpies.updateSendTokenBalance.callCount, 0);
@ -298,14 +302,14 @@ describe('Send Component', function () {
}); });
it('should not call updateSendTokenBalance or this.updateGas if network === loading', function () { it('should not call updateSendTokenBalance or this.updateGas if network === loading', function () {
wrapper.setProps({ network: 'loading' }); wrapper.setProps({ chainId: undefined });
SendTransactionScreen.prototype.updateGas.resetHistory(); SendTransactionScreen.prototype.updateGas.resetHistory();
propsMethodSpies.updateSendTokenBalance.resetHistory(); propsMethodSpies.updateSendTokenBalance.resetHistory();
wrapper.instance().componentDidUpdate({ wrapper.instance().componentDidUpdate({
from: { from: {
balance: 'balanceChanged', balance: 'balanceChanged',
}, },
network: '3', chainId: ROPSTEN_CHAIN_ID,
sendToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, // Make sure not to hit updateGas when changing asset sendToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, // Make sure not to hit updateGas when changing asset
}); });
assert.strictEqual(propsMethodSpies.updateSendTokenBalance.callCount, 0); assert.strictEqual(propsMethodSpies.updateSendTokenBalance.callCount, 0);
@ -322,7 +326,7 @@ describe('Send Component', function () {
from: { from: {
balance: 'balanceChanged', balance: 'balanceChanged',
}, },
network: '2', chainId: RINKEBY_CHAIN_ID,
sendToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, // Make sure not to hit updateGas when changing asset sendToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, // Make sure not to hit updateGas when changing asset
}); });
assert.strictEqual(propsMethodSpies.updateSendTokenBalance.callCount, 1); assert.strictEqual(propsMethodSpies.updateSendTokenBalance.callCount, 1);
@ -355,7 +359,7 @@ describe('Send Component', function () {
from: { from: {
balance: 'balancedChanged', balance: 'balancedChanged',
}, },
network: '3', // Make sure not to hit updateGas when changing network chainId: ROPSTEN_CHAIN_ID, // Make sure not to hit updateGas when changing network
sendToken: { address: 'newSelectedToken' }, sendToken: { address: 'newSelectedToken' },
}); });
assert.strictEqual( assert.strictEqual(
@ -482,7 +486,7 @@ describe('Send Component', function () {
}); });
it('should validate when input changes and has error on a bad network', function () { it('should validate when input changes and has error on a bad network', function () {
wrapper.setProps({ network: 'bad' }); wrapper.setProps({ chainId: 'bad' });
const instance = wrapper.instance(); const instance = wrapper.instance();
instance.onRecipientInputChange( instance.onRecipientInputChange(
'0x80F061544cC398520615B5d3e7a3BedD70cd4510', '0x80F061544cC398520615B5d3e7a3BedD70cd4510',
@ -498,7 +502,7 @@ describe('Send Component', function () {
}); });
it('should synchronously validate when input changes to ""', function () { it('should synchronously validate when input changes to ""', function () {
wrapper.setProps({ network: 'bad' }); wrapper.setProps({ chainId: 'bad' });
const instance = wrapper.instance(); const instance = wrapper.instance();
instance.onRecipientInputChange( instance.onRecipientInputChange(
'0x80F061544cC398520615B5d3e7a3BedD70cd4510', '0x80F061544cC398520615B5d3e7a3BedD70cd4510',

@ -30,7 +30,7 @@ import {
ERROR_FETCHING_QUOTES, ERROR_FETCHING_QUOTES,
QUOTES_NOT_AVAILABLE_ERROR, QUOTES_NOT_AVAILABLE_ERROR,
OFFLINE_FOR_MAINTENANCE, OFFLINE_FOR_MAINTENANCE,
} from '../../../helpers/constants/swaps'; } from '../../../../../shared/constants/swaps';
import { ASSET_ROUTE, DEFAULT_ROUTE } from '../../../helpers/constants/routes'; import { ASSET_ROUTE, DEFAULT_ROUTE } from '../../../helpers/constants/routes';
import { getRenderableNetworkFeesForQuote } from '../swaps.util'; import { getRenderableNetworkFeesForQuote } from '../swaps.util';

@ -36,7 +36,7 @@ import { useTokenTracker } from '../../../hooks/useTokenTracker';
import { useTokenFiatAmount } from '../../../hooks/useTokenFiatAmount'; import { useTokenFiatAmount } from '../../../hooks/useTokenFiatAmount';
import { useEthFiatAmount } from '../../../hooks/useEthFiatAmount'; import { useEthFiatAmount } from '../../../hooks/useEthFiatAmount';
import { ETH_SWAPS_TOKEN_OBJECT } from '../../../helpers/constants/swaps'; import { ETH_SWAPS_TOKEN_OBJECT } from '../../../../../shared/constants/swaps';
import { resetSwapsPostFetchState, removeToken } from '../../../store/actions'; import { resetSwapsPostFetchState, removeToken } from '../../../store/actions';
import { fetchTokenPrice, fetchTokenBalance } from '../swaps.util'; import { fetchTokenPrice, fetchTokenBalance } from '../swaps.util';

@ -44,7 +44,7 @@ import {
QUOTES_NOT_AVAILABLE_ERROR, QUOTES_NOT_AVAILABLE_ERROR,
SWAP_FAILED_ERROR, SWAP_FAILED_ERROR,
OFFLINE_FOR_MAINTENANCE, OFFLINE_FOR_MAINTENANCE,
} from '../../helpers/constants/swaps'; } from '../../../../shared/constants/swaps';
import { MAINNET_CHAIN_ID } from '../../../../shared/constants/network'; import { MAINNET_CHAIN_ID } from '../../../../shared/constants/network';
import { import {

@ -1,4 +1,4 @@
import { ETH_SWAPS_TOKEN_OBJECT } from '../../helpers/constants/swaps'; import { ETH_SWAPS_TOKEN_OBJECT } from '../../../../shared/constants/swaps';
export const TRADES_BASE_PROD_URL = export const TRADES_BASE_PROD_URL =
'https://api.metaswap.codefi.network/trades?'; 'https://api.metaswap.codefi.network/trades?';

@ -5,7 +5,7 @@ import { isValidAddress } from 'ethereumjs-util';
import { import {
ETH_SWAPS_TOKEN_OBJECT, ETH_SWAPS_TOKEN_OBJECT,
METASWAP_API_HOST, METASWAP_API_HOST,
} from '../../helpers/constants/swaps'; } from '../../../../shared/constants/swaps';
import { import {
calcTokenValue, calcTokenValue,
calcTokenAmount, calcTokenAmount,

@ -73,7 +73,7 @@ import {
getRenderableNetworkFeesForQuote, getRenderableNetworkFeesForQuote,
} from '../swaps.util'; } from '../swaps.util';
import { useTokenTracker } from '../../../hooks/useTokenTracker'; import { useTokenTracker } from '../../../hooks/useTokenTracker';
import { QUOTES_EXPIRED_ERROR } from '../../../helpers/constants/swaps'; import { QUOTES_EXPIRED_ERROR } from '../../../../../shared/constants/swaps';
import CountdownTimer from '../countdown-timer'; import CountdownTimer from '../countdown-timer';
import SwapsFooter from '../swaps-footer'; import SwapsFooter from '../swaps-footer';
import ViewQuotePriceDifference from './view-quote-price-difference'; import ViewQuotePriceDifference from './view-quote-price-difference';

@ -11,7 +11,7 @@ import {
} from '../helpers/utils/confirm-tx.util'; } from '../helpers/utils/confirm-tx.util';
import { sumHexes } from '../helpers/utils/transactions.util'; import { sumHexes } from '../helpers/utils/transactions.util';
import { transactionMatchesNetwork } from '../../../shared/modules/transaction.utils'; import { transactionMatchesNetwork } from '../../../shared/modules/transaction.utils';
import { getCurrentChainId, getCurrentNetworkId } from './selectors'; import { getCurrentChainId, deprecatedGetCurrentNetworkId } from './selectors';
import { getNativeCurrency } from '.'; import { getNativeCurrency } from '.';
const unapprovedTxsSelector = (state) => state.metamask.unapprovedTxs; const unapprovedTxsSelector = (state) => state.metamask.unapprovedTxs;
@ -32,7 +32,7 @@ export const unconfirmedTransactionsListSelector = createSelector(
unapprovedDecryptMsgsSelector, unapprovedDecryptMsgsSelector,
unapprovedEncryptionPublicKeyMsgsSelector, unapprovedEncryptionPublicKeyMsgsSelector,
unapprovedTypedMessagesSelector, unapprovedTypedMessagesSelector,
getCurrentNetworkId, deprecatedGetCurrentNetworkId,
getCurrentChainId, getCurrentChainId,
( (
unapprovedTxs = {}, unapprovedTxs = {},
@ -63,7 +63,7 @@ export const unconfirmedTransactionsHashSelector = createSelector(
unapprovedDecryptMsgsSelector, unapprovedDecryptMsgsSelector,
unapprovedEncryptionPublicKeyMsgsSelector, unapprovedEncryptionPublicKeyMsgsSelector,
unapprovedTypedMessagesSelector, unapprovedTypedMessagesSelector,
getCurrentNetworkId, deprecatedGetCurrentNetworkId,
getCurrentChainId, getCurrentChainId,
( (
unapprovedTxs = {}, unapprovedTxs = {},
@ -118,7 +118,7 @@ export const unconfirmedTransactionsCountSelector = createSelector(
unapprovedDecryptMsgCountSelector, unapprovedDecryptMsgCountSelector,
unapprovedEncryptionPublicKeyMsgCountSelector, unapprovedEncryptionPublicKeyMsgCountSelector,
unapprovedTypedMessagesCountSelector, unapprovedTypedMessagesCountSelector,
getCurrentNetworkId, deprecatedGetCurrentNetworkId,
getCurrentChainId, getCurrentChainId,
( (
unapprovedTxs = {}, unapprovedTxs = {},

@ -15,7 +15,19 @@ import {
getValueFromWeiHex, getValueFromWeiHex,
hexToDecimal, hexToDecimal,
} from '../helpers/utils/conversions.util'; } from '../helpers/utils/conversions.util';
import { ETH_SWAPS_TOKEN_OBJECT } from '../helpers/constants/swaps'; import { ETH_SWAPS_TOKEN_OBJECT } from '../../../shared/constants/swaps';
/**
* One of the only remaining valid uses of selecting the network subkey of the
* metamask state tree is to determine if the network is currently 'loading'.
*
* This will be used for all cases where this state key is accessed only for that
* purpose.
* @param {Object} state - redux state object
*/
export function isNetworkLoading(state) {
return state.metamask.network === 'loading';
}
export function getNetworkIdentifier(state) { export function getNetworkIdentifier(state) {
const { const {
@ -71,7 +83,16 @@ export function getAccountType(state) {
} }
} }
export function getCurrentNetworkId(state) { /**
* get the currently selected networkId which will be 'loading' when the
* network changes. The network id should not be used in most cases,
* instead use chainId in most situations. There are a limited number of
* use cases to use this method still, such as when comparing transaction
* metadata that predates the switch to using chainId.
* @deprecated - use getCurrentChainId instead
* @param {Object} state - redux state object
*/
export function deprecatedGetCurrentNetworkId(state) {
return state.metamask.network; return state.metamask.network;
} }
@ -136,7 +157,7 @@ export function getMetaMaskCachedBalances(state) {
// Fallback to fetching cached balances from network id // Fallback to fetching cached balances from network id
// this can eventually be removed // this can eventually be removed
const network = getCurrentNetworkId(state); const network = deprecatedGetCurrentNetworkId(state);
return ( return (
state.metamask.cachedBalances[chainId] ?? state.metamask.cachedBalances[chainId] ??
@ -347,17 +368,6 @@ export function getDomainMetadata(state) {
return state.metamask.domainMetadata; return state.metamask.domainMetadata;
} }
export const getBackgroundMetaMetricState = (state) => {
return {
network: getCurrentNetworkId(state),
accountType: getAccountType(state),
metaMetricsId: state.metamask.metaMetricsId,
numberOfTokens: getNumberOfTokens(state),
numberOfAccounts: getNumberOfAccounts(state),
participateInMetaMetrics: state.metamask.participateInMetaMetrics,
};
};
export function getRpcPrefsForCurrentProvider(state) { export function getRpcPrefsForCurrentProvider(state) {
const { frequentRpcListDetail, provider } = state.metamask; const { frequentRpcListDetail, provider } = state.metamask;
const selectRpcInfo = frequentRpcListDetail.find( const selectRpcInfo = frequentRpcListDetail.find(

@ -20,10 +20,6 @@ export function getNativeCurrency(state) {
return state.metamask.nativeCurrency; return state.metamask.nativeCurrency;
} }
export function getCurrentNetwork(state) {
return state.metamask.network;
}
export function getGasLimit(state) { export function getGasLimit(state) {
return state.metamask.send.gasLimit || '0'; return state.metamask.send.gasLimit || '0';
} }

@ -4,7 +4,7 @@ import {
KOVAN_NETWORK_ID, KOVAN_NETWORK_ID,
MAINNET_CHAIN_ID, MAINNET_CHAIN_ID,
} from '../../../../shared/constants/network'; } from '../../../../shared/constants/network';
import { TRANSACTION_CATEGORIES } from '../../../../shared/constants/transaction'; import { TRANSACTION_TYPES } from '../../../../shared/constants/transaction';
import { import {
unconfirmedTransactionsCountSelector, unconfirmedTransactionsCountSelector,
sendTokenTokenAmountAndToAddressSelector, sendTokenTokenAmountAndToAddressSelector,
@ -52,7 +52,7 @@ describe('Confirm Transaction Selector', function () {
const state = { const state = {
confirmTransaction: { confirmTransaction: {
tokenData: { tokenData: {
name: TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER, name: TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
args: getEthersArrayLikeFromObj({ args: getEthersArrayLikeFromObj({
_to: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', _to: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
_value: { toString: () => '1' }, _value: { toString: () => '1' },

@ -8,7 +8,6 @@ import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction';
import { import {
getBlockGasLimit, getBlockGasLimit,
getConversionRate, getConversionRate,
getCurrentNetwork,
getNativeCurrency, getNativeCurrency,
getGasLimit, getGasLimit,
getGasPrice, getGasPrice,
@ -116,12 +115,6 @@ describe('send selectors', function () {
}); });
}); });
describe('getCurrentNetwork()', function () {
it('should return the id of the currently selected network', function () {
assert.strictEqual(getCurrentNetwork(mockState), '3');
});
});
describe('getGasLimit()', function () { describe('getGasLimit()', function () {
it('should return the send.gasLimit', function () { it('should return the send.gasLimit', function () {
assert.strictEqual(getGasLimit(mockState), '0xFFFF'); assert.strictEqual(getGasLimit(mockState), '0xFFFF');

@ -6,12 +6,11 @@ import {
import { hexToDecimal } from '../helpers/utils/conversions.util'; import { hexToDecimal } from '../helpers/utils/conversions.util';
import txHelper from '../../lib/tx-helper'; import txHelper from '../../lib/tx-helper';
import { import {
TRANSACTION_CATEGORIES,
TRANSACTION_STATUSES, TRANSACTION_STATUSES,
TRANSACTION_TYPES, TRANSACTION_TYPES,
} from '../../../shared/constants/transaction'; } from '../../../shared/constants/transaction';
import { transactionMatchesNetwork } from '../../../shared/modules/transaction.utils'; import { transactionMatchesNetwork } from '../../../shared/modules/transaction.utils';
import { getCurrentChainId, getCurrentNetworkId } from './selectors'; import { getCurrentChainId, deprecatedGetCurrentNetworkId } from './selectors';
import { getSelectedAddress } from '.'; import { getSelectedAddress } from '.';
export const incomingTxListSelector = (state) => { export const incomingTxListSelector = (state) => {
@ -59,7 +58,7 @@ export const unapprovedMessagesSelector = createSelector(
unapprovedDecryptMsgsSelector, unapprovedDecryptMsgsSelector,
unapprovedEncryptionPublicKeyMsgsSelector, unapprovedEncryptionPublicKeyMsgsSelector,
unapprovedTypedMessagesSelector, unapprovedTypedMessagesSelector,
getCurrentNetworkId, deprecatedGetCurrentNetworkId,
getCurrentChainId, getCurrentChainId,
( (
unapprovedMsgs = {}, unapprovedMsgs = {},
@ -229,13 +228,9 @@ export const nonceSortedTransactionsSelector = createSelector(
status, status,
type, type,
time: txTime, time: txTime,
transactionCategory,
} = transaction; } = transaction;
if ( if (typeof nonce === 'undefined' || type === TRANSACTION_TYPES.INCOMING) {
typeof nonce === 'undefined' ||
transactionCategory === TRANSACTION_CATEGORIES.INCOMING
) {
const transactionGroup = { const transactionGroup = {
transactions: [transaction], transactions: [transaction],
initialTransaction: transaction, initialTransaction: transaction,
@ -244,7 +239,7 @@ export const nonceSortedTransactionsSelector = createSelector(
hasCancelled: false, hasCancelled: false,
}; };
if (transactionCategory === TRANSACTION_CATEGORIES.INCOMING) { if (type === TRANSACTION_TYPES.INCOMING) {
incomingTransactionGroups.push(transactionGroup); incomingTransactionGroups.push(transactionGroup);
} else { } else {
insertTransactionGroupByTime( insertTransactionGroupByTime(

@ -1483,7 +1483,7 @@ export function clearPendingTokens() {
}; };
} }
export function createCancelTransaction(txId, customGasPrice) { export function createCancelTransaction(txId, customGasPrice, customGasLimit) {
log.debug('background.cancelTransaction'); log.debug('background.cancelTransaction');
let newTxId; let newTxId;
@ -1492,6 +1492,7 @@ export function createCancelTransaction(txId, customGasPrice) {
background.createCancelTransaction( background.createCancelTransaction(
txId, txId,
customGasPrice, customGasPrice,
customGasLimit,
(err, newState) => { (err, newState) => {
if (err) { if (err) {
dispatch(displayWarning(err.message)); dispatch(displayWarning(err.message));

@ -22131,11 +22131,6 @@ safe-json-parse@~1.0.1:
resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-1.0.1.tgz#3e76723e38dfdda13c9b1d29a1e07ffee4b30b57" resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-1.0.1.tgz#3e76723e38dfdda13c9b1d29a1e07ffee4b30b57"
integrity sha1-PnZyPjjf3aE8mx0poeB//uSzC1c= integrity sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=
safe-json-stringify@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz#356e44bc98f1f93ce45df14bcd7c01cda86e0afd"
integrity sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==
safe-regex@^1.1.0: safe-regex@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"

Loading…
Cancel
Save