From 058f658b31110ca14b9211a5fdbc20d729cdc312 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 29 Jan 2019 12:02:24 -0330 Subject: [PATCH 01/50] Add metametrics provider and util. --- ui/app/metametrics/metametrics.provider.js | 98 ++++++++++++ ui/app/metametrics/metametrics.util.js | 167 +++++++++++++++++++++ ui/app/root.js | 7 +- ui/app/selectors.js | 44 ++++++ 4 files changed, 314 insertions(+), 2 deletions(-) create mode 100644 ui/app/metametrics/metametrics.provider.js create mode 100644 ui/app/metametrics/metametrics.util.js diff --git a/ui/app/metametrics/metametrics.provider.js b/ui/app/metametrics/metametrics.provider.js new file mode 100644 index 000000000000..7b9c2647a50e --- /dev/null +++ b/ui/app/metametrics/metametrics.provider.js @@ -0,0 +1,98 @@ +import { Component } from 'react' +import { connect } from 'react-redux' +import PropTypes from 'prop-types' +import { withRouter } from 'react-router-dom' +import { compose } from 'recompose' +import { + getCurrentNetworkId, + getSelectedAsset, + getAccountType, +} from '../selectors' +import { + txDataSelector, +} from '../selectors/confirm-transaction' +import { getEnvironmentType } from '../../../app/scripts/lib/util' +import { + sendMetaMetricsEvent, + sendCountIsTrackable, +} from './metametrics.util' + +class MetaMetricsProvider extends Component { + constructor (props) { + super(props) + + this.state = { + previousPath: '', + currentPath: window.location.href, + } + + props.history.listen(locationObj => { + this.setState({ + previousPath: this.state.currentPath, + currentPath: window.location.href, + }) + }) + } + + getChildContext () { + const props = this.props + const { pathname } = location + const { previousPath, currentPath } = this.state + + return { + metricsEvent: (config, overrides = {}) => { + const isSendFlow = Boolean(config.eventOpts && config.eventOpts.name && config.eventOpts.name.match(/^send|^confirm/) || overrides.pathname && overrides.pathname.match(/send|confirm/)) + + // if (userPermission) { + if (props.participateInMetaMetrics || config.isOptIn) { + sendMetaMetricsEvent({ + ...props, + ...config, + previousPath, + currentPath, + pathname, + excludeMetaMetricsId: isSendFlow && !sendCountIsTrackable(props.metaMetricsSendCount + 1), + ...overrides, + }) + } + }, + } + } + + render () { + return this.props.children + } +} + +MetaMetricsProvider.propTypes = { + network: PropTypes.string, + environmentType: PropTypes.string, + activeCurrency: PropTypes.string, + accountType: PropTypes.string, + metaMetricsSendCount: PropTypes.number, + children: PropTypes.object, + history: PropTypes.object, +} + +MetaMetricsProvider.childContextTypes = { + metricsEvent: PropTypes.func, +} + +const mapStateToProps = state => { + return { + network: getCurrentNetworkId(state), + environmentType: getEnvironmentType(), + activeCurrency: getSelectedAsset(state), + accountType: getAccountType(state), + confirmTransactionOrigin: txDataSelector(state).origin, + metaMetricsId: state.metamask.metaMetricsId, + participateInMetaMetrics: state.metamask.participateInMetaMetrics, + metaMetricsSendCount: state.metamask.metaMetricsSendCount, + } +} + +module.exports = compose( + withRouter, + connect(mapStateToProps) +)(MetaMetricsProvider) + diff --git a/ui/app/metametrics/metametrics.util.js b/ui/app/metametrics/metametrics.util.js new file mode 100644 index 000000000000..202c3a10f11e --- /dev/null +++ b/ui/app/metametrics/metametrics.util.js @@ -0,0 +1,167 @@ +/* eslint camelcase: 0 */ + +const ethUtil = require('ethereumjs-util') + +const METAMETRICS_BASE_URL = 'https://chromeextensionmm.innocraft.cloud/piwik.php' +const METAMETRICS_REQUIRED_PARAMS = '?idsite=1&rec=1&apiv=1' +const METAMETRICS_BASE_FULL = METAMETRICS_BASE_URL + METAMETRICS_REQUIRED_PARAMS + +const METAMETRICS_CUSTOM_HAD_ERROR = 'hadError' +const METAMETRICS_CUSTOM_HEX_DATA = 'hexData' +const METAMETRICS_CUSTOM_FUNCTION_TYPE = 'functionType' +const METAMETRICS_CUSTOM_GAS_LIMIT_CHANGE = 'gasLimitChange' +const METAMETRICS_CUSTOM_GAS_PRICE_CHANGE = 'gasPriceChange' +const METAMETRICS_CUSTOM_RECIPIENT_KNOWN = 'recipientKnown' +const METAMETRICS_CUSTOM_NUMBER_OF_TOKENS = 'numberOfTokens' +const METAMETRICS_CUSTOM_NUMBER_OF_ACCOUNTS = 'numberOfAccounts' + +const METAMETRICS_CUSTOM_NETWORK = 'network' +const METAMETRICS_CUSTOM_ENVIRONMENT_TYPE = 'environmentType' +const METAMETRICS_CUSTOM_ACTIVE_CURRENCY = 'activeCurrency' +const METAMETRICS_CUSTOM_ACCOUNT_TYPE = 'accountType' + +const customVariableNameIdMap = { + [METAMETRICS_CUSTOM_HAD_ERROR]: 1, + [METAMETRICS_CUSTOM_HEX_DATA]: 2, + [METAMETRICS_CUSTOM_FUNCTION_TYPE]: 3, + [METAMETRICS_CUSTOM_GAS_LIMIT_CHANGE]: 4, + [METAMETRICS_CUSTOM_GAS_PRICE_CHANGE]: 5, + [METAMETRICS_CUSTOM_RECIPIENT_KNOWN]: 6, + [METAMETRICS_CUSTOM_NUMBER_OF_TOKENS]: 7, + [METAMETRICS_CUSTOM_NUMBER_OF_ACCOUNTS]: 8, +} + +const customDimensionsNameIdMap = { + [METAMETRICS_CUSTOM_NETWORK]: 1, + [METAMETRICS_CUSTOM_ENVIRONMENT_TYPE]: 2, + [METAMETRICS_CUSTOM_ACTIVE_CURRENCY]: 3, + [METAMETRICS_CUSTOM_ACCOUNT_TYPE]: 4, +} + +function composeUrlRefParamAddition (previousPath, confirmTransactionOrigin) { + const externalOrigin = confirmTransactionOrigin && confirmTransactionOrigin !== 'MetaMask' + return `&urlref=${externalOrigin ? 'EXTERNAL' : encodeURIComponent(previousPath.replace(/chrome-extension:\/\/\w+/, 'http://www.metamask.io/metametrics'))}` +} + +// function composeActionNameParamAddition (pathname, pageOpts) { +// const { section, component } = pageOpts +// return `&action_name=${pathname.match(/[a-z-]+/)[0]}${section ? '%2F' + section : ''}${component ? '%2F' + component : ''}` +// } + +function composeCustomDimensionParamAddition (customDimensions) { + const customDimensionParamStrings = Object.keys(customDimensions).reduce((acc, name) => { + return [...acc, `dimension${customDimensionsNameIdMap[name]}=${customDimensions[name]}`] + }, []) + return `&${customDimensionParamStrings.join('&')}` +} + +function composeCustomVarParamAddition (customVariables) { + const customVariableIdValuePairs = Object.keys(customVariables).reduce((acc, name) => { + return { + [customVariableNameIdMap[name]]: [name, customVariables[name]], + ...acc, + } + }, {}) + return `&cvar=${encodeURIComponent(JSON.stringify(customVariableIdValuePairs))}` +} + +function composeParamAddition (paramValue, paramName) { + return paramValue !== 0 && !paramValue + ? '' + : `&${paramName}=${paramValue}` +} + +function composeUrl (config, permissionPreferences = {}) { + const { + eventOpts = {}, + customVariables = '', + pageOpts = '', + network, + environmentType, + activeCurrency, + accountType, + previousPath = '', + currentPath, + metaMetricsId, + confirmTransactionOrigin, + url: configUrl, + excludeMetaMetricsId, + } = config + const base = METAMETRICS_BASE_FULL + + const e_c = composeParamAddition(eventOpts.category, 'e_c') + const e_a = composeParamAddition(eventOpts.action, 'e_a') + const e_n = composeParamAddition(eventOpts.name, 'e_n') + + const cvar = customVariables && composeCustomVarParamAddition(customVariables) || '' + + const action_name = '' // pageOpts && pathname && composeActionNameParamAddition(pathname, pageOpts) || '' + + const urlref = previousPath && composeUrlRefParamAddition(previousPath, confirmTransactionOrigin) + + const dimensions = !pageOpts.hideDimensions && composeCustomDimensionParamAddition({ + network, + environmentType, + activeCurrency, + accountType, + }) + const url = configUrl || `&url=${encodeURIComponent(currentPath.replace(/chrome-extension:\/\/\w+/, 'http://www.metamask.io/metametrics'))}` + const _id = metaMetricsId && !excludeMetaMetricsId ? `&_id=${metaMetricsId.slice(2, 18)}` : '' + const rand = `&rand=${String(Math.random()).slice(2)}` + const pv_id = `&pv_id=${ethUtil.bufferToHex(ethUtil.sha3(url || currentPath.match(/chrome-extension:\/\/\w+\/(.+)/)[0])).slice(2, 8)}` + const uid = metaMetricsId && !excludeMetaMetricsId + ? `&uid=${metaMetricsId.slice(2, 18)}` + : excludeMetaMetricsId + ? '&uid=0000000000000000' + : '' + + return [ base, e_c, e_a, e_n, cvar, action_name, urlref, dimensions, url, _id, rand, pv_id, uid ].join('') +} + +export function sendMetaMetricsEvent (config, permissionPreferences) { + return fetch(composeUrl(config, permissionPreferences), { + 'headers': {}, + 'method': 'GET', + }) +} + +export function verifyUserPermission (config, props) { + const { + eventOpts = {}, + } = config + const { userPermissionPreferences } = props + const { + allowAll, + allowNone, + allowSendMetrics, + } = userPermissionPreferences + + if (allowNone) { + return false + } else if (allowAll) { + return true + } else if (allowSendMetrics && eventOpts.name === 'send') { + return true + } else { + return false + } +} + +const trackableSendCounts = { + 1: true, + 10: true, + 30: true, + 50: true, + 100: true, + 250: true, + 500: true, + 1000: true, + 2500: true, + 5000: true, + 10000: true, + 25000: true, +} + +export function sendCountIsTrackable (sendCount) { + return Boolean(trackableSendCounts[sendCount]) +} diff --git a/ui/app/root.js b/ui/app/root.js index f9e3709a0503..c95c565811d9 100644 --- a/ui/app/root.js +++ b/ui/app/root.js @@ -5,6 +5,7 @@ const h = require('react-hyperscript') const { HashRouter } = require('react-router-dom') const App = require('./app') const I18nProvider = require('./i18n-provider') +const MetaMetricsProvider = require('./metametrics/metametrics.provider') class Root extends Component { render () { @@ -15,8 +16,10 @@ class Root extends Component { h(HashRouter, { hashType: 'noslash', }, [ - h(I18nProvider, [ - h(App), + h(MetaMetricsProvider, [ + h(I18nProvider, [ + h(App), + ]), ]), ]), ]) diff --git a/ui/app/selectors.js b/ui/app/selectors.js index a36671b425d5..983ed50a454e 100644 --- a/ui/app/selectors.js +++ b/ui/app/selectors.js @@ -40,6 +40,10 @@ const selectors = { isBalanceCached, getAdvancedInlineGasShown, getIsMainnet, + getCurrentNetworkId, + getSelectedAsset, + getCurrentKeyring, + getAccountType, } module.exports = selectors @@ -50,6 +54,46 @@ function getNetworkIdentifier (state) { return nickname || rpcTarget || type } +function getCurrentKeyring (state) { + const identity = getSelectedIdentity(state) + + if (!identity) { + return null + } + + const simpleAddress = identity.address.substring(2).toLowerCase() + + const keyring = state.metamask.keyrings.find((kr) => { + return kr.accounts.includes(simpleAddress) || + kr.accounts.includes(identity.address) + }) + + return keyring +} + +function getAccountType (state) { + const currentKeyring = getCurrentKeyring(state) + const type = currentKeyring && currentKeyring.type + + switch (type) { + case 'Trezor Hardware': + case 'Ledger Hardware': + return 'hardware' + case 'Simple Key Pair': + return 'imported' + default: + return 'default' + } +} + +function getSelectedAsset (state) { + return getSelectedToken(state) || 'ETH' +} + +function getCurrentNetworkId (state) { + return state.metamask.network +} + function getSelectedAddress (state) { const selectedAddress = state.metamask.selectedAddress || Object.keys(getMetaMaskAccounts(state))[0] From 41e51b389cbae759c88089cebf60ea91dc7b3822 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 29 Jan 2019 12:19:17 -0330 Subject: [PATCH 02/50] Add backend api and state for participating in metametrics. --- app/scripts/controllers/preferences.js | 17 +++++++++++++++++ app/scripts/metamask-controller.js | 15 +++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 584b6bc51af4..f25fe5f171ae 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -42,6 +42,7 @@ class PreferencesController { // perform sensitive operations. featureFlags: {}, knownMethodData: {}, + participateInMetaMetrics: null, currentLocale: opts.initLangCode, identities: {}, lostIdentities: {}, @@ -52,6 +53,7 @@ class PreferencesController { }, completedOnboarding: false, completedUiMigration: true, + metaMetricsId: null, }, opts.initState) this.diagnostics = opts.diagnostics @@ -92,6 +94,21 @@ class PreferencesController { this.store.updateState({ useBlockie: val }) } + /** + * Setter for the `participateInMetaMetrics` property + * + * @param {boolean} val Whether or not the user wants to participate in MetaMetrics + * + */ + setParticipateInMetaMetrics (bool) { + this.store.updateState({ participateInMetaMetrics: bool }) + if (bool === true && !this.store.getState().metaMetricsId) { + this.store.updateState({ metaMetricsId: bufferToHex(sha3(String(Date.now()) + String(Math.round(Math.random() * Number.MAX_SAFE_INTEGER)))) }) + } else if (bool === false) { + this.store.updateState({ metaMetricsId: null }) + } + } + getSuggestedTokens () { return this.store.getState().suggestedTokens } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 41c3e364247b..c67f7988b6c8 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -385,6 +385,7 @@ module.exports = class MetamaskController extends EventEmitter { getState: (cb) => cb(null, this.getState()), setCurrentCurrency: this.setCurrentCurrency.bind(this), setUseBlockie: this.setUseBlockie.bind(this), + setParticipateInMetaMetrics: this.setParticipateInMetaMetrics.bind(this), setCurrentLocale: this.setCurrentLocale.bind(this), markAccountsFound: this.markAccountsFound.bind(this), markPasswordForgotten: this.markPasswordForgotten.bind(this), @@ -1624,6 +1625,20 @@ module.exports = class MetamaskController extends EventEmitter { } } + /** + * Sets whether or not the user will have usage data tracked with MetaMetrics + * @param {boolean} bool - True for users that wish to opt-in, false for users that wish to remain out. + * @param {Function} cb - A callback function called when complete. + */ + setParticipateInMetaMetrics (bool, cb) { + try { + this.preferencesController.setParticipateInMetaMetrics(bool) + cb(null) + } catch (err) { + cb(err) + } + } + /** * A method for setting a user's current locale, affecting the language rendered. * @param {string} key - Locale identifier. From 0849337a64d4bc5675d6ab8302522bd0406210f7 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 29 Jan 2019 12:22:47 -0330 Subject: [PATCH 03/50] Add frontend action for participating in metametrics. --- ui/app/actions.js | 23 +++++++++++++++++++++++ ui/app/reducers/metamask.js | 6 ++++++ 2 files changed, 29 insertions(+) diff --git a/ui/app/actions.js b/ui/app/actions.js index 1d01a72ad58d..45f611c75a33 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -300,6 +300,9 @@ var actions = { SET_USE_BLOCKIE: 'SET_USE_BLOCKIE', setUseBlockie, + SET_PARTICIPATE_IN_METAMETRICS: 'SET_PARTICIPATE_IN_METAMETRICS', + setParticipateInMetaMetrics, + // locale SET_CURRENT_LOCALE: 'SET_CURRENT_LOCALE', SET_LOCALE_MESSAGES: 'SET_LOCALE_MESSAGES', @@ -2607,6 +2610,26 @@ function toggleAccountMenu () { } } +function setParticipateInMetaMetrics (val) { + return (dispatch) => { + log.debug(`background.setParticipateInMetaMetrics`) + return new Promise((resolve, reject) => { + background.setParticipateInMetaMetrics(val, (err) => { + if (err) { + dispatch(actions.displayWarning(err.message)) + return reject(err) + } + + resolve(val) + }) + dispatch({ + type: actions.SET_PARTICIPATE_IN_METAMETRICS, + value: val, + }) + }) + } +} + function setUseBlockie (val) { return (dispatch) => { dispatch(actions.showLoadingIndication()) diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js index fb0fd7130789..754b94c173a1 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -57,6 +57,7 @@ function reduceMetamask (state, action) { }, completedOnboarding: false, knownMethodData: {}, + participateInMetaMetrics: null, }, state.metamask) switch (action.type) { @@ -338,6 +339,11 @@ function reduceMetamask (state, action) { coinOptions, }) + case actions.SET_PARTICIPATE_IN_METAMETRICS: + return extend(metamaskState, { + participateInMetaMetrics: action.value, + }) + case actions.SET_USE_BLOCKIE: return extend(metamaskState, { useBlockie: action.value, From 87c06c1ee50fd6e203b6dda8059c518d7191eef0 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 29 Jan 2019 12:28:02 -0330 Subject: [PATCH 04/50] Add metametrics opt-in screen. --- .../first-time-flow-switch.component.js | 9 +- .../first-time-flow-switch.container.js | 2 + .../first-time-flow.component.js | 7 ++ .../metametrics-opt-in/index.js | 1 + .../metametrics-opt-in.component.js | 102 ++++++++++++++++++ .../metametrics-opt-in.container.js | 19 ++++ .../welcome/welcome.component.js | 12 ++- .../welcome/welcome.container.js | 3 +- ui/app/routes.js | 2 + 9 files changed, 151 insertions(+), 6 deletions(-) create mode 100644 ui/app/components/pages/first-time-flow/metametrics-opt-in/index.js create mode 100644 ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js create mode 100644 ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js diff --git a/ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.component.js b/ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.component.js index 43f792e067b9..5c2294393061 100644 --- a/ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.component.js +++ b/ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.component.js @@ -7,6 +7,7 @@ import { INITIALIZE_WELCOME_ROUTE, INITIALIZE_UNLOCK_ROUTE, INITIALIZE_SEED_PHRASE_ROUTE, + INITIALIZE_METAMETRICS_OPT_IN_ROUTE, } from '../../../../routes' export default class FirstTimeFlowSwitch extends PureComponent { @@ -15,6 +16,7 @@ export default class FirstTimeFlowSwitch extends PureComponent { isInitialized: PropTypes.bool, isUnlocked: PropTypes.bool, seedPhrase: PropTypes.string, + optInMetaMetrics: PropTypes.bool, } render () { @@ -23,6 +25,7 @@ export default class FirstTimeFlowSwitch extends PureComponent { isInitialized, isUnlocked, seedPhrase, + optInMetaMetrics, } = this.props if (completedOnboarding) { @@ -45,6 +48,10 @@ export default class FirstTimeFlowSwitch extends PureComponent { return } - return + if (optInMetaMetrics === null) { + return + } + + return } } diff --git a/ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.container.js b/ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.container.js index e44c216c0687..d68f7a153135 100644 --- a/ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.container.js +++ b/ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.container.js @@ -6,12 +6,14 @@ const mapStateToProps = ({ metamask }) => { completedOnboarding, isInitialized, isUnlocked, + participateInMetaMetrics: optInMetaMetrics, } = metamask return { completedOnboarding, isInitialized, isUnlocked, + optInMetaMetrics, } } diff --git a/ui/app/components/pages/first-time-flow/first-time-flow.component.js b/ui/app/components/pages/first-time-flow/first-time-flow.component.js index 82308dda2f92..cd0f960955cd 100644 --- a/ui/app/components/pages/first-time-flow/first-time-flow.component.js +++ b/ui/app/components/pages/first-time-flow/first-time-flow.component.js @@ -8,6 +8,7 @@ import EndOfFlow from './end-of-flow' import Unlock from '../unlock-page' import CreatePassword from './create-password' import SeedPhrase from './seed-phrase' +import MetaMetricsOptInScreen from './metametrics-opt-in' import { DEFAULT_ROUTE, INITIALIZE_WELCOME_ROUTE, @@ -16,6 +17,7 @@ import { INITIALIZE_UNLOCK_ROUTE, INITIALIZE_SELECT_ACTION_ROUTE, INITIALIZE_END_OF_FLOW_ROUTE, + INITIALIZE_METAMETRICS_OPT_IN_ROUTE, } from '../../../routes' export default class FirstTimeFlow extends PureComponent { @@ -132,6 +134,11 @@ export default class FirstTimeFlow extends PureComponent { path={INITIALIZE_WELCOME_ROUTE} component={Welcome} /> + +
+
+
Would you to participate in analytics?
+
+ By doing so, you will help us make MetaMask better for everyone. + We will never track personal or identifying data. + yadda, yadda, yadda +
+
+
+
+
+ this.setState({ participateInMetaMetricsSelection: true })} + checked={Boolean(participateInMetaMetricsSelection)} + /> + +
+
+ this.setState({ participateInMetaMetricsSelection: false })} + checked={!participateInMetaMetricsSelection} + /> + +
+
+
+
+ + +
+
+ + ) + } +} diff --git a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js new file mode 100644 index 000000000000..7724ff74276d --- /dev/null +++ b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js @@ -0,0 +1,19 @@ +import { connect } from 'react-redux' +import MetaMetricsOptIn from './metametrics-opt-in.component' +import { setParticipateInMetaMetrics } from '../../../../actions' + +const mapStateToProps = state => { + const { metamask: { selectedAddress } } = state + + return { + selectedAddress, + } +} + +const mapDispatchToProps = dispatch => { + return { + setParticipateInMetaMetrics: (val) => dispatch(setParticipateInMetaMetrics(val)), + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(MetaMetricsOptIn) diff --git a/ui/app/components/pages/first-time-flow/welcome/welcome.component.js b/ui/app/components/pages/first-time-flow/welcome/welcome.component.js index 08eb8693997b..88cdb936c02f 100644 --- a/ui/app/components/pages/first-time-flow/welcome/welcome.component.js +++ b/ui/app/components/pages/first-time-flow/welcome/welcome.component.js @@ -3,12 +3,14 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' import Mascot from '../../../mascot' import Button from '../../../button' -import { INITIALIZE_SELECT_ACTION_ROUTE, INITIALIZE_UNIQUE_IMAGE_ROUTE } from '../../../../routes' +import { INITIALIZE_CREATE_PASSWORD_ROUTE, INITIALIZE_SELECT_ACTION_ROUTE } from '../../../../routes' export default class Welcome extends PureComponent { static propTypes = { history: PropTypes.object, isInitialized: PropTypes.bool, + participateInMetaMetrics: PropTypes.bool, + welcomeScreenSeen: PropTypes.bool, } static contextTypes = { @@ -22,10 +24,12 @@ export default class Welcome extends PureComponent { } componentDidMount () { - const { history, isInitialized } = this.props + const { history, participateInMetaMetrics, welcomeScreenSeen } = this.props - if (isInitialized) { - history.push(INITIALIZE_UNIQUE_IMAGE_ROUTE) + if (welcomeScreenSeen && participateInMetaMetrics !== null) { + history.push(INITIALIZE_CREATE_PASSWORD_ROUTE) + } else if (welcomeScreenSeen) { + history.push(INITIALIZE_SELECT_ACTION_ROUTE) } } diff --git a/ui/app/components/pages/first-time-flow/welcome/welcome.container.js b/ui/app/components/pages/first-time-flow/welcome/welcome.container.js index 4362d89cb647..47753e16f5cd 100644 --- a/ui/app/components/pages/first-time-flow/welcome/welcome.container.js +++ b/ui/app/components/pages/first-time-flow/welcome/welcome.container.js @@ -5,11 +5,12 @@ import { closeWelcomeScreen } from '../../../../actions' import Welcome from './welcome.component' const mapStateToProps = ({ metamask }) => { - const { welcomeScreenSeen, isInitialized } = metamask + const { welcomeScreenSeen, isInitialized, participateInMetaMetrics } = metamask return { welcomeScreenSeen, isInitialized, + participateInMetaMetrics, } } diff --git a/ui/app/routes.js b/ui/app/routes.js index 7c4e805ab792..932dfa7df2e9 100644 --- a/ui/app/routes.js +++ b/ui/app/routes.js @@ -29,6 +29,7 @@ const INITIALIZE_SELECT_ACTION_ROUTE = '/initialize/select-action' const INITIALIZE_SEED_PHRASE_ROUTE = '/initialize/seed-phrase' const INITIALIZE_END_OF_FLOW_ROUTE = '/initialize/end-of-flow' const INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE = '/initialize/seed-phrase/confirm' +const INITIALIZE_METAMETRICS_OPT_IN_ROUTE = '/initialize/metametrics-opt-in' const CONFIRM_TRANSACTION_ROUTE = '/confirm-transaction' const CONFIRM_SEND_ETHER_PATH = '/send-ether' @@ -78,4 +79,5 @@ module.exports = { CONFIRM_TRANSFER_FROM_PATH, CONFIRM_TOKEN_METHOD_PATH, SIGNATURE_REQUEST_PATH, + INITIALIZE_METAMETRICS_OPT_IN_ROUTE, } From d39011faeaa3b2641d62a34451f1ef98a9670424 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 29 Jan 2019 12:45:16 -0330 Subject: [PATCH 05/50] Add metametrics events to first time flow. --- .../new-account/new-account.component.js | 10 ++++++++++ .../unique-image/unique-image.component.js | 8 ++++++++ .../confirm-seed-phrase.component.js | 10 ++++++++++ .../reveal-seed-phrase.component.js | 12 +++++++++++- 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js b/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js index b82cba0c5df8..8d166906511f 100644 --- a/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js +++ b/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js @@ -10,6 +10,7 @@ import TextField from '../../../../text-field' export default class NewAccount extends PureComponent { static contextTypes = { + metricsEvent: PropTypes.func, t: PropTypes.func, } @@ -99,6 +100,15 @@ export default class NewAccount extends PureComponent { try { await onSubmit(password) + + this.context.metricsEvent({ + eventOpts: { + category: 'Acquisition', + action: 'userClickContinue', + name: 'onboardingStarted', + }, + }) + history.push(INITIALIZE_UNIQUE_IMAGE_ROUTE) } catch (error) { this.setState({ passwordError: error.message }) diff --git a/ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.component.js b/ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.component.js index fa76074f566c..90dd8d8f8d20 100644 --- a/ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.component.js +++ b/ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.component.js @@ -6,6 +6,7 @@ import { INITIALIZE_SEED_PHRASE_ROUTE, INITIALIZE_END_OF_FLOW_ROUTE } from '../. export default class UniqueImageScreen extends PureComponent { static contextTypes = { t: PropTypes.func, + metricsEvent: PropTypes.func, } static propTypes = { @@ -37,6 +38,13 @@ export default class UniqueImageScreen extends PureComponent { type="confirm" className="first-time-flow__button" onClick={() => { + this.context.metricsEvent({ + eventOpts: { + category: 'Acquisition', + action: 'userClickContinue', + name: 'confirmedAvatar', + }, + }) if (isImportedKeyring) { history.push(INITIALIZE_END_OF_FLOW_ROUTE) } else { diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js index b5c4bf4637b9..43effc60954c 100644 --- a/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js +++ b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js @@ -9,6 +9,7 @@ import { selectSeedWord, deselectSeedWord } from './confirm-seed-phrase.state' export default class ConfirmSeedPhrase extends PureComponent { static contextTypes = { + metricsEvent: PropTypes.func, t: PropTypes.func, } @@ -48,6 +49,15 @@ export default class ConfirmSeedPhrase extends PureComponent { try { history.push(INITIALIZE_END_OF_FLOW_ROUTE) + await completeOnboarding() + this.context.metricsEvent({ + eventOpts: { + category: 'Acquisition', + action: 'userClickContinue', + name: 'onboardingComplete', + }, + }) + history.push(INITIALIZE_END_OF_FLOW_ROUTE) } catch (error) { console.error(error.message) } diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js b/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js index 732ce14afc39..8ab9da377f0c 100644 --- a/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js +++ b/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js @@ -9,6 +9,7 @@ import { exportAsFile } from '../../../../../../app/util' export default class RevealSeedPhrase extends PureComponent { static contextTypes = { t: PropTypes.func, + metricsEvent: PropTypes.func, } static propTypes = { @@ -53,7 +54,16 @@ export default class RevealSeedPhrase extends PureComponent { !isShowingSeedPhrase && (
this.setState({ isShowingSeedPhrase: true })} + onClick={() => { + this.context.metricsEvent({ + eventOpts: { + category: 'Acquisition', + action: 'userClicksReveal', + name: 'revealedSecretWords', + }, + }) + this.setState({ isShowingSeedPhrase: true }) + }} > Date: Tue, 29 Jan 2019 12:45:38 -0330 Subject: [PATCH 06/50] Add metametrics events for route changes --- ui/app/app.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ui/app/app.js b/ui/app/app.js index 1001adc9a5ae..c70540b89e26 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -82,6 +82,22 @@ class App extends Component { if (!currentCurrency) { setCurrentCurrencyToUSD() } + + this.props.history.listen((locationObj, action) => { + if (action === 'PUSH') { + const url = `&url=${encodeURIComponent('http://www.metamask.io/metametrics' + locationObj.pathname)}` + this.context.metricsEvent({}, { + // ...props, + // ...config, + currentPath: '', + pathname: locationObj.pathname, + url, + pageOpts: { + hideDimensions: true, + }, + }) + } + }) } renderRoutes () { @@ -406,6 +422,7 @@ function mapDispatchToProps (dispatch, ownProps) { App.contextTypes = { t: PropTypes.func, + metricsEvent: PropTypes.func, } module.exports = compose( From 6cf113f372462c30fcaced43bdf1584c6b01a03e Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 29 Jan 2019 12:47:59 -0330 Subject: [PATCH 07/50] Add metametrics events for send and confirm screens --- app/scripts/controllers/preferences.js | 11 ++- app/scripts/metamask-controller.js | 11 +++ ui/app/actions.js | 22 ++++++ .../customize-gas/customize-gas.component.js | 25 +++++- .../confirm-transaction-base.component.js | 77 ++++++++++++++++++- .../confirm-transaction-base.container.js | 5 +- .../amount-max-button.component.js | 7 +- .../send-gas-row/send-gas-row.component.js | 19 ++++- .../send/send-footer/send-footer.component.js | 34 +++++++- .../tests/send-footer-component.test.js | 4 +- ui/app/ducks/confirm-transaction.duck.js | 27 +++++++ ui/app/reducers/metamask.js | 6 ++ ui/app/selectors/confirm-transaction.js | 2 +- 13 files changed, 234 insertions(+), 16 deletions(-) diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index f25fe5f171ae..bd3330202d25 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -1,6 +1,6 @@ const ObservableStore = require('obs-store') const normalizeAddress = require('eth-sig-util').normalize -const { isValidAddress } = require('ethereumjs-util') +const { isValidAddress, sha3, bufferToHex } = require('ethereumjs-util') const extend = require('xtend') @@ -54,6 +54,7 @@ class PreferencesController { completedOnboarding: false, completedUiMigration: true, metaMetricsId: null, + metaMetricsSendCount: 0, }, opts.initState) this.diagnostics = opts.diagnostics @@ -109,6 +110,14 @@ class PreferencesController { } } + setMetaMetricsSendCount (val) { + this.store.updateState({ metaMetricsSendCount: val }) + } + + getMetaMetricsSendCount () { + return this.store.getState().metaMetricsSendCount + } + getSuggestedTokens () { return this.store.getState().suggestedTokens } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index c67f7988b6c8..e294c2905a2c 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -386,6 +386,7 @@ module.exports = class MetamaskController extends EventEmitter { setCurrentCurrency: this.setCurrentCurrency.bind(this), setUseBlockie: this.setUseBlockie.bind(this), setParticipateInMetaMetrics: this.setParticipateInMetaMetrics.bind(this), + setMetaMetricsSendCount: this.setMetaMetricsSendCount.bind(this), setCurrentLocale: this.setCurrentLocale.bind(this), markAccountsFound: this.markAccountsFound.bind(this), markPasswordForgotten: this.markPasswordForgotten.bind(this), @@ -1639,6 +1640,16 @@ module.exports = class MetamaskController extends EventEmitter { } } + setMetaMetricsSendCount (val, cb) { + try { + this.preferencesController.setMetaMetricsSendCount(val) + cb(null) + } catch (err) { + cb(err) + } + } + + /** * A method for setting a user's current locale, affecting the language rendered. * @param {string} key - Locale identifier. diff --git a/ui/app/actions.js b/ui/app/actions.js index 45f611c75a33..12de38508b5d 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -301,7 +301,9 @@ var actions = { setUseBlockie, SET_PARTICIPATE_IN_METAMETRICS: 'SET_PARTICIPATE_IN_METAMETRICS', + SET_METAMETRICS_SEND_COUNT: 'SET_METAMETRICS_SEND_COUNT', setParticipateInMetaMetrics, + setMetaMetricsSendCount, // locale SET_CURRENT_LOCALE: 'SET_CURRENT_LOCALE', @@ -2630,6 +2632,26 @@ function setParticipateInMetaMetrics (val) { } } +function setMetaMetricsSendCount (val) { + return (dispatch) => { + log.debug(`background.setMetaMetricsSendCount`) + return new Promise((resolve, reject) => { + background.setMetaMetricsSendCount(val, (err) => { + if (err) { + dispatch(actions.displayWarning(err.message)) + return reject(err) + } + + resolve(val) + }) + dispatch({ + type: actions.SET_METAMETRICS_SEND_COUNT, + value: val, + }) + }) + } +} + function setUseBlockie (val) { return (dispatch) => { dispatch(actions.showLoadingIndication()) diff --git a/ui/app/components/modals/customize-gas/customize-gas.component.js b/ui/app/components/modals/customize-gas/customize-gas.component.js index 3f526bd43a71..4e2e206607c1 100644 --- a/ui/app/components/modals/customize-gas/customize-gas.component.js +++ b/ui/app/components/modals/customize-gas/customize-gas.component.js @@ -1,5 +1,6 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' +import BigNumber from 'bignumber.js' import GasModalCard from '../../customize-gas-modal/gas-modal-card' import { MIN_GAS_PRICE_GWEI } from '../../send/send.constants' import Button from '../../button' @@ -14,6 +15,7 @@ import { export default class CustomizeGas extends Component { static contextTypes = { t: PropTypes.func, + metricsEvent: PropTypes.func, } static propTypes = { @@ -73,9 +75,9 @@ export default class CustomizeGas extends Component { } render () { - const { t } = this.context + const { t, metricsEvent } = this.context const { hideModal } = this.props - const { gasPrice, gasLimit } = this.state + const { gasPrice, gasLimit, originalGasPrice, originalGasLimit } = this.state const { valid, errorKey } = this.validate() return ( @@ -128,7 +130,24 @@ export default class CustomizeGas extends Component { diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js index 4566cb390125..82ed57d0100e 100644 --- a/ui/app/components/wallet-view.js +++ b/ui/app/components/wallet-view.js @@ -26,6 +26,7 @@ module.exports = compose( WalletView.contextTypes = { t: PropTypes.func, + metricsEvent: PropTypes.func, } WalletView.defaultProps = { @@ -197,6 +198,13 @@ WalletView.prototype.render = function () { }), onClick: () => { copyToClipboard(checksummedAddress) + this.context.metricsEvent({ + eventOpts: { + category: 'Activation', + action: 'userClicks', + name: 'navCopyToClipboard', + }, + }) this.setState({ hasCopied: true }) setTimeout(() => this.setState({ hasCopied: false }), 3000) }, From 35e3175ffe898f294a5b699c11afc8368200cedd Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 29 Jan 2019 16:43:13 -0330 Subject: [PATCH 09/50] Ensures each log in is measured as a new visit by metametrics. --- ui/app/components/pages/unlock-page/unlock-page.component.js | 2 +- ui/app/metametrics/metametrics.util.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/app/components/pages/unlock-page/unlock-page.component.js b/ui/app/components/pages/unlock-page/unlock-page.component.js index f09dd5fe4252..115f944c506a 100644 --- a/ui/app/components/pages/unlock-page/unlock-page.component.js +++ b/ui/app/components/pages/unlock-page/unlock-page.component.js @@ -68,8 +68,8 @@ export default class UnlockPage extends Component { numberOfTokens: newState.tokens.length, numberOfAccounts: Object.keys(newState.accounts).length, }, + isNewVisit: true, }) - } catch ({ message }) { if (message === 'Incorrect password') { const newState = await forceUpdateMetamaskState() diff --git a/ui/app/metametrics/metametrics.util.js b/ui/app/metametrics/metametrics.util.js index 202c3a10f11e..ea8f3bf5a748 100644 --- a/ui/app/metametrics/metametrics.util.js +++ b/ui/app/metametrics/metametrics.util.js @@ -86,12 +86,14 @@ function composeUrl (config, permissionPreferences = {}) { confirmTransactionOrigin, url: configUrl, excludeMetaMetricsId, + isNewVisit, } = config const base = METAMETRICS_BASE_FULL const e_c = composeParamAddition(eventOpts.category, 'e_c') const e_a = composeParamAddition(eventOpts.action, 'e_a') const e_n = composeParamAddition(eventOpts.name, 'e_n') + const new_visit = isNewVisit ? `&new_visit=1` : '' const cvar = customVariables && composeCustomVarParamAddition(customVariables) || '' @@ -115,7 +117,7 @@ function composeUrl (config, permissionPreferences = {}) { ? '&uid=0000000000000000' : '' - return [ base, e_c, e_a, e_n, cvar, action_name, urlref, dimensions, url, _id, rand, pv_id, uid ].join('') + return [ base, e_c, e_a, e_n, cvar, action_name, urlref, dimensions, url, _id, rand, pv_id, uid, new_visit ].join('') } export function sendMetaMetricsEvent (config, permissionPreferences) { From bee4e15690e10be00a7ea488720338dd7d75806d Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 29 Jan 2019 16:55:44 -0330 Subject: [PATCH 10/50] Ensure metametrics is called with an empty string for dimensions params if specified --- ui/app/metametrics/metametrics.util.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/metametrics/metametrics.util.js b/ui/app/metametrics/metametrics.util.js index ea8f3bf5a748..a3cb64ff2d54 100644 --- a/ui/app/metametrics/metametrics.util.js +++ b/ui/app/metametrics/metametrics.util.js @@ -101,12 +101,12 @@ function composeUrl (config, permissionPreferences = {}) { const urlref = previousPath && composeUrlRefParamAddition(previousPath, confirmTransactionOrigin) - const dimensions = !pageOpts.hideDimensions && composeCustomDimensionParamAddition({ + const dimensions = !pageOpts.hideDimensions ? composeCustomDimensionParamAddition({ network, environmentType, activeCurrency, accountType, - }) + }) : '' const url = configUrl || `&url=${encodeURIComponent(currentPath.replace(/chrome-extension:\/\/\w+/, 'http://www.metamask.io/metametrics'))}` const _id = metaMetricsId && !excludeMetaMetricsId ? `&_id=${metaMetricsId.slice(2, 18)}` : '' const rand = `&rand=${String(Math.random()).slice(2)}` From 38ad6e19eb0b7355eb7646d0f9cdd7d8b79bd4d3 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 29 Jan 2019 22:19:32 -0330 Subject: [PATCH 11/50] Adds opt in metametrics modal after unlock for existing users --- ui/app/actions.js | 2 + .../modals/metametrics-opt-in/index.js | 1 + .../metametrics-opt-in.component.js | 106 ++++++++++++++++++ .../metametrics-opt-in.container.js | 24 ++++ ui/app/components/modals/modal.js | 16 +++ .../unlock-page/unlock-page.component.js | 6 +- .../unlock-page/unlock-page.container.js | 2 + 7 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 ui/app/components/modals/metametrics-opt-in/index.js create mode 100644 ui/app/components/modals/metametrics-opt-in/metametrics-opt-in.component.js create mode 100644 ui/app/components/modals/metametrics-opt-in/metametrics-opt-in.container.js diff --git a/ui/app/actions.js b/ui/app/actions.js index 12de38508b5d..bbd12d912daf 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -2593,6 +2593,7 @@ function callBackgroundThenUpdate (method, ...args) { function forceUpdateMetamaskState (dispatch) { log.debug(`background.getState`) + console.log('111111 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') return new Promise((resolve, reject) => { background.getState((err, newState) => { if (err) { @@ -2601,6 +2602,7 @@ function forceUpdateMetamaskState (dispatch) { } dispatch(actions.updateMetamaskState(newState)) + console.log('222222 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!', newState) resolve(newState) }) }) diff --git a/ui/app/components/modals/metametrics-opt-in/index.js b/ui/app/components/modals/metametrics-opt-in/index.js new file mode 100644 index 000000000000..4bc2fc3a7c82 --- /dev/null +++ b/ui/app/components/modals/metametrics-opt-in/index.js @@ -0,0 +1 @@ +export { default } from './metametrics-opt-in.container' diff --git a/ui/app/components/modals/metametrics-opt-in/metametrics-opt-in.component.js b/ui/app/components/modals/metametrics-opt-in/metametrics-opt-in.component.js new file mode 100644 index 000000000000..9f091f59aee5 --- /dev/null +++ b/ui/app/components/modals/metametrics-opt-in/metametrics-opt-in.component.js @@ -0,0 +1,106 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import Modal from '../../modal' + +export default class MetaMetricsOptIn extends Component { + static propTypes = { + address: PropTypes.string, + setParticipateInMetaMetrics: PropTypes.func, + hideModal: PropTypes.func, + } + + static contextTypes = { + metricsEvent: PropTypes.func, + t: PropTypes.func, + } + + constructor (props) { + super(props) + + this.state = { + participateInMetaMetricsSelection: null, + } + } + + render () { + const { t, metricsEvent } = this.context + const { participateInMetaMetricsSelection } = this.state + const { hideModal, setParticipateInMetaMetrics } = this.props + + return ( + { + setParticipateInMetaMetrics(this.state.participateInMetaMetricsSelection) + .then(() => { + if (!this.state.participateInMetaMetricsSelection) { + metricsEvent({ + eventOpts: { + category: 'MetaMetricsOptIn', + action: 'userSelectsOptIn', + name: 'userOptedOut', + }, + isOptIn: true, + }, { + excludeMetaMetricsId: true, + }) + } + hideModal() + }) + }} + submitText={t('ok')} + > +
+
+
+
Would you to participate in analytics?
+
+ By doing so, you will help us make MetaMask better for everyone. + We will never track personal or identifying data. + yadda, yadda, yadda +
+
+
+
+
+ this.setState({ participateInMetaMetricsSelection: true })} + checked={Boolean(participateInMetaMetricsSelection)} + /> + +
+
+ this.setState({ participateInMetaMetricsSelection: false })} + checked={!participateInMetaMetricsSelection} + /> + +
+
+
+
+ +
+
+
+
+ ) + } +} diff --git a/ui/app/components/modals/metametrics-opt-in/metametrics-opt-in.container.js b/ui/app/components/modals/metametrics-opt-in/metametrics-opt-in.container.js new file mode 100644 index 000000000000..09bd87e003cd --- /dev/null +++ b/ui/app/components/modals/metametrics-opt-in/metametrics-opt-in.container.js @@ -0,0 +1,24 @@ +import { connect } from 'react-redux' +import { compose } from 'recompose' +import MetaMetricsOptIn from './metametrics-opt-in.component' +import withModalProps from '../../../higher-order-components/with-modal-props' +import { setParticipateInMetaMetrics } from '../../../actions' + +const mapStateToProps = (state, ownProps) => { + const { unapprovedTxCount } = ownProps + + return { + unapprovedTxCount, + } +} + +const mapDispatchToProps = dispatch => { + return { + setParticipateInMetaMetrics: (val) => dispatch(setParticipateInMetaMetrics(val)), + } +} + +export default compose( + withModalProps, + connect(mapStateToProps, mapDispatchToProps), +)(MetaMetricsOptIn) diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js index 08bf205ef152..515648e56c1a 100644 --- a/ui/app/components/modals/modal.js +++ b/ui/app/components/modals/modal.js @@ -25,6 +25,9 @@ import ConfirmRemoveAccount from './confirm-remove-account' import ConfirmResetAccount from './confirm-reset-account' import TransactionConfirmed from './transaction-confirmed' import CancelTransaction from './cancel-transaction' + +import WelcomeBeta from './welcome-beta' +import MetaMetricsOptIn from './metametrics-opt-in' import RejectTransactions from './reject-transactions' import ClearApprovedOrigins from './clear-approved-origins' import ConfirmCustomizeGasModal from '../gas-customization/gas-modal-page-container' @@ -213,6 +216,19 @@ const MODALS = { }, }, + METAMETRICS_OPT_IN_MODAL: { + contents: h(MetaMetricsOptIn), + mobileModalStyle: { + ...modalContainerMobileStyle, + }, + laptopModalStyle: { + ...modalContainerLaptopStyle, + }, + contentStyle: { + borderRadius: '8px', + }, + }, + OLD_UI_NOTIFICATION_MODAL: { contents: [ h(NotifcationModal, { diff --git a/ui/app/components/pages/unlock-page/unlock-page.component.js b/ui/app/components/pages/unlock-page/unlock-page.component.js index 115f944c506a..01db99c4f281 100644 --- a/ui/app/components/pages/unlock-page/unlock-page.component.js +++ b/ui/app/components/pages/unlock-page/unlock-page.component.js @@ -46,7 +46,7 @@ export default class UnlockPage extends Component { event.stopPropagation() const { password } = this.state - const { onSubmit, forceUpdateMetamaskState } = this.props + const { onSubmit, forceUpdateMetamaskState, showOptInModal } = this.props if (password === '' || this.submitting) { return @@ -70,6 +70,10 @@ export default class UnlockPage extends Component { }, isNewVisit: true, }) + + if (newState.participateInMetaMetrics === null || newState.participateInMetaMetrics === undefined) { + showOptInModal() + } } catch ({ message }) { if (message === 'Incorrect password') { const newState = await forceUpdateMetamaskState() diff --git a/ui/app/components/pages/unlock-page/unlock-page.container.js b/ui/app/components/pages/unlock-page/unlock-page.container.js index a37e9d97b459..fe51c809551a 100644 --- a/ui/app/components/pages/unlock-page/unlock-page.container.js +++ b/ui/app/components/pages/unlock-page/unlock-page.container.js @@ -9,6 +9,7 @@ import { forgotPassword, markPasswordForgotten, forceUpdateMetamaskState, + showModal, } from '../../../actions' import UnlockPage from './unlock-page.component' @@ -25,6 +26,7 @@ const mapDispatchToProps = dispatch => { tryUnlockMetamask: password => dispatch(tryUnlockMetamask(password)), markPasswordForgotten: () => dispatch(markPasswordForgotten()), forceUpdateMetamaskState: () => forceUpdateMetamaskState(dispatch), + showOptInModal: () => dispatch(showModal({ name: 'METAMETRICS_OPT_IN_MODAL' })), } } From 9ce118271fff39699810f0ee4c056a657de0ece0 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 29 Jan 2019 22:37:51 -0330 Subject: [PATCH 12/50] Adds settings page toggle for opting in and out of MetaMetrics --- .../settings-tab/settings-tab.component.js | 29 +++++++++++++++++++ .../settings-tab/settings-tab.container.js | 3 ++ 2 files changed, 32 insertions(+) diff --git a/ui/app/components/pages/settings/settings-tab/settings-tab.component.js b/ui/app/components/pages/settings/settings-tab/settings-tab.component.js index b143d3d9cb53..8dc9e35473ad 100644 --- a/ui/app/components/pages/settings/settings-tab/settings-tab.component.js +++ b/ui/app/components/pages/settings/settings-tab/settings-tab.component.js @@ -66,6 +66,8 @@ export default class SettingsTab extends PureComponent { mobileSync: PropTypes.bool, showFiatInTestnets: PropTypes.bool, setShowFiatConversionOnTestnetsPreference: PropTypes.func.isRequired, + participateInMetaMetrics: PropTypes.bool, + setParticipateInMetaMetrics: PropTypes.func, } state = { @@ -628,6 +630,32 @@ export default class SettingsTab extends PureComponent { ) } + renderMetaMetricsOptIn () { + const { t } = this.context + const { participateInMetaMetrics, setParticipateInMetaMetrics } = this.props + + return ( +
+
+ { 'Participate in MetaMetrics' } +
+ { 'Participate in MetaMetrics to help us make MetaMask better' } +
+
+
+
+ setParticipateInMetaMetrics(!value)} + activeLabel="" + inactiveLabel="" + /> +
+
+
+ ) + } + render () { const { warning } = this.props @@ -648,6 +676,7 @@ export default class SettingsTab extends PureComponent { { this.renderAdvancedGasInputInline() } { this.renderBlockieOptIn() } { this.renderMobileSync() } + { this.renderMetaMetricsOptIn () }
) } diff --git a/ui/app/components/pages/settings/settings-tab/settings-tab.container.js b/ui/app/components/pages/settings/settings-tab/settings-tab.container.js index 5cb9a9aae3b8..f66d705f9887 100644 --- a/ui/app/components/pages/settings/settings-tab/settings-tab.container.js +++ b/ui/app/components/pages/settings/settings-tab/settings-tab.container.js @@ -31,6 +31,7 @@ const mapStateToProps = state => { } = {}, provider = {}, currentLocale, + participateInMetaMetrics, } = metamask const { useNativeCurrencyAsPrimaryCurrency, showFiatInTestnets } = preferencesSelector(state) @@ -48,6 +49,7 @@ const mapStateToProps = state => { useNativeCurrencyAsPrimaryCurrency, mobileSync, showFiatInTestnets, + participateInMetaMetrics, } } @@ -70,6 +72,7 @@ const mapDispatchToProps = dispatch => { return dispatch(setShowFiatConversionOnTestnetsPreference(value)) }, showClearApprovalModal: () => dispatch(showModal({ name: 'CLEAR_APPROVED_ORIGINS' })), + setParticipateInMetaMetrics: (val) => dispatch(setParticipateInMetaMetrics(val)), } } From e4ced75015d085a9950a164afaeea4a688e216b3 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Wed, 6 Feb 2019 00:53:01 -0330 Subject: [PATCH 13/50] Switch metametrics dimensions to page level scope --- ui/app/metametrics/metametrics.util.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/app/metametrics/metametrics.util.js b/ui/app/metametrics/metametrics.util.js index a3cb64ff2d54..a81fdc2770e6 100644 --- a/ui/app/metametrics/metametrics.util.js +++ b/ui/app/metametrics/metametrics.util.js @@ -32,10 +32,10 @@ const customVariableNameIdMap = { } const customDimensionsNameIdMap = { - [METAMETRICS_CUSTOM_NETWORK]: 1, - [METAMETRICS_CUSTOM_ENVIRONMENT_TYPE]: 2, - [METAMETRICS_CUSTOM_ACTIVE_CURRENCY]: 3, - [METAMETRICS_CUSTOM_ACCOUNT_TYPE]: 4, + [METAMETRICS_CUSTOM_NETWORK]: 5, + [METAMETRICS_CUSTOM_ENVIRONMENT_TYPE]: 6, + [METAMETRICS_CUSTOM_ACTIVE_CURRENCY]: 7, + [METAMETRICS_CUSTOM_ACCOUNT_TYPE]: 8, } function composeUrlRefParamAddition (previousPath, confirmTransactionOrigin) { From 32843484f4dcacd3d371fe1154ceee090f9686a3 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Wed, 6 Feb 2019 01:12:22 -0330 Subject: [PATCH 14/50] Lint, test and translation fixes for metametrics. --- app/_locales/en/messages.json | 6 ++++++ ui/app/components/modals/modal.js | 1 - .../pages/settings/settings-tab/settings-tab.component.js | 6 +++--- .../pages/settings/settings-tab/settings-tab.container.js | 1 + .../send-content/send-gas-row/send-gas-row.component.js | 2 +- .../send-gas-row/tests/send-gas-row-component.test.js | 2 +- 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index d4860bbfb553..77029314df90 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1000,6 +1000,12 @@ "originalTotal": { "message": "Original Total" }, + "participateInMetaMetrics": { + "message": "Participate in MetaMetrics" + }, + "participateInMetaMetricsDesciption": { + "message": "Participate in MetaMetrics to help us make MetaMask better" + }, "password": { "message": "Password" }, diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js index 515648e56c1a..168a42f6fc12 100644 --- a/ui/app/components/modals/modal.js +++ b/ui/app/components/modals/modal.js @@ -26,7 +26,6 @@ import ConfirmResetAccount from './confirm-reset-account' import TransactionConfirmed from './transaction-confirmed' import CancelTransaction from './cancel-transaction' -import WelcomeBeta from './welcome-beta' import MetaMetricsOptIn from './metametrics-opt-in' import RejectTransactions from './reject-transactions' import ClearApprovedOrigins from './clear-approved-origins' diff --git a/ui/app/components/pages/settings/settings-tab/settings-tab.component.js b/ui/app/components/pages/settings/settings-tab/settings-tab.component.js index 8dc9e35473ad..e433dcb2a256 100644 --- a/ui/app/components/pages/settings/settings-tab/settings-tab.component.js +++ b/ui/app/components/pages/settings/settings-tab/settings-tab.component.js @@ -637,9 +637,9 @@ export default class SettingsTab extends PureComponent { return (
- { 'Participate in MetaMetrics' } + { t('participateInMetaMetrics') }
- { 'Participate in MetaMetrics to help us make MetaMask better' } + { t('participateInMetaMetricsDescription') }
@@ -676,7 +676,7 @@ export default class SettingsTab extends PureComponent { { this.renderAdvancedGasInputInline() } { this.renderBlockieOptIn() } { this.renderMobileSync() } - { this.renderMetaMetricsOptIn () } + { this.renderMetaMetricsOptIn() }
) } diff --git a/ui/app/components/pages/settings/settings-tab/settings-tab.container.js b/ui/app/components/pages/settings/settings-tab/settings-tab.container.js index f66d705f9887..64c256412d74 100644 --- a/ui/app/components/pages/settings/settings-tab/settings-tab.container.js +++ b/ui/app/components/pages/settings/settings-tab/settings-tab.container.js @@ -13,6 +13,7 @@ import { showModal, setUseNativeCurrencyAsPrimaryCurrencyPreference, setShowFiatConversionOnTestnetsPreference, + setParticipateInMetaMetrics, } from '../../../../actions' import { preferencesSelector } from '../../../../selectors' diff --git a/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js b/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js index 072a21e5cf56..911d41b82a58 100644 --- a/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js +++ b/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js @@ -31,6 +31,7 @@ export default class SendGasRow extends Component { }; renderAdvancedOptionsButton () { + const { metricsEvent } = this.context const { showCustomizeGasModal } = this.props return
{ metricsEvent({ @@ -67,7 +68,6 @@ export default class SendGasRow extends Component { gasLimit, insufficientBalance, } = this.props - const { metricsEvent } = this.context const gasPriceButtonGroup =
, { context: { t: str => str + '_t' } }) + />, { context: { t: str => str + '_t', metricsEvent: () => ({}) } }) }) afterEach(() => { From 2132777251d542888b039e07518c2918255d90fa Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Wed, 6 Feb 2019 04:05:31 -0330 Subject: [PATCH 15/50] Update design for metametrics opt-in screen --- .../pages/first-time-flow/index.scss | 3 + .../metametrics-opt-in/index.scss | 111 ++++++++++++ .../metametrics-opt-in.component.js | 165 ++++++++++-------- 3 files changed, 208 insertions(+), 71 deletions(-) create mode 100644 ui/app/components/pages/first-time-flow/metametrics-opt-in/index.scss diff --git a/ui/app/components/pages/first-time-flow/index.scss b/ui/app/components/pages/first-time-flow/index.scss index e14d57f58ee4..25125ca32cf0 100644 --- a/ui/app/components/pages/first-time-flow/index.scss +++ b/ui/app/components/pages/first-time-flow/index.scss @@ -6,6 +6,9 @@ @import './end-of-flow/index'; +@import './metametrics-opt-in/index'; + + .first-time-flow { width: 100%; background-color: $white; diff --git a/ui/app/components/pages/first-time-flow/metametrics-opt-in/index.scss b/ui/app/components/pages/first-time-flow/metametrics-opt-in/index.scss new file mode 100644 index 000000000000..06a9cd0dc1f8 --- /dev/null +++ b/ui/app/components/pages/first-time-flow/metametrics-opt-in/index.scss @@ -0,0 +1,111 @@ +.metametrics-opt-in { + position: relative; + width: 100%; + + &__main { + display: flex; + flex-direction: column; + margin-left: 26.26%; + margin-right: 28%; + color: black; + + @media screen and (max-width: 575px) { + justify-content: center; + margin-left: 2%; + margin-right: 2%; + } + + .app-header__logo-container { + margin-top: 3%; + } + } + + &__title { + position: relative; + margin-top: 20px; + + font-family: Roboto; + font-style: normal; + font-weight: normal; + line-height: normal; + font-size: 42px; + } + + &__body-graphic { + margin-top: 25px; + + .fa-bar-chart { + color: #C4C4C4; + } + } + + &__description { + font-family: Roboto; + font-style: normal; + font-weight: normal; + line-height: 21px; + font-size: 16px; + margin-top: 12px; + } + + &__committments { + display: flex; + flex-direction: column; + } + + &__row { + display: flex; + margin-top: 8px; + + .fa-check { + margin-right: 12px; + color: #1ACC56; + } + + .fa-times { + margin-right: 12px; + color: #D0021B; + } + } + + &__bold { + font-weight: bold; + } + + &__break-row { + margin-top: 30px; + } + + &__body { + position: relative; + display: flex; + max-width: 730px; + flex-direction: column; + } + + &__body-text { + max-width: 548px; + margin-left: 16px; + margin-right: 16px; + } + + &__footer { + margin-top: 26px; + + .page-container__footer { + border-top: none; + max-width: 535px; + margin-bottom: 15px; + + button { + height: 44px; + min-height: 44px; + margin-right: 16px; + } + + header { + padding: 0px; + } + } + } +} \ No newline at end of file diff --git a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js index cb39e3efcf75..5374404eb518 100644 --- a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js +++ b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js @@ -1,6 +1,6 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import Breadcrumbs from '../../../breadcrumbs' +import PageContainerFooter from '../../../page-container/page-container-footer' import { INITIALIZE_CREATE_PASSWORD_ROUTE } from '../../../../routes' export default class MetaMetricsOptIn extends Component { @@ -14,86 +14,109 @@ export default class MetaMetricsOptIn extends Component { metricsEvent: PropTypes.func, } - constructor (props) { - super(props) - - this.state = { - participateInMetaMetricsSelection: null, - } - } - render () { - const { participateInMetaMetricsSelection } = this.state - - return ( -
-
-
-
Would you to participate in analytics?
-
- By doing so, you will help us make MetaMask better for everyone. - We will never track personal or identifying data. - yadda, yadda, yadda +
+
+
+ + +
+
+ +
+
Help Us Improve MetaMask
+
+
+ MetaMask would like to gather some usage data to better understand how our users interact with the extension and inform futue development. + This data will be used to continually improve the usability and user experience of our product. If you care about the usability of the ethereum + ecosystem, please consider allowing these basic analytics.
-
-
-
-
- this.setState({ participateInMetaMetricsSelection: true })} - checked={Boolean(participateInMetaMetricsSelection)} - /> - -
-
- this.setState({ participateInMetaMetricsSelection: false })} - checked={!participateInMetaMetricsSelection} - /> - -
+
+ MetaMask will.. +
+ +
+
+ +
+ Always allow you to opt-out via Settings +
+
+
+ +
+ Send anonymized click & pageview events +
+
+
+ +
+ Maintain a public aggregate dashboard to educate the community +
+
+
+ +
+ Never collect keys, addresses, transactions, balances, hashes, or any personal information +
+
+
+ +
+ Never collect your full IP address +
+
+
+ +
+ Never sell data for profit. Ever!
-
+
+ { + this.props.setParticipateInMetaMetrics(false) + .then(() => { + this.context.metricsEvent({ + eventOpts: { + category: 'MetaMetricsOptIn', + action: 'userSelectsOptIn', + name: 'userOptedOut', + }, + isOptIn: true, + }, { + excludeMetaMetricsId: true, + }) + this.props.history.push(INITIALIZE_CREATE_PASSWORD_ROUTE) + }) + }} + cancelText={'No Thanks'} + hideCancel={false} + onSubmit={() => { + this.props.setParticipateInMetaMetrics(true) .then(() => { - if (!this.state.participateInMetaMetricsSelection) { - this.context.metricsEvent({ - eventOpts: { - category: 'MetaMetricsOptIn', - action: 'userSelectsOptIn', - name: 'userOptedOut', - }, - isOptIn: true, - }, { - excludeMetaMetricsId: true, - }) - } this.props.history.push(INITIALIZE_CREATE_PASSWORD_ROUTE) }) }} - > - Select and Proceed - - + submitText={'I agree'} + submitButtonType={'confirm'} + disabled={false} + /> +
+ This data is aggregated and is therefore anonymous for the purposes of General Data Protection Regulation (EU) 2016/679. For more information in relation to our privacy practices, please see our Privacy Policy here. +
From dc97baf90d76268cfed6f295642485512ef720a4 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 18 Feb 2019 22:11:21 -0330 Subject: [PATCH 16/50] Complete responsive styling of metametrics-opt-in modal --- ui/app/components/button/button.component.js | 2 +- ui/app/components/modals/index.scss | 2 + .../modals/metametrics-opt-in-modal/index.js | 1 + .../metametrics-opt-in-modal/index.scss | 25 ++++ .../metametrics-opt-in-modal.component.js | 124 ++++++++++++++++++ .../metametrics-opt-in-modal.container.js} | 4 +- .../modals/metametrics-opt-in/index.js | 1 - .../metametrics-opt-in.component.js | 106 --------------- ui/app/components/modals/modal.js | 7 +- .../metametrics-opt-in/index.scss | 23 +++- .../metametrics-opt-in.component.js | 1 - .../metametrics-opt-in.container.js | 10 +- 12 files changed, 183 insertions(+), 123 deletions(-) create mode 100644 ui/app/components/modals/metametrics-opt-in-modal/index.js create mode 100644 ui/app/components/modals/metametrics-opt-in-modal/index.scss create mode 100644 ui/app/components/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js rename ui/app/components/modals/{metametrics-opt-in/metametrics-opt-in.container.js => metametrics-opt-in-modal/metametrics-opt-in-modal.container.js} (85%) delete mode 100644 ui/app/components/modals/metametrics-opt-in/index.js delete mode 100644 ui/app/components/modals/metametrics-opt-in/metametrics-opt-in.component.js diff --git a/ui/app/components/button/button.component.js b/ui/app/components/button/button.component.js index 5d19219b4f47..5394133a0232 100644 --- a/ui/app/components/button/button.component.js +++ b/ui/app/components/button/button.component.js @@ -18,7 +18,7 @@ const typeHash = { raised: CLASSNAME_RAISED, 'first-time': CLASSNAME_FIRST_TIME, } - +// export default class Button extends Component { static propTypes = { type: PropTypes.string, diff --git a/ui/app/components/modals/index.scss b/ui/app/components/modals/index.scss index 45453a582823..555da87ef09e 100644 --- a/ui/app/components/modals/index.scss +++ b/ui/app/components/modals/index.scss @@ -7,3 +7,5 @@ @import './qr-scanner/index'; @import './transaction-confirmed/index'; + +@import './metametrics-opt-in-modal/index'; diff --git a/ui/app/components/modals/metametrics-opt-in-modal/index.js b/ui/app/components/modals/metametrics-opt-in-modal/index.js new file mode 100644 index 000000000000..47f946757838 --- /dev/null +++ b/ui/app/components/modals/metametrics-opt-in-modal/index.js @@ -0,0 +1 @@ +export { default } from './metametrics-opt-in-modal.container' diff --git a/ui/app/components/modals/metametrics-opt-in-modal/index.scss b/ui/app/components/modals/metametrics-opt-in-modal/index.scss new file mode 100644 index 000000000000..32a52351a5c6 --- /dev/null +++ b/ui/app/components/modals/metametrics-opt-in-modal/index.scss @@ -0,0 +1,25 @@ +.metametrics-opt-in-modal { + .metametrics-opt-in__main { + justify-content: center; + margin-left: 3%; + margin-right: 0%; + max-height: 600px; + } + + .metametrics-opt-in__title { + font-size: 38px; + } + + .metametrics-opt-in__content { + padding-right: 6px; + } + + .metametrics-opt-in__footer { + @media screen and (max-width: 575px) { + margin-top: 10px; + justify-content: center; + margin-left: 2%; + max-height: 520px; + } + } +} \ No newline at end of file diff --git a/ui/app/components/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js b/ui/app/components/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js new file mode 100644 index 000000000000..e2d32cd2e10b --- /dev/null +++ b/ui/app/components/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js @@ -0,0 +1,124 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import PageContainerFooter from '../../page-container/page-container-footer' + +export default class MetaMetricsOptInModal extends Component { + static propTypes = { + setParticipateInMetaMetrics: PropTypes.func, + hideModal: PropTypes.func, + } + + static contextTypes = { + metricsEvent: PropTypes.func, + } + + render () { + return ( +
+
+
+
+ + +
+
+ +
+
Help Us Improve MetaMask
+
+
+ MetaMask would like to gather some usage data to better understand how our users interact with the extension and inform futue development. + This data will be used to continually improve the usability and user experience of our product. +
+
+ MetaMask will.. +
+ +
+
+ +
+ Always allow you to opt-out via Settings +
+
+
+ +
+ Send anonymized click & pageview events +
+
+
+ +
+ Maintain a public aggregate dashboard to educate the community +
+
+
+ +
+ Never collect keys, addresses, transactions, balances, hashes, or any personal information +
+
+
+ +
+ Never collect your full IP address +
+
+
+ +
+ Never sell data for profit. Ever! +
+
+
+
+
+ This data is aggregated and is therefore anonymous for the purposes of General Data Protection Regulation (EU) 2016/679. For more information in relation to our privacy practices, please see our Privacy Policy here. +
+
+
+ { + this.props.setParticipateInMetaMetrics(false) + .then(() => { + this.context.metricsEvent({ + eventOpts: { + category: 'MetaMetricsOptIn', + action: 'userSelectsOptIn', + name: 'userOptedOut', + }, + isOptIn: true, + }, { + excludeMetaMetricsId: true, + }) + this.props.hideModal() + }) + }} + cancelText={'No Thanks'} + hideCancel={false} + onSubmit={() => { + this.props.setParticipateInMetaMetrics(true) + .then(() => { + this.props.hideModal() + }) + }} + submitText={'I agree'} + submitButtonType={'confirm'} + disabled={false} + /> +
+
+
+ ) + } +} diff --git a/ui/app/components/modals/metametrics-opt-in/metametrics-opt-in.container.js b/ui/app/components/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js similarity index 85% rename from ui/app/components/modals/metametrics-opt-in/metametrics-opt-in.container.js rename to ui/app/components/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js index 09bd87e003cd..525806b75a24 100644 --- a/ui/app/components/modals/metametrics-opt-in/metametrics-opt-in.container.js +++ b/ui/app/components/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js @@ -1,6 +1,6 @@ import { connect } from 'react-redux' import { compose } from 'recompose' -import MetaMetricsOptIn from './metametrics-opt-in.component' +import MetaMetricsOptInModal from './metametrics-opt-in-modal.component' import withModalProps from '../../../higher-order-components/with-modal-props' import { setParticipateInMetaMetrics } from '../../../actions' @@ -21,4 +21,4 @@ const mapDispatchToProps = dispatch => { export default compose( withModalProps, connect(mapStateToProps, mapDispatchToProps), -)(MetaMetricsOptIn) +)(MetaMetricsOptInModal) diff --git a/ui/app/components/modals/metametrics-opt-in/index.js b/ui/app/components/modals/metametrics-opt-in/index.js deleted file mode 100644 index 4bc2fc3a7c82..000000000000 --- a/ui/app/components/modals/metametrics-opt-in/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './metametrics-opt-in.container' diff --git a/ui/app/components/modals/metametrics-opt-in/metametrics-opt-in.component.js b/ui/app/components/modals/metametrics-opt-in/metametrics-opt-in.component.js deleted file mode 100644 index 9f091f59aee5..000000000000 --- a/ui/app/components/modals/metametrics-opt-in/metametrics-opt-in.component.js +++ /dev/null @@ -1,106 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import Modal from '../../modal' - -export default class MetaMetricsOptIn extends Component { - static propTypes = { - address: PropTypes.string, - setParticipateInMetaMetrics: PropTypes.func, - hideModal: PropTypes.func, - } - - static contextTypes = { - metricsEvent: PropTypes.func, - t: PropTypes.func, - } - - constructor (props) { - super(props) - - this.state = { - participateInMetaMetricsSelection: null, - } - } - - render () { - const { t, metricsEvent } = this.context - const { participateInMetaMetricsSelection } = this.state - const { hideModal, setParticipateInMetaMetrics } = this.props - - return ( - { - setParticipateInMetaMetrics(this.state.participateInMetaMetricsSelection) - .then(() => { - if (!this.state.participateInMetaMetricsSelection) { - metricsEvent({ - eventOpts: { - category: 'MetaMetricsOptIn', - action: 'userSelectsOptIn', - name: 'userOptedOut', - }, - isOptIn: true, - }, { - excludeMetaMetricsId: true, - }) - } - hideModal() - }) - }} - submitText={t('ok')} - > -
-
-
-
Would you to participate in analytics?
-
- By doing so, you will help us make MetaMask better for everyone. - We will never track personal or identifying data. - yadda, yadda, yadda -
-
-
-
-
- this.setState({ participateInMetaMetricsSelection: true })} - checked={Boolean(participateInMetaMetricsSelection)} - /> - -
-
- this.setState({ participateInMetaMetricsSelection: false })} - checked={!participateInMetaMetricsSelection} - /> - -
-
-
-
- -
-
-
-
- ) - } -} diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js index 168a42f6fc12..a6dd477815bc 100644 --- a/ui/app/components/modals/modal.js +++ b/ui/app/components/modals/modal.js @@ -26,7 +26,7 @@ import ConfirmResetAccount from './confirm-reset-account' import TransactionConfirmed from './transaction-confirmed' import CancelTransaction from './cancel-transaction' -import MetaMetricsOptIn from './metametrics-opt-in' +import MetaMetricsOptInModal from './metametrics-opt-in-modal' import RejectTransactions from './reject-transactions' import ClearApprovedOrigins from './clear-approved-origins' import ConfirmCustomizeGasModal from '../gas-customization/gas-modal-page-container' @@ -216,9 +216,12 @@ const MODALS = { }, METAMETRICS_OPT_IN_MODAL: { - contents: h(MetaMetricsOptIn), + contents: h(MetaMetricsOptInModal), mobileModalStyle: { ...modalContainerMobileStyle, + width: '100%', + height: '100%', + top: '0px', }, laptopModalStyle: { ...modalContainerLaptopStyle, diff --git a/ui/app/components/pages/first-time-flow/metametrics-opt-in/index.scss b/ui/app/components/pages/first-time-flow/metametrics-opt-in/index.scss index 06a9cd0dc1f8..5139c4535f99 100644 --- a/ui/app/components/pages/first-time-flow/metametrics-opt-in/index.scss +++ b/ui/app/components/pages/first-time-flow/metametrics-opt-in/index.scss @@ -12,7 +12,8 @@ @media screen and (max-width: 575px) { justify-content: center; margin-left: 2%; - margin-right: 2%; + margin-right: 0%; + max-height: 600px; } .app-header__logo-container { @@ -53,6 +54,11 @@ flex-direction: column; } + &__content { + overflow-y: scroll; + flex: 1; + } + &__row { display: flex; margin-top: 8px; @@ -89,9 +95,24 @@ margin-right: 16px; } + &__bottom-text { + margin-top: 10px; + } + + &__content { + overflow-y: auto; + } + &__footer { margin-top: 26px; + @media screen and (max-width: 575px) { + margin-top: 10px; + justify-content: center; + margin-left: 2%; + max-height: 520px; + } + .page-container__footer { border-top: none; max-width: 535px; diff --git a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js index 5374404eb518..e5ba9776a7bb 100644 --- a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js +++ b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js @@ -5,7 +5,6 @@ import { INITIALIZE_CREATE_PASSWORD_ROUTE } from '../../../../routes' export default class MetaMetricsOptIn extends Component { static propTypes = { - address: PropTypes.string, history: PropTypes.object, setParticipateInMetaMetrics: PropTypes.func, } diff --git a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js index 7724ff74276d..24809f942378 100644 --- a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js +++ b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js @@ -2,18 +2,10 @@ import { connect } from 'react-redux' import MetaMetricsOptIn from './metametrics-opt-in.component' import { setParticipateInMetaMetrics } from '../../../../actions' -const mapStateToProps = state => { - const { metamask: { selectedAddress } } = state - - return { - selectedAddress, - } -} - const mapDispatchToProps = dispatch => { return { setParticipateInMetaMetrics: (val) => dispatch(setParticipateInMetaMetrics(val)), } } -export default connect(mapStateToProps, mapDispatchToProps)(MetaMetricsOptIn) +export default connect(null, mapDispatchToProps)(MetaMetricsOptIn) From a1145ea88e767cdd1452529872fe0cbddddfbccd Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 19 Feb 2019 10:45:00 -0330 Subject: [PATCH 17/50] Use new chart image on metrics opt in screens --- app/images/metrics-chart.svg | 8 ++++++++ .../metametrics-opt-in-modal.component.js | 2 +- .../metametrics-opt-in/metametrics-opt-in.component.js | 4 ++-- 3 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 app/images/metrics-chart.svg diff --git a/app/images/metrics-chart.svg b/app/images/metrics-chart.svg new file mode 100644 index 000000000000..e1893424467b --- /dev/null +++ b/app/images/metrics-chart.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/ui/app/components/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js b/ui/app/components/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js index e2d32cd2e10b..e332dfe7633d 100644 --- a/ui/app/components/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js +++ b/ui/app/components/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js @@ -31,7 +31,7 @@ export default class MetaMetricsOptInModal extends Component { />
- +
Help Us Improve MetaMask
diff --git a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js index e5ba9776a7bb..cf1f2bdf56c5 100644 --- a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js +++ b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js @@ -10,7 +10,7 @@ export default class MetaMetricsOptIn extends Component { } static contextTypes = { - metricsEvent: PropTypes.func, + metricsEvent: PropTypes.func,// } render () { @@ -31,7 +31,7 @@ export default class MetaMetricsOptIn extends Component { />
- +
Help Us Improve MetaMask
From 88e0cc745335b92715d329356528a1b01f2c6c82 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Wed, 27 Feb 2019 12:38:54 -0330 Subject: [PATCH 18/50] Incorporate the metametrics opt-in screen into the new onboarding flow --- app/scripts/controllers/preferences.js | 20 +++++++- app/scripts/metamask-controller.js | 19 +++++++- ui/app/actions.js | 25 ++++++++-- .../metametrics-opt-in.component.js | 48 +++++++++++++++---- .../metametrics-opt-in.container.js | 30 +++++++++++- .../confirm-seed-phrase.component.js | 1 - .../first-time-flow/select-action/index.js | 2 +- .../select-action/select-action.component.js | 10 ++-- .../select-action/select-action.container.js | 16 +++++++ ui/app/metametrics/metametrics.provider.js | 2 +- ui/app/reducers/metamask.js | 7 +++ 11 files changed, 155 insertions(+), 25 deletions(-) diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index bd3330202d25..fe7a58cad89b 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -43,6 +43,7 @@ class PreferencesController { featureFlags: {}, knownMethodData: {}, participateInMetaMetrics: null, + firstTimeFlowType: null, currentLocale: opts.initLangCode, identities: {}, lostIdentities: {}, @@ -99,15 +100,19 @@ class PreferencesController { * Setter for the `participateInMetaMetrics` property * * @param {boolean} val Whether or not the user wants to participate in MetaMetrics + * @returns {string|null} the string of the new metametrics id, or null if not set * */ setParticipateInMetaMetrics (bool) { this.store.updateState({ participateInMetaMetrics: bool }) + let metaMetricsId = null if (bool === true && !this.store.getState().metaMetricsId) { - this.store.updateState({ metaMetricsId: bufferToHex(sha3(String(Date.now()) + String(Math.round(Math.random() * Number.MAX_SAFE_INTEGER)))) }) + metaMetricsId = bufferToHex(sha3(String(Date.now()) + String(Math.round(Math.random() * Number.MAX_SAFE_INTEGER)))) + this.store.updateState({ metaMetricsId }) } else if (bool === false) { - this.store.updateState({ metaMetricsId: null }) + this.store.updateState({ metaMetricsId }) } + return metaMetricsId } setMetaMetricsSendCount (val) { @@ -118,6 +123,17 @@ class PreferencesController { return this.store.getState().metaMetricsSendCount } + /** + * Setter for the `firstTimeFlowType` property + * + * @param {String} type Indicates the type of first time flow - create or import - the user wishes to follow + * + */ + setFirstTimeFlowType (type) { + this.store.updateState({ firstTimeFlowType: type }) + } + + getSuggestedTokens () { return this.store.getState().suggestedTokens } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index e294c2905a2c..653868066374 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -387,6 +387,7 @@ module.exports = class MetamaskController extends EventEmitter { setUseBlockie: this.setUseBlockie.bind(this), setParticipateInMetaMetrics: this.setParticipateInMetaMetrics.bind(this), setMetaMetricsSendCount: this.setMetaMetricsSendCount.bind(this), + setFirstTimeFlowType: this.setFirstTimeFlowType.bind(this), setCurrentLocale: this.setCurrentLocale.bind(this), markAccountsFound: this.markAccountsFound.bind(this), markPasswordForgotten: this.markPasswordForgotten.bind(this), @@ -1633,8 +1634,8 @@ module.exports = class MetamaskController extends EventEmitter { */ setParticipateInMetaMetrics (bool, cb) { try { - this.preferencesController.setParticipateInMetaMetrics(bool) - cb(null) + const metaMetricsId = this.preferencesController.setParticipateInMetaMetrics(bool) + cb(null, metaMetricsId) } catch (err) { cb(err) } @@ -1649,6 +1650,20 @@ module.exports = class MetamaskController extends EventEmitter { } } + /** + * Sets the type of first time flow the user wishes to follow: create or import + * @param {String} type - Indicates the type of first time flow the user wishes to follow + * @param {Function} cb - A callback function called when complete. + */ + setFirstTimeFlowType (type, cb) { + try { + this.preferencesController.setFirstTimeFlowType(type) + cb(null) + } catch (err) { + cb(err) + } + } + /** * A method for setting a user's current locale, affecting the language rendered. diff --git a/ui/app/actions.js b/ui/app/actions.js index bbd12d912daf..0c467b186ed8 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -353,6 +353,9 @@ var actions = { approveProviderRequest, rejectProviderRequest, clearApprovedOrigins, + + setFirstTimeFlowType, + SET_FIRST_TIME_FLOW_TYPE: 'SET_FIRST_TIME_FLOW_TYPE', } module.exports = actions @@ -2593,7 +2596,6 @@ function callBackgroundThenUpdate (method, ...args) { function forceUpdateMetamaskState (dispatch) { log.debug(`background.getState`) - console.log('111111 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') return new Promise((resolve, reject) => { background.getState((err, newState) => { if (err) { @@ -2602,7 +2604,6 @@ function forceUpdateMetamaskState (dispatch) { } dispatch(actions.updateMetamaskState(newState)) - console.log('222222 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!', newState) resolve(newState) }) }) @@ -2618,13 +2619,14 @@ function setParticipateInMetaMetrics (val) { return (dispatch) => { log.debug(`background.setParticipateInMetaMetrics`) return new Promise((resolve, reject) => { - background.setParticipateInMetaMetrics(val, (err) => { + background.setParticipateInMetaMetrics(val, (err, metaMetricsId) => { + console.log('!@# err, metaMetricsId', err, metaMetricsId) if (err) { dispatch(actions.displayWarning(err.message)) return reject(err) } - resolve(val) + resolve([val, metaMetricsId]) }) dispatch({ type: actions.SET_PARTICIPATE_IN_METAMETRICS, @@ -2740,3 +2742,18 @@ function clearApprovedOrigins () { background.clearApprovedOrigins() } } + +function setFirstTimeFlowType (type) { + return (dispatch) => { + log.debug(`background.setFirstTimeFlowType`) + background.setFirstTimeFlowType(type, (err) => { + if (err) { + return dispatch(actions.displayWarning(err.message)) + } + }) + dispatch({ + type: actions.SET_FIRST_TIME_FLOW_TYPE, + value: type, + }) + } +} diff --git a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js index cf1f2bdf56c5..6e66b34cb840 100644 --- a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js +++ b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js @@ -1,19 +1,28 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import PageContainerFooter from '../../../page-container/page-container-footer' -import { INITIALIZE_CREATE_PASSWORD_ROUTE } from '../../../../routes' export default class MetaMetricsOptIn extends Component { static propTypes = { history: PropTypes.object, setParticipateInMetaMetrics: PropTypes.func, + nextRoute: PropTypes.string, + firstTimeSelectionMetaMetricsName: PropTypes.string, } static contextTypes = { - metricsEvent: PropTypes.func,// + metricsEvent: PropTypes.func, } render () { + const { metricsEvent } = this.context + const { + nextRoute, + history, + setParticipateInMetaMetrics, + firstTimeSelectionMetaMetricsName, + } = this.props + return (
@@ -86,27 +95,48 @@ export default class MetaMetricsOptIn extends Component {
{ - this.props.setParticipateInMetaMetrics(false) + setParticipateInMetaMetrics(false) .then(() => { - this.context.metricsEvent({ + metricsEvent({ eventOpts: { category: 'MetaMetricsOptIn', - action: 'userSelectsOptIn', + action: 'userSelectsOption', name: 'userOptedOut', }, isOptIn: true, }, { excludeMetaMetricsId: true, }) - this.props.history.push(INITIALIZE_CREATE_PASSWORD_ROUTE) + history.push(nextRoute) }) }} cancelText={'No Thanks'} hideCancel={false} onSubmit={() => { - this.props.setParticipateInMetaMetrics(true) - .then(() => { - this.props.history.push(INITIALIZE_CREATE_PASSWORD_ROUTE) + setParticipateInMetaMetrics(true) + .then(([participateStatus, metaMetricsId]) => { + return metricsEvent({ + eventOpts: { + category: 'MetaMetricsOptIn', + action: 'userSelectsOption', + name: 'userOptedIn', + }, + isOptIn: true, + }) + .then(() => { + return metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'importOrCreate', + name: firstTimeSelectionMetaMetricsName, + }, + isOptIn: true, + metaMetricsId, + }) + }) + .then(() => { + history.push(nextRoute) + }) }) }} submitText={'I agree'} diff --git a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js index 24809f942378..dd3d3898e415 100644 --- a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js +++ b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js @@ -1,6 +1,34 @@ import { connect } from 'react-redux' import MetaMetricsOptIn from './metametrics-opt-in.component' import { setParticipateInMetaMetrics } from '../../../../actions' +import { + INITIALIZE_CREATE_PASSWORD_ROUTE, + INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE, + DEFAULT_ROUTE, +} from '../../../../routes' + +const firstTimeFlowTypeNameMap = { + create: 'Selected Create New Wallet', + 'import': 'Selected Import Wallet', +} + +const mapStateToProps = ({ metamask }) => { + const { firstTimeFlowType } = metamask + + let nextRoute + if (firstTimeFlowType === 'create') { + nextRoute = INITIALIZE_CREATE_PASSWORD_ROUTE + } else if (firstTimeFlowType === 'import') { + nextRoute = INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE + } else { + nextRoute = DEFAULT_ROUTE + } + + return { + nextRoute, + firstTimeSelectionMetaMetricsName: firstTimeFlowTypeNameMap[firstTimeFlowType], + } +} const mapDispatchToProps = dispatch => { return { @@ -8,4 +36,4 @@ const mapDispatchToProps = dispatch => { } } -export default connect(null, mapDispatchToProps)(MetaMetricsOptIn) +export default connect(mapStateToProps, mapDispatchToProps)(MetaMetricsOptIn) diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js index 43effc60954c..402fd9a1082d 100644 --- a/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js +++ b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js @@ -49,7 +49,6 @@ export default class ConfirmSeedPhrase extends PureComponent { try { history.push(INITIALIZE_END_OF_FLOW_ROUTE) - await completeOnboarding() this.context.metricsEvent({ eventOpts: { category: 'Acquisition', diff --git a/ui/app/components/pages/first-time-flow/select-action/index.js b/ui/app/components/pages/first-time-flow/select-action/index.js index 3aa968834a63..4fbe1823ba27 100644 --- a/ui/app/components/pages/first-time-flow/select-action/index.js +++ b/ui/app/components/pages/first-time-flow/select-action/index.js @@ -1 +1 @@ -export { default } from './select-action.component' +export { default } from './select-action.container' diff --git a/ui/app/components/pages/first-time-flow/select-action/select-action.component.js b/ui/app/components/pages/first-time-flow/select-action/select-action.component.js index 385efe02afe3..5d5c004cfeac 100644 --- a/ui/app/components/pages/first-time-flow/select-action/select-action.component.js +++ b/ui/app/components/pages/first-time-flow/select-action/select-action.component.js @@ -2,8 +2,7 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' import Button from '../../../button' import { - INITIALIZE_CREATE_PASSWORD_ROUTE, - INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE, + INITIALIZE_METAMETRICS_OPT_IN_ROUTE, INITIALIZE_UNIQUE_IMAGE_ROUTE, } from '../../../../routes' @@ -11,6 +10,7 @@ export default class SelectAction extends PureComponent { static propTypes = { history: PropTypes.object, isInitialized: PropTypes.bool, + setFirstTimeFlowType: PropTypes.func, } static contextTypes = { @@ -26,11 +26,13 @@ export default class SelectAction extends PureComponent { } handleCreate = () => { - this.props.history.push(INITIALIZE_CREATE_PASSWORD_ROUTE) + this.props.setFirstTimeFlowType('create') + this.props.history.push(INITIALIZE_METAMETRICS_OPT_IN_ROUTE) } handleImport = () => { - this.props.history.push(INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE) + this.props.setFirstTimeFlowType('import') + this.props.history.push(INITIALIZE_METAMETRICS_OPT_IN_ROUTE) } render () { diff --git a/ui/app/components/pages/first-time-flow/select-action/select-action.container.js b/ui/app/components/pages/first-time-flow/select-action/select-action.container.js index e69de29bb2d1..85837965ae91 100644 --- a/ui/app/components/pages/first-time-flow/select-action/select-action.container.js +++ b/ui/app/components/pages/first-time-flow/select-action/select-action.container.js @@ -0,0 +1,16 @@ +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' +import { compose } from 'recompose' +import { setFirstTimeFlowType } from '../../../../actions' +import Welcome from './select-action.component' + +const mapDispatchToProps = dispatch => { + return { + setFirstTimeFlowType: type => dispatch(setFirstTimeFlowType(type)), + } +} + +export default compose( + withRouter, + connect(null, mapDispatchToProps) +)(Welcome) diff --git a/ui/app/metametrics/metametrics.provider.js b/ui/app/metametrics/metametrics.provider.js index 7b9c2647a50e..9f7b1cfe08e7 100644 --- a/ui/app/metametrics/metametrics.provider.js +++ b/ui/app/metametrics/metametrics.provider.js @@ -45,7 +45,7 @@ class MetaMetricsProvider extends Component { // if (userPermission) { if (props.participateInMetaMetrics || config.isOptIn) { - sendMetaMetricsEvent({ + return sendMetaMetricsEvent({ ...props, ...config, previousPath, diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js index 0dcafa1a0bd7..c1aa20bf78a7 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -55,6 +55,7 @@ function reduceMetamask (state, action) { useNativeCurrencyAsPrimaryCurrency: true, showFiatInTestnets: false, }, + firstTimeFlowType: null, completedOnboarding: false, knownMethodData: {}, participateInMetaMetrics: null, @@ -407,6 +408,12 @@ function reduceMetamask (state, action) { }) } + case actions.SET_FIRST_TIME_FLOW_TYPE: { + return extend(metamaskState, { + firstTimeFlowType: action.value, + }) + } + default: return metamaskState From 4c8eb5c88196682a3044e481d3742cbba2e1080b Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Wed, 27 Feb 2019 13:15:05 -0330 Subject: [PATCH 19/50] Update e2e tests to accomodate metametrics changes --- test/e2e/beta/fetch-mocks.js | 1 + test/e2e/beta/from-import-beta-ui.spec.js | 8 +++++++ .../beta/metamask-beta-responsive-ui.spec.js | 24 +++++++++++++++++++ test/e2e/beta/metamask-beta-ui.spec.js | 20 ++++++++++++---- .../pages/keychains/restore-vault.js | 4 ---- 5 files changed, 49 insertions(+), 8 deletions(-) diff --git a/test/e2e/beta/fetch-mocks.js b/test/e2e/beta/fetch-mocks.js index bb17025550e8..6b885cc10130 100644 --- a/test/e2e/beta/fetch-mocks.js +++ b/test/e2e/beta/fetch-mocks.js @@ -2,4 +2,5 @@ module.exports = { ethGasBasic: JSON.stringify({'average': 85.0, 'fastestWait': 0.6, 'fastWait': 0.6, 'fast': 200.0, 'safeLowWait': 4.8, 'blockNum': 6648312, 'avgWait': 4.2, 'block_time': 15.516129032258064, 'speed': 0.7828720873342716, 'fastest': 400.0, 'safeLow': 80.0}), ethGasPredictTable: JSON.stringify([{'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 7.2632423756, 'pct_remaining5m': 0.0, 'sum': 7.029975, 'tx_atabove': 4136.0, 'hashpower_accepting': 10.4166666667, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 1.2, 'pct_mined_5m': 0.0, 'total_seen_5m': 1.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 7.433788122, 'pct_remaining5m': 0.0, 'sum': 7.01731875, 'tx_atabove': 4136.0, 'hashpower_accepting': 10.9375, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 1.5, 'pct_mined_5m': 0.0, 'total_seen_5m': 84.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 7.433788122, 'pct_remaining5m': 0.0, 'sum': 7.01731875, 'tx_atabove': 4136.0, 'hashpower_accepting': 10.9375, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 1.7, 'pct_mined_5m': 0.0, 'total_seen_5m': 5.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 7.4638844302, 'pct_remaining5m': 0.0, 'sum': 7.01731875, 'tx_atabove': 4136.0, 'hashpower_accepting': 10.9375, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 1.8, 'pct_mined_5m': 0.0, 'total_seen_5m': 20.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 7.4839486356, 'pct_remaining5m': 0.0, 'sum': 7.01731875, 'tx_atabove': 4136.0, 'hashpower_accepting': 10.9375, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 1.9, 'pct_mined_5m': 0.0, 'total_seen_5m': 8.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 7.7347512039, 'pct_remaining5m': 0.0, 'sum': 7.0046625, 'tx_atabove': 4136.0, 'hashpower_accepting': 11.4583333333, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 2.0, 'pct_mined_5m': 0.0, 'total_seen_5m': 52.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 17.0, 'hashpower_accepting2': 7.7447833066, 'pct_remaining5m': 1.0, 'sum': 7.0046625, 'tx_atabove': 4136.0, 'hashpower_accepting': 11.4583333333, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 2.1, 'pct_mined_5m': 0.0, 'total_seen_5m': 97.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 20.0, 'hashpower_accepting2': 7.7447833066, 'pct_remaining5m': 1.0, 'sum': 7.0040625, 'tx_atabove': 4135.0, 'hashpower_accepting': 11.4583333333, 'hpa_coef2': -0.067, 'total_seen_30m': 6.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 2.2, 'pct_mined_5m': 0.0, 'total_seen_5m': 433.0, 'pct_mined_30m': 0.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 68.0, 'hashpower_accepting2': 7.7447833066, 'pct_remaining5m': 0.0, 'sum': 6.9986625, 'tx_atabove': 4126.0, 'hashpower_accepting': 11.4583333333, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 50.0, 'gasprice': 2.3, 'pct_mined_5m': 0.0, 'total_seen_5m': 14.0, 'pct_mined_30m': 0.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 7.7447833066, 'pct_remaining5m': 0.0, 'sum': 6.9980625, 'tx_atabove': 4125.0, 'hashpower_accepting': 11.4583333333, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 2.4, 'pct_mined_5m': 0.0, 'total_seen_5m': 4.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 20.0, 'hashpower_accepting2': 7.7447833066, 'pct_remaining5m': 37.0, 'sum': 6.9956625, 'tx_atabove': 4121.0, 'hashpower_accepting': 11.4583333333, 'hpa_coef2': -0.067, 'total_seen_30m': 4.0, 'int2': 6.9238, 'pct_remaining30m': 75.0, 'gasprice': 2.5, 'pct_mined_5m': 0.0, 'total_seen_5m': 45.0, 'pct_mined_30m': 0.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 79.0, 'hashpower_accepting2': 7.7447833066, 'pct_remaining5m': 0.0, 'sum': 6.9788625, 'tx_atabove': 4093.0, 'hashpower_accepting': 11.4583333333, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 100.0, 'gasprice': 2.6, 'pct_mined_5m': 0.0, 'total_seen_5m': 3.0, 'pct_mined_30m': 0.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 27.5, 'hashpower_accepting2': 7.7447833066, 'pct_remaining5m': 100.0, 'sum': 6.9764625, 'tx_atabove': 4089.0, 'hashpower_accepting': 11.4583333333, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 2.7, 'pct_mined_5m': 0.0, 'total_seen_5m': 3.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 22.5, 'hashpower_accepting2': 7.7447833066, 'pct_remaining5m': 66.0, 'sum': 6.9740625, 'tx_atabove': 4085.0, 'hashpower_accepting': 11.4583333333, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 2.8, 'pct_mined_5m': 0.0, 'total_seen_5m': 6.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 20.0, 'hashpower_accepting2': 7.7548154093, 'pct_remaining5m': 38.0, 'sum': 6.9686625, 'tx_atabove': 4076.0, 'hashpower_accepting': 11.4583333333, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 2.9, 'pct_mined_5m': 2.0, 'total_seen_5m': 36.0, 'pct_mined_30m': 0.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 27.0, 'hashpower_accepting2': 11.5268860353, 'pct_remaining5m': 77.0, 'sum': 6.8307, 'tx_atabove': 4057.0, 'hashpower_accepting': 16.6666666667, 'hpa_coef2': -0.067, 'total_seen_30m': 127.0, 'int2': 6.9238, 'pct_remaining30m': 48.0, 'gasprice': 3.0, 'pct_mined_5m': 0.0, 'total_seen_5m': 322.0, 'pct_mined_30m': 39.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 67.0, 'hashpower_accepting2': 11.5268860353, 'pct_remaining5m': 100.0, 'sum': 6.5697, 'tx_atabove': 3622.0, 'hashpower_accepting': 16.6666666667, 'hpa_coef2': -0.067, 'total_seen_30m': 85.0, 'int2': 6.9238, 'pct_remaining30m': 98.0, 'gasprice': 3.1, 'pct_mined_5m': 0.0, 'total_seen_5m': 79.0, 'pct_mined_30m': 0.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 71.0, 'hashpower_accepting2': 11.5268860353, 'pct_remaining5m': 100.0, 'sum': 6.4311, 'tx_atabove': 3391.0, 'hashpower_accepting': 16.6666666667, 'hpa_coef2': -0.067, 'total_seen_30m': 7.0, 'int2': 6.9238, 'pct_remaining30m': 100.0, 'gasprice': 3.2, 'pct_mined_5m': 0.0, 'total_seen_5m': 4.0, 'pct_mined_30m': 0.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 62.0, 'hashpower_accepting2': 11.5268860353, 'pct_remaining5m': 100.0, 'sum': 6.4209, 'tx_atabove': 3374.0, 'hashpower_accepting': 16.6666666667, 'hpa_coef2': -0.067, 'total_seen_30m': 14.0, 'int2': 6.9238, 'pct_remaining30m': 92.0, 'gasprice': 3.3, 'pct_mined_5m': 0.0, 'total_seen_5m': 7.0, 'pct_mined_30m': 0.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1472.0, 'hashpower_accepting2': 11.5569823435, 'pct_remaining5m': 100.0, 'sum': 6.3951, 'tx_atabove': 3331.0, 'hashpower_accepting': 16.6666666667, 'hpa_coef2': -0.067, 'total_seen_30m': 29.0, 'int2': 6.9238, 'pct_remaining30m': 100.0, 'gasprice': 3.4, 'pct_mined_5m': 0.0, 'total_seen_5m': 27.0, 'pct_mined_30m': 0.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 307.0, 'hashpower_accepting2': 11.5670144462, 'pct_remaining5m': 100.0, 'sum': 6.1521, 'tx_atabove': 2926.0, 'hashpower_accepting': 16.6666666667, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 3.7, 'pct_mined_5m': 0.0, 'total_seen_5m': 1.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1399.0, 'hashpower_accepting2': 11.577046549, 'pct_remaining5m': 100.0, 'sum': 6.1395, 'tx_atabove': 2905.0, 'hashpower_accepting': 16.6666666667, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 3.9, 'pct_mined_5m': 0.0, 'total_seen_5m': 1.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1005.0, 'hashpower_accepting2': 11.5971107544, 'pct_remaining5m': 88.0, 'sum': 6.1035, 'tx_atabove': 2845.0, 'hashpower_accepting': 16.6666666667, 'hpa_coef2': -0.067, 'total_seen_30m': 9.0, 'int2': 6.9238, 'pct_remaining30m': 88.0, 'gasprice': 4.0, 'pct_mined_5m': 0.0, 'total_seen_5m': 9.0, 'pct_mined_30m': 0.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1546.0, 'hashpower_accepting2': 11.6171749599, 'pct_remaining5m': null, 'sum': 5.6151, 'tx_atabove': 2031.0, 'hashpower_accepting': 16.6666666667, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 100.0, 'gasprice': 4.1, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 0.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1065.0, 'hashpower_accepting2': 11.6171749599, 'pct_remaining5m': 100.0, 'sum': 5.5509, 'tx_atabove': 1924.0, 'hashpower_accepting': 16.6666666667, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 4.3, 'pct_mined_5m': 0.0, 'total_seen_5m': 2.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 459.0, 'hashpower_accepting2': 11.6171749599, 'pct_remaining5m': 50.0, 'sum': 5.5137, 'tx_atabove': 1862.0, 'hashpower_accepting': 16.6666666667, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 4.4, 'pct_mined_5m': 0.0, 'total_seen_5m': 2.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 298.0, 'hashpower_accepting2': 11.6171749599, 'pct_remaining5m': null, 'sum': 5.4903, 'tx_atabove': 1823.0, 'hashpower_accepting': 16.6666666667, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 100.0, 'gasprice': 4.7, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 0.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 812.0, 'hashpower_accepting2': 11.6472712681, 'pct_remaining5m': 0.0, 'sum': 5.4831, 'tx_atabove': 1811.0, 'hashpower_accepting': 16.6666666667, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 100.0, 'gasprice': 4.8, 'pct_mined_5m': 0.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 0.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 541.0, 'hashpower_accepting2': 11.6472712681, 'pct_remaining5m': 100.0, 'sum': 5.4375, 'tx_atabove': 1735.0, 'hashpower_accepting': 16.6666666667, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 4.9, 'pct_mined_5m': 0.0, 'total_seen_5m': 1.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1134.0, 'hashpower_accepting2': 11.7375601926, 'pct_remaining5m': 100.0, 'sum': 5.41824375, 'tx_atabove': 1724.0, 'hashpower_accepting': 17.1875, 'hpa_coef2': -0.067, 'total_seen_30m': 6.0, 'int2': 6.9238, 'pct_remaining30m': 100.0, 'gasprice': 5.0, 'pct_mined_5m': 0.0, 'total_seen_5m': 5.0, 'pct_mined_30m': 0.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1958.0, 'hashpower_accepting2': 11.7676565008, 'pct_remaining5m': null, 'sum': 4.9567875, 'tx_atabove': 976.0, 'hashpower_accepting': 17.7083333333, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 100.0, 'gasprice': 5.2, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 0.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1203.5, 'hashpower_accepting2': 11.8077849117, 'pct_remaining5m': null, 'sum': 4.9507875, 'tx_atabove': 966.0, 'hashpower_accepting': 17.7083333333, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 100.0, 'gasprice': 5.3, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 0.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 677.5, 'hashpower_accepting2': 11.8378812199, 'pct_remaining5m': null, 'sum': 4.9141875, 'tx_atabove': 905.0, 'hashpower_accepting': 17.7083333333, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 100.0, 'gasprice': 5.5, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 0.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 250.99, 'avgdiff': 0, 'expectedWait': 1000.0, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 3.0, 'hashpower_accepting2': 13.3928571429, 'pct_remaining5m': 0.0, 'sum': 3.16120625, 'tx_atabove': 832.0, 'hashpower_accepting': 20.3125, 'hpa_coef2': -0.067, 'total_seen_30m': 9.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 6.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 12.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 5.92, 'avgdiff': 1, 'expectedWait': 23.5990451154, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 14.0248796148, 'pct_remaining5m': 0.0, 'sum': 3.10120625, 'tx_atabove': 732.0, 'hashpower_accepting': 20.3125, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 6.1, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 5.58, 'avgdiff': 1, 'expectedWait': 22.2247437161, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 14.1753611557, 'pct_remaining5m': 0.0, 'sum': 3.09640625, 'tx_atabove': 724.0, 'hashpower_accepting': 20.3125, 'hpa_coef2': -0.067, 'total_seen_30m': 3.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 6.2, 'pct_mined_5m': 100.0, 'total_seen_5m': 3.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 5.55, 'avgdiff': 1, 'expectedWait': 22.1183205662, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 14.3459069021, 'pct_remaining5m': 0.0, 'sum': 3.09580625, 'tx_atabove': 723.0, 'hashpower_accepting': 20.3125, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 6.3, 'pct_mined_5m': 100.0, 'total_seen_5m': 4.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 5.55, 'avgdiff': 1, 'expectedWait': 22.1050535543, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 14.3960674157, 'pct_remaining5m': 0.0, 'sum': 3.09460625, 'tx_atabove': 721.0, 'hashpower_accepting': 20.3125, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 6.4, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 5.54, 'avgdiff': 1, 'expectedWait': 22.0785433993, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 14.5465489567, 'pct_remaining5m': 0.0, 'sum': 3.09460625, 'tx_atabove': 721.0, 'hashpower_accepting': 20.3125, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 6.5, 'pct_mined_5m': 100.0, 'total_seen_5m': 3.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 5.54, 'avgdiff': 1, 'expectedWait': 22.0785433993, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 14.5666131621, 'pct_remaining5m': null, 'sum': 3.09460625, 'tx_atabove': 721.0, 'hashpower_accepting': 20.3125, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 6.6, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 5.54, 'avgdiff': 1, 'expectedWait': 22.0785433993, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 2.0, 'hashpower_accepting2': 14.6769662921, 'pct_remaining5m': null, 'sum': 3.09460625, 'tx_atabove': 721.0, 'hashpower_accepting': 20.3125, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 6.7, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 5.54, 'avgdiff': 1, 'expectedWait': 22.0785433993, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 14.7070626003, 'pct_remaining5m': null, 'sum': 3.09400625, 'tx_atabove': 720.0, 'hashpower_accepting': 20.3125, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 6.8, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 5.54, 'avgdiff': 1, 'expectedWait': 22.0653002466, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 14.7271268058, 'pct_remaining5m': 0.0, 'sum': 3.09400625, 'tx_atabove': 720.0, 'hashpower_accepting': 20.3125, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 6.9, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 5.54, 'avgdiff': 1, 'expectedWait': 22.0653002466, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 3.0, 'hashpower_accepting2': 15.4795345104, 'pct_remaining5m': 0.0, 'sum': 3.06749375, 'tx_atabove': 718.0, 'hashpower_accepting': 21.3541666667, 'hpa_coef2': -0.067, 'total_seen_30m': 11.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 7.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 6.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 5.39, 'avgdiff': 1, 'expectedWait': 21.4879808804, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 15.5898876404, 'pct_remaining5m': 0.0, 'sum': 3.06089375, 'tx_atabove': 707.0, 'hashpower_accepting': 21.3541666667, 'hpa_coef2': -0.067, 'total_seen_30m': 4.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 7.1, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 5.36, 'avgdiff': 1, 'expectedWait': 21.3466271869, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 15.5999197432, 'pct_remaining5m': null, 'sum': 3.06029375, 'tx_atabove': 706.0, 'hashpower_accepting': 21.3541666667, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 7.2, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 5.35, 'avgdiff': 1, 'expectedWait': 21.3338230522, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 15.8507223114, 'pct_remaining5m': 0.0, 'sum': 3.05969375, 'tx_atabove': 705.0, 'hashpower_accepting': 21.3541666667, 'hpa_coef2': -0.067, 'total_seen_30m': 5.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 7.6, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 5.35, 'avgdiff': 1, 'expectedWait': 21.3210265977, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 15.8607544141, 'pct_remaining5m': null, 'sum': 3.05909375, 'tx_atabove': 704.0, 'hashpower_accepting': 21.3541666667, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 7.7, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 0.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 5.35, 'avgdiff': 1, 'expectedWait': 21.3082378187, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 18.86035313, 'pct_remaining5m': 0.0, 'sum': 2.8933625, 'tx_atabove': 702.0, 'hashpower_accepting': 28.125, 'hpa_coef2': -0.067, 'total_seen_30m': 30.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 8.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 37.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 4.53, 'avgdiff': 1, 'expectedWait': 18.053913939, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1.0, 'hashpower_accepting2': 19.1011235955, 'pct_remaining5m': 0.0, 'sum': 2.85250625, 'tx_atabove': 655.0, 'hashpower_accepting': 28.6458333333, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 8.1, 'pct_mined_5m': 100.0, 'total_seen_5m': 5.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 4.35, 'avgdiff': 1, 'expectedWait': 17.331163684, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 19.1613162119, 'pct_remaining5m': 0.0, 'sum': 2.84890625, 'tx_atabove': 649.0, 'hashpower_accepting': 28.6458333333, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 8.2, 'pct_mined_5m': 100.0, 'total_seen_5m': 3.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 4.33, 'avgdiff': 1, 'expectedWait': 17.268883666, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 19.231540931, 'pct_remaining5m': 0.0, 'sum': 2.8097375, 'tx_atabove': 647.0, 'hashpower_accepting': 30.2083333333, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 8.4, 'pct_mined_5m': 100.0, 'total_seen_5m': 6.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 4.17, 'avgdiff': 1, 'expectedWait': 16.6055586875, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1.0, 'hashpower_accepting2': 19.5224719101, 'pct_remaining5m': 0.0, 'sum': 2.777225, 'tx_atabove': 635.0, 'hashpower_accepting': 31.25, 'hpa_coef2': -0.067, 'total_seen_30m': 7.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 8.5, 'pct_mined_5m': 100.0, 'total_seen_5m': 12.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 4.03, 'avgdiff': 1, 'expectedWait': 16.0743526708, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 19.7331460674, 'pct_remaining5m': 0.0, 'sum': 2.774225, 'tx_atabove': 630.0, 'hashpower_accepting': 31.25, 'hpa_coef2': -0.067, 'total_seen_30m': 5.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 8.6, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 4.02, 'avgdiff': 1, 'expectedWait': 16.0262018751, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 19.8033707865, 'pct_remaining5m': 0.0, 'sum': 2.72905625, 'tx_atabove': 618.0, 'hashpower_accepting': 32.8125, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 8.8, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 3.84, 'avgdiff': 1, 'expectedWait': 15.3184234339, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 19.9638844302, 'pct_remaining5m': 0.0, 'sum': 2.6954, 'tx_atabove': 583.0, 'hashpower_accepting': 33.3333333333, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 8.9, 'pct_mined_5m': 100.0, 'total_seen_5m': 6.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 3.72, 'avgdiff': 1, 'expectedWait': 14.8114421454, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 23.6155698234, 'pct_remaining5m': 0.0, 'sum': 2.3937875, 'tx_atabove': 460.0, 'hashpower_accepting': 42.7083333333, 'hpa_coef2': -0.067, 'total_seen_30m': 43.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 9.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 120.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 2.75, 'avgdiff': 1, 'expectedWait': 10.9549071782, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 24.0268860353, 'pct_remaining5m': 0.0, 'sum': 2.30313125, 'tx_atabove': 330.0, 'hashpower_accepting': 43.2291666667, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 9.1, 'pct_mined_5m': 100.0, 'total_seen_5m': 23.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 2.51, 'avgdiff': 1, 'expectedWait': 10.0054630618, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 24.1472712681, 'pct_remaining5m': 0.0, 'sum': 2.287475, 'tx_atabove': 325.0, 'hashpower_accepting': 43.75, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 9.2, 'pct_mined_5m': 100.0, 'total_seen_5m': 3.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 2.47, 'avgdiff': 1, 'expectedWait': 9.8500349165, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 24.2174959872, 'pct_remaining5m': 0.0, 'sum': 2.2609625, 'tx_atabove': 323.0, 'hashpower_accepting': 44.7916666667, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 9.3, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 2.41, 'avgdiff': 1, 'expectedWait': 9.5923173304, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 24.3880417335, 'pct_remaining5m': 0.0, 'sum': 2.22239375, 'tx_atabove': 322.0, 'hashpower_accepting': 46.3541666667, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 9.4, 'pct_mined_5m': 100.0, 'total_seen_5m': 7.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 2.32, 'avgdiff': 1, 'expectedWait': 9.2293973144, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1.0, 'hashpower_accepting2': 24.5284911717, 'pct_remaining5m': 0.0, 'sum': 2.2091375, 'tx_atabove': 321.0, 'hashpower_accepting': 46.875, 'hpa_coef2': -0.067, 'total_seen_30m': 5.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 9.5, 'pct_mined_5m': 100.0, 'total_seen_5m': 3.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 2.29, 'avgdiff': 1, 'expectedWait': 9.1078574773, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 24.7391653291, 'pct_remaining5m': 0.0, 'sum': 2.2073375, 'tx_atabove': 318.0, 'hashpower_accepting': 46.875, 'hpa_coef2': -0.067, 'total_seen_30m': 9.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 9.6, 'pct_mined_5m': 100.0, 'total_seen_5m': 8.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 2.28, 'avgdiff': 1, 'expectedWait': 9.0914780797, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 24.9699036918, 'pct_remaining5m': 0.0, 'sum': 2.182025, 'tx_atabove': 318.0, 'hashpower_accepting': 47.9166666667, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 9.7, 'pct_mined_5m': 88.0, 'total_seen_5m': 9.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 2.22, 'avgdiff': 1, 'expectedWait': 8.8642381788, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 2.0, 'hashpower_accepting2': 25.1203852327, 'pct_remaining5m': 0.0, 'sum': 2.16936875, 'tx_atabove': 318.0, 'hashpower_accepting': 48.4375, 'hpa_coef2': -0.067, 'total_seen_30m': 6.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 9.8, 'pct_mined_5m': 75.0, 'total_seen_5m': 4.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 2.2, 'avgdiff': 1, 'expectedWait': 8.7527571186, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 25.1705457464, 'pct_remaining5m': 0.0, 'sum': 2.1561125, 'tx_atabove': 317.0, 'hashpower_accepting': 48.9583333333, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 9.9, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 2.17, 'avgdiff': 1, 'expectedWait': 8.637494048, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 36.3864365971, 'pct_remaining5m': 0.0, 'sum': 1.769825, 'tx_atabove': 306.0, 'hashpower_accepting': 64.5833333333, 'hpa_coef2': -0.067, 'total_seen_30m': 353.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 10.0, 'pct_mined_5m': 99.0, 'total_seen_5m': 245.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 1.47, 'avgdiff': 1, 'expectedWait': 5.8698260519, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 36.536918138, 'pct_remaining5m': 0.0, 'sum': 1.733225, 'tx_atabove': 245.0, 'hashpower_accepting': 64.5833333333, 'hpa_coef2': -0.067, 'total_seen_30m': 7.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 10.1, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 1.42, 'avgdiff': 1, 'expectedWait': 5.658874382, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 36.7576243981, 'pct_remaining5m': 0.0, 'sum': 1.733225, 'tx_atabove': 245.0, 'hashpower_accepting': 64.5833333333, 'hpa_coef2': -0.067, 'total_seen_30m': 6.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 10.2, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 1.42, 'avgdiff': 1, 'expectedWait': 5.658874382, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 36.8378812199, 'pct_remaining5m': 0.0, 'sum': 1.732625, 'tx_atabove': 244.0, 'hashpower_accepting': 64.5833333333, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 10.3, 'pct_mined_5m': 100.0, 'total_seen_5m': 3.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 1.42, 'avgdiff': 1, 'expectedWait': 5.6554800758, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 36.8679775281, 'pct_remaining5m': 0.0, 'sum': 1.732025, 'tx_atabove': 243.0, 'hashpower_accepting': 64.5833333333, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 10.4, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 1.42, 'avgdiff': 1, 'expectedWait': 5.6520878055, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1.0, 'hashpower_accepting2': 37.8109951846, 'pct_remaining5m': 0.0, 'sum': 1.69405625, 'tx_atabove': 243.0, 'hashpower_accepting': 66.1458333333, 'hpa_coef2': -0.067, 'total_seen_30m': 12.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 10.5, 'pct_mined_5m': 100.0, 'total_seen_5m': 53.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 1.37, 'avgdiff': 1, 'expectedWait': 5.4415081179, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 37.871187801, 'pct_remaining5m': 0.0, 'sum': 1.69285625, 'tx_atabove': 241.0, 'hashpower_accepting': 66.1458333333, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 10.7, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 1.36, 'avgdiff': 1, 'expectedWait': 5.4349822245, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 38.1019261637, 'pct_remaining5m': 0.0, 'sum': 1.69285625, 'tx_atabove': 241.0, 'hashpower_accepting': 66.1458333333, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 10.8, 'pct_mined_5m': 100.0, 'total_seen_5m': 6.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 1.36, 'avgdiff': 1, 'expectedWait': 5.4349822245, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 38.1821829856, 'pct_remaining5m': 0.0, 'sum': 1.68565625, 'tx_atabove': 229.0, 'hashpower_accepting': 66.1458333333, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 10.9, 'pct_mined_5m': 100.0, 'total_seen_5m': 3.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 1.35, 'avgdiff': 1, 'expectedWait': 5.3959908897, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 40.7002407705, 'pct_remaining5m': 0.0, 'sum': 1.520525, 'tx_atabove': 228.0, 'hashpower_accepting': 72.9166666667, 'hpa_coef2': -0.067, 'total_seen_30m': 84.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 11.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 84.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 1.15, 'avgdiff': 1, 'expectedWait': 4.5746262436, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 40.8206260032, 'pct_remaining5m': 0.0, 'sum': 1.507325, 'tx_atabove': 206.0, 'hashpower_accepting': 72.9166666667, 'hpa_coef2': -0.067, 'total_seen_30m': 5.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 11.1, 'pct_mined_5m': 100.0, 'total_seen_5m': 4.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 1.13, 'avgdiff': 1, 'expectedWait': 4.5146379708, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 40.8908507223, 'pct_remaining5m': 0.0, 'sum': 1.507325, 'tx_atabove': 206.0, 'hashpower_accepting': 72.9166666667, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 11.2, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 1.13, 'avgdiff': 1, 'expectedWait': 4.5146379708, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 41.4024879615, 'pct_remaining5m': 0.0, 'sum': 1.49466875, 'tx_atabove': 206.0, 'hashpower_accepting': 73.4375, 'hpa_coef2': -0.067, 'total_seen_30m': 15.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 11.4, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 1.12, 'avgdiff': 1, 'expectedWait': 4.4578596422, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 41.4827447833, 'pct_remaining5m': 0.0, 'sum': 1.49466875, 'tx_atabove': 206.0, 'hashpower_accepting': 73.4375, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 11.5, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 1.12, 'avgdiff': 1, 'expectedWait': 4.4578596422, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 41.6131621188, 'pct_remaining5m': 0.0, 'sum': 1.49406875, 'tx_atabove': 205.0, 'hashpower_accepting': 73.4375, 'hpa_coef2': -0.067, 'total_seen_30m': 8.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 11.6, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 1.12, 'avgdiff': 1, 'expectedWait': 4.4551857287, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 41.6332263242, 'pct_remaining5m': 0.0, 'sum': 1.49406875, 'tx_atabove': 205.0, 'hashpower_accepting': 73.4375, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 11.7, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 1.12, 'avgdiff': 1, 'expectedWait': 4.4551857287, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 41.753611557, 'pct_remaining5m': 0.0, 'sum': 1.49406875, 'tx_atabove': 205.0, 'hashpower_accepting': 73.4375, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 11.8, 'pct_mined_5m': 100.0, 'total_seen_5m': 6.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 1.12, 'avgdiff': 1, 'expectedWait': 4.4551857287, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 41.7736757624, 'pct_remaining5m': null, 'sum': 1.49406875, 'tx_atabove': 205.0, 'hashpower_accepting': 73.4375, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 11.9, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 1.12, 'avgdiff': 1, 'expectedWait': 4.4551857287, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1.0, 'hashpower_accepting2': 44.7030497592, 'pct_remaining5m': 0.0, 'sum': 1.41813125, 'tx_atabove': 205.0, 'hashpower_accepting': 76.5625, 'hpa_coef2': -0.067, 'total_seen_30m': 96.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 12.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 39.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 1.04, 'avgdiff': 1, 'expectedWait': 4.1293964158, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 44.9036918138, 'pct_remaining5m': 0.0, 'sum': 1.399475, 'tx_atabove': 195.0, 'hashpower_accepting': 77.0833333333, 'hpa_coef2': -0.067, 'total_seen_30m': 11.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 12.1, 'pct_mined_5m': 100.0, 'total_seen_5m': 6.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 1.02, 'avgdiff': 1, 'expectedWait': 4.0530715456, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 45.0341091493, 'pct_remaining5m': null, 'sum': 1.38681875, 'tx_atabove': 195.0, 'hashpower_accepting': 77.6041666667, 'hpa_coef2': -0.067, 'total_seen_30m': 3.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 12.2, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 1.0, 'avgdiff': 1, 'expectedWait': 4.0020981056, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 45.1845906902, 'pct_remaining5m': 0.0, 'sum': 1.3735625, 'tx_atabove': 194.0, 'hashpower_accepting': 78.125, 'hpa_coef2': -0.067, 'total_seen_30m': 9.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 12.3, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.99, 'avgdiff': 1, 'expectedWait': 3.9493953846, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 45.1946227929, 'pct_remaining5m': null, 'sum': 1.3735625, 'tx_atabove': 194.0, 'hashpower_accepting': 78.125, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 12.4, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.99, 'avgdiff': 1, 'expectedWait': 3.9493953846, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 45.3752006421, 'pct_remaining5m': 0.0, 'sum': 1.36090625, 'tx_atabove': 194.0, 'hashpower_accepting': 78.6458333333, 'hpa_coef2': -0.067, 'total_seen_30m': 10.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 12.5, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.98, 'avgdiff': 1, 'expectedWait': 3.8997258274, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 45.4955858748, 'pct_remaining5m': 0.0, 'sum': 1.36090625, 'tx_atabove': 194.0, 'hashpower_accepting': 78.6458333333, 'hpa_coef2': -0.067, 'total_seen_30m': 4.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 12.6, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.98, 'avgdiff': 1, 'expectedWait': 3.8997258274, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 45.525682183, 'pct_remaining5m': null, 'sum': 1.36090625, 'tx_atabove': 194.0, 'hashpower_accepting': 78.6458333333, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 12.7, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.98, 'avgdiff': 1, 'expectedWait': 3.8997258274, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 45.5858747994, 'pct_remaining5m': 0.0, 'sum': 1.36090625, 'tx_atabove': 194.0, 'hashpower_accepting': 78.6458333333, 'hpa_coef2': -0.067, 'total_seen_30m': 4.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 12.8, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.98, 'avgdiff': 1, 'expectedWait': 3.8997258274, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 45.636035313, 'pct_remaining5m': 0.0, 'sum': 1.36090625, 'tx_atabove': 194.0, 'hashpower_accepting': 78.6458333333, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 12.9, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.98, 'avgdiff': 1, 'expectedWait': 3.8997258274, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1.0, 'hashpower_accepting2': 46.9903691814, 'pct_remaining5m': 0.0, 'sum': 1.31028125, 'tx_atabove': 194.0, 'hashpower_accepting': 80.7291666667, 'hpa_coef2': -0.067, 'total_seen_30m': 47.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 13.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 34.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.93, 'avgdiff': 1, 'expectedWait': 3.7072162202, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 47.5321027287, 'pct_remaining5m': 0.0, 'sum': 1.292825, 'tx_atabove': 186.0, 'hashpower_accepting': 81.25, 'hpa_coef2': -0.067, 'total_seen_30m': 9.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 13.2, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.91, 'avgdiff': 1, 'expectedWait': 3.6430636874, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 47.5621990369, 'pct_remaining5m': 0.0, 'sum': 1.292825, 'tx_atabove': 186.0, 'hashpower_accepting': 81.25, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 13.3, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.91, 'avgdiff': 1, 'expectedWait': 3.6430636874, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 47.632423756, 'pct_remaining5m': null, 'sum': 1.292825, 'tx_atabove': 186.0, 'hashpower_accepting': 81.25, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 13.4, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.91, 'avgdiff': 1, 'expectedWait': 3.6430636874, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.5, 'hashpower_accepting2': 48.1440609952, 'pct_remaining5m': 0.0, 'sum': 1.28016875, 'tx_atabove': 186.0, 'hashpower_accepting': 81.7708333333, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 13.5, 'pct_mined_5m': 100.0, 'total_seen_5m': 21.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.9, 'avgdiff': 1, 'expectedWait': 3.5972467097, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 48.4550561798, 'pct_remaining5m': 0.0, 'sum': 1.2651125, 'tx_atabove': 182.0, 'hashpower_accepting': 82.2916666667, 'hpa_coef2': -0.067, 'total_seen_30m': 10.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 13.6, 'pct_mined_5m': 100.0, 'total_seen_5m': 10.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.89, 'avgdiff': 1, 'expectedWait': 3.5434913565, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1.0, 'hashpower_accepting2': 55.9590690209, 'pct_remaining5m': 0.0, 'sum': 1.2398, 'tx_atabove': 182.0, 'hashpower_accepting': 83.3333333333, 'hpa_coef2': -0.067, 'total_seen_30m': 253.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 13.7, 'pct_mined_5m': 100.0, 'total_seen_5m': 212.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.87, 'avgdiff': 1, 'expectedWait': 3.4549224112, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 56.0593900482, 'pct_remaining5m': 0.0, 'sum': 1.226, 'tx_atabove': 159.0, 'hashpower_accepting': 83.3333333333, 'hpa_coef2': -0.067, 'total_seen_30m': 6.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 13.8, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.86, 'avgdiff': 1, 'expectedWait': 3.4075719515, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 56.1095505618, 'pct_remaining5m': 0.0, 'sum': 1.226, 'tx_atabove': 159.0, 'hashpower_accepting': 83.3333333333, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 13.9, 'pct_mined_5m': 100.0, 'total_seen_5m': 4.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.86, 'avgdiff': 1, 'expectedWait': 3.4075719515, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.5, 'hashpower_accepting2': 59.6408507223, 'pct_remaining5m': 0.0, 'sum': 1.13740625, 'tx_atabove': 159.0, 'hashpower_accepting': 86.9791666667, 'hpa_coef2': -0.067, 'total_seen_30m': 119.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 14.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 115.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.78, 'avgdiff': 1, 'expectedWait': 3.1186688184, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 59.7311396469, 'pct_remaining5m': 0.0, 'sum': 1.13440625, 'tx_atabove': 154.0, 'hashpower_accepting': 86.9791666667, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 14.1, 'pct_mined_5m': 100.0, 'total_seen_5m': 5.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.78, 'avgdiff': 1, 'expectedWait': 3.1093268319, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 59.8214285714, 'pct_remaining5m': 0.0, 'sum': 1.13440625, 'tx_atabove': 154.0, 'hashpower_accepting': 86.9791666667, 'hpa_coef2': -0.067, 'total_seen_30m': 4.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 14.2, 'pct_mined_5m': 100.0, 'total_seen_5m': 4.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.78, 'avgdiff': 1, 'expectedWait': 3.1093268319, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1.0, 'hashpower_accepting2': 60.1524879615, 'pct_remaining5m': 0.0, 'sum': 1.13380625, 'tx_atabove': 153.0, 'hashpower_accepting': 86.9791666667, 'hpa_coef2': -0.067, 'total_seen_30m': 5.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 14.4, 'pct_mined_5m': 100.0, 'total_seen_5m': 3.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.78, 'avgdiff': 1, 'expectedWait': 3.1074617954, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 60.1725521669, 'pct_remaining5m': 0.0, 'sum': 1.13320625, 'tx_atabove': 152.0, 'hashpower_accepting': 86.9791666667, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 14.5, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.78, 'avgdiff': 1, 'expectedWait': 3.1055978775, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 60.2528089888, 'pct_remaining5m': 0.0, 'sum': 1.13320625, 'tx_atabove': 152.0, 'hashpower_accepting': 86.9791666667, 'hpa_coef2': -0.067, 'total_seen_30m': 3.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 14.6, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.78, 'avgdiff': 1, 'expectedWait': 3.1055978775, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 60.6440609952, 'pct_remaining5m': 0.0, 'sum': 1.13320625, 'tx_atabove': 152.0, 'hashpower_accepting': 86.9791666667, 'hpa_coef2': -0.067, 'total_seen_30m': 10.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 14.7, 'pct_mined_5m': 100.0, 'total_seen_5m': 10.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.78, 'avgdiff': 1, 'expectedWait': 3.1055978775, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 60.6641252006, 'pct_remaining5m': 0.0, 'sum': 1.13320625, 'tx_atabove': 152.0, 'hashpower_accepting': 86.9791666667, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 14.8, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.78, 'avgdiff': 1, 'expectedWait': 3.1055978775, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 60.6942215088, 'pct_remaining5m': null, 'sum': 1.13320625, 'tx_atabove': 152.0, 'hashpower_accepting': 86.9791666667, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 14.9, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.78, 'avgdiff': 1, 'expectedWait': 3.1055978775, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 62.9113162119, 'pct_remaining5m': 0.0, 'sum': 1.0952375, 'tx_atabove': 152.0, 'hashpower_accepting': 88.5416666667, 'hpa_coef2': -0.067, 'total_seen_30m': 65.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 15.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 48.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.75, 'avgdiff': 1, 'expectedWait': 2.9898926986, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 63.4129213483, 'pct_remaining5m': 0.0, 'sum': 1.0910375, 'tx_atabove': 145.0, 'hashpower_accepting': 88.5416666667, 'hpa_coef2': -0.067, 'total_seen_30m': 11.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 15.1, 'pct_mined_5m': 100.0, 'total_seen_5m': 8.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.75, 'avgdiff': 1, 'expectedWait': 2.9773614832, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1.0, 'hashpower_accepting2': 64.4161316212, 'pct_remaining5m': 0.0, 'sum': 1.0886375, 'tx_atabove': 141.0, 'hashpower_accepting': 88.5416666667, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 15.5, 'pct_mined_5m': 100.0, 'total_seen_5m': 4.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.75, 'avgdiff': 1, 'expectedWait': 2.9702243836, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 64.4863563403, 'pct_remaining5m': 0.0, 'sum': 1.0820375, 'tx_atabove': 130.0, 'hashpower_accepting': 88.5416666667, 'hpa_coef2': -0.067, 'total_seen_30m': 5.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 15.6, 'pct_mined_5m': 50.0, 'total_seen_5m': 2.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.74, 'avgdiff': 1, 'expectedWait': 2.9506854521, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1.0, 'hashpower_accepting2': 66.1817817014, 'pct_remaining5m': 0.0, 'sum': 1.0820375, 'tx_atabove': 130.0, 'hashpower_accepting': 88.5416666667, 'hpa_coef2': -0.067, 'total_seen_30m': 24.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 15.7, 'pct_mined_5m': 100.0, 'total_seen_5m': 17.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.74, 'avgdiff': 1, 'expectedWait': 2.9506854521, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 66.2620385233, 'pct_remaining5m': 0.0, 'sum': 1.0766375, 'tx_atabove': 121.0, 'hashpower_accepting': 88.5416666667, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 15.8, 'pct_mined_5m': 83.0, 'total_seen_5m': 6.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.74, 'avgdiff': 1, 'expectedWait': 2.9347946943, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 67.2853130016, 'pct_remaining5m': 0.0, 'sum': 1.0766375, 'tx_atabove': 121.0, 'hashpower_accepting': 88.5416666667, 'hpa_coef2': -0.067, 'total_seen_30m': 15.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 16.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 11.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.74, 'avgdiff': 1, 'expectedWait': 2.9347946943, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 67.2953451043, 'pct_remaining5m': null, 'sum': 1.0748375, 'tx_atabove': 118.0, 'hashpower_accepting': 88.5416666667, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 16.1, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.74, 'avgdiff': 1, 'expectedWait': 2.9295168154, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 67.4458266453, 'pct_remaining5m': 0.0, 'sum': 1.0748375, 'tx_atabove': 118.0, 'hashpower_accepting': 88.5416666667, 'hpa_coef2': -0.067, 'total_seen_30m': 3.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 16.2, 'pct_mined_5m': 100.0, 'total_seen_5m': 7.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.74, 'avgdiff': 1, 'expectedWait': 2.9295168154, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 67.455858748, 'pct_remaining5m': 0.0, 'sum': 1.0748375, 'tx_atabove': 118.0, 'hashpower_accepting': 88.5416666667, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 16.3, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.74, 'avgdiff': 1, 'expectedWait': 2.9295168154, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 67.6565008026, 'pct_remaining5m': 0.0, 'sum': 1.0748375, 'tx_atabove': 118.0, 'hashpower_accepting': 88.5416666667, 'hpa_coef2': -0.067, 'total_seen_30m': 5.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 16.4, 'pct_mined_5m': 100.0, 'total_seen_5m': 7.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.74, 'avgdiff': 1, 'expectedWait': 2.9295168154, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 67.7267255217, 'pct_remaining5m': 0.0, 'sum': 1.0742375, 'tx_atabove': 117.0, 'hashpower_accepting': 88.5416666667, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 16.5, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.73, 'avgdiff': 1, 'expectedWait': 2.9277596325, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1.0, 'hashpower_accepting2': 68.6697431782, 'pct_remaining5m': 0.0, 'sum': 1.0742375, 'tx_atabove': 117.0, 'hashpower_accepting': 88.5416666667, 'hpa_coef2': -0.067, 'total_seen_30m': 42.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 16.7, 'pct_mined_5m': 100.0, 'total_seen_5m': 27.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.73, 'avgdiff': 1, 'expectedWait': 2.9277596325, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 68.8904494382, 'pct_remaining5m': 0.0, 'sum': 1.0718375, 'tx_atabove': 113.0, 'hashpower_accepting': 88.5416666667, 'hpa_coef2': -0.067, 'total_seen_30m': 11.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 17.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 3.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.73, 'avgdiff': 1, 'expectedWait': 2.9207414346, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 69.0308988764, 'pct_remaining5m': 0.0, 'sum': 1.0712375, 'tx_atabove': 112.0, 'hashpower_accepting': 88.5416666667, 'hpa_coef2': -0.067, 'total_seen_30m': 8.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 17.1, 'pct_mined_5m': 100.0, 'total_seen_5m': 3.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.73, 'avgdiff': 1, 'expectedWait': 2.9189895153, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 69.0409309791, 'pct_remaining5m': null, 'sum': 1.0706375, 'tx_atabove': 111.0, 'hashpower_accepting': 88.5416666667, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 17.3, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.73, 'avgdiff': 1, 'expectedWait': 2.9172386469, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 69.0710272873, 'pct_remaining5m': 0.0, 'sum': 1.0706375, 'tx_atabove': 111.0, 'hashpower_accepting': 88.5416666667, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 17.5, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.73, 'avgdiff': 1, 'expectedWait': 2.9172386469, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 69.08105939, 'pct_remaining5m': 0.0, 'sum': 1.0706375, 'tx_atabove': 111.0, 'hashpower_accepting': 88.5416666667, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 17.6, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.73, 'avgdiff': 1, 'expectedWait': 2.9172386469, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 69.1011235955, 'pct_remaining5m': null, 'sum': 1.0706375, 'tx_atabove': 111.0, 'hashpower_accepting': 88.5416666667, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 17.8, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.73, 'avgdiff': 1, 'expectedWait': 2.9172386469, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 69.1111556982, 'pct_remaining5m': 0.0, 'sum': 1.0706375, 'tx_atabove': 111.0, 'hashpower_accepting': 88.5416666667, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 17.9, 'pct_mined_5m': 0.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.73, 'avgdiff': 1, 'expectedWait': 2.9172386469, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 69.7632423756, 'pct_remaining5m': 0.0, 'sum': 1.05798125, 'tx_atabove': 111.0, 'hashpower_accepting': 89.0625, 'hpa_coef2': -0.067, 'total_seen_30m': 16.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 18.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 16.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.72, 'avgdiff': 1, 'expectedWait': 2.8805500054, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 69.7732744783, 'pct_remaining5m': 0.0, 'sum': 1.05618125, 'tx_atabove': 108.0, 'hashpower_accepting': 89.0625, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 18.1, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.72, 'avgdiff': 1, 'expectedWait': 2.875369679, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 69.8134028892, 'pct_remaining5m': 0.0, 'sum': 1.05618125, 'tx_atabove': 108.0, 'hashpower_accepting': 89.0625, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 18.5, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.72, 'avgdiff': 1, 'expectedWait': 2.875369679, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 69.823434992, 'pct_remaining5m': 0.0, 'sum': 1.05618125, 'tx_atabove': 108.0, 'hashpower_accepting': 89.0625, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 18.7, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.72, 'avgdiff': 1, 'expectedWait': 2.875369679, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 69.8535313002, 'pct_remaining5m': null, 'sum': 1.05618125, 'tx_atabove': 108.0, 'hashpower_accepting': 89.0625, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 18.8, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.72, 'avgdiff': 1, 'expectedWait': 2.875369679, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 69.8735955056, 'pct_remaining5m': 0.0, 'sum': 1.05618125, 'tx_atabove': 108.0, 'hashpower_accepting': 89.0625, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 18.9, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.72, 'avgdiff': 1, 'expectedWait': 2.875369679, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 70.3150080257, 'pct_remaining5m': 0.0, 'sum': 1.043525, 'tx_atabove': 108.0, 'hashpower_accepting': 89.5833333333, 'hpa_coef2': -0.067, 'total_seen_30m': 13.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 19.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 11.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.71, 'avgdiff': 1, 'expectedWait': 2.8392076024, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 70.3350722311, 'pct_remaining5m': 0.0, 'sum': 1.042325, 'tx_atabove': 106.0, 'hashpower_accepting': 89.5833333333, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 19.3, 'pct_mined_5m': 66.0, 'total_seen_5m': 3.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.71, 'avgdiff': 1, 'expectedWait': 2.8358025967, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 70.3752006421, 'pct_remaining5m': 0.0, 'sum': 1.042325, 'tx_atabove': 106.0, 'hashpower_accepting': 89.5833333333, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 19.4, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.71, 'avgdiff': 1, 'expectedWait': 2.8358025967, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 70.3852327448, 'pct_remaining5m': null, 'sum': 1.042325, 'tx_atabove': 106.0, 'hashpower_accepting': 89.5833333333, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 19.5, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.71, 'avgdiff': 1, 'expectedWait': 2.8358025967, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 70.4052969502, 'pct_remaining5m': null, 'sum': 1.042325, 'tx_atabove': 106.0, 'hashpower_accepting': 89.5833333333, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 19.8, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.71, 'avgdiff': 1, 'expectedWait': 2.8358025967, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 70.8266452648, 'pct_remaining5m': 0.0, 'sum': 1.042325, 'tx_atabove': 106.0, 'hashpower_accepting': 89.5833333333, 'hpa_coef2': -0.067, 'total_seen_30m': 18.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 19.9, 'pct_mined_5m': 100.0, 'total_seen_5m': 10.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.71, 'avgdiff': 1, 'expectedWait': 2.8358025967, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 75.2708667737, 'pct_remaining5m': 0.0, 'sum': 0.90310625, 'tx_atabove': 106.0, 'hashpower_accepting': 95.3125, 'hpa_coef2': -0.067, 'total_seen_30m': 144.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 20.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 185.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.62, 'avgdiff': 1, 'expectedWait': 2.4672551317, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 75.3009630819, 'pct_remaining5m': null, 'sum': 0.89650625, 'tx_atabove': 95.0, 'hashpower_accepting': 95.3125, 'hpa_coef2': -0.067, 'total_seen_30m': 3.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 20.2, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.62, 'avgdiff': 1, 'expectedWait': 2.4510248666, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 75.3210272873, 'pct_remaining5m': 0.0, 'sum': 0.89650625, 'tx_atabove': 95.0, 'hashpower_accepting': 95.3125, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 20.3, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.62, 'avgdiff': 1, 'expectedWait': 2.4510248666, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 77.7387640449, 'pct_remaining5m': 0.0, 'sum': 0.88385, 'tx_atabove': 95.0, 'hashpower_accepting': 95.8333333333, 'hpa_coef2': -0.067, 'total_seen_30m': 24.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 20.5, 'pct_mined_5m': 100.0, 'total_seen_5m': 9.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.61, 'avgdiff': 1, 'expectedWait': 2.420199561, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 77.7688603531, 'pct_remaining5m': 0.0, 'sum': 0.88325, 'tx_atabove': 94.0, 'hashpower_accepting': 95.8333333333, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 20.7, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.61, 'avgdiff': 1, 'expectedWait': 2.4187478768, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 77.9093097913, 'pct_remaining5m': null, 'sum': 0.87059375, 'tx_atabove': 94.0, 'hashpower_accepting': 96.3541666667, 'hpa_coef2': -0.067, 'total_seen_30m': 7.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 20.9, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.6, 'avgdiff': 1, 'expectedWait': 2.3883285027, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 79.7752808989, 'pct_remaining5m': 0.0, 'sum': 0.87059375, 'tx_atabove': 94.0, 'hashpower_accepting': 96.3541666667, 'hpa_coef2': -0.067, 'total_seen_30m': 39.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 21.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 36.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.6, 'avgdiff': 1, 'expectedWait': 2.3883285027, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 79.7953451043, 'pct_remaining5m': 0.0, 'sum': 0.86819375, 'tx_atabove': 90.0, 'hashpower_accepting': 96.3541666667, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 21.1, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.6, 'avgdiff': 1, 'expectedWait': 2.3826033871, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 79.8154093098, 'pct_remaining5m': null, 'sum': 0.86819375, 'tx_atabove': 90.0, 'hashpower_accepting': 96.3541666667, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 21.4, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.6, 'avgdiff': 1, 'expectedWait': 2.3826033871, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 79.845505618, 'pct_remaining5m': 0.0, 'sum': 0.86819375, 'tx_atabove': 90.0, 'hashpower_accepting': 96.3541666667, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 21.7, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.6, 'avgdiff': 1, 'expectedWait': 2.3826033871, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 80.3069823435, 'pct_remaining5m': 0.0, 'sum': 0.86819375, 'tx_atabove': 90.0, 'hashpower_accepting': 96.3541666667, 'hpa_coef2': -0.067, 'total_seen_30m': 9.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 21.9, 'pct_mined_5m': 100.0, 'total_seen_5m': 3.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.6, 'avgdiff': 1, 'expectedWait': 2.3826033871, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 80.8888443018, 'pct_remaining5m': 0.0, 'sum': 0.8555375, 'tx_atabove': 90.0, 'hashpower_accepting': 96.875, 'hpa_coef2': -0.067, 'total_seen_30m': 13.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 22.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 13.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.59, 'avgdiff': 1, 'expectedWait': 2.352638584, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 80.9089085072, 'pct_remaining5m': null, 'sum': 0.8555375, 'tx_atabove': 90.0, 'hashpower_accepting': 96.875, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 22.1, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.59, 'avgdiff': 1, 'expectedWait': 2.352638584, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 80.91894061, 'pct_remaining5m': 0.0, 'sum': 0.8555375, 'tx_atabove': 90.0, 'hashpower_accepting': 96.875, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 22.2, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.59, 'avgdiff': 1, 'expectedWait': 2.352638584, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 80.9289727127, 'pct_remaining5m': null, 'sum': 0.8555375, 'tx_atabove': 90.0, 'hashpower_accepting': 96.875, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 22.3, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.59, 'avgdiff': 1, 'expectedWait': 2.352638584, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 80.9490369181, 'pct_remaining5m': 0.0, 'sum': 0.8555375, 'tx_atabove': 90.0, 'hashpower_accepting': 96.875, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 22.5, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.59, 'avgdiff': 1, 'expectedWait': 2.352638584, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 80.9791332263, 'pct_remaining5m': 0.0, 'sum': 0.8555375, 'tx_atabove': 90.0, 'hashpower_accepting': 96.875, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 22.8, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.59, 'avgdiff': 1, 'expectedWait': 2.352638584, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 80.9891653291, 'pct_remaining5m': null, 'sum': 0.8555375, 'tx_atabove': 90.0, 'hashpower_accepting': 96.875, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 22.9, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.59, 'avgdiff': 1, 'expectedWait': 2.352638584, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 81.47070626, 'pct_remaining5m': 0.0, 'sum': 0.8555375, 'tx_atabove': 90.0, 'hashpower_accepting': 96.875, 'hpa_coef2': -0.067, 'total_seen_30m': 15.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 23.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 19.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.59, 'avgdiff': 1, 'expectedWait': 2.352638584, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 81.4907704655, 'pct_remaining5m': 0.0, 'sum': 0.8531375, 'tx_atabove': 86.0, 'hashpower_accepting': 96.875, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 23.1, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.59, 'avgdiff': 1, 'expectedWait': 2.3469990216, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 81.5108346709, 'pct_remaining5m': 0.0, 'sum': 0.8525375, 'tx_atabove': 85.0, 'hashpower_accepting': 96.875, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 23.3, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.59, 'avgdiff': 1, 'expectedWait': 2.3455912446, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 81.6011235955, 'pct_remaining5m': 0.0, 'sum': 0.8525375, 'tx_atabove': 85.0, 'hashpower_accepting': 96.875, 'hpa_coef2': -0.067, 'total_seen_30m': 3.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 23.7, 'pct_mined_5m': 100.0, 'total_seen_5m': 3.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.59, 'avgdiff': 1, 'expectedWait': 2.3455912446, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 81.621187801, 'pct_remaining5m': 0.0, 'sum': 0.8525375, 'tx_atabove': 85.0, 'hashpower_accepting': 96.875, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 23.9, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.59, 'avgdiff': 1, 'expectedWait': 2.3455912446, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 81.8719903692, 'pct_remaining5m': 0.0, 'sum': 0.8525375, 'tx_atabove': 85.0, 'hashpower_accepting': 96.875, 'hpa_coef2': -0.067, 'total_seen_30m': 7.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 24.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 9.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.59, 'avgdiff': 1, 'expectedWait': 2.3455912446, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 81.8820224719, 'pct_remaining5m': 0.0, 'sum': 0.8501375, 'tx_atabove': 81.0, 'hashpower_accepting': 96.875, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 24.3, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.59, 'avgdiff': 1, 'expectedWait': 2.3399685755, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 81.9121187801, 'pct_remaining5m': 0.0, 'sum': 0.8501375, 'tx_atabove': 81.0, 'hashpower_accepting': 96.875, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 24.6, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.59, 'avgdiff': 1, 'expectedWait': 2.3399685755, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 81.9321829856, 'pct_remaining5m': 0.0, 'sum': 0.8501375, 'tx_atabove': 81.0, 'hashpower_accepting': 96.875, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 24.7, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.59, 'avgdiff': 1, 'expectedWait': 2.3399685755, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 83.3868378812, 'pct_remaining5m': 0.0, 'sum': 0.83688125, 'tx_atabove': 80.0, 'hashpower_accepting': 97.3958333333, 'hpa_coef2': -0.067, 'total_seen_30m': 24.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 25.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 32.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.58, 'avgdiff': 1, 'expectedWait': 2.3091540608, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 83.6276083467, 'pct_remaining5m': 0.0, 'sum': 0.82128125, 'tx_atabove': 54.0, 'hashpower_accepting': 97.3958333333, 'hpa_coef2': -0.067, 'total_seen_30m': 10.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 26.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 7.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.57, 'avgdiff': 1, 'expectedWait': 2.2734107799, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 83.7379614767, 'pct_remaining5m': 0.0, 'sum': 0.82008125, 'tx_atabove': 52.0, 'hashpower_accepting': 97.3958333333, 'hpa_coef2': -0.067, 'total_seen_30m': 5.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 27.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.57, 'avgdiff': 1, 'expectedWait': 2.2706843231, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 83.9887640449, 'pct_remaining5m': 0.0, 'sum': 0.81948125, 'tx_atabove': 51.0, 'hashpower_accepting': 97.3958333333, 'hpa_coef2': -0.067, 'total_seen_30m': 13.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 28.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 7.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.57, 'avgdiff': 1, 'expectedWait': 2.2693223212, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 84.0690208668, 'pct_remaining5m': 0.0, 'sum': 0.806825, 'tx_atabove': 51.0, 'hashpower_accepting': 97.9166666667, 'hpa_coef2': -0.067, 'total_seen_30m': 4.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 29.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.56, 'avgdiff': 1, 'expectedWait': 2.240782197, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 85.8447030498, 'pct_remaining5m': 0.0, 'sum': 0.7809125, 'tx_atabove': 50.0, 'hashpower_accepting': 98.9583333333, 'hpa_coef2': -0.067, 'total_seen_30m': 55.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 30.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 49.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.55, 'avgdiff': 1, 'expectedWait': 2.1834637674, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 85.9751203852, 'pct_remaining5m': 0.0, 'sum': 0.7743125, 'tx_atabove': 39.0, 'hashpower_accepting': 98.9583333333, 'hpa_coef2': -0.067, 'total_seen_30m': 6.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 31.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 5.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.54, 'avgdiff': 1, 'expectedWait': 2.169100358, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 86.1356340289, 'pct_remaining5m': 0.0, 'sum': 0.7743125, 'tx_atabove': 39.0, 'hashpower_accepting': 98.9583333333, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 32.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 4.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.54, 'avgdiff': 1, 'expectedWait': 2.169100358, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 86.2760834671, 'pct_remaining5m': 0.0, 'sum': 0.7743125, 'tx_atabove': 39.0, 'hashpower_accepting': 98.9583333333, 'hpa_coef2': -0.067, 'total_seen_30m': 3.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 33.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 6.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.54, 'avgdiff': 1, 'expectedWait': 2.169100358, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 86.4165329053, 'pct_remaining5m': 0.0, 'sum': 0.7737125, 'tx_atabove': 38.0, 'hashpower_accepting': 98.9583333333, 'hpa_coef2': -0.067, 'total_seen_30m': 6.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 34.0, 'pct_mined_5m': 60.0, 'total_seen_5m': 5.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.54, 'avgdiff': 1, 'expectedWait': 2.1677992881, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.5, 'hashpower_accepting2': 86.7475922953, 'pct_remaining5m': 0.0, 'sum': 0.7737125, 'tx_atabove': 38.0, 'hashpower_accepting': 98.9583333333, 'hpa_coef2': -0.067, 'total_seen_30m': 10.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 35.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 17.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.54, 'avgdiff': 1, 'expectedWait': 2.1677992881, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 86.7877207063, 'pct_remaining5m': 0.0, 'sum': 0.7725125, 'tx_atabove': 36.0, 'hashpower_accepting': 98.9583333333, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 36.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.54, 'avgdiff': 1, 'expectedWait': 2.1651994891, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 86.8579454254, 'pct_remaining5m': 0.0, 'sum': 0.7725125, 'tx_atabove': 36.0, 'hashpower_accepting': 98.9583333333, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 37.0, 'pct_mined_5m': 66.0, 'total_seen_5m': 3.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.54, 'avgdiff': 1, 'expectedWait': 2.1651994891, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 86.908105939, 'pct_remaining5m': 0.0, 'sum': 0.7725125, 'tx_atabove': 36.0, 'hashpower_accepting': 98.9583333333, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 38.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.54, 'avgdiff': 1, 'expectedWait': 2.1651994891, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 86.9783306581, 'pct_remaining5m': 0.0, 'sum': 0.7725125, 'tx_atabove': 36.0, 'hashpower_accepting': 98.9583333333, 'hpa_coef2': -0.067, 'total_seen_30m': 3.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 39.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.54, 'avgdiff': 1, 'expectedWait': 2.1651994891, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1.0, 'hashpower_accepting2': 87.4498394864, 'pct_remaining5m': 0.0, 'sum': 0.75985625, 'tx_atabove': 36.0, 'hashpower_accepting': 99.4791666667, 'hpa_coef2': -0.067, 'total_seen_30m': 16.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 40.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 14.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.54, 'avgdiff': 1, 'expectedWait': 2.1379688654, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1.0, 'hashpower_accepting2': 89.6167736758, 'pct_remaining5m': 0.0, 'sum': 0.75805625, 'tx_atabove': 33.0, 'hashpower_accepting': 99.4791666667, 'hpa_coef2': -0.067, 'total_seen_30m': 112.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 41.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 57.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.54, 'avgdiff': 1, 'expectedWait': 2.1341239829, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 89.6869983949, 'pct_remaining5m': 0.0, 'sum': 0.75445625, 'tx_atabove': 27.0, 'hashpower_accepting': 99.4791666667, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 42.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 3.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.53, 'avgdiff': 1, 'expectedWait': 2.1264549491, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 89.7070626003, 'pct_remaining5m': null, 'sum': 0.75385625, 'tx_atabove': 26.0, 'hashpower_accepting': 99.4791666667, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 43.0, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.53, 'avgdiff': 1, 'expectedWait': 2.1251794588, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1.0, 'hashpower_accepting2': 89.9478330658, 'pct_remaining5m': 0.0, 'sum': 0.75385625, 'tx_atabove': 26.0, 'hashpower_accepting': 99.4791666667, 'hpa_coef2': -0.067, 'total_seen_30m': 9.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 44.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 9.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.53, 'avgdiff': 1, 'expectedWait': 2.1251794588, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 90.088282504, 'pct_remaining5m': 0.0, 'sum': 0.7394, 'tx_atabove': 23.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 8.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 45.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 5.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.53, 'avgdiff': 1, 'expectedWait': 2.0946783304, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1.0, 'hashpower_accepting2': 90.4995987159, 'pct_remaining5m': 0.0, 'sum': 0.7394, 'tx_atabove': 23.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 17.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 46.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 19.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.53, 'avgdiff': 1, 'expectedWait': 2.0946783304, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 90.5999197432, 'pct_remaining5m': 0.0, 'sum': 0.7382, 'tx_atabove': 21.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 4.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 47.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.53, 'avgdiff': 1, 'expectedWait': 2.0921662239, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 90.6199839486, 'pct_remaining5m': null, 'sum': 0.7382, 'tx_atabove': 21.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 48.0, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.53, 'avgdiff': 1, 'expectedWait': 2.0921662239, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 90.6500802568, 'pct_remaining5m': null, 'sum': 0.7382, 'tx_atabove': 21.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 49.0, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.53, 'avgdiff': 1, 'expectedWait': 2.0921662239, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1.0, 'hashpower_accepting2': 94.1412520064, 'pct_remaining5m': 0.0, 'sum': 0.7382, 'tx_atabove': 21.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 62.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 50.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 71.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.53, 'avgdiff': 1, 'expectedWait': 2.0921662239, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 94.2114767255, 'pct_remaining5m': 0.0, 'sum': 0.7358, 'tx_atabove': 17.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 51.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 3.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0871510456, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 94.4422150883, 'pct_remaining5m': 0.0, 'sum': 0.7358, 'tx_atabove': 17.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 5.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 52.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0871510456, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 94.4723113965, 'pct_remaining5m': null, 'sum': 0.7358, 'tx_atabove': 17.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 54.0, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0871510456, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 94.4823434992, 'pct_remaining5m': null, 'sum': 0.7346, 'tx_atabove': 15.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 56.0, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0846479665, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 95.0140449438, 'pct_remaining5m': 0.0, 'sum': 0.7346, 'tx_atabove': 15.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 27.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 60.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 17.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0846479665, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 95.6059390048, 'pct_remaining5m': 0.0, 'sum': 0.7334, 'tx_atabove': 13.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 28.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 61.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 14.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0821478893, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 96.8699839486, 'pct_remaining5m': 0.0, 'sum': 0.7322, 'tx_atabove': 11.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 21.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 63.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 25.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0796508104, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 1.0, 'hashpower_accepting2': 98.3948635634, 'pct_remaining5m': 0.0, 'sum': 0.7298, 'tx_atabove': 7.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 83.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 64.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 38.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0746656331, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 98.4149277689, 'pct_remaining5m': 0.0, 'sum': 0.728, 'tx_atabove': 4.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 65.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0709345939, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 98.4951845907, 'pct_remaining5m': 0.0, 'sum': 0.7274, 'tx_atabove': 3.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 3.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 66.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 3.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0696924058, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 98.5052166934, 'pct_remaining5m': null, 'sum': 0.7274, 'tx_atabove': 3.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 69.0, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0696924058, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 98.5152487961, 'pct_remaining5m': null, 'sum': 0.7274, 'tx_atabove': 3.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 70.0, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0696924058, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 98.5553772071, 'pct_remaining5m': 0.0, 'sum': 0.7274, 'tx_atabove': 3.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 72.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0696924058, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 98.5754414125, 'pct_remaining5m': 0.0, 'sum': 0.7274, 'tx_atabove': 3.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 73.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0696924058, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 98.5854735152, 'pct_remaining5m': 0.0, 'sum': 0.7274, 'tx_atabove': 3.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 77.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0696924058, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 98.6055377207, 'pct_remaining5m': null, 'sum': 0.7274, 'tx_atabove': 3.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 79.0, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0696924058, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 98.8663723917, 'pct_remaining5m': 0.0, 'sum': 0.7274, 'tx_atabove': 3.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 14.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 80.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 6.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0696924058, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 98.8864365971, 'pct_remaining5m': 0.0, 'sum': 0.7268, 'tx_atabove': 2.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 84.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0684509628, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 98.9466292135, 'pct_remaining5m': 0.0, 'sum': 0.7268, 'tx_atabove': 2.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 88.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0684509628, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 98.9566613162, 'pct_remaining5m': null, 'sum': 0.7268, 'tx_atabove': 2.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 90.0, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0684509628, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 99.0469502408, 'pct_remaining5m': 0.0, 'sum': 0.7268, 'tx_atabove': 2.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 4.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 91.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 3.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0684509628, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 99.1272070626, 'pct_remaining5m': 0.0, 'sum': 0.7268, 'tx_atabove': 2.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 3.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 96.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0684509628, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 99.1372391653, 'pct_remaining5m': 0.0, 'sum': 0.7268, 'tx_atabove': 2.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 97.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0684509628, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 99.1472712681, 'pct_remaining5m': 0.0, 'sum': 0.7268, 'tx_atabove': 2.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 99.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0684509628, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 99.7090690209, 'pct_remaining5m': 0.0, 'sum': 0.7268, 'tx_atabove': 2.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 23.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 100.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 22.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0684509628, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 99.8394863563, 'pct_remaining5m': 0.0, 'sum': 0.7268, 'tx_atabove': 2.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 7.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 101.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0684509628, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 99.9097110754, 'pct_remaining5m': 0.0, 'sum': 0.7268, 'tx_atabove': 2.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 2.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 120.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0684509628, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 99.9297752809, 'pct_remaining5m': 0.0, 'sum': 0.7268, 'tx_atabove': 2.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': null, 'int2': 6.9238, 'pct_remaining30m': null, 'gasprice': 134.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 2.0, 'pct_mined_30m': null, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0684509628, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': 0.0, 'hashpower_accepting2': 99.9498394864, 'pct_remaining5m': 0.0, 'sum': 0.7268, 'tx_atabove': 2.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 137.0, 'pct_mined_5m': 100.0, 'total_seen_5m': 1.0, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0684509628, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}, {'intercept': 4.8015, 'age': null, 'hashpower_accepting2': 99.9699036918, 'pct_remaining5m': null, 'sum': 0.7256, 'tx_atabove': 0.0, 'hashpower_accepting': 100.0, 'hpa_coef2': -0.067, 'total_seen_30m': 1.0, 'int2': 6.9238, 'pct_remaining30m': 0.0, 'gasprice': 180.0, 'pct_mined_5m': null, 'total_seen_5m': null, 'pct_mined_30m': 100.0, 'tx_atabove_coef': 0.0006, 'average': 600, 'safelow': 600, 'nomine': 550, 'expectedTime': 0.52, 'avgdiff': 1, 'expectedWait': 2.0659703104, 'avgdiff_coef': -1.6459, 'hpa_coef': -0.0243}]), gasExpress: JSON.stringify({'safeLow': 2.0, 'standard': 3.0, 'fast': 10.0, 'fastest': 52.0, 'block_time': 15, 'blockNum': 6693030}), + metametrics: JSON.stringify({ 'mockMetaMetricsResponse': true }), } diff --git a/test/e2e/beta/from-import-beta-ui.spec.js b/test/e2e/beta/from-import-beta-ui.spec.js index ad3e8b1fe2a1..42dd72f60d66 100644 --- a/test/e2e/beta/from-import-beta-ui.spec.js +++ b/test/e2e/beta/from-import-beta-ui.spec.js @@ -72,6 +72,8 @@ describe('Using MetaMask with an existing account', function () { 'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasBasic + '\')) }); } else if ' + '(args[0] === "https://ethgasstation.info/json/predictTable.json") { return ' + 'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasPredictTable + '\')) }); } else if ' + + '(args[0].match(/chromeextensionmm/)) { return ' + + 'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.metametrics + '\')) }); } else if ' + '(args[0] === "https://dev.blockscale.net/api/gasexpress.json") { return ' + 'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.gasExpress + '\')) }); } ' + 'return window.origFetch(...args); }' @@ -110,6 +112,12 @@ describe('Using MetaMask with an existing account', function () { await delay(largeDelayMs) }) + it('clicks the "No thanks" option on the metametrics opt-in screen', async () => { + const optOutButton = await findElement(driver, By.css('.btn-default')) + optOutButton.click() + await delay(largeDelayMs) + }) + it('imports a seed phrase', async () => { const [seedTextArea] = await findElements(driver, By.css('textarea.first-time-flow__textarea')) await seedTextArea.sendKeys(testSeedPhrase) diff --git a/test/e2e/beta/metamask-beta-responsive-ui.spec.js b/test/e2e/beta/metamask-beta-responsive-ui.spec.js index 781811d6d3f3..29b0f56f7d2d 100644 --- a/test/e2e/beta/metamask-beta-responsive-ui.spec.js +++ b/test/e2e/beta/metamask-beta-responsive-ui.spec.js @@ -18,6 +18,7 @@ const { loadExtension, verboseReportOnFailure, } = require('./helpers') +const fetchMockResponses = require('./fetch-mocks.js') describe('MetaMask', function () { let extensionId @@ -61,6 +62,23 @@ describe('MetaMask', function () { await driver.get(extensionUrl) }) + beforeEach(async function () { + await driver.executeScript( + 'window.origFetch = window.fetch.bind(window);' + + 'window.fetch = ' + + '(...args) => { ' + + 'if (args[0] === "https://ethgasstation.info/json/ethgasAPI.json") { return ' + + 'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasBasic + '\')) }); } else if ' + + '(args[0] === "https://ethgasstation.info/json/predictTable.json") { return ' + + 'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasPredictTable + '\')) }); } else if ' + + '(args[0].match(/chromeextensionmm/)) { return ' + + 'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.metametrics + '\')) }); } else if ' + + '(args[0] === "https://dev.blockscale.net/api/gasexpress.json") { return ' + + 'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.gasExpress + '\')) }); } ' + + 'return window.origFetch(...args); }' + ) + }) + afterEach(async function () { if (process.env.SELENIUM_BROWSER === 'chrome') { const errors = await checkBrowserForConsoleErrors(driver) @@ -93,6 +111,12 @@ describe('MetaMask', function () { await delay(largeDelayMs) }) + it('clicks the "No thanks" option on the metametrics opt-in screen', async () => { + const optOutButton = await findElement(driver, By.css('.btn-default')) + optOutButton.click() + await delay(largeDelayMs) + }) + it('accepts a secure password', async () => { const passwordBox = await findElement(driver, By.css('.first-time-flow__form #create-password')) const passwordBoxConfirm = await findElement(driver, By.css('.first-time-flow__form #confirm-password')) diff --git a/test/e2e/beta/metamask-beta-ui.spec.js b/test/e2e/beta/metamask-beta-ui.spec.js index 26aa50a43924..f369a8b18eb7 100644 --- a/test/e2e/beta/metamask-beta-ui.spec.js +++ b/test/e2e/beta/metamask-beta-ui.spec.js @@ -76,6 +76,8 @@ describe('MetaMask', function () { 'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasBasic + '\')) }); } else if ' + '(args[0] === "https://ethgasstation.info/json/predictTable.json") { return ' + 'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasPredictTable + '\')) }); } else if ' + + '(args[0].match(/chromeextensionmm/)) { return ' + + 'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.metametrics + '\')) }); } else if ' + '(args[0] === "https://dev.blockscale.net/api/gasexpress.json") { return ' + 'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.gasExpress + '\')) }); } ' + 'return window.origFetch(...args); }' @@ -114,6 +116,12 @@ describe('MetaMask', function () { await delay(largeDelayMs) }) + it('clicks the "No thanks" option on the metametrics opt-in screen', async () => { + const optOutButton = await findElement(driver, By.css('.btn-default')) + optOutButton.click() + await delay(largeDelayMs) + }) + it('accepts a secure password', async () => { const passwordBox = await findElement(driver, By.css('.first-time-flow__form #create-password')) const passwordBoxConfirm = await findElement(driver, By.css('.first-time-flow__form #confirm-password')) @@ -688,12 +696,16 @@ describe('MetaMask', function () { }) it('rejects a transaction', async () => { + await delay(tinyDelayMs / 2) const rejectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Reject')]`), 10000) + await delay(tinyDelayMs / 2) await rejectButton.click() await delay(regularDelayMs) const navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation')) + await delay(tinyDelayMs / 2) const navigationText = await navigationElement.getText() + await delay(tinyDelayMs / 2) assert.equal(navigationText.includes('3'), true, 'transaction rejected') }) @@ -864,9 +876,9 @@ describe('MetaMask', function () { const balance = await findElement(driver, By.css('.transaction-view-balance__primary-balance')) await delay(regularDelayMs) if (process.env.SELENIUM_BROWSER !== 'firefox') { - await driver.wait(until.elementTextMatches(balance, /^87.*\s*ETH.*$/), 10000) + await driver.wait(until.elementTextMatches(balance, /^(75|76).*\s*ETH.*$/), 10000) const tokenAmount = await balance.getText() - assert.ok(/^87.*\s*ETH.*$/.test(tokenAmount)) + assert.ok(/^(75|76).*\s*ETH.*$/.test(tokenAmount)) await delay(regularDelayMs) } }) @@ -1124,7 +1136,7 @@ describe('MetaMask', function () { const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary')) await driver.wait(until.elementTextMatches(txValues[0], /-7\s*TST/)) const txStatuses = await findElements(driver, By.css('.transaction-list-item__action')) - await driver.wait(until.elementTextMatches(txStatuses[0], /Sent\sToken/)) + await driver.wait(until.elementTextMatches(txStatuses[0], /Sent\sToken/), 10000) const walletBalance = await findElement(driver, By.css('.wallet-balance')) await walletBalance.click() @@ -1137,7 +1149,7 @@ describe('MetaMask', function () { // or possibly until we use latest version of firefox in the tests if (process.env.SELENIUM_BROWSER !== 'firefox') { const tokenBalanceAmount = await findElements(driver, By.css('.transaction-view-balance__primary-balance')) - await driver.wait(until.elementTextMatches(tokenBalanceAmount[0], /43\s*TST/)) + await driver.wait(until.elementTextMatches(tokenBalanceAmount[0], /43\s*TST/), 10000) } }) }) diff --git a/ui/app/components/pages/keychains/restore-vault.js b/ui/app/components/pages/keychains/restore-vault.js index 0ab9bdfccb93..73ff5191a295 100644 --- a/ui/app/components/pages/keychains/restore-vault.js +++ b/ui/app/components/pages/keychains/restore-vault.js @@ -186,10 +186,6 @@ class RestoreVaultPage extends Component { } } -RestoreVaultPage.contextTypes = { - t: PropTypes.func, -} - export default connect( ({ appState: { warning, isLoading } }) => ({ warning, isLoading }), dispatch => ({ From 65caafca12df797280ad8da070e471de6986952a Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Wed, 27 Feb 2019 13:19:45 -0330 Subject: [PATCH 20/50] Mock out metametrics network requests in integration tests --- test/integration/lib/confirm-sig-requests.js | 8 ++++++++ test/integration/lib/currency-localization.js | 9 +++++++++ test/integration/lib/send-new-ui.js | 2 ++ test/integration/lib/tx-list-items.js | 8 ++++++++ 4 files changed, 27 insertions(+) diff --git a/test/integration/lib/confirm-sig-requests.js b/test/integration/lib/confirm-sig-requests.js index 041a1af341f1..e4540c4f2682 100644 --- a/test/integration/lib/confirm-sig-requests.js +++ b/test/integration/lib/confirm-sig-requests.js @@ -3,6 +3,7 @@ const { timeout, queryAsync, } = require('../../lib/util') +const fetchMockResponses = require('../../e2e/beta/fetch-mocks.js') QUnit.module('confirm sig requests') @@ -19,6 +20,13 @@ async function runConfirmSigRequestsTest (assert, done) { selectState.val('confirm sig requests') reactTriggerChange(selectState[0]) + global.fetch = (...args) => { + if (args[0].match(/chromeextensionmm/)) { + return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.metametrics)) }) + } + return window.fetch(...args) + } + const pendingRequestItem = $.find('.transaction-list-item .transaction-list-item__grid') if (pendingRequestItem[2]) { diff --git a/test/integration/lib/currency-localization.js b/test/integration/lib/currency-localization.js index f6b751ba2e10..cd10efa303d6 100644 --- a/test/integration/lib/currency-localization.js +++ b/test/integration/lib/currency-localization.js @@ -4,6 +4,7 @@ const { queryAsync, findAsync, } = require('../../lib/util') +const fetchMockResponses = require('../../e2e/beta/fetch-mocks.js') QUnit.module('currency localization') @@ -19,6 +20,14 @@ async function runCurrencyLocalizationTest (assert, done) { console.log('*** start runCurrencyLocalizationTest') const selectState = await queryAsync($, 'select') selectState.val('currency localization') + + global.fetch = (...args) => { + if (args[0].match(/chromeextensionmm/)) { + return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.metametrics)) }) + } + return window.fetch(...args) + } + await timeout(1000) reactTriggerChange(selectState[0]) await timeout(1000) diff --git a/test/integration/lib/send-new-ui.js b/test/integration/lib/send-new-ui.js index 145c9ad3b864..d7003f4cce98 100644 --- a/test/integration/lib/send-new-ui.js +++ b/test/integration/lib/send-new-ui.js @@ -32,6 +32,8 @@ async function runSendFlowTest (assert, done) { return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.ethGasPredictTable)) }) } else if (args[0] === 'https://dev.blockscale.net/api/gasexpress.json') { return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.gasExpress)) }) + } else if (args[0].match(/chromeextensionmm/)) { + return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.metametrics)) }) } return window.fetch(...args) } diff --git a/test/integration/lib/tx-list-items.js b/test/integration/lib/tx-list-items.js index bf6f87937914..c0056dd22e42 100644 --- a/test/integration/lib/tx-list-items.js +++ b/test/integration/lib/tx-list-items.js @@ -3,6 +3,7 @@ const { queryAsync, findAsync, } = require('../../lib/util') +const fetchMockResponses = require('../../e2e/beta/fetch-mocks.js') QUnit.module('tx list items') @@ -25,6 +26,13 @@ async function runTxListItemsTest (assert, done) { selectState.val('tx list items') reactTriggerChange(selectState[0]) + global.fetch = (...args) => { + if (args[0].match(/chromeextensionmm/)) { + return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.metametrics)) }) + } + return window.fetch(...args) + } + const metamaskLogo = await queryAsync($, '.app-header__logo-container') assert.ok(metamaskLogo[0], 'metamask logo present') metamaskLogo[0].click() From ae5857205c19094c2d1baecce9a2617d8076352a Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Wed, 27 Feb 2019 22:02:58 -0330 Subject: [PATCH 21/50] Fix tx-list integration test to support metametrics provider. --- development/states/tx-list-items.json | 3 ++- ui/app/metametrics/metametrics.provider.js | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/development/states/tx-list-items.json b/development/states/tx-list-items.json index 7939e3d94a4b..d4e3f3860672 100644 --- a/development/states/tx-list-items.json +++ b/development/states/tx-list-items.json @@ -1403,5 +1403,6 @@ ], "priceAndTimeEstimatesLastRetrieved": 1541527901281, "errors": {} - } + }, + "confirmTransaction": {} } diff --git a/ui/app/metametrics/metametrics.provider.js b/ui/app/metametrics/metametrics.provider.js index 9f7b1cfe08e7..7fb5b9ee7182 100644 --- a/ui/app/metametrics/metametrics.provider.js +++ b/ui/app/metametrics/metametrics.provider.js @@ -79,12 +79,14 @@ MetaMetricsProvider.childContextTypes = { } const mapStateToProps = state => { + const txData = txDataSelector(state) || {} + return { network: getCurrentNetworkId(state), environmentType: getEnvironmentType(), activeCurrency: getSelectedAsset(state), accountType: getAccountType(state), - confirmTransactionOrigin: txDataSelector(state).origin, + confirmTransactionOrigin: txData.origin, metaMetricsId: state.metamask.metaMetricsId, participateInMetaMetrics: state.metamask.participateInMetaMetrics, metaMetricsSendCount: state.metamask.metaMetricsSendCount, From ec2934dea0bf11d520ae6820e608fead5b62ddda Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Thu, 28 Feb 2019 11:56:28 -0330 Subject: [PATCH 22/50] Send number of tokens and accounts data with every metametrics event. --- ui/app/metametrics/metametrics.provider.js | 4 ++++ ui/app/metametrics/metametrics.util.js | 12 ++++++++---- ui/app/selectors.js | 11 +++++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/ui/app/metametrics/metametrics.provider.js b/ui/app/metametrics/metametrics.provider.js index 7fb5b9ee7182..e71bb24704c8 100644 --- a/ui/app/metametrics/metametrics.provider.js +++ b/ui/app/metametrics/metametrics.provider.js @@ -7,6 +7,8 @@ import { getCurrentNetworkId, getSelectedAsset, getAccountType, + getNumberOfAccounts, + getNumberOfTokens, } from '../selectors' import { txDataSelector, @@ -90,6 +92,8 @@ const mapStateToProps = state => { metaMetricsId: state.metamask.metaMetricsId, participateInMetaMetrics: state.metamask.participateInMetaMetrics, metaMetricsSendCount: state.metamask.metaMetricsSendCount, + numberOfTokens: getNumberOfTokens(state), + numberOfAccounts: getNumberOfAccounts(state), } } diff --git a/ui/app/metametrics/metametrics.util.js b/ui/app/metametrics/metametrics.util.js index a81fdc2770e6..e897829bde0a 100644 --- a/ui/app/metametrics/metametrics.util.js +++ b/ui/app/metametrics/metametrics.util.js @@ -12,13 +12,13 @@ const METAMETRICS_CUSTOM_FUNCTION_TYPE = 'functionType' const METAMETRICS_CUSTOM_GAS_LIMIT_CHANGE = 'gasLimitChange' const METAMETRICS_CUSTOM_GAS_PRICE_CHANGE = 'gasPriceChange' const METAMETRICS_CUSTOM_RECIPIENT_KNOWN = 'recipientKnown' -const METAMETRICS_CUSTOM_NUMBER_OF_TOKENS = 'numberOfTokens' -const METAMETRICS_CUSTOM_NUMBER_OF_ACCOUNTS = 'numberOfAccounts' const METAMETRICS_CUSTOM_NETWORK = 'network' const METAMETRICS_CUSTOM_ENVIRONMENT_TYPE = 'environmentType' const METAMETRICS_CUSTOM_ACTIVE_CURRENCY = 'activeCurrency' const METAMETRICS_CUSTOM_ACCOUNT_TYPE = 'accountType' +const METAMETRICS_CUSTOM_NUMBER_OF_TOKENS = 'numberOfTokens' +const METAMETRICS_CUSTOM_NUMBER_OF_ACCOUNTS = 'numberOfAccounts' const customVariableNameIdMap = { [METAMETRICS_CUSTOM_HAD_ERROR]: 1, @@ -27,8 +27,6 @@ const customVariableNameIdMap = { [METAMETRICS_CUSTOM_GAS_LIMIT_CHANGE]: 4, [METAMETRICS_CUSTOM_GAS_PRICE_CHANGE]: 5, [METAMETRICS_CUSTOM_RECIPIENT_KNOWN]: 6, - [METAMETRICS_CUSTOM_NUMBER_OF_TOKENS]: 7, - [METAMETRICS_CUSTOM_NUMBER_OF_ACCOUNTS]: 8, } const customDimensionsNameIdMap = { @@ -36,6 +34,8 @@ const customDimensionsNameIdMap = { [METAMETRICS_CUSTOM_ENVIRONMENT_TYPE]: 6, [METAMETRICS_CUSTOM_ACTIVE_CURRENCY]: 7, [METAMETRICS_CUSTOM_ACCOUNT_TYPE]: 8, + [METAMETRICS_CUSTOM_NUMBER_OF_TOKENS]: 9, + [METAMETRICS_CUSTOM_NUMBER_OF_ACCOUNTS]: 10, } function composeUrlRefParamAddition (previousPath, confirmTransactionOrigin) { @@ -80,6 +80,8 @@ function composeUrl (config, permissionPreferences = {}) { environmentType, activeCurrency, accountType, + numberOfTokens, + numberOfAccounts, previousPath = '', currentPath, metaMetricsId, @@ -106,6 +108,8 @@ function composeUrl (config, permissionPreferences = {}) { environmentType, activeCurrency, accountType, + numberOfTokens: customVariables && customVariables.numberOfTokens || numberOfTokens, + numberOfAccounts: customVariables && customVariables.numberOfAccounts || numberOfAccounts, }) : '' const url = configUrl || `&url=${encodeURIComponent(currentPath.replace(/chrome-extension:\/\/\w+/, 'http://www.metamask.io/metametrics'))}` const _id = metaMetricsId && !excludeMetaMetricsId ? `&_id=${metaMetricsId.slice(2, 18)}` : '' diff --git a/ui/app/selectors.js b/ui/app/selectors.js index 983ed50a454e..a83324e4b0c0 100644 --- a/ui/app/selectors.js +++ b/ui/app/selectors.js @@ -44,6 +44,8 @@ const selectors = { getSelectedAsset, getCurrentKeyring, getAccountType, + getNumberOfAccounts, + getNumberOfTokens, } module.exports = selectors @@ -107,6 +109,15 @@ function getSelectedIdentity (state) { return identities[selectedAddress] } +function getNumberOfAccounts (state) { + return Object.keys(state.metamask.accounts).length +} + +function getNumberOfTokens (state) { + const tokens = state.metamask.tokens + return tokens ? tokens.length : 0 +} + function getMetaMaskAccounts (state) { const currentAccounts = state.metamask.accounts const cachedBalances = state.metamask.cachedBalances[state.metamask.network] From 556bc99ce5b6fd5737e5d9e317bc7e3c376316e5 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Thu, 28 Feb 2019 12:54:45 -0330 Subject: [PATCH 23/50] Update metametrics event descriptor schema and add new events. --- ui/app/app.js | 14 +++++ .../account-menu/account-menu.component.js | 33 +++++++---- .../app-header/app-header.component.js | 36 ++++++----- .../app-header/app-header.container.js | 2 + .../dropdowns/account-details-dropdown.js | 19 ++++-- .../components/dropdowns/network-dropdown.js | 28 +++++++-- .../components/menu-bar/menu-bar.component.js | 6 +- .../components/menu-bar/menu-bar.container.js | 3 +- .../confirm-transaction-base.component.js | 59 +++++++++++-------- .../create-account/connect-hardware/index.js | 18 ++++++ .../create-account/import-account/json.js | 15 +++++ .../import-account/private-key.js | 15 +++++ .../pages/create-account/new-account.js | 13 ++-- .../import-with-seed-phrase.component.js | 15 +++++ .../new-account/new-account.component.js | 14 ++++- .../unique-image/unique-image.component.js | 6 +- .../end-of-flow/end-of-flow.component.js | 11 +++- .../end-of-flow/end-of-flow.container.js | 16 ++++- .../metametrics-opt-in.component.js | 14 ++--- .../confirm-seed-phrase.component.js | 6 +- .../reveal-seed-phrase.component.js | 14 ++++- .../settings-tab/settings-tab.component.js | 44 +++++++------- .../unlock-page/unlock-page.component.js | 14 ++--- .../provider-page-container.component.js | 18 +++--- .../send-gas-row/send-gas-row.component.js | 21 ++++--- .../send-to-row/send-to-row.component.js | 12 +++- .../send/send-footer/send-footer.component.js | 51 ++++++++-------- .../send/send-footer/send-footer.container.js | 2 + .../tests/send-footer-component.test.js | 1 + .../tests/send-footer-container.test.js | 2 + .../sender-to-recipient.component.js | 8 ++- .../components/sidebars/sidebar.component.js | 8 ++- ui/app/components/signature-request.js | 18 ++---- ui/app/components/token-cell.js | 12 ++++ .../transaction-activity-log.component.js | 1 + ...transaction-list-item-details.component.js | 35 +++++++++++ .../transaction-list-item.component.js | 17 +++--- .../transaction-view-balance.component.js | 21 ++++--- ui/app/components/wallet-view.js | 8 ++- ui/app/ducks/confirm-transaction.duck.js | 26 -------- 40 files changed, 459 insertions(+), 217 deletions(-) diff --git a/ui/app/app.js b/ui/app/app.js index c70540b89e26..8254a36a0efd 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -15,6 +15,7 @@ const ConfirmTransaction = require('./components/pages/confirm-transaction') // slideout menu const Sidebar = require('./components/sidebars').default +const { WALLET_VIEW_SIDEBAR } = require('./components/sidebars/sidebar.constants') // other views import Home from './components/pages/home' @@ -175,6 +176,18 @@ class App extends Component { this.getConnectingLabel(loadingMessage) : null log.debug('Main ui render function') + const sidebarOnOverlayClose = sidebarType === WALLET_VIEW_SIDEBAR + ? () => { + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Wallet Sidebar', + name: 'Closed Sidebare Via Overlay', + }, + }) + } + : null + const { isOpen: sidebarIsOpen, transitionName: sidebarTransitionName, @@ -214,6 +227,7 @@ class App extends Component { transitionName={sidebarTransitionName} type={sidebarType} sidebarProps={sidebar.props} + onOverlayClose={sidebarOnOverlayClose} /> { this.context.metricsEvent({ eventOpts: { - category: 'Activation/Retention', - action: 'userSwitchedAccounts', - name: 'navSwitchAccounts', + category: 'Navigation', + action: 'Main Menu', + name: 'Switched Account', }, }) showAccountDetail(identity.address) @@ -243,9 +243,9 @@ export default class AccountMenu extends PureComponent { toggleAccountMenu() metricsEvent({ eventOpts: { - category: 'Accounts', - action: 'userClick', - name: 'accountsNewAccount', + category: 'Navigation', + action: 'Main Menu', + name: 'Clicked Create Accoount', }, }) history.push(NEW_ACCOUNT_ROUTE) @@ -261,6 +261,13 @@ export default class AccountMenu extends PureComponent { { toggleAccountMenu() + metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Main Menu', + name: 'Clicked Import Account', + }, + }) history.push(IMPORT_ACCOUNT_ROUTE) }} icon={ @@ -274,7 +281,13 @@ export default class AccountMenu extends PureComponent { { toggleAccountMenu() - + metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Main Menu', + name: 'Clicked Connect Hardware', + }, + }) if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) { global.platform.openExtensionInBrowser(CONNECT_HARDWARE_ROUTE) } else { @@ -306,9 +319,9 @@ export default class AccountMenu extends PureComponent { history.push(SETTINGS_ROUTE) this.context.metricsEvent({ eventOpts: { - category: 'Activation/Retention', - action: 'userClickSettings', - name: 'settingsOpen', + category: 'Navigation', + action: 'Main Menu', + name: 'Opened Settings', }, }) }} diff --git a/ui/app/components/app-header/app-header.component.js b/ui/app/components/app-header/app-header.component.js index 030b77290c9a..14f8b9f304ec 100644 --- a/ui/app/components/app-header/app-header.component.js +++ b/ui/app/components/app-header/app-header.component.js @@ -18,6 +18,7 @@ export default class AppHeader extends PureComponent { isUnlocked: PropTypes.bool, hideNetworkIndicator: PropTypes.bool, disabled: PropTypes.bool, + isAccountMenuOpen: PropTypes.bool, } static contextTypes = { @@ -31,13 +32,22 @@ export default class AppHeader extends PureComponent { const { networkDropdownOpen, showNetworkDropdown, hideNetworkDropdown } = this.props - return networkDropdownOpen === false - ? showNetworkDropdown() - : hideNetworkDropdown() + if (networkDropdownOpen === false) { + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Home', + name: 'Opened Network Menu', + }, + }) + showNetworkDropdown() + } else { + hideNetworkDropdown() + } } renderAccountMenu () { - const { isUnlocked, toggleAccountMenu, selectedAddress, disabled } = this.props + const { isUnlocked, toggleAccountMenu, selectedAddress, disabled, isAccountMenuOpen } = this.props return isUnlocked && (
{ - this.context.metricsEvent({ - eventOpts: { - category: 'Accounts', - action: 'userClick', - name: 'accountsOpenedMenu', - }, - pageOpts: { - section: 'header', - component: 'accountDropdownIcon', - }, - }) if (!disabled) { + !isAccountMenuOpen && this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Home', + name: 'Opened Main Menu', + }, + }) toggleAccountMenu() } }} diff --git a/ui/app/components/app-header/app-header.container.js b/ui/app/components/app-header/app-header.container.js index 30d3f8cc4b0e..1abc2afeb209 100644 --- a/ui/app/components/app-header/app-header.container.js +++ b/ui/app/components/app-header/app-header.container.js @@ -13,6 +13,7 @@ const mapStateToProps = state => { provider, selectedAddress, isUnlocked, + isAccountMenuOpen, } = metamask return { @@ -21,6 +22,7 @@ const mapStateToProps = state => { provider, selectedAddress, isUnlocked, + isAccountMenuOpen, } } diff --git a/ui/app/components/dropdowns/account-details-dropdown.js b/ui/app/components/dropdowns/account-details-dropdown.js index e1f6ae50a709..bda8b9517834 100644 --- a/ui/app/components/dropdowns/account-details-dropdown.js +++ b/ui/app/components/dropdowns/account-details-dropdown.js @@ -75,9 +75,9 @@ AccountDetailsDropdown.prototype.render = function () { e.stopPropagation() this.context.metricsEvent({ eventOpts: { - category: 'Activation', - action: 'userClicks', - name: 'navExpandView', + category: 'Navigation', + action: 'Account Options', + name: 'Clicked Expand View', }, }) global.platform.openExtensionInBrowser() @@ -90,6 +90,13 @@ AccountDetailsDropdown.prototype.render = function () { onClick: (e) => { e.stopPropagation() showAccountDetailModal() + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Account Options', + name: 'Viewed Account Details', + }, + }) this.props.onClose() }, text: this.context.t('accountDetails'), @@ -100,9 +107,9 @@ AccountDetailsDropdown.prototype.render = function () { e.stopPropagation() this.context.metricsEvent({ eventOpts: { - category: 'Activation', - action: 'userClicks', - name: 'navViewOnEtherScan', + category: 'Navigation', + action: 'Account Options', + name: 'Clicked View on Etherscan', }, }) viewOnEtherscan(address, network) diff --git a/ui/app/components/dropdowns/network-dropdown.js b/ui/app/components/dropdowns/network-dropdown.js index 6e002219ac98..86a5a5268307 100644 --- a/ui/app/components/dropdowns/network-dropdown.js +++ b/ui/app/components/dropdowns/network-dropdown.js @@ -60,6 +60,7 @@ function NetworkDropdown () { NetworkDropdown.contextTypes = { t: PropTypes.func, + metricsEvent: PropTypes.func, } module.exports = compose( @@ -120,7 +121,7 @@ NetworkDropdown.prototype.render = function () { { key: 'main', closeMenu: () => this.props.hideNetworkDropdown(), - onClick: () => props.setProviderType('mainnet'), + onClick: () => this.handleClick('mainnet'), style: { ...dropdownMenuItemStyle, borderColor: '#038789' }, }, [ @@ -142,7 +143,7 @@ NetworkDropdown.prototype.render = function () { { key: 'ropsten', closeMenu: () => this.props.hideNetworkDropdown(), - onClick: () => props.setProviderType('ropsten'), + onClick: () => this.handleClick('ropsten'), style: dropdownMenuItemStyle, }, [ @@ -164,7 +165,7 @@ NetworkDropdown.prototype.render = function () { { key: 'kovan', closeMenu: () => this.props.hideNetworkDropdown(), - onClick: () => props.setProviderType('kovan'), + onClick: () => this.handleClick('kovan'), style: dropdownMenuItemStyle, }, [ @@ -186,7 +187,7 @@ NetworkDropdown.prototype.render = function () { { key: 'rinkeby', closeMenu: () => this.props.hideNetworkDropdown(), - onClick: () => props.setProviderType('rinkeby'), + onClick: () => this.handleClick('rinkeby'), style: dropdownMenuItemStyle, }, [ @@ -208,7 +209,7 @@ NetworkDropdown.prototype.render = function () { { key: 'default', closeMenu: () => this.props.hideNetworkDropdown(), - onClick: () => props.setProviderType('localhost'), + onClick: () => this.handleClick('localhost'), style: dropdownMenuItemStyle, }, [ @@ -252,6 +253,23 @@ NetworkDropdown.prototype.render = function () { ]) } +NetworkDropdown.prototype.handleClick = function (newProviderType) { + const { providerType, setProviderType } = this.props + const { metricsEvent } = this.context + + metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Home', + name: 'Opened Network Menu', + }, + customVariables: { + fromNetwork: providerType, + toNetwork: newProviderType, + }, + }) + setProviderType(newProviderType) +} NetworkDropdown.prototype.getNetworkName = function () { const { provider } = this.props diff --git a/ui/app/components/menu-bar/menu-bar.component.js b/ui/app/components/menu-bar/menu-bar.component.js index f9d57530b9a7..24f84516db90 100644 --- a/ui/app/components/menu-bar/menu-bar.component.js +++ b/ui/app/components/menu-bar/menu-bar.component.js @@ -35,9 +35,9 @@ export default class MenuBar extends PureComponent { onClick={() => { this.context.metricsEvent({ eventOpts: { - category: 'Activation', - action: 'userClicksHamburger', - name: 'navOpenedHamburger', + category: 'Navigation', + action: 'Home', + name: 'Copied Address', }, }) sidebarOpen ? hideSidebar() : showSidebar() diff --git a/ui/app/components/menu-bar/menu-bar.container.js b/ui/app/components/menu-bar/menu-bar.container.js index ae32882ae5c2..4c52764028ab 100644 --- a/ui/app/components/menu-bar/menu-bar.container.js +++ b/ui/app/components/menu-bar/menu-bar.container.js @@ -1,4 +1,5 @@ import { connect } from 'react-redux' +import { WALLET_VIEW_SIDEBAR } from '../sidebars/sidebar.constants' import MenuBar from './menu-bar.component' import { showSidebar, hideSidebar } from '../../actions' @@ -16,7 +17,7 @@ const mapDispatchToProps = dispatch => { showSidebar: () => { dispatch(showSidebar({ transitionName: 'sidebar-right', - type: 'wallet-view', + type: WALLET_VIEW_SIDEBAR, })) }, hideSidebar: () => dispatch(hideSidebar()), diff --git a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js index c57381075753..0d2cd0aa1f86 100644 --- a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js +++ b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js @@ -157,7 +157,19 @@ export default class ConfirmTransactionBase extends Component { } handleEditGas () { - const { onEditGas, showCustomizeGasModal } = this.props + const { onEditGas, showCustomizeGasModal, methodData = {} } = this.props + + this.context.metricsEvent({ + eventOpts: { + category: 'Transactions', + action: 'Confirm Screen', + name: 'User clicks "Edit" on gas', + }, + customVariables: { + recipientKnown: null, + functionType: methodData.name || 'notFound', + }, + }) if (onEditGas) { onEditGas() @@ -277,7 +289,20 @@ export default class ConfirmTransactionBase extends Component { } handleEdit () { - const { txData, tokenData, tokenProps, onEdit } = this.props + const { txData, tokenData, tokenProps, onEdit, methodData = {} } = this.props + + this.context.metricsEvent({ + eventOpts: { + category: 'Transactions', + action: 'Confirm Screen', + name: 'Edit Transaction', + }, + customVariables: { + recipientKnown: null, + functionType: methodData.name || 'notFound', + }, + }) + onEdit({ txData, tokenData, tokenProps }) } @@ -307,13 +332,9 @@ export default class ConfirmTransactionBase extends Component { if (onCancel) { metricsEvent({ eventOpts: { - category: 'Activation', - action: 'userSees', - name: 'confirmCancelled', - }, - pageOpts: { - section: 'footer', - component: 'confirmScreenCancelButton', + category: 'Transactions', + action: 'Confirm Screen', + name: 'Cancel', }, customVariables: { recipientKnown: null, @@ -345,13 +366,9 @@ export default class ConfirmTransactionBase extends Component { }, () => { metricsEvent({ eventOpts: { - category: 'Activation', - action: 'userSees', - name: 'confirmCompleted', - }, - pageOpts: { - section: 'footer', - component: 'confirmScreenSubmitButton', + category: 'Transactions', + action: 'Confirm Screen', + name: 'Transaction Completed', }, customVariables: { recipientKnown: null, @@ -475,13 +492,9 @@ export default class ConfirmTransactionBase extends Component { const { metricsEvent } = this.context metricsEvent({ eventOpts: { - category: 'Activation', - action: 'userSees', - name: 'confirmStarted', - }, - pageOpts: { - section: 'all', - component: 'all', + category: 'Transactions', + action: 'Confirm Screen', + name: 'User sees Confirm screen', }, }) } diff --git a/ui/app/components/pages/create-account/connect-hardware/index.js b/ui/app/components/pages/create-account/connect-hardware/index.js index bd877fd4e1f2..712cc5cbb712 100644 --- a/ui/app/components/pages/create-account/connect-hardware/index.js +++ b/ui/app/components/pages/create-account/connect-hardware/index.js @@ -154,8 +154,25 @@ class ConnectHardwareForm extends Component { this.props.unlockHardwareWalletAccount(this.state.selectedAccount, device) .then(_ => { + this.context.metricsEvent({ + eventOpts: { + category: 'Accounts', + action: 'Connected Hardware Wallet', + name: 'Connected Account with: ' + device, + }, + }) this.props.history.push(DEFAULT_ROUTE) }).catch(e => { + this.context.metricsEvent({ + eventOpts: { + category: 'Accounts', + action: 'Connected Hardware Wallet', + name: 'Error connecting hardware wallet', + }, + customVariables: { + error: e.toString(), + }, + }) this.setState({ error: e.toString() }) }) } @@ -268,6 +285,7 @@ const mapDispatchToProps = dispatch => { ConnectHardwareForm.contextTypes = { t: PropTypes.func, + metricsEvent: PropTypes.func, } module.exports = connect(mapStateToProps, mapDispatchToProps)( diff --git a/ui/app/components/pages/create-account/import-account/json.js b/ui/app/components/pages/create-account/import-account/json.js index 8bb6e154b3f1..9aeea5579c3e 100644 --- a/ui/app/components/pages/create-account/import-account/json.js +++ b/ui/app/components/pages/create-account/import-account/json.js @@ -108,9 +108,23 @@ class JsonImportSubview extends Component { .then(({ selectedAddress }) => { if (selectedAddress) { history.push(DEFAULT_ROUTE) + this.context.metricsEvent({ + eventOpts: { + category: 'Accounts', + action: 'Import Account', + name: 'Imported Account with JSON', + }, + }) displayWarning(null) } else { displayWarning('Error importing account.') + this.context.metricsEvent({ + eventOpts: { + category: 'Accounts', + action: 'Import Account', + name: 'Error importing JSON', + }, + }) setSelectedAddress(firstAddress) } }) @@ -147,6 +161,7 @@ const mapDispatchToProps = dispatch => { JsonImportSubview.contextTypes = { t: PropTypes.func, + metricsEvent: PropTypes.func, } module.exports = compose( diff --git a/ui/app/components/pages/create-account/import-account/private-key.js b/ui/app/components/pages/create-account/import-account/private-key.js index 45068b96edbc..4ba31806f3df 100644 --- a/ui/app/components/pages/create-account/import-account/private-key.js +++ b/ui/app/components/pages/create-account/import-account/private-key.js @@ -12,6 +12,7 @@ import Button from '../../../button' PrivateKeyImportView.contextTypes = { t: PropTypes.func, + metricsEvent: PropTypes.func, } module.exports = compose( @@ -102,10 +103,24 @@ PrivateKeyImportView.prototype.createNewKeychain = function () { importNewAccount('Private Key', [ privateKey ]) .then(({ selectedAddress }) => { if (selectedAddress) { + this.context.metricsEvent({ + eventOpts: { + category: 'Accounts', + action: 'Import Account', + name: 'Imported Account with Private Key', + }, + }) history.push(DEFAULT_ROUTE) displayWarning(null) } else { displayWarning('Error importing account.') + this.context.metricsEvent({ + eventOpts: { + category: 'Accounts', + action: 'Import Account', + name: 'Error importing with Private Key', + }, + }) setSelectedAddress(firstAddress) } }) diff --git a/ui/app/components/pages/create-account/new-account.js b/ui/app/components/pages/create-account/new-account.js index c9473d34b4d0..16355525047e 100644 --- a/ui/app/components/pages/create-account/new-account.js +++ b/ui/app/components/pages/create-account/new-account.js @@ -56,18 +56,21 @@ class NewAccountCreateForm extends Component { this.context.metricsEvent({ eventOpts: { category: 'Accounts', - action: 'backendCall', - name: 'successAdding', + action: 'Add New Account', + name: 'Added New Account', }, }) history.push(DEFAULT_ROUTE) }) - .catch(() => { + .catch((e) => { this.context.metricsEvent({ eventOpts: { category: 'Accounts', - action: 'backendCall', - name: 'errorAdding', + action: 'Add New Account', + name: 'Error', + }, + customVariables: { + error: e.message, }, }) }) diff --git a/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js b/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js index 2e99147bb46e..aab89bf12e7a 100644 --- a/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js +++ b/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js @@ -104,6 +104,13 @@ export default class ImportWithSeedPhrase extends PureComponent { try { await onSubmit(password, seedPhrase) + this.context.metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Import Seed Phrase', + name: 'Import Complete', + }, + }) history.push(INITIALIZE_UNIQUE_IMAGE_ROUTE) } catch (error) { this.setState({ seedPhraseError: error.message }) @@ -132,6 +139,14 @@ export default class ImportWithSeedPhrase extends PureComponent { } toggleTermsCheck = () => { + this.context.metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Import Seed Phrase', + name: 'Check ToS', + }, + }) + this.setState((prevState) => ({ termsChecked: !prevState.termsChecked, })) diff --git a/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js b/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js index 8d166906511f..44c19e42df88 100644 --- a/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js +++ b/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js @@ -103,9 +103,9 @@ export default class NewAccount extends PureComponent { this.context.metricsEvent({ eventOpts: { - category: 'Acquisition', - action: 'userClickContinue', - name: 'onboardingStarted', + category: 'Onboarding', + action: 'Create Password', + name: 'Submit Password', }, }) @@ -123,6 +123,14 @@ export default class NewAccount extends PureComponent { } toggleTermsCheck = () => { + this.context.metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Create Password', + name: 'Check ToS', + }, + }) + this.setState((prevState) => ({ termsChecked: !prevState.termsChecked, })) diff --git a/ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.component.js b/ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.component.js index 90dd8d8f8d20..95cf91453668 100644 --- a/ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.component.js +++ b/ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.component.js @@ -40,9 +40,9 @@ export default class UniqueImageScreen extends PureComponent { onClick={() => { this.context.metricsEvent({ eventOpts: { - category: 'Acquisition', - action: 'userClickContinue', - name: 'confirmedAvatar', + category: 'Onboarding', + action: 'Agree to Phishing Warning', + name: 'Agree to Phishing Warning', }, }) if (isImportedKeyring) { diff --git a/ui/app/components/pages/first-time-flow/end-of-flow/end-of-flow.component.js b/ui/app/components/pages/first-time-flow/end-of-flow/end-of-flow.component.js index 2ca5fd8ec8a4..064d4dddc57f 100644 --- a/ui/app/components/pages/first-time-flow/end-of-flow/end-of-flow.component.js +++ b/ui/app/components/pages/first-time-flow/end-of-flow/end-of-flow.component.js @@ -6,16 +6,18 @@ import { DEFAULT_ROUTE } from '../../../../routes' export default class EndOfFlowScreen extends PureComponent { static contextTypes = { t: PropTypes.func, + metricsEvent: PropTypes.func, } static propTypes = { history: PropTypes.object, completeOnboarding: PropTypes.func, + completionMetaMetricsName: PropTypes.string, } render () { const { t } = this.context - const { history, completeOnboarding } = this.props + const { history, completeOnboarding, completionMetaMetricsName } = this.props return (
@@ -59,6 +61,13 @@ export default class EndOfFlowScreen extends PureComponent { className="first-time-flow__button" onClick={async () => { await completeOnboarding() + this.context.metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Onboarding Complete', + name: completionMetaMetricsName, + }, + }) history.push(DEFAULT_ROUTE) }} > diff --git a/ui/app/components/pages/first-time-flow/end-of-flow/end-of-flow.container.js b/ui/app/components/pages/first-time-flow/end-of-flow/end-of-flow.container.js index ffe2c0efb01e..351c8d2c5ecf 100644 --- a/ui/app/components/pages/first-time-flow/end-of-flow/end-of-flow.container.js +++ b/ui/app/components/pages/first-time-flow/end-of-flow/end-of-flow.container.js @@ -2,10 +2,24 @@ import { connect } from 'react-redux' import EndOfFlow from './end-of-flow.component' import { setCompletedOnboarding } from '../../../../actions' +const firstTimeFlowTypeNameMap = { + create: 'Selected Create New Wallet', + 'import': 'Selected Import Wallet', +} + +const mapStateToProps = ({ metamask }) => { + const { firstTimeFlowType } = metamask + + return { + completionMetaMetricsName: firstTimeFlowTypeNameMap[firstTimeFlowType], + } +} + + const mapDispatchToProps = dispatch => { return { completeOnboarding: () => dispatch(setCompletedOnboarding()), } } -export default connect(null, mapDispatchToProps)(EndOfFlow) +export default connect(mapStateToProps, mapDispatchToProps)(EndOfFlow) diff --git a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js index 6e66b34cb840..4b685d465522 100644 --- a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js +++ b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js @@ -99,9 +99,9 @@ export default class MetaMetricsOptIn extends Component { .then(() => { metricsEvent({ eventOpts: { - category: 'MetaMetricsOptIn', - action: 'userSelectsOption', - name: 'userOptedOut', + category: 'Onboarding', + action: 'Metrics Option', + name: 'Metrics Opt In', }, isOptIn: true, }, { @@ -117,9 +117,9 @@ export default class MetaMetricsOptIn extends Component { .then(([participateStatus, metaMetricsId]) => { return metricsEvent({ eventOpts: { - category: 'MetaMetricsOptIn', - action: 'userSelectsOption', - name: 'userOptedIn', + category: 'Onboarding', + action: 'Metrics Option', + name: 'Metrics Opt Out', }, isOptIn: true, }) @@ -127,7 +127,7 @@ export default class MetaMetricsOptIn extends Component { return metricsEvent({ eventOpts: { category: 'Onboarding', - action: 'importOrCreate', + action: 'Import or Create', name: firstTimeSelectionMetaMetricsName, }, isOptIn: true, diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js index 402fd9a1082d..e974fd904d44 100644 --- a/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js +++ b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js @@ -51,9 +51,9 @@ export default class ConfirmSeedPhrase extends PureComponent { history.push(INITIALIZE_END_OF_FLOW_ROUTE) this.context.metricsEvent({ eventOpts: { - category: 'Acquisition', - action: 'userClickContinue', - name: 'onboardingComplete', + category: 'Onboarding', + action: 'Seed Phrase Setup', + name: 'Verify Complete', }, }) history.push(INITIALIZE_END_OF_FLOW_ROUTE) diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js b/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js index 8ab9da377f0c..cb8a01322fbf 100644 --- a/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js +++ b/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js @@ -30,6 +30,14 @@ export default class RevealSeedPhrase extends PureComponent { const { isShowingSeedPhrase } = this.state const { history } = this.props + this.context.metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Seed Phrase Setup', + name: 'Advance to Verify', + }, + }) + if (!isShowingSeedPhrase) { return } @@ -57,9 +65,9 @@ export default class RevealSeedPhrase extends PureComponent { onClick={() => { this.context.metricsEvent({ eventOpts: { - category: 'Acquisition', - action: 'userClicksReveal', - name: 'revealedSecretWords', + category: 'Onboarding', + action: 'Seed Phrase Setup', + name: 'Revealed Words', }, }) this.setState({ isShowingSeedPhrase: true }) diff --git a/ui/app/components/pages/settings/settings-tab/settings-tab.component.js b/ui/app/components/pages/settings/settings-tab/settings-tab.component.js index e433dcb2a256..73b2cfc2901b 100644 --- a/ui/app/components/pages/settings/settings-tab/settings-tab.component.js +++ b/ui/app/components/pages/settings/settings-tab/settings-tab.component.js @@ -238,39 +238,28 @@ export default class SettingsTab extends PureComponent { validateRpc (newRpc, chainId, ticker = 'ETH', nickname) { const { setRpcTarget, displayWarning } = this.props if (validUrl.isWebUri(newRpc)) { - if (!!chainId && Number.isNaN(parseInt(chainId))) { - this.context.metricsEvent({ - eventOpts: { - category: 'Activation/Retention', - action: 'userEnteredCustomRpc', - name: 'settingsCustomRPCError', - }, - customVariables: { - networkId: newRpc, - chainId, - }, - }) - return displayWarning(`${this.context.t('invalidInput')} chainId`) - } - this.context.metricsEvent({ eventOpts: { - category: 'Activation/Retention', - action: 'userEnteredCustomRpc', - name: 'settingsCustomRPCSuccess', + category: 'Settings', + action: 'Custom RPC', + name: 'Success', }, customVariables: { networkId: newRpc, chainId, }, }) + if (!!chainId && Number.isNaN(parseInt(chainId))) { + return displayWarning(`${this.context.t('invalidInput')} chainId`) + } + setRpcTarget(newRpc, chainId, ticker, nickname) } else { this.context.metricsEvent({ eventOpts: { - category: 'Activation/Retention', - action: 'userEnteredCustomRpc', - name: 'settingsCustomRPCError', + category: 'Settings', + action: 'Custom RPC', + name: 'Error', }, customVariables: { networkId: newRpc, @@ -368,6 +357,13 @@ export default class SettingsTab extends PureComponent { large onClick={event => { event.preventDefault() + this.context.metricsEvent({ + eventOpts: { + category: 'Settings', + action: 'Reveal Seed Phrase', + name: 'Reveal Seed Phrase', + }, + }) history.push(REVEAL_SEED_ROUTE) }} > @@ -431,9 +427,9 @@ export default class SettingsTab extends PureComponent { event.preventDefault() this.context.metricsEvent({ eventOpts: { - category: 'Activation/Retention', - action: 'userClickResetAccount', - name: 'settingsUserResetAccount', + category: 'Settings', + action: 'Reset Account', + name: 'Reset Account', }, }) showResetAccountConfirmationModal() diff --git a/ui/app/components/pages/unlock-page/unlock-page.component.js b/ui/app/components/pages/unlock-page/unlock-page.component.js index 01db99c4f281..f9036b6f53e7 100644 --- a/ui/app/components/pages/unlock-page/unlock-page.component.js +++ b/ui/app/components/pages/unlock-page/unlock-page.component.js @@ -60,10 +60,10 @@ export default class UnlockPage extends Component { const newState = await forceUpdateMetamaskState() this.context.metricsEvent({ eventOpts: { - category: 'Retention', - action: 'userEnteredPassword', - name: 'unlockSuccess', - }, + category: 'Navigation', + action: 'Unlock', + name: 'Success', + }, customVariables: { numberOfTokens: newState.tokens.length, numberOfAccounts: Object.keys(newState.accounts).length, @@ -79,9 +79,9 @@ export default class UnlockPage extends Component { const newState = await forceUpdateMetamaskState() this.context.metricsEvent({ eventOpts: { - category: 'Retention', - action: 'userEnteredPassword', - name: 'incorrectPassword', + category: 'Navigation', + action: 'Unlock', + name: 'Incorrect Passowrd', }, customVariables: { numberOfTokens: newState.tokens.length, diff --git a/ui/app/components/provider-page-container/provider-page-container.component.js b/ui/app/components/provider-page-container/provider-page-container.component.js index 05643ce28974..ff063166de94 100644 --- a/ui/app/components/provider-page-container/provider-page-container.component.js +++ b/ui/app/components/provider-page-container/provider-page-container.component.js @@ -21,9 +21,9 @@ export default class ProviderPageContainer extends PureComponent { componentDidMount () { this.context.metricsEvent({ eventOpts: { - category: 'Activation/Retention', - action: 'dappRequestsAccess', - name: 'connectPopupOpened', + category: 'Auth', + action: 'Connect', + name: 'Popup Opened', }, }) } @@ -32,9 +32,9 @@ export default class ProviderPageContainer extends PureComponent { const { tabID, rejectProviderRequest } = this.props this.context.metricsEvent({ eventOpts: { - category: 'Activation/Retention', - action: 'userClicksCancel', - name: 'connectCanceled', + category: 'Auth', + action: 'Connect', + name: 'Canceled', }, }) rejectProviderRequest(tabID) @@ -44,9 +44,9 @@ export default class ProviderPageContainer extends PureComponent { const { approveProviderRequest, tabID } = this.props this.context.metricsEvent({ eventOpts: { - category: 'Activation/Retention', - action: 'userClicksConfirm', - name: 'connectConfirmed', + category: 'Auth', + action: 'Connect', + name: 'Confirmed', }, }) approveProviderRequest(tabID) diff --git a/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js b/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js index 911d41b82a58..32d389b99ee1 100644 --- a/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js +++ b/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js @@ -36,13 +36,9 @@ export default class SendGasRow extends Component { return
{ metricsEvent({ eventOpts: { - category: 'Activation', - action: 'userOpens', - name: 'sendOpenCustomizeGas', - }, - pageOpts: { - section: 'formConent', - component: 'sendScreenGasRow', + category: 'Transactions', + action: 'Edit Screen', + name: 'Clicked "Advanced Options"', }, }) showCustomizeGasModal() @@ -68,12 +64,23 @@ export default class SendGasRow extends Component { gasLimit, insufficientBalance, } = this.props + const { metricsEvent } = this.context const gasPriceButtonGroup =
{ + metricsEvent({ + eventOpts: { + category: 'Transactions', + action: 'Edit Screen', + name: 'Changed Gas Button', + }, + }) + gasPriceButtonGroupProps(...args) + }} /> { this.renderAdvancedOptionsButton() }
diff --git a/ui/app/components/send/send-content/send-to-row/send-to-row.component.js b/ui/app/components/send/send-content/send-to-row/send-to-row.component.js index 3fbf9a76bf47..434204490815 100644 --- a/ui/app/components/send/send-content/send-to-row/send-to-row.component.js +++ b/ui/app/components/send/send-content/send-to-row/send-to-row.component.js @@ -27,6 +27,7 @@ export default class SendToRow extends Component { static contextTypes = { t: PropTypes.func, + metricsEvent: PropTypes.func, } handleToChange (to, nickname = '', toError, toWarning, network) { @@ -62,7 +63,16 @@ export default class SendToRow extends Component { warningType={'to'} > this.props.scanQrCode()} + scanQrCode={_ => { + this.context.metricsEvent({ + eventOpts: { + category: 'Transactions', + action: 'Edit Screen', + name: 'Used QR scanner', + }, + }) + this.props.scanQrCode() + }} accounts={toAccounts} closeDropdown={() => closeToDropdown()} dropdownOpen={toDropdownOpen} diff --git a/ui/app/components/send/send-footer/send-footer.component.js b/ui/app/components/send/send-footer/send-footer.component.js index 72412ce9e5ff..d943b4b2274e 100644 --- a/ui/app/components/send/send-footer/send-footer.component.js +++ b/ui/app/components/send/send-footer/send-footer.component.js @@ -26,10 +26,7 @@ export default class SendFooter extends Component { tokenBalance: PropTypes.string, unapprovedTxs: PropTypes.object, update: PropTypes.func, - } - - state = { - hadError: false, + sendErrors: PropTypes.object, } static contextTypes = { @@ -86,24 +83,17 @@ export default class SendFooter extends Component { }) : sign({ data, selectedToken, to, amount, from, gas, gasPrice }) - metricsEvent({ - eventOpts: { - category: 'Activation', - action: 'userClick', - name: 'sendCompletedEditScreen', - }, - pageOpts: { - section: 'Footer', - component: 'sendScreenNextButton', - }, - customVariables: { - hadError: this.state.hadError, - hexData: Boolean(data), - }, - }) - Promise.resolve(promise) - .then(() => history.push(CONFIRM_TRANSACTION_ROUTE)) + .then(() => { + metricsEvent({ + eventOpts: { + category: 'Transactions', + action: 'Edit Screen', + name: 'Complete', + }, + }) + history.push(CONFIRM_TRANSACTION_ROUTE) + }) } formShouldBeDisabled () { @@ -114,8 +104,23 @@ export default class SendFooter extends Component { } componentDidUpdate (prevProps) { - if (!prevProps.inError && this.props.inError) { - this.setState({ hadError: true }) + const { inError, sendErrors } = this.props + const { metricsEvent } = this.context + if (!prevProps.inError && inError) { + const errorField = Object.keys(sendErrors).find(key => sendErrors[key]) + const errorMessage = sendErrors[errorField] + + metricsEvent({ + eventOpts: { + category: 'Transactions', + action: 'Edit Screen', + name: 'Error', + }, + customVariables: { + errorField, + errorMessage, + }, + }) } } diff --git a/ui/app/components/send/send-footer/send-footer.container.js b/ui/app/components/send/send-footer/send-footer.container.js index 60de4d0303d8..0c6120cc5a54 100644 --- a/ui/app/components/send/send-footer/send-footer.container.js +++ b/ui/app/components/send/send-footer/send-footer.container.js @@ -21,6 +21,7 @@ import { getSendHexData, getTokenBalance, getUnapprovedTxs, + getSendErrors, } from '../send.selectors' import { isSendFormInError, @@ -48,6 +49,7 @@ function mapStateToProps (state) { toAccounts: getSendToAccounts(state), tokenBalance: getTokenBalance(state), unapprovedTxs: getUnapprovedTxs(state), + sendErrors: getSendErrors(state), } } diff --git a/ui/app/components/send/send-footer/tests/send-footer-component.test.js b/ui/app/components/send/send-footer/tests/send-footer-component.test.js index dd532bdda2da..4b63e422da53 100644 --- a/ui/app/components/send/send-footer/tests/send-footer-component.test.js +++ b/ui/app/components/send/send-footer/tests/send-footer-component.test.js @@ -45,6 +45,7 @@ describe('SendFooter Component', function () { tokenBalance={'mockTokenBalance'} unapprovedTxs={['mockTx']} update={propsMethodSpies.update} + sendErrors={{}} />, { context: { t: str => str, metricsEvent: () => ({}) } }) }) diff --git a/ui/app/components/send/send-footer/tests/send-footer-container.test.js b/ui/app/components/send/send-footer/tests/send-footer-container.test.js index daefa5103a68..70cb28df34a2 100644 --- a/ui/app/components/send/send-footer/tests/send-footer-container.test.js +++ b/ui/app/components/send/send-footer/tests/send-footer-container.test.js @@ -42,6 +42,7 @@ proxyquire('../send-footer.container.js', { getTokenBalance: (s) => `mockTokenBalance:${s}`, getSendHexData: (s) => `mockHexData:${s}`, getUnapprovedTxs: (s) => `mockUnapprovedTxs:${s}`, + getSendErrors: (s) => `mockSendErrors:${s}`, }, './send-footer.selectors': { isSendFormInError: (s) => `mockInError:${s}` }, './send-footer.utils': utilsStubs, @@ -66,6 +67,7 @@ describe('send-footer container', () => { toAccounts: 'mockToAccounts:mockState', tokenBalance: 'mockTokenBalance:mockState', unapprovedTxs: 'mockUnapprovedTxs:mockState', + sendErrors: 'mockSendErrors:mockState', }) }) diff --git a/ui/app/components/sender-to-recipient/sender-to-recipient.component.js b/ui/app/components/sender-to-recipient/sender-to-recipient.component.js index 89a1a9c08c62..65102a7ad177 100644 --- a/ui/app/components/sender-to-recipient/sender-to-recipient.component.js +++ b/ui/app/components/sender-to-recipient/sender-to-recipient.component.js @@ -23,6 +23,8 @@ export default class SenderToRecipient extends PureComponent { variant: PropTypes.oneOf([DEFAULT_VARIANT, CARDS_VARIANT, FLAT_VARIANT]), addressOnly: PropTypes.bool, assetImage: PropTypes.string, + onRecipientClick: PropTypes.func, + onSenderClick: PropTypes.func, } static defaultProps = { @@ -86,7 +88,7 @@ export default class SenderToRecipient extends PureComponent { renderRecipientWithAddress () { const { t } = this.context - const { recipientName, recipientAddress, addressOnly } = this.props + const { recipientName, recipientAddress, addressOnly, onRecipientClick } = this.props const checksummedRecipientAddress = checksumAddress(recipientAddress) return ( @@ -95,6 +97,7 @@ export default class SenderToRecipient extends PureComponent { onClick={() => { this.setState({ recipientAddressCopied: true }) copyToClipboard(checksummedRecipientAddress) + onRecipientClick() }} > { this.renderRecipientIdenticon() } @@ -151,7 +154,7 @@ export default class SenderToRecipient extends PureComponent { } render () { - const { senderAddress, recipientAddress, variant } = this.props + const { senderAddress, recipientAddress, variant, onSenderClick } = this.props const checksummedSenderAddress = checksumAddress(senderAddress) return ( @@ -161,6 +164,7 @@ export default class SenderToRecipient extends PureComponent { onClick={() => { this.setState({ senderAddressCopied: true }) copyToClipboard(checksummedSenderAddress) + onSenderClick() }} > { this.renderSenderIdenticon() } diff --git a/ui/app/components/sidebars/sidebar.component.js b/ui/app/components/sidebars/sidebar.component.js index f68515ad6e06..02494b48b26b 100644 --- a/ui/app/components/sidebars/sidebar.component.js +++ b/ui/app/components/sidebars/sidebar.component.js @@ -14,10 +14,16 @@ export default class Sidebar extends Component { transitionName: PropTypes.string, type: PropTypes.string, sidebarProps: PropTypes.object, + onOverlayClose: PropTypes.func, }; renderOverlay () { - return
this.props.hideSidebar()} /> + const { onOverlayClose } = this.props + + return
{ + onOverlayClose && onOverlayClose() + this.props.hideSidebar() + }} /> } renderSidebarContent () { diff --git a/ui/app/components/signature-request.js b/ui/app/components/signature-request.js index 04a9c552c6a2..25bd9a7b1b84 100644 --- a/ui/app/components/signature-request.js +++ b/ui/app/components/signature-request.js @@ -267,12 +267,9 @@ SignatureRequest.prototype.renderFooter = function () { cancel(event).then(() => { this.context.metricsEvent({ eventOpts: { - category: 'Activation', - action: 'userClicksCancel', - name: 'signCancelled', - }, - customVariables: { - functionType: type, + category: 'Transactions', + action: 'Sign Request', + name: 'Cancel', }, }) this.props.clearConfirmTransaction() @@ -288,12 +285,9 @@ SignatureRequest.prototype.renderFooter = function () { sign(event).then(() => { this.context.metricsEvent({ eventOpts: { - category: 'Activation', - action: 'userClicksSign', - name: 'signConfirmed', - }, - customVariables: { - functionType: type, + category: 'Transactions', + action: 'Sign Request', + name: 'Confirm', }, }) this.props.clearConfirmTransaction() diff --git a/ui/app/components/token-cell.js b/ui/app/components/token-cell.js index 75ba347fa2fb..d9c80b4f4c87 100644 --- a/ui/app/components/token-cell.js +++ b/ui/app/components/token-cell.js @@ -1,4 +1,5 @@ const Component = require('react').Component +const PropTypes = require('prop-types') const h = require('react-hyperscript') const inherits = require('util').inherits const connect = require('react-redux').connect @@ -40,6 +41,10 @@ function TokenCell () { } } +TokenCell.contextTypes = { + metricsEvent: PropTypes.func, +} + TokenCell.prototype.render = function () { const { tokenMenuOpen } = this.state const props = this.props @@ -88,6 +93,13 @@ TokenCell.prototype.render = function () { // onClick: this.view.bind(this, address, userAddress, network), onClick: () => { setSelectedToken(address) + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Token Menu', + name: 'Clicked Token', + }, + }) selectedTokenAddress !== address && sidebarOpen && hideSidebar() }, }, [ diff --git a/ui/app/components/transaction-activity-log/transaction-activity-log.component.js b/ui/app/components/transaction-activity-log/transaction-activity-log.component.js index 4e4e553c0512..ca46d7830c39 100644 --- a/ui/app/components/transaction-activity-log/transaction-activity-log.component.js +++ b/ui/app/components/transaction-activity-log/transaction-activity-log.component.js @@ -10,6 +10,7 @@ import prefixForNetwork from '../../../lib/etherscan-prefix-for-network' export default class TransactionActivityLog extends PureComponent { static contextTypes = { t: PropTypes.func, + metricEvent: PropTypes.func, } static propTypes = { diff --git a/ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js b/ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js index eaf1166f0968..3e39212d3e96 100644 --- a/ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js +++ b/ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js @@ -12,6 +12,7 @@ import prefixForNetwork from '../../../lib/etherscan-prefix-for-network' export default class TransactionListItemDetails extends PureComponent { static contextTypes = { t: PropTypes.func, + metricsEvent: PropTypes.func, } static propTypes = { @@ -33,6 +34,14 @@ export default class TransactionListItemDetails extends PureComponent { const prefix = prefixForNetwork(metamaskNetworkId) const etherscanUrl = `https://${prefix}etherscan.io/tx/${hash}` + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Activity Log', + name: 'Clicked "View on Etherscan"', + }, + }) + global.platform.openWindow({ url: etherscanUrl }) } @@ -55,6 +64,14 @@ export default class TransactionListItemDetails extends PureComponent { const { primaryTransaction: transaction } = transactionGroup const { hash } = transaction + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Activity Log', + name: 'Copied Transaction ID', + }, + }) + this.setState({ justCopied: true }, () => { copyToClipboard(hash) setTimeout(() => this.setState({ justCopied: false }), 1000) @@ -125,6 +142,24 @@ export default class TransactionListItemDetails extends PureComponent { addressOnly recipientAddress={to} senderAddress={from} + onRecipientClick={() => { + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Activity Log', + name: 'Copied "To" Address', + }, + }) + }} + onSenderClick={() => { + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Activity Log', + name: 'Copied "From" Address', + }, + }) + }} />
diff --git a/ui/app/components/transaction-list-item/transaction-list-item.component.js b/ui/app/components/transaction-list-item/transaction-list-item.component.js index 625b9da7da55..e843fe1a005a 100644 --- a/ui/app/components/transaction-list-item/transaction-list-item.component.js +++ b/ui/app/components/transaction-list-item/transaction-list-item.component.js @@ -59,13 +59,16 @@ export default class TransactionListItem extends PureComponent { return } - this.context.metricsEvent({ - eventOpts: { - category: 'Activation/Retention', - action: 'userClicks', - name: 'navExpandTransaction', - }, - }) + if (!showTransactionDetails) { + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Home', + name: 'Expand Transaction', + }, + }) + } + this.setState({ showTransactionDetails: !showTransactionDetails }) } diff --git a/ui/app/components/transaction-view-balance/transaction-view-balance.component.js b/ui/app/components/transaction-view-balance/transaction-view-balance.component.js index 0c6169c8c904..a18e959b5a00 100644 --- a/ui/app/components/transaction-view-balance/transaction-view-balance.component.js +++ b/ui/app/components/transaction-view-balance/transaction-view-balance.component.js @@ -89,7 +89,16 @@ export default class TransactionViewBalance extends PureComponent { @@ -101,13 +110,9 @@ export default class TransactionViewBalance extends PureComponent { onClick={() => { metricsEvent({ eventOpts: { - category: 'Activation', - action: 'userClick', - name: 'sendStarted', - }, - pageOpts: { - section: 'Header', - component: 'homeScreenSendButton', + category: 'Navigation', + action: 'Home', + name: 'Clicked Send', }, }) history.push(SEND_ROUTE) diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js index 82ed57d0100e..400b9aa90930 100644 --- a/ui/app/components/wallet-view.js +++ b/ui/app/components/wallet-view.js @@ -40,7 +40,6 @@ function mapStateToProps (state) { sidebarOpen: state.appState.sidebar.isOpen, identities: state.metamask.identities, accounts: selectors.getMetaMaskAccounts(state), - tokens: state.metamask.tokens, keyrings: state.metamask.keyrings, selectedAddress: selectors.getSelectedAddress(state), selectedAccount: selectors.getSelectedAccount(state), @@ -111,6 +110,13 @@ WalletView.prototype.renderAddToken = function () { return h(AddTokenButton, { onClick () { history.push(ADD_TOKEN_ROUTE) + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Token Menu', + name: 'Clicked "Add Token"', + }, + }) if (sidebarOpen) { hideSidebar() } diff --git a/ui/app/ducks/confirm-transaction.duck.js b/ui/app/ducks/confirm-transaction.duck.js index a9fe32efd0cf..f75ff809a1d4 100644 --- a/ui/app/ducks/confirm-transaction.duck.js +++ b/ui/app/ducks/confirm-transaction.duck.js @@ -373,32 +373,6 @@ export function setTransactionToConfirm (transactionId) { dispatch(setFetchingData(true)) const methodData = await getMethodData(data) - if (methodData.name) { - // sendMetaMetricsEvent({ - // props: { - // network: getCurrentNetworkId(state), - // environmentType: getEnvironmentType(), - // activeCurrency: getSelectedAsset(state), - // accountType: getAccountType(state), - // userPermissionPreferences: getUserMetricsPermissions(state), - // confirmTransactionOrigin: txDataSelector(state).origin, - // }, - // config: { - // eventOpts: { - // category: 'Activation', - // action: 'extensionGetSuccess', - // name: 'confirmFoundFundctionName', - // }, - // pageOpts: { - // section: 'null', - // component: 'null', - // }, - // }, - // currentPath: window.location.href, - // pathname: window.location.href, - // }) - } - dispatch(updateMethodData(methodData)) } catch (error) { dispatch(updateMethodData({})) From d5d54d7c1d30cba223571ef39622fa3ee42f7538 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Thu, 28 Feb 2019 17:25:11 -0330 Subject: [PATCH 24/50] Fix import tos bug and send gas button bug due to metametrics changes. --- .../import-with-seed-phrase.component.js | 1 + .../send/send-content/send-gas-row/send-gas-row.component.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js b/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js index aab89bf12e7a..af6cd80bbf0a 100644 --- a/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js +++ b/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js @@ -11,6 +11,7 @@ import { export default class ImportWithSeedPhrase extends PureComponent { static contextTypes = { t: PropTypes.func, + metricsEvent: PropTypes.func, } static propTypes = { diff --git a/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js b/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js index 32d389b99ee1..bf744662685c 100644 --- a/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js +++ b/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js @@ -79,7 +79,7 @@ export default class SendGasRow extends Component { name: 'Changed Gas Button', }, }) - gasPriceButtonGroupProps(...args) + gasPriceButtonGroupProps.handleGasPriceSelection(...args) }} /> { this.renderAdvancedOptionsButton() } From 5fe45cf1e2cd26624d9483b0fcefc5f73be8664f Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 1 Mar 2019 16:55:26 -0330 Subject: [PATCH 25/50] Various small fixes on the metametrics branch. --- .../account-menu/account-menu.component.js | 2 +- .../confirm-transaction-base.component.js | 18 +++++++++++++----- .../metametrics-opt-in/index.scss | 5 +++++ .../metametrics-opt-in.component.js | 13 +++++++++---- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/ui/app/components/account-menu/account-menu.component.js b/ui/app/components/account-menu/account-menu.component.js index ccad8a850ea0..ce7482108fcc 100644 --- a/ui/app/components/account-menu/account-menu.component.js +++ b/ui/app/components/account-menu/account-menu.component.js @@ -245,7 +245,7 @@ export default class AccountMenu extends PureComponent { eventOpts: { category: 'Navigation', action: 'Main Menu', - name: 'Clicked Create Accoount', + name: 'Clicked Create Account', }, }) history.push(NEW_ACCOUNT_ROUTE) diff --git a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js index 0d2cd0aa1f86..372e6df4a7e8 100644 --- a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js +++ b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js @@ -157,7 +157,7 @@ export default class ConfirmTransactionBase extends Component { } handleEditGas () { - const { onEditGas, showCustomizeGasModal, methodData = {} } = this.props + const { onEditGas, showCustomizeGasModal, methodData = {}, txData: { origin } } = this.props this.context.metricsEvent({ eventOpts: { @@ -168,6 +168,7 @@ export default class ConfirmTransactionBase extends Component { customVariables: { recipientKnown: null, functionType: methodData.name || 'notFound', + origin, }, }) @@ -289,7 +290,7 @@ export default class ConfirmTransactionBase extends Component { } handleEdit () { - const { txData, tokenData, tokenProps, onEdit, methodData = {} } = this.props + const { txData, tokenData, tokenProps, onEdit, methodData = {}, txData: { origin } } = this.props this.context.metricsEvent({ eventOpts: { @@ -300,6 +301,7 @@ export default class ConfirmTransactionBase extends Component { customVariables: { recipientKnown: null, functionType: methodData.name || 'notFound', + origin, }, }) @@ -327,7 +329,7 @@ export default class ConfirmTransactionBase extends Component { handleCancel () { const { metricsEvent } = this.context - const { onCancel, txData, cancelTransaction, history, clearConfirmTransaction, methodData = {} } = this.props + const { onCancel, txData, cancelTransaction, history, clearConfirmTransaction, methodData = {}, txData: { origin } } = this.props if (onCancel) { metricsEvent({ @@ -339,6 +341,7 @@ export default class ConfirmTransactionBase extends Component { customVariables: { recipientKnown: null, functionType: methodData.name || 'notFound', + origin, }, }) onCancel(txData) @@ -353,7 +356,7 @@ export default class ConfirmTransactionBase extends Component { handleSubmit () { const { metricsEvent } = this.context - const { sendTransaction, clearConfirmTransaction, txData, history, onSubmit, methodData = {}, metaMetricsSendCount = 0, setMetaMetricsSendCount } = this.props + const { txData: { origin }, sendTransaction, clearConfirmTransaction, txData, history, onSubmit, methodData = {}, metaMetricsSendCount = 0, setMetaMetricsSendCount } = this.props const { submitting } = this.state if (submitting) { @@ -373,6 +376,7 @@ export default class ConfirmTransactionBase extends Component { customVariables: { recipientKnown: null, functionType: methodData.name || 'notFound', + origin, }, }) if (onSubmit) { @@ -489,12 +493,16 @@ export default class ConfirmTransactionBase extends Component { } componentDidMount () { + const { txData: { origin } = {} } = this.props const { metricsEvent } = this.context metricsEvent({ eventOpts: { category: 'Transactions', action: 'Confirm Screen', - name: 'User sees Confirm screen', + name: 'Confirm: Started', + }, + customVariables: { + origin, }, }) } diff --git a/ui/app/components/pages/first-time-flow/metametrics-opt-in/index.scss b/ui/app/components/pages/first-time-flow/metametrics-opt-in/index.scss index 5139c4535f99..8b90cd168474 100644 --- a/ui/app/components/pages/first-time-flow/metametrics-opt-in/index.scss +++ b/ui/app/components/pages/first-time-flow/metametrics-opt-in/index.scss @@ -2,6 +2,10 @@ position: relative; width: 100%; + a { + color: #2f9ae0bf; + } + &__main { display: flex; flex-direction: column; @@ -97,6 +101,7 @@ &__bottom-text { margin-top: 10px; + color: #9a9a9a; } &__content { diff --git a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js index 4b685d465522..2e7b6764bdb7 100644 --- a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js +++ b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js @@ -45,9 +45,8 @@ export default class MetaMetricsOptIn extends Component {
Help Us Improve MetaMask
- MetaMask would like to gather some usage data to better understand how our users interact with the extension and inform futue development. - This data will be used to continually improve the usability and user experience of our product. If you care about the usability of the ethereum - ecosystem, please consider allowing these basic analytics. + MetaMask would like to gather usage data to better understand how our users interact with the extension. This data + will be used to continually improve the usability and user experience of our product and the etheruem ecosystem.
MetaMask will.. @@ -144,7 +143,13 @@ export default class MetaMetricsOptIn extends Component { disabled={false} />
- This data is aggregated and is therefore anonymous for the purposes of General Data Protection Regulation (EU) 2016/679. For more information in relation to our privacy practices, please see our Privacy Policy here. + This data is aggregated and is therefore anonymous for the purposes of General Data Protection Regulation (EU) 2016/679. For more information in relation to our privacy practices, please see our + Privacy Policy here + .
From 88055b57048c150ecc58486221d9f4f75544d729 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 1 Mar 2019 16:57:42 -0330 Subject: [PATCH 26/50] Add origin custom variable type to metametrics.util --- ui/app/metametrics/metametrics.util.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/app/metametrics/metametrics.util.js b/ui/app/metametrics/metametrics.util.js index e897829bde0a..60cf41905328 100644 --- a/ui/app/metametrics/metametrics.util.js +++ b/ui/app/metametrics/metametrics.util.js @@ -19,6 +19,7 @@ const METAMETRICS_CUSTOM_ACTIVE_CURRENCY = 'activeCurrency' const METAMETRICS_CUSTOM_ACCOUNT_TYPE = 'accountType' const METAMETRICS_CUSTOM_NUMBER_OF_TOKENS = 'numberOfTokens' const METAMETRICS_CUSTOM_NUMBER_OF_ACCOUNTS = 'numberOfAccounts' +const METAMETRICS_CUSTOM_CONFIRM_SCREEN_ORIGIN = 'origin' const customVariableNameIdMap = { [METAMETRICS_CUSTOM_HAD_ERROR]: 1, @@ -27,6 +28,7 @@ const customVariableNameIdMap = { [METAMETRICS_CUSTOM_GAS_LIMIT_CHANGE]: 4, [METAMETRICS_CUSTOM_GAS_PRICE_CHANGE]: 5, [METAMETRICS_CUSTOM_RECIPIENT_KNOWN]: 6, + [METAMETRICS_CUSTOM_CONFIRM_SCREEN_ORIGIN]: 7, } const customDimensionsNameIdMap = { From 2061a61326f00d440900a431b8ce3f6445e9fd13 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 1 Mar 2019 19:47:21 -0330 Subject: [PATCH 27/50] Fix names of onboarding complete actions (metametrics). --- .../first-time-flow/end-of-flow/end-of-flow.container.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/components/pages/first-time-flow/end-of-flow/end-of-flow.container.js b/ui/app/components/pages/first-time-flow/end-of-flow/end-of-flow.container.js index 351c8d2c5ecf..91ae5a941d95 100644 --- a/ui/app/components/pages/first-time-flow/end-of-flow/end-of-flow.container.js +++ b/ui/app/components/pages/first-time-flow/end-of-flow/end-of-flow.container.js @@ -3,8 +3,8 @@ import EndOfFlow from './end-of-flow.component' import { setCompletedOnboarding } from '../../../../actions' const firstTimeFlowTypeNameMap = { - create: 'Selected Create New Wallet', - 'import': 'Selected Import Wallet', + create: 'New Wallet Created', + 'import': 'New Wallet Imported', } const mapStateToProps = ({ metamask }) => { From 268ae099d443172770d56026a851964cf8de3ada Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 1 Mar 2019 19:52:29 -0330 Subject: [PATCH 28/50] Fix names of Metrics Options actions (metametrics). --- .../metametrics-opt-in/metametrics-opt-in.component.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js index 2e7b6764bdb7..15bd01575892 100644 --- a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js +++ b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js @@ -100,7 +100,7 @@ export default class MetaMetricsOptIn extends Component { eventOpts: { category: 'Onboarding', action: 'Metrics Option', - name: 'Metrics Opt In', + name: 'Metrics Opt Out', }, isOptIn: true, }, { @@ -118,7 +118,7 @@ export default class MetaMetricsOptIn extends Component { eventOpts: { category: 'Onboarding', action: 'Metrics Option', - name: 'Metrics Opt Out', + name: 'Metrics Opt In', }, isOptIn: true, }) From debd05891c1a545c63dbe68d8285402127605fe1 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 4 Mar 2019 11:44:52 -0330 Subject: [PATCH 29/50] Clean up code related to metametrics. --- app/scripts/controllers/preferences.js | 4 +-- ui/app/actions.js | 11 +++--- ui/app/app.js | 2 -- ui/app/components/button/button.component.js | 2 +- .../metametrics-opt-in.component.js | 4 ++- .../components/sidebars/sidebar.component.js | 11 +++--- ui/app/metametrics/metametrics.provider.js | 36 ++++++++++--------- ui/app/metametrics/metametrics.util.js | 9 ++--- ui/app/selectors.js | 3 +- 9 files changed, 42 insertions(+), 40 deletions(-) diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index fe7a58cad89b..f923413537dd 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -99,14 +99,14 @@ class PreferencesController { /** * Setter for the `participateInMetaMetrics` property * - * @param {boolean} val Whether or not the user wants to participate in MetaMetrics + * @param {boolean} bool Whether or not the user wants to participate in MetaMetrics * @returns {string|null} the string of the new metametrics id, or null if not set * */ setParticipateInMetaMetrics (bool) { this.store.updateState({ participateInMetaMetrics: bool }) let metaMetricsId = null - if (bool === true && !this.store.getState().metaMetricsId) { + if (bool && !this.store.getState().metaMetricsId) { metaMetricsId = bufferToHex(sha3(String(Date.now()) + String(Math.round(Math.random() * Number.MAX_SAFE_INTEGER)))) this.store.updateState({ metaMetricsId }) } else if (bool === false) { diff --git a/ui/app/actions.js b/ui/app/actions.js index 0c467b186ed8..cb1154fb244a 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -2620,18 +2620,19 @@ function setParticipateInMetaMetrics (val) { log.debug(`background.setParticipateInMetaMetrics`) return new Promise((resolve, reject) => { background.setParticipateInMetaMetrics(val, (err, metaMetricsId) => { - console.log('!@# err, metaMetricsId', err, metaMetricsId) + log.debug(err) if (err) { dispatch(actions.displayWarning(err.message)) return reject(err) } + dispatch({ + type: actions.SET_PARTICIPATE_IN_METAMETRICS, + value: val, + }) + resolve([val, metaMetricsId]) }) - dispatch({ - type: actions.SET_PARTICIPATE_IN_METAMETRICS, - value: val, - }) }) } } diff --git a/ui/app/app.js b/ui/app/app.js index 8254a36a0efd..b9f6cafe7709 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -88,8 +88,6 @@ class App extends Component { if (action === 'PUSH') { const url = `&url=${encodeURIComponent('http://www.metamask.io/metametrics' + locationObj.pathname)}` this.context.metricsEvent({}, { - // ...props, - // ...config, currentPath: '', pathname: locationObj.pathname, url, diff --git a/ui/app/components/button/button.component.js b/ui/app/components/button/button.component.js index 5394133a0232..5d19219b4f47 100644 --- a/ui/app/components/button/button.component.js +++ b/ui/app/components/button/button.component.js @@ -18,7 +18,7 @@ const typeHash = { raised: CLASSNAME_RAISED, 'first-time': CLASSNAME_FIRST_TIME, } -// + export default class Button extends Component { static propTypes = { type: PropTypes.string, diff --git a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js index 15bd01575892..5fdde1b81340 100644 --- a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js +++ b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js @@ -96,7 +96,7 @@ export default class MetaMetricsOptIn extends Component { onCancel={() => { setParticipateInMetaMetrics(false) .then(() => { - metricsEvent({ + return metricsEvent({ eventOpts: { category: 'Onboarding', action: 'Metrics Option', @@ -106,6 +106,8 @@ export default class MetaMetricsOptIn extends Component { }, { excludeMetaMetricsId: true, }) + }) + .then(() => { history.push(nextRoute) }) }} diff --git a/ui/app/components/sidebars/sidebar.component.js b/ui/app/components/sidebars/sidebar.component.js index 02494b48b26b..b9e0f9e817a0 100644 --- a/ui/app/components/sidebars/sidebar.component.js +++ b/ui/app/components/sidebars/sidebar.component.js @@ -20,10 +20,13 @@ export default class Sidebar extends Component { renderOverlay () { const { onOverlayClose } = this.props - return
{ - onOverlayClose && onOverlayClose() - this.props.hideSidebar() - }} /> + return
{ + onOverlayClose && onOverlayClose() + this.props.hideSidebar() + } + } /> } renderSidebarContent () { diff --git a/ui/app/metametrics/metametrics.provider.js b/ui/app/metametrics/metametrics.provider.js index e71bb24704c8..a3088569ad14 100644 --- a/ui/app/metametrics/metametrics.provider.js +++ b/ui/app/metametrics/metametrics.provider.js @@ -20,6 +20,20 @@ import { } from './metametrics.util' class MetaMetricsProvider extends Component { + propTypes = { + network: PropTypes.string.isRequired, + environmentType: PropTypes.string.isRequired, + activeCurrency: PropTypes.string.isRequired, + accountType: PropTypes.string.isRequired, + metaMetricsSendCount: PropTypes.number.isRequired, + children: PropTypes.object.isRequired, + history: PropTypes.object.isRequired, + } + + childContextTypes = { + metricsEvent: PropTypes.func, + } + constructor (props) { super(props) @@ -42,10 +56,12 @@ class MetaMetricsProvider extends Component { const { previousPath, currentPath } = this.state return { - metricsEvent: (config, overrides = {}) => { - const isSendFlow = Boolean(config.eventOpts && config.eventOpts.name && config.eventOpts.name.match(/^send|^confirm/) || overrides.pathname && overrides.pathname.match(/send|confirm/)) + metricsEvent: (config = {}, overrides = {}) => { + const { eventOpts = {} } = config + const { name = '' } = eventOpts + const { pathname: overRidePathName = '' } = overrides + const isSendFlow = Boolean(name.match(/^send|^confirm/) || overRidePathName.match(/send|confirm/)) - // if (userPermission) { if (props.participateInMetaMetrics || config.isOptIn) { return sendMetaMetricsEvent({ ...props, @@ -66,20 +82,6 @@ class MetaMetricsProvider extends Component { } } -MetaMetricsProvider.propTypes = { - network: PropTypes.string, - environmentType: PropTypes.string, - activeCurrency: PropTypes.string, - accountType: PropTypes.string, - metaMetricsSendCount: PropTypes.number, - children: PropTypes.object, - history: PropTypes.object, -} - -MetaMetricsProvider.childContextTypes = { - metricsEvent: PropTypes.func, -} - const mapStateToProps = state => { const txData = txDataSelector(state) || {} diff --git a/ui/app/metametrics/metametrics.util.js b/ui/app/metametrics/metametrics.util.js index 60cf41905328..0b487e90bdf9 100644 --- a/ui/app/metametrics/metametrics.util.js +++ b/ui/app/metametrics/metametrics.util.js @@ -12,6 +12,7 @@ const METAMETRICS_CUSTOM_FUNCTION_TYPE = 'functionType' const METAMETRICS_CUSTOM_GAS_LIMIT_CHANGE = 'gasLimitChange' const METAMETRICS_CUSTOM_GAS_PRICE_CHANGE = 'gasPriceChange' const METAMETRICS_CUSTOM_RECIPIENT_KNOWN = 'recipientKnown' +const METAMETRICS_CUSTOM_CONFIRM_SCREEN_ORIGIN = 'origin' const METAMETRICS_CUSTOM_NETWORK = 'network' const METAMETRICS_CUSTOM_ENVIRONMENT_TYPE = 'environmentType' @@ -19,7 +20,6 @@ const METAMETRICS_CUSTOM_ACTIVE_CURRENCY = 'activeCurrency' const METAMETRICS_CUSTOM_ACCOUNT_TYPE = 'accountType' const METAMETRICS_CUSTOM_NUMBER_OF_TOKENS = 'numberOfTokens' const METAMETRICS_CUSTOM_NUMBER_OF_ACCOUNTS = 'numberOfAccounts' -const METAMETRICS_CUSTOM_CONFIRM_SCREEN_ORIGIN = 'origin' const customVariableNameIdMap = { [METAMETRICS_CUSTOM_HAD_ERROR]: 1, @@ -45,11 +45,6 @@ function composeUrlRefParamAddition (previousPath, confirmTransactionOrigin) { return `&urlref=${externalOrigin ? 'EXTERNAL' : encodeURIComponent(previousPath.replace(/chrome-extension:\/\/\w+/, 'http://www.metamask.io/metametrics'))}` } -// function composeActionNameParamAddition (pathname, pageOpts) { -// const { section, component } = pageOpts -// return `&action_name=${pathname.match(/[a-z-]+/)[0]}${section ? '%2F' + section : ''}${component ? '%2F' + component : ''}` -// } - function composeCustomDimensionParamAddition (customDimensions) { const customDimensionParamStrings = Object.keys(customDimensions).reduce((acc, name) => { return [...acc, `dimension${customDimensionsNameIdMap[name]}=${customDimensions[name]}`] @@ -101,7 +96,7 @@ function composeUrl (config, permissionPreferences = {}) { const cvar = customVariables && composeCustomVarParamAddition(customVariables) || '' - const action_name = '' // pageOpts && pathname && composeActionNameParamAddition(pathname, pageOpts) || '' + const action_name = '' const urlref = previousPath && composeUrlRefParamAddition(previousPath, confirmTransactionOrigin) diff --git a/ui/app/selectors.js b/ui/app/selectors.js index a83324e4b0c0..663c3f12b80e 100644 --- a/ui/app/selectors.js +++ b/ui/app/selectors.js @@ -1,4 +1,5 @@ import {NETWORK_TYPES} from './constants/common' +import { stripHexPrefix } from 'ethereumjs-util' const abi = require('human-standard-token-abi') import { @@ -63,7 +64,7 @@ function getCurrentKeyring (state) { return null } - const simpleAddress = identity.address.substring(2).toLowerCase() + const simpleAddress = stripHexPrefix(identity.address).toLowerCase() const keyring = state.metamask.keyrings.find((kr) => { return kr.accounts.includes(simpleAddress) || From d33f43422ce3c935026ebf24b81dd50873e2564d Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 4 Mar 2019 11:45:33 -0330 Subject: [PATCH 30/50] Fix bad merge conflict resolution and improve promise handling in sendMetaMetrics event and confrim tx base --- ui/app/actions.js | 9 +-- .../confirm-transaction-base.component.js | 71 +++++++------------ 2 files changed, 31 insertions(+), 49 deletions(-) diff --git a/ui/app/actions.js b/ui/app/actions.js index cb1154fb244a..d8363eba6b21 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -2647,12 +2647,13 @@ function setMetaMetricsSendCount (val) { return reject(err) } + dispatch({ + type: actions.SET_METAMETRICS_SEND_COUNT, + value: val, + }) + resolve(val) }) - dispatch({ - type: actions.SET_METAMETRICS_SEND_COUNT, - value: val, - }) }) } } diff --git a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js index 372e6df4a7e8..e76b4699b4e6 100644 --- a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js +++ b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js @@ -379,54 +379,35 @@ export default class ConfirmTransactionBase extends Component { origin, }, }) - if (onSubmit) { - Promise.resolve(onSubmit(txData)) - .then(() => { - this.setState({ - submitting: false, - }) - }) - } else { - sendTransaction(txData) - .then(() => { - clearConfirmTransaction() - this.setState({ - submitting: false, - }, () => { - history.push(DEFAULT_ROUTE) - }) - }) - .catch(error => { - this.setState({ - submitting: false, - submitError: error.message, - }) - }) - } - }) - - setMetaMetricsSendCount(metaMetricsSendCount + 1) - if (onSubmit) { - Promise.resolve(onSubmit(txData)) - .then(() => { - this.setState({ submitting: false }) - }) - .catch(() => { - setMetaMetricsSendCount(metaMetricsSendCount - 1) - }) - } else { - sendTransaction(txData) + setMetaMetricsSendCount(metaMetricsSendCount + 1) .then(() => { - clearConfirmTransaction() - this.setState({ submitting: false }) - history.push(DEFAULT_ROUTE) + if (onSubmit) { + Promise.resolve(onSubmit(txData)) + .then(() => { + this.setState({ + submitting: false, + }) + }) + } else { + sendTransaction(txData) + .then(() => { + clearConfirmTransaction() + this.setState({ + submitting: false, + }, () => { + history.push(DEFAULT_ROUTE) + }) + }) + .catch(error => { + this.setState({ + submitting: false, + submitError: error.message, + }) + }) + } }) - .catch(error => { - setMetaMetricsSendCount(metaMetricsSendCount - 1) - this.setState({ submitting: false, submitError: error.message }) - }) - } + }) } renderTitleComponent () { From c4ee46d72e78869fdc2d0c76e20692a819b073f5 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 4 Mar 2019 11:56:17 -0330 Subject: [PATCH 31/50] Don't send a second metrics event if user has gone back during first time flow. --- .../metametrics-opt-in.component.js | 70 +++++++++++-------- .../metametrics-opt-in.container.js | 3 +- 2 files changed, 41 insertions(+), 32 deletions(-) diff --git a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js index 5fdde1b81340..6fa73a9e887a 100644 --- a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js +++ b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js @@ -8,6 +8,7 @@ export default class MetaMetricsOptIn extends Component { setParticipateInMetaMetrics: PropTypes.func, nextRoute: PropTypes.string, firstTimeSelectionMetaMetricsName: PropTypes.string, + participateInMetaMetrics: PropTypes.bool, } static contextTypes = { @@ -21,6 +22,7 @@ export default class MetaMetricsOptIn extends Component { history, setParticipateInMetaMetrics, firstTimeSelectionMetaMetricsName, + participateInMetaMetrics, } = this.props return ( @@ -96,49 +98,55 @@ export default class MetaMetricsOptIn extends Component { onCancel={() => { setParticipateInMetaMetrics(false) .then(() => { - return metricsEvent({ - eventOpts: { - category: 'Onboarding', - action: 'Metrics Option', - name: 'Metrics Opt Out', - }, - isOptIn: true, - }, { - excludeMetaMetricsId: true, + if (participateInMetaMetrics === null) { + return metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Metrics Option', + name: 'Metrics Opt Out', + }, + isOptIn: true, + }, { + excludeMetaMetricsId: true, + }) + .then(() => { + history.push(nextRoute) }) - }) - .then(() => { - history.push(nextRoute) - }) + } + }) }} cancelText={'No Thanks'} hideCancel={false} onSubmit={() => { setParticipateInMetaMetrics(true) .then(([participateStatus, metaMetricsId]) => { - return metricsEvent({ - eventOpts: { - category: 'Onboarding', - action: 'Metrics Option', - name: 'Metrics Opt In', - }, - isOptIn: true, - }) - .then(() => { - return metricsEvent({ + const promise = participateInMetaMetrics === null + ? metricsEvent({ eventOpts: { category: 'Onboarding', - action: 'Import or Create', - name: firstTimeSelectionMetaMetricsName, + action: 'Metrics Option', + name: 'Metrics Opt In', }, isOptIn: true, - metaMetricsId, }) - }) - .then(() => { - history.push(nextRoute) - }) - }) + : Promise.resolve() + + promise + .then(() => { + return metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Import or Create', + name: firstTimeSelectionMetaMetricsName, + }, + isOptIn: true, + metaMetricsId, + }) + }) + .then(() => { + history.push(nextRoute) + }) + }) }} submitText={'I agree'} submitButtonType={'confirm'} diff --git a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js index dd3d3898e415..d2c5b39f450f 100644 --- a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js +++ b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js @@ -13,7 +13,7 @@ const firstTimeFlowTypeNameMap = { } const mapStateToProps = ({ metamask }) => { - const { firstTimeFlowType } = metamask + const { firstTimeFlowType, participateInMetaMetrics } = metamask let nextRoute if (firstTimeFlowType === 'create') { @@ -27,6 +27,7 @@ const mapStateToProps = ({ metamask }) => { return { nextRoute, firstTimeSelectionMetaMetricsName: firstTimeFlowTypeNameMap[firstTimeFlowType], + participateInMetaMetrics, } } From f20eb101a5b62348673fb16ccbb88b1753f5564c Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 4 Mar 2019 11:58:44 -0330 Subject: [PATCH 32/50] Collect metametrics on going back from onboarding create/import. --- .../import-with-seed-phrase.component.js | 7 +++++++ .../create-password/new-account/new-account.component.js | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js b/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js index af6cd80bbf0a..358ed96eb43d 100644 --- a/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js +++ b/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js @@ -166,6 +166,13 @@ export default class ImportWithSeedPhrase extends PureComponent { { e.preventDefault() + this.context.metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Import Seed Phrase', + name: 'Go Back from Onboarding Import', + }, + }) this.props.history.push(INITIALIZE_SELECT_ACTION_ROUTE) }} href="#" diff --git a/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js b/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js index 44c19e42df88..aae21ba70c52 100644 --- a/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js +++ b/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js @@ -146,6 +146,13 @@ export default class NewAccount extends PureComponent { { e.preventDefault() + this.context.metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Create Password', + name: 'Go Back from Onboarding Create', + }, + }) this.props.history.push(INITIALIZE_SELECT_ACTION_ROUTE) }} href="#" From 55b09f231c1cebb87f3b76c00b4b5b985408f43c Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 4 Mar 2019 12:06:15 -0330 Subject: [PATCH 33/50] Add missing custom variable constants for metametrics --- .../components/pages/create-account/new-account.js | 2 +- .../pages/unlock-page/unlock-page.component.js | 10 +++------- ui/app/metametrics/metametrics.util.js | 12 ++++++++++++ 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/ui/app/components/pages/create-account/new-account.js b/ui/app/components/pages/create-account/new-account.js index 16355525047e..a7595e3468ca 100644 --- a/ui/app/components/pages/create-account/new-account.js +++ b/ui/app/components/pages/create-account/new-account.js @@ -70,7 +70,7 @@ class NewAccountCreateForm extends Component { name: 'Error', }, customVariables: { - error: e.message, + errorMessage: e.message, }, }) }) diff --git a/ui/app/components/pages/unlock-page/unlock-page.component.js b/ui/app/components/pages/unlock-page/unlock-page.component.js index f9036b6f53e7..3ba870885788 100644 --- a/ui/app/components/pages/unlock-page/unlock-page.component.js +++ b/ui/app/components/pages/unlock-page/unlock-page.component.js @@ -60,13 +60,9 @@ export default class UnlockPage extends Component { const newState = await forceUpdateMetamaskState() this.context.metricsEvent({ eventOpts: { - category: 'Navigation', - action: 'Unlock', - name: 'Success', - }, - customVariables: { - numberOfTokens: newState.tokens.length, - numberOfAccounts: Object.keys(newState.accounts).length, + category: 'Navigation', + action: 'Unlock', + name: 'Success', }, isNewVisit: true, }) diff --git a/ui/app/metametrics/metametrics.util.js b/ui/app/metametrics/metametrics.util.js index 0b487e90bdf9..74badced9188 100644 --- a/ui/app/metametrics/metametrics.util.js +++ b/ui/app/metametrics/metametrics.util.js @@ -13,6 +13,12 @@ const METAMETRICS_CUSTOM_GAS_LIMIT_CHANGE = 'gasLimitChange' const METAMETRICS_CUSTOM_GAS_PRICE_CHANGE = 'gasPriceChange' const METAMETRICS_CUSTOM_RECIPIENT_KNOWN = 'recipientKnown' const METAMETRICS_CUSTOM_CONFIRM_SCREEN_ORIGIN = 'origin' +const METAMETRICS_CUSTOM_FROM_NETWORK = 'fromNetwork' +const METAMETRICS_CUSTOM_TO_NETWORK = 'toNetwork' +const METAMETRICS_CUSTOM_ERROR_FIELD = 'errorField' +const METAMETRICS_CUSTOM_ERROR_MESSAGE = 'errorMessage' +const METAMETRICS_CUSTOM_RPC_NETWORK_ID = 'networkId' +const METAMETRICS_CUSTOM_RPC_CHAIN_ID = 'chainId' const METAMETRICS_CUSTOM_NETWORK = 'network' const METAMETRICS_CUSTOM_ENVIRONMENT_TYPE = 'environmentType' @@ -29,6 +35,12 @@ const customVariableNameIdMap = { [METAMETRICS_CUSTOM_GAS_PRICE_CHANGE]: 5, [METAMETRICS_CUSTOM_RECIPIENT_KNOWN]: 6, [METAMETRICS_CUSTOM_CONFIRM_SCREEN_ORIGIN]: 7, + [METAMETRICS_CUSTOM_FROM_NETWORK]: 8, + [METAMETRICS_CUSTOM_TO_NETWORK]: 9, + [METAMETRICS_CUSTOM_RPC_NETWORK_ID]: 10, + [METAMETRICS_CUSTOM_RPC_CHAIN_ID]: 11, + [METAMETRICS_CUSTOM_ERROR_FIELD]: 12, + [METAMETRICS_CUSTOM_ERROR_MESSAGE]: 13, } const customDimensionsNameIdMap = { From 4a74b0af35a35c7e33130e00b47e5ef57f9bff92 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 4 Mar 2019 12:11:55 -0330 Subject: [PATCH 34/50] Fix metametrics provider --- ui/app/metametrics/metametrics.provider.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/metametrics/metametrics.provider.js b/ui/app/metametrics/metametrics.provider.js index a3088569ad14..5ff0294e51ec 100644 --- a/ui/app/metametrics/metametrics.provider.js +++ b/ui/app/metametrics/metametrics.provider.js @@ -20,7 +20,7 @@ import { } from './metametrics.util' class MetaMetricsProvider extends Component { - propTypes = { + static propTypes = { network: PropTypes.string.isRequired, environmentType: PropTypes.string.isRequired, activeCurrency: PropTypes.string.isRequired, @@ -30,7 +30,7 @@ class MetaMetricsProvider extends Component { history: PropTypes.object.isRequired, } - childContextTypes = { + static childContextTypes = { metricsEvent: PropTypes.func, } From 9a6f943901b3f71085552bdfe39732f4bb199379 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 4 Mar 2019 12:35:22 -0330 Subject: [PATCH 35/50] Make height of opt-in modal responsive. --- .../components/modals/metametrics-opt-in-modal/index.scss | 7 ++++++- ui/app/components/modals/modal.js | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ui/app/components/modals/metametrics-opt-in-modal/index.scss b/ui/app/components/modals/metametrics-opt-in-modal/index.scss index 32a52351a5c6..88b6d7a4da19 100644 --- a/ui/app/components/modals/metametrics-opt-in-modal/index.scss +++ b/ui/app/components/modals/metametrics-opt-in-modal/index.scss @@ -3,9 +3,14 @@ justify-content: center; margin-left: 3%; margin-right: 0%; - max-height: 600px; + max-height: 75vh; + + @media screen and (max-width: 575px) { + max-height: 100vh; + } } + .metametrics-opt-in__title { font-size: 38px; } diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js index a6dd477815bc..8ab599a716dc 100644 --- a/ui/app/components/modals/modal.js +++ b/ui/app/components/modals/modal.js @@ -225,6 +225,7 @@ const MODALS = { }, laptopModalStyle: { ...modalContainerLaptopStyle, + top: '10%', }, contentStyle: { borderRadius: '8px', From 8eadc38f9c9b21ca56c853590b2ea2fde6ac1bff Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 4 Mar 2019 12:37:11 -0330 Subject: [PATCH 36/50] Adjust text content for opt-in modal. --- .../metametrics-opt-in-modal.component.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/components/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js b/ui/app/components/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js index e332dfe7633d..cc725aba5de0 100644 --- a/ui/app/components/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js +++ b/ui/app/components/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js @@ -36,8 +36,8 @@ export default class MetaMetricsOptInModal extends Component {
Help Us Improve MetaMask
- MetaMask would like to gather some usage data to better understand how our users interact with the extension and inform futue development. - This data will be used to continually improve the usability and user experience of our product. + MetaMask would like to gather usage data to better understand how our users interact with the extension. This data + will be used to continually improve the usability and user experience of our product and the etheruem ecosystem.
MetaMask will.. From 8ea52156487bf5c231995d032ceaec33ffabbee5 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 4 Mar 2019 12:40:24 -0330 Subject: [PATCH 37/50] Update metametrics event names and clean up code in opt-in-modal --- .../metametrics-opt-in-modal.component.js | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/ui/app/components/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js b/ui/app/components/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js index cc725aba5de0..36f1ed92daaf 100644 --- a/ui/app/components/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js +++ b/ui/app/components/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js @@ -13,6 +13,9 @@ export default class MetaMetricsOptInModal extends Component { } render () { + const { metricsEvent } = this.context + const { setParticipateInMetaMetrics, hideModal } = this.props + return (
@@ -89,27 +92,35 @@ export default class MetaMetricsOptInModal extends Component {
{ - this.props.setParticipateInMetaMetrics(false) + setParticipateInMetaMetrics(false) .then(() => { - this.context.metricsEvent({ + metricsEvent({ eventOpts: { - category: 'MetaMetricsOptIn', - action: 'userSelectsOptIn', - name: 'userOptedOut', + category: 'Onboarding', + action: 'Metrics Option', + name: 'Metrics Opt Out', }, isOptIn: true, }, { excludeMetaMetricsId: true, }) - this.props.hideModal() + hideModal() }) }} cancelText={'No Thanks'} hideCancel={false} onSubmit={() => { - this.props.setParticipateInMetaMetrics(true) + setParticipateInMetaMetrics(true) .then(() => { - this.props.hideModal() + metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Metrics Option', + name: 'Metrics Opt In', + }, + isOptIn: true, + }) + hideModal() }) }} submitText={'I agree'} From 8b7b253a801e08c623a53b80e4532ca4ec68b263 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 4 Mar 2019 13:04:14 -0330 Subject: [PATCH 38/50] Put phishing warning step next to last in onboarding flow --- .../new-account/new-account.component.js | 4 +-- .../unique-image/unique-image.component.js | 11 +++----- .../first-time-flow.selectors.js | 26 +++++++++++++++++++ .../metametrics-opt-in.container.js | 21 +++------------ .../confirm-seed-phrase.component.js | 5 ++-- .../select-action/select-action.component.js | 6 ++--- .../select-action/select-action.container.js | 9 ++++++- 7 files changed, 48 insertions(+), 34 deletions(-) create mode 100644 ui/app/components/pages/first-time-flow/first-time-flow.selectors.js diff --git a/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js b/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js index aae21ba70c52..6bb1ad073ce8 100644 --- a/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js +++ b/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' import Button from '../../../../button' import { - INITIALIZE_UNIQUE_IMAGE_ROUTE, + INITIALIZE_SEED_PHRASE_ROUTE, INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE, INITIALIZE_SELECT_ACTION_ROUTE, } from '../../../../../routes' @@ -109,7 +109,7 @@ export default class NewAccount extends PureComponent { }, }) - history.push(INITIALIZE_UNIQUE_IMAGE_ROUTE) + history.push(INITIALIZE_SEED_PHRASE_ROUTE) } catch (error) { this.setState({ passwordError: error.message }) } diff --git a/ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.component.js b/ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.component.js index 95cf91453668..cbc85c0e47cb 100644 --- a/ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.component.js +++ b/ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.component.js @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' import Button from '../../../../button' -import { INITIALIZE_SEED_PHRASE_ROUTE, INITIALIZE_END_OF_FLOW_ROUTE } from '../../../../../routes' +import { INITIALIZE_END_OF_FLOW_ROUTE } from '../../../../../routes' export default class UniqueImageScreen extends PureComponent { static contextTypes = { @@ -11,12 +11,11 @@ export default class UniqueImageScreen extends PureComponent { static propTypes = { history: PropTypes.object, - isImportedKeyring: PropTypes.bool, } render () { const { t } = this.context - const { history, isImportedKeyring } = this.props + const { history } = this.props return (
@@ -45,11 +44,7 @@ export default class UniqueImageScreen extends PureComponent { name: 'Agree to Phishing Warning', }, }) - if (isImportedKeyring) { - history.push(INITIALIZE_END_OF_FLOW_ROUTE) - } else { - history.push(INITIALIZE_SEED_PHRASE_ROUTE) - } + history.push(INITIALIZE_END_OF_FLOW_ROUTE) }} > { t('next') } diff --git a/ui/app/components/pages/first-time-flow/first-time-flow.selectors.js b/ui/app/components/pages/first-time-flow/first-time-flow.selectors.js new file mode 100644 index 000000000000..1286afed9f34 --- /dev/null +++ b/ui/app/components/pages/first-time-flow/first-time-flow.selectors.js @@ -0,0 +1,26 @@ +import { + INITIALIZE_CREATE_PASSWORD_ROUTE, + INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE, + DEFAULT_ROUTE, +} from '../../../routes' + +const selectors = { + getFirstTimeFlowTypeRoute, +} + +module.exports = selectors + +function getFirstTimeFlowTypeRoute (state) { + const { firstTimeFlowType } = state.metamask + + let nextRoute + if (firstTimeFlowType === 'create') { + nextRoute = INITIALIZE_CREATE_PASSWORD_ROUTE + } else if (firstTimeFlowType === 'import') { + nextRoute = INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE + } else { + nextRoute = DEFAULT_ROUTE + } + + return nextRoute +} diff --git a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js index d2c5b39f450f..b13af8bf6a95 100644 --- a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js +++ b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js @@ -1,31 +1,18 @@ import { connect } from 'react-redux' import MetaMetricsOptIn from './metametrics-opt-in.component' import { setParticipateInMetaMetrics } from '../../../../actions' -import { - INITIALIZE_CREATE_PASSWORD_ROUTE, - INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE, - DEFAULT_ROUTE, -} from '../../../../routes' +import { getFirstTimeFlowTypeRoute } from '../first-time-flow.selectors' const firstTimeFlowTypeNameMap = { create: 'Selected Create New Wallet', 'import': 'Selected Import Wallet', } -const mapStateToProps = ({ metamask }) => { - const { firstTimeFlowType, participateInMetaMetrics } = metamask - - let nextRoute - if (firstTimeFlowType === 'create') { - nextRoute = INITIALIZE_CREATE_PASSWORD_ROUTE - } else if (firstTimeFlowType === 'import') { - nextRoute = INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE - } else { - nextRoute = DEFAULT_ROUTE - } +const mapStateToProps = (state) => { + const { firstTimeFlowType, participateInMetaMetrics } = state.metamask return { - nextRoute, + nextRoute: getFirstTimeFlowTypeRoute(state), firstTimeSelectionMetaMetricsName: firstTimeFlowTypeNameMap[firstTimeFlowType], participateInMetaMetrics, } diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js index e974fd904d44..1645a9f96569 100644 --- a/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js +++ b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' import classnames from 'classnames' import shuffle from 'lodash.shuffle' import Button from '../../../../button' -import { INITIALIZE_END_OF_FLOW_ROUTE, INITIALIZE_SEED_PHRASE_ROUTE } from '../../../../../routes' +import { INITIALIZE_UNIQUE_IMAGE_ROUTE, INITIALIZE_SEED_PHRASE_ROUTE } from '../../../../../routes' import { exportAsFile } from '../../../../../../app/util' import { selectSeedWord, deselectSeedWord } from './confirm-seed-phrase.state' @@ -48,7 +48,6 @@ export default class ConfirmSeedPhrase extends PureComponent { } try { - history.push(INITIALIZE_END_OF_FLOW_ROUTE) this.context.metricsEvent({ eventOpts: { category: 'Onboarding', @@ -56,7 +55,7 @@ export default class ConfirmSeedPhrase extends PureComponent { name: 'Verify Complete', }, }) - history.push(INITIALIZE_END_OF_FLOW_ROUTE) + history.push(INITIALIZE_UNIQUE_IMAGE_ROUTE) } catch (error) { console.error(error.message) } diff --git a/ui/app/components/pages/first-time-flow/select-action/select-action.component.js b/ui/app/components/pages/first-time-flow/select-action/select-action.component.js index 5d5c004cfeac..d994935c5e04 100644 --- a/ui/app/components/pages/first-time-flow/select-action/select-action.component.js +++ b/ui/app/components/pages/first-time-flow/select-action/select-action.component.js @@ -3,7 +3,6 @@ import PropTypes from 'prop-types' import Button from '../../../button' import { INITIALIZE_METAMETRICS_OPT_IN_ROUTE, - INITIALIZE_UNIQUE_IMAGE_ROUTE, } from '../../../../routes' export default class SelectAction extends PureComponent { @@ -11,6 +10,7 @@ export default class SelectAction extends PureComponent { history: PropTypes.object, isInitialized: PropTypes.bool, setFirstTimeFlowType: PropTypes.func, + nextRoute: PropTypes.string, } static contextTypes = { @@ -18,10 +18,10 @@ export default class SelectAction extends PureComponent { } componentDidMount () { - const { history, isInitialized } = this.props + const { history, isInitialized, nextRoute } = this.props if (isInitialized) { - history.push(INITIALIZE_UNIQUE_IMAGE_ROUTE) + history.push(nextRoute) } } diff --git a/ui/app/components/pages/first-time-flow/select-action/select-action.container.js b/ui/app/components/pages/first-time-flow/select-action/select-action.container.js index 85837965ae91..42fac7af27b2 100644 --- a/ui/app/components/pages/first-time-flow/select-action/select-action.container.js +++ b/ui/app/components/pages/first-time-flow/select-action/select-action.container.js @@ -2,8 +2,15 @@ import { connect } from 'react-redux' import { withRouter } from 'react-router-dom' import { compose } from 'recompose' import { setFirstTimeFlowType } from '../../../../actions' +import { getFirstTimeFlowTypeRoute } from '../first-time-flow.selectors' import Welcome from './select-action.component' +const mapStateToProps = (state) => { + return { + nextRoute: getFirstTimeFlowTypeRoute(state), + } +} + const mapDispatchToProps = dispatch => { return { setFirstTimeFlowType: type => dispatch(setFirstTimeFlowType(type)), @@ -12,5 +19,5 @@ const mapDispatchToProps = dispatch => { export default compose( withRouter, - connect(null, mapDispatchToProps) + connect(mapStateToProps, mapDispatchToProps) )(Welcome) From ae8d9c0d36a5527d048adb4e4b7fe83d2b53b2e5 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 4 Mar 2019 13:22:10 -0330 Subject: [PATCH 39/50] Link terms of service on create and import screens of first time flow --- .../import-with-seed-phrase.component.js | 10 +++++++++- .../new-account/new-account.component.js | 10 +++++++++- ui/app/components/pages/first-time-flow/index.scss | 4 ++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js b/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js index 358ed96eb43d..0662a3ab5301 100644 --- a/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js +++ b/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js @@ -231,7 +231,15 @@ export default class ImportWithSeedPhrase extends PureComponent { {termsChecked ? : null}
- { t('agreeTermsOfService') } + I have read and agree to the
+ + { 'Terms of Use' } + +
- I agree to the Terms Of Service + I have read and agree to the + + { 'Terms of Use' } + +
+
+ { t('thisWillCreate') } +
- ( - - )} - /> { '• ' + t('endOfFlowMessage5') }
+
+ { '• ' + t('endOfFlowMessage6') } +
+
+ { '• ' + t('endOfFlowMessage7') } +
- { '*' + t('endOfFlowMessage6') } + { '*' + t('endOfFlowMessage8') }