Merge pull request #9976 from MetaMask/Version-v8.1.6
Version v8.1.6 RCfeature/default_network_editable
commit
52e428fbe6
@ -1,41 +0,0 @@ |
||||
const path = require('path') |
||||
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin') |
||||
|
||||
module.exports = { |
||||
module: { |
||||
strictExportPresence: true, |
||||
rules: [ |
||||
{ |
||||
test: /\.scss$/, |
||||
loaders: [ |
||||
'style-loader', |
||||
{ |
||||
loader: 'css-loader', |
||||
options: { |
||||
import: false, |
||||
url: false, |
||||
}, |
||||
}, |
||||
'resolve-url-loader', |
||||
{ |
||||
loader: 'sass-loader', |
||||
options: { |
||||
sourceMap: true, |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
}, |
||||
plugins: [ |
||||
new CopyWebpackPlugin({ |
||||
patterns: [ |
||||
{ |
||||
from: path.join('node_modules', '@fortawesome', 'fontawesome-free', 'webfonts'), |
||||
to: path.join('fonts', 'fontawesome'), |
||||
}, |
||||
], |
||||
}), |
||||
], |
||||
} |
After Width: | Height: | Size: 2.4 KiB |
@ -0,0 +1,366 @@ |
||||
import { merge, omit } from 'lodash' |
||||
import ObservableStore from 'obs-store' |
||||
import { bufferToHex, sha3 } from 'ethereumjs-util' |
||||
import { ENVIRONMENT_TYPE_BACKGROUND } from '../lib/enums' |
||||
import { |
||||
METAMETRICS_ANONYMOUS_ID, |
||||
METAMETRICS_BACKGROUND_PAGE_OBJECT, |
||||
} from '../../../shared/constants/metametrics' |
||||
|
||||
/** |
||||
* Used to determine whether or not to attach a user's metametrics id |
||||
* to events that include on-chain data. This helps to prevent identifying |
||||
* a user by being able to trace their activity on etherscan/block exploring |
||||
*/ |
||||
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]) |
||||
} |
||||
|
||||
/** |
||||
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsContext} MetaMetricsContext |
||||
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsEventPayload} MetaMetricsEventPayload |
||||
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsEventOptions} MetaMetricsEventOptions |
||||
* @typedef {import('../../../shared/constants/metametrics').SegmentEventPayload} SegmentEventPayload |
||||
* @typedef {import('../../../shared/constants/metametrics').SegmentInterface} SegmentInterface |
||||
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsPagePayload} MetaMetricsPagePayload |
||||
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsPageOptions} MetaMetricsPageOptions |
||||
*/ |
||||
|
||||
/** |
||||
* @typedef {Object} MetaMetricsControllerState |
||||
* @property {?string} metaMetricsId - The user's metaMetricsId that will be |
||||
* attached to all non-anonymized event payloads |
||||
* @property {?boolean} participateInMetaMetrics - The user's preference for |
||||
* participating in the MetaMetrics analytics program. This setting controls |
||||
* whether or not events are tracked |
||||
* @property {number} metaMetricsSendCount - How many send transactions have |
||||
* been tracked through this controller. Used to prevent attaching sensitive |
||||
* data that can be traced through on chain data. |
||||
*/ |
||||
|
||||
export default class MetaMetricsController { |
||||
/** |
||||
* @param {Object} segment - an instance of analytics-node for tracking |
||||
* events that conform to the new MetaMetrics tracking plan. |
||||
* @param {Object} segmentLegacy - an instance of analytics-node for |
||||
* tracking legacy schema events. Will eventually be phased out |
||||
* @param {Object} preferencesStore - The preferences controller store, used |
||||
* to access and subscribe to preferences that will be attached to events |
||||
* @param {function} onNetworkDidChange - Used to attach a listener to the |
||||
* networkDidChange event emitted by the networkController |
||||
* @param {function} getCurrentChainId - Gets the current chain id from the |
||||
* network controller |
||||
* @param {function} getNetworkIdentifier - Gets the current network |
||||
* identifier from the network controller |
||||
* @param {string} version - The version of the extension |
||||
* @param {string} environment - The environment the extension is running in |
||||
* @param {MetaMetricsControllerState} initState - State to initialized with |
||||
*/ |
||||
constructor({ |
||||
segment, |
||||
segmentLegacy, |
||||
preferencesStore, |
||||
onNetworkDidChange, |
||||
getCurrentChainId, |
||||
getNetworkIdentifier, |
||||
version, |
||||
environment, |
||||
initState, |
||||
}) { |
||||
const prefState = preferencesStore.getState() |
||||
this.chainId = getCurrentChainId() |
||||
this.network = getNetworkIdentifier() |
||||
this.locale = prefState.currentLocale.replace('_', '-') |
||||
this.version = |
||||
environment === 'production' ? version : `${version}-${environment}` |
||||
|
||||
this.store = new ObservableStore({ |
||||
participateInMetaMetrics: null, |
||||
metaMetricsId: null, |
||||
metaMetricsSendCount: 0, |
||||
...initState, |
||||
}) |
||||
|
||||
preferencesStore.subscribe(({ currentLocale }) => { |
||||
this.locale = currentLocale.replace('_', '-') |
||||
}) |
||||
|
||||
onNetworkDidChange(() => { |
||||
this.chainId = getCurrentChainId() |
||||
this.network = getNetworkIdentifier() |
||||
}) |
||||
this.segment = segment |
||||
this.segmentLegacy = segmentLegacy |
||||
} |
||||
|
||||
generateMetaMetricsId() { |
||||
return bufferToHex( |
||||
sha3( |
||||
String(Date.now()) + |
||||
String(Math.round(Math.random() * Number.MAX_SAFE_INTEGER)), |
||||
), |
||||
) |
||||
} |
||||
|
||||
/** |
||||
* Setter for the `participateInMetaMetrics` property |
||||
* |
||||
* @param {boolean} participateInMetaMetrics - 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(participateInMetaMetrics) { |
||||
let { metaMetricsId } = this.state |
||||
if (participateInMetaMetrics && !metaMetricsId) { |
||||
metaMetricsId = this.generateMetaMetricsId() |
||||
} else if (participateInMetaMetrics === false) { |
||||
metaMetricsId = null |
||||
} |
||||
this.store.updateState({ participateInMetaMetrics, metaMetricsId }) |
||||
return metaMetricsId |
||||
} |
||||
|
||||
get state() { |
||||
return this.store.getState() |
||||
} |
||||
|
||||
setMetaMetricsSendCount(val) { |
||||
this.store.updateState({ metaMetricsSendCount: val }) |
||||
} |
||||
|
||||
/** |
||||
* Build the context object to attach to page and track events. |
||||
* @private |
||||
* @param {Pick<MetaMetricsContext, 'referrer'>} [referrer] - dapp origin that initialized |
||||
* the notification window. |
||||
* @param {Pick<MetaMetricsContext, 'page'>} [page] - page object describing the current |
||||
* view of the extension. Defaults to the background-process object. |
||||
* @returns {MetaMetricsContext} |
||||
*/ |
||||
_buildContext(referrer, page = METAMETRICS_BACKGROUND_PAGE_OBJECT) { |
||||
return { |
||||
app: { |
||||
name: 'MetaMask Extension', |
||||
version: this.version, |
||||
}, |
||||
userAgent: window.navigator.userAgent, |
||||
page, |
||||
referrer, |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Build's the event payload, processing all fields into a format that can be |
||||
* fed to Segment's track method |
||||
* @private |
||||
* @param { |
||||
* Omit<MetaMetricsEventPayload, 'sensitiveProperties'> |
||||
* } rawPayload - raw payload provided to trackEvent |
||||
* @returns {SegmentEventPayload} - formatted event payload for segment |
||||
*/ |
||||
_buildEventPayload(rawPayload) { |
||||
const { |
||||
event, |
||||
properties, |
||||
revenue, |
||||
value, |
||||
currency, |
||||
category, |
||||
page, |
||||
referrer, |
||||
environmentType = ENVIRONMENT_TYPE_BACKGROUND, |
||||
} = rawPayload |
||||
return { |
||||
event, |
||||
properties: { |
||||
// These values are omitted from properties because they have special meaning
|
||||
// in segment. https://segment.com/docs/connections/spec/track/#properties.
|
||||
// to avoid accidentally using these inappropriately, you must add them as top
|
||||
// level properties on the event payload. We also exclude locale to prevent consumers
|
||||
// from overwriting this context level property. We track it as a property
|
||||
// because not all destinations map locale from context.
|
||||
...omit(properties, ['revenue', 'locale', 'currency', 'value']), |
||||
revenue, |
||||
value, |
||||
currency, |
||||
category, |
||||
network: this.network, |
||||
locale: this.locale, |
||||
chain_id: this.chainId, |
||||
environment_type: environmentType, |
||||
}, |
||||
context: this._buildContext(referrer, page), |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Perform validation on the payload and update the id type to use before |
||||
* sending to Segment. Also examines the options to route and handle the |
||||
* event appropriately. |
||||
* @private |
||||
* @param {SegmentEventPayload} payload - properties to attach to event |
||||
* @param {MetaMetricsEventOptions} [options] - options for routing and |
||||
* handling the event |
||||
* @returns {Promise<void>} |
||||
*/ |
||||
_track(payload, options) { |
||||
const { |
||||
isOptIn, |
||||
metaMetricsId: metaMetricsIdOverride, |
||||
matomoEvent, |
||||
flushImmediately, |
||||
} = options || {} |
||||
let idType = 'userId' |
||||
let idValue = this.state.metaMetricsId |
||||
let excludeMetaMetricsId = options?.excludeMetaMetricsId ?? false |
||||
// This is carried over from the old implementation, and will likely need
|
||||
// to be updated to work with the new tracking plan. I think we should use
|
||||
// a config setting for this instead of trying to match the event name
|
||||
const isSendFlow = Boolean(payload.event.match(/^send|^confirm/iu)) |
||||
if ( |
||||
isSendFlow && |
||||
this.state.metaMetricsSendCount && |
||||
!sendCountIsTrackable(this.state.metaMetricsSendCount + 1) |
||||
) { |
||||
excludeMetaMetricsId = true |
||||
} |
||||
// If we are tracking sensitive data we will always use the anonymousId
|
||||
// property as well as our METAMETRICS_ANONYMOUS_ID. This prevents us from
|
||||
// associating potentially identifiable information with a specific id.
|
||||
// During the opt in flow we will track all events, but do so with the
|
||||
// anonymous id. The one exception to that rule is after the user opts in
|
||||
// to MetaMetrics. When that happens we receive back the user's new
|
||||
// MetaMetrics id before it is fully persisted to state. To avoid a race
|
||||
// condition we explicitly pass the new id to the track method. In that
|
||||
// case we will track the opt in event to the user's id. In all other cases
|
||||
// we use the metaMetricsId from state.
|
||||
if (excludeMetaMetricsId || (isOptIn && !metaMetricsIdOverride)) { |
||||
idType = 'anonymousId' |
||||
idValue = METAMETRICS_ANONYMOUS_ID |
||||
} else if (isOptIn && metaMetricsIdOverride) { |
||||
idValue = metaMetricsIdOverride |
||||
} |
||||
payload[idType] = idValue |
||||
|
||||
// Promises will only resolve when the event is sent to segment. For any
|
||||
// event that relies on this promise being fulfilled before performing UI
|
||||
// updates, or otherwise delaying user interaction, supply the
|
||||
// 'flushImmediately' flag to the trackEvent method.
|
||||
return new Promise((resolve, reject) => { |
||||
const callback = (err) => { |
||||
if (err) { |
||||
return reject(err) |
||||
} |
||||
return resolve() |
||||
} |
||||
|
||||
const target = matomoEvent === true ? this.segmentLegacy : this.segment |
||||
|
||||
target.track(payload, callback) |
||||
if (flushImmediately) { |
||||
target.flush() |
||||
} |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* track a page view with Segment |
||||
* @param {MetaMetricsPagePayload} payload - details of the page viewed |
||||
* @param {MetaMetricsPageOptions} [options] - options for handling the page |
||||
* view |
||||
*/ |
||||
trackPage({ name, params, environmentType, page, referrer }, options) { |
||||
if (this.state.participateInMetaMetrics === false) { |
||||
return |
||||
} |
||||
|
||||
if (this.state.participateInMetaMetrics === null && !options?.isOptInPath) { |
||||
return |
||||
} |
||||
const { metaMetricsId } = this.state |
||||
const idTrait = metaMetricsId ? 'userId' : 'anonymousId' |
||||
const idValue = metaMetricsId ?? METAMETRICS_ANONYMOUS_ID |
||||
this.segment.page({ |
||||
[idTrait]: idValue, |
||||
name, |
||||
properties: { |
||||
params, |
||||
locale: this.locale, |
||||
network: this.network, |
||||
chain_id: this.chainId, |
||||
environment_type: environmentType, |
||||
}, |
||||
context: this._buildContext(referrer, page), |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* track a metametrics event, performing necessary payload manipulation and |
||||
* routing the event to the appropriate segment source. Will split events |
||||
* with sensitiveProperties into two events, tracking the sensitiveProperties |
||||
* with the anonymousId only. |
||||
* @param {MetaMetricsEventPayload} payload - details of the event |
||||
* @param {MetaMetricsEventOptions} [options] - options for handling/routing the event |
||||
* @returns {Promise<void>} |
||||
*/ |
||||
async trackEvent(payload, options) { |
||||
// event and category are required fields for all payloads
|
||||
if (!payload.event || !payload.category) { |
||||
throw new Error('Must specify event and category.') |
||||
} |
||||
|
||||
if (!this.state.participateInMetaMetrics && !options?.isOptIn) { |
||||
return |
||||
} |
||||
|
||||
// We might track multiple events if sensitiveProperties is included, this array will hold
|
||||
// the promises returned from this._track.
|
||||
const events = [] |
||||
|
||||
if (payload.sensitiveProperties) { |
||||
// sensitiveProperties will only be tracked using the anonymousId property and generic id
|
||||
// If the event options already specify to exclude the metaMetricsId we throw an error as
|
||||
// a signal to the developer that the event was implemented incorrectly
|
||||
if (options?.excludeMetaMetricsId === true) { |
||||
throw new Error( |
||||
'sensitiveProperties was specified in an event payload that also set the excludeMetaMetricsId flag', |
||||
) |
||||
} |
||||
|
||||
const combinedProperties = merge( |
||||
payload.sensitiveProperties, |
||||
payload.properties, |
||||
) |
||||
|
||||
events.push( |
||||
this._track( |
||||
this._buildEventPayload({ |
||||
...payload, |
||||
properties: combinedProperties, |
||||
}), |
||||
{ ...options, excludeMetaMetricsId: true }, |
||||
), |
||||
) |
||||
} |
||||
|
||||
events.push(this._track(this._buildEventPayload(payload), options)) |
||||
|
||||
await Promise.all(events) |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
import setupSentry from './lib/setupSentry' |
||||
|
||||
// setup sentry error reporting
|
||||
global.sentry = setupSentry({ |
||||
release: process.env.METAMASK_VERSION, |
||||
getState: () => global.getSentryState?.() || {}, |
||||
}) |
@ -1,37 +0,0 @@ |
||||
/** |
||||
* Freezes the Promise global and prevents its reassignment. |
||||
*/ |
||||
import deepFreeze from 'deep-freeze-strict' |
||||
|
||||
if (process.env.IN_TEST !== 'true' && process.env.METAMASK_ENV !== 'test') { |
||||
freeze(global, 'Promise') |
||||
} |
||||
|
||||
/** |
||||
* Makes a key:value pair on a target object immutable, with limitations. |
||||
* The key cannot be reassigned or deleted, and the value is recursively frozen |
||||
* using Object.freeze. |
||||
* |
||||
* Because of JavaScript language limitations, this is does not mean that the |
||||
* value is completely immutable. It is, however, better than nothing. |
||||
* |
||||
* @param {Object} target - The target object to freeze a property on. |
||||
* @param {string} key - The key to freeze. |
||||
* @param {any} [value] - The value to freeze, if different from the existing value on the target. |
||||
* @param {boolean} [enumerable=true] - If given a value, whether the property is enumerable. |
||||
*/ |
||||
function freeze(target, key, value, enumerable = true) { |
||||
const opts = { |
||||
configurable: false, |
||||
writable: false, |
||||
} |
||||
|
||||
if (value === undefined) { |
||||
target[key] = deepFreeze(target[key]) |
||||
} else { |
||||
opts.value = deepFreeze(value) |
||||
opts.enumerable = enumerable |
||||
} |
||||
|
||||
Object.defineProperty(target, key, opts) |
||||
} |
@ -1,4 +1,5 @@ |
||||
import logWeb3Usage from './log-web3-usage' |
||||
import watchAsset from './watch-asset' |
||||
|
||||
const handlers = [logWeb3Usage] |
||||
const handlers = [logWeb3Usage, watchAsset] |
||||
export default handlers |
||||
|
@ -0,0 +1,40 @@ |
||||
import { MESSAGE_TYPE } from '../../enums' |
||||
|
||||
const watchAsset = { |
||||
methodNames: [MESSAGE_TYPE.WATCH_ASSET, MESSAGE_TYPE.WATCH_ASSET_LEGACY], |
||||
implementation: watchAssetHandler, |
||||
} |
||||
export default watchAsset |
||||
|
||||
/** |
||||
* @typedef {Object} WatchAssetOptions |
||||
* @property {Function} handleWatchAssetRequest - The wallet_watchAsset method implementation. |
||||
*/ |
||||
|
||||
/** |
||||
* @typedef {Object} WatchAssetParam |
||||
* @property {string} type - The type of the asset to watch. |
||||
* @property {Object} options - Watch options for the asset. |
||||
*/ |
||||
|
||||
/** |
||||
* @param {import('json-rpc-engine').JsonRpcRequest<WatchAssetParam>} req - The JSON-RPC request object. |
||||
* @param {import('json-rpc-engine').JsonRpcResponse<true>} res - The JSON-RPC response object. |
||||
* @param {Function} _next - The json-rpc-engine 'next' callback. |
||||
* @param {Function} end - The json-rpc-engine 'end' callback. |
||||
* @param {WatchAssetOptions} options |
||||
*/ |
||||
async function watchAssetHandler( |
||||
req, |
||||
res, |
||||
_next, |
||||
end, |
||||
{ handleWatchAssetRequest }, |
||||
) { |
||||
try { |
||||
res.result = await handleWatchAssetRequest(req) |
||||
return end() |
||||
} catch (error) { |
||||
return end(error) |
||||
} |
||||
} |
@ -0,0 +1,101 @@ |
||||
import Analytics from 'analytics-node' |
||||
|
||||
const isDevOrTestEnvironment = Boolean( |
||||
process.env.METAMASK_DEBUG || process.env.IN_TEST, |
||||
) |
||||
const SEGMENT_WRITE_KEY = process.env.SEGMENT_WRITE_KEY ?? null |
||||
const SEGMENT_LEGACY_WRITE_KEY = process.env.SEGMENT_LEGACY_WRITE_KEY ?? null |
||||
const SEGMENT_HOST = process.env.SEGMENT_HOST ?? null |
||||
|
||||
// flushAt controls how many events are sent to segment at once. Segment will
|
||||
// hold onto a queue of events until it hits this number, then it sends them as
|
||||
// a batch. This setting defaults to 20, but in development we likely want to
|
||||
// see events in real time for debugging, so this is set to 1 to disable the
|
||||
// queueing mechanism.
|
||||
const SEGMENT_FLUSH_AT = |
||||
process.env.METAMASK_ENVIRONMENT === 'production' ? undefined : 1 |
||||
|
||||
// flushInterval controls how frequently the queue is flushed to segment.
|
||||
// This happens regardless of the size of the queue. The default setting is
|
||||
// 10,000ms (10 seconds). This default is rather high, though thankfully
|
||||
// using the background process as our event handler means we don't have to
|
||||
// deal with short lived sessions that happen faster than the interval
|
||||
// e.g confirmations. This is set to 5,000ms (5 seconds) arbitrarily with the
|
||||
// intent of having a value less than 10 seconds.
|
||||
const SEGMENT_FLUSH_INTERVAL = 5000 |
||||
|
||||
/** |
||||
* Creates a mock segment module for usage in test environments. This is used |
||||
* when building the application in test mode to catch event calls and prevent |
||||
* them from being sent to segment. It is also used in unit tests to mock and |
||||
* spy on the methods to ensure proper behavior |
||||
* @param {number} flushAt - number of events to queue before sending to segment |
||||
* @param {number} flushInterval - ms interval to flush queue and send to segment |
||||
* @returns {SegmentInterface} |
||||
*/ |
||||
export const createSegmentMock = ( |
||||
flushAt = SEGMENT_FLUSH_AT, |
||||
flushInterval = SEGMENT_FLUSH_INTERVAL, |
||||
) => { |
||||
const segmentMock = { |
||||
// Internal queue to keep track of events and properly mimic segment's
|
||||
// queueing behavior.
|
||||
queue: [], |
||||
|
||||
/** |
||||
* Used to immediately send all queued events and reset the queue to zero. |
||||
* For our purposes this simply triggers the callback method registered with |
||||
* the event. |
||||
*/ |
||||
flush() { |
||||
segmentMock.queue.forEach(([_, callback]) => { |
||||
callback() |
||||
}) |
||||
segmentMock.queue = [] |
||||
}, |
||||
|
||||
/** |
||||
* Track an event and add it to the queue. If the queue size reaches the |
||||
* flushAt threshold, flush the queue. |
||||
*/ |
||||
track(payload, callback = () => undefined) { |
||||
segmentMock.queue.push([payload, callback]) |
||||
|
||||
if (segmentMock.queue.length >= flushAt) { |
||||
segmentMock.flush() |
||||
} |
||||
}, |
||||
|
||||
/** |
||||
* A true NOOP, these methods are either not used or do not await callback |
||||
* and therefore require no functionality. |
||||
*/ |
||||
page() { |
||||
// noop
|
||||
}, |
||||
identify() { |
||||
// noop
|
||||
}, |
||||
} |
||||
// Mimic the flushInterval behavior with an interval
|
||||
setInterval(segmentMock.flush, flushInterval) |
||||
return segmentMock |
||||
} |
||||
|
||||
export const segment = |
||||
!SEGMENT_WRITE_KEY || (isDevOrTestEnvironment && !SEGMENT_HOST) |
||||
? createSegmentMock(SEGMENT_FLUSH_AT, SEGMENT_FLUSH_INTERVAL) |
||||
: new Analytics(SEGMENT_WRITE_KEY, { |
||||
host: SEGMENT_HOST, |
||||
flushAt: SEGMENT_FLUSH_AT, |
||||
flushInterval: SEGMENT_FLUSH_INTERVAL, |
||||
}) |
||||
|
||||
export const segmentLegacy = |
||||
!SEGMENT_LEGACY_WRITE_KEY || (isDevOrTestEnvironment && !SEGMENT_HOST) |
||||
? createSegmentMock(SEGMENT_FLUSH_AT, SEGMENT_FLUSH_INTERVAL) |
||||
: new Analytics(SEGMENT_LEGACY_WRITE_KEY, { |
||||
host: SEGMENT_HOST, |
||||
flushAt: SEGMENT_FLUSH_AT, |
||||
flushInterval: SEGMENT_FLUSH_INTERVAL, |
||||
}) |
@ -0,0 +1,44 @@ |
||||
import { cloneDeep } from 'lodash' |
||||
|
||||
const version = 49 |
||||
|
||||
/** |
||||
* Migrate metaMetrics state to the new MetaMetrics controller |
||||
*/ |
||||
export default { |
||||
version, |
||||
async migrate(originalVersionedData) { |
||||
const versionedData = cloneDeep(originalVersionedData) |
||||
versionedData.meta.version = version |
||||
const state = versionedData.data |
||||
versionedData.data = transformState(state) |
||||
return versionedData |
||||
}, |
||||
} |
||||
|
||||
function transformState(state = {}) { |
||||
if (state.PreferencesController) { |
||||
const { |
||||
metaMetricsId, |
||||
participateInMetaMetrics, |
||||
metaMetricsSendCount, |
||||
} = state.PreferencesController |
||||
state.MetaMetricsController = state.MetaMetricsController ?? {} |
||||
|
||||
if (metaMetricsId !== undefined) { |
||||
state.MetaMetricsController.metaMetricsId = metaMetricsId |
||||
delete state.PreferencesController.metaMetricsId |
||||
} |
||||
|
||||
if (participateInMetaMetrics !== undefined) { |
||||
state.MetaMetricsController.participateInMetaMetrics = participateInMetaMetrics |
||||
delete state.PreferencesController.participateInMetaMetrics |
||||
} |
||||
|
||||
if (metaMetricsSendCount !== undefined) { |
||||
state.MetaMetricsController.metaMetricsSendCount = metaMetricsSendCount |
||||
delete state.PreferencesController.metaMetricsSendCount |
||||
} |
||||
} |
||||
return state |
||||
} |
@ -0,0 +1,32 @@ |
||||
import { cloneDeep } from 'lodash' |
||||
|
||||
const version = 50 |
||||
|
||||
const LEGACY_LOCAL_STORAGE_KEYS = [ |
||||
'METASWAP_GAS_PRICE_ESTIMATES_LAST_RETRIEVED', |
||||
'METASWAP_GAS_PRICE_ESTIMATES', |
||||
'cachedFetch', |
||||
'BASIC_PRICE_ESTIMATES_LAST_RETRIEVED', |
||||
'BASIC_PRICE_ESTIMATES', |
||||
'BASIC_GAS_AND_TIME_API_ESTIMATES', |
||||
'BASIC_GAS_AND_TIME_API_ESTIMATES_LAST_RETRIEVED', |
||||
'GAS_API_ESTIMATES_LAST_RETRIEVED', |
||||
'GAS_API_ESTIMATES', |
||||
] |
||||
|
||||
/** |
||||
* Migrate metaMetrics state to the new MetaMetrics controller |
||||
*/ |
||||
export default { |
||||
version, |
||||
async migrate(originalVersionedData) { |
||||
const versionedData = cloneDeep(originalVersionedData) |
||||
versionedData.meta.version = version |
||||
|
||||
LEGACY_LOCAL_STORAGE_KEYS.forEach((key) => |
||||
window.localStorage.removeItem(key), |
||||
) |
||||
|
||||
return versionedData |
||||
}, |
||||
} |
@ -0,0 +1,7 @@ |
||||
// Freezes all intrinsics
|
||||
// eslint-disable-next-line no-undef,import/unambiguous
|
||||
lockdown({ |
||||
errorTaming: 'unsafe', |
||||
mathTaming: 'unsafe', |
||||
dateTaming: 'unsafe', |
||||
}) |
@ -0,0 +1,140 @@ |
||||
// Type Imports
|
||||
/** |
||||
* @typedef {import('../../app/scripts/lib/enums').EnvironmentType} EnvironmentType |
||||
*/ |
||||
|
||||
// Type Declarations
|
||||
/** |
||||
* Used to attach context of where the user was at in the application when the |
||||
* event was triggered. Also included as full details of the current page in |
||||
* page events. |
||||
* @typedef {Object} MetaMetricsPageObject |
||||
* @property {string} [path] - the path of the current page (e.g /home) |
||||
* @property {string} [title] - the title of the current page (e.g 'home') |
||||
* @property {string} [url] - the fully qualified url of the current page |
||||
*/ |
||||
|
||||
/** |
||||
* For metamask, this is the dapp that triggered an interaction |
||||
* @typedef {Object} MetaMetricsReferrerObject |
||||
* @property {string} [url] - the origin of the dapp issuing the |
||||
* notification |
||||
*/ |
||||
|
||||
/** |
||||
* We attach context to every meta metrics event that help to qualify our |
||||
* analytics. This type has all optional values because it represents a |
||||
* returned object from a method call. Ideally app and userAgent are |
||||
* defined on every event. This is confirmed in the getTrackMetaMetricsEvent |
||||
* function, but still provides the consumer a way to override these values if |
||||
* necessary. |
||||
* @typedef {Object} MetaMetricsContext |
||||
* @property {Object} app |
||||
* @property {string} app.name - the name of the application tracking the event |
||||
* @property {string} app.version - the version of the application |
||||
* @property {string} userAgent - the useragent string of the user |
||||
* @property {MetaMetricsPageObject} [page] - an object representing details of |
||||
* the current page |
||||
* @property {MetaMetricsReferrerObject} [referrer] - for metamask, this is the |
||||
* dapp that triggered an interaction |
||||
*/ |
||||
|
||||
/** |
||||
* @typedef {Object} MetaMetricsEventPayload |
||||
* @property {string} event - event name to track |
||||
* @property {string} category - category to associate event to |
||||
* @property {string} [environmentType] - The type of environment this event |
||||
* occurred in. Defaults to the background process type |
||||
* @property {object} [properties] - object of custom values to track, keys |
||||
* in this object must be in snake_case |
||||
* @property {object} [sensitiveProperties] - Object of sensitive values to |
||||
* track. Keys in this object must be in snake_case. These properties will be |
||||
* sent in an additional event that excludes the user's metaMetricsId |
||||
* @property {number} [revenue] - amount of currency that event creates in |
||||
* revenue for MetaMask |
||||
* @property {string} [currency] - ISO 4127 format currency for events with |
||||
* revenue, defaults to US dollars |
||||
* @property {number} [value] - Abstract business "value" attributable to |
||||
* customers who trigger this event |
||||
* @property {MetaMetricsPageObject} [page] - the page/route that the event |
||||
* occurred on |
||||
* @property {MetaMetricsReferrerObject} [referrer] - the origin of the dapp |
||||
* that triggered the event |
||||
*/ |
||||
|
||||
/** |
||||
* @typedef {Object} MetaMetricsEventOptions |
||||
* @property {boolean} [isOptIn] - happened during opt in/out workflow |
||||
* @property {boolean} [flushImmediately] - When true will automatically flush |
||||
* the segment queue after tracking the event. Recommended if the result of |
||||
* tracking the event must be known before UI transition or update |
||||
* @property {boolean} [excludeMetaMetricsId] - whether to exclude the user's |
||||
* metametrics id for anonymity |
||||
* @property {string} [metaMetricsId] - an override for the metaMetricsId in |
||||
* the event one is created as part of an asynchronous workflow, such as |
||||
* awaiting the result of the metametrics opt-in function that generates the |
||||
* user's metametrics id |
||||
* @property {boolean} [matomoEvent] - is this event a holdover from matomo |
||||
* that needs further migration? when true, sends the data to a special |
||||
* segment source that marks the event data as not conforming to our schema |
||||
*/ |
||||
|
||||
/** |
||||
* Represents the shape of data sent to the segment.track method. |
||||
* @typedef {Object} SegmentEventPayload |
||||
* @property {string} [userId] - The metametrics id for the user |
||||
* @property {string} [anonymousId] - An anonymousId that is used to track |
||||
* sensitive data while preserving anonymity. |
||||
* @property {string} event - name of the event to track |
||||
* @property {Object} properties - properties to attach to the event |
||||
* @property {MetaMetricsContext} context - the context the event occurred in |
||||
*/ |
||||
|
||||
/** |
||||
* @typedef {Object} MetaMetricsPagePayload |
||||
* @property {string} name - The name of the page that was viewed |
||||
* @property {Object} [params] - The variadic parts of the page url |
||||
* example (route: `/asset/:asset`, path: `/asset/ETH`) |
||||
* params: { asset: 'ETH' } |
||||
* @property {EnvironmentType} environmentType - the environment type that the |
||||
* page was viewed in |
||||
* @property {MetaMetricsPageObject} [page] - the details of the page |
||||
* @property {MetaMetricsReferrerObject} [referrer] - dapp that triggered the page |
||||
* view |
||||
*/ |
||||
|
||||
/** |
||||
* @typedef {Object} MetaMetricsPageOptions |
||||
* @property {boolean} [isOptInPath] - is the current path one of the pages in |
||||
* the onboarding workflow? If true and participateInMetaMetrics is null track |
||||
* the page view |
||||
*/ |
||||
|
||||
export const METAMETRICS_ANONYMOUS_ID = '0x0000000000000000' |
||||
|
||||
/** |
||||
* This object is used to identify events that are triggered by the background |
||||
* process. |
||||
* @type {MetaMetricsPageObject} |
||||
*/ |
||||
export const METAMETRICS_BACKGROUND_PAGE_OBJECT = { |
||||
path: '/background-process', |
||||
title: 'Background Process', |
||||
url: '/background-process', |
||||
} |
||||
|
||||
/** |
||||
* @typedef {Object} SegmentInterface |
||||
* @property {SegmentEventPayload[]} queue - A queue of events to be sent when |
||||
* the flushAt limit has been reached, or flushInterval occurs |
||||
* @property {() => void} flush - Immediately flush the queue, resetting it to |
||||
* an empty array and sending the pending events to Segment |
||||
* @property {( |
||||
* payload: SegmentEventPayload, |
||||
* callback: (err?: Error) => void |
||||
* ) => void} track - Track an event with Segment, using the internal batching |
||||
* mechanism to optimize network requests |
||||
* @property {(payload: Object) => void} page - Track a page view with Segment |
||||
* @property {() => void} identify - Identify an anonymous user. We do not |
||||
* currently use this method. |
||||
*/ |
@ -0,0 +1,3 @@ |
||||
### Shared Modules |
||||
|
||||
This folder is reserved for modules that can be used globally within both the background and ui applications. |
@ -1,302 +0,0 @@ |
||||
import Analytics from 'analytics-node' |
||||
import { merge, omit, pick } from 'lodash' |
||||
|
||||
// flushAt controls how many events are sent to segment at once. Segment
|
||||
// will hold onto a queue of events until it hits this number, then it sends
|
||||
// them as a batch. This setting defaults to 20, but that is too high for
|
||||
// notification workflows. We also cannot send each event as singular payloads
|
||||
// because it seems to bombard segment and potentially cause event loss.
|
||||
// I chose 5 here because it is sufficiently high enough to optimize our network
|
||||
// requests, while also being low enough to be reasonable.
|
||||
const flushAt = process.env.METAMASK_ENVIRONMENT === 'production' ? 5 : 1 |
||||
// flushInterval controls how frequently the queue is flushed to segment.
|
||||
// This happens regardless of the size of the queue. The default setting is
|
||||
// 10,000ms (10 seconds). This default is absurdly high for our typical user
|
||||
// flow through confirmations. I have chosen 10 ms here because it works really
|
||||
// well with our wrapped track function. The track function returns a promise
|
||||
// that is only fulfilled when it has been sent to segment. A 10 ms delay is
|
||||
// negligible to the user, but allows us to properly batch events that happen
|
||||
// in rapid succession.
|
||||
const flushInterval = 10 |
||||
|
||||
export const METAMETRICS_ANONYMOUS_ID = '0x0000000000000000' |
||||
|
||||
const segmentNoop = { |
||||
track(_, callback = () => undefined) { |
||||
// Need to call the callback so that environments without a segment id still
|
||||
// resolve the promise from trackMetaMetricsEvent
|
||||
return callback() |
||||
}, |
||||
page() { |
||||
// noop
|
||||
}, |
||||
identify() { |
||||
// noop
|
||||
}, |
||||
} |
||||
|
||||
/** |
||||
* Used to determine whether or not to attach a user's metametrics id |
||||
* to events that include on-chain data. This helps to prevent identifying |
||||
* a user by being able to trace their activity on etherscan/block exploring |
||||
*/ |
||||
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]) |
||||
} |
||||
|
||||
const isDevOrTestEnvironment = Boolean( |
||||
process.env.METAMASK_DEBUG || process.env.IN_TEST, |
||||
) |
||||
|
||||
// This allows us to overwrite the metric destination for testing purposes
|
||||
const host = process.env.SEGMENT_HOST ?? undefined |
||||
|
||||
// We do not want to track events on development builds unless specifically
|
||||
// provided a SEGMENT_WRITE_KEY. This also holds true for test environments and
|
||||
// E2E, which is handled in the build process by never providing the SEGMENT_WRITE_KEY
|
||||
// when process.env.IN_TEST is truthy
|
||||
export const segment = |
||||
!process.env.SEGMENT_WRITE_KEY || (isDevOrTestEnvironment && !host) |
||||
? segmentNoop |
||||
: new Analytics(process.env.SEGMENT_WRITE_KEY, { |
||||
host, |
||||
flushAt, |
||||
flushInterval, |
||||
}) |
||||
|
||||
export const segmentLegacy = |
||||
!process.env.SEGMENT_LEGACY_WRITE_KEY || (isDevOrTestEnvironment && !host) |
||||
? segmentNoop |
||||
: new Analytics(process.env.SEGMENT_LEGACY_WRITE_KEY, { |
||||
host, |
||||
flushAt, |
||||
flushInterval, |
||||
}) |
||||
|
||||
/** |
||||
* We attach context to every meta metrics event that help to qualify our analytics. |
||||
* This type has all optional values because it represents a returned object from a |
||||
* method call. Ideally app and userAgent are defined on every event. This is confirmed |
||||
* in the getTrackMetaMetricsEvent function, but still provides the consumer a way to |
||||
* override these values if necessary. |
||||
* @typedef {Object} MetaMetricsContext |
||||
* @property {Object} app |
||||
* @property {string} app.name - the name of the application tracking the event |
||||
* @property {string} app.version - the version of the application |
||||
* @property {string} userAgent - the useragent string of the user |
||||
* @property {Object} [page] - an object representing details of the current page |
||||
* @property {string} [page.path] - the path of the current page (e.g /home) |
||||
* @property {string} [page.title] - the title of the current page (e.g 'home') |
||||
* @property {string} [page.url] - the fully qualified url of the current page |
||||
* @property {Object} [referrer] - for metamask, this is the dapp that triggered an interaction |
||||
* @property {string} [referrer.url] - the origin of the dapp issuing the notification |
||||
*/ |
||||
|
||||
/** |
||||
* page and referrer from the MetaMetricsContext are very dynamic in nature and may be |
||||
* provided as part of the initial context payload when creating the trackMetaMetricsEvent function, |
||||
* or at the event level when calling the trackMetaMetricsEvent function. |
||||
* @typedef {Pick<MetaMetricsContext, 'page' | 'referrer'>} MetaMetricsDynamicContext |
||||
*/ |
||||
|
||||
/** |
||||
* @typedef {import('../../app/scripts/lib/enums').EnvironmentType} EnvironmentType |
||||
*/ |
||||
|
||||
/** |
||||
* @typedef {Object} MetaMetricsRequiredState |
||||
* @property {bool} participateInMetaMetrics - has the user opted into metametrics |
||||
* @property {string} [metaMetricsId] - the user's metaMetricsId, if they have opted in |
||||
* @property {MetaMetricsDynamicContext} context - context about the event |
||||
* @property {string} chainId - the chain id of the current network |
||||
* @property {string} locale - the locale string of the current user |
||||
* @property {string} network - the name of the current network |
||||
* @property {EnvironmentType} environmentType - environment that the event happened in |
||||
* @property {string} [metaMetricsSendCount] - number of transactions sent, used to add metametricsId |
||||
* intermittently to events with onchain data attached to them used to protect identity of users. |
||||
*/ |
||||
|
||||
/** |
||||
* @typedef {Object} MetaMetricsEventPayload |
||||
* @property {string} event - event name to track |
||||
* @property {string} category - category to associate event to |
||||
* @property {boolean} [isOptIn] - happened during opt in/out workflow |
||||
* @property {object} [properties] - object of custom values to track, snake_case |
||||
* @property {object} [sensitiveProperties] - Object of sensitive values to track, snake_case. |
||||
* These properties will be sent in an additional event that excludes the user's metaMetricsId. |
||||
* @property {number} [revenue] - amount of currency that event creates in revenue for MetaMask |
||||
* @property {string} [currency] - ISO 4127 format currency for events with revenue, defaults to US dollars |
||||
* @property {number} [value] - Abstract "value" that this event has for MetaMask. |
||||
* @property {boolean} [excludeMetaMetricsId] - whether to exclude the user's metametrics id for anonymity |
||||
* @property {string} [metaMetricsId] - an override for the metaMetricsId in the event one is created as part |
||||
* of an asynchronous workflow, such as awaiting the result of the metametrics opt-in function that generates the |
||||
* user's metametrics id. |
||||
* @property {boolean} [matomoEvent] - is this event a holdover from matomo that needs further migration? |
||||
* when true, sends the data to a special segment source that marks the event data as not conforming to our |
||||
* ideal schema |
||||
* @property {MetaMetricsDynamicContext} [eventContext] - additional context to attach to event |
||||
*/ |
||||
|
||||
/** |
||||
* Returns a function for tracking Segment events. |
||||
* |
||||
* @param {string} metamaskVersion - The current version of the MetaMask extension. |
||||
* @param {() => MetaMetricsRequiredState} getDynamicState - A function returning required fields |
||||
* @returns {(payload: MetaMetricsEventPayload) => Promise<void>} function to track an event |
||||
*/ |
||||
export function getTrackMetaMetricsEvent(metamaskVersion, getDynamicState) { |
||||
const version = |
||||
process.env.METAMASK_ENVIRONMENT === 'production' |
||||
? metamaskVersion |
||||
: `${metamaskVersion}-${process.env.METAMASK_ENVIRONMENT}` |
||||
|
||||
return function trackMetaMetricsEvent({ |
||||
event, |
||||
category, |
||||
isOptIn, |
||||
properties = {}, |
||||
sensitiveProperties, |
||||
revenue, |
||||
currency, |
||||
value, |
||||
metaMetricsId: metaMetricsIdOverride, |
||||
excludeMetaMetricsId: excludeId, |
||||
matomoEvent = false, |
||||
eventContext = {}, |
||||
}) { |
||||
if (!event || !category) { |
||||
throw new Error('Must specify event and category.') |
||||
} |
||||
// Uses recursion to track a duplicate event with sensitive properties included,
|
||||
// but metaMetricsId excluded
|
||||
if (sensitiveProperties) { |
||||
if (excludeId === true) { |
||||
throw new Error( |
||||
'sensitiveProperties was specified in an event payload that also set the excludeMetaMetricsId flag', |
||||
) |
||||
} |
||||
trackMetaMetricsEvent({ |
||||
event, |
||||
category, |
||||
isOptIn, |
||||
properties: merge(sensitiveProperties, properties), |
||||
revenue, |
||||
currency, |
||||
value, |
||||
excludeMetaMetricsId: true, |
||||
matomoEvent, |
||||
eventContext, |
||||
}) |
||||
} |
||||
const { |
||||
participateInMetaMetrics, |
||||
context: providedContext, |
||||
metaMetricsId, |
||||
environmentType, |
||||
chainId, |
||||
locale, |
||||
network, |
||||
metaMetricsSendCount, |
||||
} = getDynamicState() |
||||
|
||||
let excludeMetaMetricsId = excludeId ?? false |
||||
|
||||
// This is carried over from the old implementation, and will likely need
|
||||
// to be updated to work with the new tracking plan. I think we should use
|
||||
// a config setting for this instead of trying to match the event name
|
||||
const isSendFlow = Boolean(event.match(/^send|^confirm/u)) |
||||
if ( |
||||
isSendFlow && |
||||
metaMetricsSendCount && |
||||
!sendCountIsTrackable(metaMetricsSendCount + 1) |
||||
) { |
||||
excludeMetaMetricsId = true |
||||
} |
||||
|
||||
if (!participateInMetaMetrics && !isOptIn) { |
||||
return Promise.resolve() |
||||
} |
||||
|
||||
/** @type {MetaMetricsContext} */ |
||||
const context = { |
||||
app: { |
||||
name: 'MetaMask Extension', |
||||
version, |
||||
}, |
||||
userAgent: window.navigator.userAgent, |
||||
...pick(providedContext, ['page', 'referrer']), |
||||
...pick(eventContext, ['page', 'referrer']), |
||||
} |
||||
|
||||
const trackOptions = { |
||||
event, |
||||
properties: { |
||||
// These values are omitted from properties because they have special meaning
|
||||
// in segment. https://segment.com/docs/connections/spec/track/#properties.
|
||||
// to avoid accidentally using these inappropriately, you must add them as top
|
||||
// level properties on the event payload. We also exclude locale to prevent consumers
|
||||
// from overwriting this context level property. We track it as a property
|
||||
// because not all destinations map locale from context.
|
||||
...omit(properties, ['revenue', 'locale', 'currency', 'value']), |
||||
revenue, |
||||
value, |
||||
currency, |
||||
category, |
||||
network, |
||||
locale, |
||||
chain_id: chainId, |
||||
environment_type: environmentType, |
||||
}, |
||||
context, |
||||
} |
||||
|
||||
// If we are tracking sensitive data we will always use the anonymousId property
|
||||
// as well as our METAMETRICS_ANONYMOUS_ID. This prevents us from associating potentially
|
||||
// identifiable information with a specific id. During the opt in flow we will track all
|
||||
// events, but do so with the anonymous id. The one exception to that rule is after the
|
||||
// user opts in to MetaMetrics. When that happens we receive back the user's new MetaMetrics
|
||||
// id before it is fully persisted to state. To avoid a race condition we explicitly pass the
|
||||
// new id to the track method. In that case we will track the opt in event to the user's id.
|
||||
// In all other cases we use the metaMetricsId from state.
|
||||
if (excludeMetaMetricsId) { |
||||
trackOptions.anonymousId = METAMETRICS_ANONYMOUS_ID |
||||
} else if (isOptIn && metaMetricsIdOverride) { |
||||
trackOptions.userId = metaMetricsIdOverride |
||||
} else if (isOptIn) { |
||||
trackOptions.anonymousId = METAMETRICS_ANONYMOUS_ID |
||||
} else { |
||||
trackOptions.userId = metaMetricsId |
||||
} |
||||
|
||||
return new Promise((resolve, reject) => { |
||||
// This is only safe to do because we have set an extremely low (10ms) flushInterval.
|
||||
const callback = (err) => { |
||||
if (err) { |
||||
return reject(err) |
||||
} |
||||
return resolve() |
||||
} |
||||
|
||||
if (matomoEvent === true) { |
||||
segmentLegacy.track(trackOptions, callback) |
||||
} else { |
||||
segment.track(trackOptions, callback) |
||||
} |
||||
}) |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,168 @@ |
||||
{ |
||||
"data": { |
||||
"AppStateController": { |
||||
"connectedStatusPopoverHasBeenShown": false, |
||||
"swapsWelcomeMessageHasBeenShown": true |
||||
}, |
||||
"CachedBalancesController": { |
||||
"cachedBalances": { |
||||
"4": {}, |
||||
"1337": { |
||||
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": "0x15af1d78b58c40000" |
||||
} |
||||
} |
||||
}, |
||||
"CurrencyController": { |
||||
"conversionDate": 1594348502.519, |
||||
"conversionRate": 240.09, |
||||
"currentCurrency": "usd", |
||||
"nativeCurrency": "ETH" |
||||
}, |
||||
"IncomingTransactionsController": { |
||||
"incomingTransactions": {}, |
||||
"incomingTxLastFetchedBlocksByNetwork": { |
||||
"goerli": null, |
||||
"kovan": null, |
||||
"mainnet": null, |
||||
"rinkeby": 5570536, |
||||
"localhost": 98 |
||||
} |
||||
}, |
||||
"KeyringController": { |
||||
"vault": "{\"data\":\"s6TpYjlUNsn7ifhEFTkuDGBUM1GyOlPrim7JSjtfIxgTt8/6MiXgiR/CtFfR4dWW2xhq85/NGIBYEeWrZThGdKGarBzeIqBfLFhw9n509jprzJ0zc2Rf+9HVFGLw+xxC4xPxgCS0IIWeAJQ+XtGcHmn0UZXriXm8Ja4kdlow6SWinB7sr/WM3R0+frYs4WgllkwggDf2/Tv6VHygvLnhtzp6hIJFyTjh+l/KnyJTyZW1TkZhDaNDzX3SCOHT\",\"iv\":\"FbeHDAW5afeWNORfNJBR0Q==\",\"salt\":\"TxZ+WbCW6891C9LK/hbMAoUsSEW1E8pyGLVBU6x5KR8=\"}" |
||||
}, |
||||
"NetworkController": { |
||||
"provider": { |
||||
"nickname": "Localhost 8545", |
||||
"rpcUrl": "http://localhost:8545", |
||||
"chainId": "0x539", |
||||
"ticker": "ETH", |
||||
"type": "rpc" |
||||
}, |
||||
"network": "1337" |
||||
}, |
||||
"OnboardingController": { |
||||
"onboardingTabs": {}, |
||||
"seedPhraseBackedUp": false |
||||
}, |
||||
"PermissionsMetadata": { |
||||
"permissionsLog": [ |
||||
{ |
||||
"id": 1764280960, |
||||
"method": "eth_requestAccounts", |
||||
"methodType": "restricted", |
||||
"origin": "http://127.0.0.1:8080", |
||||
"request": { |
||||
"method": "eth_requestAccounts", |
||||
"jsonrpc": "2.0", |
||||
"id": 1764280960, |
||||
"origin": "http://127.0.0.1:8080", |
||||
"tabId": 2 |
||||
}, |
||||
"requestTime": 1594348329232, |
||||
"response": { |
||||
"id": 1764280960, |
||||
"jsonrpc": "2.0", |
||||
"result": ["0x5cfe73b6021e818b776b421b1c4db2474086a7e1"] |
||||
}, |
||||
"responseTime": 1594348332276, |
||||
"success": true |
||||
} |
||||
], |
||||
"permissionsHistory": { |
||||
"http://127.0.0.1:8080": { |
||||
"eth_accounts": { |
||||
"accounts": { |
||||
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": 1594348332276 |
||||
}, |
||||
"lastApproved": 1594348332276 |
||||
} |
||||
} |
||||
}, |
||||
"domainMetadata": { |
||||
"http://127.0.0.1:8080": { |
||||
"name": "E2E Test Dapp", |
||||
"icon": "http://127.0.0.1:8080/metamask-fox.svg", |
||||
"lastUpdated": 1594348323811, |
||||
"host": "127.0.0.1:8080" |
||||
} |
||||
} |
||||
}, |
||||
"PreferencesController": { |
||||
"frequentRpcListDetail": [], |
||||
"accountTokens": { |
||||
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": { |
||||
"rinkeby": [], |
||||
"ropsten": [] |
||||
} |
||||
}, |
||||
"assetImages": {}, |
||||
"tokens": [], |
||||
"suggestedTokens": {}, |
||||
"useBlockie": false, |
||||
"useNonceField": false, |
||||
"usePhishDetect": true, |
||||
"featureFlags": { |
||||
"showIncomingTransactions": true, |
||||
"transactionTime": false |
||||
}, |
||||
"knownMethodData": {}, |
||||
"participateInMetaMetrics": true, |
||||
"firstTimeFlowType": "create", |
||||
"currentLocale": "en", |
||||
"identities": { |
||||
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": { |
||||
"address": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1", |
||||
"name": "Account 1" |
||||
} |
||||
}, |
||||
"lostIdentities": {}, |
||||
"forgottenPassword": false, |
||||
"preferences": { |
||||
"useNativeCurrencyAsPrimaryCurrency": true |
||||
}, |
||||
"completedOnboarding": true, |
||||
"metaMetricsId": "fake-metrics-id", |
||||
"metaMetricsSendCount": 0, |
||||
"ipfsGateway": "dweb.link", |
||||
"selectedAddress": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1" |
||||
}, |
||||
"config": {}, |
||||
"firstTimeInfo": { |
||||
"date": 1575697234195, |
||||
"version": "7.7.0" |
||||
}, |
||||
"PermissionsController": { |
||||
"permissionsRequests": [], |
||||
"permissionsDescriptions": {}, |
||||
"domains": { |
||||
"http://127.0.0.1:8080": { |
||||
"permissions": [ |
||||
{ |
||||
"@context": ["https://github.com/MetaMask/rpc-cap"], |
||||
"parentCapability": "eth_accounts", |
||||
"id": "f55a1c15-ea48-4088-968e-63be474d42fa", |
||||
"date": 1594348332268, |
||||
"invoker": "http://127.0.0.1:8080", |
||||
"caveats": [ |
||||
{ |
||||
"type": "limitResponseLength", |
||||
"value": 1, |
||||
"name": "primaryAccountOnly" |
||||
}, |
||||
{ |
||||
"type": "filterResponse", |
||||
"value": ["0x5cfe73b6021e818b776b421b1c4db2474086a7e1"], |
||||
"name": "exposedAccounts" |
||||
} |
||||
] |
||||
} |
||||
] |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"meta": { |
||||
"version": 47 |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue