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. 60
      app/scripts/controllers/transactions/index.js
  5. 2
      app/scripts/lib/account-tracker.js
  6. 36
      app/scripts/lib/buy-eth-url.js
  7. 55
      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. 54
      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 {
TRANSACTION_CATEGORIES,
TRANSACTION_TYPES,
TRANSACTION_STATUSES,
} from '../../../shared/constants/transaction';
import {
@ -296,7 +296,7 @@ export default class IncomingTransactionsController {
value: bnToHex(new BN(txMeta.value)),
},
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 {
HISTORY_STORE_KEY,
@ -151,7 +151,7 @@ export default class PermissionsLogController {
? LOG_METHOD_TYPES.internal
: LOG_METHOD_TYPES.restricted,
origin: request.origin,
request: cloneDeep(request),
request: stringify(request, null, 2),
requestTime: Date.now(),
response: null,
responseTime: null,
@ -174,7 +174,7 @@ export default class PermissionsLogController {
return;
}
entry.response = cloneDeep(response);
entry.response = stringify(response, null, 2);
entry.responseTime = time;
entry.success = !response.error;
}

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

@ -19,7 +19,6 @@ import {
import { TRANSACTION_NO_CONTRACT_ERROR_KEY } from '../../../../ui/app/helpers/constants/error-keys';
import { getSwapsTokensReceivedFromTxMeta } from '../../../../ui/app/pages/swaps/swaps.util';
import {
TRANSACTION_CATEGORIES,
TRANSACTION_STATUSES,
TRANSACTION_TYPES,
} from '../../../../shared/constants/transaction';
@ -235,11 +234,10 @@ export default class TransactionController extends EventEmitter {
`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
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({
txParams: normalizedTxParams,
type: TRANSACTION_TYPES.STANDARD,
});
if (origin === 'metamask') {
@ -265,11 +263,10 @@ export default class TransactionController extends EventEmitter {
txMeta.origin = origin;
const {
transactionCategory,
getCodeResponse,
} = await this._determineTransactionCategory(txParams);
txMeta.transactionCategory = transactionCategory;
const { type, getCodeResponse } = await this._determineTransactionType(
txParams,
);
txMeta.type = type;
// ensure value
txMeta.txParams.value = txMeta.txParams.value
@ -347,7 +344,7 @@ export default class TransactionController extends EventEmitter {
return {};
} else if (
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 (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
* @returns {txMeta}
*/
async createCancelTransaction(originalTxId, customGasPrice) {
async createCancelTransaction(originalTxId, customGasPrice, customGasLimit) {
const originalTxMeta = this.txStateManager.getTx(originalTxId);
const { txParams } = originalTxMeta;
const { gasPrice: lastGasPrice, from, nonce } = txParams;
@ -401,7 +398,7 @@ export default class TransactionController extends EventEmitter {
from,
to: from,
nonce,
gas: '0x5208',
gas: customGasLimit || '0x5208',
value: '0x0',
gasPrice: newGasPrice,
},
@ -581,7 +578,7 @@ export default class TransactionController extends EventEmitter {
async publishTransaction(txId, rawTx) {
const txMeta = this.txStateManager.getTx(txId);
txMeta.rawTx = rawTx;
if (txMeta.transactionCategory === TRANSACTION_CATEGORIES.SWAP) {
if (txMeta.type === TRANSACTION_TYPES.SWAP) {
const preTxBalance = await this.query.getBalance(txMeta.txParams.from);
txMeta.preTxBalance = preTxBalance.toString(16);
}
@ -637,7 +634,7 @@ export default class TransactionController extends EventEmitter {
'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 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,
contractDeployment, contractMethodCall
* @typedef { 'transfer' | 'approve' | 'transferfrom' | 'contractInteraction'| 'sentEther' } InferrableTransactionTypes
*/
/**
* @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 _determineTransactionCategory(txParams) {
async _determineTransactionType(txParams) {
const { data, to } = txParams;
let name;
try {
@ -825,16 +839,16 @@ export default class TransactionController extends EventEmitter {
}
const tokenMethodName = [
TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE,
TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER,
TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER_FROM,
TRANSACTION_TYPES.TOKEN_METHOD_APPROVE,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM,
].find((methodName) => methodName === name && name.toLowerCase());
let result;
if (data && tokenMethodName) {
result = tokenMethodName;
} else if (data && !to) {
result = TRANSACTION_CATEGORIES.DEPLOY_CONTRACT;
result = TRANSACTION_TYPES.DEPLOY_CONTRACT;
}
let code;
@ -849,11 +863,11 @@ export default class TransactionController extends EventEmitter {
const codeIsEmpty = !code || code === '0x' || code === '0x0';
result = codeIsEmpty
? TRANSACTION_CATEGORIES.SENT_ETHER
: TRANSACTION_CATEGORIES.CONTRACT_INTERACTION;
? TRANSACTION_TYPES.SENT_ETHER
: TRANSACTION_TYPES.CONTRACT_INTERACTION;
}
return { transactionCategory: result, getCodeResponse: code };
return { type: result, getCodeResponse: code };
}
/**

@ -286,7 +286,7 @@ export default class AccountTracker {
return;
}
addresses.forEach((address, index) => {
const balance = bnToHex(result[index]);
const balance = result[index] ? bnToHex(result[index]) : '0x0';
accounts[address] = { address, balance };
});
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
*
* @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.address - The address the bought ETH should be sent to. Only relevant if network === '1'.
* @returns {string|undefined} The url at which the user can access ETH, while in the given network. If the passed
* network does not match any of the specified cases, or if no network is given, returns undefined.
* @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 chainId === '0x1'.
* @returns {string|undefined} The url at which the user can access ETH, while in the given chain. If the passed
* 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
if (!service) {
// eslint-disable-next-line no-param-reassign
service = getDefaultServiceForNetwork(network);
service = getDefaultServiceForChain(chainId);
}
switch (service) {
@ -33,21 +41,21 @@ export default function getBuyEthUrl({ network, address, service }) {
}
}
function getDefaultServiceForNetwork(network) {
switch (network) {
case '1':
function getDefaultServiceForChain(chainId) {
switch (chainId) {
case MAINNET_CHAIN_ID:
return 'wyre';
case '3':
case ROPSTEN_CHAIN_ID:
return 'metamask-faucet';
case '4':
case RINKEBY_CHAIN_ID:
return 'rinkeby-faucet';
case '42':
case KOVAN_CHAIN_ID:
return 'kovan-faucet';
case '5':
case GOERLI_CHAIN_ID:
return 'goerli-faucet';
default:
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,
PhishingController,
} from '@metamask/controllers';
import { getBackgroundMetaMetricState } from '../../ui/app/selectors';
import { TRANSACTION_STATUSES } from '../../shared/constants/transaction';
import { MAINNET_CHAIN_ID } from '../../shared/constants/network';
import ComposableObservableStore from './lib/ComposableObservableStore';
@ -330,12 +329,23 @@ export default class MetamaskController extends EventEmitter {
this.platform.showTransactionNotification(txMeta, rpcPrefs);
const { txReceipt } = txMeta;
const metamaskState = await this.getState();
if (txReceipt && txReceipt.status === '0x0') {
this.sendBackgroundMetaMetrics({
this.metaMetricsController.trackEvent(
{
category: 'Background',
properties: {
action: 'Transactions',
name: 'On Chain Failure',
customVariables: { errorMessage: txMeta.simulationFails?.reason },
});
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);
return {
...{ isInitialized },
isInitialized,
...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
* 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
* @returns {Object} MetaMask state
*/
async createCancelTransaction(originalTxId, customGasPrice) {
async createCancelTransaction(originalTxId, customGasPrice, customGasLimit) {
await this.txController.createCancelTransaction(
originalTxId,
customGasPrice,
customGasLimit,
);
const state = await this.getState();
return state;
@ -2413,32 +2420,6 @@ export default class MetamaskController extends EventEmitter {
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.
*

@ -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('./051').default,
require('./052').default,
require('./053').default,
];
export default migrations;

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

@ -134,6 +134,7 @@
"extension-port-stream": "^2.0.0",
"extensionizer": "^1.0.1",
"fast-json-patch": "^2.0.4",
"fast-safe-stringify": "^2.0.7",
"fuse.js": "^3.2.0",
"globalthis": "^1.0.1",
"human-standard-token-abi": "^2.0.0",
@ -175,7 +176,6 @@
"reselect": "^3.0.1",
"rpc-cap": "^3.2.1",
"safe-event-emitter": "^1.0.1",
"safe-json-stringify": "^1.2.0",
"single-call-balance-checker-abi": "^1.0.0",
"swappable-obj-proxy": "^1.1.0",
"textarea-caret": "^3.0.1",
@ -317,7 +317,8 @@
"gc-stats": false,
"github:assemblyscript/assemblyscript": 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 GOERLI_NETWORK_ID = '5';
export const KOVAN_NETWORK_ID = '42';
export const LOCALHOST_NETWORK_ID = '1337';
export const MAINNET_CHAIN_ID = '0x1';
export const ROPSTEN_CHAIN_ID = '0x3';
export const RINKEBY_CHAIN_ID = '0x4';
export const GOERLI_CHAIN_ID = '0x5';
export const KOVAN_CHAIN_ID = '0x2a';
export const LOCALHOST_CHAIN_ID = '0x539';
/**
* The largest possible chain ID we can handle.

@ -1,8 +1,26 @@
/**
* Transaction Type is a MetaMask construct used internally
* @typedef {Object} TransactionTypes
* @property {'standard'} STANDARD - A standard transaction, usually the first with
* a given nonce
* @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 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
* 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.
@ -16,9 +34,17 @@
* @type {TransactionTypes}
*/
export const TRANSACTION_TYPES = {
STANDARD: 'standard',
CANCEL: 'cancel',
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',
};
/**
* @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
* of transactions.

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

@ -1,20 +1,26 @@
import assert from 'assert';
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 () {
const mainnet = {
network: '1',
chainId: MAINNET_CHAIN_ID,
amount: 5,
address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
};
const ropsten = {
network: '3',
chainId: ROPSTEN_CHAIN_ID,
};
const rinkeby = {
network: '4',
chainId: RINKEBY_CHAIN_ID,
};
const kovan = {
network: '42',
chainId: KOVAN_CHAIN_ID,
};
it('returns wyre url with address for network 1', function () {

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

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

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

@ -9,7 +9,7 @@ import {
ROPSTEN_NETWORK_ID,
MAINNET_NETWORK_ID,
} 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 SwapsController, {
utils,

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

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

@ -1,9 +1,7 @@
import assert from 'assert';
import proxyquire from 'proxyquire';
import sinon from 'sinon';
import { TRANSACTION_STATUSES } from '../../../../../../../shared/constants/transaction';
let mapStateToProps;
let mapDispatchToProps;
let mergeProps;
@ -25,8 +23,7 @@ const sendActionSpies = {
proxyquire('../gas-modal-page-container.container.js', {
'react-redux': {
connect: (ms, md, mp) => {
mapStateToProps = ms;
connect: (_, md, mp) => {
mapDispatchToProps = md;
mergeProps = mp;
return () => ({});
@ -48,225 +45,6 @@ proxyquire('../gas-modal-page-container.container.js', {
});
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 () {
let dispatchSpy;
let mapDispatchToPropsObject;

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

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

@ -1,47 +1,40 @@
import { connect } from 'react-redux';
import { compose } from 'redux';
import { multiplyCurrencies } from '../../../../helpers/utils/conversion-util';
import withModalProps from '../../../../helpers/higher-order-components/with-modal-props';
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';
const mapStateToProps = (state, ownProps) => {
const { metamask } = state;
const { transactionId, originalGasPrice } = ownProps;
const {
transactionId,
originalGasPrice,
newGasFee,
defaultNewGasPrice,
gasLimit,
} = ownProps;
const { currentNetworkTxList } = metamask;
const transaction = currentNetworkTxList.find(
({ id }) => id === transactionId,
);
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 {
transactionId,
transactionStatus,
originalGasPrice,
defaultNewGasPrice,
newGasFee,
gasLimit,
};
};
const mapDispatchToProps = (dispatch) => {
return {
createCancelTransaction: (txId, customGasPrice) => {
return dispatch(createCancelTransaction(txId, customGasPrice));
createCancelTransaction: (txId, customGasPrice, customGasLimit) => {
return dispatch(
createCancelTransaction(txId, customGasPrice, customGasLimit),
);
},
showTransactionConfirmedModal: () =>
dispatch(showModal({ name: 'TRANSACTION_CONFIRMED' })),
@ -49,7 +42,12 @@ const mapDispatchToProps = (dispatch) => {
};
const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { transactionId, defaultNewGasPrice, ...restStateProps } = stateProps;
const {
transactionId,
defaultNewGasPrice,
gasLimit,
...restStateProps
} = stateProps;
// eslint-disable-next-line no-shadow
const { createCancelTransaction, ...restDispatchProps } = dispatchProps;
@ -58,7 +56,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
...restDispatchProps,
...ownProps,
createCancelTransaction: () =>
createCancelTransaction(transactionId, defaultNewGasPrice),
createCancelTransaction(transactionId, defaultNewGasPrice, gasLimit),
};
};

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

@ -5,15 +5,20 @@ import {
showModal,
hideWarning,
} from '../../../../store/actions';
import { getIsTestnet, getIsMainnet } from '../../../../selectors/selectors';
import {
getIsTestnet,
getIsMainnet,
getCurrentChainId,
getSelectedAddress,
} from '../../../../selectors/selectors';
import DepositEtherModal from './deposit-ether-modal.component';
function mapStateToProps(state) {
return {
network: state.metamask.network,
chainId: getCurrentChainId(state),
isTestnet: getIsTestnet(state),
isMainnet: getIsMainnet(state),
address: state.metamask.selectedAddress,
address: getSelectedAddress(state),
};
}
@ -31,7 +36,7 @@ function mapDispatchToProps(dispatch) {
showAccountDetailModal: () => {
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';
import Chip from '../../ui/chip/chip';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { isNetworkLoading } from '../../../selectors';
export default function NetworkDisplay({
colored,
@ -27,14 +28,14 @@ export default function NetworkDisplay({
targetNetwork,
onClick,
}) {
const networkIsLoading = useSelector(isNetworkLoading);
const currentNetwork = useSelector((state) => ({
network: state.metamask.network,
nickname: state.metamask.provider.nickname,
type: state.metamask.provider.type,
}));
const t = useI18nContext();
const { network = '', nickname: networkNickname, type: networkType } =
const { nickname: networkNickname, type: networkType } =
targetNetwork ?? currentNetwork;
return (
@ -45,7 +46,7 @@ export default function NetworkDisplay({
<LoadingIndicator
alt={t('attemptingConnect')}
title={t('attemptingConnect')}
isLoading={network === 'loading'}
isLoading={networkIsLoading}
>
<ColorIndicator
color={networkType === NETWORK_TYPE_RPC ? COLORS.UI4 : networkType}

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

@ -6,19 +6,16 @@ import {
} from '../../../selectors';
import { getHexGasTotal } from '../../../helpers/utils/confirm-tx.util';
import { sumHexes } from '../../../helpers/utils/transactions.util';
import { TRANSACTION_CATEGORIES } from '../../../../../shared/constants/transaction';
import TransactionBreakdown from './transaction-breakdown.component';
const mapStateToProps = (state, ownProps) => {
const { transaction, transactionCategory } = ownProps;
const { transaction, isTokenApprove } = ownProps;
const {
txParams: { gas, gasPrice, value } = {},
txReceipt: { gasUsed } = {},
} = transaction;
const { showFiatInTestnets } = getPreferences(state);
const isMainnet = getIsMainnet(state);
const isTokenApprove =
transactionCategory === TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE;
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 Popover from '../../ui/popover';
import { getBlockExplorerUrlForTx } from '../../../../../shared/modules/transaction.utils';
import { TRANSACTION_TYPES } from '../../../../../shared/constants/transaction';
export default class TransactionListItemDetails extends PureComponent {
static contextTypes = {
@ -156,7 +157,7 @@ export default class TransactionListItemDetails extends PureComponent {
} = this.props;
const {
primaryTransaction: transaction,
initialTransaction: { transactionCategory },
initialTransaction: { type },
} = transactionGroup;
const { hash } = transaction;
@ -255,7 +256,7 @@ export default class TransactionListItemDetails extends PureComponent {
<div className="transaction-list-item-details__cards-container">
<TransactionBreakdown
nonce={transactionGroup.initialTransaction.txParams.nonce}
transactionCategory={transactionCategory}
isTokenApprove={type === TRANSACTION_TYPES.TOKEN_METHOD_APPROVE}
transaction={transaction}
primaryCurrency={primaryCurrency}
className="transaction-list-item-details__transaction-breakdown"

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

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

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

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

@ -5,10 +5,9 @@ import log from 'loglevel';
import { addHexPrefix } from '../../../../app/scripts/lib/util';
import {
TRANSACTION_CATEGORIES,
TRANSACTION_TYPES,
TRANSACTION_GROUP_STATUSES,
TRANSACTION_STATUSES,
TRANSACTION_TYPES,
} from '../../../../shared/constants/transaction';
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
*
* @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
*/
export function isTokenMethodAction(transactionCategory) {
export function isTokenMethodAction(type) {
return [
TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER,
TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE,
TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER_FROM,
].includes(transactionCategory);
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
TRANSACTION_TYPES.TOKEN_METHOD_APPROVE,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM,
].includes(type);
}
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.
* @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
*/
export function getTransactionCategoryTitle(t, transactionCategory) {
switch (transactionCategory) {
case TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER: {
export function getTransactionTypeTitle(t, type) {
switch (type) {
case TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER: {
return t('transfer');
}
case TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER_FROM: {
case TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM: {
return t('transferFrom');
}
case TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE: {
case TRANSACTION_TYPES.TOKEN_METHOD_APPROVE: {
return t('approve');
}
case TRANSACTION_CATEGORIES.SENT_ETHER: {
case TRANSACTION_TYPES.SENT_ETHER: {
return t('sentEther');
}
case TRANSACTION_CATEGORIES.CONTRACT_INTERACTION: {
case TRANSACTION_TYPES.CONTRACT_INTERACTION: {
return t('contractInteraction');
}
case TRANSACTION_CATEGORIES.DEPLOY_CONTRACT: {
case TRANSACTION_TYPES.DEPLOY_CONTRACT: {
return t('contractDeployment');
}
case TRANSACTION_CATEGORIES.SWAP: {
case TRANSACTION_TYPES.SWAP: {
return t('swap');
}
case TRANSACTION_CATEGORIES.SWAP_APPROVAL: {
case TRANSACTION_TYPES.SWAP_APPROVAL: {
return t('swapApproval');
}
default: {
throw new Error(
`Unrecognized transaction category: ${transactionCategory}`,
);
throw new Error(`Unrecognized transaction type: ${type}`);
}
}
}

@ -1,6 +1,6 @@
import assert from 'assert';
import {
TRANSACTION_CATEGORIES,
TRANSACTION_TYPES,
TRANSACTION_GROUP_STATUSES,
TRANSACTION_STATUSES,
} from '../../../../shared/constants/transaction';
@ -14,7 +14,7 @@ describe('Transactions utils', function () {
);
assert.ok(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 value = args._value.toString();
assert.strictEqual(to, '0x50A9D56C2B8BA9A5c7f2C08C3d26E0499F23a706');

@ -4,6 +4,14 @@ import BigNumber from 'bignumber.js';
import ethUtil from 'ethereumjs-util';
import { DateTime } from 'luxon';
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
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);
});
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 (
!netId ||
netId === '1' ||
netId === '3' ||
netId === '4' ||
netId === '42' ||
netId === '1337'
!chainId ||
chainId === MAINNET_CHAIN_ID ||
chainId === ROPSTEN_CHAIN_ID ||
chainId === RINKEBY_CHAIN_ID ||
chainId === KOVAN_CHAIN_ID ||
chainId === GOERLI_CHAIN_ID ||
chainId === LOCALHOST_CHAIN_ID
) {
return true;
}

@ -7,6 +7,7 @@ import { getConversionRate, getSelectedAccount } from '../../selectors';
import { useCancelTransaction } from '../useCancelTransaction';
import { showModal } from '../../store/actions';
import { increaseLastGasPrice } from '../../helpers/utils/confirm-tx.util';
import * as actionConstants from '../../store/actionConstants';
describe('useCancelTransaction', function () {
let useSelector;
@ -46,7 +47,7 @@ describe('useCancelTransaction', function () {
);
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(() =>
useCancelTransaction(transactionGroup),
);
@ -55,12 +56,35 @@ describe('useCancelTransaction', function () {
preventDefault: () => 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(
dispatch.calledWith(
showModal({
name: 'CANCEL_TRANSACTION',
transactionId,
originalGasPrice,
newGasFee: '0x5208',
defaultNewGasPrice: '0x1',
gasLimit: '0x5208',
}),
),
true,
@ -98,7 +122,7 @@ describe('useCancelTransaction', function () {
);
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(() =>
useCancelTransaction(transactionGroup),
);
@ -107,12 +131,31 @@ describe('useCancelTransaction', function () {
preventDefault: () => 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(
dispatch.calledWith(
showModal({
name: 'CANCEL_TRANSACTION',
transactionId,
originalGasPrice,
newGasFee: '0x5208',
defaultNewGasPrice: '0x1',
gasLimit: '0x5208',
}),
),
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 () {
sinon.restore();
});

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

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

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

@ -2,7 +2,7 @@ import { useSelector } from 'react-redux';
import { useRouteMatch } from 'react-router-dom';
import { getTokens } from '../ducks/metamask/metamask';
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.

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

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

@ -1,7 +1,7 @@
import { useState, useEffect, useRef, useCallback } from 'react';
import TokenTracker from '@metamask/eth-token-tracker';
import { useSelector } from 'react-redux';
import { getCurrentNetwork, getSelectedAddress } from '../selectors';
import { getCurrentChainId, getSelectedAddress } from '../selectors';
import { useEqualityCheck } from './useEqualityCheck';
export function useTokenTracker(
@ -9,7 +9,7 @@ export function useTokenTracker(
includeFailedTokens = false,
hideZeroBalanceTokens = false,
) {
const network = useSelector(getCurrentNetwork);
const chainId = useSelector(getCurrentChainId);
const userAddress = useSelector(getSelectedAddress);
const [loading, setLoading] = useState(() => tokens?.length >= 0);
const [tokensWithBalances, setTokensWithBalances] = useState([]);
@ -74,14 +74,14 @@ export function useTokenTracker(
// Effect to set loading state and initialize tracker when values change
useEffect(() => {
// This effect will only run initially and when:
// 1. network is updated,
// 1. chainId is updated,
// 2. userAddress is changed,
// 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
// values are in the process of updating by setting loading state.
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
// When the values above change, the effect will be restarted. We also teardown
// tracker because inevitably this effect will run again momentarily.
@ -98,7 +98,7 @@ export function useTokenTracker(
}, [
userAddress,
teardownTracker,
network,
chainId,
memoizedTokens,
updateBalances,
buildTracker,

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

@ -37,7 +37,7 @@
font-size: $font-size-paragraph;
color: $Black-100;
background-color: inherit;
padding: 2px 8px;
padding: 2px 0 2px 8px;
}
&__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 {
calcTokenValue,
@ -14,7 +14,7 @@ export function getCustomTxParamsData(
if (!tokenData) {
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(
`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 TextField from '../../components/ui/text-field';
import {
TRANSACTION_CATEGORIES,
TRANSACTION_TYPES,
TRANSACTION_STATUSES,
} from '../../../../shared/constants/transaction';
import { getTransactionCategoryTitle } from '../../helpers/utils/transactions.util';
import { getTransactionTypeTitle } from '../../helpers/utils/transactions.util';
export default class ConfirmTransactionBase extends Component {
static contextTypes = {
@ -87,7 +87,7 @@ export default class ConfirmTransactionBase extends Component {
advancedInlineGasShown: PropTypes.bool,
insufficientBalance: PropTypes.bool,
hideFiatConversion: PropTypes.bool,
transactionCategory: PropTypes.string,
type: PropTypes.string,
getNextNonce: PropTypes.func,
nextNonce: PropTypes.number,
tryReverseResolveAddress: PropTypes.func.isRequired,
@ -218,7 +218,7 @@ export default class ConfirmTransactionBase extends Component {
functionType:
actionKey ||
getMethodName(methodData.name) ||
TRANSACTION_CATEGORIES.CONTRACT_INTERACTION,
TRANSACTION_TYPES.CONTRACT_INTERACTION,
origin,
},
});
@ -395,7 +395,7 @@ export default class ConfirmTransactionBase extends Component {
functionType:
actionKey ||
getMethodName(methodData.name) ||
TRANSACTION_CATEGORIES.CONTRACT_INTERACTION,
TRANSACTION_TYPES.CONTRACT_INTERACTION,
origin,
},
});
@ -450,7 +450,7 @@ export default class ConfirmTransactionBase extends Component {
functionType:
actionKey ||
getMethodName(methodData.name) ||
TRANSACTION_CATEGORIES.CONTRACT_INTERACTION,
TRANSACTION_TYPES.CONTRACT_INTERACTION,
origin,
},
});
@ -500,7 +500,7 @@ export default class ConfirmTransactionBase extends Component {
functionType:
actionKey ||
getMethodName(methodData.name) ||
TRANSACTION_CATEGORIES.CONTRACT_INTERACTION,
TRANSACTION_TYPES.CONTRACT_INTERACTION,
origin,
},
});
@ -667,7 +667,7 @@ export default class ConfirmTransactionBase extends Component {
customNonceValue,
assetImage,
unapprovedTxCount,
transactionCategory,
type,
hideSenderToRecipient,
showAccountInHeader,
txData,
@ -690,8 +690,8 @@ export default class ConfirmTransactionBase extends Component {
let functionType = getMethodName(name);
if (!functionType) {
if (transactionCategory) {
functionType = getTransactionCategoryTitle(t, transactionCategory);
if (type) {
functionType = getTransactionTypeTitle(t, type);
} else {
functionType = t('contractInteraction');
}

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

@ -15,7 +15,7 @@ import {
ENCRYPTION_PUBLIC_KEY_REQUEST_PATH,
} from '../../helpers/constants/routes';
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 {
static propTypes = {
@ -24,29 +24,29 @@ export default class ConfirmTransactionSwitch extends Component {
redirectToTransaction() {
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}`;
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}`;
return <Redirect to={{ pathname }} />;
}
if (data) {
switch (transactionCategory) {
case TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER: {
switch (type) {
case TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER: {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_TOKEN_PATH}`;
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}`;
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}`;
return <Redirect to={{ pathname }} />;
}

@ -27,7 +27,7 @@ const mapStateToProps = (state, ownProps) => {
const transaction = totalUnconfirmed
? unapprovedTxs[id] || unconfirmedTransactions[0]
: {};
const { id: transactionId, transactionCategory } = transaction;
const { id: transactionId, type } = transaction;
return {
totalUnapprovedCount: totalUnconfirmed,
@ -38,7 +38,7 @@ const mapStateToProps = (state, ownProps) => {
paramsTransactionId: id && String(id),
transactionId: transactionId && String(transactionId),
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 MEW_PATH = `m/44'/60'/0'`;
const BIP44_PATH = `m/44'/60'/0'/0`;
const HD_PATHS = [
{ name: 'Ledger Live', value: LEDGER_LIVE_PATH },
{ name: 'Legacy (MEW / MyCrypto)', value: MEW_PATH },
{ name: `BIP44 Standard (e.g. Trezor)`, value: BIP44_PATH },
];
class ConnectHardwareForm extends Component {

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

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

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

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

@ -5,7 +5,6 @@ import { compose } from 'redux';
import {
getBlockGasLimit,
getConversionRate,
getCurrentNetwork,
getGasLimit,
getGasPrice,
getGasTotal,
@ -24,6 +23,7 @@ import {
getAddressBook,
getSendTokenAddress,
isCustomPriceExcessive,
getCurrentChainId,
} from '../../selectors';
import {
@ -56,7 +56,7 @@ function mapStateToProps(state) {
gasLimit: getGasLimit(state),
gasPrice: getGasPrice(state),
gasTotal: getGasTotal(state),
network: getCurrentNetwork(state),
chainId: getCurrentChainId(state),
primaryCurrency: getPrimaryCurrency(state),
qrCodeData: getQrCodeData(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 SendContent from '../send-content/send-content.container';
import SendFooter from '../send-footer/send-footer.container';
import {
RINKEBY_CHAIN_ID,
ROPSTEN_CHAIN_ID,
} from '../../../../../shared/constants/network';
describe('Send Component', function () {
let wrapper;
@ -59,7 +63,7 @@ describe('Send Component', function () {
gasPrice="mockGasPrice"
gasTotal="mockGasTotal"
history={{ mockProp: 'history-abc' }}
network="3"
chainId={ROPSTEN_CHAIN_ID}
primaryCurrency="mockPrimaryCurrency"
selectedAddress="mockSelectedAddress"
sendToken={{ address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }}
@ -287,7 +291,7 @@ describe('Send Component', function () {
from: {
balance: 'balanceChanged',
},
network: '3',
chainId: ROPSTEN_CHAIN_ID,
sendToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, // Make sure not to hit updateGas when changing asset
});
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 () {
wrapper.setProps({ network: 'loading' });
wrapper.setProps({ chainId: undefined });
SendTransactionScreen.prototype.updateGas.resetHistory();
propsMethodSpies.updateSendTokenBalance.resetHistory();
wrapper.instance().componentDidUpdate({
from: {
balance: 'balanceChanged',
},
network: '3',
chainId: ROPSTEN_CHAIN_ID,
sendToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, // Make sure not to hit updateGas when changing asset
});
assert.strictEqual(propsMethodSpies.updateSendTokenBalance.callCount, 0);
@ -322,7 +326,7 @@ describe('Send Component', function () {
from: {
balance: 'balanceChanged',
},
network: '2',
chainId: RINKEBY_CHAIN_ID,
sendToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, // Make sure not to hit updateGas when changing asset
});
assert.strictEqual(propsMethodSpies.updateSendTokenBalance.callCount, 1);
@ -355,7 +359,7 @@ describe('Send Component', function () {
from: {
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' },
});
assert.strictEqual(
@ -482,7 +486,7 @@ describe('Send Component', 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();
instance.onRecipientInputChange(
'0x80F061544cC398520615B5d3e7a3BedD70cd4510',
@ -498,7 +502,7 @@ describe('Send Component', function () {
});
it('should synchronously validate when input changes to ""', function () {
wrapper.setProps({ network: 'bad' });
wrapper.setProps({ chainId: 'bad' });
const instance = wrapper.instance();
instance.onRecipientInputChange(
'0x80F061544cC398520615B5d3e7a3BedD70cd4510',

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

@ -36,7 +36,7 @@ import { useTokenTracker } from '../../../hooks/useTokenTracker';
import { useTokenFiatAmount } from '../../../hooks/useTokenFiatAmount';
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 { fetchTokenPrice, fetchTokenBalance } from '../swaps.util';

@ -44,7 +44,7 @@ import {
QUOTES_NOT_AVAILABLE_ERROR,
SWAP_FAILED_ERROR,
OFFLINE_FOR_MAINTENANCE,
} from '../../helpers/constants/swaps';
} from '../../../../shared/constants/swaps';
import { MAINNET_CHAIN_ID } from '../../../../shared/constants/network';
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 =
'https://api.metaswap.codefi.network/trades?';

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

@ -73,7 +73,7 @@ import {
getRenderableNetworkFeesForQuote,
} from '../swaps.util';
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 SwapsFooter from '../swaps-footer';
import ViewQuotePriceDifference from './view-quote-price-difference';

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

@ -15,7 +15,19 @@ import {
getValueFromWeiHex,
hexToDecimal,
} 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) {
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;
}
@ -136,7 +157,7 @@ export function getMetaMaskCachedBalances(state) {
// Fallback to fetching cached balances from network id
// this can eventually be removed
const network = getCurrentNetworkId(state);
const network = deprecatedGetCurrentNetworkId(state);
return (
state.metamask.cachedBalances[chainId] ??
@ -347,17 +368,6 @@ export function getDomainMetadata(state) {
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) {
const { frequentRpcListDetail, provider } = state.metamask;
const selectRpcInfo = frequentRpcListDetail.find(

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

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

@ -8,7 +8,6 @@ import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction';
import {
getBlockGasLimit,
getConversionRate,
getCurrentNetwork,
getNativeCurrency,
getGasLimit,
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 () {
it('should return the send.gasLimit', function () {
assert.strictEqual(getGasLimit(mockState), '0xFFFF');

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

@ -1483,7 +1483,7 @@ export function clearPendingTokens() {
};
}
export function createCancelTransaction(txId, customGasPrice) {
export function createCancelTransaction(txId, customGasPrice, customGasLimit) {
log.debug('background.cancelTransaction');
let newTxId;
@ -1492,6 +1492,7 @@ export function createCancelTransaction(txId, customGasPrice) {
background.createCancelTransaction(
txId,
customGasPrice,
customGasLimit,
(err, newState) => {
if (err) {
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"
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:
version "1.1.0"
resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"

Loading…
Cancel
Save