mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 01:47:00 +01:00
[RFC] add prettier to eslint (#8595)
This commit is contained in:
parent
55bff07bbf
commit
2ebf8756a4
231
.eslintrc.js
231
.eslintrc.js
@ -2,19 +2,19 @@ module.exports = {
|
||||
root: true,
|
||||
parser: '@babel/eslint-parser',
|
||||
parserOptions: {
|
||||
'sourceType': 'module',
|
||||
'ecmaVersion': 2017,
|
||||
'ecmaFeatures': {
|
||||
'experimentalObjectRestSpread': true,
|
||||
'impliedStrict': true,
|
||||
'modules': true,
|
||||
'blockBindings': true,
|
||||
'arrowFunctions': true,
|
||||
'objectLiteralShorthandMethods': true,
|
||||
'objectLiteralShorthandProperties': true,
|
||||
'templateStrings': true,
|
||||
'classes': true,
|
||||
'jsx': true,
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 2017,
|
||||
ecmaFeatures: {
|
||||
experimentalObjectRestSpread: true,
|
||||
impliedStrict: true,
|
||||
modules: true,
|
||||
blockBindings: true,
|
||||
arrowFunctions: true,
|
||||
objectLiteralShorthandMethods: true,
|
||||
objectLiteralShorthandProperties: true,
|
||||
templateStrings: true,
|
||||
classes: true,
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
|
||||
@ -28,6 +28,9 @@ module.exports = {
|
||||
'coverage/',
|
||||
'app/scripts/chromereload.js',
|
||||
'app/vendor/**',
|
||||
'test/e2e/send-eth-with-private-key-test/**',
|
||||
'nyc_output/**',
|
||||
'.vscode/**',
|
||||
],
|
||||
|
||||
extends: [
|
||||
@ -38,11 +41,7 @@ module.exports = {
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
|
||||
plugins: [
|
||||
'@babel',
|
||||
'react',
|
||||
'import',
|
||||
],
|
||||
plugins: ['@babel', 'react', 'import', 'prettier'],
|
||||
|
||||
globals: {
|
||||
document: 'readonly',
|
||||
@ -50,6 +49,67 @@ module.exports = {
|
||||
},
|
||||
|
||||
rules: {
|
||||
// Prettier changes and reasoning
|
||||
|
||||
'prettier/prettier': 'error',
|
||||
|
||||
// Our usage of spaces before *named* function parens is unusual, and
|
||||
// doesn't match the prettier spec. prettier does not offer an option
|
||||
// to configure this
|
||||
'space-before-function-paren': [
|
||||
'error',
|
||||
{ anonymous: 'always', named: 'never' },
|
||||
],
|
||||
// Our eslint config has the default setting for this as error. This
|
||||
// include beforeBlockComment: true, but in order to match the prettier
|
||||
// spec you have to enable before and after blocks, objects and arrays
|
||||
// https://github.com/prettier/eslint-config-prettier#lines-around-comment
|
||||
'lines-around-comment': [
|
||||
'error',
|
||||
{
|
||||
beforeBlockComment: true,
|
||||
afterLineComment: false,
|
||||
allowBlockStart: true,
|
||||
allowBlockEnd: true,
|
||||
allowObjectStart: true,
|
||||
allowObjectEnd: true,
|
||||
allowArrayStart: true,
|
||||
allowArrayEnd: true,
|
||||
},
|
||||
],
|
||||
// Prettier has some opinions on mixed-operators, and there is ongoing work
|
||||
// to make the output code clear. It is better today then it was when the first
|
||||
// PR to add prettier. That being said, the workaround for keeping this rule enabled
|
||||
// requires breaking parts of operations into different variables -- which I believe
|
||||
// to be worse. https://github.com/prettier/eslint-config-prettier#no-mixed-operators
|
||||
'no-mixed-operators': 'off',
|
||||
// Prettier wraps single line functions with ternaries, etc in parens by default, but
|
||||
// if the line is long enough it breaks it into a separate line and removes the parens.
|
||||
// The second behavior conflicts with this rule. There is some guides on the repo about
|
||||
// how you can keep it enabled:
|
||||
// https://github.com/prettier/eslint-config-prettier#no-confusing-arrow
|
||||
// However, in practice this conflicts with prettier adding parens around short lines,
|
||||
// when autofixing in vscode and others.
|
||||
'no-confusing-arrow': 'off',
|
||||
// There is no configuration in prettier for how it stylizes regexes, which conflicts
|
||||
// with wrap-regex.
|
||||
'wrap-regex': 'off',
|
||||
// Prettier handles all indentation automagically. it can be configured here
|
||||
// https://prettier.io/docs/en/options.html#tab-width but the default matches our
|
||||
// style.
|
||||
indent: 'off',
|
||||
// This rule conflicts with the way that prettier breaks code across multiple lines when
|
||||
// it exceeds the maximum length. Prettier optimizes for readability while simultaneously
|
||||
// maximizing the amount of code per line.
|
||||
'function-paren-newline': 'off',
|
||||
// This rule throws an error when there is a line break in an arrow function declaration
|
||||
// but prettier breaks arrow function declarations to be as readable as possible while
|
||||
// still conforming to the width rules.
|
||||
'implicit-arrow-linebreak': 'off',
|
||||
// This rule would result in an increase in white space in lines with generator functions,
|
||||
// which impacts prettier's goal of maximizing code per line and readability. There is no
|
||||
// current workaround.
|
||||
'generator-star-spacing': 'off',
|
||||
'default-param-last': 'off',
|
||||
'require-atomic-updates': 'off',
|
||||
'import/no-unassigned-import': 'off',
|
||||
@ -57,29 +117,32 @@ module.exports = {
|
||||
'react/no-unused-prop-types': 'error',
|
||||
'react/no-unused-state': 'error',
|
||||
'react/jsx-boolean-value': 'error',
|
||||
'react/jsx-curly-brace-presence': ['error', { 'props': 'never', 'children': 'never' }],
|
||||
'react/jsx-curly-brace-presence': [
|
||||
'error',
|
||||
{ props: 'never', children: 'never' },
|
||||
],
|
||||
'react/jsx-equals-spacing': 'error',
|
||||
'react/no-deprecated': 'error',
|
||||
'react/default-props-match-prop-types': 'error',
|
||||
'react/jsx-closing-tag-location': 'error',
|
||||
'react/jsx-closing-tag-location': [
|
||||
'error',
|
||||
{ selfClosing: 'tag-aligned', nonEmpty: 'tag-aligned' },
|
||||
],
|
||||
'react/jsx-no-duplicate-props': 'error',
|
||||
'react/jsx-closing-bracket-location': 'error',
|
||||
'react/jsx-first-prop-new-line': ['error', 'multiline'],
|
||||
'react/jsx-max-props-per-line': ['error', { 'maximum': 1, 'when': 'multiline' }],
|
||||
'react/jsx-tag-spacing': ['error', {
|
||||
'closingSlash': 'never',
|
||||
'beforeSelfClosing': 'always',
|
||||
'afterOpening': 'never',
|
||||
}],
|
||||
'react/jsx-wrap-multilines': ['error', {
|
||||
'declaration': 'parens-new-line',
|
||||
'assignment': 'parens-new-line',
|
||||
'return': 'parens-new-line',
|
||||
'arrow': 'parens-new-line',
|
||||
'condition': 'parens-new-line',
|
||||
'logical': 'parens-new-line',
|
||||
'prop': 'parens-new-line',
|
||||
}],
|
||||
'react/jsx-max-props-per-line': [
|
||||
'error',
|
||||
{ maximum: 1, when: 'multiline' },
|
||||
],
|
||||
'react/jsx-tag-spacing': [
|
||||
'error',
|
||||
{
|
||||
closingSlash: 'never',
|
||||
beforeSelfClosing: 'always',
|
||||
afterOpening: 'never',
|
||||
},
|
||||
],
|
||||
|
||||
'no-invalid-this': 'off',
|
||||
'@babel/no-invalid-this': 'error',
|
||||
@ -93,67 +156,59 @@ module.exports = {
|
||||
'node/no-unpublished-import': 'off',
|
||||
'node/no-unpublished-require': 'off',
|
||||
},
|
||||
|
||||
overrides: [{
|
||||
files: [
|
||||
'test/e2e/**/*.js',
|
||||
],
|
||||
rules: {
|
||||
'mocha/no-hooks-for-single-case': 'off',
|
||||
overrides: [
|
||||
{
|
||||
files: ['test/e2e/**/*.js'],
|
||||
rules: {
|
||||
'mocha/no-hooks-for-single-case': 'off',
|
||||
},
|
||||
},
|
||||
}, {
|
||||
files: [
|
||||
'app/scripts/migrations/*.js',
|
||||
'*.stories.js',
|
||||
],
|
||||
rules: {
|
||||
'import/no-anonymous-default-export': ['error', { 'allowObject': true }],
|
||||
{
|
||||
files: ['app/scripts/migrations/*.js', '*.stories.js'],
|
||||
rules: {
|
||||
'import/no-anonymous-default-export': ['error', { allowObject: true }],
|
||||
},
|
||||
},
|
||||
}, {
|
||||
files: [
|
||||
'app/scripts/migrations/*.js',
|
||||
],
|
||||
rules: {
|
||||
'node/global-require': 'off',
|
||||
{
|
||||
files: ['app/scripts/migrations/*.js'],
|
||||
rules: {
|
||||
'node/global-require': 'off',
|
||||
},
|
||||
},
|
||||
}, {
|
||||
files: [
|
||||
'test/**/*-test.js',
|
||||
'test/**/*.spec.js',
|
||||
],
|
||||
rules: {
|
||||
// Mocha will re-assign `this` in a test context
|
||||
'@babel/no-invalid-this': 'off',
|
||||
{
|
||||
files: ['test/**/*-test.js', 'test/**/*.spec.js'],
|
||||
rules: {
|
||||
// Mocha will re-assign `this` in a test context
|
||||
'@babel/no-invalid-this': 'off',
|
||||
},
|
||||
},
|
||||
}, {
|
||||
files: [
|
||||
'development/**/*.js',
|
||||
'test/e2e/benchmark.js',
|
||||
'test/helper.js',
|
||||
],
|
||||
rules: {
|
||||
'node/no-process-exit': 'off',
|
||||
'node/shebang': 'off',
|
||||
{
|
||||
files: ['development/**/*.js', 'test/e2e/benchmark.js', 'test/helper.js'],
|
||||
rules: {
|
||||
'node/no-process-exit': 'off',
|
||||
'node/shebang': 'off',
|
||||
},
|
||||
},
|
||||
}, {
|
||||
files: [
|
||||
'.eslintrc.js',
|
||||
'babel.config.js',
|
||||
'nyc.config.js',
|
||||
'stylelint.config.js',
|
||||
'development/**/*.js',
|
||||
'test/e2e/**/*.js',
|
||||
'test/env.js',
|
||||
'test/setup.js',
|
||||
],
|
||||
parserOptions: {
|
||||
sourceType: 'script',
|
||||
{
|
||||
files: [
|
||||
'.eslintrc.js',
|
||||
'babel.config.js',
|
||||
'nyc.config.js',
|
||||
'stylelint.config.js',
|
||||
'development/**/*.js',
|
||||
'test/e2e/**/*.js',
|
||||
'test/env.js',
|
||||
'test/setup.js',
|
||||
],
|
||||
parserOptions: {
|
||||
sourceType: 'script',
|
||||
},
|
||||
},
|
||||
}],
|
||||
],
|
||||
|
||||
settings: {
|
||||
'react': {
|
||||
'version': 'detect',
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -6,3 +6,4 @@ coverage/
|
||||
app/vendor/**
|
||||
.nyc_output/**
|
||||
.vscode/**
|
||||
test/e2e/send-eth-with-private-key-test/**
|
||||
|
3
.prettierrc.yml
Normal file
3
.prettierrc.yml
Normal file
@ -0,0 +1,3 @@
|
||||
singleQuote: true
|
||||
semi: false
|
||||
trailingComma: all
|
@ -4,8 +4,7 @@ import importers from 'ethereumjs-wallet/thirdparty'
|
||||
import ethUtil from 'ethereumjs-util'
|
||||
|
||||
const accountImporter = {
|
||||
|
||||
importAccount (strategy, args) {
|
||||
importAccount(strategy, args) {
|
||||
try {
|
||||
const importer = this.strategies[strategy]
|
||||
const privateKeyHex = importer(...args)
|
||||
@ -43,10 +42,9 @@ const accountImporter = {
|
||||
return walletToPrivateKey(wallet)
|
||||
},
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
function walletToPrivateKey (wallet) {
|
||||
function walletToPrivateKey(wallet) {
|
||||
const privateKeyBuffer = wallet.getPrivateKey()
|
||||
return ethUtil.bufferToHex(privateKeyBuffer)
|
||||
}
|
||||
|
@ -60,9 +60,7 @@ const requestAccountTabIds = {}
|
||||
|
||||
// state persistence
|
||||
const inTest = process.env.IN_TEST === 'true'
|
||||
const localStore = inTest
|
||||
? new ReadOnlyNetworkStore()
|
||||
: new LocalStore()
|
||||
const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore()
|
||||
let versionedData
|
||||
|
||||
if (inTest || process.env.METAMASK_DEBUG) {
|
||||
@ -143,7 +141,7 @@ initialize().catch(log.error)
|
||||
* Initializes the MetaMask controller, and sets up all platform configuration.
|
||||
* @returns {Promise} - Setup complete.
|
||||
*/
|
||||
async function initialize () {
|
||||
async function initialize() {
|
||||
const initState = await loadStateFromPersistence()
|
||||
const initLangCode = await getFirstPreferredLangCode()
|
||||
await setupController(initState, initLangCode)
|
||||
@ -159,15 +157,15 @@ async function initialize () {
|
||||
* Migrates that data schema in case it was last loaded on an older version.
|
||||
* @returns {Promise<MetaMaskState>} - Last data emitted from previous instance of MetaMask.
|
||||
*/
|
||||
async function loadStateFromPersistence () {
|
||||
async function loadStateFromPersistence() {
|
||||
// migrations
|
||||
const migrator = new Migrator({ migrations })
|
||||
migrator.on('error', console.warn)
|
||||
|
||||
// read from disk
|
||||
// first from preferred, async API:
|
||||
versionedData = (await localStore.get()) ||
|
||||
migrator.generateInitialState(firstTimeState)
|
||||
versionedData =
|
||||
(await localStore.get()) || migrator.generateInitialState(firstTimeState)
|
||||
|
||||
// check if somehow state is empty
|
||||
// this should never happen but new error reporting suggests that it has
|
||||
@ -219,7 +217,7 @@ async function loadStateFromPersistence () {
|
||||
* @param {string} initLangCode - The region code for the language preferred by the current user.
|
||||
* @returns {Promise} - After setup is complete.
|
||||
*/
|
||||
function setupController (initState, initLangCode) {
|
||||
function setupController(initState, initLangCode) {
|
||||
//
|
||||
// MetaMask Controller
|
||||
//
|
||||
@ -249,7 +247,9 @@ function setupController (initState, initLangCode) {
|
||||
|
||||
setupEnsIpfsResolver({
|
||||
getCurrentNetwork: controller.getCurrentNetwork,
|
||||
getIpfsGateway: controller.preferencesController.getIpfsGateway.bind(controller.preferencesController),
|
||||
getIpfsGateway: controller.preferencesController.getIpfsGateway.bind(
|
||||
controller.preferencesController,
|
||||
),
|
||||
provider: controller.provider,
|
||||
})
|
||||
|
||||
@ -269,12 +269,12 @@ function setupController (initState, initLangCode) {
|
||||
* @param {Object} state - The state object as emitted by the MetaMaskController.
|
||||
* @returns {VersionedData} - The state object wrapped in an object that includes a metadata key.
|
||||
*/
|
||||
function versionifyData (state) {
|
||||
function versionifyData(state) {
|
||||
versionedData.data = state
|
||||
return versionedData
|
||||
}
|
||||
|
||||
async function persistData (state) {
|
||||
async function persistData(state) {
|
||||
if (!state) {
|
||||
throw new Error('MetaMask - updated state is missing')
|
||||
}
|
||||
@ -303,12 +303,14 @@ function setupController (initState, initLangCode) {
|
||||
[ENVIRONMENT_TYPE_FULLSCREEN]: true,
|
||||
}
|
||||
|
||||
const metamaskBlockedPorts = [
|
||||
'trezor-connect',
|
||||
]
|
||||
const metamaskBlockedPorts = ['trezor-connect']
|
||||
|
||||
const isClientOpenStatus = () => {
|
||||
return popupIsOpen || Boolean(Object.keys(openMetamaskTabsIDs).length) || notificationIsOpen
|
||||
return (
|
||||
popupIsOpen ||
|
||||
Boolean(Object.keys(openMetamaskTabsIDs).length) ||
|
||||
notificationIsOpen
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -323,7 +325,7 @@ function setupController (initState, initLangCode) {
|
||||
* This method identifies trusted (MetaMask) interfaces, and connects them differently from untrusted (web pages).
|
||||
* @param {Port} remotePort - The port provided by a new context.
|
||||
*/
|
||||
function connectRemote (remotePort) {
|
||||
function connectRemote(remotePort) {
|
||||
const processName = remotePort.name
|
||||
const isMetaMaskInternalProcess = metamaskInternalProcessHash[processName]
|
||||
|
||||
@ -381,7 +383,7 @@ function setupController (initState, initLangCode) {
|
||||
}
|
||||
|
||||
// communication with page or other extension
|
||||
function connectExternal (remotePort) {
|
||||
function connectExternal(remotePort) {
|
||||
const portStream = new PortStream(remotePort)
|
||||
controller.setupUntrustedCommunication(portStream, remotePort.sender)
|
||||
}
|
||||
@ -404,18 +406,30 @@ function setupController (initState, initLangCode) {
|
||||
* Updates the Web Extension's "badge" number, on the little fox in the toolbar.
|
||||
* The number reflects the current number of pending transactions or message signatures needing user approval.
|
||||
*/
|
||||
function updateBadge () {
|
||||
function updateBadge() {
|
||||
let label = ''
|
||||
const unapprovedTxCount = controller.txController.getUnapprovedTxCount()
|
||||
const { unapprovedMsgCount } = controller.messageManager
|
||||
const { unapprovedPersonalMsgCount } = controller.personalMessageManager
|
||||
const { unapprovedDecryptMsgCount } = controller.decryptMessageManager
|
||||
const { unapprovedEncryptionPublicKeyMsgCount } = controller.encryptionPublicKeyManager
|
||||
const {
|
||||
unapprovedEncryptionPublicKeyMsgCount,
|
||||
} = controller.encryptionPublicKeyManager
|
||||
const { unapprovedTypedMessagesCount } = controller.typedMessageManager
|
||||
const pendingPermissionRequests = Object.keys(controller.permissionsController.permissions.state.permissionsRequests).length
|
||||
const waitingForUnlockCount = controller.appStateController.waitingForUnlock.length
|
||||
const count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgCount + unapprovedDecryptMsgCount + unapprovedEncryptionPublicKeyMsgCount +
|
||||
unapprovedTypedMessagesCount + pendingPermissionRequests + waitingForUnlockCount
|
||||
const pendingPermissionRequests = Object.keys(
|
||||
controller.permissionsController.permissions.state.permissionsRequests,
|
||||
).length
|
||||
const waitingForUnlockCount =
|
||||
controller.appStateController.waitingForUnlock.length
|
||||
const count =
|
||||
unapprovedTxCount +
|
||||
unapprovedMsgCount +
|
||||
unapprovedPersonalMsgCount +
|
||||
unapprovedDecryptMsgCount +
|
||||
unapprovedEncryptionPublicKeyMsgCount +
|
||||
unapprovedTypedMessagesCount +
|
||||
pendingPermissionRequests +
|
||||
waitingForUnlockCount
|
||||
if (count) {
|
||||
label = String(count)
|
||||
}
|
||||
@ -433,9 +447,11 @@ function setupController (initState, initLangCode) {
|
||||
/**
|
||||
* Opens the browser popup for user confirmation
|
||||
*/
|
||||
async function triggerUi () {
|
||||
async function triggerUi() {
|
||||
const tabs = await platform.getActiveTabs()
|
||||
const currentlyActiveMetamaskTab = Boolean(tabs.find((tab) => openMetamaskTabsIDs[tab.id]))
|
||||
const currentlyActiveMetamaskTab = Boolean(
|
||||
tabs.find((tab) => openMetamaskTabsIDs[tab.id]),
|
||||
)
|
||||
if (!popupIsOpen && !currentlyActiveMetamaskTab) {
|
||||
await notificationManager.showPopup()
|
||||
}
|
||||
@ -445,23 +461,24 @@ async function triggerUi () {
|
||||
* Opens the browser popup for user confirmation of watchAsset
|
||||
* then it waits until user interact with the UI
|
||||
*/
|
||||
async function openPopup () {
|
||||
async function openPopup() {
|
||||
await triggerUi()
|
||||
await new Promise(
|
||||
(resolve) => {
|
||||
const interval = setInterval(() => {
|
||||
if (!notificationIsOpen) {
|
||||
clearInterval(interval)
|
||||
resolve()
|
||||
}
|
||||
}, 1000)
|
||||
},
|
||||
)
|
||||
await new Promise((resolve) => {
|
||||
const interval = setInterval(() => {
|
||||
if (!notificationIsOpen) {
|
||||
clearInterval(interval)
|
||||
resolve()
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
// On first install, open a new tab with MetaMask
|
||||
extension.runtime.onInstalled.addListener(({ reason }) => {
|
||||
if (reason === 'install' && !(process.env.METAMASK_DEBUG || process.env.IN_TEST)) {
|
||||
if (
|
||||
reason === 'install' &&
|
||||
!(process.env.METAMASK_DEBUG || process.env.IN_TEST)
|
||||
) {
|
||||
platform.openExtensionInBrowser()
|
||||
}
|
||||
})
|
||||
|
@ -9,7 +9,10 @@ import PortStream from 'extension-port-stream'
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js'), 'utf8')
|
||||
const inpageContent = fs.readFileSync(
|
||||
path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js'),
|
||||
'utf8',
|
||||
)
|
||||
const inpageSuffix = `//# sourceURL=${extension.runtime.getURL('inpage.js')}\n`
|
||||
const inpageBundle = inpageContent + inpageSuffix
|
||||
|
||||
@ -30,7 +33,7 @@ if (shouldInjectProvider()) {
|
||||
*
|
||||
* @param {string} content - Code to be executed in the current document
|
||||
*/
|
||||
function injectScript (content) {
|
||||
function injectScript(content) {
|
||||
try {
|
||||
const container = document.head || document.documentElement
|
||||
const scriptTag = document.createElement('script')
|
||||
@ -47,7 +50,7 @@ function injectScript (content) {
|
||||
* Sets up the stream communication and submits site metadata
|
||||
*
|
||||
*/
|
||||
async function start () {
|
||||
async function start() {
|
||||
await setupStreams()
|
||||
await domIsReady()
|
||||
}
|
||||
@ -57,7 +60,7 @@ async function start () {
|
||||
* browser extension and local per-page browser context.
|
||||
*
|
||||
*/
|
||||
async function setupStreams () {
|
||||
async function setupStreams() {
|
||||
// the transport-specific streams for communication between inpage and background
|
||||
const pageStream = new LocalMessageDuplexStream({
|
||||
name: 'contentscript',
|
||||
@ -73,17 +76,11 @@ async function setupStreams () {
|
||||
const extensionMux = new ObjectMultiplex()
|
||||
extensionMux.setMaxListeners(25)
|
||||
|
||||
pump(
|
||||
pageMux,
|
||||
pageStream,
|
||||
pageMux,
|
||||
(err) => logStreamDisconnectWarning('MetaMask Inpage Multiplex', err),
|
||||
pump(pageMux, pageStream, pageMux, (err) =>
|
||||
logStreamDisconnectWarning('MetaMask Inpage Multiplex', err),
|
||||
)
|
||||
pump(
|
||||
extensionMux,
|
||||
extensionStream,
|
||||
extensionMux,
|
||||
(err) => logStreamDisconnectWarning('MetaMask Background Multiplex', err),
|
||||
pump(extensionMux, extensionStream, extensionMux, (err) =>
|
||||
logStreamDisconnectWarning('MetaMask Background Multiplex', err),
|
||||
)
|
||||
|
||||
// forward communication across inpage-background for these channels only
|
||||
@ -95,14 +92,14 @@ async function setupStreams () {
|
||||
phishingStream.once('data', redirectToPhishingWarning)
|
||||
}
|
||||
|
||||
function forwardTrafficBetweenMuxers (channelName, muxA, muxB) {
|
||||
function forwardTrafficBetweenMuxers(channelName, muxA, muxB) {
|
||||
const channelA = muxA.createStream(channelName)
|
||||
const channelB = muxB.createStream(channelName)
|
||||
pump(
|
||||
channelA,
|
||||
channelB,
|
||||
channelA,
|
||||
(err) => logStreamDisconnectWarning(`MetaMask muxed traffic for channel "${channelName}" failed.`, err),
|
||||
pump(channelA, channelB, channelA, (err) =>
|
||||
logStreamDisconnectWarning(
|
||||
`MetaMask muxed traffic for channel "${channelName}" failed.`,
|
||||
err,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@ -112,7 +109,7 @@ function forwardTrafficBetweenMuxers (channelName, muxA, muxB) {
|
||||
* @param {string} remoteLabel - Remote stream name
|
||||
* @param {Error} err - Stream connection error
|
||||
*/
|
||||
function logStreamDisconnectWarning (remoteLabel, err) {
|
||||
function logStreamDisconnectWarning(remoteLabel, err) {
|
||||
let warningMsg = `MetamaskContentscript - lost connection to ${remoteLabel}`
|
||||
if (err) {
|
||||
warningMsg += `\n${err.stack}`
|
||||
@ -125,9 +122,13 @@ function logStreamDisconnectWarning (remoteLabel, err) {
|
||||
*
|
||||
* @returns {boolean} {@code true} - if the provider should be injected
|
||||
*/
|
||||
function shouldInjectProvider () {
|
||||
return doctypeCheck() && suffixCheck() &&
|
||||
documentElementCheck() && !blockedDomainCheck()
|
||||
function shouldInjectProvider() {
|
||||
return (
|
||||
doctypeCheck() &&
|
||||
suffixCheck() &&
|
||||
documentElementCheck() &&
|
||||
!blockedDomainCheck()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -135,7 +136,7 @@ function shouldInjectProvider () {
|
||||
*
|
||||
* @returns {boolean} {@code true} - if the doctype is html or if none exists
|
||||
*/
|
||||
function doctypeCheck () {
|
||||
function doctypeCheck() {
|
||||
const { doctype } = window.document
|
||||
if (doctype) {
|
||||
return doctype.name === 'html'
|
||||
@ -152,11 +153,8 @@ function doctypeCheck () {
|
||||
*
|
||||
* @returns {boolean} - whether or not the extension of the current document is prohibited
|
||||
*/
|
||||
function suffixCheck () {
|
||||
const prohibitedTypes = [
|
||||
/\.xml$/u,
|
||||
/\.pdf$/u,
|
||||
]
|
||||
function suffixCheck() {
|
||||
const prohibitedTypes = [/\.xml$/u, /\.pdf$/u]
|
||||
const currentUrl = window.location.pathname
|
||||
for (let i = 0; i < prohibitedTypes.length; i++) {
|
||||
if (prohibitedTypes[i].test(currentUrl)) {
|
||||
@ -171,7 +169,7 @@ function suffixCheck () {
|
||||
*
|
||||
* @returns {boolean} {@code true} - if the documentElement is an html node or if none exists
|
||||
*/
|
||||
function documentElementCheck () {
|
||||
function documentElementCheck() {
|
||||
const documentElement = document.documentElement.nodeName
|
||||
if (documentElement) {
|
||||
return documentElement.toLowerCase() === 'html'
|
||||
@ -184,7 +182,7 @@ function documentElementCheck () {
|
||||
*
|
||||
* @returns {boolean} {@code true} - if the current domain is blocked
|
||||
*/
|
||||
function blockedDomainCheck () {
|
||||
function blockedDomainCheck() {
|
||||
const blockedDomains = [
|
||||
'uscourts.gov',
|
||||
'dropbox.com',
|
||||
@ -201,7 +199,10 @@ function blockedDomainCheck () {
|
||||
let currentRegex
|
||||
for (let i = 0; i < blockedDomains.length; i++) {
|
||||
const blockedDomain = blockedDomains[i].replace('.', '\\.')
|
||||
currentRegex = new RegExp(`(?:https?:\\/\\/)(?:(?!${blockedDomain}).)*$`, 'u')
|
||||
currentRegex = new RegExp(
|
||||
`(?:https?:\\/\\/)(?:(?!${blockedDomain}).)*$`,
|
||||
'u',
|
||||
)
|
||||
if (!currentRegex.test(currentUrl)) {
|
||||
return true
|
||||
}
|
||||
@ -212,7 +213,7 @@ function blockedDomainCheck () {
|
||||
/**
|
||||
* Redirects the current page to a phishing information page
|
||||
*/
|
||||
function redirectToPhishingWarning () {
|
||||
function redirectToPhishingWarning() {
|
||||
console.log('MetaMask - routing to Phishing Warning component')
|
||||
const extensionURL = extension.runtime.getURL('phishing.html')
|
||||
window.location.href = `${extensionURL}#${querystring.stringify({
|
||||
@ -224,11 +225,13 @@ function redirectToPhishingWarning () {
|
||||
/**
|
||||
* Returns a promise that resolves when the DOM is loaded (does not wait for images to load)
|
||||
*/
|
||||
async function domIsReady () {
|
||||
async function domIsReady() {
|
||||
// already loaded
|
||||
if (['interactive', 'complete'].includes(document.readyState)) {
|
||||
return undefined
|
||||
}
|
||||
// wait for load
|
||||
return new Promise((resolve) => window.addEventListener('DOMContentLoaded', resolve, { once: true }))
|
||||
return new Promise((resolve) =>
|
||||
window.addEventListener('DOMContentLoaded', resolve, { once: true }),
|
||||
)
|
||||
}
|
||||
|
@ -21,14 +21,13 @@ export const ALERT_TYPES = {
|
||||
}
|
||||
|
||||
const defaultState = {
|
||||
alertEnabledness: Object.keys(ALERT_TYPES)
|
||||
.reduce(
|
||||
(alertEnabledness, alertType) => {
|
||||
alertEnabledness[alertType] = true
|
||||
return alertEnabledness
|
||||
},
|
||||
{},
|
||||
),
|
||||
alertEnabledness: Object.keys(ALERT_TYPES).reduce(
|
||||
(alertEnabledness, alertType) => {
|
||||
alertEnabledness[alertType] = true
|
||||
return alertEnabledness
|
||||
},
|
||||
{},
|
||||
),
|
||||
unconnectedAccountAlertShownOrigins: {},
|
||||
}
|
||||
|
||||
@ -37,12 +36,11 @@ const defaultState = {
|
||||
* alert related state
|
||||
*/
|
||||
export default class AlertController {
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {AlertControllerOptions} [opts] - Controller configuration parameters
|
||||
*/
|
||||
constructor (opts = {}) {
|
||||
constructor(opts = {}) {
|
||||
const { initState, preferencesStore } = opts
|
||||
const state = {
|
||||
...defaultState,
|
||||
@ -56,14 +54,17 @@ export default class AlertController {
|
||||
|
||||
preferencesStore.subscribe(({ selectedAddress }) => {
|
||||
const currentState = this.store.getState()
|
||||
if (currentState.unconnectedAccountAlertShownOrigins && this.selectedAddress !== selectedAddress) {
|
||||
if (
|
||||
currentState.unconnectedAccountAlertShownOrigins &&
|
||||
this.selectedAddress !== selectedAddress
|
||||
) {
|
||||
this.selectedAddress = selectedAddress
|
||||
this.store.updateState({ unconnectedAccountAlertShownOrigins: {} })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setAlertEnabledness (alertId, enabledness) {
|
||||
setAlertEnabledness(alertId, enabledness) {
|
||||
let { alertEnabledness } = this.store.getState()
|
||||
alertEnabledness = { ...alertEnabledness }
|
||||
alertEnabledness[alertId] = enabledness
|
||||
@ -74,9 +75,11 @@ export default class AlertController {
|
||||
* Sets the "switch to connected" alert as shown for the given origin
|
||||
* @param {string} origin - The origin the alert has been shown for
|
||||
*/
|
||||
setUnconnectedAccountAlertShown (origin) {
|
||||
setUnconnectedAccountAlertShown(origin) {
|
||||
let { unconnectedAccountAlertShownOrigins } = this.store.getState()
|
||||
unconnectedAccountAlertShownOrigins = { ...unconnectedAccountAlertShownOrigins }
|
||||
unconnectedAccountAlertShownOrigins = {
|
||||
...unconnectedAccountAlertShownOrigins,
|
||||
}
|
||||
unconnectedAccountAlertShownOrigins[origin] = true
|
||||
this.store.updateState({ unconnectedAccountAlertShownOrigins })
|
||||
}
|
||||
|
@ -2,12 +2,11 @@ import EventEmitter from 'events'
|
||||
import ObservableStore from 'obs-store'
|
||||
|
||||
export default class AppStateController extends EventEmitter {
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param opts
|
||||
*/
|
||||
constructor (opts = {}) {
|
||||
constructor(opts = {}) {
|
||||
const {
|
||||
addUnlockListener,
|
||||
isUnlocked,
|
||||
@ -23,7 +22,8 @@ export default class AppStateController extends EventEmitter {
|
||||
timeoutMinutes: 0,
|
||||
connectedStatusPopoverHasBeenShown: true,
|
||||
swapsWelcomeMessageHasBeenShown: false,
|
||||
defaultHomeActiveTabName: null, ...initState,
|
||||
defaultHomeActiveTabName: null,
|
||||
...initState,
|
||||
})
|
||||
this.timer = null
|
||||
|
||||
@ -53,7 +53,7 @@ export default class AppStateController extends EventEmitter {
|
||||
* @returns {Promise<void>} A promise that resolves when the extension is
|
||||
* unlocked, or immediately if the extension is already unlocked.
|
||||
*/
|
||||
getUnlockPromise (shouldShowUnlockRequest) {
|
||||
getUnlockPromise(shouldShowUnlockRequest) {
|
||||
return new Promise((resolve) => {
|
||||
if (this.isUnlocked()) {
|
||||
resolve()
|
||||
@ -72,7 +72,7 @@ export default class AppStateController extends EventEmitter {
|
||||
* @param {boolean} shouldShowUnlockRequest - Whether the extension notification
|
||||
* popup should be opened.
|
||||
*/
|
||||
waitForUnlock (resolve, shouldShowUnlockRequest) {
|
||||
waitForUnlock(resolve, shouldShowUnlockRequest) {
|
||||
this.waitingForUnlock.push({ resolve })
|
||||
this.emit('updateBadge')
|
||||
if (shouldShowUnlockRequest) {
|
||||
@ -83,7 +83,7 @@ export default class AppStateController extends EventEmitter {
|
||||
/**
|
||||
* Drains the waitingForUnlock queue, resolving all the related Promises.
|
||||
*/
|
||||
handleUnlock () {
|
||||
handleUnlock() {
|
||||
if (this.waitingForUnlock.length > 0) {
|
||||
while (this.waitingForUnlock.length > 0) {
|
||||
this.waitingForUnlock.shift().resolve()
|
||||
@ -96,7 +96,7 @@ export default class AppStateController extends EventEmitter {
|
||||
* Sets the default home tab
|
||||
* @param {string} [defaultHomeActiveTabName] - the tab name
|
||||
*/
|
||||
setDefaultHomeActiveTabName (defaultHomeActiveTabName) {
|
||||
setDefaultHomeActiveTabName(defaultHomeActiveTabName) {
|
||||
this.store.updateState({
|
||||
defaultHomeActiveTabName,
|
||||
})
|
||||
@ -105,7 +105,7 @@ export default class AppStateController extends EventEmitter {
|
||||
/**
|
||||
* Record that the user has seen the connected status info popover
|
||||
*/
|
||||
setConnectedStatusPopoverHasBeenShown () {
|
||||
setConnectedStatusPopoverHasBeenShown() {
|
||||
this.store.updateState({
|
||||
connectedStatusPopoverHasBeenShown: true,
|
||||
})
|
||||
@ -114,7 +114,7 @@ export default class AppStateController extends EventEmitter {
|
||||
/**
|
||||
* Record that the user has seen the swap screen welcome message
|
||||
*/
|
||||
setSwapsWelcomeMessageHasBeenShown () {
|
||||
setSwapsWelcomeMessageHasBeenShown() {
|
||||
this.store.updateState({
|
||||
swapsWelcomeMessageHasBeenShown: true,
|
||||
})
|
||||
@ -124,7 +124,7 @@ export default class AppStateController extends EventEmitter {
|
||||
* Sets the last active time to the current time
|
||||
* @returns {void}
|
||||
*/
|
||||
setLastActiveTime () {
|
||||
setLastActiveTime() {
|
||||
this._resetTimer()
|
||||
}
|
||||
|
||||
@ -134,7 +134,7 @@ export default class AppStateController extends EventEmitter {
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
_setInactiveTimeout (timeoutMinutes) {
|
||||
_setInactiveTimeout(timeoutMinutes) {
|
||||
this.store.updateState({
|
||||
timeoutMinutes,
|
||||
})
|
||||
@ -151,7 +151,7 @@ export default class AppStateController extends EventEmitter {
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
_resetTimer () {
|
||||
_resetTimer() {
|
||||
const { timeoutMinutes } = this.store.getState()
|
||||
|
||||
if (this.timer) {
|
||||
@ -162,6 +162,9 @@ export default class AppStateController extends EventEmitter {
|
||||
return
|
||||
}
|
||||
|
||||
this.timer = setTimeout(() => this.onInactiveTimeout(), timeoutMinutes * 60 * 1000)
|
||||
this.timer = setTimeout(
|
||||
() => this.onInactiveTimeout(),
|
||||
timeoutMinutes * 60 * 1000,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -12,13 +12,12 @@ import ObservableStore from 'obs-store'
|
||||
* a cache of account balances in local storage
|
||||
*/
|
||||
export default class CachedBalancesController {
|
||||
|
||||
/**
|
||||
* Creates a new controller instance
|
||||
*
|
||||
* @param {CachedBalancesOptions} [opts] Controller configuration parameters
|
||||
*/
|
||||
constructor (opts = {}) {
|
||||
constructor(opts = {}) {
|
||||
const { accountTracker, getNetwork } = opts
|
||||
|
||||
this.accountTracker = accountTracker
|
||||
@ -37,15 +36,18 @@ export default class CachedBalancesController {
|
||||
* @param {Object} obj - The the recently updated accounts object for the current network
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async updateCachedBalances ({ accounts }) {
|
||||
async updateCachedBalances({ accounts }) {
|
||||
const network = await this.getNetwork()
|
||||
const balancesToCache = await this._generateBalancesToCache(accounts, network)
|
||||
const balancesToCache = await this._generateBalancesToCache(
|
||||
accounts,
|
||||
network,
|
||||
)
|
||||
this.store.updateState({
|
||||
cachedBalances: balancesToCache,
|
||||
})
|
||||
}
|
||||
|
||||
_generateBalancesToCache (newAccounts, currentNetwork) {
|
||||
_generateBalancesToCache(newAccounts, currentNetwork) {
|
||||
const { cachedBalances } = this.store.getState()
|
||||
const currentNetworkBalancesToCache = { ...cachedBalances[currentNetwork] }
|
||||
|
||||
@ -68,7 +70,7 @@ export default class CachedBalancesController {
|
||||
* Removes cachedBalances
|
||||
*/
|
||||
|
||||
clearCachedBalances () {
|
||||
clearCachedBalances() {
|
||||
this.store.updateState({ cachedBalances: {} })
|
||||
}
|
||||
|
||||
@ -80,7 +82,7 @@ export default class CachedBalancesController {
|
||||
* @private
|
||||
*
|
||||
*/
|
||||
_registerUpdates () {
|
||||
_registerUpdates() {
|
||||
const update = this.updateCachedBalances.bind(this)
|
||||
this.accountTracker.store.subscribe(update)
|
||||
}
|
||||
|
@ -6,20 +6,25 @@ import { MAINNET } from './network/enums'
|
||||
|
||||
// By default, poll every 3 minutes
|
||||
const DEFAULT_INTERVAL = 180 * 1000
|
||||
const SINGLE_CALL_BALANCES_ADDRESS = '0xb1f8e55c7f64d203c1400b9d8555d050f94adf39'
|
||||
const SINGLE_CALL_BALANCES_ADDRESS =
|
||||
'0xb1f8e55c7f64d203c1400b9d8555d050f94adf39'
|
||||
|
||||
/**
|
||||
* A controller that polls for token exchange
|
||||
* rates based on a user's current token list
|
||||
*/
|
||||
export default class DetectTokensController {
|
||||
|
||||
/**
|
||||
* Creates a DetectTokensController
|
||||
*
|
||||
* @param {Object} [config] - Options to configure controller
|
||||
*/
|
||||
constructor ({ interval = DEFAULT_INTERVAL, preferences, network, keyringMemStore } = {}) {
|
||||
constructor({
|
||||
interval = DEFAULT_INTERVAL,
|
||||
preferences,
|
||||
network,
|
||||
keyringMemStore,
|
||||
} = {}) {
|
||||
this.preferences = preferences
|
||||
this.interval = interval
|
||||
this.network = network
|
||||
@ -29,7 +34,7 @@ export default class DetectTokensController {
|
||||
/**
|
||||
* For each token in eth-contract-metadata, find check selectedAddress balance.
|
||||
*/
|
||||
async detectNewTokens () {
|
||||
async detectNewTokens() {
|
||||
if (!this.isActive) {
|
||||
return
|
||||
}
|
||||
@ -40,7 +45,10 @@ export default class DetectTokensController {
|
||||
const tokensToDetect = []
|
||||
this.web3.setProvider(this._network._provider)
|
||||
for (const contractAddress in contracts) {
|
||||
if (contracts[contractAddress].erc20 && !(this.tokenAddresses.includes(contractAddress.toLowerCase()))) {
|
||||
if (
|
||||
contracts[contractAddress].erc20 &&
|
||||
!this.tokenAddresses.includes(contractAddress.toLowerCase())
|
||||
) {
|
||||
tokensToDetect.push(contractAddress)
|
||||
}
|
||||
}
|
||||
@ -49,20 +57,29 @@ export default class DetectTokensController {
|
||||
try {
|
||||
result = await this._getTokenBalances(tokensToDetect)
|
||||
} catch (error) {
|
||||
warn(`MetaMask - DetectTokensController single call balance fetch failed`, error)
|
||||
warn(
|
||||
`MetaMask - DetectTokensController single call balance fetch failed`,
|
||||
error,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
tokensToDetect.forEach((tokenAddress, index) => {
|
||||
const balance = result[index]
|
||||
if (balance && !balance.isZero()) {
|
||||
this._preferences.addToken(tokenAddress, contracts[tokenAddress].symbol, contracts[tokenAddress].decimals)
|
||||
this._preferences.addToken(
|
||||
tokenAddress,
|
||||
contracts[tokenAddress].symbol,
|
||||
contracts[tokenAddress].decimals,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async _getTokenBalances (tokens) {
|
||||
const ethContract = this.web3.eth.contract(SINGLE_CALL_BALANCES_ABI).at(SINGLE_CALL_BALANCES_ADDRESS)
|
||||
async _getTokenBalances(tokens) {
|
||||
const ethContract = this.web3.eth
|
||||
.contract(SINGLE_CALL_BALANCES_ABI)
|
||||
.at(SINGLE_CALL_BALANCES_ADDRESS)
|
||||
return new Promise((resolve, reject) => {
|
||||
ethContract.balances([this.selectedAddress], tokens, (error, result) => {
|
||||
if (error) {
|
||||
@ -78,7 +95,7 @@ export default class DetectTokensController {
|
||||
* in case of address change or user session initialization.
|
||||
*
|
||||
*/
|
||||
restartTokenDetection () {
|
||||
restartTokenDetection() {
|
||||
if (!(this.isActive && this.selectedAddress)) {
|
||||
return
|
||||
}
|
||||
@ -90,7 +107,7 @@ export default class DetectTokensController {
|
||||
/**
|
||||
* @type {Number}
|
||||
*/
|
||||
set interval (interval) {
|
||||
set interval(interval) {
|
||||
this._handle && clearInterval(this._handle)
|
||||
if (!interval) {
|
||||
return
|
||||
@ -104,7 +121,7 @@ export default class DetectTokensController {
|
||||
* In setter when selectedAddress is changed, detectNewTokens and restart polling
|
||||
* @type {Object}
|
||||
*/
|
||||
set preferences (preferences) {
|
||||
set preferences(preferences) {
|
||||
if (!preferences) {
|
||||
return
|
||||
}
|
||||
@ -129,7 +146,7 @@ export default class DetectTokensController {
|
||||
/**
|
||||
* @type {Object}
|
||||
*/
|
||||
set network (network) {
|
||||
set network(network) {
|
||||
if (!network) {
|
||||
return
|
||||
}
|
||||
@ -141,7 +158,7 @@ export default class DetectTokensController {
|
||||
* In setter when isUnlocked is updated to true, detectNewTokens and restart polling
|
||||
* @type {Object}
|
||||
*/
|
||||
set keyringMemStore (keyringMemStore) {
|
||||
set keyringMemStore(keyringMemStore) {
|
||||
if (!keyringMemStore) {
|
||||
return
|
||||
}
|
||||
@ -160,7 +177,7 @@ export default class DetectTokensController {
|
||||
* Internal isActive state
|
||||
* @type {Object}
|
||||
*/
|
||||
get isActive () {
|
||||
get isActive() {
|
||||
return this.isOpen && this.isUnlocked
|
||||
}
|
||||
/* eslint-enable accessor-pairs */
|
||||
|
@ -2,22 +2,22 @@ import EthJsEns from 'ethjs-ens'
|
||||
import ensNetworkMap from 'ethereum-ens-network-map'
|
||||
|
||||
export default class Ens {
|
||||
static getNetworkEnsSupport (network) {
|
||||
static getNetworkEnsSupport(network) {
|
||||
return Boolean(ensNetworkMap[network])
|
||||
}
|
||||
|
||||
constructor ({ network, provider } = {}) {
|
||||
constructor({ network, provider } = {}) {
|
||||
this._ethJsEns = new EthJsEns({
|
||||
network,
|
||||
provider,
|
||||
})
|
||||
}
|
||||
|
||||
lookup (ensName) {
|
||||
lookup(ensName) {
|
||||
return this._ethJsEns.lookup(ensName)
|
||||
}
|
||||
|
||||
reverse (address) {
|
||||
reverse(address) {
|
||||
return this._ethJsEns.reverse(address)
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
||||
const ZERO_X_ERROR_ADDRESS = '0x'
|
||||
|
||||
export default class EnsController {
|
||||
constructor ({ ens, provider, networkStore } = {}) {
|
||||
constructor({ ens, provider, networkStore } = {}) {
|
||||
const initState = {
|
||||
ensResolutionsByAddress: {},
|
||||
}
|
||||
@ -38,11 +38,11 @@ export default class EnsController {
|
||||
})
|
||||
}
|
||||
|
||||
reverseResolveAddress (address) {
|
||||
reverseResolveAddress(address) {
|
||||
return this._reverseResolveAddress(ethUtil.toChecksumAddress(address))
|
||||
}
|
||||
|
||||
async _reverseResolveAddress (address) {
|
||||
async _reverseResolveAddress(address) {
|
||||
if (!this._ens) {
|
||||
return undefined
|
||||
}
|
||||
@ -68,7 +68,10 @@ export default class EnsController {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (registeredAddress === ZERO_ADDRESS || registeredAddress === ZERO_X_ERROR_ADDRESS) {
|
||||
if (
|
||||
registeredAddress === ZERO_ADDRESS ||
|
||||
registeredAddress === ZERO_X_ERROR_ADDRESS
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
@ -80,7 +83,7 @@ export default class EnsController {
|
||||
return domain
|
||||
}
|
||||
|
||||
_updateResolutionsByAddress (address, domain) {
|
||||
_updateResolutionsByAddress(address, domain) {
|
||||
const oldState = this.store.getState()
|
||||
this.store.putState({
|
||||
ensResolutionsByAddress: {
|
||||
|
@ -40,13 +40,8 @@ const etherscanSupportedNetworks = [
|
||||
]
|
||||
|
||||
export default class IncomingTransactionsController {
|
||||
|
||||
constructor (opts = {}) {
|
||||
const {
|
||||
blockTracker,
|
||||
networkController,
|
||||
preferencesController,
|
||||
} = opts
|
||||
constructor(opts = {}) {
|
||||
const { blockTracker, networkController, preferencesController } = opts
|
||||
this.blockTracker = blockTracker
|
||||
this.networkController = networkController
|
||||
this.preferencesController = preferencesController
|
||||
@ -68,38 +63,51 @@ export default class IncomingTransactionsController {
|
||||
[MAINNET]: null,
|
||||
[RINKEBY]: null,
|
||||
[ROPSTEN]: null,
|
||||
}, ...opts.initState,
|
||||
},
|
||||
...opts.initState,
|
||||
}
|
||||
this.store = new ObservableStore(initState)
|
||||
|
||||
this.preferencesController.store.subscribe(pairwise((prevState, currState) => {
|
||||
const { featureFlags: { showIncomingTransactions: prevShowIncomingTransactions } = {} } = prevState
|
||||
const { featureFlags: { showIncomingTransactions: currShowIncomingTransactions } = {} } = currState
|
||||
this.preferencesController.store.subscribe(
|
||||
pairwise((prevState, currState) => {
|
||||
const {
|
||||
featureFlags: {
|
||||
showIncomingTransactions: prevShowIncomingTransactions,
|
||||
} = {},
|
||||
} = prevState
|
||||
const {
|
||||
featureFlags: {
|
||||
showIncomingTransactions: currShowIncomingTransactions,
|
||||
} = {},
|
||||
} = currState
|
||||
|
||||
if (currShowIncomingTransactions === prevShowIncomingTransactions) {
|
||||
return
|
||||
}
|
||||
if (currShowIncomingTransactions === prevShowIncomingTransactions) {
|
||||
return
|
||||
}
|
||||
|
||||
if (prevShowIncomingTransactions && !currShowIncomingTransactions) {
|
||||
this.stop()
|
||||
return
|
||||
}
|
||||
if (prevShowIncomingTransactions && !currShowIncomingTransactions) {
|
||||
this.stop()
|
||||
return
|
||||
}
|
||||
|
||||
this.start()
|
||||
}))
|
||||
this.start()
|
||||
}),
|
||||
)
|
||||
|
||||
this.preferencesController.store.subscribe(pairwise(async (prevState, currState) => {
|
||||
const { selectedAddress: prevSelectedAddress } = prevState
|
||||
const { selectedAddress: currSelectedAddress } = currState
|
||||
this.preferencesController.store.subscribe(
|
||||
pairwise(async (prevState, currState) => {
|
||||
const { selectedAddress: prevSelectedAddress } = prevState
|
||||
const { selectedAddress: currSelectedAddress } = currState
|
||||
|
||||
if (currSelectedAddress === prevSelectedAddress) {
|
||||
return
|
||||
}
|
||||
if (currSelectedAddress === prevSelectedAddress) {
|
||||
return
|
||||
}
|
||||
|
||||
await this._update({
|
||||
address: currSelectedAddress,
|
||||
})
|
||||
}))
|
||||
await this._update({
|
||||
address: currSelectedAddress,
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
this.networkController.on('networkDidChange', async () => {
|
||||
const address = this.preferencesController.getSelectedAddress()
|
||||
@ -109,7 +117,7 @@ export default class IncomingTransactionsController {
|
||||
})
|
||||
}
|
||||
|
||||
start () {
|
||||
start() {
|
||||
const { featureFlags = {} } = this.preferencesController.store.getState()
|
||||
const { showIncomingTransactions } = featureFlags
|
||||
|
||||
@ -121,36 +129,45 @@ export default class IncomingTransactionsController {
|
||||
this.blockTracker.addListener('latest', this._onLatestBlock)
|
||||
}
|
||||
|
||||
stop () {
|
||||
stop() {
|
||||
this.blockTracker.removeListener('latest', this._onLatestBlock)
|
||||
}
|
||||
|
||||
async _update ({ address, newBlockNumberDec } = {}) {
|
||||
async _update({ address, newBlockNumberDec } = {}) {
|
||||
const chainId = this.networkController.getCurrentChainId()
|
||||
if (!etherscanSupportedNetworks.includes(chainId)) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const dataForUpdate = await this._getDataForUpdate({ address, chainId, newBlockNumberDec })
|
||||
const dataForUpdate = await this._getDataForUpdate({
|
||||
address,
|
||||
chainId,
|
||||
newBlockNumberDec,
|
||||
})
|
||||
this._updateStateWithNewTxData(dataForUpdate)
|
||||
} catch (err) {
|
||||
log.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
async _getDataForUpdate ({ address, chainId, newBlockNumberDec } = {}) {
|
||||
async _getDataForUpdate({ address, chainId, newBlockNumberDec } = {}) {
|
||||
const {
|
||||
incomingTransactions: currentIncomingTxs,
|
||||
incomingTxLastFetchedBlocksByNetwork: currentBlocksByNetwork,
|
||||
} = this.store.getState()
|
||||
|
||||
const lastFetchBlockByCurrentNetwork = currentBlocksByNetwork[CHAIN_ID_TO_TYPE_MAP[chainId]]
|
||||
const lastFetchBlockByCurrentNetwork =
|
||||
currentBlocksByNetwork[CHAIN_ID_TO_TYPE_MAP[chainId]]
|
||||
let blockToFetchFrom = lastFetchBlockByCurrentNetwork || newBlockNumberDec
|
||||
if (blockToFetchFrom === undefined) {
|
||||
blockToFetchFrom = parseInt(this.blockTracker.getCurrentBlock(), 16)
|
||||
}
|
||||
|
||||
const { latestIncomingTxBlockNumber, txs: newTxs } = await this._fetchAll(address, blockToFetchFrom, chainId)
|
||||
const { latestIncomingTxBlockNumber, txs: newTxs } = await this._fetchAll(
|
||||
address,
|
||||
blockToFetchFrom,
|
||||
chainId,
|
||||
)
|
||||
|
||||
return {
|
||||
latestIncomingTxBlockNumber,
|
||||
@ -162,7 +179,7 @@ export default class IncomingTransactionsController {
|
||||
}
|
||||
}
|
||||
|
||||
_updateStateWithNewTxData ({
|
||||
_updateStateWithNewTxData({
|
||||
latestIncomingTxBlockNumber,
|
||||
newTxs,
|
||||
currentIncomingTxs,
|
||||
@ -189,15 +206,16 @@ export default class IncomingTransactionsController {
|
||||
})
|
||||
}
|
||||
|
||||
async _fetchAll (address, fromBlock, chainId) {
|
||||
async _fetchAll(address, fromBlock, chainId) {
|
||||
const fetchedTxResponse = await this._fetchTxs(address, fromBlock, chainId)
|
||||
return this._processTxFetchResponse(fetchedTxResponse)
|
||||
}
|
||||
|
||||
async _fetchTxs (address, fromBlock, chainId) {
|
||||
const etherscanSubdomain = chainId === MAINNET_CHAIN_ID
|
||||
? 'api'
|
||||
: `api-${CHAIN_ID_TO_TYPE_MAP[chainId]}`
|
||||
async _fetchTxs(address, fromBlock, chainId) {
|
||||
const etherscanSubdomain =
|
||||
chainId === MAINNET_CHAIN_ID
|
||||
? 'api'
|
||||
: `api-${CHAIN_ID_TO_TYPE_MAP[chainId]}`
|
||||
|
||||
const apiUrl = `https://${etherscanSubdomain}.etherscan.io`
|
||||
let url = `${apiUrl}/api?module=account&action=txlist&address=${address}&tag=latest&page=1`
|
||||
@ -215,7 +233,7 @@ export default class IncomingTransactionsController {
|
||||
}
|
||||
}
|
||||
|
||||
_processTxFetchResponse ({ status, result = [], address, chainId }) {
|
||||
_processTxFetchResponse({ status, result = [], address, chainId }) {
|
||||
if (status === '1' && Array.isArray(result) && result.length > 0) {
|
||||
const remoteTxList = {}
|
||||
const remoteTxs = []
|
||||
@ -226,7 +244,11 @@ export default class IncomingTransactionsController {
|
||||
}
|
||||
})
|
||||
|
||||
const incomingTxs = remoteTxs.filter((tx) => tx.txParams.to && tx.txParams.to.toLowerCase() === address.toLowerCase())
|
||||
const incomingTxs = remoteTxs.filter(
|
||||
(tx) =>
|
||||
tx.txParams.to &&
|
||||
tx.txParams.to.toLowerCase() === address.toLowerCase(),
|
||||
)
|
||||
incomingTxs.sort((a, b) => (a.time < b.time ? -1 : 1))
|
||||
|
||||
let latestIncomingTxBlockNumber = null
|
||||
@ -234,7 +256,8 @@ export default class IncomingTransactionsController {
|
||||
if (
|
||||
tx.blockNumber &&
|
||||
(!latestIncomingTxBlockNumber ||
|
||||
parseInt(latestIncomingTxBlockNumber, 10) < parseInt(tx.blockNumber, 10))
|
||||
parseInt(latestIncomingTxBlockNumber, 10) <
|
||||
parseInt(tx.blockNumber, 10))
|
||||
) {
|
||||
latestIncomingTxBlockNumber = tx.blockNumber
|
||||
}
|
||||
@ -250,7 +273,7 @@ export default class IncomingTransactionsController {
|
||||
}
|
||||
}
|
||||
|
||||
_normalizeTxFromEtherscan (txMeta, chainId) {
|
||||
_normalizeTxFromEtherscan(txMeta, chainId) {
|
||||
const time = parseInt(txMeta.timeStamp, 10) * 1000
|
||||
const status = txMeta.isError === '0' ? 'confirmed' : 'failed'
|
||||
return {
|
||||
@ -273,7 +296,7 @@ export default class IncomingTransactionsController {
|
||||
}
|
||||
}
|
||||
|
||||
function pairwise (fn) {
|
||||
function pairwise(fn) {
|
||||
let first = true
|
||||
let cache
|
||||
return (value) => {
|
||||
|
@ -1,4 +1,8 @@
|
||||
export const SINGLE_CALL_BALANCES_ADDRESS = '0xb1f8e55c7f64d203c1400b9d8555d050f94adf39'
|
||||
export const SINGLE_CALL_BALANCES_ADDRESS_RINKEBY = '0x9f510b19f1ad66f0dcf6e45559fab0d6752c1db7'
|
||||
export const SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN = '0xb8e671734ce5c8d7dfbbea5574fa4cf39f7a54a4'
|
||||
export const SINGLE_CALL_BALANCES_ADDRESS_KOVAN = '0xb1d3fbb2f83aecd196f474c16ca5d9cffa0d0ffc'
|
||||
export const SINGLE_CALL_BALANCES_ADDRESS =
|
||||
'0xb1f8e55c7f64d203c1400b9d8555d050f94adf39'
|
||||
export const SINGLE_CALL_BALANCES_ADDRESS_RINKEBY =
|
||||
'0x9f510b19f1ad66f0dcf6e45559fab0d6752c1db7'
|
||||
export const SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN =
|
||||
'0xb8e671734ce5c8d7dfbbea5574fa4cf39f7a54a4'
|
||||
export const SINGLE_CALL_BALANCES_ADDRESS_KOVAN =
|
||||
'0xb1d3fbb2f83aecd196f474c16ca5d9cffa0d0ffc'
|
||||
|
@ -10,7 +10,7 @@ import createInfuraMiddleware from 'eth-json-rpc-infura'
|
||||
import BlockTracker from 'eth-block-tracker'
|
||||
import * as networkEnums from './enums'
|
||||
|
||||
export default function createInfuraClient ({ network, projectId }) {
|
||||
export default function createInfuraClient({ network, projectId }) {
|
||||
const infuraMiddleware = createInfuraMiddleware({
|
||||
network,
|
||||
projectId,
|
||||
@ -32,7 +32,7 @@ export default function createInfuraClient ({ network, projectId }) {
|
||||
return { networkMiddleware, blockTracker }
|
||||
}
|
||||
|
||||
function createNetworkAndChainIdMiddleware ({ network }) {
|
||||
function createNetworkAndChainIdMiddleware({ network }) {
|
||||
let chainId
|
||||
let netId
|
||||
|
||||
|
@ -9,16 +9,12 @@ import providerFromMiddleware from 'eth-json-rpc-middleware/providerFromMiddlewa
|
||||
import BlockTracker from 'eth-block-tracker'
|
||||
|
||||
const inTest = process.env.IN_TEST === 'true'
|
||||
const blockTrackerOpts = inTest
|
||||
? { pollingInterval: 1000 }
|
||||
: {}
|
||||
const blockTrackerOpts = inTest ? { pollingInterval: 1000 } : {}
|
||||
const getTestMiddlewares = () => {
|
||||
return inTest
|
||||
? [createEstimateGasDelayTestMiddleware()]
|
||||
: []
|
||||
return inTest ? [createEstimateGasDelayTestMiddleware()] : []
|
||||
}
|
||||
|
||||
export default function createJsonRpcClient ({ rpcUrl, chainId }) {
|
||||
export default function createJsonRpcClient({ rpcUrl, chainId }) {
|
||||
const fetchMiddleware = createFetchMiddleware({ rpcUrl })
|
||||
const blockProvider = providerFromMiddleware(fetchMiddleware)
|
||||
const blockTracker = new BlockTracker({
|
||||
@ -39,7 +35,7 @@ export default function createJsonRpcClient ({ rpcUrl, chainId }) {
|
||||
return { networkMiddleware, blockTracker }
|
||||
}
|
||||
|
||||
function createChainIdMiddleware (chainId) {
|
||||
function createChainIdMiddleware(chainId) {
|
||||
return (req, res, next, end) => {
|
||||
if (req.method === 'eth_chainId') {
|
||||
res.result = chainId
|
||||
@ -53,7 +49,7 @@ function createChainIdMiddleware (chainId) {
|
||||
* For use in tests only.
|
||||
* Adds a delay to `eth_estimateGas` calls.
|
||||
*/
|
||||
function createEstimateGasDelayTestMiddleware () {
|
||||
function createEstimateGasDelayTestMiddleware() {
|
||||
return createAsyncMiddleware(async (req, _, next) => {
|
||||
if (req.method === 'eth_estimateGas') {
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
|
@ -1,9 +1,12 @@
|
||||
import mergeMiddleware from 'json-rpc-engine/src/mergeMiddleware'
|
||||
import createScaffoldMiddleware from 'json-rpc-engine/src/createScaffoldMiddleware'
|
||||
import createWalletSubprovider from 'eth-json-rpc-middleware/wallet'
|
||||
import { createPendingNonceMiddleware, createPendingTxMiddleware } from './middleware/pending'
|
||||
import {
|
||||
createPendingNonceMiddleware,
|
||||
createPendingTxMiddleware,
|
||||
} from './middleware/pending'
|
||||
|
||||
export default function createMetamaskMiddleware ({
|
||||
export default function createMetamaskMiddleware({
|
||||
version,
|
||||
getAccounts,
|
||||
processTransaction,
|
||||
|
@ -22,13 +22,7 @@ export const KOVAN_DISPLAY_NAME = 'Kovan'
|
||||
export const MAINNET_DISPLAY_NAME = 'Ethereum Mainnet'
|
||||
export const GOERLI_DISPLAY_NAME = 'Goerli'
|
||||
|
||||
export const INFURA_PROVIDER_TYPES = [
|
||||
ROPSTEN,
|
||||
RINKEBY,
|
||||
KOVAN,
|
||||
MAINNET,
|
||||
GOERLI,
|
||||
]
|
||||
export const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET, GOERLI]
|
||||
|
||||
export const NETWORK_TYPE_TO_ID_MAP = {
|
||||
[ROPSTEN]: { networkId: ROPSTEN_NETWORK_ID, chainId: ROPSTEN_CHAIN_ID },
|
||||
@ -58,20 +52,16 @@ export const NETWORK_TO_NAME_MAP = {
|
||||
[MAINNET_CHAIN_ID]: MAINNET_DISPLAY_NAME,
|
||||
}
|
||||
|
||||
export const CHAIN_ID_TO_TYPE_MAP = Object.entries(NETWORK_TYPE_TO_ID_MAP)
|
||||
.reduce(
|
||||
(chainIdToTypeMap, [networkType, { chainId }]) => {
|
||||
chainIdToTypeMap[chainId] = networkType
|
||||
return chainIdToTypeMap
|
||||
},
|
||||
{},
|
||||
)
|
||||
export const CHAIN_ID_TO_TYPE_MAP = Object.entries(
|
||||
NETWORK_TYPE_TO_ID_MAP,
|
||||
).reduce((chainIdToTypeMap, [networkType, { chainId }]) => {
|
||||
chainIdToTypeMap[chainId] = networkType
|
||||
return chainIdToTypeMap
|
||||
}, {})
|
||||
|
||||
export const CHAIN_ID_TO_NETWORK_ID_MAP = Object.values(NETWORK_TYPE_TO_ID_MAP)
|
||||
.reduce(
|
||||
(chainIdToNetworkIdMap, { chainId, networkId }) => {
|
||||
chainIdToNetworkIdMap[chainId] = networkId
|
||||
return chainIdToNetworkIdMap
|
||||
},
|
||||
{},
|
||||
)
|
||||
export const CHAIN_ID_TO_NETWORK_ID_MAP = Object.values(
|
||||
NETWORK_TYPE_TO_ID_MAP,
|
||||
).reduce((chainIdToNetworkIdMap, { chainId, networkId }) => {
|
||||
chainIdToNetworkIdMap[chainId] = networkId
|
||||
return chainIdToNetworkIdMap
|
||||
}, {})
|
||||
|
@ -1,7 +1,7 @@
|
||||
import createAsyncMiddleware from 'json-rpc-engine/src/createAsyncMiddleware'
|
||||
import { formatTxMetaForRpcResult } from '../util'
|
||||
|
||||
export function createPendingNonceMiddleware ({ getPendingNonce }) {
|
||||
export function createPendingNonceMiddleware({ getPendingNonce }) {
|
||||
return createAsyncMiddleware(async (req, res, next) => {
|
||||
const { method, params } = req
|
||||
if (method !== 'eth_getTransactionCount') {
|
||||
@ -17,7 +17,7 @@ export function createPendingNonceMiddleware ({ getPendingNonce }) {
|
||||
})
|
||||
}
|
||||
|
||||
export function createPendingTxMiddleware ({ getPendingTransactionByHash }) {
|
||||
export function createPendingTxMiddleware({ getPendingTransactionByHash }) {
|
||||
return createAsyncMiddleware(async (req, res, next) => {
|
||||
const { method, params } = req
|
||||
if (method !== 'eth_getTransactionByHash') {
|
||||
|
@ -5,7 +5,10 @@ import ComposedStore from 'obs-store/lib/composed'
|
||||
import JsonRpcEngine from 'json-rpc-engine'
|
||||
import providerFromEngine from 'eth-json-rpc-middleware/providerFromEngine'
|
||||
import log from 'loglevel'
|
||||
import { createSwappableProxy, createEventEmitterProxy } from 'swappable-obj-proxy'
|
||||
import {
|
||||
createSwappableProxy,
|
||||
createEventEmitterProxy,
|
||||
} from 'swappable-obj-proxy'
|
||||
import EthQuery from 'eth-query'
|
||||
import createMetamaskMiddleware from './createMetamaskMiddleware'
|
||||
import createInfuraClient from './createInfuraClient'
|
||||
@ -40,8 +43,7 @@ const defaultProviderConfig = {
|
||||
}
|
||||
|
||||
export default class NetworkController extends EventEmitter {
|
||||
|
||||
constructor (opts = {}) {
|
||||
constructor(opts = {}) {
|
||||
super()
|
||||
|
||||
// create stores
|
||||
@ -72,7 +74,7 @@ export default class NetworkController extends EventEmitter {
|
||||
* @throws {Error} if the project ID is not a valid string
|
||||
* @return {void}
|
||||
*/
|
||||
setInfuraProjectId (projectId) {
|
||||
setInfuraProjectId(projectId) {
|
||||
if (!projectId || typeof projectId !== 'string') {
|
||||
throw new Error('Invalid Infura project ID')
|
||||
}
|
||||
@ -80,7 +82,7 @@ export default class NetworkController extends EventEmitter {
|
||||
this._infuraProjectId = projectId
|
||||
}
|
||||
|
||||
initializeProvider (providerParams) {
|
||||
initializeProvider(providerParams) {
|
||||
this._baseProviderParams = providerParams
|
||||
const { type, rpcUrl, chainId } = this.getProviderConfig()
|
||||
this._configureProvider({ type, rpcUrl, chainId })
|
||||
@ -88,41 +90,45 @@ export default class NetworkController extends EventEmitter {
|
||||
}
|
||||
|
||||
// return the proxies so the references will always be good
|
||||
getProviderAndBlockTracker () {
|
||||
getProviderAndBlockTracker() {
|
||||
const provider = this._providerProxy
|
||||
const blockTracker = this._blockTrackerProxy
|
||||
return { provider, blockTracker }
|
||||
}
|
||||
|
||||
verifyNetwork () {
|
||||
verifyNetwork() {
|
||||
// Check network when restoring connectivity:
|
||||
if (this.isNetworkLoading()) {
|
||||
this.lookupNetwork()
|
||||
}
|
||||
}
|
||||
|
||||
getNetworkState () {
|
||||
getNetworkState() {
|
||||
return this.networkStore.getState()
|
||||
}
|
||||
|
||||
setNetworkState (network) {
|
||||
setNetworkState(network) {
|
||||
this.networkStore.putState(network)
|
||||
}
|
||||
|
||||
isNetworkLoading () {
|
||||
isNetworkLoading() {
|
||||
return this.getNetworkState() === 'loading'
|
||||
}
|
||||
|
||||
lookupNetwork () {
|
||||
lookupNetwork() {
|
||||
// Prevent firing when provider is not defined.
|
||||
if (!this._provider) {
|
||||
log.warn('NetworkController - lookupNetwork aborted due to missing provider')
|
||||
log.warn(
|
||||
'NetworkController - lookupNetwork aborted due to missing provider',
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const chainId = this.getCurrentChainId()
|
||||
if (!chainId) {
|
||||
log.warn('NetworkController - lookupNetwork aborted due to missing chainId')
|
||||
log.warn(
|
||||
'NetworkController - lookupNetwork aborted due to missing chainId',
|
||||
)
|
||||
this.setNetworkState('loading')
|
||||
return
|
||||
}
|
||||
@ -143,12 +149,12 @@ export default class NetworkController extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
getCurrentChainId () {
|
||||
getCurrentChainId() {
|
||||
const { type, chainId: configChainId } = this.getProviderConfig()
|
||||
return NETWORK_TYPE_TO_ID_MAP[type]?.chainId || configChainId
|
||||
}
|
||||
|
||||
setRpcTarget (rpcUrl, chainId, ticker = 'ETH', nickname = '', rpcPrefs) {
|
||||
setRpcTarget(rpcUrl, chainId, ticker = 'ETH', nickname = '', rpcPrefs) {
|
||||
this.setProviderConfig({
|
||||
type: 'rpc',
|
||||
rpcUrl,
|
||||
@ -159,26 +165,33 @@ export default class NetworkController extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
async setProviderType (type, rpcUrl = '', ticker = 'ETH', nickname = '') {
|
||||
assert.notEqual(type, 'rpc', `NetworkController - cannot call "setProviderType" with type 'rpc'. use "setRpcTarget"`)
|
||||
assert(INFURA_PROVIDER_TYPES.includes(type), `NetworkController - Unknown rpc type "${type}"`)
|
||||
async setProviderType(type, rpcUrl = '', ticker = 'ETH', nickname = '') {
|
||||
assert.notEqual(
|
||||
type,
|
||||
'rpc',
|
||||
`NetworkController - cannot call "setProviderType" with type 'rpc'. use "setRpcTarget"`,
|
||||
)
|
||||
assert(
|
||||
INFURA_PROVIDER_TYPES.includes(type),
|
||||
`NetworkController - Unknown rpc type "${type}"`,
|
||||
)
|
||||
const { chainId } = NETWORK_TYPE_TO_ID_MAP[type]
|
||||
this.setProviderConfig({ type, rpcUrl, chainId, ticker, nickname })
|
||||
}
|
||||
|
||||
resetConnection () {
|
||||
resetConnection() {
|
||||
this.setProviderConfig(this.getProviderConfig())
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the provider config and switches the network.
|
||||
*/
|
||||
setProviderConfig (config) {
|
||||
setProviderConfig(config) {
|
||||
this.providerStore.updateState(config)
|
||||
this._switchNetwork(config)
|
||||
}
|
||||
|
||||
getProviderConfig () {
|
||||
getProviderConfig() {
|
||||
return this.providerStore.getState()
|
||||
}
|
||||
|
||||
@ -186,26 +199,28 @@ export default class NetworkController extends EventEmitter {
|
||||
// Private
|
||||
//
|
||||
|
||||
_switchNetwork (opts) {
|
||||
_switchNetwork(opts) {
|
||||
this.setNetworkState('loading')
|
||||
this._configureProvider(opts)
|
||||
this.emit('networkDidChange', opts.type)
|
||||
}
|
||||
|
||||
_configureProvider ({ type, rpcUrl, chainId }) {
|
||||
_configureProvider({ type, rpcUrl, chainId }) {
|
||||
// infura type-based endpoints
|
||||
const isInfura = INFURA_PROVIDER_TYPES.includes(type)
|
||||
if (isInfura) {
|
||||
this._configureInfuraProvider(type, this._infuraProjectId)
|
||||
// url-based rpc endpoints
|
||||
// url-based rpc endpoints
|
||||
} else if (type === 'rpc') {
|
||||
this._configureStandardProvider(rpcUrl, chainId)
|
||||
} else {
|
||||
throw new Error(`NetworkController - _configureProvider - unknown type "${type}"`)
|
||||
throw new Error(
|
||||
`NetworkController - _configureProvider - unknown type "${type}"`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
_configureInfuraProvider (type, projectId) {
|
||||
_configureInfuraProvider(type, projectId) {
|
||||
log.info('NetworkController - configureInfuraProvider', type)
|
||||
const networkClient = createInfuraClient({
|
||||
network: type,
|
||||
@ -214,14 +229,16 @@ export default class NetworkController extends EventEmitter {
|
||||
this._setNetworkClient(networkClient)
|
||||
}
|
||||
|
||||
_configureStandardProvider (rpcUrl, chainId) {
|
||||
_configureStandardProvider(rpcUrl, chainId) {
|
||||
log.info('NetworkController - configureStandardProvider', rpcUrl)
|
||||
const networkClient = createJsonRpcClient({ rpcUrl, chainId })
|
||||
this._setNetworkClient(networkClient)
|
||||
}
|
||||
|
||||
_setNetworkClient ({ networkMiddleware, blockTracker }) {
|
||||
const metamaskMiddleware = createMetamaskMiddleware(this._baseProviderParams)
|
||||
_setNetworkClient({ networkMiddleware, blockTracker }) {
|
||||
const metamaskMiddleware = createMetamaskMiddleware(
|
||||
this._baseProviderParams,
|
||||
)
|
||||
const engine = new JsonRpcEngine()
|
||||
engine.push(metamaskMiddleware)
|
||||
engine.push(networkMiddleware)
|
||||
@ -229,7 +246,7 @@ export default class NetworkController extends EventEmitter {
|
||||
this._setProviderAndBlockTracker({ provider, blockTracker })
|
||||
}
|
||||
|
||||
_setProviderAndBlockTracker ({ provider, blockTracker }) {
|
||||
_setProviderAndBlockTracker({ provider, blockTracker }) {
|
||||
// update or intialize proxies
|
||||
if (this._providerProxy) {
|
||||
this._providerProxy.setTarget(provider)
|
||||
@ -239,7 +256,9 @@ export default class NetworkController extends EventEmitter {
|
||||
if (this._blockTrackerProxy) {
|
||||
this._blockTrackerProxy.setTarget(blockTracker)
|
||||
} else {
|
||||
this._blockTrackerProxy = createEventEmitterProxy(blockTracker, { eventFilter: 'skipInternal' })
|
||||
this._blockTrackerProxy = createEventEmitterProxy(blockTracker, {
|
||||
eventFilter: 'skipInternal',
|
||||
})
|
||||
}
|
||||
// set new provider and blockTracker
|
||||
this._provider = provider
|
||||
|
@ -2,21 +2,23 @@ import { NETWORK_TO_NAME_MAP } from './enums'
|
||||
|
||||
export const getNetworkDisplayName = (key) => NETWORK_TO_NAME_MAP[key]
|
||||
|
||||
export function formatTxMetaForRpcResult (txMeta) {
|
||||
export function formatTxMetaForRpcResult(txMeta) {
|
||||
return {
|
||||
'blockHash': txMeta.txReceipt ? txMeta.txReceipt.blockHash : null,
|
||||
'blockNumber': txMeta.txReceipt ? txMeta.txReceipt.blockNumber : null,
|
||||
'from': txMeta.txParams.from,
|
||||
'gas': txMeta.txParams.gas,
|
||||
'gasPrice': txMeta.txParams.gasPrice,
|
||||
'hash': txMeta.hash,
|
||||
'input': txMeta.txParams.data || '0x',
|
||||
'nonce': txMeta.txParams.nonce,
|
||||
'to': txMeta.txParams.to,
|
||||
'transactionIndex': txMeta.txReceipt ? txMeta.txReceipt.transactionIndex : null,
|
||||
'value': txMeta.txParams.value || '0x0',
|
||||
'v': txMeta.v,
|
||||
'r': txMeta.r,
|
||||
's': txMeta.s,
|
||||
blockHash: txMeta.txReceipt ? txMeta.txReceipt.blockHash : null,
|
||||
blockNumber: txMeta.txReceipt ? txMeta.txReceipt.blockNumber : null,
|
||||
from: txMeta.txParams.from,
|
||||
gas: txMeta.txParams.gas,
|
||||
gasPrice: txMeta.txParams.gasPrice,
|
||||
hash: txMeta.hash,
|
||||
input: txMeta.txParams.data || '0x',
|
||||
nonce: txMeta.txParams.nonce,
|
||||
to: txMeta.txParams.to,
|
||||
transactionIndex: txMeta.txReceipt
|
||||
? txMeta.txReceipt.transactionIndex
|
||||
: null,
|
||||
value: txMeta.txParams.value || '0x0',
|
||||
v: txMeta.v,
|
||||
r: txMeta.r,
|
||||
s: txMeta.s,
|
||||
}
|
||||
}
|
||||
|
@ -17,13 +17,12 @@ import log from 'loglevel'
|
||||
* state related to onboarding
|
||||
*/
|
||||
export default class OnboardingController {
|
||||
|
||||
/**
|
||||
* Creates a new controller instance
|
||||
*
|
||||
* @param {OnboardingOptions} [opts] Controller configuration parameters
|
||||
*/
|
||||
constructor (opts = {}) {
|
||||
constructor(opts = {}) {
|
||||
const initialTransientState = {
|
||||
onboardingTabs: {},
|
||||
}
|
||||
@ -46,7 +45,7 @@ export default class OnboardingController {
|
||||
})
|
||||
}
|
||||
|
||||
setSeedPhraseBackedUp (newSeedPhraseBackUpState) {
|
||||
setSeedPhraseBackedUp(newSeedPhraseBackUpState) {
|
||||
this.store.updateState({
|
||||
seedPhraseBackedUp: newSeedPhraseBackUpState,
|
||||
})
|
||||
@ -65,7 +64,9 @@ export default class OnboardingController {
|
||||
}
|
||||
const onboardingTabs = { ...this.store.getState().onboardingTabs }
|
||||
if (!onboardingTabs[location] || onboardingTabs[location] !== tabId) {
|
||||
log.debug(`Registering onboarding tab at location '${location}' with tabId '${tabId}'`)
|
||||
log.debug(
|
||||
`Registering onboarding tab at location '${location}' with tabId '${tabId}'`,
|
||||
)
|
||||
onboardingTabs[location] = tabId
|
||||
this.store.updateState({ onboardingTabs })
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
export const WALLET_PREFIX = 'wallet_'
|
||||
|
||||
export const HISTORY_STORE_KEY = 'permissionsHistory'
|
||||
@ -23,9 +22,7 @@ export const NOTIFICATION_NAMES = {
|
||||
accountsChanged: 'wallet_accountsChanged',
|
||||
}
|
||||
|
||||
export const LOG_IGNORE_METHODS = [
|
||||
'wallet_sendDomainMetadata',
|
||||
]
|
||||
export const LOG_IGNORE_METHODS = ['wallet_sendDomainMetadata']
|
||||
|
||||
export const LOG_METHOD_TYPES = {
|
||||
restricted: 'restricted',
|
||||
|
@ -24,8 +24,7 @@ import {
|
||||
} from './enums'
|
||||
|
||||
export class PermissionsController {
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
{
|
||||
getKeyringAccounts,
|
||||
getRestrictedMethods,
|
||||
@ -38,7 +37,6 @@ export class PermissionsController {
|
||||
restoredPermissions = {},
|
||||
restoredState = {},
|
||||
) {
|
||||
|
||||
// additional top-level store key set in _initializeMetadataStore
|
||||
this.store = new ObservableStore({
|
||||
[LOG_STORE_KEY]: restoredState[LOG_STORE_KEY] || [],
|
||||
@ -75,8 +73,7 @@ export class PermissionsController {
|
||||
})
|
||||
}
|
||||
|
||||
createMiddleware ({ origin, extensionId }) {
|
||||
|
||||
createMiddleware({ origin, extensionId }) {
|
||||
if (typeof origin !== 'string' || !origin.length) {
|
||||
throw new Error('Must provide non-empty string origin.')
|
||||
}
|
||||
@ -91,20 +88,26 @@ export class PermissionsController {
|
||||
|
||||
engine.push(this.permissionsLog.createMiddleware())
|
||||
|
||||
engine.push(createPermissionsMethodMiddleware({
|
||||
addDomainMetadata: this.addDomainMetadata.bind(this),
|
||||
getAccounts: this.getAccounts.bind(this, origin),
|
||||
getUnlockPromise: () => this._getUnlockPromise(true),
|
||||
hasPermission: this.hasPermission.bind(this, origin),
|
||||
notifyAccountsChanged: this.notifyAccountsChanged.bind(this, origin),
|
||||
requestAccountsPermission: this._requestPermissions.bind(
|
||||
this, { origin }, { eth_accounts: {} },
|
||||
),
|
||||
}))
|
||||
engine.push(
|
||||
createPermissionsMethodMiddleware({
|
||||
addDomainMetadata: this.addDomainMetadata.bind(this),
|
||||
getAccounts: this.getAccounts.bind(this, origin),
|
||||
getUnlockPromise: () => this._getUnlockPromise(true),
|
||||
hasPermission: this.hasPermission.bind(this, origin),
|
||||
notifyAccountsChanged: this.notifyAccountsChanged.bind(this, origin),
|
||||
requestAccountsPermission: this._requestPermissions.bind(
|
||||
this,
|
||||
{ origin },
|
||||
{ eth_accounts: {} },
|
||||
),
|
||||
}),
|
||||
)
|
||||
|
||||
engine.push(this.permissions.providerMiddlewareFunction.bind(
|
||||
this.permissions, { origin },
|
||||
))
|
||||
engine.push(
|
||||
this.permissions.providerMiddlewareFunction.bind(this.permissions, {
|
||||
origin,
|
||||
}),
|
||||
)
|
||||
|
||||
return asMiddleware(engine)
|
||||
}
|
||||
@ -114,7 +117,7 @@ export class PermissionsController {
|
||||
* @param {string} origin - The requesting origin
|
||||
* @returns {Promise<string>} The permissions request ID
|
||||
*/
|
||||
async requestAccountsPermissionWithId (origin) {
|
||||
async requestAccountsPermissionWithId(origin) {
|
||||
const id = nanoid()
|
||||
this._requestPermissions({ origin }, { eth_accounts: {} }, id)
|
||||
return id
|
||||
@ -127,16 +130,19 @@ export class PermissionsController {
|
||||
*
|
||||
* @param {string} origin - The origin string.
|
||||
*/
|
||||
getAccounts (origin) {
|
||||
getAccounts(origin) {
|
||||
return new Promise((resolve, _) => {
|
||||
|
||||
const req = { method: 'eth_accounts' }
|
||||
const res = {}
|
||||
this.permissions.providerMiddlewareFunction(
|
||||
{ origin }, req, res, () => undefined, _end,
|
||||
{ origin },
|
||||
req,
|
||||
res,
|
||||
() => undefined,
|
||||
_end,
|
||||
)
|
||||
|
||||
function _end () {
|
||||
function _end() {
|
||||
if (res.error || !Array.isArray(res.result)) {
|
||||
resolve([])
|
||||
} else {
|
||||
@ -153,7 +159,7 @@ export class PermissionsController {
|
||||
* @param {string} permission - The permission to check for.
|
||||
* @returns {boolean} Whether the origin has the permission.
|
||||
*/
|
||||
hasPermission (origin, permission) {
|
||||
hasPermission(origin, permission) {
|
||||
return Boolean(this.permissions.getPermission(origin, permission))
|
||||
}
|
||||
|
||||
@ -162,7 +168,7 @@ export class PermissionsController {
|
||||
*
|
||||
* @returns {Object} identities
|
||||
*/
|
||||
_getIdentities () {
|
||||
_getIdentities() {
|
||||
return this.preferences.getState().identities
|
||||
}
|
||||
|
||||
@ -175,9 +181,8 @@ export class PermissionsController {
|
||||
* @returns {Promise<IOcapLdCapability[]>} A Promise that resolves with the
|
||||
* approved permissions, or rejects with an error.
|
||||
*/
|
||||
_requestPermissions (domain, permissions, id) {
|
||||
_requestPermissions(domain, permissions, id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
// rpc-cap assigns an id to the request if there is none, as expected by
|
||||
// requestUserApproval below
|
||||
const req = {
|
||||
@ -188,10 +193,14 @@ export class PermissionsController {
|
||||
const res = {}
|
||||
|
||||
this.permissions.providerMiddlewareFunction(
|
||||
domain, req, res, () => undefined, _end,
|
||||
domain,
|
||||
req,
|
||||
res,
|
||||
() => undefined,
|
||||
_end,
|
||||
)
|
||||
|
||||
function _end (_err) {
|
||||
function _end(_err) {
|
||||
const err = _err || res.error
|
||||
if (err) {
|
||||
reject(err)
|
||||
@ -211,8 +220,7 @@ export class PermissionsController {
|
||||
* @param {Object} approved - The request object approved by the user
|
||||
* @param {Array} accounts - The accounts to expose, if any
|
||||
*/
|
||||
async approvePermissionsRequest (approved, accounts) {
|
||||
|
||||
async approvePermissionsRequest(approved, accounts) {
|
||||
const { id } = approved.metadata
|
||||
const approval = this.pendingApprovals.get(id)
|
||||
|
||||
@ -222,28 +230,29 @@ export class PermissionsController {
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
if (Object.keys(approved.permissions).length === 0) {
|
||||
|
||||
approval.reject(ethErrors.rpc.invalidRequest({
|
||||
message: 'Must request at least one permission.',
|
||||
}))
|
||||
|
||||
approval.reject(
|
||||
ethErrors.rpc.invalidRequest({
|
||||
message: 'Must request at least one permission.',
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
|
||||
// attempt to finalize the request and resolve it,
|
||||
// settings caveats as necessary
|
||||
approved.permissions = await this.finalizePermissionsRequest(
|
||||
approved.permissions, accounts,
|
||||
approved.permissions,
|
||||
accounts,
|
||||
)
|
||||
approval.resolve(approved.permissions)
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
// if finalization fails, reject the request
|
||||
approval.reject(ethErrors.rpc.invalidRequest({
|
||||
message: err.message, data: err,
|
||||
}))
|
||||
approval.reject(
|
||||
ethErrors.rpc.invalidRequest({
|
||||
message: err.message,
|
||||
data: err,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
this._removePendingApproval(id)
|
||||
@ -256,7 +265,7 @@ export class PermissionsController {
|
||||
*
|
||||
* @param {string} id - The id of the request rejected by the user
|
||||
*/
|
||||
async rejectPermissionsRequest (id) {
|
||||
async rejectPermissionsRequest(id) {
|
||||
const approval = this.pendingApprovals.get(id)
|
||||
|
||||
if (!approval) {
|
||||
@ -277,8 +286,7 @@ export class PermissionsController {
|
||||
* @param {string} origin - The origin to expose the account to.
|
||||
* @param {string} account - The new account to expose.
|
||||
*/
|
||||
async addPermittedAccount (origin, account) {
|
||||
|
||||
async addPermittedAccount(origin, account) {
|
||||
const domains = this.permissions.getDomains()
|
||||
if (!domains[origin]) {
|
||||
throw new Error('Unrecognized domain')
|
||||
@ -294,7 +302,8 @@ export class PermissionsController {
|
||||
}
|
||||
|
||||
this.permissions.updateCaveatFor(
|
||||
origin, 'eth_accounts',
|
||||
origin,
|
||||
'eth_accounts',
|
||||
CAVEAT_NAMES.exposedAccounts,
|
||||
[...oldPermittedAccounts, account],
|
||||
)
|
||||
@ -315,8 +324,7 @@ export class PermissionsController {
|
||||
* @param {string} origin - The origin to remove the account from.
|
||||
* @param {string} account - The account to remove.
|
||||
*/
|
||||
async removePermittedAccount (origin, account) {
|
||||
|
||||
async removePermittedAccount(origin, account) {
|
||||
const domains = this.permissions.getDomains()
|
||||
if (!domains[origin]) {
|
||||
throw new Error('Unrecognized domain')
|
||||
@ -331,15 +339,16 @@ export class PermissionsController {
|
||||
throw new Error('Account is not permitted for origin')
|
||||
}
|
||||
|
||||
let newPermittedAccounts = oldPermittedAccounts
|
||||
.filter((acc) => acc !== account)
|
||||
let newPermittedAccounts = oldPermittedAccounts.filter(
|
||||
(acc) => acc !== account,
|
||||
)
|
||||
|
||||
if (newPermittedAccounts.length === 0) {
|
||||
this.removePermissionsFor({ [origin]: ['eth_accounts'] })
|
||||
} else {
|
||||
|
||||
this.permissions.updateCaveatFor(
|
||||
origin, 'eth_accounts',
|
||||
origin,
|
||||
'eth_accounts',
|
||||
CAVEAT_NAMES.exposedAccounts,
|
||||
newPermittedAccounts,
|
||||
)
|
||||
@ -358,14 +367,19 @@ export class PermissionsController {
|
||||
*
|
||||
* @param {string} account - The account to remove.
|
||||
*/
|
||||
async removeAllAccountPermissions (account) {
|
||||
async removeAllAccountPermissions(account) {
|
||||
this.validatePermittedAccounts([account])
|
||||
|
||||
const domains = this.permissions.getDomains()
|
||||
const connectedOrigins = Object.keys(domains)
|
||||
.filter((origin) => this._getPermittedAccounts(origin).includes(account))
|
||||
const connectedOrigins = Object.keys(domains).filter((origin) =>
|
||||
this._getPermittedAccounts(origin).includes(account),
|
||||
)
|
||||
|
||||
await Promise.all(connectedOrigins.map((origin) => this.removePermittedAccount(origin, account)))
|
||||
await Promise.all(
|
||||
connectedOrigins.map((origin) =>
|
||||
this.removePermittedAccount(origin, account),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -378,15 +392,13 @@ export class PermissionsController {
|
||||
* @param {string[]} requestedAccounts - The accounts to expose, if any.
|
||||
* @returns {Object} The finalized permissions request object.
|
||||
*/
|
||||
async finalizePermissionsRequest (requestedPermissions, requestedAccounts) {
|
||||
|
||||
async finalizePermissionsRequest(requestedPermissions, requestedAccounts) {
|
||||
const finalizedPermissions = cloneDeep(requestedPermissions)
|
||||
const finalizedAccounts = cloneDeep(requestedAccounts)
|
||||
|
||||
const { eth_accounts: ethAccounts } = finalizedPermissions
|
||||
|
||||
if (ethAccounts) {
|
||||
|
||||
this.validatePermittedAccounts(finalizedAccounts)
|
||||
|
||||
if (!ethAccounts.caveats) {
|
||||
@ -394,9 +406,11 @@ export class PermissionsController {
|
||||
}
|
||||
|
||||
// caveat names are unique, and we will only construct this caveat here
|
||||
ethAccounts.caveats = ethAccounts.caveats.filter((c) => (
|
||||
c.name !== CAVEAT_NAMES.exposedAccounts && c.name !== CAVEAT_NAMES.primaryAccountOnly
|
||||
))
|
||||
ethAccounts.caveats = ethAccounts.caveats.filter(
|
||||
(c) =>
|
||||
c.name !== CAVEAT_NAMES.exposedAccounts &&
|
||||
c.name !== CAVEAT_NAMES.primaryAccountOnly,
|
||||
)
|
||||
|
||||
ethAccounts.caveats.push({
|
||||
type: CAVEAT_TYPES.limitResponseLength,
|
||||
@ -420,7 +434,7 @@ export class PermissionsController {
|
||||
*
|
||||
* @param {string[]} accounts - An array of addresses.
|
||||
*/
|
||||
validatePermittedAccounts (accounts) {
|
||||
validatePermittedAccounts(accounts) {
|
||||
if (!Array.isArray(accounts) || accounts.length === 0) {
|
||||
throw new Error('Must provide non-empty array of account(s).')
|
||||
}
|
||||
@ -441,8 +455,7 @@ export class PermissionsController {
|
||||
* @param {string} origin - The origin of the domain to notify.
|
||||
* @param {Array<string>} newAccounts - The currently permitted accounts.
|
||||
*/
|
||||
notifyAccountsChanged (origin, newAccounts) {
|
||||
|
||||
notifyAccountsChanged(origin, newAccounts) {
|
||||
if (typeof origin !== 'string' || !origin) {
|
||||
throw new Error(`Invalid origin: '${origin}'`)
|
||||
}
|
||||
@ -459,9 +472,7 @@ export class PermissionsController {
|
||||
// if the accounts changed from the perspective of the dapp,
|
||||
// update "last seen" time for the origin and account(s)
|
||||
// exception: no accounts -> no times to update
|
||||
this.permissionsLog.updateAccountsHistory(
|
||||
origin, newAccounts,
|
||||
)
|
||||
this.permissionsLog.updateAccountsHistory(origin, newAccounts)
|
||||
|
||||
// NOTE:
|
||||
// we don't check for accounts changing in the notifyAllDomains case,
|
||||
@ -478,14 +489,11 @@ export class PermissionsController {
|
||||
* @param {Object} domains { origin: [permissions] } - The map of domain
|
||||
* origins to permissions to remove.
|
||||
*/
|
||||
removePermissionsFor (domains) {
|
||||
|
||||
removePermissionsFor(domains) {
|
||||
Object.entries(domains).forEach(([origin, perms]) => {
|
||||
|
||||
this.permissions.removePermissionsFor(
|
||||
origin,
|
||||
perms.map((methodName) => {
|
||||
|
||||
if (methodName === 'eth_accounts') {
|
||||
this.notifyAccountsChanged(origin, [])
|
||||
}
|
||||
@ -499,7 +507,7 @@ export class PermissionsController {
|
||||
/**
|
||||
* Removes all known domains and their related permissions.
|
||||
*/
|
||||
clearPermissions () {
|
||||
clearPermissions() {
|
||||
this.permissions.clearDomains()
|
||||
this._notifyAllDomains({
|
||||
method: NOTIFICATION_NAMES.accountsChanged,
|
||||
@ -517,8 +525,7 @@ export class PermissionsController {
|
||||
* @param {string} origin - The origin whose domain metadata to store.
|
||||
* @param {Object} metadata - The domain's metadata that will be stored.
|
||||
*/
|
||||
addDomainMetadata (origin, metadata) {
|
||||
|
||||
addDomainMetadata(origin, metadata) {
|
||||
const oldMetadataState = this.store.getState()[METADATA_STORE_KEY]
|
||||
const newMetadataState = { ...oldMetadataState }
|
||||
|
||||
@ -541,7 +548,10 @@ export class PermissionsController {
|
||||
lastUpdated: Date.now(),
|
||||
}
|
||||
|
||||
if (!newMetadataState[origin].extensionId && !newMetadataState[origin].host) {
|
||||
if (
|
||||
!newMetadataState[origin].extensionId &&
|
||||
!newMetadataState[origin].host
|
||||
) {
|
||||
newMetadataState[origin].host = new URL(origin).host
|
||||
}
|
||||
|
||||
@ -557,8 +567,7 @@ export class PermissionsController {
|
||||
*
|
||||
* @param {Object} restoredState - The restored permissions controller state.
|
||||
*/
|
||||
_initializeMetadataStore (restoredState) {
|
||||
|
||||
_initializeMetadataStore(restoredState) {
|
||||
const metadataState = restoredState[METADATA_STORE_KEY] || {}
|
||||
const newMetadataState = this._trimDomainMetadata(metadataState)
|
||||
|
||||
@ -574,8 +583,7 @@ export class PermissionsController {
|
||||
* @param {Object} metadataState - The metadata store state object to trim.
|
||||
* @returns {Object} The new metadata state object.
|
||||
*/
|
||||
_trimDomainMetadata (metadataState) {
|
||||
|
||||
_trimDomainMetadata(metadataState) {
|
||||
const newMetadataState = { ...metadataState }
|
||||
const origins = Object.keys(metadataState)
|
||||
const permissionsDomains = this.permissions.getDomains()
|
||||
@ -593,7 +601,7 @@ export class PermissionsController {
|
||||
* Replaces the existing domain metadata with the passed-in object.
|
||||
* @param {Object} newMetadataState - The new metadata to set.
|
||||
*/
|
||||
_setDomainMetadata (newMetadataState) {
|
||||
_setDomainMetadata(newMetadataState) {
|
||||
this.store.updateState({ [METADATA_STORE_KEY]: newMetadataState })
|
||||
}
|
||||
|
||||
@ -603,11 +611,10 @@ export class PermissionsController {
|
||||
* @param {string} origin - The origin to obtain permitted accounts for
|
||||
* @returns {Array<string>|null} The list of permitted accounts
|
||||
*/
|
||||
_getPermittedAccounts (origin) {
|
||||
_getPermittedAccounts(origin) {
|
||||
const permittedAccounts = this.permissions
|
||||
.getPermission(origin, 'eth_accounts')
|
||||
?.caveats
|
||||
?.find((caveat) => caveat.name === CAVEAT_NAMES.exposedAccounts)
|
||||
?.caveats?.find((caveat) => caveat.name === CAVEAT_NAMES.exposedAccounts)
|
||||
?.value
|
||||
|
||||
return permittedAccounts || null
|
||||
@ -622,8 +629,7 @@ export class PermissionsController {
|
||||
*
|
||||
* @param {string} account - The newly selected account's address.
|
||||
*/
|
||||
async _handleAccountSelected (account) {
|
||||
|
||||
async _handleAccountSelected(account) {
|
||||
if (typeof account !== 'string') {
|
||||
throw new Error('Selected account should be a non-empty string.')
|
||||
}
|
||||
@ -631,20 +637,20 @@ export class PermissionsController {
|
||||
const domains = this.permissions.getDomains() || {}
|
||||
const connectedDomains = Object.entries(domains)
|
||||
.filter(([_, { permissions }]) => {
|
||||
const ethAccounts = permissions.find((permission) => permission.parentCapability === 'eth_accounts')
|
||||
const exposedAccounts = ethAccounts
|
||||
?.caveats
|
||||
.find((caveat) => caveat.name === 'exposedAccounts')
|
||||
?.value
|
||||
const ethAccounts = permissions.find(
|
||||
(permission) => permission.parentCapability === 'eth_accounts',
|
||||
)
|
||||
const exposedAccounts = ethAccounts?.caveats.find(
|
||||
(caveat) => caveat.name === 'exposedAccounts',
|
||||
)?.value
|
||||
return exposedAccounts?.includes(account)
|
||||
})
|
||||
.map(([domain]) => domain)
|
||||
|
||||
await Promise.all(
|
||||
connectedDomains
|
||||
.map(
|
||||
(origin) => this._handleConnectedAccountSelected(origin),
|
||||
),
|
||||
connectedDomains.map((origin) =>
|
||||
this._handleConnectedAccountSelected(origin),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@ -656,7 +662,7 @@ export class PermissionsController {
|
||||
*
|
||||
* @param {string} origin - The origin
|
||||
*/
|
||||
async _handleConnectedAccountSelected (origin) {
|
||||
async _handleConnectedAccountSelected(origin) {
|
||||
const permittedAccounts = await this.getAccounts(origin)
|
||||
|
||||
this.notifyAccountsChanged(origin, permittedAccounts)
|
||||
@ -669,8 +675,7 @@ export class PermissionsController {
|
||||
* @param {Function} resolve - The function resolving the pending approval Promise.
|
||||
* @param {Function} reject - The function rejecting the pending approval Promise.
|
||||
*/
|
||||
_addPendingApproval (id, origin, resolve, reject) {
|
||||
|
||||
_addPendingApproval(id, origin, resolve, reject) {
|
||||
if (
|
||||
this.pendingApprovalOrigins.has(origin) ||
|
||||
this.pendingApprovals.has(id)
|
||||
@ -688,7 +693,7 @@ export class PermissionsController {
|
||||
* Removes the pending approval with the given id.
|
||||
* @param {string} id - The id of the pending approval to remove.
|
||||
*/
|
||||
_removePendingApproval (id) {
|
||||
_removePendingApproval(id) {
|
||||
const { origin } = this.pendingApprovals.get(id)
|
||||
this.pendingApprovalOrigins.delete(origin)
|
||||
this.pendingApprovals.delete(id)
|
||||
@ -700,49 +705,52 @@ export class PermissionsController {
|
||||
*
|
||||
* @param {string} origin = The origin string representing the domain.
|
||||
*/
|
||||
_initializePermissions (restoredState) {
|
||||
|
||||
_initializePermissions(restoredState) {
|
||||
// these permission requests are almost certainly stale
|
||||
const initState = { ...restoredState, permissionsRequests: [] }
|
||||
|
||||
this.permissions = new RpcCap({
|
||||
this.permissions = new RpcCap(
|
||||
{
|
||||
// Supports passthrough methods:
|
||||
safeMethods: SAFE_METHODS,
|
||||
|
||||
// Supports passthrough methods:
|
||||
safeMethods: SAFE_METHODS,
|
||||
// optional prefix for internal methods
|
||||
methodPrefix: WALLET_PREFIX,
|
||||
|
||||
// optional prefix for internal methods
|
||||
methodPrefix: WALLET_PREFIX,
|
||||
restrictedMethods: this._restrictedMethods,
|
||||
|
||||
restrictedMethods: this._restrictedMethods,
|
||||
/**
|
||||
* A promise-returning callback used to determine whether to approve
|
||||
* permissions requests or not.
|
||||
*
|
||||
* Currently only returns a boolean, but eventually should return any
|
||||
* specific parameters or amendments to the permissions.
|
||||
*
|
||||
* @param {string} req - The internal rpc-cap user request object.
|
||||
*/
|
||||
requestUserApproval: async (req) => {
|
||||
const {
|
||||
metadata: { id, origin },
|
||||
} = req
|
||||
|
||||
/**
|
||||
* A promise-returning callback used to determine whether to approve
|
||||
* permissions requests or not.
|
||||
*
|
||||
* Currently only returns a boolean, but eventually should return any
|
||||
* specific parameters or amendments to the permissions.
|
||||
*
|
||||
* @param {string} req - The internal rpc-cap user request object.
|
||||
*/
|
||||
requestUserApproval: async (req) => {
|
||||
const { metadata: { id, origin } } = req
|
||||
if (this.pendingApprovalOrigins.has(origin)) {
|
||||
throw ethErrors.rpc.resourceUnavailable(
|
||||
'Permissions request already pending; please wait.',
|
||||
)
|
||||
}
|
||||
|
||||
if (this.pendingApprovalOrigins.has(origin)) {
|
||||
throw ethErrors.rpc.resourceUnavailable(
|
||||
'Permissions request already pending; please wait.',
|
||||
)
|
||||
}
|
||||
this._showPermissionRequest()
|
||||
|
||||
this._showPermissionRequest()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this._addPendingApproval(id, origin, resolve, reject)
|
||||
})
|
||||
return new Promise((resolve, reject) => {
|
||||
this._addPendingApproval(id, origin, resolve, reject)
|
||||
})
|
||||
},
|
||||
},
|
||||
}, initState)
|
||||
initState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function addInternalMethodPrefix (method) {
|
||||
export function addInternalMethodPrefix(method) {
|
||||
return WALLET_PREFIX + method
|
||||
}
|
||||
|
@ -14,8 +14,7 @@ import {
|
||||
* and permissions-related methods.
|
||||
*/
|
||||
export default class PermissionsLogController {
|
||||
|
||||
constructor ({ restrictedMethods, store }) {
|
||||
constructor({ restrictedMethods, store }) {
|
||||
this.restrictedMethods = restrictedMethods
|
||||
this.store = store
|
||||
}
|
||||
@ -25,7 +24,7 @@ export default class PermissionsLogController {
|
||||
*
|
||||
* @returns {Array<Object>} The activity log.
|
||||
*/
|
||||
getActivityLog () {
|
||||
getActivityLog() {
|
||||
return this.store.getState()[LOG_STORE_KEY] || []
|
||||
}
|
||||
|
||||
@ -34,7 +33,7 @@ export default class PermissionsLogController {
|
||||
*
|
||||
* @param {Array<Object>} logs - The new activity log array.
|
||||
*/
|
||||
updateActivityLog (logs) {
|
||||
updateActivityLog(logs) {
|
||||
this.store.updateState({ [LOG_STORE_KEY]: logs })
|
||||
}
|
||||
|
||||
@ -43,7 +42,7 @@ export default class PermissionsLogController {
|
||||
*
|
||||
* @returns {Object} The permissions history log.
|
||||
*/
|
||||
getHistory () {
|
||||
getHistory() {
|
||||
return this.store.getState()[HISTORY_STORE_KEY] || {}
|
||||
}
|
||||
|
||||
@ -52,7 +51,7 @@ export default class PermissionsLogController {
|
||||
*
|
||||
* @param {Object} history - The new permissions history log object.
|
||||
*/
|
||||
updateHistory (history) {
|
||||
updateHistory(history) {
|
||||
this.store.updateState({ [HISTORY_STORE_KEY]: history })
|
||||
}
|
||||
|
||||
@ -63,8 +62,7 @@ export default class PermissionsLogController {
|
||||
* @param {string} origin - The origin that the accounts are exposed to.
|
||||
* @param {Array<string>} accounts - The accounts.
|
||||
*/
|
||||
updateAccountsHistory (origin, accounts) {
|
||||
|
||||
updateAccountsHistory(origin, accounts) {
|
||||
if (accounts.length === 0) {
|
||||
return
|
||||
}
|
||||
@ -88,9 +86,8 @@ export default class PermissionsLogController {
|
||||
*
|
||||
* @returns {JsonRpcEngineMiddleware} The permissions log middleware.
|
||||
*/
|
||||
createMiddleware () {
|
||||
createMiddleware() {
|
||||
return (req, res, next, _end) => {
|
||||
|
||||
let activityEntry, requestedMethods
|
||||
const { origin, method } = req
|
||||
const isInternal = method.startsWith(WALLET_PREFIX)
|
||||
@ -100,7 +97,6 @@ export default class PermissionsLogController {
|
||||
!LOG_IGNORE_METHODS.includes(method) &&
|
||||
(isInternal || this.restrictedMethods.includes(method))
|
||||
) {
|
||||
|
||||
activityEntry = this.logRequest(req, isInternal)
|
||||
|
||||
if (method === `${WALLET_PREFIX}requestPermissions`) {
|
||||
@ -109,7 +105,6 @@ export default class PermissionsLogController {
|
||||
requestedMethods = this.getRequestedMethods(req)
|
||||
}
|
||||
} else if (method === 'eth_requestAccounts') {
|
||||
|
||||
// eth_requestAccounts is a special case; we need to extract the accounts
|
||||
// from it
|
||||
activityEntry = this.logRequest(req, isInternal)
|
||||
@ -122,7 +117,6 @@ export default class PermissionsLogController {
|
||||
|
||||
// call next with a return handler for capturing the response
|
||||
next((cb) => {
|
||||
|
||||
const time = Date.now()
|
||||
this.logResponse(activityEntry, res, time)
|
||||
|
||||
@ -130,7 +124,10 @@ export default class PermissionsLogController {
|
||||
// any permissions or accounts changes will be recorded on the response,
|
||||
// so we only log permissions history here
|
||||
this.logPermissionsHistory(
|
||||
requestedMethods, origin, res.result, time,
|
||||
requestedMethods,
|
||||
origin,
|
||||
res.result,
|
||||
time,
|
||||
method === 'eth_requestAccounts',
|
||||
)
|
||||
}
|
||||
@ -145,13 +142,13 @@ export default class PermissionsLogController {
|
||||
* @param {Object} request - The request object.
|
||||
* @param {boolean} isInternal - Whether the request is internal.
|
||||
*/
|
||||
logRequest (request, isInternal) {
|
||||
logRequest(request, isInternal) {
|
||||
const activityEntry = {
|
||||
id: request.id,
|
||||
method: request.method,
|
||||
methodType: (
|
||||
isInternal ? LOG_METHOD_TYPES.internal : LOG_METHOD_TYPES.restricted
|
||||
),
|
||||
methodType: isInternal
|
||||
? LOG_METHOD_TYPES.internal
|
||||
: LOG_METHOD_TYPES.restricted,
|
||||
origin: request.origin,
|
||||
request: cloneDeep(request),
|
||||
requestTime: Date.now(),
|
||||
@ -171,8 +168,7 @@ export default class PermissionsLogController {
|
||||
* @param {Object} response - The response object.
|
||||
* @param {number} time - Output from Date.now()
|
||||
*/
|
||||
logResponse (entry, response, time) {
|
||||
|
||||
logResponse(entry, response, time) {
|
||||
if (!entry || !response) {
|
||||
return
|
||||
}
|
||||
@ -188,8 +184,7 @@ export default class PermissionsLogController {
|
||||
*
|
||||
* @param {Object} entry - The activity log entry.
|
||||
*/
|
||||
commitNewActivity (entry) {
|
||||
|
||||
commitNewActivity(entry) {
|
||||
const logs = this.getActivityLog()
|
||||
|
||||
// add new entry to end of log
|
||||
@ -212,32 +207,31 @@ export default class PermissionsLogController {
|
||||
* @param {string} time - The time of the request, i.e. Date.now().
|
||||
* @param {boolean} isEthRequestAccounts - Whether the permissions request was 'eth_requestAccounts'.
|
||||
*/
|
||||
logPermissionsHistory (
|
||||
requestedMethods, origin, result,
|
||||
time, isEthRequestAccounts,
|
||||
logPermissionsHistory(
|
||||
requestedMethods,
|
||||
origin,
|
||||
result,
|
||||
time,
|
||||
isEthRequestAccounts,
|
||||
) {
|
||||
|
||||
let accounts, newEntries
|
||||
|
||||
if (isEthRequestAccounts) {
|
||||
|
||||
accounts = result
|
||||
const accountToTimeMap = getAccountToTimeMap(accounts, time)
|
||||
|
||||
newEntries = {
|
||||
'eth_accounts': {
|
||||
eth_accounts: {
|
||||
accounts: accountToTimeMap,
|
||||
lastApproved: time,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
|
||||
// Records new "lastApproved" times for the granted permissions, if any.
|
||||
// Special handling for eth_accounts, in order to record the time the
|
||||
// accounts were last seen or approved by the origin.
|
||||
newEntries = result
|
||||
.map((perm) => {
|
||||
|
||||
if (perm.parentCapability === 'eth_accounts') {
|
||||
accounts = this.getAccountsFromPermission(perm)
|
||||
}
|
||||
@ -245,13 +239,10 @@ export default class PermissionsLogController {
|
||||
return perm.parentCapability
|
||||
})
|
||||
.reduce((acc, method) => {
|
||||
|
||||
// all approved permissions will be included in the response,
|
||||
// not just the newly requested ones
|
||||
if (requestedMethods.includes(method)) {
|
||||
|
||||
if (method === 'eth_accounts') {
|
||||
|
||||
const accountToTimeMap = getAccountToTimeMap(accounts, time)
|
||||
|
||||
acc[method] = {
|
||||
@ -280,8 +271,7 @@ export default class PermissionsLogController {
|
||||
* @param {string} origin - The requesting origin.
|
||||
* @param {Object} newEntries - The new entries to commit.
|
||||
*/
|
||||
commitNewHistory (origin, newEntries) {
|
||||
|
||||
commitNewHistory(origin, newEntries) {
|
||||
// a simple merge updates most permissions
|
||||
const history = this.getHistory()
|
||||
const newOriginHistory = {
|
||||
@ -291,19 +281,16 @@ export default class PermissionsLogController {
|
||||
|
||||
// eth_accounts requires special handling, because of information
|
||||
// we store about the accounts
|
||||
const existingEthAccountsEntry = (
|
||||
const existingEthAccountsEntry =
|
||||
history[origin] && history[origin].eth_accounts
|
||||
)
|
||||
const newEthAccountsEntry = newEntries.eth_accounts
|
||||
|
||||
if (existingEthAccountsEntry && newEthAccountsEntry) {
|
||||
|
||||
// we may intend to update just the accounts, not the permission
|
||||
// itself
|
||||
const lastApproved = (
|
||||
const lastApproved =
|
||||
newEthAccountsEntry.lastApproved ||
|
||||
existingEthAccountsEntry.lastApproved
|
||||
)
|
||||
|
||||
// merge old and new eth_accounts history entries
|
||||
newOriginHistory.eth_accounts = {
|
||||
@ -326,7 +313,7 @@ export default class PermissionsLogController {
|
||||
* @param {Object} request - The request object.
|
||||
* @returns {Array<string>} The names of the requested permissions.
|
||||
*/
|
||||
getRequestedMethods (request) {
|
||||
getRequestedMethods(request) {
|
||||
if (
|
||||
!request.params ||
|
||||
!request.params[0] ||
|
||||
@ -345,20 +332,17 @@ export default class PermissionsLogController {
|
||||
* @param {Object} perm - The permissions object.
|
||||
* @returns {Array<string>} The permitted accounts.
|
||||
*/
|
||||
getAccountsFromPermission (perm) {
|
||||
|
||||
getAccountsFromPermission(perm) {
|
||||
if (perm.parentCapability !== 'eth_accounts' || !perm.caveats) {
|
||||
return []
|
||||
}
|
||||
|
||||
const accounts = new Set()
|
||||
for (const caveat of perm.caveats) {
|
||||
|
||||
if (
|
||||
caveat.name === CAVEAT_NAMES.exposedAccounts &&
|
||||
Array.isArray(caveat.value)
|
||||
) {
|
||||
|
||||
for (const value of caveat.value) {
|
||||
accounts.add(value)
|
||||
}
|
||||
@ -377,8 +361,6 @@ export default class PermissionsLogController {
|
||||
* @param {number} time - A time, e.g. Date.now().
|
||||
* @returns {Object} A string:number map of addresses to time.
|
||||
*/
|
||||
function getAccountToTimeMap (accounts, time) {
|
||||
return accounts.reduce(
|
||||
(acc, account) => ({ ...acc, [account]: time }), {},
|
||||
)
|
||||
function getAccountToTimeMap(accounts, time) {
|
||||
return accounts.reduce((acc, account) => ({ ...acc, [account]: time }), {})
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { ethErrors } from 'eth-json-rpc-errors'
|
||||
/**
|
||||
* Create middleware for handling certain methods and preprocessing permissions requests.
|
||||
*/
|
||||
export default function createPermissionsMethodMiddleware ({
|
||||
export default function createPermissionsMethodMiddleware({
|
||||
addDomainMetadata,
|
||||
getAccounts,
|
||||
getUnlockPromise,
|
||||
@ -12,26 +12,21 @@ export default function createPermissionsMethodMiddleware ({
|
||||
notifyAccountsChanged,
|
||||
requestAccountsPermission,
|
||||
}) {
|
||||
|
||||
let isProcessingRequestAccounts = false
|
||||
|
||||
return createAsyncMiddleware(async (req, res, next) => {
|
||||
|
||||
let responseHandler
|
||||
|
||||
switch (req.method) {
|
||||
|
||||
// Intercepting eth_accounts requests for backwards compatibility:
|
||||
// The getAccounts call below wraps the rpc-cap middleware, and returns
|
||||
// an empty array in case of errors (such as 4100:unauthorized)
|
||||
case 'eth_accounts': {
|
||||
|
||||
res.result = await getAccounts()
|
||||
return
|
||||
}
|
||||
|
||||
case 'eth_requestAccounts': {
|
||||
|
||||
if (isProcessingRequestAccounts) {
|
||||
res.error = ethErrors.rpc.resourceUnavailable(
|
||||
'Already processing eth_requestAccounts. Please wait.',
|
||||
@ -79,7 +74,6 @@ export default function createPermissionsMethodMiddleware ({
|
||||
// custom method for getting metadata from the requesting domain,
|
||||
// sent automatically by the inpage provider when it's initialized
|
||||
case 'wallet_sendDomainMetadata': {
|
||||
|
||||
if (typeof req.domainMetadata?.name === 'string') {
|
||||
addDomainMetadata(req.origin, req.domainMetadata)
|
||||
}
|
||||
@ -89,11 +83,8 @@ export default function createPermissionsMethodMiddleware ({
|
||||
|
||||
// register return handler to send accountsChanged notification
|
||||
case 'wallet_requestPermissions': {
|
||||
|
||||
if ('eth_accounts' in req.params?.[0]) {
|
||||
|
||||
responseHandler = async () => {
|
||||
|
||||
if (Array.isArray(res.result)) {
|
||||
for (const permission of res.result) {
|
||||
if (permission.parentCapability === 'eth_accounts') {
|
||||
|
@ -1,6 +1,9 @@
|
||||
export default function getRestrictedMethods ({ getIdentities, getKeyringAccounts }) {
|
||||
export default function getRestrictedMethods({
|
||||
getIdentities,
|
||||
getKeyringAccounts,
|
||||
}) {
|
||||
return {
|
||||
'eth_accounts': {
|
||||
eth_accounts: {
|
||||
method: async (_, res, __, end) => {
|
||||
try {
|
||||
const accounts = await getKeyringAccounts()
|
||||
@ -10,7 +13,10 @@ export default function getRestrictedMethods ({ getIdentities, getKeyringAccount
|
||||
throw new Error(`Missing identity for address ${firstAddress}`)
|
||||
} else if (!identities[secondAddress]) {
|
||||
throw new Error(`Missing identity for address ${secondAddress}`)
|
||||
} else if (identities[firstAddress].lastSelected === identities[secondAddress].lastSelected) {
|
||||
} else if (
|
||||
identities[firstAddress].lastSelected ===
|
||||
identities[secondAddress].lastSelected
|
||||
) {
|
||||
return 0
|
||||
} else if (identities[firstAddress].lastSelected === undefined) {
|
||||
return 1
|
||||
@ -18,7 +24,10 @@ export default function getRestrictedMethods ({ getIdentities, getKeyringAccount
|
||||
return -1
|
||||
}
|
||||
|
||||
return identities[secondAddress].lastSelected - identities[firstAddress].lastSelected
|
||||
return (
|
||||
identities[secondAddress].lastSelected -
|
||||
identities[firstAddress].lastSelected
|
||||
)
|
||||
})
|
||||
end()
|
||||
} catch (err) {
|
||||
|
@ -9,7 +9,6 @@ import { addInternalMethodPrefix } from './permissions'
|
||||
import { NETWORK_TYPE_TO_ID_MAP } from './network/enums'
|
||||
|
||||
export default class PreferencesController {
|
||||
|
||||
/**
|
||||
*
|
||||
* @typedef {Object} PreferencesController
|
||||
@ -30,7 +29,7 @@ export default class PreferencesController {
|
||||
* @property {string} store.selectedAddress A hex string that matches the currently selected address in the app
|
||||
*
|
||||
*/
|
||||
constructor (opts = {}) {
|
||||
constructor(opts = {}) {
|
||||
const initState = {
|
||||
frequentRpcListDetail: [],
|
||||
accountTokens: {},
|
||||
@ -66,7 +65,8 @@ export default class PreferencesController {
|
||||
metaMetricsSendCount: 0,
|
||||
|
||||
// ENS decentralized website resolution
|
||||
ipfsGateway: 'dweb.link', ...opts.initState,
|
||||
ipfsGateway: 'dweb.link',
|
||||
...opts.initState,
|
||||
}
|
||||
|
||||
this.network = opts.network
|
||||
@ -86,7 +86,7 @@ export default class PreferencesController {
|
||||
* Sets the {@code forgottenPassword} state property
|
||||
* @param {boolean} forgottenPassword - whether or not the user has forgotten their password
|
||||
*/
|
||||
setPasswordForgotten (forgottenPassword) {
|
||||
setPasswordForgotten(forgottenPassword) {
|
||||
this.store.updateState({ forgottenPassword })
|
||||
}
|
||||
|
||||
@ -96,7 +96,7 @@ export default class PreferencesController {
|
||||
* @param {boolean} val - Whether or not the user prefers blockie indicators
|
||||
*
|
||||
*/
|
||||
setUseBlockie (val) {
|
||||
setUseBlockie(val) {
|
||||
this.store.updateState({ useBlockie: val })
|
||||
}
|
||||
|
||||
@ -106,7 +106,7 @@ export default class PreferencesController {
|
||||
* @param {boolean} val - Whether or not the user prefers to set nonce
|
||||
*
|
||||
*/
|
||||
setUseNonceField (val) {
|
||||
setUseNonceField(val) {
|
||||
this.store.updateState({ useNonceField: val })
|
||||
}
|
||||
|
||||
@ -116,7 +116,7 @@ export default class PreferencesController {
|
||||
* @param {boolean} val - Whether or not the user prefers phishing domain protection
|
||||
*
|
||||
*/
|
||||
setUsePhishDetect (val) {
|
||||
setUsePhishDetect(val) {
|
||||
this.store.updateState({ usePhishDetect: val })
|
||||
}
|
||||
|
||||
@ -127,11 +127,16 @@ export default class PreferencesController {
|
||||
* @returns {string|null} - the string of the new metametrics id, or null if not set
|
||||
*
|
||||
*/
|
||||
setParticipateInMetaMetrics (bool) {
|
||||
setParticipateInMetaMetrics(bool) {
|
||||
this.store.updateState({ participateInMetaMetrics: bool })
|
||||
let metaMetricsId = null
|
||||
if (bool && !this.store.getState().metaMetricsId) {
|
||||
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 })
|
||||
@ -139,11 +144,11 @@ export default class PreferencesController {
|
||||
return metaMetricsId
|
||||
}
|
||||
|
||||
getParticipateInMetaMetrics () {
|
||||
getParticipateInMetaMetrics() {
|
||||
return this.store.getState().participateInMetaMetrics
|
||||
}
|
||||
|
||||
setMetaMetricsSendCount (val) {
|
||||
setMetaMetricsSendCount(val) {
|
||||
this.store.updateState({ metaMetricsSendCount: val })
|
||||
}
|
||||
|
||||
@ -153,19 +158,19 @@ export default class PreferencesController {
|
||||
* @param {string} type - Indicates the type of first time flow - create or import - the user wishes to follow
|
||||
*
|
||||
*/
|
||||
setFirstTimeFlowType (type) {
|
||||
setFirstTimeFlowType(type) {
|
||||
this.store.updateState({ firstTimeFlowType: type })
|
||||
}
|
||||
|
||||
getSuggestedTokens () {
|
||||
getSuggestedTokens() {
|
||||
return this.store.getState().suggestedTokens
|
||||
}
|
||||
|
||||
getAssetImages () {
|
||||
getAssetImages() {
|
||||
return this.store.getState().assetImages
|
||||
}
|
||||
|
||||
addSuggestedERC20Asset (tokenOpts) {
|
||||
addSuggestedERC20Asset(tokenOpts) {
|
||||
this._validateERC20AssetParams(tokenOpts)
|
||||
const suggested = this.getSuggestedTokens()
|
||||
const { rawAddress, symbol, decimals, image } = tokenOpts
|
||||
@ -181,7 +186,7 @@ export default class PreferencesController {
|
||||
* @param {string} fourBytePrefix - Four-byte method signature
|
||||
* @param {string} methodData - Corresponding data method
|
||||
*/
|
||||
addKnownMethodData (fourBytePrefix, methodData) {
|
||||
addKnownMethodData(fourBytePrefix, methodData) {
|
||||
const { knownMethodData } = this.store.getState()
|
||||
knownMethodData[fourBytePrefix] = methodData
|
||||
this.store.updateState({ knownMethodData })
|
||||
@ -195,7 +200,7 @@ export default class PreferencesController {
|
||||
* @param {Function} - next
|
||||
* @param {Function} - end
|
||||
*/
|
||||
async requestWatchAsset (req, res, next, end) {
|
||||
async requestWatchAsset(req, res, next, end) {
|
||||
if (
|
||||
req.method === 'metamask_watchAsset' ||
|
||||
req.method === addInternalMethodPrefix('watchAsset')
|
||||
@ -227,8 +232,10 @@ export default class PreferencesController {
|
||||
* @param {string} key - he preferred language locale key
|
||||
*
|
||||
*/
|
||||
setCurrentLocale (key) {
|
||||
const textDirection = (['ar', 'dv', 'fa', 'he', 'ku'].includes(key)) ? 'rtl' : 'auto'
|
||||
setCurrentLocale(key) {
|
||||
const textDirection = ['ar', 'dv', 'fa', 'he', 'ku'].includes(key)
|
||||
? 'rtl'
|
||||
: 'auto'
|
||||
this.store.updateState({
|
||||
currentLocale: key,
|
||||
textDirection,
|
||||
@ -243,7 +250,7 @@ export default class PreferencesController {
|
||||
* @param {string[]} addresses - An array of hex addresses
|
||||
*
|
||||
*/
|
||||
setAddresses (addresses) {
|
||||
setAddresses(addresses) {
|
||||
const oldIdentities = this.store.getState().identities
|
||||
const oldAccountTokens = this.store.getState().accountTokens
|
||||
|
||||
@ -266,7 +273,7 @@ export default class PreferencesController {
|
||||
* @param {string} address - A hex address
|
||||
* @returns {string} - the address that was removed
|
||||
*/
|
||||
removeAddress (address) {
|
||||
removeAddress(address) {
|
||||
const { identities } = this.store.getState()
|
||||
const { accountTokens } = this.store.getState()
|
||||
if (!identities[address]) {
|
||||
@ -291,7 +298,7 @@ export default class PreferencesController {
|
||||
* @param {string[]} addresses - An array of hex addresses
|
||||
*
|
||||
*/
|
||||
addAddresses (addresses) {
|
||||
addAddresses(addresses) {
|
||||
const { identities, accountTokens } = this.store.getState()
|
||||
addresses.forEach((address) => {
|
||||
// skip if already exists
|
||||
@ -314,8 +321,7 @@ export default class PreferencesController {
|
||||
* @param {Array<string>} addresses - known to the vault.
|
||||
* @returns {Promise<string>} - selectedAddress the selected address.
|
||||
*/
|
||||
syncAddresses (addresses) {
|
||||
|
||||
syncAddresses(addresses) {
|
||||
if (!Array.isArray(addresses) || addresses.length === 0) {
|
||||
throw new Error('Expected non-empty array of addresses.')
|
||||
}
|
||||
@ -332,7 +338,6 @@ export default class PreferencesController {
|
||||
|
||||
// Identities are no longer present.
|
||||
if (Object.keys(newlyLost).length > 0) {
|
||||
|
||||
// store lost accounts
|
||||
Object.keys(newlyLost).forEach((key) => {
|
||||
lostIdentities[key] = newlyLost[key]
|
||||
@ -353,7 +358,7 @@ export default class PreferencesController {
|
||||
return selected
|
||||
}
|
||||
|
||||
removeSuggestedTokens () {
|
||||
removeSuggestedTokens() {
|
||||
return new Promise((resolve) => {
|
||||
this.store.updateState({ suggestedTokens: {} })
|
||||
resolve({})
|
||||
@ -367,7 +372,7 @@ export default class PreferencesController {
|
||||
* @returns {Promise<void>} - Promise resolves with tokens
|
||||
*
|
||||
*/
|
||||
setSelectedAddress (_address) {
|
||||
setSelectedAddress(_address) {
|
||||
const address = normalizeAddress(_address)
|
||||
this._updateTokens(address)
|
||||
|
||||
@ -388,7 +393,7 @@ export default class PreferencesController {
|
||||
* @returns {string} - The hex address for the currently selected account
|
||||
*
|
||||
*/
|
||||
getSelectedAddress () {
|
||||
getSelectedAddress() {
|
||||
return this.store.getState().selectedAddress
|
||||
}
|
||||
|
||||
@ -413,7 +418,7 @@ export default class PreferencesController {
|
||||
* @returns {Promise<array>} - Promises the new array of AddedToken objects.
|
||||
*
|
||||
*/
|
||||
async addToken (rawAddress, symbol, decimals, image) {
|
||||
async addToken(rawAddress, symbol, decimals, image) {
|
||||
const address = normalizeAddress(rawAddress)
|
||||
const newEntry = { address, symbol, decimals }
|
||||
const { tokens } = this.store.getState()
|
||||
@ -440,7 +445,7 @@ export default class PreferencesController {
|
||||
* @returns {Promise<array>} - The new array of AddedToken objects
|
||||
*
|
||||
*/
|
||||
removeToken (rawAddress) {
|
||||
removeToken(rawAddress) {
|
||||
const { tokens } = this.store.getState()
|
||||
const assetImages = this.getAssetImages()
|
||||
const updatedTokens = tokens.filter((token) => token.address !== rawAddress)
|
||||
@ -455,7 +460,7 @@ export default class PreferencesController {
|
||||
* @returns {array} - The current array of AddedToken objects
|
||||
*
|
||||
*/
|
||||
getTokens () {
|
||||
getTokens() {
|
||||
return this.store.getState().tokens
|
||||
}
|
||||
|
||||
@ -465,9 +470,11 @@ export default class PreferencesController {
|
||||
* @param {string} label - the custom label for the account
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
setAccountLabel (account, label) {
|
||||
setAccountLabel(account, label) {
|
||||
if (!account) {
|
||||
throw new Error(`setAccountLabel requires a valid address, got ${String(account)}`)
|
||||
throw new Error(
|
||||
`setAccountLabel requires a valid address, got ${String(account)}`,
|
||||
)
|
||||
}
|
||||
const address = normalizeAddress(account)
|
||||
const { identities } = this.store.getState()
|
||||
@ -488,7 +495,7 @@ export default class PreferencesController {
|
||||
* @param {Object} [newRpcDetails.rpcPrefs] - Optional RPC preferences, such as the block explorer URL
|
||||
*
|
||||
*/
|
||||
async updateRpc (newRpcDetails) {
|
||||
async updateRpc(newRpcDetails) {
|
||||
const rpcList = this.getFrequentRpcListDetail()
|
||||
const index = rpcList.findIndex((element) => {
|
||||
return element.rpcUrl === newRpcDetails.rpcUrl
|
||||
@ -497,7 +504,6 @@ export default class PreferencesController {
|
||||
const rpcDetail = rpcList[index]
|
||||
const updatedRpc = { ...rpcDetail, ...newRpcDetails }
|
||||
if (rpcDetail.chainId !== updatedRpc.chainId) {
|
||||
|
||||
// When the chainId is changed, associated address book entries should
|
||||
// also be migrated. The address book entries are keyed by the `network` state,
|
||||
// which for custom networks is the chainId with a fallback to the networkId
|
||||
@ -506,13 +512,17 @@ export default class PreferencesController {
|
||||
let addressBookKey = rpcDetail.chainId
|
||||
if (!addressBookKey) {
|
||||
// We need to find the networkId to determine what these addresses were keyed by
|
||||
const provider = new ethers.providers.JsonRpcProvider(rpcDetail.rpcUrl)
|
||||
const provider = new ethers.providers.JsonRpcProvider(
|
||||
rpcDetail.rpcUrl,
|
||||
)
|
||||
try {
|
||||
addressBookKey = await provider.send('net_version')
|
||||
assert(typeof addressBookKey === 'string')
|
||||
} catch (error) {
|
||||
log.debug(error)
|
||||
log.warn(`Failed to get networkId from ${rpcDetail.rpcUrl}; skipping address book migration`)
|
||||
log.warn(
|
||||
`Failed to get networkId from ${rpcDetail.rpcUrl}; skipping address book migration`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -521,10 +531,12 @@ export default class PreferencesController {
|
||||
// on both networks, since we don't know which network each contact is intended for.
|
||||
|
||||
let duplicate = false
|
||||
const builtInProviderNetworkIds = Object.values(NETWORK_TYPE_TO_ID_MAP)
|
||||
.map((ids) => ids.networkId)
|
||||
const otherRpcEntries = rpcList
|
||||
.filter((entry) => entry.rpcUrl !== newRpcDetails.rpcUrl)
|
||||
const builtInProviderNetworkIds = Object.values(
|
||||
NETWORK_TYPE_TO_ID_MAP,
|
||||
).map((ids) => ids.networkId)
|
||||
const otherRpcEntries = rpcList.filter(
|
||||
(entry) => entry.rpcUrl !== newRpcDetails.rpcUrl,
|
||||
)
|
||||
if (
|
||||
builtInProviderNetworkIds.includes(addressBookKey) ||
|
||||
otherRpcEntries.some((entry) => entry.chainId === addressBookKey)
|
||||
@ -532,7 +544,11 @@ export default class PreferencesController {
|
||||
duplicate = true
|
||||
}
|
||||
|
||||
this.migrateAddressBookState(addressBookKey, updatedRpc.chainId, duplicate)
|
||||
this.migrateAddressBookState(
|
||||
addressBookKey,
|
||||
updatedRpc.chainId,
|
||||
duplicate,
|
||||
)
|
||||
}
|
||||
rpcList[index] = updatedRpc
|
||||
this.store.updateState({ frequentRpcListDetail: rpcList })
|
||||
@ -552,7 +568,13 @@ export default class PreferencesController {
|
||||
* @param {Object} [rpcPrefs] - Optional RPC preferences, such as the block explorer URL
|
||||
*
|
||||
*/
|
||||
addToFrequentRpcList (rpcUrl, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) {
|
||||
addToFrequentRpcList(
|
||||
rpcUrl,
|
||||
chainId,
|
||||
ticker = 'ETH',
|
||||
nickname = '',
|
||||
rpcPrefs = {},
|
||||
) {
|
||||
const rpcList = this.getFrequentRpcListDetail()
|
||||
|
||||
const index = rpcList.findIndex((element) => {
|
||||
@ -577,7 +599,7 @@ export default class PreferencesController {
|
||||
* @returns {Promise<array>} - Promise resolving to updated frequentRpcList.
|
||||
*
|
||||
*/
|
||||
removeFromFrequentRpcList (url) {
|
||||
removeFromFrequentRpcList(url) {
|
||||
const rpcList = this.getFrequentRpcListDetail()
|
||||
const index = rpcList.findIndex((element) => {
|
||||
return element.rpcUrl === url
|
||||
@ -595,7 +617,7 @@ export default class PreferencesController {
|
||||
* @returns {array<array>} - An array of rpc urls.
|
||||
*
|
||||
*/
|
||||
getFrequentRpcListDetail () {
|
||||
getFrequentRpcListDetail() {
|
||||
return this.store.getState().frequentRpcListDetail
|
||||
}
|
||||
|
||||
@ -607,7 +629,7 @@ export default class PreferencesController {
|
||||
* @returns {Promise<object>} - Promises a new object; the updated featureFlags object.
|
||||
*
|
||||
*/
|
||||
setFeatureFlag (feature, activated) {
|
||||
setFeatureFlag(feature, activated) {
|
||||
const currentFeatureFlags = this.store.getState().featureFlags
|
||||
const updatedFeatureFlags = {
|
||||
...currentFeatureFlags,
|
||||
@ -626,7 +648,7 @@ export default class PreferencesController {
|
||||
* @param {boolean} value - Indicates whether or not the preference should be enabled or disabled.
|
||||
* @returns {Promise<object>} - Promises a new object; the updated preferences object.
|
||||
*/
|
||||
setPreference (preference, value) {
|
||||
setPreference(preference, value) {
|
||||
const currentPreferences = this.getPreferences()
|
||||
const updatedPreferences = {
|
||||
...currentPreferences,
|
||||
@ -641,7 +663,7 @@ export default class PreferencesController {
|
||||
* A getter for the `preferences` property
|
||||
* @returns {Object} - A key-boolean map of user-selected preferences.
|
||||
*/
|
||||
getPreferences () {
|
||||
getPreferences() {
|
||||
return this.store.getState().preferences
|
||||
}
|
||||
|
||||
@ -649,7 +671,7 @@ export default class PreferencesController {
|
||||
* Sets the completedOnboarding state to true, indicating that the user has completed the
|
||||
* onboarding process.
|
||||
*/
|
||||
completeOnboarding () {
|
||||
completeOnboarding() {
|
||||
this.store.updateState({ completedOnboarding: true })
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
@ -658,7 +680,7 @@ export default class PreferencesController {
|
||||
* A getter for the `ipfsGateway` property
|
||||
* @returns {string} - The current IPFS gateway domain
|
||||
*/
|
||||
getIpfsGateway () {
|
||||
getIpfsGateway() {
|
||||
return this.store.getState().ipfsGateway
|
||||
}
|
||||
|
||||
@ -667,7 +689,7 @@ export default class PreferencesController {
|
||||
* @param {string} domain - The new IPFS gateway domain
|
||||
* @returns {Promise<string>} - A promise of the update IPFS gateway domain
|
||||
*/
|
||||
setIpfsGateway (domain) {
|
||||
setIpfsGateway(domain) {
|
||||
this.store.updateState({ ipfsGateway: domain })
|
||||
return Promise.resolve(domain)
|
||||
}
|
||||
@ -681,7 +703,7 @@ export default class PreferencesController {
|
||||
*
|
||||
*
|
||||
*/
|
||||
_subscribeProviderType () {
|
||||
_subscribeProviderType() {
|
||||
this.network.providerStore.subscribe(() => {
|
||||
const { tokens } = this._getTokenRelatedStates()
|
||||
this.store.updateState({ tokens })
|
||||
@ -694,8 +716,12 @@ export default class PreferencesController {
|
||||
* @param {array} tokens - Array of tokens to be updated.
|
||||
*
|
||||
*/
|
||||
_updateAccountTokens (tokens, assetImages) {
|
||||
const { accountTokens, providerType, selectedAddress } = this._getTokenRelatedStates()
|
||||
_updateAccountTokens(tokens, assetImages) {
|
||||
const {
|
||||
accountTokens,
|
||||
providerType,
|
||||
selectedAddress,
|
||||
} = this._getTokenRelatedStates()
|
||||
accountTokens[selectedAddress][providerType] = tokens
|
||||
this.store.updateState({ accountTokens, tokens, assetImages })
|
||||
}
|
||||
@ -706,7 +732,7 @@ export default class PreferencesController {
|
||||
* @param {string} selectedAddress - Account address to be updated with.
|
||||
*
|
||||
*/
|
||||
_updateTokens (selectedAddress) {
|
||||
_updateTokens(selectedAddress) {
|
||||
const { tokens } = this._getTokenRelatedStates(selectedAddress)
|
||||
this.store.updateState({ tokens })
|
||||
}
|
||||
@ -718,7 +744,7 @@ export default class PreferencesController {
|
||||
* @returns {Object.<array, object, string, string>} - States to interact with tokens in `accountTokens`
|
||||
*
|
||||
*/
|
||||
_getTokenRelatedStates (selectedAddress) {
|
||||
_getTokenRelatedStates(selectedAddress) {
|
||||
const { accountTokens } = this.store.getState()
|
||||
if (!selectedAddress) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
@ -741,7 +767,7 @@ export default class PreferencesController {
|
||||
* @param {Promise} promise - Promise according to addition of ERC20 token
|
||||
*
|
||||
*/
|
||||
async _handleWatchAssetERC20 (options) {
|
||||
async _handleWatchAssetERC20(options) {
|
||||
const { address, symbol, decimals, image } = options
|
||||
const rawAddress = address
|
||||
try {
|
||||
@ -752,7 +778,9 @@ export default class PreferencesController {
|
||||
const tokenOpts = { rawAddress, decimals, symbol, image }
|
||||
this.addSuggestedERC20Asset(tokenOpts)
|
||||
return this.openPopup().then(() => {
|
||||
const tokenAddresses = this.getTokens().filter((token) => token.address === normalizeAddress(rawAddress))
|
||||
const tokenAddresses = this.getTokens().filter(
|
||||
(token) => token.address === normalizeAddress(rawAddress),
|
||||
)
|
||||
return tokenAddresses.length > 0
|
||||
})
|
||||
}
|
||||
@ -765,17 +793,21 @@ export default class PreferencesController {
|
||||
* doesn't fulfill requirements
|
||||
*
|
||||
*/
|
||||
_validateERC20AssetParams (opts) {
|
||||
_validateERC20AssetParams(opts) {
|
||||
const { rawAddress, symbol, decimals } = opts
|
||||
if (!rawAddress || !symbol || typeof decimals === 'undefined') {
|
||||
throw new Error(`Cannot suggest token without address, symbol, and decimals`)
|
||||
throw new Error(
|
||||
`Cannot suggest token without address, symbol, and decimals`,
|
||||
)
|
||||
}
|
||||
if (!(symbol.length < 7)) {
|
||||
throw new Error(`Invalid symbol ${symbol} more than six characters`)
|
||||
}
|
||||
const numDecimals = parseInt(decimals, 10)
|
||||
if (isNaN(numDecimals) || numDecimals > 36 || numDecimals < 0) {
|
||||
throw new Error(`Invalid decimals ${decimals} must be at least 0, and not over 36`)
|
||||
throw new Error(
|
||||
`Invalid decimals ${decimals} must be at least 0, and not over 36`,
|
||||
)
|
||||
}
|
||||
if (!isValidAddress(rawAddress)) {
|
||||
throw new Error(`Invalid address ${rawAddress}`)
|
||||
|
@ -28,17 +28,14 @@ const MAX_GAS_LIMIT = 2500000
|
||||
// 3 seems to be an appropriate balance of giving users the time they need when MetaMask is not left idle, and turning polling off when it is.
|
||||
const POLL_COUNT_LIMIT = 3
|
||||
|
||||
function calculateGasEstimateWithRefund (maxGas = MAX_GAS_LIMIT, estimatedRefund = 0, estimatedGas = 0) {
|
||||
const maxGasMinusRefund = new BigNumber(
|
||||
maxGas,
|
||||
10,
|
||||
)
|
||||
.minus(estimatedRefund, 10)
|
||||
function calculateGasEstimateWithRefund(
|
||||
maxGas = MAX_GAS_LIMIT,
|
||||
estimatedRefund = 0,
|
||||
estimatedGas = 0,
|
||||
) {
|
||||
const maxGasMinusRefund = new BigNumber(maxGas, 10).minus(estimatedRefund, 10)
|
||||
|
||||
const gasEstimateWithRefund = maxGasMinusRefund.lt(
|
||||
estimatedGas,
|
||||
16,
|
||||
)
|
||||
const gasEstimateWithRefund = maxGasMinusRefund.lt(estimatedGas, 16)
|
||||
? maxGasMinusRefund.toString(16)
|
||||
: estimatedGas
|
||||
|
||||
@ -68,7 +65,7 @@ const initialState = {
|
||||
}
|
||||
|
||||
export default class SwapsController {
|
||||
constructor ({
|
||||
constructor({
|
||||
getBufferedGasLimit,
|
||||
networkController,
|
||||
provider,
|
||||
@ -108,18 +105,26 @@ export default class SwapsController {
|
||||
// that quotes will no longer be available after 1 or 2 minutes. When fetchAndSetQuotes is first called it, receives fetch that parameters are stored in
|
||||
// state. These stored parameters are used on subsequent calls made during polling.
|
||||
// Note: we stop polling after 3 requests, until new quotes are explicitly asked for. The logic that enforces that maximum is in the body of fetchAndSetQuotes
|
||||
pollForNewQuotes () {
|
||||
pollForNewQuotes() {
|
||||
this.pollingTimeout = setTimeout(() => {
|
||||
const { swapsState } = this.store.getState()
|
||||
this.fetchAndSetQuotes(swapsState.fetchParams, swapsState.fetchParams?.metaData, true)
|
||||
this.fetchAndSetQuotes(
|
||||
swapsState.fetchParams,
|
||||
swapsState.fetchParams?.metaData,
|
||||
true,
|
||||
)
|
||||
}, QUOTE_POLLING_INTERVAL)
|
||||
}
|
||||
|
||||
stopPollingForQuotes () {
|
||||
stopPollingForQuotes() {
|
||||
clearTimeout(this.pollingTimeout)
|
||||
}
|
||||
|
||||
async fetchAndSetQuotes (fetchParams, fetchParamsMetaData = {}, isPolledRequest) {
|
||||
async fetchAndSetQuotes(
|
||||
fetchParams,
|
||||
fetchParamsMetaData = {},
|
||||
isPolledRequest,
|
||||
) {
|
||||
if (!fetchParams) {
|
||||
return null
|
||||
}
|
||||
@ -150,7 +155,10 @@ export default class SwapsController {
|
||||
const quotesLastFetched = Date.now()
|
||||
|
||||
let approvalRequired = false
|
||||
if (fetchParams.sourceToken !== ETH_SWAPS_TOKEN_ADDRESS && Object.values(newQuotes).length) {
|
||||
if (
|
||||
fetchParams.sourceToken !== ETH_SWAPS_TOKEN_ADDRESS &&
|
||||
Object.values(newQuotes).length
|
||||
) {
|
||||
const allowance = await this._getERC20Allowance(
|
||||
fetchParams.sourceToken,
|
||||
fetchParams.fromAddress,
|
||||
@ -167,7 +175,9 @@ export default class SwapsController {
|
||||
approvalNeeded: null,
|
||||
}))
|
||||
} else if (!isPolledRequest) {
|
||||
const { gasLimit: approvalGas } = await this.timedoutGasReturn(Object.values(newQuotes)[0].approvalNeeded)
|
||||
const { gasLimit: approvalGas } = await this.timedoutGasReturn(
|
||||
Object.values(newQuotes)[0].approvalNeeded,
|
||||
)
|
||||
|
||||
newQuotes = mapValues(newQuotes, (quote) => ({
|
||||
...quote,
|
||||
@ -190,7 +200,9 @@ export default class SwapsController {
|
||||
if (Object.values(newQuotes).length === 0) {
|
||||
this.setSwapsErrorKey(QUOTES_NOT_AVAILABLE_ERROR)
|
||||
} else {
|
||||
const topQuoteData = await this._findTopQuoteAndCalculateSavings(newQuotes)
|
||||
const topQuoteData = await this._findTopQuoteAndCalculateSavings(
|
||||
newQuotes,
|
||||
)
|
||||
|
||||
if (topQuoteData.topAggId) {
|
||||
topAggId = topQuoteData.topAggId
|
||||
@ -235,32 +247,34 @@ export default class SwapsController {
|
||||
return [newQuotes, topAggId]
|
||||
}
|
||||
|
||||
safeRefetchQuotes () {
|
||||
safeRefetchQuotes() {
|
||||
const { swapsState } = this.store.getState()
|
||||
if (!this.pollingTimeout && swapsState.fetchParams) {
|
||||
this.fetchAndSetQuotes(swapsState.fetchParams)
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedQuoteAggId (selectedAggId) {
|
||||
setSelectedQuoteAggId(selectedAggId) {
|
||||
const { swapsState } = this.store.getState()
|
||||
this.store.updateState({ swapsState: { ...swapsState, selectedAggId } })
|
||||
}
|
||||
|
||||
setSwapsTokens (tokens) {
|
||||
setSwapsTokens(tokens) {
|
||||
const { swapsState } = this.store.getState()
|
||||
this.store.updateState({ swapsState: { ...swapsState, tokens } })
|
||||
}
|
||||
|
||||
setSwapsErrorKey (errorKey) {
|
||||
setSwapsErrorKey(errorKey) {
|
||||
const { swapsState } = this.store.getState()
|
||||
this.store.updateState({ swapsState: { ...swapsState, errorKey } })
|
||||
}
|
||||
|
||||
async getAllQuotesWithGasEstimates (quotes) {
|
||||
async getAllQuotesWithGasEstimates(quotes) {
|
||||
const quoteGasData = await Promise.all(
|
||||
Object.values(quotes).map(async (quote) => {
|
||||
const { gasLimit, simulationFails } = await this.timedoutGasReturn(quote.trade)
|
||||
const { gasLimit, simulationFails } = await this.timedoutGasReturn(
|
||||
quote.trade,
|
||||
)
|
||||
return [gasLimit, simulationFails, quote.aggregator]
|
||||
}),
|
||||
)
|
||||
@ -268,7 +282,11 @@ export default class SwapsController {
|
||||
const newQuotes = {}
|
||||
quoteGasData.forEach(([gasLimit, simulationFails, aggId]) => {
|
||||
if (gasLimit && !simulationFails) {
|
||||
const gasEstimateWithRefund = calculateGasEstimateWithRefund(quotes[aggId].maxGas, quotes[aggId].estimatedRefund, gasLimit)
|
||||
const gasEstimateWithRefund = calculateGasEstimateWithRefund(
|
||||
quotes[aggId].maxGas,
|
||||
quotes[aggId].estimatedRefund,
|
||||
gasLimit,
|
||||
)
|
||||
|
||||
newQuotes[aggId] = {
|
||||
...quotes[aggId],
|
||||
@ -285,7 +303,7 @@ export default class SwapsController {
|
||||
return newQuotes
|
||||
}
|
||||
|
||||
timedoutGasReturn (tradeTxParams) {
|
||||
timedoutGasReturn(tradeTxParams) {
|
||||
return new Promise((resolve) => {
|
||||
let gasTimedOut = false
|
||||
|
||||
@ -321,7 +339,7 @@ export default class SwapsController {
|
||||
})
|
||||
}
|
||||
|
||||
async setInitialGasEstimate (initialAggId) {
|
||||
async setInitialGasEstimate(initialAggId) {
|
||||
const { swapsState } = this.store.getState()
|
||||
|
||||
const quoteToUpdate = { ...swapsState.quotes[initialAggId] }
|
||||
@ -332,64 +350,73 @@ export default class SwapsController {
|
||||
} = await this.timedoutGasReturn(quoteToUpdate.trade)
|
||||
|
||||
if (newGasEstimate && !simulationFails) {
|
||||
const gasEstimateWithRefund = calculateGasEstimateWithRefund(quoteToUpdate.maxGas, quoteToUpdate.estimatedRefund, newGasEstimate)
|
||||
const gasEstimateWithRefund = calculateGasEstimateWithRefund(
|
||||
quoteToUpdate.maxGas,
|
||||
quoteToUpdate.estimatedRefund,
|
||||
newGasEstimate,
|
||||
)
|
||||
|
||||
quoteToUpdate.gasEstimate = newGasEstimate
|
||||
quoteToUpdate.gasEstimateWithRefund = gasEstimateWithRefund
|
||||
}
|
||||
|
||||
this.store.updateState({
|
||||
swapsState: { ...swapsState, quotes: { ...swapsState.quotes, [initialAggId]: quoteToUpdate } },
|
||||
swapsState: {
|
||||
...swapsState,
|
||||
quotes: { ...swapsState.quotes, [initialAggId]: quoteToUpdate },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
setApproveTxId (approveTxId) {
|
||||
setApproveTxId(approveTxId) {
|
||||
const { swapsState } = this.store.getState()
|
||||
this.store.updateState({ swapsState: { ...swapsState, approveTxId } })
|
||||
}
|
||||
|
||||
setTradeTxId (tradeTxId) {
|
||||
setTradeTxId(tradeTxId) {
|
||||
const { swapsState } = this.store.getState()
|
||||
this.store.updateState({ swapsState: { ...swapsState, tradeTxId } })
|
||||
}
|
||||
|
||||
setQuotesLastFetched (quotesLastFetched) {
|
||||
setQuotesLastFetched(quotesLastFetched) {
|
||||
const { swapsState } = this.store.getState()
|
||||
this.store.updateState({ swapsState: { ...swapsState, quotesLastFetched } })
|
||||
}
|
||||
|
||||
setSwapsTxGasPrice (gasPrice) {
|
||||
setSwapsTxGasPrice(gasPrice) {
|
||||
const { swapsState } = this.store.getState()
|
||||
this.store.updateState({
|
||||
swapsState: { ...swapsState, customGasPrice: gasPrice },
|
||||
})
|
||||
}
|
||||
|
||||
setSwapsTxGasLimit (gasLimit) {
|
||||
setSwapsTxGasLimit(gasLimit) {
|
||||
const { swapsState } = this.store.getState()
|
||||
this.store.updateState({
|
||||
swapsState: { ...swapsState, customMaxGas: gasLimit },
|
||||
})
|
||||
}
|
||||
|
||||
setCustomApproveTxData (data) {
|
||||
setCustomApproveTxData(data) {
|
||||
const { swapsState } = this.store.getState()
|
||||
this.store.updateState({
|
||||
swapsState: { ...swapsState, customApproveTxData: data },
|
||||
})
|
||||
}
|
||||
|
||||
setBackgroundSwapRouteState (routeState) {
|
||||
setBackgroundSwapRouteState(routeState) {
|
||||
const { swapsState } = this.store.getState()
|
||||
this.store.updateState({ swapsState: { ...swapsState, routeState } })
|
||||
}
|
||||
|
||||
setSwapsLiveness (swapsFeatureIsLive) {
|
||||
setSwapsLiveness(swapsFeatureIsLive) {
|
||||
const { swapsState } = this.store.getState()
|
||||
this.store.updateState({ swapsState: { ...swapsState, swapsFeatureIsLive } })
|
||||
this.store.updateState({
|
||||
swapsState: { ...swapsState, swapsFeatureIsLive },
|
||||
})
|
||||
}
|
||||
|
||||
resetPostFetchState () {
|
||||
resetPostFetchState() {
|
||||
const { swapsState } = this.store.getState()
|
||||
|
||||
this.store.updateState({
|
||||
@ -403,21 +430,25 @@ export default class SwapsController {
|
||||
clearTimeout(this.pollingTimeout)
|
||||
}
|
||||
|
||||
resetSwapsState () {
|
||||
resetSwapsState() {
|
||||
const { swapsState } = this.store.getState()
|
||||
|
||||
this.store.updateState({
|
||||
swapsState: { ...initialState.swapsState, tokens: swapsState.tokens, swapsFeatureIsLive: swapsState.swapsFeatureIsLive },
|
||||
swapsState: {
|
||||
...initialState.swapsState,
|
||||
tokens: swapsState.tokens,
|
||||
swapsFeatureIsLive: swapsState.swapsFeatureIsLive,
|
||||
},
|
||||
})
|
||||
clearTimeout(this.pollingTimeout)
|
||||
}
|
||||
|
||||
async _getEthersGasPrice () {
|
||||
async _getEthersGasPrice() {
|
||||
const ethersGasPrice = await this.ethersProvider.getGasPrice()
|
||||
return ethersGasPrice.toHexString()
|
||||
}
|
||||
|
||||
async _findTopQuoteAndCalculateSavings (quotes = {}) {
|
||||
async _findTopQuoteAndCalculateSavings(quotes = {}) {
|
||||
const tokenConversionRates = this.tokenRatesStore.getState()
|
||||
.contractExchangeRates
|
||||
const {
|
||||
@ -429,7 +460,7 @@ export default class SwapsController {
|
||||
return {}
|
||||
}
|
||||
|
||||
const usedGasPrice = customGasPrice || await this._getEthersGasPrice()
|
||||
const usedGasPrice = customGasPrice || (await this._getEthersGasPrice())
|
||||
|
||||
let topAggId = ''
|
||||
let ethTradeValueOfBestQuote = null
|
||||
@ -468,8 +499,10 @@ export default class SwapsController {
|
||||
// It always includes any external fees charged by the quote source. In
|
||||
// addition, if the source asset is ETH, trade.value includes the amount
|
||||
// of swapped ETH.
|
||||
const totalWeiCost = new BigNumber(gasTotalInWeiHex, 16)
|
||||
.plus(trade.value, 16)
|
||||
const totalWeiCost = new BigNumber(gasTotalInWeiHex, 16).plus(
|
||||
trade.value,
|
||||
16,
|
||||
)
|
||||
|
||||
const totalEthCost = conversionUtil(totalWeiCost, {
|
||||
fromCurrency: 'ETH',
|
||||
@ -482,32 +515,33 @@ export default class SwapsController {
|
||||
// The total fee is aggregator/exchange fees plus gas fees.
|
||||
// If the swap is from ETH, subtract the sourceAmount from the total cost.
|
||||
// Otherwise, the total fee is simply trade.value plus gas fees.
|
||||
const ethFee = sourceToken === ETH_SWAPS_TOKEN_ADDRESS
|
||||
? conversionUtil(
|
||||
totalWeiCost.minus(sourceAmount, 10), // sourceAmount is in wei
|
||||
{
|
||||
fromCurrency: 'ETH',
|
||||
fromDenomination: 'WEI',
|
||||
toDenomination: 'ETH',
|
||||
fromNumericBase: 'BN',
|
||||
numberOfDecimals: 6,
|
||||
},
|
||||
)
|
||||
: totalEthCost
|
||||
const ethFee =
|
||||
sourceToken === ETH_SWAPS_TOKEN_ADDRESS
|
||||
? conversionUtil(
|
||||
totalWeiCost.minus(sourceAmount, 10), // sourceAmount is in wei
|
||||
{
|
||||
fromCurrency: 'ETH',
|
||||
fromDenomination: 'WEI',
|
||||
toDenomination: 'ETH',
|
||||
fromNumericBase: 'BN',
|
||||
numberOfDecimals: 6,
|
||||
},
|
||||
)
|
||||
: totalEthCost
|
||||
|
||||
const tokenConversionRate = tokenConversionRates[destinationToken]
|
||||
const ethValueOfTrade =
|
||||
destinationToken === ETH_SWAPS_TOKEN_ADDRESS
|
||||
? calcTokenAmount(destinationAmount, 18).minus(totalEthCost, 10)
|
||||
: new BigNumber(tokenConversionRate || 1, 10)
|
||||
.times(
|
||||
calcTokenAmount(
|
||||
destinationAmount,
|
||||
destinationTokenInfo.decimals,
|
||||
),
|
||||
10,
|
||||
)
|
||||
.minus(tokenConversionRate ? totalEthCost : 0, 10)
|
||||
.times(
|
||||
calcTokenAmount(
|
||||
destinationAmount,
|
||||
destinationTokenInfo.decimals,
|
||||
),
|
||||
10,
|
||||
)
|
||||
.minus(tokenConversionRate ? totalEthCost : 0, 10)
|
||||
|
||||
// collect values for savings calculation
|
||||
allEthTradeValues.push(ethValueOfTrade)
|
||||
@ -540,10 +574,7 @@ export default class SwapsController {
|
||||
|
||||
// Performance savings are calculated as:
|
||||
// medianFeeOfAllTrades - feeForBestTrade
|
||||
savings.fee = getMedian(allEthFees).minus(
|
||||
ethFeeForBestQuote,
|
||||
10,
|
||||
)
|
||||
savings.fee = getMedian(allEthFees).minus(ethFeeForBestQuote, 10)
|
||||
|
||||
// Total savings are the sum of performance and fee savings
|
||||
savings.total = savings.performance.plus(savings.fee, 10).toString(10)
|
||||
@ -554,9 +585,11 @@ export default class SwapsController {
|
||||
return { topAggId, isBest, savings }
|
||||
}
|
||||
|
||||
async _getERC20Allowance (contractAddress, walletAddress) {
|
||||
async _getERC20Allowance(contractAddress, walletAddress) {
|
||||
const contract = new ethers.Contract(
|
||||
contractAddress, abi, this.ethersProvider,
|
||||
contractAddress,
|
||||
abi,
|
||||
this.ethersProvider,
|
||||
)
|
||||
return await contract.allowance(walletAddress, METASWAP_ADDRESS)
|
||||
}
|
||||
@ -569,7 +602,7 @@ export default class SwapsController {
|
||||
* If the browser goes offline, the interval is cleared and swaps are disabled
|
||||
* until the value can be fetched again.
|
||||
*/
|
||||
_setupSwapsLivenessFetching () {
|
||||
_setupSwapsLivenessFetching() {
|
||||
const TEN_MINUTES_MS = 10 * 60 * 1000
|
||||
let intervalId = null
|
||||
|
||||
@ -577,7 +610,10 @@ export default class SwapsController {
|
||||
if (window.navigator.onLine && intervalId === null) {
|
||||
// Set the interval first to prevent race condition between listener and
|
||||
// initial call to this function.
|
||||
intervalId = setInterval(this._fetchAndSetSwapsLiveness.bind(this), TEN_MINUTES_MS)
|
||||
intervalId = setInterval(
|
||||
this._fetchAndSetSwapsLiveness.bind(this),
|
||||
TEN_MINUTES_MS,
|
||||
)
|
||||
this._fetchAndSetSwapsLiveness()
|
||||
}
|
||||
}
|
||||
@ -608,7 +644,7 @@ export default class SwapsController {
|
||||
* Only updates state if the fetched/computed flag value differs from current
|
||||
* state.
|
||||
*/
|
||||
async _fetchAndSetSwapsLiveness () {
|
||||
async _fetchAndSetSwapsLiveness() {
|
||||
const { swapsState } = this.store.getState()
|
||||
const { swapsFeatureIsLive: oldSwapsFeatureIsLive } = swapsState
|
||||
let swapsFeatureIsLive = false
|
||||
@ -637,7 +673,9 @@ export default class SwapsController {
|
||||
}
|
||||
|
||||
if (!successfullyFetched) {
|
||||
log.error('Failed to fetch swaps feature flag 3 times. Setting to false and trying again next interval.')
|
||||
log.error(
|
||||
'Failed to fetch swaps feature flag 3 times. Setting to false and trying again next interval.',
|
||||
)
|
||||
}
|
||||
|
||||
if (swapsFeatureIsLive !== oldSwapsFeatureIsLive) {
|
||||
@ -653,7 +691,7 @@ export default class SwapsController {
|
||||
* values. The array will be sorted in place.
|
||||
* @returns {import('bignumber.js').BigNumber} The median of the sample.
|
||||
*/
|
||||
function getMedian (values) {
|
||||
function getMedian(values) {
|
||||
if (!Array.isArray(values) || values.length === 0) {
|
||||
throw new Error('Expected non-empty array param.')
|
||||
}
|
||||
@ -672,9 +710,7 @@ function getMedian (values) {
|
||||
|
||||
// return mean of middle two values
|
||||
const upperIndex = values.length / 2
|
||||
return values[upperIndex]
|
||||
.plus(values[upperIndex - 1])
|
||||
.dividedBy(2)
|
||||
return values[upperIndex].plus(values[upperIndex - 1]).dividedBy(2)
|
||||
}
|
||||
|
||||
export const utils = {
|
||||
|
@ -18,7 +18,7 @@ import createMetamaskMiddleware from './network/createMetamaskMiddleware'
|
||||
const SYNC_TIMEOUT = 60 * 1000 // one minute
|
||||
|
||||
export default class ThreeBoxController {
|
||||
constructor (opts = {}) {
|
||||
constructor(opts = {}) {
|
||||
const {
|
||||
preferencesController,
|
||||
keyringController,
|
||||
@ -41,16 +41,22 @@ export default class ThreeBoxController {
|
||||
const accounts = await this.keyringController.getAccounts()
|
||||
|
||||
if (isUnlocked && accounts[0]) {
|
||||
const appKeyAddress = await this.keyringController.getAppKeyAddress(accounts[0], 'wallet://3box.metamask.io')
|
||||
const appKeyAddress = await this.keyringController.getAppKeyAddress(
|
||||
accounts[0],
|
||||
'wallet://3box.metamask.io',
|
||||
)
|
||||
return [appKeyAddress]
|
||||
}
|
||||
return []
|
||||
},
|
||||
processPersonalMessage: async (msgParams) => {
|
||||
const accounts = await this.keyringController.getAccounts()
|
||||
return keyringController.signPersonalMessage({ ...msgParams, from: accounts[0] }, {
|
||||
withAppKeyOrigin: 'wallet://3box.metamask.io',
|
||||
})
|
||||
return keyringController.signPersonalMessage(
|
||||
{ ...msgParams, from: accounts[0] },
|
||||
{
|
||||
withAppKeyOrigin: 'wallet://3box.metamask.io',
|
||||
},
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
@ -65,14 +71,16 @@ export default class ThreeBoxController {
|
||||
}
|
||||
this.store = new ObservableStore(initState)
|
||||
this.registeringUpdates = false
|
||||
this.lastMigration = migrations.sort((a, b) => a.version - b.version).slice(-1)[0]
|
||||
this.lastMigration = migrations
|
||||
.sort((a, b) => a.version - b.version)
|
||||
.slice(-1)[0]
|
||||
|
||||
if (initState.threeBoxSyncingAllowed) {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
|
||||
async init () {
|
||||
async init() {
|
||||
const accounts = await this.keyringController.getAccounts()
|
||||
this.address = accounts[0]
|
||||
if (this.address && !(this.box && this.store.getState().threeBoxSynced)) {
|
||||
@ -80,7 +88,7 @@ export default class ThreeBoxController {
|
||||
}
|
||||
}
|
||||
|
||||
async _update3Box () {
|
||||
async _update3Box() {
|
||||
try {
|
||||
const { threeBoxSyncingAllowed, threeBoxSynced } = this.store.getState()
|
||||
if (threeBoxSyncingAllowed && threeBoxSynced) {
|
||||
@ -99,7 +107,7 @@ export default class ThreeBoxController {
|
||||
}
|
||||
}
|
||||
|
||||
_createProvider (providerOpts) {
|
||||
_createProvider(providerOpts) {
|
||||
const metamaskMiddleware = createMetamaskMiddleware(providerOpts)
|
||||
const engine = new JsonRpcEngine()
|
||||
engine.push(createOriginMiddleware({ origin: '3Box' }))
|
||||
@ -108,7 +116,7 @@ export default class ThreeBoxController {
|
||||
return provider
|
||||
}
|
||||
|
||||
_waitForOnSyncDone () {
|
||||
_waitForOnSyncDone() {
|
||||
return new Promise((resolve) => {
|
||||
this.box.onSyncDone(() => {
|
||||
log.debug('3Box box sync done')
|
||||
@ -117,9 +125,12 @@ export default class ThreeBoxController {
|
||||
})
|
||||
}
|
||||
|
||||
async new3Box () {
|
||||
async new3Box() {
|
||||
const accounts = await this.keyringController.getAccounts()
|
||||
this.address = await this.keyringController.getAppKeyAddress(accounts[0], 'wallet://3box.metamask.io')
|
||||
this.address = await this.keyringController.getAppKeyAddress(
|
||||
accounts[0],
|
||||
'wallet://3box.metamask.io',
|
||||
)
|
||||
let backupExists
|
||||
try {
|
||||
const threeBoxConfig = await Box.getConfig(this.address)
|
||||
@ -170,20 +181,22 @@ export default class ThreeBoxController {
|
||||
}
|
||||
}
|
||||
|
||||
async getLastUpdated () {
|
||||
async getLastUpdated() {
|
||||
const res = await this.space.private.get('metamaskBackup')
|
||||
const parsedRes = JSON.parse(res || '{}')
|
||||
return parsedRes.lastUpdated
|
||||
}
|
||||
|
||||
async migrateBackedUpState (backedUpState) {
|
||||
async migrateBackedUpState(backedUpState) {
|
||||
const migrator = new Migrator({ migrations })
|
||||
const { preferences, addressBook } = JSON.parse(backedUpState)
|
||||
const formattedStateBackup = {
|
||||
PreferencesController: preferences,
|
||||
AddressBookController: addressBook,
|
||||
}
|
||||
const initialMigrationState = migrator.generateInitialState(formattedStateBackup)
|
||||
const initialMigrationState = migrator.generateInitialState(
|
||||
formattedStateBackup,
|
||||
)
|
||||
const migratedState = await migrator.migrateData(initialMigrationState)
|
||||
return {
|
||||
preferences: migratedState.data.PreferencesController,
|
||||
@ -191,31 +204,30 @@ export default class ThreeBoxController {
|
||||
}
|
||||
}
|
||||
|
||||
async restoreFromThreeBox () {
|
||||
async restoreFromThreeBox() {
|
||||
const backedUpState = await this.space.private.get('metamaskBackup')
|
||||
const {
|
||||
preferences,
|
||||
addressBook,
|
||||
} = await this.migrateBackedUpState(backedUpState)
|
||||
const { preferences, addressBook } = await this.migrateBackedUpState(
|
||||
backedUpState,
|
||||
)
|
||||
this.store.updateState({ threeBoxLastUpdated: backedUpState.lastUpdated })
|
||||
preferences && this.preferencesController.store.updateState(preferences)
|
||||
addressBook && this.addressBookController.update(addressBook, true)
|
||||
this.setShowRestorePromptToFalse()
|
||||
}
|
||||
|
||||
turnThreeBoxSyncingOn () {
|
||||
turnThreeBoxSyncingOn() {
|
||||
this._registerUpdates()
|
||||
}
|
||||
|
||||
turnThreeBoxSyncingOff () {
|
||||
turnThreeBoxSyncingOff() {
|
||||
this.box.logout()
|
||||
}
|
||||
|
||||
setShowRestorePromptToFalse () {
|
||||
setShowRestorePromptToFalse() {
|
||||
this.store.updateState({ showRestorePrompt: false })
|
||||
}
|
||||
|
||||
setThreeBoxSyncingPermission (newThreeboxSyncingState) {
|
||||
setThreeBoxSyncingPermission(newThreeboxSyncingState) {
|
||||
if (this.store.getState().threeBoxDisabled) {
|
||||
return
|
||||
}
|
||||
@ -232,11 +244,11 @@ export default class ThreeBoxController {
|
||||
}
|
||||
}
|
||||
|
||||
getThreeBoxSyncingState () {
|
||||
getThreeBoxSyncingState() {
|
||||
return this.store.getState().threeBoxSyncingAllowed
|
||||
}
|
||||
|
||||
_registerUpdates () {
|
||||
_registerUpdates() {
|
||||
if (!this.registeringUpdates) {
|
||||
const updatePreferences = this._update3Box.bind(this)
|
||||
this.preferencesController.store.subscribe(updatePreferences)
|
||||
|
@ -11,13 +11,12 @@ const DEFAULT_INTERVAL = 180 * 1000
|
||||
* rates based on a user's current token list
|
||||
*/
|
||||
export default class TokenRatesController {
|
||||
|
||||
/**
|
||||
* Creates a TokenRatesController
|
||||
*
|
||||
* @param {Object} [config] - Options to configure controller
|
||||
*/
|
||||
constructor ({ currency, preferences } = {}) {
|
||||
constructor({ currency, preferences } = {}) {
|
||||
this.store = new ObservableStore()
|
||||
this.currency = currency
|
||||
this.preferences = preferences
|
||||
@ -26,21 +25,32 @@ export default class TokenRatesController {
|
||||
/**
|
||||
* Updates exchange rates for all tokens
|
||||
*/
|
||||
async updateExchangeRates () {
|
||||
async updateExchangeRates() {
|
||||
const contractExchangeRates = {}
|
||||
const nativeCurrency = this.currency ? this.currency.state.nativeCurrency.toLowerCase() : 'eth'
|
||||
const nativeCurrency = this.currency
|
||||
? this.currency.state.nativeCurrency.toLowerCase()
|
||||
: 'eth'
|
||||
const pairs = this._tokens.map((token) => token.address).join(',')
|
||||
const query = `contract_addresses=${pairs}&vs_currencies=${nativeCurrency}`
|
||||
if (this._tokens.length > 0) {
|
||||
try {
|
||||
const response = await window.fetch(`https://api.coingecko.com/api/v3/simple/token_price/ethereum?${query}`)
|
||||
const response = await window.fetch(
|
||||
`https://api.coingecko.com/api/v3/simple/token_price/ethereum?${query}`,
|
||||
)
|
||||
const prices = await response.json()
|
||||
this._tokens.forEach((token) => {
|
||||
const price = prices[token.address.toLowerCase()] || prices[ethUtil.toChecksumAddress(token.address)]
|
||||
contractExchangeRates[normalizeAddress(token.address)] = price ? price[nativeCurrency] : 0
|
||||
const price =
|
||||
prices[token.address.toLowerCase()] ||
|
||||
prices[ethUtil.toChecksumAddress(token.address)]
|
||||
contractExchangeRates[normalizeAddress(token.address)] = price
|
||||
? price[nativeCurrency]
|
||||
: 0
|
||||
})
|
||||
} catch (error) {
|
||||
log.warn(`MetaMask - TokenRatesController exchange rate fetch failed.`, error)
|
||||
log.warn(
|
||||
`MetaMask - TokenRatesController exchange rate fetch failed.`,
|
||||
error,
|
||||
)
|
||||
}
|
||||
}
|
||||
this.store.putState({ contractExchangeRates })
|
||||
@ -50,7 +60,7 @@ export default class TokenRatesController {
|
||||
/**
|
||||
* @type {Object}
|
||||
*/
|
||||
set preferences (preferences) {
|
||||
set preferences(preferences) {
|
||||
this._preferences && this._preferences.unsubscribe()
|
||||
if (!preferences) {
|
||||
return
|
||||
@ -65,13 +75,13 @@ export default class TokenRatesController {
|
||||
/**
|
||||
* @type {Array}
|
||||
*/
|
||||
set tokens (tokens) {
|
||||
set tokens(tokens) {
|
||||
this._tokens = tokens
|
||||
this.updateExchangeRates()
|
||||
}
|
||||
/* eslint-enable accessor-pairs */
|
||||
|
||||
start (interval = DEFAULT_INTERVAL) {
|
||||
start(interval = DEFAULT_INTERVAL) {
|
||||
this._handle && clearInterval(this._handle)
|
||||
if (!interval) {
|
||||
return
|
||||
@ -82,7 +92,7 @@ export default class TokenRatesController {
|
||||
this.updateExchangeRates()
|
||||
}
|
||||
|
||||
stop () {
|
||||
stop() {
|
||||
this._handle && clearInterval(this._handle)
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ const MAX_MEMSTORE_TX_LIST_SIZE = 100 // Number of transactions (by unique nonce
|
||||
*/
|
||||
|
||||
export default class TransactionController extends EventEmitter {
|
||||
constructor (opts) {
|
||||
constructor(opts) {
|
||||
super()
|
||||
this.networkStore = opts.networkStore || new ObservableStore({})
|
||||
this._getCurrentChainId = opts.getCurrentChainId
|
||||
@ -95,8 +95,12 @@ export default class TransactionController extends EventEmitter {
|
||||
this.nonceTracker = new NonceTracker({
|
||||
provider: this.provider,
|
||||
blockTracker: this.blockTracker,
|
||||
getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
|
||||
getConfirmedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager),
|
||||
getPendingTransactions: this.txStateManager.getPendingTransactions.bind(
|
||||
this.txStateManager,
|
||||
),
|
||||
getConfirmedTransactions: this.txStateManager.getConfirmedTransactions.bind(
|
||||
this.txStateManager,
|
||||
),
|
||||
})
|
||||
|
||||
this.pendingTxTracker = new PendingTransactionTracker({
|
||||
@ -109,7 +113,9 @@ export default class TransactionController extends EventEmitter {
|
||||
return [...pending, ...approved]
|
||||
},
|
||||
approveTransaction: this.approveTransaction.bind(this),
|
||||
getCompletedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager),
|
||||
getCompletedTransactions: this.txStateManager.getConfirmedTransactions.bind(
|
||||
this.txStateManager,
|
||||
),
|
||||
})
|
||||
|
||||
this.txStateManager.store.subscribe(() => this.emit('update:badge'))
|
||||
@ -132,7 +138,7 @@ export default class TransactionController extends EventEmitter {
|
||||
*
|
||||
* @returns {number} The numerical chainId.
|
||||
*/
|
||||
getChainId () {
|
||||
getChainId() {
|
||||
const networkState = this.networkStore.getState()
|
||||
const chainId = this._getCurrentChainId()
|
||||
const integerChainId = parseInt(chainId, 16)
|
||||
@ -146,7 +152,7 @@ export default class TransactionController extends EventEmitter {
|
||||
Adds a tx to the txlist
|
||||
@emits ${txMeta.id}:unapproved
|
||||
*/
|
||||
addTx (txMeta) {
|
||||
addTx(txMeta) {
|
||||
this.txStateManager.addTx(txMeta)
|
||||
this.emit(`${txMeta.id}:unapproved`, txMeta)
|
||||
}
|
||||
@ -155,37 +161,62 @@ export default class TransactionController extends EventEmitter {
|
||||
Wipes the transactions for a given account
|
||||
@param {string} address - hex string of the from address for txs being removed
|
||||
*/
|
||||
wipeTransactions (address) {
|
||||
wipeTransactions(address) {
|
||||
this.txStateManager.wipeTransactions(address)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new unapproved transaction to the pipeline
|
||||
*
|
||||
* @returns {Promise<string>} - the hash of the transaction after being submitted to the network
|
||||
* @param {Object} txParams - txParams for the transaction
|
||||
* @param {Object} opts - with the key origin to put the origin on the txMeta
|
||||
*/
|
||||
async newUnapprovedTransaction (txParams, opts = {}) {
|
||||
* Add a new unapproved transaction to the pipeline
|
||||
*
|
||||
* @returns {Promise<string>} - the hash of the transaction after being submitted to the network
|
||||
* @param {Object} txParams - txParams for the transaction
|
||||
* @param {Object} opts - with the key origin to put the origin on the txMeta
|
||||
*/
|
||||
async newUnapprovedTransaction(txParams, opts = {}) {
|
||||
log.debug(
|
||||
`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`,
|
||||
)
|
||||
|
||||
log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`)
|
||||
|
||||
const initialTxMeta = await this.addUnapprovedTransaction(txParams, opts.origin)
|
||||
const initialTxMeta = await this.addUnapprovedTransaction(
|
||||
txParams,
|
||||
opts.origin,
|
||||
)
|
||||
|
||||
// listen for tx completion (success, fail)
|
||||
return new Promise((resolve, reject) => {
|
||||
this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => {
|
||||
switch (finishedTxMeta.status) {
|
||||
case 'submitted':
|
||||
return resolve(finishedTxMeta.hash)
|
||||
case 'rejected':
|
||||
return reject(cleanErrorStack(ethErrors.provider.userRejectedRequest('MetaMask Tx Signature: User denied transaction signature.')))
|
||||
case 'failed':
|
||||
return reject(cleanErrorStack(ethErrors.rpc.internal(finishedTxMeta.err.message)))
|
||||
default:
|
||||
return reject(cleanErrorStack(ethErrors.rpc.internal(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`)))
|
||||
}
|
||||
})
|
||||
this.txStateManager.once(
|
||||
`${initialTxMeta.id}:finished`,
|
||||
(finishedTxMeta) => {
|
||||
switch (finishedTxMeta.status) {
|
||||
case 'submitted':
|
||||
return resolve(finishedTxMeta.hash)
|
||||
case 'rejected':
|
||||
return reject(
|
||||
cleanErrorStack(
|
||||
ethErrors.provider.userRejectedRequest(
|
||||
'MetaMask Tx Signature: User denied transaction signature.',
|
||||
),
|
||||
),
|
||||
)
|
||||
case 'failed':
|
||||
return reject(
|
||||
cleanErrorStack(
|
||||
ethErrors.rpc.internal(finishedTxMeta.err.message),
|
||||
),
|
||||
)
|
||||
default:
|
||||
return reject(
|
||||
cleanErrorStack(
|
||||
ethErrors.rpc.internal(
|
||||
`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(
|
||||
finishedTxMeta.txParams,
|
||||
)}`,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@ -195,8 +226,7 @@ export default class TransactionController extends EventEmitter {
|
||||
*
|
||||
* @returns {txMeta}
|
||||
*/
|
||||
async addUnapprovedTransaction (txParams, origin) {
|
||||
|
||||
async addUnapprovedTransaction(txParams, origin) {
|
||||
// validate
|
||||
const normalizedTxParams = txUtils.normalizeTxParams(txParams)
|
||||
|
||||
@ -236,7 +266,10 @@ export default class TransactionController extends EventEmitter {
|
||||
|
||||
txMeta.origin = origin
|
||||
|
||||
const { transactionCategory, getCodeResponse } = await this._determineTransactionCategory(txParams)
|
||||
const {
|
||||
transactionCategory,
|
||||
getCodeResponse,
|
||||
} = await this._determineTransactionCategory(txParams)
|
||||
txMeta.transactionCategory = transactionCategory
|
||||
|
||||
// ensure value
|
||||
@ -269,9 +302,12 @@ export default class TransactionController extends EventEmitter {
|
||||
* @param {Object} txMeta - the txMeta object
|
||||
* @returns {Promise<object>} - resolves with txMeta
|
||||
*/
|
||||
async addTxGasDefaults (txMeta, getCodeResponse) {
|
||||
async addTxGasDefaults(txMeta, getCodeResponse) {
|
||||
const defaultGasPrice = await this._getDefaultGasPrice(txMeta)
|
||||
const { gasLimit: defaultGasLimit, simulationFails } = await this._getDefaultGasLimit(txMeta, getCodeResponse)
|
||||
const {
|
||||
gasLimit: defaultGasLimit,
|
||||
simulationFails,
|
||||
} = await this._getDefaultGasLimit(txMeta, getCodeResponse)
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
txMeta = this.txStateManager.getTx(txMeta.id)
|
||||
@ -292,7 +328,7 @@ export default class TransactionController extends EventEmitter {
|
||||
* @param {Object} txMeta - The txMeta object
|
||||
* @returns {Promise<string|undefined>} The default gas price
|
||||
*/
|
||||
async _getDefaultGasPrice (txMeta) {
|
||||
async _getDefaultGasPrice(txMeta) {
|
||||
if (txMeta.txParams.gasPrice) {
|
||||
return undefined
|
||||
}
|
||||
@ -307,7 +343,7 @@ export default class TransactionController extends EventEmitter {
|
||||
* @param {string} getCodeResponse - The transaction category code response, used for debugging purposes
|
||||
* @returns {Promise<Object>} Object containing the default gas limit, or the simulation failure object
|
||||
*/
|
||||
async _getDefaultGasLimit (txMeta, getCodeResponse) {
|
||||
async _getDefaultGasLimit(txMeta, getCodeResponse) {
|
||||
if (txMeta.txParams.gas) {
|
||||
return {}
|
||||
} else if (
|
||||
@ -316,7 +352,9 @@ export default class TransactionController extends EventEmitter {
|
||||
) {
|
||||
// if there's data in the params, but there's no contract code, it's not a valid transaction
|
||||
if (txMeta.txParams.data) {
|
||||
const err = new Error('TxGasUtil - Trying to call a function on a non-contract address')
|
||||
const err = new Error(
|
||||
'TxGasUtil - Trying to call a function on a non-contract address',
|
||||
)
|
||||
// set error key so ui can display localized error message
|
||||
err.errorKey = TRANSACTION_NO_CONTRACT_ERROR_KEY
|
||||
|
||||
@ -329,10 +367,17 @@ export default class TransactionController extends EventEmitter {
|
||||
return { gasLimit: SIMPLE_GAS_COST }
|
||||
}
|
||||
|
||||
const { blockGasLimit, estimatedGasHex, simulationFails } = await this.txGasUtil.analyzeGasUsage(txMeta)
|
||||
const {
|
||||
blockGasLimit,
|
||||
estimatedGasHex,
|
||||
simulationFails,
|
||||
} = await this.txGasUtil.analyzeGasUsage(txMeta)
|
||||
|
||||
// add additional gas buffer to our estimation for safety
|
||||
const gasLimit = this.txGasUtil.addGasBuffer(ethUtil.addHexPrefix(estimatedGasHex), blockGasLimit)
|
||||
const gasLimit = this.txGasUtil.addGasBuffer(
|
||||
ethUtil.addHexPrefix(estimatedGasHex),
|
||||
blockGasLimit,
|
||||
)
|
||||
return { gasLimit, simulationFails }
|
||||
}
|
||||
|
||||
@ -344,12 +389,14 @@ export default class TransactionController extends EventEmitter {
|
||||
* @param {string} [customGasPrice] - the hex value to use for the cancel transaction
|
||||
* @returns {txMeta}
|
||||
*/
|
||||
async createCancelTransaction (originalTxId, customGasPrice) {
|
||||
async createCancelTransaction(originalTxId, customGasPrice) {
|
||||
const originalTxMeta = this.txStateManager.getTx(originalTxId)
|
||||
const { txParams } = originalTxMeta
|
||||
const { gasPrice: lastGasPrice, from, nonce } = txParams
|
||||
|
||||
const newGasPrice = customGasPrice || bnToHex(BnMultiplyByFraction(hexToBn(lastGasPrice), 11, 10))
|
||||
const newGasPrice =
|
||||
customGasPrice ||
|
||||
bnToHex(BnMultiplyByFraction(hexToBn(lastGasPrice), 11, 10))
|
||||
const newTxMeta = this.txStateManager.generateTxMeta({
|
||||
txParams: {
|
||||
from,
|
||||
@ -380,12 +427,14 @@ export default class TransactionController extends EventEmitter {
|
||||
* @param {string} [customGasLimit] - The new custom gas limt, in hex
|
||||
* @returns {txMeta}
|
||||
*/
|
||||
async createSpeedUpTransaction (originalTxId, customGasPrice, customGasLimit) {
|
||||
async createSpeedUpTransaction(originalTxId, customGasPrice, customGasLimit) {
|
||||
const originalTxMeta = this.txStateManager.getTx(originalTxId)
|
||||
const { txParams } = originalTxMeta
|
||||
const { gasPrice: lastGasPrice } = txParams
|
||||
|
||||
const newGasPrice = customGasPrice || bnToHex(BnMultiplyByFraction(hexToBn(lastGasPrice), 11, 10))
|
||||
const newGasPrice =
|
||||
customGasPrice ||
|
||||
bnToHex(BnMultiplyByFraction(hexToBn(lastGasPrice), 11, 10))
|
||||
|
||||
const newTxMeta = this.txStateManager.generateTxMeta({
|
||||
txParams: {
|
||||
@ -411,7 +460,7 @@ export default class TransactionController extends EventEmitter {
|
||||
updates the txMeta in the txStateManager
|
||||
@param {Object} txMeta - the updated txMeta
|
||||
*/
|
||||
async updateTransaction (txMeta) {
|
||||
async updateTransaction(txMeta) {
|
||||
this.txStateManager.updateTx(txMeta, 'confTx: user updated transaction')
|
||||
}
|
||||
|
||||
@ -419,7 +468,7 @@ export default class TransactionController extends EventEmitter {
|
||||
updates and approves the transaction
|
||||
@param {Object} txMeta
|
||||
*/
|
||||
async updateAndApproveTransaction (txMeta) {
|
||||
async updateAndApproveTransaction(txMeta) {
|
||||
this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction')
|
||||
await this.approveTransaction(txMeta.id)
|
||||
}
|
||||
@ -432,7 +481,7 @@ export default class TransactionController extends EventEmitter {
|
||||
if any of these steps fails the tx status will be set to failed
|
||||
@param {number} txId - the tx's Id
|
||||
*/
|
||||
async approveTransaction (txId) {
|
||||
async approveTransaction(txId) {
|
||||
// TODO: Move this safety out of this function.
|
||||
// Since this transaction is async,
|
||||
// we need to keep track of what is currently being signed,
|
||||
@ -456,8 +505,11 @@ export default class TransactionController extends EventEmitter {
|
||||
// add nonce to txParams
|
||||
// if txMeta has lastGasPrice then it is a retry at same nonce with higher
|
||||
// gas price transaction and their for the nonce should not be calculated
|
||||
const nonce = txMeta.lastGasPrice ? txMeta.txParams.nonce : nonceLock.nextNonce
|
||||
const customOrNonce = (customNonceValue === 0) ? customNonceValue : customNonceValue || nonce
|
||||
const nonce = txMeta.lastGasPrice
|
||||
? txMeta.txParams.nonce
|
||||
: nonceLock.nextNonce
|
||||
const customOrNonce =
|
||||
customNonceValue === 0 ? customNonceValue : customNonceValue || nonce
|
||||
|
||||
txMeta.txParams.nonce = ethUtil.addHexPrefix(customOrNonce.toString(16))
|
||||
// add nonce debugging information to txMeta
|
||||
@ -494,7 +546,7 @@ export default class TransactionController extends EventEmitter {
|
||||
@param {number} txId - the tx's Id
|
||||
@returns {string} - rawTx
|
||||
*/
|
||||
async signTransaction (txId) {
|
||||
async signTransaction(txId) {
|
||||
const txMeta = this.txStateManager.getTx(txId)
|
||||
// add network/chain id
|
||||
const chainId = this.getChainId()
|
||||
@ -510,7 +562,10 @@ export default class TransactionController extends EventEmitter {
|
||||
txMeta.s = ethUtil.bufferToHex(ethTx.s)
|
||||
txMeta.v = ethUtil.bufferToHex(ethTx.v)
|
||||
|
||||
this.txStateManager.updateTx(txMeta, 'transactions#signTransaction: add r, s, v values')
|
||||
this.txStateManager.updateTx(
|
||||
txMeta,
|
||||
'transactions#signTransaction: add r, s, v values',
|
||||
)
|
||||
|
||||
// set state to signed
|
||||
this.txStateManager.setTxStatusSigned(txMeta.id)
|
||||
@ -524,7 +579,7 @@ export default class TransactionController extends EventEmitter {
|
||||
@param {string} rawTx - the hex string of the serialized signed transaction
|
||||
@returns {Promise<void>}
|
||||
*/
|
||||
async publishTransaction (txId, rawTx) {
|
||||
async publishTransaction(txId, rawTx) {
|
||||
const txMeta = this.txStateManager.getTx(txId)
|
||||
txMeta.rawTx = rawTx
|
||||
if (txMeta.transactionCategory === SWAP) {
|
||||
@ -554,7 +609,7 @@ export default class TransactionController extends EventEmitter {
|
||||
* @param {number} txId - The tx's ID
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async confirmTransaction (txId, txReceipt) {
|
||||
async confirmTransaction(txId, txReceipt) {
|
||||
// get the txReceipt before marking the transaction confirmed
|
||||
// to ensure the receipt is gotten before the ui revives the tx
|
||||
const txMeta = this.txStateManager.getTx(txId)
|
||||
@ -566,9 +621,10 @@ export default class TransactionController extends EventEmitter {
|
||||
try {
|
||||
// It seems that sometimes the numerical values being returned from
|
||||
// this.query.getTransactionReceipt are BN instances and not strings.
|
||||
const gasUsed = typeof txReceipt.gasUsed === 'string'
|
||||
? txReceipt.gasUsed
|
||||
: txReceipt.gasUsed.toString(16)
|
||||
const gasUsed =
|
||||
typeof txReceipt.gasUsed === 'string'
|
||||
? txReceipt.gasUsed
|
||||
: txReceipt.gasUsed.toString(16)
|
||||
|
||||
txMeta.txReceipt = {
|
||||
...txReceipt,
|
||||
@ -577,7 +633,10 @@ export default class TransactionController extends EventEmitter {
|
||||
this.txStateManager.setTxStatusConfirmed(txId)
|
||||
this._markNonceDuplicatesDropped(txId)
|
||||
|
||||
this.txStateManager.updateTx(txMeta, 'transactions#confirmTransaction - add txReceipt')
|
||||
this.txStateManager.updateTx(
|
||||
txMeta,
|
||||
'transactions#confirmTransaction - add txReceipt',
|
||||
)
|
||||
|
||||
if (txMeta.transactionCategory === SWAP) {
|
||||
const postTxBalance = await this.query.getBalance(txMeta.txParams.from)
|
||||
@ -589,11 +648,13 @@ export default class TransactionController extends EventEmitter {
|
||||
|
||||
latestTxMeta.postTxBalance = postTxBalance.toString(16)
|
||||
|
||||
this.txStateManager.updateTx(latestTxMeta, 'transactions#confirmTransaction - add postTxBalance')
|
||||
this.txStateManager.updateTx(
|
||||
latestTxMeta,
|
||||
'transactions#confirmTransaction - add postTxBalance',
|
||||
)
|
||||
|
||||
this._trackSwapsMetrics(latestTxMeta, approvalTxMeta)
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
log.error(err)
|
||||
}
|
||||
@ -604,7 +665,7 @@ export default class TransactionController extends EventEmitter {
|
||||
@param {number} txId - the tx's Id
|
||||
@returns {Promise<void>}
|
||||
*/
|
||||
async cancelTransaction (txId) {
|
||||
async cancelTransaction(txId) {
|
||||
this.txStateManager.setTxStatusRejected(txId)
|
||||
}
|
||||
|
||||
@ -613,7 +674,7 @@ export default class TransactionController extends EventEmitter {
|
||||
@param {number} txId - the tx's Id
|
||||
@param {string} txHash - the hash for the txMeta
|
||||
*/
|
||||
setTxHash (txId, txHash) {
|
||||
setTxHash(txId, txHash) {
|
||||
// Add the tx hash to the persisted meta-tx object
|
||||
const txMeta = this.txStateManager.getTx(txId)
|
||||
txMeta.hash = txHash
|
||||
@ -624,8 +685,7 @@ export default class TransactionController extends EventEmitter {
|
||||
// PRIVATE METHODS
|
||||
//
|
||||
/** maps methods for convenience*/
|
||||
_mapMethods () {
|
||||
|
||||
_mapMethods() {
|
||||
/** @returns {Object} - the state in transaction controller */
|
||||
this.getState = () => this.memStore.getState()
|
||||
|
||||
@ -633,23 +693,27 @@ export default class TransactionController extends EventEmitter {
|
||||
this.getNetwork = () => this.networkStore.getState()
|
||||
|
||||
/** @returns {string} - the user selected address */
|
||||
this.getSelectedAddress = () => this.preferencesStore.getState().selectedAddress
|
||||
this.getSelectedAddress = () =>
|
||||
this.preferencesStore.getState().selectedAddress
|
||||
|
||||
/** @returns {array} - transactions whos status is unapproved */
|
||||
this.getUnapprovedTxCount = () => Object.keys(this.txStateManager.getUnapprovedTxList()).length
|
||||
this.getUnapprovedTxCount = () =>
|
||||
Object.keys(this.txStateManager.getUnapprovedTxList()).length
|
||||
|
||||
/**
|
||||
@returns {number} - number of transactions that have the status submitted
|
||||
@param {string} account - hex prefixed account
|
||||
*/
|
||||
this.getPendingTxCount = (account) => this.txStateManager.getPendingTransactions(account).length
|
||||
this.getPendingTxCount = (account) =>
|
||||
this.txStateManager.getPendingTransactions(account).length
|
||||
|
||||
/** see txStateManager */
|
||||
this.getFilteredTxList = (opts) => this.txStateManager.getFilteredTxList(opts)
|
||||
this.getFilteredTxList = (opts) =>
|
||||
this.txStateManager.getFilteredTxList(opts)
|
||||
}
|
||||
|
||||
// called once on startup
|
||||
async _updatePendingTxsAfterFirstBlock () {
|
||||
async _updatePendingTxsAfterFirstBlock() {
|
||||
// wait for first block so we know we're ready
|
||||
await this.blockTracker.getLatestBlock()
|
||||
// get status update for all pending transactions (for the current network)
|
||||
@ -662,49 +726,78 @@ export default class TransactionController extends EventEmitter {
|
||||
transition txMetas to a failed state or try to redo those tasks.
|
||||
*/
|
||||
|
||||
_onBootCleanUp () {
|
||||
this.txStateManager.getFilteredTxList({
|
||||
status: 'unapproved',
|
||||
loadingDefaults: true,
|
||||
}).forEach((tx) => {
|
||||
_onBootCleanUp() {
|
||||
this.txStateManager
|
||||
.getFilteredTxList({
|
||||
status: 'unapproved',
|
||||
loadingDefaults: true,
|
||||
})
|
||||
.forEach((tx) => {
|
||||
this.addTxGasDefaults(tx)
|
||||
.then((txMeta) => {
|
||||
txMeta.loadingDefaults = false
|
||||
this.txStateManager.updateTx(
|
||||
txMeta,
|
||||
'transactions: gas estimation for tx on boot',
|
||||
)
|
||||
})
|
||||
.catch((error) => {
|
||||
const txMeta = this.txStateManager.getTx(tx.id)
|
||||
txMeta.loadingDefaults = false
|
||||
this.txStateManager.updateTx(
|
||||
txMeta,
|
||||
'failed to estimate gas during boot cleanup.',
|
||||
)
|
||||
this.txStateManager.setTxStatusFailed(txMeta.id, error)
|
||||
})
|
||||
})
|
||||
|
||||
this.addTxGasDefaults(tx)
|
||||
.then((txMeta) => {
|
||||
txMeta.loadingDefaults = false
|
||||
this.txStateManager.updateTx(txMeta, 'transactions: gas estimation for tx on boot')
|
||||
}).catch((error) => {
|
||||
const txMeta = this.txStateManager.getTx(tx.id)
|
||||
txMeta.loadingDefaults = false
|
||||
this.txStateManager.updateTx(txMeta, 'failed to estimate gas during boot cleanup.')
|
||||
this.txStateManager.setTxStatusFailed(txMeta.id, error)
|
||||
})
|
||||
})
|
||||
|
||||
this.txStateManager.getFilteredTxList({
|
||||
status: TRANSACTION_STATUS_APPROVED,
|
||||
}).forEach((txMeta) => {
|
||||
const txSignError = new Error('Transaction found as "approved" during boot - possibly stuck during signing')
|
||||
this.txStateManager.setTxStatusFailed(txMeta.id, txSignError)
|
||||
})
|
||||
this.txStateManager
|
||||
.getFilteredTxList({
|
||||
status: TRANSACTION_STATUS_APPROVED,
|
||||
})
|
||||
.forEach((txMeta) => {
|
||||
const txSignError = new Error(
|
||||
'Transaction found as "approved" during boot - possibly stuck during signing',
|
||||
)
|
||||
this.txStateManager.setTxStatusFailed(txMeta.id, txSignError)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
is called in constructor applies the listeners for pendingTxTracker txStateManager
|
||||
and blockTracker
|
||||
*/
|
||||
_setupListeners () {
|
||||
this.txStateManager.on('tx:status-update', this.emit.bind(this, 'tx:status-update'))
|
||||
_setupListeners() {
|
||||
this.txStateManager.on(
|
||||
'tx:status-update',
|
||||
this.emit.bind(this, 'tx:status-update'),
|
||||
)
|
||||
this._setupBlockTrackerListener()
|
||||
this.pendingTxTracker.on('tx:warning', (txMeta) => {
|
||||
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning')
|
||||
this.txStateManager.updateTx(
|
||||
txMeta,
|
||||
'transactions/pending-tx-tracker#event: tx:warning',
|
||||
)
|
||||
})
|
||||
this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager))
|
||||
this.pendingTxTracker.on('tx:confirmed', (txId, transactionReceipt) => this.confirmTransaction(txId, transactionReceipt))
|
||||
this.pendingTxTracker.on('tx:dropped', this.txStateManager.setTxStatusDropped.bind(this.txStateManager))
|
||||
this.pendingTxTracker.on(
|
||||
'tx:failed',
|
||||
this.txStateManager.setTxStatusFailed.bind(this.txStateManager),
|
||||
)
|
||||
this.pendingTxTracker.on('tx:confirmed', (txId, transactionReceipt) =>
|
||||
this.confirmTransaction(txId, transactionReceipt),
|
||||
)
|
||||
this.pendingTxTracker.on(
|
||||
'tx:dropped',
|
||||
this.txStateManager.setTxStatusDropped.bind(this.txStateManager),
|
||||
)
|
||||
this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => {
|
||||
if (!txMeta.firstRetryBlockNumber) {
|
||||
txMeta.firstRetryBlockNumber = latestBlockNumber
|
||||
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:block-update')
|
||||
this.txStateManager.updateTx(
|
||||
txMeta,
|
||||
'transactions/pending-tx-tracker#event: tx:block-update',
|
||||
)
|
||||
}
|
||||
})
|
||||
this.pendingTxTracker.on('tx:retry', (txMeta) => {
|
||||
@ -712,7 +805,10 @@ export default class TransactionController extends EventEmitter {
|
||||
txMeta.retryCount = 0
|
||||
}
|
||||
txMeta.retryCount += 1
|
||||
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:retry')
|
||||
this.txStateManager.updateTx(
|
||||
txMeta,
|
||||
'transactions/pending-tx-tracker#event: tx:retry',
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@ -720,7 +816,7 @@ export default class TransactionController extends EventEmitter {
|
||||
Returns a "type" for a transaction out of the following list: simpleSend, tokenTransfer, tokenApprove,
|
||||
contractDeployment, contractMethodCall
|
||||
*/
|
||||
async _determineTransactionCategory (txParams) {
|
||||
async _determineTransactionCategory(txParams) {
|
||||
const { data, to } = txParams
|
||||
let name
|
||||
try {
|
||||
@ -765,7 +861,7 @@ export default class TransactionController extends EventEmitter {
|
||||
|
||||
@param {number} txId - the txId of the transaction that has been confirmed in a block
|
||||
*/
|
||||
_markNonceDuplicatesDropped (txId) {
|
||||
_markNonceDuplicatesDropped(txId) {
|
||||
// get the confirmed transactions nonce and from address
|
||||
const txMeta = this.txStateManager.getTx(txId)
|
||||
const { nonce, from } = txMeta.txParams
|
||||
@ -779,12 +875,15 @@ export default class TransactionController extends EventEmitter {
|
||||
return
|
||||
}
|
||||
otherTxMeta.replacedBy = txMeta.hash
|
||||
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:confirmed reference to confirmed txHash with same nonce')
|
||||
this.txStateManager.updateTx(
|
||||
txMeta,
|
||||
'transactions/pending-tx-tracker#event: tx:confirmed reference to confirmed txHash with same nonce',
|
||||
)
|
||||
this.txStateManager.setTxStatusDropped(otherTxMeta.id)
|
||||
})
|
||||
}
|
||||
|
||||
_setupBlockTrackerListener () {
|
||||
_setupBlockTrackerListener() {
|
||||
let listenersAreActive = false
|
||||
const latestBlockHandler = this._onLatestBlock.bind(this)
|
||||
const { blockTracker, txStateManager } = this
|
||||
@ -792,7 +891,7 @@ export default class TransactionController extends EventEmitter {
|
||||
txStateManager.on('tx:status-update', updateSubscription)
|
||||
updateSubscription()
|
||||
|
||||
function updateSubscription () {
|
||||
function updateSubscription() {
|
||||
const pendingTxs = txStateManager.getPendingTransactions()
|
||||
if (!listenersAreActive && pendingTxs.length > 0) {
|
||||
blockTracker.on('latest', latestBlockHandler)
|
||||
@ -804,7 +903,7 @@ export default class TransactionController extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
async _onLatestBlock (blockNumber) {
|
||||
async _onLatestBlock(blockNumber) {
|
||||
try {
|
||||
await this.pendingTxTracker.updatePendingTxs()
|
||||
} catch (err) {
|
||||
@ -820,13 +919,15 @@ export default class TransactionController extends EventEmitter {
|
||||
/**
|
||||
Updates the memStore in transaction controller
|
||||
*/
|
||||
_updateMemstore () {
|
||||
_updateMemstore() {
|
||||
const unapprovedTxs = this.txStateManager.getUnapprovedTxList()
|
||||
const currentNetworkTxList = this.txStateManager.getTxList(MAX_MEMSTORE_TX_LIST_SIZE)
|
||||
const currentNetworkTxList = this.txStateManager.getTxList(
|
||||
MAX_MEMSTORE_TX_LIST_SIZE,
|
||||
)
|
||||
this.memStore.updateState({ unapprovedTxs, currentNetworkTxList })
|
||||
}
|
||||
|
||||
_trackSwapsMetrics (txMeta, approvalTxMeta) {
|
||||
_trackSwapsMetrics(txMeta, approvalTxMeta) {
|
||||
if (this._getParticipateInMetrics() && txMeta.swapMetaData) {
|
||||
if (txMeta.txReceipt.status === '0x0') {
|
||||
this._trackMetaMetricsEvent({
|
||||
@ -851,19 +952,18 @@ export default class TransactionController extends EventEmitter {
|
||||
approvalTxMeta,
|
||||
)
|
||||
|
||||
const quoteVsExecutionRatio = `${
|
||||
(new BigNumber(tokensReceived, 10))
|
||||
.div(txMeta.swapMetaData.token_to_amount, 10)
|
||||
.times(100)
|
||||
.round(2)
|
||||
}%`
|
||||
const quoteVsExecutionRatio = `${new BigNumber(tokensReceived, 10)
|
||||
.div(txMeta.swapMetaData.token_to_amount, 10)
|
||||
.times(100)
|
||||
.round(2)}%`
|
||||
|
||||
const estimatedVsUsedGasRatio = `${
|
||||
(new BigNumber(txMeta.txReceipt.gasUsed, 16))
|
||||
.div(txMeta.swapMetaData.estimated_gas, 10)
|
||||
.times(100)
|
||||
.round(2)
|
||||
}%`
|
||||
const estimatedVsUsedGasRatio = `${new BigNumber(
|
||||
txMeta.txReceipt.gasUsed,
|
||||
16,
|
||||
)
|
||||
.div(txMeta.swapMetaData.estimated_gas, 10)
|
||||
.times(100)
|
||||
.round(2)}%`
|
||||
|
||||
this._trackMetaMetricsEvent({
|
||||
event: 'Swap Completed',
|
||||
|
@ -6,10 +6,10 @@ import { cloneDeep } from 'lodash'
|
||||
@param {array} longHistory
|
||||
@returns {array}
|
||||
*/
|
||||
export function migrateFromSnapshotsToDiffs (longHistory) {
|
||||
export function migrateFromSnapshotsToDiffs(longHistory) {
|
||||
return (
|
||||
longHistory
|
||||
// convert non-initial history entries into diffs
|
||||
// convert non-initial history entries into diffs
|
||||
.map((entry, index) => {
|
||||
if (index === 0) {
|
||||
return entry
|
||||
@ -31,7 +31,7 @@ export function migrateFromSnapshotsToDiffs (longHistory) {
|
||||
@param {string} [note] - a optional note for the state change
|
||||
@returns {array}
|
||||
*/
|
||||
export function generateHistoryEntry (previousState, newState, note) {
|
||||
export function generateHistoryEntry(previousState, newState, note) {
|
||||
const entry = jsonDiffer.compare(previousState, newState)
|
||||
// Add a note to the first op, since it breaks if we append it to the entry
|
||||
if (entry[0]) {
|
||||
@ -48,9 +48,11 @@ export function generateHistoryEntry (previousState, newState, note) {
|
||||
Recovers previous txMeta state obj
|
||||
@returns {Object}
|
||||
*/
|
||||
export function replayHistory (_shortHistory) {
|
||||
export function replayHistory(_shortHistory) {
|
||||
const shortHistory = cloneDeep(_shortHistory)
|
||||
return shortHistory.reduce((val, entry) => jsonDiffer.applyPatch(val, entry).newDocument)
|
||||
return shortHistory.reduce(
|
||||
(val, entry) => jsonDiffer.applyPatch(val, entry).newDocument,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -58,7 +60,7 @@ export function replayHistory (_shortHistory) {
|
||||
* @param {Object} txMeta - the tx metadata object
|
||||
* @returns {Object} a deep clone without history
|
||||
*/
|
||||
export function snapshotFromTxMeta (txMeta) {
|
||||
export function snapshotFromTxMeta(txMeta) {
|
||||
const shallow = { ...txMeta }
|
||||
delete shallow.history
|
||||
return cloneDeep(shallow)
|
||||
|
@ -2,7 +2,8 @@ import { addHexPrefix, isValidAddress } from 'ethereumjs-util'
|
||||
|
||||
const normalizers = {
|
||||
from: (from) => addHexPrefix(from),
|
||||
to: (to, lowerCase) => (lowerCase ? addHexPrefix(to).toLowerCase() : addHexPrefix(to)),
|
||||
to: (to, lowerCase) =>
|
||||
lowerCase ? addHexPrefix(to).toLowerCase() : addHexPrefix(to),
|
||||
nonce: (nonce) => addHexPrefix(nonce),
|
||||
value: (value) => addHexPrefix(value),
|
||||
data: (data) => addHexPrefix(data),
|
||||
@ -17,7 +18,7 @@ const normalizers = {
|
||||
* Default: true
|
||||
* @returns {Object} the normalized tx params
|
||||
*/
|
||||
export function normalizeTxParams (txParams, lowerCase = true) {
|
||||
export function normalizeTxParams(txParams, lowerCase = true) {
|
||||
// apply only keys in the normalizers
|
||||
const normalizedTxParams = {}
|
||||
for (const key in normalizers) {
|
||||
@ -33,17 +34,21 @@ export function normalizeTxParams (txParams, lowerCase = true) {
|
||||
* @param {Object} txParams - the tx params
|
||||
* @throws {Error} if the tx params contains invalid fields
|
||||
*/
|
||||
export function validateTxParams (txParams) {
|
||||
export function validateTxParams(txParams) {
|
||||
validateFrom(txParams)
|
||||
validateRecipient(txParams)
|
||||
if ('value' in txParams) {
|
||||
const value = txParams.value.toString()
|
||||
if (value.includes('-')) {
|
||||
throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`)
|
||||
throw new Error(
|
||||
`Invalid transaction value of ${txParams.value} not a positive number.`,
|
||||
)
|
||||
}
|
||||
|
||||
if (value.includes('.')) {
|
||||
throw new Error(`Invalid transaction value of ${txParams.value} number must be in wei`)
|
||||
throw new Error(
|
||||
`Invalid transaction value of ${txParams.value} number must be in wei`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -53,7 +58,7 @@ export function validateTxParams (txParams) {
|
||||
* @param {Object} txParams
|
||||
* @throws {Error} if the from address isn't valid
|
||||
*/
|
||||
export function validateFrom (txParams) {
|
||||
export function validateFrom(txParams) {
|
||||
if (!(typeof txParams.from === 'string')) {
|
||||
throw new Error(`Invalid from address ${txParams.from} not a string`)
|
||||
}
|
||||
@ -68,7 +73,7 @@ export function validateFrom (txParams) {
|
||||
* @returns {Object} the tx params
|
||||
* @throws {Error} if the recipient is invalid OR there isn't tx data
|
||||
*/
|
||||
export function validateRecipient (txParams) {
|
||||
export function validateRecipient(txParams) {
|
||||
if (txParams.to === '0x' || txParams.to === null) {
|
||||
if (txParams.data) {
|
||||
delete txParams.to
|
||||
@ -85,7 +90,7 @@ export function validateRecipient (txParams) {
|
||||
* Returns a list of final states
|
||||
* @returns {string[]} the states that can be considered final states
|
||||
*/
|
||||
export function getFinalStates () {
|
||||
export function getFinalStates() {
|
||||
return [
|
||||
'rejected', // the user has responded no!
|
||||
'confirmed', // the tx has been included in a block.
|
||||
|
@ -19,7 +19,6 @@ import EthQuery from 'ethjs-query'
|
||||
*/
|
||||
|
||||
export default class PendingTransactionTracker extends EventEmitter {
|
||||
|
||||
/**
|
||||
* We wait this many blocks before emitting a 'tx:dropped' event
|
||||
*
|
||||
@ -37,9 +36,9 @@ export default class PendingTransactionTracker extends EventEmitter {
|
||||
*/
|
||||
droppedBlocksBufferByHash = new Map()
|
||||
|
||||
constructor (config) {
|
||||
constructor(config) {
|
||||
super()
|
||||
this.query = config.query || (new EthQuery(config.provider))
|
||||
this.query = config.query || new EthQuery(config.provider)
|
||||
this.nonceTracker = config.nonceTracker
|
||||
this.getPendingTransactions = config.getPendingTransactions
|
||||
this.getCompletedTransactions = config.getCompletedTransactions
|
||||
@ -51,14 +50,18 @@ export default class PendingTransactionTracker extends EventEmitter {
|
||||
/**
|
||||
checks the network for signed txs and releases the nonce global lock if it is
|
||||
*/
|
||||
async updatePendingTxs () {
|
||||
async updatePendingTxs() {
|
||||
// in order to keep the nonceTracker accurate we block it while updating pending transactions
|
||||
const nonceGlobalLock = await this.nonceTracker.getGlobalLock()
|
||||
try {
|
||||
const pendingTxs = this.getPendingTransactions()
|
||||
await Promise.all(pendingTxs.map((txMeta) => this._checkPendingTx(txMeta)))
|
||||
await Promise.all(
|
||||
pendingTxs.map((txMeta) => this._checkPendingTx(txMeta)),
|
||||
)
|
||||
} catch (err) {
|
||||
log.error('PendingTransactionTracker - Error updating pending transactions')
|
||||
log.error(
|
||||
'PendingTransactionTracker - Error updating pending transactions',
|
||||
)
|
||||
log.error(err)
|
||||
}
|
||||
nonceGlobalLock.releaseLock()
|
||||
@ -70,7 +73,7 @@ export default class PendingTransactionTracker extends EventEmitter {
|
||||
* @emits tx:warning
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async resubmitPendingTxs (blockNumber) {
|
||||
async resubmitPendingTxs(blockNumber) {
|
||||
const pending = this.getPendingTransactions()
|
||||
if (!pending.length) {
|
||||
return
|
||||
@ -79,18 +82,20 @@ export default class PendingTransactionTracker extends EventEmitter {
|
||||
try {
|
||||
await this._resubmitTx(txMeta, blockNumber)
|
||||
} catch (err) {
|
||||
const errorMessage = err.value?.message?.toLowerCase() || err.message.toLowerCase()
|
||||
const isKnownTx = (
|
||||
const errorMessage =
|
||||
err.value?.message?.toLowerCase() || err.message.toLowerCase()
|
||||
const isKnownTx =
|
||||
// geth
|
||||
errorMessage.includes('replacement transaction underpriced') ||
|
||||
errorMessage.includes('known transaction') ||
|
||||
// parity
|
||||
errorMessage.includes('gas price too low to replace') ||
|
||||
errorMessage.includes('transaction with the same hash was already imported') ||
|
||||
errorMessage.includes(
|
||||
'transaction with the same hash was already imported',
|
||||
) ||
|
||||
// other
|
||||
errorMessage.includes('gateway timeout') ||
|
||||
errorMessage.includes('nonce too low')
|
||||
)
|
||||
// ignore resubmit warnings, return early
|
||||
if (isKnownTx) {
|
||||
return
|
||||
@ -117,13 +122,16 @@ export default class PendingTransactionTracker extends EventEmitter {
|
||||
* @emits tx:retry
|
||||
* @private
|
||||
*/
|
||||
async _resubmitTx (txMeta, latestBlockNumber) {
|
||||
async _resubmitTx(txMeta, latestBlockNumber) {
|
||||
if (!txMeta.firstRetryBlockNumber) {
|
||||
this.emit('tx:block-update', txMeta, latestBlockNumber)
|
||||
}
|
||||
|
||||
const firstRetryBlockNumber = txMeta.firstRetryBlockNumber || latestBlockNumber
|
||||
const txBlockDistance = Number.parseInt(latestBlockNumber, 16) - Number.parseInt(firstRetryBlockNumber, 16)
|
||||
const firstRetryBlockNumber =
|
||||
txMeta.firstRetryBlockNumber || latestBlockNumber
|
||||
const txBlockDistance =
|
||||
Number.parseInt(latestBlockNumber, 16) -
|
||||
Number.parseInt(firstRetryBlockNumber, 16)
|
||||
|
||||
const retryCount = txMeta.retryCount || 0
|
||||
|
||||
@ -155,7 +163,7 @@ export default class PendingTransactionTracker extends EventEmitter {
|
||||
* @emits tx:warning
|
||||
* @private
|
||||
*/
|
||||
async _checkPendingTx (txMeta) {
|
||||
async _checkPendingTx(txMeta) {
|
||||
const txHash = txMeta.hash
|
||||
const txId = txMeta.id
|
||||
|
||||
@ -167,7 +175,9 @@ export default class PendingTransactionTracker extends EventEmitter {
|
||||
// extra check in case there was an uncaught error during the
|
||||
// signature and submission process
|
||||
if (!txHash) {
|
||||
const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.')
|
||||
const noTxHashErr = new Error(
|
||||
'We had an error while submitting this transaction, please try again.',
|
||||
)
|
||||
noTxHashErr.name = 'NoTxHashError'
|
||||
this.emit('tx:failed', txId, noTxHashErr)
|
||||
|
||||
@ -206,8 +216,11 @@ export default class PendingTransactionTracker extends EventEmitter {
|
||||
* @returns {Promise<boolean>}
|
||||
* @private
|
||||
*/
|
||||
async _checkIfTxWasDropped (txMeta) {
|
||||
const { hash: txHash, txParams: { nonce, from } } = txMeta
|
||||
async _checkIfTxWasDropped(txMeta) {
|
||||
const {
|
||||
hash: txHash,
|
||||
txParams: { nonce, from },
|
||||
} = txMeta
|
||||
const networkNextNonce = await this.query.getTransactionCount(from)
|
||||
|
||||
if (parseInt(nonce, 16) >= networkNextNonce.toNumber()) {
|
||||
@ -235,14 +248,16 @@ export default class PendingTransactionTracker extends EventEmitter {
|
||||
* @returns {Promise<boolean>}
|
||||
* @private
|
||||
*/
|
||||
async _checkIfNonceIsTaken (txMeta) {
|
||||
async _checkIfNonceIsTaken(txMeta) {
|
||||
const address = txMeta.txParams.from
|
||||
const completed = this.getCompletedTransactions(address)
|
||||
return completed.some(
|
||||
// This is called while the transaction is in-flight, so it is possible that the
|
||||
// list of completed transactions now includes the transaction we were looking at
|
||||
// and if that is the case, don't consider the transaction to have taken its own nonce
|
||||
(other) => !(other.id === txMeta.id) && other.txParams.nonce === txMeta.txParams.nonce,
|
||||
(other) =>
|
||||
!(other.id === txMeta.id) &&
|
||||
other.txParams.nonce === txMeta.txParams.nonce,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,7 @@ and used to do things like calculate gas of a tx.
|
||||
*/
|
||||
|
||||
export default class TxGasUtil {
|
||||
|
||||
constructor (provider) {
|
||||
constructor(provider) {
|
||||
this.query = new EthQuery(provider)
|
||||
}
|
||||
|
||||
@ -29,7 +28,7 @@ export default class TxGasUtil {
|
||||
@param {Object} txMeta - the txMeta object
|
||||
@returns {GasAnalysisResult} The result of the gas analysis
|
||||
*/
|
||||
async analyzeGasUsage (txMeta) {
|
||||
async analyzeGasUsage(txMeta) {
|
||||
const block = await this.query.getBlockByNumber('latest', false)
|
||||
|
||||
// fallback to block gasLimit
|
||||
@ -56,7 +55,7 @@ export default class TxGasUtil {
|
||||
@param {Object} txMeta - the txMeta object
|
||||
@returns {string} - the estimated gas limit as a hex string
|
||||
*/
|
||||
async estimateTxGas (txMeta) {
|
||||
async estimateTxGas(txMeta) {
|
||||
const { txParams } = txMeta
|
||||
|
||||
// estimate tx gas requirements
|
||||
@ -70,7 +69,7 @@ export default class TxGasUtil {
|
||||
@param {string} blockGasLimitHex - the block gas limit
|
||||
@returns {string} - the buffered gas limit as a hex string
|
||||
*/
|
||||
addGasBuffer (initialGasLimitHex, blockGasLimitHex, multiplier = 1.5) {
|
||||
addGasBuffer(initialGasLimitHex, blockGasLimitHex, multiplier = 1.5) {
|
||||
const initialGasLimitBn = hexToBn(initialGasLimitHex)
|
||||
const blockGasLimitBn = hexToBn(blockGasLimitHex)
|
||||
const upperGasLimitBn = blockGasLimitBn.muln(0.9)
|
||||
@ -88,11 +87,19 @@ export default class TxGasUtil {
|
||||
return bnToHex(upperGasLimitBn)
|
||||
}
|
||||
|
||||
async getBufferedGasLimit (txMeta, multiplier) {
|
||||
const { blockGasLimit, estimatedGasHex, simulationFails } = await this.analyzeGasUsage(txMeta)
|
||||
async getBufferedGasLimit(txMeta, multiplier) {
|
||||
const {
|
||||
blockGasLimit,
|
||||
estimatedGasHex,
|
||||
simulationFails,
|
||||
} = await this.analyzeGasUsage(txMeta)
|
||||
|
||||
// add additional gas buffer to our estimation for safety
|
||||
const gasLimit = this.addGasBuffer(ethUtil.addHexPrefix(estimatedGasHex), blockGasLimit, multiplier)
|
||||
const gasLimit = this.addGasBuffer(
|
||||
ethUtil.addHexPrefix(estimatedGasHex),
|
||||
blockGasLimit,
|
||||
multiplier,
|
||||
)
|
||||
return { gasLimit, simulationFails }
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,11 @@ import EventEmitter from 'safe-event-emitter'
|
||||
import ObservableStore from 'obs-store'
|
||||
import log from 'loglevel'
|
||||
import createId from '../../lib/random-id'
|
||||
import { generateHistoryEntry, replayHistory, snapshotFromTxMeta } from './lib/tx-state-history-helpers'
|
||||
import {
|
||||
generateHistoryEntry,
|
||||
replayHistory,
|
||||
snapshotFromTxMeta,
|
||||
} from './lib/tx-state-history-helpers'
|
||||
import { getFinalStates, normalizeTxParams } from './lib/util'
|
||||
|
||||
/**
|
||||
@ -28,12 +32,10 @@ import { getFinalStates, normalizeTxParams } from './lib/util'
|
||||
@class
|
||||
*/
|
||||
export default class TransactionStateManager extends EventEmitter {
|
||||
constructor ({ initState, txHistoryLimit, getNetwork }) {
|
||||
constructor({ initState, txHistoryLimit, getNetwork }) {
|
||||
super()
|
||||
|
||||
this.store = new ObservableStore(
|
||||
{ transactions: [], ...initState },
|
||||
)
|
||||
this.store = new ObservableStore({ transactions: [], ...initState })
|
||||
this.txHistoryLimit = txHistoryLimit
|
||||
this.getNetwork = getNetwork
|
||||
}
|
||||
@ -42,17 +44,18 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
@param {Object} opts - the object to use when overwriting defaults
|
||||
@returns {txMeta} - the default txMeta object
|
||||
*/
|
||||
generateTxMeta (opts) {
|
||||
generateTxMeta(opts) {
|
||||
const netId = this.getNetwork()
|
||||
if (netId === 'loading') {
|
||||
throw new Error('MetaMask is having trouble connecting to the network')
|
||||
}
|
||||
return {
|
||||
id: createId(),
|
||||
time: (new Date()).getTime(),
|
||||
time: new Date().getTime(),
|
||||
status: 'unapproved',
|
||||
metamaskNetworkId: netId,
|
||||
loadingDefaults: true, ...opts,
|
||||
loadingDefaults: true,
|
||||
...opts,
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +67,7 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
* @param {number} [limit] a limit for the number of transactions to return
|
||||
* @returns {Object[]} The {@code txMeta}s, filtered to the current network
|
||||
*/
|
||||
getTxList (limit) {
|
||||
getTxList(limit) {
|
||||
const network = this.getNetwork()
|
||||
const fullTxList = this.getFullTxList()
|
||||
|
||||
@ -95,14 +98,14 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
/**
|
||||
@returns {array} - of all the txMetas in store
|
||||
*/
|
||||
getFullTxList () {
|
||||
getFullTxList() {
|
||||
return this.store.getState().transactions
|
||||
}
|
||||
|
||||
/**
|
||||
@returns {array} - the tx list whose status is unapproved
|
||||
*/
|
||||
getUnapprovedTxList () {
|
||||
getUnapprovedTxList() {
|
||||
const txList = this.getTxsByMetaData('status', 'unapproved')
|
||||
return txList.reduce((result, tx) => {
|
||||
result[tx.id] = tx
|
||||
@ -115,7 +118,7 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
@returns {array} - the tx list whose status is approved if no address is provide
|
||||
returns all txMetas who's status is approved for the current network
|
||||
*/
|
||||
getApprovedTransactions (address) {
|
||||
getApprovedTransactions(address) {
|
||||
const opts = { status: 'approved' }
|
||||
if (address) {
|
||||
opts.from = address
|
||||
@ -128,7 +131,7 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
@returns {array} - the tx list whose status is submitted if no address is provide
|
||||
returns all txMetas who's status is submitted for the current network
|
||||
*/
|
||||
getPendingTransactions (address) {
|
||||
getPendingTransactions(address) {
|
||||
const opts = { status: 'submitted' }
|
||||
if (address) {
|
||||
opts.from = address
|
||||
@ -141,7 +144,7 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
@returns {array} - the tx list whose status is confirmed if no address is provide
|
||||
returns all txMetas who's status is confirmed for the current network
|
||||
*/
|
||||
getConfirmedTransactions (address) {
|
||||
getConfirmedTransactions(address) {
|
||||
const opts = { status: 'confirmed' }
|
||||
if (address) {
|
||||
opts.from = address
|
||||
@ -158,7 +161,7 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
@param {Object} txMeta
|
||||
@returns {Object} - the txMeta
|
||||
*/
|
||||
addTx (txMeta) {
|
||||
addTx(txMeta) {
|
||||
// normalize and validate txParams if present
|
||||
if (txMeta.txParams) {
|
||||
txMeta.txParams = this.normalizeAndValidateTxParams(txMeta.txParams)
|
||||
@ -193,8 +196,9 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
transactions.splice(index, 1)
|
||||
}
|
||||
}
|
||||
const newTxIndex = transactions
|
||||
.findIndex((currentTxMeta) => currentTxMeta.time > txMeta.time)
|
||||
const newTxIndex = transactions.findIndex(
|
||||
(currentTxMeta) => currentTxMeta.time > txMeta.time,
|
||||
)
|
||||
|
||||
newTxIndex === -1
|
||||
? transactions.push(txMeta)
|
||||
@ -208,7 +212,7 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
@returns {Object} - the txMeta who matches the given id if none found
|
||||
for the network returns undefined
|
||||
*/
|
||||
getTx (txId) {
|
||||
getTx(txId) {
|
||||
const txMeta = this.getTxsByMetaData('id', txId)[0]
|
||||
return txMeta
|
||||
}
|
||||
@ -218,7 +222,7 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
@param {Object} txMeta - the txMeta to update
|
||||
@param {string} [note] - a note about the update for history
|
||||
*/
|
||||
updateTx (txMeta, note) {
|
||||
updateTx(txMeta, note) {
|
||||
// normalize and validate txParams if present
|
||||
if (txMeta.txParams) {
|
||||
txMeta.txParams = this.normalizeAndValidateTxParams(txMeta.txParams)
|
||||
@ -248,7 +252,7 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
@param {number} txId - the id of the txMeta
|
||||
@param {Object} txParams - the updated txParams
|
||||
*/
|
||||
updateTxParams (txId, txParams) {
|
||||
updateTxParams(txId, txParams) {
|
||||
const txMeta = this.getTx(txId)
|
||||
txMeta.txParams = { ...txMeta.txParams, ...txParams }
|
||||
this.updateTx(txMeta, `txStateManager#updateTxParams`)
|
||||
@ -258,7 +262,7 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
* normalize and validate txParams members
|
||||
* @param {Object} txParams - txParams
|
||||
*/
|
||||
normalizeAndValidateTxParams (txParams) {
|
||||
normalizeAndValidateTxParams(txParams) {
|
||||
if (typeof txParams.data === 'undefined') {
|
||||
delete txParams.data
|
||||
}
|
||||
@ -272,19 +276,23 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
validates txParams members by type
|
||||
@param {Object} txParams - txParams to validate
|
||||
*/
|
||||
validateTxParams (txParams) {
|
||||
validateTxParams(txParams) {
|
||||
Object.keys(txParams).forEach((key) => {
|
||||
const value = txParams[key]
|
||||
// validate types
|
||||
switch (key) {
|
||||
case 'chainId':
|
||||
if (typeof value !== 'number' && typeof value !== 'string') {
|
||||
throw new Error(`${key} in txParams is not a Number or hex string. got: (${value})`)
|
||||
throw new Error(
|
||||
`${key} in txParams is not a Number or hex string. got: (${value})`,
|
||||
)
|
||||
}
|
||||
break
|
||||
default:
|
||||
if (typeof value !== 'string') {
|
||||
throw new Error(`${key} in txParams is not a string. got: (${value})`)
|
||||
throw new Error(
|
||||
`${key} in txParams is not a string. got: (${value})`,
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
@ -319,7 +327,7 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
or for filtering for all txs from one account
|
||||
and that have been 'confirmed'
|
||||
*/
|
||||
getFilteredTxList (opts, initialList) {
|
||||
getFilteredTxList(opts, initialList) {
|
||||
let filteredTxList = initialList
|
||||
Object.keys(opts).forEach((key) => {
|
||||
filteredTxList = this.getTxsByMetaData(key, opts[key], filteredTxList)
|
||||
@ -335,7 +343,7 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
from txStateManager#getTxList
|
||||
@returns {array} - a list of txMetas who matches the search params
|
||||
*/
|
||||
getTxsByMetaData (key, value, txList = this.getTxList()) {
|
||||
getTxsByMetaData(key, value, txList = this.getTxList()) {
|
||||
const filter = typeof value === 'function' ? value : (v) => v === value
|
||||
|
||||
return txList.filter((txMeta) => {
|
||||
@ -352,7 +360,7 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
@param {number} txId - the txMeta Id
|
||||
@returns {string} - the status of the tx.
|
||||
*/
|
||||
getTxStatus (txId) {
|
||||
getTxStatus(txId) {
|
||||
const txMeta = this.getTx(txId)
|
||||
return txMeta.status
|
||||
}
|
||||
@ -361,7 +369,7 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
should update the status of the tx to 'rejected'.
|
||||
@param {number} txId - the txMeta Id
|
||||
*/
|
||||
setTxStatusRejected (txId) {
|
||||
setTxStatusRejected(txId) {
|
||||
this._setTxStatus(txId, 'rejected')
|
||||
this._removeTx(txId)
|
||||
}
|
||||
@ -370,7 +378,7 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
should update the status of the tx to 'unapproved'.
|
||||
@param {number} txId - the txMeta Id
|
||||
*/
|
||||
setTxStatusUnapproved (txId) {
|
||||
setTxStatusUnapproved(txId) {
|
||||
this._setTxStatus(txId, 'unapproved')
|
||||
}
|
||||
|
||||
@ -378,7 +386,7 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
should update the status of the tx to 'approved'.
|
||||
@param {number} txId - the txMeta Id
|
||||
*/
|
||||
setTxStatusApproved (txId) {
|
||||
setTxStatusApproved(txId) {
|
||||
this._setTxStatus(txId, 'approved')
|
||||
}
|
||||
|
||||
@ -386,7 +394,7 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
should update the status of the tx to 'signed'.
|
||||
@param {number} txId - the txMeta Id
|
||||
*/
|
||||
setTxStatusSigned (txId) {
|
||||
setTxStatusSigned(txId) {
|
||||
this._setTxStatus(txId, 'signed')
|
||||
}
|
||||
|
||||
@ -395,9 +403,9 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
and add a time stamp for when it was called
|
||||
@param {number} txId - the txMeta Id
|
||||
*/
|
||||
setTxStatusSubmitted (txId) {
|
||||
setTxStatusSubmitted(txId) {
|
||||
const txMeta = this.getTx(txId)
|
||||
txMeta.submittedTime = (new Date()).getTime()
|
||||
txMeta.submittedTime = new Date().getTime()
|
||||
this.updateTx(txMeta, 'txStateManager - add submitted time stamp')
|
||||
this._setTxStatus(txId, 'submitted')
|
||||
}
|
||||
@ -406,7 +414,7 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
should update the status of the tx to 'confirmed'.
|
||||
@param {number} txId - the txMeta Id
|
||||
*/
|
||||
setTxStatusConfirmed (txId) {
|
||||
setTxStatusConfirmed(txId) {
|
||||
this._setTxStatus(txId, 'confirmed')
|
||||
}
|
||||
|
||||
@ -414,7 +422,7 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
should update the status of the tx to 'dropped'.
|
||||
@param {number} txId - the txMeta Id
|
||||
*/
|
||||
setTxStatusDropped (txId) {
|
||||
setTxStatusDropped(txId) {
|
||||
this._setTxStatus(txId, 'dropped')
|
||||
}
|
||||
|
||||
@ -424,7 +432,7 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
@param {number} txId - the txMeta Id
|
||||
@param {erroObject} err - error object
|
||||
*/
|
||||
setTxStatusFailed (txId, err) {
|
||||
setTxStatusFailed(txId, err) {
|
||||
const error = err || new Error('Internal metamask failure')
|
||||
|
||||
const txMeta = this.getTx(txId)
|
||||
@ -442,13 +450,19 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
from the txList
|
||||
@param {string} address - hex string of the from address on the txParams to remove
|
||||
*/
|
||||
wipeTransactions (address) {
|
||||
wipeTransactions(address) {
|
||||
// network only tx
|
||||
const txs = this.getFullTxList()
|
||||
const network = this.getNetwork()
|
||||
|
||||
// Filter out the ones from the current account and network
|
||||
const otherAccountTxs = txs.filter((txMeta) => !(txMeta.txParams.from === address && txMeta.metamaskNetworkId === network))
|
||||
const otherAccountTxs = txs.filter(
|
||||
(txMeta) =>
|
||||
!(
|
||||
txMeta.txParams.from === address &&
|
||||
txMeta.metamaskNetworkId === network
|
||||
),
|
||||
)
|
||||
|
||||
// Update state
|
||||
this._saveTxList(otherAccountTxs)
|
||||
@ -475,7 +489,7 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
@emits ${txMeta.id}:finished - if it is a finished state. Passes the txMeta
|
||||
@emits update:badge
|
||||
*/
|
||||
_setTxStatus (txId, status) {
|
||||
_setTxStatus(txId, status) {
|
||||
const txMeta = this.getTx(txId)
|
||||
|
||||
if (!txMeta) {
|
||||
@ -501,11 +515,11 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
@param {array} transactions - the list of transactions to save
|
||||
*/
|
||||
// Function is intended only for internal use
|
||||
_saveTxList (transactions) {
|
||||
_saveTxList(transactions) {
|
||||
this.store.updateState({ transactions })
|
||||
}
|
||||
|
||||
_removeTx (txId) {
|
||||
_removeTx(txId) {
|
||||
const transactionList = this.getFullTxList()
|
||||
this._saveTxList(transactionList.filter((txMeta) => txMeta.id !== txId))
|
||||
}
|
||||
@ -514,9 +528,11 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
* Filters out the unapproved transactions
|
||||
*/
|
||||
|
||||
clearUnapprovedTxs () {
|
||||
clearUnapprovedTxs() {
|
||||
const transactions = this.getFullTxList()
|
||||
const nonUnapprovedTxs = transactions.filter((tx) => tx.status !== 'unapproved')
|
||||
const nonUnapprovedTxs = transactions.filter(
|
||||
(tx) => tx.status !== 'unapproved',
|
||||
)
|
||||
this._saveTxList(nonUnapprovedTxs)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
/**
|
||||
* @typedef {Object} FirstTimeState
|
||||
* @property {Object} config Initial configuration parameters
|
||||
|
@ -5,14 +5,13 @@ import ObservableStore from 'obs-store'
|
||||
* structure of child stores based on configuration
|
||||
*/
|
||||
export default class ComposableObservableStore extends ObservableStore {
|
||||
|
||||
/**
|
||||
* Create a new store
|
||||
*
|
||||
* @param {Object} [initState] - The initial store state
|
||||
* @param {Object} [config] - Map of internal state keys to child stores
|
||||
*/
|
||||
constructor (initState, config) {
|
||||
constructor(initState, config) {
|
||||
super(initState)
|
||||
this.updateStructure(config)
|
||||
}
|
||||
@ -22,7 +21,7 @@ export default class ComposableObservableStore extends ObservableStore {
|
||||
*
|
||||
* @param {Object} [config] - Map of internal state keys to child stores
|
||||
*/
|
||||
updateStructure (config) {
|
||||
updateStructure(config) {
|
||||
this.config = config
|
||||
this.removeAllListeners()
|
||||
for (const key in config) {
|
||||
@ -40,12 +39,14 @@ export default class ComposableObservableStore extends ObservableStore {
|
||||
*
|
||||
* @returns {Object} - Object containing merged child store state
|
||||
*/
|
||||
getFlatState () {
|
||||
getFlatState() {
|
||||
let flatState = {}
|
||||
for (const key in this.config) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.config, key)) {
|
||||
const controller = this.config[key]
|
||||
const state = controller.getState ? controller.getState() : controller.state
|
||||
const state = controller.getState
|
||||
? controller.getState()
|
||||
: controller.state
|
||||
flatState = { ...flatState, ...state }
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,12 @@ import log from 'loglevel'
|
||||
import pify from 'pify'
|
||||
import Web3 from 'web3'
|
||||
import SINGLE_CALL_BALANCES_ABI from 'single-call-balance-checker-abi'
|
||||
import { MAINNET_CHAIN_ID, RINKEBY_CHAIN_ID, ROPSTEN_CHAIN_ID, KOVAN_CHAIN_ID } from '../controllers/network/enums'
|
||||
import {
|
||||
MAINNET_CHAIN_ID,
|
||||
RINKEBY_CHAIN_ID,
|
||||
ROPSTEN_CHAIN_ID,
|
||||
KOVAN_CHAIN_ID,
|
||||
} from '../controllers/network/enums'
|
||||
|
||||
import {
|
||||
SINGLE_CALL_BALANCES_ADDRESS,
|
||||
@ -42,14 +47,13 @@ import { bnToHex } from './util'
|
||||
*
|
||||
*/
|
||||
export default class AccountTracker {
|
||||
|
||||
/**
|
||||
* @param {Object} opts - Options for initializing the controller
|
||||
* @param {Object} opts.provider - An EIP-1193 provider instance that uses the current global network
|
||||
* @param {Object} opts.blockTracker - A block tracker, which emits events for each new block
|
||||
* @param {Function} opts.getCurrentChainId - A function that returns the `chainId` for the current global network
|
||||
*/
|
||||
constructor (opts = {}) {
|
||||
constructor(opts = {}) {
|
||||
const initState = {
|
||||
accounts: {},
|
||||
currentBlockGasLimit: '',
|
||||
@ -71,7 +75,7 @@ export default class AccountTracker {
|
||||
this.web3 = new Web3(this._provider)
|
||||
}
|
||||
|
||||
start () {
|
||||
start() {
|
||||
// remove first to avoid double add
|
||||
this._blockTracker.removeListener('latest', this._updateForBlock)
|
||||
// add listener
|
||||
@ -80,7 +84,7 @@ export default class AccountTracker {
|
||||
this._updateAccounts()
|
||||
}
|
||||
|
||||
stop () {
|
||||
stop() {
|
||||
// remove listener
|
||||
this._blockTracker.removeListener('latest', this._updateForBlock)
|
||||
}
|
||||
@ -96,7 +100,7 @@ export default class AccountTracker {
|
||||
* in sync
|
||||
*
|
||||
*/
|
||||
syncWithAddresses (addresses) {
|
||||
syncWithAddresses(addresses) {
|
||||
const { accounts } = this.store.getState()
|
||||
const locals = Object.keys(accounts)
|
||||
|
||||
@ -125,7 +129,7 @@ export default class AccountTracker {
|
||||
* @param {array} addresses - An array of hex addresses of new accounts to track
|
||||
*
|
||||
*/
|
||||
addAccounts (addresses) {
|
||||
addAccounts(addresses) {
|
||||
const { accounts } = this.store.getState()
|
||||
// add initial state for addresses
|
||||
addresses.forEach((address) => {
|
||||
@ -146,7 +150,7 @@ export default class AccountTracker {
|
||||
* @param {array} an - array of hex addresses to stop tracking
|
||||
*
|
||||
*/
|
||||
removeAccount (addresses) {
|
||||
removeAccount(addresses) {
|
||||
const { accounts } = this.store.getState()
|
||||
// remove each state object
|
||||
addresses.forEach((address) => {
|
||||
@ -160,7 +164,7 @@ export default class AccountTracker {
|
||||
* Removes all addresses and associated balances
|
||||
*/
|
||||
|
||||
clearAccounts () {
|
||||
clearAccounts() {
|
||||
this.store.updateState({ accounts: {} })
|
||||
}
|
||||
|
||||
@ -173,7 +177,7 @@ export default class AccountTracker {
|
||||
* @fires 'block' The updated state, if all account updates are successful
|
||||
*
|
||||
*/
|
||||
async _updateForBlock (blockNumber) {
|
||||
async _updateForBlock(blockNumber) {
|
||||
this._currentBlockNumber = blockNumber
|
||||
|
||||
// block gasLimit polling shouldn't be in account-tracker shouldn't be here...
|
||||
@ -198,26 +202,38 @@ export default class AccountTracker {
|
||||
* @returns {Promise} - after all account balances updated
|
||||
*
|
||||
*/
|
||||
async _updateAccounts () {
|
||||
async _updateAccounts() {
|
||||
const { accounts } = this.store.getState()
|
||||
const addresses = Object.keys(accounts)
|
||||
const chainId = this.getCurrentChainId()
|
||||
|
||||
switch (chainId) {
|
||||
case MAINNET_CHAIN_ID:
|
||||
await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS)
|
||||
await this._updateAccountsViaBalanceChecker(
|
||||
addresses,
|
||||
SINGLE_CALL_BALANCES_ADDRESS,
|
||||
)
|
||||
break
|
||||
|
||||
case RINKEBY_CHAIN_ID:
|
||||
await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_RINKEBY)
|
||||
await this._updateAccountsViaBalanceChecker(
|
||||
addresses,
|
||||
SINGLE_CALL_BALANCES_ADDRESS_RINKEBY,
|
||||
)
|
||||
break
|
||||
|
||||
case ROPSTEN_CHAIN_ID:
|
||||
await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN)
|
||||
await this._updateAccountsViaBalanceChecker(
|
||||
addresses,
|
||||
SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN,
|
||||
)
|
||||
break
|
||||
|
||||
case KOVAN_CHAIN_ID:
|
||||
await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_KOVAN)
|
||||
await this._updateAccountsViaBalanceChecker(
|
||||
addresses,
|
||||
SINGLE_CALL_BALANCES_ADDRESS_KOVAN,
|
||||
)
|
||||
break
|
||||
|
||||
default:
|
||||
@ -233,7 +249,7 @@ export default class AccountTracker {
|
||||
* @returns {Promise} - after the account balance is updated
|
||||
*
|
||||
*/
|
||||
async _updateAccount (address) {
|
||||
async _updateAccount(address) {
|
||||
// query balance
|
||||
const balance = await this._query.getBalance(address)
|
||||
const result = { address, balance }
|
||||
@ -252,15 +268,20 @@ export default class AccountTracker {
|
||||
* @param {*} addresses
|
||||
* @param {*} deployedContractAddress
|
||||
*/
|
||||
async _updateAccountsViaBalanceChecker (addresses, deployedContractAddress) {
|
||||
async _updateAccountsViaBalanceChecker(addresses, deployedContractAddress) {
|
||||
const { accounts } = this.store.getState()
|
||||
this.web3.setProvider(this._provider)
|
||||
const ethContract = this.web3.eth.contract(SINGLE_CALL_BALANCES_ABI).at(deployedContractAddress)
|
||||
const ethContract = this.web3.eth
|
||||
.contract(SINGLE_CALL_BALANCES_ABI)
|
||||
.at(deployedContractAddress)
|
||||
const ethBalance = ['0x0']
|
||||
|
||||
ethContract.balances(addresses, ethBalance, (error, result) => {
|
||||
if (error) {
|
||||
log.warn(`MetaMask - Account Tracker single call balance fetch failed`, error)
|
||||
log.warn(
|
||||
`MetaMask - Account Tracker single call balance fetch failed`,
|
||||
error,
|
||||
)
|
||||
Promise.all(addresses.map(this._updateAccount.bind(this)))
|
||||
return
|
||||
}
|
||||
@ -271,5 +292,4 @@ export default class AccountTracker {
|
||||
this.store.updateState({ accounts })
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
* network does not match any of the specified cases, or if no network is given, returns undefined.
|
||||
*
|
||||
*/
|
||||
export default function getBuyEthUrl ({ network, address, service }) {
|
||||
export default function getBuyEthUrl({ network, address, service }) {
|
||||
// default service by network if not specified
|
||||
if (!service) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
@ -33,7 +33,7 @@ export default function getBuyEthUrl ({ network, address, service }) {
|
||||
}
|
||||
}
|
||||
|
||||
function getDefaultServiceForNetwork (network) {
|
||||
function getDefaultServiceForNetwork(network) {
|
||||
switch (network) {
|
||||
case '1':
|
||||
return 'wyre'
|
||||
@ -46,6 +46,8 @@ function getDefaultServiceForNetwork (network) {
|
||||
case '5':
|
||||
return 'goerli-faucet'
|
||||
default:
|
||||
throw new Error(`No default cryptocurrency exchange or faucet for networkId: "${network}"`)
|
||||
throw new Error(
|
||||
`No default cryptocurrency exchange or faucet for networkId: "${network}"`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -3,12 +3,12 @@
|
||||
* @param {Error} err - error
|
||||
* @returns {Error} - Error with clean stack trace.
|
||||
*/
|
||||
export default function cleanErrorStack (err) {
|
||||
export default function cleanErrorStack(err) {
|
||||
let { name } = err
|
||||
name = (name === undefined) ? 'Error' : String(name)
|
||||
name = name === undefined ? 'Error' : String(name)
|
||||
|
||||
let msg = err.message
|
||||
msg = (msg === undefined) ? '' : String(msg)
|
||||
msg = msg === undefined ? '' : String(msg)
|
||||
|
||||
if (name === '') {
|
||||
err.stack = err.message
|
||||
|
@ -5,8 +5,12 @@ import log from 'loglevel'
|
||||
* @param {{ origin: string }} opts - The middleware options
|
||||
* @returns {Function}
|
||||
*/
|
||||
export default function createLoggerMiddleware (opts) {
|
||||
return function loggerMiddleware (/** @type {any} */ req, /** @type {any} */ res, /** @type {Function} */ next) {
|
||||
export default function createLoggerMiddleware(opts) {
|
||||
return function loggerMiddleware(
|
||||
/** @type {any} */ req,
|
||||
/** @type {any} */ res,
|
||||
/** @type {Function} */ next,
|
||||
) {
|
||||
next((/** @type {Function} */ cb) => {
|
||||
if (res.error) {
|
||||
log.error('Error in RPC response:\n', res)
|
||||
|
@ -6,8 +6,11 @@ import extension from 'extensionizer'
|
||||
* @param {{ location: string, registerOnboarding: Function }} opts - The middleware options
|
||||
* @returns {(req: any, res: any, next: Function, end: Function) => void}
|
||||
*/
|
||||
export default function createOnboardingMiddleware ({ location, registerOnboarding }) {
|
||||
return async function originMiddleware (req, res, next, end) {
|
||||
export default function createOnboardingMiddleware({
|
||||
location,
|
||||
registerOnboarding,
|
||||
}) {
|
||||
return async function originMiddleware(req, res, next, end) {
|
||||
try {
|
||||
if (req.method !== 'wallet_registerOnboarding') {
|
||||
next()
|
||||
@ -16,7 +19,9 @@ export default function createOnboardingMiddleware ({ location, registerOnboardi
|
||||
if (req.tabId && req.tabId !== extension.tabs.TAB_ID_NONE) {
|
||||
await registerOnboarding(location, req.tabId)
|
||||
} else {
|
||||
log.debug(`'wallet_registerOnboarding' message from ${location} ignored due to missing tabId`)
|
||||
log.debug(
|
||||
`'wallet_registerOnboarding' message from ${location} ignored due to missing tabId`,
|
||||
)
|
||||
}
|
||||
res.result = true
|
||||
end()
|
||||
|
@ -3,8 +3,12 @@
|
||||
* @param {{ origin: string }} opts - The middleware options
|
||||
* @returns {Function}
|
||||
*/
|
||||
export default function createOriginMiddleware (opts) {
|
||||
return function originMiddleware (/** @type {any} */ req, /** @type {any} */ _, /** @type {Function} */ next) {
|
||||
export default function createOriginMiddleware(opts) {
|
||||
return function originMiddleware(
|
||||
/** @type {any} */ req,
|
||||
/** @type {any} */ _,
|
||||
/** @type {Function} */ next,
|
||||
) {
|
||||
req.origin = opts.origin
|
||||
next()
|
||||
}
|
||||
|
@ -2,20 +2,18 @@ import { Writable as WritableStream } from 'readable-stream'
|
||||
import promiseToCallback from 'promise-to-callback'
|
||||
|
||||
class AsyncWritableStream extends WritableStream {
|
||||
|
||||
constructor (asyncWriteFn, _opts) {
|
||||
constructor(asyncWriteFn, _opts) {
|
||||
const opts = { objectMode: true, ..._opts }
|
||||
super(opts)
|
||||
this._asyncWriteFn = asyncWriteFn
|
||||
}
|
||||
|
||||
// write from incoming stream to state
|
||||
_write (chunk, encoding, callback) {
|
||||
_write(chunk, encoding, callback) {
|
||||
promiseToCallback(this._asyncWriteFn(chunk, encoding))(callback)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default function createStreamSink (asyncWriteFn, _opts) {
|
||||
export default function createStreamSink(asyncWriteFn, _opts) {
|
||||
return new AsyncWritableStream(asyncWriteFn, _opts)
|
||||
}
|
||||
|
@ -3,8 +3,12 @@
|
||||
* @param {{ tabId: number }} opts - The middleware options
|
||||
* @returns {Function}
|
||||
*/
|
||||
export default function createTabIdMiddleware (opts) {
|
||||
return function tabIdMiddleware (/** @type {any} */ req, /** @type {any} */ _, /** @type {Function} */ next) {
|
||||
export default function createTabIdMiddleware(opts) {
|
||||
return function tabIdMiddleware(
|
||||
/** @type {any} */ req,
|
||||
/** @type {any} */ _,
|
||||
/** @type {Function} */ next,
|
||||
) {
|
||||
req.tabId = opts.tabId
|
||||
next()
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import log from 'loglevel'
|
||||
import createId from './random-id'
|
||||
import { MESSAGE_TYPE } from './enums'
|
||||
|
||||
const hexRe = /^[0-9A-Fa-f]+$/ug
|
||||
const hexRe = /^[0-9A-Fa-f]+$/gu
|
||||
|
||||
/**
|
||||
* Represents, and contains data about, an 'eth_decrypt' type decryption request. These are created when a
|
||||
@ -26,7 +26,6 @@ const hexRe = /^[0-9A-Fa-f]+$/ug
|
||||
*/
|
||||
|
||||
export default class DecryptMessageManager extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Controller in charge of managing - storing, adding, removing, updating - DecryptMessage.
|
||||
*
|
||||
@ -37,7 +36,7 @@ export default class DecryptMessageManager extends EventEmitter {
|
||||
* @property {array} messages Holds all messages that have been created by this DecryptMessageManager
|
||||
*
|
||||
*/
|
||||
constructor () {
|
||||
constructor() {
|
||||
super()
|
||||
this.memStore = new ObservableStore({
|
||||
unapprovedDecryptMsgs: {},
|
||||
@ -52,7 +51,7 @@ export default class DecryptMessageManager extends EventEmitter {
|
||||
* @returns {number} The number of 'unapproved' DecryptMessages in this.messages
|
||||
*
|
||||
*/
|
||||
get unapprovedDecryptMsgCount () {
|
||||
get unapprovedDecryptMsgCount() {
|
||||
return Object.keys(this.getUnapprovedMsgs()).length
|
||||
}
|
||||
|
||||
@ -63,8 +62,9 @@ export default class DecryptMessageManager extends EventEmitter {
|
||||
* this.messages
|
||||
*
|
||||
*/
|
||||
getUnapprovedMsgs () {
|
||||
return this.messages.filter((msg) => msg.status === 'unapproved')
|
||||
getUnapprovedMsgs() {
|
||||
return this.messages
|
||||
.filter((msg) => msg.status === 'unapproved')
|
||||
.reduce((result, msg) => {
|
||||
result[msg.id] = msg
|
||||
return result
|
||||
@ -81,7 +81,7 @@ export default class DecryptMessageManager extends EventEmitter {
|
||||
* @returns {Promise<Buffer>} The raw decrypted message contents
|
||||
*
|
||||
*/
|
||||
addUnapprovedMessageAsync (msgParams, req) {
|
||||
addUnapprovedMessageAsync(msgParams, req) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!msgParams.from) {
|
||||
reject(new Error('MetaMask Decryption: from field is required.'))
|
||||
@ -94,13 +94,23 @@ export default class DecryptMessageManager extends EventEmitter {
|
||||
resolve(data.rawData)
|
||||
return
|
||||
case 'rejected':
|
||||
reject(ethErrors.provider.userRejectedRequest('MetaMask Decryption: User denied message decryption.'))
|
||||
reject(
|
||||
ethErrors.provider.userRejectedRequest(
|
||||
'MetaMask Decryption: User denied message decryption.',
|
||||
),
|
||||
)
|
||||
return
|
||||
case 'errored':
|
||||
reject(new Error('This message cannot be decrypted'))
|
||||
return
|
||||
default:
|
||||
reject(new Error(`MetaMask Decryption: Unknown problem: ${JSON.stringify(msgParams)}`))
|
||||
reject(
|
||||
new Error(
|
||||
`MetaMask Decryption: Unknown problem: ${JSON.stringify(
|
||||
msgParams,
|
||||
)}`,
|
||||
),
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -116,15 +126,19 @@ export default class DecryptMessageManager extends EventEmitter {
|
||||
* @returns {number} The id of the newly created DecryptMessage.
|
||||
*
|
||||
*/
|
||||
addUnapprovedMessage (msgParams, req) {
|
||||
log.debug(`DecryptMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`)
|
||||
addUnapprovedMessage(msgParams, req) {
|
||||
log.debug(
|
||||
`DecryptMessageManager addUnapprovedMessage: ${JSON.stringify(
|
||||
msgParams,
|
||||
)}`,
|
||||
)
|
||||
// add origin from request
|
||||
if (req) {
|
||||
msgParams.origin = req.origin
|
||||
}
|
||||
msgParams.data = this.normalizeMsgData(msgParams.data)
|
||||
// create txData obj with parameters and meta data
|
||||
const time = (new Date()).getTime()
|
||||
const time = new Date().getTime()
|
||||
const msgId = createId()
|
||||
const msgData = {
|
||||
id: msgId,
|
||||
@ -147,7 +161,7 @@ export default class DecryptMessageManager extends EventEmitter {
|
||||
* @param {Message} msg The DecryptMessage to add to this.messages
|
||||
*
|
||||
*/
|
||||
addMsg (msg) {
|
||||
addMsg(msg) {
|
||||
this.messages.push(msg)
|
||||
this._saveMsgList()
|
||||
}
|
||||
@ -160,7 +174,7 @@ export default class DecryptMessageManager extends EventEmitter {
|
||||
* if no DecryptMessage has that id.
|
||||
*
|
||||
*/
|
||||
getMsg (msgId) {
|
||||
getMsg(msgId) {
|
||||
return this.messages.find((msg) => msg.id === msgId)
|
||||
}
|
||||
|
||||
@ -173,7 +187,7 @@ export default class DecryptMessageManager extends EventEmitter {
|
||||
* @returns {Promise<object>} Promises the msgParams object with metamaskId removed.
|
||||
*
|
||||
*/
|
||||
approveMessage (msgParams) {
|
||||
approveMessage(msgParams) {
|
||||
this.setMsgStatusApproved(msgParams.metamaskId)
|
||||
return this.prepMsgForDecryption(msgParams)
|
||||
}
|
||||
@ -184,7 +198,7 @@ export default class DecryptMessageManager extends EventEmitter {
|
||||
* @param {number} msgId The id of the DecryptMessage to approve.
|
||||
*
|
||||
*/
|
||||
setMsgStatusApproved (msgId) {
|
||||
setMsgStatusApproved(msgId) {
|
||||
this._setMsgStatus(msgId, 'approved')
|
||||
}
|
||||
|
||||
@ -196,7 +210,7 @@ export default class DecryptMessageManager extends EventEmitter {
|
||||
* @param {buffer} rawData The raw data of the message request
|
||||
*
|
||||
*/
|
||||
setMsgStatusDecrypted (msgId, rawData) {
|
||||
setMsgStatusDecrypted(msgId, rawData) {
|
||||
const msg = this.getMsg(msgId)
|
||||
msg.rawData = rawData
|
||||
this._updateMsg(msg)
|
||||
@ -210,7 +224,7 @@ export default class DecryptMessageManager extends EventEmitter {
|
||||
* @returns {Promise<object>} Promises the msgParams with the metamaskId property removed
|
||||
*
|
||||
*/
|
||||
prepMsgForDecryption (msgParams) {
|
||||
prepMsgForDecryption(msgParams) {
|
||||
delete msgParams.metamaskId
|
||||
return Promise.resolve(msgParams)
|
||||
}
|
||||
@ -221,7 +235,7 @@ export default class DecryptMessageManager extends EventEmitter {
|
||||
* @param {number} msgId The id of the DecryptMessage to reject.
|
||||
*
|
||||
*/
|
||||
rejectMsg (msgId) {
|
||||
rejectMsg(msgId) {
|
||||
this._setMsgStatus(msgId, 'rejected')
|
||||
}
|
||||
|
||||
@ -231,7 +245,7 @@ export default class DecryptMessageManager extends EventEmitter {
|
||||
* @param {number} msgId The id of the TypedMessage to error
|
||||
*
|
||||
*/
|
||||
errorMessage (msgId, error) {
|
||||
errorMessage(msgId, error) {
|
||||
const msg = this.getMsg(msgId)
|
||||
msg.error = error
|
||||
this._updateMsg(msg)
|
||||
@ -251,15 +265,21 @@ export default class DecryptMessageManager extends EventEmitter {
|
||||
* with the DecryptMessage
|
||||
*
|
||||
*/
|
||||
_setMsgStatus (msgId, status) {
|
||||
_setMsgStatus(msgId, status) {
|
||||
const msg = this.getMsg(msgId)
|
||||
if (!msg) {
|
||||
throw new Error(`DecryptMessageManager - Message not found for id: "${msgId}".`)
|
||||
throw new Error(
|
||||
`DecryptMessageManager - Message not found for id: "${msgId}".`,
|
||||
)
|
||||
}
|
||||
msg.status = status
|
||||
this._updateMsg(msg)
|
||||
this.emit(`${msgId}:${status}`, msg)
|
||||
if (status === 'rejected' || status === 'decrypted' || status === 'errored') {
|
||||
if (
|
||||
status === 'rejected' ||
|
||||
status === 'decrypted' ||
|
||||
status === 'errored'
|
||||
) {
|
||||
this.emit(`${msgId}:finished`, msg)
|
||||
}
|
||||
}
|
||||
@ -273,7 +293,7 @@ export default class DecryptMessageManager extends EventEmitter {
|
||||
* id) in this.messages
|
||||
*
|
||||
*/
|
||||
_updateMsg (msg) {
|
||||
_updateMsg(msg) {
|
||||
const index = this.messages.findIndex((message) => message.id === msg.id)
|
||||
if (index !== -1) {
|
||||
this.messages[index] = msg
|
||||
@ -288,10 +308,13 @@ export default class DecryptMessageManager extends EventEmitter {
|
||||
* @fires 'updateBadge'
|
||||
*
|
||||
*/
|
||||
_saveMsgList () {
|
||||
_saveMsgList() {
|
||||
const unapprovedDecryptMsgs = this.getUnapprovedMsgs()
|
||||
const unapprovedDecryptMsgCount = Object.keys(unapprovedDecryptMsgs).length
|
||||
this.memStore.updateState({ unapprovedDecryptMsgs, unapprovedDecryptMsgCount })
|
||||
this.memStore.updateState({
|
||||
unapprovedDecryptMsgs,
|
||||
unapprovedDecryptMsgCount,
|
||||
})
|
||||
this.emit('updateBadge')
|
||||
}
|
||||
|
||||
@ -302,7 +325,7 @@ export default class DecryptMessageManager extends EventEmitter {
|
||||
* @returns {string} A hex string conversion of the buffer data
|
||||
*
|
||||
*/
|
||||
normalizeMsgData (data) {
|
||||
normalizeMsgData(data) {
|
||||
try {
|
||||
const stripped = ethUtil.stripHexPrefix(data)
|
||||
if (stripped.match(hexRe)) {
|
||||
@ -314,5 +337,4 @@ export default class DecryptMessageManager extends EventEmitter {
|
||||
|
||||
return ethUtil.bufferToHex(Buffer.from(data, 'utf8'))
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ import { MESSAGE_TYPE } from './enums'
|
||||
*/
|
||||
|
||||
export default class EncryptionPublicKeyManager extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Controller in charge of managing - storing, adding, removing, updating - EncryptionPublicKey.
|
||||
*
|
||||
@ -34,7 +33,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
|
||||
* @property {array} messages Holds all messages that have been created by this EncryptionPublicKeyManager
|
||||
*
|
||||
*/
|
||||
constructor () {
|
||||
constructor() {
|
||||
super()
|
||||
this.memStore = new ObservableStore({
|
||||
unapprovedEncryptionPublicKeyMsgs: {},
|
||||
@ -49,7 +48,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
|
||||
* @returns {number} The number of 'unapproved' EncryptionPublicKeys in this.messages
|
||||
*
|
||||
*/
|
||||
get unapprovedEncryptionPublicKeyMsgCount () {
|
||||
get unapprovedEncryptionPublicKeyMsgCount() {
|
||||
return Object.keys(this.getUnapprovedMsgs()).length
|
||||
}
|
||||
|
||||
@ -60,8 +59,9 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
|
||||
* this.messages
|
||||
*
|
||||
*/
|
||||
getUnapprovedMsgs () {
|
||||
return this.messages.filter((msg) => msg.status === 'unapproved')
|
||||
getUnapprovedMsgs() {
|
||||
return this.messages
|
||||
.filter((msg) => msg.status === 'unapproved')
|
||||
.reduce((result, msg) => {
|
||||
result[msg.id] = msg
|
||||
return result
|
||||
@ -78,7 +78,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
|
||||
* @returns {Promise<Buffer>} The raw public key contents
|
||||
*
|
||||
*/
|
||||
addUnapprovedMessageAsync (address, req) {
|
||||
addUnapprovedMessageAsync(address, req) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!address) {
|
||||
reject(new Error('MetaMask Message: address field is required.'))
|
||||
@ -91,10 +91,20 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
|
||||
resolve(data.rawData)
|
||||
return
|
||||
case 'rejected':
|
||||
reject(ethErrors.provider.userRejectedRequest('MetaMask EncryptionPublicKey: User denied message EncryptionPublicKey.'))
|
||||
reject(
|
||||
ethErrors.provider.userRejectedRequest(
|
||||
'MetaMask EncryptionPublicKey: User denied message EncryptionPublicKey.',
|
||||
),
|
||||
)
|
||||
return
|
||||
default:
|
||||
reject(new Error(`MetaMask EncryptionPublicKey: Unknown problem: ${JSON.stringify(address)}`))
|
||||
reject(
|
||||
new Error(
|
||||
`MetaMask EncryptionPublicKey: Unknown problem: ${JSON.stringify(
|
||||
address,
|
||||
)}`,
|
||||
),
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -110,10 +120,10 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
|
||||
* @returns {number} The id of the newly created EncryptionPublicKey.
|
||||
*
|
||||
*/
|
||||
addUnapprovedMessage (address, req) {
|
||||
addUnapprovedMessage(address, req) {
|
||||
log.debug(`EncryptionPublicKeyManager addUnapprovedMessage: address`)
|
||||
// create txData obj with parameters and meta data
|
||||
const time = (new Date()).getTime()
|
||||
const time = new Date().getTime()
|
||||
const msgId = createId()
|
||||
const msgData = {
|
||||
id: msgId,
|
||||
@ -141,7 +151,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
|
||||
* @param {Message} msg The EncryptionPublicKey to add to this.messages
|
||||
*
|
||||
*/
|
||||
addMsg (msg) {
|
||||
addMsg(msg) {
|
||||
this.messages.push(msg)
|
||||
this._saveMsgList()
|
||||
}
|
||||
@ -154,7 +164,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
|
||||
* if no EncryptionPublicKey has that id.
|
||||
*
|
||||
*/
|
||||
getMsg (msgId) {
|
||||
getMsg(msgId) {
|
||||
return this.messages.find((msg) => msg.id === msgId)
|
||||
}
|
||||
|
||||
@ -167,7 +177,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
|
||||
* @returns {Promise<object>} Promises the msgParams object with metamaskId removed.
|
||||
*
|
||||
*/
|
||||
approveMessage (msgParams) {
|
||||
approveMessage(msgParams) {
|
||||
this.setMsgStatusApproved(msgParams.metamaskId)
|
||||
return this.prepMsgForEncryptionPublicKey(msgParams)
|
||||
}
|
||||
@ -178,7 +188,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
|
||||
* @param {number} msgId The id of the EncryptionPublicKey to approve.
|
||||
*
|
||||
*/
|
||||
setMsgStatusApproved (msgId) {
|
||||
setMsgStatusApproved(msgId) {
|
||||
this._setMsgStatus(msgId, 'approved')
|
||||
}
|
||||
|
||||
@ -190,7 +200,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
|
||||
* @param {buffer} rawData The raw data of the message request
|
||||
*
|
||||
*/
|
||||
setMsgStatusReceived (msgId, rawData) {
|
||||
setMsgStatusReceived(msgId, rawData) {
|
||||
const msg = this.getMsg(msgId)
|
||||
msg.rawData = rawData
|
||||
this._updateMsg(msg)
|
||||
@ -204,7 +214,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
|
||||
* @returns {Promise<object>} Promises the msgParams with the metamaskId property removed
|
||||
*
|
||||
*/
|
||||
prepMsgForEncryptionPublicKey (msgParams) {
|
||||
prepMsgForEncryptionPublicKey(msgParams) {
|
||||
delete msgParams.metamaskId
|
||||
return Promise.resolve(msgParams)
|
||||
}
|
||||
@ -215,7 +225,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
|
||||
* @param {number} msgId The id of the EncryptionPublicKey to reject.
|
||||
*
|
||||
*/
|
||||
rejectMsg (msgId) {
|
||||
rejectMsg(msgId) {
|
||||
this._setMsgStatus(msgId, 'rejected')
|
||||
}
|
||||
|
||||
@ -225,7 +235,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
|
||||
* @param {number} msgId The id of the TypedMessage to error
|
||||
*
|
||||
*/
|
||||
errorMessage (msgId, error) {
|
||||
errorMessage(msgId, error) {
|
||||
const msg = this.getMsg(msgId)
|
||||
msg.error = error
|
||||
this._updateMsg(msg)
|
||||
@ -245,10 +255,12 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
|
||||
* with the EncryptionPublicKey
|
||||
*
|
||||
*/
|
||||
_setMsgStatus (msgId, status) {
|
||||
_setMsgStatus(msgId, status) {
|
||||
const msg = this.getMsg(msgId)
|
||||
if (!msg) {
|
||||
throw new Error(`EncryptionPublicKeyManager - Message not found for id: "${msgId}".`)
|
||||
throw new Error(
|
||||
`EncryptionPublicKeyManager - Message not found for id: "${msgId}".`,
|
||||
)
|
||||
}
|
||||
msg.status = status
|
||||
this._updateMsg(msg)
|
||||
@ -267,7 +279,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
|
||||
* id) in this.messages
|
||||
*
|
||||
*/
|
||||
_updateMsg (msg) {
|
||||
_updateMsg(msg) {
|
||||
const index = this.messages.findIndex((message) => message.id === msg.id)
|
||||
if (index !== -1) {
|
||||
this.messages[index] = msg
|
||||
@ -282,10 +294,15 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
|
||||
* @fires 'updateBadge'
|
||||
*
|
||||
*/
|
||||
_saveMsgList () {
|
||||
_saveMsgList() {
|
||||
const unapprovedEncryptionPublicKeyMsgs = this.getUnapprovedMsgs()
|
||||
const unapprovedEncryptionPublicKeyMsgCount = Object.keys(unapprovedEncryptionPublicKeyMsgs).length
|
||||
this.memStore.updateState({ unapprovedEncryptionPublicKeyMsgs, unapprovedEncryptionPublicKeyMsgCount })
|
||||
const unapprovedEncryptionPublicKeyMsgCount = Object.keys(
|
||||
unapprovedEncryptionPublicKeyMsgs,
|
||||
).length
|
||||
this.memStore.updateState({
|
||||
unapprovedEncryptionPublicKeyMsgs,
|
||||
unapprovedEncryptionPublicKeyMsgCount,
|
||||
})
|
||||
this.emit('updateBadge')
|
||||
}
|
||||
}
|
||||
|
@ -1,2 +1,109 @@
|
||||
const abi = [{ 'constant': true, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }], 'name': 'resolver', 'outputs': [{ 'name': '', 'type': 'address' }], 'payable': false, 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }], 'name': 'owner', 'outputs': [{ 'name': '', 'type': 'address' }], 'payable': false, 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'label', 'type': 'bytes32' }, { 'name': 'owner', 'type': 'address' }], 'name': 'setSubnodeOwner', 'outputs': [], 'payable': false, 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'ttl', 'type': 'uint64' }], 'name': 'setTTL', 'outputs': [], 'payable': false, 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }], 'name': 'ttl', 'outputs': [{ 'name': '', 'type': 'uint64' }], 'payable': false, 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'resolver', 'type': 'address' }], 'name': 'setResolver', 'outputs': [], 'payable': false, 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'owner', 'type': 'address' }], 'name': 'setOwner', 'outputs': [], 'payable': false, 'type': 'function' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'node', 'type': 'bytes32' }, { 'indexed': false, 'name': 'owner', 'type': 'address' }], 'name': 'Transfer', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'node', 'type': 'bytes32' }, { 'indexed': true, 'name': 'label', 'type': 'bytes32' }, { 'indexed': false, 'name': 'owner', 'type': 'address' }], 'name': 'NewOwner', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'node', 'type': 'bytes32' }, { 'indexed': false, 'name': 'resolver', 'type': 'address' }], 'name': 'NewResolver', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'node', 'type': 'bytes32' }, { 'indexed': false, 'name': 'ttl', 'type': 'uint64' }], 'name': 'NewTTL', 'type': 'event' }]
|
||||
const abi = [
|
||||
{
|
||||
constant: true,
|
||||
inputs: [{ name: 'node', type: 'bytes32' }],
|
||||
name: 'resolver',
|
||||
outputs: [{ name: '', type: 'address' }],
|
||||
payable: false,
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [{ name: 'node', type: 'bytes32' }],
|
||||
name: 'owner',
|
||||
outputs: [{ name: '', type: 'address' }],
|
||||
payable: false,
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{ name: 'node', type: 'bytes32' },
|
||||
{ name: 'label', type: 'bytes32' },
|
||||
{ name: 'owner', type: 'address' },
|
||||
],
|
||||
name: 'setSubnodeOwner',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{ name: 'node', type: 'bytes32' },
|
||||
{ name: 'ttl', type: 'uint64' },
|
||||
],
|
||||
name: 'setTTL',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [{ name: 'node', type: 'bytes32' }],
|
||||
name: 'ttl',
|
||||
outputs: [{ name: '', type: 'uint64' }],
|
||||
payable: false,
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{ name: 'node', type: 'bytes32' },
|
||||
{ name: 'resolver', type: 'address' },
|
||||
],
|
||||
name: 'setResolver',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{ name: 'node', type: 'bytes32' },
|
||||
{ name: 'owner', type: 'address' },
|
||||
],
|
||||
name: 'setOwner',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{ indexed: true, name: 'node', type: 'bytes32' },
|
||||
{ indexed: false, name: 'owner', type: 'address' },
|
||||
],
|
||||
name: 'Transfer',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{ indexed: true, name: 'node', type: 'bytes32' },
|
||||
{ indexed: true, name: 'label', type: 'bytes32' },
|
||||
{ indexed: false, name: 'owner', type: 'address' },
|
||||
],
|
||||
name: 'NewOwner',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{ indexed: true, name: 'node', type: 'bytes32' },
|
||||
{ indexed: false, name: 'resolver', type: 'address' },
|
||||
],
|
||||
name: 'NewResolver',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{ indexed: true, name: 'node', type: 'bytes32' },
|
||||
{ indexed: false, name: 'ttl', type: 'uint64' },
|
||||
],
|
||||
name: 'NewTTL',
|
||||
type: 'event',
|
||||
},
|
||||
]
|
||||
export default abi
|
||||
|
@ -1,2 +1,236 @@
|
||||
const abi = [{ 'constant': false, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'hash', 'type': 'bytes32' }], 'name': 'setContent', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }], 'name': 'content', 'outputs': [{ 'name': '', 'type': 'bytes32' }], 'payable': false, 'stateMutability': 'view', 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': 'interfaceID', 'type': 'bytes4' }], 'name': 'supportsInterface', 'outputs': [{ 'name': '', 'type': 'bool' }], 'payable': false, 'stateMutability': 'pure', 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'key', 'type': 'string' }, { 'name': 'value', 'type': 'string' }], 'name': 'setText', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'contentTypes', 'type': 'uint256' }], 'name': 'ABI', 'outputs': [{ 'name': 'contentType', 'type': 'uint256' }, { 'name': 'data', 'type': 'bytes' }], 'payable': false, 'stateMutability': 'view', 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'x', 'type': 'bytes32' }, { 'name': 'y', 'type': 'bytes32' }], 'name': 'setPubkey', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'hash', 'type': 'bytes' }], 'name': 'setContenthash', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }], 'name': 'addr', 'outputs': [{ 'name': '', 'type': 'address' }], 'payable': false, 'stateMutability': 'view', 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'key', 'type': 'string' }], 'name': 'text', 'outputs': [{ 'name': '', 'type': 'string' }], 'payable': false, 'stateMutability': 'view', 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'contentType', 'type': 'uint256' }, { 'name': 'data', 'type': 'bytes' }], 'name': 'setABI', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }], 'name': 'name', 'outputs': [{ 'name': '', 'type': 'string' }], 'payable': false, 'stateMutability': 'view', 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'name', 'type': 'string' }], 'name': 'setName', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }], 'name': 'contenthash', 'outputs': [{ 'name': '', 'type': 'bytes' }], 'payable': false, 'stateMutability': 'view', 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }], 'name': 'pubkey', 'outputs': [{ 'name': 'x', 'type': 'bytes32' }, { 'name': 'y', 'type': 'bytes32' }], 'payable': false, 'stateMutability': 'view', 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': 'node', 'type': 'bytes32' }, { 'name': 'addr', 'type': 'address' }], 'name': 'setAddr', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function' }, { 'inputs': [{ 'name': 'ensAddr', 'type': 'address' }], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'node', 'type': 'bytes32' }, { 'indexed': false, 'name': 'a', 'type': 'address' }], 'name': 'AddrChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'node', 'type': 'bytes32' }, { 'indexed': false, 'name': 'name', 'type': 'string' }], 'name': 'NameChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'node', 'type': 'bytes32' }, { 'indexed': true, 'name': 'contentType', 'type': 'uint256' }], 'name': 'ABIChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'node', 'type': 'bytes32' }, { 'indexed': false, 'name': 'x', 'type': 'bytes32' }, { 'indexed': false, 'name': 'y', 'type': 'bytes32' }], 'name': 'PubkeyChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'node', 'type': 'bytes32' }, { 'indexed': false, 'name': 'indexedKey', 'type': 'string' }, { 'indexed': false, 'name': 'key', 'type': 'string' }], 'name': 'TextChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'node', 'type': 'bytes32' }, { 'indexed': false, 'name': 'hash', 'type': 'bytes' }], 'name': 'ContenthashChanged', 'type': 'event' }]
|
||||
const abi = [
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{ name: 'node', type: 'bytes32' },
|
||||
{ name: 'hash', type: 'bytes32' },
|
||||
],
|
||||
name: 'setContent',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [{ name: 'node', type: 'bytes32' }],
|
||||
name: 'content',
|
||||
outputs: [{ name: '', type: 'bytes32' }],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [{ name: 'interfaceID', type: 'bytes4' }],
|
||||
name: 'supportsInterface',
|
||||
outputs: [{ name: '', type: 'bool' }],
|
||||
payable: false,
|
||||
stateMutability: 'pure',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{ name: 'node', type: 'bytes32' },
|
||||
{ name: 'key', type: 'string' },
|
||||
{ name: 'value', type: 'string' },
|
||||
],
|
||||
name: 'setText',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{ name: 'node', type: 'bytes32' },
|
||||
{ name: 'contentTypes', type: 'uint256' },
|
||||
],
|
||||
name: 'ABI',
|
||||
outputs: [
|
||||
{ name: 'contentType', type: 'uint256' },
|
||||
{ name: 'data', type: 'bytes' },
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{ name: 'node', type: 'bytes32' },
|
||||
{ name: 'x', type: 'bytes32' },
|
||||
{ name: 'y', type: 'bytes32' },
|
||||
],
|
||||
name: 'setPubkey',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{ name: 'node', type: 'bytes32' },
|
||||
{ name: 'hash', type: 'bytes' },
|
||||
],
|
||||
name: 'setContenthash',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [{ name: 'node', type: 'bytes32' }],
|
||||
name: 'addr',
|
||||
outputs: [{ name: '', type: 'address' }],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{ name: 'node', type: 'bytes32' },
|
||||
{ name: 'key', type: 'string' },
|
||||
],
|
||||
name: 'text',
|
||||
outputs: [{ name: '', type: 'string' }],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{ name: 'node', type: 'bytes32' },
|
||||
{ name: 'contentType', type: 'uint256' },
|
||||
{ name: 'data', type: 'bytes' },
|
||||
],
|
||||
name: 'setABI',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [{ name: 'node', type: 'bytes32' }],
|
||||
name: 'name',
|
||||
outputs: [{ name: '', type: 'string' }],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{ name: 'node', type: 'bytes32' },
|
||||
{ name: 'name', type: 'string' },
|
||||
],
|
||||
name: 'setName',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [{ name: 'node', type: 'bytes32' }],
|
||||
name: 'contenthash',
|
||||
outputs: [{ name: '', type: 'bytes' }],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [{ name: 'node', type: 'bytes32' }],
|
||||
name: 'pubkey',
|
||||
outputs: [
|
||||
{ name: 'x', type: 'bytes32' },
|
||||
{ name: 'y', type: 'bytes32' },
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{ name: 'node', type: 'bytes32' },
|
||||
{ name: 'addr', type: 'address' },
|
||||
],
|
||||
name: 'setAddr',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
inputs: [{ name: 'ensAddr', type: 'address' }],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'constructor',
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{ indexed: true, name: 'node', type: 'bytes32' },
|
||||
{ indexed: false, name: 'a', type: 'address' },
|
||||
],
|
||||
name: 'AddrChanged',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{ indexed: true, name: 'node', type: 'bytes32' },
|
||||
{ indexed: false, name: 'name', type: 'string' },
|
||||
],
|
||||
name: 'NameChanged',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{ indexed: true, name: 'node', type: 'bytes32' },
|
||||
{ indexed: true, name: 'contentType', type: 'uint256' },
|
||||
],
|
||||
name: 'ABIChanged',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{ indexed: true, name: 'node', type: 'bytes32' },
|
||||
{ indexed: false, name: 'x', type: 'bytes32' },
|
||||
{ indexed: false, name: 'y', type: 'bytes32' },
|
||||
],
|
||||
name: 'PubkeyChanged',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{ indexed: true, name: 'node', type: 'bytes32' },
|
||||
{ indexed: false, name: 'indexedKey', type: 'string' },
|
||||
{ indexed: false, name: 'key', type: 'string' },
|
||||
],
|
||||
name: 'TextChanged',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{ indexed: true, name: 'node', type: 'bytes32' },
|
||||
{ indexed: false, name: 'hash', type: 'bytes' },
|
||||
],
|
||||
name: 'ContenthashChanged',
|
||||
type: 'event',
|
||||
},
|
||||
]
|
||||
export default abi
|
||||
|
@ -5,7 +5,7 @@ import contentHash from 'content-hash'
|
||||
import registryAbi from './contracts/registry'
|
||||
import resolverAbi from './contracts/resolver'
|
||||
|
||||
export default async function resolveEnsToIpfsContentId ({ provider, name }) {
|
||||
export default async function resolveEnsToIpfsContentId({ provider, name }) {
|
||||
const eth = new Eth(provider)
|
||||
const hash = namehash.hash(name)
|
||||
const contract = new EthContract(eth)
|
||||
@ -13,7 +13,9 @@ export default async function resolveEnsToIpfsContentId ({ provider, name }) {
|
||||
const chainId = Number.parseInt(await eth.net_version(), 10)
|
||||
const registryAddress = getRegistryForChainId(chainId)
|
||||
if (!registryAddress) {
|
||||
throw new Error(`EnsIpfsResolver - no known ens-ipfs registry for chainId "${chainId}"`)
|
||||
throw new Error(
|
||||
`EnsIpfsResolver - no known ens-ipfs registry for chainId "${chainId}"`,
|
||||
)
|
||||
}
|
||||
const Registry = contract(registryAbi).at(registryAddress)
|
||||
// lookup resolver
|
||||
@ -33,7 +35,9 @@ export default async function resolveEnsToIpfsContentId ({ provider, name }) {
|
||||
const type = contentHash.getCodec(rawContentHash)
|
||||
|
||||
if (type === 'ipfs-ns' || type === 'ipns-ns') {
|
||||
decodedContentHash = contentHash.helpers.cidV0ToV1Base32(decodedContentHash)
|
||||
decodedContentHash = contentHash.helpers.cidV0ToV1Base32(
|
||||
decodedContentHash,
|
||||
)
|
||||
}
|
||||
|
||||
return { type, hash: decodedContentHash }
|
||||
@ -43,15 +47,25 @@ export default async function resolveEnsToIpfsContentId ({ provider, name }) {
|
||||
const contentLookupResult = await Resolver.content(hash)
|
||||
const content = contentLookupResult[0]
|
||||
if (hexValueIsEmpty(content)) {
|
||||
throw new Error(`EnsIpfsResolver - no content ID found for name "${name}"`)
|
||||
throw new Error(
|
||||
`EnsIpfsResolver - no content ID found for name "${name}"`,
|
||||
)
|
||||
}
|
||||
return { type: 'swarm-ns', hash: content.slice(2) }
|
||||
}
|
||||
throw new Error(`EnsIpfsResolver - the resolver for name "${name}" is not standard, it should either supports contenthash() or content()`)
|
||||
throw new Error(
|
||||
`EnsIpfsResolver - the resolver for name "${name}" is not standard, it should either supports contenthash() or content()`,
|
||||
)
|
||||
}
|
||||
|
||||
function hexValueIsEmpty (value) {
|
||||
return [undefined, null, '0x', '0x0', '0x0000000000000000000000000000000000000000000000000000000000000000'].includes(value)
|
||||
function hexValueIsEmpty(value) {
|
||||
return [
|
||||
undefined,
|
||||
null,
|
||||
'0x',
|
||||
'0x0',
|
||||
'0x0000000000000000000000000000000000000000000000000000000000000000',
|
||||
].includes(value)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -59,7 +73,7 @@ function hexValueIsEmpty (value) {
|
||||
* @param {number} chainId the chain ID
|
||||
* @returns {string|null} the registry address if known, null otherwise
|
||||
*/
|
||||
function getRegistryForChainId (chainId) {
|
||||
function getRegistryForChainId(chainId) {
|
||||
switch (chainId) {
|
||||
case 1:
|
||||
case 3:
|
||||
|
@ -3,21 +3,27 @@ import resolveEnsToIpfsContentId from './resolver'
|
||||
|
||||
const supportedTopLevelDomains = ['eth']
|
||||
|
||||
export default function setupEnsIpfsResolver ({ provider, getCurrentNetwork, getIpfsGateway }) {
|
||||
|
||||
export default function setupEnsIpfsResolver({
|
||||
provider,
|
||||
getCurrentNetwork,
|
||||
getIpfsGateway,
|
||||
}) {
|
||||
// install listener
|
||||
const urlPatterns = supportedTopLevelDomains.map((tld) => `*://*.${tld}/*`)
|
||||
extension.webRequest.onErrorOccurred.addListener(webRequestDidFail, { urls: urlPatterns, types: ['main_frame'] })
|
||||
extension.webRequest.onErrorOccurred.addListener(webRequestDidFail, {
|
||||
urls: urlPatterns,
|
||||
types: ['main_frame'],
|
||||
})
|
||||
|
||||
// return api object
|
||||
return {
|
||||
// uninstall listener
|
||||
remove () {
|
||||
remove() {
|
||||
extension.webRequest.onErrorOccurred.removeListener(webRequestDidFail)
|
||||
},
|
||||
}
|
||||
|
||||
async function webRequestDidFail (details) {
|
||||
async function webRequestDidFail(details) {
|
||||
const { tabId, url } = details
|
||||
// ignore requests that are not associated with tabs
|
||||
// only attempt ENS resolution on mainnet
|
||||
@ -36,14 +42,17 @@ export default function setupEnsIpfsResolver ({ provider, getCurrentNetwork, get
|
||||
attemptResolve({ tabId, name, pathname, search, fragment })
|
||||
}
|
||||
|
||||
async function attemptResolve ({ tabId, name, pathname, search, fragment }) {
|
||||
async function attemptResolve({ tabId, name, pathname, search, fragment }) {
|
||||
const ipfsGateway = getIpfsGateway()
|
||||
extension.tabs.update(tabId, { url: `loading.html` })
|
||||
let url = `https://app.ens.domains/name/${name}`
|
||||
try {
|
||||
const { type, hash } = await resolveEnsToIpfsContentId({ provider, name })
|
||||
if (type === 'ipfs-ns' || type === 'ipns-ns') {
|
||||
const resolvedUrl = `https://${hash}.${type.slice(0, 4)}.${ipfsGateway}${pathname}${search || ''}${fragment || ''}`
|
||||
const resolvedUrl = `https://${hash}.${type.slice(
|
||||
0,
|
||||
4,
|
||||
)}.${ipfsGateway}${pathname}${search || ''}${fragment || ''}`
|
||||
try {
|
||||
// check if ipfs gateway has result
|
||||
const response = await window.fetch(resolvedUrl, { method: 'HEAD' })
|
||||
@ -54,11 +63,15 @@ export default function setupEnsIpfsResolver ({ provider, getCurrentNetwork, get
|
||||
console.warn(err)
|
||||
}
|
||||
} else if (type === 'swarm-ns') {
|
||||
url = `https://swarm-gateways.net/bzz:/${hash}${pathname}${search || ''}${fragment || ''}`
|
||||
url = `https://swarm-gateways.net/bzz:/${hash}${pathname}${
|
||||
search || ''
|
||||
}${fragment || ''}`
|
||||
} else if (type === 'onion' || type === 'onion3') {
|
||||
url = `http://${hash}.onion${pathname}${search || ''}${fragment || ''}`
|
||||
} else if (type === 'zeronet') {
|
||||
url = `http://127.0.0.1:43110/${hash}${pathname}${search || ''}${fragment || ''}`
|
||||
url = `http://127.0.0.1:43110/${hash}${pathname}${search || ''}${
|
||||
fragment || ''
|
||||
}`
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(err)
|
||||
|
@ -12,12 +12,14 @@ const errorLabelPrefix = 'Error: '
|
||||
* // returns 'Transaction Failed: replacement transaction underpriced'
|
||||
* extractEthjsErrorMessage(`Error: [ethjs-rpc] rpc error with payload {"id":3947817945380,"jsonrpc":"2.0","params":["0xf8eb8208708477359400830398539406012c8cf97bead5deae237070f9587f8e7a266d80b8843d7d3f5a0000000000000000000000000000000000000000000000000000000000081d1a000000000000000000000000000000000000000000000000001ff973cafa800000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000000000003f48025a04c32a9b630e0d9e7ff361562d850c86b7a884908135956a7e4a336fa0300d19ca06830776423f25218e8d19b267161db526e66895567147015b1f3fc47aef9a3c7"],"method":"eth_sendRawTransaction"} Error: replacement transaction underpriced`)
|
||||
*
|
||||
*/
|
||||
export default function extractEthjsErrorMessage (errorMessage) {
|
||||
*/
|
||||
export default function extractEthjsErrorMessage(errorMessage) {
|
||||
const isEthjsRpcError = errorMessage.includes(ethJsRpcSlug)
|
||||
if (isEthjsRpcError) {
|
||||
const payloadAndError = errorMessage.slice(ethJsRpcSlug.length)
|
||||
const originalError = payloadAndError.slice(payloadAndError.indexOf(errorLabelPrefix) + errorLabelPrefix.length)
|
||||
const originalError = payloadAndError.slice(
|
||||
payloadAndError.indexOf(errorLabelPrefix) + errorLabelPrefix.length,
|
||||
)
|
||||
return originalError
|
||||
}
|
||||
return errorMessage
|
||||
|
@ -1,5 +1,5 @@
|
||||
const fetchWithTimeout = ({ timeout = 120000 } = {}) => {
|
||||
return async function _fetch (url, opts) {
|
||||
return async function _fetch(url, opts) {
|
||||
const abortController = new window.AbortController()
|
||||
const abortSignal = abortController.signal
|
||||
const f = window.fetch(url, {
|
||||
|
@ -1,13 +1,9 @@
|
||||
|
||||
/**
|
||||
* 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'
|
||||
) {
|
||||
if (process.env.IN_TEST !== 'true' && process.env.METAMASK_ENV !== 'test') {
|
||||
freeze(global, 'Promise')
|
||||
}
|
||||
|
||||
@ -24,10 +20,10 @@ if (
|
||||
* @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) {
|
||||
|
||||
function freeze(target, key, value, enumerable = true) {
|
||||
const opts = {
|
||||
configurable: false, writable: false,
|
||||
configurable: false,
|
||||
writable: false,
|
||||
}
|
||||
|
||||
if (value === undefined) {
|
||||
|
@ -2,16 +2,16 @@ import extension from 'extensionizer'
|
||||
import promisify from 'pify'
|
||||
import allLocales from '../../_locales/index.json'
|
||||
|
||||
const getPreferredLocales = extension.i18n ? promisify(
|
||||
extension.i18n.getAcceptLanguages,
|
||||
{ errorFirst: false },
|
||||
) : async () => []
|
||||
const getPreferredLocales = extension.i18n
|
||||
? promisify(extension.i18n.getAcceptLanguages, { errorFirst: false })
|
||||
: async () => []
|
||||
|
||||
// mapping some browsers return hyphen instead underscore in locale codes (e.g. zh_TW -> zh-tw)
|
||||
const existingLocaleCodes = {}
|
||||
allLocales.forEach((locale) => {
|
||||
if (locale && locale.code) {
|
||||
existingLocaleCodes[locale.code.toLowerCase().replace('_', '-')] = locale.code
|
||||
existingLocaleCodes[locale.code.toLowerCase().replace('_', '-')] =
|
||||
locale.code
|
||||
}
|
||||
})
|
||||
|
||||
@ -22,7 +22,7 @@ allLocales.forEach((locale) => {
|
||||
* @returns {Promise<string>} - Promises a locale code, either one from the user's preferred list that we have a translation for, or 'en'
|
||||
*
|
||||
*/
|
||||
export default async function getFirstPreferredLangCode () {
|
||||
export default async function getFirstPreferredLangCode() {
|
||||
let userPreferredLocaleCodes
|
||||
|
||||
try {
|
||||
@ -40,7 +40,9 @@ export default async function getFirstPreferredLangCode () {
|
||||
|
||||
const firstPreferredLangCode = userPreferredLocaleCodes
|
||||
.map((code) => code.toLowerCase().replace('_', '-'))
|
||||
.find((code) => Object.prototype.hasOwnProperty.call(existingLocaleCodes, code))
|
||||
.find((code) =>
|
||||
Object.prototype.hasOwnProperty.call(existingLocaleCodes, code),
|
||||
)
|
||||
|
||||
return existingLocaleCodes[firstPreferredLangCode] || 'en'
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import { cloneDeep } from 'lodash'
|
||||
* replaced with the javascript type of that value.
|
||||
*
|
||||
*/
|
||||
export default function getObjStructure (obj) {
|
||||
export default function getObjStructure(obj) {
|
||||
const structure = cloneDeep(obj)
|
||||
return deepMap(structure, (value) => {
|
||||
return value === null ? 'null' : typeof value
|
||||
@ -36,7 +36,7 @@ export default function getObjStructure (obj) {
|
||||
* @param {Function} visit - The modifier to apply to each non-object property value
|
||||
* @returns {Object} - The modified object
|
||||
*/
|
||||
function deepMap (target = {}, visit) {
|
||||
function deepMap(target = {}, visit) {
|
||||
Object.entries(target).forEach(([key, value]) => {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
target[key] = deepMap(value, visit)
|
||||
|
@ -6,11 +6,10 @@ import { checkForError } from './util'
|
||||
* A wrapper around the extension's storage local API
|
||||
*/
|
||||
export default class ExtensionStore {
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
constructor () {
|
||||
constructor() {
|
||||
this.isSupported = Boolean(extension.storage.local)
|
||||
if (!this.isSupported) {
|
||||
log.error('Storage local API not available.')
|
||||
@ -21,7 +20,7 @@ export default class ExtensionStore {
|
||||
* Returns all of the keys currently saved
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
async get () {
|
||||
async get() {
|
||||
if (!this.isSupported) {
|
||||
return undefined
|
||||
}
|
||||
@ -39,7 +38,7 @@ export default class ExtensionStore {
|
||||
* @param {Object} state - The state to set
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async set (state) {
|
||||
async set(state) {
|
||||
return this._set(state)
|
||||
}
|
||||
|
||||
@ -48,7 +47,7 @@ export default class ExtensionStore {
|
||||
* @private
|
||||
* @returns {Object} - the key-value map from local storage
|
||||
*/
|
||||
_get () {
|
||||
_get() {
|
||||
const { local } = extension.storage
|
||||
return new Promise((resolve, reject) => {
|
||||
local.get(null, (/** @type {any} */ result) => {
|
||||
@ -68,7 +67,7 @@ export default class ExtensionStore {
|
||||
* @returns {Promise<void>}
|
||||
* @private
|
||||
*/
|
||||
_set (obj) {
|
||||
_set(obj) {
|
||||
const { local } = extension.storage
|
||||
return new Promise((resolve, reject) => {
|
||||
local.set(obj, () => {
|
||||
@ -88,6 +87,6 @@ export default class ExtensionStore {
|
||||
* @param {Object} obj - The object to check
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isEmpty (obj) {
|
||||
function isEmpty(obj) {
|
||||
return Object.keys(obj).length === 0
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ import { MESSAGE_TYPE } from './enums'
|
||||
*/
|
||||
|
||||
export default class MessageManager extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Controller in charge of managing - storing, adding, removing, updating - Messages.
|
||||
*
|
||||
@ -36,7 +35,7 @@ export default class MessageManager extends EventEmitter {
|
||||
* @property {array} messages Holds all messages that have been created by this MessageManager
|
||||
*
|
||||
*/
|
||||
constructor () {
|
||||
constructor() {
|
||||
super()
|
||||
this.memStore = new ObservableStore({
|
||||
unapprovedMsgs: {},
|
||||
@ -51,7 +50,7 @@ export default class MessageManager extends EventEmitter {
|
||||
* @returns {number} - The number of 'unapproved' Messages in this.messages
|
||||
*
|
||||
*/
|
||||
get unapprovedMsgCount () {
|
||||
get unapprovedMsgCount() {
|
||||
return Object.keys(this.getUnapprovedMsgs()).length
|
||||
}
|
||||
|
||||
@ -61,8 +60,9 @@ export default class MessageManager extends EventEmitter {
|
||||
* @returns {Object} - An index of Message ids to Messages, for all 'unapproved' Messages in this.messages
|
||||
*
|
||||
*/
|
||||
getUnapprovedMsgs () {
|
||||
return this.messages.filter((msg) => msg.status === 'unapproved')
|
||||
getUnapprovedMsgs() {
|
||||
return this.messages
|
||||
.filter((msg) => msg.status === 'unapproved')
|
||||
.reduce((result, msg) => {
|
||||
result[msg.id] = msg
|
||||
return result
|
||||
@ -78,7 +78,7 @@ export default class MessageManager extends EventEmitter {
|
||||
* @returns {promise} - after signature has been
|
||||
*
|
||||
*/
|
||||
addUnapprovedMessageAsync (msgParams, req) {
|
||||
addUnapprovedMessageAsync(msgParams, req) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const msgId = this.addUnapprovedMessage(msgParams, req)
|
||||
// await finished
|
||||
@ -87,9 +87,19 @@ export default class MessageManager extends EventEmitter {
|
||||
case 'signed':
|
||||
return resolve(data.rawSig)
|
||||
case 'rejected':
|
||||
return reject(ethErrors.provider.userRejectedRequest('MetaMask Message Signature: User denied message signature.'))
|
||||
return reject(
|
||||
ethErrors.provider.userRejectedRequest(
|
||||
'MetaMask Message Signature: User denied message signature.',
|
||||
),
|
||||
)
|
||||
default:
|
||||
return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
|
||||
return reject(
|
||||
new Error(
|
||||
`MetaMask Message Signature: Unknown problem: ${JSON.stringify(
|
||||
msgParams,
|
||||
)}`,
|
||||
),
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -104,14 +114,14 @@ export default class MessageManager extends EventEmitter {
|
||||
* @returns {number} - The id of the newly created message.
|
||||
*
|
||||
*/
|
||||
addUnapprovedMessage (msgParams, req) {
|
||||
addUnapprovedMessage(msgParams, req) {
|
||||
// add origin from request
|
||||
if (req) {
|
||||
msgParams.origin = req.origin
|
||||
}
|
||||
msgParams.data = normalizeMsgData(msgParams.data)
|
||||
// create txData obj with parameters and meta data
|
||||
const time = (new Date()).getTime()
|
||||
const time = new Date().getTime()
|
||||
const msgId = createId()
|
||||
const msgData = {
|
||||
id: msgId,
|
||||
@ -134,7 +144,7 @@ export default class MessageManager extends EventEmitter {
|
||||
* @param {Message} msg - The Message to add to this.messages
|
||||
*
|
||||
*/
|
||||
addMsg (msg) {
|
||||
addMsg(msg) {
|
||||
this.messages.push(msg)
|
||||
this._saveMsgList()
|
||||
}
|
||||
@ -146,7 +156,7 @@ export default class MessageManager extends EventEmitter {
|
||||
* @returns {Message|undefined} - The Message with the id that matches the passed msgId, or undefined if no Message has that id.
|
||||
*
|
||||
*/
|
||||
getMsg (msgId) {
|
||||
getMsg(msgId) {
|
||||
return this.messages.find((msg) => msg.id === msgId)
|
||||
}
|
||||
|
||||
@ -159,7 +169,7 @@ export default class MessageManager extends EventEmitter {
|
||||
* @returns {Promise<object>} - Promises the msgParams object with metamaskId removed.
|
||||
*
|
||||
*/
|
||||
approveMessage (msgParams) {
|
||||
approveMessage(msgParams) {
|
||||
this.setMsgStatusApproved(msgParams.metamaskId)
|
||||
return this.prepMsgForSigning(msgParams)
|
||||
}
|
||||
@ -170,7 +180,7 @@ export default class MessageManager extends EventEmitter {
|
||||
* @param {number} msgId - The id of the Message to approve.
|
||||
*
|
||||
*/
|
||||
setMsgStatusApproved (msgId) {
|
||||
setMsgStatusApproved(msgId) {
|
||||
this._setMsgStatus(msgId, 'approved')
|
||||
}
|
||||
|
||||
@ -182,7 +192,7 @@ export default class MessageManager extends EventEmitter {
|
||||
* @param {buffer} rawSig - The raw data of the signature request
|
||||
*
|
||||
*/
|
||||
setMsgStatusSigned (msgId, rawSig) {
|
||||
setMsgStatusSigned(msgId, rawSig) {
|
||||
const msg = this.getMsg(msgId)
|
||||
msg.rawSig = rawSig
|
||||
this._updateMsg(msg)
|
||||
@ -196,7 +206,7 @@ export default class MessageManager extends EventEmitter {
|
||||
* @returns {Promise<object>} - Promises the msgParams with the metamaskId property removed
|
||||
*
|
||||
*/
|
||||
prepMsgForSigning (msgParams) {
|
||||
prepMsgForSigning(msgParams) {
|
||||
delete msgParams.metamaskId
|
||||
return Promise.resolve(msgParams)
|
||||
}
|
||||
@ -207,7 +217,7 @@ export default class MessageManager extends EventEmitter {
|
||||
* @param {number} msgId - The id of the Message to reject.
|
||||
*
|
||||
*/
|
||||
rejectMsg (msgId) {
|
||||
rejectMsg(msgId) {
|
||||
this._setMsgStatus(msgId, 'rejected')
|
||||
}
|
||||
|
||||
@ -223,7 +233,7 @@ export default class MessageManager extends EventEmitter {
|
||||
* @fires If status is 'rejected' or 'signed', an event with a name equal to `${msgId}:finished` is fired along with the message
|
||||
*
|
||||
*/
|
||||
_setMsgStatus (msgId, status) {
|
||||
_setMsgStatus(msgId, status) {
|
||||
const msg = this.getMsg(msgId)
|
||||
if (!msg) {
|
||||
throw new Error(`MessageManager - Message not found for id: "${msgId}".`)
|
||||
@ -244,7 +254,7 @@ export default class MessageManager extends EventEmitter {
|
||||
* @param {msg} Message - A Message that will replace an existing Message (with the same id) in this.messages
|
||||
*
|
||||
*/
|
||||
_updateMsg (msg) {
|
||||
_updateMsg(msg) {
|
||||
const index = this.messages.findIndex((message) => message.id === msg.id)
|
||||
if (index !== -1) {
|
||||
this.messages[index] = msg
|
||||
@ -259,13 +269,12 @@ export default class MessageManager extends EventEmitter {
|
||||
* @fires 'updateBadge'
|
||||
*
|
||||
*/
|
||||
_saveMsgList () {
|
||||
_saveMsgList() {
|
||||
const unapprovedMsgs = this.getUnapprovedMsgs()
|
||||
const unapprovedMsgCount = Object.keys(unapprovedMsgs).length
|
||||
this.memStore.updateState({ unapprovedMsgs, unapprovedMsgCount })
|
||||
this.emit('updateBadge')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -275,7 +284,7 @@ export default class MessageManager extends EventEmitter {
|
||||
* @returns {string} - A hex string conversion of the buffer data
|
||||
*
|
||||
*/
|
||||
function normalizeMsgData (data) {
|
||||
function normalizeMsgData(data) {
|
||||
if (data.slice(0, 2) === '0x') {
|
||||
// data is already hex
|
||||
return data
|
||||
|
@ -13,12 +13,11 @@ import EventEmitter from 'events'
|
||||
*/
|
||||
|
||||
export default class Migrator extends EventEmitter {
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {MigratorOptions} opts
|
||||
*/
|
||||
constructor (opts = {}) {
|
||||
constructor(opts = {}) {
|
||||
super()
|
||||
const migrations = opts.migrations || []
|
||||
// sort migrations by version
|
||||
@ -26,11 +25,12 @@ export default class Migrator extends EventEmitter {
|
||||
// grab migration with highest version
|
||||
const lastMigration = this.migrations.slice(-1)[0]
|
||||
// use specified defaultVersion or highest migration version
|
||||
this.defaultVersion = opts.defaultVersion || (lastMigration && lastMigration.version) || 0
|
||||
this.defaultVersion =
|
||||
opts.defaultVersion || (lastMigration && lastMigration.version) || 0
|
||||
}
|
||||
|
||||
// run all pending migrations on meta in place
|
||||
async migrateData (versionedData = this.generateInitialState()) {
|
||||
async migrateData(versionedData = this.generateInitialState()) {
|
||||
// get all migrations that have not yet been run
|
||||
const pendingMigrations = this.migrations.filter(migrationIsPending)
|
||||
|
||||
@ -42,8 +42,13 @@ export default class Migrator extends EventEmitter {
|
||||
if (!migratedData.data) {
|
||||
throw new Error('Migrator - migration returned empty data')
|
||||
}
|
||||
if (migratedData.version !== undefined && migratedData.meta.version !== migration.version) {
|
||||
throw new Error('Migrator - Migration did not update version number correctly')
|
||||
if (
|
||||
migratedData.version !== undefined &&
|
||||
migratedData.meta.version !== migration.version
|
||||
) {
|
||||
throw new Error(
|
||||
'Migrator - Migration did not update version number correctly',
|
||||
)
|
||||
}
|
||||
// accept the migration as good
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
@ -69,7 +74,7 @@ export default class Migrator extends EventEmitter {
|
||||
* @param {Migration} migration
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function migrationIsPending (migration) {
|
||||
function migrationIsPending(migration) {
|
||||
return migration.version > versionedData.meta.version
|
||||
}
|
||||
}
|
||||
@ -79,7 +84,7 @@ export default class Migrator extends EventEmitter {
|
||||
* @param {Object} [data] - The data for the initial state
|
||||
* @returns {{meta: {version: number}, data: any}}
|
||||
*/
|
||||
generateInitialState (data) {
|
||||
generateInitialState(data) {
|
||||
return {
|
||||
meta: {
|
||||
version: this.defaultVersion,
|
||||
@ -87,5 +92,4 @@ export default class Migrator extends EventEmitter {
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,21 +8,21 @@ const FIXTURE_SERVER_URL = `http://${FIXTURE_SERVER_HOST}:${FIXTURE_SERVER_PORT}
|
||||
* A read-only network-based storage wrapper
|
||||
*/
|
||||
export default class ReadOnlyNetworkStore {
|
||||
constructor () {
|
||||
constructor() {
|
||||
this._initialized = false
|
||||
this._initializing = this._init()
|
||||
this._state = undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares this store as compatible with the current browser
|
||||
*/
|
||||
* Declares this store as compatible with the current browser
|
||||
*/
|
||||
isSupported = true
|
||||
|
||||
/**
|
||||
* Initializes by loading state from the network
|
||||
*/
|
||||
async _init () {
|
||||
async _init() {
|
||||
try {
|
||||
const response = await window.fetch(FIXTURE_SERVER_URL)
|
||||
if (response.ok) {
|
||||
@ -39,7 +39,7 @@ export default class ReadOnlyNetworkStore {
|
||||
* Returns state
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
async get () {
|
||||
async get() {
|
||||
if (!this._initialized) {
|
||||
await this._initializing
|
||||
}
|
||||
@ -51,7 +51,7 @@ export default class ReadOnlyNetworkStore {
|
||||
* @param {Object} state - The state to set
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async set (state) {
|
||||
async set(state) {
|
||||
if (!this._initialized) {
|
||||
await this._initializing
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ const callbackNoop = function (err) {
|
||||
* @param {Object} context - The context in which the fn is to be called, most often a this reference
|
||||
*
|
||||
*/
|
||||
export default function nodeify (fn, context) {
|
||||
export default function nodeify(fn, context) {
|
||||
return function (...args) {
|
||||
const lastArg = args[args.length - 1]
|
||||
const lastArgIsCallback = typeof lastArg === 'function'
|
||||
|
@ -4,7 +4,6 @@ const NOTIFICATION_HEIGHT = 620
|
||||
const NOTIFICATION_WIDTH = 360
|
||||
|
||||
export default class NotificationManager {
|
||||
|
||||
/**
|
||||
* A collection of methods for controlling the showing and hiding of the notification popup.
|
||||
*
|
||||
@ -12,7 +11,7 @@ export default class NotificationManager {
|
||||
*
|
||||
*/
|
||||
|
||||
constructor () {
|
||||
constructor() {
|
||||
this.platform = new ExtensionPlatform()
|
||||
}
|
||||
|
||||
@ -21,7 +20,7 @@ export default class NotificationManager {
|
||||
* notification windows are given a 'popup' type.
|
||||
*
|
||||
*/
|
||||
async showPopup () {
|
||||
async showPopup() {
|
||||
const popup = await this._getPopup()
|
||||
|
||||
// Bring focus to chrome popup
|
||||
@ -71,7 +70,7 @@ export default class NotificationManager {
|
||||
* @param {Function} cb - A node style callback that to which the found notification window will be passed.
|
||||
*
|
||||
*/
|
||||
async _getPopup () {
|
||||
async _getPopup() {
|
||||
const windows = await this.platform.getAllWindows()
|
||||
return this._getPopupIn(windows)
|
||||
}
|
||||
@ -83,11 +82,12 @@ export default class NotificationManager {
|
||||
* @param {array} windows - An array of objects containing data about the open MetaMask extension windows.
|
||||
*
|
||||
*/
|
||||
_getPopupIn (windows) {
|
||||
return windows ? windows.find((win) => {
|
||||
// Returns notification popup
|
||||
return (win && win.type === 'popup' && win.id === this._popupId)
|
||||
}) : null
|
||||
_getPopupIn(windows) {
|
||||
return windows
|
||||
? windows.find((win) => {
|
||||
// Returns notification popup
|
||||
return win && win.type === 'popup' && win.id === this._popupId
|
||||
})
|
||||
: null
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import log from 'loglevel'
|
||||
import createId from './random-id'
|
||||
import { MESSAGE_TYPE } from './enums'
|
||||
|
||||
const hexRe = /^[0-9A-Fa-f]+$/ug
|
||||
const hexRe = /^[0-9A-Fa-f]+$/gu
|
||||
|
||||
/**
|
||||
* Represents, and contains data about, an 'personal_sign' type signature request. These are created when a
|
||||
@ -28,7 +28,6 @@ const hexRe = /^[0-9A-Fa-f]+$/ug
|
||||
*/
|
||||
|
||||
export default class PersonalMessageManager extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Controller in charge of managing - storing, adding, removing, updating - PersonalMessage.
|
||||
*
|
||||
@ -40,7 +39,7 @@ export default class PersonalMessageManager extends EventEmitter {
|
||||
* @property {array} messages Holds all messages that have been created by this PersonalMessageManager
|
||||
*
|
||||
*/
|
||||
constructor () {
|
||||
constructor() {
|
||||
super()
|
||||
this.memStore = new ObservableStore({
|
||||
unapprovedPersonalMsgs: {},
|
||||
@ -55,7 +54,7 @@ export default class PersonalMessageManager extends EventEmitter {
|
||||
* @returns {number} - The number of 'unapproved' PersonalMessages in this.messages
|
||||
*
|
||||
*/
|
||||
get unapprovedPersonalMsgCount () {
|
||||
get unapprovedPersonalMsgCount() {
|
||||
return Object.keys(this.getUnapprovedMsgs()).length
|
||||
}
|
||||
|
||||
@ -66,8 +65,9 @@ export default class PersonalMessageManager extends EventEmitter {
|
||||
* this.messages
|
||||
*
|
||||
*/
|
||||
getUnapprovedMsgs () {
|
||||
return this.messages.filter((msg) => msg.status === 'unapproved')
|
||||
getUnapprovedMsgs() {
|
||||
return this.messages
|
||||
.filter((msg) => msg.status === 'unapproved')
|
||||
.reduce((result, msg) => {
|
||||
result[msg.id] = msg
|
||||
return result
|
||||
@ -84,7 +84,7 @@ export default class PersonalMessageManager extends EventEmitter {
|
||||
* @returns {promise} - When the message has been signed or rejected
|
||||
*
|
||||
*/
|
||||
addUnapprovedMessageAsync (msgParams, req) {
|
||||
addUnapprovedMessageAsync(msgParams, req) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!msgParams.from) {
|
||||
reject(new Error('MetaMask Message Signature: from field is required.'))
|
||||
@ -97,10 +97,20 @@ export default class PersonalMessageManager extends EventEmitter {
|
||||
resolve(data.rawSig)
|
||||
return
|
||||
case 'rejected':
|
||||
reject(ethErrors.provider.userRejectedRequest('MetaMask Message Signature: User denied message signature.'))
|
||||
reject(
|
||||
ethErrors.provider.userRejectedRequest(
|
||||
'MetaMask Message Signature: User denied message signature.',
|
||||
),
|
||||
)
|
||||
return
|
||||
default:
|
||||
reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
|
||||
reject(
|
||||
new Error(
|
||||
`MetaMask Message Signature: Unknown problem: ${JSON.stringify(
|
||||
msgParams,
|
||||
)}`,
|
||||
),
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -116,15 +126,19 @@ export default class PersonalMessageManager extends EventEmitter {
|
||||
* @returns {number} - The id of the newly created PersonalMessage.
|
||||
*
|
||||
*/
|
||||
addUnapprovedMessage (msgParams, req) {
|
||||
log.debug(`PersonalMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`)
|
||||
addUnapprovedMessage(msgParams, req) {
|
||||
log.debug(
|
||||
`PersonalMessageManager addUnapprovedMessage: ${JSON.stringify(
|
||||
msgParams,
|
||||
)}`,
|
||||
)
|
||||
// add origin from request
|
||||
if (req) {
|
||||
msgParams.origin = req.origin
|
||||
}
|
||||
msgParams.data = this.normalizeMsgData(msgParams.data)
|
||||
// create txData obj with parameters and meta data
|
||||
const time = (new Date()).getTime()
|
||||
const time = new Date().getTime()
|
||||
const msgId = createId()
|
||||
const msgData = {
|
||||
id: msgId,
|
||||
@ -147,7 +161,7 @@ export default class PersonalMessageManager extends EventEmitter {
|
||||
* @param {Message} msg - The PersonalMessage to add to this.messages
|
||||
*
|
||||
*/
|
||||
addMsg (msg) {
|
||||
addMsg(msg) {
|
||||
this.messages.push(msg)
|
||||
this._saveMsgList()
|
||||
}
|
||||
@ -160,7 +174,7 @@ export default class PersonalMessageManager extends EventEmitter {
|
||||
* if no PersonalMessage has that id.
|
||||
*
|
||||
*/
|
||||
getMsg (msgId) {
|
||||
getMsg(msgId) {
|
||||
return this.messages.find((msg) => msg.id === msgId)
|
||||
}
|
||||
|
||||
@ -173,7 +187,7 @@ export default class PersonalMessageManager extends EventEmitter {
|
||||
* @returns {Promise<object>} - Promises the msgParams object with metamaskId removed.
|
||||
*
|
||||
*/
|
||||
approveMessage (msgParams) {
|
||||
approveMessage(msgParams) {
|
||||
this.setMsgStatusApproved(msgParams.metamaskId)
|
||||
return this.prepMsgForSigning(msgParams)
|
||||
}
|
||||
@ -184,7 +198,7 @@ export default class PersonalMessageManager extends EventEmitter {
|
||||
* @param {number} msgId - The id of the PersonalMessage to approve.
|
||||
*
|
||||
*/
|
||||
setMsgStatusApproved (msgId) {
|
||||
setMsgStatusApproved(msgId) {
|
||||
this._setMsgStatus(msgId, 'approved')
|
||||
}
|
||||
|
||||
@ -196,7 +210,7 @@ export default class PersonalMessageManager extends EventEmitter {
|
||||
* @param {buffer} rawSig - The raw data of the signature request
|
||||
*
|
||||
*/
|
||||
setMsgStatusSigned (msgId, rawSig) {
|
||||
setMsgStatusSigned(msgId, rawSig) {
|
||||
const msg = this.getMsg(msgId)
|
||||
msg.rawSig = rawSig
|
||||
this._updateMsg(msg)
|
||||
@ -210,7 +224,7 @@ export default class PersonalMessageManager extends EventEmitter {
|
||||
* @returns {Promise<object>} - Promises the msgParams with the metamaskId property removed
|
||||
*
|
||||
*/
|
||||
prepMsgForSigning (msgParams) {
|
||||
prepMsgForSigning(msgParams) {
|
||||
delete msgParams.metamaskId
|
||||
return Promise.resolve(msgParams)
|
||||
}
|
||||
@ -221,7 +235,7 @@ export default class PersonalMessageManager extends EventEmitter {
|
||||
* @param {number} msgId - The id of the PersonalMessage to reject.
|
||||
*
|
||||
*/
|
||||
rejectMsg (msgId) {
|
||||
rejectMsg(msgId) {
|
||||
this._setMsgStatus(msgId, 'rejected')
|
||||
}
|
||||
|
||||
@ -238,10 +252,12 @@ export default class PersonalMessageManager extends EventEmitter {
|
||||
* with the PersonalMessage
|
||||
*
|
||||
*/
|
||||
_setMsgStatus (msgId, status) {
|
||||
_setMsgStatus(msgId, status) {
|
||||
const msg = this.getMsg(msgId)
|
||||
if (!msg) {
|
||||
throw new Error(`PersonalMessageManager - Message not found for id: "${msgId}".`)
|
||||
throw new Error(
|
||||
`PersonalMessageManager - Message not found for id: "${msgId}".`,
|
||||
)
|
||||
}
|
||||
msg.status = status
|
||||
this._updateMsg(msg)
|
||||
@ -260,7 +276,7 @@ export default class PersonalMessageManager extends EventEmitter {
|
||||
* id) in this.messages
|
||||
*
|
||||
*/
|
||||
_updateMsg (msg) {
|
||||
_updateMsg(msg) {
|
||||
const index = this.messages.findIndex((message) => message.id === msg.id)
|
||||
if (index !== -1) {
|
||||
this.messages[index] = msg
|
||||
@ -275,10 +291,14 @@ export default class PersonalMessageManager extends EventEmitter {
|
||||
* @fires 'updateBadge'
|
||||
*
|
||||
*/
|
||||
_saveMsgList () {
|
||||
_saveMsgList() {
|
||||
const unapprovedPersonalMsgs = this.getUnapprovedMsgs()
|
||||
const unapprovedPersonalMsgCount = Object.keys(unapprovedPersonalMsgs).length
|
||||
this.memStore.updateState({ unapprovedPersonalMsgs, unapprovedPersonalMsgCount })
|
||||
const unapprovedPersonalMsgCount = Object.keys(unapprovedPersonalMsgs)
|
||||
.length
|
||||
this.memStore.updateState({
|
||||
unapprovedPersonalMsgs,
|
||||
unapprovedPersonalMsgCount,
|
||||
})
|
||||
this.emit('updateBadge')
|
||||
}
|
||||
|
||||
@ -289,7 +309,7 @@ export default class PersonalMessageManager extends EventEmitter {
|
||||
* @returns {string} - A hex string conversion of the buffer data
|
||||
*
|
||||
*/
|
||||
normalizeMsgData (data) {
|
||||
normalizeMsgData(data) {
|
||||
try {
|
||||
const stripped = ethUtil.stripHexPrefix(data)
|
||||
if (stripped.match(hexRe)) {
|
||||
@ -301,5 +321,4 @@ export default class PersonalMessageManager extends EventEmitter {
|
||||
|
||||
return ethUtil.bufferToHex(Buffer.from(data, 'utf8'))
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
const MAX = Number.MAX_SAFE_INTEGER
|
||||
|
||||
let idCounter = Math.round(Math.random() * MAX)
|
||||
export default function createRandomId () {
|
||||
export default function createRandomId() {
|
||||
idCounter %= MAX
|
||||
// eslint-disable-next-line no-plusplus
|
||||
return idCounter++
|
||||
|
@ -23,8 +23,8 @@ const handlerMap = handlers.reduce((map, handler) => {
|
||||
* @param {Function} opts.sendMetrics - A function for sending a metrics event
|
||||
* @returns {(req: Object, res: Object, next: Function, end: Function) => void}
|
||||
*/
|
||||
export default function createMethodMiddleware (opts) {
|
||||
return function methodMiddleware (req, res, next, end) {
|
||||
export default function createMethodMiddleware(opts) {
|
||||
return function methodMiddleware(req, res, next, end) {
|
||||
if (handlerMap.has(req.method)) {
|
||||
return handlerMap.get(req.method)(req, res, next, end, opts)
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
import logWeb3Usage from './log-web3-usage'
|
||||
|
||||
const handlers = [
|
||||
logWeb3Usage,
|
||||
]
|
||||
const handlers = [logWeb3Usage]
|
||||
export default handlers
|
||||
|
@ -34,10 +34,7 @@ const recordedWeb3Usage = {}
|
||||
* @param {Function} end - The json-rpc-engine 'end' callback.
|
||||
* @param {LogWeb3UsageOptions} options
|
||||
*/
|
||||
function logWeb3UsageHandler (
|
||||
req, res, _next, end,
|
||||
{ origin, sendMetrics },
|
||||
) {
|
||||
function logWeb3UsageHandler(req, res, _next, end, { origin, sendMetrics }) {
|
||||
const { action, name } = req.params[0]
|
||||
|
||||
if (!recordedWeb3Usage[origin]) {
|
||||
|
@ -2,7 +2,6 @@ import KeyringController from 'eth-keyring-controller'
|
||||
import log from 'loglevel'
|
||||
|
||||
const seedPhraseVerifier = {
|
||||
|
||||
/**
|
||||
* Verifies if the seed words can restore the accounts.
|
||||
*
|
||||
@ -15,8 +14,8 @@ const seedPhraseVerifier = {
|
||||
* @param {string} seedWords - The seed words to verify
|
||||
* @returns {Promise<void>} - Promises undefined
|
||||
*
|
||||
*/
|
||||
async verifyAccounts (createdAccounts, seedWords) {
|
||||
*/
|
||||
async verifyAccounts(createdAccounts, seedWords) {
|
||||
if (!createdAccounts || createdAccounts.length < 1) {
|
||||
throw new Error('No created accounts defined.')
|
||||
}
|
||||
@ -39,8 +38,12 @@ const seedPhraseVerifier = {
|
||||
}
|
||||
|
||||
for (let i = 0; i < restoredAccounts.length; i++) {
|
||||
if (restoredAccounts[i].toLowerCase() !== createdAccounts[i].toLowerCase()) {
|
||||
throw new Error(`Not identical accounts! Original: ${createdAccounts[i]}, Restored: ${restoredAccounts[i]}`)
|
||||
if (
|
||||
restoredAccounts[i].toLowerCase() !== createdAccounts[i].toLowerCase()
|
||||
) {
|
||||
throw new Error(
|
||||
`Not identical accounts! Original: ${createdAccounts[i]}, Restored: ${restoredAccounts[i]}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -4,7 +4,7 @@
|
||||
// https://github.com/getsentry/sentry-javascript/pull/1293
|
||||
//
|
||||
|
||||
export default function setupFetchDebugging () {
|
||||
export default function setupFetchDebugging() {
|
||||
if (!window.fetch) {
|
||||
return
|
||||
}
|
||||
@ -12,14 +12,19 @@ export default function setupFetchDebugging () {
|
||||
|
||||
window.fetch = wrappedFetch
|
||||
|
||||
async function wrappedFetch (...args) {
|
||||
async function wrappedFetch(...args) {
|
||||
const initialStack = getCurrentStack()
|
||||
try {
|
||||
return await originalFetch.call(window, ...args)
|
||||
} catch (err) {
|
||||
if (!err.stack) {
|
||||
console.warn('FetchDebugger - fetch encountered an Error without a stack', err)
|
||||
console.warn('FetchDebugger - overriding stack to point of original call')
|
||||
console.warn(
|
||||
'FetchDebugger - fetch encountered an Error without a stack',
|
||||
err,
|
||||
)
|
||||
console.warn(
|
||||
'FetchDebugger - overriding stack to point of original call',
|
||||
)
|
||||
err.stack = initialStack
|
||||
}
|
||||
throw err
|
||||
@ -27,7 +32,7 @@ export default function setupFetchDebugging () {
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentStack () {
|
||||
function getCurrentStack() {
|
||||
try {
|
||||
throw new Error('Fake error for generating stack trace')
|
||||
} catch (err) {
|
||||
|
@ -8,7 +8,8 @@ import extractEthjsErrorMessage from './extractEthjsErrorMessage'
|
||||
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
|
||||
const METAMASK_ENVIRONMENT = process.env.METAMASK_ENVIRONMENT
|
||||
/* eslint-enable prefer-destructuring */
|
||||
const SENTRY_DSN_DEV = 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496'
|
||||
const SENTRY_DSN_DEV =
|
||||
'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496'
|
||||
|
||||
// This describes the subset of Redux state attached to errors sent to Sentry
|
||||
// These properties have some potential to be useful for debugging, and they do
|
||||
@ -66,19 +67,25 @@ export const SENTRY_STATE = {
|
||||
unconnectedAccount: true,
|
||||
}
|
||||
|
||||
export default function setupSentry ({ release, getState }) {
|
||||
export default function setupSentry({ release, getState }) {
|
||||
let sentryTarget
|
||||
|
||||
if (METAMASK_DEBUG) {
|
||||
return undefined
|
||||
} else if (METAMASK_ENVIRONMENT === 'production') {
|
||||
if (!process.env.SENTRY_DSN) {
|
||||
throw new Error(`Missing SENTRY_DSN environment variable in production environment`)
|
||||
throw new Error(
|
||||
`Missing SENTRY_DSN environment variable in production environment`,
|
||||
)
|
||||
}
|
||||
console.log(`Setting up Sentry Remote Error Reporting for '${METAMASK_ENVIRONMENT}': SENTRY_DSN`)
|
||||
console.log(
|
||||
`Setting up Sentry Remote Error Reporting for '${METAMASK_ENVIRONMENT}': SENTRY_DSN`,
|
||||
)
|
||||
sentryTarget = process.env.SENTRY_DSN
|
||||
} else {
|
||||
console.log(`Setting up Sentry Remote Error Reporting for '${METAMASK_ENVIRONMENT}': SENTRY_DSN_DEV`)
|
||||
console.log(
|
||||
`Setting up Sentry Remote Error Reporting for '${METAMASK_ENVIRONMENT}': SENTRY_DSN_DEV`,
|
||||
)
|
||||
sentryTarget = SENTRY_DSN_DEV
|
||||
}
|
||||
|
||||
@ -86,15 +93,12 @@ export default function setupSentry ({ release, getState }) {
|
||||
dsn: sentryTarget,
|
||||
debug: METAMASK_DEBUG,
|
||||
environment: METAMASK_ENVIRONMENT,
|
||||
integrations: [
|
||||
new Dedupe(),
|
||||
new ExtraErrorData(),
|
||||
],
|
||||
integrations: [new Dedupe(), new ExtraErrorData()],
|
||||
release,
|
||||
beforeSend: (report) => rewriteReport(report),
|
||||
})
|
||||
|
||||
function rewriteReport (report) {
|
||||
function rewriteReport(report) {
|
||||
try {
|
||||
// simplify certain complex error messages (e.g. Ethjs)
|
||||
simplifyErrorMessages(report)
|
||||
@ -117,12 +121,16 @@ export default function setupSentry ({ release, getState }) {
|
||||
return Sentry
|
||||
}
|
||||
|
||||
function simplifyErrorMessages (report) {
|
||||
function simplifyErrorMessages(report) {
|
||||
rewriteErrorMessages(report, (errorMessage) => {
|
||||
// simplify ethjs error messages
|
||||
let simplifiedErrorMessage = extractEthjsErrorMessage(errorMessage)
|
||||
// simplify 'Transaction Failed: known transaction'
|
||||
if (simplifiedErrorMessage.indexOf('Transaction Failed: known transaction') === 0) {
|
||||
if (
|
||||
simplifiedErrorMessage.indexOf(
|
||||
'Transaction Failed: known transaction',
|
||||
) === 0
|
||||
) {
|
||||
// cut the hash from the error message
|
||||
simplifiedErrorMessage = 'Transaction Failed: known transaction'
|
||||
}
|
||||
@ -130,7 +138,7 @@ function simplifyErrorMessages (report) {
|
||||
})
|
||||
}
|
||||
|
||||
function rewriteErrorMessages (report, rewriteFn) {
|
||||
function rewriteErrorMessages(report, rewriteFn) {
|
||||
// rewrite top level message
|
||||
if (typeof report.message === 'string') {
|
||||
report.message = rewriteFn(report.message)
|
||||
@ -145,7 +153,7 @@ function rewriteErrorMessages (report, rewriteFn) {
|
||||
}
|
||||
}
|
||||
|
||||
function rewriteReportUrls (report) {
|
||||
function rewriteReportUrls(report) {
|
||||
// update request url
|
||||
report.request.url = toMetamaskUrl(report.request.url)
|
||||
// update exception stack trace
|
||||
@ -160,7 +168,7 @@ function rewriteReportUrls (report) {
|
||||
}
|
||||
}
|
||||
|
||||
function toMetamaskUrl (origUrl) {
|
||||
function toMetamaskUrl(origUrl) {
|
||||
const filePath = origUrl.split(window.location.origin)[1]
|
||||
if (!filePath) {
|
||||
return origUrl
|
||||
|
@ -5,13 +5,13 @@
|
||||
|
||||
import 'web3/dist/web3.min'
|
||||
|
||||
const shouldLogUsage = !([
|
||||
const shouldLogUsage = ![
|
||||
'docs.metamask.io',
|
||||
'metamask.github.io',
|
||||
'metamask.io',
|
||||
].includes(window.location.hostname))
|
||||
].includes(window.location.hostname)
|
||||
|
||||
export default function setupWeb3 (log) {
|
||||
export default function setupWeb3(log) {
|
||||
// export web3 as a global, checking for usage
|
||||
let reloadInProgress = false
|
||||
let lastTimeUsed
|
||||
@ -33,13 +33,14 @@ export default function setupWeb3 (log) {
|
||||
|
||||
const web3Proxy = new Proxy(web3, {
|
||||
get: (_web3, key) => {
|
||||
|
||||
// get the time of use
|
||||
lastTimeUsed = Date.now()
|
||||
|
||||
// show warning once on web3 access
|
||||
if (!hasBeenWarned) {
|
||||
console.warn(`MetaMask: We will stop injecting web3 in Q4 2020.\nPlease see this article for more information: https://medium.com/metamask/no-longer-injecting-web3-js-4a899ad6e59e`)
|
||||
console.warn(
|
||||
`MetaMask: We will stop injecting web3 in Q4 2020.\nPlease see this article for more information: https://medium.com/metamask/no-longer-injecting-web3-js-4a899ad6e59e`,
|
||||
)
|
||||
hasBeenWarned = true
|
||||
}
|
||||
|
||||
@ -118,7 +119,7 @@ export default function setupWeb3 (log) {
|
||||
}
|
||||
|
||||
// reload the page
|
||||
function triggerReset () {
|
||||
function triggerReset() {
|
||||
global.location.reload()
|
||||
}
|
||||
|
||||
@ -128,8 +129,6 @@ function triggerReset () {
|
||||
*
|
||||
* @param {any} key - The key to stringify
|
||||
*/
|
||||
function stringifyKey (key) {
|
||||
return typeof key === 'string'
|
||||
? key
|
||||
: `typeof ${typeof key}`
|
||||
function stringifyKey(key) {
|
||||
return typeof key === 'string' ? key : `typeof ${typeof key}`
|
||||
}
|
||||
|
@ -6,17 +6,12 @@ import pump from 'pump'
|
||||
* @param {any} connectionStream - the stream to mux
|
||||
* @returns {stream.Stream} - the multiplexed stream
|
||||
*/
|
||||
export function setupMultiplex (connectionStream) {
|
||||
export function setupMultiplex(connectionStream) {
|
||||
const mux = new ObjectMultiplex()
|
||||
pump(
|
||||
connectionStream,
|
||||
mux,
|
||||
connectionStream,
|
||||
(err) => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
}
|
||||
},
|
||||
)
|
||||
pump(connectionStream, mux, connectionStream, (err) => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
return mux
|
||||
}
|
||||
|
@ -28,11 +28,10 @@ import { MESSAGE_TYPE } from './enums'
|
||||
*/
|
||||
|
||||
export default class TypedMessageManager extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Controller in charge of managing - storing, adding, removing, updating - TypedMessage.
|
||||
*/
|
||||
constructor ({ getCurrentChainId }) {
|
||||
constructor({ getCurrentChainId }) {
|
||||
super()
|
||||
this._getCurrentChainId = getCurrentChainId
|
||||
this.memStore = new ObservableStore({
|
||||
@ -48,7 +47,7 @@ export default class TypedMessageManager extends EventEmitter {
|
||||
* @returns {number} - The number of 'unapproved' TypedMessages in this.messages
|
||||
*
|
||||
*/
|
||||
get unapprovedTypedMessagesCount () {
|
||||
get unapprovedTypedMessagesCount() {
|
||||
return Object.keys(this.getUnapprovedMsgs()).length
|
||||
}
|
||||
|
||||
@ -59,8 +58,9 @@ export default class TypedMessageManager extends EventEmitter {
|
||||
* this.messages
|
||||
*
|
||||
*/
|
||||
getUnapprovedMsgs () {
|
||||
return this.messages.filter((msg) => msg.status === 'unapproved')
|
||||
getUnapprovedMsgs() {
|
||||
return this.messages
|
||||
.filter((msg) => msg.status === 'unapproved')
|
||||
.reduce((result, msg) => {
|
||||
result[msg.id] = msg
|
||||
return result
|
||||
@ -77,7 +77,7 @@ export default class TypedMessageManager extends EventEmitter {
|
||||
* @returns {promise} - When the message has been signed or rejected
|
||||
*
|
||||
*/
|
||||
addUnapprovedMessageAsync (msgParams, req, version) {
|
||||
addUnapprovedMessageAsync(msgParams, req, version) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const msgId = this.addUnapprovedMessage(msgParams, req, version)
|
||||
this.once(`${msgId}:finished`, (data) => {
|
||||
@ -85,11 +85,23 @@ export default class TypedMessageManager extends EventEmitter {
|
||||
case 'signed':
|
||||
return resolve(data.rawSig)
|
||||
case 'rejected':
|
||||
return reject(ethErrors.provider.userRejectedRequest('MetaMask Message Signature: User denied message signature.'))
|
||||
return reject(
|
||||
ethErrors.provider.userRejectedRequest(
|
||||
'MetaMask Message Signature: User denied message signature.',
|
||||
),
|
||||
)
|
||||
case 'errored':
|
||||
return reject(new Error(`MetaMask Message Signature: ${data.error}`))
|
||||
return reject(
|
||||
new Error(`MetaMask Message Signature: ${data.error}`),
|
||||
)
|
||||
default:
|
||||
return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
|
||||
return reject(
|
||||
new Error(
|
||||
`MetaMask Message Signature: Unknown problem: ${JSON.stringify(
|
||||
msgParams,
|
||||
)}`,
|
||||
),
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -105,18 +117,19 @@ export default class TypedMessageManager extends EventEmitter {
|
||||
* @returns {number} - The id of the newly created TypedMessage.
|
||||
*
|
||||
*/
|
||||
addUnapprovedMessage (msgParams, req, version) {
|
||||
|
||||
addUnapprovedMessage(msgParams, req, version) {
|
||||
msgParams.version = version
|
||||
if (req) {
|
||||
msgParams.origin = req.origin
|
||||
}
|
||||
this.validateParams(msgParams)
|
||||
|
||||
log.debug(`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`)
|
||||
log.debug(
|
||||
`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`,
|
||||
)
|
||||
|
||||
// create txData obj with parameters and meta data
|
||||
const time = (new Date()).getTime()
|
||||
const time = new Date().getTime()
|
||||
const msgId = createId()
|
||||
const msgData = {
|
||||
id: msgId,
|
||||
@ -138,8 +151,7 @@ export default class TypedMessageManager extends EventEmitter {
|
||||
* @param {Object} params - The params to validate
|
||||
*
|
||||
*/
|
||||
validateParams (params) {
|
||||
|
||||
validateParams(params) {
|
||||
assert.ok(params && typeof params === 'object', 'Params must be an object.')
|
||||
assert.ok('data' in params, 'Params must include a "data" field.')
|
||||
assert.ok('from' in params, 'Params must include a "from" field.')
|
||||
@ -157,19 +169,40 @@ export default class TypedMessageManager extends EventEmitter {
|
||||
break
|
||||
case 'V3':
|
||||
case 'V4': {
|
||||
assert.equal(typeof params.data, 'string', '"params.data" must be a string.')
|
||||
assert.equal(
|
||||
typeof params.data,
|
||||
'string',
|
||||
'"params.data" must be a string.',
|
||||
)
|
||||
let data
|
||||
assert.doesNotThrow(() => {
|
||||
data = JSON.parse(params.data)
|
||||
}, '"data" must be a valid JSON string.')
|
||||
const validation = jsonschema.validate(data, sigUtil.TYPED_MESSAGE_SCHEMA)
|
||||
assert.ok(data.primaryType in data.types, `Primary type of "${data.primaryType}" has no type definition.`)
|
||||
assert.equal(validation.errors.length, 0, 'Signing data must conform to EIP-712 schema. See https://git.io/fNtcx.')
|
||||
const validation = jsonschema.validate(
|
||||
data,
|
||||
sigUtil.TYPED_MESSAGE_SCHEMA,
|
||||
)
|
||||
assert.ok(
|
||||
data.primaryType in data.types,
|
||||
`Primary type of "${data.primaryType}" has no type definition.`,
|
||||
)
|
||||
assert.equal(
|
||||
validation.errors.length,
|
||||
0,
|
||||
'Signing data must conform to EIP-712 schema. See https://git.io/fNtcx.',
|
||||
)
|
||||
const { chainId } = data.domain
|
||||
if (chainId) {
|
||||
const activeChainId = parseInt(this._getCurrentChainId(), 16)
|
||||
assert.ok(!Number.isNaN(activeChainId), `Cannot sign messages for chainId "${chainId}", because MetaMask is switching networks.`)
|
||||
assert.equal(chainId, activeChainId, `Provided chainId "${chainId}" must match the active chainId "${activeChainId}"`)
|
||||
assert.ok(
|
||||
!Number.isNaN(activeChainId),
|
||||
`Cannot sign messages for chainId "${chainId}", because MetaMask is switching networks.`,
|
||||
)
|
||||
assert.equal(
|
||||
chainId,
|
||||
activeChainId,
|
||||
`Provided chainId "${chainId}" must match the active chainId "${activeChainId}"`,
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
@ -185,7 +218,7 @@ export default class TypedMessageManager extends EventEmitter {
|
||||
* @param {Message} msg - The TypedMessage to add to this.messages
|
||||
*
|
||||
*/
|
||||
addMsg (msg) {
|
||||
addMsg(msg) {
|
||||
this.messages.push(msg)
|
||||
this._saveMsgList()
|
||||
}
|
||||
@ -198,7 +231,7 @@ export default class TypedMessageManager extends EventEmitter {
|
||||
* if no TypedMessage has that id.
|
||||
*
|
||||
*/
|
||||
getMsg (msgId) {
|
||||
getMsg(msgId) {
|
||||
return this.messages.find((msg) => msg.id === msgId)
|
||||
}
|
||||
|
||||
@ -211,7 +244,7 @@ export default class TypedMessageManager extends EventEmitter {
|
||||
* @returns {Promise<object>} - Promises the msgParams object with metamaskId removed.
|
||||
*
|
||||
*/
|
||||
approveMessage (msgParams) {
|
||||
approveMessage(msgParams) {
|
||||
this.setMsgStatusApproved(msgParams.metamaskId)
|
||||
return this.prepMsgForSigning(msgParams)
|
||||
}
|
||||
@ -222,7 +255,7 @@ export default class TypedMessageManager extends EventEmitter {
|
||||
* @param {number} msgId - The id of the TypedMessage to approve.
|
||||
*
|
||||
*/
|
||||
setMsgStatusApproved (msgId) {
|
||||
setMsgStatusApproved(msgId) {
|
||||
this._setMsgStatus(msgId, 'approved')
|
||||
}
|
||||
|
||||
@ -234,7 +267,7 @@ export default class TypedMessageManager extends EventEmitter {
|
||||
* @param {buffer} rawSig - The raw data of the signature request
|
||||
*
|
||||
*/
|
||||
setMsgStatusSigned (msgId, rawSig) {
|
||||
setMsgStatusSigned(msgId, rawSig) {
|
||||
const msg = this.getMsg(msgId)
|
||||
msg.rawSig = rawSig
|
||||
this._updateMsg(msg)
|
||||
@ -248,7 +281,7 @@ export default class TypedMessageManager extends EventEmitter {
|
||||
* @returns {Promise<object>} - Promises the msgParams with the metamaskId property removed
|
||||
*
|
||||
*/
|
||||
prepMsgForSigning (msgParams) {
|
||||
prepMsgForSigning(msgParams) {
|
||||
delete msgParams.metamaskId
|
||||
delete msgParams.version
|
||||
return Promise.resolve(msgParams)
|
||||
@ -260,7 +293,7 @@ export default class TypedMessageManager extends EventEmitter {
|
||||
* @param {number} msgId - The id of the TypedMessage to reject.
|
||||
*
|
||||
*/
|
||||
rejectMsg (msgId) {
|
||||
rejectMsg(msgId) {
|
||||
this._setMsgStatus(msgId, 'rejected')
|
||||
}
|
||||
|
||||
@ -270,7 +303,7 @@ export default class TypedMessageManager extends EventEmitter {
|
||||
* @param {number} msgId - The id of the TypedMessage to error
|
||||
*
|
||||
*/
|
||||
errorMessage (msgId, error) {
|
||||
errorMessage(msgId, error) {
|
||||
const msg = this.getMsg(msgId)
|
||||
msg.error = error
|
||||
this._updateMsg(msg)
|
||||
@ -294,10 +327,12 @@ export default class TypedMessageManager extends EventEmitter {
|
||||
* with the TypedMessage
|
||||
*
|
||||
*/
|
||||
_setMsgStatus (msgId, status) {
|
||||
_setMsgStatus(msgId, status) {
|
||||
const msg = this.getMsg(msgId)
|
||||
if (!msg) {
|
||||
throw new Error(`TypedMessageManager - Message not found for id: "${msgId}".`)
|
||||
throw new Error(
|
||||
`TypedMessageManager - Message not found for id: "${msgId}".`,
|
||||
)
|
||||
}
|
||||
msg.status = status
|
||||
this._updateMsg(msg)
|
||||
@ -316,7 +351,7 @@ export default class TypedMessageManager extends EventEmitter {
|
||||
* id) in this.messages
|
||||
*
|
||||
*/
|
||||
_updateMsg (msg) {
|
||||
_updateMsg(msg) {
|
||||
const index = this.messages.findIndex((message) => message.id === msg.id)
|
||||
if (index !== -1) {
|
||||
this.messages[index] = msg
|
||||
@ -331,11 +366,14 @@ export default class TypedMessageManager extends EventEmitter {
|
||||
* @fires 'updateBadge'
|
||||
*
|
||||
*/
|
||||
_saveMsgList () {
|
||||
_saveMsgList() {
|
||||
const unapprovedTypedMessages = this.getUnapprovedMsgs()
|
||||
const unapprovedTypedMessagesCount = Object.keys(unapprovedTypedMessages).length
|
||||
this.memStore.updateState({ unapprovedTypedMessages, unapprovedTypedMessagesCount })
|
||||
const unapprovedTypedMessagesCount = Object.keys(unapprovedTypedMessages)
|
||||
.length
|
||||
this.memStore.updateState({
|
||||
unapprovedTypedMessages,
|
||||
unapprovedTypedMessagesCount,
|
||||
})
|
||||
this.emit('updateBadge')
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -44,7 +44,8 @@ const getEnvironmentTypeMemo = memoize((url) => {
|
||||
* @param {string} [url] - the URL of the window
|
||||
* @returns {string} the environment ENUM
|
||||
*/
|
||||
const getEnvironmentType = (url = window.location.href) => getEnvironmentTypeMemo(url)
|
||||
const getEnvironmentType = (url = window.location.href) =>
|
||||
getEnvironmentTypeMemo(url)
|
||||
|
||||
/**
|
||||
* Returns the platform (browser) where the extension is running.
|
||||
@ -80,10 +81,18 @@ const getPlatform = (_) => {
|
||||
* @returns {boolean} - Whether the balance is greater than or equal to the value plus the value of gas times gasPrice
|
||||
*
|
||||
*/
|
||||
function sufficientBalance (txParams, hexBalance) {
|
||||
function sufficientBalance(txParams, hexBalance) {
|
||||
// validate hexBalance is a hex string
|
||||
assert.equal(typeof hexBalance, 'string', 'sufficientBalance - hexBalance is not a hex string')
|
||||
assert.equal(hexBalance.slice(0, 2), '0x', 'sufficientBalance - hexBalance is not a hex string')
|
||||
assert.equal(
|
||||
typeof hexBalance,
|
||||
'string',
|
||||
'sufficientBalance - hexBalance is not a hex string',
|
||||
)
|
||||
assert.equal(
|
||||
hexBalance.slice(0, 2),
|
||||
'0x',
|
||||
'sufficientBalance - hexBalance is not a hex string',
|
||||
)
|
||||
|
||||
const balance = hexToBn(hexBalance)
|
||||
const value = hexToBn(txParams.value)
|
||||
@ -101,7 +110,7 @@ function sufficientBalance (txParams, hexBalance) {
|
||||
* @returns {string} - A '0x' prefixed hex string
|
||||
*
|
||||
*/
|
||||
function bnToHex (inputBn) {
|
||||
function bnToHex(inputBn) {
|
||||
return ethUtil.addHexPrefix(inputBn.toString(16))
|
||||
}
|
||||
|
||||
@ -112,7 +121,7 @@ function bnToHex (inputBn) {
|
||||
* @returns {Object} - A BN object
|
||||
*
|
||||
*/
|
||||
function hexToBn (inputHex) {
|
||||
function hexToBn(inputHex) {
|
||||
return new BN(ethUtil.stripHexPrefix(inputHex), 16)
|
||||
}
|
||||
|
||||
@ -125,7 +134,7 @@ function hexToBn (inputHex) {
|
||||
* @returns {BN} - The product of the multiplication
|
||||
*
|
||||
*/
|
||||
function BnMultiplyByFraction (targetBN, numerator, denominator) {
|
||||
function BnMultiplyByFraction(targetBN, numerator, denominator) {
|
||||
const numBN = new BN(numerator)
|
||||
const denomBN = new BN(denominator)
|
||||
return targetBN.mul(numBN).div(denomBN)
|
||||
@ -136,7 +145,7 @@ function BnMultiplyByFraction (targetBN, numerator, denominator) {
|
||||
* this is a workaround for the non-standard error object that's used
|
||||
* @returns {Error|undefined}
|
||||
*/
|
||||
function checkForError () {
|
||||
function checkForError() {
|
||||
const { lastError } = extension.runtime
|
||||
if (!lastError) {
|
||||
return undefined
|
||||
@ -157,11 +166,11 @@ function checkForError () {
|
||||
* @returns {boolean} True if the value is a correctly formatted hex string,
|
||||
* false otherwise.
|
||||
*/
|
||||
function isPrefixedFormattedHexString (value) {
|
||||
function isPrefixedFormattedHexString(value) {
|
||||
if (typeof value !== 'string') {
|
||||
return false
|
||||
}
|
||||
return (/^0x[1-9a-f]+[0-9a-f]*$/ui).test(value)
|
||||
return /^0x[1-9a-f]+[0-9a-f]*$/iu.test(value)
|
||||
}
|
||||
|
||||
export {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,13 +5,14 @@ const version = 2
|
||||
export default {
|
||||
version,
|
||||
|
||||
migrate (originalVersionedData) {
|
||||
migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
if (versionedData.data.config.provider.type === 'etherscan') {
|
||||
versionedData.data.config.provider.type = 'rpc'
|
||||
versionedData.data.config.provider.rpcTarget = 'https://rpc.metamask.io/'
|
||||
versionedData.data.config.provider.rpcTarget =
|
||||
'https://rpc.metamask.io/'
|
||||
}
|
||||
} catch (_) {
|
||||
// empty
|
||||
|
@ -7,7 +7,7 @@ const newTestRpc = 'https://testrpc.metamask.io/'
|
||||
export default {
|
||||
version,
|
||||
|
||||
migrate (originalVersionedData) {
|
||||
migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
|
@ -5,7 +5,7 @@ const version = 4
|
||||
export default {
|
||||
version,
|
||||
|
||||
migrate (versionedData) {
|
||||
migrate(versionedData) {
|
||||
const safeVersionedData = cloneDeep(versionedData)
|
||||
safeVersionedData.meta.version = version
|
||||
try {
|
||||
|
@ -11,7 +11,7 @@ const version = 5
|
||||
export default {
|
||||
version,
|
||||
|
||||
migrate (originalVersionedData) {
|
||||
migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
@ -25,7 +25,7 @@ export default {
|
||||
},
|
||||
}
|
||||
|
||||
function selectSubstateForKeyringController (state) {
|
||||
function selectSubstateForKeyringController(state) {
|
||||
const { config } = state
|
||||
const newState = {
|
||||
...state,
|
||||
|
@ -11,7 +11,7 @@ const version = 6
|
||||
export default {
|
||||
version,
|
||||
|
||||
migrate (originalVersionedData) {
|
||||
migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
@ -25,7 +25,7 @@ export default {
|
||||
},
|
||||
}
|
||||
|
||||
function migrateState (state) {
|
||||
function migrateState(state) {
|
||||
const keyringSubstate = state.KeyringController
|
||||
|
||||
// add new state
|
||||
|
@ -11,7 +11,7 @@ const version = 7
|
||||
export default {
|
||||
version,
|
||||
|
||||
migrate (originalVersionedData) {
|
||||
migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
@ -25,7 +25,7 @@ export default {
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
function transformState(state) {
|
||||
const newState = {
|
||||
...state,
|
||||
TransactionManager: {
|
||||
|
@ -11,7 +11,7 @@ const version = 8
|
||||
export default {
|
||||
version,
|
||||
|
||||
migrate (originalVersionedData) {
|
||||
migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
@ -25,7 +25,7 @@ export default {
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
function transformState(state) {
|
||||
const newState = {
|
||||
...state,
|
||||
NoticeController: {
|
||||
|
@ -11,7 +11,7 @@ const version = 9
|
||||
export default {
|
||||
version,
|
||||
|
||||
migrate (originalVersionedData) {
|
||||
migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
@ -25,7 +25,7 @@ export default {
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
function transformState(state) {
|
||||
const newState = merge({}, state, {
|
||||
CurrencyController: {
|
||||
currentCurrency: state.currentFiat || state.fiatCurrency || 'USD',
|
||||
|
@ -11,7 +11,7 @@ const version = 10
|
||||
export default {
|
||||
version,
|
||||
|
||||
migrate (originalVersionedData) {
|
||||
migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
@ -25,7 +25,7 @@ export default {
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
function transformState(state) {
|
||||
const newState = merge({}, state, {
|
||||
ShapeShiftController: {
|
||||
shapeShiftTxList: state.shapeShiftTxList || [],
|
||||
|
@ -11,7 +11,7 @@ const version = 11
|
||||
export default {
|
||||
version,
|
||||
|
||||
migrate (originalVersionedData) {
|
||||
migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
@ -25,7 +25,7 @@ export default {
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
function transformState(state) {
|
||||
const newState = state
|
||||
delete newState.TOSHash
|
||||
delete newState.isDisclaimerConfirmed
|
||||
|
@ -11,7 +11,7 @@ const version = 12
|
||||
export default {
|
||||
version,
|
||||
|
||||
migrate (originalVersionedData) {
|
||||
migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
@ -25,7 +25,7 @@ export default {
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
function transformState(state) {
|
||||
const newState = state
|
||||
newState.NoticeController.noticesList.forEach((notice) => {
|
||||
if (notice.read) {
|
||||
|
@ -11,7 +11,7 @@ const version = 13
|
||||
export default {
|
||||
version,
|
||||
|
||||
migrate (originalVersionedData) {
|
||||
migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
@ -25,7 +25,7 @@ export default {
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
function transformState(state) {
|
||||
const newState = state
|
||||
const { config } = newState
|
||||
if (config && config.provider) {
|
||||
|
@ -11,7 +11,7 @@ const version = 14
|
||||
export default {
|
||||
version,
|
||||
|
||||
migrate (originalVersionedData) {
|
||||
migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
@ -25,7 +25,7 @@ export default {
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
function transformState(state) {
|
||||
const newState = state
|
||||
newState.NetworkController = {}
|
||||
newState.NetworkController.provider = newState.config.provider
|
||||
|
@ -12,7 +12,7 @@ const version = 15
|
||||
export default {
|
||||
version,
|
||||
|
||||
migrate (originalVersionedData) {
|
||||
migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
@ -26,7 +26,7 @@ export default {
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
function transformState(state) {
|
||||
const newState = state
|
||||
const { TransactionController } = newState
|
||||
if (TransactionController && TransactionController.transactions) {
|
||||
|
@ -12,7 +12,7 @@ const version = 16
|
||||
export default {
|
||||
version,
|
||||
|
||||
migrate (originalVersionedData) {
|
||||
migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
@ -26,7 +26,7 @@ export default {
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
function transformState(state) {
|
||||
const newState = state
|
||||
const { TransactionController } = newState
|
||||
if (TransactionController && TransactionController.transactions) {
|
||||
@ -36,7 +36,9 @@ function transformState (state) {
|
||||
if (!txMeta.err) {
|
||||
return txMeta
|
||||
}
|
||||
if (txMeta.err === 'transaction with the same hash was already imported.') {
|
||||
if (
|
||||
txMeta.err === 'transaction with the same hash was already imported.'
|
||||
) {
|
||||
txMeta.status = 'submitted'
|
||||
delete txMeta.err
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ const version = 17
|
||||
export default {
|
||||
version,
|
||||
|
||||
migrate (originalVersionedData) {
|
||||
migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
@ -25,7 +25,7 @@ export default {
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
function transformState(state) {
|
||||
const newState = state
|
||||
const { TransactionController } = newState
|
||||
if (TransactionController && TransactionController.transactions) {
|
||||
|
@ -15,7 +15,7 @@ const version = 18
|
||||
export default {
|
||||
version,
|
||||
|
||||
migrate (originalVersionedData) {
|
||||
migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
@ -29,7 +29,7 @@ export default {
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
function transformState(state) {
|
||||
const newState = state
|
||||
const { TransactionController } = newState
|
||||
if (TransactionController && TransactionController.transactions) {
|
||||
@ -42,13 +42,11 @@ function transformState (state) {
|
||||
return txMeta
|
||||
}
|
||||
// has history: migrate
|
||||
const newHistory = (
|
||||
migrateFromSnapshotsToDiffs(txMeta.history)
|
||||
const newHistory = migrateFromSnapshotsToDiffs(txMeta.history)
|
||||
// remove empty diffs
|
||||
.filter((entry) => {
|
||||
return !Array.isArray(entry) || entry.length > 0
|
||||
})
|
||||
)
|
||||
.filter((entry) => {
|
||||
return !Array.isArray(entry) || entry.length > 0
|
||||
})
|
||||
txMeta.history = newHistory
|
||||
return txMeta
|
||||
})
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
/*
|
||||
|
||||
This migration sets transactions as failed
|
||||
@ -13,7 +12,7 @@ const version = 19
|
||||
export default {
|
||||
version,
|
||||
|
||||
migrate (originalVersionedData) {
|
||||
migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
@ -27,44 +26,54 @@ export default {
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
function transformState(state) {
|
||||
const newState = state
|
||||
const { TransactionController } = newState
|
||||
if (TransactionController && TransactionController.transactions) {
|
||||
|
||||
const { transactions } = newState.TransactionController
|
||||
|
||||
newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => {
|
||||
if (txMeta.status !== 'submitted') {
|
||||
return txMeta
|
||||
}
|
||||
|
||||
const confirmedTxs = txList.filter((tx) => tx.status === 'confirmed')
|
||||
.filter((tx) => tx.txParams.from === txMeta.txParams.from)
|
||||
.filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from)
|
||||
const highestConfirmedNonce = getHighestNonce(confirmedTxs)
|
||||
|
||||
const pendingTxs = txList.filter((tx) => tx.status === 'submitted')
|
||||
.filter((tx) => tx.txParams.from === txMeta.txParams.from)
|
||||
.filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from)
|
||||
const highestContinuousNonce = getHighestContinuousFrom(pendingTxs, highestConfirmedNonce)
|
||||
|
||||
const maxNonce = Math.max(highestContinuousNonce, highestConfirmedNonce)
|
||||
|
||||
if (parseInt(txMeta.txParams.nonce, 16) > maxNonce + 1) {
|
||||
txMeta.status = 'failed'
|
||||
txMeta.err = {
|
||||
message: 'nonce too high',
|
||||
note: 'migration 019 custom error',
|
||||
newState.TransactionController.transactions = transactions.map(
|
||||
(txMeta, _, txList) => {
|
||||
if (txMeta.status !== 'submitted') {
|
||||
return txMeta
|
||||
}
|
||||
}
|
||||
return txMeta
|
||||
})
|
||||
|
||||
const confirmedTxs = txList
|
||||
.filter((tx) => tx.status === 'confirmed')
|
||||
.filter((tx) => tx.txParams.from === txMeta.txParams.from)
|
||||
.filter(
|
||||
(tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from,
|
||||
)
|
||||
const highestConfirmedNonce = getHighestNonce(confirmedTxs)
|
||||
|
||||
const pendingTxs = txList
|
||||
.filter((tx) => tx.status === 'submitted')
|
||||
.filter((tx) => tx.txParams.from === txMeta.txParams.from)
|
||||
.filter(
|
||||
(tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from,
|
||||
)
|
||||
const highestContinuousNonce = getHighestContinuousFrom(
|
||||
pendingTxs,
|
||||
highestConfirmedNonce,
|
||||
)
|
||||
|
||||
const maxNonce = Math.max(highestContinuousNonce, highestConfirmedNonce)
|
||||
|
||||
if (parseInt(txMeta.txParams.nonce, 16) > maxNonce + 1) {
|
||||
txMeta.status = 'failed'
|
||||
txMeta.err = {
|
||||
message: 'nonce too high',
|
||||
note: 'migration 019 custom error',
|
||||
}
|
||||
}
|
||||
return txMeta
|
||||
},
|
||||
)
|
||||
}
|
||||
return newState
|
||||
}
|
||||
|
||||
function getHighestContinuousFrom (txList, startPoint) {
|
||||
function getHighestContinuousFrom(txList, startPoint) {
|
||||
const nonces = txList.map((txMeta) => {
|
||||
const { nonce } = txMeta.txParams
|
||||
return parseInt(nonce, 16)
|
||||
@ -78,7 +87,7 @@ function getHighestContinuousFrom (txList, startPoint) {
|
||||
return highest
|
||||
}
|
||||
|
||||
function getHighestNonce (txList) {
|
||||
function getHighestNonce(txList) {
|
||||
const nonces = txList.map((txMeta) => {
|
||||
const { nonce } = txMeta.txParams
|
||||
return parseInt(nonce || '0x0', 16)
|
||||
@ -86,4 +95,3 @@ function getHighestNonce (txList) {
|
||||
const highestNonce = Math.max.apply(null, nonces)
|
||||
return highestNonce
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ const version = 20
|
||||
export default {
|
||||
version,
|
||||
|
||||
migrate (originalVersionedData) {
|
||||
migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
@ -27,10 +27,9 @@ export default {
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
function transformState(state) {
|
||||
const newState = state
|
||||
if ('metamask' in newState &&
|
||||
!('firstTimeInfo' in newState.metamask)) {
|
||||
if ('metamask' in newState && !('firstTimeInfo' in newState.metamask)) {
|
||||
newState.metamask.firstTimeInfo = {
|
||||
version: '3.12.0',
|
||||
date: Date.now(),
|
||||
@ -38,4 +37,3 @@ function transformState (state) {
|
||||
}
|
||||
return newState
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ const version = 21
|
||||
export default {
|
||||
version,
|
||||
|
||||
migrate (originalVersionedData) {
|
||||
migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
@ -25,10 +25,9 @@ export default {
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
function transformState(state) {
|
||||
const newState = state
|
||||
delete newState.BlacklistController
|
||||
delete newState.RecentBlocks
|
||||
return newState
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
/*
|
||||
|
||||
This migration adds submittedTime to the txMeta if it is not their
|
||||
@ -12,7 +11,7 @@ const version = 22
|
||||
export default {
|
||||
version,
|
||||
|
||||
migrate (originalVersionedData) {
|
||||
migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
@ -26,7 +25,7 @@ export default {
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
function transformState(state) {
|
||||
const newState = state
|
||||
const { TransactionController } = newState
|
||||
if (TransactionController && TransactionController.transactions) {
|
||||
@ -36,7 +35,7 @@ function transformState (state) {
|
||||
if (txMeta.status !== 'submitted' || txMeta.submittedTime) {
|
||||
return txMeta
|
||||
}
|
||||
txMeta.submittedTime = (new Date()).getTime()
|
||||
txMeta.submittedTime = new Date().getTime()
|
||||
return txMeta
|
||||
})
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
/*
|
||||
|
||||
This migration removes transactions that are no longer usefull down to 40 total
|
||||
@ -12,7 +11,7 @@ const version = 23
|
||||
export default {
|
||||
version,
|
||||
|
||||
migrate (originalVersionedData) {
|
||||
migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
@ -26,7 +25,7 @@ export default {
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
function transformState(state) {
|
||||
const newState = state
|
||||
|
||||
const { TransactionController } = newState
|
||||
@ -41,10 +40,12 @@ function transformState (state) {
|
||||
let stripping = true
|
||||
while (reverseTxList.length > 40 && stripping) {
|
||||
const txIndex = reverseTxList.findIndex((txMeta) => {
|
||||
return (txMeta.status === 'failed' ||
|
||||
txMeta.status === 'rejected' ||
|
||||
txMeta.status === 'confirmed' ||
|
||||
txMeta.status === 'dropped')
|
||||
return (
|
||||
txMeta.status === 'failed' ||
|
||||
txMeta.status === 'rejected' ||
|
||||
txMeta.status === 'confirmed' ||
|
||||
txMeta.status === 'dropped'
|
||||
)
|
||||
})
|
||||
if (txIndex < 0) {
|
||||
stripping = false
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
/*
|
||||
|
||||
This migration ensures that the from address in txParams is to lower case for
|
||||
@ -13,7 +12,7 @@ const version = 24
|
||||
export default {
|
||||
version,
|
||||
|
||||
async migrate (originalVersionedData) {
|
||||
async migrate(originalVersionedData) {
|
||||
const versionedData = cloneDeep(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
const state = versionedData.data
|
||||
@ -23,21 +22,23 @@ export default {
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
function transformState(state) {
|
||||
const newState = state
|
||||
if (!newState.TransactionController) {
|
||||
return newState
|
||||
}
|
||||
const { transactions } = newState.TransactionController
|
||||
newState.TransactionController.transactions = transactions.map((txMeta, _) => {
|
||||
if (
|
||||
txMeta.status === 'unapproved' &&
|
||||
txMeta.txParams &&
|
||||
txMeta.txParams.from
|
||||
) {
|
||||
txMeta.txParams.from = txMeta.txParams.from.toLowerCase()
|
||||
}
|
||||
return txMeta
|
||||
})
|
||||
newState.TransactionController.transactions = transactions.map(
|
||||
(txMeta, _) => {
|
||||
if (
|
||||
txMeta.status === 'unapproved' &&
|
||||
txMeta.txParams &&
|
||||
txMeta.txParams.from
|
||||
) {
|
||||
txMeta.txParams.from = txMeta.txParams.from.toLowerCase()
|
||||
}
|
||||
return txMeta
|
||||
},
|
||||
)
|
||||
return newState
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user