Merge pull request #7323 from MetaMask/Version-v7.4.0
Version v7.4.0 RC
@ -35,6 +35,9 @@ workflows:
|
||||
- test-unit:
|
||||
requires:
|
||||
- prep-deps
|
||||
- test-unit-global:
|
||||
requires:
|
||||
- prep-deps
|
||||
- test-mozilla-lint:
|
||||
requires:
|
||||
- prep-deps
|
||||
@ -51,6 +54,7 @@ workflows:
|
||||
requires:
|
||||
- test-lint
|
||||
- test-unit
|
||||
- test-unit-global
|
||||
- test-mozilla-lint
|
||||
- test-e2e-chrome
|
||||
- test-e2e-firefox
|
||||
@ -310,6 +314,16 @@ jobs:
|
||||
paths:
|
||||
- .nyc_output
|
||||
- coverage
|
||||
test-unit-global:
|
||||
docker:
|
||||
- image: circleci/node:10.16-browsers
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: test:unit:global
|
||||
command: yarn test:unit:global
|
||||
test-mozilla-lint:
|
||||
docker:
|
||||
- image: circleci/node:10.16-browsers
|
||||
|
15
CHANGELOG.md
@ -2,8 +2,21 @@
|
||||
|
||||
## Current Develop Branch
|
||||
|
||||
## 7.4.0 Tue Oct 29 2019
|
||||
- [#7186](https://github.com/MetaMask/metamask-extension/pull/7186): Use `AdvancedGasInputs` in `AdvancedTabContent`
|
||||
- [#7304](https://github.com/MetaMask/metamask-extension/pull/7304): Move signTypedData signing out to keyrings
|
||||
- [#7306](https://github.com/MetaMask/metamask-extension/pull/7306): correct the zh-TW translation
|
||||
- [#7309](https://github.com/MetaMask/metamask-extension/pull/7309): Freeze Promise global on boot
|
||||
- [#7296](https://github.com/MetaMask/metamask-extension/pull/7296): Add "Retry" option for failed transactions
|
||||
- [#7319](https://github.com/MetaMask/metamask-extension/pull/7319): Fix transaction list item status spacing issue
|
||||
- [#7218](https://github.com/MetaMask/metamask-extension/pull/7218): Add hostname and extensionId to site metadata
|
||||
- [#7324](https://github.com/MetaMask/metamask-extension/pull/7324): Fix contact deletion
|
||||
- [#7326](https://github.com/MetaMask/metamask-extension/pull/7326): Fix edit contact details
|
||||
- [#7325](https://github.com/MetaMask/metamask-extension/pull/7325): Update eth-json-rpc-filters to fix memory leak
|
||||
- [#7334](https://github.com/MetaMask/metamask-extension/pull/7334): Add web3 deprecation warning
|
||||
|
||||
## 7.3.1 Mon Oct 21 2019
|
||||
- [#7298](https://github.com/MetaMask/metamask-extension/pull/7298): Turn off full screen vs popup a/b test
|
||||
- [#7298](https://github.com/MetaMask/metamask-extension/pull/7298): Turn off full screen vs popup a/b test
|
||||
|
||||
## 7.3.0 Fri Sep 27 2019
|
||||
- [#6972](https://github.com/MetaMask/metamask-extension/pull/6972): 3box integration
|
||||
|
@ -1442,6 +1442,9 @@
|
||||
"viewOnEtherscan": {
|
||||
"message": "View on Etherscan"
|
||||
},
|
||||
"retryTransaction": {
|
||||
"message": "Retry Transaction"
|
||||
},
|
||||
"visitWebSite": {
|
||||
"message": "Visit our web site"
|
||||
},
|
||||
|
@ -162,7 +162,7 @@
|
||||
"description": "helper for inputting hex as decimal input"
|
||||
},
|
||||
"blockExplorerUrl": {
|
||||
"message": "封鎖 Explorer"
|
||||
"message": "區塊鏈瀏覽器"
|
||||
},
|
||||
"blockExplorerView": {
|
||||
"message": "在 $1 觀看帳號 ",
|
||||
@ -199,7 +199,7 @@
|
||||
"message": "開啟"
|
||||
},
|
||||
"optionalBlockExplorerUrl": {
|
||||
"message": "封鎖 Explorer URL(非必要)"
|
||||
"message": "區塊鏈瀏覽器 URL(非必要)"
|
||||
},
|
||||
"cancel": {
|
||||
"message": "取消"
|
||||
@ -641,7 +641,7 @@
|
||||
"message": "無效的 RPC URI"
|
||||
},
|
||||
"invalidBlockExplorerURL": {
|
||||
"message": "無效的 Block Explorer URI"
|
||||
"message": "無效的區塊鏈瀏覽器 URL"
|
||||
},
|
||||
"invalidSeedPhrase": {
|
||||
"message": "無效的助憶詞"
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "__MSG_appName__",
|
||||
"short_name": "__MSG_appName__",
|
||||
"version": "7.3.1",
|
||||
"version": "7.4.0",
|
||||
"manifest_version": 2,
|
||||
"author": "https://metamask.io",
|
||||
"description": "__MSG_appDescription__",
|
||||
|
@ -2,13 +2,14 @@
|
||||
* @file The entry point for the web extension singleton process.
|
||||
*/
|
||||
|
||||
// this needs to run before anything else
|
||||
|
||||
// these need to run before anything else
|
||||
require('./lib/freezeGlobals')
|
||||
require('./lib/setupFetchDebugging')()
|
||||
|
||||
// polyfills
|
||||
import 'abortcontroller-polyfill/dist/polyfill-patch-fetch'
|
||||
|
||||
const urlUtil = require('url')
|
||||
const endOfStream = require('end-of-stream')
|
||||
const pump = require('pump')
|
||||
const debounce = require('debounce-stream')
|
||||
@ -350,7 +351,10 @@ function setupController (initState, initLangCode) {
|
||||
const portStream = new PortStream(remotePort)
|
||||
// communication with popup
|
||||
controller.isClientOpen = true
|
||||
controller.setupTrustedCommunication(portStream, 'MetaMask')
|
||||
// construct fake URL for identifying internal messages
|
||||
const metamaskUrl = new URL(window.location)
|
||||
metamaskUrl.hostname = 'metamask'
|
||||
controller.setupTrustedCommunication(portStream, metamaskUrl)
|
||||
|
||||
if (processName === ENVIRONMENT_TYPE_POPUP) {
|
||||
popupIsOpen = true
|
||||
@ -386,9 +390,13 @@ function setupController (initState, initLangCode) {
|
||||
|
||||
// communication with page or other extension
|
||||
function connectExternal (remotePort) {
|
||||
const originDomain = urlUtil.parse(remotePort.sender.url).hostname
|
||||
const senderUrl = new URL(remotePort.sender.url)
|
||||
let extensionId
|
||||
if (remotePort.sender.id !== extension.runtime.id) {
|
||||
extensionId = remotePort.sender.id
|
||||
}
|
||||
const portStream = new PortStream(remotePort)
|
||||
controller.setupUntrustedCommunication(portStream, originDomain)
|
||||
controller.setupUntrustedCommunication(portStream, senderUrl, extensionId)
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -31,19 +31,26 @@ class ProviderApprovalController extends SafeEventEmitter {
|
||||
*
|
||||
* @param {object} opts - opts for the middleware contains the origin for the middleware
|
||||
*/
|
||||
createMiddleware ({ origin, getSiteMetadata }) {
|
||||
createMiddleware ({ senderUrl, extensionId, getSiteMetadata }) {
|
||||
return createAsyncMiddleware(async (req, res, next) => {
|
||||
// only handle requestAccounts
|
||||
if (req.method !== 'eth_requestAccounts') return next()
|
||||
// if already approved or privacy mode disabled, return early
|
||||
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
|
||||
const origin = senderUrl.hostname
|
||||
if (this.shouldExposeAccounts(origin) && isUnlocked) {
|
||||
res.result = [this.preferencesController.getSelectedAddress()]
|
||||
return
|
||||
}
|
||||
// register the provider request
|
||||
const metadata = await getSiteMetadata(origin)
|
||||
this._handleProviderRequest(origin, metadata.name, metadata.icon)
|
||||
const metadata = { hostname: senderUrl.hostname, origin }
|
||||
if (extensionId) {
|
||||
metadata.extensionId = extensionId
|
||||
} else {
|
||||
const siteMetadata = await getSiteMetadata(origin)
|
||||
Object.assign(metadata, { siteTitle: siteMetadata.name, siteImage: siteMetadata.icon})
|
||||
}
|
||||
this._handleProviderRequest(metadata)
|
||||
// wait for resolution of request
|
||||
const approved = await new Promise(resolve => this.once(`resolvedRequest:${origin}`, ({ approved }) => resolve(approved)))
|
||||
if (approved) {
|
||||
@ -54,19 +61,26 @@ class ProviderApprovalController extends SafeEventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} SiteMetadata
|
||||
* @param {string} hostname - The hostname of the site
|
||||
* @param {string} origin - The origin of the site
|
||||
* @param {string} [siteTitle] - The title of the site
|
||||
* @param {string} [siteImage] - The icon for the site
|
||||
* @param {string} [extensionId] - The extension ID of the extension
|
||||
*/
|
||||
/**
|
||||
* Called when a tab requests access to a full Ethereum provider API
|
||||
*
|
||||
* @param {string} origin - Origin of the window requesting full provider access
|
||||
* @param {string} siteTitle - The title of the document requesting full provider access
|
||||
* @param {string} siteImage - The icon of the window requesting full provider access
|
||||
* @param {SiteMetadata} siteMetadata - The metadata for the site requesting full provider access
|
||||
*/
|
||||
_handleProviderRequest (origin, siteTitle, siteImage) {
|
||||
_handleProviderRequest (siteMetadata) {
|
||||
const { providerRequests } = this.memStore.getState()
|
||||
const origin = siteMetadata.origin
|
||||
this.memStore.updateState({
|
||||
providerRequests: [
|
||||
...providerRequests,
|
||||
{ origin, siteTitle, siteImage },
|
||||
siteMetadata,
|
||||
],
|
||||
})
|
||||
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
|
||||
@ -98,6 +112,7 @@ class ProviderApprovalController extends SafeEventEmitter {
|
||||
[origin]: {
|
||||
siteTitle: providerRequest ? providerRequest.siteTitle : null,
|
||||
siteImage: providerRequest ? providerRequest.siteImage : null,
|
||||
hostname: providerRequest ? providerRequest.hostname : null,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
@ -174,27 +174,6 @@ log.debug('MetaMask - injected web3')
|
||||
|
||||
setupDappAutoReload(web3, inpageProvider.publicConfigStore)
|
||||
|
||||
// export global web3, with usage-detection and deprecation warning
|
||||
|
||||
/* TODO: Uncomment this area once auto-reload.js has been deprecated:
|
||||
let hasBeenWarned = false
|
||||
global.web3 = new Proxy(web3, {
|
||||
get: (_web3, key) => {
|
||||
// show warning once on web3 access
|
||||
if (!hasBeenWarned && key !== 'currentProvider') {
|
||||
console.warn('MetaMask: web3 will be deprecated in the near future in favor of the ethereumProvider \nhttps://github.com/MetaMask/faq/blob/master/detecting_metamask.md#web3-deprecation')
|
||||
hasBeenWarned = true
|
||||
}
|
||||
// return value normally
|
||||
return _web3[key]
|
||||
},
|
||||
set: (_web3, key, value) => {
|
||||
// set value normally
|
||||
_web3[key] = value
|
||||
},
|
||||
})
|
||||
*/
|
||||
|
||||
// set web3 defaultAccount
|
||||
inpageProvider.publicConfigStore.subscribe(function (state) {
|
||||
web3.eth.defaultAccount = state.selectedAddress
|
||||
|
@ -5,11 +5,17 @@ function setupDappAutoReload (web3, observable) {
|
||||
let reloadInProgress = false
|
||||
let lastTimeUsed
|
||||
let lastSeenNetwork
|
||||
let hasBeenWarned = false
|
||||
|
||||
global.web3 = new Proxy(web3, {
|
||||
get: (_web3, key) => {
|
||||
// get the time of use
|
||||
lastTimeUsed = Date.now()
|
||||
// show warning once on web3 access
|
||||
if (!hasBeenWarned && key !== 'currentProvider') {
|
||||
console.warn('MetaMask: web3 will be deprecated in the near future in favor of the ethereumProvider\nhttps://medium.com/metamask/4a899ad6e59e')
|
||||
hasBeenWarned = true
|
||||
}
|
||||
// return value normally
|
||||
return _web3[key]
|
||||
},
|
||||
|
41
app/scripts/lib/freezeGlobals.js
Normal file
@ -0,0 +1,41 @@
|
||||
|
||||
/**
|
||||
* Freezes the Promise global and prevents its reassignment.
|
||||
*/
|
||||
const deepFreeze = require('deep-freeze-strict')
|
||||
|
||||
if (
|
||||
process.env.IN_TEST !== 'true' &&
|
||||
process.env.METAMASK_ENV !== 'test'
|
||||
) {
|
||||
freeze(global, 'Promise')
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a key:value pair on a target object immutable, with limitations.
|
||||
* The key cannot be reassigned or deleted, and the value is recursively frozen
|
||||
* using Object.freeze.
|
||||
*
|
||||
* Because of JavaScript language limitations, this is does not mean that the
|
||||
* value is completely immutable. It is, however, better than nothing.
|
||||
*
|
||||
* @param {Object} target - The target object to freeze a property on.
|
||||
* @param {String} key - The key to freeze.
|
||||
* @param {any} [value] - The value to freeze, if different from the existing value on the target.
|
||||
* @param {boolean} [enumerable=true] - If given a value, whether the property is enumerable.
|
||||
*/
|
||||
function freeze (target, key, value, enumerable = true) {
|
||||
|
||||
const opts = {
|
||||
configurable: false, writable: false,
|
||||
}
|
||||
|
||||
if (value !== undefined) {
|
||||
opts.value = deepFreeze(value)
|
||||
opts.enumerable = enumerable
|
||||
} else {
|
||||
target[key] = deepFreeze(target[key])
|
||||
}
|
||||
|
||||
Object.defineProperty(target, key, opts)
|
||||
}
|
@ -53,10 +53,8 @@ const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
|
||||
const log = require('loglevel')
|
||||
const TrezorKeyring = require('eth-trezor-keyring')
|
||||
const LedgerBridgeKeyring = require('eth-ledger-bridge-keyring')
|
||||
const HW_WALLETS_KEYRINGS = [TrezorKeyring.type, LedgerBridgeKeyring.type]
|
||||
const EthQuery = require('eth-query')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const sigUtil = require('eth-sig-util')
|
||||
const contractMap = require('eth-contract-metadata')
|
||||
const {
|
||||
AddressBookController,
|
||||
@ -336,7 +334,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
// Expose no accounts if this origin has not been approved, preventing
|
||||
// account-requring RPC methods from completing successfully
|
||||
const exposeAccounts = this.providerApprovalController.shouldExposeAccounts(origin)
|
||||
if (origin !== 'MetaMask' && !exposeAccounts) { return [] }
|
||||
if (origin !== 'metamask' && !exposeAccounts) { return [] }
|
||||
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
|
||||
const selectedAddress = this.preferencesController.getSelectedAddress()
|
||||
// only show address if account is unlocked
|
||||
@ -1168,28 +1166,16 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
const version = msgParams.version
|
||||
try {
|
||||
const cleanMsgParams = await this.typedMessageManager.approveMessage(msgParams)
|
||||
const address = sigUtil.normalize(cleanMsgParams.from)
|
||||
const keyring = await this.keyringController.getKeyringForAccount(address)
|
||||
let signature
|
||||
// HW Wallet keyrings don't expose private keys
|
||||
// so we need to handle it separately
|
||||
if (!HW_WALLETS_KEYRINGS.includes(keyring.type)) {
|
||||
const wallet = keyring._getWalletForAccount(address)
|
||||
const privKey = ethUtil.toBuffer(wallet.getPrivateKey())
|
||||
switch (version) {
|
||||
case 'V1':
|
||||
signature = sigUtil.signTypedDataLegacy(privKey, { data: cleanMsgParams.data })
|
||||
break
|
||||
case 'V3':
|
||||
signature = sigUtil.signTypedData(privKey, { data: JSON.parse(cleanMsgParams.data) })
|
||||
break
|
||||
case 'V4':
|
||||
signature = sigUtil.signTypedData_v4(privKey, { data: JSON.parse(cleanMsgParams.data) })
|
||||
break
|
||||
|
||||
// For some reason every version after V1 used stringified params.
|
||||
if (version !== 'V1') {
|
||||
// But we don't have to require that. We can stop suggesting it now:
|
||||
if (typeof cleanMsgParams.data === 'string') {
|
||||
cleanMsgParams.data = JSON.parse(cleanMsgParams.data)
|
||||
}
|
||||
} else {
|
||||
signature = await keyring.signTypedData(address, cleanMsgParams.data)
|
||||
}
|
||||
|
||||
const signature = await this.keyringController.signTypedMessage(cleanMsgParams, { version })
|
||||
this.typedMessageManager.setMsgStatusSigned(msgId, signature)
|
||||
return this.getState()
|
||||
} catch (error) {
|
||||
@ -1332,23 +1318,25 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
* Used to create a multiplexed stream for connecting to an untrusted context
|
||||
* like a Dapp or other extension.
|
||||
* @param {*} connectionStream - The Duplex stream to connect to.
|
||||
* @param {string} originDomain - The domain requesting the stream, which
|
||||
* may trigger a blacklist reload.
|
||||
* @param {URL} senderUrl - The URL of the resource requesting the stream,
|
||||
* which may trigger a blacklist reload.
|
||||
* @param {string} extensionId - The extension id of the sender, if the sender
|
||||
* is an extension
|
||||
*/
|
||||
setupUntrustedCommunication (connectionStream, originDomain) {
|
||||
setupUntrustedCommunication (connectionStream, senderUrl, extensionId) {
|
||||
// Check if new connection is blacklisted
|
||||
if (this.phishingController.test(originDomain)) {
|
||||
log.debug('MetaMask - sending phishing warning for', originDomain)
|
||||
this.sendPhishingWarning(connectionStream, originDomain)
|
||||
if (this.phishingController.test(senderUrl.hostname)) {
|
||||
log.debug('MetaMask - sending phishing warning for', senderUrl.hostname)
|
||||
this.sendPhishingWarning(connectionStream, senderUrl.hostname)
|
||||
return
|
||||
}
|
||||
|
||||
// setup multiplexing
|
||||
const mux = setupMultiplex(connectionStream)
|
||||
// connect features
|
||||
const publicApi = this.setupPublicApi(mux.createStream('publicApi'), originDomain)
|
||||
this.setupProviderConnection(mux.createStream('provider'), originDomain, publicApi)
|
||||
this.setupPublicConfig(mux.createStream('publicConfig'), originDomain)
|
||||
const publicApi = this.setupPublicApi(mux.createStream('publicApi'))
|
||||
this.setupProviderConnection(mux.createStream('provider'), senderUrl, extensionId, publicApi)
|
||||
this.setupPublicConfig(mux.createStream('publicConfig'), senderUrl)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1358,15 +1346,15 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
* functions, like the ability to approve transactions or sign messages.
|
||||
*
|
||||
* @param {*} connectionStream - The duplex stream to connect to.
|
||||
* @param {string} originDomain - The domain requesting the connection,
|
||||
* @param {URL} senderUrl - The URL requesting the connection,
|
||||
* used in logging and error reporting.
|
||||
*/
|
||||
setupTrustedCommunication (connectionStream, originDomain) {
|
||||
setupTrustedCommunication (connectionStream, senderUrl) {
|
||||
// setup multiplexing
|
||||
const mux = setupMultiplex(connectionStream)
|
||||
// connect features
|
||||
this.setupControllerConnection(mux.createStream('controller'))
|
||||
this.setupProviderConnection(mux.createStream('provider'), originDomain)
|
||||
this.setupProviderConnection(mux.createStream('provider'), senderUrl)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1419,11 +1407,14 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
/**
|
||||
* A method for serving our ethereum provider over a given stream.
|
||||
* @param {*} outStream - The stream to provide over.
|
||||
* @param {string} origin - The URI of the requesting resource.
|
||||
* @param {URL} senderUrl - The URI of the requesting resource.
|
||||
* @param {string} extensionId - The id of the extension, if the requesting
|
||||
* resource is an extension.
|
||||
* @param {object} publicApi - The public API
|
||||
*/
|
||||
setupProviderConnection (outStream, origin, publicApi) {
|
||||
setupProviderConnection (outStream, senderUrl, extensionId, publicApi) {
|
||||
const getSiteMetadata = publicApi && publicApi.getSiteMetadata
|
||||
const engine = this.setupProviderEngine(origin, getSiteMetadata)
|
||||
const engine = this.setupProviderEngine(senderUrl, extensionId, getSiteMetadata)
|
||||
|
||||
// setup connection
|
||||
const providerStream = createEngineStream({ engine })
|
||||
@ -1433,7 +1424,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
providerStream,
|
||||
outStream,
|
||||
(err) => {
|
||||
// cleanup filter polyfill middleware
|
||||
// handle any middleware cleanup
|
||||
engine._middleware.forEach((mid) => {
|
||||
if (mid.destroy && typeof mid.destroy === 'function') {
|
||||
mid.destroy()
|
||||
@ -1447,7 +1438,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
/**
|
||||
* A method for creating a provider that is safely restricted for the requesting domain.
|
||||
**/
|
||||
setupProviderEngine (origin, getSiteMetadata) {
|
||||
setupProviderEngine (senderUrl, extensionId, getSiteMetadata) {
|
||||
const origin = senderUrl.hostname
|
||||
// setup json rpc engine stack
|
||||
const engine = new RpcEngine()
|
||||
const provider = this.provider
|
||||
@ -1470,7 +1462,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
engine.push(this.preferencesController.requestWatchAsset.bind(this.preferencesController))
|
||||
// requestAccounts
|
||||
engine.push(this.providerApprovalController.createMiddleware({
|
||||
origin,
|
||||
senderUrl,
|
||||
extensionId,
|
||||
getSiteMetadata,
|
||||
}))
|
||||
// forward to metamask primary provider
|
||||
@ -1487,11 +1480,12 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
* this is a good candidate for deprecation.
|
||||
*
|
||||
* @param {*} outStream - The stream to provide public config over.
|
||||
* @param {URL} senderUrl - The URL of requesting resource
|
||||
*/
|
||||
setupPublicConfig (outStream, originDomain) {
|
||||
setupPublicConfig (outStream, senderUrl) {
|
||||
const configStore = this.createPublicConfigStore({
|
||||
// check the providerApprovalController's approvedOrigins
|
||||
checkIsEnabled: () => this.providerApprovalController.shouldExposeAccounts(originDomain),
|
||||
checkIsEnabled: () => this.providerApprovalController.shouldExposeAccounts(senderUrl.hostname),
|
||||
})
|
||||
const configStream = asStream(configStore)
|
||||
|
||||
|
@ -1,3 +1,7 @@
|
||||
|
||||
// this must run before anything else
|
||||
require('./lib/freezeGlobals')
|
||||
|
||||
// polyfills
|
||||
import 'abortcontroller-polyfill/dist/polyfill-patch-fetch'
|
||||
|
||||
|
14
package.json
@ -15,6 +15,7 @@
|
||||
"watch:test:unit": "nodemon --exec \"yarn test:unit\" ./test ./app ./ui",
|
||||
"sendwithprivatedapp": "static-server test/e2e/send-eth-with-private-key-test --port 8080",
|
||||
"test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\" \"ui/app/**/*.test.js\"",
|
||||
"test:unit:global": "mocha test/unit-global/*",
|
||||
"test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js",
|
||||
"test:integration": "yarn test:integration:build && yarn test:flat",
|
||||
"test:integration:build": "gulp build:scss",
|
||||
@ -50,7 +51,7 @@
|
||||
},
|
||||
"resolutions": {
|
||||
"3box/ipfs/ipld-zcash/zcash-bitcore-lib/lodash": "^4.17.12",
|
||||
"pubnub/superagent-proxy/proxy-agent/pac-proxy-agent/https-proxy-agent": "^3.0.0"
|
||||
"pubnub/superagent-proxy": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"3box": "^1.10.2",
|
||||
@ -80,6 +81,7 @@
|
||||
"debounce": "1.1.0",
|
||||
"debounce-stream": "^2.0.0",
|
||||
"deep-extend": "^0.5.1",
|
||||
"deep-freeze-strict": "1.1.1",
|
||||
"detect-node": "^2.0.3",
|
||||
"detectrtc": "^1.3.6",
|
||||
"dnode": "^1.2.2",
|
||||
@ -88,10 +90,10 @@
|
||||
"eth-contract-metadata": "^1.9.2",
|
||||
"eth-ens-namehash": "^2.0.8",
|
||||
"eth-json-rpc-errors": "^1.1.0",
|
||||
"eth-json-rpc-filters": "^4.1.0",
|
||||
"eth-json-rpc-filters": "^4.1.1",
|
||||
"eth-json-rpc-infura": "^4.0.1",
|
||||
"eth-json-rpc-middleware": "^4.2.0",
|
||||
"eth-keyring-controller": "^5.1.0",
|
||||
"eth-keyring-controller": "^5.3.0",
|
||||
"eth-ledger-bridge-keyring": "^0.2.0",
|
||||
"eth-method-registry": "^1.2.0",
|
||||
"eth-phishing-detect": "^1.1.4",
|
||||
@ -112,7 +114,7 @@
|
||||
"extensionizer": "^1.0.1",
|
||||
"fast-json-patch": "^2.0.4",
|
||||
"fuse.js": "^3.2.0",
|
||||
"gaba": "^1.7.0",
|
||||
"gaba": "^1.7.5",
|
||||
"human-standard-token-abi": "^2.0.0",
|
||||
"jazzicon": "^1.2.0",
|
||||
"json-rpc-engine": "^5.1.4",
|
||||
@ -201,9 +203,7 @@
|
||||
"coveralls": "^3.0.0",
|
||||
"cross-env": "^5.1.4",
|
||||
"css-loader": "^2.1.1",
|
||||
"deep-freeze-strict": "^1.1.1",
|
||||
"del": "^3.0.0",
|
||||
"read-installed": "^4.0.3",
|
||||
"deps-dump": "^1.1.0",
|
||||
"envify": "^4.0.0",
|
||||
"enzyme": "^3.4.4",
|
||||
@ -266,6 +266,7 @@
|
||||
"radgrad-jsdoc-template": "^1.1.3",
|
||||
"react-devtools": "^3.6.1",
|
||||
"react-test-renderer": "^15.6.2",
|
||||
"read-installed": "^4.0.3",
|
||||
"redux-mock-store": "^1.5.3",
|
||||
"redux-test-utils": "^0.2.2",
|
||||
"remote-redux-devtools": "^0.5.16",
|
||||
@ -283,7 +284,6 @@
|
||||
"style-loader": "^0.21.0",
|
||||
"stylelint": "^9.10.1",
|
||||
"stylelint-config-standard": "^18.2.0",
|
||||
"tape": "^4.5.1",
|
||||
"testem": "^2.16.0",
|
||||
"through2": "^2.0.3",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
|
@ -8,14 +8,13 @@ const {
|
||||
checkBrowserForConsoleErrors,
|
||||
findElement,
|
||||
findElements,
|
||||
loadExtension,
|
||||
verboseReportOnFailure,
|
||||
setupFetchMocking,
|
||||
prepareExtensionForTesting,
|
||||
} = require('./helpers')
|
||||
const enLocaleMessages = require('../../app/_locales/en/messages.json')
|
||||
|
||||
describe('MetaMask', function () {
|
||||
let extensionId
|
||||
let driver
|
||||
|
||||
const testSeedPhrase = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress'
|
||||
@ -29,7 +28,6 @@ describe('MetaMask', function () {
|
||||
before(async function () {
|
||||
const result = await prepareExtensionForTesting()
|
||||
driver = result.driver
|
||||
extensionId = result.extensionId
|
||||
await setupFetchMocking(driver)
|
||||
})
|
||||
|
||||
@ -54,7 +52,7 @@ describe('MetaMask', function () {
|
||||
describe('Going through the first time flow', () => {
|
||||
it('clicks the continue button on the welcome screen', async () => {
|
||||
await findElement(driver, By.css('.welcome-page__header'))
|
||||
const welcomeScreenBtn = await findElement(driver, By.css('.first-time-flow__button'))
|
||||
const welcomeScreenBtn = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
|
||||
welcomeScreenBtn.click()
|
||||
await delay(largeDelayMs)
|
||||
})
|
||||
@ -99,7 +97,7 @@ describe('MetaMask', function () {
|
||||
assert.equal(seedPhrase.split(' ').length, 12)
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const nextScreen = (await findElements(driver, By.css('button.first-time-flow__button')))[1]
|
||||
const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.next.message}')]`))
|
||||
await nextScreen.click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
@ -112,37 +110,12 @@ describe('MetaMask', function () {
|
||||
await delay(tinyDelayMs)
|
||||
}
|
||||
|
||||
async function retypeSeedPhrase (words, wasReloaded, count = 0) {
|
||||
try {
|
||||
if (wasReloaded) {
|
||||
const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button')
|
||||
await driver.wait(until.elementLocated(byRevealButton, 10000))
|
||||
const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000)
|
||||
await revealSeedPhraseButton.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
|
||||
await nextScreen.click()
|
||||
await delay(regularDelayMs)
|
||||
}
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
await clickWordAndWait(words[i])
|
||||
}
|
||||
} catch (e) {
|
||||
if (count > 2) {
|
||||
throw e
|
||||
} else {
|
||||
await loadExtension(driver, extensionId)
|
||||
await retypeSeedPhrase(words, true, count + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it('can retype the seed phrase', async () => {
|
||||
const words = seedPhrase.split(' ')
|
||||
|
||||
await retypeSeedPhrase(words)
|
||||
for (const word of words) {
|
||||
await clickWordAndWait(word)
|
||||
}
|
||||
|
||||
const confirm = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
|
||||
await confirm.click()
|
||||
@ -151,7 +124,7 @@ describe('MetaMask', function () {
|
||||
|
||||
it('clicks through the success screen', async () => {
|
||||
await findElement(driver, By.xpath(`//div[contains(text(), 'Congratulations')]`))
|
||||
const doneButton = await findElement(driver, By.css('button.first-time-flow__button'))
|
||||
const doneButton = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`))
|
||||
await doneButton.click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
@ -183,7 +156,7 @@ describe('MetaMask', function () {
|
||||
|
||||
await passwordInputs[0].sendKeys('correct horse battery staple')
|
||||
await passwordInputs[1].sendKeys('correct horse battery staple')
|
||||
await driver.findElement(By.css('.first-time-flow__button')).click()
|
||||
await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.restore.message}')]`)).click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
|
||||
|
@ -7,7 +7,6 @@ const {
|
||||
const {
|
||||
checkBrowserForConsoleErrors,
|
||||
findElement,
|
||||
findElements,
|
||||
openNewPage,
|
||||
verboseReportOnFailure,
|
||||
waitUntilXWindowHandles,
|
||||
@ -15,6 +14,7 @@ const {
|
||||
setupFetchMocking,
|
||||
prepareExtensionForTesting,
|
||||
} = require('./helpers')
|
||||
const enLocaleMessages = require('../../app/_locales/en/messages.json')
|
||||
|
||||
describe('MetaMask', function () {
|
||||
let driver
|
||||
@ -54,7 +54,7 @@ describe('MetaMask', function () {
|
||||
describe('Going through the first time flow, but skipping the seed phrase challenge', () => {
|
||||
it('clicks the continue button on the welcome screen', async () => {
|
||||
await findElement(driver, By.css('.welcome-page__header'))
|
||||
const welcomeScreenBtn = await findElement(driver, By.css('.first-time-flow__button'))
|
||||
const welcomeScreenBtn = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
|
||||
welcomeScreenBtn.click()
|
||||
await delay(largeDelayMs)
|
||||
})
|
||||
@ -87,8 +87,8 @@ describe('MetaMask', function () {
|
||||
})
|
||||
|
||||
it('skips the seed phrase challenge', async () => {
|
||||
const buttons = await findElements(driver, By.css('.first-time-flow__button'))
|
||||
await buttons[0].click()
|
||||
const button = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.remindMeLater.message}')]`))
|
||||
await button.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const detailsButton = await findElement(driver, By.css('.account-details__details-button'))
|
||||
|
@ -12,6 +12,7 @@ const {
|
||||
setupFetchMocking,
|
||||
prepareExtensionForTesting,
|
||||
} = require('./helpers')
|
||||
const enLocaleMessages = require('../../app/_locales/en/messages.json')
|
||||
|
||||
describe('Using MetaMask with an existing account', function () {
|
||||
let driver
|
||||
@ -54,7 +55,7 @@ describe('Using MetaMask with an existing account', function () {
|
||||
describe('First time flow starting from an existing seed phrase', () => {
|
||||
it('clicks the continue button on the welcome screen', async () => {
|
||||
await findElement(driver, By.css('.welcome-page__header'))
|
||||
const welcomeScreenBtn = await findElement(driver, By.css('.first-time-flow__button'))
|
||||
const welcomeScreenBtn = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
|
||||
welcomeScreenBtn.click()
|
||||
await delay(largeDelayMs)
|
||||
})
|
||||
@ -91,7 +92,7 @@ describe('Using MetaMask with an existing account', function () {
|
||||
|
||||
it('clicks through the success screen', async () => {
|
||||
await findElement(driver, By.xpath(`//div[contains(text(), 'Congratulations')]`))
|
||||
const doneButton = await findElement(driver, By.css('button.first-time-flow__button'))
|
||||
const doneButton = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`))
|
||||
await doneButton.click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
|
@ -9,15 +9,14 @@ const {
|
||||
checkBrowserForConsoleErrors,
|
||||
findElement,
|
||||
findElements,
|
||||
loadExtension,
|
||||
openNewPage,
|
||||
verboseReportOnFailure,
|
||||
setupFetchMocking,
|
||||
prepareExtensionForTesting,
|
||||
} = require('./helpers')
|
||||
const enLocaleMessages = require('../../app/_locales/en/messages.json')
|
||||
|
||||
describe('MetaMask', function () {
|
||||
let extensionId
|
||||
let driver
|
||||
let publicAddress
|
||||
|
||||
@ -31,7 +30,6 @@ describe('MetaMask', function () {
|
||||
before(async function () {
|
||||
const result = await prepareExtensionForTesting()
|
||||
driver = result.driver
|
||||
extensionId = result.extensionId
|
||||
await setupFetchMocking(driver)
|
||||
})
|
||||
|
||||
@ -56,7 +54,7 @@ describe('MetaMask', function () {
|
||||
describe('Going through the first time flow, but skipping the seed phrase challenge', () => {
|
||||
it('clicks the continue button on the welcome screen', async () => {
|
||||
await findElement(driver, By.css('.welcome-page__header'))
|
||||
const welcomeScreenBtn = await findElement(driver, By.css('.first-time-flow__button'))
|
||||
const welcomeScreenBtn = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
|
||||
welcomeScreenBtn.click()
|
||||
await delay(largeDelayMs)
|
||||
})
|
||||
@ -89,8 +87,8 @@ describe('MetaMask', function () {
|
||||
})
|
||||
|
||||
it('skips the seed phrase challenge', async () => {
|
||||
const buttons = await findElements(driver, By.css('.first-time-flow__button'))
|
||||
await buttons[0].click()
|
||||
const button = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.remindMeLater.message}')]`))
|
||||
await button.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const detailsButton = await findElement(driver, By.css('.account-details__details-button'))
|
||||
@ -173,7 +171,7 @@ describe('MetaMask', function () {
|
||||
assert.equal(seedPhrase.split(' ').length, 12)
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const nextScreen = (await findElements(driver, By.css('button.first-time-flow__button')))[1]
|
||||
const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.next.message}')]`))
|
||||
await nextScreen.click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
@ -186,37 +184,12 @@ describe('MetaMask', function () {
|
||||
await delay(tinyDelayMs)
|
||||
}
|
||||
|
||||
async function retypeSeedPhrase (words, wasReloaded, count = 0) {
|
||||
try {
|
||||
if (wasReloaded) {
|
||||
const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button')
|
||||
await driver.wait(until.elementLocated(byRevealButton, 10000))
|
||||
const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000)
|
||||
await revealSeedPhraseButton.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
|
||||
await nextScreen.click()
|
||||
await delay(regularDelayMs)
|
||||
}
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
await clickWordAndWait(words[i])
|
||||
}
|
||||
} catch (e) {
|
||||
if (count > 2) {
|
||||
throw e
|
||||
} else {
|
||||
await loadExtension(driver, extensionId)
|
||||
await retypeSeedPhrase(words, true, count + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it('can retype the seed phrase', async () => {
|
||||
const words = seedPhrase.split(' ')
|
||||
|
||||
await retypeSeedPhrase(words)
|
||||
for (const word of words) {
|
||||
await clickWordAndWait(word)
|
||||
}
|
||||
|
||||
const confirm = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
|
||||
await confirm.click()
|
||||
|
@ -8,14 +8,13 @@ const {
|
||||
checkBrowserForConsoleErrors,
|
||||
findElement,
|
||||
findElements,
|
||||
loadExtension,
|
||||
verboseReportOnFailure,
|
||||
setupFetchMocking,
|
||||
prepareExtensionForTesting,
|
||||
} = require('./helpers')
|
||||
const enLocaleMessages = require('../../app/_locales/en/messages.json')
|
||||
|
||||
describe('MetaMask', function () {
|
||||
let extensionId
|
||||
let driver
|
||||
|
||||
const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'
|
||||
@ -29,7 +28,6 @@ describe('MetaMask', function () {
|
||||
before(async function () {
|
||||
const result = await prepareExtensionForTesting({ responsive: true })
|
||||
driver = result.driver
|
||||
extensionId = result.extensionId
|
||||
await setupFetchMocking(driver)
|
||||
})
|
||||
|
||||
@ -54,7 +52,7 @@ describe('MetaMask', function () {
|
||||
describe('Going through the first time flow', () => {
|
||||
it('clicks the continue button on the welcome screen', async () => {
|
||||
await findElement(driver, By.css('.welcome-page__header'))
|
||||
const welcomeScreenBtn = await findElement(driver, By.css('.first-time-flow__button'))
|
||||
const welcomeScreenBtn = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
|
||||
welcomeScreenBtn.click()
|
||||
await delay(largeDelayMs)
|
||||
})
|
||||
@ -99,7 +97,7 @@ describe('MetaMask', function () {
|
||||
assert.equal(seedPhrase.split(' ').length, 12)
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const nextScreen = (await findElements(driver, By.css('button.first-time-flow__button')))[1]
|
||||
const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.next.message}')]`))
|
||||
await nextScreen.click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
@ -112,37 +110,12 @@ describe('MetaMask', function () {
|
||||
await delay(tinyDelayMs)
|
||||
}
|
||||
|
||||
async function retypeSeedPhrase (words, wasReloaded, count = 0) {
|
||||
try {
|
||||
if (wasReloaded) {
|
||||
const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button')
|
||||
await driver.wait(until.elementLocated(byRevealButton, 10000))
|
||||
const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000)
|
||||
await revealSeedPhraseButton.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
|
||||
await nextScreen.click()
|
||||
await delay(regularDelayMs)
|
||||
}
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
await clickWordAndWait(words[i])
|
||||
}
|
||||
} catch (e) {
|
||||
if (count > 2) {
|
||||
throw e
|
||||
} else {
|
||||
await loadExtension(driver, extensionId)
|
||||
await retypeSeedPhrase(words, true, count + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it('can retype the seed phrase', async () => {
|
||||
const words = seedPhrase.split(' ')
|
||||
|
||||
await retypeSeedPhrase(words)
|
||||
for (const word of words) {
|
||||
await clickWordAndWait(word)
|
||||
}
|
||||
|
||||
const confirm = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
|
||||
await confirm.click()
|
||||
@ -151,7 +124,7 @@ describe('MetaMask', function () {
|
||||
|
||||
it('clicks through the success screen', async () => {
|
||||
await findElement(driver, By.xpath(`//div[contains(text(), 'Congratulations')]`))
|
||||
const doneButton = await findElement(driver, By.css('button.first-time-flow__button'))
|
||||
const doneButton = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`))
|
||||
await doneButton.click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
@ -192,7 +165,7 @@ describe('MetaMask', function () {
|
||||
|
||||
await passwordInputs[0].sendKeys('correct horse battery staple')
|
||||
await passwordInputs[1].sendKeys('correct horse battery staple')
|
||||
await driver.findElement(By.css('.first-time-flow__button')).click()
|
||||
await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.restore.message}')]`)).click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
|
||||
|
@ -10,7 +10,6 @@ const {
|
||||
closeAllWindowHandlesExcept,
|
||||
findElement,
|
||||
findElements,
|
||||
loadExtension,
|
||||
openNewPage,
|
||||
switchToWindowWithTitle,
|
||||
verboseReportOnFailure,
|
||||
@ -18,9 +17,9 @@ const {
|
||||
setupFetchMocking,
|
||||
prepareExtensionForTesting,
|
||||
} = require('./helpers')
|
||||
const enLocaleMessages = require('../../app/_locales/en/messages.json')
|
||||
|
||||
describe('MetaMask', function () {
|
||||
let extensionId
|
||||
let driver
|
||||
let tokenAddress
|
||||
|
||||
@ -35,7 +34,6 @@ describe('MetaMask', function () {
|
||||
before(async function () {
|
||||
const result = await prepareExtensionForTesting()
|
||||
driver = result.driver
|
||||
extensionId = result.extensionId
|
||||
await setupFetchMocking(driver)
|
||||
})
|
||||
|
||||
@ -60,7 +58,7 @@ describe('MetaMask', function () {
|
||||
describe('Going through the first time flow', () => {
|
||||
it('clicks the continue button on the welcome screen', async () => {
|
||||
await findElement(driver, By.css('.welcome-page__header'))
|
||||
const welcomeScreenBtn = await findElement(driver, By.css('.first-time-flow__button'))
|
||||
const welcomeScreenBtn = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
|
||||
welcomeScreenBtn.click()
|
||||
await delay(largeDelayMs)
|
||||
})
|
||||
@ -105,7 +103,7 @@ describe('MetaMask', function () {
|
||||
assert.equal(seedPhrase.split(' ').length, 12)
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const nextScreen = (await findElements(driver, By.css('button.first-time-flow__button')))[1]
|
||||
const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.next.message}')]`))
|
||||
await nextScreen.click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
@ -118,37 +116,12 @@ describe('MetaMask', function () {
|
||||
await delay(tinyDelayMs)
|
||||
}
|
||||
|
||||
async function retypeSeedPhrase (words, wasReloaded, count = 0) {
|
||||
try {
|
||||
if (wasReloaded) {
|
||||
const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button')
|
||||
await driver.wait(until.elementLocated(byRevealButton, 10000))
|
||||
const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000)
|
||||
await revealSeedPhraseButton.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
|
||||
await nextScreen.click()
|
||||
await delay(regularDelayMs)
|
||||
}
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
await clickWordAndWait(words[i])
|
||||
}
|
||||
} catch (e) {
|
||||
if (count > 2) {
|
||||
throw e
|
||||
} else {
|
||||
await loadExtension(driver, extensionId)
|
||||
await retypeSeedPhrase(words, true, count + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it('can retype the seed phrase', async () => {
|
||||
const words = seedPhrase.split(' ')
|
||||
|
||||
await retypeSeedPhrase(words)
|
||||
for (const word of words) {
|
||||
await clickWordAndWait(word)
|
||||
}
|
||||
|
||||
const confirm = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
|
||||
await confirm.click()
|
||||
@ -157,7 +130,7 @@ describe('MetaMask', function () {
|
||||
|
||||
it('clicks through the success screen', async () => {
|
||||
await findElement(driver, By.xpath(`//div[contains(text(), 'Congratulations')]`))
|
||||
const doneButton = await findElement(driver, By.css('button.first-time-flow__button'))
|
||||
const doneButton = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`))
|
||||
await doneButton.click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
@ -249,7 +222,7 @@ describe('MetaMask', function () {
|
||||
|
||||
await passwordInputs[0].sendKeys('correct horse battery staple')
|
||||
await passwordInputs[1].sendKeys('correct horse battery staple')
|
||||
await driver.findElement(By.css('.first-time-flow__button')).click()
|
||||
await driver.findElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.restore.message}')]`)).click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
|
||||
@ -475,13 +448,13 @@ describe('MetaMask', function () {
|
||||
await approveButton.click()
|
||||
|
||||
await driver.switchTo().window(dapp)
|
||||
await delay(regularDelayMs)
|
||||
await delay(2000)
|
||||
})
|
||||
|
||||
it('initiates a send from the dapp', async () => {
|
||||
const send3eth = await findElement(driver, By.xpath(`//button[contains(text(), 'Send')]`), 10000)
|
||||
await send3eth.click()
|
||||
await delay(5000)
|
||||
await delay(2000)
|
||||
|
||||
windowHandles = await driver.getAllWindowHandles()
|
||||
await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles)
|
||||
@ -494,8 +467,6 @@ describe('MetaMask', function () {
|
||||
await delay(50)
|
||||
|
||||
|
||||
await gasPriceInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasPriceInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasPriceInput.sendKeys('10')
|
||||
@ -504,9 +475,9 @@ describe('MetaMask', function () {
|
||||
await delay(50)
|
||||
await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
|
||||
await delay(50)
|
||||
|
||||
await gasLimitInput.sendKeys('25000')
|
||||
await delay(largeDelayMs * 2)
|
||||
|
||||
await delay(1000)
|
||||
|
||||
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`), 10000)
|
||||
await confirmButton.click()
|
||||
@ -792,14 +763,12 @@ describe('MetaMask', function () {
|
||||
await modalTabs[1].click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-tab__gas-edit-row__input'))
|
||||
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-gas-inputs__gas-edit-row__input'))
|
||||
const gasLimitValue = await gasLimitInput.getAttribute('value')
|
||||
assert(Number(gasLimitValue) < 100000, 'Gas Limit too high')
|
||||
await gasPriceInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
|
||||
await delay(50)
|
||||
|
||||
await gasPriceInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasPriceInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasPriceInput.sendKeys('10')
|
||||
@ -808,18 +777,9 @@ describe('MetaMask', function () {
|
||||
await delay(50)
|
||||
await gasLimitInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasLimitInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasLimitInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasLimitInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasLimitInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasLimitInput.sendKeys('60001')
|
||||
await delay(50)
|
||||
await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'e'))
|
||||
await delay(50)
|
||||
|
||||
await delay(1000)
|
||||
|
||||
const save = await findElement(driver, By.xpath(`//button[contains(text(), 'Save')]`))
|
||||
await save.click()
|
||||
@ -914,7 +874,7 @@ describe('MetaMask', function () {
|
||||
await advancedTabButton.click()
|
||||
await delay(tinyDelayMs)
|
||||
|
||||
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-tab__gas-edit-row__input'))
|
||||
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-gas-inputs__gas-edit-row__input'))
|
||||
assert(gasPriceInput.getAttribute('value'), 20)
|
||||
assert(gasLimitInput.getAttribute('value'), 4700000)
|
||||
|
||||
@ -1103,12 +1063,10 @@ describe('MetaMask', function () {
|
||||
await modalTabs[1].click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-tab__gas-edit-row__input'))
|
||||
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-gas-inputs__gas-edit-row__input'))
|
||||
await gasPriceInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
|
||||
await delay(50)
|
||||
|
||||
await gasPriceInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasPriceInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasPriceInput.sendKeys('10')
|
||||
@ -1117,18 +1075,9 @@ describe('MetaMask', function () {
|
||||
await delay(50)
|
||||
await gasLimitInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasLimitInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasLimitInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasLimitInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasLimitInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasLimitInput.sendKeys('60000')
|
||||
await delay(50)
|
||||
await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'e'))
|
||||
await delay(50)
|
||||
|
||||
await delay(1000)
|
||||
|
||||
const save = await findElement(driver, By.css('.page-container__footer-button'))
|
||||
await save.click()
|
||||
@ -1246,12 +1195,10 @@ describe('MetaMask', function () {
|
||||
await modalTabs[1].click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-tab__gas-edit-row__input'))
|
||||
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-gas-inputs__gas-edit-row__input'))
|
||||
await gasPriceInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
|
||||
await delay(50)
|
||||
|
||||
await gasPriceInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasPriceInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasPriceInput.sendKeys('10')
|
||||
@ -1260,18 +1207,9 @@ describe('MetaMask', function () {
|
||||
await delay(50)
|
||||
await gasLimitInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasLimitInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasLimitInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasLimitInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasLimitInput.sendKeys(Key.BACK_SPACE)
|
||||
await delay(50)
|
||||
await gasLimitInput.sendKeys('60001')
|
||||
await delay(50)
|
||||
await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'e'))
|
||||
await delay(50)
|
||||
|
||||
await delay(1000)
|
||||
|
||||
const save = await findElement(driver, By.css('.page-container__footer-button'))
|
||||
await save.click()
|
||||
|
@ -12,6 +12,7 @@ const {
|
||||
setupFetchMocking,
|
||||
prepareExtensionForTesting,
|
||||
} = require('./helpers')
|
||||
const enLocaleMessages = require('../../app/_locales/en/messages.json')
|
||||
|
||||
describe('Using MetaMask with an existing account', function () {
|
||||
let driver
|
||||
@ -51,7 +52,7 @@ describe('Using MetaMask with an existing account', function () {
|
||||
describe('First time flow starting from an existing seed phrase', () => {
|
||||
it('clicks the continue button on the welcome screen', async () => {
|
||||
await findElement(driver, By.css('.welcome-page__header'))
|
||||
const welcomeScreenBtn = await findElement(driver, By.css('.first-time-flow__button'))
|
||||
const welcomeScreenBtn = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
|
||||
welcomeScreenBtn.click()
|
||||
await delay(largeDelayMs)
|
||||
})
|
||||
@ -88,7 +89,7 @@ describe('Using MetaMask with an existing account', function () {
|
||||
|
||||
it('clicks through the success screen', async () => {
|
||||
await findElement(driver, By.xpath(`//div[contains(text(), 'Congratulations')]`))
|
||||
const doneButton = await findElement(driver, By.css('button.first-time-flow__button'))
|
||||
const doneButton = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`))
|
||||
await doneButton.click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
@ -113,7 +114,7 @@ describe('Using MetaMask with an existing account', function () {
|
||||
|
||||
const gasModal = await driver.findElement(By.css('span .modal'))
|
||||
|
||||
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-tab__gas-edit-row__input'))
|
||||
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-gas-inputs__gas-edit-row__input'))
|
||||
await gasPriceInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
|
||||
await delay(50)
|
||||
|
||||
@ -131,6 +132,8 @@ describe('Using MetaMask with an existing account', function () {
|
||||
|
||||
await gasLimitInput.sendKeys('25000')
|
||||
|
||||
await delay(1000)
|
||||
|
||||
const save = await findElement(driver, By.xpath(`//button[contains(text(), 'Save')]`))
|
||||
await save.click()
|
||||
await driver.wait(until.stalenessOf(gasModal))
|
||||
@ -170,7 +173,7 @@ describe('Using MetaMask with an existing account', function () {
|
||||
|
||||
const gasModal = await driver.findElement(By.css('span .modal'))
|
||||
|
||||
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-tab__gas-edit-row__input'))
|
||||
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-gas-inputs__gas-edit-row__input'))
|
||||
await gasPriceInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
|
||||
await delay(50)
|
||||
|
||||
@ -187,6 +190,8 @@ describe('Using MetaMask with an existing account', function () {
|
||||
|
||||
await gasLimitInput.sendKeys('100000')
|
||||
|
||||
await delay(1000)
|
||||
|
||||
const save = await findElement(driver, By.xpath(`//button[contains(text(), 'Save')]`))
|
||||
await save.click()
|
||||
await driver.wait(until.stalenessOf(gasModal))
|
||||
|
@ -12,6 +12,7 @@ const {
|
||||
setupFetchMocking,
|
||||
prepareExtensionForTesting,
|
||||
} = require('./helpers')
|
||||
const enLocaleMessages = require('../../app/_locales/en/messages.json')
|
||||
|
||||
describe('MetaMask', function () {
|
||||
let driver
|
||||
@ -53,7 +54,7 @@ describe('MetaMask', function () {
|
||||
describe('First time flow starting from an existing seed phrase', () => {
|
||||
it('clicks the continue button on the welcome screen', async () => {
|
||||
await findElement(driver, By.css('.welcome-page__header'))
|
||||
const welcomeScreenBtn = await findElement(driver, By.css('.first-time-flow__button'))
|
||||
const welcomeScreenBtn = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
|
||||
welcomeScreenBtn.click()
|
||||
await delay(largeDelayMs)
|
||||
})
|
||||
@ -90,7 +91,7 @@ describe('MetaMask', function () {
|
||||
|
||||
it('clicks through the success screen', async () => {
|
||||
await findElement(driver, By.xpath(`//div[contains(text(), 'Congratulations')]`))
|
||||
const doneButton = await findElement(driver, By.css('button.first-time-flow__button'))
|
||||
const doneButton = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`))
|
||||
await doneButton.click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
@ -176,7 +177,7 @@ describe('MetaMask', function () {
|
||||
describe('First time flow starting from an existing seed phrase', () => {
|
||||
it('clicks the continue button on the welcome screen', async () => {
|
||||
await findElement(driver2, By.css('.welcome-page__header'))
|
||||
const welcomeScreenBtn = await findElement(driver2, By.css('.first-time-flow__button'))
|
||||
const welcomeScreenBtn = await findElement(driver2, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
|
||||
welcomeScreenBtn.click()
|
||||
await delay(largeDelayMs)
|
||||
})
|
||||
@ -213,7 +214,7 @@ describe('MetaMask', function () {
|
||||
|
||||
it('clicks through the success screen', async () => {
|
||||
await findElement(driver2, By.xpath(`//div[contains(text(), 'Congratulations')]`))
|
||||
const doneButton = await findElement(driver2, By.css('button.first-time-flow__button'))
|
||||
const doneButton = await findElement(driver2, By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`))
|
||||
await doneButton.click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
|
@ -15,6 +15,7 @@ const {
|
||||
setupFetchMocking,
|
||||
prepareExtensionForTesting,
|
||||
} = require('./helpers')
|
||||
const enLocaleMessages = require('../../app/_locales/en/messages.json')
|
||||
|
||||
describe('Using MetaMask with an existing account', function () {
|
||||
let driver
|
||||
@ -65,7 +66,7 @@ describe('Using MetaMask with an existing account', function () {
|
||||
describe('First time flow starting from an existing seed phrase', () => {
|
||||
it('clicks the continue button on the welcome screen', async () => {
|
||||
await findElement(driver, By.css('.welcome-page__header'))
|
||||
const welcomeScreenBtn = await findElement(driver, By.css('.first-time-flow__button'))
|
||||
const welcomeScreenBtn = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
|
||||
welcomeScreenBtn.click()
|
||||
await delay(largeDelayMs)
|
||||
})
|
||||
@ -102,7 +103,7 @@ describe('Using MetaMask with an existing account', function () {
|
||||
|
||||
it('clicks through the success screen', async () => {
|
||||
await findElement(driver, By.xpath(`//div[contains(text(), 'Congratulations')]`))
|
||||
const doneButton = await findElement(driver, By.css('button.first-time-flow__button'))
|
||||
const doneButton = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`))
|
||||
await doneButton.click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
|
55
test/unit-global/frozenPromise.js
Normal file
@ -0,0 +1,55 @@
|
||||
|
||||
/* eslint-disable no-native-reassign */
|
||||
|
||||
// this is what we're testing
|
||||
require('../../app/scripts/lib/freezeGlobals')
|
||||
|
||||
const assert = require('assert')
|
||||
|
||||
describe('Promise global is immutable', () => {
|
||||
|
||||
it('throws when reassinging promise (syntax 1)', () => {
|
||||
try {
|
||||
Promise = {}
|
||||
assert.fail('did not throw error')
|
||||
} catch (err) {
|
||||
assert.ok(err, 'did throw error')
|
||||
}
|
||||
})
|
||||
|
||||
it('throws when reassinging promise (syntax 2)', () => {
|
||||
try {
|
||||
global.Promise = {}
|
||||
assert.fail('did not throw error')
|
||||
} catch (err) {
|
||||
assert.ok(err, 'did throw error')
|
||||
}
|
||||
})
|
||||
|
||||
it('throws when mutating existing Promise property', () => {
|
||||
try {
|
||||
Promise.all = () => {}
|
||||
assert.fail('did not throw error')
|
||||
} catch (err) {
|
||||
assert.ok(err, 'did throw error')
|
||||
}
|
||||
})
|
||||
|
||||
it('throws when adding new Promise property', () => {
|
||||
try {
|
||||
Promise.foo = 'bar'
|
||||
assert.fail('did not throw error')
|
||||
} catch (err) {
|
||||
assert.ok(err, 'did throw error')
|
||||
}
|
||||
})
|
||||
|
||||
it('throws when deleting Promise from global', () => {
|
||||
try {
|
||||
delete global.Promise
|
||||
assert.fail('did not throw error')
|
||||
} catch (err) {
|
||||
assert.ok(err, 'did throw error')
|
||||
}
|
||||
})
|
||||
})
|
55
test/unit/app/controllers/balance-controller.spec.js
Normal file
@ -0,0 +1,55 @@
|
||||
const assert = require('assert')
|
||||
const ObservableStore = require('obs-store')
|
||||
const PollingBlockTracker = require('eth-block-tracker')
|
||||
|
||||
const BalanceController = require('../../../../app/scripts/controllers/balance')
|
||||
const AccountTracker = require('../../../../app/scripts/lib/account-tracker')
|
||||
const TransactionController = require('../../../../app/scripts/controllers/transactions')
|
||||
const { createTestProviderTools } = require('../../../stub/provider')
|
||||
const provider = createTestProviderTools({ scaffold: {}}).provider
|
||||
|
||||
const TEST_ADDRESS = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'
|
||||
|
||||
const accounts = {
|
||||
'0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': {
|
||||
balance: '0x5e942b06dc24c4d50',
|
||||
address: TEST_ADDRESS,
|
||||
},
|
||||
}
|
||||
|
||||
describe('Balance Controller', () => {
|
||||
|
||||
let balanceController
|
||||
|
||||
it('errors when address, accountTracker, txController, or blockTracker', function () {
|
||||
try {
|
||||
balanceController = new BalanceController()
|
||||
} catch (error) {
|
||||
assert.equal(error.message, 'Cannot construct a balance checker without address, accountTracker, txController, and blockTracker.')
|
||||
}
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
balanceController = new BalanceController({
|
||||
address: TEST_ADDRESS,
|
||||
accountTracker: new AccountTracker({
|
||||
provider,
|
||||
blockTracker: new PollingBlockTracker({ provider }),
|
||||
}),
|
||||
txController: new TransactionController({
|
||||
provider,
|
||||
networkStore: new ObservableStore(),
|
||||
blockTracker: new PollingBlockTracker({ provider }),
|
||||
}),
|
||||
blockTracker: new PollingBlockTracker({ provider }),
|
||||
})
|
||||
|
||||
balanceController.accountTracker.store.updateState({ accounts })
|
||||
})
|
||||
|
||||
it('updates balance controller ethBalance from account tracker', async function () {
|
||||
await balanceController.updateBalance()
|
||||
const balanceControllerState = balanceController.store.getState()
|
||||
assert.equal(balanceControllerState.ethBalance, '0x5e942b06dc24c4d50')
|
||||
})
|
||||
})
|
@ -2,6 +2,7 @@ const assert = require('assert')
|
||||
const sinon = require('sinon')
|
||||
const clone = require('clone')
|
||||
const nock = require('nock')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const createThoughStream = require('through2').obj
|
||||
const blacklistJSON = require('eth-phishing-detect/src/config')
|
||||
const firstTimeState = require('../../../unit/localhostState')
|
||||
@ -103,6 +104,51 @@ describe('MetaMaskController', function () {
|
||||
sandbox.restore()
|
||||
})
|
||||
|
||||
describe('#getAccounts', function () {
|
||||
|
||||
beforeEach(async function () {
|
||||
const password = 'a-fake-password'
|
||||
|
||||
await metamaskController.createNewVaultAndRestore(password, TEST_SEED)
|
||||
})
|
||||
|
||||
it('returns first address when dapp calls web3.eth.getAccounts', function () {
|
||||
metamaskController.networkController._baseProviderParams.getAccounts((err, res) => {
|
||||
assert.ifError(err)
|
||||
assert.equal(res.length, 1)
|
||||
assert.equal(res[0], '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#importAccountWithStrategy', function () {
|
||||
|
||||
const importPrivkey = '4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553'
|
||||
|
||||
beforeEach(async function () {
|
||||
const password = 'a-fake-password'
|
||||
|
||||
await metamaskController.createNewVaultAndRestore(password, TEST_SEED)
|
||||
await metamaskController.importAccountWithStrategy('Private Key', [ importPrivkey ])
|
||||
})
|
||||
|
||||
it('adds private key to keyrings in KeyringController', async function () {
|
||||
const simpleKeyrings = metamaskController.keyringController.getKeyringsByType('Simple Key Pair')
|
||||
const privKeyBuffer = simpleKeyrings[0].wallets[0]._privKey
|
||||
const pubKeyBuffer = simpleKeyrings[0].wallets[0]._pubKey
|
||||
const addressBuffer = ethUtil.pubToAddress(pubKeyBuffer)
|
||||
const privKey = ethUtil.bufferToHex(privKeyBuffer)
|
||||
const pubKey = ethUtil.bufferToHex(addressBuffer)
|
||||
assert.equal(privKey, ethUtil.addHexPrefix(importPrivkey))
|
||||
assert.equal(pubKey, '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc')
|
||||
})
|
||||
|
||||
it('adds private key to keyrings in KeyringController', async function () {
|
||||
const keyringAccounts = await metamaskController.keyringController.getAccounts()
|
||||
assert.equal(keyringAccounts[keyringAccounts.length - 1], '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc')
|
||||
})
|
||||
})
|
||||
|
||||
describe('submitPassword', function () {
|
||||
const password = 'password'
|
||||
|
||||
@ -751,7 +797,7 @@ describe('MetaMaskController', function () {
|
||||
describe('#setupUntrustedCommunication', function () {
|
||||
let streamTest
|
||||
|
||||
const phishingUrl = 'myethereumwalletntw.com'
|
||||
const phishingUrl = new URL('http://myethereumwalletntw.com')
|
||||
|
||||
afterEach(function () {
|
||||
streamTest.end()
|
||||
@ -764,7 +810,7 @@ describe('MetaMaskController', function () {
|
||||
|
||||
streamTest = createThoughStream((chunk, _, cb) => {
|
||||
if (chunk.name !== 'phishing') return cb()
|
||||
assert.equal(chunk.data.hostname, phishingUrl)
|
||||
assert.equal(chunk.data.hostname, phishingUrl.hostname)
|
||||
resolve()
|
||||
cb()
|
||||
})
|
||||
|
@ -25,14 +25,17 @@ describe('ProviderApprovalController', () => {
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
const metadata = {
|
||||
hostname: 'https://example.com',
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}
|
||||
|
||||
controller._handleProviderRequest(metadata)
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
approvedOrigins: {},
|
||||
providerRequests: [{
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}],
|
||||
providerRequests: [metadata],
|
||||
})
|
||||
})
|
||||
|
||||
@ -41,14 +44,16 @@ describe('ProviderApprovalController', () => {
|
||||
keyringController: mockLockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
const metadata = {
|
||||
hostname: 'https://example.com',
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}
|
||||
controller._handleProviderRequest(metadata)
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
approvedOrigins: {},
|
||||
providerRequests: [{
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}],
|
||||
providerRequests: [metadata],
|
||||
})
|
||||
})
|
||||
|
||||
@ -57,19 +62,23 @@ describe('ProviderApprovalController', () => {
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example1.com', 'Example 1', 'https://example1.com/logo.svg')
|
||||
controller._handleProviderRequest('example2.com', 'Example 2', 'https://example2.com/logo.svg')
|
||||
const metadata = [{
|
||||
hostname: 'https://example1.com',
|
||||
origin: 'example1.com',
|
||||
siteTitle: 'Example 1',
|
||||
siteImage: 'https://example1.com/logo.svg',
|
||||
}, {
|
||||
hostname: 'https://example2.com',
|
||||
origin: 'example2.com',
|
||||
siteTitle: 'Example 2',
|
||||
siteImage: 'https://example2.com/logo.svg',
|
||||
}]
|
||||
|
||||
controller._handleProviderRequest(metadata[0])
|
||||
controller._handleProviderRequest(metadata[1])
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
approvedOrigins: {},
|
||||
providerRequests: [{
|
||||
origin: 'example1.com',
|
||||
siteTitle: 'Example 1',
|
||||
siteImage: 'https://example1.com/logo.svg',
|
||||
}, {
|
||||
origin: 'example2.com',
|
||||
siteTitle: 'Example 2',
|
||||
siteImage: 'https://example2.com/logo.svg',
|
||||
}],
|
||||
providerRequests: metadata,
|
||||
})
|
||||
})
|
||||
|
||||
@ -78,19 +87,23 @@ describe('ProviderApprovalController', () => {
|
||||
keyringController: mockLockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example1.com', 'Example 1', 'https://example1.com/logo.svg')
|
||||
controller._handleProviderRequest('example2.com', 'Example 2', 'https://example2.com/logo.svg')
|
||||
const metadata = [{
|
||||
hostname: 'https://example1.com',
|
||||
origin: 'example1.com',
|
||||
siteTitle: 'Example 1',
|
||||
siteImage: 'https://example1.com/logo.svg',
|
||||
}, {
|
||||
hostname: 'https://example2.com',
|
||||
origin: 'example2.com',
|
||||
siteTitle: 'Example 2',
|
||||
siteImage: 'https://example2.com/logo.svg',
|
||||
}]
|
||||
|
||||
controller._handleProviderRequest(metadata[0])
|
||||
controller._handleProviderRequest(metadata[1])
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
approvedOrigins: {},
|
||||
providerRequests: [{
|
||||
origin: 'example1.com',
|
||||
siteTitle: 'Example 1',
|
||||
siteImage: 'https://example1.com/logo.svg',
|
||||
}, {
|
||||
origin: 'example2.com',
|
||||
siteTitle: 'Example 2',
|
||||
siteImage: 'https://example2.com/logo.svg',
|
||||
}],
|
||||
providerRequests: metadata,
|
||||
})
|
||||
})
|
||||
|
||||
@ -101,7 +114,13 @@ describe('ProviderApprovalController', () => {
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
const metadata = {
|
||||
hostname: 'https://example.com',
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}
|
||||
controller._handleProviderRequest(metadata)
|
||||
assert.ok(openPopup.calledOnce)
|
||||
})
|
||||
|
||||
@ -112,7 +131,13 @@ describe('ProviderApprovalController', () => {
|
||||
keyringController: mockLockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
const metadata = {
|
||||
hostname: 'https://example.com',
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}
|
||||
controller._handleProviderRequest(metadata)
|
||||
assert.ok(openPopup.calledOnce)
|
||||
})
|
||||
|
||||
@ -131,7 +156,13 @@ describe('ProviderApprovalController', () => {
|
||||
},
|
||||
},
|
||||
})
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
const metadata = {
|
||||
hostname: 'https://example.com',
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}
|
||||
controller._handleProviderRequest(metadata)
|
||||
assert.ok(openPopup.notCalled)
|
||||
})
|
||||
})
|
||||
@ -142,12 +173,19 @@ describe('ProviderApprovalController', () => {
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
const metadata = {
|
||||
hostname: 'https://example.com',
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}
|
||||
controller._handleProviderRequest(metadata)
|
||||
controller.approveProviderRequestByOrigin('example.com')
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
providerRequests: [],
|
||||
approvedOrigins: {
|
||||
'example.com': {
|
||||
hostname: 'https://example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
},
|
||||
@ -160,13 +198,20 @@ describe('ProviderApprovalController', () => {
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
const metadata = {
|
||||
hostname: 'https://example.com',
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}
|
||||
controller._handleProviderRequest(metadata)
|
||||
controller._handleProviderRequest(metadata)
|
||||
controller.approveProviderRequestByOrigin('example.com')
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
providerRequests: [],
|
||||
approvedOrigins: {
|
||||
'example.com': {
|
||||
hostname: 'https://example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
},
|
||||
@ -184,6 +229,7 @@ describe('ProviderApprovalController', () => {
|
||||
providerRequests: [],
|
||||
approvedOrigins: {
|
||||
'example.com': {
|
||||
hostname: null,
|
||||
siteTitle: null,
|
||||
siteImage: null,
|
||||
},
|
||||
@ -198,7 +244,13 @@ describe('ProviderApprovalController', () => {
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
const metadata = {
|
||||
hostname: 'https://example.com',
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}
|
||||
controller._handleProviderRequest(metadata)
|
||||
controller.approveProviderRequestByOrigin('example.com')
|
||||
controller.rejectProviderRequestByOrigin('example.com')
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
@ -226,7 +278,13 @@ describe('ProviderApprovalController', () => {
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
const metadata = {
|
||||
hostname: 'https://example.com',
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}
|
||||
controller._handleProviderRequest(metadata)
|
||||
controller.approveProviderRequestByOrigin('example.com')
|
||||
controller.clearApprovedOrigins()
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
@ -242,7 +300,13 @@ describe('ProviderApprovalController', () => {
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
const metadata = {
|
||||
hostname: 'https://example.com',
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}
|
||||
controller._handleProviderRequest(metadata)
|
||||
controller.approveProviderRequestByOrigin('example.com')
|
||||
assert.ok(controller.shouldExposeAccounts('example.com'))
|
||||
})
|
||||
@ -252,7 +316,13 @@ describe('ProviderApprovalController', () => {
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
const metadata = {
|
||||
hostname: 'https://example.com',
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}
|
||||
controller._handleProviderRequest(metadata)
|
||||
controller.approveProviderRequestByOrigin('example.com')
|
||||
assert.ok(!controller.shouldExposeAccounts('bad.website'))
|
||||
})
|
||||
|
116
test/unit/app/typed-message-manager.spec.js
Normal file
@ -0,0 +1,116 @@
|
||||
import assert from 'assert'
|
||||
import sinon from 'sinon'
|
||||
import NetworkController from '../../../app/scripts/controllers/network/index'
|
||||
import TypedMessageManager from '../../../app/scripts/lib/typed-message-manager'
|
||||
|
||||
describe('Typed Message Manager', () => {
|
||||
let typedMessageManager, msgParamsV1, msgParamsV3, typedMsgs, messages, msgId, numberMsgId
|
||||
|
||||
const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813'
|
||||
const networkController = new NetworkController()
|
||||
sinon.stub(networkController, 'getNetworkState').returns('1')
|
||||
|
||||
beforeEach(() => {
|
||||
typedMessageManager = new TypedMessageManager({
|
||||
networkController,
|
||||
})
|
||||
|
||||
msgParamsV1 = {
|
||||
from: address,
|
||||
data: [
|
||||
{ type: 'string', name: 'unit test', value: 'hello there' },
|
||||
{ type: 'uint32', name: 'A number, but not really a number', value: '$$$' },
|
||||
],
|
||||
}
|
||||
|
||||
msgParamsV3 = {
|
||||
from: address,
|
||||
data: JSON.stringify({
|
||||
'types': {
|
||||
'EIP712Domain': [
|
||||
{'name': 'name', 'type': 'string' },
|
||||
{'name': 'version', 'type': 'string' },
|
||||
{'name': 'chainId', ' type': 'uint256' },
|
||||
{'name': 'verifyingContract', ' type': 'address' },
|
||||
],
|
||||
'Person': [
|
||||
{'name': 'name', 'type': 'string' },
|
||||
{'name': 'wallet', ' type': 'address' },
|
||||
],
|
||||
'Mail': [
|
||||
{'name': 'from', 'type': 'Person' },
|
||||
{'name': 'to', 'type': 'Person' },
|
||||
{'name': 'contents', 'type': 'string' },
|
||||
],
|
||||
},
|
||||
'primaryType': 'Mail',
|
||||
'domain': {
|
||||
'name': 'Ether Mainl',
|
||||
'version': '1',
|
||||
'chainId': 1,
|
||||
'verifyingContract': '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
|
||||
},
|
||||
'message': {
|
||||
'from': {
|
||||
'name': 'Cow',
|
||||
'wallet': '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
|
||||
},
|
||||
'to': {
|
||||
'name': 'Bob',
|
||||
'wallet': '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
|
||||
},
|
||||
'contents': 'Hello, Bob!',
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
typedMessageManager.addUnapprovedMessage(msgParamsV3, 'V3')
|
||||
typedMsgs = typedMessageManager.getUnapprovedMsgs()
|
||||
messages = typedMessageManager.messages
|
||||
msgId = Object.keys(typedMsgs)[0]
|
||||
messages[0].msgParams.metamaskId = parseInt(msgId)
|
||||
numberMsgId = parseInt(msgId)
|
||||
})
|
||||
|
||||
it('supports version 1 of signedTypedData', () => {
|
||||
typedMessageManager.addUnapprovedMessage(msgParamsV1, 'V1')
|
||||
assert.equal(messages[messages.length - 1].msgParams.data, msgParamsV1.data)
|
||||
})
|
||||
|
||||
it('has params address', function () {
|
||||
assert.equal(typedMsgs[msgId].msgParams.from, address)
|
||||
})
|
||||
|
||||
it('adds to unapproved messages and sets status to unapproved', function () {
|
||||
assert.equal(typedMsgs[msgId].status, 'unapproved')
|
||||
})
|
||||
|
||||
it('validates params', function () {
|
||||
assert.doesNotThrow(() => {
|
||||
typedMessageManager.validateParams(messages[0])
|
||||
}, 'Does not throw with valid parameters')
|
||||
})
|
||||
|
||||
it('gets unapproved by id', function () {
|
||||
const getMsg = typedMessageManager.getMsg(numberMsgId)
|
||||
assert.equal(getMsg.id, numberMsgId)
|
||||
})
|
||||
|
||||
it('approves messages', async function () {
|
||||
const messageMetaMaskId = messages[0].msgParams
|
||||
typedMessageManager.approveMessage(messageMetaMaskId)
|
||||
assert.equal(messages[0].status, 'approved')
|
||||
})
|
||||
|
||||
it('sets msg status to signed and adds a raw sig to message details', function () {
|
||||
typedMessageManager.setMsgStatusSigned(numberMsgId, 'raw sig')
|
||||
assert.equal(messages[0].status, 'signed')
|
||||
assert.equal(messages[0].rawSig, 'raw sig')
|
||||
})
|
||||
|
||||
it('rejects message', function () {
|
||||
typedMessageManager.rejectMsg(numberMsgId)
|
||||
assert.equal(messages[0].status, 'rejected')
|
||||
})
|
||||
|
||||
})
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable */
|
||||
// Used to inspect long objects
|
||||
// util.inspect({JSON}, false, null))
|
||||
// const util = require('util')
|
||||
@ -29,6 +30,8 @@ describe('Actions', () => {
|
||||
|
||||
const noop = () => {}
|
||||
|
||||
const currentNetworkId = 42
|
||||
|
||||
let background, metamaskController
|
||||
|
||||
const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium'
|
||||
@ -37,7 +40,6 @@ describe('Actions', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
|
||||
metamaskController = new MetaMaskController({
|
||||
provider,
|
||||
keyringController: new KeyringController({}),
|
||||
@ -88,11 +90,9 @@ describe('Actions', () => {
|
||||
submitPasswordSpy = sinon.spy(background, 'submitPassword')
|
||||
verifySeedPhraseSpy = sinon.spy(background, 'verifySeedPhrase')
|
||||
|
||||
return store.dispatch(actions.tryUnlockMetamask())
|
||||
.then(() => {
|
||||
assert(submitPasswordSpy.calledOnce)
|
||||
assert(verifySeedPhraseSpy.calledOnce)
|
||||
})
|
||||
await store.dispatch(actions.tryUnlockMetamask())
|
||||
assert(submitPasswordSpy.calledOnce)
|
||||
assert(verifySeedPhraseSpy.calledOnce)
|
||||
})
|
||||
|
||||
it('errors on submitPassword will fail', async () => {
|
||||
@ -193,15 +193,17 @@ describe('Actions', () => {
|
||||
describe('#requestRevealSeedWords', () => {
|
||||
let submitPasswordSpy
|
||||
|
||||
it('calls submitPassword in background', () => {
|
||||
afterEach(() => {
|
||||
submitPasswordSpy.restore()
|
||||
})
|
||||
|
||||
it('calls submitPassword in background', async () => {
|
||||
const store = mockStore()
|
||||
|
||||
submitPasswordSpy = sinon.spy(background, 'verifySeedPhrase')
|
||||
|
||||
return store.dispatch(actions.requestRevealSeedWords())
|
||||
.then(() => {
|
||||
assert(submitPasswordSpy.calledOnce)
|
||||
})
|
||||
await store.dispatch(actions.requestRevealSeedWords())
|
||||
assert(submitPasswordSpy.calledOnce)
|
||||
})
|
||||
|
||||
it('displays warning error message then callback in background errors', async () => {
|
||||
@ -235,8 +237,9 @@ describe('Actions', () => {
|
||||
removeAccountSpy.restore()
|
||||
})
|
||||
|
||||
it('calls removeAccount in background and expect actions to show account', () => {
|
||||
it('calls removeAccount in background and expect actions to show account', async () => {
|
||||
const store = mockStore(devState)
|
||||
|
||||
const expectedActions = [
|
||||
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
|
||||
{ type: 'HIDE_LOADING_INDICATION' },
|
||||
@ -245,20 +248,20 @@ describe('Actions', () => {
|
||||
|
||||
removeAccountSpy = sinon.spy(background, 'removeAccount')
|
||||
|
||||
return store.dispatch(actions.removeAccount('0xe18035bf8712672935fdb4e5e431b1a0183d2dfc'))
|
||||
.then(() => {
|
||||
assert(removeAccountSpy.calledOnce)
|
||||
assert.deepEqual(store.getActions(), expectedActions)
|
||||
})
|
||||
await store.dispatch(actions.removeAccount('0xe18035bf8712672935fdb4e5e431b1a0183d2dfc'))
|
||||
assert(removeAccountSpy.calledOnce)
|
||||
assert.deepEqual(store.getActions(), expectedActions)
|
||||
})
|
||||
|
||||
it('displays warning error message when removeAccount callback errors', async () => {
|
||||
const store = mockStore()
|
||||
|
||||
const expectedActions = [
|
||||
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
|
||||
{ type: 'HIDE_LOADING_INDICATION' },
|
||||
{ type: 'DISPLAY_WARNING', value: 'error' },
|
||||
]
|
||||
|
||||
removeAccountSpy = sinon.stub(background, 'removeAccount')
|
||||
removeAccountSpy.callsFake((_, callback) => {
|
||||
callback(new Error('error'))
|
||||
@ -331,11 +334,9 @@ describe('Actions', () => {
|
||||
|
||||
resetAccountSpy = sinon.spy(background, 'resetAccount')
|
||||
|
||||
return store.dispatch(actions.resetAccount())
|
||||
.then(() => {
|
||||
assert(resetAccountSpy.calledOnce)
|
||||
assert.deepEqual(store.getActions(), expectedActions)
|
||||
})
|
||||
await store.dispatch(actions.resetAccount())
|
||||
assert(resetAccountSpy.calledOnce)
|
||||
assert.deepEqual(store.getActions(), expectedActions)
|
||||
})
|
||||
|
||||
it('throws if resetAccount throws', async () => {
|
||||
@ -376,10 +377,8 @@ describe('Actions', () => {
|
||||
|
||||
const importPrivkey = 'c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3'
|
||||
|
||||
return store.dispatch(actions.importNewAccount('Private Key', [ importPrivkey ]))
|
||||
.then(() => {
|
||||
assert(importAccountWithStrategySpy.calledOnce)
|
||||
})
|
||||
store.dispatch(actions.importNewAccount('Private Key', [ importPrivkey ]))
|
||||
assert(importAccountWithStrategySpy.calledOnce)
|
||||
})
|
||||
|
||||
it('displays warning error message when importAccount in background callback errors', async () => {
|
||||
@ -407,21 +406,181 @@ describe('Actions', () => {
|
||||
|
||||
describe('#addNewAccount', () => {
|
||||
|
||||
let addNewAccountSpy
|
||||
|
||||
afterEach(() => {
|
||||
addNewAccountSpy.restore()
|
||||
})
|
||||
|
||||
it('Adds a new account', () => {
|
||||
const store = mockStore({ metamask: devState })
|
||||
|
||||
addNewAccountSpy = sinon.spy(background, 'addNewAccount')
|
||||
const addNewAccountSpy = sinon.spy(background, 'addNewAccount')
|
||||
|
||||
return store.dispatch(actions.addNewAccount())
|
||||
.then(() => {
|
||||
assert(addNewAccountSpy.calledOnce)
|
||||
})
|
||||
store.dispatch(actions.addNewAccount())
|
||||
assert(addNewAccountSpy.calledOnce)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('#checkHardwareStatus', () => {
|
||||
|
||||
let checkHardwareStatusSpy
|
||||
|
||||
beforeEach(() => {
|
||||
checkHardwareStatusSpy = sinon.stub(background, 'checkHardwareStatus')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
checkHardwareStatusSpy.restore()
|
||||
})
|
||||
|
||||
it('calls checkHardwareStatus in background', async () => {
|
||||
|
||||
const store = mockStore()
|
||||
|
||||
store.dispatch(await actions.checkHardwareStatus('ledger', `m/44'/60'/0'/0`))
|
||||
assert.equal(checkHardwareStatusSpy.calledOnce, true)
|
||||
})
|
||||
|
||||
it('shows loading indicator and displays error', async () => {
|
||||
const store = mockStore()
|
||||
|
||||
const expectedActions = [
|
||||
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
|
||||
{ type: 'DISPLAY_WARNING', value: 'error' },
|
||||
]
|
||||
|
||||
checkHardwareStatusSpy.callsFake((deviceName, hdPath, callback) => {
|
||||
callback(new Error('error'))
|
||||
})
|
||||
|
||||
try {
|
||||
await store.dispatch(actions.checkHardwareStatus())
|
||||
assert.fail('Should have thrown error')
|
||||
} catch (_) {
|
||||
assert.deepEqual(store.getActions(), expectedActions)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#forgetDevice', () => {
|
||||
|
||||
let forgetDeviceSpy
|
||||
|
||||
beforeEach(() => {
|
||||
forgetDeviceSpy = sinon.stub(background, 'forgetDevice')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
forgetDeviceSpy.restore()
|
||||
})
|
||||
|
||||
it('calls forgetDevice in background', () => {
|
||||
|
||||
const store = mockStore()
|
||||
|
||||
store.dispatch(actions.forgetDevice('ledger'))
|
||||
assert.equal(forgetDeviceSpy.calledOnce, true)
|
||||
|
||||
})
|
||||
|
||||
it('shows loading indicator and displays error', async () => {
|
||||
const store = mockStore()
|
||||
|
||||
const expectedActions = [
|
||||
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
|
||||
{ type: 'DISPLAY_WARNING', value: 'error' },
|
||||
]
|
||||
|
||||
forgetDeviceSpy.callsFake((deviceName, callback) => {
|
||||
callback(new Error('error'))
|
||||
})
|
||||
|
||||
try {
|
||||
await store.dispatch(actions.forgetDevice())
|
||||
assert.fail('Should have thrown error')
|
||||
} catch (_) {
|
||||
assert.deepEqual(store.getActions(), expectedActions)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#connectHardware', () => {
|
||||
|
||||
let connectHardwareSpy
|
||||
|
||||
beforeEach(() => {
|
||||
connectHardwareSpy = sinon.stub(background, 'connectHardware')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
connectHardwareSpy.restore()
|
||||
})
|
||||
|
||||
it('calls connectHardware in background', () => {
|
||||
|
||||
const store = mockStore()
|
||||
|
||||
store.dispatch(actions.connectHardware('ledger', 0, `m/44'/60'/0'/0`))
|
||||
assert.equal(connectHardwareSpy.calledOnce, true)
|
||||
|
||||
})
|
||||
|
||||
it('shows loading indicator and displays error', async () => {
|
||||
const store = mockStore()
|
||||
|
||||
const expectedActions = [
|
||||
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
|
||||
{ type: 'DISPLAY_WARNING', value: 'error' },
|
||||
]
|
||||
|
||||
connectHardwareSpy.callsFake((deviceName, page, hdPath, callback) => {
|
||||
callback(new Error('error'))
|
||||
})
|
||||
|
||||
try {
|
||||
await store.dispatch(actions.connectHardware())
|
||||
assert.fail('Should have thrown error')
|
||||
} catch (_) {
|
||||
assert.deepEqual(store.getActions(), expectedActions)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#unlockHardwareWalletAccount', () => {
|
||||
|
||||
let unlockHardwareWalletAccountSpy
|
||||
|
||||
beforeEach(() => {
|
||||
unlockHardwareWalletAccountSpy = sinon.stub(background, 'unlockHardwareWalletAccount')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
unlockHardwareWalletAccountSpy.restore()
|
||||
})
|
||||
|
||||
it('calls unlockHardwareWalletAccount in background', () => {
|
||||
|
||||
const store = mockStore()
|
||||
|
||||
store.dispatch(actions.unlockHardwareWalletAccount('ledger', 0, `m/44'/60'/0'/0`))
|
||||
assert.equal(unlockHardwareWalletAccountSpy.calledOnce, true)
|
||||
|
||||
})
|
||||
|
||||
it('shows loading indicator and displays error', async() => {
|
||||
const store = mockStore()
|
||||
|
||||
const expectedActions = [
|
||||
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
|
||||
{ type: 'DISPLAY_WARNING', value: 'error' },
|
||||
]
|
||||
|
||||
unlockHardwareWalletAccountSpy.callsFake((deviceName, page, hdPath, callback) => {
|
||||
callback(new Error('error'))
|
||||
})
|
||||
|
||||
try {
|
||||
await store.dispatch(actions.unlockHardwareWalletAccount())
|
||||
assert.fail('Should have thrown error')
|
||||
} catch (error) {
|
||||
assert.deepEqual(store.getActions(), expectedActions)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@ -485,11 +644,8 @@ describe('Actions', () => {
|
||||
const store = mockStore()
|
||||
|
||||
signMessageSpy = sinon.spy(background, 'signMessage')
|
||||
|
||||
return store.dispatch(actions.signMsg(msgParams))
|
||||
.then(() => {
|
||||
assert(signMessageSpy.calledOnce)
|
||||
})
|
||||
store.dispatch(actions.signMsg(msgParams))
|
||||
assert(signMessageSpy.calledOnce)
|
||||
|
||||
})
|
||||
|
||||
@ -543,10 +699,8 @@ describe('Actions', () => {
|
||||
|
||||
signPersonalMessageSpy = sinon.spy(background, 'signPersonalMessage')
|
||||
|
||||
return store.dispatch(actions.signPersonalMsg(msgParams))
|
||||
.then(() => {
|
||||
assert(signPersonalMessageSpy.calledOnce)
|
||||
})
|
||||
store.dispatch(actions.signPersonalMsg(msgParams))
|
||||
assert(signPersonalMessageSpy.calledOnce)
|
||||
|
||||
})
|
||||
|
||||
@ -574,12 +728,100 @@ describe('Actions', () => {
|
||||
|
||||
})
|
||||
|
||||
describe('#signTypedMsg', () => {
|
||||
let signTypedMsgSpy, messages, typedMessages, msgId
|
||||
|
||||
const msgParamsV3 = {
|
||||
from: '0x0DCD5D886577d5081B0c52e242Ef29E70Be3E7bc',
|
||||
data: JSON.stringify({
|
||||
'types': {
|
||||
'EIP712Domain': [
|
||||
{'name': 'name', 'type': 'string'},
|
||||
{'name': 'version', 'type': 'string'},
|
||||
{'name': 'chainId', 'type': 'uint256'},
|
||||
{'name': 'verifyingContract', 'type': 'address'},
|
||||
],
|
||||
'Person': [
|
||||
{'name': 'name', 'type': 'string'},
|
||||
{'name': 'wallet', 'type': 'address'},
|
||||
],
|
||||
'Mail': [
|
||||
{'name': 'from', 'type': 'Person'},
|
||||
{'name': 'to', 'type': 'Person'},
|
||||
{'name': 'contents', 'type': 'string'},
|
||||
],
|
||||
},
|
||||
'primaryType': 'Mail',
|
||||
'domain': {
|
||||
'name': 'Ether Mainl',
|
||||
'version': '1',
|
||||
'chainId': 1,
|
||||
'verifyingContract': '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
|
||||
},
|
||||
'message': {
|
||||
'from': {
|
||||
'name': 'Cow',
|
||||
'wallet': '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
|
||||
},
|
||||
'to': {
|
||||
'name': 'Bob',
|
||||
'wallet': '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
|
||||
},
|
||||
'contents': 'Hello, Bob!',
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
metamaskController.newUnsignedTypedMessage(msgParamsV3, 'V3')
|
||||
messages = metamaskController.typedMessageManager.getUnapprovedMsgs()
|
||||
typedMessages = metamaskController.typedMessageManager.messages
|
||||
msgId = Object.keys(messages)[0]
|
||||
typedMessages[0].msgParams.metamaskId = parseInt(msgId)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
signTypedMsgSpy.restore()
|
||||
})
|
||||
|
||||
it('calls signTypedMsg in background with no error', () => {
|
||||
const store = mockStore()
|
||||
signTypedMsgSpy = sinon.stub(background, 'signTypedMessage')
|
||||
|
||||
store.dispatch(actions.signTypedMsg(msgParamsV3))
|
||||
assert(signTypedMsgSpy.calledOnce)
|
||||
})
|
||||
|
||||
it('returns expected actions with error', async () => {
|
||||
const store = mockStore()
|
||||
const expectedActions = [
|
||||
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
|
||||
{ type: 'UPDATE_METAMASK_STATE', value: undefined },
|
||||
{ type: 'HIDE_LOADING_INDICATION' },
|
||||
{ type: 'DISPLAY_WARNING', value: 'error' },
|
||||
]
|
||||
|
||||
signTypedMsgSpy = sinon.stub(background, 'signTypedMessage')
|
||||
|
||||
signTypedMsgSpy.callsFake((_, callback) => {
|
||||
callback(new Error('error'))
|
||||
})
|
||||
|
||||
try {
|
||||
await store.dispatch(actions.signTypedMsg())
|
||||
assert.fail('Should have thrown error')
|
||||
} catch (_) {
|
||||
assert.deepEqual(store.getActions(), expectedActions)
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('#signTx', () => {
|
||||
|
||||
let sendTransactionSpy
|
||||
|
||||
beforeEach(() => {
|
||||
global.ethQuery = new EthQuery(provider)
|
||||
sendTransactionSpy = sinon.stub(global.ethQuery, 'sendTransaction')
|
||||
})
|
||||
|
||||
@ -589,6 +831,7 @@ describe('Actions', () => {
|
||||
|
||||
it('calls sendTransaction in global ethQuery', () => {
|
||||
const store = mockStore()
|
||||
|
||||
store.dispatch(actions.signTx())
|
||||
assert(sendTransactionSpy.calledOnce)
|
||||
})
|
||||
@ -608,6 +851,71 @@ describe('Actions', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#updatedGasData', () => {
|
||||
it('errors when get code does not return', async () => {
|
||||
const store = mockStore()
|
||||
|
||||
const expectedActions = [
|
||||
{ type: 'GAS_LOADING_STARTED' },
|
||||
{ type: 'UPDATE_SEND_ERRORS', value: { gasLoadingError: 'gasLoadingError' } },
|
||||
{ type: 'GAS_LOADING_FINISHED' },
|
||||
]
|
||||
|
||||
const mockData = {
|
||||
gasPrice: '0x3b9aca00', //
|
||||
blockGasLimit: '0x6ad79a', // 7002010
|
||||
selectedAddress: '0x0DCD5D886577d5081B0c52e242Ef29E70Be3E7bc',
|
||||
to: '0xEC1Adf982415D2Ef5ec55899b9Bfb8BC0f29251B',
|
||||
value: '0xde0b6b3a7640000', // 1000000000000000000
|
||||
}
|
||||
|
||||
try {
|
||||
await store.dispatch(actions.updateGasData(mockData))
|
||||
assert.fail('Should have thrown error')
|
||||
} catch (error) {
|
||||
assert.deepEqual(store.getActions(), expectedActions)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#updatedGasData', () => {
|
||||
|
||||
const stub = sinon.stub().returns('0x')
|
||||
|
||||
const mockData = {
|
||||
gasPrice: '0x3b9aca00', //
|
||||
blockGasLimit: '0x6ad79a', // 7002010
|
||||
selectedAddress: '0x0DCD5D886577d5081B0c52e242Ef29E70Be3E7bc',
|
||||
to: '0xEC1Adf982415D2Ef5ec55899b9Bfb8BC0f29251B',
|
||||
value: '0xde0b6b3a7640000', // 1000000000000000000
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
global.eth = {
|
||||
getCode: stub,
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
stub.reset()
|
||||
})
|
||||
|
||||
it('returns default gas limit for basic eth transaction', async () => {
|
||||
const store = mockStore()
|
||||
|
||||
const expectedActions = [
|
||||
{ type: 'GAS_LOADING_STARTED' },
|
||||
{ type: 'UPDATE_GAS_LIMIT', value: '0x5208' },
|
||||
{ type: 'metamask/gas/SET_CUSTOM_GAS_LIMIT', value: '0x5208' },
|
||||
{ type: 'UPDATE_SEND_ERRORS', value: { gasLoadingError: null } },
|
||||
{ type: 'GAS_LOADING_FINISHED' },
|
||||
]
|
||||
|
||||
await store.dispatch(actions.updateGasData(mockData))
|
||||
assert.deepEqual(store.getActions(), expectedActions)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#signTokenTx', () => {
|
||||
|
||||
let tokenSpy
|
||||
@ -628,6 +936,61 @@ describe('Actions', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#updateTransaction', () => {
|
||||
|
||||
let updateTransactionSpy, updateTransactionParamsSpy
|
||||
|
||||
const txParams = {
|
||||
'from': '0x1',
|
||||
'gas': '0x5208',
|
||||
'gasPrice': '0x3b9aca00',
|
||||
'to': '0x2',
|
||||
'value': '0x0',
|
||||
}
|
||||
|
||||
const txData = { id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: txParams }
|
||||
|
||||
beforeEach( async () => {
|
||||
await metamaskController.txController.txStateManager.addTx(txData)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
updateTransactionSpy.restore()
|
||||
updateTransactionParamsSpy.restore()
|
||||
})
|
||||
|
||||
it('updates transaction', async () => {
|
||||
const store = mockStore()
|
||||
|
||||
updateTransactionSpy = sinon.spy(background, 'updateTransaction')
|
||||
updateTransactionParamsSpy = sinon.spy(actions, 'updateTransactionParams')
|
||||
|
||||
const result = [ txData.id, txParams ]
|
||||
|
||||
await store.dispatch(actions.updateTransaction(txData))
|
||||
assert(updateTransactionSpy.calledOnce)
|
||||
assert(updateTransactionParamsSpy.calledOnce)
|
||||
|
||||
assert.deepEqual(updateTransactionParamsSpy.args[0], result)
|
||||
})
|
||||
|
||||
it('rejects with error message', async () => {
|
||||
const store = mockStore()
|
||||
|
||||
updateTransactionSpy = sinon.stub(background, 'updateTransaction')
|
||||
updateTransactionSpy.callsFake((res, callback) => {
|
||||
callback(new Error('error'))
|
||||
})
|
||||
|
||||
try {
|
||||
await store.dispatch(actions.updateTransaction(txData))
|
||||
assert.fail('Should have thrown error')
|
||||
} catch (error) {
|
||||
assert.equal(error.message, 'error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#lockMetamask', () => {
|
||||
let backgroundSetLockedSpy
|
||||
|
||||
@ -635,18 +998,16 @@ describe('Actions', () => {
|
||||
backgroundSetLockedSpy.restore()
|
||||
})
|
||||
|
||||
it('calls setLocked', () => {
|
||||
it('calls setLocked', async () => {
|
||||
const store = mockStore()
|
||||
|
||||
backgroundSetLockedSpy = sinon.spy(background, 'setLocked')
|
||||
|
||||
return store.dispatch(actions.lockMetamask())
|
||||
.then(() => {
|
||||
assert(backgroundSetLockedSpy.calledOnce)
|
||||
})
|
||||
await store.dispatch(actions.lockMetamask())
|
||||
assert(backgroundSetLockedSpy.calledOnce)
|
||||
})
|
||||
|
||||
it('returns display warning error with value when setLocked in background callback errors', () => {
|
||||
it('returns display warning error with value when setLocked in background callback errors', async () => {
|
||||
const store = mockStore()
|
||||
|
||||
const expectedActions = [
|
||||
@ -660,10 +1021,13 @@ describe('Actions', () => {
|
||||
callback(new Error('error'))
|
||||
})
|
||||
|
||||
return store.dispatch(actions.lockMetamask())
|
||||
.then(() => {
|
||||
assert.deepEqual(store.getActions(), expectedActions)
|
||||
})
|
||||
try {
|
||||
await store.dispatch(actions.lockMetamask())
|
||||
assert.fail('Should have thrown error')
|
||||
} catch (error) {
|
||||
assert.deepEqual(store.getActions(), expectedActions)
|
||||
}
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
@ -748,13 +1112,11 @@ describe('Actions', () => {
|
||||
addTokenSpy.restore()
|
||||
})
|
||||
|
||||
it('calls addToken in background', () => {
|
||||
it('calls addToken in background', async () => {
|
||||
const store = mockStore()
|
||||
|
||||
store.dispatch(actions.addToken())
|
||||
.then(() => {
|
||||
assert(addTokenSpy.calledOnce)
|
||||
})
|
||||
assert(addTokenSpy.calledOnce)
|
||||
})
|
||||
|
||||
it('errors when addToken in background throws', async () => {
|
||||
@ -790,12 +1152,10 @@ describe('Actions', () => {
|
||||
removeTokenSpy.restore()
|
||||
})
|
||||
|
||||
it('calls removeToken in background', () => {
|
||||
it('calls removeToken in background', async () => {
|
||||
const store = mockStore()
|
||||
store.dispatch(actions.removeToken())
|
||||
.then(() => {
|
||||
assert(removeTokenSpy.calledOnce)
|
||||
})
|
||||
store.dispatch(await actions.removeToken())
|
||||
assert(removeTokenSpy.calledOnce)
|
||||
})
|
||||
|
||||
it('errors when removeToken in background fails', async () => {
|
||||
@ -910,7 +1270,7 @@ describe('Actions', () => {
|
||||
exportAccountSpy.restore()
|
||||
})
|
||||
|
||||
it('returns expected actions for successful action', () => {
|
||||
it('returns expected actions for successful action', async () => {
|
||||
const store = mockStore(devState)
|
||||
const expectedActions = [
|
||||
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
|
||||
@ -921,12 +1281,10 @@ describe('Actions', () => {
|
||||
submitPasswordSpy = sinon.spy(background, 'submitPassword')
|
||||
exportAccountSpy = sinon.spy(background, 'exportAccount')
|
||||
|
||||
return store.dispatch(actions.exportAccount(password, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'))
|
||||
.then(() => {
|
||||
assert(submitPasswordSpy.calledOnce)
|
||||
assert(exportAccountSpy.calledOnce)
|
||||
assert.deepEqual(store.getActions(), expectedActions)
|
||||
})
|
||||
await store.dispatch(actions.exportAccount(password, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'))
|
||||
assert(submitPasswordSpy.calledOnce)
|
||||
assert(exportAccountSpy.calledOnce)
|
||||
assert.deepEqual(store.getActions(), expectedActions)
|
||||
})
|
||||
|
||||
it('returns action errors when first func callback errors', async () => {
|
||||
@ -1082,9 +1440,7 @@ describe('Actions', () => {
|
||||
getTransactionCountSpy = sinon.spy(global.ethQuery, 'getTransactionCount')
|
||||
|
||||
store.dispatch(actions.updateNetworkNonce())
|
||||
.then(() => {
|
||||
assert(getTransactionCountSpy.calledOnce)
|
||||
})
|
||||
assert(getTransactionCountSpy.calledOnce)
|
||||
})
|
||||
|
||||
it('errors when getTransactionCount throws', async () => {
|
||||
@ -1155,7 +1511,7 @@ describe('Actions', () => {
|
||||
fetchMock.restore()
|
||||
})
|
||||
|
||||
it('calls expected actions', () => {
|
||||
it('calls expected actions', async () => {
|
||||
const store = mockStore()
|
||||
setCurrentLocaleSpy = sinon.spy(background, 'setCurrentLocale')
|
||||
|
||||
@ -1165,14 +1521,12 @@ describe('Actions', () => {
|
||||
{ type: 'HIDE_LOADING_INDICATION' },
|
||||
]
|
||||
|
||||
return store.dispatch(actions.updateCurrentLocale('en'))
|
||||
.then(() => {
|
||||
assert(setCurrentLocaleSpy.calledOnce)
|
||||
assert.deepEqual(store.getActions(), expectedActions)
|
||||
})
|
||||
await store.dispatch(actions.updateCurrentLocale('en'))
|
||||
assert(setCurrentLocaleSpy.calledOnce)
|
||||
assert.deepEqual(store.getActions(), expectedActions)
|
||||
})
|
||||
|
||||
it('calls expected actions', () => {
|
||||
it('errors when setCurrentLocale throws', async () => {
|
||||
const store = mockStore()
|
||||
const expectedActions = [
|
||||
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
|
||||
@ -1184,48 +1538,54 @@ describe('Actions', () => {
|
||||
callback(new Error('error'))
|
||||
})
|
||||
|
||||
return store.dispatch(actions.updateCurrentLocale('en'))
|
||||
.then(() => {
|
||||
assert.deepEqual(store.getActions(), expectedActions)
|
||||
})
|
||||
try {
|
||||
await store.dispatch(actions.updateCurrentLocale('en'))
|
||||
assert.fail('Should have thrown error')
|
||||
} catch (_) {
|
||||
assert.deepEqual(store.getActions(), expectedActions)
|
||||
}
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
describe('#markPasswordForgotten', () => {
|
||||
let markPasswordForgottenSpy
|
||||
let markPasswordForgottenSpy, forgotPasswordSpy
|
||||
|
||||
beforeEach(() => {
|
||||
markPasswordForgottenSpy = sinon.stub(background, 'markPasswordForgotten')
|
||||
markPasswordForgottenSpy = sinon.spy(background, 'markPasswordForgotten')
|
||||
forgotPasswordSpy = sinon.spy(actions, 'forgotPassword')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
markPasswordForgottenSpy.restore()
|
||||
forgotPasswordSpy.restore()
|
||||
})
|
||||
|
||||
it('calls markPasswordForgotten', () => {
|
||||
const store = mockStore()
|
||||
store.dispatch(actions.markPasswordForgotten())
|
||||
assert(forgotPasswordSpy.calledOnce)
|
||||
assert(markPasswordForgottenSpy.calledOnce)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#unMarkPasswordForgotten', () => {
|
||||
let unMarkPasswordForgottenSpy
|
||||
let unMarkPasswordForgottenSpy, forgotPasswordSpy
|
||||
|
||||
beforeEach(() => {
|
||||
unMarkPasswordForgottenSpy = sinon.stub(background, 'unMarkPasswordForgotten')
|
||||
unMarkPasswordForgottenSpy = sinon.stub(background, 'unMarkPasswordForgotten').returns(forgotPasswordSpy)
|
||||
forgotPasswordSpy = sinon.spy(actions, 'forgotPassword')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
unMarkPasswordForgottenSpy.restore()
|
||||
forgotPasswordSpy.restore()
|
||||
})
|
||||
|
||||
it('calls unMarkPasswordForgotten', () => {
|
||||
it('calls unMarkPasswordForgotten', async () => {
|
||||
const store = mockStore()
|
||||
store.dispatch(actions.unMarkPasswordForgotten())
|
||||
store.dispatch(await actions.unMarkPasswordForgotten())
|
||||
assert(unMarkPasswordForgottenSpy.calledOnce)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
})
|
||||
|
@ -3,22 +3,11 @@ import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import debounce from 'lodash.debounce'
|
||||
|
||||
export default class AdvancedTabContent extends Component {
|
||||
export default class AdvancedGasInputs extends Component {
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
gasPrice: this.props.customGasPrice,
|
||||
gasLimit: this.props.customGasLimit,
|
||||
}
|
||||
this.changeGasPrice = debounce(this.changeGasPrice, 500)
|
||||
this.changeGasLimit = debounce(this.changeGasLimit, 500)
|
||||
}
|
||||
|
||||
|
||||
static propTypes = {
|
||||
updateCustomGasPrice: PropTypes.func,
|
||||
updateCustomGasLimit: PropTypes.func,
|
||||
@ -31,6 +20,16 @@ export default class AdvancedTabContent extends Component {
|
||||
showGasLimitInfoModal: PropTypes.func,
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
gasPrice: this.props.customGasPrice,
|
||||
gasLimit: this.props.customGasLimit,
|
||||
}
|
||||
this.changeGasPrice = debounce(this.changeGasPrice, 500)
|
||||
this.changeGasLimit = debounce(this.changeGasLimit, 500)
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
const { customGasPrice: prevCustomGasPrice, customGasLimit: prevCustomGasLimit } = prevProps
|
||||
const { customGasPrice, customGasLimit } = this.props
|
||||
@ -50,12 +49,7 @@ export default class AdvancedTabContent extends Component {
|
||||
}
|
||||
|
||||
changeGasLimit = (e) => {
|
||||
if (e.target.value < 21000) {
|
||||
this.setState({ gasLimit: 21000 })
|
||||
this.props.updateCustomGasLimit(21000)
|
||||
} else {
|
||||
this.props.updateCustomGasLimit(Number(e.target.value))
|
||||
}
|
||||
this.props.updateCustomGasLimit(Number(e.target.value))
|
||||
}
|
||||
|
||||
onChangeGasPrice = (e) => {
|
||||
@ -67,89 +61,83 @@ export default class AdvancedTabContent extends Component {
|
||||
this.props.updateCustomGasPrice(Number(e.target.value))
|
||||
}
|
||||
|
||||
gasInputError ({ labelKey, insufficientBalance, customPriceIsSafe, isSpeedUp, value }) {
|
||||
gasPriceError ({ insufficientBalance, customPriceIsSafe, isSpeedUp, gasPrice }) {
|
||||
const { t } = this.context
|
||||
let errorText
|
||||
let errorType
|
||||
let isInError = true
|
||||
|
||||
|
||||
if (insufficientBalance) {
|
||||
errorText = t('insufficientBalance')
|
||||
errorType = 'error'
|
||||
} else if (labelKey === 'gasPrice' && isSpeedUp && value === 0) {
|
||||
errorText = t('zeroGasPriceOnSpeedUpError')
|
||||
errorType = 'error'
|
||||
} else if (labelKey === 'gasPrice' && !customPriceIsSafe) {
|
||||
errorText = t('gasPriceExtremelyLow')
|
||||
errorType = 'warning'
|
||||
} else {
|
||||
isInError = false
|
||||
return {
|
||||
errorText: t('insufficientBalance'),
|
||||
errorType: 'error',
|
||||
}
|
||||
} else if (isSpeedUp && gasPrice === 0) {
|
||||
return {
|
||||
errorText: t('zeroGasPriceOnSpeedUpError'),
|
||||
errorType: 'error',
|
||||
}
|
||||
} else if (!customPriceIsSafe) {
|
||||
return {
|
||||
errorText: t('gasPriceExtremelyLow'),
|
||||
errorType: 'warning',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isInError,
|
||||
errorText,
|
||||
errorType,
|
||||
return {}
|
||||
}
|
||||
|
||||
gasLimitError ({ insufficientBalance, gasLimit }) {
|
||||
const { t } = this.context
|
||||
|
||||
if (insufficientBalance) {
|
||||
return {
|
||||
errorText: t('insufficientBalance'),
|
||||
errorType: 'error',
|
||||
}
|
||||
} else if (gasLimit < 21000) {
|
||||
return {
|
||||
errorText: t('gasLimitTooLow'),
|
||||
errorType: 'error',
|
||||
}
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
gasInput ({ labelKey, value, onChange, insufficientBalance, customPriceIsSafe, isSpeedUp }) {
|
||||
const {
|
||||
isInError,
|
||||
errorText,
|
||||
errorType,
|
||||
} = this.gasInputError({ labelKey, insufficientBalance, customPriceIsSafe, isSpeedUp, value })
|
||||
|
||||
return (
|
||||
<div className="advanced-gas-inputs__gas-edit-row__input-wrapper">
|
||||
<input
|
||||
className={classnames('advanced-gas-inputs__gas-edit-row__input', {
|
||||
'advanced-gas-inputs__gas-edit-row__input--error': isInError && errorType === 'error',
|
||||
'advanced-gas-inputs__gas-edit-row__input--warning': isInError && errorType === 'warning',
|
||||
})}
|
||||
type="number"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<div className={classnames('advanced-gas-inputs__gas-edit-row__input-arrows', {
|
||||
'advanced-gas-inputs__gas-edit-row__input--error': isInError && errorType === 'error',
|
||||
'advanced-gas-inputs__gas-edit-row__input--warning': isInError && errorType === 'warning',
|
||||
})}>
|
||||
<div
|
||||
className="advanced-gas-inputs__gas-edit-row__input-arrows__i-wrap"
|
||||
onClick={() => onChange({ target: { value: value + 1 } })}
|
||||
>
|
||||
<i className="fa fa-sm fa-angle-up" />
|
||||
</div>
|
||||
<div
|
||||
className="advanced-gas-inputs__gas-edit-row__input-arrows__i-wrap"
|
||||
onClick={() => onChange({ target: { value: Math.max(value - 1, 0) } })}
|
||||
>
|
||||
<i className="fa fa-sm fa-angle-down" />
|
||||
</div>
|
||||
</div>
|
||||
{ isInError
|
||||
? <div className={`advanced-gas-inputs__gas-edit-row__${errorType}-text`}>
|
||||
{ errorText }
|
||||
</div>
|
||||
: null }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
infoButton (onClick) {
|
||||
return <i className="fa fa-info-circle" onClick={onClick} />
|
||||
}
|
||||
|
||||
renderGasEditRow (gasInputArgs) {
|
||||
renderGasInput ({ value, onChange, errorComponent, errorType, infoOnClick, label }) {
|
||||
return (
|
||||
<div className="advanced-gas-inputs__gas-edit-row">
|
||||
<div className="advanced-gas-inputs__gas-edit-row__label">
|
||||
{ this.context.t(gasInputArgs.labelKey) }
|
||||
{ this.infoButton(() => gasInputArgs.infoOnClick()) }
|
||||
{ label }
|
||||
<i className="fa fa-info-circle" onClick={infoOnClick} />
|
||||
</div>
|
||||
<div className="advanced-gas-inputs__gas-edit-row__input-wrapper">
|
||||
<input
|
||||
className={classnames('advanced-gas-inputs__gas-edit-row__input', {
|
||||
'advanced-gas-inputs__gas-edit-row__input--error': errorType === 'error',
|
||||
'advanced-gas-inputs__gas-edit-row__input--warning': errorType === 'warning',
|
||||
})}
|
||||
type="number"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<div className={classnames('advanced-gas-inputs__gas-edit-row__input-arrows', {
|
||||
'advanced-gas-inputs__gas-edit-row__input--error': errorType === 'error',
|
||||
'advanced-gas-inputs__gas-edit-row__input--warning': errorType === 'warning',
|
||||
})}>
|
||||
<div
|
||||
className="advanced-gas-inputs__gas-edit-row__input-arrows__i-wrap"
|
||||
onClick={() => onChange({ target: { value: value + 1 } })}
|
||||
>
|
||||
<i className="fa fa-sm fa-angle-up" />
|
||||
</div>
|
||||
<div
|
||||
className="advanced-gas-inputs__gas-edit-row__input-arrows__i-wrap"
|
||||
onClick={() => onChange({ target: { value: Math.max(value - 1, 0) } })}
|
||||
>
|
||||
<i className="fa fa-sm fa-angle-down" />
|
||||
</div>
|
||||
</div>
|
||||
{ errorComponent }
|
||||
</div>
|
||||
{ this.gasInput(gasInputArgs) }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -162,25 +150,47 @@ export default class AdvancedTabContent extends Component {
|
||||
showGasPriceInfoModal,
|
||||
showGasLimitInfoModal,
|
||||
} = this.props
|
||||
const {
|
||||
gasPrice,
|
||||
gasLimit,
|
||||
} = this.state
|
||||
|
||||
const {
|
||||
errorText: gasPriceErrorText,
|
||||
errorType: gasPriceErrorType,
|
||||
} = this.gasPriceError({ insufficientBalance, customPriceIsSafe, isSpeedUp, gasPrice })
|
||||
const gasPriceErrorComponent = gasPriceErrorType ?
|
||||
<div className={`advanced-gas-inputs__gas-edit-row__${gasPriceErrorType}-text`}>
|
||||
{ gasPriceErrorText }
|
||||
</div> :
|
||||
null
|
||||
|
||||
const {
|
||||
errorText: gasLimitErrorText,
|
||||
errorType: gasLimitErrorType,
|
||||
} = this.gasLimitError({ insufficientBalance, gasLimit })
|
||||
const gasLimitErrorComponent = gasLimitErrorType ?
|
||||
<div className={`advanced-gas-inputs__gas-edit-row__${gasLimitErrorType}-text`}>
|
||||
{ gasLimitErrorText }
|
||||
</div> :
|
||||
null
|
||||
|
||||
return (
|
||||
<div className="advanced-gas-inputs__gas-edit-rows">
|
||||
{ this.renderGasEditRow({
|
||||
labelKey: 'gasPrice',
|
||||
{ this.renderGasInput({
|
||||
label: this.context.t('gasPrice'),
|
||||
value: this.state.gasPrice,
|
||||
onChange: this.onChangeGasPrice,
|
||||
insufficientBalance,
|
||||
customPriceIsSafe,
|
||||
showGWEI: true,
|
||||
isSpeedUp,
|
||||
errorComponent: gasPriceErrorComponent,
|
||||
errorType: gasPriceErrorType,
|
||||
infoOnClick: showGasPriceInfoModal,
|
||||
}) }
|
||||
{ this.renderGasEditRow({
|
||||
labelKey: 'gasLimit',
|
||||
{ this.renderGasInput({
|
||||
label: this.context.t('gasLimit'),
|
||||
value: this.state.gasLimit,
|
||||
onChange: this.onChangeGasLimit,
|
||||
insufficientBalance,
|
||||
customPriceIsSafe,
|
||||
errorComponent: gasLimitErrorComponent,
|
||||
errorType: gasLimitErrorType,
|
||||
infoOnClick: showGasLimitInfoModal,
|
||||
}) }
|
||||
</div>
|
||||
|
@ -25,9 +25,9 @@ const mapDispatchToProps = dispatch => {
|
||||
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||
const {customGasPrice, customGasLimit, updateCustomGasPrice, updateCustomGasLimit} = ownProps
|
||||
return {
|
||||
...ownProps,
|
||||
...stateProps,
|
||||
...dispatchProps,
|
||||
...ownProps,
|
||||
customGasPrice: convertGasPriceForInputs(customGasPrice),
|
||||
customGasLimit: convertGasLimitForInputs(customGasLimit),
|
||||
updateCustomGasPrice: (price) => updateCustomGasPrice(decGWEIToHexWEI(price)),
|
||||
|
@ -1,9 +1,8 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import Loading from '../../../../ui/loading-screen'
|
||||
import GasPriceChart from '../../gas-price-chart'
|
||||
import debounce from 'lodash.debounce'
|
||||
import AdvancedGasInputs from '../../advanced-gas-inputs'
|
||||
|
||||
export default class AdvancedTabContent extends Component {
|
||||
static contextTypes = {
|
||||
@ -13,8 +12,8 @@ export default class AdvancedTabContent extends Component {
|
||||
static propTypes = {
|
||||
updateCustomGasPrice: PropTypes.func,
|
||||
updateCustomGasLimit: PropTypes.func,
|
||||
customGasPrice: PropTypes.number,
|
||||
customGasLimit: PropTypes.number,
|
||||
customModalGasPriceInHex: PropTypes.string,
|
||||
customModalGasLimitInHex: PropTypes.string,
|
||||
gasEstimatesLoading: PropTypes.bool,
|
||||
millisecondsRemaining: PropTypes.number,
|
||||
transactionFee: PropTypes.string,
|
||||
@ -26,95 +25,6 @@ export default class AdvancedTabContent extends Component {
|
||||
isEthereumNetwork: PropTypes.bool,
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.debouncedGasLimitReset = debounce((dVal) => {
|
||||
if (dVal < 21000) {
|
||||
props.updateCustomGasLimit(21000)
|
||||
}
|
||||
}, 1000, { trailing: true })
|
||||
this.onChangeGasLimit = (val) => {
|
||||
props.updateCustomGasLimit(val)
|
||||
this.debouncedGasLimitReset(val)
|
||||
}
|
||||
}
|
||||
|
||||
gasInputError ({ labelKey, insufficientBalance, customPriceIsSafe, isSpeedUp, value }) {
|
||||
const { t } = this.context
|
||||
let errorText
|
||||
let errorType
|
||||
let isInError = true
|
||||
|
||||
|
||||
if (insufficientBalance) {
|
||||
errorText = t('insufficientBalance')
|
||||
errorType = 'error'
|
||||
} else if (labelKey === 'gasPrice' && isSpeedUp && value === 0) {
|
||||
errorText = t('zeroGasPriceOnSpeedUpError')
|
||||
errorType = 'error'
|
||||
} else if (labelKey === 'gasPrice' && !customPriceIsSafe) {
|
||||
errorText = t('gasPriceExtremelyLow')
|
||||
errorType = 'warning'
|
||||
} else {
|
||||
isInError = false
|
||||
}
|
||||
|
||||
return {
|
||||
isInError,
|
||||
errorText,
|
||||
errorType,
|
||||
}
|
||||
}
|
||||
|
||||
gasInput ({ labelKey, value, onChange, insufficientBalance, customPriceIsSafe, isSpeedUp }) {
|
||||
const {
|
||||
isInError,
|
||||
errorText,
|
||||
errorType,
|
||||
} = this.gasInputError({ labelKey, insufficientBalance, customPriceIsSafe, isSpeedUp, value })
|
||||
|
||||
return (
|
||||
<div className="advanced-tab__gas-edit-row__input-wrapper">
|
||||
<input
|
||||
className={classnames('advanced-tab__gas-edit-row__input', {
|
||||
'advanced-tab__gas-edit-row__input--error': isInError && errorType === 'error',
|
||||
'advanced-tab__gas-edit-row__input--warning': isInError && errorType === 'warning',
|
||||
})}
|
||||
type="number"
|
||||
value={value}
|
||||
onChange={event => onChange(Number(event.target.value))}
|
||||
/>
|
||||
<div className={classnames('advanced-tab__gas-edit-row__input-arrows', {
|
||||
'advanced-tab__gas-edit-row__input--error': isInError && errorType === 'error',
|
||||
'advanced-tab__gas-edit-row__input--warning': isInError && errorType === 'warning',
|
||||
})}>
|
||||
<div
|
||||
className="advanced-tab__gas-edit-row__input-arrows__i-wrap"
|
||||
onClick={() => onChange(value + 1)}
|
||||
>
|
||||
<i className="fa fa-sm fa-angle-up" />
|
||||
</div>
|
||||
<div
|
||||
className="advanced-tab__gas-edit-row__input-arrows__i-wrap"
|
||||
onClick={() => onChange(Math.max(value - 1, 0))}
|
||||
>
|
||||
<i className="fa fa-sm fa-angle-down" />
|
||||
</div>
|
||||
</div>
|
||||
{ isInError
|
||||
? <div className={`advanced-tab__gas-edit-row__${errorType}-text`}>
|
||||
{ errorText }
|
||||
</div>
|
||||
: null }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
infoButton (onClick) {
|
||||
return <i className="fa fa-info-circle" onClick={onClick} />
|
||||
}
|
||||
|
||||
renderDataSummary (transactionFee, timeRemaining) {
|
||||
return (
|
||||
<div className="advanced-tab__transaction-data-summary">
|
||||
@ -132,56 +42,14 @@ export default class AdvancedTabContent extends Component {
|
||||
)
|
||||
}
|
||||
|
||||
renderGasEditRow (gasInputArgs) {
|
||||
return (
|
||||
<div className="advanced-tab__gas-edit-row">
|
||||
<div className="advanced-tab__gas-edit-row__label">
|
||||
{ this.context.t(gasInputArgs.labelKey) }
|
||||
{ this.infoButton(() => {}) }
|
||||
</div>
|
||||
{ this.gasInput(gasInputArgs) }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderGasEditRows ({
|
||||
customGasPrice,
|
||||
updateCustomGasPrice,
|
||||
customGasLimit,
|
||||
insufficientBalance,
|
||||
customPriceIsSafe,
|
||||
isSpeedUp,
|
||||
}) {
|
||||
return (
|
||||
<div className="advanced-tab__gas-edit-rows">
|
||||
{ this.renderGasEditRow({
|
||||
labelKey: 'gasPrice',
|
||||
value: customGasPrice,
|
||||
onChange: updateCustomGasPrice,
|
||||
insufficientBalance,
|
||||
customPriceIsSafe,
|
||||
showGWEI: true,
|
||||
isSpeedUp,
|
||||
}) }
|
||||
{ this.renderGasEditRow({
|
||||
labelKey: 'gasLimit',
|
||||
value: customGasLimit,
|
||||
onChange: this.onChangeGasLimit,
|
||||
insufficientBalance,
|
||||
customPriceIsSafe,
|
||||
}) }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { t } = this.context
|
||||
const {
|
||||
updateCustomGasPrice,
|
||||
updateCustomGasLimit,
|
||||
timeRemaining,
|
||||
customGasPrice,
|
||||
customGasLimit,
|
||||
customModalGasPriceInHex,
|
||||
customModalGasLimitInHex,
|
||||
insufficientBalance,
|
||||
gasChartProps,
|
||||
gasEstimatesLoading,
|
||||
@ -195,15 +63,17 @@ export default class AdvancedTabContent extends Component {
|
||||
<div className="advanced-tab">
|
||||
{ this.renderDataSummary(transactionFee, timeRemaining) }
|
||||
<div className="advanced-tab__fee-chart">
|
||||
{ this.renderGasEditRows({
|
||||
customGasPrice,
|
||||
updateCustomGasPrice,
|
||||
customGasLimit,
|
||||
updateCustomGasLimit,
|
||||
insufficientBalance,
|
||||
customPriceIsSafe,
|
||||
isSpeedUp,
|
||||
}) }
|
||||
<div className="advanced-tab__gas-inputs">
|
||||
<AdvancedGasInputs
|
||||
updateCustomGasPrice={updateCustomGasPrice}
|
||||
updateCustomGasLimit={updateCustomGasLimit}
|
||||
customGasPrice={customModalGasPriceInHex}
|
||||
customGasLimit={customModalGasLimitInHex}
|
||||
insufficientBalance={insufficientBalance}
|
||||
customPriceIsSafe={customPriceIsSafe}
|
||||
isSpeedUp={isSpeedUp}
|
||||
/>
|
||||
</div>
|
||||
{ isEthereumNetwork
|
||||
? <div>
|
||||
<div className="advanced-tab__fee-chart__title">{ t('liveGasPricePredictions') }</div>
|
||||
|
@ -92,137 +92,13 @@
|
||||
padding-right: 27px;
|
||||
}
|
||||
|
||||
&__gas-edit-rows {
|
||||
height: 73px;
|
||||
&__gas-inputs {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
justify-content: space-between;
|
||||
margin-left: 20px;
|
||||
margin-right: 10px;
|
||||
margin-top: 9px;
|
||||
}
|
||||
|
||||
&__gas-edit-row {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
|
||||
&__label {
|
||||
color: #313B5E;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.fa-info-circle {
|
||||
color: $silver;
|
||||
margin-left: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fa-info-circle:hover {
|
||||
color: $mid-gray;
|
||||
}
|
||||
}
|
||||
|
||||
&__error-text {
|
||||
font-size: 12px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
&__warning-text {
|
||||
font-size: 12px;
|
||||
color: orange;
|
||||
}
|
||||
|
||||
&__input-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__input {
|
||||
/*rtl:ignore*/
|
||||
direction: ltr;
|
||||
border: 1px solid $dusty-gray;
|
||||
border-radius: 4px;
|
||||
color: $mid-gray;
|
||||
font-size: 16px;
|
||||
height: 24px;
|
||||
width: 155px;
|
||||
padding-left: 8px;
|
||||
padding-top: 2px;
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
&__input--error {
|
||||
border: 1px solid $red;
|
||||
}
|
||||
|
||||
&__input--warning {
|
||||
border: 1px solid $orange;
|
||||
}
|
||||
|
||||
&__input-arrows {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
/*rtl:ignore*/
|
||||
right: 0px;
|
||||
width: 17px;
|
||||
height: 24px;
|
||||
border: 1px solid #dadada;
|
||||
border-top-right-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: #9b9b9b;
|
||||
font-size: .8em;
|
||||
border-bottom-right-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
&__i-wrap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__i-wrap:hover {
|
||||
background: #4EADE7;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
i:hover {
|
||||
background: #4EADE7;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&__input-arrows--error {
|
||||
border: 1px solid $red;
|
||||
}
|
||||
|
||||
&__input-arrows--warning {
|
||||
border: 1px solid $orange;
|
||||
}
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[type="number"]:hover::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__gwei-symbol {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 10px;
|
||||
color: $dusty-gray;
|
||||
}
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px
|
||||
}
|
||||
}
|
||||
|
@ -12,11 +12,7 @@ const propsMethodSpies = {
|
||||
updateCustomGasLimit: sinon.spy(),
|
||||
}
|
||||
|
||||
sinon.spy(AdvancedTabContent.prototype, 'renderGasEditRow')
|
||||
sinon.spy(AdvancedTabContent.prototype, 'gasInput')
|
||||
sinon.spy(AdvancedTabContent.prototype, 'renderGasEditRows')
|
||||
sinon.spy(AdvancedTabContent.prototype, 'renderDataSummary')
|
||||
sinon.spy(AdvancedTabContent.prototype, 'gasInputError')
|
||||
|
||||
describe('AdvancedTabContent Component', function () {
|
||||
let wrapper
|
||||
@ -25,23 +21,20 @@ describe('AdvancedTabContent Component', function () {
|
||||
wrapper = shallow(<AdvancedTabContent
|
||||
updateCustomGasPrice={propsMethodSpies.updateCustomGasPrice}
|
||||
updateCustomGasLimit={propsMethodSpies.updateCustomGasLimit}
|
||||
customGasPrice={11}
|
||||
customGasLimit={23456}
|
||||
timeRemaining={21500}
|
||||
customModalGasPriceInHex={'11'}
|
||||
customModalGasLimitInHex={'23456'}
|
||||
timeRemaining={'21500'}
|
||||
transactionFee={'$0.25'}
|
||||
insufficientBalance={false}
|
||||
customPriceIsSafe={true}
|
||||
isSpeedUp={false}
|
||||
isEthereumNetwork={true}
|
||||
/>, { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } })
|
||||
/>)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
propsMethodSpies.updateCustomGasPrice.resetHistory()
|
||||
propsMethodSpies.updateCustomGasLimit.resetHistory()
|
||||
AdvancedTabContent.prototype.renderGasEditRow.resetHistory()
|
||||
AdvancedTabContent.prototype.gasInput.resetHistory()
|
||||
AdvancedTabContent.prototype.renderGasEditRows.resetHistory()
|
||||
AdvancedTabContent.prototype.renderDataSummary.resetHistory()
|
||||
})
|
||||
|
||||
@ -59,7 +52,6 @@ describe('AdvancedTabContent Component', function () {
|
||||
|
||||
const feeChartDiv = advancedTabChildren.at(1)
|
||||
|
||||
assert(feeChartDiv.childAt(0).hasClass('advanced-tab__gas-edit-rows'))
|
||||
assert(feeChartDiv.childAt(1).childAt(0).hasClass('advanced-tab__fee-chart__title'))
|
||||
assert(feeChartDiv.childAt(1).childAt(1).is(GasPriceChart))
|
||||
assert(feeChartDiv.childAt(1).childAt(2).hasClass('advanced-tab__fee-chart__speed-buttons'))
|
||||
@ -75,31 +67,15 @@ describe('AdvancedTabContent Component', function () {
|
||||
|
||||
const feeChartDiv = advancedTabChildren.at(1)
|
||||
|
||||
assert(feeChartDiv.childAt(0).hasClass('advanced-tab__gas-edit-rows'))
|
||||
assert(feeChartDiv.childAt(1).childAt(0).hasClass('advanced-tab__fee-chart__title'))
|
||||
assert(feeChartDiv.childAt(1).childAt(1).is(Loading))
|
||||
assert(feeChartDiv.childAt(1).childAt(2).hasClass('advanced-tab__fee-chart__speed-buttons'))
|
||||
})
|
||||
|
||||
it('should call renderDataSummary with the expected params', () => {
|
||||
assert.equal(AdvancedTabContent.prototype.renderGasEditRows.callCount, 1)
|
||||
const renderDataSummaryArgs = AdvancedTabContent.prototype.renderDataSummary.getCall(0).args
|
||||
assert.deepEqual(renderDataSummaryArgs, ['$0.25', 21500])
|
||||
})
|
||||
|
||||
it('should call renderGasEditRows with the expected params', () => {
|
||||
assert.equal(AdvancedTabContent.prototype.renderGasEditRows.callCount, 1)
|
||||
const renderGasEditRowArgs = AdvancedTabContent.prototype.renderGasEditRows.getCall(0).args
|
||||
assert.deepEqual(renderGasEditRowArgs, [{
|
||||
customGasPrice: 11,
|
||||
customGasLimit: 23456,
|
||||
insufficientBalance: false,
|
||||
customPriceIsSafe: true,
|
||||
updateCustomGasPrice: propsMethodSpies.updateCustomGasPrice,
|
||||
updateCustomGasLimit: propsMethodSpies.updateCustomGasLimit,
|
||||
isSpeedUp: false,
|
||||
}])
|
||||
})
|
||||
})
|
||||
|
||||
describe('renderDataSummary()', () => {
|
||||
@ -129,237 +105,4 @@ describe('AdvancedTabContent Component', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('renderGasEditRow()', () => {
|
||||
let gasEditRow
|
||||
|
||||
beforeEach(() => {
|
||||
AdvancedTabContent.prototype.gasInput.resetHistory()
|
||||
gasEditRow = shallow(wrapper.instance().renderGasEditRow({
|
||||
labelKey: 'mockLabelKey',
|
||||
someArg: 'argA',
|
||||
}))
|
||||
})
|
||||
|
||||
it('should render the gas-edit-row root node', () => {
|
||||
assert(gasEditRow.hasClass('advanced-tab__gas-edit-row'))
|
||||
})
|
||||
|
||||
it('should render a label and an input', () => {
|
||||
const gasEditRowChildren = gasEditRow.children()
|
||||
assert.equal(gasEditRowChildren.length, 2)
|
||||
assert(gasEditRowChildren.at(0).hasClass('advanced-tab__gas-edit-row__label'))
|
||||
assert(gasEditRowChildren.at(1).hasClass('advanced-tab__gas-edit-row__input-wrapper'))
|
||||
})
|
||||
|
||||
it('should render the label key and info button', () => {
|
||||
const gasRowLabelChildren = gasEditRow.children().at(0).children()
|
||||
assert.equal(gasRowLabelChildren.length, 2)
|
||||
assert(gasRowLabelChildren.at(0), 'mockLabelKey')
|
||||
assert(gasRowLabelChildren.at(1).hasClass('fa-info-circle'))
|
||||
})
|
||||
|
||||
it('should call this.gasInput with the correct args', () => {
|
||||
const gasInputSpyArgs = AdvancedTabContent.prototype.gasInput.args
|
||||
assert.deepEqual(gasInputSpyArgs[0], [ { labelKey: 'mockLabelKey', someArg: 'argA' } ])
|
||||
})
|
||||
})
|
||||
|
||||
describe('renderGasEditRows()', () => {
|
||||
let gasEditRows
|
||||
let tempOnChangeGasLimit
|
||||
|
||||
beforeEach(() => {
|
||||
tempOnChangeGasLimit = wrapper.instance().onChangeGasLimit
|
||||
wrapper.instance().onChangeGasLimit = () => 'mockOnChangeGasLimit'
|
||||
AdvancedTabContent.prototype.renderGasEditRow.resetHistory()
|
||||
gasEditRows = shallow(wrapper.instance().renderGasEditRows(
|
||||
'mockGasPrice',
|
||||
() => 'mockUpdateCustomGasPriceReturn',
|
||||
'mockGasLimit',
|
||||
() => 'mockUpdateCustomGasLimitReturn',
|
||||
false
|
||||
))
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.instance().onChangeGasLimit = tempOnChangeGasLimit
|
||||
})
|
||||
|
||||
it('should render the gas-edit-rows root node', () => {
|
||||
assert(gasEditRows.hasClass('advanced-tab__gas-edit-rows'))
|
||||
})
|
||||
|
||||
it('should render two rows', () => {
|
||||
const gasEditRowsChildren = gasEditRows.children()
|
||||
assert.equal(gasEditRowsChildren.length, 2)
|
||||
assert(gasEditRowsChildren.at(0).hasClass('advanced-tab__gas-edit-row'))
|
||||
assert(gasEditRowsChildren.at(1).hasClass('advanced-tab__gas-edit-row'))
|
||||
})
|
||||
|
||||
it('should call this.renderGasEditRow twice, with the expected args', () => {
|
||||
const renderGasEditRowSpyArgs = AdvancedTabContent.prototype.renderGasEditRow.args
|
||||
assert.equal(renderGasEditRowSpyArgs.length, 2)
|
||||
assert.deepEqual(renderGasEditRowSpyArgs[0].map(String), [{
|
||||
labelKey: 'gasPrice',
|
||||
value: 'mockGasLimit',
|
||||
onChange: () => 'mockOnChangeGasLimit',
|
||||
insufficientBalance: false,
|
||||
customPriceIsSafe: true,
|
||||
showGWEI: true,
|
||||
}].map(String))
|
||||
assert.deepEqual(renderGasEditRowSpyArgs[1].map(String), [{
|
||||
labelKey: 'gasPrice',
|
||||
value: 'mockGasPrice',
|
||||
onChange: () => 'mockUpdateCustomGasPriceReturn',
|
||||
insufficientBalance: false,
|
||||
customPriceIsSafe: true,
|
||||
showGWEI: true,
|
||||
}].map(String))
|
||||
})
|
||||
})
|
||||
|
||||
describe('infoButton()', () => {
|
||||
let infoButton
|
||||
|
||||
beforeEach(() => {
|
||||
AdvancedTabContent.prototype.renderGasEditRow.resetHistory()
|
||||
infoButton = shallow(wrapper.instance().infoButton(() => 'mockOnClickReturn'))
|
||||
})
|
||||
|
||||
it('should render the i element', () => {
|
||||
assert(infoButton.hasClass('fa-info-circle'))
|
||||
})
|
||||
|
||||
it('should pass the onClick argument to the i tag onClick prop', () => {
|
||||
assert(infoButton.props().onClick(), 'mockOnClickReturn')
|
||||
})
|
||||
})
|
||||
|
||||
describe('gasInput()', () => {
|
||||
let gasInput
|
||||
|
||||
beforeEach(() => {
|
||||
AdvancedTabContent.prototype.renderGasEditRow.resetHistory()
|
||||
AdvancedTabContent.prototype.gasInputError.resetHistory()
|
||||
gasInput = shallow(wrapper.instance().gasInput({
|
||||
labelKey: 'gasPrice',
|
||||
value: 321,
|
||||
onChange: value => value + 7,
|
||||
insufficientBalance: false,
|
||||
showGWEI: true,
|
||||
customPriceIsSafe: true,
|
||||
isSpeedUp: false,
|
||||
}))
|
||||
})
|
||||
|
||||
it('should render the input-wrapper root node', () => {
|
||||
assert(gasInput.hasClass('advanced-tab__gas-edit-row__input-wrapper'))
|
||||
})
|
||||
|
||||
it('should render two children, including an input', () => {
|
||||
assert.equal(gasInput.children().length, 2)
|
||||
assert(gasInput.children().at(0).hasClass('advanced-tab__gas-edit-row__input'))
|
||||
})
|
||||
|
||||
it('should call the passed onChange method with the value of the input onChange event', () => {
|
||||
const inputOnChange = gasInput.find('input').props().onChange
|
||||
assert.equal(inputOnChange({ target: { value: 8} }), 15)
|
||||
})
|
||||
|
||||
it('should have two input arrows', () => {
|
||||
const upArrow = gasInput.find('.fa-angle-up')
|
||||
assert.equal(upArrow.length, 1)
|
||||
const downArrow = gasInput.find('.fa-angle-down')
|
||||
assert.equal(downArrow.length, 1)
|
||||
})
|
||||
|
||||
it('should call onChange with the value incremented decremented when its onchange method is called', () => {
|
||||
const upArrow = gasInput.find('.advanced-tab__gas-edit-row__input-arrows__i-wrap').at(0)
|
||||
assert.equal(upArrow.props().onClick(), 329)
|
||||
const downArrow = gasInput.find('.advanced-tab__gas-edit-row__input-arrows__i-wrap').at(1)
|
||||
assert.equal(downArrow.props().onClick(), 327)
|
||||
})
|
||||
|
||||
it('should call gasInputError with the expected params', () => {
|
||||
assert.equal(AdvancedTabContent.prototype.gasInputError.callCount, 1)
|
||||
const gasInputErrorArgs = AdvancedTabContent.prototype.gasInputError.getCall(0).args
|
||||
assert.deepEqual(gasInputErrorArgs, [{
|
||||
labelKey: 'gasPrice',
|
||||
insufficientBalance: false,
|
||||
customPriceIsSafe: true,
|
||||
value: 321,
|
||||
isSpeedUp: false,
|
||||
}])
|
||||
})
|
||||
})
|
||||
|
||||
describe('gasInputError()', () => {
|
||||
let gasInputError
|
||||
|
||||
beforeEach(() => {
|
||||
AdvancedTabContent.prototype.renderGasEditRow.resetHistory()
|
||||
gasInputError = wrapper.instance().gasInputError({
|
||||
labelKey: '',
|
||||
insufficientBalance: false,
|
||||
customPriceIsSafe: true,
|
||||
isSpeedUp: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('should return an insufficientBalance error', () => {
|
||||
const gasInputError = wrapper.instance().gasInputError({
|
||||
labelKey: 'gasPrice',
|
||||
insufficientBalance: true,
|
||||
customPriceIsSafe: true,
|
||||
isSpeedUp: false,
|
||||
value: 1,
|
||||
})
|
||||
assert.deepEqual(gasInputError, {
|
||||
isInError: true,
|
||||
errorText: 'insufficientBalance',
|
||||
errorType: 'error',
|
||||
})
|
||||
})
|
||||
|
||||
it('should return a zero gas on retry error', () => {
|
||||
const gasInputError = wrapper.instance().gasInputError({
|
||||
labelKey: 'gasPrice',
|
||||
insufficientBalance: false,
|
||||
customPriceIsSafe: false,
|
||||
isSpeedUp: true,
|
||||
value: 0,
|
||||
})
|
||||
assert.deepEqual(gasInputError, {
|
||||
isInError: true,
|
||||
errorText: 'zeroGasPriceOnSpeedUpError',
|
||||
errorType: 'error',
|
||||
})
|
||||
})
|
||||
|
||||
it('should return a low gas warning', () => {
|
||||
const gasInputError = wrapper.instance().gasInputError({
|
||||
labelKey: 'gasPrice',
|
||||
insufficientBalance: false,
|
||||
customPriceIsSafe: false,
|
||||
isSpeedUp: false,
|
||||
value: 1,
|
||||
})
|
||||
assert.deepEqual(gasInputError, {
|
||||
isInError: true,
|
||||
errorText: 'gasPriceExtremelyLow',
|
||||
errorType: 'warning',
|
||||
})
|
||||
})
|
||||
|
||||
it('should return isInError false if there is no error', () => {
|
||||
gasInputError = wrapper.instance().gasInputError({
|
||||
labelKey: 'gasPrice',
|
||||
insufficientBalance: false,
|
||||
customPriceIsSafe: true,
|
||||
value: 1,
|
||||
})
|
||||
assert.equal(gasInputError.isInError, false)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
@ -16,11 +16,15 @@ export default class GasModalPageContainer extends Component {
|
||||
hideBasic: PropTypes.bool,
|
||||
updateCustomGasPrice: PropTypes.func,
|
||||
updateCustomGasLimit: PropTypes.func,
|
||||
currentTimeEstimate: PropTypes.string,
|
||||
customGasPrice: PropTypes.number,
|
||||
customGasLimit: PropTypes.number,
|
||||
insufficientBalance: PropTypes.bool,
|
||||
fetchBasicGasAndTimeEstimates: PropTypes.func,
|
||||
fetchGasEstimates: PropTypes.func,
|
||||
gasPriceButtonGroupProps: PropTypes.object,
|
||||
gasChartProps: PropTypes.object,
|
||||
gasEstimatesLoading: PropTypes.bool,
|
||||
infoRowProps: PropTypes.shape({
|
||||
originalTotalFiat: PropTypes.string,
|
||||
originalTotalEth: PropTypes.string,
|
||||
@ -38,6 +42,7 @@ export default class GasModalPageContainer extends Component {
|
||||
]),
|
||||
customPriceIsSafe: PropTypes.bool,
|
||||
isSpeedUp: PropTypes.bool,
|
||||
isRetry: PropTypes.bool,
|
||||
disableSave: PropTypes.bool,
|
||||
isEthereumNetwork: PropTypes.bool,
|
||||
}
|
||||
@ -64,35 +69,39 @@ export default class GasModalPageContainer extends Component {
|
||||
)
|
||||
}
|
||||
|
||||
renderAdvancedTabContent ({
|
||||
convertThenUpdateCustomGasPrice,
|
||||
convertThenUpdateCustomGasLimit,
|
||||
customGasPrice,
|
||||
customGasLimit,
|
||||
newTotalFiat,
|
||||
gasChartProps,
|
||||
currentTimeEstimate,
|
||||
insufficientBalance,
|
||||
gasEstimatesLoading,
|
||||
customPriceIsSafe,
|
||||
isSpeedUp,
|
||||
transactionFee,
|
||||
isEthereumNetwork,
|
||||
}) {
|
||||
renderAdvancedTabContent () {
|
||||
const {
|
||||
updateCustomGasPrice,
|
||||
updateCustomGasLimit,
|
||||
customModalGasPriceInHex,
|
||||
customModalGasLimitInHex,
|
||||
gasChartProps,
|
||||
currentTimeEstimate,
|
||||
insufficientBalance,
|
||||
gasEstimatesLoading,
|
||||
customPriceIsSafe,
|
||||
isSpeedUp,
|
||||
isRetry,
|
||||
infoRowProps: {
|
||||
transactionFee,
|
||||
},
|
||||
isEthereumNetwork,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<AdvancedTabContent
|
||||
updateCustomGasPrice={convertThenUpdateCustomGasPrice}
|
||||
updateCustomGasLimit={convertThenUpdateCustomGasLimit}
|
||||
customGasPrice={customGasPrice}
|
||||
customGasLimit={customGasLimit}
|
||||
updateCustomGasPrice={updateCustomGasPrice}
|
||||
updateCustomGasLimit={updateCustomGasLimit}
|
||||
customModalGasPriceInHex={customModalGasPriceInHex}
|
||||
customModalGasLimitInHex={customModalGasLimitInHex}
|
||||
timeRemaining={currentTimeEstimate}
|
||||
transactionFee={transactionFee}
|
||||
totalFee={newTotalFiat}
|
||||
gasChartProps={gasChartProps}
|
||||
insufficientBalance={insufficientBalance}
|
||||
gasEstimatesLoading={gasEstimatesLoading}
|
||||
customPriceIsSafe={customPriceIsSafe}
|
||||
isSpeedUp={isSpeedUp}
|
||||
isRetry={isRetry}
|
||||
isEthereumNetwork={isEthereumNetwork}
|
||||
/>
|
||||
)
|
||||
@ -122,20 +131,27 @@ export default class GasModalPageContainer extends Component {
|
||||
)
|
||||
}
|
||||
|
||||
renderTabs ({
|
||||
newTotalFiat,
|
||||
newTotalEth,
|
||||
sendAmount,
|
||||
transactionFee,
|
||||
},
|
||||
{
|
||||
gasPriceButtonGroupProps,
|
||||
hideBasic,
|
||||
...advancedTabProps
|
||||
}) {
|
||||
renderTabs () {
|
||||
const {
|
||||
gasPriceButtonGroupProps,
|
||||
hideBasic,
|
||||
infoRowProps: {
|
||||
newTotalFiat,
|
||||
newTotalEth,
|
||||
sendAmount,
|
||||
transactionFee,
|
||||
},
|
||||
} = this.props
|
||||
|
||||
let tabsToRender = [
|
||||
{ name: 'basic', content: this.renderBasicTabContent(gasPriceButtonGroupProps) },
|
||||
{ name: 'advanced', content: this.renderAdvancedTabContent({ transactionFee, ...advancedTabProps }) },
|
||||
{
|
||||
name: this.context.t('basic'),
|
||||
content: this.renderBasicTabContent(gasPriceButtonGroupProps),
|
||||
},
|
||||
{
|
||||
name: this.context.t('advanced'),
|
||||
content: this.renderAdvancedTabContent(),
|
||||
},
|
||||
]
|
||||
|
||||
if (hideBasic) {
|
||||
@ -144,7 +160,7 @@ export default class GasModalPageContainer extends Component {
|
||||
|
||||
return (
|
||||
<Tabs>
|
||||
{tabsToRender.map(({ name, content }, i) => <Tab name={this.context.t(name)} key={`gas-modal-tab-${i}`}>
|
||||
{tabsToRender.map(({ name, content }, i) => <Tab name={name} key={`gas-modal-tab-${i}`}>
|
||||
<div className="gas-modal-content">
|
||||
{ content }
|
||||
{ this.renderInfoRows(newTotalFiat, newTotalEth, sendAmount, transactionFee) }
|
||||
@ -158,13 +174,11 @@ export default class GasModalPageContainer extends Component {
|
||||
render () {
|
||||
const {
|
||||
cancelAndClose,
|
||||
infoRowProps,
|
||||
onSubmit,
|
||||
customModalGasPriceInHex,
|
||||
customModalGasLimitInHex,
|
||||
disableSave,
|
||||
isSpeedUp,
|
||||
...tabProps
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
@ -172,7 +186,7 @@ export default class GasModalPageContainer extends Component {
|
||||
<PageContainer
|
||||
title={this.context.t('customGas')}
|
||||
subtitle={this.context.t('customGasSubTitle')}
|
||||
tabsComponent={this.renderTabs(infoRowProps, tabProps)}
|
||||
tabsComponent={this.renderTabs()}
|
||||
disabled={disableSave}
|
||||
onCancel={() => cancelAndClose()}
|
||||
onClose={() => cancelAndClose()}
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
setGasLimit,
|
||||
setGasPrice,
|
||||
createSpeedUpTransaction,
|
||||
createRetryTransaction,
|
||||
hideSidebar,
|
||||
updateSendAmount,
|
||||
setGasTotal,
|
||||
@ -56,7 +57,6 @@ import {
|
||||
addHexWEIsToDec,
|
||||
subtractHexWEIsToDec,
|
||||
decEthToConvertedCurrency as ethTotalToConvertedCurrency,
|
||||
decGWEIToHexWEI,
|
||||
hexWEIToDecGWEI,
|
||||
} from '../../../../helpers/utils/conversions.util'
|
||||
import {
|
||||
@ -154,6 +154,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
},
|
||||
transaction: txData || transaction,
|
||||
isSpeedUp: transaction.status === 'submitted',
|
||||
isRetry: transaction.status === 'failed',
|
||||
txId: transaction.id,
|
||||
insufficientBalance,
|
||||
gasEstimatesLoading,
|
||||
@ -175,8 +176,7 @@ const mapDispatchToProps = dispatch => {
|
||||
},
|
||||
hideModal: () => dispatch(hideModal()),
|
||||
updateCustomGasPrice,
|
||||
convertThenUpdateCustomGasPrice: newPrice => updateCustomGasPrice(decGWEIToHexWEI(newPrice)),
|
||||
convertThenUpdateCustomGasLimit: newLimit => dispatch(setCustomGasLimit(addHexPrefix(newLimit.toString(16)))),
|
||||
updateCustomGasLimit: newLimit => dispatch(setCustomGasLimit(addHexPrefix(newLimit))),
|
||||
setGasData: (newLimit, newPrice) => {
|
||||
dispatch(setGasLimit(newLimit))
|
||||
dispatch(setGasPrice(newPrice))
|
||||
@ -189,6 +189,9 @@ const mapDispatchToProps = dispatch => {
|
||||
createSpeedUpTransaction: (txId, gasPrice) => {
|
||||
return dispatch(createSpeedUpTransaction(txId, gasPrice))
|
||||
},
|
||||
createRetryTransaction: (txId, gasPrice) => {
|
||||
return dispatch(createRetryTransaction(txId, gasPrice))
|
||||
},
|
||||
hideGasButtonGroup: () => dispatch(hideGasButtonGroup()),
|
||||
setCustomTimeEstimate: (timeEstimateInSeconds) => dispatch(setCustomTimeEstimate(timeEstimateInSeconds)),
|
||||
hideSidebar: () => dispatch(hideSidebar()),
|
||||
@ -208,6 +211,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||
isConfirm,
|
||||
txId,
|
||||
isSpeedUp,
|
||||
isRetry,
|
||||
insufficientBalance,
|
||||
maxModeOn,
|
||||
customGasPrice,
|
||||
@ -219,11 +223,11 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||
transaction,
|
||||
} = stateProps
|
||||
const {
|
||||
updateCustomGasPrice: dispatchUpdateCustomGasPrice,
|
||||
hideGasButtonGroup: dispatchHideGasButtonGroup,
|
||||
setGasData: dispatchSetGasData,
|
||||
updateConfirmTxGasAndCalculate: dispatchUpdateConfirmTxGasAndCalculate,
|
||||
createSpeedUpTransaction: dispatchCreateSpeedUpTransaction,
|
||||
createRetryTransaction: dispatchCreateRetryTransaction,
|
||||
hideSidebar: dispatchHideSidebar,
|
||||
cancelAndClose: dispatchCancelAndClose,
|
||||
hideModal: dispatchHideModal,
|
||||
@ -251,6 +255,10 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||
dispatchCreateSpeedUpTransaction(txId, gasPrice)
|
||||
dispatchHideSidebar()
|
||||
dispatchCancelAndClose()
|
||||
} else if (isRetry) {
|
||||
dispatchCreateRetryTransaction(txId, gasPrice)
|
||||
dispatchHideSidebar()
|
||||
dispatchCancelAndClose()
|
||||
} else {
|
||||
dispatchSetGasData(gasLimit, gasPrice)
|
||||
dispatchHideGasButtonGroup()
|
||||
@ -267,11 +275,11 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||
},
|
||||
gasPriceButtonGroupProps: {
|
||||
...gasPriceButtonGroupProps,
|
||||
handleGasPriceSelection: dispatchUpdateCustomGasPrice,
|
||||
handleGasPriceSelection: otherDispatchProps.updateCustomGasPrice,
|
||||
},
|
||||
cancelAndClose: () => {
|
||||
dispatchCancelAndClose()
|
||||
if (isSpeedUp) {
|
||||
if (isSpeedUp || isRetry) {
|
||||
dispatchHideSidebar()
|
||||
}
|
||||
},
|
||||
|
@ -79,7 +79,7 @@ describe('GasModalPageContainer Component', function () {
|
||||
customGasLimitInHex={'mockCustomGasLimitInHex'}
|
||||
insufficientBalance={false}
|
||||
disableSave={false}
|
||||
/>, { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } })
|
||||
/>)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
@ -158,10 +158,7 @@ describe('GasModalPageContainer Component', function () {
|
||||
})
|
||||
|
||||
it('should render a Tabs component with "Basic" and "Advanced" tabs', () => {
|
||||
const renderTabsResult = wrapper.instance().renderTabs(mockInfoRowProps, {
|
||||
gasPriceButtonGroupProps: mockGasPriceButtonGroupProps,
|
||||
otherProps: 'mockAdvancedTabProps',
|
||||
})
|
||||
const renderTabsResult = wrapper.instance().renderTabs()
|
||||
const renderedTabs = shallow(renderTabsResult)
|
||||
assert.equal(renderedTabs.props().className, 'tabs')
|
||||
|
||||
@ -175,23 +172,10 @@ describe('GasModalPageContainer Component', function () {
|
||||
assert.equal(tabs.at(1).childAt(0).props().className, 'gas-modal-content')
|
||||
})
|
||||
|
||||
it('should call renderBasicTabContent and renderAdvancedTabContent with the expected props', () => {
|
||||
assert.equal(GP.renderBasicTabContent.callCount, 0)
|
||||
assert.equal(GP.renderAdvancedTabContent.callCount, 0)
|
||||
|
||||
wrapper.instance().renderTabs(mockInfoRowProps, { gasPriceButtonGroupProps: mockGasPriceButtonGroupProps, otherProps: 'mockAdvancedTabProps' })
|
||||
|
||||
assert.equal(GP.renderBasicTabContent.callCount, 1)
|
||||
assert.equal(GP.renderAdvancedTabContent.callCount, 1)
|
||||
|
||||
assert.deepEqual(GP.renderBasicTabContent.getCall(0).args[0], mockGasPriceButtonGroupProps)
|
||||
assert.deepEqual(GP.renderAdvancedTabContent.getCall(0).args[0], { transactionFee: 'mockTransactionFee', otherProps: 'mockAdvancedTabProps' })
|
||||
})
|
||||
|
||||
it('should call renderInfoRows with the expected props', () => {
|
||||
assert.equal(GP.renderInfoRows.callCount, 0)
|
||||
|
||||
wrapper.instance().renderTabs(mockInfoRowProps, { gasPriceButtonGroupProps: mockGasPriceButtonGroupProps, otherProps: 'mockAdvancedTabProps' })
|
||||
wrapper.instance().renderTabs()
|
||||
|
||||
assert.equal(GP.renderInfoRows.callCount, 2)
|
||||
|
||||
@ -200,11 +184,25 @@ describe('GasModalPageContainer Component', function () {
|
||||
})
|
||||
|
||||
it('should not render the basic tab if hideBasic is true', () => {
|
||||
const renderTabsResult = wrapper.instance().renderTabs(mockInfoRowProps, {
|
||||
gasPriceButtonGroupProps: mockGasPriceButtonGroupProps,
|
||||
otherProps: 'mockAdvancedTabProps',
|
||||
hideBasic: true,
|
||||
})
|
||||
wrapper = shallow(<GasModalPageContainer
|
||||
cancelAndClose={propsMethodSpies.cancelAndClose}
|
||||
onSubmit={propsMethodSpies.onSubmit}
|
||||
fetchBasicGasAndTimeEstimates={propsMethodSpies.fetchBasicGasAndTimeEstimates}
|
||||
fetchGasEstimates={propsMethodSpies.fetchGasEstimates}
|
||||
updateCustomGasPrice={() => 'mockupdateCustomGasPrice'}
|
||||
updateCustomGasLimit={() => 'mockupdateCustomGasLimit'}
|
||||
customGasPrice={21}
|
||||
customGasLimit={54321}
|
||||
gasPriceButtonGroupProps={mockGasPriceButtonGroupProps}
|
||||
infoRowProps={mockInfoRowProps}
|
||||
currentTimeEstimate={'1 min 31 sec'}
|
||||
customGasPriceInHex={'mockCustomGasPriceInHex'}
|
||||
customGasLimitInHex={'mockCustomGasLimitInHex'}
|
||||
insufficientBalance={false}
|
||||
disableSave={false}
|
||||
hideBasic={true}
|
||||
/>)
|
||||
const renderTabsResult = wrapper.instance().renderTabs()
|
||||
|
||||
const renderedTabs = shallow(renderTabsResult)
|
||||
const tabs = renderedTabs.find(Tab)
|
||||
@ -224,28 +222,6 @@ describe('GasModalPageContainer Component', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('renderAdvancedTabContent', () => {
|
||||
it('should render with the correct props', () => {
|
||||
const renderAdvancedTabContentResult = wrapper.instance().renderAdvancedTabContent({
|
||||
convertThenUpdateCustomGasPrice: () => 'mockConvertThenUpdateCustomGasPrice',
|
||||
convertThenUpdateCustomGasLimit: () => 'mockConvertThenUpdateCustomGasLimit',
|
||||
customGasPrice: 123,
|
||||
customGasLimit: 456,
|
||||
newTotalFiat: '$0.30',
|
||||
currentTimeEstimate: '1 min 31 sec',
|
||||
gasEstimatesLoading: 'mockGasEstimatesLoading',
|
||||
})
|
||||
const advancedTabContentProps = renderAdvancedTabContentResult.props
|
||||
assert.equal(advancedTabContentProps.updateCustomGasPrice(), 'mockConvertThenUpdateCustomGasPrice')
|
||||
assert.equal(advancedTabContentProps.updateCustomGasLimit(), 'mockConvertThenUpdateCustomGasLimit')
|
||||
assert.equal(advancedTabContentProps.customGasPrice, 123)
|
||||
assert.equal(advancedTabContentProps.customGasLimit, 456)
|
||||
assert.equal(advancedTabContentProps.timeRemaining, '1 min 31 sec')
|
||||
assert.equal(advancedTabContentProps.totalFee, '$0.30')
|
||||
assert.equal(advancedTabContentProps.gasEstimatesLoading, 'mockGasEstimatesLoading')
|
||||
})
|
||||
})
|
||||
|
||||
describe('renderInfoRows', () => {
|
||||
it('should render the info rows with the passed data', () => {
|
||||
const baseClassName = 'gas-modal-content__info-row'
|
||||
|
@ -157,6 +157,7 @@ describe('gas-modal-page-container container', () => {
|
||||
},
|
||||
insufficientBalance: true,
|
||||
isSpeedUp: false,
|
||||
isRetry: false,
|
||||
txId: 34,
|
||||
isEthereumNetwork: true,
|
||||
isMainnet: true,
|
||||
@ -297,19 +298,19 @@ describe('gas-modal-page-container container', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('convertThenUpdateCustomGasPrice()', () => {
|
||||
it('should dispatch a setCustomGasPrice action with the arg passed to convertThenUpdateCustomGasPrice converted to WEI', () => {
|
||||
mapDispatchToPropsObject.convertThenUpdateCustomGasPrice('0xffff')
|
||||
describe('updateCustomGasPrice()', () => {
|
||||
it('should dispatch a setCustomGasPrice action', () => {
|
||||
mapDispatchToPropsObject.updateCustomGasPrice('0xffff')
|
||||
assert(dispatchSpy.calledOnce)
|
||||
assert(gasActionSpies.setCustomGasPrice.calledOnce)
|
||||
assert.equal(gasActionSpies.setCustomGasPrice.getCall(0).args[0], '0x3b9a8e653600')
|
||||
assert.equal(gasActionSpies.setCustomGasPrice.getCall(0).args[0], '0xffff')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe('convertThenUpdateCustomGasLimit()', () => {
|
||||
it('should dispatch a setCustomGasLimit action with the arg passed to convertThenUpdateCustomGasLimit converted to hex', () => {
|
||||
mapDispatchToPropsObject.convertThenUpdateCustomGasLimit(16)
|
||||
describe('updateCustomGasLimit()', () => {
|
||||
it('should dispatch a setCustomGasLimit action', () => {
|
||||
mapDispatchToPropsObject.updateCustomGasLimit('0x10')
|
||||
assert(dispatchSpy.calledOnce)
|
||||
assert(gasActionSpies.setCustomGasLimit.calledOnce)
|
||||
assert.equal(gasActionSpies.setCustomGasLimit.getCall(0).args[0], '0x10')
|
||||
|
@ -2,9 +2,11 @@ import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ButtonGroup from '../../../ui/button-group'
|
||||
import Button from '../../../ui/button'
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../../helpers/constants/common'
|
||||
|
||||
|
||||
const GAS_OBJECT_PROPTYPES_SHAPE = {
|
||||
label: PropTypes.string,
|
||||
gasEstimateType: PropTypes.oneOf(Object.values(GAS_ESTIMATE_TYPES)).isRequired,
|
||||
feeInPrimaryCurrency: PropTypes.string,
|
||||
feeInSecondaryCurrency: PropTypes.string,
|
||||
timeEstimate: PropTypes.string,
|
||||
@ -27,8 +29,19 @@ export default class GasPriceButtonGroup extends Component {
|
||||
showCheck: PropTypes.bool,
|
||||
}
|
||||
|
||||
gasEstimateTypeLabel (gasEstimateType) {
|
||||
if (gasEstimateType === GAS_ESTIMATE_TYPES.SLOW) {
|
||||
return this.context.t('slow')
|
||||
} else if (gasEstimateType === GAS_ESTIMATE_TYPES.AVERAGE) {
|
||||
return this.context.t('average')
|
||||
} else if (gasEstimateType === GAS_ESTIMATE_TYPES.FAST) {
|
||||
return this.context.t('fast')
|
||||
}
|
||||
throw new Error(`Unrecognized gas estimate type: ${gasEstimateType}`)
|
||||
}
|
||||
|
||||
renderButtonContent ({
|
||||
labelKey,
|
||||
gasEstimateType,
|
||||
feeInPrimaryCurrency,
|
||||
feeInSecondaryCurrency,
|
||||
timeEstimate,
|
||||
@ -37,7 +50,7 @@ export default class GasPriceButtonGroup extends Component {
|
||||
showCheck,
|
||||
}) {
|
||||
return (<div>
|
||||
{ labelKey && <div className={`${className}__label`}>{ this.context.t(labelKey) }</div> }
|
||||
{ gasEstimateType && <div className={`${className}__label`}>{ this.gasEstimateTypeLabel(gasEstimateType) }</div> }
|
||||
{ timeEstimate && <div className={`${className}__time-estimate`}>{ timeEstimate }</div> }
|
||||
{ feeInPrimaryCurrency && <div className={`${className}__primary-currency`}>{ feeInPrimaryCurrency }</div> }
|
||||
{ feeInSecondaryCurrency && <div className={`${className}__secondary-currency`}>{ feeInSecondaryCurrency }</div> }
|
||||
|
@ -156,15 +156,15 @@ describe('GasPriceButtonGroup Component', function () {
|
||||
})
|
||||
|
||||
describe('renderButtonContent', () => {
|
||||
it('should render a label if passed a labelKey', () => {
|
||||
it('should render a label if passed a gasEstimateType', () => {
|
||||
const renderButtonContentResult = wrapper.instance().renderButtonContent({
|
||||
labelKey: 'mockLabelKey',
|
||||
gasEstimateType: 'SLOW',
|
||||
}, {
|
||||
className: 'someClass',
|
||||
})
|
||||
const wrappedRenderButtonContentResult = shallow(renderButtonContentResult)
|
||||
assert.equal(wrappedRenderButtonContentResult.childAt(0).children().length, 1)
|
||||
assert.equal(wrappedRenderButtonContentResult.find('.someClass__label').text(), 'mockLabelKey')
|
||||
assert.equal(wrappedRenderButtonContentResult.find('.someClass__label').text(), 'slow')
|
||||
})
|
||||
|
||||
it('should render a feeInPrimaryCurrency if passed a feeInPrimaryCurrency', () => {
|
||||
@ -211,7 +211,7 @@ describe('GasPriceButtonGroup Component', function () {
|
||||
|
||||
it('should render all elements if all args passed', () => {
|
||||
const renderButtonContentResult = wrapper.instance().renderButtonContent({
|
||||
labelKey: 'mockLabel',
|
||||
gasEstimateType: 'SLOW',
|
||||
feeInPrimaryCurrency: 'mockFeeInPrimaryCurrency',
|
||||
feeInSecondaryCurrency: 'mockFeeInSecondaryCurrency',
|
||||
timeEstimate: 'mockTimeEstimate',
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
export default class AdvancedTabContent extends Component {
|
||||
export default class GasSlider extends Component {
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
lowLabel: PropTypes.string,
|
||||
|
@ -7,15 +7,17 @@ export default class ProviderPageContainerContent extends PureComponent {
|
||||
origin: PropTypes.string.isRequired,
|
||||
selectedIdentity: PropTypes.object.isRequired,
|
||||
siteImage: PropTypes.string,
|
||||
siteTitle: PropTypes.string.isRequired,
|
||||
siteTitle: PropTypes.string,
|
||||
hostname: PropTypes.string,
|
||||
extensionId: PropTypes.string,
|
||||
}
|
||||
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
renderConnectVisual = () => {
|
||||
const { origin, selectedIdentity, siteImage, siteTitle } = this.props
|
||||
renderConnectVisual = (title, identifier) => {
|
||||
const { selectedIdentity, siteImage } = this.props
|
||||
|
||||
return (
|
||||
<div className="provider-approval-visual">
|
||||
@ -27,11 +29,11 @@ export default class ProviderPageContainerContent extends PureComponent {
|
||||
/>
|
||||
) : (
|
||||
<i className="provider-approval-visual__identicon--default">
|
||||
{siteTitle.charAt(0).toUpperCase()}
|
||||
{title.charAt(0).toUpperCase()}
|
||||
</i>
|
||||
)}
|
||||
<h1>{siteTitle}</h1>
|
||||
<h2>{origin}</h2>
|
||||
<h1>{title}</h1>
|
||||
<h2>{identifier}</h2>
|
||||
</section>
|
||||
<span className="provider-approval-visual__check" />
|
||||
<section>
|
||||
@ -47,15 +49,23 @@ export default class ProviderPageContainerContent extends PureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { siteTitle } = this.props
|
||||
const { siteTitle, hostname, extensionId } = this.props
|
||||
const { t } = this.context
|
||||
|
||||
const title = extensionId ?
|
||||
'External Extension' :
|
||||
siteTitle || hostname
|
||||
|
||||
const identifier = extensionId ?
|
||||
`Extension ID: '${extensionId}'` :
|
||||
hostname
|
||||
|
||||
return (
|
||||
<div className="provider-approval-container__content">
|
||||
<section>
|
||||
<h2>{t('connectRequest')}</h2>
|
||||
{this.renderConnectVisual()}
|
||||
<h1>{t('providerRequest', [siteTitle])}</h1>
|
||||
{this.renderConnectVisual(title, identifier)}
|
||||
<h1>{t('providerRequest', [title])}</h1>
|
||||
<p>
|
||||
{t('providerRequestInfo')}
|
||||
<br/>
|
||||
|
@ -9,7 +9,9 @@ export default class ProviderPageContainer extends PureComponent {
|
||||
rejectProviderRequestByOrigin: PropTypes.func.isRequired,
|
||||
origin: PropTypes.string.isRequired,
|
||||
siteImage: PropTypes.string,
|
||||
siteTitle: PropTypes.string.isRequired,
|
||||
siteTitle: PropTypes.string,
|
||||
hostname: PropTypes.string,
|
||||
extensionId: PropTypes.string,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
@ -52,7 +54,7 @@ export default class ProviderPageContainer extends PureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const {origin, siteImage, siteTitle} = this.props
|
||||
const {origin, siteImage, siteTitle, hostname, extensionId} = this.props
|
||||
|
||||
return (
|
||||
<div className="page-container provider-approval-container">
|
||||
@ -61,6 +63,8 @@ export default class ProviderPageContainer extends PureComponent {
|
||||
origin={origin}
|
||||
siteImage={siteImage}
|
||||
siteTitle={siteTitle}
|
||||
hostname={hostname}
|
||||
extensionId={extensionId}
|
||||
/>
|
||||
<PageContainerFooter
|
||||
onCancel={() => this.onCancel()}
|
||||
|
@ -70,7 +70,7 @@ describe('TransactionListItemDetails Component', () => {
|
||||
const wrapper = shallow(
|
||||
<TransactionListItemDetails
|
||||
transactionGroup={transactionGroup}
|
||||
showRetry={true}
|
||||
showSpeedUp={true}
|
||||
/>,
|
||||
{ context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } }
|
||||
)
|
||||
|
@ -21,6 +21,7 @@ export default class TransactionListItemDetails extends PureComponent {
|
||||
onCancel: PropTypes.func,
|
||||
onRetry: PropTypes.func,
|
||||
showCancel: PropTypes.bool,
|
||||
showSpeedUp: PropTypes.bool,
|
||||
showRetry: PropTypes.bool,
|
||||
isEarliestNonce: PropTypes.bool,
|
||||
cancelDisabled: PropTypes.bool,
|
||||
@ -123,6 +124,7 @@ export default class TransactionListItemDetails extends PureComponent {
|
||||
const { justCopied } = this.state
|
||||
const {
|
||||
transactionGroup,
|
||||
showSpeedUp,
|
||||
showRetry,
|
||||
onCancel,
|
||||
onRetry,
|
||||
@ -138,7 +140,7 @@ export default class TransactionListItemDetails extends PureComponent {
|
||||
<div>{ t('details') }</div>
|
||||
<div className="transaction-list-item-details__header-buttons">
|
||||
{
|
||||
showRetry && (
|
||||
showSpeedUp && (
|
||||
<Button
|
||||
type="raised"
|
||||
onClick={this.handleRetry}
|
||||
@ -172,6 +174,17 @@ export default class TransactionListItemDetails extends PureComponent {
|
||||
<img src="/images/arrow-popout.svg" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
{
|
||||
showRetry && <Tooltip title={blockExplorerUrl ? t('viewOnCustomBlockExplorer', [blockExplorerUrl]) : t('retryTransaction')}>
|
||||
<Button
|
||||
type="raised"
|
||||
onClick={this.handleRetry}
|
||||
className="transaction-list-item-details__header-button"
|
||||
>
|
||||
<i className="fa fa-refresh"></i>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className="transaction-list-item-details__body">
|
||||
|
@ -17,6 +17,7 @@
|
||||
grid-template-areas:
|
||||
"identicon action status primary-amount"
|
||||
"identicon nonce status secondary-amount";
|
||||
grid-template-rows: 24px;
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
padding: .5rem 1rem;
|
||||
@ -25,6 +26,7 @@
|
||||
"nonce nonce nonce"
|
||||
"identicon action primary-amount"
|
||||
"identicon status secondary-amount";
|
||||
grid-template-rows: auto 24px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
@ -24,7 +24,7 @@ export default class TransactionListItem extends PureComponent {
|
||||
showCancelModal: PropTypes.func,
|
||||
showCancel: PropTypes.bool,
|
||||
hasEnoughCancelGas: PropTypes.bool,
|
||||
showRetry: PropTypes.bool,
|
||||
showSpeedUp: PropTypes.bool,
|
||||
isEarliestNonce: PropTypes.bool,
|
||||
showFiat: PropTypes.bool,
|
||||
token: PropTypes.object,
|
||||
@ -177,7 +177,7 @@ export default class TransactionListItem extends PureComponent {
|
||||
primaryTransaction,
|
||||
showCancel,
|
||||
hasEnoughCancelGas,
|
||||
showRetry,
|
||||
showSpeedUp,
|
||||
tokenData,
|
||||
transactionGroup,
|
||||
rpcPrefs,
|
||||
@ -233,7 +233,8 @@ export default class TransactionListItem extends PureComponent {
|
||||
<TransactionListItemDetails
|
||||
transactionGroup={transactionGroup}
|
||||
onRetry={this.handleRetry}
|
||||
showRetry={showRetry}
|
||||
showSpeedUp={showSpeedUp}
|
||||
showRetry={getStatusKey(primaryTransaction) === 'failed'}
|
||||
isEarliestNonce={isEarliestNonce}
|
||||
onCancel={this.handleCancel}
|
||||
showCancel={showCancel}
|
||||
|
@ -37,7 +37,7 @@ export default class TransactionList extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
shouldShowRetry = (transactionGroup, isEarliestNonce) => {
|
||||
shouldShowSpeedUp = (transactionGroup, isEarliestNonce) => {
|
||||
const { transactions = [], hasRetried } = transactionGroup
|
||||
const [earliestTransaction = {}] = transactions
|
||||
const { submittedTime } = earliestTransaction
|
||||
@ -100,7 +100,7 @@ export default class TransactionList extends PureComponent {
|
||||
<TransactionListItem
|
||||
transactionGroup={transactionGroup}
|
||||
key={`${transactionGroup.nonce}:${index}`}
|
||||
showRetry={isPendingTx && this.shouldShowRetry(transactionGroup, index === 0)}
|
||||
showSpeedUp={isPendingTx && this.shouldShowSpeedUp(transactionGroup, index === 0)}
|
||||
showCancel={isPendingTx && this.shouldShowCancel(transactionGroup)}
|
||||
isEarliestNonce={isPendingTx && index === 0}
|
||||
token={selectedToken}
|
||||
|
@ -50,6 +50,7 @@
|
||||
color: $curious-blue;
|
||||
border-bottom: 3px solid $curious-blue;
|
||||
cursor: initial;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,3 +12,9 @@ export const NETWORK_TYPES = {
|
||||
ROPSTEN: 'ropsten',
|
||||
GOERLI: 'goerli',
|
||||
}
|
||||
|
||||
export const GAS_ESTIMATE_TYPES = {
|
||||
SLOW: 'SLOW',
|
||||
AVERAGE: 'AVERAGE',
|
||||
FAST: 'FAST',
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ const customDimensionsNameIdMap = {
|
||||
}
|
||||
|
||||
function composeUrlRefParamAddition (previousPath, confirmTransactionOrigin) {
|
||||
const externalOrigin = confirmTransactionOrigin && confirmTransactionOrigin !== 'MetaMask'
|
||||
const externalOrigin = confirmTransactionOrigin && confirmTransactionOrigin !== 'metamask'
|
||||
return `&urlref=${externalOrigin ? 'EXTERNAL' : encodeURIComponent(previousPath.replace(/chrome-extension:\/\/\w+/, METAMETRICS_TRACKING_URL))}`
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
import { CONFIRMED_STATUS, DROPPED_STATUS } from '../../helpers/constants/transactions'
|
||||
import UserPreferencedCurrencyDisplay from '../../components/app/user-preferenced-currency-display'
|
||||
import { PRIMARY, SECONDARY } from '../../helpers/constants/common'
|
||||
import { hexToDecimal } from '../../helpers/utils/conversions.util'
|
||||
import AdvancedGasInputs from '../../components/app/gas-customization/advanced-gas-inputs'
|
||||
import TextField from '../../components/ui/text-field'
|
||||
|
||||
@ -171,7 +172,7 @@ export default class ConfirmTransactionBase extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
if (customGas.gasLimit < 21000) {
|
||||
if (hexToDecimal(customGas.gasLimit) < 21000) {
|
||||
return {
|
||||
valid: false,
|
||||
errorKey: GAS_LIMIT_TOO_LOW_ERROR_KEY,
|
||||
|
79
ui/app/pages/create-account/create-account.component.js
Normal file
@ -0,0 +1,79 @@
|
||||
import React, { Component } from 'react'
|
||||
import { Switch, Route, matchPath } from 'react-router-dom'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import NewAccountCreateForm from './new-account.container'
|
||||
import NewAccountImportForm from './import-account'
|
||||
import ConnectHardwareForm from './connect-hardware'
|
||||
import {
|
||||
NEW_ACCOUNT_ROUTE,
|
||||
IMPORT_ACCOUNT_ROUTE,
|
||||
CONNECT_HARDWARE_ROUTE,
|
||||
} from '../../helpers/constants/routes'
|
||||
|
||||
export default class CreateAccountPage extends Component {
|
||||
renderTabs () {
|
||||
const { history, location: { pathname }} = this.props
|
||||
const getClassNames = path => classnames('new-account__tabs__tab', {
|
||||
'new-account__tabs__selected': matchPath(pathname, {
|
||||
path,
|
||||
exact: true,
|
||||
}),
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="new-account__tabs">
|
||||
<div className={getClassNames(NEW_ACCOUNT_ROUTE)} onClick={() => history.push(NEW_ACCOUNT_ROUTE)}>{
|
||||
this.context.t('create')
|
||||
}</div>
|
||||
<div className={getClassNames(IMPORT_ACCOUNT_ROUTE)} onClick={() => history.push(IMPORT_ACCOUNT_ROUTE)}>{
|
||||
this.context.t('import')
|
||||
}</div>
|
||||
<div className={getClassNames(CONNECT_HARDWARE_ROUTE)} onClick={() => history.push(CONNECT_HARDWARE_ROUTE)}>{
|
||||
this.context.t('connect')
|
||||
}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className="new-account">
|
||||
<div className="new-account__header">
|
||||
<div className={`new-account__header ${this.context.t('newAccount')}`}>
|
||||
{this.renderTabs()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="new-account__form">
|
||||
<Switch>
|
||||
<Route
|
||||
exact={true}
|
||||
path={NEW_ACCOUNT_ROUTE}
|
||||
component={NewAccountCreateForm}
|
||||
/>
|
||||
<Route
|
||||
exact={true}
|
||||
path={IMPORT_ACCOUNT_ROUTE}
|
||||
component={NewAccountImportForm}
|
||||
/>
|
||||
<Route
|
||||
exact={true}
|
||||
path={CONNECT_HARDWARE_ROUTE}
|
||||
component={ConnectHardwareForm}
|
||||
/>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
CreateAccountPage.propTypes = {
|
||||
location: PropTypes.object,
|
||||
history: PropTypes.object,
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
CreateAccountPage.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
20
ui/app/pages/create-account/create-account.container.js
Normal file
@ -0,0 +1,20 @@
|
||||
import { connect } from 'react-redux'
|
||||
import actions from '../../store/actions'
|
||||
import { getCurrentViewContext } from '../../selectors/selectors'
|
||||
import CreateAccountPage from './create-account.component'
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
displayedForm: getCurrentViewContext(state),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
displayForm: form => dispatch(actions.setNewAccountForm(form)),
|
||||
showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)),
|
||||
showExportPrivateKeyModal: () => {
|
||||
dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' }))
|
||||
},
|
||||
hideModal: () => dispatch(actions.hideModal()),
|
||||
setAccountLabel: (address, label) => dispatch(actions.setAccountLabel(address, label)),
|
||||
})
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(CreateAccountPage)
|
@ -1,113 +1 @@
|
||||
const Component = require('react').Component
|
||||
const { Switch, Route, matchPath } = require('react-router-dom')
|
||||
const PropTypes = require('prop-types')
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../store/actions')
|
||||
const { getCurrentViewContext } = require('../../selectors/selectors')
|
||||
const classnames = require('classnames')
|
||||
const NewAccountCreateForm = require('./new-account')
|
||||
const NewAccountImportForm = require('./import-account')
|
||||
const ConnectHardwareForm = require('./connect-hardware')
|
||||
const {
|
||||
NEW_ACCOUNT_ROUTE,
|
||||
IMPORT_ACCOUNT_ROUTE,
|
||||
CONNECT_HARDWARE_ROUTE,
|
||||
} = require('../../helpers/constants/routes')
|
||||
|
||||
class CreateAccountPage extends Component {
|
||||
renderTabs () {
|
||||
const { history, location } = this.props
|
||||
|
||||
return h('div.new-account__tabs', [
|
||||
h('div.new-account__tabs__tab', {
|
||||
className: classnames('new-account__tabs__tab', {
|
||||
'new-account__tabs__selected': matchPath(location.pathname, {
|
||||
path: NEW_ACCOUNT_ROUTE, exact: true,
|
||||
}),
|
||||
}),
|
||||
onClick: () => history.push(NEW_ACCOUNT_ROUTE),
|
||||
}, [
|
||||
this.context.t('create'),
|
||||
]),
|
||||
|
||||
h('div.new-account__tabs__tab', {
|
||||
className: classnames('new-account__tabs__tab', {
|
||||
'new-account__tabs__selected': matchPath(location.pathname, {
|
||||
path: IMPORT_ACCOUNT_ROUTE, exact: true,
|
||||
}),
|
||||
}),
|
||||
onClick: () => history.push(IMPORT_ACCOUNT_ROUTE),
|
||||
}, [
|
||||
this.context.t('import'),
|
||||
]),
|
||||
h(
|
||||
'div.new-account__tabs__tab',
|
||||
{
|
||||
className: classnames('new-account__tabs__tab', {
|
||||
'new-account__tabs__selected': matchPath(location.pathname, {
|
||||
path: CONNECT_HARDWARE_ROUTE,
|
||||
exact: true,
|
||||
}),
|
||||
}),
|
||||
onClick: () => history.push(CONNECT_HARDWARE_ROUTE),
|
||||
},
|
||||
this.context.t('connect')
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
render () {
|
||||
return h('div.new-account', {}, [
|
||||
h('div.new-account__header', [
|
||||
h('div.new-account__title', this.context.t('newAccount')),
|
||||
this.renderTabs(),
|
||||
]),
|
||||
h('div.new-account__form', [
|
||||
h(Switch, [
|
||||
h(Route, {
|
||||
exact: true,
|
||||
path: NEW_ACCOUNT_ROUTE,
|
||||
component: NewAccountCreateForm,
|
||||
}),
|
||||
h(Route, {
|
||||
exact: true,
|
||||
path: IMPORT_ACCOUNT_ROUTE,
|
||||
component: NewAccountImportForm,
|
||||
}),
|
||||
h(Route, {
|
||||
exact: true,
|
||||
path: CONNECT_HARDWARE_ROUTE,
|
||||
component: ConnectHardwareForm,
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
CreateAccountPage.propTypes = {
|
||||
location: PropTypes.object,
|
||||
history: PropTypes.object,
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
CreateAccountPage.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
displayedForm: getCurrentViewContext(state),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
displayForm: form => dispatch(actions.setNewAccountForm(form)),
|
||||
showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)),
|
||||
showExportPrivateKeyModal: () => {
|
||||
dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' }))
|
||||
},
|
||||
hideModal: () => dispatch(actions.hideModal()),
|
||||
setAccountLabel: (address, label) => dispatch(actions.setAccountLabel(address, label)),
|
||||
})
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(CreateAccountPage)
|
||||
export { default } from './create-account.container'
|
||||
|
91
ui/app/pages/create-account/new-account.component.js
Normal file
@ -0,0 +1,91 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { DEFAULT_ROUTE } from '../../helpers/constants/routes'
|
||||
import Button from '../../components/ui/button'
|
||||
|
||||
export default class NewAccountCreateForm extends Component {
|
||||
constructor (props, context) {
|
||||
super(props)
|
||||
const { newAccountNumber = 0 } = props
|
||||
|
||||
this.state = {
|
||||
newAccountName: '',
|
||||
defaultAccountName: context.t('newAccountNumberName', [newAccountNumber]),
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { newAccountName, defaultAccountName } = this.state
|
||||
const { history, createAccount } = this.props
|
||||
const createClick = _ => {
|
||||
createAccount(newAccountName || defaultAccountName)
|
||||
.then(() => {
|
||||
this.context.metricsEvent({
|
||||
eventOpts: {
|
||||
category: 'Accounts',
|
||||
action: 'Add New Account',
|
||||
name: 'Added New Account',
|
||||
},
|
||||
})
|
||||
history.push(DEFAULT_ROUTE)
|
||||
})
|
||||
.catch((e) => {
|
||||
this.context.metricsEvent({
|
||||
eventOpts: {
|
||||
category: 'Accounts',
|
||||
action: 'Add New Account',
|
||||
name: 'Error',
|
||||
},
|
||||
customVariables: {
|
||||
errorMessage: e.message,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="new-account-create-form">
|
||||
<div className="new-account-create-form__input-label">
|
||||
{this.context.t('accountName')}
|
||||
</div>
|
||||
<div className="new-account-create-form__input-wrapper">
|
||||
<input className="new-account-create-form__input"
|
||||
value={newAccountName}
|
||||
placeholder={defaultAccountName}
|
||||
onChange={event => this.setState({ newAccountName: event.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="new-account-create-form__buttons">
|
||||
<Button
|
||||
type="default"
|
||||
large={true}
|
||||
className="new-account-create-form__button"
|
||||
onClick={() => history.push(DEFAULT_ROUTE)}
|
||||
>{this.context.t('cancel')}</Button>
|
||||
<Button
|
||||
type="secondary"
|
||||
large={true}
|
||||
className="new-account-create-form__button"
|
||||
onClick={createClick}
|
||||
>{this.context.t('create')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
NewAccountCreateForm.propTypes = {
|
||||
hideModal: PropTypes.func,
|
||||
showImportPage: PropTypes.func,
|
||||
showConnectPage: PropTypes.func,
|
||||
createAccount: PropTypes.func,
|
||||
numberOfExistingAccounts: PropTypes.number,
|
||||
newAccountNumber: PropTypes.number,
|
||||
history: PropTypes.object,
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
NewAccountCreateForm.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
metricsEvent: PropTypes.func,
|
||||
}
|
35
ui/app/pages/create-account/new-account.container.js
Normal file
@ -0,0 +1,35 @@
|
||||
import { connect } from 'react-redux'
|
||||
import actions from '../../store/actions'
|
||||
import NewAccountCreateForm from './new-account.component'
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const { metamask: { network, selectedAddress, identities = {} } } = state
|
||||
const numberOfExistingAccounts = Object.keys(identities).length
|
||||
const newAccountNumber = numberOfExistingAccounts + 1
|
||||
|
||||
return {
|
||||
network,
|
||||
address: selectedAddress,
|
||||
numberOfExistingAccounts,
|
||||
newAccountNumber,
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
toCoinbase: address => dispatch(actions.buyEth({ network: '1', address, amount: 0 })),
|
||||
hideModal: () => dispatch(actions.hideModal()),
|
||||
createAccount: newAccountName => {
|
||||
return dispatch(actions.addNewAccount())
|
||||
.then(newAccountAddress => {
|
||||
if (newAccountName) {
|
||||
dispatch(actions.setAccountLabel(newAccountAddress, newAccountName))
|
||||
}
|
||||
})
|
||||
},
|
||||
showImportPage: () => dispatch(actions.showImportPage()),
|
||||
showConnectPage: () => dispatch(actions.showConnectPage()),
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(NewAccountCreateForm)
|
@ -1,130 +0,0 @@
|
||||
const { Component } = require('react')
|
||||
const PropTypes = require('prop-types')
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../store/actions')
|
||||
const { DEFAULT_ROUTE } = require('../../helpers/constants/routes')
|
||||
import Button from '../../components/ui/button'
|
||||
|
||||
class NewAccountCreateForm extends Component {
|
||||
constructor (props, context) {
|
||||
super(props)
|
||||
|
||||
const { numberOfExistingAccounts = 0 } = props
|
||||
const newAccountNumber = numberOfExistingAccounts + 1
|
||||
|
||||
this.state = {
|
||||
newAccountName: '',
|
||||
defaultAccountName: context.t('newAccountNumberName', [newAccountNumber]),
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { newAccountName, defaultAccountName } = this.state
|
||||
const { history, createAccount } = this.props
|
||||
|
||||
return h('div.new-account-create-form', [
|
||||
|
||||
h('div.new-account-create-form__input-label', {}, [
|
||||
this.context.t('accountName'),
|
||||
]),
|
||||
|
||||
h('div.new-account-create-form__input-wrapper', {}, [
|
||||
h('input.new-account-create-form__input', {
|
||||
value: newAccountName,
|
||||
placeholder: defaultAccountName,
|
||||
onChange: event => this.setState({ newAccountName: event.target.value }),
|
||||
}, []),
|
||||
]),
|
||||
|
||||
h('div.new-account-create-form__buttons', {}, [
|
||||
|
||||
h(Button, {
|
||||
type: 'default',
|
||||
large: true,
|
||||
className: 'new-account-create-form__button',
|
||||
onClick: () => history.push(DEFAULT_ROUTE),
|
||||
}, [this.context.t('cancel')]),
|
||||
|
||||
h(Button, {
|
||||
type: 'secondary',
|
||||
large: true,
|
||||
className: 'new-account-create-form__button',
|
||||
onClick: () => {
|
||||
createAccount(newAccountName || defaultAccountName)
|
||||
.then(() => {
|
||||
this.context.metricsEvent({
|
||||
eventOpts: {
|
||||
category: 'Accounts',
|
||||
action: 'Add New Account',
|
||||
name: 'Added New Account',
|
||||
},
|
||||
})
|
||||
history.push(DEFAULT_ROUTE)
|
||||
})
|
||||
.catch((e) => {
|
||||
this.context.metricsEvent({
|
||||
eventOpts: {
|
||||
category: 'Accounts',
|
||||
action: 'Add New Account',
|
||||
name: 'Error',
|
||||
},
|
||||
customVariables: {
|
||||
errorMessage: e.message,
|
||||
},
|
||||
})
|
||||
})
|
||||
},
|
||||
}, [this.context.t('create')]),
|
||||
|
||||
]),
|
||||
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
NewAccountCreateForm.propTypes = {
|
||||
hideModal: PropTypes.func,
|
||||
showImportPage: PropTypes.func,
|
||||
showConnectPage: PropTypes.func,
|
||||
createAccount: PropTypes.func,
|
||||
numberOfExistingAccounts: PropTypes.number,
|
||||
history: PropTypes.object,
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const { metamask: { network, selectedAddress, identities = {} } } = state
|
||||
const numberOfExistingAccounts = Object.keys(identities).length
|
||||
|
||||
return {
|
||||
network,
|
||||
address: selectedAddress,
|
||||
numberOfExistingAccounts,
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
toCoinbase: address => dispatch(actions.buyEth({ network: '1', address, amount: 0 })),
|
||||
hideModal: () => dispatch(actions.hideModal()),
|
||||
createAccount: newAccountName => {
|
||||
return dispatch(actions.addNewAccount())
|
||||
.then(newAccountAddress => {
|
||||
if (newAccountName) {
|
||||
dispatch(actions.setAccountLabel(newAccountAddress, newAccountName))
|
||||
}
|
||||
})
|
||||
},
|
||||
showImportPage: () => dispatch(actions.showImportPage()),
|
||||
showConnectPage: () => dispatch(actions.showConnectPage()),
|
||||
}
|
||||
}
|
||||
|
||||
NewAccountCreateForm.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
metricsEvent: PropTypes.func,
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(NewAccountCreateForm)
|
||||
|
@ -6,7 +6,13 @@ export default class ProviderApproval extends Component {
|
||||
static propTypes = {
|
||||
approveProviderRequestByOrigin: PropTypes.func.isRequired,
|
||||
rejectProviderRequestByOrigin: PropTypes.func.isRequired,
|
||||
providerRequest: PropTypes.object.isRequired,
|
||||
providerRequest: PropTypes.exact({
|
||||
hostname: PropTypes.string.isRequired,
|
||||
siteImage: PropTypes.string,
|
||||
siteTitle: PropTypes.string,
|
||||
origin: PropTypes.string.isRequired,
|
||||
extensionId: PropTypes.string,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
@ -20,9 +26,10 @@ export default class ProviderApproval extends Component {
|
||||
approveProviderRequestByOrigin={approveProviderRequestByOrigin}
|
||||
rejectProviderRequestByOrigin={rejectProviderRequestByOrigin}
|
||||
origin={providerRequest.origin}
|
||||
tabID={providerRequest.tabID}
|
||||
siteImage={providerRequest.siteImage}
|
||||
siteTitle={providerRequest.siteTitle}
|
||||
hostname={providerRequest.hostname}
|
||||
extensionId={providerRequest.extensionId}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import classnames from 'classnames'
|
||||
// init
|
||||
import FirstTimeFlow from '../first-time-flow'
|
||||
// accounts
|
||||
const SendTransactionScreen = require('../send/send.container')
|
||||
import SendTransactionScreen from '../send'
|
||||
const ConfirmTransaction = require('../confirm-transaction')
|
||||
|
||||
// slideout menu
|
||||
@ -31,7 +31,7 @@ const MobileSyncPage = require('../mobile-sync')
|
||||
const AddTokenPage = require('../add-token')
|
||||
const ConfirmAddTokenPage = require('../confirm-add-token')
|
||||
const ConfirmAddSuggestedTokenPage = require('../confirm-add-suggested-token')
|
||||
const CreateAccountPage = require('../create-account')
|
||||
import CreateAccountPage from '../create-account'
|
||||
|
||||
const Loading = require('../../components/ui/loading-screen')
|
||||
const LoadingNetwork = require('../../components/app/loading-network-screen').default
|
||||
@ -206,6 +206,10 @@ class Routes extends Component {
|
||||
}
|
||||
: null
|
||||
|
||||
const sidebarShouldClose = sidebarTransaction &&
|
||||
!sidebarTransaction.status === 'failed' &&
|
||||
!submittedPendingTransactions.find(({ id }) => id === sidebarTransaction.id)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames('app', { 'mouse-user-styles': isMouseUser})}
|
||||
@ -232,7 +236,7 @@ class Routes extends Component {
|
||||
}
|
||||
<Sidebar
|
||||
sidebarOpen={sidebarIsOpen}
|
||||
sidebarShouldClose={sidebarTransaction && !submittedPendingTransactions.find(({ id }) => id === sidebarTransaction.id)}
|
||||
sidebarShouldClose={sidebarShouldClose}
|
||||
hideSidebar={this.props.hideSidebar}
|
||||
transitionName={sidebarTransitionName}
|
||||
type={sidebarType}
|
||||
|
@ -27,7 +27,7 @@ export default class SendFooter extends Component {
|
||||
unapprovedTxs: PropTypes.object,
|
||||
update: PropTypes.func,
|
||||
sendErrors: PropTypes.object,
|
||||
gasChangedLabel: PropTypes.string,
|
||||
gasEstimateType: PropTypes.string,
|
||||
}
|
||||
|
||||
static contextTypes = {
|
||||
@ -58,7 +58,7 @@ export default class SendFooter extends Component {
|
||||
update,
|
||||
toAccounts,
|
||||
history,
|
||||
gasChangedLabel,
|
||||
gasEstimateType,
|
||||
} = this.props
|
||||
const { metricsEvent } = this.context
|
||||
|
||||
@ -94,7 +94,7 @@ export default class SendFooter extends Component {
|
||||
name: 'Complete',
|
||||
},
|
||||
customVariables: {
|
||||
gasChanged: gasChangedLabel,
|
||||
gasChanged: gasEstimateType,
|
||||
},
|
||||
})
|
||||
history.push(CONFIRM_TRANSACTION_ROUTE)
|
||||
@ -102,9 +102,10 @@ export default class SendFooter extends Component {
|
||||
}
|
||||
|
||||
formShouldBeDisabled () {
|
||||
const { data, inError, selectedToken, tokenBalance, gasTotal, to } = this.props
|
||||
const { data, inError, selectedToken, tokenBalance, gasTotal, to, gasLimit } = this.props
|
||||
const missingTokenBalance = selectedToken && !tokenBalance
|
||||
const shouldBeDisabled = inError || !gasTotal || missingTokenBalance || !(data || to)
|
||||
const gasLimitTooLow = gasLimit < 5208 // 5208 is hex value of 21000, minimum gas limit
|
||||
const shouldBeDisabled = inError || !gasTotal || missingTokenBalance || !(data || to) || gasLimitTooLow
|
||||
return shouldBeDisabled
|
||||
}
|
||||
|
||||
|
@ -42,8 +42,8 @@ function mapStateToProps (state) {
|
||||
const gasButtonInfo = getRenderableEstimateDataForSmallButtonsFromGWEI(state)
|
||||
const gasPrice = getGasPrice(state)
|
||||
const activeButtonIndex = getDefaultActiveButtonIndex(gasButtonInfo, gasPrice)
|
||||
const gasChangedLabel = activeButtonIndex >= 0
|
||||
? gasButtonInfo[activeButtonIndex].labelKey
|
||||
const gasEstimateType = activeButtonIndex >= 0
|
||||
? gasButtonInfo[activeButtonIndex].gasEstimateType
|
||||
: 'custom'
|
||||
|
||||
return {
|
||||
@ -61,7 +61,7 @@ function mapStateToProps (state) {
|
||||
tokenBalance: getTokenBalance(state),
|
||||
unapprovedTxs: getUnapprovedTxs(state),
|
||||
sendErrors: getSendErrors(state),
|
||||
gasChangedLabel,
|
||||
gasEstimateType,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ proxyquire('../send-footer.container.js', {
|
||||
'./send-footer.selectors': { isSendFormInError: (s) => `mockInError:${s}` },
|
||||
'./send-footer.utils': utilsStubs,
|
||||
'../../../selectors/custom-gas': {
|
||||
getRenderableEstimateDataForSmallButtonsFromGWEI: (s) => ([{ labelKey: `mockLabel:${s}` }]),
|
||||
getRenderableEstimateDataForSmallButtonsFromGWEI: (s) => ([{ gasEstimateType: `mockGasEstimateType:${s}` }]),
|
||||
getDefaultActiveButtonIndex: () => 0,
|
||||
},
|
||||
})
|
||||
@ -73,7 +73,7 @@ describe('send-footer container', () => {
|
||||
tokenBalance: 'mockTokenBalance:mockState',
|
||||
unapprovedTxs: 'mockUnapprovedTxs:mockState',
|
||||
sendErrors: 'mockSendErrors:mockState',
|
||||
gasChangedLabel: 'mockLabel:mockState',
|
||||
gasEstimateType: 'mockGasEstimateType:mockState',
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -62,11 +62,6 @@ import {
|
||||
SEND_ROUTE,
|
||||
} from '../../helpers/constants/routes'
|
||||
|
||||
module.exports = compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps, mapDispatchToProps)
|
||||
)(SendEther)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
amount: getSendAmount(state),
|
||||
@ -140,3 +135,8 @@ function mapDispatchToProps (dispatch) {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps, mapDispatchToProps)
|
||||
)(SendEther)
|
||||
|
@ -18,22 +18,29 @@ export default class EditContact extends PureComponent {
|
||||
history: PropTypes.object,
|
||||
name: PropTypes.string,
|
||||
address: PropTypes.string,
|
||||
chainId: PropTypes.string,
|
||||
memo: PropTypes.string,
|
||||
viewRoute: PropTypes.string,
|
||||
listRoute: PropTypes.string,
|
||||
setAccountLabel: PropTypes.func,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
name: '',
|
||||
address: '',
|
||||
memo: '',
|
||||
}
|
||||
|
||||
state = {
|
||||
newName: '',
|
||||
newAddress: '',
|
||||
newMemo: '',
|
||||
newName: this.props.name,
|
||||
newAddress: this.props.address,
|
||||
newMemo: this.props.memo,
|
||||
error: '',
|
||||
}
|
||||
|
||||
render () {
|
||||
const { t } = this.context
|
||||
const { history, name, addToAddressBook, removeFromAddressBook, address, memo, viewRoute, listRoute, setAccountLabel } = this.props
|
||||
const { history, name, addToAddressBook, removeFromAddressBook, address, chainId, memo, viewRoute, listRoute, setAccountLabel } = this.props
|
||||
|
||||
return (
|
||||
<div className="settings-page__content-row address-book__edit-contact">
|
||||
@ -43,7 +50,7 @@ export default class EditContact extends PureComponent {
|
||||
type="link"
|
||||
className="settings-page__address-book-button"
|
||||
onClick={() => {
|
||||
removeFromAddressBook(address)
|
||||
removeFromAddressBook(chainId, address)
|
||||
history.push(listRoute)
|
||||
}}
|
||||
>
|
||||
@ -59,7 +66,7 @@ export default class EditContact extends PureComponent {
|
||||
type="text"
|
||||
id="nickname"
|
||||
placeholder={this.context.t('addAlias')}
|
||||
value={this.state.newName || name}
|
||||
value={this.state.newName}
|
||||
onChange={e => this.setState({ newName: e.target.value })}
|
||||
fullWidth
|
||||
margin="dense"
|
||||
@ -73,8 +80,7 @@ export default class EditContact extends PureComponent {
|
||||
<TextField
|
||||
type="text"
|
||||
id="address"
|
||||
placeholder={address}
|
||||
value={this.state.newAddress || address}
|
||||
value={this.state.newAddress}
|
||||
error={this.state.error}
|
||||
onChange={e => this.setState({ newAddress: e.target.value })}
|
||||
fullWidth
|
||||
@ -90,7 +96,7 @@ export default class EditContact extends PureComponent {
|
||||
type="text"
|
||||
id="memo"
|
||||
placeholder={memo}
|
||||
value={this.state.newMemo || memo}
|
||||
value={this.state.newMemo}
|
||||
onChange={e => this.setState({ newMemo: e.target.value })}
|
||||
fullWidth
|
||||
margin="dense"
|
||||
@ -109,12 +115,12 @@ export default class EditContact extends PureComponent {
|
||||
if (this.state.newAddress !== '' && this.state.newAddress !== address) {
|
||||
// if the user makes a valid change to the address field, remove the original address
|
||||
if (isValidAddress(this.state.newAddress)) {
|
||||
removeFromAddressBook(address)
|
||||
removeFromAddressBook(chainId, address)
|
||||
addToAddressBook(this.state.newAddress, this.state.newName || name, this.state.newMemo || memo)
|
||||
setAccountLabel(this.state.newAddress, this.state.newName || name)
|
||||
history.push(listRoute)
|
||||
} else {
|
||||
this.setState({ error: 'invalid address' })
|
||||
this.setState({ error: this.context.t('invalidAddress') })
|
||||
}
|
||||
} else {
|
||||
// update name
|
||||
|
@ -21,10 +21,13 @@ const mapStateToProps = (state, ownProps) => {
|
||||
|
||||
const { memo, name } = getAddressBookEntry(state, address) || state.metamask.identities[address]
|
||||
|
||||
const chainId = state.metamask.network
|
||||
|
||||
const showingMyAccounts = Boolean(pathname.match(CONTACT_MY_ACCOUNTS_EDIT_ROUTE))
|
||||
|
||||
return {
|
||||
address,
|
||||
chainId,
|
||||
name,
|
||||
memo,
|
||||
viewRoute: showingMyAccounts ? CONTACT_MY_ACCOUNTS_VIEW_ROUTE : CONTACT_VIEW_ROUTE,
|
||||
@ -36,7 +39,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
addToAddressBook: (recipient, nickname, memo) => dispatch(addToAddressBook(recipient, nickname, memo)),
|
||||
removeFromAddressBook: (addressToRemove) => dispatch(removeFromAddressBook(addressToRemove)),
|
||||
removeFromAddressBook: (chainId, addressToRemove) => dispatch(removeFromAddressBook(chainId, addressToRemove)),
|
||||
setAccountLabel: (address, label) => dispatch(setAccountLabel(address, label)),
|
||||
}
|
||||
}
|
||||
|
@ -98,6 +98,8 @@
|
||||
|
||||
&--copy-icon {
|
||||
padding-left: 4px;
|
||||
width: 30px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,8 @@ import {
|
||||
} from '../pages/send/send.utils'
|
||||
import { addHexPrefix } from 'ethereumjs-util'
|
||||
|
||||
import { GAS_ESTIMATE_TYPES } from '../helpers/constants/common'
|
||||
|
||||
const selectors = {
|
||||
formatTimeEstimate,
|
||||
getAveragePriceEstimateInHexWEI,
|
||||
@ -250,7 +252,7 @@ function getRenderableBasicEstimateData (state, gasLimit) {
|
||||
|
||||
return [
|
||||
{
|
||||
labelKey: 'slow',
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.SLOW,
|
||||
feeInPrimaryCurrency: getRenderableEthFee(safeLow, gasLimit),
|
||||
feeInSecondaryCurrency: showFiat
|
||||
? getRenderableConvertedCurrencyFee(safeLow, gasLimit, currentCurrency, conversionRate)
|
||||
@ -259,7 +261,7 @@ function getRenderableBasicEstimateData (state, gasLimit) {
|
||||
priceInHexWei: getGasPriceInHexWei(safeLow),
|
||||
},
|
||||
{
|
||||
labelKey: 'average',
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.AVERAGE,
|
||||
feeInPrimaryCurrency: getRenderableEthFee(average, gasLimit),
|
||||
feeInSecondaryCurrency: showFiat
|
||||
? getRenderableConvertedCurrencyFee(average, gasLimit, currentCurrency, conversionRate)
|
||||
@ -268,7 +270,7 @@ function getRenderableBasicEstimateData (state, gasLimit) {
|
||||
priceInHexWei: getGasPriceInHexWei(average),
|
||||
},
|
||||
{
|
||||
labelKey: 'fast',
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.FAST,
|
||||
feeInPrimaryCurrency: getRenderableEthFee(fast, gasLimit),
|
||||
feeInSecondaryCurrency: showFiat
|
||||
? getRenderableConvertedCurrencyFee(fast, gasLimit, currentCurrency, conversionRate)
|
||||
@ -302,7 +304,7 @@ function getRenderableEstimateDataForSmallButtonsFromGWEI (state) {
|
||||
|
||||
return [
|
||||
{
|
||||
labelKey: 'slow',
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.SLOW,
|
||||
feeInSecondaryCurrency: showFiat
|
||||
? getRenderableConvertedCurrencyFee(safeLow, gasLimit, currentCurrency, conversionRate)
|
||||
: '',
|
||||
@ -310,7 +312,7 @@ function getRenderableEstimateDataForSmallButtonsFromGWEI (state) {
|
||||
priceInHexWei: getGasPriceInHexWei(safeLow, true),
|
||||
},
|
||||
{
|
||||
labelKey: 'average',
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.AVERAGE,
|
||||
feeInSecondaryCurrency: showFiat
|
||||
? getRenderableConvertedCurrencyFee(average, gasLimit, currentCurrency, conversionRate)
|
||||
: '',
|
||||
@ -318,7 +320,7 @@ function getRenderableEstimateDataForSmallButtonsFromGWEI (state) {
|
||||
priceInHexWei: getGasPriceInHexWei(average, true),
|
||||
},
|
||||
{
|
||||
labelKey: 'fast',
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.FAST,
|
||||
feeInSecondaryCurrency: showFiat
|
||||
? getRenderableConvertedCurrencyFee(fast, gasLimit, currentCurrency, conversionRate)
|
||||
: '',
|
||||
|
@ -77,21 +77,21 @@ describe('custom-gas selectors', () => {
|
||||
{
|
||||
expectedResult: [
|
||||
{
|
||||
labelKey: 'slow',
|
||||
gasEstimateType: 'SLOW',
|
||||
feeInSecondaryCurrency: '$0.01',
|
||||
feeInPrimaryCurrency: '0.0000525 ETH',
|
||||
timeEstimate: '~6 min 36 sec',
|
||||
priceInHexWei: '0x9502f900',
|
||||
},
|
||||
{
|
||||
labelKey: 'average',
|
||||
gasEstimateType: 'AVERAGE',
|
||||
feeInPrimaryCurrency: '0.000084 ETH',
|
||||
feeInSecondaryCurrency: '$0.02',
|
||||
priceInHexWei: '0xee6b2800',
|
||||
timeEstimate: '~5 min 18 sec',
|
||||
},
|
||||
{
|
||||
labelKey: 'fast',
|
||||
gasEstimateType: 'FAST',
|
||||
feeInSecondaryCurrency: '$0.03',
|
||||
feeInPrimaryCurrency: '0.000105 ETH',
|
||||
timeEstimate: '~3 min 18 sec',
|
||||
@ -127,7 +127,7 @@ describe('custom-gas selectors', () => {
|
||||
{
|
||||
expectedResult: [
|
||||
{
|
||||
labelKey: 'slow',
|
||||
gasEstimateType: 'SLOW',
|
||||
feeInSecondaryCurrency: '$0.27',
|
||||
feeInPrimaryCurrency: '0.000105 ETH',
|
||||
timeEstimate: '~13 min 12 sec',
|
||||
@ -136,12 +136,12 @@ describe('custom-gas selectors', () => {
|
||||
{
|
||||
feeInPrimaryCurrency: '0.000147 ETH',
|
||||
feeInSecondaryCurrency: '$0.38',
|
||||
labelKey: 'average',
|
||||
gasEstimateType: 'AVERAGE',
|
||||
priceInHexWei: '0x1a13b8600',
|
||||
timeEstimate: '~10 min 6 sec',
|
||||
},
|
||||
{
|
||||
labelKey: 'fast',
|
||||
gasEstimateType: 'FAST',
|
||||
feeInSecondaryCurrency: '$0.54',
|
||||
feeInPrimaryCurrency: '0.00021 ETH',
|
||||
timeEstimate: '~6 min 36 sec',
|
||||
@ -180,21 +180,21 @@ describe('custom-gas selectors', () => {
|
||||
{
|
||||
expectedResult: [
|
||||
{
|
||||
labelKey: 'slow',
|
||||
gasEstimateType: 'SLOW',
|
||||
feeInSecondaryCurrency: '',
|
||||
feeInPrimaryCurrency: '0.000105 ETH',
|
||||
timeEstimate: '~13 min 12 sec',
|
||||
priceInHexWei: '0x12a05f200',
|
||||
},
|
||||
{
|
||||
labelKey: 'average',
|
||||
gasEstimateType: 'AVERAGE',
|
||||
feeInPrimaryCurrency: '0.000147 ETH',
|
||||
feeInSecondaryCurrency: '',
|
||||
timeEstimate: '~10 min 6 sec',
|
||||
priceInHexWei: '0x1a13b8600',
|
||||
},
|
||||
{
|
||||
labelKey: 'fast',
|
||||
gasEstimateType: 'FAST',
|
||||
feeInSecondaryCurrency: '',
|
||||
feeInPrimaryCurrency: '0.00021 ETH',
|
||||
timeEstimate: '~6 min 36 sec',
|
||||
@ -233,21 +233,21 @@ describe('custom-gas selectors', () => {
|
||||
{
|
||||
expectedResult: [
|
||||
{
|
||||
labelKey: 'slow',
|
||||
gasEstimateType: 'SLOW',
|
||||
feeInSecondaryCurrency: '$0.27',
|
||||
feeInPrimaryCurrency: '0.000105 ETH',
|
||||
timeEstimate: '~13 min 12 sec',
|
||||
priceInHexWei: '0x12a05f200',
|
||||
},
|
||||
{
|
||||
labelKey: 'average',
|
||||
gasEstimateType: 'AVERAGE',
|
||||
feeInPrimaryCurrency: '0.000147 ETH',
|
||||
feeInSecondaryCurrency: '$0.38',
|
||||
priceInHexWei: '0x1a13b8600',
|
||||
timeEstimate: '~10 min 6 sec',
|
||||
},
|
||||
{
|
||||
labelKey: 'fast',
|
||||
gasEstimateType: 'FAST',
|
||||
feeInSecondaryCurrency: '$0.54',
|
||||
feeInPrimaryCurrency: '0.00021 ETH',
|
||||
timeEstimate: '~6 min 36 sec',
|
||||
@ -286,21 +286,21 @@ describe('custom-gas selectors', () => {
|
||||
{
|
||||
expectedResult: [
|
||||
{
|
||||
labelKey: 'slow',
|
||||
gasEstimateType: 'SLOW',
|
||||
feeInSecondaryCurrency: '$0.27',
|
||||
feeInPrimaryCurrency: '0.000105 ETH',
|
||||
timeEstimate: '~13 min 12 sec',
|
||||
priceInHexWei: '0x12a05f200',
|
||||
},
|
||||
{
|
||||
labelKey: 'average',
|
||||
gasEstimateType: 'AVERAGE',
|
||||
feeInPrimaryCurrency: '0.000147 ETH',
|
||||
feeInSecondaryCurrency: '$0.38',
|
||||
priceInHexWei: '0x1a13b8600',
|
||||
timeEstimate: '~10 min 6 sec',
|
||||
},
|
||||
{
|
||||
labelKey: 'fast',
|
||||
gasEstimateType: 'FAST',
|
||||
feeInSecondaryCurrency: '$0.54',
|
||||
feeInPrimaryCurrency: '0.00021 ETH',
|
||||
timeEstimate: '~6 min 36 sec',
|
||||
@ -355,19 +355,19 @@ describe('custom-gas selectors', () => {
|
||||
{
|
||||
feeInSecondaryCurrency: '$0.13',
|
||||
feeInPrimaryCurrency: '0.00052 ETH',
|
||||
labelKey: 'slow',
|
||||
gasEstimateType: 'SLOW',
|
||||
priceInHexWei: '0x5d21dba00',
|
||||
},
|
||||
{
|
||||
feeInSecondaryCurrency: '$0.16',
|
||||
feeInPrimaryCurrency: '0.00063 ETH',
|
||||
labelKey: 'average',
|
||||
gasEstimateType: 'AVERAGE',
|
||||
priceInHexWei: '0x6fc23ac00',
|
||||
},
|
||||
{
|
||||
feeInSecondaryCurrency: '$0.27',
|
||||
feeInPrimaryCurrency: '0.00105 ETH',
|
||||
labelKey: 'fast',
|
||||
gasEstimateType: 'FAST',
|
||||
priceInHexWei: '0xba43b7400',
|
||||
},
|
||||
],
|
||||
@ -405,19 +405,19 @@ describe('custom-gas selectors', () => {
|
||||
{
|
||||
feeInSecondaryCurrency: '$2.68',
|
||||
feeInPrimaryCurrency: '0.00105 ETH',
|
||||
labelKey: 'slow',
|
||||
gasEstimateType: 'SLOW',
|
||||
priceInHexWei: '0xba43b7400',
|
||||
},
|
||||
{
|
||||
feeInSecondaryCurrency: '$4.03',
|
||||
feeInPrimaryCurrency: '0.00157 ETH',
|
||||
labelKey: 'average',
|
||||
gasEstimateType: 'AVERAGE',
|
||||
priceInHexWei: '0x1176592e00',
|
||||
},
|
||||
{
|
||||
feeInSecondaryCurrency: '$5.37',
|
||||
feeInPrimaryCurrency: '0.0021 ETH',
|
||||
labelKey: 'fast',
|
||||
gasEstimateType: 'FAST',
|
||||
priceInHexWei: '0x174876e800',
|
||||
},
|
||||
],
|
||||
@ -455,19 +455,19 @@ describe('custom-gas selectors', () => {
|
||||
{
|
||||
feeInSecondaryCurrency: '',
|
||||
feeInPrimaryCurrency: '0.00105 ETH',
|
||||
labelKey: 'slow',
|
||||
gasEstimateType: 'SLOW',
|
||||
priceInHexWei: '0xba43b7400',
|
||||
},
|
||||
{
|
||||
feeInSecondaryCurrency: '',
|
||||
feeInPrimaryCurrency: '0.00157 ETH',
|
||||
labelKey: 'average',
|
||||
gasEstimateType: 'AVERAGE',
|
||||
priceInHexWei: '0x1176592e00',
|
||||
},
|
||||
{
|
||||
feeInSecondaryCurrency: '',
|
||||
feeInPrimaryCurrency: '0.0021 ETH',
|
||||
labelKey: 'fast',
|
||||
gasEstimateType: 'FAST',
|
||||
priceInHexWei: '0x174876e800',
|
||||
},
|
||||
],
|
||||
@ -505,19 +505,19 @@ describe('custom-gas selectors', () => {
|
||||
{
|
||||
feeInSecondaryCurrency: '$2.68',
|
||||
feeInPrimaryCurrency: '0.00105 ETH',
|
||||
labelKey: 'slow',
|
||||
gasEstimateType: 'SLOW',
|
||||
priceInHexWei: '0xba43b7400',
|
||||
},
|
||||
{
|
||||
feeInSecondaryCurrency: '$4.03',
|
||||
feeInPrimaryCurrency: '0.00157 ETH',
|
||||
labelKey: 'average',
|
||||
gasEstimateType: 'AVERAGE',
|
||||
priceInHexWei: '0x1176592e00',
|
||||
},
|
||||
{
|
||||
feeInSecondaryCurrency: '$5.37',
|
||||
feeInPrimaryCurrency: '0.0021 ETH',
|
||||
labelKey: 'fast',
|
||||
gasEstimateType: 'FAST',
|
||||
priceInHexWei: '0x174876e800',
|
||||
},
|
||||
],
|
||||
@ -555,19 +555,19 @@ describe('custom-gas selectors', () => {
|
||||
{
|
||||
feeInSecondaryCurrency: '$2.68',
|
||||
feeInPrimaryCurrency: '0.00105 ETH',
|
||||
labelKey: 'slow',
|
||||
gasEstimateType: 'SLOW',
|
||||
priceInHexWei: '0xba43b7400',
|
||||
},
|
||||
{
|
||||
feeInSecondaryCurrency: '$4.03',
|
||||
feeInPrimaryCurrency: '0.00157 ETH',
|
||||
labelKey: 'average',
|
||||
gasEstimateType: 'AVERAGE',
|
||||
priceInHexWei: '0x1176592e00',
|
||||
},
|
||||
{
|
||||
feeInSecondaryCurrency: '$5.37',
|
||||
feeInPrimaryCurrency: '0.0021 ETH',
|
||||
labelKey: 'fast',
|
||||
gasEstimateType: 'FAST',
|
||||
priceInHexWei: '0x174876e800',
|
||||
},
|
||||
],
|
||||
|
@ -350,6 +350,7 @@ var actions = {
|
||||
|
||||
createCancelTransaction,
|
||||
createSpeedUpTransaction,
|
||||
createRetryTransaction,
|
||||
|
||||
approveProviderRequestByOrigin,
|
||||
rejectProviderRequestByOrigin,
|
||||
@ -1860,6 +1861,28 @@ function createSpeedUpTransaction (txId, customGasPrice) {
|
||||
}
|
||||
}
|
||||
|
||||
function createRetryTransaction (txId, customGasPrice) {
|
||||
log.debug('background.createRetryTransaction')
|
||||
let newTx
|
||||
|
||||
return dispatch => {
|
||||
return new Promise((resolve, reject) => {
|
||||
background.createSpeedUpTransaction(txId, customGasPrice, (err, newState) => {
|
||||
if (err) {
|
||||
dispatch(actions.displayWarning(err.message))
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
const { selectedAddressTxList } = newState
|
||||
newTx = selectedAddressTxList[selectedAddressTxList.length - 1]
|
||||
resolve(newState)
|
||||
})
|
||||
})
|
||||
.then(newState => dispatch(actions.updateMetamaskState(newState)))
|
||||
.then(() => newTx)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// config
|
||||
//
|
||||
@ -1988,11 +2011,11 @@ function addToAddressBook (recipient, nickname = '', memo = '') {
|
||||
* @description Calls the addressBookController to remove an existing address.
|
||||
* @param {String} addressToRemove - Address of the entry to remove from the address book
|
||||
*/
|
||||
function removeFromAddressBook (addressToRemove) {
|
||||
function removeFromAddressBook (chainId, addressToRemove) {
|
||||
log.debug(`background.removeFromAddressBook`)
|
||||
|
||||
return () => {
|
||||
background.removeFromAddressBook(checksumAddress(addressToRemove))
|
||||
background.removeFromAddressBook(chainId, checksumAddress(addressToRemove))
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 119 KiB |
Before Width: | Height: | Size: 119 KiB |
Before Width: | Height: | Size: 115 KiB |
Before Width: | Height: | Size: 108 KiB |
Before Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 244 KiB |
Before Width: | Height: | Size: 215 KiB |
Before Width: | Height: | Size: 209 KiB |
Before Width: | Height: | Size: 247 KiB |
Before Width: | Height: | Size: 189 KiB |
Before Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 256 KiB |
Before Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 202 KiB |
Before Width: | Height: | Size: 506 KiB |
Before Width: | Height: | Size: 280 KiB |
Before Width: | Height: | Size: 290 KiB |