mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge pull request #10397 from MetaMask/Version-v9.0.5
Version v9.0.5 RC
This commit is contained in:
commit
4b75135864
@ -3,10 +3,10 @@ version: 2.1
|
||||
executors:
|
||||
node-browsers:
|
||||
docker:
|
||||
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
|
||||
- image: circleci/node:14-browsers
|
||||
node-browsers-medium-plus:
|
||||
docker:
|
||||
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
|
||||
- image: circleci/node:14-browsers
|
||||
resource_class: medium+
|
||||
environment:
|
||||
NODE_OPTIONS: --max_old_space_size=2048
|
||||
@ -90,6 +90,7 @@ workflows:
|
||||
requires:
|
||||
- prep-deps
|
||||
- prep-build
|
||||
- prep-build-storybook
|
||||
- benchmark
|
||||
- all-tests-pass
|
||||
- job-publish-release:
|
||||
@ -214,7 +215,7 @@ jobs:
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- .out
|
||||
- storybook-build
|
||||
|
||||
test-lint:
|
||||
executor: node-browsers
|
||||
@ -262,6 +263,9 @@ jobs:
|
||||
executor: node-browsers
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Re-Install Chrome
|
||||
command: ./.circleci/scripts/chrome-install.sh
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
@ -286,6 +290,9 @@ jobs:
|
||||
executor: node-browsers
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Re-Install Chrome
|
||||
command: ./.circleci/scripts/chrome-install.sh
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
@ -361,9 +368,12 @@ jobs:
|
||||
destination: test-artifacts
|
||||
|
||||
benchmark:
|
||||
executor: node-browsers
|
||||
executor: node-browsers-medium-plus
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Re-Install Chrome
|
||||
command: ./.circleci/scripts/chrome-install.sh
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
@ -404,15 +414,12 @@ jobs:
|
||||
- store_artifacts:
|
||||
path: test-artifacts
|
||||
destination: test-artifacts
|
||||
# important: generate sesify viz AFTER uploading builds as artifacts
|
||||
# Temporarily disabled until we can update to a version of `sesify` with
|
||||
# this fix included: https://github.com/LavaMoat/LavaMoat/pull/121
|
||||
# - run:
|
||||
# name: build:sesify-viz
|
||||
# command: ./.circleci/scripts/create-sesify-viz
|
||||
- store_artifacts:
|
||||
path: build-artifacts
|
||||
destination: build-artifacts
|
||||
- store_artifacts:
|
||||
path: storybook-build
|
||||
destination: storybook
|
||||
- run:
|
||||
name: build:announce
|
||||
command: ./development/metamaskbot-build-announce.js
|
||||
|
19
.circleci/scripts/chrome-install.sh
Executable file
19
.circleci/scripts/chrome-install.sh
Executable file
@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
set -u
|
||||
set -o pipefail
|
||||
|
||||
CHROME_VERSION='79.0.3945.117-1'
|
||||
CHROME_BINARY="google-chrome-stable_${CHROME_VERSION}_amd64.deb"
|
||||
CHROME_BINARY_URL="http://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/${CHROME_BINARY}"
|
||||
|
||||
wget -O "${CHROME_BINARY}" -t 5 "${CHROME_BINARY_URL}"
|
||||
|
||||
(sudo dpkg -i "${CHROME_BINARY}" || sudo apt-get -fy install)
|
||||
|
||||
rm -rf "${CHROME_BINARY}"
|
||||
|
||||
sudo sed -i 's|HERE/chrome"|HERE/chrome" --disable-setuid-sandbox --no-sandbox|g' "/opt/google/chrome/google-chrome"
|
||||
|
||||
printf '%s\n' "CHROME ${CHROME_VERSION} configured"
|
@ -1,13 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -x
|
||||
set -e
|
||||
set -u
|
||||
set -o pipefail
|
||||
|
||||
# prepare artifacts dir
|
||||
mkdir -p ./build-artifacts/deps-viz/
|
||||
|
||||
# generate viz
|
||||
SESIFY_AUTOGEN=1 yarn build scripts:core:prod:background
|
||||
npx sesify-viz --deps sesify/deps-background.json --config sesify/background.json --dest ./build-artifacts/deps-viz/background
|
@ -5,7 +5,7 @@ set -x
|
||||
# Exit immediately if a command exits with a non-zero status.
|
||||
set -e
|
||||
|
||||
yarn --frozen-lockfile --ignore-scripts --har
|
||||
yarn --frozen-lockfile --har
|
||||
|
||||
# Move HAR file into directory with consistent name so that we can cache it
|
||||
mkdir -p build-artifacts/yarn-install-har
|
||||
@ -15,23 +15,5 @@ then
|
||||
mv ./*.har build-artifacts/yarn-install-har/
|
||||
fi
|
||||
|
||||
# run each in subshell so directory change does not persist
|
||||
# scripts can be any of:
|
||||
# preinstall
|
||||
# install
|
||||
# postinstall
|
||||
|
||||
# for build
|
||||
(cd node_modules/node-sass && yarn run postinstall)
|
||||
(cd node_modules/optipng-bin && yarn run postinstall)
|
||||
(cd node_modules/gifsicle && yarn run postinstall)
|
||||
(cd node_modules/jpegtran-bin && yarn run postinstall)
|
||||
|
||||
# for test
|
||||
(cd node_modules/scrypt && yarn run install)
|
||||
(cd node_modules/weak && yarn run install)
|
||||
(cd node_modules/chromedriver && yarn run install)
|
||||
(cd node_modules/geckodriver && yarn run postinstall)
|
||||
|
||||
# for release
|
||||
(cd node_modules/@sentry/cli && yarn run install)
|
||||
# use @lavamoat/allow-scripts instead of manually running install scripts so directory change does not persist
|
||||
yarn allow-scripts
|
||||
|
14
.eslintrc.js
14
.eslintrc.js
@ -53,13 +53,6 @@ module.exports = {
|
||||
|
||||
'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
|
||||
@ -147,7 +140,10 @@ module.exports = {
|
||||
'no-invalid-this': 'off',
|
||||
'@babel/no-invalid-this': 'error',
|
||||
|
||||
'@babel/semi': ['error', 'never'],
|
||||
// prettier handles these
|
||||
semi: 'off',
|
||||
'@babel/semi': 'off',
|
||||
|
||||
'mocha/no-setup-in-describe': 'off',
|
||||
'node/no-process-env': 'off',
|
||||
|
||||
@ -213,4 +209,4 @@ module.exports = {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -26,7 +26,7 @@ temp
|
||||
.DS_Store
|
||||
app/.DS_Store
|
||||
|
||||
.out/
|
||||
storybook-build/
|
||||
coverage/
|
||||
dist
|
||||
builds/
|
||||
|
@ -1,3 +1,2 @@
|
||||
singleQuote: true
|
||||
semi: false
|
||||
trailingComma: all
|
||||
|
57
.storybook/i18n.js
Normal file
57
.storybook/i18n.js
Normal file
@ -0,0 +1,57 @@
|
||||
import React, { Component, createContext, useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getMessage } from '../ui/app/helpers/utils/i18n-helper';
|
||||
import { I18nContext } from '../ui/app/contexts/i18n';
|
||||
|
||||
export { I18nContext }
|
||||
|
||||
export const I18nProvider = (props) => {
|
||||
const { currentLocale, current, en } = props
|
||||
|
||||
const t = useMemo(() => {
|
||||
return (key, ...args) =>
|
||||
getMessage(currentLocale, current, key, ...args) ||
|
||||
getMessage(currentLocale, en, key, ...args);
|
||||
}, [currentLocale, current, en]);
|
||||
|
||||
return (
|
||||
<I18nContext.Provider value={t}>{props.children}</I18nContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
I18nProvider.propTypes = {
|
||||
currentLocale: PropTypes.string,
|
||||
current: PropTypes.object,
|
||||
en: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
I18nProvider.defaultProps = {
|
||||
children: undefined,
|
||||
};
|
||||
|
||||
export class LegacyI18nProvider extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
children: undefined,
|
||||
};
|
||||
|
||||
static contextType = I18nContext;
|
||||
|
||||
static childContextTypes = {
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
t: this.context,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
58
.storybook/locales.js
Normal file
58
.storybook/locales.js
Normal file
@ -0,0 +1,58 @@
|
||||
import * as am from '../app/_locales/am/messages.json';
|
||||
import * as ar from '../app/_locales/ar/messages.json';
|
||||
import * as bg from '../app/_locales/bg/messages.json';
|
||||
import * as bn from '../app/_locales/bn/messages.json';
|
||||
import * as ca from '../app/_locales/ca/messages.json';
|
||||
import * as cs from '../app/_locales/cs/messages.json';
|
||||
import * as da from '../app/_locales/da/messages.json';
|
||||
import * as de from '../app/_locales/de/messages.json';
|
||||
import * as el from '../app/_locales/el/messages.json';
|
||||
import * as en from '../app/_locales/en/messages.json';
|
||||
import * as es from '../app/_locales/es/messages.json';
|
||||
import * as es_419 from '../app/_locales/es_419/messages.json';
|
||||
import * as et from '../app/_locales/et/messages.json';
|
||||
import * as fa from '../app/_locales/fa/messages.json';
|
||||
import * as fi from '../app/_locales/fi/messages.json';
|
||||
import * as fil from '../app/_locales/fil/messages.json';
|
||||
import * as fr from '../app/_locales/fr/messages.json';
|
||||
import * as gu from '../app/_locales/gu/messages.json';
|
||||
import * as he from '../app/_locales/he/messages.json';
|
||||
import * as hi from '../app/_locales/hi/messages.json';
|
||||
import * as hn from '../app/_locales/hn/messages.json';
|
||||
import * as hr from '../app/_locales/hr/messages.json';
|
||||
import * as ht from '../app/_locales/ht/messages.json';
|
||||
import * as hu from '../app/_locales/hu/messages.json';
|
||||
import * as id from '../app/_locales/id/messages.json';
|
||||
import * as it from '../app/_locales/it/messages.json';
|
||||
import * as ja from '../app/_locales/ja/messages.json';
|
||||
import * as kn from '../app/_locales/kn/messages.json';
|
||||
import * as ko from '../app/_locales/ko/messages.json';
|
||||
import * as lt from '../app/_locales/lt/messages.json';
|
||||
import * as lv from '../app/_locales/lv/messages.json';
|
||||
import * as ml from '../app/_locales/ml/messages.json';
|
||||
import * as mr from '../app/_locales/mr/messages.json';
|
||||
import * as ms from '../app/_locales/ms/messages.json';
|
||||
import * as nl from '../app/_locales/nl/messages.json';
|
||||
import * as no from '../app/_locales/no/messages.json';
|
||||
import * as ph from '../app/_locales/ph/messages.json';
|
||||
import * as pl from '../app/_locales/pl/messages.json';
|
||||
import * as pt from '../app/_locales/pt/messages.json';
|
||||
import * as pt_BR from '../app/_locales/pt_BR/messages.json';
|
||||
import * as pt_PT from '../app/_locales/pt_PT/messages.json';
|
||||
import * as ro from '../app/_locales/ro/messages.json';
|
||||
import * as ru from '../app/_locales/ru/messages.json';
|
||||
import * as sk from '../app/_locales/sk/messages.json';
|
||||
import * as sl from '../app/_locales/sl/messages.json';
|
||||
import * as sr from '../app/_locales/sr/messages.json';
|
||||
import * as sv from '../app/_locales/sv/messages.json';
|
||||
import * as sw from '../app/_locales/sw/messages.json';
|
||||
import * as ta from '../app/_locales/ta/messages.json';
|
||||
import * as te from '../app/_locales/te/messages.json';
|
||||
import * as th from '../app/_locales/th/messages.json';
|
||||
import * as tr from '../app/_locales/tr/messages.json';
|
||||
import * as uk from '../app/_locales/uk/messages.json';
|
||||
import * as vi from '../app/_locales/vi/messages.json';
|
||||
import * as zh_CN from '../app/_locales/zh_CN/messages.json';
|
||||
import * as zh_TW from '../app/_locales/zh_TW/messages.json';
|
||||
|
||||
export { am, ar, bg, bn, ca, cs, da, de, el, en, es, es_419, et, fa, fi, fil, fr, gu, he, hi, hn, hr, ht, hu, id, it, ja, kn, ko, lt, lv, ml, mr, ms, nl, no, ph, pl, pt, pt_BR, pt_PT, ro, ru, sk, sl, sr, sv, sw, ta, te, th, tr, uk, vi, zh_CN, zh_TW };
|
@ -8,6 +8,7 @@ module.exports = {
|
||||
'@storybook/addon-knobs',
|
||||
'@storybook/addon-actions',
|
||||
'@storybook/addon-backgrounds',
|
||||
'@storybook/addon-toolbars',
|
||||
],
|
||||
webpackFinal: async (config) => {
|
||||
config.module.strictExportPresence = true
|
||||
|
@ -1,11 +1,12 @@
|
||||
import React from 'react'
|
||||
import { addDecorator, addParameters } from '@storybook/react'
|
||||
import { withKnobs } from '@storybook/addon-knobs'
|
||||
import { I18nProvider, LegacyI18nProvider } from '../ui/app/contexts/i18n'
|
||||
import { Provider } from 'react-redux'
|
||||
import configureStore from '../ui/app/store/store'
|
||||
import '../ui/app/css/index.scss'
|
||||
import en from '../app/_locales/en/messages'
|
||||
import localeList from '../app/_locales/index.json'
|
||||
import * as allLocales from './locales'
|
||||
import { I18nProvider, LegacyI18nProvider } from './i18n'
|
||||
|
||||
addParameters({
|
||||
backgrounds: {
|
||||
@ -17,6 +18,20 @@ addParameters({
|
||||
}
|
||||
})
|
||||
|
||||
export const globalTypes = {
|
||||
locale: {
|
||||
name: 'Locale',
|
||||
description: 'internationalization locale',
|
||||
defaultValue: 'en',
|
||||
toolbar: {
|
||||
icon: 'globe',
|
||||
items: localeList.map(({ code, name }) => {
|
||||
return { value: code, right: code, title: name }
|
||||
})
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const styles = {
|
||||
height: '100vh',
|
||||
display: 'flex',
|
||||
@ -25,25 +40,28 @@ const styles = {
|
||||
}
|
||||
|
||||
const store = configureStore({
|
||||
metamask: { metamask: { currentLocale: 'en' } },
|
||||
|
||||
localeMessages: {
|
||||
current: en,
|
||||
en: en,
|
||||
},
|
||||
metamask: { metamask: { } },
|
||||
})
|
||||
|
||||
const metamaskDecorator = story => (
|
||||
<Provider store={store}>
|
||||
<I18nProvider>
|
||||
<LegacyI18nProvider>
|
||||
<div style={styles}>
|
||||
{ story() }
|
||||
</div>
|
||||
</LegacyI18nProvider>
|
||||
</I18nProvider>
|
||||
</Provider>
|
||||
)
|
||||
const metamaskDecorator = (story, context) => {
|
||||
const currentLocale = context.globals.locale
|
||||
const current = allLocales[currentLocale]
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<I18nProvider
|
||||
currentLocale={currentLocale}
|
||||
current={current}
|
||||
en={allLocales.en}
|
||||
>
|
||||
<LegacyI18nProvider>
|
||||
<div style={styles}>
|
||||
{ story() }
|
||||
</div>
|
||||
</LegacyI18nProvider>
|
||||
</I18nProvider>
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
|
||||
addDecorator(withKnobs)
|
||||
addDecorator(metamaskDecorator)
|
||||
|
15
CHANGELOG.md
15
CHANGELOG.md
@ -2,6 +2,21 @@
|
||||
|
||||
## Current Develop Branch
|
||||
|
||||
## 9.0.5 Mon Feb 08 2021
|
||||
- [#10278](https://github.com/MetaMask/metamask-extension/pull/10278): Allow editing transaction amount after clicking max
|
||||
- [#10214](https://github.com/MetaMask/metamask-extension/pull/10214): Standardize size, shape and color of network color indicators
|
||||
- [#10298](https://github.com/MetaMask/metamask-extension/pull/10298): Use network primary currency instead of always defaulting to ETH in the confirm approve screen
|
||||
- [#10300](https://github.com/MetaMask/metamask-extension/pull/10300): Add origin to signature request confirmation page
|
||||
- [#10296](https://github.com/MetaMask/metamask-extension/pull/10296): Add origin to transaction confirmation
|
||||
- [#10266](https://github.com/MetaMask/metamask-extension/pull/10266): Update `ko` localized messages
|
||||
- [#10263](https://github.com/MetaMask/metamask-extension/pull/10263): Update `id` localized messages
|
||||
- [#10347](https://github.com/MetaMask/metamask-extension/pull/10347): Require click of "Continue" button to interact with swap screen if there is a price impact warning for present swap
|
||||
- [#10373](https://github.com/MetaMask/metamask-extension/pull/10373): Change copy of submit button on swaps screen
|
||||
- [#10346](https://github.com/MetaMask/metamask-extension/pull/10346): Swaps token sources/verification messaging update
|
||||
- [#10378](https://github.com/MetaMask/metamask-extension/pull/10378): Stop showing the window.web3 in-app popup if the dapp is just using web3.currentProvider
|
||||
- [#10326](https://github.com/MetaMask/metamask-extension/pull/10326): Throw error when attempting to get an encryption key via eth_getEncryptionPublicKey when connected to Ledger HW
|
||||
- [#10386](https://github.com/MetaMask/metamask-extension/pull/10386): Make action buttons on message components in swaps flow accessible
|
||||
|
||||
## 9.0.4 Fri Jan 22 2021
|
||||
- [#10285](https://github.com/MetaMask/metamask-extension/pull/10285): Update @metamask/contract-metadata from v1.21.0 to 1.22.0
|
||||
- [#10174](https://github.com/MetaMask/metamask-extension/pull/10174): Move fox to bottom of 'About' page
|
||||
|
@ -17,10 +17,10 @@ To learn how to contribute to the MetaMask project itself, visit our [Internal D
|
||||
|
||||
## Building locally
|
||||
|
||||
- Install [Node.js](https://nodejs.org) version 10
|
||||
- Install [Node.js](https://nodejs.org) version 14
|
||||
- If you are using [nvm](https://github.com/creationix/nvm#installation) (recommended) running `nvm use` will automatically choose the right node version for you.
|
||||
- Install [Yarn](https://yarnpkg.com/en/docs/install)
|
||||
- Install dependencies: `yarn`
|
||||
- Install dependencies: `yarn setup` (not the usual install command)
|
||||
- Copy the `.metamaskrc.dist` file to `.metamaskrc`
|
||||
- Replace the `INFURA_PROJECT_ID` value with your own personal [Infura Project ID](https://infura.io/docs).
|
||||
- If debugging MetaMetrics, you'll need to add a value for `SEGMENT_WRITE_KEY` [Segment write key](https://segment.com/docs/connections/find-writekey/).
|
||||
|
@ -1253,9 +1253,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "የግል ዘር ሐረግዎ"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "ፊርማዎ እየተጠየቀ ነው"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "በስፒድ አፕ ላይ ዜሮ የነዳጅ ዋጋ"
|
||||
}
|
||||
|
@ -1249,9 +1249,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "عبارة الأمان الشخصية الخاصة بك"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "جاري طلب توقيعك"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "سعر الغاز صفر عند التعجيل"
|
||||
}
|
||||
|
@ -1252,9 +1252,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Вашата лична фраза зародиш"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Изисква се вашият подпис"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Нулева цена на газ при ускоряване"
|
||||
}
|
||||
|
@ -1256,9 +1256,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "আপনার ব্যক্তিগত সীড ফ্রেজ"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "আপনার স্বাক্ষরের অনুরোধ জানানো হয়েছে"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "স্পীড আপ এ শূণ্য গ্যাসের মূল্য"
|
||||
}
|
||||
|
@ -1225,9 +1225,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "La teva frase privada de seeds"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Es necessita la teva firma"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Gas a cost zero accelerant-se"
|
||||
}
|
||||
|
@ -464,8 +464,5 @@
|
||||
},
|
||||
"youSign": {
|
||||
"message": "Podepisujete"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Je vyžadován váš podpis"
|
||||
}
|
||||
}
|
||||
|
@ -1225,9 +1225,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Din private seed-sætning"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Din signatur er ønsket"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Nul brændstofpris på fremskynding"
|
||||
}
|
||||
|
@ -1213,9 +1213,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Ihr privater Seed-Schlüssel"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Deine Unterschrift wird angefordert"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Keine Gaskosten bei Beschleunigung"
|
||||
}
|
||||
|
@ -1250,9 +1250,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Η προσωπική σας φράση φύτρου"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Ζητείται η υπογραφή σας"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Μηδενική τιμή καυσίμου κατά την επιτάχυνση"
|
||||
}
|
||||
|
@ -364,6 +364,9 @@
|
||||
"contactsSettingsDescription": {
|
||||
"message": "Add, edit, remove, and manage your contacts"
|
||||
},
|
||||
"continue": {
|
||||
"message": "Continue"
|
||||
},
|
||||
"continueToWyre": {
|
||||
"message": "Continue to Wyre"
|
||||
},
|
||||
@ -1672,9 +1675,6 @@
|
||||
"swapFinalizing": {
|
||||
"message": "Finalizing..."
|
||||
},
|
||||
"swapGetQuotes": {
|
||||
"message": "Get quotes"
|
||||
},
|
||||
"swapHighSlippageWarning": {
|
||||
"message": "Slippage amount is very high. Make sure you know what you are doing!"
|
||||
},
|
||||
@ -1713,7 +1713,7 @@
|
||||
"message": "MetaMask fee"
|
||||
},
|
||||
"swapMetaMaskFeeDescription": {
|
||||
"message": "We find the best price from the top liquidity sources, every time. A fee of $1% is automatically factored into each quote, which supports ongoing development to make MetaMask even better.",
|
||||
"message": "We find the best price from the top liquidity sources, every time. A fee of $1% is automatically factored into this quote.",
|
||||
"description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number."
|
||||
},
|
||||
"swapNQuotes": {
|
||||
@ -1735,6 +1735,9 @@
|
||||
"message": "You are about to swap $1 $2 (~$3) for $4 $5 (~$6).",
|
||||
"description": "This message represents the price slippage for the swap. $1 and $4 are a number (ex: 2.89), $2 and $5 are symbols (ex: ETH), and $3 and $6 are fiat currency amounts."
|
||||
},
|
||||
"swapPriceDifferenceAcknowledgement": {
|
||||
"message": "I'm aware"
|
||||
},
|
||||
"swapPriceDifferenceTitle": {
|
||||
"message": "Price difference of ~$1%",
|
||||
"description": "$1 is a number (ex: 1.23) that represents the price difference."
|
||||
@ -1792,6 +1795,9 @@
|
||||
"swapRequestForQuotation": {
|
||||
"message": "Request for quotation"
|
||||
},
|
||||
"swapReviewSwap": {
|
||||
"message": "Review Swap"
|
||||
},
|
||||
"swapSearchForAToken": {
|
||||
"message": "Search for a token"
|
||||
},
|
||||
@ -1839,6 +1845,17 @@
|
||||
"message": "Swap $1 to $2",
|
||||
"description": "Used in the transaction display list to describe a swap. $1 and $2 are the symbols of tokens in involved in a swap."
|
||||
},
|
||||
"swapTokenVerificationMessage": {
|
||||
"message": "Always confirm the token address on $1.",
|
||||
"description": "Points the user to Etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"Etherscan\" followed by an info icon that shows more info on hover."
|
||||
},
|
||||
"swapTokenVerificationOnlyOneSource": {
|
||||
"message": "Only verified on 1 source."
|
||||
},
|
||||
"swapTokenVerificationSources": {
|
||||
"message": "Verified on $1 sources.",
|
||||
"description": "Indicates the number of token information sources that recognize the symbol + address. $1 is a decimal number."
|
||||
},
|
||||
"swapTransactionComplete": {
|
||||
"message": "Transaction complete"
|
||||
},
|
||||
@ -2086,6 +2103,9 @@
|
||||
"viewAccount": {
|
||||
"message": "View Account"
|
||||
},
|
||||
"viewAllDetails": {
|
||||
"message": "View all details"
|
||||
},
|
||||
"viewContact": {
|
||||
"message": "View Contact"
|
||||
},
|
||||
@ -2139,9 +2159,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Your private seed phrase"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Your signature is being requested"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Zero gas price on speed up"
|
||||
}
|
||||
|
@ -985,9 +985,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Tu frase semilla privada"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Tu firma ya fue solicitada"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "No hubo precio de gas al agilizar"
|
||||
}
|
||||
|
@ -1235,9 +1235,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Tu frase de inicialización privada"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Su firma se está procesando"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "El precio del gas es cero en aceleración"
|
||||
}
|
||||
|
@ -1246,9 +1246,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Teie privaatne seemnefraas"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Taotletakse teie allkirja"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Null gaasihind kiirendamisel"
|
||||
}
|
||||
|
@ -1256,9 +1256,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "عبارت بازیاب شخصی شما"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "امضاء شما درخواست میشود"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "قیمت صفر گاز بسوی سرعت"
|
||||
}
|
||||
|
@ -1253,9 +1253,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Sinun yksityinen siemenlauseesi"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Allekirjoitustasi pyydetään"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Polttoaine ei maksa kiihdytyksen yhteydessä"
|
||||
}
|
||||
|
@ -1147,9 +1147,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Ang iyong pribadong seed phrase"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Hinihiling ang iyong signature"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Zero ang presyo ng gas sa speed up"
|
||||
}
|
||||
|
@ -1232,9 +1232,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Votre phrase Seed privée"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Votre signature est demandée"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Prix de l'essence zéro sur l'accélération"
|
||||
}
|
||||
|
@ -1250,9 +1250,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "ה-seed phrase הפרטי שלך"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "נדרשת חתימתך"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "מחיר גז אפס על האצה"
|
||||
}
|
||||
|
@ -1657,9 +1657,6 @@
|
||||
"swapFinalizing": {
|
||||
"message": "अंतिम रूप दिया जा रहा है..."
|
||||
},
|
||||
"swapGetQuotes": {
|
||||
"message": "उद्धरण प्राप्त करें"
|
||||
},
|
||||
"swapHighSlippageWarning": {
|
||||
"message": "स्लिपेज राशि बहुत अधिक है। सुनिश्चित करें कि आप जानते हैं कि आप क्या कर रहे हैं!"
|
||||
},
|
||||
@ -2096,9 +2093,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "आपका निजी सीड फ्रेज़"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "आपके हस्ताक्षर का अनुरोध किया जा रहा है"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "ज़ीरो गैस मूल्य में तेज़ी"
|
||||
}
|
||||
|
@ -423,8 +423,5 @@
|
||||
},
|
||||
"youSign": {
|
||||
"message": "आप हस्ताक्षर कर रहे हैं"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "आपका हस्ताक्षर अनुरोध किया जा रहा है"
|
||||
}
|
||||
}
|
||||
|
@ -1246,9 +1246,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Vaša privatna početna rečenica"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Vaš se potpis zahtijeva"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Nulta cijena goriva kod ubrzavanja"
|
||||
}
|
||||
|
@ -780,8 +780,5 @@
|
||||
},
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Seed fraz prive ou a"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Yo mande siyati ou"
|
||||
}
|
||||
}
|
||||
|
@ -1246,9 +1246,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Az ön privát seed mondata"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Szükség van az aláírására"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Nulla gázár a gyorsuláshoz"
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1718,9 +1718,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "La tua frase seed privata"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "E' richiesta la tua firma"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Prezzo del gas maggiore di zero"
|
||||
}
|
||||
|
@ -474,8 +474,5 @@
|
||||
},
|
||||
"youSign": {
|
||||
"message": "署名しています。"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "あなたの署名がリクエストされています。"
|
||||
}
|
||||
}
|
||||
|
@ -1256,9 +1256,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "ನಿಮ್ಮ ಖಾಸಗಿ ಸೀಡ್ ಫ್ರೇಸ್"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "ನಿಮ್ಮ ಸಹಿಯನ್ನು ವಿನಂತಿಸಲಾಗಿದೆ"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "ವೇಗ ಹೆಚ್ಚಿಸುವುದಕ್ಕೆ ಶೂನ್ಯ ಗ್ಯಾಸ್ ಬೆಲೆ"
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1256,9 +1256,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Jūsų asmeninė atkūrimo frazė"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Prašoma jūsų parašo"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Nustatykite nulinę dujų kainą greitėjant"
|
||||
}
|
||||
|
@ -1252,9 +1252,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Jūsu privātā atkopšanas frāze"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Nepieciešams jūsu paraksts"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Gas nulles cena pie paātrinājuma"
|
||||
}
|
||||
|
@ -1230,9 +1230,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Ungkapan benih peribadi anda"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Tandatangan anda sedang diminta"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Sifar harga gas untuk pencepatan"
|
||||
}
|
||||
|
@ -410,8 +410,5 @@
|
||||
},
|
||||
"youSign": {
|
||||
"message": "U ondertekent"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Uw handtekening wordt aangevraagd"
|
||||
}
|
||||
}
|
||||
|
@ -1228,9 +1228,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Din private frøfrase"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Det bes om signaturen din "
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Null bensinpris for fremskynding"
|
||||
}
|
||||
|
@ -252,8 +252,5 @@
|
||||
},
|
||||
"youSign": {
|
||||
"message": "Ikaw ay nagsa-sign"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Hinihiling ang iyong signature"
|
||||
}
|
||||
}
|
||||
|
@ -1244,9 +1244,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Twoja prywatna fraza seed"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Twój podpis jest wymagany"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Przyspieszenie z zerową ceną gazu"
|
||||
}
|
||||
|
@ -420,8 +420,5 @@
|
||||
},
|
||||
"youSign": {
|
||||
"message": "Está a assinar"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "A sua assinatura está a ser pedida"
|
||||
}
|
||||
}
|
||||
|
@ -1238,9 +1238,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Sua frase-semente particular"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Sua assinatura está sendo solicitada"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Preço de Gas zero na agilização"
|
||||
}
|
||||
|
@ -1237,9 +1237,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Expresia dvs. seed privată"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Semnătura dvs. este solicitată"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Preț gas zero la accelerare"
|
||||
}
|
||||
|
@ -1292,8 +1292,5 @@
|
||||
},
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Ваша сид-фраза"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Запрашивается ваша подпись"
|
||||
}
|
||||
}
|
||||
|
@ -1213,9 +1213,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Vaša súkromná seed fráza"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Je vyžadován váš podpis"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Nulová cena za GAS pri zrýchlení"
|
||||
}
|
||||
|
@ -1235,9 +1235,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Vaš zasebni seed phrase"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Zahtevan je bil vaš podpis"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Ničelni gas price na pospešitvi"
|
||||
}
|
||||
|
@ -1241,9 +1241,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Vaša privatna šifra za oporavak naloga (seed phrase)"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Zahteva se vaš potpis"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Nulta cena gasa ubrzava"
|
||||
}
|
||||
|
@ -1231,9 +1231,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Din privata seedphrase"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "En begäran om din signatur har skickats"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Inget gaspris vid uppsnabbning"
|
||||
}
|
||||
|
@ -1234,9 +1234,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Kirai chako kianzio cha binafsi"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Saini yako inaombwa"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Bei ya gesi sifuri kwenye kuongeza kasi"
|
||||
}
|
||||
|
@ -552,8 +552,5 @@
|
||||
},
|
||||
"youSign": {
|
||||
"message": "நீங்கள் கையெழுத்திடுகிறீர்கள்"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "உங்கள் கையொப்பம் கோரப்படுகிறது"
|
||||
}
|
||||
}
|
||||
|
@ -573,8 +573,5 @@
|
||||
},
|
||||
"youSign": {
|
||||
"message": "คุณกำลังเซ็นชื่อ"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "ลายเซ็นของคุณกำลังได้รับการร้องขอ"
|
||||
}
|
||||
}
|
||||
|
@ -486,8 +486,5 @@
|
||||
},
|
||||
"youSign": {
|
||||
"message": "İmzalıyorsunuz"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "İmzanız isteniyor"
|
||||
}
|
||||
}
|
||||
|
@ -1256,9 +1256,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "Ваша секретна seed-фраза"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Надійшов запит вашого підпису"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Ціна пального на прискорення - нуль"
|
||||
}
|
||||
|
@ -303,8 +303,5 @@
|
||||
},
|
||||
"youSign": {
|
||||
"message": "Bạn đang ký nhận"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Chữ ký của bạn đang được yêu cầu"
|
||||
}
|
||||
}
|
||||
|
@ -1241,9 +1241,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "你的私有助记词"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "正在请求你的签名"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Gas 价格加速上涨"
|
||||
}
|
||||
|
@ -1244,9 +1244,6 @@
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "您的助憶詞"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "正在請求您的簽署"
|
||||
},
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "加速的 Gas 價格為 0"
|
||||
}
|
||||
|
@ -78,6 +78,6 @@
|
||||
"notifications"
|
||||
],
|
||||
"short_name": "__MSG_appName__",
|
||||
"version": "9.0.4",
|
||||
"version": "9.0.5",
|
||||
"web_accessible_resources": ["inpage.js", "phishing.html"]
|
||||
}
|
||||
|
@ -1,53 +1,53 @@
|
||||
import log from 'loglevel'
|
||||
import Wallet from 'ethereumjs-wallet'
|
||||
import importers from 'ethereumjs-wallet/thirdparty'
|
||||
import ethUtil from 'ethereumjs-util'
|
||||
import { addHexPrefix } from '../lib/util'
|
||||
import log from 'loglevel';
|
||||
import Wallet from 'ethereumjs-wallet';
|
||||
import importers from 'ethereumjs-wallet/thirdparty';
|
||||
import ethUtil from 'ethereumjs-util';
|
||||
import { addHexPrefix } from '../lib/util';
|
||||
|
||||
const accountImporter = {
|
||||
importAccount(strategy, args) {
|
||||
try {
|
||||
const importer = this.strategies[strategy]
|
||||
const privateKeyHex = importer(...args)
|
||||
return Promise.resolve(privateKeyHex)
|
||||
const importer = this.strategies[strategy];
|
||||
const privateKeyHex = importer(...args);
|
||||
return Promise.resolve(privateKeyHex);
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
return Promise.reject(e);
|
||||
}
|
||||
},
|
||||
|
||||
strategies: {
|
||||
'Private Key': (privateKey) => {
|
||||
if (!privateKey) {
|
||||
throw new Error('Cannot import an empty key.')
|
||||
throw new Error('Cannot import an empty key.');
|
||||
}
|
||||
|
||||
const prefixed = addHexPrefix(privateKey)
|
||||
const buffer = ethUtil.toBuffer(prefixed)
|
||||
const prefixed = addHexPrefix(privateKey);
|
||||
const buffer = ethUtil.toBuffer(prefixed);
|
||||
|
||||
if (!ethUtil.isValidPrivate(buffer)) {
|
||||
throw new Error('Cannot import invalid private key.')
|
||||
throw new Error('Cannot import invalid private key.');
|
||||
}
|
||||
|
||||
const stripped = ethUtil.stripHexPrefix(prefixed)
|
||||
return stripped
|
||||
const stripped = ethUtil.stripHexPrefix(prefixed);
|
||||
return stripped;
|
||||
},
|
||||
'JSON File': (input, password) => {
|
||||
let wallet
|
||||
let wallet;
|
||||
try {
|
||||
wallet = importers.fromEtherWallet(input, password)
|
||||
wallet = importers.fromEtherWallet(input, password);
|
||||
} catch (e) {
|
||||
log.debug('Attempt to import as EtherWallet format failed, trying V3')
|
||||
wallet = Wallet.fromV3(input, password, true)
|
||||
log.debug('Attempt to import as EtherWallet format failed, trying V3');
|
||||
wallet = Wallet.fromV3(input, password, true);
|
||||
}
|
||||
|
||||
return walletToPrivateKey(wallet)
|
||||
return walletToPrivateKey(wallet);
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
function walletToPrivateKey(wallet) {
|
||||
const privateKeyBuffer = wallet.getPrivateKey()
|
||||
return ethUtil.bufferToHex(privateKeyBuffer)
|
||||
const privateKeyBuffer = wallet.getPrivateKey();
|
||||
return ethUtil.bufferToHex(privateKeyBuffer);
|
||||
}
|
||||
|
||||
export default accountImporter
|
||||
export default accountImporter;
|
||||
|
@ -3,68 +3,68 @@
|
||||
*/
|
||||
// these need to run before anything else
|
||||
/* eslint-disable import/first,import/order */
|
||||
import setupFetchDebugging from './lib/setupFetchDebugging'
|
||||
import setupFetchDebugging from './lib/setupFetchDebugging';
|
||||
/* eslint-enable import/order */
|
||||
|
||||
setupFetchDebugging()
|
||||
setupFetchDebugging();
|
||||
|
||||
// polyfills
|
||||
import 'abortcontroller-polyfill/dist/polyfill-patch-fetch'
|
||||
import 'abortcontroller-polyfill/dist/polyfill-patch-fetch';
|
||||
|
||||
import endOfStream from 'end-of-stream'
|
||||
import pump from 'pump'
|
||||
import debounce from 'debounce-stream'
|
||||
import log from 'loglevel'
|
||||
import extension from 'extensionizer'
|
||||
import { storeAsStream, storeTransformStream } from '@metamask/obs-store'
|
||||
import PortStream from 'extension-port-stream'
|
||||
import { captureException } from '@sentry/browser'
|
||||
import endOfStream from 'end-of-stream';
|
||||
import pump from 'pump';
|
||||
import debounce from 'debounce-stream';
|
||||
import log from 'loglevel';
|
||||
import extension from 'extensionizer';
|
||||
import { storeAsStream, storeTransformStream } from '@metamask/obs-store';
|
||||
import PortStream from 'extension-port-stream';
|
||||
import { captureException } from '@sentry/browser';
|
||||
|
||||
import {
|
||||
ENVIRONMENT_TYPE_POPUP,
|
||||
ENVIRONMENT_TYPE_NOTIFICATION,
|
||||
ENVIRONMENT_TYPE_FULLSCREEN,
|
||||
} from '../../shared/constants/app'
|
||||
import migrations from './migrations'
|
||||
import Migrator from './lib/migrator'
|
||||
import ExtensionPlatform from './platforms/extension'
|
||||
import LocalStore from './lib/local-store'
|
||||
import ReadOnlyNetworkStore from './lib/network-store'
|
||||
import createStreamSink from './lib/createStreamSink'
|
||||
import NotificationManager from './lib/notification-manager'
|
||||
import MetamaskController from './metamask-controller'
|
||||
import rawFirstTimeState from './first-time-state'
|
||||
import getFirstPreferredLangCode from './lib/get-first-preferred-lang-code'
|
||||
import getObjStructure from './lib/getObjStructure'
|
||||
import setupEnsIpfsResolver from './lib/ens-ipfs/setup'
|
||||
} from '../../shared/constants/app';
|
||||
import migrations from './migrations';
|
||||
import Migrator from './lib/migrator';
|
||||
import ExtensionPlatform from './platforms/extension';
|
||||
import LocalStore from './lib/local-store';
|
||||
import ReadOnlyNetworkStore from './lib/network-store';
|
||||
import createStreamSink from './lib/createStreamSink';
|
||||
import NotificationManager from './lib/notification-manager';
|
||||
import MetamaskController from './metamask-controller';
|
||||
import rawFirstTimeState from './first-time-state';
|
||||
import getFirstPreferredLangCode from './lib/get-first-preferred-lang-code';
|
||||
import getObjStructure from './lib/getObjStructure';
|
||||
import setupEnsIpfsResolver from './lib/ens-ipfs/setup';
|
||||
/* eslint-enable import/first */
|
||||
|
||||
const { sentry } = global
|
||||
const firstTimeState = { ...rawFirstTimeState }
|
||||
const { sentry } = global;
|
||||
const firstTimeState = { ...rawFirstTimeState };
|
||||
|
||||
log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn')
|
||||
log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn');
|
||||
|
||||
const platform = new ExtensionPlatform()
|
||||
const platform = new ExtensionPlatform();
|
||||
|
||||
const notificationManager = new NotificationManager()
|
||||
global.METAMASK_NOTIFIER = notificationManager
|
||||
const notificationManager = new NotificationManager();
|
||||
global.METAMASK_NOTIFIER = notificationManager;
|
||||
|
||||
let popupIsOpen = false
|
||||
let notificationIsOpen = false
|
||||
const openMetamaskTabsIDs = {}
|
||||
const requestAccountTabIds = {}
|
||||
let popupIsOpen = false;
|
||||
let notificationIsOpen = false;
|
||||
const openMetamaskTabsIDs = {};
|
||||
const requestAccountTabIds = {};
|
||||
|
||||
// state persistence
|
||||
const inTest = process.env.IN_TEST === 'true'
|
||||
const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore()
|
||||
let versionedData
|
||||
const inTest = process.env.IN_TEST === 'true';
|
||||
const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore();
|
||||
let versionedData;
|
||||
|
||||
if (inTest || process.env.METAMASK_DEBUG) {
|
||||
global.metamaskGetState = localStore.get.bind(localStore)
|
||||
global.metamaskGetState = localStore.get.bind(localStore);
|
||||
}
|
||||
|
||||
// initialization flow
|
||||
initialize().catch(log.error)
|
||||
initialize().catch(log.error);
|
||||
|
||||
/**
|
||||
* An object representing a transaction, in whatever state it is in.
|
||||
@ -139,10 +139,10 @@ initialize().catch(log.error)
|
||||
* @returns {Promise} Setup complete.
|
||||
*/
|
||||
async function initialize() {
|
||||
const initState = await loadStateFromPersistence()
|
||||
const initLangCode = await getFirstPreferredLangCode()
|
||||
await setupController(initState, initLangCode)
|
||||
log.debug('MetaMask initialization complete.')
|
||||
const initState = await loadStateFromPersistence();
|
||||
const initLangCode = await getFirstPreferredLangCode();
|
||||
await setupController(initState, initLangCode);
|
||||
log.debug('MetaMask initialization complete.');
|
||||
}
|
||||
|
||||
//
|
||||
@ -156,13 +156,13 @@ async function initialize() {
|
||||
*/
|
||||
async function loadStateFromPersistence() {
|
||||
// migrations
|
||||
const migrator = new Migrator({ migrations })
|
||||
migrator.on('error', console.warn)
|
||||
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)
|
||||
(await localStore.get()) || migrator.generateInitialState(firstTimeState);
|
||||
|
||||
// check if somehow state is empty
|
||||
// this should never happen but new error reporting suggests that it has
|
||||
@ -170,38 +170,38 @@ async function loadStateFromPersistence() {
|
||||
// https://github.com/metamask/metamask-extension/issues/3919
|
||||
if (versionedData && !versionedData.data) {
|
||||
// unable to recover, clear state
|
||||
versionedData = migrator.generateInitialState(firstTimeState)
|
||||
sentry.captureMessage('MetaMask - Empty vault found - unable to recover')
|
||||
versionedData = migrator.generateInitialState(firstTimeState);
|
||||
sentry.captureMessage('MetaMask - Empty vault found - unable to recover');
|
||||
}
|
||||
|
||||
// report migration errors to sentry
|
||||
migrator.on('error', (err) => {
|
||||
// get vault structure without secrets
|
||||
const vaultStructure = getObjStructure(versionedData)
|
||||
const vaultStructure = getObjStructure(versionedData);
|
||||
sentry.captureException(err, {
|
||||
// "extra" key is required by Sentry
|
||||
extra: { vaultStructure },
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// migrate data
|
||||
versionedData = await migrator.migrateData(versionedData)
|
||||
versionedData = await migrator.migrateData(versionedData);
|
||||
if (!versionedData) {
|
||||
throw new Error('MetaMask - migrator returned undefined')
|
||||
throw new Error('MetaMask - migrator returned undefined');
|
||||
}
|
||||
|
||||
// write to disk
|
||||
if (localStore.isSupported) {
|
||||
localStore.set(versionedData)
|
||||
localStore.set(versionedData);
|
||||
} else {
|
||||
// throw in setTimeout so as to not block boot
|
||||
setTimeout(() => {
|
||||
throw new Error('MetaMask - Localstore not supported')
|
||||
})
|
||||
throw new Error('MetaMask - Localstore not supported');
|
||||
});
|
||||
}
|
||||
|
||||
// return just the data
|
||||
return versionedData.data
|
||||
return versionedData.data;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -232,12 +232,12 @@ function setupController(initState, initLangCode) {
|
||||
platform,
|
||||
extension,
|
||||
getRequestAccountTabIds: () => {
|
||||
return requestAccountTabIds
|
||||
return requestAccountTabIds;
|
||||
},
|
||||
getOpenMetamaskTabsIds: () => {
|
||||
return openMetamaskTabsIDs
|
||||
return openMetamaskTabsIDs;
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
setupEnsIpfsResolver({
|
||||
getCurrentNetwork: controller.getCurrentNetwork,
|
||||
@ -245,7 +245,7 @@ function setupController(initState, initLangCode) {
|
||||
controller.preferencesController,
|
||||
),
|
||||
provider: controller.provider,
|
||||
})
|
||||
});
|
||||
|
||||
// setup state persistence
|
||||
pump(
|
||||
@ -254,9 +254,9 @@ function setupController(initState, initLangCode) {
|
||||
storeTransformStream(versionifyData),
|
||||
createStreamSink(persistData),
|
||||
(error) => {
|
||||
log.error('MetaMask - Persistence pipeline failed', error)
|
||||
log.error('MetaMask - Persistence pipeline failed', error);
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Assigns the given state to the versioned object (with metadata), and returns that.
|
||||
@ -264,32 +264,32 @@ function setupController(initState, initLangCode) {
|
||||
* @returns {VersionedData} The state object wrapped in an object that includes a metadata key.
|
||||
*/
|
||||
function versionifyData(state) {
|
||||
versionedData.data = state
|
||||
return versionedData
|
||||
versionedData.data = state;
|
||||
return versionedData;
|
||||
}
|
||||
|
||||
let dataPersistenceFailing = false
|
||||
let dataPersistenceFailing = false;
|
||||
|
||||
async function persistData(state) {
|
||||
if (!state) {
|
||||
throw new Error('MetaMask - updated state is missing')
|
||||
throw new Error('MetaMask - updated state is missing');
|
||||
}
|
||||
if (!state.data) {
|
||||
throw new Error('MetaMask - updated state does not have data')
|
||||
throw new Error('MetaMask - updated state does not have data');
|
||||
}
|
||||
if (localStore.isSupported) {
|
||||
try {
|
||||
await localStore.set(state)
|
||||
await localStore.set(state);
|
||||
if (dataPersistenceFailing) {
|
||||
dataPersistenceFailing = false
|
||||
dataPersistenceFailing = false;
|
||||
}
|
||||
} catch (err) {
|
||||
// log error so we dont break the pipeline
|
||||
if (!dataPersistenceFailing) {
|
||||
dataPersistenceFailing = true
|
||||
captureException(err)
|
||||
dataPersistenceFailing = true;
|
||||
captureException(err);
|
||||
}
|
||||
log.error('error setting state in local store:', err)
|
||||
log.error('error setting state in local store:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -297,24 +297,24 @@ function setupController(initState, initLangCode) {
|
||||
//
|
||||
// connect to other contexts
|
||||
//
|
||||
extension.runtime.onConnect.addListener(connectRemote)
|
||||
extension.runtime.onConnectExternal.addListener(connectExternal)
|
||||
extension.runtime.onConnect.addListener(connectRemote);
|
||||
extension.runtime.onConnectExternal.addListener(connectExternal);
|
||||
|
||||
const metamaskInternalProcessHash = {
|
||||
[ENVIRONMENT_TYPE_POPUP]: true,
|
||||
[ENVIRONMENT_TYPE_NOTIFICATION]: true,
|
||||
[ENVIRONMENT_TYPE_FULLSCREEN]: true,
|
||||
}
|
||||
};
|
||||
|
||||
const metamaskBlockedPorts = ['trezor-connect']
|
||||
const metamaskBlockedPorts = ['trezor-connect'];
|
||||
|
||||
const isClientOpenStatus = () => {
|
||||
return (
|
||||
popupIsOpen ||
|
||||
Boolean(Object.keys(openMetamaskTabsIDs).length) ||
|
||||
notificationIsOpen
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* A runtime.Port object, as provided by the browser:
|
||||
@ -329,99 +329,99 @@ function setupController(initState, initLangCode) {
|
||||
* @param {Port} remotePort - The port provided by a new context.
|
||||
*/
|
||||
function connectRemote(remotePort) {
|
||||
const processName = remotePort.name
|
||||
const isMetaMaskInternalProcess = metamaskInternalProcessHash[processName]
|
||||
const processName = remotePort.name;
|
||||
const isMetaMaskInternalProcess = metamaskInternalProcessHash[processName];
|
||||
|
||||
if (metamaskBlockedPorts.includes(remotePort.name)) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMetaMaskInternalProcess) {
|
||||
const portStream = new PortStream(remotePort)
|
||||
const portStream = new PortStream(remotePort);
|
||||
// communication with popup
|
||||
controller.isClientOpen = true
|
||||
controller.setupTrustedCommunication(portStream, remotePort.sender)
|
||||
controller.isClientOpen = true;
|
||||
controller.setupTrustedCommunication(portStream, remotePort.sender);
|
||||
|
||||
if (processName === ENVIRONMENT_TYPE_POPUP) {
|
||||
popupIsOpen = true
|
||||
popupIsOpen = true;
|
||||
|
||||
endOfStream(portStream, () => {
|
||||
popupIsOpen = false
|
||||
controller.isClientOpen = isClientOpenStatus()
|
||||
})
|
||||
popupIsOpen = false;
|
||||
controller.isClientOpen = isClientOpenStatus();
|
||||
});
|
||||
}
|
||||
|
||||
if (processName === ENVIRONMENT_TYPE_NOTIFICATION) {
|
||||
notificationIsOpen = true
|
||||
notificationIsOpen = true;
|
||||
|
||||
endOfStream(portStream, () => {
|
||||
notificationIsOpen = false
|
||||
controller.isClientOpen = isClientOpenStatus()
|
||||
})
|
||||
notificationIsOpen = false;
|
||||
controller.isClientOpen = isClientOpenStatus();
|
||||
});
|
||||
}
|
||||
|
||||
if (processName === ENVIRONMENT_TYPE_FULLSCREEN) {
|
||||
const tabId = remotePort.sender.tab.id
|
||||
openMetamaskTabsIDs[tabId] = true
|
||||
const tabId = remotePort.sender.tab.id;
|
||||
openMetamaskTabsIDs[tabId] = true;
|
||||
|
||||
endOfStream(portStream, () => {
|
||||
delete openMetamaskTabsIDs[tabId]
|
||||
controller.isClientOpen = isClientOpenStatus()
|
||||
})
|
||||
delete openMetamaskTabsIDs[tabId];
|
||||
controller.isClientOpen = isClientOpenStatus();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (remotePort.sender && remotePort.sender.tab && remotePort.sender.url) {
|
||||
const tabId = remotePort.sender.tab.id
|
||||
const url = new URL(remotePort.sender.url)
|
||||
const { origin } = url
|
||||
const tabId = remotePort.sender.tab.id;
|
||||
const url = new URL(remotePort.sender.url);
|
||||
const { origin } = url;
|
||||
|
||||
remotePort.onMessage.addListener((msg) => {
|
||||
if (msg.data && msg.data.method === 'eth_requestAccounts') {
|
||||
requestAccountTabIds[origin] = tabId
|
||||
requestAccountTabIds[origin] = tabId;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
connectExternal(remotePort)
|
||||
connectExternal(remotePort);
|
||||
}
|
||||
}
|
||||
|
||||
// communication with page or other extension
|
||||
function connectExternal(remotePort) {
|
||||
const portStream = new PortStream(remotePort)
|
||||
controller.setupUntrustedCommunication(portStream, remotePort.sender)
|
||||
const portStream = new PortStream(remotePort);
|
||||
controller.setupUntrustedCommunication(portStream, remotePort.sender);
|
||||
}
|
||||
|
||||
//
|
||||
// User Interface setup
|
||||
//
|
||||
|
||||
updateBadge()
|
||||
controller.txController.on('update:badge', updateBadge)
|
||||
controller.messageManager.on('updateBadge', updateBadge)
|
||||
controller.personalMessageManager.on('updateBadge', updateBadge)
|
||||
controller.decryptMessageManager.on('updateBadge', updateBadge)
|
||||
controller.encryptionPublicKeyManager.on('updateBadge', updateBadge)
|
||||
controller.typedMessageManager.on('updateBadge', updateBadge)
|
||||
controller.approvalController.subscribe(updateBadge)
|
||||
controller.appStateController.on('updateBadge', updateBadge)
|
||||
updateBadge();
|
||||
controller.txController.on('update:badge', updateBadge);
|
||||
controller.messageManager.on('updateBadge', updateBadge);
|
||||
controller.personalMessageManager.on('updateBadge', updateBadge);
|
||||
controller.decryptMessageManager.on('updateBadge', updateBadge);
|
||||
controller.encryptionPublicKeyManager.on('updateBadge', updateBadge);
|
||||
controller.typedMessageManager.on('updateBadge', updateBadge);
|
||||
controller.approvalController.subscribe(updateBadge);
|
||||
controller.appStateController.on('updateBadge', updateBadge);
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
let label = ''
|
||||
const unapprovedTxCount = controller.txController.getUnapprovedTxCount()
|
||||
const { unapprovedMsgCount } = controller.messageManager
|
||||
const { unapprovedPersonalMsgCount } = controller.personalMessageManager
|
||||
const { unapprovedDecryptMsgCount } = controller.decryptMessageManager
|
||||
let label = '';
|
||||
const unapprovedTxCount = controller.txController.getUnapprovedTxCount();
|
||||
const { unapprovedMsgCount } = controller.messageManager;
|
||||
const { unapprovedPersonalMsgCount } = controller.personalMessageManager;
|
||||
const { unapprovedDecryptMsgCount } = controller.decryptMessageManager;
|
||||
const {
|
||||
unapprovedEncryptionPublicKeyMsgCount,
|
||||
} = controller.encryptionPublicKeyManager
|
||||
const { unapprovedTypedMessagesCount } = controller.typedMessageManager
|
||||
const pendingApprovalCount = controller.approvalController.getTotalApprovalCount()
|
||||
} = controller.encryptionPublicKeyManager;
|
||||
const { unapprovedTypedMessagesCount } = controller.typedMessageManager;
|
||||
const pendingApprovalCount = controller.approvalController.getTotalApprovalCount();
|
||||
const waitingForUnlockCount =
|
||||
controller.appStateController.waitingForUnlock.length
|
||||
controller.appStateController.waitingForUnlock.length;
|
||||
const count =
|
||||
unapprovedTxCount +
|
||||
unapprovedMsgCount +
|
||||
@ -430,15 +430,15 @@ function setupController(initState, initLangCode) {
|
||||
unapprovedEncryptionPublicKeyMsgCount +
|
||||
unapprovedTypedMessagesCount +
|
||||
pendingApprovalCount +
|
||||
waitingForUnlockCount
|
||||
waitingForUnlockCount;
|
||||
if (count) {
|
||||
label = String(count)
|
||||
label = String(count);
|
||||
}
|
||||
extension.browserAction.setBadgeText({ text: label })
|
||||
extension.browserAction.setBadgeBackgroundColor({ color: '#037DD6' })
|
||||
extension.browserAction.setBadgeText({ text: label });
|
||||
extension.browserAction.setBadgeBackgroundColor({ color: '#037DD6' });
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
//
|
||||
@ -449,18 +449,18 @@ function setupController(initState, initLangCode) {
|
||||
* Opens the browser popup for user confirmation
|
||||
*/
|
||||
async function triggerUi() {
|
||||
const tabs = await platform.getActiveTabs()
|
||||
const tabs = await platform.getActiveTabs();
|
||||
const currentlyActiveMetamaskTab = Boolean(
|
||||
tabs.find((tab) => openMetamaskTabsIDs[tab.id]),
|
||||
)
|
||||
);
|
||||
// Vivaldi is not closing port connection on popup close, so popupIsOpen does not work correctly
|
||||
// To be reviewed in the future if this behaviour is fixed - also the way we determine isVivaldi variable might change at some point
|
||||
const isVivaldi =
|
||||
tabs.length > 0 &&
|
||||
tabs[0].extData &&
|
||||
tabs[0].extData.indexOf('vivaldi_tab') > -1
|
||||
tabs[0].extData.indexOf('vivaldi_tab') > -1;
|
||||
if ((isVivaldi || !popupIsOpen) && !currentlyActiveMetamaskTab) {
|
||||
await notificationManager.showPopup()
|
||||
await notificationManager.showPopup();
|
||||
}
|
||||
}
|
||||
|
||||
@ -469,15 +469,15 @@ async function triggerUi() {
|
||||
* then it waits until user interact with the UI
|
||||
*/
|
||||
async function openPopup() {
|
||||
await triggerUi()
|
||||
await triggerUi();
|
||||
await new Promise((resolve) => {
|
||||
const interval = setInterval(() => {
|
||||
if (!notificationIsOpen) {
|
||||
clearInterval(interval)
|
||||
resolve()
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
// On first install, open a new tab with MetaMask
|
||||
@ -486,6 +486,6 @@ extension.runtime.onInstalled.addListener(({ reason }) => {
|
||||
reason === 'install' &&
|
||||
!(process.env.METAMASK_DEBUG || process.env.IN_TEST)
|
||||
) {
|
||||
platform.openExtensionInBrowser()
|
||||
platform.openExtensionInBrowser();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
@ -1,8 +1,8 @@
|
||||
export const SINGLE_CALL_BALANCES_ADDRESS =
|
||||
'0xb1f8e55c7f64d203c1400b9d8555d050f94adf39'
|
||||
'0xb1f8e55c7f64d203c1400b9d8555d050f94adf39';
|
||||
export const SINGLE_CALL_BALANCES_ADDRESS_RINKEBY =
|
||||
'0x9f510b19f1ad66f0dcf6e45559fab0d6752c1db7'
|
||||
'0x9f510b19f1ad66f0dcf6e45559fab0d6752c1db7';
|
||||
export const SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN =
|
||||
'0xb8e671734ce5c8d7dfbbea5574fa4cf39f7a54a4'
|
||||
'0xb8e671734ce5c8d7dfbbea5574fa4cf39f7a54a4';
|
||||
export const SINGLE_CALL_BALANCES_ADDRESS_KOVAN =
|
||||
'0xb1d3fbb2f83aecd196f474c16ca5d9cffa0d0ffc'
|
||||
'0xb1d3fbb2f83aecd196f474c16ca5d9cffa0d0ffc';
|
||||
|
@ -1,35 +1,35 @@
|
||||
import querystring from 'querystring'
|
||||
import pump from 'pump'
|
||||
import LocalMessageDuplexStream from 'post-message-stream'
|
||||
import ObjectMultiplex from 'obj-multiplex'
|
||||
import extension from 'extensionizer'
|
||||
import PortStream from 'extension-port-stream'
|
||||
import { obj as createThoughStream } from 'through2'
|
||||
import querystring from 'querystring';
|
||||
import pump from 'pump';
|
||||
import LocalMessageDuplexStream from 'post-message-stream';
|
||||
import ObjectMultiplex from 'obj-multiplex';
|
||||
import extension from 'extensionizer';
|
||||
import PortStream from 'extension-port-stream';
|
||||
import { obj as createThoughStream } from 'through2';
|
||||
|
||||
// These require calls need to use require to be statically recognized by browserify
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
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
|
||||
);
|
||||
const inpageSuffix = `//# sourceURL=${extension.runtime.getURL('inpage.js')}\n`;
|
||||
const inpageBundle = inpageContent + inpageSuffix;
|
||||
|
||||
const CONTENT_SCRIPT = 'metamask-contentscript'
|
||||
const INPAGE = 'metamask-inpage'
|
||||
const PROVIDER = 'metamask-provider'
|
||||
const CONTENT_SCRIPT = 'metamask-contentscript';
|
||||
const INPAGE = 'metamask-inpage';
|
||||
const PROVIDER = 'metamask-provider';
|
||||
|
||||
// TODO:LegacyProvider: Delete
|
||||
const LEGACY_CONTENT_SCRIPT = 'contentscript'
|
||||
const LEGACY_INPAGE = 'inpage'
|
||||
const LEGACY_PROVIDER = 'provider'
|
||||
const LEGACY_PUBLIC_CONFIG = 'publicConfig'
|
||||
const LEGACY_CONTENT_SCRIPT = 'contentscript';
|
||||
const LEGACY_INPAGE = 'inpage';
|
||||
const LEGACY_PROVIDER = 'provider';
|
||||
const LEGACY_PUBLIC_CONFIG = 'publicConfig';
|
||||
|
||||
if (shouldInjectProvider()) {
|
||||
injectScript(inpageBundle)
|
||||
setupStreams()
|
||||
injectScript(inpageBundle);
|
||||
setupStreams();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -39,14 +39,14 @@ if (shouldInjectProvider()) {
|
||||
*/
|
||||
function injectScript(content) {
|
||||
try {
|
||||
const container = document.head || document.documentElement
|
||||
const scriptTag = document.createElement('script')
|
||||
scriptTag.setAttribute('async', 'false')
|
||||
scriptTag.textContent = content
|
||||
container.insertBefore(scriptTag, container.children[0])
|
||||
container.removeChild(scriptTag)
|
||||
const container = document.head || document.documentElement;
|
||||
const scriptTag = document.createElement('script');
|
||||
scriptTag.setAttribute('async', 'false');
|
||||
scriptTag.textContent = content;
|
||||
container.insertBefore(scriptTag, container.children[0]);
|
||||
container.removeChild(scriptTag);
|
||||
} catch (error) {
|
||||
console.error('MetaMask: Provider injection failed.', error)
|
||||
console.error('MetaMask: Provider injection failed.', error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,81 +60,81 @@ async function setupStreams() {
|
||||
const pageStream = new LocalMessageDuplexStream({
|
||||
name: CONTENT_SCRIPT,
|
||||
target: INPAGE,
|
||||
})
|
||||
const extensionPort = extension.runtime.connect({ name: CONTENT_SCRIPT })
|
||||
const extensionStream = new PortStream(extensionPort)
|
||||
});
|
||||
const extensionPort = extension.runtime.connect({ name: CONTENT_SCRIPT });
|
||||
const extensionStream = new PortStream(extensionPort);
|
||||
|
||||
// create and connect channel muxers
|
||||
// so we can handle the channels individually
|
||||
const pageMux = new ObjectMultiplex()
|
||||
pageMux.setMaxListeners(25)
|
||||
const extensionMux = new ObjectMultiplex()
|
||||
extensionMux.setMaxListeners(25)
|
||||
extensionMux.ignoreStream(LEGACY_PUBLIC_CONFIG) // TODO:LegacyProvider: Delete
|
||||
const pageMux = new ObjectMultiplex();
|
||||
pageMux.setMaxListeners(25);
|
||||
const extensionMux = new ObjectMultiplex();
|
||||
extensionMux.setMaxListeners(25);
|
||||
extensionMux.ignoreStream(LEGACY_PUBLIC_CONFIG); // TODO:LegacyProvider: Delete
|
||||
|
||||
pump(pageMux, pageStream, pageMux, (err) =>
|
||||
logStreamDisconnectWarning('MetaMask Inpage Multiplex', err),
|
||||
)
|
||||
);
|
||||
pump(extensionMux, extensionStream, extensionMux, (err) => {
|
||||
logStreamDisconnectWarning('MetaMask Background Multiplex', err)
|
||||
notifyInpageOfStreamFailure()
|
||||
})
|
||||
logStreamDisconnectWarning('MetaMask Background Multiplex', err);
|
||||
notifyInpageOfStreamFailure();
|
||||
});
|
||||
|
||||
// forward communication across inpage-background for these channels only
|
||||
forwardTrafficBetweenMuxes(PROVIDER, pageMux, extensionMux)
|
||||
forwardTrafficBetweenMuxes(PROVIDER, pageMux, extensionMux);
|
||||
|
||||
// connect "phishing" channel to warning system
|
||||
const phishingStream = extensionMux.createStream('phishing')
|
||||
phishingStream.once('data', redirectToPhishingWarning)
|
||||
const phishingStream = extensionMux.createStream('phishing');
|
||||
phishingStream.once('data', redirectToPhishingWarning);
|
||||
|
||||
// TODO:LegacyProvider: Delete
|
||||
// handle legacy provider
|
||||
const legacyPageStream = new LocalMessageDuplexStream({
|
||||
name: LEGACY_CONTENT_SCRIPT,
|
||||
target: LEGACY_INPAGE,
|
||||
})
|
||||
});
|
||||
|
||||
const legacyPageMux = new ObjectMultiplex()
|
||||
legacyPageMux.setMaxListeners(25)
|
||||
const legacyExtensionMux = new ObjectMultiplex()
|
||||
legacyExtensionMux.setMaxListeners(25)
|
||||
const legacyPageMux = new ObjectMultiplex();
|
||||
legacyPageMux.setMaxListeners(25);
|
||||
const legacyExtensionMux = new ObjectMultiplex();
|
||||
legacyExtensionMux.setMaxListeners(25);
|
||||
|
||||
pump(legacyPageMux, legacyPageStream, legacyPageMux, (err) =>
|
||||
logStreamDisconnectWarning('MetaMask Legacy Inpage Multiplex', err),
|
||||
)
|
||||
);
|
||||
pump(
|
||||
legacyExtensionMux,
|
||||
extensionStream,
|
||||
getNotificationTransformStream(),
|
||||
legacyExtensionMux,
|
||||
(err) => {
|
||||
logStreamDisconnectWarning('MetaMask Background Legacy Multiplex', err)
|
||||
notifyInpageOfStreamFailure()
|
||||
logStreamDisconnectWarning('MetaMask Background Legacy Multiplex', err);
|
||||
notifyInpageOfStreamFailure();
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
forwardNamedTrafficBetweenMuxes(
|
||||
LEGACY_PROVIDER,
|
||||
PROVIDER,
|
||||
legacyPageMux,
|
||||
legacyExtensionMux,
|
||||
)
|
||||
);
|
||||
forwardTrafficBetweenMuxes(
|
||||
LEGACY_PUBLIC_CONFIG,
|
||||
legacyPageMux,
|
||||
legacyExtensionMux,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function forwardTrafficBetweenMuxes(channelName, muxA, muxB) {
|
||||
const channelA = muxA.createStream(channelName)
|
||||
const channelB = muxB.createStream(channelName)
|
||||
const channelA = muxA.createStream(channelName);
|
||||
const channelB = muxB.createStream(channelName);
|
||||
pump(channelA, channelB, channelA, (error) =>
|
||||
console.debug(
|
||||
`MetaMask: Muxed traffic for channel "${channelName}" failed.`,
|
||||
error,
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// TODO:LegacyProvider: Delete
|
||||
@ -144,14 +144,14 @@ function forwardNamedTrafficBetweenMuxes(
|
||||
muxA,
|
||||
muxB,
|
||||
) {
|
||||
const channelA = muxA.createStream(channelAName)
|
||||
const channelB = muxB.createStream(channelBName)
|
||||
const channelA = muxA.createStream(channelAName);
|
||||
const channelB = muxB.createStream(channelBName);
|
||||
pump(channelA, channelB, channelA, (error) =>
|
||||
console.debug(
|
||||
`MetaMask: Muxed traffic between channels "${channelAName}" and "${channelBName}" failed.`,
|
||||
error,
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// TODO:LegacyProvider: Delete
|
||||
@ -159,13 +159,13 @@ function getNotificationTransformStream() {
|
||||
return createThoughStream((chunk, _, cb) => {
|
||||
if (chunk?.name === PROVIDER) {
|
||||
if (chunk.data?.method === 'metamask_accountsChanged') {
|
||||
chunk.data.method = 'wallet_accountsChanged'
|
||||
chunk.data.result = chunk.data.params
|
||||
delete chunk.data.params
|
||||
chunk.data.method = 'wallet_accountsChanged';
|
||||
chunk.data.result = chunk.data.params;
|
||||
delete chunk.data.params;
|
||||
}
|
||||
}
|
||||
cb(null, chunk)
|
||||
})
|
||||
cb(null, chunk);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -178,7 +178,7 @@ function logStreamDisconnectWarning(remoteLabel, error) {
|
||||
console.debug(
|
||||
`MetaMask: Content script lost connection to "${remoteLabel}".`,
|
||||
error,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -200,7 +200,7 @@ function notifyInpageOfStreamFailure() {
|
||||
},
|
||||
},
|
||||
window.location.origin,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -214,7 +214,7 @@ function shouldInjectProvider() {
|
||||
suffixCheck() &&
|
||||
documentElementCheck() &&
|
||||
!blockedDomainCheck()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -223,11 +223,11 @@ function shouldInjectProvider() {
|
||||
* @returns {boolean} {@code true} if the doctype is html or if none exists
|
||||
*/
|
||||
function doctypeCheck() {
|
||||
const { doctype } = window.document
|
||||
const { doctype } = window.document;
|
||||
if (doctype) {
|
||||
return doctype.name === 'html'
|
||||
return doctype.name === 'html';
|
||||
}
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -240,14 +240,14 @@ function doctypeCheck() {
|
||||
* @returns {boolean} whether or not the extension of the current document is prohibited
|
||||
*/
|
||||
function suffixCheck() {
|
||||
const prohibitedTypes = [/\.xml$/u, /\.pdf$/u]
|
||||
const currentUrl = window.location.pathname
|
||||
const prohibitedTypes = [/\.xml$/u, /\.pdf$/u];
|
||||
const currentUrl = window.location.pathname;
|
||||
for (let i = 0; i < prohibitedTypes.length; i++) {
|
||||
if (prohibitedTypes[i].test(currentUrl)) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -256,11 +256,11 @@ function suffixCheck() {
|
||||
* @returns {boolean} {@code true} if the documentElement is an html node or if none exists
|
||||
*/
|
||||
function documentElementCheck() {
|
||||
const documentElement = document.documentElement.nodeName
|
||||
const documentElement = document.documentElement.nodeName;
|
||||
if (documentElement) {
|
||||
return documentElement.toLowerCase() === 'html'
|
||||
return documentElement.toLowerCase() === 'html';
|
||||
}
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -280,30 +280,30 @@ function blockedDomainCheck() {
|
||||
'ani.gamer.com.tw',
|
||||
'blueskybooking.com',
|
||||
'sharefile.com',
|
||||
]
|
||||
const currentUrl = window.location.href
|
||||
let currentRegex
|
||||
];
|
||||
const currentUrl = window.location.href;
|
||||
let currentRegex;
|
||||
for (let i = 0; i < blockedDomains.length; i++) {
|
||||
const blockedDomain = blockedDomains[i].replace('.', '\\.')
|
||||
const blockedDomain = blockedDomains[i].replace('.', '\\.');
|
||||
currentRegex = new RegExp(
|
||||
`(?:https?:\\/\\/)(?:(?!${blockedDomain}).)*$`,
|
||||
'u',
|
||||
)
|
||||
);
|
||||
if (!currentRegex.test(currentUrl)) {
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects the current page to a phishing information page
|
||||
*/
|
||||
function redirectToPhishingWarning() {
|
||||
console.debug('MetaMask: Routing to Phishing Warning component.')
|
||||
const extensionURL = extension.runtime.getURL('phishing.html')
|
||||
console.debug('MetaMask: Routing to Phishing Warning component.');
|
||||
const extensionURL = extension.runtime.getURL('phishing.html');
|
||||
window.location.href = `${extensionURL}#${querystring.stringify({
|
||||
hostname: window.location.hostname,
|
||||
href: window.location.href,
|
||||
})}`
|
||||
})}`;
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { ObservableStore } from '@metamask/obs-store'
|
||||
import { ObservableStore } from '@metamask/obs-store';
|
||||
import {
|
||||
TOGGLEABLE_ALERT_TYPES,
|
||||
WEB3_SHIM_USAGE_ALERT_STATES,
|
||||
} from '../../../shared/constants/alerts'
|
||||
} from '../../../shared/constants/alerts';
|
||||
|
||||
/**
|
||||
* @typedef {Object} AlertControllerInitState
|
||||
@ -21,14 +21,14 @@ import {
|
||||
const defaultState = {
|
||||
alertEnabledness: TOGGLEABLE_ALERT_TYPES.reduce(
|
||||
(alertEnabledness, alertType) => {
|
||||
alertEnabledness[alertType] = true
|
||||
return alertEnabledness
|
||||
alertEnabledness[alertType] = true;
|
||||
return alertEnabledness;
|
||||
},
|
||||
{},
|
||||
),
|
||||
unconnectedAccountAlertShownOrigins: {},
|
||||
web3ShimUsageOrigins: {},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Controller responsible for maintaining alert-related state.
|
||||
@ -39,36 +39,36 @@ export default class AlertController {
|
||||
* @param {AlertControllerOptions} [opts] - Controller configuration parameters
|
||||
*/
|
||||
constructor(opts = {}) {
|
||||
const { initState = {}, preferencesStore } = opts
|
||||
const { initState = {}, preferencesStore } = opts;
|
||||
const state = {
|
||||
...defaultState,
|
||||
alertEnabledness: {
|
||||
...defaultState.alertEnabledness,
|
||||
...initState.alertEnabledness,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
this.store = new ObservableStore(state)
|
||||
this.store = new ObservableStore(state);
|
||||
|
||||
this.selectedAddress = preferencesStore.getState().selectedAddress
|
||||
this.selectedAddress = preferencesStore.getState().selectedAddress;
|
||||
|
||||
preferencesStore.subscribe(({ selectedAddress }) => {
|
||||
const currentState = this.store.getState()
|
||||
const currentState = this.store.getState();
|
||||
if (
|
||||
currentState.unconnectedAccountAlertShownOrigins &&
|
||||
this.selectedAddress !== selectedAddress
|
||||
) {
|
||||
this.selectedAddress = selectedAddress
|
||||
this.store.updateState({ unconnectedAccountAlertShownOrigins: {} })
|
||||
this.selectedAddress = selectedAddress;
|
||||
this.store.updateState({ unconnectedAccountAlertShownOrigins: {} });
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
setAlertEnabledness(alertId, enabledness) {
|
||||
let { alertEnabledness } = this.store.getState()
|
||||
alertEnabledness = { ...alertEnabledness }
|
||||
alertEnabledness[alertId] = enabledness
|
||||
this.store.updateState({ alertEnabledness })
|
||||
let { alertEnabledness } = this.store.getState();
|
||||
alertEnabledness = { ...alertEnabledness };
|
||||
alertEnabledness[alertId] = enabledness;
|
||||
this.store.updateState({ alertEnabledness });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,12 +76,12 @@ export default class AlertController {
|
||||
* @param {string} origin - The origin the alert has been shown for
|
||||
*/
|
||||
setUnconnectedAccountAlertShown(origin) {
|
||||
let { unconnectedAccountAlertShownOrigins } = this.store.getState()
|
||||
let { unconnectedAccountAlertShownOrigins } = this.store.getState();
|
||||
unconnectedAccountAlertShownOrigins = {
|
||||
...unconnectedAccountAlertShownOrigins,
|
||||
}
|
||||
unconnectedAccountAlertShownOrigins[origin] = true
|
||||
this.store.updateState({ unconnectedAccountAlertShownOrigins })
|
||||
};
|
||||
unconnectedAccountAlertShownOrigins[origin] = true;
|
||||
this.store.updateState({ unconnectedAccountAlertShownOrigins });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -92,7 +92,7 @@ export default class AlertController {
|
||||
* origin, or undefined.
|
||||
*/
|
||||
getWeb3ShimUsageState(origin) {
|
||||
return this.store.getState().web3ShimUsageOrigins[origin]
|
||||
return this.store.getState().web3ShimUsageOrigins[origin];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -101,7 +101,7 @@ export default class AlertController {
|
||||
* @param {string} origin - The origin the that used the web3 shim.
|
||||
*/
|
||||
setWeb3ShimUsageRecorded(origin) {
|
||||
this._setWeb3ShimUsageState(origin, WEB3_SHIM_USAGE_ALERT_STATES.RECORDED)
|
||||
this._setWeb3ShimUsageState(origin, WEB3_SHIM_USAGE_ALERT_STATES.RECORDED);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -111,7 +111,7 @@ export default class AlertController {
|
||||
* dismissed for.
|
||||
*/
|
||||
setWeb3ShimUsageAlertDismissed(origin) {
|
||||
this._setWeb3ShimUsageState(origin, WEB3_SHIM_USAGE_ALERT_STATES.DISMISSED)
|
||||
this._setWeb3ShimUsageState(origin, WEB3_SHIM_USAGE_ALERT_STATES.DISMISSED);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -120,11 +120,11 @@ export default class AlertController {
|
||||
* @param {number} value - The state value to set.
|
||||
*/
|
||||
_setWeb3ShimUsageState(origin, value) {
|
||||
let { web3ShimUsageOrigins } = this.store.getState()
|
||||
let { web3ShimUsageOrigins } = this.store.getState();
|
||||
web3ShimUsageOrigins = {
|
||||
...web3ShimUsageOrigins,
|
||||
}
|
||||
web3ShimUsageOrigins[origin] = value
|
||||
this.store.updateState({ web3ShimUsageOrigins })
|
||||
};
|
||||
web3ShimUsageOrigins[origin] = value;
|
||||
this.store.updateState({ web3ShimUsageOrigins });
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import EventEmitter from 'events'
|
||||
import { ObservableStore } from '@metamask/obs-store'
|
||||
import EventEmitter from 'events';
|
||||
import { ObservableStore } from '@metamask/obs-store';
|
||||
|
||||
export default class AppStateController extends EventEmitter {
|
||||
/**
|
||||
@ -14,34 +14,34 @@ export default class AppStateController extends EventEmitter {
|
||||
onInactiveTimeout,
|
||||
showUnlockRequest,
|
||||
preferencesStore,
|
||||
} = opts
|
||||
super()
|
||||
} = opts;
|
||||
super();
|
||||
|
||||
this.onInactiveTimeout = onInactiveTimeout || (() => undefined)
|
||||
this.onInactiveTimeout = onInactiveTimeout || (() => undefined);
|
||||
this.store = new ObservableStore({
|
||||
timeoutMinutes: 0,
|
||||
connectedStatusPopoverHasBeenShown: true,
|
||||
swapsWelcomeMessageHasBeenShown: false,
|
||||
defaultHomeActiveTabName: null,
|
||||
...initState,
|
||||
})
|
||||
this.timer = null
|
||||
});
|
||||
this.timer = null;
|
||||
|
||||
this.isUnlocked = isUnlocked
|
||||
this.waitingForUnlock = []
|
||||
addUnlockListener(this.handleUnlock.bind(this))
|
||||
this.isUnlocked = isUnlocked;
|
||||
this.waitingForUnlock = [];
|
||||
addUnlockListener(this.handleUnlock.bind(this));
|
||||
|
||||
this._showUnlockRequest = showUnlockRequest
|
||||
this._showUnlockRequest = showUnlockRequest;
|
||||
|
||||
preferencesStore.subscribe(({ preferences }) => {
|
||||
const currentState = this.store.getState()
|
||||
const currentState = this.store.getState();
|
||||
if (currentState.timeoutMinutes !== preferences.autoLockTimeLimit) {
|
||||
this._setInactiveTimeout(preferences.autoLockTimeLimit)
|
||||
this._setInactiveTimeout(preferences.autoLockTimeLimit);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const { preferences } = preferencesStore.getState()
|
||||
this._setInactiveTimeout(preferences.autoLockTimeLimit)
|
||||
const { preferences } = preferencesStore.getState();
|
||||
this._setInactiveTimeout(preferences.autoLockTimeLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -56,11 +56,11 @@ export default class AppStateController extends EventEmitter {
|
||||
getUnlockPromise(shouldShowUnlockRequest) {
|
||||
return new Promise((resolve) => {
|
||||
if (this.isUnlocked()) {
|
||||
resolve()
|
||||
resolve();
|
||||
} else {
|
||||
this.waitForUnlock(resolve, shouldShowUnlockRequest)
|
||||
this.waitForUnlock(resolve, shouldShowUnlockRequest);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,10 +73,10 @@ export default class AppStateController extends EventEmitter {
|
||||
* popup should be opened.
|
||||
*/
|
||||
waitForUnlock(resolve, shouldShowUnlockRequest) {
|
||||
this.waitingForUnlock.push({ resolve })
|
||||
this.emit('updateBadge')
|
||||
this.waitingForUnlock.push({ resolve });
|
||||
this.emit('updateBadge');
|
||||
if (shouldShowUnlockRequest) {
|
||||
this._showUnlockRequest()
|
||||
this._showUnlockRequest();
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,9 +86,9 @@ export default class AppStateController extends EventEmitter {
|
||||
handleUnlock() {
|
||||
if (this.waitingForUnlock.length > 0) {
|
||||
while (this.waitingForUnlock.length > 0) {
|
||||
this.waitingForUnlock.shift().resolve()
|
||||
this.waitingForUnlock.shift().resolve();
|
||||
}
|
||||
this.emit('updateBadge')
|
||||
this.emit('updateBadge');
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,7 +99,7 @@ export default class AppStateController extends EventEmitter {
|
||||
setDefaultHomeActiveTabName(defaultHomeActiveTabName) {
|
||||
this.store.updateState({
|
||||
defaultHomeActiveTabName,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,7 +108,7 @@ export default class AppStateController extends EventEmitter {
|
||||
setConnectedStatusPopoverHasBeenShown() {
|
||||
this.store.updateState({
|
||||
connectedStatusPopoverHasBeenShown: true,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -117,7 +117,7 @@ export default class AppStateController extends EventEmitter {
|
||||
setSwapsWelcomeMessageHasBeenShown() {
|
||||
this.store.updateState({
|
||||
swapsWelcomeMessageHasBeenShown: true,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -125,7 +125,7 @@ export default class AppStateController extends EventEmitter {
|
||||
* @returns {void}
|
||||
*/
|
||||
setLastActiveTime() {
|
||||
this._resetTimer()
|
||||
this._resetTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -137,9 +137,9 @@ export default class AppStateController extends EventEmitter {
|
||||
_setInactiveTimeout(timeoutMinutes) {
|
||||
this.store.updateState({
|
||||
timeoutMinutes,
|
||||
})
|
||||
});
|
||||
|
||||
this._resetTimer()
|
||||
this._resetTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -152,19 +152,19 @@ export default class AppStateController extends EventEmitter {
|
||||
* @private
|
||||
*/
|
||||
_resetTimer() {
|
||||
const { timeoutMinutes } = this.store.getState()
|
||||
const { timeoutMinutes } = this.store.getState();
|
||||
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer)
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
|
||||
if (!timeoutMinutes) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
this.timer = setTimeout(
|
||||
() => this.onInactiveTimeout(),
|
||||
timeoutMinutes * 60 * 1000,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ObservableStore } from '@metamask/obs-store'
|
||||
import { ObservableStore } from '@metamask/obs-store';
|
||||
|
||||
/**
|
||||
* @typedef {Object} CachedBalancesOptions
|
||||
@ -18,15 +18,15 @@ export default class CachedBalancesController {
|
||||
* @param {CachedBalancesOptions} [opts] - Controller configuration parameters
|
||||
*/
|
||||
constructor(opts = {}) {
|
||||
const { accountTracker, getNetwork } = opts
|
||||
const { accountTracker, getNetwork } = opts;
|
||||
|
||||
this.accountTracker = accountTracker
|
||||
this.getNetwork = getNetwork
|
||||
this.accountTracker = accountTracker;
|
||||
this.getNetwork = getNetwork;
|
||||
|
||||
const initState = { cachedBalances: {}, ...opts.initState }
|
||||
this.store = new ObservableStore(initState)
|
||||
const initState = { cachedBalances: {}, ...opts.initState };
|
||||
this.store = new ObservableStore(initState);
|
||||
|
||||
this._registerUpdates()
|
||||
this._registerUpdates();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -37,33 +37,33 @@ export default class CachedBalancesController {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async updateCachedBalances({ accounts }) {
|
||||
const network = await this.getNetwork()
|
||||
const network = await this.getNetwork();
|
||||
const balancesToCache = await this._generateBalancesToCache(
|
||||
accounts,
|
||||
network,
|
||||
)
|
||||
);
|
||||
this.store.updateState({
|
||||
cachedBalances: balancesToCache,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
_generateBalancesToCache(newAccounts, currentNetwork) {
|
||||
const { cachedBalances } = this.store.getState()
|
||||
const currentNetworkBalancesToCache = { ...cachedBalances[currentNetwork] }
|
||||
const { cachedBalances } = this.store.getState();
|
||||
const currentNetworkBalancesToCache = { ...cachedBalances[currentNetwork] };
|
||||
|
||||
Object.keys(newAccounts).forEach((accountID) => {
|
||||
const account = newAccounts[accountID]
|
||||
const account = newAccounts[accountID];
|
||||
|
||||
if (account.balance) {
|
||||
currentNetworkBalancesToCache[accountID] = account.balance
|
||||
currentNetworkBalancesToCache[accountID] = account.balance;
|
||||
}
|
||||
})
|
||||
});
|
||||
const balancesToCache = {
|
||||
...cachedBalances,
|
||||
[currentNetwork]: currentNetworkBalancesToCache,
|
||||
}
|
||||
};
|
||||
|
||||
return balancesToCache
|
||||
return balancesToCache;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -71,7 +71,7 @@ export default class CachedBalancesController {
|
||||
*/
|
||||
|
||||
clearCachedBalances() {
|
||||
this.store.updateState({ cachedBalances: {} })
|
||||
this.store.updateState({ cachedBalances: {} });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,7 +83,7 @@ export default class CachedBalancesController {
|
||||
*
|
||||
*/
|
||||
_registerUpdates() {
|
||||
const update = this.updateCachedBalances.bind(this)
|
||||
this.accountTracker.store.subscribe(update)
|
||||
const update = this.updateCachedBalances.bind(this);
|
||||
this.accountTracker.store.subscribe(update);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import Web3 from 'web3'
|
||||
import contracts from '@metamask/contract-metadata'
|
||||
import { warn } from 'loglevel'
|
||||
import SINGLE_CALL_BALANCES_ABI from 'single-call-balance-checker-abi'
|
||||
import { MAINNET_CHAIN_ID } from '../../../shared/constants/network'
|
||||
import { SINGLE_CALL_BALANCES_ADDRESS } from '../constants/contracts'
|
||||
import Web3 from 'web3';
|
||||
import contracts from '@metamask/contract-metadata';
|
||||
import { warn } from 'loglevel';
|
||||
import SINGLE_CALL_BALANCES_ABI from 'single-call-balance-checker-abi';
|
||||
import { MAINNET_CHAIN_ID } from '../../../shared/constants/network';
|
||||
import { SINGLE_CALL_BALANCES_ADDRESS } from '../constants/contracts';
|
||||
|
||||
// By default, poll every 3 minutes
|
||||
const DEFAULT_INTERVAL = 180 * 1000
|
||||
const DEFAULT_INTERVAL = 180 * 1000;
|
||||
|
||||
/**
|
||||
* A controller that polls for token exchange
|
||||
@ -24,10 +24,10 @@ export default class DetectTokensController {
|
||||
network,
|
||||
keyringMemStore,
|
||||
} = {}) {
|
||||
this.preferences = preferences
|
||||
this.interval = interval
|
||||
this.network = network
|
||||
this.keyringMemStore = keyringMemStore
|
||||
this.preferences = preferences;
|
||||
this.interval = interval;
|
||||
this.network = network;
|
||||
this.keyringMemStore = keyringMemStore;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -35,59 +35,59 @@ export default class DetectTokensController {
|
||||
*/
|
||||
async detectNewTokens() {
|
||||
if (!this.isActive) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (this._network.store.getState().provider.chainId !== MAINNET_CHAIN_ID) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const tokensToDetect = []
|
||||
this.web3.setProvider(this._network._provider)
|
||||
const tokensToDetect = [];
|
||||
this.web3.setProvider(this._network._provider);
|
||||
for (const contractAddress in contracts) {
|
||||
if (
|
||||
contracts[contractAddress].erc20 &&
|
||||
!this.tokenAddresses.includes(contractAddress.toLowerCase()) &&
|
||||
!this.hiddenTokens.includes(contractAddress.toLowerCase())
|
||||
) {
|
||||
tokensToDetect.push(contractAddress)
|
||||
tokensToDetect.push(contractAddress);
|
||||
}
|
||||
}
|
||||
|
||||
let result
|
||||
let result;
|
||||
try {
|
||||
result = await this._getTokenBalances(tokensToDetect)
|
||||
result = await this._getTokenBalances(tokensToDetect);
|
||||
} catch (error) {
|
||||
warn(
|
||||
`MetaMask - DetectTokensController single call balance fetch failed`,
|
||||
error,
|
||||
)
|
||||
return
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
tokensToDetect.forEach((tokenAddress, index) => {
|
||||
const balance = result[index]
|
||||
const balance = result[index];
|
||||
if (balance && !balance.isZero()) {
|
||||
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)
|
||||
.at(SINGLE_CALL_BALANCES_ADDRESS);
|
||||
return new Promise((resolve, reject) => {
|
||||
ethContract.balances([this.selectedAddress], tokens, (error, result) => {
|
||||
if (error) {
|
||||
return reject(error)
|
||||
return reject(error);
|
||||
}
|
||||
return resolve(result)
|
||||
})
|
||||
})
|
||||
return resolve(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -97,10 +97,10 @@ export default class DetectTokensController {
|
||||
*/
|
||||
restartTokenDetection() {
|
||||
if (!(this.isActive && this.selectedAddress)) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this.detectNewTokens()
|
||||
this.interval = DEFAULT_INTERVAL
|
||||
this.detectNewTokens();
|
||||
this.interval = DEFAULT_INTERVAL;
|
||||
}
|
||||
|
||||
/* eslint-disable accessor-pairs */
|
||||
@ -108,13 +108,13 @@ export default class DetectTokensController {
|
||||
* @type {Number}
|
||||
*/
|
||||
set interval(interval) {
|
||||
this._handle && clearInterval(this._handle)
|
||||
this._handle && clearInterval(this._handle);
|
||||
if (!interval) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this._handle = setInterval(() => {
|
||||
this.detectNewTokens()
|
||||
}, interval)
|
||||
this.detectNewTokens();
|
||||
}, interval);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -123,26 +123,26 @@ export default class DetectTokensController {
|
||||
*/
|
||||
set preferences(preferences) {
|
||||
if (!preferences) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this._preferences = preferences
|
||||
const currentTokens = preferences.store.getState().tokens
|
||||
this._preferences = preferences;
|
||||
const currentTokens = preferences.store.getState().tokens;
|
||||
this.tokenAddresses = currentTokens
|
||||
? currentTokens.map((token) => token.address)
|
||||
: []
|
||||
this.hiddenTokens = preferences.store.getState().hiddenTokens
|
||||
: [];
|
||||
this.hiddenTokens = preferences.store.getState().hiddenTokens;
|
||||
preferences.store.subscribe(({ tokens = [], hiddenTokens = [] }) => {
|
||||
this.tokenAddresses = tokens.map((token) => {
|
||||
return token.address
|
||||
})
|
||||
this.hiddenTokens = hiddenTokens
|
||||
})
|
||||
return token.address;
|
||||
});
|
||||
this.hiddenTokens = hiddenTokens;
|
||||
});
|
||||
preferences.store.subscribe(({ selectedAddress }) => {
|
||||
if (this.selectedAddress !== selectedAddress) {
|
||||
this.selectedAddress = selectedAddress
|
||||
this.restartTokenDetection()
|
||||
this.selectedAddress = selectedAddress;
|
||||
this.restartTokenDetection();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -150,10 +150,10 @@ export default class DetectTokensController {
|
||||
*/
|
||||
set network(network) {
|
||||
if (!network) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this._network = network
|
||||
this.web3 = new Web3(network._provider)
|
||||
this._network = network;
|
||||
this.web3 = new Web3(network._provider);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -162,17 +162,17 @@ export default class DetectTokensController {
|
||||
*/
|
||||
set keyringMemStore(keyringMemStore) {
|
||||
if (!keyringMemStore) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this._keyringMemStore = keyringMemStore
|
||||
this._keyringMemStore = keyringMemStore;
|
||||
this._keyringMemStore.subscribe(({ isUnlocked }) => {
|
||||
if (this.isUnlocked !== isUnlocked) {
|
||||
this.isUnlocked = isUnlocked
|
||||
this.isUnlocked = isUnlocked;
|
||||
if (isUnlocked) {
|
||||
this.restartTokenDetection()
|
||||
this.restartTokenDetection();
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -180,7 +180,7 @@ export default class DetectTokensController {
|
||||
* @type {Object}
|
||||
*/
|
||||
get isActive() {
|
||||
return this.isOpen && this.isUnlocked
|
||||
return this.isOpen && this.isUnlocked;
|
||||
}
|
||||
/* eslint-enable accessor-pairs */
|
||||
}
|
||||
|
@ -1,23 +1,23 @@
|
||||
import EthJsEns from 'ethjs-ens'
|
||||
import ensNetworkMap from 'ethereum-ens-network-map'
|
||||
import EthJsEns from 'ethjs-ens';
|
||||
import ensNetworkMap from 'ethereum-ens-network-map';
|
||||
|
||||
export default class Ens {
|
||||
static getNetworkEnsSupport(network) {
|
||||
return Boolean(ensNetworkMap[network])
|
||||
return Boolean(ensNetworkMap[network]);
|
||||
}
|
||||
|
||||
constructor({ network, provider } = {}) {
|
||||
this._ethJsEns = new EthJsEns({
|
||||
network,
|
||||
provider,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
lookup(ensName) {
|
||||
return this._ethJsEns.lookup(ensName)
|
||||
return this._ethJsEns.lookup(ensName);
|
||||
}
|
||||
|
||||
reverse(address) {
|
||||
return this._ethJsEns.reverse(address)
|
||||
return this._ethJsEns.reverse(address);
|
||||
}
|
||||
}
|
||||
|
@ -1,95 +1,95 @@
|
||||
import punycode from 'punycode/punycode'
|
||||
import ethUtil from 'ethereumjs-util'
|
||||
import { ObservableStore } from '@metamask/obs-store'
|
||||
import log from 'loglevel'
|
||||
import Ens from './ens'
|
||||
import punycode from 'punycode/punycode';
|
||||
import ethUtil from 'ethereumjs-util';
|
||||
import { ObservableStore } from '@metamask/obs-store';
|
||||
import log from 'loglevel';
|
||||
import Ens from './ens';
|
||||
|
||||
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
||||
const ZERO_X_ERROR_ADDRESS = '0x'
|
||||
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
||||
const ZERO_X_ERROR_ADDRESS = '0x';
|
||||
|
||||
export default class EnsController {
|
||||
constructor({ ens, provider, networkStore } = {}) {
|
||||
const initState = {
|
||||
ensResolutionsByAddress: {},
|
||||
}
|
||||
};
|
||||
|
||||
this._ens = ens
|
||||
this._ens = ens;
|
||||
if (!this._ens) {
|
||||
const network = networkStore.getState()
|
||||
const network = networkStore.getState();
|
||||
if (Ens.getNetworkEnsSupport(network)) {
|
||||
this._ens = new Ens({
|
||||
network,
|
||||
provider,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.store = new ObservableStore(initState)
|
||||
this.store = new ObservableStore(initState);
|
||||
networkStore.subscribe((network) => {
|
||||
this.store.putState(initState)
|
||||
this.store.putState(initState);
|
||||
if (Ens.getNetworkEnsSupport(network)) {
|
||||
this._ens = new Ens({
|
||||
network,
|
||||
provider,
|
||||
})
|
||||
});
|
||||
} else {
|
||||
delete this._ens
|
||||
delete this._ens;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
reverseResolveAddress(address) {
|
||||
return this._reverseResolveAddress(ethUtil.toChecksumAddress(address))
|
||||
return this._reverseResolveAddress(ethUtil.toChecksumAddress(address));
|
||||
}
|
||||
|
||||
async _reverseResolveAddress(address) {
|
||||
if (!this._ens) {
|
||||
return undefined
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const state = this.store.getState()
|
||||
const state = this.store.getState();
|
||||
if (state.ensResolutionsByAddress[address]) {
|
||||
return state.ensResolutionsByAddress[address]
|
||||
return state.ensResolutionsByAddress[address];
|
||||
}
|
||||
|
||||
let domain
|
||||
let domain;
|
||||
try {
|
||||
domain = await this._ens.reverse(address)
|
||||
domain = await this._ens.reverse(address);
|
||||
} catch (error) {
|
||||
log.debug(error)
|
||||
return undefined
|
||||
log.debug(error);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let registeredAddress
|
||||
let registeredAddress;
|
||||
try {
|
||||
registeredAddress = await this._ens.lookup(domain)
|
||||
registeredAddress = await this._ens.lookup(domain);
|
||||
} catch (error) {
|
||||
log.debug(error)
|
||||
return undefined
|
||||
log.debug(error);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
registeredAddress === ZERO_ADDRESS ||
|
||||
registeredAddress === ZERO_X_ERROR_ADDRESS
|
||||
) {
|
||||
return undefined
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (ethUtil.toChecksumAddress(registeredAddress) !== address) {
|
||||
return undefined
|
||||
return undefined;
|
||||
}
|
||||
|
||||
this._updateResolutionsByAddress(address, punycode.toASCII(domain))
|
||||
return domain
|
||||
this._updateResolutionsByAddress(address, punycode.toASCII(domain));
|
||||
return domain;
|
||||
}
|
||||
|
||||
_updateResolutionsByAddress(address, domain) {
|
||||
const oldState = this.store.getState()
|
||||
const oldState = this.store.getState();
|
||||
this.store.putState({
|
||||
ensResolutionsByAddress: {
|
||||
...oldState.ensResolutionsByAddress,
|
||||
[address]: domain,
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { ObservableStore } from '@metamask/obs-store'
|
||||
import log from 'loglevel'
|
||||
import BN from 'bn.js'
|
||||
import createId from '../lib/random-id'
|
||||
import { bnToHex } from '../lib/util'
|
||||
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout'
|
||||
import { ObservableStore } from '@metamask/obs-store';
|
||||
import log from 'loglevel';
|
||||
import BN from 'bn.js';
|
||||
import createId from '../lib/random-id';
|
||||
import { bnToHex } from '../lib/util';
|
||||
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout';
|
||||
|
||||
import {
|
||||
TRANSACTION_CATEGORIES,
|
||||
TRANSACTION_STATUSES,
|
||||
} from '../../../shared/constants/transaction'
|
||||
} from '../../../shared/constants/transaction';
|
||||
import {
|
||||
CHAIN_ID_TO_NETWORK_ID_MAP,
|
||||
CHAIN_ID_TO_TYPE_MAP,
|
||||
@ -22,9 +22,9 @@ import {
|
||||
RINKEBY_CHAIN_ID,
|
||||
ROPSTEN,
|
||||
ROPSTEN_CHAIN_ID,
|
||||
} from '../../../shared/constants/network'
|
||||
} from '../../../shared/constants/network';
|
||||
|
||||
const fetchWithTimeout = getFetchWithTimeout(30000)
|
||||
const fetchWithTimeout = getFetchWithTimeout(30000);
|
||||
|
||||
/**
|
||||
* This controller is responsible for retrieving incoming transactions. Etherscan is polled once every block to check
|
||||
@ -39,23 +39,23 @@ const etherscanSupportedNetworks = [
|
||||
MAINNET_CHAIN_ID,
|
||||
RINKEBY_CHAIN_ID,
|
||||
ROPSTEN_CHAIN_ID,
|
||||
]
|
||||
];
|
||||
|
||||
export default class IncomingTransactionsController {
|
||||
constructor(opts = {}) {
|
||||
const { blockTracker, networkController, preferencesController } = opts
|
||||
this.blockTracker = blockTracker
|
||||
this.networkController = networkController
|
||||
this.preferencesController = preferencesController
|
||||
const { blockTracker, networkController, preferencesController } = opts;
|
||||
this.blockTracker = blockTracker;
|
||||
this.networkController = networkController;
|
||||
this.preferencesController = preferencesController;
|
||||
|
||||
this._onLatestBlock = async (newBlockNumberHex) => {
|
||||
const selectedAddress = this.preferencesController.getSelectedAddress()
|
||||
const newBlockNumberDec = parseInt(newBlockNumberHex, 16)
|
||||
const selectedAddress = this.preferencesController.getSelectedAddress();
|
||||
const newBlockNumberDec = parseInt(newBlockNumberHex, 16);
|
||||
await this._update({
|
||||
address: selectedAddress,
|
||||
newBlockNumberDec,
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const initState = {
|
||||
incomingTransactions: {},
|
||||
@ -67,8 +67,8 @@ export default class IncomingTransactionsController {
|
||||
[ROPSTEN]: null,
|
||||
},
|
||||
...opts.initState,
|
||||
}
|
||||
this.store = new ObservableStore(initState)
|
||||
};
|
||||
this.store = new ObservableStore(initState);
|
||||
|
||||
this.preferencesController.store.subscribe(
|
||||
pairwise((prevState, currState) => {
|
||||
@ -76,79 +76,79 @@ export default class IncomingTransactionsController {
|
||||
featureFlags: {
|
||||
showIncomingTransactions: prevShowIncomingTransactions,
|
||||
} = {},
|
||||
} = prevState
|
||||
} = prevState;
|
||||
const {
|
||||
featureFlags: {
|
||||
showIncomingTransactions: currShowIncomingTransactions,
|
||||
} = {},
|
||||
} = currState
|
||||
} = currState;
|
||||
|
||||
if (currShowIncomingTransactions === prevShowIncomingTransactions) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (prevShowIncomingTransactions && !currShowIncomingTransactions) {
|
||||
this.stop()
|
||||
return
|
||||
this.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
this.start()
|
||||
this.start();
|
||||
}),
|
||||
)
|
||||
);
|
||||
|
||||
this.preferencesController.store.subscribe(
|
||||
pairwise(async (prevState, currState) => {
|
||||
const { selectedAddress: prevSelectedAddress } = prevState
|
||||
const { selectedAddress: currSelectedAddress } = currState
|
||||
const { selectedAddress: prevSelectedAddress } = prevState;
|
||||
const { selectedAddress: currSelectedAddress } = currState;
|
||||
|
||||
if (currSelectedAddress === prevSelectedAddress) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
await this._update({
|
||||
address: currSelectedAddress,
|
||||
})
|
||||
});
|
||||
}),
|
||||
)
|
||||
);
|
||||
|
||||
this.networkController.on('networkDidChange', async () => {
|
||||
const address = this.preferencesController.getSelectedAddress()
|
||||
const address = this.preferencesController.getSelectedAddress();
|
||||
await this._update({
|
||||
address,
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
start() {
|
||||
const { featureFlags = {} } = this.preferencesController.store.getState()
|
||||
const { showIncomingTransactions } = featureFlags
|
||||
const { featureFlags = {} } = this.preferencesController.store.getState();
|
||||
const { showIncomingTransactions } = featureFlags;
|
||||
|
||||
if (!showIncomingTransactions) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
this.blockTracker.removeListener('latest', this._onLatestBlock)
|
||||
this.blockTracker.addListener('latest', this._onLatestBlock)
|
||||
this.blockTracker.removeListener('latest', this._onLatestBlock);
|
||||
this.blockTracker.addListener('latest', this._onLatestBlock);
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.blockTracker.removeListener('latest', this._onLatestBlock)
|
||||
this.blockTracker.removeListener('latest', this._onLatestBlock);
|
||||
}
|
||||
|
||||
async _update({ address, newBlockNumberDec } = {}) {
|
||||
const chainId = this.networkController.getCurrentChainId()
|
||||
const chainId = this.networkController.getCurrentChainId();
|
||||
if (!etherscanSupportedNetworks.includes(chainId)) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const dataForUpdate = await this._getDataForUpdate({
|
||||
address,
|
||||
chainId,
|
||||
newBlockNumberDec,
|
||||
})
|
||||
this._updateStateWithNewTxData(dataForUpdate)
|
||||
});
|
||||
this._updateStateWithNewTxData(dataForUpdate);
|
||||
} catch (err) {
|
||||
log.error(err)
|
||||
log.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,20 +156,20 @@ export default class IncomingTransactionsController {
|
||||
const {
|
||||
incomingTransactions: currentIncomingTxs,
|
||||
incomingTxLastFetchedBlocksByNetwork: currentBlocksByNetwork,
|
||||
} = this.store.getState()
|
||||
} = this.store.getState();
|
||||
|
||||
const lastFetchBlockByCurrentNetwork =
|
||||
currentBlocksByNetwork[CHAIN_ID_TO_TYPE_MAP[chainId]]
|
||||
let blockToFetchFrom = lastFetchBlockByCurrentNetwork || newBlockNumberDec
|
||||
currentBlocksByNetwork[CHAIN_ID_TO_TYPE_MAP[chainId]];
|
||||
let blockToFetchFrom = lastFetchBlockByCurrentNetwork || newBlockNumberDec;
|
||||
if (blockToFetchFrom === undefined) {
|
||||
blockToFetchFrom = parseInt(this.blockTracker.getCurrentBlock(), 16)
|
||||
blockToFetchFrom = parseInt(this.blockTracker.getCurrentBlock(), 16);
|
||||
}
|
||||
|
||||
const { latestIncomingTxBlockNumber, txs: newTxs } = await this._fetchAll(
|
||||
address,
|
||||
blockToFetchFrom,
|
||||
chainId,
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
latestIncomingTxBlockNumber,
|
||||
@ -178,7 +178,7 @@ export default class IncomingTransactionsController {
|
||||
currentBlocksByNetwork,
|
||||
fetchedBlockNumber: blockToFetchFrom,
|
||||
chainId,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_updateStateWithNewTxData({
|
||||
@ -191,13 +191,13 @@ export default class IncomingTransactionsController {
|
||||
}) {
|
||||
const newLatestBlockHashByNetwork = latestIncomingTxBlockNumber
|
||||
? parseInt(latestIncomingTxBlockNumber, 10) + 1
|
||||
: fetchedBlockNumber + 1
|
||||
: fetchedBlockNumber + 1;
|
||||
const newIncomingTransactions = {
|
||||
...currentIncomingTxs,
|
||||
}
|
||||
};
|
||||
newTxs.forEach((tx) => {
|
||||
newIncomingTransactions[tx.hash] = tx
|
||||
})
|
||||
newIncomingTransactions[tx.hash] = tx;
|
||||
});
|
||||
|
||||
this.store.updateState({
|
||||
incomingTxLastFetchedBlocksByNetwork: {
|
||||
@ -205,53 +205,53 @@ export default class IncomingTransactionsController {
|
||||
[CHAIN_ID_TO_TYPE_MAP[chainId]]: newLatestBlockHashByNetwork,
|
||||
},
|
||||
incomingTransactions: newIncomingTransactions,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async _fetchAll(address, fromBlock, chainId) {
|
||||
const fetchedTxResponse = await this._fetchTxs(address, fromBlock, chainId)
|
||||
return this._processTxFetchResponse(fetchedTxResponse)
|
||||
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]}`
|
||||
: `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`
|
||||
const apiUrl = `https://${etherscanSubdomain}.etherscan.io`;
|
||||
let url = `${apiUrl}/api?module=account&action=txlist&address=${address}&tag=latest&page=1`;
|
||||
|
||||
if (fromBlock) {
|
||||
url += `&startBlock=${parseInt(fromBlock, 10)}`
|
||||
url += `&startBlock=${parseInt(fromBlock, 10)}`;
|
||||
}
|
||||
const response = await fetchWithTimeout(url)
|
||||
const parsedResponse = await response.json()
|
||||
const response = await fetchWithTimeout(url);
|
||||
const parsedResponse = await response.json();
|
||||
|
||||
return {
|
||||
...parsedResponse,
|
||||
address,
|
||||
chainId,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_processTxFetchResponse({ status, result = [], address, chainId }) {
|
||||
if (status === '1' && Array.isArray(result) && result.length > 0) {
|
||||
const remoteTxList = {}
|
||||
const remoteTxs = []
|
||||
const remoteTxList = {};
|
||||
const remoteTxs = [];
|
||||
result.forEach((tx) => {
|
||||
if (!remoteTxList[tx.hash]) {
|
||||
remoteTxs.push(this._normalizeTxFromEtherscan(tx, chainId))
|
||||
remoteTxList[tx.hash] = 1
|
||||
remoteTxs.push(this._normalizeTxFromEtherscan(tx, chainId));
|
||||
remoteTxList[tx.hash] = 1;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const incomingTxs = remoteTxs.filter(
|
||||
(tx) => tx.txParams?.to?.toLowerCase() === address.toLowerCase(),
|
||||
)
|
||||
incomingTxs.sort((a, b) => (a.time < b.time ? -1 : 1))
|
||||
);
|
||||
incomingTxs.sort((a, b) => (a.time < b.time ? -1 : 1));
|
||||
|
||||
let latestIncomingTxBlockNumber = null
|
||||
let latestIncomingTxBlockNumber = null;
|
||||
incomingTxs.forEach((tx) => {
|
||||
if (
|
||||
tx.blockNumber &&
|
||||
@ -259,26 +259,26 @@ export default class IncomingTransactionsController {
|
||||
parseInt(latestIncomingTxBlockNumber, 10) <
|
||||
parseInt(tx.blockNumber, 10))
|
||||
) {
|
||||
latestIncomingTxBlockNumber = tx.blockNumber
|
||||
latestIncomingTxBlockNumber = tx.blockNumber;
|
||||
}
|
||||
})
|
||||
});
|
||||
return {
|
||||
latestIncomingTxBlockNumber,
|
||||
txs: incomingTxs,
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
latestIncomingTxBlockNumber: null,
|
||||
txs: [],
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_normalizeTxFromEtherscan(txMeta, chainId) {
|
||||
const time = parseInt(txMeta.timeStamp, 10) * 1000
|
||||
const time = parseInt(txMeta.timeStamp, 10) * 1000;
|
||||
const status =
|
||||
txMeta.isError === '0'
|
||||
? TRANSACTION_STATUSES.CONFIRMED
|
||||
: TRANSACTION_STATUSES.FAILED
|
||||
: TRANSACTION_STATUSES.FAILED;
|
||||
return {
|
||||
blockNumber: txMeta.blockNumber,
|
||||
id: createId(),
|
||||
@ -295,22 +295,22 @@ export default class IncomingTransactionsController {
|
||||
},
|
||||
hash: txMeta.hash,
|
||||
transactionCategory: TRANSACTION_CATEGORIES.INCOMING,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function pairwise(fn) {
|
||||
let first = true
|
||||
let cache
|
||||
let first = true;
|
||||
let cache;
|
||||
return (value) => {
|
||||
try {
|
||||
if (first) {
|
||||
first = false
|
||||
return fn(value, value)
|
||||
first = false;
|
||||
return fn(value, value);
|
||||
}
|
||||
return fn(cache, value)
|
||||
return fn(cache, value);
|
||||
} finally {
|
||||
cache = value
|
||||
cache = value;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { merge, omit } from 'lodash'
|
||||
import { ObservableStore } from '@metamask/obs-store'
|
||||
import { bufferToHex, sha3 } from 'ethereumjs-util'
|
||||
import { ENVIRONMENT_TYPE_BACKGROUND } from '../../../shared/constants/app'
|
||||
import { merge, omit } from 'lodash';
|
||||
import { ObservableStore } from '@metamask/obs-store';
|
||||
import { bufferToHex, sha3 } from 'ethereumjs-util';
|
||||
import { ENVIRONMENT_TYPE_BACKGROUND } from '../../../shared/constants/app';
|
||||
import {
|
||||
METAMETRICS_ANONYMOUS_ID,
|
||||
METAMETRICS_BACKGROUND_PAGE_OBJECT,
|
||||
} from '../../../shared/constants/metametrics'
|
||||
} from '../../../shared/constants/metametrics';
|
||||
|
||||
/**
|
||||
* Used to determine whether or not to attach a user's metametrics id
|
||||
@ -25,10 +25,10 @@ const trackableSendCounts = {
|
||||
5000: true,
|
||||
10000: true,
|
||||
25000: true,
|
||||
}
|
||||
};
|
||||
|
||||
export function sendCountIsTrackable(sendCount) {
|
||||
return Boolean(trackableSendCounts[sendCount])
|
||||
return Boolean(trackableSendCounts[sendCount]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,30 +82,30 @@ export default class MetaMetricsController {
|
||||
environment,
|
||||
initState,
|
||||
}) {
|
||||
const prefState = preferencesStore.getState()
|
||||
this.chainId = getCurrentChainId()
|
||||
this.network = getNetworkIdentifier()
|
||||
this.locale = prefState.currentLocale.replace('_', '-')
|
||||
const prefState = preferencesStore.getState();
|
||||
this.chainId = getCurrentChainId();
|
||||
this.network = getNetworkIdentifier();
|
||||
this.locale = prefState.currentLocale.replace('_', '-');
|
||||
this.version =
|
||||
environment === 'production' ? version : `${version}-${environment}`
|
||||
environment === 'production' ? version : `${version}-${environment}`;
|
||||
|
||||
this.store = new ObservableStore({
|
||||
participateInMetaMetrics: null,
|
||||
metaMetricsId: null,
|
||||
metaMetricsSendCount: 0,
|
||||
...initState,
|
||||
})
|
||||
});
|
||||
|
||||
preferencesStore.subscribe(({ currentLocale }) => {
|
||||
this.locale = currentLocale.replace('_', '-')
|
||||
})
|
||||
this.locale = currentLocale.replace('_', '-');
|
||||
});
|
||||
|
||||
onNetworkDidChange(() => {
|
||||
this.chainId = getCurrentChainId()
|
||||
this.network = getNetworkIdentifier()
|
||||
})
|
||||
this.segment = segment
|
||||
this.segmentLegacy = segmentLegacy
|
||||
this.chainId = getCurrentChainId();
|
||||
this.network = getNetworkIdentifier();
|
||||
});
|
||||
this.segment = segment;
|
||||
this.segmentLegacy = segmentLegacy;
|
||||
}
|
||||
|
||||
generateMetaMetricsId() {
|
||||
@ -114,7 +114,7 @@ export default class MetaMetricsController {
|
||||
String(Date.now()) +
|
||||
String(Math.round(Math.random() * Number.MAX_SAFE_INTEGER)),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,22 +126,22 @@ export default class MetaMetricsController {
|
||||
* if not set
|
||||
*/
|
||||
setParticipateInMetaMetrics(participateInMetaMetrics) {
|
||||
let { metaMetricsId } = this.state
|
||||
let { metaMetricsId } = this.state;
|
||||
if (participateInMetaMetrics && !metaMetricsId) {
|
||||
metaMetricsId = this.generateMetaMetricsId()
|
||||
metaMetricsId = this.generateMetaMetricsId();
|
||||
} else if (participateInMetaMetrics === false) {
|
||||
metaMetricsId = null
|
||||
metaMetricsId = null;
|
||||
}
|
||||
this.store.updateState({ participateInMetaMetrics, metaMetricsId })
|
||||
return metaMetricsId
|
||||
this.store.updateState({ participateInMetaMetrics, metaMetricsId });
|
||||
return metaMetricsId;
|
||||
}
|
||||
|
||||
get state() {
|
||||
return this.store.getState()
|
||||
return this.store.getState();
|
||||
}
|
||||
|
||||
setMetaMetricsSendCount(val) {
|
||||
this.store.updateState({ metaMetricsSendCount: val })
|
||||
this.store.updateState({ metaMetricsSendCount: val });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -162,7 +162,7 @@ export default class MetaMetricsController {
|
||||
userAgent: window.navigator.userAgent,
|
||||
page,
|
||||
referrer,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -185,7 +185,7 @@ export default class MetaMetricsController {
|
||||
page,
|
||||
referrer,
|
||||
environmentType = ENVIRONMENT_TYPE_BACKGROUND,
|
||||
} = rawPayload
|
||||
} = rawPayload;
|
||||
return {
|
||||
event,
|
||||
properties: {
|
||||
@ -206,7 +206,7 @@ export default class MetaMetricsController {
|
||||
environment_type: environmentType,
|
||||
},
|
||||
context: this._buildContext(referrer, page),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -225,20 +225,20 @@ export default class MetaMetricsController {
|
||||
metaMetricsId: metaMetricsIdOverride,
|
||||
matomoEvent,
|
||||
flushImmediately,
|
||||
} = options || {}
|
||||
let idType = 'userId'
|
||||
let idValue = this.state.metaMetricsId
|
||||
let excludeMetaMetricsId = options?.excludeMetaMetricsId ?? false
|
||||
} = options || {};
|
||||
let idType = 'userId';
|
||||
let idValue = this.state.metaMetricsId;
|
||||
let excludeMetaMetricsId = options?.excludeMetaMetricsId ?? false;
|
||||
// This is carried over from the old implementation, and will likely need
|
||||
// to be updated to work with the new tracking plan. I think we should use
|
||||
// a config setting for this instead of trying to match the event name
|
||||
const isSendFlow = Boolean(payload.event.match(/^send|^confirm/iu))
|
||||
const isSendFlow = Boolean(payload.event.match(/^send|^confirm/iu));
|
||||
if (
|
||||
isSendFlow &&
|
||||
this.state.metaMetricsSendCount &&
|
||||
!sendCountIsTrackable(this.state.metaMetricsSendCount + 1)
|
||||
) {
|
||||
excludeMetaMetricsId = true
|
||||
excludeMetaMetricsId = true;
|
||||
}
|
||||
// If we are tracking sensitive data we will always use the anonymousId
|
||||
// property as well as our METAMETRICS_ANONYMOUS_ID. This prevents us from
|
||||
@ -251,12 +251,12 @@ export default class MetaMetricsController {
|
||||
// case we will track the opt in event to the user's id. In all other cases
|
||||
// we use the metaMetricsId from state.
|
||||
if (excludeMetaMetricsId || (isOptIn && !metaMetricsIdOverride)) {
|
||||
idType = 'anonymousId'
|
||||
idValue = METAMETRICS_ANONYMOUS_ID
|
||||
idType = 'anonymousId';
|
||||
idValue = METAMETRICS_ANONYMOUS_ID;
|
||||
} else if (isOptIn && metaMetricsIdOverride) {
|
||||
idValue = metaMetricsIdOverride
|
||||
idValue = metaMetricsIdOverride;
|
||||
}
|
||||
payload[idType] = idValue
|
||||
payload[idType] = idValue;
|
||||
|
||||
// Promises will only resolve when the event is sent to segment. For any
|
||||
// event that relies on this promise being fulfilled before performing UI
|
||||
@ -269,20 +269,20 @@ export default class MetaMetricsController {
|
||||
// that seemingly breaks with lockdown enabled. Creating a new error
|
||||
// here prevents the system from freezing when the network request to
|
||||
// segment fails for any reason.
|
||||
const safeError = new Error(err.message)
|
||||
safeError.stack = err.stack
|
||||
return reject(safeError)
|
||||
const safeError = new Error(err.message);
|
||||
safeError.stack = err.stack;
|
||||
return reject(safeError);
|
||||
}
|
||||
return resolve()
|
||||
}
|
||||
return resolve();
|
||||
};
|
||||
|
||||
const target = matomoEvent === true ? this.segmentLegacy : this.segment
|
||||
const target = matomoEvent === true ? this.segmentLegacy : this.segment;
|
||||
|
||||
target.track(payload, callback)
|
||||
target.track(payload, callback);
|
||||
if (flushImmediately) {
|
||||
target.flush()
|
||||
target.flush();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -293,15 +293,15 @@ export default class MetaMetricsController {
|
||||
*/
|
||||
trackPage({ name, params, environmentType, page, referrer }, options) {
|
||||
if (this.state.participateInMetaMetrics === false) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.participateInMetaMetrics === null && !options?.isOptInPath) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const { metaMetricsId } = this.state
|
||||
const idTrait = metaMetricsId ? 'userId' : 'anonymousId'
|
||||
const idValue = metaMetricsId ?? METAMETRICS_ANONYMOUS_ID
|
||||
const { metaMetricsId } = this.state;
|
||||
const idTrait = metaMetricsId ? 'userId' : 'anonymousId';
|
||||
const idValue = metaMetricsId ?? METAMETRICS_ANONYMOUS_ID;
|
||||
this.segment.page({
|
||||
[idTrait]: idValue,
|
||||
name,
|
||||
@ -313,7 +313,7 @@ export default class MetaMetricsController {
|
||||
environment_type: environmentType,
|
||||
},
|
||||
context: this._buildContext(referrer, page),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -328,16 +328,16 @@ export default class MetaMetricsController {
|
||||
async trackEvent(payload, options) {
|
||||
// event and category are required fields for all payloads
|
||||
if (!payload.event || !payload.category) {
|
||||
throw new Error('Must specify event and category.')
|
||||
throw new Error('Must specify event and category.');
|
||||
}
|
||||
|
||||
if (!this.state.participateInMetaMetrics && !options?.isOptIn) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// We might track multiple events if sensitiveProperties is included, this array will hold
|
||||
// the promises returned from this._track.
|
||||
const events = []
|
||||
const events = [];
|
||||
|
||||
if (payload.sensitiveProperties) {
|
||||
// sensitiveProperties will only be tracked using the anonymousId property and generic id
|
||||
@ -346,13 +346,13 @@ export default class MetaMetricsController {
|
||||
if (options?.excludeMetaMetricsId === true) {
|
||||
throw new Error(
|
||||
'sensitiveProperties was specified in an event payload that also set the excludeMetaMetricsId flag',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const combinedProperties = merge(
|
||||
payload.sensitiveProperties,
|
||||
payload.properties,
|
||||
)
|
||||
);
|
||||
|
||||
events.push(
|
||||
this._track(
|
||||
@ -362,11 +362,11 @@ export default class MetaMetricsController {
|
||||
}),
|
||||
{ ...options, excludeMetaMetricsId: true },
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
events.push(this._track(this._buildEventPayload(payload), options))
|
||||
events.push(this._track(this._buildEventPayload(payload), options));
|
||||
|
||||
await Promise.all(events)
|
||||
await Promise.all(events);
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { createScaffoldMiddleware, mergeMiddleware } from 'json-rpc-engine'
|
||||
import createBlockReRefMiddleware from 'eth-json-rpc-middleware/block-ref'
|
||||
import createRetryOnEmptyMiddleware from 'eth-json-rpc-middleware/retryOnEmpty'
|
||||
import createBlockCacheMiddleware from 'eth-json-rpc-middleware/block-cache'
|
||||
import createInflightMiddleware from 'eth-json-rpc-middleware/inflight-cache'
|
||||
import createBlockTrackerInspectorMiddleware from 'eth-json-rpc-middleware/block-tracker-inspector'
|
||||
import providerFromMiddleware from 'eth-json-rpc-middleware/providerFromMiddleware'
|
||||
import createInfuraMiddleware from 'eth-json-rpc-infura'
|
||||
import BlockTracker from 'eth-block-tracker'
|
||||
import { createScaffoldMiddleware, mergeMiddleware } from 'json-rpc-engine';
|
||||
import createBlockReRefMiddleware from 'eth-json-rpc-middleware/block-ref';
|
||||
import createRetryOnEmptyMiddleware from 'eth-json-rpc-middleware/retryOnEmpty';
|
||||
import createBlockCacheMiddleware from 'eth-json-rpc-middleware/block-cache';
|
||||
import createInflightMiddleware from 'eth-json-rpc-middleware/inflight-cache';
|
||||
import createBlockTrackerInspectorMiddleware from 'eth-json-rpc-middleware/block-tracker-inspector';
|
||||
import providerFromMiddleware from 'eth-json-rpc-middleware/providerFromMiddleware';
|
||||
import createInfuraMiddleware from 'eth-json-rpc-infura';
|
||||
import BlockTracker from 'eth-block-tracker';
|
||||
|
||||
import { NETWORK_TYPE_TO_ID_MAP } from '../../../../shared/constants/network'
|
||||
import { NETWORK_TYPE_TO_ID_MAP } from '../../../../shared/constants/network';
|
||||
|
||||
export default function createInfuraClient({ network, projectId }) {
|
||||
const infuraMiddleware = createInfuraMiddleware({
|
||||
@ -16,9 +16,9 @@ export default function createInfuraClient({ network, projectId }) {
|
||||
projectId,
|
||||
maxAttempts: 5,
|
||||
source: 'metamask',
|
||||
})
|
||||
const infuraProvider = providerFromMiddleware(infuraMiddleware)
|
||||
const blockTracker = new BlockTracker({ provider: infuraProvider })
|
||||
});
|
||||
const infuraProvider = providerFromMiddleware(infuraMiddleware);
|
||||
const blockTracker = new BlockTracker({ provider: infuraProvider });
|
||||
|
||||
const networkMiddleware = mergeMiddleware([
|
||||
createNetworkAndChainIdMiddleware({ network }),
|
||||
@ -28,19 +28,19 @@ export default function createInfuraClient({ network, projectId }) {
|
||||
createRetryOnEmptyMiddleware({ blockTracker, provider: infuraProvider }),
|
||||
createBlockTrackerInspectorMiddleware({ blockTracker }),
|
||||
infuraMiddleware,
|
||||
])
|
||||
return { networkMiddleware, blockTracker }
|
||||
]);
|
||||
return { networkMiddleware, blockTracker };
|
||||
}
|
||||
|
||||
function createNetworkAndChainIdMiddleware({ network }) {
|
||||
if (!NETWORK_TYPE_TO_ID_MAP[network]) {
|
||||
throw new Error(`createInfuraClient - unknown network "${network}"`)
|
||||
throw new Error(`createInfuraClient - unknown network "${network}"`);
|
||||
}
|
||||
|
||||
const { chainId, networkId } = NETWORK_TYPE_TO_ID_MAP[network]
|
||||
const { chainId, networkId } = NETWORK_TYPE_TO_ID_MAP[network];
|
||||
|
||||
return createScaffoldMiddleware({
|
||||
eth_chainId: chainId,
|
||||
net_version: networkId,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
@ -1,25 +1,25 @@
|
||||
import { createAsyncMiddleware, mergeMiddleware } from 'json-rpc-engine'
|
||||
import createFetchMiddleware from 'eth-json-rpc-middleware/fetch'
|
||||
import createBlockRefRewriteMiddleware from 'eth-json-rpc-middleware/block-ref-rewrite'
|
||||
import createBlockCacheMiddleware from 'eth-json-rpc-middleware/block-cache'
|
||||
import createInflightMiddleware from 'eth-json-rpc-middleware/inflight-cache'
|
||||
import createBlockTrackerInspectorMiddleware from 'eth-json-rpc-middleware/block-tracker-inspector'
|
||||
import providerFromMiddleware from 'eth-json-rpc-middleware/providerFromMiddleware'
|
||||
import BlockTracker from 'eth-block-tracker'
|
||||
import { createAsyncMiddleware, mergeMiddleware } from 'json-rpc-engine';
|
||||
import createFetchMiddleware from 'eth-json-rpc-middleware/fetch';
|
||||
import createBlockRefRewriteMiddleware from 'eth-json-rpc-middleware/block-ref-rewrite';
|
||||
import createBlockCacheMiddleware from 'eth-json-rpc-middleware/block-cache';
|
||||
import createInflightMiddleware from 'eth-json-rpc-middleware/inflight-cache';
|
||||
import createBlockTrackerInspectorMiddleware from 'eth-json-rpc-middleware/block-tracker-inspector';
|
||||
import providerFromMiddleware from 'eth-json-rpc-middleware/providerFromMiddleware';
|
||||
import BlockTracker from 'eth-block-tracker';
|
||||
|
||||
const inTest = process.env.IN_TEST === 'true'
|
||||
const blockTrackerOpts = inTest ? { pollingInterval: 1000 } : {}
|
||||
const inTest = process.env.IN_TEST === 'true';
|
||||
const blockTrackerOpts = inTest ? { pollingInterval: 1000 } : {};
|
||||
const getTestMiddlewares = () => {
|
||||
return inTest ? [createEstimateGasDelayTestMiddleware()] : []
|
||||
}
|
||||
return inTest ? [createEstimateGasDelayTestMiddleware()] : [];
|
||||
};
|
||||
|
||||
export default function createJsonRpcClient({ rpcUrl, chainId }) {
|
||||
const fetchMiddleware = createFetchMiddleware({ rpcUrl })
|
||||
const blockProvider = providerFromMiddleware(fetchMiddleware)
|
||||
const fetchMiddleware = createFetchMiddleware({ rpcUrl });
|
||||
const blockProvider = providerFromMiddleware(fetchMiddleware);
|
||||
const blockTracker = new BlockTracker({
|
||||
...blockTrackerOpts,
|
||||
provider: blockProvider,
|
||||
})
|
||||
});
|
||||
|
||||
const networkMiddleware = mergeMiddleware([
|
||||
...getTestMiddlewares(),
|
||||
@ -29,19 +29,19 @@ export default function createJsonRpcClient({ rpcUrl, chainId }) {
|
||||
createInflightMiddleware(),
|
||||
createBlockTrackerInspectorMiddleware({ blockTracker }),
|
||||
fetchMiddleware,
|
||||
])
|
||||
]);
|
||||
|
||||
return { networkMiddleware, blockTracker }
|
||||
return { networkMiddleware, blockTracker };
|
||||
}
|
||||
|
||||
function createChainIdMiddleware(chainId) {
|
||||
return (req, res, next, end) => {
|
||||
if (req.method === 'eth_chainId') {
|
||||
res.result = chainId
|
||||
return end()
|
||||
res.result = chainId;
|
||||
return end();
|
||||
}
|
||||
return next()
|
||||
}
|
||||
return next();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -51,8 +51,8 @@ function createChainIdMiddleware(chainId) {
|
||||
function createEstimateGasDelayTestMiddleware() {
|
||||
return createAsyncMiddleware(async (req, _, next) => {
|
||||
if (req.method === 'eth_estimateGas') {
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
}
|
||||
return next()
|
||||
})
|
||||
return next();
|
||||
});
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { createScaffoldMiddleware, mergeMiddleware } from 'json-rpc-engine'
|
||||
import createWalletSubprovider from 'eth-json-rpc-middleware/wallet'
|
||||
import { createScaffoldMiddleware, mergeMiddleware } from 'json-rpc-engine';
|
||||
import createWalletSubprovider from 'eth-json-rpc-middleware/wallet';
|
||||
import {
|
||||
createPendingNonceMiddleware,
|
||||
createPendingTxMiddleware,
|
||||
} from './middleware/pending'
|
||||
} from './middleware/pending';
|
||||
|
||||
export default function createMetamaskMiddleware({
|
||||
version,
|
||||
@ -38,6 +38,6 @@ export default function createMetamaskMiddleware({
|
||||
}),
|
||||
createPendingNonceMiddleware({ getPendingNonce }),
|
||||
createPendingTxMiddleware({ getPendingTransactionByHash }),
|
||||
])
|
||||
return metamaskMiddleware
|
||||
]);
|
||||
return metamaskMiddleware;
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
export { default } from './network'
|
||||
export { default } from './network';
|
||||
|
@ -1,35 +1,35 @@
|
||||
import { createAsyncMiddleware } from 'json-rpc-engine'
|
||||
import { formatTxMetaForRpcResult } from '../util'
|
||||
import { createAsyncMiddleware } from 'json-rpc-engine';
|
||||
import { formatTxMetaForRpcResult } from '../util';
|
||||
|
||||
export function createPendingNonceMiddleware({ getPendingNonce }) {
|
||||
return createAsyncMiddleware(async (req, res, next) => {
|
||||
const { method, params } = req
|
||||
const { method, params } = req;
|
||||
if (method !== 'eth_getTransactionCount') {
|
||||
next()
|
||||
return
|
||||
next();
|
||||
return;
|
||||
}
|
||||
const [param, blockRef] = params
|
||||
const [param, blockRef] = params;
|
||||
if (blockRef !== 'pending') {
|
||||
next()
|
||||
return
|
||||
next();
|
||||
return;
|
||||
}
|
||||
res.result = await getPendingNonce(param)
|
||||
})
|
||||
res.result = await getPendingNonce(param);
|
||||
});
|
||||
}
|
||||
|
||||
export function createPendingTxMiddleware({ getPendingTransactionByHash }) {
|
||||
return createAsyncMiddleware(async (req, res, next) => {
|
||||
const { method, params } = req
|
||||
const { method, params } = req;
|
||||
if (method !== 'eth_getTransactionByHash') {
|
||||
next()
|
||||
return
|
||||
next();
|
||||
return;
|
||||
}
|
||||
const [hash] = params
|
||||
const txMeta = getPendingTransactionByHash(hash)
|
||||
const [hash] = params;
|
||||
const txMeta = getPendingTransactionByHash(hash);
|
||||
if (!txMeta) {
|
||||
next()
|
||||
return
|
||||
next();
|
||||
return;
|
||||
}
|
||||
res.result = formatTxMetaForRpcResult(txMeta)
|
||||
})
|
||||
res.result = formatTxMetaForRpcResult(txMeta);
|
||||
});
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
import assert from 'assert'
|
||||
import EventEmitter from 'events'
|
||||
import { ComposedStore, ObservableStore } from '@metamask/obs-store'
|
||||
import { JsonRpcEngine } from 'json-rpc-engine'
|
||||
import providerFromEngine from 'eth-json-rpc-middleware/providerFromEngine'
|
||||
import log from 'loglevel'
|
||||
import assert from 'assert';
|
||||
import EventEmitter from 'events';
|
||||
import { ComposedStore, ObservableStore } from '@metamask/obs-store';
|
||||
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 EthQuery from 'eth-query'
|
||||
} from 'swappable-obj-proxy';
|
||||
import EthQuery from 'eth-query';
|
||||
import {
|
||||
RINKEBY,
|
||||
MAINNET,
|
||||
@ -17,63 +17,63 @@ import {
|
||||
NETWORK_TYPE_TO_ID_MAP,
|
||||
MAINNET_CHAIN_ID,
|
||||
RINKEBY_CHAIN_ID,
|
||||
} from '../../../../shared/constants/network'
|
||||
} from '../../../../shared/constants/network';
|
||||
import {
|
||||
isPrefixedFormattedHexString,
|
||||
isSafeChainId,
|
||||
} from '../../../../shared/modules/utils'
|
||||
import createMetamaskMiddleware from './createMetamaskMiddleware'
|
||||
import createInfuraClient from './createInfuraClient'
|
||||
import createJsonRpcClient from './createJsonRpcClient'
|
||||
} from '../../../../shared/modules/utils';
|
||||
import createMetamaskMiddleware from './createMetamaskMiddleware';
|
||||
import createInfuraClient from './createInfuraClient';
|
||||
import createJsonRpcClient from './createJsonRpcClient';
|
||||
|
||||
const env = process.env.METAMASK_ENV
|
||||
const env = process.env.METAMASK_ENV;
|
||||
|
||||
let defaultProviderConfigOpts
|
||||
let defaultProviderConfigOpts;
|
||||
if (process.env.IN_TEST === 'true') {
|
||||
defaultProviderConfigOpts = {
|
||||
type: NETWORK_TYPE_RPC,
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
chainId: '0x539',
|
||||
nickname: 'Localhost 8545',
|
||||
}
|
||||
};
|
||||
} else if (process.env.METAMASK_DEBUG || env === 'test') {
|
||||
defaultProviderConfigOpts = { type: RINKEBY, chainId: RINKEBY_CHAIN_ID }
|
||||
defaultProviderConfigOpts = { type: RINKEBY, chainId: RINKEBY_CHAIN_ID };
|
||||
} else {
|
||||
defaultProviderConfigOpts = { type: MAINNET, chainId: MAINNET_CHAIN_ID }
|
||||
defaultProviderConfigOpts = { type: MAINNET, chainId: MAINNET_CHAIN_ID };
|
||||
}
|
||||
|
||||
const defaultProviderConfig = {
|
||||
ticker: 'ETH',
|
||||
...defaultProviderConfigOpts,
|
||||
}
|
||||
};
|
||||
|
||||
export default class NetworkController extends EventEmitter {
|
||||
constructor(opts = {}) {
|
||||
super()
|
||||
super();
|
||||
|
||||
// create stores
|
||||
this.providerStore = new ObservableStore(
|
||||
opts.provider || { ...defaultProviderConfig },
|
||||
)
|
||||
);
|
||||
this.previousProviderStore = new ObservableStore(
|
||||
this.providerStore.getState(),
|
||||
)
|
||||
this.networkStore = new ObservableStore('loading')
|
||||
);
|
||||
this.networkStore = new ObservableStore('loading');
|
||||
this.store = new ComposedStore({
|
||||
provider: this.providerStore,
|
||||
previousProviderStore: this.previousProviderStore,
|
||||
network: this.networkStore,
|
||||
})
|
||||
});
|
||||
|
||||
// provider and block tracker
|
||||
this._provider = null
|
||||
this._blockTracker = null
|
||||
this._provider = null;
|
||||
this._blockTracker = null;
|
||||
|
||||
// provider and block tracker proxies - because the network changes
|
||||
this._providerProxy = null
|
||||
this._blockTrackerProxy = null
|
||||
this._providerProxy = null;
|
||||
this._blockTrackerProxy = null;
|
||||
|
||||
this.on('networkDidChange', this.lookupNetwork)
|
||||
this.on('networkDidChange', this.lookupNetwork);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -85,43 +85,43 @@ export default class NetworkController extends EventEmitter {
|
||||
*/
|
||||
setInfuraProjectId(projectId) {
|
||||
if (!projectId || typeof projectId !== 'string') {
|
||||
throw new Error('Invalid Infura project ID')
|
||||
throw new Error('Invalid Infura project ID');
|
||||
}
|
||||
|
||||
this._infuraProjectId = projectId
|
||||
this._infuraProjectId = projectId;
|
||||
}
|
||||
|
||||
initializeProvider(providerParams) {
|
||||
this._baseProviderParams = providerParams
|
||||
const { type, rpcUrl, chainId } = this.getProviderConfig()
|
||||
this._configureProvider({ type, rpcUrl, chainId })
|
||||
this.lookupNetwork()
|
||||
this._baseProviderParams = providerParams;
|
||||
const { type, rpcUrl, chainId } = this.getProviderConfig();
|
||||
this._configureProvider({ type, rpcUrl, chainId });
|
||||
this.lookupNetwork();
|
||||
}
|
||||
|
||||
// return the proxies so the references will always be good
|
||||
getProviderAndBlockTracker() {
|
||||
const provider = this._providerProxy
|
||||
const blockTracker = this._blockTrackerProxy
|
||||
return { provider, blockTracker }
|
||||
const provider = this._providerProxy;
|
||||
const blockTracker = this._blockTrackerProxy;
|
||||
return { provider, blockTracker };
|
||||
}
|
||||
|
||||
verifyNetwork() {
|
||||
// Check network when restoring connectivity:
|
||||
if (this.isNetworkLoading()) {
|
||||
this.lookupNetwork()
|
||||
this.lookupNetwork();
|
||||
}
|
||||
}
|
||||
|
||||
getNetworkState() {
|
||||
return this.networkStore.getState()
|
||||
return this.networkStore.getState();
|
||||
}
|
||||
|
||||
setNetworkState(network) {
|
||||
this.networkStore.putState(network)
|
||||
this.networkStore.putState(network);
|
||||
}
|
||||
|
||||
isNetworkLoading() {
|
||||
return this.getNetworkState() === 'loading'
|
||||
return this.getNetworkState() === 'loading';
|
||||
}
|
||||
|
||||
lookupNetwork() {
|
||||
@ -129,49 +129,49 @@ export default class NetworkController extends EventEmitter {
|
||||
if (!this._provider) {
|
||||
log.warn(
|
||||
'NetworkController - lookupNetwork aborted due to missing provider',
|
||||
)
|
||||
return
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const chainId = this.getCurrentChainId()
|
||||
const chainId = this.getCurrentChainId();
|
||||
if (!chainId) {
|
||||
log.warn(
|
||||
'NetworkController - lookupNetwork aborted due to missing chainId',
|
||||
)
|
||||
this.setNetworkState('loading')
|
||||
return
|
||||
);
|
||||
this.setNetworkState('loading');
|
||||
return;
|
||||
}
|
||||
|
||||
// Ping the RPC endpoint so we can confirm that it works
|
||||
const ethQuery = new EthQuery(this._provider)
|
||||
const initialNetwork = this.getNetworkState()
|
||||
const ethQuery = new EthQuery(this._provider);
|
||||
const initialNetwork = this.getNetworkState();
|
||||
ethQuery.sendAsync({ method: 'net_version' }, (err, networkVersion) => {
|
||||
const currentNetwork = this.getNetworkState()
|
||||
const currentNetwork = this.getNetworkState();
|
||||
if (initialNetwork === currentNetwork) {
|
||||
if (err) {
|
||||
this.setNetworkState('loading')
|
||||
return
|
||||
this.setNetworkState('loading');
|
||||
return;
|
||||
}
|
||||
|
||||
this.setNetworkState(networkVersion)
|
||||
this.setNetworkState(networkVersion);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
getCurrentChainId() {
|
||||
const { type, chainId: configChainId } = this.getProviderConfig()
|
||||
return NETWORK_TYPE_TO_ID_MAP[type]?.chainId || configChainId
|
||||
const { type, chainId: configChainId } = this.getProviderConfig();
|
||||
return NETWORK_TYPE_TO_ID_MAP[type]?.chainId || configChainId;
|
||||
}
|
||||
|
||||
setRpcTarget(rpcUrl, chainId, ticker = 'ETH', nickname = '', rpcPrefs) {
|
||||
assert.ok(
|
||||
isPrefixedFormattedHexString(chainId),
|
||||
`Invalid chain ID "${chainId}": invalid hex string.`,
|
||||
)
|
||||
);
|
||||
assert.ok(
|
||||
isSafeChainId(parseInt(chainId, 16)),
|
||||
`Invalid chain ID "${chainId}": numerical value greater than max safe value.`,
|
||||
)
|
||||
);
|
||||
this.setProviderConfig({
|
||||
type: NETWORK_TYPE_RPC,
|
||||
rpcUrl,
|
||||
@ -179,7 +179,7 @@ export default class NetworkController extends EventEmitter {
|
||||
ticker,
|
||||
nickname,
|
||||
rpcPrefs,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async setProviderType(type, rpcUrl = '', ticker = 'ETH', nickname = '') {
|
||||
@ -187,41 +187,41 @@ export default class NetworkController extends EventEmitter {
|
||||
type,
|
||||
NETWORK_TYPE_RPC,
|
||||
`NetworkController - cannot call "setProviderType" with type "${NETWORK_TYPE_RPC}". Use "setRpcTarget"`,
|
||||
)
|
||||
);
|
||||
assert.ok(
|
||||
INFURA_PROVIDER_TYPES.includes(type),
|
||||
`Unknown Infura provider type "${type}".`,
|
||||
)
|
||||
const { chainId } = NETWORK_TYPE_TO_ID_MAP[type]
|
||||
this.setProviderConfig({ type, rpcUrl, chainId, ticker, nickname })
|
||||
);
|
||||
const { chainId } = NETWORK_TYPE_TO_ID_MAP[type];
|
||||
this.setProviderConfig({ type, rpcUrl, chainId, ticker, nickname });
|
||||
}
|
||||
|
||||
resetConnection() {
|
||||
this.setProviderConfig(this.getProviderConfig())
|
||||
this.setProviderConfig(this.getProviderConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the provider config and switches the network.
|
||||
*/
|
||||
setProviderConfig(config) {
|
||||
this.previousProviderStore.updateState(this.getProviderConfig())
|
||||
this.providerStore.updateState(config)
|
||||
this._switchNetwork(config)
|
||||
this.previousProviderStore.updateState(this.getProviderConfig());
|
||||
this.providerStore.updateState(config);
|
||||
this._switchNetwork(config);
|
||||
}
|
||||
|
||||
rollbackToPreviousProvider() {
|
||||
const config = this.previousProviderStore.getState()
|
||||
this.providerStore.updateState(config)
|
||||
this._switchNetwork(config)
|
||||
const config = this.previousProviderStore.getState();
|
||||
this.providerStore.updateState(config);
|
||||
this._switchNetwork(config);
|
||||
}
|
||||
|
||||
getProviderConfig() {
|
||||
return this.providerStore.getState()
|
||||
return this.providerStore.getState();
|
||||
}
|
||||
|
||||
getNetworkIdentifier() {
|
||||
const provider = this.providerStore.getState()
|
||||
return provider.type === NETWORK_TYPE_RPC ? provider.rpcUrl : provider.type
|
||||
const provider = this.providerStore.getState();
|
||||
return provider.type === NETWORK_TYPE_RPC ? provider.rpcUrl : provider.type;
|
||||
}
|
||||
|
||||
//
|
||||
@ -229,68 +229,68 @@ export default class NetworkController extends EventEmitter {
|
||||
//
|
||||
|
||||
_switchNetwork(opts) {
|
||||
this.setNetworkState('loading')
|
||||
this._configureProvider(opts)
|
||||
this.emit('networkDidChange', opts.type)
|
||||
this.setNetworkState('loading');
|
||||
this._configureProvider(opts);
|
||||
this.emit('networkDidChange', opts.type);
|
||||
}
|
||||
|
||||
_configureProvider({ type, rpcUrl, chainId }) {
|
||||
// infura type-based endpoints
|
||||
const isInfura = INFURA_PROVIDER_TYPES.includes(type)
|
||||
const isInfura = INFURA_PROVIDER_TYPES.includes(type);
|
||||
if (isInfura) {
|
||||
this._configureInfuraProvider(type, this._infuraProjectId)
|
||||
this._configureInfuraProvider(type, this._infuraProjectId);
|
||||
// url-based rpc endpoints
|
||||
} else if (type === NETWORK_TYPE_RPC) {
|
||||
this._configureStandardProvider(rpcUrl, chainId)
|
||||
this._configureStandardProvider(rpcUrl, chainId);
|
||||
} else {
|
||||
throw new Error(
|
||||
`NetworkController - _configureProvider - unknown type "${type}"`,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_configureInfuraProvider(type, projectId) {
|
||||
log.info('NetworkController - configureInfuraProvider', type)
|
||||
log.info('NetworkController - configureInfuraProvider', type);
|
||||
const networkClient = createInfuraClient({
|
||||
network: type,
|
||||
projectId,
|
||||
})
|
||||
this._setNetworkClient(networkClient)
|
||||
});
|
||||
this._setNetworkClient(networkClient);
|
||||
}
|
||||
|
||||
_configureStandardProvider(rpcUrl, chainId) {
|
||||
log.info('NetworkController - configureStandardProvider', rpcUrl)
|
||||
const networkClient = createJsonRpcClient({ rpcUrl, chainId })
|
||||
this._setNetworkClient(networkClient)
|
||||
log.info('NetworkController - configureStandardProvider', rpcUrl);
|
||||
const networkClient = createJsonRpcClient({ rpcUrl, chainId });
|
||||
this._setNetworkClient(networkClient);
|
||||
}
|
||||
|
||||
_setNetworkClient({ networkMiddleware, blockTracker }) {
|
||||
const metamaskMiddleware = createMetamaskMiddleware(
|
||||
this._baseProviderParams,
|
||||
)
|
||||
const engine = new JsonRpcEngine()
|
||||
engine.push(metamaskMiddleware)
|
||||
engine.push(networkMiddleware)
|
||||
const provider = providerFromEngine(engine)
|
||||
this._setProviderAndBlockTracker({ provider, blockTracker })
|
||||
);
|
||||
const engine = new JsonRpcEngine();
|
||||
engine.push(metamaskMiddleware);
|
||||
engine.push(networkMiddleware);
|
||||
const provider = providerFromEngine(engine);
|
||||
this._setProviderAndBlockTracker({ provider, blockTracker });
|
||||
}
|
||||
|
||||
_setProviderAndBlockTracker({ provider, blockTracker }) {
|
||||
// update or intialize proxies
|
||||
if (this._providerProxy) {
|
||||
this._providerProxy.setTarget(provider)
|
||||
this._providerProxy.setTarget(provider);
|
||||
} else {
|
||||
this._providerProxy = createSwappableProxy(provider)
|
||||
this._providerProxy = createSwappableProxy(provider);
|
||||
}
|
||||
if (this._blockTrackerProxy) {
|
||||
this._blockTrackerProxy.setTarget(blockTracker)
|
||||
this._blockTrackerProxy.setTarget(blockTracker);
|
||||
} else {
|
||||
this._blockTrackerProxy = createEventEmitterProxy(blockTracker, {
|
||||
eventFilter: 'skipInternal',
|
||||
})
|
||||
});
|
||||
}
|
||||
// set new provider and blockTracker
|
||||
this._provider = provider
|
||||
this._blockTracker = blockTracker
|
||||
this._provider = provider;
|
||||
this._blockTracker = blockTracker;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { NETWORK_TO_NAME_MAP } from '../../../../shared/constants/network'
|
||||
import { NETWORK_TO_NAME_MAP } from '../../../../shared/constants/network';
|
||||
|
||||
export const getNetworkDisplayName = (key) => NETWORK_TO_NAME_MAP[key]
|
||||
export const getNetworkDisplayName = (key) => NETWORK_TO_NAME_MAP[key];
|
||||
|
||||
export function formatTxMetaForRpcResult(txMeta) {
|
||||
return {
|
||||
@ -20,5 +20,5 @@ export function formatTxMetaForRpcResult(txMeta) {
|
||||
v: txMeta.v,
|
||||
r: txMeta.r,
|
||||
s: txMeta.s,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ObservableStore } from '@metamask/obs-store'
|
||||
import log from 'loglevel'
|
||||
import { ObservableStore } from '@metamask/obs-store';
|
||||
import log from 'loglevel';
|
||||
|
||||
/**
|
||||
* @typedef {Object} InitState
|
||||
@ -25,30 +25,30 @@ export default class OnboardingController {
|
||||
constructor(opts = {}) {
|
||||
const initialTransientState = {
|
||||
onboardingTabs: {},
|
||||
}
|
||||
};
|
||||
const initState = {
|
||||
seedPhraseBackedUp: null,
|
||||
...opts.initState,
|
||||
...initialTransientState,
|
||||
}
|
||||
this.store = new ObservableStore(initState)
|
||||
this.preferencesController = opts.preferencesController
|
||||
this.completedOnboarding = this.preferencesController.store.getState().completedOnboarding
|
||||
};
|
||||
this.store = new ObservableStore(initState);
|
||||
this.preferencesController = opts.preferencesController;
|
||||
this.completedOnboarding = this.preferencesController.store.getState().completedOnboarding;
|
||||
|
||||
this.preferencesController.store.subscribe(({ completedOnboarding }) => {
|
||||
if (completedOnboarding !== this.completedOnboarding) {
|
||||
this.completedOnboarding = completedOnboarding
|
||||
this.completedOnboarding = completedOnboarding;
|
||||
if (completedOnboarding) {
|
||||
this.store.updateState(initialTransientState)
|
||||
this.store.updateState(initialTransientState);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
setSeedPhraseBackedUp(newSeedPhraseBackUpState) {
|
||||
this.store.updateState({
|
||||
seedPhraseBackedUp: newSeedPhraseBackUpState,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -59,16 +59,16 @@ export default class OnboardingController {
|
||||
*/
|
||||
registerOnboarding = async (location, tabId) => {
|
||||
if (this.completedOnboarding) {
|
||||
log.debug('Ignoring registerOnboarding; user already onboarded')
|
||||
return
|
||||
log.debug('Ignoring registerOnboarding; user already onboarded');
|
||||
return;
|
||||
}
|
||||
const onboardingTabs = { ...this.store.getState().onboardingTabs }
|
||||
const onboardingTabs = { ...this.store.getState().onboardingTabs };
|
||||
if (!onboardingTabs[location] || onboardingTabs[location] !== tabId) {
|
||||
log.debug(
|
||||
`Registering onboarding tab at location '${location}' with tabId '${tabId}'`,
|
||||
)
|
||||
onboardingTabs[location] = tabId
|
||||
this.store.updateState({ onboardingTabs })
|
||||
);
|
||||
onboardingTabs[location] = tabId;
|
||||
this.store.updateState({ onboardingTabs });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,37 +1,37 @@
|
||||
export const APPROVAL_TYPE = 'wallet_requestPermissions'
|
||||
export const APPROVAL_TYPE = 'wallet_requestPermissions';
|
||||
|
||||
export const WALLET_PREFIX = 'wallet_'
|
||||
export const WALLET_PREFIX = 'wallet_';
|
||||
|
||||
export const HISTORY_STORE_KEY = 'permissionsHistory'
|
||||
export const HISTORY_STORE_KEY = 'permissionsHistory';
|
||||
|
||||
export const LOG_STORE_KEY = 'permissionsLog'
|
||||
export const LOG_STORE_KEY = 'permissionsLog';
|
||||
|
||||
export const METADATA_STORE_KEY = 'domainMetadata'
|
||||
export const METADATA_STORE_KEY = 'domainMetadata';
|
||||
|
||||
export const METADATA_CACHE_MAX_SIZE = 100
|
||||
export const METADATA_CACHE_MAX_SIZE = 100;
|
||||
|
||||
export const CAVEAT_TYPES = {
|
||||
limitResponseLength: 'limitResponseLength',
|
||||
filterResponse: 'filterResponse',
|
||||
}
|
||||
};
|
||||
|
||||
export const NOTIFICATION_NAMES = {
|
||||
accountsChanged: 'metamask_accountsChanged',
|
||||
unlockStateChanged: 'metamask_unlockStateChanged',
|
||||
chainChanged: 'metamask_chainChanged',
|
||||
}
|
||||
};
|
||||
|
||||
export const LOG_IGNORE_METHODS = [
|
||||
'wallet_registerOnboarding',
|
||||
'wallet_watchAsset',
|
||||
]
|
||||
];
|
||||
|
||||
export const LOG_METHOD_TYPES = {
|
||||
restricted: 'restricted',
|
||||
internal: 'internal',
|
||||
}
|
||||
};
|
||||
|
||||
export const LOG_LIMIT = 100
|
||||
export const LOG_LIMIT = 100;
|
||||
|
||||
export const SAFE_METHODS = [
|
||||
'eth_blockNumber',
|
||||
@ -90,4 +90,4 @@ export const SAFE_METHODS = [
|
||||
'wallet_watchAsset',
|
||||
'web3_clientVersion',
|
||||
'web3_sha3',
|
||||
]
|
||||
];
|
||||
|
@ -1,12 +1,12 @@
|
||||
import nanoid from 'nanoid'
|
||||
import { JsonRpcEngine } from 'json-rpc-engine'
|
||||
import { ObservableStore } from '@metamask/obs-store'
|
||||
import log from 'loglevel'
|
||||
import { CapabilitiesController as RpcCap } from 'rpc-cap'
|
||||
import { ethErrors } from 'eth-rpc-errors'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import nanoid from 'nanoid';
|
||||
import { JsonRpcEngine } from 'json-rpc-engine';
|
||||
import { ObservableStore } from '@metamask/obs-store';
|
||||
import log from 'loglevel';
|
||||
import { CapabilitiesController as RpcCap } from 'rpc-cap';
|
||||
import { ethErrors } from 'eth-rpc-errors';
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
import { CAVEAT_NAMES } from '../../../../shared/constants/permissions'
|
||||
import { CAVEAT_NAMES } from '../../../../shared/constants/permissions';
|
||||
import {
|
||||
APPROVAL_TYPE,
|
||||
SAFE_METHODS, // methods that do not require any permissions to use
|
||||
@ -17,13 +17,13 @@ import {
|
||||
HISTORY_STORE_KEY,
|
||||
NOTIFICATION_NAMES,
|
||||
CAVEAT_TYPES,
|
||||
} from './enums'
|
||||
} from './enums';
|
||||
|
||||
import createPermissionsMethodMiddleware from './permissionsMethodMiddleware'
|
||||
import PermissionsLogController from './permissionsLog'
|
||||
import createPermissionsMethodMiddleware from './permissionsMethodMiddleware';
|
||||
import PermissionsLogController from './permissionsLog';
|
||||
|
||||
// instanbul ignore next
|
||||
const noop = () => undefined
|
||||
const noop = () => undefined;
|
||||
|
||||
export class PermissionsController {
|
||||
constructor(
|
||||
@ -44,56 +44,56 @@ export class PermissionsController {
|
||||
this.store = new ObservableStore({
|
||||
[LOG_STORE_KEY]: restoredState[LOG_STORE_KEY] || [],
|
||||
[HISTORY_STORE_KEY]: restoredState[HISTORY_STORE_KEY] || {},
|
||||
})
|
||||
});
|
||||
|
||||
this.getKeyringAccounts = getKeyringAccounts
|
||||
this._getUnlockPromise = getUnlockPromise
|
||||
this._notifyDomain = notifyDomain
|
||||
this._notifyAllDomains = notifyAllDomains
|
||||
this._isUnlocked = isUnlocked
|
||||
this.getKeyringAccounts = getKeyringAccounts;
|
||||
this._getUnlockPromise = getUnlockPromise;
|
||||
this._notifyDomain = notifyDomain;
|
||||
this._notifyAllDomains = notifyAllDomains;
|
||||
this._isUnlocked = isUnlocked;
|
||||
|
||||
this._restrictedMethods = getRestrictedMethods({
|
||||
getKeyringAccounts: this.getKeyringAccounts.bind(this),
|
||||
getIdentities: this._getIdentities.bind(this),
|
||||
})
|
||||
});
|
||||
this.permissionsLog = new PermissionsLogController({
|
||||
restrictedMethods: Object.keys(this._restrictedMethods),
|
||||
store: this.store,
|
||||
})
|
||||
});
|
||||
|
||||
/**
|
||||
* @type {import('@metamask/controllers').ApprovalController}
|
||||
* @public
|
||||
*/
|
||||
this.approvals = approvals
|
||||
this._initializePermissions(restoredPermissions)
|
||||
this._lastSelectedAddress = preferences.getState().selectedAddress
|
||||
this.preferences = preferences
|
||||
this.approvals = approvals;
|
||||
this._initializePermissions(restoredPermissions);
|
||||
this._lastSelectedAddress = preferences.getState().selectedAddress;
|
||||
this.preferences = preferences;
|
||||
|
||||
this._initializeMetadataStore(restoredState)
|
||||
this._initializeMetadataStore(restoredState);
|
||||
|
||||
preferences.subscribe(async ({ selectedAddress }) => {
|
||||
if (selectedAddress && selectedAddress !== this._lastSelectedAddress) {
|
||||
this._lastSelectedAddress = selectedAddress
|
||||
await this._handleAccountSelected(selectedAddress)
|
||||
this._lastSelectedAddress = selectedAddress;
|
||||
await this._handleAccountSelected(selectedAddress);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
createMiddleware({ origin, extensionId }) {
|
||||
if (typeof origin !== 'string' || !origin.length) {
|
||||
throw new Error('Must provide non-empty string origin.')
|
||||
throw new Error('Must provide non-empty string origin.');
|
||||
}
|
||||
|
||||
const metadataState = this.store.getState()[METADATA_STORE_KEY]
|
||||
const metadataState = this.store.getState()[METADATA_STORE_KEY];
|
||||
|
||||
if (extensionId && metadataState[origin]?.extensionId !== extensionId) {
|
||||
this.addDomainMetadata(origin, { extensionId })
|
||||
this.addDomainMetadata(origin, { extensionId });
|
||||
}
|
||||
|
||||
const engine = new JsonRpcEngine()
|
||||
const engine = new JsonRpcEngine();
|
||||
|
||||
engine.push(this.permissionsLog.createMiddleware())
|
||||
engine.push(this.permissionsLog.createMiddleware());
|
||||
|
||||
engine.push(
|
||||
createPermissionsMethodMiddleware({
|
||||
@ -108,15 +108,15 @@ export class PermissionsController {
|
||||
{ eth_accounts: {} },
|
||||
),
|
||||
}),
|
||||
)
|
||||
);
|
||||
|
||||
engine.push(
|
||||
this.permissions.providerMiddlewareFunction.bind(this.permissions, {
|
||||
origin,
|
||||
}),
|
||||
)
|
||||
);
|
||||
|
||||
return engine.asMiddleware()
|
||||
return engine.asMiddleware();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -125,9 +125,9 @@ export class PermissionsController {
|
||||
* @returns {Promise<string>} The permissions request ID
|
||||
*/
|
||||
async requestAccountsPermissionWithId(origin) {
|
||||
const id = nanoid()
|
||||
this._requestPermissions({ origin }, { eth_accounts: {} }, id)
|
||||
return id
|
||||
const id = nanoid();
|
||||
this._requestPermissions({ origin }, { eth_accounts: {} }, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -139,24 +139,24 @@ export class PermissionsController {
|
||||
*/
|
||||
getAccounts(origin) {
|
||||
return new Promise((resolve, _) => {
|
||||
const req = { method: 'eth_accounts' }
|
||||
const res = {}
|
||||
const req = { method: 'eth_accounts' };
|
||||
const res = {};
|
||||
this.permissions.providerMiddlewareFunction(
|
||||
{ origin },
|
||||
req,
|
||||
res,
|
||||
noop,
|
||||
_end,
|
||||
)
|
||||
);
|
||||
|
||||
function _end() {
|
||||
if (res.error || !Array.isArray(res.result)) {
|
||||
resolve([])
|
||||
resolve([]);
|
||||
} else {
|
||||
resolve(res.result)
|
||||
resolve(res.result);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -167,7 +167,7 @@ export class PermissionsController {
|
||||
* @returns {boolean} Whether the origin has the permission.
|
||||
*/
|
||||
hasPermission(origin, permission) {
|
||||
return Boolean(this.permissions.getPermission(origin, permission))
|
||||
return Boolean(this.permissions.getPermission(origin, permission));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -176,7 +176,7 @@ export class PermissionsController {
|
||||
* @returns {Object} identities
|
||||
*/
|
||||
_getIdentities() {
|
||||
return this.preferences.getState().identities
|
||||
return this.preferences.getState().identities;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -196,20 +196,20 @@ export class PermissionsController {
|
||||
id,
|
||||
method: 'wallet_requestPermissions',
|
||||
params: [permissions],
|
||||
}
|
||||
const res = {}
|
||||
};
|
||||
const res = {};
|
||||
|
||||
this.permissions.providerMiddlewareFunction(domain, req, res, noop, _end)
|
||||
this.permissions.providerMiddlewareFunction(domain, req, res, noop, _end);
|
||||
|
||||
function _end(_err) {
|
||||
const err = _err || res.error
|
||||
const err = _err || res.error;
|
||||
if (err) {
|
||||
reject(err)
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(res.result)
|
||||
resolve(res.result);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -222,11 +222,11 @@ export class PermissionsController {
|
||||
* @param {Array} accounts - The accounts to expose, if any
|
||||
*/
|
||||
async approvePermissionsRequest(approved, accounts) {
|
||||
const { id } = approved.metadata
|
||||
const { id } = approved.metadata;
|
||||
|
||||
if (!this.approvals.has({ id })) {
|
||||
log.debug(`Permissions request with id '${id}' not found.`)
|
||||
return
|
||||
log.debug(`Permissions request with id '${id}' not found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
@ -236,15 +236,15 @@ export class PermissionsController {
|
||||
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,
|
||||
)
|
||||
this.approvals.resolve(id, approved.permissions)
|
||||
);
|
||||
this.approvals.resolve(id, approved.permissions);
|
||||
}
|
||||
} catch (err) {
|
||||
// if finalization fails, reject the request
|
||||
@ -254,7 +254,7 @@ export class PermissionsController {
|
||||
message: err.message,
|
||||
data: err,
|
||||
}),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -267,11 +267,11 @@ export class PermissionsController {
|
||||
*/
|
||||
async rejectPermissionsRequest(id) {
|
||||
if (!this.approvals.has({ id })) {
|
||||
log.debug(`Permissions request with id '${id}' not found.`)
|
||||
return
|
||||
log.debug(`Permissions request with id '${id}' not found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.approvals.reject(id, ethErrors.provider.userRejectedRequest())
|
||||
this.approvals.reject(id, ethErrors.provider.userRejectedRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -284,18 +284,18 @@ export class PermissionsController {
|
||||
* @param {string} account - The new account to expose.
|
||||
*/
|
||||
async addPermittedAccount(origin, account) {
|
||||
const domains = this.permissions.getDomains()
|
||||
const domains = this.permissions.getDomains();
|
||||
if (!domains[origin]) {
|
||||
throw new Error('Unrecognized domain')
|
||||
throw new Error('Unrecognized domain');
|
||||
}
|
||||
|
||||
this.validatePermittedAccounts([account])
|
||||
this.validatePermittedAccounts([account]);
|
||||
|
||||
const oldPermittedAccounts = this._getPermittedAccounts(origin)
|
||||
const oldPermittedAccounts = this._getPermittedAccounts(origin);
|
||||
if (!oldPermittedAccounts) {
|
||||
throw new Error(`Origin does not have 'eth_accounts' permission`)
|
||||
throw new Error(`Origin does not have 'eth_accounts' permission`);
|
||||
} else if (oldPermittedAccounts.includes(account)) {
|
||||
throw new Error('Account is already permitted for origin')
|
||||
throw new Error('Account is already permitted for origin');
|
||||
}
|
||||
|
||||
this.permissions.updateCaveatFor(
|
||||
@ -303,11 +303,11 @@ export class PermissionsController {
|
||||
'eth_accounts',
|
||||
CAVEAT_NAMES.exposedAccounts,
|
||||
[...oldPermittedAccounts, account],
|
||||
)
|
||||
);
|
||||
|
||||
const permittedAccounts = await this.getAccounts(origin)
|
||||
const permittedAccounts = await this.getAccounts(origin);
|
||||
|
||||
this.notifyAccountsChanged(origin, permittedAccounts)
|
||||
this.notifyAccountsChanged(origin, permittedAccounts);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -322,38 +322,38 @@ export class PermissionsController {
|
||||
* @param {string} account - The account to remove.
|
||||
*/
|
||||
async removePermittedAccount(origin, account) {
|
||||
const domains = this.permissions.getDomains()
|
||||
const domains = this.permissions.getDomains();
|
||||
if (!domains[origin]) {
|
||||
throw new Error('Unrecognized domain')
|
||||
throw new Error('Unrecognized domain');
|
||||
}
|
||||
|
||||
this.validatePermittedAccounts([account])
|
||||
this.validatePermittedAccounts([account]);
|
||||
|
||||
const oldPermittedAccounts = this._getPermittedAccounts(origin)
|
||||
const oldPermittedAccounts = this._getPermittedAccounts(origin);
|
||||
if (!oldPermittedAccounts) {
|
||||
throw new Error(`Origin does not have 'eth_accounts' permission`)
|
||||
throw new Error(`Origin does not have 'eth_accounts' permission`);
|
||||
} else if (!oldPermittedAccounts.includes(account)) {
|
||||
throw new Error('Account is not permitted for origin')
|
||||
throw new Error('Account is not permitted for origin');
|
||||
}
|
||||
|
||||
let newPermittedAccounts = oldPermittedAccounts.filter(
|
||||
(acc) => acc !== account,
|
||||
)
|
||||
);
|
||||
|
||||
if (newPermittedAccounts.length === 0) {
|
||||
this.removePermissionsFor({ [origin]: ['eth_accounts'] })
|
||||
this.removePermissionsFor({ [origin]: ['eth_accounts'] });
|
||||
} else {
|
||||
this.permissions.updateCaveatFor(
|
||||
origin,
|
||||
'eth_accounts',
|
||||
CAVEAT_NAMES.exposedAccounts,
|
||||
newPermittedAccounts,
|
||||
)
|
||||
);
|
||||
|
||||
newPermittedAccounts = await this.getAccounts(origin)
|
||||
newPermittedAccounts = await this.getAccounts(origin);
|
||||
}
|
||||
|
||||
this.notifyAccountsChanged(origin, newPermittedAccounts)
|
||||
this.notifyAccountsChanged(origin, newPermittedAccounts);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -365,18 +365,18 @@ export class PermissionsController {
|
||||
* @param {string} account - The account to remove.
|
||||
*/
|
||||
async removeAllAccountPermissions(account) {
|
||||
this.validatePermittedAccounts([account])
|
||||
this.validatePermittedAccounts([account]);
|
||||
|
||||
const domains = this.permissions.getDomains()
|
||||
const domains = this.permissions.getDomains();
|
||||
const connectedOrigins = Object.keys(domains).filter((origin) =>
|
||||
this._getPermittedAccounts(origin).includes(account),
|
||||
)
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
connectedOrigins.map((origin) =>
|
||||
this.removePermittedAccount(origin, account),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -390,16 +390,16 @@ export class PermissionsController {
|
||||
* @returns {Object} The finalized permissions request object.
|
||||
*/
|
||||
async finalizePermissionsRequest(requestedPermissions, requestedAccounts) {
|
||||
const finalizedPermissions = cloneDeep(requestedPermissions)
|
||||
const finalizedAccounts = cloneDeep(requestedAccounts)
|
||||
const finalizedPermissions = cloneDeep(requestedPermissions);
|
||||
const finalizedAccounts = cloneDeep(requestedAccounts);
|
||||
|
||||
const { eth_accounts: ethAccounts } = finalizedPermissions
|
||||
const { eth_accounts: ethAccounts } = finalizedPermissions;
|
||||
|
||||
if (ethAccounts) {
|
||||
this.validatePermittedAccounts(finalizedAccounts)
|
||||
this.validatePermittedAccounts(finalizedAccounts);
|
||||
|
||||
if (!ethAccounts.caveats) {
|
||||
ethAccounts.caveats = []
|
||||
ethAccounts.caveats = [];
|
||||
}
|
||||
|
||||
// caveat names are unique, and we will only construct this caveat here
|
||||
@ -407,22 +407,22 @@ export class PermissionsController {
|
||||
(c) =>
|
||||
c.name !== CAVEAT_NAMES.exposedAccounts &&
|
||||
c.name !== CAVEAT_NAMES.primaryAccountOnly,
|
||||
)
|
||||
);
|
||||
|
||||
ethAccounts.caveats.push({
|
||||
type: CAVEAT_TYPES.limitResponseLength,
|
||||
value: 1,
|
||||
name: CAVEAT_NAMES.primaryAccountOnly,
|
||||
})
|
||||
});
|
||||
|
||||
ethAccounts.caveats.push({
|
||||
type: CAVEAT_TYPES.filterResponse,
|
||||
value: finalizedAccounts,
|
||||
name: CAVEAT_NAMES.exposedAccounts,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return finalizedPermissions
|
||||
return finalizedPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -433,16 +433,16 @@ export class PermissionsController {
|
||||
*/
|
||||
validatePermittedAccounts(accounts) {
|
||||
if (!Array.isArray(accounts) || accounts.length === 0) {
|
||||
throw new Error('Must provide non-empty array of account(s).')
|
||||
throw new Error('Must provide non-empty array of account(s).');
|
||||
}
|
||||
|
||||
// assert accounts exist
|
||||
const allIdentities = this._getIdentities()
|
||||
const allIdentities = this._getIdentities();
|
||||
accounts.forEach((acc) => {
|
||||
if (!allIdentities[acc]) {
|
||||
throw new Error(`Unknown account: ${acc}`)
|
||||
throw new Error(`Unknown account: ${acc}`);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -454,11 +454,11 @@ export class PermissionsController {
|
||||
*/
|
||||
notifyAccountsChanged(origin, newAccounts) {
|
||||
if (typeof origin !== 'string' || !origin) {
|
||||
throw new Error(`Invalid origin: '${origin}'`)
|
||||
throw new Error(`Invalid origin: '${origin}'`);
|
||||
}
|
||||
|
||||
if (!Array.isArray(newAccounts)) {
|
||||
throw new Error('Invalid accounts', newAccounts)
|
||||
throw new Error('Invalid accounts', newAccounts);
|
||||
}
|
||||
|
||||
// We do not share accounts when the extension is locked.
|
||||
@ -466,8 +466,8 @@ export class PermissionsController {
|
||||
this._notifyDomain(origin, {
|
||||
method: NOTIFICATION_NAMES.accountsChanged,
|
||||
params: newAccounts,
|
||||
})
|
||||
this.permissionsLog.updateAccountsHistory(origin, newAccounts)
|
||||
});
|
||||
this.permissionsLog.updateAccountsHistory(origin, newAccounts);
|
||||
}
|
||||
|
||||
// NOTE:
|
||||
@ -491,26 +491,26 @@ export class PermissionsController {
|
||||
origin,
|
||||
perms.map((methodName) => {
|
||||
if (methodName === 'eth_accounts') {
|
||||
this.notifyAccountsChanged(origin, [])
|
||||
this.notifyAccountsChanged(origin, []);
|
||||
}
|
||||
|
||||
return { parentCapability: methodName }
|
||||
return { parentCapability: methodName };
|
||||
}),
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all known domains and their related permissions.
|
||||
*/
|
||||
clearPermissions() {
|
||||
this.permissions.clearDomains()
|
||||
this.permissions.clearDomains();
|
||||
// It's safe to notify that no accounts are available, regardless of
|
||||
// extension lock state
|
||||
this._notifyAllDomains({
|
||||
method: NOTIFICATION_NAMES.accountsChanged,
|
||||
params: [],
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -524,18 +524,18 @@ export class PermissionsController {
|
||||
* @param {Object} metadata - The domain's metadata that will be stored.
|
||||
*/
|
||||
addDomainMetadata(origin, metadata) {
|
||||
const oldMetadataState = this.store.getState()[METADATA_STORE_KEY]
|
||||
const newMetadataState = { ...oldMetadataState }
|
||||
const oldMetadataState = this.store.getState()[METADATA_STORE_KEY];
|
||||
const newMetadataState = { ...oldMetadataState };
|
||||
|
||||
// delete pending metadata origin from queue, and delete its metadata if
|
||||
// it doesn't have any permissions
|
||||
if (this._pendingSiteMetadata.size >= METADATA_CACHE_MAX_SIZE) {
|
||||
const permissionsDomains = this.permissions.getDomains()
|
||||
const permissionsDomains = this.permissions.getDomains();
|
||||
|
||||
const oldOrigin = this._pendingSiteMetadata.values().next().value
|
||||
this._pendingSiteMetadata.delete(oldOrigin)
|
||||
const oldOrigin = this._pendingSiteMetadata.values().next().value;
|
||||
this._pendingSiteMetadata.delete(oldOrigin);
|
||||
if (!permissionsDomains[oldOrigin]) {
|
||||
delete newMetadataState[oldOrigin]
|
||||
delete newMetadataState[oldOrigin];
|
||||
}
|
||||
}
|
||||
|
||||
@ -544,17 +544,17 @@ export class PermissionsController {
|
||||
...oldMetadataState[origin],
|
||||
...metadata,
|
||||
lastUpdated: Date.now(),
|
||||
}
|
||||
};
|
||||
|
||||
if (
|
||||
!newMetadataState[origin].extensionId &&
|
||||
!newMetadataState[origin].host
|
||||
) {
|
||||
newMetadataState[origin].host = new URL(origin).host
|
||||
newMetadataState[origin].host = new URL(origin).host;
|
||||
}
|
||||
|
||||
this._pendingSiteMetadata.add(origin)
|
||||
this._setDomainMetadata(newMetadataState)
|
||||
this._pendingSiteMetadata.add(origin);
|
||||
this._setDomainMetadata(newMetadataState);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -566,11 +566,11 @@ export class PermissionsController {
|
||||
* @param {Object} restoredState - The restored permissions controller state.
|
||||
*/
|
||||
_initializeMetadataStore(restoredState) {
|
||||
const metadataState = restoredState[METADATA_STORE_KEY] || {}
|
||||
const newMetadataState = this._trimDomainMetadata(metadataState)
|
||||
const metadataState = restoredState[METADATA_STORE_KEY] || {};
|
||||
const newMetadataState = this._trimDomainMetadata(metadataState);
|
||||
|
||||
this._pendingSiteMetadata = new Set()
|
||||
this._setDomainMetadata(newMetadataState)
|
||||
this._pendingSiteMetadata = new Set();
|
||||
this._setDomainMetadata(newMetadataState);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -582,17 +582,17 @@ export class PermissionsController {
|
||||
* @returns {Object} The new metadata state object.
|
||||
*/
|
||||
_trimDomainMetadata(metadataState) {
|
||||
const newMetadataState = { ...metadataState }
|
||||
const origins = Object.keys(metadataState)
|
||||
const permissionsDomains = this.permissions.getDomains()
|
||||
const newMetadataState = { ...metadataState };
|
||||
const origins = Object.keys(metadataState);
|
||||
const permissionsDomains = this.permissions.getDomains();
|
||||
|
||||
origins.forEach((origin) => {
|
||||
if (!permissionsDomains[origin]) {
|
||||
delete newMetadataState[origin]
|
||||
delete newMetadataState[origin];
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return newMetadataState
|
||||
return newMetadataState;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -600,7 +600,7 @@ export class PermissionsController {
|
||||
* @param {Object} newMetadataState - The new metadata to set.
|
||||
*/
|
||||
_setDomainMetadata(newMetadataState) {
|
||||
this.store.updateState({ [METADATA_STORE_KEY]: newMetadataState })
|
||||
this.store.updateState({ [METADATA_STORE_KEY]: newMetadataState });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -613,9 +613,9 @@ export class PermissionsController {
|
||||
const permittedAccounts = this.permissions
|
||||
.getPermission(origin, 'eth_accounts')
|
||||
?.caveats?.find((caveat) => caveat.name === CAVEAT_NAMES.exposedAccounts)
|
||||
?.value
|
||||
?.value;
|
||||
|
||||
return permittedAccounts || null
|
||||
return permittedAccounts || null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -629,27 +629,27 @@ export class PermissionsController {
|
||||
*/
|
||||
async _handleAccountSelected(account) {
|
||||
if (typeof account !== 'string') {
|
||||
throw new Error('Selected account should be a non-empty string.')
|
||||
throw new Error('Selected account should be a non-empty string.');
|
||||
}
|
||||
|
||||
const domains = this.permissions.getDomains() || {}
|
||||
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
|
||||
return exposedAccounts?.includes(account)
|
||||
)?.value;
|
||||
return exposedAccounts?.includes(account);
|
||||
})
|
||||
.map(([domain]) => domain)
|
||||
.map(([domain]) => domain);
|
||||
|
||||
await Promise.all(
|
||||
connectedDomains.map((origin) =>
|
||||
this._handleConnectedAccountSelected(origin),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -661,9 +661,9 @@ export class PermissionsController {
|
||||
* @param {string} origin - The origin
|
||||
*/
|
||||
async _handleConnectedAccountSelected(origin) {
|
||||
const permittedAccounts = await this.getAccounts(origin)
|
||||
const permittedAccounts = await this.getAccounts(origin);
|
||||
|
||||
this.notifyAccountsChanged(origin, permittedAccounts)
|
||||
this.notifyAccountsChanged(origin, permittedAccounts);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -674,7 +674,7 @@ export class PermissionsController {
|
||||
*/
|
||||
_initializePermissions(restoredState) {
|
||||
// these permission requests are almost certainly stale
|
||||
const initState = { ...restoredState, permissionsRequests: [] }
|
||||
const initState = { ...restoredState, permissionsRequests: [] };
|
||||
|
||||
this.permissions = new RpcCap(
|
||||
{
|
||||
@ -698,16 +698,16 @@ export class PermissionsController {
|
||||
requestUserApproval: async (req) => {
|
||||
const {
|
||||
metadata: { id, origin },
|
||||
} = req
|
||||
} = req;
|
||||
|
||||
return this.approvals.addAndShowApprovalRequest({
|
||||
id,
|
||||
origin,
|
||||
type: APPROVAL_TYPE,
|
||||
})
|
||||
});
|
||||
},
|
||||
},
|
||||
initState,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { CAVEAT_NAMES } from '../../../../shared/constants/permissions'
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { CAVEAT_NAMES } from '../../../../shared/constants/permissions';
|
||||
import {
|
||||
HISTORY_STORE_KEY,
|
||||
LOG_IGNORE_METHODS,
|
||||
@ -7,7 +7,7 @@ import {
|
||||
LOG_METHOD_TYPES,
|
||||
LOG_STORE_KEY,
|
||||
WALLET_PREFIX,
|
||||
} from './enums'
|
||||
} from './enums';
|
||||
|
||||
/**
|
||||
* Controller with middleware for logging requests and responses to restricted
|
||||
@ -15,8 +15,8 @@ import {
|
||||
*/
|
||||
export default class PermissionsLogController {
|
||||
constructor({ restrictedMethods, store }) {
|
||||
this.restrictedMethods = restrictedMethods
|
||||
this.store = store
|
||||
this.restrictedMethods = restrictedMethods;
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -25,7 +25,7 @@ export default class PermissionsLogController {
|
||||
* @returns {Array<Object>} The activity log.
|
||||
*/
|
||||
getActivityLog() {
|
||||
return this.store.getState()[LOG_STORE_KEY] || []
|
||||
return this.store.getState()[LOG_STORE_KEY] || [];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -34,7 +34,7 @@ export default class PermissionsLogController {
|
||||
* @param {Array<Object>} logs - The new activity log array.
|
||||
*/
|
||||
updateActivityLog(logs) {
|
||||
this.store.updateState({ [LOG_STORE_KEY]: logs })
|
||||
this.store.updateState({ [LOG_STORE_KEY]: logs });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -43,7 +43,7 @@ export default class PermissionsLogController {
|
||||
* @returns {Object} The permissions history log.
|
||||
*/
|
||||
getHistory() {
|
||||
return this.store.getState()[HISTORY_STORE_KEY] || {}
|
||||
return this.store.getState()[HISTORY_STORE_KEY] || {};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -52,7 +52,7 @@ export default class PermissionsLogController {
|
||||
* @param {Object} history - The new permissions history log object.
|
||||
*/
|
||||
updateHistory(history) {
|
||||
this.store.updateState({ [HISTORY_STORE_KEY]: history })
|
||||
this.store.updateState({ [HISTORY_STORE_KEY]: history });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -65,16 +65,16 @@ export default class PermissionsLogController {
|
||||
*/
|
||||
updateAccountsHistory(origin, accounts) {
|
||||
if (accounts.length === 0) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const accountToTimeMap = getAccountToTimeMap(accounts, Date.now())
|
||||
const accountToTimeMap = getAccountToTimeMap(accounts, Date.now());
|
||||
|
||||
this.commitNewHistory(origin, {
|
||||
eth_accounts: {
|
||||
accounts: accountToTimeMap,
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -89,37 +89,37 @@ export default class PermissionsLogController {
|
||||
*/
|
||||
createMiddleware() {
|
||||
return (req, res, next, _end) => {
|
||||
let activityEntry, requestedMethods
|
||||
const { origin, method } = req
|
||||
const isInternal = method.startsWith(WALLET_PREFIX)
|
||||
let activityEntry, requestedMethods;
|
||||
const { origin, method } = req;
|
||||
const isInternal = method.startsWith(WALLET_PREFIX);
|
||||
|
||||
// we only log certain methods
|
||||
if (
|
||||
!LOG_IGNORE_METHODS.includes(method) &&
|
||||
(isInternal || this.restrictedMethods.includes(method))
|
||||
) {
|
||||
activityEntry = this.logRequest(req, isInternal)
|
||||
activityEntry = this.logRequest(req, isInternal);
|
||||
|
||||
if (method === `${WALLET_PREFIX}requestPermissions`) {
|
||||
// get the corresponding methods from the requested permissions so
|
||||
// that we can record permissions history
|
||||
requestedMethods = this.getRequestedMethods(req)
|
||||
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)
|
||||
requestedMethods = ['eth_accounts']
|
||||
activityEntry = this.logRequest(req, isInternal);
|
||||
requestedMethods = ['eth_accounts'];
|
||||
} else {
|
||||
// no-op
|
||||
next()
|
||||
return
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
// call next with a return handler for capturing the response
|
||||
next((cb) => {
|
||||
const time = Date.now()
|
||||
this.logResponse(activityEntry, res, time)
|
||||
const time = Date.now();
|
||||
this.logResponse(activityEntry, res, time);
|
||||
|
||||
if (requestedMethods && !res.error && res.result) {
|
||||
// any permissions or accounts changes will be recorded on the response,
|
||||
@ -130,11 +130,11 @@ export default class PermissionsLogController {
|
||||
res.result,
|
||||
time,
|
||||
method === 'eth_requestAccounts',
|
||||
)
|
||||
);
|
||||
}
|
||||
cb()
|
||||
})
|
||||
}
|
||||
cb();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -156,9 +156,9 @@ export default class PermissionsLogController {
|
||||
response: null,
|
||||
responseTime: null,
|
||||
success: null,
|
||||
}
|
||||
this.commitNewActivity(activityEntry)
|
||||
return activityEntry
|
||||
};
|
||||
this.commitNewActivity(activityEntry);
|
||||
return activityEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -171,12 +171,12 @@ export default class PermissionsLogController {
|
||||
*/
|
||||
logResponse(entry, response, time) {
|
||||
if (!entry || !response) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
entry.response = cloneDeep(response)
|
||||
entry.responseTime = time
|
||||
entry.success = !response.error
|
||||
entry.response = cloneDeep(response);
|
||||
entry.responseTime = time;
|
||||
entry.success = !response.error;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -186,17 +186,17 @@ export default class PermissionsLogController {
|
||||
* @param {Object} entry - The activity log entry.
|
||||
*/
|
||||
commitNewActivity(entry) {
|
||||
const logs = this.getActivityLog()
|
||||
const logs = this.getActivityLog();
|
||||
|
||||
// add new entry to end of log
|
||||
logs.push(entry)
|
||||
logs.push(entry);
|
||||
|
||||
// remove oldest log if exceeding size limit
|
||||
if (logs.length > LOG_LIMIT) {
|
||||
logs.shift()
|
||||
logs.shift();
|
||||
}
|
||||
|
||||
this.updateActivityLog(logs)
|
||||
this.updateActivityLog(logs);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -215,18 +215,18 @@ export default class PermissionsLogController {
|
||||
time,
|
||||
isEthRequestAccounts,
|
||||
) {
|
||||
let accounts, newEntries
|
||||
let accounts, newEntries;
|
||||
|
||||
if (isEthRequestAccounts) {
|
||||
accounts = result
|
||||
const accountToTimeMap = getAccountToTimeMap(accounts, time)
|
||||
accounts = result;
|
||||
const accountToTimeMap = getAccountToTimeMap(accounts, time);
|
||||
|
||||
newEntries = {
|
||||
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
|
||||
@ -234,33 +234,33 @@ export default class PermissionsLogController {
|
||||
newEntries = result
|
||||
.map((perm) => {
|
||||
if (perm.parentCapability === 'eth_accounts') {
|
||||
accounts = this.getAccountsFromPermission(perm)
|
||||
accounts = this.getAccountsFromPermission(perm);
|
||||
}
|
||||
|
||||
return perm.parentCapability
|
||||
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)
|
||||
const accountToTimeMap = getAccountToTimeMap(accounts, time);
|
||||
|
||||
acc[method] = {
|
||||
lastApproved: time,
|
||||
accounts: accountToTimeMap,
|
||||
}
|
||||
};
|
||||
} else {
|
||||
acc[method] = { lastApproved: time }
|
||||
acc[method] = { lastApproved: time };
|
||||
}
|
||||
}
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
if (Object.keys(newEntries).length > 0) {
|
||||
this.commitNewHistory(origin, newEntries)
|
||||
this.commitNewHistory(origin, newEntries);
|
||||
}
|
||||
}
|
||||
|
||||
@ -274,24 +274,24 @@ export default class PermissionsLogController {
|
||||
*/
|
||||
commitNewHistory(origin, newEntries) {
|
||||
// a simple merge updates most permissions
|
||||
const history = this.getHistory()
|
||||
const history = this.getHistory();
|
||||
const newOriginHistory = {
|
||||
...history[origin],
|
||||
...newEntries,
|
||||
}
|
||||
};
|
||||
|
||||
// eth_accounts requires special handling, because of information
|
||||
// we store about the accounts
|
||||
const existingEthAccountsEntry =
|
||||
history[origin] && history[origin].eth_accounts
|
||||
const newEthAccountsEntry = newEntries.eth_accounts
|
||||
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 =
|
||||
newEthAccountsEntry.lastApproved ||
|
||||
existingEthAccountsEntry.lastApproved
|
||||
existingEthAccountsEntry.lastApproved;
|
||||
|
||||
// merge old and new eth_accounts history entries
|
||||
newOriginHistory.eth_accounts = {
|
||||
@ -300,12 +300,12 @@ export default class PermissionsLogController {
|
||||
...existingEthAccountsEntry.accounts,
|
||||
...newEthAccountsEntry.accounts,
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
history[origin] = newOriginHistory
|
||||
history[origin] = newOriginHistory;
|
||||
|
||||
this.updateHistory(history)
|
||||
this.updateHistory(history);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -321,9 +321,9 @@ export default class PermissionsLogController {
|
||||
typeof request.params[0] !== 'object' ||
|
||||
Array.isArray(request.params[0])
|
||||
) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
return Object.keys(request.params[0])
|
||||
return Object.keys(request.params[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -335,21 +335,21 @@ export default class PermissionsLogController {
|
||||
*/
|
||||
getAccountsFromPermission(perm) {
|
||||
if (perm.parentCapability !== 'eth_accounts' || !perm.caveats) {
|
||||
return []
|
||||
return [];
|
||||
}
|
||||
|
||||
const accounts = new Set()
|
||||
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)
|
||||
accounts.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [...accounts]
|
||||
return [...accounts];
|
||||
}
|
||||
}
|
||||
|
||||
@ -363,5 +363,5 @@ export default class PermissionsLogController {
|
||||
* @returns {Object} A string:number map of addresses to time.
|
||||
*/
|
||||
function getAccountToTimeMap(accounts, time) {
|
||||
return accounts.reduce((acc, account) => ({ ...acc, [account]: time }), {})
|
||||
return accounts.reduce((acc, account) => ({ ...acc, [account]: time }), {});
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { createAsyncMiddleware } from 'json-rpc-engine'
|
||||
import { ethErrors } from 'eth-rpc-errors'
|
||||
import { createAsyncMiddleware } from 'json-rpc-engine';
|
||||
import { ethErrors } from 'eth-rpc-errors';
|
||||
|
||||
/**
|
||||
* Create middleware for handling certain methods and preprocessing permissions requests.
|
||||
@ -12,73 +12,73 @@ export default function createPermissionsMethodMiddleware({
|
||||
notifyAccountsChanged,
|
||||
requestAccountsPermission,
|
||||
}) {
|
||||
let isProcessingRequestAccounts = false
|
||||
let isProcessingRequestAccounts = false;
|
||||
|
||||
return createAsyncMiddleware(async (req, res, next) => {
|
||||
let responseHandler
|
||||
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
|
||||
res.result = await getAccounts();
|
||||
return;
|
||||
}
|
||||
|
||||
case 'eth_requestAccounts': {
|
||||
if (isProcessingRequestAccounts) {
|
||||
res.error = ethErrors.rpc.resourceUnavailable(
|
||||
'Already processing eth_requestAccounts. Please wait.',
|
||||
)
|
||||
return
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasPermission('eth_accounts')) {
|
||||
isProcessingRequestAccounts = true
|
||||
await getUnlockPromise()
|
||||
isProcessingRequestAccounts = false
|
||||
isProcessingRequestAccounts = true;
|
||||
await getUnlockPromise();
|
||||
isProcessingRequestAccounts = false;
|
||||
}
|
||||
|
||||
// first, just try to get accounts
|
||||
let accounts = await getAccounts()
|
||||
let accounts = await getAccounts();
|
||||
if (accounts.length > 0) {
|
||||
res.result = accounts
|
||||
return
|
||||
res.result = accounts;
|
||||
return;
|
||||
}
|
||||
|
||||
// if no accounts, request the accounts permission
|
||||
try {
|
||||
await requestAccountsPermission()
|
||||
await requestAccountsPermission();
|
||||
} catch (err) {
|
||||
res.error = err
|
||||
return
|
||||
res.error = err;
|
||||
return;
|
||||
}
|
||||
|
||||
// get the accounts again
|
||||
accounts = await getAccounts()
|
||||
accounts = await getAccounts();
|
||||
/* istanbul ignore else: too hard to induce, see below comment */
|
||||
if (accounts.length > 0) {
|
||||
res.result = accounts
|
||||
res.result = accounts;
|
||||
} else {
|
||||
// this should never happen, because it should be caught in the
|
||||
// above catch clause
|
||||
res.error = ethErrors.rpc.internal(
|
||||
'Accounts unexpectedly unavailable. Please report this bug.',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// custom method for getting metadata from the requesting domain,
|
||||
// sent automatically by the inpage provider when it's initialized
|
||||
case 'metamask_sendDomainMetadata': {
|
||||
if (typeof req.params?.name === 'string') {
|
||||
addDomainMetadata(req.origin, req.params)
|
||||
addDomainMetadata(req.origin, req.params);
|
||||
}
|
||||
res.result = true
|
||||
return
|
||||
res.result = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// register return handler to send accountsChanged notification
|
||||
@ -88,25 +88,25 @@ export default function createPermissionsMethodMiddleware({
|
||||
if (Array.isArray(res.result)) {
|
||||
for (const permission of res.result) {
|
||||
if (permission.parentCapability === 'eth_accounts') {
|
||||
notifyAccountsChanged(await getAccounts())
|
||||
notifyAccountsChanged(await getAccounts());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
break
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break
|
||||
break;
|
||||
}
|
||||
|
||||
// when this promise resolves, the response is on its way back
|
||||
// eslint-disable-next-line node/callback-return
|
||||
await next()
|
||||
await next();
|
||||
|
||||
if (responseHandler) {
|
||||
responseHandler()
|
||||
responseHandler();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
@ -6,35 +6,35 @@ export default function getRestrictedMethods({
|
||||
eth_accounts: {
|
||||
method: async (_, res, __, end) => {
|
||||
try {
|
||||
const accounts = await getKeyringAccounts()
|
||||
const identities = getIdentities()
|
||||
const accounts = await getKeyringAccounts();
|
||||
const identities = getIdentities();
|
||||
res.result = accounts.sort((firstAddress, secondAddress) => {
|
||||
if (!identities[firstAddress]) {
|
||||
throw new Error(`Missing identity for address ${firstAddress}`)
|
||||
throw new Error(`Missing identity for address ${firstAddress}`);
|
||||
} else if (!identities[secondAddress]) {
|
||||
throw new Error(`Missing identity for address ${secondAddress}`)
|
||||
throw new Error(`Missing identity for address ${secondAddress}`);
|
||||
} else if (
|
||||
identities[firstAddress].lastSelected ===
|
||||
identities[secondAddress].lastSelected
|
||||
) {
|
||||
return 0
|
||||
return 0;
|
||||
} else if (identities[firstAddress].lastSelected === undefined) {
|
||||
return 1
|
||||
return 1;
|
||||
} else if (identities[secondAddress].lastSelected === undefined) {
|
||||
return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (
|
||||
identities[secondAddress].lastSelected -
|
||||
identities[firstAddress].lastSelected
|
||||
)
|
||||
})
|
||||
end()
|
||||
);
|
||||
});
|
||||
end();
|
||||
} catch (err) {
|
||||
res.error = err
|
||||
end(err)
|
||||
res.error = err;
|
||||
end(err);
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { strict as assert } from 'assert'
|
||||
import { ObservableStore } from '@metamask/obs-store'
|
||||
import { ethErrors } from 'eth-rpc-errors'
|
||||
import { normalize as normalizeAddress } from 'eth-sig-util'
|
||||
import { isValidAddress } from 'ethereumjs-util'
|
||||
import ethers from 'ethers'
|
||||
import log from 'loglevel'
|
||||
import { LISTED_CONTRACT_ADDRESSES } from '../../../shared/constants/tokens'
|
||||
import { NETWORK_TYPE_TO_ID_MAP } from '../../../shared/constants/network'
|
||||
import { isPrefixedFormattedHexString } from '../../../shared/modules/utils'
|
||||
import { strict as assert } from 'assert';
|
||||
import { ObservableStore } from '@metamask/obs-store';
|
||||
import { ethErrors } from 'eth-rpc-errors';
|
||||
import { normalize as normalizeAddress } from 'eth-sig-util';
|
||||
import { isValidAddress } from 'ethereumjs-util';
|
||||
import ethers from 'ethers';
|
||||
import log from 'loglevel';
|
||||
import { LISTED_CONTRACT_ADDRESSES } from '../../../shared/constants/tokens';
|
||||
import { NETWORK_TYPE_TO_ID_MAP } from '../../../shared/constants/network';
|
||||
import { isPrefixedFormattedHexString } from '../../../shared/modules/utils';
|
||||
|
||||
export default class PreferencesController {
|
||||
/**
|
||||
@ -65,18 +65,18 @@ export default class PreferencesController {
|
||||
// ENS decentralized website resolution
|
||||
ipfsGateway: 'dweb.link',
|
||||
...opts.initState,
|
||||
}
|
||||
};
|
||||
|
||||
this.network = opts.network
|
||||
this.store = new ObservableStore(initState)
|
||||
this.store.setMaxListeners(12)
|
||||
this.openPopup = opts.openPopup
|
||||
this.migrateAddressBookState = opts.migrateAddressBookState
|
||||
this._subscribeProviderType()
|
||||
this.network = opts.network;
|
||||
this.store = new ObservableStore(initState);
|
||||
this.store.setMaxListeners(12);
|
||||
this.openPopup = opts.openPopup;
|
||||
this.migrateAddressBookState = opts.migrateAddressBookState;
|
||||
this._subscribeProviderType();
|
||||
|
||||
global.setPreference = (key, value) => {
|
||||
return this.setFeatureFlag(key, value)
|
||||
}
|
||||
return this.setFeatureFlag(key, value);
|
||||
};
|
||||
}
|
||||
// PUBLIC METHODS
|
||||
|
||||
@ -85,7 +85,7 @@ export default class PreferencesController {
|
||||
* @param {boolean} forgottenPassword - whether or not the user has forgotten their password
|
||||
*/
|
||||
setPasswordForgotten(forgottenPassword) {
|
||||
this.store.updateState({ forgottenPassword })
|
||||
this.store.updateState({ forgottenPassword });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -95,7 +95,7 @@ export default class PreferencesController {
|
||||
*
|
||||
*/
|
||||
setUseBlockie(val) {
|
||||
this.store.updateState({ useBlockie: val })
|
||||
this.store.updateState({ useBlockie: val });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -105,7 +105,7 @@ export default class PreferencesController {
|
||||
*
|
||||
*/
|
||||
setUseNonceField(val) {
|
||||
this.store.updateState({ useNonceField: val })
|
||||
this.store.updateState({ useNonceField: val });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -115,7 +115,7 @@ export default class PreferencesController {
|
||||
*
|
||||
*/
|
||||
setUsePhishDetect(val) {
|
||||
this.store.updateState({ usePhishDetect: val })
|
||||
this.store.updateState({ usePhishDetect: val });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -125,15 +125,15 @@ export default class PreferencesController {
|
||||
*
|
||||
*/
|
||||
setFirstTimeFlowType(type) {
|
||||
this.store.updateState({ firstTimeFlowType: type })
|
||||
this.store.updateState({ firstTimeFlowType: type });
|
||||
}
|
||||
|
||||
getSuggestedTokens() {
|
||||
return this.store.getState().suggestedTokens
|
||||
return this.store.getState().suggestedTokens;
|
||||
}
|
||||
|
||||
getAssetImages() {
|
||||
return this.store.getState().assetImages
|
||||
return this.store.getState().assetImages;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -143,9 +143,9 @@ export default class PreferencesController {
|
||||
* @param {string} methodData - Corresponding data method
|
||||
*/
|
||||
addKnownMethodData(fourBytePrefix, methodData) {
|
||||
const { knownMethodData } = this.store.getState()
|
||||
knownMethodData[fourBytePrefix] = methodData
|
||||
this.store.updateState({ knownMethodData })
|
||||
const { knownMethodData } = this.store.getState();
|
||||
knownMethodData[fourBytePrefix] = methodData;
|
||||
this.store.updateState({ knownMethodData });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -154,15 +154,15 @@ export default class PreferencesController {
|
||||
* @param {Object} req - The watchAsset JSON-RPC request object.
|
||||
*/
|
||||
async requestWatchAsset(req) {
|
||||
const { type, options } = req.params
|
||||
const { type, options } = req.params;
|
||||
|
||||
switch (type) {
|
||||
case 'ERC20':
|
||||
return await this._handleWatchAssetERC20(options)
|
||||
return await this._handleWatchAssetERC20(options);
|
||||
default:
|
||||
throw ethErrors.rpc.invalidParams(
|
||||
`Asset of type "${type}" not supported.`,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,12 +175,12 @@ export default class PreferencesController {
|
||||
setCurrentLocale(key) {
|
||||
const textDirection = ['ar', 'dv', 'fa', 'he', 'ku'].includes(key)
|
||||
? 'rtl'
|
||||
: 'auto'
|
||||
: 'auto';
|
||||
this.store.updateState({
|
||||
currentLocale: key,
|
||||
textDirection,
|
||||
})
|
||||
return textDirection
|
||||
});
|
||||
return textDirection;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -191,26 +191,26 @@ export default class PreferencesController {
|
||||
*
|
||||
*/
|
||||
setAddresses(addresses) {
|
||||
const oldIdentities = this.store.getState().identities
|
||||
const oldAccountTokens = this.store.getState().accountTokens
|
||||
const oldAccountHiddenTokens = this.store.getState().accountHiddenTokens
|
||||
const oldIdentities = this.store.getState().identities;
|
||||
const oldAccountTokens = this.store.getState().accountTokens;
|
||||
const oldAccountHiddenTokens = this.store.getState().accountHiddenTokens;
|
||||
|
||||
const identities = addresses.reduce((ids, address, index) => {
|
||||
const oldId = oldIdentities[address] || {}
|
||||
ids[address] = { name: `Account ${index + 1}`, address, ...oldId }
|
||||
return ids
|
||||
}, {})
|
||||
const oldId = oldIdentities[address] || {};
|
||||
ids[address] = { name: `Account ${index + 1}`, address, ...oldId };
|
||||
return ids;
|
||||
}, {});
|
||||
const accountTokens = addresses.reduce((tokens, address) => {
|
||||
const oldTokens = oldAccountTokens[address] || {}
|
||||
tokens[address] = oldTokens
|
||||
return tokens
|
||||
}, {})
|
||||
const oldTokens = oldAccountTokens[address] || {};
|
||||
tokens[address] = oldTokens;
|
||||
return tokens;
|
||||
}, {});
|
||||
const accountHiddenTokens = addresses.reduce((hiddenTokens, address) => {
|
||||
const oldHiddenTokens = oldAccountHiddenTokens[address] || {}
|
||||
hiddenTokens[address] = oldHiddenTokens
|
||||
return hiddenTokens
|
||||
}, {})
|
||||
this.store.updateState({ identities, accountTokens, accountHiddenTokens })
|
||||
const oldHiddenTokens = oldAccountHiddenTokens[address] || {};
|
||||
hiddenTokens[address] = oldHiddenTokens;
|
||||
return hiddenTokens;
|
||||
}, {});
|
||||
this.store.updateState({ identities, accountTokens, accountHiddenTokens });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -224,23 +224,23 @@ export default class PreferencesController {
|
||||
identities,
|
||||
accountTokens,
|
||||
accountHiddenTokens,
|
||||
} = this.store.getState()
|
||||
} = this.store.getState();
|
||||
|
||||
if (!identities[address]) {
|
||||
throw new Error(`${address} can't be deleted cause it was not found`)
|
||||
throw new Error(`${address} can't be deleted cause it was not found`);
|
||||
}
|
||||
delete identities[address]
|
||||
delete accountTokens[address]
|
||||
delete accountHiddenTokens[address]
|
||||
this.store.updateState({ identities, accountTokens, accountHiddenTokens })
|
||||
delete identities[address];
|
||||
delete accountTokens[address];
|
||||
delete accountHiddenTokens[address];
|
||||
this.store.updateState({ identities, accountTokens, accountHiddenTokens });
|
||||
|
||||
// If the selected account is no longer valid,
|
||||
// select an arbitrary other account:
|
||||
if (address === this.getSelectedAddress()) {
|
||||
const selected = Object.keys(identities)[0]
|
||||
this.setSelectedAddress(selected)
|
||||
const selected = Object.keys(identities)[0];
|
||||
this.setSelectedAddress(selected);
|
||||
}
|
||||
return address
|
||||
return address;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -254,20 +254,20 @@ export default class PreferencesController {
|
||||
identities,
|
||||
accountTokens,
|
||||
accountHiddenTokens,
|
||||
} = this.store.getState()
|
||||
} = this.store.getState();
|
||||
addresses.forEach((address) => {
|
||||
// skip if already exists
|
||||
if (identities[address]) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
// add missing identity
|
||||
const identityCount = Object.keys(identities).length
|
||||
const identityCount = Object.keys(identities).length;
|
||||
|
||||
accountTokens[address] = {}
|
||||
accountHiddenTokens[address] = {}
|
||||
identities[address] = { name: `Account ${identityCount + 1}`, address }
|
||||
})
|
||||
this.store.updateState({ identities, accountTokens, accountHiddenTokens })
|
||||
accountTokens[address] = {};
|
||||
accountHiddenTokens[address] = {};
|
||||
identities[address] = { name: `Account ${identityCount + 1}`, address };
|
||||
});
|
||||
this.store.updateState({ identities, accountTokens, accountHiddenTokens });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -279,46 +279,46 @@ export default class PreferencesController {
|
||||
*/
|
||||
syncAddresses(addresses) {
|
||||
if (!Array.isArray(addresses) || addresses.length === 0) {
|
||||
throw new Error('Expected non-empty array of addresses.')
|
||||
throw new Error('Expected non-empty array of addresses.');
|
||||
}
|
||||
|
||||
const { identities, lostIdentities } = this.store.getState()
|
||||
const { identities, lostIdentities } = this.store.getState();
|
||||
|
||||
const newlyLost = {}
|
||||
const newlyLost = {};
|
||||
Object.keys(identities).forEach((identity) => {
|
||||
if (!addresses.includes(identity)) {
|
||||
newlyLost[identity] = identities[identity]
|
||||
delete identities[identity]
|
||||
newlyLost[identity] = identities[identity];
|
||||
delete identities[identity];
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Identities are no longer present.
|
||||
if (Object.keys(newlyLost).length > 0) {
|
||||
// store lost accounts
|
||||
Object.keys(newlyLost).forEach((key) => {
|
||||
lostIdentities[key] = newlyLost[key]
|
||||
})
|
||||
lostIdentities[key] = newlyLost[key];
|
||||
});
|
||||
}
|
||||
|
||||
this.store.updateState({ identities, lostIdentities })
|
||||
this.addAddresses(addresses)
|
||||
this.store.updateState({ identities, lostIdentities });
|
||||
this.addAddresses(addresses);
|
||||
|
||||
// If the selected account is no longer valid,
|
||||
// select an arbitrary other account:
|
||||
let selected = this.getSelectedAddress()
|
||||
let selected = this.getSelectedAddress();
|
||||
if (!addresses.includes(selected)) {
|
||||
selected = addresses[0]
|
||||
this.setSelectedAddress(selected)
|
||||
selected = addresses[0];
|
||||
this.setSelectedAddress(selected);
|
||||
}
|
||||
|
||||
return selected
|
||||
return selected;
|
||||
}
|
||||
|
||||
removeSuggestedTokens() {
|
||||
return new Promise((resolve) => {
|
||||
this.store.updateState({ suggestedTokens: {} })
|
||||
resolve({})
|
||||
})
|
||||
this.store.updateState({ suggestedTokens: {} });
|
||||
resolve({});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -329,18 +329,18 @@ export default class PreferencesController {
|
||||
*
|
||||
*/
|
||||
setSelectedAddress(_address) {
|
||||
const address = normalizeAddress(_address)
|
||||
this._updateTokens(address)
|
||||
const address = normalizeAddress(_address);
|
||||
this._updateTokens(address);
|
||||
|
||||
const { identities, tokens } = this.store.getState()
|
||||
const selectedIdentity = identities[address]
|
||||
const { identities, tokens } = this.store.getState();
|
||||
const selectedIdentity = identities[address];
|
||||
if (!selectedIdentity) {
|
||||
throw new Error(`Identity for '${address} not found`)
|
||||
throw new Error(`Identity for '${address} not found`);
|
||||
}
|
||||
|
||||
selectedIdentity.lastSelected = Date.now()
|
||||
this.store.updateState({ identities, selectedAddress: address })
|
||||
return Promise.resolve(tokens)
|
||||
selectedIdentity.lastSelected = Date.now();
|
||||
this.store.updateState({ identities, selectedAddress: address });
|
||||
return Promise.resolve(tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -350,7 +350,7 @@ export default class PreferencesController {
|
||||
*
|
||||
*/
|
||||
getSelectedAddress() {
|
||||
return this.store.getState().selectedAddress
|
||||
return this.store.getState().selectedAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -375,26 +375,26 @@ export default class PreferencesController {
|
||||
*
|
||||
*/
|
||||
async addToken(rawAddress, symbol, decimals, image) {
|
||||
const address = normalizeAddress(rawAddress)
|
||||
const newEntry = { address, symbol, decimals }
|
||||
const { tokens, hiddenTokens } = this.store.getState()
|
||||
const assetImages = this.getAssetImages()
|
||||
const address = normalizeAddress(rawAddress);
|
||||
const newEntry = { address, symbol, decimals };
|
||||
const { tokens, hiddenTokens } = this.store.getState();
|
||||
const assetImages = this.getAssetImages();
|
||||
const updatedHiddenTokens = hiddenTokens.filter(
|
||||
(tokenAddress) => tokenAddress !== rawAddress.toLowerCase(),
|
||||
)
|
||||
);
|
||||
const previousEntry = tokens.find((token) => {
|
||||
return token.address === address
|
||||
})
|
||||
const previousIndex = tokens.indexOf(previousEntry)
|
||||
return token.address === address;
|
||||
});
|
||||
const previousIndex = tokens.indexOf(previousEntry);
|
||||
|
||||
if (previousEntry) {
|
||||
tokens[previousIndex] = newEntry
|
||||
tokens[previousIndex] = newEntry;
|
||||
} else {
|
||||
tokens.push(newEntry)
|
||||
tokens.push(newEntry);
|
||||
}
|
||||
assetImages[address] = image
|
||||
this._updateAccountTokens(tokens, assetImages, updatedHiddenTokens)
|
||||
return Promise.resolve(tokens)
|
||||
assetImages[address] = image;
|
||||
this._updateAccountTokens(tokens, assetImages, updatedHiddenTokens);
|
||||
return Promise.resolve(tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -405,13 +405,15 @@ export default class PreferencesController {
|
||||
*
|
||||
*/
|
||||
removeToken(rawAddress) {
|
||||
const { tokens, hiddenTokens } = this.store.getState()
|
||||
const assetImages = this.getAssetImages()
|
||||
const updatedTokens = tokens.filter((token) => token.address !== rawAddress)
|
||||
const updatedHiddenTokens = [...hiddenTokens, rawAddress.toLowerCase()]
|
||||
delete assetImages[rawAddress]
|
||||
this._updateAccountTokens(updatedTokens, assetImages, updatedHiddenTokens)
|
||||
return Promise.resolve(updatedTokens)
|
||||
const { tokens, hiddenTokens } = this.store.getState();
|
||||
const assetImages = this.getAssetImages();
|
||||
const updatedTokens = tokens.filter(
|
||||
(token) => token.address !== rawAddress,
|
||||
);
|
||||
const updatedHiddenTokens = [...hiddenTokens, rawAddress.toLowerCase()];
|
||||
delete assetImages[rawAddress];
|
||||
this._updateAccountTokens(updatedTokens, assetImages, updatedHiddenTokens);
|
||||
return Promise.resolve(updatedTokens);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -421,7 +423,7 @@ export default class PreferencesController {
|
||||
*
|
||||
*/
|
||||
getTokens() {
|
||||
return this.store.getState().tokens
|
||||
return this.store.getState().tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -434,14 +436,14 @@ export default class PreferencesController {
|
||||
if (!account) {
|
||||
throw new Error(
|
||||
`setAccountLabel requires a valid address, got ${String(account)}`,
|
||||
)
|
||||
);
|
||||
}
|
||||
const address = normalizeAddress(account)
|
||||
const { identities } = this.store.getState()
|
||||
identities[address] = identities[address] || {}
|
||||
identities[address].name = label
|
||||
this.store.updateState({ identities })
|
||||
return Promise.resolve(label)
|
||||
const address = normalizeAddress(account);
|
||||
const { identities } = this.store.getState();
|
||||
identities[address] = identities[address] || {};
|
||||
identities[address].name = label;
|
||||
this.store.updateState({ identities });
|
||||
return Promise.resolve(label);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -456,33 +458,33 @@ export default class PreferencesController {
|
||||
*
|
||||
*/
|
||||
async updateRpc(newRpcDetails) {
|
||||
const rpcList = this.getFrequentRpcListDetail()
|
||||
const rpcList = this.getFrequentRpcListDetail();
|
||||
const index = rpcList.findIndex((element) => {
|
||||
return element.rpcUrl === newRpcDetails.rpcUrl
|
||||
})
|
||||
return element.rpcUrl === newRpcDetails.rpcUrl;
|
||||
});
|
||||
if (index > -1) {
|
||||
const rpcDetail = rpcList[index]
|
||||
const updatedRpc = { ...rpcDetail, ...newRpcDetails }
|
||||
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
|
||||
// if the chainId is not set.
|
||||
|
||||
let addressBookKey = rpcDetail.chainId
|
||||
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,
|
||||
)
|
||||
);
|
||||
try {
|
||||
addressBookKey = await provider.send('net_version')
|
||||
assert(typeof addressBookKey === 'string')
|
||||
addressBookKey = await provider.send('net_version');
|
||||
assert(typeof addressBookKey === 'string');
|
||||
} catch (error) {
|
||||
log.debug(error)
|
||||
log.debug(error);
|
||||
log.warn(
|
||||
`Failed to get networkId from ${rpcDetail.rpcUrl}; skipping address book migration`,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -490,31 +492,37 @@ export default class PreferencesController {
|
||||
// value. In this case, the contact book entries are duplicated so that they remain
|
||||
// on both networks, since we don't know which network each contact is intended for.
|
||||
|
||||
let duplicate = false
|
||||
let duplicate = false;
|
||||
const builtInProviderNetworkIds = Object.values(
|
||||
NETWORK_TYPE_TO_ID_MAP,
|
||||
).map((ids) => ids.networkId)
|
||||
).map((ids) => ids.networkId);
|
||||
const otherRpcEntries = rpcList.filter(
|
||||
(entry) => entry.rpcUrl !== newRpcDetails.rpcUrl,
|
||||
)
|
||||
);
|
||||
if (
|
||||
builtInProviderNetworkIds.includes(addressBookKey) ||
|
||||
otherRpcEntries.some((entry) => entry.chainId === addressBookKey)
|
||||
) {
|
||||
duplicate = true
|
||||
duplicate = true;
|
||||
}
|
||||
|
||||
this.migrateAddressBookState(
|
||||
addressBookKey,
|
||||
updatedRpc.chainId,
|
||||
duplicate,
|
||||
)
|
||||
);
|
||||
}
|
||||
rpcList[index] = updatedRpc
|
||||
this.store.updateState({ frequentRpcListDetail: rpcList })
|
||||
rpcList[index] = updatedRpc;
|
||||
this.store.updateState({ frequentRpcListDetail: rpcList });
|
||||
} else {
|
||||
const { rpcUrl, chainId, ticker, nickname, rpcPrefs = {} } = newRpcDetails
|
||||
this.addToFrequentRpcList(rpcUrl, chainId, ticker, nickname, rpcPrefs)
|
||||
const {
|
||||
rpcUrl,
|
||||
chainId,
|
||||
ticker,
|
||||
nickname,
|
||||
rpcPrefs = {},
|
||||
} = newRpcDetails;
|
||||
this.addToFrequentRpcList(rpcUrl, chainId, ticker, nickname, rpcPrefs);
|
||||
}
|
||||
}
|
||||
|
||||
@ -535,21 +543,21 @@ export default class PreferencesController {
|
||||
nickname = '',
|
||||
rpcPrefs = {},
|
||||
) {
|
||||
const rpcList = this.getFrequentRpcListDetail()
|
||||
const rpcList = this.getFrequentRpcListDetail();
|
||||
|
||||
const index = rpcList.findIndex((element) => {
|
||||
return element.rpcUrl === rpcUrl
|
||||
})
|
||||
return element.rpcUrl === rpcUrl;
|
||||
});
|
||||
if (index !== -1) {
|
||||
rpcList.splice(index, 1)
|
||||
rpcList.splice(index, 1);
|
||||
}
|
||||
|
||||
if (!isPrefixedFormattedHexString(chainId)) {
|
||||
throw new Error(`Invalid chainId: "${chainId}"`)
|
||||
throw new Error(`Invalid chainId: "${chainId}"`);
|
||||
}
|
||||
|
||||
rpcList.push({ rpcUrl, chainId, ticker, nickname, rpcPrefs })
|
||||
this.store.updateState({ frequentRpcListDetail: rpcList })
|
||||
rpcList.push({ rpcUrl, chainId, ticker, nickname, rpcPrefs });
|
||||
this.store.updateState({ frequentRpcListDetail: rpcList });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -560,15 +568,15 @@ export default class PreferencesController {
|
||||
*
|
||||
*/
|
||||
removeFromFrequentRpcList(url) {
|
||||
const rpcList = this.getFrequentRpcListDetail()
|
||||
const rpcList = this.getFrequentRpcListDetail();
|
||||
const index = rpcList.findIndex((element) => {
|
||||
return element.rpcUrl === url
|
||||
})
|
||||
return element.rpcUrl === url;
|
||||
});
|
||||
if (index !== -1) {
|
||||
rpcList.splice(index, 1)
|
||||
rpcList.splice(index, 1);
|
||||
}
|
||||
this.store.updateState({ frequentRpcListDetail: rpcList })
|
||||
return Promise.resolve(rpcList)
|
||||
this.store.updateState({ frequentRpcListDetail: rpcList });
|
||||
return Promise.resolve(rpcList);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -578,7 +586,7 @@ export default class PreferencesController {
|
||||
*
|
||||
*/
|
||||
getFrequentRpcListDetail() {
|
||||
return this.store.getState().frequentRpcListDetail
|
||||
return this.store.getState().frequentRpcListDetail;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -590,15 +598,15 @@ export default class PreferencesController {
|
||||
*
|
||||
*/
|
||||
setFeatureFlag(feature, activated) {
|
||||
const currentFeatureFlags = this.store.getState().featureFlags
|
||||
const currentFeatureFlags = this.store.getState().featureFlags;
|
||||
const updatedFeatureFlags = {
|
||||
...currentFeatureFlags,
|
||||
[feature]: activated,
|
||||
}
|
||||
};
|
||||
|
||||
this.store.updateState({ featureFlags: updatedFeatureFlags })
|
||||
this.store.updateState({ featureFlags: updatedFeatureFlags });
|
||||
|
||||
return Promise.resolve(updatedFeatureFlags)
|
||||
return Promise.resolve(updatedFeatureFlags);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -609,14 +617,14 @@ export default class PreferencesController {
|
||||
* @returns {Promise<object>} Promises a new object; the updated preferences object.
|
||||
*/
|
||||
setPreference(preference, value) {
|
||||
const currentPreferences = this.getPreferences()
|
||||
const currentPreferences = this.getPreferences();
|
||||
const updatedPreferences = {
|
||||
...currentPreferences,
|
||||
[preference]: value,
|
||||
}
|
||||
};
|
||||
|
||||
this.store.updateState({ preferences: updatedPreferences })
|
||||
return Promise.resolve(updatedPreferences)
|
||||
this.store.updateState({ preferences: updatedPreferences });
|
||||
return Promise.resolve(updatedPreferences);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -624,7 +632,7 @@ export default class PreferencesController {
|
||||
* @returns {Object} A key-boolean map of user-selected preferences.
|
||||
*/
|
||||
getPreferences() {
|
||||
return this.store.getState().preferences
|
||||
return this.store.getState().preferences;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -632,8 +640,8 @@ export default class PreferencesController {
|
||||
* onboarding process.
|
||||
*/
|
||||
completeOnboarding() {
|
||||
this.store.updateState({ completedOnboarding: true })
|
||||
return Promise.resolve(true)
|
||||
this.store.updateState({ completedOnboarding: true });
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -641,7 +649,7 @@ export default class PreferencesController {
|
||||
* @returns {string} The current IPFS gateway domain
|
||||
*/
|
||||
getIpfsGateway() {
|
||||
return this.store.getState().ipfsGateway
|
||||
return this.store.getState().ipfsGateway;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -650,8 +658,8 @@ export default class PreferencesController {
|
||||
* @returns {Promise<string>} A promise of the update IPFS gateway domain
|
||||
*/
|
||||
setIpfsGateway(domain) {
|
||||
this.store.updateState({ ipfsGateway: domain })
|
||||
return Promise.resolve(domain)
|
||||
this.store.updateState({ ipfsGateway: domain });
|
||||
return Promise.resolve(domain);
|
||||
}
|
||||
|
||||
//
|
||||
@ -665,9 +673,9 @@ export default class PreferencesController {
|
||||
*/
|
||||
_subscribeProviderType() {
|
||||
this.network.providerStore.subscribe(() => {
|
||||
const { tokens, hiddenTokens } = this._getTokenRelatedStates()
|
||||
this._updateAccountTokens(tokens, this.getAssetImages(), hiddenTokens)
|
||||
})
|
||||
const { tokens, hiddenTokens } = this._getTokenRelatedStates();
|
||||
this._updateAccountTokens(tokens, this.getAssetImages(), hiddenTokens);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -684,16 +692,16 @@ export default class PreferencesController {
|
||||
providerType,
|
||||
selectedAddress,
|
||||
accountHiddenTokens,
|
||||
} = this._getTokenRelatedStates()
|
||||
accountTokens[selectedAddress][providerType] = tokens
|
||||
accountHiddenTokens[selectedAddress][providerType] = hiddenTokens
|
||||
} = this._getTokenRelatedStates();
|
||||
accountTokens[selectedAddress][providerType] = tokens;
|
||||
accountHiddenTokens[selectedAddress][providerType] = hiddenTokens;
|
||||
this.store.updateState({
|
||||
accountTokens,
|
||||
tokens,
|
||||
assetImages,
|
||||
accountHiddenTokens,
|
||||
hiddenTokens,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -705,8 +713,8 @@ export default class PreferencesController {
|
||||
_updateTokens(selectedAddress) {
|
||||
const { tokens, hiddenTokens } = this._getTokenRelatedStates(
|
||||
selectedAddress,
|
||||
)
|
||||
this.store.updateState({ tokens, hiddenTokens })
|
||||
);
|
||||
this.store.updateState({ tokens, hiddenTokens });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -717,26 +725,26 @@ export default class PreferencesController {
|
||||
*
|
||||
*/
|
||||
_getTokenRelatedStates(selectedAddress) {
|
||||
const { accountTokens, accountHiddenTokens } = this.store.getState()
|
||||
const { accountTokens, accountHiddenTokens } = this.store.getState();
|
||||
if (!selectedAddress) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
selectedAddress = this.store.getState().selectedAddress
|
||||
selectedAddress = this.store.getState().selectedAddress;
|
||||
}
|
||||
const providerType = this.network.providerStore.getState().type
|
||||
const providerType = this.network.providerStore.getState().type;
|
||||
if (!(selectedAddress in accountTokens)) {
|
||||
accountTokens[selectedAddress] = {}
|
||||
accountTokens[selectedAddress] = {};
|
||||
}
|
||||
if (!(selectedAddress in accountHiddenTokens)) {
|
||||
accountHiddenTokens[selectedAddress] = {}
|
||||
accountHiddenTokens[selectedAddress] = {};
|
||||
}
|
||||
if (!(providerType in accountTokens[selectedAddress])) {
|
||||
accountTokens[selectedAddress][providerType] = []
|
||||
accountTokens[selectedAddress][providerType] = [];
|
||||
}
|
||||
if (!(providerType in accountHiddenTokens[selectedAddress])) {
|
||||
accountHiddenTokens[selectedAddress][providerType] = []
|
||||
accountHiddenTokens[selectedAddress][providerType] = [];
|
||||
}
|
||||
const tokens = accountTokens[selectedAddress][providerType]
|
||||
const hiddenTokens = accountHiddenTokens[selectedAddress][providerType]
|
||||
const tokens = accountTokens[selectedAddress][providerType];
|
||||
const hiddenTokens = accountHiddenTokens[selectedAddress][providerType];
|
||||
return {
|
||||
tokens,
|
||||
accountTokens,
|
||||
@ -744,7 +752,7 @@ export default class PreferencesController {
|
||||
accountHiddenTokens,
|
||||
providerType,
|
||||
selectedAddress,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -754,17 +762,17 @@ export default class PreferencesController {
|
||||
*
|
||||
*/
|
||||
async _handleWatchAssetERC20(tokenMetadata) {
|
||||
this._validateERC20AssetParams(tokenMetadata)
|
||||
this._validateERC20AssetParams(tokenMetadata);
|
||||
|
||||
const address = normalizeAddress(tokenMetadata.address)
|
||||
const { symbol, decimals, image } = tokenMetadata
|
||||
this._addSuggestedERC20Asset(address, symbol, decimals, image)
|
||||
const address = normalizeAddress(tokenMetadata.address);
|
||||
const { symbol, decimals, image } = tokenMetadata;
|
||||
this._addSuggestedERC20Asset(address, symbol, decimals, image);
|
||||
|
||||
await this.openPopup()
|
||||
await this.openPopup();
|
||||
const tokenAddresses = this.getTokens().filter(
|
||||
(token) => token.address === address,
|
||||
)
|
||||
return tokenAddresses.length > 0
|
||||
);
|
||||
return tokenAddresses.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -779,24 +787,24 @@ export default class PreferencesController {
|
||||
if (!address || !symbol || typeof decimals === 'undefined') {
|
||||
throw ethErrors.rpc.invalidParams(
|
||||
`Must specify address, symbol, and decimals.`,
|
||||
)
|
||||
);
|
||||
}
|
||||
if (typeof symbol !== 'string') {
|
||||
throw ethErrors.rpc.invalidParams(`Invalid symbol: not a string.`)
|
||||
throw ethErrors.rpc.invalidParams(`Invalid symbol: not a string.`);
|
||||
}
|
||||
if (!(symbol.length < 7)) {
|
||||
throw ethErrors.rpc.invalidParams(
|
||||
`Invalid symbol "${symbol}": longer than 6 characters.`,
|
||||
)
|
||||
);
|
||||
}
|
||||
const numDecimals = parseInt(decimals, 10)
|
||||
const numDecimals = parseInt(decimals, 10);
|
||||
if (isNaN(numDecimals) || numDecimals > 36 || numDecimals < 0) {
|
||||
throw ethErrors.rpc.invalidParams(
|
||||
`Invalid decimals "${decimals}": must be 0 <= 36.`,
|
||||
)
|
||||
);
|
||||
}
|
||||
if (!isValidAddress(address)) {
|
||||
throw ethErrors.rpc.invalidParams(`Invalid address "${address}".`)
|
||||
throw ethErrors.rpc.invalidParams(`Invalid address "${address}".`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -807,9 +815,9 @@ export default class PreferencesController {
|
||||
decimals,
|
||||
image,
|
||||
unlisted: !LISTED_CONTRACT_ADDRESSES.includes(address),
|
||||
}
|
||||
const suggested = this.getSuggestedTokens()
|
||||
suggested[address] = newEntry
|
||||
this.store.updateState({ suggestedTokens: suggested })
|
||||
};
|
||||
const suggested = this.getSuggestedTokens();
|
||||
suggested[address] = newEntry;
|
||||
this.store.updateState({ suggestedTokens: suggested });
|
||||
}
|
||||
}
|
||||
|
@ -1,54 +1,57 @@
|
||||
import { ethers } from 'ethers'
|
||||
import log from 'loglevel'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { ObservableStore } from '@metamask/obs-store'
|
||||
import { mapValues, cloneDeep } from 'lodash'
|
||||
import abi from 'human-standard-token-abi'
|
||||
import { calcTokenAmount } from '../../../ui/app/helpers/utils/token-util'
|
||||
import { calcGasTotal } from '../../../ui/app/pages/send/send.utils'
|
||||
import { conversionUtil } from '../../../ui/app/helpers/utils/conversion-util'
|
||||
import { ethers } from 'ethers';
|
||||
import log from 'loglevel';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { ObservableStore } from '@metamask/obs-store';
|
||||
import { mapValues, cloneDeep } from 'lodash';
|
||||
import abi from 'human-standard-token-abi';
|
||||
import { calcTokenAmount } from '../../../ui/app/helpers/utils/token-util';
|
||||
import { calcGasTotal } from '../../../ui/app/pages/send/send.utils';
|
||||
import { conversionUtil } from '../../../ui/app/helpers/utils/conversion-util';
|
||||
import {
|
||||
ETH_SWAPS_TOKEN_ADDRESS,
|
||||
DEFAULT_ERC20_APPROVE_GAS,
|
||||
QUOTES_EXPIRED_ERROR,
|
||||
QUOTES_NOT_AVAILABLE_ERROR,
|
||||
SWAPS_FETCH_ORDER_CONFLICT,
|
||||
} from '../../../ui/app/helpers/constants/swaps'
|
||||
} from '../../../ui/app/helpers/constants/swaps';
|
||||
import {
|
||||
fetchTradesInfo as defaultFetchTradesInfo,
|
||||
fetchSwapsFeatureLiveness as defaultFetchSwapsFeatureLiveness,
|
||||
fetchSwapsQuoteRefreshTime as defaultFetchSwapsQuoteRefreshTime,
|
||||
} from '../../../ui/app/pages/swaps/swaps.util'
|
||||
} from '../../../ui/app/pages/swaps/swaps.util';
|
||||
|
||||
const METASWAP_ADDRESS = '0x881d40237659c251811cec9c364ef91dc08d300c'
|
||||
const METASWAP_ADDRESS = '0x881d40237659c251811cec9c364ef91dc08d300c';
|
||||
|
||||
// The MAX_GAS_LIMIT is a number that is higher than the maximum gas costs we have observed on any aggregator
|
||||
const MAX_GAS_LIMIT = 2500000
|
||||
const MAX_GAS_LIMIT = 2500000;
|
||||
|
||||
// To ensure that our serves are not spammed if MetaMask is left idle, we limit the number of fetches for quotes that are made on timed intervals.
|
||||
// 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
|
||||
const POLL_COUNT_LIMIT = 3;
|
||||
|
||||
// If for any reason the MetaSwap API fails to provide a refresh time,
|
||||
// provide a reasonable fallback to avoid further errors
|
||||
const FALLBACK_QUOTE_REFRESH_TIME = 60000
|
||||
const FALLBACK_QUOTE_REFRESH_TIME = 60000;
|
||||
|
||||
// This is the amount of time to wait, after successfully fetching quotes
|
||||
// and their gas estimates, before fetching for new quotes
|
||||
const QUOTE_POLLING_DIFFERENCE_INTERVAL = 10 * 1000
|
||||
const QUOTE_POLLING_DIFFERENCE_INTERVAL = 10 * 1000;
|
||||
|
||||
function calculateGasEstimateWithRefund(
|
||||
maxGas = MAX_GAS_LIMIT,
|
||||
estimatedRefund = 0,
|
||||
estimatedGas = 0,
|
||||
) {
|
||||
const maxGasMinusRefund = new BigNumber(maxGas, 10).minus(estimatedRefund, 10)
|
||||
const maxGasMinusRefund = new BigNumber(maxGas, 10).minus(
|
||||
estimatedRefund,
|
||||
10,
|
||||
);
|
||||
|
||||
const gasEstimateWithRefund = maxGasMinusRefund.lt(estimatedGas, 16)
|
||||
? maxGasMinusRefund.toString(16)
|
||||
: estimatedGas
|
||||
: estimatedGas;
|
||||
|
||||
return gasEstimateWithRefund
|
||||
return gasEstimateWithRefund;
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
@ -69,7 +72,7 @@ const initialState = {
|
||||
swapsFeatureIsLive: false,
|
||||
swapsQuoteRefreshTime: FALLBACK_QUOTE_REFRESH_TIME,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default class SwapsController {
|
||||
constructor({
|
||||
@ -84,46 +87,46 @@ export default class SwapsController {
|
||||
}) {
|
||||
this.store = new ObservableStore({
|
||||
swapsState: { ...initialState.swapsState },
|
||||
})
|
||||
});
|
||||
|
||||
this._fetchTradesInfo = fetchTradesInfo
|
||||
this._fetchSwapsFeatureLiveness = fetchSwapsFeatureLiveness
|
||||
this._fetchSwapsQuoteRefreshTime = fetchSwapsQuoteRefreshTime
|
||||
this._fetchTradesInfo = fetchTradesInfo;
|
||||
this._fetchSwapsFeatureLiveness = fetchSwapsFeatureLiveness;
|
||||
this._fetchSwapsQuoteRefreshTime = fetchSwapsQuoteRefreshTime;
|
||||
|
||||
this.getBufferedGasLimit = getBufferedGasLimit
|
||||
this.tokenRatesStore = tokenRatesStore
|
||||
this.getBufferedGasLimit = getBufferedGasLimit;
|
||||
this.tokenRatesStore = tokenRatesStore;
|
||||
|
||||
this.pollCount = 0
|
||||
this.getProviderConfig = getProviderConfig
|
||||
this.pollCount = 0;
|
||||
this.getProviderConfig = getProviderConfig;
|
||||
|
||||
this.indexOfNewestCallInFlight = 0
|
||||
this.indexOfNewestCallInFlight = 0;
|
||||
|
||||
this.ethersProvider = new ethers.providers.Web3Provider(provider)
|
||||
this._currentNetwork = networkController.store.getState().network
|
||||
this.ethersProvider = new ethers.providers.Web3Provider(provider);
|
||||
this._currentNetwork = networkController.store.getState().network;
|
||||
networkController.on('networkDidChange', (network) => {
|
||||
if (network !== 'loading' && network !== this._currentNetwork) {
|
||||
this._currentNetwork = network
|
||||
this.ethersProvider = new ethers.providers.Web3Provider(provider)
|
||||
this._currentNetwork = network;
|
||||
this.ethersProvider = new ethers.providers.Web3Provider(provider);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this._setupSwapsLivenessFetching()
|
||||
this._setupSwapsLivenessFetching();
|
||||
}
|
||||
|
||||
// Sets the refresh rate for quote updates from the MetaSwap API
|
||||
async _setSwapsQuoteRefreshTime() {
|
||||
// Default to fallback time unless API returns valid response
|
||||
let swapsQuoteRefreshTime = FALLBACK_QUOTE_REFRESH_TIME
|
||||
let swapsQuoteRefreshTime = FALLBACK_QUOTE_REFRESH_TIME;
|
||||
try {
|
||||
swapsQuoteRefreshTime = await this._fetchSwapsQuoteRefreshTime()
|
||||
swapsQuoteRefreshTime = await this._fetchSwapsQuoteRefreshTime();
|
||||
} catch (e) {
|
||||
console.error('Request for swaps quote refresh time failed: ', e)
|
||||
console.error('Request for swaps quote refresh time failed: ', e);
|
||||
}
|
||||
|
||||
const { swapsState } = this.store.getState()
|
||||
const { swapsState } = this.store.getState();
|
||||
this.store.updateState({
|
||||
swapsState: { ...swapsState, swapsQuoteRefreshTime },
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Once quotes are fetched, we poll for new ones to keep the quotes up to date. Market and aggregator contract conditions can change fast enough
|
||||
@ -133,20 +136,20 @@ export default class SwapsController {
|
||||
pollForNewQuotes() {
|
||||
const {
|
||||
swapsState: { swapsQuoteRefreshTime },
|
||||
} = this.store.getState()
|
||||
} = this.store.getState();
|
||||
|
||||
this.pollingTimeout = setTimeout(() => {
|
||||
const { swapsState } = this.store.getState()
|
||||
const { swapsState } = this.store.getState();
|
||||
this.fetchAndSetQuotes(
|
||||
swapsState.fetchParams,
|
||||
swapsState.fetchParams?.metaData,
|
||||
true,
|
||||
)
|
||||
}, swapsQuoteRefreshTime - QUOTE_POLLING_DIFFERENCE_INTERVAL)
|
||||
);
|
||||
}, swapsQuoteRefreshTime - QUOTE_POLLING_DIFFERENCE_INTERVAL);
|
||||
}
|
||||
|
||||
stopPollingForQuotes() {
|
||||
clearTimeout(this.pollingTimeout)
|
||||
clearTimeout(this.pollingTimeout);
|
||||
}
|
||||
|
||||
async fetchAndSetQuotes(
|
||||
@ -155,37 +158,37 @@ export default class SwapsController {
|
||||
isPolledRequest,
|
||||
) {
|
||||
if (!fetchParams) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
// Every time we get a new request that is not from the polling, we reset the poll count so we can poll for up to three more sets of quotes with these new params.
|
||||
if (!isPolledRequest) {
|
||||
this.pollCount = 0
|
||||
this.pollCount = 0;
|
||||
}
|
||||
|
||||
// If there are any pending poll requests, clear them so that they don't get call while this new fetch is in process
|
||||
clearTimeout(this.pollingTimeout)
|
||||
clearTimeout(this.pollingTimeout);
|
||||
|
||||
if (!isPolledRequest) {
|
||||
this.setSwapsErrorKey('')
|
||||
this.setSwapsErrorKey('');
|
||||
}
|
||||
|
||||
const indexOfCurrentCall = this.indexOfNewestCallInFlight + 1
|
||||
this.indexOfNewestCallInFlight = indexOfCurrentCall
|
||||
const indexOfCurrentCall = this.indexOfNewestCallInFlight + 1;
|
||||
this.indexOfNewestCallInFlight = indexOfCurrentCall;
|
||||
|
||||
let [newQuotes] = await Promise.all([
|
||||
this._fetchTradesInfo(fetchParams),
|
||||
this._setSwapsQuoteRefreshTime(),
|
||||
])
|
||||
]);
|
||||
|
||||
newQuotes = mapValues(newQuotes, (quote) => ({
|
||||
...quote,
|
||||
sourceTokenInfo: fetchParamsMetaData.sourceTokenInfo,
|
||||
destinationTokenInfo: fetchParamsMetaData.destinationTokenInfo,
|
||||
}))
|
||||
}));
|
||||
|
||||
const quotesLastFetched = Date.now()
|
||||
const quotesLastFetched = Date.now();
|
||||
|
||||
let approvalRequired = false
|
||||
let approvalRequired = false;
|
||||
if (
|
||||
fetchParams.sourceToken !== ETH_SWAPS_TOKEN_ADDRESS &&
|
||||
Object.values(newQuotes).length
|
||||
@ -193,22 +196,22 @@ export default class SwapsController {
|
||||
const allowance = await this._getERC20Allowance(
|
||||
fetchParams.sourceToken,
|
||||
fetchParams.fromAddress,
|
||||
)
|
||||
);
|
||||
|
||||
// For a user to be able to swap a token, they need to have approved the MetaSwap contract to withdraw that token.
|
||||
// _getERC20Allowance() returns the amount of the token they have approved for withdrawal. If that amount is greater
|
||||
// than 0, it means that approval has already occured and is not needed. Otherwise, for tokens to be swapped, a new
|
||||
// call of the ERC-20 approve method is required.
|
||||
approvalRequired = allowance.eq(0)
|
||||
approvalRequired = allowance.eq(0);
|
||||
if (!approvalRequired) {
|
||||
newQuotes = mapValues(newQuotes, (quote) => ({
|
||||
...quote,
|
||||
approvalNeeded: null,
|
||||
}))
|
||||
}));
|
||||
} else if (!isPolledRequest) {
|
||||
const { gasLimit: approvalGas } = await this.timedoutGasReturn(
|
||||
Object.values(newQuotes)[0].approvalNeeded,
|
||||
)
|
||||
);
|
||||
|
||||
newQuotes = mapValues(newQuotes, (quote) => ({
|
||||
...quote,
|
||||
@ -216,39 +219,39 @@ export default class SwapsController {
|
||||
...quote.approvalNeeded,
|
||||
gas: approvalGas || DEFAULT_ERC20_APPROVE_GAS,
|
||||
},
|
||||
}))
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
let topAggId = null
|
||||
let topAggId = null;
|
||||
|
||||
// We can reduce time on the loading screen by only doing this after the
|
||||
// loading screen and best quote have rendered.
|
||||
if (!approvalRequired && !fetchParams?.balanceError) {
|
||||
newQuotes = await this.getAllQuotesWithGasEstimates(newQuotes)
|
||||
newQuotes = await this.getAllQuotesWithGasEstimates(newQuotes);
|
||||
}
|
||||
|
||||
if (Object.values(newQuotes).length === 0) {
|
||||
this.setSwapsErrorKey(QUOTES_NOT_AVAILABLE_ERROR)
|
||||
this.setSwapsErrorKey(QUOTES_NOT_AVAILABLE_ERROR);
|
||||
} else {
|
||||
const [
|
||||
_topAggId,
|
||||
quotesWithSavingsAndFeeData,
|
||||
] = await this._findTopQuoteAndCalculateSavings(newQuotes)
|
||||
topAggId = _topAggId
|
||||
newQuotes = quotesWithSavingsAndFeeData
|
||||
] = await this._findTopQuoteAndCalculateSavings(newQuotes);
|
||||
topAggId = _topAggId;
|
||||
newQuotes = quotesWithSavingsAndFeeData;
|
||||
}
|
||||
|
||||
// If a newer call has been made, don't update state with old information
|
||||
// Prevents timing conflicts between fetches
|
||||
if (this.indexOfNewestCallInFlight !== indexOfCurrentCall) {
|
||||
throw new Error(SWAPS_FETCH_ORDER_CONFLICT)
|
||||
throw new Error(SWAPS_FETCH_ORDER_CONFLICT);
|
||||
}
|
||||
|
||||
const { swapsState } = this.store.getState()
|
||||
let { selectedAggId } = swapsState
|
||||
const { swapsState } = this.store.getState();
|
||||
let { selectedAggId } = swapsState;
|
||||
if (!newQuotes[selectedAggId]) {
|
||||
selectedAggId = null
|
||||
selectedAggId = null;
|
||||
}
|
||||
|
||||
this.store.updateState({
|
||||
@ -260,41 +263,41 @@ export default class SwapsController {
|
||||
selectedAggId,
|
||||
topAggId,
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
// We only want to do up to a maximum of three requests from polling.
|
||||
this.pollCount += 1
|
||||
this.pollCount += 1;
|
||||
if (this.pollCount < POLL_COUNT_LIMIT + 1) {
|
||||
this.pollForNewQuotes()
|
||||
this.pollForNewQuotes();
|
||||
} else {
|
||||
this.resetPostFetchState()
|
||||
this.setSwapsErrorKey(QUOTES_EXPIRED_ERROR)
|
||||
return null
|
||||
this.resetPostFetchState();
|
||||
this.setSwapsErrorKey(QUOTES_EXPIRED_ERROR);
|
||||
return null;
|
||||
}
|
||||
|
||||
return [newQuotes, topAggId]
|
||||
return [newQuotes, topAggId];
|
||||
}
|
||||
|
||||
safeRefetchQuotes() {
|
||||
const { swapsState } = this.store.getState()
|
||||
const { swapsState } = this.store.getState();
|
||||
if (!this.pollingTimeout && swapsState.fetchParams) {
|
||||
this.fetchAndSetQuotes(swapsState.fetchParams)
|
||||
this.fetchAndSetQuotes(swapsState.fetchParams);
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedQuoteAggId(selectedAggId) {
|
||||
const { swapsState } = this.store.getState()
|
||||
this.store.updateState({ swapsState: { ...swapsState, selectedAggId } })
|
||||
const { swapsState } = this.store.getState();
|
||||
this.store.updateState({ swapsState: { ...swapsState, selectedAggId } });
|
||||
}
|
||||
|
||||
setSwapsTokens(tokens) {
|
||||
const { swapsState } = this.store.getState()
|
||||
this.store.updateState({ swapsState: { ...swapsState, tokens } })
|
||||
const { swapsState } = this.store.getState();
|
||||
this.store.updateState({ swapsState: { ...swapsState, tokens } });
|
||||
}
|
||||
|
||||
setSwapsErrorKey(errorKey) {
|
||||
const { swapsState } = this.store.getState()
|
||||
this.store.updateState({ swapsState: { ...swapsState, errorKey } })
|
||||
const { swapsState } = this.store.getState();
|
||||
this.store.updateState({ swapsState: { ...swapsState, errorKey } });
|
||||
}
|
||||
|
||||
async getAllQuotesWithGasEstimates(quotes) {
|
||||
@ -302,43 +305,43 @@ export default class SwapsController {
|
||||
Object.values(quotes).map(async (quote) => {
|
||||
const { gasLimit, simulationFails } = await this.timedoutGasReturn(
|
||||
quote.trade,
|
||||
)
|
||||
return [gasLimit, simulationFails, quote.aggregator]
|
||||
);
|
||||
return [gasLimit, simulationFails, quote.aggregator];
|
||||
}),
|
||||
)
|
||||
);
|
||||
|
||||
const newQuotes = {}
|
||||
const newQuotes = {};
|
||||
quoteGasData.forEach(([gasLimit, simulationFails, aggId]) => {
|
||||
if (gasLimit && !simulationFails) {
|
||||
const gasEstimateWithRefund = calculateGasEstimateWithRefund(
|
||||
quotes[aggId].maxGas,
|
||||
quotes[aggId].estimatedRefund,
|
||||
gasLimit,
|
||||
)
|
||||
);
|
||||
|
||||
newQuotes[aggId] = {
|
||||
...quotes[aggId],
|
||||
gasEstimate: gasLimit,
|
||||
gasEstimateWithRefund,
|
||||
}
|
||||
};
|
||||
} else if (quotes[aggId].approvalNeeded) {
|
||||
// If gas estimation fails, but an ERC-20 approve is needed, then we do not add any estimate property to the quote object
|
||||
// Such quotes will rely on the maxGas and averageGas properties from the api
|
||||
newQuotes[aggId] = quotes[aggId]
|
||||
newQuotes[aggId] = quotes[aggId];
|
||||
}
|
||||
// If gas estimation fails and no approval is needed, then we filter that quote out, so that it is not shown to the user
|
||||
})
|
||||
return newQuotes
|
||||
});
|
||||
return newQuotes;
|
||||
}
|
||||
|
||||
timedoutGasReturn(tradeTxParams) {
|
||||
return new Promise((resolve) => {
|
||||
let gasTimedOut = false
|
||||
let gasTimedOut = false;
|
||||
|
||||
const gasTimeout = setTimeout(() => {
|
||||
gasTimedOut = true
|
||||
resolve({ gasLimit: null, simulationFails: true })
|
||||
}, 5000)
|
||||
gasTimedOut = true;
|
||||
resolve({ gasLimit: null, simulationFails: true });
|
||||
}, 5000);
|
||||
|
||||
// Remove gas from params that will be passed to the `estimateGas` call
|
||||
// Including it can cause the estimate to fail if the actual gas needed
|
||||
@ -348,44 +351,44 @@ export default class SwapsController {
|
||||
from: tradeTxParams.from,
|
||||
to: tradeTxParams.to,
|
||||
value: tradeTxParams.value,
|
||||
}
|
||||
};
|
||||
|
||||
this.getBufferedGasLimit({ txParams: tradeTxParamsForGasEstimate }, 1)
|
||||
.then(({ gasLimit, simulationFails }) => {
|
||||
if (!gasTimedOut) {
|
||||
clearTimeout(gasTimeout)
|
||||
resolve({ gasLimit, simulationFails })
|
||||
clearTimeout(gasTimeout);
|
||||
resolve({ gasLimit, simulationFails });
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
log.error(e)
|
||||
log.error(e);
|
||||
if (!gasTimedOut) {
|
||||
clearTimeout(gasTimeout)
|
||||
resolve({ gasLimit: null, simulationFails: true })
|
||||
clearTimeout(gasTimeout);
|
||||
resolve({ gasLimit: null, simulationFails: true });
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async setInitialGasEstimate(initialAggId) {
|
||||
const { swapsState } = this.store.getState()
|
||||
const { swapsState } = this.store.getState();
|
||||
|
||||
const quoteToUpdate = { ...swapsState.quotes[initialAggId] }
|
||||
const quoteToUpdate = { ...swapsState.quotes[initialAggId] };
|
||||
|
||||
const {
|
||||
gasLimit: newGasEstimate,
|
||||
simulationFails,
|
||||
} = await this.timedoutGasReturn(quoteToUpdate.trade)
|
||||
} = await this.timedoutGasReturn(quoteToUpdate.trade);
|
||||
|
||||
if (newGasEstimate && !simulationFails) {
|
||||
const gasEstimateWithRefund = calculateGasEstimateWithRefund(
|
||||
quoteToUpdate.maxGas,
|
||||
quoteToUpdate.estimatedRefund,
|
||||
newGasEstimate,
|
||||
)
|
||||
);
|
||||
|
||||
quoteToUpdate.gasEstimate = newGasEstimate
|
||||
quoteToUpdate.gasEstimateWithRefund = gasEstimateWithRefund
|
||||
quoteToUpdate.gasEstimate = newGasEstimate;
|
||||
quoteToUpdate.gasEstimateWithRefund = gasEstimateWithRefund;
|
||||
}
|
||||
|
||||
this.store.updateState({
|
||||
@ -393,59 +396,61 @@ export default class SwapsController {
|
||||
...swapsState,
|
||||
quotes: { ...swapsState.quotes, [initialAggId]: quoteToUpdate },
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
setApproveTxId(approveTxId) {
|
||||
const { swapsState } = this.store.getState()
|
||||
this.store.updateState({ swapsState: { ...swapsState, approveTxId } })
|
||||
const { swapsState } = this.store.getState();
|
||||
this.store.updateState({ swapsState: { ...swapsState, approveTxId } });
|
||||
}
|
||||
|
||||
setTradeTxId(tradeTxId) {
|
||||
const { swapsState } = this.store.getState()
|
||||
this.store.updateState({ swapsState: { ...swapsState, tradeTxId } })
|
||||
const { swapsState } = this.store.getState();
|
||||
this.store.updateState({ swapsState: { ...swapsState, tradeTxId } });
|
||||
}
|
||||
|
||||
setQuotesLastFetched(quotesLastFetched) {
|
||||
const { swapsState } = this.store.getState()
|
||||
this.store.updateState({ swapsState: { ...swapsState, quotesLastFetched } })
|
||||
const { swapsState } = this.store.getState();
|
||||
this.store.updateState({
|
||||
swapsState: { ...swapsState, quotesLastFetched },
|
||||
});
|
||||
}
|
||||
|
||||
setSwapsTxGasPrice(gasPrice) {
|
||||
const { swapsState } = this.store.getState()
|
||||
const { swapsState } = this.store.getState();
|
||||
this.store.updateState({
|
||||
swapsState: { ...swapsState, customGasPrice: gasPrice },
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
setSwapsTxGasLimit(gasLimit) {
|
||||
const { swapsState } = this.store.getState()
|
||||
const { swapsState } = this.store.getState();
|
||||
this.store.updateState({
|
||||
swapsState: { ...swapsState, customMaxGas: gasLimit },
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
setCustomApproveTxData(data) {
|
||||
const { swapsState } = this.store.getState()
|
||||
const { swapsState } = this.store.getState();
|
||||
this.store.updateState({
|
||||
swapsState: { ...swapsState, customApproveTxData: data },
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
setBackgroundSwapRouteState(routeState) {
|
||||
const { swapsState } = this.store.getState()
|
||||
this.store.updateState({ swapsState: { ...swapsState, routeState } })
|
||||
const { swapsState } = this.store.getState();
|
||||
this.store.updateState({ swapsState: { ...swapsState, routeState } });
|
||||
}
|
||||
|
||||
setSwapsLiveness(swapsFeatureIsLive) {
|
||||
const { swapsState } = this.store.getState()
|
||||
const { swapsState } = this.store.getState();
|
||||
this.store.updateState({
|
||||
swapsState: { ...swapsState, swapsFeatureIsLive },
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
resetPostFetchState() {
|
||||
const { swapsState } = this.store.getState()
|
||||
const { swapsState } = this.store.getState();
|
||||
|
||||
this.store.updateState({
|
||||
swapsState: {
|
||||
@ -455,12 +460,12 @@ export default class SwapsController {
|
||||
swapsFeatureIsLive: swapsState.swapsFeatureIsLive,
|
||||
swapsQuoteRefreshTime: swapsState.swapsQuoteRefreshTime,
|
||||
},
|
||||
})
|
||||
clearTimeout(this.pollingTimeout)
|
||||
});
|
||||
clearTimeout(this.pollingTimeout);
|
||||
}
|
||||
|
||||
resetSwapsState() {
|
||||
const { swapsState } = this.store.getState()
|
||||
const { swapsState } = this.store.getState();
|
||||
|
||||
this.store.updateState({
|
||||
swapsState: {
|
||||
@ -469,33 +474,33 @@ export default class SwapsController {
|
||||
swapsFeatureIsLive: swapsState.swapsFeatureIsLive,
|
||||
swapsQuoteRefreshTime: swapsState.swapsQuoteRefreshTime,
|
||||
},
|
||||
})
|
||||
clearTimeout(this.pollingTimeout)
|
||||
});
|
||||
clearTimeout(this.pollingTimeout);
|
||||
}
|
||||
|
||||
async _getEthersGasPrice() {
|
||||
const ethersGasPrice = await this.ethersProvider.getGasPrice()
|
||||
return ethersGasPrice.toHexString()
|
||||
const ethersGasPrice = await this.ethersProvider.getGasPrice();
|
||||
return ethersGasPrice.toHexString();
|
||||
}
|
||||
|
||||
async _findTopQuoteAndCalculateSavings(quotes = {}) {
|
||||
const tokenConversionRates = this.tokenRatesStore.getState()
|
||||
.contractExchangeRates
|
||||
.contractExchangeRates;
|
||||
const {
|
||||
swapsState: { customGasPrice },
|
||||
} = this.store.getState()
|
||||
} = this.store.getState();
|
||||
|
||||
const numQuotes = Object.keys(quotes).length
|
||||
const numQuotes = Object.keys(quotes).length;
|
||||
if (!numQuotes) {
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
const newQuotes = cloneDeep(quotes)
|
||||
const newQuotes = cloneDeep(quotes);
|
||||
|
||||
const usedGasPrice = customGasPrice || (await this._getEthersGasPrice())
|
||||
const usedGasPrice = customGasPrice || (await this._getEthersGasPrice());
|
||||
|
||||
let topAggId = null
|
||||
let overallValueOfBestQuoteForSorting = null
|
||||
let topAggId = null;
|
||||
let overallValueOfBestQuoteForSorting = null;
|
||||
|
||||
Object.values(newQuotes).forEach((quote) => {
|
||||
const {
|
||||
@ -510,20 +515,20 @@ export default class SwapsController {
|
||||
sourceToken,
|
||||
trade,
|
||||
fee: metaMaskFee,
|
||||
} = quote
|
||||
} = quote;
|
||||
|
||||
const tradeGasLimitForCalculation = gasEstimate
|
||||
? new BigNumber(gasEstimate, 16)
|
||||
: new BigNumber(averageGas || MAX_GAS_LIMIT, 10)
|
||||
: new BigNumber(averageGas || MAX_GAS_LIMIT, 10);
|
||||
|
||||
const totalGasLimitForCalculation = tradeGasLimitForCalculation
|
||||
.plus(approvalNeeded?.gas || '0x0', 16)
|
||||
.toString(16)
|
||||
.toString(16);
|
||||
|
||||
const gasTotalInWeiHex = calcGasTotal(
|
||||
totalGasLimitForCalculation,
|
||||
usedGasPrice,
|
||||
)
|
||||
);
|
||||
|
||||
// trade.value is a sum of different values depending on the transaction.
|
||||
// It always includes any external fees charged by the quote source. In
|
||||
@ -532,7 +537,7 @@ export default class SwapsController {
|
||||
const totalWeiCost = new BigNumber(gasTotalInWeiHex, 16).plus(
|
||||
trade.value,
|
||||
16,
|
||||
)
|
||||
);
|
||||
|
||||
const totalEthCost = conversionUtil(totalWeiCost, {
|
||||
fromCurrency: 'ETH',
|
||||
@ -540,7 +545,7 @@ export default class SwapsController {
|
||||
toDenomination: 'ETH',
|
||||
fromNumericBase: 'BN',
|
||||
numberOfDecimals: 6,
|
||||
})
|
||||
});
|
||||
|
||||
// The total fee is aggregator/exchange fees plus gas fees.
|
||||
// If the swap is from ETH, subtract the sourceAmount from the total cost.
|
||||
@ -557,103 +562,103 @@ export default class SwapsController {
|
||||
numberOfDecimals: 6,
|
||||
},
|
||||
)
|
||||
: totalEthCost
|
||||
: totalEthCost;
|
||||
|
||||
const decimalAdjustedDestinationAmount = calcTokenAmount(
|
||||
destinationAmount,
|
||||
destinationTokenInfo.decimals,
|
||||
)
|
||||
);
|
||||
|
||||
const tokenPercentageOfPreFeeDestAmount = new BigNumber(100, 10)
|
||||
.minus(metaMaskFee, 10)
|
||||
.div(100)
|
||||
.div(100);
|
||||
const destinationAmountBeforeMetaMaskFee = decimalAdjustedDestinationAmount.div(
|
||||
tokenPercentageOfPreFeeDestAmount,
|
||||
)
|
||||
);
|
||||
const metaMaskFeeInTokens = destinationAmountBeforeMetaMaskFee.minus(
|
||||
decimalAdjustedDestinationAmount,
|
||||
)
|
||||
);
|
||||
|
||||
const tokenConversionRate = tokenConversionRates[destinationToken]
|
||||
const conversionRateForSorting = tokenConversionRate || 1
|
||||
const tokenConversionRate = tokenConversionRates[destinationToken];
|
||||
const conversionRateForSorting = tokenConversionRate || 1;
|
||||
|
||||
const ethValueOfTokens = decimalAdjustedDestinationAmount.times(
|
||||
conversionRateForSorting,
|
||||
10,
|
||||
)
|
||||
);
|
||||
|
||||
const conversionRateForCalculations =
|
||||
destinationToken === ETH_SWAPS_TOKEN_ADDRESS ? 1 : tokenConversionRate
|
||||
destinationToken === ETH_SWAPS_TOKEN_ADDRESS ? 1 : tokenConversionRate;
|
||||
|
||||
const overallValueOfQuoteForSorting =
|
||||
conversionRateForCalculations === undefined
|
||||
? ethValueOfTokens
|
||||
: ethValueOfTokens.minus(ethFee, 10)
|
||||
: ethValueOfTokens.minus(ethFee, 10);
|
||||
|
||||
quote.ethFee = ethFee.toString(10)
|
||||
quote.ethFee = ethFee.toString(10);
|
||||
|
||||
if (conversionRateForCalculations !== undefined) {
|
||||
quote.ethValueOfTokens = ethValueOfTokens.toString(10)
|
||||
quote.overallValueOfQuote = overallValueOfQuoteForSorting.toString(10)
|
||||
quote.ethValueOfTokens = ethValueOfTokens.toString(10);
|
||||
quote.overallValueOfQuote = overallValueOfQuoteForSorting.toString(10);
|
||||
quote.metaMaskFeeInEth = metaMaskFeeInTokens
|
||||
.times(conversionRateForCalculations)
|
||||
.toString(10)
|
||||
.toString(10);
|
||||
}
|
||||
|
||||
if (
|
||||
overallValueOfBestQuoteForSorting === null ||
|
||||
overallValueOfQuoteForSorting.gt(overallValueOfBestQuoteForSorting)
|
||||
) {
|
||||
topAggId = aggregator
|
||||
overallValueOfBestQuoteForSorting = overallValueOfQuoteForSorting
|
||||
topAggId = aggregator;
|
||||
overallValueOfBestQuoteForSorting = overallValueOfQuoteForSorting;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const isBest =
|
||||
newQuotes[topAggId].destinationToken === ETH_SWAPS_TOKEN_ADDRESS ||
|
||||
Boolean(tokenConversionRates[newQuotes[topAggId]?.destinationToken])
|
||||
Boolean(tokenConversionRates[newQuotes[topAggId]?.destinationToken]);
|
||||
|
||||
let savings = null
|
||||
let savings = null;
|
||||
|
||||
if (isBest) {
|
||||
const bestQuote = newQuotes[topAggId]
|
||||
const bestQuote = newQuotes[topAggId];
|
||||
|
||||
savings = {}
|
||||
savings = {};
|
||||
|
||||
const {
|
||||
ethFee: medianEthFee,
|
||||
metaMaskFeeInEth: medianMetaMaskFee,
|
||||
ethValueOfTokens: medianEthValueOfTokens,
|
||||
} = getMedianEthValueQuote(Object.values(newQuotes))
|
||||
} = getMedianEthValueQuote(Object.values(newQuotes));
|
||||
|
||||
// Performance savings are calculated as:
|
||||
// (ethValueOfTokens for the best trade) - (ethValueOfTokens for the media trade)
|
||||
savings.performance = new BigNumber(bestQuote.ethValueOfTokens, 10).minus(
|
||||
medianEthValueOfTokens,
|
||||
10,
|
||||
)
|
||||
);
|
||||
|
||||
// Fee savings are calculated as:
|
||||
// (fee for the median trade) - (fee for the best trade)
|
||||
savings.fee = new BigNumber(medianEthFee).minus(bestQuote.ethFee, 10)
|
||||
savings.fee = new BigNumber(medianEthFee).minus(bestQuote.ethFee, 10);
|
||||
|
||||
savings.metaMaskFee = bestQuote.metaMaskFeeInEth
|
||||
savings.metaMaskFee = bestQuote.metaMaskFeeInEth;
|
||||
|
||||
// Total savings are calculated as:
|
||||
// performance savings + fee savings - metamask fee
|
||||
savings.total = savings.performance
|
||||
.plus(savings.fee)
|
||||
.minus(savings.metaMaskFee)
|
||||
.toString(10)
|
||||
savings.performance = savings.performance.toString(10)
|
||||
savings.fee = savings.fee.toString(10)
|
||||
savings.medianMetaMaskFee = medianMetaMaskFee
|
||||
.toString(10);
|
||||
savings.performance = savings.performance.toString(10);
|
||||
savings.fee = savings.fee.toString(10);
|
||||
savings.medianMetaMaskFee = medianMetaMaskFee;
|
||||
|
||||
newQuotes[topAggId].isBestQuote = true
|
||||
newQuotes[topAggId].savings = savings
|
||||
newQuotes[topAggId].isBestQuote = true;
|
||||
newQuotes[topAggId].savings = savings;
|
||||
}
|
||||
|
||||
return [topAggId, newQuotes]
|
||||
return [topAggId, newQuotes];
|
||||
}
|
||||
|
||||
async _getERC20Allowance(contractAddress, walletAddress) {
|
||||
@ -661,8 +666,8 @@ export default class SwapsController {
|
||||
contractAddress,
|
||||
abi,
|
||||
this.ethersProvider,
|
||||
)
|
||||
return await contract.allowance(walletAddress, METASWAP_ADDRESS)
|
||||
);
|
||||
return await contract.allowance(walletAddress, METASWAP_ADDRESS);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -674,8 +679,8 @@ export default class SwapsController {
|
||||
* until the value can be fetched again.
|
||||
*/
|
||||
_setupSwapsLivenessFetching() {
|
||||
const TEN_MINUTES_MS = 10 * 60 * 1000
|
||||
let intervalId = null
|
||||
const TEN_MINUTES_MS = 10 * 60 * 1000;
|
||||
let intervalId = null;
|
||||
|
||||
const fetchAndSetupInterval = () => {
|
||||
if (window.navigator.onLine && intervalId === null) {
|
||||
@ -684,25 +689,25 @@ export default class SwapsController {
|
||||
intervalId = setInterval(
|
||||
this._fetchAndSetSwapsLiveness.bind(this),
|
||||
TEN_MINUTES_MS,
|
||||
)
|
||||
this._fetchAndSetSwapsLiveness()
|
||||
);
|
||||
this._fetchAndSetSwapsLiveness();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('online', fetchAndSetupInterval)
|
||||
window.addEventListener('online', fetchAndSetupInterval);
|
||||
window.addEventListener('offline', () => {
|
||||
if (intervalId !== null) {
|
||||
clearInterval(intervalId)
|
||||
intervalId = null
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
|
||||
const { swapsState } = this.store.getState()
|
||||
const { swapsState } = this.store.getState();
|
||||
if (swapsState.swapsFeatureIsLive) {
|
||||
this.setSwapsLiveness(false)
|
||||
this.setSwapsLiveness(false);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
fetchAndSetupInterval()
|
||||
fetchAndSetupInterval();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -716,41 +721,41 @@ export default class SwapsController {
|
||||
* state.
|
||||
*/
|
||||
async _fetchAndSetSwapsLiveness() {
|
||||
const { swapsState } = this.store.getState()
|
||||
const { swapsFeatureIsLive: oldSwapsFeatureIsLive } = swapsState
|
||||
let swapsFeatureIsLive = false
|
||||
let successfullyFetched = false
|
||||
let numAttempts = 0
|
||||
const { swapsState } = this.store.getState();
|
||||
const { swapsFeatureIsLive: oldSwapsFeatureIsLive } = swapsState;
|
||||
let swapsFeatureIsLive = false;
|
||||
let successfullyFetched = false;
|
||||
let numAttempts = 0;
|
||||
|
||||
const fetchAndIncrementNumAttempts = async () => {
|
||||
try {
|
||||
swapsFeatureIsLive = Boolean(await this._fetchSwapsFeatureLiveness())
|
||||
successfullyFetched = true
|
||||
swapsFeatureIsLive = Boolean(await this._fetchSwapsFeatureLiveness());
|
||||
successfullyFetched = true;
|
||||
} catch (err) {
|
||||
log.error(err)
|
||||
numAttempts += 1
|
||||
log.error(err);
|
||||
numAttempts += 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await fetchAndIncrementNumAttempts()
|
||||
await fetchAndIncrementNumAttempts();
|
||||
|
||||
// The loop conditions are modified by fetchAndIncrementNumAttempts.
|
||||
// eslint-disable-next-line no-unmodified-loop-condition
|
||||
while (!successfullyFetched && numAttempts < 3) {
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 5000) // 5 seconds
|
||||
})
|
||||
await fetchAndIncrementNumAttempts()
|
||||
setTimeout(resolve, 5000); // 5 seconds
|
||||
});
|
||||
await fetchAndIncrementNumAttempts();
|
||||
}
|
||||
|
||||
if (!successfullyFetched) {
|
||||
log.error(
|
||||
'Failed to fetch swaps feature flag 3 times. Setting to false and trying again next interval.',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (swapsFeatureIsLive !== oldSwapsFeatureIsLive) {
|
||||
this.setSwapsLiveness(swapsFeatureIsLive)
|
||||
this.setSwapsLiveness(swapsFeatureIsLive);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -763,50 +768,50 @@ export default class SwapsController {
|
||||
*/
|
||||
function getMedianEthValueQuote(_quotes) {
|
||||
if (!Array.isArray(_quotes) || _quotes.length === 0) {
|
||||
throw new Error('Expected non-empty array param.')
|
||||
throw new Error('Expected non-empty array param.');
|
||||
}
|
||||
|
||||
const quotes = [..._quotes]
|
||||
const quotes = [..._quotes];
|
||||
|
||||
quotes.sort((quoteA, quoteB) => {
|
||||
const overallValueOfQuoteA = new BigNumber(quoteA.overallValueOfQuote, 10)
|
||||
const overallValueOfQuoteB = new BigNumber(quoteB.overallValueOfQuote, 10)
|
||||
const overallValueOfQuoteA = new BigNumber(quoteA.overallValueOfQuote, 10);
|
||||
const overallValueOfQuoteB = new BigNumber(quoteB.overallValueOfQuote, 10);
|
||||
if (overallValueOfQuoteA.equals(overallValueOfQuoteB)) {
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
return overallValueOfQuoteA.lessThan(overallValueOfQuoteB) ? -1 : 1
|
||||
})
|
||||
return overallValueOfQuoteA.lessThan(overallValueOfQuoteB) ? -1 : 1;
|
||||
});
|
||||
|
||||
if (quotes.length % 2 === 1) {
|
||||
// return middle values
|
||||
const medianOverallValue =
|
||||
quotes[(quotes.length - 1) / 2].overallValueOfQuote
|
||||
quotes[(quotes.length - 1) / 2].overallValueOfQuote;
|
||||
const quotesMatchingMedianQuoteValue = quotes.filter(
|
||||
(quote) => medianOverallValue === quote.overallValueOfQuote,
|
||||
)
|
||||
return meansOfQuotesFeesAndValue(quotesMatchingMedianQuoteValue)
|
||||
);
|
||||
return meansOfQuotesFeesAndValue(quotesMatchingMedianQuoteValue);
|
||||
}
|
||||
|
||||
// return mean of middle two values
|
||||
const upperIndex = quotes.length / 2
|
||||
const lowerIndex = upperIndex - 1
|
||||
const upperIndex = quotes.length / 2;
|
||||
const lowerIndex = upperIndex - 1;
|
||||
|
||||
const overallValueAtUpperIndex = quotes[upperIndex].overallValueOfQuote
|
||||
const overallValueAtLowerIndex = quotes[lowerIndex].overallValueOfQuote
|
||||
const overallValueAtUpperIndex = quotes[upperIndex].overallValueOfQuote;
|
||||
const overallValueAtLowerIndex = quotes[lowerIndex].overallValueOfQuote;
|
||||
|
||||
const quotesMatchingUpperIndexValue = quotes.filter(
|
||||
(quote) => overallValueAtUpperIndex === quote.overallValueOfQuote,
|
||||
)
|
||||
);
|
||||
const quotesMatchingLowerIndexValue = quotes.filter(
|
||||
(quote) => overallValueAtLowerIndex === quote.overallValueOfQuote,
|
||||
)
|
||||
);
|
||||
|
||||
const feesAndValueAtUpperIndex = meansOfQuotesFeesAndValue(
|
||||
quotesMatchingUpperIndexValue,
|
||||
)
|
||||
);
|
||||
const feesAndValueAtLowerIndex = meansOfQuotesFeesAndValue(
|
||||
quotesMatchingLowerIndexValue,
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
ethFee: new BigNumber(feesAndValueAtUpperIndex.ethFee, 10)
|
||||
@ -827,7 +832,7 @@ function getMedianEthValueQuote(_quotes) {
|
||||
.plus(feesAndValueAtLowerIndex.ethValueOfTokens, 10)
|
||||
.dividedBy(2)
|
||||
.toString(10),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -857,7 +862,7 @@ function meansOfQuotesFeesAndValue(quotes) {
|
||||
metaMaskFeeInEth: new BigNumber(0, 10),
|
||||
ethValueOfTokens: new BigNumber(0, 10),
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
ethFee: feeAndValueSumsAsBigNumbers.ethFee
|
||||
@ -869,10 +874,10 @@ function meansOfQuotesFeesAndValue(quotes) {
|
||||
ethValueOfTokens: feeAndValueSumsAsBigNumbers.ethValueOfTokens
|
||||
.div(quotes.length, 10)
|
||||
.toString(10),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const utils = {
|
||||
getMedianEthValueQuote,
|
||||
meansOfQuotesFeesAndValue,
|
||||
}
|
||||
};
|
||||
|
@ -1,21 +1,21 @@
|
||||
import { ObservableStore } from '@metamask/obs-store'
|
||||
import { ObservableStore } from '@metamask/obs-store';
|
||||
|
||||
/* eslint-disable import/first,import/order */
|
||||
const Box = process.env.IN_TEST
|
||||
? require('../../../development/mock-3box')
|
||||
: require('3box')
|
||||
: require('3box');
|
||||
/* eslint-enable import/order */
|
||||
|
||||
import log from 'loglevel'
|
||||
import { JsonRpcEngine } from 'json-rpc-engine'
|
||||
import providerFromEngine from 'eth-json-rpc-middleware/providerFromEngine'
|
||||
import Migrator from '../lib/migrator'
|
||||
import migrations from '../migrations'
|
||||
import createOriginMiddleware from '../lib/createOriginMiddleware'
|
||||
import createMetamaskMiddleware from './network/createMetamaskMiddleware'
|
||||
import log from 'loglevel';
|
||||
import { JsonRpcEngine } from 'json-rpc-engine';
|
||||
import providerFromEngine from 'eth-json-rpc-middleware/providerFromEngine';
|
||||
import Migrator from '../lib/migrator';
|
||||
import migrations from '../migrations';
|
||||
import createOriginMiddleware from '../lib/createOriginMiddleware';
|
||||
import createMetamaskMiddleware from './network/createMetamaskMiddleware';
|
||||
/* eslint-enable import/first */
|
||||
|
||||
const SYNC_TIMEOUT = 60 * 1000 // one minute
|
||||
const SYNC_TIMEOUT = 60 * 1000; // one minute
|
||||
|
||||
export default class ThreeBoxController {
|
||||
constructor(opts = {}) {
|
||||
@ -25,40 +25,40 @@ export default class ThreeBoxController {
|
||||
addressBookController,
|
||||
version,
|
||||
getKeyringControllerState,
|
||||
} = opts
|
||||
} = opts;
|
||||
|
||||
this.preferencesController = preferencesController
|
||||
this.addressBookController = addressBookController
|
||||
this.keyringController = keyringController
|
||||
this.preferencesController = preferencesController;
|
||||
this.addressBookController = addressBookController;
|
||||
this.keyringController = keyringController;
|
||||
this.provider = this._createProvider({
|
||||
version,
|
||||
getAccounts: async ({ origin }) => {
|
||||
if (origin !== '3Box') {
|
||||
return []
|
||||
return [];
|
||||
}
|
||||
const { isUnlocked } = getKeyringControllerState()
|
||||
const { isUnlocked } = getKeyringControllerState();
|
||||
|
||||
const accounts = await this.keyringController.getAccounts()
|
||||
const accounts = await this.keyringController.getAccounts();
|
||||
|
||||
if (isUnlocked && accounts[0]) {
|
||||
const appKeyAddress = await this.keyringController.getAppKeyAddress(
|
||||
accounts[0],
|
||||
'wallet://3box.metamask.io',
|
||||
)
|
||||
return [appKeyAddress]
|
||||
);
|
||||
return [appKeyAddress];
|
||||
}
|
||||
return []
|
||||
return [];
|
||||
},
|
||||
processPersonalMessage: async (msgParams) => {
|
||||
const accounts = await this.keyringController.getAccounts()
|
||||
const accounts = await this.keyringController.getAccounts();
|
||||
return keyringController.signPersonalMessage(
|
||||
{ ...msgParams, from: accounts[0] },
|
||||
{
|
||||
withAppKeyOrigin: 'wallet://3box.metamask.io',
|
||||
},
|
||||
)
|
||||
);
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
const initState = {
|
||||
threeBoxSyncingAllowed: false,
|
||||
@ -68,193 +68,196 @@ export default class ThreeBoxController {
|
||||
threeBoxAddress: null,
|
||||
threeBoxSynced: false,
|
||||
threeBoxDisabled: false,
|
||||
}
|
||||
this.store = new ObservableStore(initState)
|
||||
this.registeringUpdates = false
|
||||
};
|
||||
this.store = new ObservableStore(initState);
|
||||
this.registeringUpdates = false;
|
||||
this.lastMigration = migrations
|
||||
.sort((a, b) => a.version - b.version)
|
||||
.slice(-1)[0]
|
||||
.slice(-1)[0];
|
||||
|
||||
if (initState.threeBoxSyncingAllowed) {
|
||||
this.init()
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
|
||||
async init() {
|
||||
const accounts = await this.keyringController.getAccounts()
|
||||
this.address = accounts[0]
|
||||
const accounts = await this.keyringController.getAccounts();
|
||||
this.address = accounts[0];
|
||||
if (this.address && !(this.box && this.store.getState().threeBoxSynced)) {
|
||||
await this.new3Box()
|
||||
await this.new3Box();
|
||||
}
|
||||
}
|
||||
|
||||
async _update3Box() {
|
||||
try {
|
||||
const { threeBoxSyncingAllowed, threeBoxSynced } = this.store.getState()
|
||||
const { threeBoxSyncingAllowed, threeBoxSynced } = this.store.getState();
|
||||
if (threeBoxSyncingAllowed && threeBoxSynced) {
|
||||
const newState = {
|
||||
preferences: this.preferencesController.store.getState(),
|
||||
addressBook: this.addressBookController.state,
|
||||
lastUpdated: Date.now(),
|
||||
lastMigration: this.lastMigration,
|
||||
}
|
||||
};
|
||||
|
||||
await this.space.private.set('metamaskBackup', JSON.stringify(newState))
|
||||
await this.setShowRestorePromptToFalse()
|
||||
await this.space.private.set(
|
||||
'metamaskBackup',
|
||||
JSON.stringify(newState),
|
||||
);
|
||||
await this.setShowRestorePromptToFalse();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
_createProvider(providerOpts) {
|
||||
const metamaskMiddleware = createMetamaskMiddleware(providerOpts)
|
||||
const engine = new JsonRpcEngine()
|
||||
engine.push(createOriginMiddleware({ origin: '3Box' }))
|
||||
engine.push(metamaskMiddleware)
|
||||
const provider = providerFromEngine(engine)
|
||||
return provider
|
||||
const metamaskMiddleware = createMetamaskMiddleware(providerOpts);
|
||||
const engine = new JsonRpcEngine();
|
||||
engine.push(createOriginMiddleware({ origin: '3Box' }));
|
||||
engine.push(metamaskMiddleware);
|
||||
const provider = providerFromEngine(engine);
|
||||
return provider;
|
||||
}
|
||||
|
||||
_waitForOnSyncDone() {
|
||||
return new Promise((resolve) => {
|
||||
this.box.onSyncDone(() => {
|
||||
log.debug('3Box box sync done')
|
||||
return resolve()
|
||||
})
|
||||
})
|
||||
log.debug('3Box box sync done');
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async new3Box() {
|
||||
const accounts = await this.keyringController.getAccounts()
|
||||
const accounts = await this.keyringController.getAccounts();
|
||||
this.address = await this.keyringController.getAppKeyAddress(
|
||||
accounts[0],
|
||||
'wallet://3box.metamask.io',
|
||||
)
|
||||
let backupExists
|
||||
);
|
||||
let backupExists;
|
||||
try {
|
||||
const threeBoxConfig = await Box.getConfig(this.address)
|
||||
backupExists = threeBoxConfig.spaces && threeBoxConfig.spaces.metamask
|
||||
const threeBoxConfig = await Box.getConfig(this.address);
|
||||
backupExists = threeBoxConfig.spaces && threeBoxConfig.spaces.metamask;
|
||||
} catch (e) {
|
||||
if (e.message.match(/^Error: Invalid response \(404\)/u)) {
|
||||
backupExists = false
|
||||
backupExists = false;
|
||||
} else {
|
||||
throw e
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
if (this.getThreeBoxSyncingState() || backupExists) {
|
||||
this.store.updateState({ threeBoxSynced: false })
|
||||
this.store.updateState({ threeBoxSynced: false });
|
||||
|
||||
let timedOut = false
|
||||
let timedOut = false;
|
||||
const syncTimeout = setTimeout(() => {
|
||||
log.error(`3Box sync timed out after ${SYNC_TIMEOUT} ms`)
|
||||
timedOut = true
|
||||
log.error(`3Box sync timed out after ${SYNC_TIMEOUT} ms`);
|
||||
timedOut = true;
|
||||
this.store.updateState({
|
||||
threeBoxDisabled: true,
|
||||
threeBoxSyncingAllowed: false,
|
||||
})
|
||||
}, SYNC_TIMEOUT)
|
||||
});
|
||||
}, SYNC_TIMEOUT);
|
||||
try {
|
||||
this.box = await Box.openBox(this.address, this.provider)
|
||||
await this._waitForOnSyncDone()
|
||||
this.box = await Box.openBox(this.address, this.provider);
|
||||
await this._waitForOnSyncDone();
|
||||
this.space = await this.box.openSpace('metamask', {
|
||||
onSyncDone: async () => {
|
||||
const stateUpdate = {
|
||||
threeBoxSynced: true,
|
||||
threeBoxAddress: this.address,
|
||||
}
|
||||
};
|
||||
if (timedOut) {
|
||||
log.info(`3Box sync completed after timeout; no longer disabled`)
|
||||
stateUpdate.threeBoxDisabled = false
|
||||
log.info(`3Box sync completed after timeout; no longer disabled`);
|
||||
stateUpdate.threeBoxDisabled = false;
|
||||
}
|
||||
|
||||
clearTimeout(syncTimeout)
|
||||
this.store.updateState(stateUpdate)
|
||||
clearTimeout(syncTimeout);
|
||||
this.store.updateState(stateUpdate);
|
||||
|
||||
log.debug('3Box space sync done')
|
||||
log.debug('3Box space sync done');
|
||||
},
|
||||
})
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
throw e
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getLastUpdated() {
|
||||
const res = await this.space.private.get('metamaskBackup')
|
||||
const parsedRes = JSON.parse(res || '{}')
|
||||
return parsedRes.lastUpdated
|
||||
const res = await this.space.private.get('metamaskBackup');
|
||||
const parsedRes = JSON.parse(res || '{}');
|
||||
return parsedRes.lastUpdated;
|
||||
}
|
||||
|
||||
async migrateBackedUpState(backedUpState) {
|
||||
const migrator = new Migrator({ migrations })
|
||||
const { preferences, addressBook } = JSON.parse(backedUpState)
|
||||
const migrator = new Migrator({ migrations });
|
||||
const { preferences, addressBook } = JSON.parse(backedUpState);
|
||||
const formattedStateBackup = {
|
||||
PreferencesController: preferences,
|
||||
AddressBookController: addressBook,
|
||||
}
|
||||
};
|
||||
const initialMigrationState = migrator.generateInitialState(
|
||||
formattedStateBackup,
|
||||
)
|
||||
const migratedState = await migrator.migrateData(initialMigrationState)
|
||||
);
|
||||
const migratedState = await migrator.migrateData(initialMigrationState);
|
||||
return {
|
||||
preferences: migratedState.data.PreferencesController,
|
||||
addressBook: migratedState.data.AddressBookController,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async restoreFromThreeBox() {
|
||||
const backedUpState = await this.space.private.get('metamaskBackup')
|
||||
const backedUpState = await this.space.private.get('metamaskBackup');
|
||||
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()
|
||||
);
|
||||
this.store.updateState({ threeBoxLastUpdated: backedUpState.lastUpdated });
|
||||
preferences && this.preferencesController.store.updateState(preferences);
|
||||
addressBook && this.addressBookController.update(addressBook, true);
|
||||
this.setShowRestorePromptToFalse();
|
||||
}
|
||||
|
||||
turnThreeBoxSyncingOn() {
|
||||
this._registerUpdates()
|
||||
this._registerUpdates();
|
||||
}
|
||||
|
||||
turnThreeBoxSyncingOff() {
|
||||
this.box.logout()
|
||||
this.box.logout();
|
||||
}
|
||||
|
||||
setShowRestorePromptToFalse() {
|
||||
this.store.updateState({ showRestorePrompt: false })
|
||||
this.store.updateState({ showRestorePrompt: false });
|
||||
}
|
||||
|
||||
setThreeBoxSyncingPermission(newThreeboxSyncingState) {
|
||||
if (this.store.getState().threeBoxDisabled) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this.store.updateState({
|
||||
threeBoxSyncingAllowed: newThreeboxSyncingState,
|
||||
})
|
||||
});
|
||||
|
||||
if (newThreeboxSyncingState && this.box) {
|
||||
this.turnThreeBoxSyncingOn()
|
||||
this.turnThreeBoxSyncingOn();
|
||||
}
|
||||
|
||||
if (!newThreeboxSyncingState && this.box) {
|
||||
this.turnThreeBoxSyncingOff()
|
||||
this.turnThreeBoxSyncingOff();
|
||||
}
|
||||
}
|
||||
|
||||
getThreeBoxSyncingState() {
|
||||
return this.store.getState().threeBoxSyncingAllowed
|
||||
return this.store.getState().threeBoxSyncingAllowed;
|
||||
}
|
||||
|
||||
_registerUpdates() {
|
||||
if (!this.registeringUpdates) {
|
||||
const updatePreferences = this._update3Box.bind(this)
|
||||
this.preferencesController.store.subscribe(updatePreferences)
|
||||
const updateAddressBook = this._update3Box.bind(this)
|
||||
this.addressBookController.subscribe(updateAddressBook)
|
||||
this.registeringUpdates = true
|
||||
const updatePreferences = this._update3Box.bind(this);
|
||||
this.preferencesController.store.subscribe(updatePreferences);
|
||||
const updateAddressBook = this._update3Box.bind(this);
|
||||
this.addressBookController.subscribe(updateAddressBook);
|
||||
this.registeringUpdates = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { ObservableStore } from '@metamask/obs-store'
|
||||
import log from 'loglevel'
|
||||
import { normalize as normalizeAddress } from 'eth-sig-util'
|
||||
import ethUtil from 'ethereumjs-util'
|
||||
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout'
|
||||
import { ObservableStore } from '@metamask/obs-store';
|
||||
import log from 'loglevel';
|
||||
import { normalize as normalizeAddress } from 'eth-sig-util';
|
||||
import ethUtil from 'ethereumjs-util';
|
||||
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout';
|
||||
|
||||
const fetchWithTimeout = getFetchWithTimeout(30000)
|
||||
const fetchWithTimeout = getFetchWithTimeout(30000);
|
||||
|
||||
// By default, poll every 3 minutes
|
||||
const DEFAULT_INTERVAL = 180 * 1000
|
||||
const DEFAULT_INTERVAL = 180 * 1000;
|
||||
|
||||
/**
|
||||
* A controller that polls for token exchange
|
||||
@ -20,43 +20,43 @@ export default class TokenRatesController {
|
||||
* @param {Object} [config] - Options to configure controller
|
||||
*/
|
||||
constructor({ currency, preferences } = {}) {
|
||||
this.store = new ObservableStore()
|
||||
this.currency = currency
|
||||
this.preferences = preferences
|
||||
this.store = new ObservableStore();
|
||||
this.currency = currency;
|
||||
this.preferences = preferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates exchange rates for all tokens
|
||||
*/
|
||||
async updateExchangeRates() {
|
||||
const contractExchangeRates = {}
|
||||
const contractExchangeRates = {};
|
||||
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}`
|
||||
: '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 fetchWithTimeout(
|
||||
`https://api.coingecko.com/api/v3/simple/token_price/ethereum?${query}`,
|
||||
)
|
||||
const prices = await response.json()
|
||||
);
|
||||
const prices = await response.json();
|
||||
this._tokens.forEach((token) => {
|
||||
const price =
|
||||
prices[token.address.toLowerCase()] ||
|
||||
prices[ethUtil.toChecksumAddress(token.address)]
|
||||
prices[ethUtil.toChecksumAddress(token.address)];
|
||||
contractExchangeRates[normalizeAddress(token.address)] = price
|
||||
? price[nativeCurrency]
|
||||
: 0
|
||||
})
|
||||
: 0;
|
||||
});
|
||||
} catch (error) {
|
||||
log.warn(
|
||||
`MetaMask - TokenRatesController exchange rate fetch failed.`,
|
||||
error,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
this.store.putState({ contractExchangeRates })
|
||||
this.store.putState({ contractExchangeRates });
|
||||
}
|
||||
|
||||
/* eslint-disable accessor-pairs */
|
||||
@ -64,38 +64,38 @@ export default class TokenRatesController {
|
||||
* @type {Object}
|
||||
*/
|
||||
set preferences(preferences) {
|
||||
this._preferences && this._preferences.unsubscribe()
|
||||
this._preferences && this._preferences.unsubscribe();
|
||||
if (!preferences) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this._preferences = preferences
|
||||
this.tokens = preferences.getState().tokens
|
||||
this._preferences = preferences;
|
||||
this.tokens = preferences.getState().tokens;
|
||||
preferences.subscribe(({ tokens = [] }) => {
|
||||
this.tokens = tokens
|
||||
})
|
||||
this.tokens = tokens;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Array}
|
||||
*/
|
||||
set tokens(tokens) {
|
||||
this._tokens = tokens
|
||||
this.updateExchangeRates()
|
||||
this._tokens = tokens;
|
||||
this.updateExchangeRates();
|
||||
}
|
||||
/* eslint-enable accessor-pairs */
|
||||
|
||||
start(interval = DEFAULT_INTERVAL) {
|
||||
this._handle && clearInterval(this._handle)
|
||||
this._handle && clearInterval(this._handle);
|
||||
if (!interval) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this._handle = setInterval(() => {
|
||||
this.updateExchangeRates()
|
||||
}, interval)
|
||||
this.updateExchangeRates()
|
||||
this.updateExchangeRates();
|
||||
}, interval);
|
||||
this.updateExchangeRates();
|
||||
}
|
||||
|
||||
stop() {
|
||||
this._handle && clearInterval(this._handle)
|
||||
this._handle && clearInterval(this._handle);
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
import jsonDiffer from 'fast-json-patch'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import jsonDiffer from 'fast-json-patch';
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
/**
|
||||
converts non-initial history entries into diffs
|
||||
@ -12,11 +12,11 @@ export function migrateFromSnapshotsToDiffs(longHistory) {
|
||||
// convert non-initial history entries into diffs
|
||||
.map((entry, index) => {
|
||||
if (index === 0) {
|
||||
return entry
|
||||
return entry;
|
||||
}
|
||||
return generateHistoryEntry(longHistory[index - 1], entry)
|
||||
return generateHistoryEntry(longHistory[index - 1], entry);
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -32,16 +32,16 @@ export function migrateFromSnapshotsToDiffs(longHistory) {
|
||||
@returns {Array}
|
||||
*/
|
||||
export function generateHistoryEntry(previousState, newState, note) {
|
||||
const entry = jsonDiffer.compare(previousState, newState)
|
||||
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]) {
|
||||
if (note) {
|
||||
entry[0].note = note
|
||||
entry[0].note = note;
|
||||
}
|
||||
|
||||
entry[0].timestamp = Date.now()
|
||||
entry[0].timestamp = Date.now();
|
||||
}
|
||||
return entry
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -49,10 +49,10 @@ export function generateHistoryEntry(previousState, newState, note) {
|
||||
@returns {Object}
|
||||
*/
|
||||
export function replayHistory(_shortHistory) {
|
||||
const shortHistory = cloneDeep(_shortHistory)
|
||||
const shortHistory = cloneDeep(_shortHistory);
|
||||
return shortHistory.reduce(
|
||||
(val, entry) => jsonDiffer.applyPatch(val, entry).newDocument,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -61,7 +61,7 @@ export function replayHistory(_shortHistory) {
|
||||
* @returns {Object} a deep clone without history
|
||||
*/
|
||||
export function snapshotFromTxMeta(txMeta) {
|
||||
const shallow = { ...txMeta }
|
||||
delete shallow.history
|
||||
return cloneDeep(shallow)
|
||||
const shallow = { ...txMeta };
|
||||
delete shallow.history;
|
||||
return cloneDeep(shallow);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { isValidAddress } from 'ethereumjs-util'
|
||||
import { ethErrors } from 'eth-rpc-errors'
|
||||
import { addHexPrefix } from '../../../lib/util'
|
||||
import { TRANSACTION_STATUSES } from '../../../../../shared/constants/transaction'
|
||||
import { isValidAddress } from 'ethereumjs-util';
|
||||
import { ethErrors } from 'eth-rpc-errors';
|
||||
import { addHexPrefix } from '../../../lib/util';
|
||||
import { TRANSACTION_STATUSES } from '../../../../../shared/constants/transaction';
|
||||
|
||||
const normalizers = {
|
||||
from: (from) => addHexPrefix(from),
|
||||
@ -12,7 +12,7 @@ const normalizers = {
|
||||
data: (data) => addHexPrefix(data),
|
||||
gas: (gas) => addHexPrefix(gas),
|
||||
gasPrice: (gasPrice) => addHexPrefix(gasPrice),
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalizes the given txParams
|
||||
@ -23,13 +23,13 @@ const normalizers = {
|
||||
*/
|
||||
export function normalizeTxParams(txParams, lowerCase = true) {
|
||||
// apply only keys in the normalizers
|
||||
const normalizedTxParams = {}
|
||||
const normalizedTxParams = {};
|
||||
for (const key in normalizers) {
|
||||
if (txParams[key]) {
|
||||
normalizedTxParams[key] = normalizers[key](txParams[key], lowerCase)
|
||||
normalizedTxParams[key] = normalizers[key](txParams[key], lowerCase);
|
||||
}
|
||||
}
|
||||
return normalizedTxParams
|
||||
return normalizedTxParams;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -41,28 +41,28 @@ export function validateTxParams(txParams) {
|
||||
if (!txParams || typeof txParams !== 'object' || Array.isArray(txParams)) {
|
||||
throw ethErrors.rpc.invalidParams(
|
||||
'Invalid transaction params: must be an object.',
|
||||
)
|
||||
);
|
||||
}
|
||||
if (!txParams.to && !txParams.data) {
|
||||
throw ethErrors.rpc.invalidParams(
|
||||
'Invalid transaction params: must specify "data" for contract deployments, or "to" (and optionally "data") for all other types of transactions.',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
validateFrom(txParams)
|
||||
validateRecipient(txParams)
|
||||
validateFrom(txParams);
|
||||
validateRecipient(txParams);
|
||||
if ('value' in txParams) {
|
||||
const value = txParams.value.toString()
|
||||
const value = txParams.value.toString();
|
||||
if (value.includes('-')) {
|
||||
throw ethErrors.rpc.invalidParams(
|
||||
`Invalid transaction value "${txParams.value}": not a positive number.`,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (value.includes('.')) {
|
||||
throw ethErrors.rpc.invalidParams(
|
||||
`Invalid transaction value of "${txParams.value}": number must be in wei.`,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -76,10 +76,10 @@ export function validateFrom(txParams) {
|
||||
if (!(typeof txParams.from === 'string')) {
|
||||
throw ethErrors.rpc.invalidParams(
|
||||
`Invalid "from" address "${txParams.from}": not a string.`,
|
||||
)
|
||||
);
|
||||
}
|
||||
if (!isValidAddress(txParams.from)) {
|
||||
throw ethErrors.rpc.invalidParams('Invalid "from" address.')
|
||||
throw ethErrors.rpc.invalidParams('Invalid "from" address.');
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,14 +92,14 @@ export function validateFrom(txParams) {
|
||||
export function validateRecipient(txParams) {
|
||||
if (txParams.to === '0x' || txParams.to === null) {
|
||||
if (txParams.data) {
|
||||
delete txParams.to
|
||||
delete txParams.to;
|
||||
} else {
|
||||
throw ethErrors.rpc.invalidParams('Invalid "to" address.')
|
||||
throw ethErrors.rpc.invalidParams('Invalid "to" address.');
|
||||
}
|
||||
} else if (txParams.to !== undefined && !isValidAddress(txParams.to)) {
|
||||
throw ethErrors.rpc.invalidParams('Invalid "to" address.')
|
||||
throw ethErrors.rpc.invalidParams('Invalid "to" address.');
|
||||
}
|
||||
return txParams
|
||||
return txParams;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,5 +112,5 @@ export function getFinalStates() {
|
||||
TRANSACTION_STATUSES.CONFIRMED, // the tx has been included in a block.
|
||||
TRANSACTION_STATUSES.FAILED, // the tx failed for some reason, included on tx data.
|
||||
TRANSACTION_STATUSES.DROPPED, // the tx nonce was already used
|
||||
]
|
||||
];
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import EventEmitter from 'safe-event-emitter'
|
||||
import log from 'loglevel'
|
||||
import EthQuery from 'ethjs-query'
|
||||
import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction'
|
||||
import EventEmitter from 'safe-event-emitter';
|
||||
import log from 'loglevel';
|
||||
import EthQuery from 'ethjs-query';
|
||||
import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction';
|
||||
|
||||
/**
|
||||
|
||||
@ -27,7 +27,7 @@ export default class PendingTransactionTracker extends EventEmitter {
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
DROPPED_BUFFER_COUNT = 3
|
||||
DROPPED_BUFFER_COUNT = 3;
|
||||
|
||||
/**
|
||||
* A map of transaction hashes to the number of blocks we've seen
|
||||
@ -35,17 +35,17 @@ export default class PendingTransactionTracker extends EventEmitter {
|
||||
*
|
||||
* @type {Map<String, number>}
|
||||
*/
|
||||
droppedBlocksBufferByHash = new Map()
|
||||
droppedBlocksBufferByHash = new Map();
|
||||
|
||||
constructor(config) {
|
||||
super()
|
||||
this.query = config.query || new EthQuery(config.provider)
|
||||
this.nonceTracker = config.nonceTracker
|
||||
this.getPendingTransactions = config.getPendingTransactions
|
||||
this.getCompletedTransactions = config.getCompletedTransactions
|
||||
this.publishTransaction = config.publishTransaction
|
||||
this.approveTransaction = config.approveTransaction
|
||||
this.confirmTransaction = config.confirmTransaction
|
||||
super();
|
||||
this.query = config.query || new EthQuery(config.provider);
|
||||
this.nonceTracker = config.nonceTracker;
|
||||
this.getPendingTransactions = config.getPendingTransactions;
|
||||
this.getCompletedTransactions = config.getCompletedTransactions;
|
||||
this.publishTransaction = config.publishTransaction;
|
||||
this.approveTransaction = config.approveTransaction;
|
||||
this.confirmTransaction = config.confirmTransaction;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -53,19 +53,19 @@ export default class PendingTransactionTracker extends EventEmitter {
|
||||
*/
|
||||
async updatePendingTxs() {
|
||||
// in order to keep the nonceTracker accurate we block it while updating pending transactions
|
||||
const nonceGlobalLock = await this.nonceTracker.getGlobalLock()
|
||||
const nonceGlobalLock = await this.nonceTracker.getGlobalLock();
|
||||
try {
|
||||
const pendingTxs = this.getPendingTransactions()
|
||||
const pendingTxs = this.getPendingTransactions();
|
||||
await Promise.all(
|
||||
pendingTxs.map((txMeta) => this._checkPendingTx(txMeta)),
|
||||
)
|
||||
);
|
||||
} catch (err) {
|
||||
log.error(
|
||||
'PendingTransactionTracker - Error updating pending transactions',
|
||||
)
|
||||
log.error(err)
|
||||
);
|
||||
log.error(err);
|
||||
}
|
||||
nonceGlobalLock.releaseLock()
|
||||
nonceGlobalLock.releaseLock();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,16 +75,16 @@ export default class PendingTransactionTracker extends EventEmitter {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async resubmitPendingTxs(blockNumber) {
|
||||
const pending = this.getPendingTransactions()
|
||||
const pending = this.getPendingTransactions();
|
||||
if (!pending.length) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
for (const txMeta of pending) {
|
||||
try {
|
||||
await this._resubmitTx(txMeta, blockNumber)
|
||||
await this._resubmitTx(txMeta, blockNumber);
|
||||
} catch (err) {
|
||||
const errorMessage =
|
||||
err.value?.message?.toLowerCase() || err.message.toLowerCase()
|
||||
err.value?.message?.toLowerCase() || err.message.toLowerCase();
|
||||
const isKnownTx =
|
||||
// geth
|
||||
errorMessage.includes('replacement transaction underpriced') ||
|
||||
@ -96,17 +96,17 @@ export default class PendingTransactionTracker extends EventEmitter {
|
||||
) ||
|
||||
// other
|
||||
errorMessage.includes('gateway timeout') ||
|
||||
errorMessage.includes('nonce too low')
|
||||
errorMessage.includes('nonce too low');
|
||||
// ignore resubmit warnings, return early
|
||||
if (isKnownTx) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
// encountered real error - transition to error state
|
||||
txMeta.warning = {
|
||||
error: errorMessage,
|
||||
message: 'There was an error when resubmitting this transaction.',
|
||||
}
|
||||
this.emit('tx:warning', txMeta, err)
|
||||
};
|
||||
this.emit('tx:warning', txMeta, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -125,33 +125,33 @@ export default class PendingTransactionTracker extends EventEmitter {
|
||||
*/
|
||||
async _resubmitTx(txMeta, latestBlockNumber) {
|
||||
if (!txMeta.firstRetryBlockNumber) {
|
||||
this.emit('tx:block-update', txMeta, latestBlockNumber)
|
||||
this.emit('tx:block-update', txMeta, latestBlockNumber);
|
||||
}
|
||||
|
||||
const firstRetryBlockNumber =
|
||||
txMeta.firstRetryBlockNumber || latestBlockNumber
|
||||
txMeta.firstRetryBlockNumber || latestBlockNumber;
|
||||
const txBlockDistance =
|
||||
Number.parseInt(latestBlockNumber, 16) -
|
||||
Number.parseInt(firstRetryBlockNumber, 16)
|
||||
Number.parseInt(firstRetryBlockNumber, 16);
|
||||
|
||||
const retryCount = txMeta.retryCount || 0
|
||||
const retryCount = txMeta.retryCount || 0;
|
||||
|
||||
// Exponential backoff to limit retries at publishing
|
||||
if (txBlockDistance <= Math.pow(2, retryCount) - 1) {
|
||||
return undefined
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Only auto-submit already-signed txs:
|
||||
if (!('rawTx' in txMeta)) {
|
||||
return this.approveTransaction(txMeta.id)
|
||||
return this.approveTransaction(txMeta.id);
|
||||
}
|
||||
|
||||
const { rawTx } = txMeta
|
||||
const txHash = await this.publishTransaction(rawTx)
|
||||
const { rawTx } = txMeta;
|
||||
const txHash = await this.publishTransaction(rawTx);
|
||||
|
||||
// Increment successful tries:
|
||||
this.emit('tx:retry', txMeta)
|
||||
return txHash
|
||||
this.emit('tx:retry', txMeta);
|
||||
return txHash;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -165,12 +165,12 @@ export default class PendingTransactionTracker extends EventEmitter {
|
||||
* @private
|
||||
*/
|
||||
async _checkPendingTx(txMeta) {
|
||||
const txHash = txMeta.hash
|
||||
const txId = txMeta.id
|
||||
const txHash = txMeta.hash;
|
||||
const txId = txMeta.id;
|
||||
|
||||
// Only check submitted txs
|
||||
if (txMeta.status !== TRANSACTION_STATUSES.SUBMITTED) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// extra check in case there was an uncaught error during the
|
||||
@ -178,35 +178,35 @@ export default class PendingTransactionTracker extends EventEmitter {
|
||||
if (!txHash) {
|
||||
const noTxHashErr = new Error(
|
||||
'We had an error while submitting this transaction, please try again.',
|
||||
)
|
||||
noTxHashErr.name = 'NoTxHashError'
|
||||
this.emit('tx:failed', txId, noTxHashErr)
|
||||
);
|
||||
noTxHashErr.name = 'NoTxHashError';
|
||||
this.emit('tx:failed', txId, noTxHashErr);
|
||||
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (await this._checkIfNonceIsTaken(txMeta)) {
|
||||
this.emit('tx:dropped', txId)
|
||||
return
|
||||
this.emit('tx:dropped', txId);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const transactionReceipt = await this.query.getTransactionReceipt(txHash)
|
||||
const transactionReceipt = await this.query.getTransactionReceipt(txHash);
|
||||
if (transactionReceipt?.blockNumber) {
|
||||
this.emit('tx:confirmed', txId, transactionReceipt)
|
||||
return
|
||||
this.emit('tx:confirmed', txId, transactionReceipt);
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
txMeta.warning = {
|
||||
error: err.message,
|
||||
message: 'There was a problem loading this transaction.',
|
||||
}
|
||||
this.emit('tx:warning', txMeta, err)
|
||||
return
|
||||
};
|
||||
this.emit('tx:warning', txMeta, err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (await this._checkIfTxWasDropped(txMeta)) {
|
||||
this.emit('tx:dropped', txId)
|
||||
this.emit('tx:dropped', txId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,26 +221,26 @@ export default class PendingTransactionTracker extends EventEmitter {
|
||||
const {
|
||||
hash: txHash,
|
||||
txParams: { nonce, from },
|
||||
} = txMeta
|
||||
const networkNextNonce = await this.query.getTransactionCount(from)
|
||||
} = txMeta;
|
||||
const networkNextNonce = await this.query.getTransactionCount(from);
|
||||
|
||||
if (parseInt(nonce, 16) >= networkNextNonce.toNumber()) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.droppedBlocksBufferByHash.has(txHash)) {
|
||||
this.droppedBlocksBufferByHash.set(txHash, 0)
|
||||
this.droppedBlocksBufferByHash.set(txHash, 0);
|
||||
}
|
||||
|
||||
const currentBlockBuffer = this.droppedBlocksBufferByHash.get(txHash)
|
||||
const currentBlockBuffer = this.droppedBlocksBufferByHash.get(txHash);
|
||||
|
||||
if (currentBlockBuffer < this.DROPPED_BUFFER_COUNT) {
|
||||
this.droppedBlocksBufferByHash.set(txHash, currentBlockBuffer + 1)
|
||||
return false
|
||||
this.droppedBlocksBufferByHash.set(txHash, currentBlockBuffer + 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.droppedBlocksBufferByHash.delete(txHash)
|
||||
return true
|
||||
this.droppedBlocksBufferByHash.delete(txHash);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -250,8 +250,8 @@ export default class PendingTransactionTracker extends EventEmitter {
|
||||
* @private
|
||||
*/
|
||||
async _checkIfNonceIsTaken(txMeta) {
|
||||
const address = txMeta.txParams.from
|
||||
const completed = this.getCompletedTransactions(address)
|
||||
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
|
||||
@ -259,6 +259,6 @@ export default class PendingTransactionTracker extends EventEmitter {
|
||||
(other) =>
|
||||
!(other.id === txMeta.id) &&
|
||||
other.txParams.nonce === txMeta.txParams.nonce,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user