mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge pull request #9843 from MetaMask/Version-v8.1.4
Version v8.1.4 RC
This commit is contained in:
commit
2e73285f23
@ -309,9 +309,11 @@ jobs:
|
|||||||
path: test-artifacts
|
path: test-artifacts
|
||||||
destination: test-artifacts
|
destination: test-artifacts
|
||||||
# important: generate sesify viz AFTER uploading builds as artifacts
|
# important: generate sesify viz AFTER uploading builds as artifacts
|
||||||
- run:
|
# Temporarily disabled until we can update to a version of `sesify` with
|
||||||
name: build:sesify-viz
|
# this fix included: https://github.com/LavaMoat/LavaMoat/pull/121
|
||||||
command: ./.circleci/scripts/create-sesify-viz
|
# - run:
|
||||||
|
# name: build:sesify-viz
|
||||||
|
# command: ./.circleci/scripts/create-sesify-viz
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: build-artifacts
|
path: build-artifacts
|
||||||
destination: build-artifacts
|
destination: build-artifacts
|
||||||
|
231
.eslintrc.js
231
.eslintrc.js
@ -2,19 +2,19 @@ module.exports = {
|
|||||||
root: true,
|
root: true,
|
||||||
parser: '@babel/eslint-parser',
|
parser: '@babel/eslint-parser',
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
'sourceType': 'module',
|
sourceType: 'module',
|
||||||
'ecmaVersion': 2017,
|
ecmaVersion: 2017,
|
||||||
'ecmaFeatures': {
|
ecmaFeatures: {
|
||||||
'experimentalObjectRestSpread': true,
|
experimentalObjectRestSpread: true,
|
||||||
'impliedStrict': true,
|
impliedStrict: true,
|
||||||
'modules': true,
|
modules: true,
|
||||||
'blockBindings': true,
|
blockBindings: true,
|
||||||
'arrowFunctions': true,
|
arrowFunctions: true,
|
||||||
'objectLiteralShorthandMethods': true,
|
objectLiteralShorthandMethods: true,
|
||||||
'objectLiteralShorthandProperties': true,
|
objectLiteralShorthandProperties: true,
|
||||||
'templateStrings': true,
|
templateStrings: true,
|
||||||
'classes': true,
|
classes: true,
|
||||||
'jsx': true,
|
jsx: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -28,6 +28,9 @@ module.exports = {
|
|||||||
'coverage/',
|
'coverage/',
|
||||||
'app/scripts/chromereload.js',
|
'app/scripts/chromereload.js',
|
||||||
'app/vendor/**',
|
'app/vendor/**',
|
||||||
|
'test/e2e/send-eth-with-private-key-test/**',
|
||||||
|
'nyc_output/**',
|
||||||
|
'.vscode/**',
|
||||||
],
|
],
|
||||||
|
|
||||||
extends: [
|
extends: [
|
||||||
@ -38,11 +41,7 @@ module.exports = {
|
|||||||
'plugin:react-hooks/recommended',
|
'plugin:react-hooks/recommended',
|
||||||
],
|
],
|
||||||
|
|
||||||
plugins: [
|
plugins: ['@babel', 'react', 'import', 'prettier'],
|
||||||
'@babel',
|
|
||||||
'react',
|
|
||||||
'import',
|
|
||||||
],
|
|
||||||
|
|
||||||
globals: {
|
globals: {
|
||||||
document: 'readonly',
|
document: 'readonly',
|
||||||
@ -50,6 +49,67 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
rules: {
|
rules: {
|
||||||
|
// Prettier changes and reasoning
|
||||||
|
|
||||||
|
'prettier/prettier': 'error',
|
||||||
|
|
||||||
|
// Our usage of spaces before *named* function parens is unusual, and
|
||||||
|
// doesn't match the prettier spec. prettier does not offer an option
|
||||||
|
// to configure this
|
||||||
|
'space-before-function-paren': [
|
||||||
|
'error',
|
||||||
|
{ anonymous: 'always', named: 'never' },
|
||||||
|
],
|
||||||
|
// Our eslint config has the default setting for this as error. This
|
||||||
|
// include beforeBlockComment: true, but in order to match the prettier
|
||||||
|
// spec you have to enable before and after blocks, objects and arrays
|
||||||
|
// https://github.com/prettier/eslint-config-prettier#lines-around-comment
|
||||||
|
'lines-around-comment': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
beforeBlockComment: true,
|
||||||
|
afterLineComment: false,
|
||||||
|
allowBlockStart: true,
|
||||||
|
allowBlockEnd: true,
|
||||||
|
allowObjectStart: true,
|
||||||
|
allowObjectEnd: true,
|
||||||
|
allowArrayStart: true,
|
||||||
|
allowArrayEnd: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// Prettier has some opinions on mixed-operators, and there is ongoing work
|
||||||
|
// to make the output code clear. It is better today then it was when the first
|
||||||
|
// PR to add prettier. That being said, the workaround for keeping this rule enabled
|
||||||
|
// requires breaking parts of operations into different variables -- which I believe
|
||||||
|
// to be worse. https://github.com/prettier/eslint-config-prettier#no-mixed-operators
|
||||||
|
'no-mixed-operators': 'off',
|
||||||
|
// Prettier wraps single line functions with ternaries, etc in parens by default, but
|
||||||
|
// if the line is long enough it breaks it into a separate line and removes the parens.
|
||||||
|
// The second behavior conflicts with this rule. There is some guides on the repo about
|
||||||
|
// how you can keep it enabled:
|
||||||
|
// https://github.com/prettier/eslint-config-prettier#no-confusing-arrow
|
||||||
|
// However, in practice this conflicts with prettier adding parens around short lines,
|
||||||
|
// when autofixing in vscode and others.
|
||||||
|
'no-confusing-arrow': 'off',
|
||||||
|
// There is no configuration in prettier for how it stylizes regexes, which conflicts
|
||||||
|
// with wrap-regex.
|
||||||
|
'wrap-regex': 'off',
|
||||||
|
// Prettier handles all indentation automagically. it can be configured here
|
||||||
|
// https://prettier.io/docs/en/options.html#tab-width but the default matches our
|
||||||
|
// style.
|
||||||
|
indent: 'off',
|
||||||
|
// This rule conflicts with the way that prettier breaks code across multiple lines when
|
||||||
|
// it exceeds the maximum length. Prettier optimizes for readability while simultaneously
|
||||||
|
// maximizing the amount of code per line.
|
||||||
|
'function-paren-newline': 'off',
|
||||||
|
// This rule throws an error when there is a line break in an arrow function declaration
|
||||||
|
// but prettier breaks arrow function declarations to be as readable as possible while
|
||||||
|
// still conforming to the width rules.
|
||||||
|
'implicit-arrow-linebreak': 'off',
|
||||||
|
// This rule would result in an increase in white space in lines with generator functions,
|
||||||
|
// which impacts prettier's goal of maximizing code per line and readability. There is no
|
||||||
|
// current workaround.
|
||||||
|
'generator-star-spacing': 'off',
|
||||||
'default-param-last': 'off',
|
'default-param-last': 'off',
|
||||||
'require-atomic-updates': 'off',
|
'require-atomic-updates': 'off',
|
||||||
'import/no-unassigned-import': 'off',
|
'import/no-unassigned-import': 'off',
|
||||||
@ -57,29 +117,32 @@ module.exports = {
|
|||||||
'react/no-unused-prop-types': 'error',
|
'react/no-unused-prop-types': 'error',
|
||||||
'react/no-unused-state': 'error',
|
'react/no-unused-state': 'error',
|
||||||
'react/jsx-boolean-value': 'error',
|
'react/jsx-boolean-value': 'error',
|
||||||
'react/jsx-curly-brace-presence': ['error', { 'props': 'never', 'children': 'never' }],
|
'react/jsx-curly-brace-presence': [
|
||||||
|
'error',
|
||||||
|
{ props: 'never', children: 'never' },
|
||||||
|
],
|
||||||
'react/jsx-equals-spacing': 'error',
|
'react/jsx-equals-spacing': 'error',
|
||||||
'react/no-deprecated': 'error',
|
'react/no-deprecated': 'error',
|
||||||
'react/default-props-match-prop-types': 'error',
|
'react/default-props-match-prop-types': 'error',
|
||||||
'react/jsx-closing-tag-location': 'error',
|
'react/jsx-closing-tag-location': [
|
||||||
|
'error',
|
||||||
|
{ selfClosing: 'tag-aligned', nonEmpty: 'tag-aligned' },
|
||||||
|
],
|
||||||
'react/jsx-no-duplicate-props': 'error',
|
'react/jsx-no-duplicate-props': 'error',
|
||||||
'react/jsx-closing-bracket-location': 'error',
|
'react/jsx-closing-bracket-location': 'error',
|
||||||
'react/jsx-first-prop-new-line': ['error', 'multiline'],
|
'react/jsx-first-prop-new-line': ['error', 'multiline'],
|
||||||
'react/jsx-max-props-per-line': ['error', { 'maximum': 1, 'when': 'multiline' }],
|
'react/jsx-max-props-per-line': [
|
||||||
'react/jsx-tag-spacing': ['error', {
|
'error',
|
||||||
'closingSlash': 'never',
|
{ maximum: 1, when: 'multiline' },
|
||||||
'beforeSelfClosing': 'always',
|
],
|
||||||
'afterOpening': 'never',
|
'react/jsx-tag-spacing': [
|
||||||
}],
|
'error',
|
||||||
'react/jsx-wrap-multilines': ['error', {
|
{
|
||||||
'declaration': 'parens-new-line',
|
closingSlash: 'never',
|
||||||
'assignment': 'parens-new-line',
|
beforeSelfClosing: 'always',
|
||||||
'return': 'parens-new-line',
|
afterOpening: 'never',
|
||||||
'arrow': 'parens-new-line',
|
},
|
||||||
'condition': 'parens-new-line',
|
],
|
||||||
'logical': 'parens-new-line',
|
|
||||||
'prop': 'parens-new-line',
|
|
||||||
}],
|
|
||||||
|
|
||||||
'no-invalid-this': 'off',
|
'no-invalid-this': 'off',
|
||||||
'@babel/no-invalid-this': 'error',
|
'@babel/no-invalid-this': 'error',
|
||||||
@ -93,67 +156,59 @@ module.exports = {
|
|||||||
'node/no-unpublished-import': 'off',
|
'node/no-unpublished-import': 'off',
|
||||||
'node/no-unpublished-require': 'off',
|
'node/no-unpublished-require': 'off',
|
||||||
},
|
},
|
||||||
|
overrides: [
|
||||||
overrides: [{
|
{
|
||||||
files: [
|
files: ['test/e2e/**/*.js'],
|
||||||
'test/e2e/**/*.js',
|
rules: {
|
||||||
],
|
'mocha/no-hooks-for-single-case': 'off',
|
||||||
rules: {
|
},
|
||||||
'mocha/no-hooks-for-single-case': 'off',
|
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
files: [
|
files: ['app/scripts/migrations/*.js', '*.stories.js'],
|
||||||
'app/scripts/migrations/*.js',
|
rules: {
|
||||||
'*.stories.js',
|
'import/no-anonymous-default-export': ['error', { allowObject: true }],
|
||||||
],
|
},
|
||||||
rules: {
|
|
||||||
'import/no-anonymous-default-export': ['error', { 'allowObject': true }],
|
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
files: [
|
files: ['app/scripts/migrations/*.js'],
|
||||||
'app/scripts/migrations/*.js',
|
rules: {
|
||||||
],
|
'node/global-require': 'off',
|
||||||
rules: {
|
},
|
||||||
'node/global-require': 'off',
|
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
files: [
|
files: ['test/**/*-test.js', 'test/**/*.spec.js'],
|
||||||
'test/**/*-test.js',
|
rules: {
|
||||||
'test/**/*.spec.js',
|
// Mocha will re-assign `this` in a test context
|
||||||
],
|
'@babel/no-invalid-this': 'off',
|
||||||
rules: {
|
},
|
||||||
// Mocha will re-assign `this` in a test context
|
|
||||||
'@babel/no-invalid-this': 'off',
|
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
files: [
|
files: ['development/**/*.js', 'test/e2e/benchmark.js', 'test/helper.js'],
|
||||||
'development/**/*.js',
|
rules: {
|
||||||
'test/e2e/benchmark.js',
|
'node/no-process-exit': 'off',
|
||||||
'test/helper.js',
|
'node/shebang': 'off',
|
||||||
],
|
},
|
||||||
rules: {
|
|
||||||
'node/no-process-exit': 'off',
|
|
||||||
'node/shebang': 'off',
|
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
files: [
|
files: [
|
||||||
'.eslintrc.js',
|
'.eslintrc.js',
|
||||||
'babel.config.js',
|
'babel.config.js',
|
||||||
'nyc.config.js',
|
'nyc.config.js',
|
||||||
'stylelint.config.js',
|
'stylelint.config.js',
|
||||||
'development/**/*.js',
|
'development/**/*.js',
|
||||||
'test/e2e/**/*.js',
|
'test/e2e/**/*.js',
|
||||||
'test/env.js',
|
'test/env.js',
|
||||||
'test/setup.js',
|
'test/setup.js',
|
||||||
],
|
],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
sourceType: 'script',
|
sourceType: 'script',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}],
|
],
|
||||||
|
|
||||||
settings: {
|
settings: {
|
||||||
'react': {
|
react: {
|
||||||
'version': 'detect',
|
version: 'detect',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -6,3 +6,4 @@ coverage/
|
|||||||
app/vendor/**
|
app/vendor/**
|
||||||
.nyc_output/**
|
.nyc_output/**
|
||||||
.vscode/**
|
.vscode/**
|
||||||
|
test/e2e/send-eth-with-private-key-test/**
|
||||||
|
3
.prettierrc.yml
Normal file
3
.prettierrc.yml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
singleQuote: true
|
||||||
|
semi: false
|
||||||
|
trailingComma: all
|
34
CHANGELOG.md
34
CHANGELOG.md
@ -3,6 +3,40 @@
|
|||||||
## Current Develop Branch
|
## Current Develop Branch
|
||||||
- [#9612](https://github.com/MetaMask/metamask-extension/pull/9612): Update main-quote-summary designs/styles
|
- [#9612](https://github.com/MetaMask/metamask-extension/pull/9612): Update main-quote-summary designs/styles
|
||||||
|
|
||||||
|
## 8.1.4 Tue Nov 10 2020
|
||||||
|
- [#9687](https://github.com/MetaMask/metamask-extension/pull/9687): Allow speeding up of underpriced transactions
|
||||||
|
- [#9694](https://github.com/MetaMask/metamask-extension/pull/9694): normalize UI component font styles
|
||||||
|
- [#9695](https://github.com/MetaMask/metamask-extension/pull/9695): normalize app component font styles
|
||||||
|
- [#9696](https://github.com/MetaMask/metamask-extension/pull/9696): normalize deprecated itcss font styles
|
||||||
|
- [#9697](https://github.com/MetaMask/metamask-extension/pull/9697): normalize page font styles
|
||||||
|
- [#9740](https://github.com/MetaMask/metamask-extension/pull/9740): Standardize network settings page
|
||||||
|
- [#9750](https://github.com/MetaMask/metamask-extension/pull/9750): Make swap arrows accessible, make swaps advanced options accessible
|
||||||
|
- [#9766](https://github.com/MetaMask/metamask-extension/pull/9766): Use 1px borders on inputs and buttons
|
||||||
|
- [#9767](https://github.com/MetaMask/metamask-extension/pull/9767): Remove border radius from transfer button
|
||||||
|
- [#9764](https://github.com/MetaMask/metamask-extension/pull/9764): Update custom RPC network dropdown icons
|
||||||
|
- [#9763](https://github.com/MetaMask/metamask-extension/pull/9763): Add confirmation for network dropdown delete action
|
||||||
|
- [#9583](https://github.com/MetaMask/metamask-extension/pull/9583): Use `chainId` for incoming transactions controller
|
||||||
|
- [#9748](https://github.com/MetaMask/metamask-extension/pull/9748): Autofocus input, improve accessibility of restore page
|
||||||
|
- [#9778](https://github.com/MetaMask/metamask-extension/pull/9778): Shorten unit input width and use ellipses for overflow
|
||||||
|
- [#9746](https://github.com/MetaMask/metamask-extension/pull/9746): Make the login screen's Restore and Import links accessible
|
||||||
|
- [#9780](https://github.com/MetaMask/metamask-extension/pull/9780): Display decimal chain ID in network form
|
||||||
|
- [#9599](https://github.com/MetaMask/metamask-extension/pull/9599): Use MetaSwap API for gas price estimation in swaps
|
||||||
|
- [#9518](https://github.com/MetaMask/metamask-extension/pull/9518): Make all UI tabs accessible via keyboard
|
||||||
|
- [#9808](https://github.com/MetaMask/metamask-extension/pull/9808): Always allow overwriting invalid custom RPC chain ID
|
||||||
|
- [#9812](https://github.com/MetaMask/metamask-extension/pull/9812): Fix send header cancel button alignment
|
||||||
|
- [#9271](https://github.com/MetaMask/metamask-extension/pull/9271): Do not check popupIsOpen on Vivaldi
|
||||||
|
- [#9306](https://github.com/MetaMask/metamask-extension/pull/9306): Fix UI crash when dapp submits negative gas price
|
||||||
|
- [#9257](https://github.com/MetaMask/metamask-extension/pull/9257): Add sort and search to AddRecipient accounts list
|
||||||
|
- [#9824](https://github.com/MetaMask/metamask-extension/pull/9824): Move `externally_connectable` from base to Chrome manifest
|
||||||
|
- [#9815](https://github.com/MetaMask/metamask-extension/pull/9815): Add support for custom network RPC URL with basic auth
|
||||||
|
- [#9822](https://github.com/MetaMask/metamask-extension/pull/9822): Make QR code button focusable
|
||||||
|
- [#9832](https://github.com/MetaMask/metamask-extension/pull/9832): Warn instead of throw on duplicate web3
|
||||||
|
- [#9838](https://github.com/MetaMask/metamask-extension/pull/9838): @metamask/controllers@4.0.0
|
||||||
|
- [#9856](https://github.com/MetaMask/metamask-extension/pull/9856): Prevent user from getting stuck on opt in page
|
||||||
|
- [#9845](https://github.com/MetaMask/metamask-extension/pull/9845): Show a 'send eth' button on home screen in full screen mode
|
||||||
|
- [#9871](https://github.com/MetaMask/metamask-extension/pull/9871): Show send text upon hover in main asset list
|
||||||
|
- [#9880](https://github.com/MetaMask/metamask-extension/pull/9880): Properly detect U2F errors in hardware wallet
|
||||||
|
|
||||||
## 8.1.3 Mon Oct 26 2020
|
## 8.1.3 Mon Oct 26 2020
|
||||||
- [#9642](https://github.com/MetaMask/metamask-extension/pull/9642) Prevent excessive overflow from swap dropdowns
|
- [#9642](https://github.com/MetaMask/metamask-extension/pull/9642) Prevent excessive overflow from swap dropdowns
|
||||||
- [#9658](https://github.com/MetaMask/metamask-extension/pull/9658): Fix sorting Quote Source column of quote sort list
|
- [#9658](https://github.com/MetaMask/metamask-extension/pull/9658): Fix sorting Quote Source column of quote sort list
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "ሰርዝ"
|
"message": "ሰርዝ"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "ሙከራን ሰርዝ"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "የስረዛ ነዳጅ ወጪ"
|
"message": "የስረዛ ነዳጅ ወጪ"
|
||||||
},
|
},
|
||||||
@ -308,9 +305,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "ይህን አውታረ መረብ ለመሰረዝ እንደሚፈልጉ እርግጠኛ ነዎት?"
|
"message": "ይህን አውታረ መረብ ለመሰረዝ እንደሚፈልጉ እርግጠኛ ነዎት?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "ማጠራቀም"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Ether አስቀምጥ"
|
"message": "Ether አስቀምጥ"
|
||||||
},
|
},
|
||||||
@ -737,7 +731,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "ኤክስፕሎረር URL አግድ (አማራጭ)"
|
"message": "ኤክስፕሎረር URL አግድ (አማራጭ)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "ምልክት (አማራጭ)"
|
"message": "ምልክት (አማራጭ)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -985,9 +979,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "የተላከ ether"
|
"message": "የተላከ ether"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "የተላኩ ተለዋጭ ስሞች"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "እያንዳንዱን ቃል በነጠላ ክፍት ቦታ ይለያዩ"
|
"message": "እያንዳንዱን ቃል በነጠላ ክፍት ቦታ ይለያዩ"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "إلغاء"
|
"message": "إلغاء"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "إلغاء المحاولة"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "رسوم الإلغاء بعملة جاس"
|
"message": "رسوم الإلغاء بعملة جاس"
|
||||||
},
|
},
|
||||||
@ -308,9 +305,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "هل أنت متأكد أنك تريد حذف هذه الشبكة؟"
|
"message": "هل أنت متأكد أنك تريد حذف هذه الشبكة؟"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "إيداع"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "إيداع عملة إيثير"
|
"message": "إيداع عملة إيثير"
|
||||||
},
|
},
|
||||||
@ -733,7 +727,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "العنوان الإلكتروني لمستكشف البلوكات (اختياري)"
|
"message": "العنوان الإلكتروني لمستكشف البلوكات (اختياري)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "الرمز (اختياري)"
|
"message": "الرمز (اختياري)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -981,9 +975,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "أرسل عملة إيثير"
|
"message": "أرسل عملة إيثير"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "العملات الرمزية المرسلة"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "افصل كل كلمة بمسافة واحدة"
|
"message": "افصل كل كلمة بمسافة واحدة"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Отказ"
|
"message": "Отказ"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Отмяна на опита"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Такса в газ за анулиране "
|
"message": "Такса в газ за анулиране "
|
||||||
},
|
},
|
||||||
@ -308,9 +305,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Наистина ли искате да изтриете тази мрежа?"
|
"message": "Наистина ли искате да изтриете тази мрежа?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Депозит"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Депозирайте етер"
|
"message": "Депозирайте етер"
|
||||||
},
|
},
|
||||||
@ -736,7 +730,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Блокиране на Explorer URL (по избор)"
|
"message": "Блокиране на Explorer URL (по избор)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Символ (по избор)"
|
"message": "Символ (по избор)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -984,9 +978,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "изпратен етер"
|
"message": "изпратен етер"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "изпратени жетони"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Отделете всяка дума с интервал"
|
"message": "Отделете всяка дума с интервал"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "বাতিল করুন"
|
"message": "বাতিল করুন"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "প্রচেষ্টা বাতিল করুন"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "বাতিল করার গ্যাস ফী"
|
"message": "বাতিল করার গ্যাস ফী"
|
||||||
},
|
},
|
||||||
@ -308,9 +305,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "আপনি কি এই নেটওয়ার্কটি মোছার বিষয়ে নিশ্চিত?"
|
"message": "আপনি কি এই নেটওয়ার্কটি মোছার বিষয়ে নিশ্চিত?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "জমা "
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "ইথার জমা করুন"
|
"message": "ইথার জমা করুন"
|
||||||
},
|
},
|
||||||
@ -740,7 +734,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "এক্সপ্লোরার URL ব্লক করুন (ঐচ্ছিক)"
|
"message": "এক্সপ্লোরার URL ব্লক করুন (ঐচ্ছিক)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "প্রতীক (ঐচ্ছিক)"
|
"message": "প্রতীক (ঐচ্ছিক)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -988,9 +982,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "পাঠানো ইথার "
|
"message": "পাঠানো ইথার "
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "টোকেনগুলি পাঠান"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "প্রতিটি শব্দকে একটি স্পেস দিয়ে আলাদা করুন"
|
"message": "প্রতিটি শব্দকে একটি স্পেস দিয়ে আলাদা করুন"
|
||||||
},
|
},
|
||||||
|
@ -161,9 +161,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Cancel·la"
|
"message": "Cancel·la"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Cancel·la l'intent"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Preu de cancel·lació del gas"
|
"message": "Preu de cancel·lació del gas"
|
||||||
},
|
},
|
||||||
@ -305,9 +302,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Estàs segur que vols eliminar aquesta xarxa?"
|
"message": "Estàs segur que vols eliminar aquesta xarxa?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Depòsit"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Diposita Ether"
|
"message": "Diposita Ether"
|
||||||
},
|
},
|
||||||
@ -724,7 +718,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Bloqueja l'URL d'Explorer (opcional)"
|
"message": "Bloqueja l'URL d'Explorer (opcional)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Símbol (opcional)"
|
"message": "Símbol (opcional)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -966,9 +960,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "envia ether"
|
"message": "envia ether"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "fitxes enviades"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Separa cada paraula amb un sol espai"
|
"message": "Separa cada paraula amb un sol espai"
|
||||||
},
|
},
|
||||||
|
@ -118,9 +118,6 @@
|
|||||||
"defaultNetwork": {
|
"defaultNetwork": {
|
||||||
"message": "Výchozí síť pro Etherové transakce je Main Net."
|
"message": "Výchozí síť pro Etherové transakce je Main Net."
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Vklad"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Vložit Ether"
|
"message": "Vložit Ether"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Afbryd"
|
"message": "Afbryd"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Annullér forsøg"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Gebyr for brændstofannullering"
|
"message": "Gebyr for brændstofannullering"
|
||||||
},
|
},
|
||||||
@ -308,9 +305,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Er du sikker på, at du vil slette dette netværk?"
|
"message": "Er du sikker på, at du vil slette dette netværk?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Indbetal"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Indbetal Ether"
|
"message": "Indbetal Ether"
|
||||||
},
|
},
|
||||||
@ -724,7 +718,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Blok-stifinder-URL (valgfrit)"
|
"message": "Blok-stifinder-URL (valgfrit)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Symbol (valgfrit)"
|
"message": "Symbol (valgfrit)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -966,9 +960,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "sendte ether"
|
"message": "sendte ether"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "afsendte tokens"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Separer hvert ord med et enkelt mellemrum"
|
"message": "Separer hvert ord med et enkelt mellemrum"
|
||||||
},
|
},
|
||||||
|
@ -158,9 +158,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Abbrechen"
|
"message": "Abbrechen"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Versuch abbrechen"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Stornierungs-Gasgebühr"
|
"message": "Stornierungs-Gasgebühr"
|
||||||
},
|
},
|
||||||
@ -299,9 +296,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Sind Sie sicher, dass Sie dieses Netzwerk löschen möchten?"
|
"message": "Sind Sie sicher, dass Sie dieses Netzwerk löschen möchten?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Einzahlen"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Ether einzahlen"
|
"message": "Ether einzahlen"
|
||||||
},
|
},
|
||||||
@ -957,9 +951,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "Ether senden"
|
"message": "Ether senden"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "gesendete Token"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Trennen Sie die Wörter mit einem einzelnen Leerzeichen voneinander"
|
"message": "Trennen Sie die Wörter mit einem einzelnen Leerzeichen voneinander"
|
||||||
},
|
},
|
||||||
|
@ -161,9 +161,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Ακύρωση"
|
"message": "Ακύρωση"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Ακύρωση Προσπάθειας"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Ακύρωση Χρέωσης Αερίου"
|
"message": "Ακύρωση Χρέωσης Αερίου"
|
||||||
},
|
},
|
||||||
@ -305,9 +302,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Θέλετε σίγουρα να διαγράψετε αυτό το δίκτυο;"
|
"message": "Θέλετε σίγουρα να διαγράψετε αυτό το δίκτυο;"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Κατάθεση"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Κατάθεση Ether"
|
"message": "Κατάθεση Ether"
|
||||||
},
|
},
|
||||||
@ -737,7 +731,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Διεύθυνση URL Εξερευνητή Μπλοκ (προαιρετικό)"
|
"message": "Διεύθυνση URL Εξερευνητή Μπλοκ (προαιρετικό)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Σύμβολο (προαιρετικό)"
|
"message": "Σύμβολο (προαιρετικό)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -985,9 +979,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "απεσταλμένα ether"
|
"message": "απεσταλμένα ether"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "αποστολή token"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Διαχωρίστε κάθε λέξη με ένα μόνο κενό"
|
"message": "Διαχωρίστε κάθε λέξη με ένα μόνο κενό"
|
||||||
},
|
},
|
||||||
|
@ -239,9 +239,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Cancel"
|
"message": "Cancel"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Cancel Attempt"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Cancellation Gas Fee"
|
"message": "Cancellation Gas Fee"
|
||||||
},
|
},
|
||||||
@ -477,9 +474,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Are you sure you want to delete this network?"
|
"message": "Are you sure you want to delete this network?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Deposit"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Deposit Ether"
|
"message": "Deposit Ether"
|
||||||
},
|
},
|
||||||
@ -875,7 +869,7 @@
|
|||||||
"message": "Invalid IPFS Gateway: The value must be a valid URL"
|
"message": "Invalid IPFS Gateway: The value must be a valid URL"
|
||||||
},
|
},
|
||||||
"invalidNumber": {
|
"invalidNumber": {
|
||||||
"message": "Invalid number. Enter a decimal or hexadecimal number."
|
"message": "Invalid number. Enter a decimal or '0x'-prefixed hexadecimal number."
|
||||||
},
|
},
|
||||||
"invalidNumberLeadingZeros": {
|
"invalidNumberLeadingZeros": {
|
||||||
"message": "Invalid number. Remove any leading zeros."
|
"message": "Invalid number. Remove any leading zeros."
|
||||||
@ -1037,7 +1031,7 @@
|
|||||||
"message": "Network Name"
|
"message": "Network Name"
|
||||||
},
|
},
|
||||||
"networkSettingsChainIdDescription": {
|
"networkSettingsChainIdDescription": {
|
||||||
"message": "The chain ID is used for signing transactions. It must match the chain ID returned by the network. Enter a decimal or hexadecimal number starting with '0x'."
|
"message": "The chain ID is used for signing transactions. It must match the chain ID returned by the network. You can enter a decimal or '0x'-prefixed hexadecimal number, but we will display the number in decimal."
|
||||||
},
|
},
|
||||||
"networkSettingsDescription": {
|
"networkSettingsDescription": {
|
||||||
"message": "Add and edit custom RPC networks"
|
"message": "Add and edit custom RPC networks"
|
||||||
@ -1156,8 +1150,8 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Block Explorer URL (optional)"
|
"message": "Block Explorer URL (optional)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Symbol (optional)"
|
"message": "Currency Symbol (optional)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
"message": "Order a Trezor or Ledger and keep your funds in cold storage"
|
"message": "Order a Trezor or Ledger and keep your funds in cold storage"
|
||||||
@ -1458,9 +1452,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "sent ether"
|
"message": "sent ether"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "sent tokens"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Separate each word with a single space"
|
"message": "Separate each word with a single space"
|
||||||
},
|
},
|
||||||
@ -1827,6 +1818,9 @@
|
|||||||
"swapSwapFrom": {
|
"swapSwapFrom": {
|
||||||
"message": "Swap from"
|
"message": "Swap from"
|
||||||
},
|
},
|
||||||
|
"swapSwapSwitch": {
|
||||||
|
"message": "Switch from and to tokens"
|
||||||
|
},
|
||||||
"swapSwapTo": {
|
"swapSwapTo": {
|
||||||
"message": "Swap to"
|
"message": "Swap to"
|
||||||
},
|
},
|
||||||
|
@ -136,9 +136,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Cancelar"
|
"message": "Cancelar"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Intentar cancelar"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Comisión de Gas por cancelación"
|
"message": "Comisión de Gas por cancelación"
|
||||||
},
|
},
|
||||||
@ -271,9 +268,6 @@
|
|||||||
"deleteAccount": {
|
"deleteAccount": {
|
||||||
"message": "Eliminar Cuenta"
|
"message": "Eliminar Cuenta"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Depositar"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Depositar Ether"
|
"message": "Depositar Ether"
|
||||||
},
|
},
|
||||||
@ -580,7 +574,7 @@
|
|||||||
"ofTextNofM": {
|
"ofTextNofM": {
|
||||||
"message": "de"
|
"message": "de"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Símbolo (opcional)"
|
"message": "Símbolo (opcional)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -783,9 +777,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "se mandó ether"
|
"message": "se mandó ether"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "se mandaron tokens"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Separar a cada palabra con un sólo espacio"
|
"message": "Separar a cada palabra con un sólo espacio"
|
||||||
},
|
},
|
||||||
|
@ -161,9 +161,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Cancelar"
|
"message": "Cancelar"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Cancelar intento"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Tasa de cancelación de gas"
|
"message": "Tasa de cancelación de gas"
|
||||||
},
|
},
|
||||||
@ -305,9 +302,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "¿Estás seguro de que deseas borrar esta red?"
|
"message": "¿Estás seguro de que deseas borrar esta red?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Depósito"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Depositar Ethers"
|
"message": "Depositar Ethers"
|
||||||
},
|
},
|
||||||
@ -725,7 +719,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Bloquear la URL de Explorer (opcional)"
|
"message": "Bloquear la URL de Explorer (opcional)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Símbolo (opcional)"
|
"message": "Símbolo (opcional)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -973,9 +967,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "Ethers enviados"
|
"message": "Ethers enviados"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "tokens enviados"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Separa cada palabra con un solo espacio"
|
"message": "Separa cada palabra con un solo espacio"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Tühista"
|
"message": "Tühista"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Tühista katse"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Tühistamise gaasitasu"
|
"message": "Tühistamise gaasitasu"
|
||||||
},
|
},
|
||||||
@ -308,9 +305,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Olete kindel, et soovite selle võrgu kustutada?"
|
"message": "Olete kindel, et soovite selle võrgu kustutada?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Sissemakse"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Eetri sissemakse"
|
"message": "Eetri sissemakse"
|
||||||
},
|
},
|
||||||
@ -730,7 +724,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Blokeeri Exploreri URL (valikuline)"
|
"message": "Blokeeri Exploreri URL (valikuline)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Sümbol (valikuline)"
|
"message": "Sümbol (valikuline)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -978,9 +972,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "saadetud eeter"
|
"message": "saadetud eeter"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "saadetud load"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Eraldage iga sõna ühe tühikuga"
|
"message": "Eraldage iga sõna ühe tühikuga"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "لغو"
|
"message": "لغو"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "لغو تلاش"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "لغو فیس گاز"
|
"message": "لغو فیس گاز"
|
||||||
},
|
},
|
||||||
@ -308,9 +305,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "آیا مطمئن هستید که این شبکه حذف شود؟"
|
"message": "آیا مطمئن هستید که این شبکه حذف شود؟"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "سپرده"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "پرداخت ایتر"
|
"message": "پرداخت ایتر"
|
||||||
},
|
},
|
||||||
@ -740,7 +734,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "بلاک کردن مرورگر URL (انتخابی)"
|
"message": "بلاک کردن مرورگر URL (انتخابی)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "سمبول (انتخابی)"
|
"message": "سمبول (انتخابی)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -988,9 +982,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "ایتر ارسال شد"
|
"message": "ایتر ارسال شد"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "رمزیاب های فرستاده شده"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "هر کلمه را با یک فاصله واحد جدا سازید"
|
"message": "هر کلمه را با یک فاصله واحد جدا سازید"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Peruuta"
|
"message": "Peruuta"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Peruuta yritys"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Peruutuksen gas-maksu"
|
"message": "Peruutuksen gas-maksu"
|
||||||
},
|
},
|
||||||
@ -308,9 +305,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Haluatko varmasti poistaa tämän verkon?"
|
"message": "Haluatko varmasti poistaa tämän verkon?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Talleta"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Talleta Etheriä"
|
"message": "Talleta Etheriä"
|
||||||
},
|
},
|
||||||
@ -737,7 +731,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Estä Explorerin URL-osoite (valinnainen)"
|
"message": "Estä Explorerin URL-osoite (valinnainen)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Symboli (valinnainen)"
|
"message": "Symboli (valinnainen)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -985,9 +979,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "lähetä etheriä"
|
"message": "lähetä etheriä"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "lähetetyt poletit"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Erottele sanat toisistaan yhdellä välilyönnillä"
|
"message": "Erottele sanat toisistaan yhdellä välilyönnillä"
|
||||||
},
|
},
|
||||||
|
@ -146,9 +146,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Kanselahin"
|
"message": "Kanselahin"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Kanselahin ang Pagtangka"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Gas Fee sa Pagkansela"
|
"message": "Gas Fee sa Pagkansela"
|
||||||
},
|
},
|
||||||
@ -284,9 +281,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Sigurado ka bang gusto mong i-delete ang network na ito?"
|
"message": "Sigurado ka bang gusto mong i-delete ang network na ito?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Deposito"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Magdeposito ng Ether"
|
"message": "Magdeposito ng Ether"
|
||||||
},
|
},
|
||||||
@ -671,7 +665,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Block Explorer URL (opsyonal)"
|
"message": "Block Explorer URL (opsyonal)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Simbolo (opsyonal)"
|
"message": "Simbolo (opsyonal)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -900,9 +894,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "nagpadala ng ether"
|
"message": "nagpadala ng ether"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "mga ipinadalang token"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Paghiwa-hiwalayin ang bawat salita gamit ang isang space"
|
"message": "Paghiwa-hiwalayin ang bawat salita gamit ang isang space"
|
||||||
},
|
},
|
||||||
|
@ -155,9 +155,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Annuler"
|
"message": "Annuler"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Annuler la tentative."
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Frais en gas de l'annulation"
|
"message": "Frais en gas de l'annulation"
|
||||||
},
|
},
|
||||||
@ -299,9 +296,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Êtes-vous sûr de vouloir supprimer ce réseau ?"
|
"message": "Êtes-vous sûr de vouloir supprimer ce réseau ?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Déposer"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Déposer de l'Ether"
|
"message": "Déposer de l'Ether"
|
||||||
},
|
},
|
||||||
@ -722,7 +716,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Bloquer l'URL de l'explorateur (facultatif)"
|
"message": "Bloquer l'URL de l'explorateur (facultatif)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Symbole (facultatif)"
|
"message": "Symbole (facultatif)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -970,9 +964,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "Ether envoyé"
|
"message": "Ether envoyé"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "Jetons envoyés"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Separez chaque mot avec un espace simple"
|
"message": "Separez chaque mot avec un espace simple"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "ביטול"
|
"message": "ביטול"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "בטל ניסיון"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "עמלת דלק עבור ביטול"
|
"message": "עמלת דלק עבור ביטול"
|
||||||
},
|
},
|
||||||
@ -308,9 +305,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "הנך בטוח/ה שברצונך למחוק רשת זו?"
|
"message": "הנך בטוח/ה שברצונך למחוק רשת זו?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "הפקדה"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "הפקדת את'ר"
|
"message": "הפקדת את'ר"
|
||||||
},
|
},
|
||||||
@ -737,7 +731,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "חסום כתובת URL של אקספלורר (אופציונלי)"
|
"message": "חסום כתובת URL של אקספלורר (אופציונלי)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "סמל (אופציונלי)"
|
"message": "סמל (אופציונלי)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -982,9 +976,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "את'ר שנשלח"
|
"message": "את'ר שנשלח"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "טוקנים שנשלחו"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "יש להפריד כל מילה עם רווח יחיד"
|
"message": "יש להפריד כל מילה עם רווח יחיד"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "रद्द करें"
|
"message": "रद्द करें"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "प्रयास रद्द करें"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "रद्दीकरण गैस शुल्क"
|
"message": "रद्दीकरण गैस शुल्क"
|
||||||
},
|
},
|
||||||
@ -308,9 +305,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "क्या आप वाकई इस नेटवर्क को हटाना चाहते हैं?"
|
"message": "क्या आप वाकई इस नेटवर्क को हटाना चाहते हैं?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "जमा "
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Ether जमा करें"
|
"message": "Ether जमा करें"
|
||||||
},
|
},
|
||||||
@ -737,7 +731,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "एक्सप्लोरर यूआरएल ब्लॉक (वैकल्पिक)"
|
"message": "एक्सप्लोरर यूआरएल ब्लॉक (वैकल्पिक)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "सिम्बल (वैकल्पिक)"
|
"message": "सिम्बल (वैकल्पिक)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -985,9 +979,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "भेजे गए ether"
|
"message": "भेजे गए ether"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "भेजे गए टोकन"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "प्रत्येक शब्द को एक स्पेस से अलग करें"
|
"message": "प्रत्येक शब्द को एक स्पेस से अलग करें"
|
||||||
},
|
},
|
||||||
|
@ -97,9 +97,6 @@
|
|||||||
"defaultNetwork": {
|
"defaultNetwork": {
|
||||||
"message": "ईथर लेनदेन के लिए डिफ़ॉल्ट नेटवर्क मुख्य नेट है।"
|
"message": "ईथर लेनदेन के लिए डिफ़ॉल्ट नेटवर्क मुख्य नेट है।"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "जमा"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "जमा - Ether"
|
"message": "जमा - Ether"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Odustani"
|
"message": "Odustani"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Otkaži pokušaj"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Otkazivanje naknade za gorivo"
|
"message": "Otkazivanje naknade za gorivo"
|
||||||
},
|
},
|
||||||
@ -308,9 +305,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Sigurno želite izbrisati ovu mrežu?"
|
"message": "Sigurno želite izbrisati ovu mrežu?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Polog"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Položi Ether"
|
"message": "Položi Ether"
|
||||||
},
|
},
|
||||||
@ -733,7 +727,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Blokiraj Explorerov URL (neobavezno)"
|
"message": "Blokiraj Explorerov URL (neobavezno)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Simbol (neobavezno)"
|
"message": "Simbol (neobavezno)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -981,9 +975,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "pošalji ether"
|
"message": "pošalji ether"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "pošalji tokene"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Odvojite pojedinačne riječi jednim razmakom"
|
"message": "Odvojite pojedinačne riječi jednim razmakom"
|
||||||
},
|
},
|
||||||
|
@ -85,9 +85,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Anile"
|
"message": "Anile"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Teste Anile"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Anilasyon Gaz Chaj"
|
"message": "Anilasyon Gaz Chaj"
|
||||||
},
|
},
|
||||||
@ -169,9 +166,6 @@
|
|||||||
"defaultNetwork": {
|
"defaultNetwork": {
|
||||||
"message": "Dfo rezo a pou tranzaksyon Ether se Mainnet."
|
"message": "Dfo rezo a pou tranzaksyon Ether se Mainnet."
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Depo"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Depo Ether"
|
"message": "Depo Ether"
|
||||||
},
|
},
|
||||||
@ -615,9 +609,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "Voye ether"
|
"message": "Voye ether"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "tokens deja voye"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Separe chak mo ak yon sèl espas"
|
"message": "Separe chak mo ak yon sèl espas"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Mégse"
|
"message": "Mégse"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Kísérlet megszakítása"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "A törlés gázára"
|
"message": "A törlés gázára"
|
||||||
},
|
},
|
||||||
@ -308,9 +305,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Biztosan törli ezt a hálózatot?"
|
"message": "Biztosan törli ezt a hálózatot?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Befizetés"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Ether befizetése"
|
"message": "Ether befizetése"
|
||||||
},
|
},
|
||||||
@ -733,7 +727,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Explorer URL letiltása (nem kötelező)"
|
"message": "Explorer URL letiltása (nem kötelező)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Szimbólum (opcionális)"
|
"message": "Szimbólum (opcionális)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -981,9 +975,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "elküldött ether"
|
"message": "elküldött ether"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "elküldött érmék"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Minden egyes szavat szóközzel válasszon el"
|
"message": "Minden egyes szavat szóközzel válasszon el"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Batal"
|
"message": "Batal"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Batalkan Percobaan"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Pembatalan Biaya Gas"
|
"message": "Pembatalan Biaya Gas"
|
||||||
},
|
},
|
||||||
@ -721,7 +718,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Blokir URL Penjelajah (opsional)"
|
"message": "Blokir URL Penjelajah (opsional)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Simbol (opsional)"
|
"message": "Simbol (opsional)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -969,9 +966,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "kirim ether"
|
"message": "kirim ether"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "token terkirim"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Pisahkan setiap kata dengan spasi tunggal"
|
"message": "Pisahkan setiap kata dengan spasi tunggal"
|
||||||
},
|
},
|
||||||
|
@ -226,9 +226,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Annulla"
|
"message": "Annulla"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Tentativo di Annullamento"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Costo di Annullamento in Gas"
|
"message": "Costo di Annullamento in Gas"
|
||||||
},
|
},
|
||||||
@ -464,9 +461,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Sei sicuro di voler eliminare questa rete?"
|
"message": "Sei sicuro di voler eliminare questa rete?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Deposita"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Deposita Ether"
|
"message": "Deposita Ether"
|
||||||
},
|
},
|
||||||
@ -1056,7 +1050,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "URL del Block Explorer (opzionale)"
|
"message": "URL del Block Explorer (opzionale)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Simbolo (opzionale)"
|
"message": "Simbolo (opzionale)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -1358,9 +1352,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "ether inviati"
|
"message": "ether inviati"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "tokens inviati"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Separa ogni parola con un solo spazio"
|
"message": "Separa ogni parola con un solo spazio"
|
||||||
},
|
},
|
||||||
|
@ -169,9 +169,6 @@
|
|||||||
"defaultNetwork": {
|
"defaultNetwork": {
|
||||||
"message": "デフォルトのEther送受信ネットワークはメインネットです。"
|
"message": "デフォルトのEther送受信ネットワークはメインネットです。"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "振込"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Etherを振込"
|
"message": "Etherを振込"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "ರದ್ದುಮಾಡಿ"
|
"message": "ರದ್ದುಮಾಡಿ"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "ಪ್ರಯತ್ನವನ್ನು ರದ್ದುಪಡಿಸಿ"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "ರದ್ದುಗೊಳಿಸುವ ಗ್ಯಾಸ್ ಶುಲ್ಕ"
|
"message": "ರದ್ದುಗೊಳಿಸುವ ಗ್ಯಾಸ್ ಶುಲ್ಕ"
|
||||||
},
|
},
|
||||||
@ -308,9 +305,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "ನೀವು ಈ ನೆಟ್ವರ್ಕ್ ಅನ್ನು ಖಚಿತವಾಗಿ ಅಳಿಸಲು ಬಯಸುತ್ತೀರಾ?"
|
"message": "ನೀವು ಈ ನೆಟ್ವರ್ಕ್ ಅನ್ನು ಖಚಿತವಾಗಿ ಅಳಿಸಲು ಬಯಸುತ್ತೀರಾ?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "ಠೇವಣಿ"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "ಎಥರ್ ಠೇವಣಿ ಮಾಡಿ"
|
"message": "ಎಥರ್ ಠೇವಣಿ ಮಾಡಿ"
|
||||||
},
|
},
|
||||||
@ -740,7 +734,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "ಅನ್ವೇಷಕ URL ಅನ್ನು ನಿರ್ಬಂಧಿಸಿ (ಐಚ್ಛಿಕ)"
|
"message": "ಅನ್ವೇಷಕ URL ಅನ್ನು ನಿರ್ಬಂಧಿಸಿ (ಐಚ್ಛಿಕ)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "ಚಿಹ್ನೆ (ಐಚ್ಛಿಕ)"
|
"message": "ಚಿಹ್ನೆ (ಐಚ್ಛಿಕ)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -988,9 +982,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "ಕಳುಹಿಸಲಾದ ಎಥರ್"
|
"message": "ಕಳುಹಿಸಲಾದ ಎಥರ್"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "ಕಳುಹಿಸಲಾದ ಟೋಕನ್ಗಳು"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "ಒಂದು ಸ್ಪೇಸ್ ಮೂಲಕ ಪ್ರತಿ ಪದವನ್ನು ಬೇರ್ಪಡಿಸಿ"
|
"message": "ಒಂದು ಸ್ಪೇಸ್ ಮೂಲಕ ಪ್ರತಿ ಪದವನ್ನು ಬೇರ್ಪಡಿಸಿ"
|
||||||
},
|
},
|
||||||
|
@ -161,9 +161,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "취소"
|
"message": "취소"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "취소 시도"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "취소 가스 수수료"
|
"message": "취소 가스 수수료"
|
||||||
},
|
},
|
||||||
@ -305,9 +302,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "정말로 이 네트워크를 삭제하시겠습니까?"
|
"message": "정말로 이 네트워크를 삭제하시겠습니까?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "입금"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "이더리움 입금하기"
|
"message": "이더리움 입금하기"
|
||||||
},
|
},
|
||||||
@ -734,7 +728,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "익스플로러 URL 차단 (선택 사항)"
|
"message": "익스플로러 URL 차단 (선택 사항)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Symbol (선택)"
|
"message": "Symbol (선택)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -979,9 +973,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "전송된 이더"
|
"message": "전송된 이더"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "전송된 토큰"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "각 단어는 공백 한칸으로 분리합니다"
|
"message": "각 단어는 공백 한칸으로 분리합니다"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Atšaukti"
|
"message": "Atšaukti"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Atšaukti mėginimą"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Dujų mokesčio atšaukimas"
|
"message": "Dujų mokesčio atšaukimas"
|
||||||
},
|
},
|
||||||
@ -308,9 +305,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Ar tikrai norite panaikinti šį tinklą?"
|
"message": "Ar tikrai norite panaikinti šį tinklą?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Indėlis"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Įnešti eterių"
|
"message": "Įnešti eterių"
|
||||||
},
|
},
|
||||||
@ -740,7 +734,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Blokuoti naršyklės URL (pasirinktinai)"
|
"message": "Blokuoti naršyklės URL (pasirinktinai)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Simbolis (nebūtinas)"
|
"message": "Simbolis (nebūtinas)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -988,9 +982,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "siųsti eterių"
|
"message": "siųsti eterių"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "išsiųsti žetonai"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Kiekvieną žodį atskirkite viengubu tarpu"
|
"message": "Kiekvieną žodį atskirkite viengubu tarpu"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Atcelt"
|
"message": "Atcelt"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Atcelt mēģinājumu"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Atcelšanas maksājums par Gas"
|
"message": "Atcelšanas maksājums par Gas"
|
||||||
},
|
},
|
||||||
@ -308,9 +305,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Vai tiešām vēlaties dzēst šo tīklu?"
|
"message": "Vai tiešām vēlaties dzēst šo tīklu?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Iemaksa"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Noguldīt Ether"
|
"message": "Noguldīt Ether"
|
||||||
},
|
},
|
||||||
@ -736,7 +730,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Bloķēt Explorer URL (pēc izvēles)"
|
"message": "Bloķēt Explorer URL (pēc izvēles)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Simbols (neobligāti)"
|
"message": "Simbols (neobligāti)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -984,9 +978,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "nosūtītie ether"
|
"message": "nosūtītie ether"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "nosūtītie marķieri"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Atdaliet katru vārdu ar vienu atstarpi"
|
"message": "Atdaliet katru vārdu ar vienu atstarpi"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Batal"
|
"message": "Batal"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Batalkan Percubaan"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Fi Gas Pembatalan"
|
"message": "Fi Gas Pembatalan"
|
||||||
},
|
},
|
||||||
@ -714,7 +711,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Sekat URL Explorer (pilihan)"
|
"message": "Sekat URL Explorer (pilihan)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Simbol (pilihan)"
|
"message": "Simbol (pilihan)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -962,9 +959,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "menghantar ether"
|
"message": "menghantar ether"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "token dihantar"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Pisahkan setiap perkataan dengan ruang tunggal"
|
"message": "Pisahkan setiap perkataan dengan ruang tunggal"
|
||||||
},
|
},
|
||||||
|
@ -94,9 +94,6 @@
|
|||||||
"defaultNetwork": {
|
"defaultNetwork": {
|
||||||
"message": "Het standaardnetwerk voor Ether-transacties is Main Net."
|
"message": "Het standaardnetwerk voor Ether-transacties is Main Net."
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Storting"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Stort Ether"
|
"message": "Stort Ether"
|
||||||
},
|
},
|
||||||
|
@ -161,9 +161,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Avbryt"
|
"message": "Avbryt"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Avbryt forsøk"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Kansellering gassavgift"
|
"message": "Kansellering gassavgift"
|
||||||
},
|
},
|
||||||
@ -305,9 +302,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Er du sikker på at du vil slette dette nettverket?"
|
"message": "Er du sikker på at du vil slette dette nettverket?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Innskudd"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Sett inn Ether "
|
"message": "Sett inn Ether "
|
||||||
},
|
},
|
||||||
@ -727,7 +721,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Blokker Explorer URL (valgfritt)"
|
"message": "Blokker Explorer URL (valgfritt)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Symbol (valgfritt)"
|
"message": "Symbol (valgfritt)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -966,9 +960,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "sendt ether"
|
"message": "sendt ether"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "sendte tokener "
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Del hvert ord med et enkelt mellomrom "
|
"message": "Del hvert ord med et enkelt mellomrom "
|
||||||
},
|
},
|
||||||
|
@ -73,9 +73,6 @@
|
|||||||
"defaultNetwork": {
|
"defaultNetwork": {
|
||||||
"message": "Ang default network para sa Ether transactions ay ang Main Net."
|
"message": "Ang default network para sa Ether transactions ay ang Main Net."
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Deposito"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "I-deposito ang Ether"
|
"message": "I-deposito ang Ether"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Anuluj"
|
"message": "Anuluj"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Anuluj próbę"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Opłata za gaz za anulowanie"
|
"message": "Opłata za gaz za anulowanie"
|
||||||
},
|
},
|
||||||
@ -308,9 +305,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Czy na pewno chcesz usunąć tę sieć?"
|
"message": "Czy na pewno chcesz usunąć tę sieć?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Zdeponuj"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Zdeponuj Eter"
|
"message": "Zdeponuj Eter"
|
||||||
},
|
},
|
||||||
@ -734,7 +728,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Adres URL przeglądarki łańcucha bloków (opcjonalnie)"
|
"message": "Adres URL przeglądarki łańcucha bloków (opcjonalnie)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Symbol (opcjonalnie)"
|
"message": "Symbol (opcjonalnie)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -982,9 +976,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "wyślij eter"
|
"message": "wyślij eter"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "wysłane tokeny"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Oddziel słowa pojedynczą spacją"
|
"message": "Oddziel słowa pojedynczą spacją"
|
||||||
},
|
},
|
||||||
|
@ -97,9 +97,6 @@
|
|||||||
"defaultNetwork": {
|
"defaultNetwork": {
|
||||||
"message": "A rede pré definida para transações em Ether é a Main Net."
|
"message": "A rede pré definida para transações em Ether é a Main Net."
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Depósito"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Depositar Ether"
|
"message": "Depositar Ether"
|
||||||
},
|
},
|
||||||
|
@ -158,9 +158,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Cancelar"
|
"message": "Cancelar"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Tentativa de cancelamento"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Tarifa de Gas de cancelamento"
|
"message": "Tarifa de Gas de cancelamento"
|
||||||
},
|
},
|
||||||
@ -302,9 +299,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Tem certeza de que deseja excluir esta rede?"
|
"message": "Tem certeza de que deseja excluir esta rede?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Depósito"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Depositar Ether"
|
"message": "Depositar Ether"
|
||||||
},
|
},
|
||||||
@ -728,7 +722,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "URL exploradora de blocos (opcional)"
|
"message": "URL exploradora de blocos (opcional)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Símbolo (opcional)"
|
"message": "Símbolo (opcional)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -976,9 +970,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "ether enviado"
|
"message": "ether enviado"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "tokens enviados"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Separe cada palavra com um único espaço"
|
"message": "Separe cada palavra com um único espaço"
|
||||||
},
|
},
|
||||||
|
@ -45,9 +45,6 @@
|
|||||||
"delete": {
|
"delete": {
|
||||||
"message": "Eliminar"
|
"message": "Eliminar"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Depósito"
|
|
||||||
},
|
|
||||||
"details": {
|
"details": {
|
||||||
"message": "Detalhes"
|
"message": "Detalhes"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Anulare"
|
"message": "Anulare"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Anulare încercare"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Taxă de anulare în gaz"
|
"message": "Taxă de anulare în gaz"
|
||||||
},
|
},
|
||||||
@ -308,9 +305,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Sigur vreți să ștergeți această rețea?"
|
"message": "Sigur vreți să ștergeți această rețea?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Depunere"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Depuneți Ether"
|
"message": "Depuneți Ether"
|
||||||
},
|
},
|
||||||
@ -727,7 +721,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "URL explorator bloc (opțional)"
|
"message": "URL explorator bloc (opțional)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Simbol (opțional)"
|
"message": "Simbol (opțional)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -975,9 +969,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "trimiteți ether"
|
"message": "trimiteți ether"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "tokenuri trimise"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Despărțiți fiecare cuvânt cu un spațiu"
|
"message": "Despărțiți fiecare cuvânt cu un spațiu"
|
||||||
},
|
},
|
||||||
|
@ -163,9 +163,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Отмена"
|
"message": "Отмена"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Попытка отмены транзакции"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Комиссия за газ на отмену"
|
"message": "Комиссия за газ на отмену"
|
||||||
},
|
},
|
||||||
@ -330,9 +327,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Вы уверены, что хотите удалить эту сеть?"
|
"message": "Вы уверены, что хотите удалить эту сеть?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Пополнить"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Пополнить Ether"
|
"message": "Пополнить Ether"
|
||||||
},
|
},
|
||||||
@ -769,7 +763,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "URL блок-эксплорера (необязательно)"
|
"message": "URL блок-эксплорера (необязательно)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Символ (необязательно)"
|
"message": "Символ (необязательно)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -1024,9 +1018,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "Отправить Ether"
|
"message": "Отправить Ether"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "Отправленные токены"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Разделяйте каждое слово одним пробелом"
|
"message": "Разделяйте каждое слово одним пробелом"
|
||||||
},
|
},
|
||||||
|
@ -158,9 +158,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Zrušit"
|
"message": "Zrušit"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Zrušiť pokus"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Storno poplatok za GAS"
|
"message": "Storno poplatok za GAS"
|
||||||
},
|
},
|
||||||
@ -302,9 +299,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Naozaj chcete túto sieť odstrániť?"
|
"message": "Naozaj chcete túto sieť odstrániť?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Vklad"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Vložit Ether"
|
"message": "Vložit Ether"
|
||||||
},
|
},
|
||||||
@ -709,7 +703,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Blokovať URL Explorera (voliteľné)"
|
"message": "Blokovať URL Explorera (voliteľné)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Symbol (voliteľné)"
|
"message": "Symbol (voliteľné)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -951,9 +945,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "poslaný ether"
|
"message": "poslaný ether"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "poslané tokeny"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Každé slovo oddeľte jednou medzerou"
|
"message": "Každé slovo oddeľte jednou medzerou"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Prekliči"
|
"message": "Prekliči"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Prekliči poskus"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Preklicani znesek gas"
|
"message": "Preklicani znesek gas"
|
||||||
},
|
},
|
||||||
@ -308,9 +305,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Ali ste prepričani, da želite izbrisati to omrežje?"
|
"message": "Ali ste prepričani, da želite izbrisati to omrežje?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Vplačaj"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Vplačilo ethra"
|
"message": "Vplačilo ethra"
|
||||||
},
|
},
|
||||||
@ -725,7 +719,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Blokiraj URL Explorerja (poljubno)"
|
"message": "Blokiraj URL Explorerja (poljubno)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Simbol (nezahtevano)"
|
"message": "Simbol (nezahtevano)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -970,9 +964,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "poslani ether"
|
"message": "poslani ether"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "poslani žetoni"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Vsako besedo ločite z enim presledkom"
|
"message": "Vsako besedo ločite z enim presledkom"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Otkaži"
|
"message": "Otkaži"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Otkaži pokušaj"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Otkazivanje gas naknade"
|
"message": "Otkazivanje gas naknade"
|
||||||
},
|
},
|
||||||
@ -305,9 +302,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Da li ste sigurni da želite da izbrišete ovu mrežu?"
|
"message": "Da li ste sigurni da želite da izbrišete ovu mrežu?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Depozit"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Dajte depozit Ether-u"
|
"message": "Dajte depozit Ether-u"
|
||||||
},
|
},
|
||||||
@ -731,7 +725,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Blokirajte URL Explorer-a (opciono)"
|
"message": "Blokirajte URL Explorer-a (opciono)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Simbol (opciono)"
|
"message": "Simbol (opciono)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -979,9 +973,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "ether je poslat"
|
"message": "ether je poslat"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "poslati tokeni"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Razdvojite svaku reč jednim mestom razmaka"
|
"message": "Razdvojite svaku reč jednim mestom razmaka"
|
||||||
},
|
},
|
||||||
|
@ -161,9 +161,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Avbryt"
|
"message": "Avbryt"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Avbryt försök"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Gasavgift för avbrytning"
|
"message": "Gasavgift för avbrytning"
|
||||||
},
|
},
|
||||||
@ -302,9 +299,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Är du säker på att du vill ta bort detta nätverk?"
|
"message": "Är du säker på att du vill ta bort detta nätverk?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Deposition"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Sätt in Ether"
|
"message": "Sätt in Ether"
|
||||||
},
|
},
|
||||||
@ -724,7 +718,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Block Explorer URL (valfritt)"
|
"message": "Block Explorer URL (valfritt)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Symbol (frivillig)"
|
"message": "Symbol (frivillig)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -972,9 +966,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "skickat ether"
|
"message": "skickat ether"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "skickade tokens"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Lägg in ett mellanslag mellan varje ord"
|
"message": "Lägg in ett mellanslag mellan varje ord"
|
||||||
},
|
},
|
||||||
|
@ -158,9 +158,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Ghairi"
|
"message": "Ghairi"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Jaribio la Kubatilisha"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Ada ya Kubatilisha Gesi"
|
"message": "Ada ya Kubatilisha Gesi"
|
||||||
},
|
},
|
||||||
@ -302,9 +299,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Una uhakika unataka kufuta mtandao huu?"
|
"message": "Una uhakika unataka kufuta mtandao huu?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Fedha zilizopo kwenye akaunti"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Weka Ether"
|
"message": "Weka Ether"
|
||||||
},
|
},
|
||||||
@ -718,7 +712,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "URL ya Block Explorer URL (hiari)"
|
"message": "URL ya Block Explorer URL (hiari)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Ishara (hiari)"
|
"message": "Ishara (hiari)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -966,9 +960,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "ether iliyotumwa"
|
"message": "ether iliyotumwa"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "vianzio vilivyotumwa"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Tenganisha kila neno kwa nafasi moja"
|
"message": "Tenganisha kila neno kwa nafasi moja"
|
||||||
},
|
},
|
||||||
|
@ -136,9 +136,6 @@
|
|||||||
"delete": {
|
"delete": {
|
||||||
"message": "நீக்கு"
|
"message": "நீக்கு"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "வைப்புத்தொகை"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "வைப்புத்தொகை எதிர் "
|
"message": "வைப்புத்தொகை எதிர் "
|
||||||
},
|
},
|
||||||
|
@ -142,9 +142,6 @@
|
|||||||
"deleteNetwork": {
|
"deleteNetwork": {
|
||||||
"message": "ลบเครือข่าย?"
|
"message": "ลบเครือข่าย?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "ฝาก"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "การฝากอีเธอร์"
|
"message": "การฝากอีเธอร์"
|
||||||
},
|
},
|
||||||
|
@ -118,9 +118,6 @@
|
|||||||
"defaultNetwork": {
|
"defaultNetwork": {
|
||||||
"message": "Ether işlemleri için varsayılan ağ Main Net."
|
"message": "Ether işlemleri için varsayılan ağ Main Net."
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Yatır"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Ether yatır"
|
"message": "Ether yatır"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Скасувати"
|
"message": "Скасувати"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "Відмінити спробу"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "Вартість пального за скасування"
|
"message": "Вартість пального за скасування"
|
||||||
},
|
},
|
||||||
@ -308,9 +305,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "Ви впевнені, що хочете видалити цю мережу?"
|
"message": "Ви впевнені, що хочете видалити цю мережу?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Депозит"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Депонувати Ether"
|
"message": "Депонувати Ether"
|
||||||
},
|
},
|
||||||
@ -740,7 +734,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "Блокувати Explorer URL (не обов'язково)"
|
"message": "Блокувати Explorer URL (не обов'язково)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Символ (не обов'язково)"
|
"message": "Символ (не обов'язково)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -988,9 +982,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "надісланий ефір"
|
"message": "надісланий ефір"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "надіслані токени"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Відділіть кожне слово одним пробілом"
|
"message": "Відділіть кожне слово одним пробілом"
|
||||||
},
|
},
|
||||||
|
@ -79,9 +79,6 @@
|
|||||||
"defaultNetwork": {
|
"defaultNetwork": {
|
||||||
"message": "Mạng lưới mặc định dùng cho các giao dịch Ether là Main Net (tiền ETH thật)."
|
"message": "Mạng lưới mặc định dùng cho các giao dịch Ether là Main Net (tiền ETH thật)."
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "Ký gửi/nạp tiền"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "Ký gửi Ether"
|
"message": "Ký gửi Ether"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "取消"
|
"message": "取消"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "取消操作"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "取消天然气费"
|
"message": "取消天然气费"
|
||||||
},
|
},
|
||||||
@ -308,9 +305,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "您是否确认要删除该网络?"
|
"message": "您是否确认要删除该网络?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "存入"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "存入 Ether"
|
"message": "存入 Ether"
|
||||||
},
|
},
|
||||||
@ -722,7 +716,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "屏蔽管理器 URL(选填)"
|
"message": "屏蔽管理器 URL(选填)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "符号(选填)"
|
"message": "符号(选填)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -970,9 +964,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "以太币已发送"
|
"message": "以太币已发送"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "代币已发送"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "用空格分隔每个单词"
|
"message": "用空格分隔每个单词"
|
||||||
},
|
},
|
||||||
|
@ -164,9 +164,6 @@
|
|||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "取消"
|
"message": "取消"
|
||||||
},
|
},
|
||||||
"cancelAttempt": {
|
|
||||||
"message": "嘗試取消"
|
|
||||||
},
|
|
||||||
"cancellationGasFee": {
|
"cancellationGasFee": {
|
||||||
"message": "需要的手續費"
|
"message": "需要的手續費"
|
||||||
},
|
},
|
||||||
@ -308,9 +305,6 @@
|
|||||||
"deleteNetworkDescription": {
|
"deleteNetworkDescription": {
|
||||||
"message": "你確定要刪除網路嗎?"
|
"message": "你確定要刪除網路嗎?"
|
||||||
},
|
},
|
||||||
"deposit": {
|
|
||||||
"message": "存入"
|
|
||||||
},
|
|
||||||
"depositEther": {
|
"depositEther": {
|
||||||
"message": "存入乙太幣"
|
"message": "存入乙太幣"
|
||||||
},
|
},
|
||||||
@ -731,7 +725,7 @@
|
|||||||
"optionalBlockExplorerUrl": {
|
"optionalBlockExplorerUrl": {
|
||||||
"message": "區塊鏈瀏覽器 URL(非必要)"
|
"message": "區塊鏈瀏覽器 URL(非必要)"
|
||||||
},
|
},
|
||||||
"optionalSymbol": {
|
"optionalCurrencySymbol": {
|
||||||
"message": "Symbol (可選)"
|
"message": "Symbol (可選)"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
@ -967,9 +961,6 @@
|
|||||||
"sentEther": {
|
"sentEther": {
|
||||||
"message": "發送乙太幣"
|
"message": "發送乙太幣"
|
||||||
},
|
},
|
||||||
"sentTokens": {
|
|
||||||
"message": "發送代幣"
|
|
||||||
},
|
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "單詞之間請以空白間隔"
|
"message": "單詞之間請以空白間隔"
|
||||||
},
|
},
|
||||||
|
@ -41,10 +41,6 @@
|
|||||||
],
|
],
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"description": "__MSG_appDescription__",
|
"description": "__MSG_appDescription__",
|
||||||
"externally_connectable": {
|
|
||||||
"matches": ["https://metamask.io/*"],
|
|
||||||
"ids": ["*"]
|
|
||||||
},
|
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "images/icon-16.png",
|
"16": "images/icon-16.png",
|
||||||
"19": "images/icon-19.png",
|
"19": "images/icon-19.png",
|
||||||
@ -68,6 +64,6 @@
|
|||||||
"notifications"
|
"notifications"
|
||||||
],
|
],
|
||||||
"short_name": "__MSG_appName__",
|
"short_name": "__MSG_appName__",
|
||||||
"version": "8.1.3",
|
"version": "8.1.4",
|
||||||
"web_accessible_resources": ["inpage.js", "phishing.html"]
|
"web_accessible_resources": ["inpage.js", "phishing.html"]
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"externally_connectable": {
|
||||||
|
"matches": ["https://metamask.io/*"],
|
||||||
|
"ids": ["*"]
|
||||||
|
},
|
||||||
"minimum_chrome_version": "58"
|
"minimum_chrome_version": "58"
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,10 @@ import log from 'loglevel'
|
|||||||
import Wallet from 'ethereumjs-wallet'
|
import Wallet from 'ethereumjs-wallet'
|
||||||
import importers from 'ethereumjs-wallet/thirdparty'
|
import importers from 'ethereumjs-wallet/thirdparty'
|
||||||
import ethUtil from 'ethereumjs-util'
|
import ethUtil from 'ethereumjs-util'
|
||||||
|
import { addHexPrefix } from '../lib/util'
|
||||||
|
|
||||||
const accountImporter = {
|
const accountImporter = {
|
||||||
|
importAccount(strategy, args) {
|
||||||
importAccount (strategy, args) {
|
|
||||||
try {
|
try {
|
||||||
const importer = this.strategies[strategy]
|
const importer = this.strategies[strategy]
|
||||||
const privateKeyHex = importer(...args)
|
const privateKeyHex = importer(...args)
|
||||||
@ -21,7 +21,7 @@ const accountImporter = {
|
|||||||
throw new Error('Cannot import an empty key.')
|
throw new Error('Cannot import an empty key.')
|
||||||
}
|
}
|
||||||
|
|
||||||
const prefixed = ethUtil.addHexPrefix(privateKey)
|
const prefixed = addHexPrefix(privateKey)
|
||||||
const buffer = ethUtil.toBuffer(prefixed)
|
const buffer = ethUtil.toBuffer(prefixed)
|
||||||
|
|
||||||
if (!ethUtil.isValidPrivate(buffer)) {
|
if (!ethUtil.isValidPrivate(buffer)) {
|
||||||
@ -43,10 +43,9 @@ const accountImporter = {
|
|||||||
return walletToPrivateKey(wallet)
|
return walletToPrivateKey(wallet)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function walletToPrivateKey (wallet) {
|
function walletToPrivateKey(wallet) {
|
||||||
const privateKeyBuffer = wallet.getPrivateKey()
|
const privateKeyBuffer = wallet.getPrivateKey()
|
||||||
return ethUtil.bufferToHex(privateKeyBuffer)
|
return ethUtil.bufferToHex(privateKeyBuffer)
|
||||||
}
|
}
|
||||||
|
@ -60,9 +60,7 @@ const requestAccountTabIds = {}
|
|||||||
|
|
||||||
// state persistence
|
// state persistence
|
||||||
const inTest = process.env.IN_TEST === 'true'
|
const inTest = process.env.IN_TEST === 'true'
|
||||||
const localStore = inTest
|
const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore()
|
||||||
? new ReadOnlyNetworkStore()
|
|
||||||
: new LocalStore()
|
|
||||||
let versionedData
|
let versionedData
|
||||||
|
|
||||||
if (inTest || process.env.METAMASK_DEBUG) {
|
if (inTest || process.env.METAMASK_DEBUG) {
|
||||||
@ -141,9 +139,9 @@ initialize().catch(log.error)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the MetaMask controller, and sets up all platform configuration.
|
* Initializes the MetaMask controller, and sets up all platform configuration.
|
||||||
* @returns {Promise} - Setup complete.
|
* @returns {Promise} Setup complete.
|
||||||
*/
|
*/
|
||||||
async function initialize () {
|
async function initialize() {
|
||||||
const initState = await loadStateFromPersistence()
|
const initState = await loadStateFromPersistence()
|
||||||
const initLangCode = await getFirstPreferredLangCode()
|
const initLangCode = await getFirstPreferredLangCode()
|
||||||
await setupController(initState, initLangCode)
|
await setupController(initState, initLangCode)
|
||||||
@ -157,17 +155,17 @@ async function initialize () {
|
|||||||
/**
|
/**
|
||||||
* Loads any stored data, prioritizing the latest storage strategy.
|
* Loads any stored data, prioritizing the latest storage strategy.
|
||||||
* Migrates that data schema in case it was last loaded on an older version.
|
* Migrates that data schema in case it was last loaded on an older version.
|
||||||
* @returns {Promise<MetaMaskState>} - Last data emitted from previous instance of MetaMask.
|
* @returns {Promise<MetaMaskState>} Last data emitted from previous instance of MetaMask.
|
||||||
*/
|
*/
|
||||||
async function loadStateFromPersistence () {
|
async function loadStateFromPersistence() {
|
||||||
// migrations
|
// migrations
|
||||||
const migrator = new Migrator({ migrations })
|
const migrator = new Migrator({ migrations })
|
||||||
migrator.on('error', console.warn)
|
migrator.on('error', console.warn)
|
||||||
|
|
||||||
// read from disk
|
// read from disk
|
||||||
// first from preferred, async API:
|
// first from preferred, async API:
|
||||||
versionedData = (await localStore.get()) ||
|
versionedData =
|
||||||
migrator.generateInitialState(firstTimeState)
|
(await localStore.get()) || migrator.generateInitialState(firstTimeState)
|
||||||
|
|
||||||
// check if somehow state is empty
|
// check if somehow state is empty
|
||||||
// this should never happen but new error reporting suggests that it has
|
// this should never happen but new error reporting suggests that it has
|
||||||
@ -217,9 +215,9 @@ async function loadStateFromPersistence () {
|
|||||||
*
|
*
|
||||||
* @param {Object} initState - The initial state to start the controller with, matches the state that is emitted from the controller.
|
* @param {Object} initState - The initial state to start the controller with, matches the state that is emitted from the controller.
|
||||||
* @param {string} initLangCode - The region code for the language preferred by the current user.
|
* @param {string} initLangCode - The region code for the language preferred by the current user.
|
||||||
* @returns {Promise} - After setup is complete.
|
* @returns {Promise} After setup is complete.
|
||||||
*/
|
*/
|
||||||
function setupController (initState, initLangCode) {
|
function setupController(initState, initLangCode) {
|
||||||
//
|
//
|
||||||
// MetaMask Controller
|
// MetaMask Controller
|
||||||
//
|
//
|
||||||
@ -249,7 +247,9 @@ function setupController (initState, initLangCode) {
|
|||||||
|
|
||||||
setupEnsIpfsResolver({
|
setupEnsIpfsResolver({
|
||||||
getCurrentNetwork: controller.getCurrentNetwork,
|
getCurrentNetwork: controller.getCurrentNetwork,
|
||||||
getIpfsGateway: controller.preferencesController.getIpfsGateway.bind(controller.preferencesController),
|
getIpfsGateway: controller.preferencesController.getIpfsGateway.bind(
|
||||||
|
controller.preferencesController,
|
||||||
|
),
|
||||||
provider: controller.provider,
|
provider: controller.provider,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -267,14 +267,14 @@ function setupController (initState, initLangCode) {
|
|||||||
/**
|
/**
|
||||||
* Assigns the given state to the versioned object (with metadata), and returns that.
|
* Assigns the given state to the versioned object (with metadata), and returns that.
|
||||||
* @param {Object} state - The state object as emitted by the MetaMaskController.
|
* @param {Object} state - The state object as emitted by the MetaMaskController.
|
||||||
* @returns {VersionedData} - The state object wrapped in an object that includes a metadata key.
|
* @returns {VersionedData} The state object wrapped in an object that includes a metadata key.
|
||||||
*/
|
*/
|
||||||
function versionifyData (state) {
|
function versionifyData(state) {
|
||||||
versionedData.data = state
|
versionedData.data = state
|
||||||
return versionedData
|
return versionedData
|
||||||
}
|
}
|
||||||
|
|
||||||
async function persistData (state) {
|
async function persistData(state) {
|
||||||
if (!state) {
|
if (!state) {
|
||||||
throw new Error('MetaMask - updated state is missing')
|
throw new Error('MetaMask - updated state is missing')
|
||||||
}
|
}
|
||||||
@ -303,12 +303,14 @@ function setupController (initState, initLangCode) {
|
|||||||
[ENVIRONMENT_TYPE_FULLSCREEN]: true,
|
[ENVIRONMENT_TYPE_FULLSCREEN]: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
const metamaskBlockedPorts = [
|
const metamaskBlockedPorts = ['trezor-connect']
|
||||||
'trezor-connect',
|
|
||||||
]
|
|
||||||
|
|
||||||
const isClientOpenStatus = () => {
|
const isClientOpenStatus = () => {
|
||||||
return popupIsOpen || Boolean(Object.keys(openMetamaskTabsIDs).length) || notificationIsOpen
|
return (
|
||||||
|
popupIsOpen ||
|
||||||
|
Boolean(Object.keys(openMetamaskTabsIDs).length) ||
|
||||||
|
notificationIsOpen
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -323,7 +325,7 @@ function setupController (initState, initLangCode) {
|
|||||||
* This method identifies trusted (MetaMask) interfaces, and connects them differently from untrusted (web pages).
|
* This method identifies trusted (MetaMask) interfaces, and connects them differently from untrusted (web pages).
|
||||||
* @param {Port} remotePort - The port provided by a new context.
|
* @param {Port} remotePort - The port provided by a new context.
|
||||||
*/
|
*/
|
||||||
function connectRemote (remotePort) {
|
function connectRemote(remotePort) {
|
||||||
const processName = remotePort.name
|
const processName = remotePort.name
|
||||||
const isMetaMaskInternalProcess = metamaskInternalProcessHash[processName]
|
const isMetaMaskInternalProcess = metamaskInternalProcessHash[processName]
|
||||||
|
|
||||||
@ -381,7 +383,7 @@ function setupController (initState, initLangCode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// communication with page or other extension
|
// communication with page or other extension
|
||||||
function connectExternal (remotePort) {
|
function connectExternal(remotePort) {
|
||||||
const portStream = new PortStream(remotePort)
|
const portStream = new PortStream(remotePort)
|
||||||
controller.setupUntrustedCommunication(portStream, remotePort.sender)
|
controller.setupUntrustedCommunication(portStream, remotePort.sender)
|
||||||
}
|
}
|
||||||
@ -404,18 +406,30 @@ function setupController (initState, initLangCode) {
|
|||||||
* Updates the Web Extension's "badge" number, on the little fox in the toolbar.
|
* 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.
|
* The number reflects the current number of pending transactions or message signatures needing user approval.
|
||||||
*/
|
*/
|
||||||
function updateBadge () {
|
function updateBadge() {
|
||||||
let label = ''
|
let label = ''
|
||||||
const unapprovedTxCount = controller.txController.getUnapprovedTxCount()
|
const unapprovedTxCount = controller.txController.getUnapprovedTxCount()
|
||||||
const { unapprovedMsgCount } = controller.messageManager
|
const { unapprovedMsgCount } = controller.messageManager
|
||||||
const { unapprovedPersonalMsgCount } = controller.personalMessageManager
|
const { unapprovedPersonalMsgCount } = controller.personalMessageManager
|
||||||
const { unapprovedDecryptMsgCount } = controller.decryptMessageManager
|
const { unapprovedDecryptMsgCount } = controller.decryptMessageManager
|
||||||
const { unapprovedEncryptionPublicKeyMsgCount } = controller.encryptionPublicKeyManager
|
const {
|
||||||
|
unapprovedEncryptionPublicKeyMsgCount,
|
||||||
|
} = controller.encryptionPublicKeyManager
|
||||||
const { unapprovedTypedMessagesCount } = controller.typedMessageManager
|
const { unapprovedTypedMessagesCount } = controller.typedMessageManager
|
||||||
const pendingPermissionRequests = Object.keys(controller.permissionsController.permissions.state.permissionsRequests).length
|
const pendingPermissionRequests = Object.keys(
|
||||||
const waitingForUnlockCount = controller.appStateController.waitingForUnlock.length
|
controller.permissionsController.permissions.state.permissionsRequests,
|
||||||
const count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgCount + unapprovedDecryptMsgCount + unapprovedEncryptionPublicKeyMsgCount +
|
).length
|
||||||
unapprovedTypedMessagesCount + pendingPermissionRequests + waitingForUnlockCount
|
const waitingForUnlockCount =
|
||||||
|
controller.appStateController.waitingForUnlock.length
|
||||||
|
const count =
|
||||||
|
unapprovedTxCount +
|
||||||
|
unapprovedMsgCount +
|
||||||
|
unapprovedPersonalMsgCount +
|
||||||
|
unapprovedDecryptMsgCount +
|
||||||
|
unapprovedEncryptionPublicKeyMsgCount +
|
||||||
|
unapprovedTypedMessagesCount +
|
||||||
|
pendingPermissionRequests +
|
||||||
|
waitingForUnlockCount
|
||||||
if (count) {
|
if (count) {
|
||||||
label = String(count)
|
label = String(count)
|
||||||
}
|
}
|
||||||
@ -433,10 +447,18 @@ function setupController (initState, initLangCode) {
|
|||||||
/**
|
/**
|
||||||
* Opens the browser popup for user confirmation
|
* Opens the browser popup for user confirmation
|
||||||
*/
|
*/
|
||||||
async function triggerUi () {
|
async function triggerUi() {
|
||||||
const tabs = await platform.getActiveTabs()
|
const tabs = await platform.getActiveTabs()
|
||||||
const currentlyActiveMetamaskTab = Boolean(tabs.find((tab) => openMetamaskTabsIDs[tab.id]))
|
const currentlyActiveMetamaskTab = Boolean(
|
||||||
if (!popupIsOpen && !currentlyActiveMetamaskTab) {
|
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
|
||||||
|
if ((isVivaldi || !popupIsOpen) && !currentlyActiveMetamaskTab) {
|
||||||
await notificationManager.showPopup()
|
await notificationManager.showPopup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -445,23 +467,24 @@ async function triggerUi () {
|
|||||||
* Opens the browser popup for user confirmation of watchAsset
|
* Opens the browser popup for user confirmation of watchAsset
|
||||||
* then it waits until user interact with the UI
|
* then it waits until user interact with the UI
|
||||||
*/
|
*/
|
||||||
async function openPopup () {
|
async function openPopup() {
|
||||||
await triggerUi()
|
await triggerUi()
|
||||||
await new Promise(
|
await new Promise((resolve) => {
|
||||||
(resolve) => {
|
const interval = setInterval(() => {
|
||||||
const interval = setInterval(() => {
|
if (!notificationIsOpen) {
|
||||||
if (!notificationIsOpen) {
|
clearInterval(interval)
|
||||||
clearInterval(interval)
|
resolve()
|
||||||
resolve()
|
}
|
||||||
}
|
}, 1000)
|
||||||
}, 1000)
|
})
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// On first install, open a new tab with MetaMask
|
// On first install, open a new tab with MetaMask
|
||||||
extension.runtime.onInstalled.addListener(({ reason }) => {
|
extension.runtime.onInstalled.addListener(({ reason }) => {
|
||||||
if (reason === 'install' && !(process.env.METAMASK_DEBUG || process.env.IN_TEST)) {
|
if (
|
||||||
|
reason === 'install' &&
|
||||||
|
!(process.env.METAMASK_DEBUG || process.env.IN_TEST)
|
||||||
|
) {
|
||||||
platform.openExtensionInBrowser()
|
platform.openExtensionInBrowser()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -9,7 +9,10 @@ import PortStream from 'extension-port-stream'
|
|||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js'), 'utf8')
|
const inpageContent = fs.readFileSync(
|
||||||
|
path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js'),
|
||||||
|
'utf8',
|
||||||
|
)
|
||||||
const inpageSuffix = `//# sourceURL=${extension.runtime.getURL('inpage.js')}\n`
|
const inpageSuffix = `//# sourceURL=${extension.runtime.getURL('inpage.js')}\n`
|
||||||
const inpageBundle = inpageContent + inpageSuffix
|
const inpageBundle = inpageContent + inpageSuffix
|
||||||
|
|
||||||
@ -30,7 +33,7 @@ if (shouldInjectProvider()) {
|
|||||||
*
|
*
|
||||||
* @param {string} content - Code to be executed in the current document
|
* @param {string} content - Code to be executed in the current document
|
||||||
*/
|
*/
|
||||||
function injectScript (content) {
|
function injectScript(content) {
|
||||||
try {
|
try {
|
||||||
const container = document.head || document.documentElement
|
const container = document.head || document.documentElement
|
||||||
const scriptTag = document.createElement('script')
|
const scriptTag = document.createElement('script')
|
||||||
@ -47,7 +50,7 @@ function injectScript (content) {
|
|||||||
* Sets up the stream communication and submits site metadata
|
* Sets up the stream communication and submits site metadata
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async function start () {
|
async function start() {
|
||||||
await setupStreams()
|
await setupStreams()
|
||||||
await domIsReady()
|
await domIsReady()
|
||||||
}
|
}
|
||||||
@ -57,7 +60,7 @@ async function start () {
|
|||||||
* browser extension and local per-page browser context.
|
* browser extension and local per-page browser context.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async function setupStreams () {
|
async function setupStreams() {
|
||||||
// the transport-specific streams for communication between inpage and background
|
// the transport-specific streams for communication between inpage and background
|
||||||
const pageStream = new LocalMessageDuplexStream({
|
const pageStream = new LocalMessageDuplexStream({
|
||||||
name: 'contentscript',
|
name: 'contentscript',
|
||||||
@ -73,17 +76,11 @@ async function setupStreams () {
|
|||||||
const extensionMux = new ObjectMultiplex()
|
const extensionMux = new ObjectMultiplex()
|
||||||
extensionMux.setMaxListeners(25)
|
extensionMux.setMaxListeners(25)
|
||||||
|
|
||||||
pump(
|
pump(pageMux, pageStream, pageMux, (err) =>
|
||||||
pageMux,
|
logStreamDisconnectWarning('MetaMask Inpage Multiplex', err),
|
||||||
pageStream,
|
|
||||||
pageMux,
|
|
||||||
(err) => logStreamDisconnectWarning('MetaMask Inpage Multiplex', err),
|
|
||||||
)
|
)
|
||||||
pump(
|
pump(extensionMux, extensionStream, extensionMux, (err) =>
|
||||||
extensionMux,
|
logStreamDisconnectWarning('MetaMask Background Multiplex', err),
|
||||||
extensionStream,
|
|
||||||
extensionMux,
|
|
||||||
(err) => logStreamDisconnectWarning('MetaMask Background Multiplex', err),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// forward communication across inpage-background for these channels only
|
// forward communication across inpage-background for these channels only
|
||||||
@ -95,14 +92,14 @@ async function setupStreams () {
|
|||||||
phishingStream.once('data', redirectToPhishingWarning)
|
phishingStream.once('data', redirectToPhishingWarning)
|
||||||
}
|
}
|
||||||
|
|
||||||
function forwardTrafficBetweenMuxers (channelName, muxA, muxB) {
|
function forwardTrafficBetweenMuxers(channelName, muxA, muxB) {
|
||||||
const channelA = muxA.createStream(channelName)
|
const channelA = muxA.createStream(channelName)
|
||||||
const channelB = muxB.createStream(channelName)
|
const channelB = muxB.createStream(channelName)
|
||||||
pump(
|
pump(channelA, channelB, channelA, (err) =>
|
||||||
channelA,
|
logStreamDisconnectWarning(
|
||||||
channelB,
|
`MetaMask muxed traffic for channel "${channelName}" failed.`,
|
||||||
channelA,
|
err,
|
||||||
(err) => logStreamDisconnectWarning(`MetaMask muxed traffic for channel "${channelName}" failed.`, err),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +109,7 @@ function forwardTrafficBetweenMuxers (channelName, muxA, muxB) {
|
|||||||
* @param {string} remoteLabel - Remote stream name
|
* @param {string} remoteLabel - Remote stream name
|
||||||
* @param {Error} err - Stream connection error
|
* @param {Error} err - Stream connection error
|
||||||
*/
|
*/
|
||||||
function logStreamDisconnectWarning (remoteLabel, err) {
|
function logStreamDisconnectWarning(remoteLabel, err) {
|
||||||
let warningMsg = `MetamaskContentscript - lost connection to ${remoteLabel}`
|
let warningMsg = `MetamaskContentscript - lost connection to ${remoteLabel}`
|
||||||
if (err) {
|
if (err) {
|
||||||
warningMsg += `\n${err.stack}`
|
warningMsg += `\n${err.stack}`
|
||||||
@ -123,19 +120,23 @@ function logStreamDisconnectWarning (remoteLabel, err) {
|
|||||||
/**
|
/**
|
||||||
* Determines if the provider should be injected
|
* Determines if the provider should be injected
|
||||||
*
|
*
|
||||||
* @returns {boolean} {@code true} - if the provider should be injected
|
* @returns {boolean} {@code true} Whether the provider should be injected
|
||||||
*/
|
*/
|
||||||
function shouldInjectProvider () {
|
function shouldInjectProvider() {
|
||||||
return doctypeCheck() && suffixCheck() &&
|
return (
|
||||||
documentElementCheck() && !blockedDomainCheck()
|
doctypeCheck() &&
|
||||||
|
suffixCheck() &&
|
||||||
|
documentElementCheck() &&
|
||||||
|
!blockedDomainCheck()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the doctype of the current document if it exists
|
* Checks the doctype of the current document if it exists
|
||||||
*
|
*
|
||||||
* @returns {boolean} {@code true} - if the doctype is html or if none exists
|
* @returns {boolean} {@code true} if the doctype is html or if none exists
|
||||||
*/
|
*/
|
||||||
function doctypeCheck () {
|
function doctypeCheck() {
|
||||||
const { doctype } = window.document
|
const { doctype } = window.document
|
||||||
if (doctype) {
|
if (doctype) {
|
||||||
return doctype.name === 'html'
|
return doctype.name === 'html'
|
||||||
@ -150,13 +151,10 @@ function doctypeCheck () {
|
|||||||
* that we should not inject the provider into. This check is indifferent of
|
* that we should not inject the provider into. This check is indifferent of
|
||||||
* query parameters in the location.
|
* query parameters in the location.
|
||||||
*
|
*
|
||||||
* @returns {boolean} - whether or not the extension of the current document is prohibited
|
* @returns {boolean} whether or not the extension of the current document is prohibited
|
||||||
*/
|
*/
|
||||||
function suffixCheck () {
|
function suffixCheck() {
|
||||||
const prohibitedTypes = [
|
const prohibitedTypes = [/\.xml$/u, /\.pdf$/u]
|
||||||
/\.xml$/u,
|
|
||||||
/\.pdf$/u,
|
|
||||||
]
|
|
||||||
const currentUrl = window.location.pathname
|
const currentUrl = window.location.pathname
|
||||||
for (let i = 0; i < prohibitedTypes.length; i++) {
|
for (let i = 0; i < prohibitedTypes.length; i++) {
|
||||||
if (prohibitedTypes[i].test(currentUrl)) {
|
if (prohibitedTypes[i].test(currentUrl)) {
|
||||||
@ -169,9 +167,9 @@ function suffixCheck () {
|
|||||||
/**
|
/**
|
||||||
* Checks the documentElement of the current document
|
* Checks the documentElement of the current document
|
||||||
*
|
*
|
||||||
* @returns {boolean} {@code true} - if the documentElement is an html node or if none exists
|
* @returns {boolean} {@code true} if the documentElement is an html node or if none exists
|
||||||
*/
|
*/
|
||||||
function documentElementCheck () {
|
function documentElementCheck() {
|
||||||
const documentElement = document.documentElement.nodeName
|
const documentElement = document.documentElement.nodeName
|
||||||
if (documentElement) {
|
if (documentElement) {
|
||||||
return documentElement.toLowerCase() === 'html'
|
return documentElement.toLowerCase() === 'html'
|
||||||
@ -182,9 +180,9 @@ function documentElementCheck () {
|
|||||||
/**
|
/**
|
||||||
* Checks if the current domain is blocked
|
* Checks if the current domain is blocked
|
||||||
*
|
*
|
||||||
* @returns {boolean} {@code true} - if the current domain is blocked
|
* @returns {boolean} {@code true} if the current domain is blocked
|
||||||
*/
|
*/
|
||||||
function blockedDomainCheck () {
|
function blockedDomainCheck() {
|
||||||
const blockedDomains = [
|
const blockedDomains = [
|
||||||
'uscourts.gov',
|
'uscourts.gov',
|
||||||
'dropbox.com',
|
'dropbox.com',
|
||||||
@ -201,7 +199,10 @@ function blockedDomainCheck () {
|
|||||||
let currentRegex
|
let currentRegex
|
||||||
for (let i = 0; i < blockedDomains.length; i++) {
|
for (let i = 0; i < blockedDomains.length; i++) {
|
||||||
const blockedDomain = blockedDomains[i].replace('.', '\\.')
|
const blockedDomain = blockedDomains[i].replace('.', '\\.')
|
||||||
currentRegex = new RegExp(`(?:https?:\\/\\/)(?:(?!${blockedDomain}).)*$`, 'u')
|
currentRegex = new RegExp(
|
||||||
|
`(?:https?:\\/\\/)(?:(?!${blockedDomain}).)*$`,
|
||||||
|
'u',
|
||||||
|
)
|
||||||
if (!currentRegex.test(currentUrl)) {
|
if (!currentRegex.test(currentUrl)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -212,7 +213,7 @@ function blockedDomainCheck () {
|
|||||||
/**
|
/**
|
||||||
* Redirects the current page to a phishing information page
|
* Redirects the current page to a phishing information page
|
||||||
*/
|
*/
|
||||||
function redirectToPhishingWarning () {
|
function redirectToPhishingWarning() {
|
||||||
console.log('MetaMask - routing to Phishing Warning component')
|
console.log('MetaMask - routing to Phishing Warning component')
|
||||||
const extensionURL = extension.runtime.getURL('phishing.html')
|
const extensionURL = extension.runtime.getURL('phishing.html')
|
||||||
window.location.href = `${extensionURL}#${querystring.stringify({
|
window.location.href = `${extensionURL}#${querystring.stringify({
|
||||||
@ -224,11 +225,13 @@ function redirectToPhishingWarning () {
|
|||||||
/**
|
/**
|
||||||
* Returns a promise that resolves when the DOM is loaded (does not wait for images to load)
|
* Returns a promise that resolves when the DOM is loaded (does not wait for images to load)
|
||||||
*/
|
*/
|
||||||
async function domIsReady () {
|
async function domIsReady() {
|
||||||
// already loaded
|
// already loaded
|
||||||
if (['interactive', 'complete'].includes(document.readyState)) {
|
if (['interactive', 'complete'].includes(document.readyState)) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
// wait for load
|
// wait for load
|
||||||
return new Promise((resolve) => window.addEventListener('DOMContentLoaded', resolve, { once: true }))
|
return new Promise((resolve) =>
|
||||||
|
window.addEventListener('DOMContentLoaded', resolve, { once: true }),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -21,14 +21,13 @@ export const ALERT_TYPES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
alertEnabledness: Object.keys(ALERT_TYPES)
|
alertEnabledness: Object.keys(ALERT_TYPES).reduce(
|
||||||
.reduce(
|
(alertEnabledness, alertType) => {
|
||||||
(alertEnabledness, alertType) => {
|
alertEnabledness[alertType] = true
|
||||||
alertEnabledness[alertType] = true
|
return alertEnabledness
|
||||||
return alertEnabledness
|
},
|
||||||
},
|
{},
|
||||||
{},
|
),
|
||||||
),
|
|
||||||
unconnectedAccountAlertShownOrigins: {},
|
unconnectedAccountAlertShownOrigins: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,12 +36,11 @@ const defaultState = {
|
|||||||
* alert related state
|
* alert related state
|
||||||
*/
|
*/
|
||||||
export default class AlertController {
|
export default class AlertController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @constructor
|
* @constructor
|
||||||
* @param {AlertControllerOptions} [opts] - Controller configuration parameters
|
* @param {AlertControllerOptions} [opts] - Controller configuration parameters
|
||||||
*/
|
*/
|
||||||
constructor (opts = {}) {
|
constructor(opts = {}) {
|
||||||
const { initState, preferencesStore } = opts
|
const { initState, preferencesStore } = opts
|
||||||
const state = {
|
const state = {
|
||||||
...defaultState,
|
...defaultState,
|
||||||
@ -56,14 +54,17 @@ export default class AlertController {
|
|||||||
|
|
||||||
preferencesStore.subscribe(({ selectedAddress }) => {
|
preferencesStore.subscribe(({ selectedAddress }) => {
|
||||||
const currentState = this.store.getState()
|
const currentState = this.store.getState()
|
||||||
if (currentState.unconnectedAccountAlertShownOrigins && this.selectedAddress !== selectedAddress) {
|
if (
|
||||||
|
currentState.unconnectedAccountAlertShownOrigins &&
|
||||||
|
this.selectedAddress !== selectedAddress
|
||||||
|
) {
|
||||||
this.selectedAddress = selectedAddress
|
this.selectedAddress = selectedAddress
|
||||||
this.store.updateState({ unconnectedAccountAlertShownOrigins: {} })
|
this.store.updateState({ unconnectedAccountAlertShownOrigins: {} })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setAlertEnabledness (alertId, enabledness) {
|
setAlertEnabledness(alertId, enabledness) {
|
||||||
let { alertEnabledness } = this.store.getState()
|
let { alertEnabledness } = this.store.getState()
|
||||||
alertEnabledness = { ...alertEnabledness }
|
alertEnabledness = { ...alertEnabledness }
|
||||||
alertEnabledness[alertId] = enabledness
|
alertEnabledness[alertId] = enabledness
|
||||||
@ -74,9 +75,11 @@ export default class AlertController {
|
|||||||
* Sets the "switch to connected" alert as shown for the given origin
|
* Sets the "switch to connected" alert as shown for the given origin
|
||||||
* @param {string} origin - The origin the alert has been shown for
|
* @param {string} origin - The origin the alert has been shown for
|
||||||
*/
|
*/
|
||||||
setUnconnectedAccountAlertShown (origin) {
|
setUnconnectedAccountAlertShown(origin) {
|
||||||
let { unconnectedAccountAlertShownOrigins } = this.store.getState()
|
let { unconnectedAccountAlertShownOrigins } = this.store.getState()
|
||||||
unconnectedAccountAlertShownOrigins = { ...unconnectedAccountAlertShownOrigins }
|
unconnectedAccountAlertShownOrigins = {
|
||||||
|
...unconnectedAccountAlertShownOrigins,
|
||||||
|
}
|
||||||
unconnectedAccountAlertShownOrigins[origin] = true
|
unconnectedAccountAlertShownOrigins[origin] = true
|
||||||
this.store.updateState({ unconnectedAccountAlertShownOrigins })
|
this.store.updateState({ unconnectedAccountAlertShownOrigins })
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,11 @@ import EventEmitter from 'events'
|
|||||||
import ObservableStore from 'obs-store'
|
import ObservableStore from 'obs-store'
|
||||||
|
|
||||||
export default class AppStateController extends EventEmitter {
|
export default class AppStateController extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @constructor
|
* @constructor
|
||||||
* @param opts
|
* @param {Object} opts
|
||||||
*/
|
*/
|
||||||
constructor (opts = {}) {
|
constructor(opts = {}) {
|
||||||
const {
|
const {
|
||||||
addUnlockListener,
|
addUnlockListener,
|
||||||
isUnlocked,
|
isUnlocked,
|
||||||
@ -23,7 +22,8 @@ export default class AppStateController extends EventEmitter {
|
|||||||
timeoutMinutes: 0,
|
timeoutMinutes: 0,
|
||||||
connectedStatusPopoverHasBeenShown: true,
|
connectedStatusPopoverHasBeenShown: true,
|
||||||
swapsWelcomeMessageHasBeenShown: false,
|
swapsWelcomeMessageHasBeenShown: false,
|
||||||
defaultHomeActiveTabName: null, ...initState,
|
defaultHomeActiveTabName: null,
|
||||||
|
...initState,
|
||||||
})
|
})
|
||||||
this.timer = null
|
this.timer = null
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ export default class AppStateController extends EventEmitter {
|
|||||||
* @returns {Promise<void>} A promise that resolves when the extension is
|
* @returns {Promise<void>} A promise that resolves when the extension is
|
||||||
* unlocked, or immediately if the extension is already unlocked.
|
* unlocked, or immediately if the extension is already unlocked.
|
||||||
*/
|
*/
|
||||||
getUnlockPromise (shouldShowUnlockRequest) {
|
getUnlockPromise(shouldShowUnlockRequest) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (this.isUnlocked()) {
|
if (this.isUnlocked()) {
|
||||||
resolve()
|
resolve()
|
||||||
@ -72,7 +72,7 @@ export default class AppStateController extends EventEmitter {
|
|||||||
* @param {boolean} shouldShowUnlockRequest - Whether the extension notification
|
* @param {boolean} shouldShowUnlockRequest - Whether the extension notification
|
||||||
* popup should be opened.
|
* popup should be opened.
|
||||||
*/
|
*/
|
||||||
waitForUnlock (resolve, shouldShowUnlockRequest) {
|
waitForUnlock(resolve, shouldShowUnlockRequest) {
|
||||||
this.waitingForUnlock.push({ resolve })
|
this.waitingForUnlock.push({ resolve })
|
||||||
this.emit('updateBadge')
|
this.emit('updateBadge')
|
||||||
if (shouldShowUnlockRequest) {
|
if (shouldShowUnlockRequest) {
|
||||||
@ -83,7 +83,7 @@ export default class AppStateController extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
* Drains the waitingForUnlock queue, resolving all the related Promises.
|
* Drains the waitingForUnlock queue, resolving all the related Promises.
|
||||||
*/
|
*/
|
||||||
handleUnlock () {
|
handleUnlock() {
|
||||||
if (this.waitingForUnlock.length > 0) {
|
if (this.waitingForUnlock.length > 0) {
|
||||||
while (this.waitingForUnlock.length > 0) {
|
while (this.waitingForUnlock.length > 0) {
|
||||||
this.waitingForUnlock.shift().resolve()
|
this.waitingForUnlock.shift().resolve()
|
||||||
@ -96,7 +96,7 @@ export default class AppStateController extends EventEmitter {
|
|||||||
* Sets the default home tab
|
* Sets the default home tab
|
||||||
* @param {string} [defaultHomeActiveTabName] - the tab name
|
* @param {string} [defaultHomeActiveTabName] - the tab name
|
||||||
*/
|
*/
|
||||||
setDefaultHomeActiveTabName (defaultHomeActiveTabName) {
|
setDefaultHomeActiveTabName(defaultHomeActiveTabName) {
|
||||||
this.store.updateState({
|
this.store.updateState({
|
||||||
defaultHomeActiveTabName,
|
defaultHomeActiveTabName,
|
||||||
})
|
})
|
||||||
@ -105,7 +105,7 @@ export default class AppStateController extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
* Record that the user has seen the connected status info popover
|
* Record that the user has seen the connected status info popover
|
||||||
*/
|
*/
|
||||||
setConnectedStatusPopoverHasBeenShown () {
|
setConnectedStatusPopoverHasBeenShown() {
|
||||||
this.store.updateState({
|
this.store.updateState({
|
||||||
connectedStatusPopoverHasBeenShown: true,
|
connectedStatusPopoverHasBeenShown: true,
|
||||||
})
|
})
|
||||||
@ -114,7 +114,7 @@ export default class AppStateController extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
* Record that the user has seen the swap screen welcome message
|
* Record that the user has seen the swap screen welcome message
|
||||||
*/
|
*/
|
||||||
setSwapsWelcomeMessageHasBeenShown () {
|
setSwapsWelcomeMessageHasBeenShown() {
|
||||||
this.store.updateState({
|
this.store.updateState({
|
||||||
swapsWelcomeMessageHasBeenShown: true,
|
swapsWelcomeMessageHasBeenShown: true,
|
||||||
})
|
})
|
||||||
@ -124,7 +124,7 @@ export default class AppStateController extends EventEmitter {
|
|||||||
* Sets the last active time to the current time
|
* Sets the last active time to the current time
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
setLastActiveTime () {
|
setLastActiveTime() {
|
||||||
this._resetTimer()
|
this._resetTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ export default class AppStateController extends EventEmitter {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_setInactiveTimeout (timeoutMinutes) {
|
_setInactiveTimeout(timeoutMinutes) {
|
||||||
this.store.updateState({
|
this.store.updateState({
|
||||||
timeoutMinutes,
|
timeoutMinutes,
|
||||||
})
|
})
|
||||||
@ -151,7 +151,7 @@ export default class AppStateController extends EventEmitter {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_resetTimer () {
|
_resetTimer() {
|
||||||
const { timeoutMinutes } = this.store.getState()
|
const { timeoutMinutes } = this.store.getState()
|
||||||
|
|
||||||
if (this.timer) {
|
if (this.timer) {
|
||||||
@ -162,6 +162,9 @@ export default class AppStateController extends EventEmitter {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.timer = setTimeout(() => this.onInactiveTimeout(), timeoutMinutes * 60 * 1000)
|
this.timer = setTimeout(
|
||||||
|
() => this.onInactiveTimeout(),
|
||||||
|
timeoutMinutes * 60 * 1000,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,13 +12,12 @@ import ObservableStore from 'obs-store'
|
|||||||
* a cache of account balances in local storage
|
* a cache of account balances in local storage
|
||||||
*/
|
*/
|
||||||
export default class CachedBalancesController {
|
export default class CachedBalancesController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new controller instance
|
* Creates a new controller instance
|
||||||
*
|
*
|
||||||
* @param {CachedBalancesOptions} [opts] Controller configuration parameters
|
* @param {CachedBalancesOptions} [opts] - Controller configuration parameters
|
||||||
*/
|
*/
|
||||||
constructor (opts = {}) {
|
constructor(opts = {}) {
|
||||||
const { accountTracker, getNetwork } = opts
|
const { accountTracker, getNetwork } = opts
|
||||||
|
|
||||||
this.accountTracker = accountTracker
|
this.accountTracker = accountTracker
|
||||||
@ -37,15 +36,18 @@ export default class CachedBalancesController {
|
|||||||
* @param {Object} obj - The the recently updated accounts object for the current network
|
* @param {Object} obj - The the recently updated accounts object for the current network
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async updateCachedBalances ({ accounts }) {
|
async updateCachedBalances({ accounts }) {
|
||||||
const network = await this.getNetwork()
|
const network = await this.getNetwork()
|
||||||
const balancesToCache = await this._generateBalancesToCache(accounts, network)
|
const balancesToCache = await this._generateBalancesToCache(
|
||||||
|
accounts,
|
||||||
|
network,
|
||||||
|
)
|
||||||
this.store.updateState({
|
this.store.updateState({
|
||||||
cachedBalances: balancesToCache,
|
cachedBalances: balancesToCache,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
_generateBalancesToCache (newAccounts, currentNetwork) {
|
_generateBalancesToCache(newAccounts, currentNetwork) {
|
||||||
const { cachedBalances } = this.store.getState()
|
const { cachedBalances } = this.store.getState()
|
||||||
const currentNetworkBalancesToCache = { ...cachedBalances[currentNetwork] }
|
const currentNetworkBalancesToCache = { ...cachedBalances[currentNetwork] }
|
||||||
|
|
||||||
@ -68,7 +70,7 @@ export default class CachedBalancesController {
|
|||||||
* Removes cachedBalances
|
* Removes cachedBalances
|
||||||
*/
|
*/
|
||||||
|
|
||||||
clearCachedBalances () {
|
clearCachedBalances() {
|
||||||
this.store.updateState({ cachedBalances: {} })
|
this.store.updateState({ cachedBalances: {} })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +82,7 @@ export default class CachedBalancesController {
|
|||||||
* @private
|
* @private
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_registerUpdates () {
|
_registerUpdates() {
|
||||||
const update = this.updateCachedBalances.bind(this)
|
const update = this.updateCachedBalances.bind(this)
|
||||||
this.accountTracker.store.subscribe(update)
|
this.accountTracker.store.subscribe(update)
|
||||||
}
|
}
|
||||||
|
@ -6,20 +6,25 @@ import { MAINNET } from './network/enums'
|
|||||||
|
|
||||||
// By default, poll every 3 minutes
|
// By default, poll every 3 minutes
|
||||||
const DEFAULT_INTERVAL = 180 * 1000
|
const DEFAULT_INTERVAL = 180 * 1000
|
||||||
const SINGLE_CALL_BALANCES_ADDRESS = '0xb1f8e55c7f64d203c1400b9d8555d050f94adf39'
|
const SINGLE_CALL_BALANCES_ADDRESS =
|
||||||
|
'0xb1f8e55c7f64d203c1400b9d8555d050f94adf39'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A controller that polls for token exchange
|
* A controller that polls for token exchange
|
||||||
* rates based on a user's current token list
|
* rates based on a user's current token list
|
||||||
*/
|
*/
|
||||||
export default class DetectTokensController {
|
export default class DetectTokensController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a DetectTokensController
|
* Creates a DetectTokensController
|
||||||
*
|
*
|
||||||
* @param {Object} [config] - Options to configure controller
|
* @param {Object} [config] - Options to configure controller
|
||||||
*/
|
*/
|
||||||
constructor ({ interval = DEFAULT_INTERVAL, preferences, network, keyringMemStore } = {}) {
|
constructor({
|
||||||
|
interval = DEFAULT_INTERVAL,
|
||||||
|
preferences,
|
||||||
|
network,
|
||||||
|
keyringMemStore,
|
||||||
|
} = {}) {
|
||||||
this.preferences = preferences
|
this.preferences = preferences
|
||||||
this.interval = interval
|
this.interval = interval
|
||||||
this.network = network
|
this.network = network
|
||||||
@ -29,7 +34,7 @@ export default class DetectTokensController {
|
|||||||
/**
|
/**
|
||||||
* For each token in eth-contract-metadata, find check selectedAddress balance.
|
* For each token in eth-contract-metadata, find check selectedAddress balance.
|
||||||
*/
|
*/
|
||||||
async detectNewTokens () {
|
async detectNewTokens() {
|
||||||
if (!this.isActive) {
|
if (!this.isActive) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -40,7 +45,10 @@ export default class DetectTokensController {
|
|||||||
const tokensToDetect = []
|
const tokensToDetect = []
|
||||||
this.web3.setProvider(this._network._provider)
|
this.web3.setProvider(this._network._provider)
|
||||||
for (const contractAddress in contracts) {
|
for (const contractAddress in contracts) {
|
||||||
if (contracts[contractAddress].erc20 && !(this.tokenAddresses.includes(contractAddress.toLowerCase()))) {
|
if (
|
||||||
|
contracts[contractAddress].erc20 &&
|
||||||
|
!this.tokenAddresses.includes(contractAddress.toLowerCase())
|
||||||
|
) {
|
||||||
tokensToDetect.push(contractAddress)
|
tokensToDetect.push(contractAddress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,20 +57,29 @@ export default class DetectTokensController {
|
|||||||
try {
|
try {
|
||||||
result = await this._getTokenBalances(tokensToDetect)
|
result = await this._getTokenBalances(tokensToDetect)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
warn(`MetaMask - DetectTokensController single call balance fetch failed`, error)
|
warn(
|
||||||
|
`MetaMask - DetectTokensController single call balance fetch failed`,
|
||||||
|
error,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tokensToDetect.forEach((tokenAddress, index) => {
|
tokensToDetect.forEach((tokenAddress, index) => {
|
||||||
const balance = result[index]
|
const balance = result[index]
|
||||||
if (balance && !balance.isZero()) {
|
if (balance && !balance.isZero()) {
|
||||||
this._preferences.addToken(tokenAddress, contracts[tokenAddress].symbol, contracts[tokenAddress].decimals)
|
this._preferences.addToken(
|
||||||
|
tokenAddress,
|
||||||
|
contracts[tokenAddress].symbol,
|
||||||
|
contracts[tokenAddress].decimals,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getTokenBalances (tokens) {
|
async _getTokenBalances(tokens) {
|
||||||
const ethContract = this.web3.eth.contract(SINGLE_CALL_BALANCES_ABI).at(SINGLE_CALL_BALANCES_ADDRESS)
|
const ethContract = this.web3.eth
|
||||||
|
.contract(SINGLE_CALL_BALANCES_ABI)
|
||||||
|
.at(SINGLE_CALL_BALANCES_ADDRESS)
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
ethContract.balances([this.selectedAddress], tokens, (error, result) => {
|
ethContract.balances([this.selectedAddress], tokens, (error, result) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
@ -78,7 +95,7 @@ export default class DetectTokensController {
|
|||||||
* in case of address change or user session initialization.
|
* in case of address change or user session initialization.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
restartTokenDetection () {
|
restartTokenDetection() {
|
||||||
if (!(this.isActive && this.selectedAddress)) {
|
if (!(this.isActive && this.selectedAddress)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -90,7 +107,7 @@ export default class DetectTokensController {
|
|||||||
/**
|
/**
|
||||||
* @type {Number}
|
* @type {Number}
|
||||||
*/
|
*/
|
||||||
set interval (interval) {
|
set interval(interval) {
|
||||||
this._handle && clearInterval(this._handle)
|
this._handle && clearInterval(this._handle)
|
||||||
if (!interval) {
|
if (!interval) {
|
||||||
return
|
return
|
||||||
@ -104,7 +121,7 @@ export default class DetectTokensController {
|
|||||||
* In setter when selectedAddress is changed, detectNewTokens and restart polling
|
* In setter when selectedAddress is changed, detectNewTokens and restart polling
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
set preferences (preferences) {
|
set preferences(preferences) {
|
||||||
if (!preferences) {
|
if (!preferences) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -129,7 +146,7 @@ export default class DetectTokensController {
|
|||||||
/**
|
/**
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
set network (network) {
|
set network(network) {
|
||||||
if (!network) {
|
if (!network) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -141,7 +158,7 @@ export default class DetectTokensController {
|
|||||||
* In setter when isUnlocked is updated to true, detectNewTokens and restart polling
|
* In setter when isUnlocked is updated to true, detectNewTokens and restart polling
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
set keyringMemStore (keyringMemStore) {
|
set keyringMemStore(keyringMemStore) {
|
||||||
if (!keyringMemStore) {
|
if (!keyringMemStore) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -160,7 +177,7 @@ export default class DetectTokensController {
|
|||||||
* Internal isActive state
|
* Internal isActive state
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
get isActive () {
|
get isActive() {
|
||||||
return this.isOpen && this.isUnlocked
|
return this.isOpen && this.isUnlocked
|
||||||
}
|
}
|
||||||
/* eslint-enable accessor-pairs */
|
/* eslint-enable accessor-pairs */
|
||||||
|
@ -2,22 +2,22 @@ import EthJsEns from 'ethjs-ens'
|
|||||||
import ensNetworkMap from 'ethereum-ens-network-map'
|
import ensNetworkMap from 'ethereum-ens-network-map'
|
||||||
|
|
||||||
export default class Ens {
|
export default class Ens {
|
||||||
static getNetworkEnsSupport (network) {
|
static getNetworkEnsSupport(network) {
|
||||||
return Boolean(ensNetworkMap[network])
|
return Boolean(ensNetworkMap[network])
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor ({ network, provider } = {}) {
|
constructor({ network, provider } = {}) {
|
||||||
this._ethJsEns = new EthJsEns({
|
this._ethJsEns = new EthJsEns({
|
||||||
network,
|
network,
|
||||||
provider,
|
provider,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
lookup (ensName) {
|
lookup(ensName) {
|
||||||
return this._ethJsEns.lookup(ensName)
|
return this._ethJsEns.lookup(ensName)
|
||||||
}
|
}
|
||||||
|
|
||||||
reverse (address) {
|
reverse(address) {
|
||||||
return this._ethJsEns.reverse(address)
|
return this._ethJsEns.reverse(address)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
|||||||
const ZERO_X_ERROR_ADDRESS = '0x'
|
const ZERO_X_ERROR_ADDRESS = '0x'
|
||||||
|
|
||||||
export default class EnsController {
|
export default class EnsController {
|
||||||
constructor ({ ens, provider, networkStore } = {}) {
|
constructor({ ens, provider, networkStore } = {}) {
|
||||||
const initState = {
|
const initState = {
|
||||||
ensResolutionsByAddress: {},
|
ensResolutionsByAddress: {},
|
||||||
}
|
}
|
||||||
@ -38,11 +38,11 @@ export default class EnsController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
reverseResolveAddress (address) {
|
reverseResolveAddress(address) {
|
||||||
return this._reverseResolveAddress(ethUtil.toChecksumAddress(address))
|
return this._reverseResolveAddress(ethUtil.toChecksumAddress(address))
|
||||||
}
|
}
|
||||||
|
|
||||||
async _reverseResolveAddress (address) {
|
async _reverseResolveAddress(address) {
|
||||||
if (!this._ens) {
|
if (!this._ens) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
@ -68,7 +68,10 @@ export default class EnsController {
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
if (registeredAddress === ZERO_ADDRESS || registeredAddress === ZERO_X_ERROR_ADDRESS) {
|
if (
|
||||||
|
registeredAddress === ZERO_ADDRESS ||
|
||||||
|
registeredAddress === ZERO_X_ERROR_ADDRESS
|
||||||
|
) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +83,7 @@ export default class EnsController {
|
|||||||
return domain
|
return domain
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateResolutionsByAddress (address, domain) {
|
_updateResolutionsByAddress(address, domain) {
|
||||||
const oldState = this.store.getState()
|
const oldState = this.store.getState()
|
||||||
this.store.putState({
|
this.store.putState({
|
||||||
ensResolutionsByAddress: {
|
ensResolutionsByAddress: {
|
||||||
|
@ -6,30 +6,49 @@ import { bnToHex } from '../lib/util'
|
|||||||
import fetchWithTimeout from '../lib/fetch-with-timeout'
|
import fetchWithTimeout from '../lib/fetch-with-timeout'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ROPSTEN,
|
TRANSACTION_CATEGORIES,
|
||||||
RINKEBY,
|
TRANSACTION_STATUSES,
|
||||||
KOVAN,
|
} from '../../../shared/constants/transaction'
|
||||||
|
import {
|
||||||
|
CHAIN_ID_TO_NETWORK_ID_MAP,
|
||||||
|
CHAIN_ID_TO_TYPE_MAP,
|
||||||
GOERLI,
|
GOERLI,
|
||||||
|
GOERLI_CHAIN_ID,
|
||||||
|
KOVAN,
|
||||||
|
KOVAN_CHAIN_ID,
|
||||||
MAINNET,
|
MAINNET,
|
||||||
NETWORK_TYPE_TO_ID_MAP,
|
MAINNET_CHAIN_ID,
|
||||||
|
RINKEBY,
|
||||||
|
RINKEBY_CHAIN_ID,
|
||||||
|
ROPSTEN,
|
||||||
|
ROPSTEN_CHAIN_ID,
|
||||||
} from './network/enums'
|
} from './network/enums'
|
||||||
|
|
||||||
const fetch = fetchWithTimeout({
|
const fetch = fetchWithTimeout({
|
||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
})
|
})
|
||||||
|
|
||||||
export default class IncomingTransactionsController {
|
/**
|
||||||
|
* This controller is responsible for retrieving incoming transactions. Etherscan is polled once every block to check
|
||||||
|
* for new incoming transactions for the current selected account on the current network
|
||||||
|
*
|
||||||
|
* Note that only the built-in Infura networks are supported (i.e. anything in `INFURA_PROVIDER_TYPES`). We will not
|
||||||
|
* attempt to retrieve incoming transactions on any custom RPC endpoints.
|
||||||
|
*/
|
||||||
|
const etherscanSupportedNetworks = [
|
||||||
|
GOERLI_CHAIN_ID,
|
||||||
|
KOVAN_CHAIN_ID,
|
||||||
|
MAINNET_CHAIN_ID,
|
||||||
|
RINKEBY_CHAIN_ID,
|
||||||
|
ROPSTEN_CHAIN_ID,
|
||||||
|
]
|
||||||
|
|
||||||
constructor (opts = {}) {
|
export default class IncomingTransactionsController {
|
||||||
const {
|
constructor(opts = {}) {
|
||||||
blockTracker,
|
const { blockTracker, networkController, preferencesController } = opts
|
||||||
networkController,
|
|
||||||
preferencesController,
|
|
||||||
} = opts
|
|
||||||
this.blockTracker = blockTracker
|
this.blockTracker = blockTracker
|
||||||
this.networkController = networkController
|
this.networkController = networkController
|
||||||
this.preferencesController = preferencesController
|
this.preferencesController = preferencesController
|
||||||
this.getCurrentNetwork = () => networkController.getProviderConfig().type
|
|
||||||
|
|
||||||
this._onLatestBlock = async (newBlockNumberHex) => {
|
this._onLatestBlock = async (newBlockNumberHex) => {
|
||||||
const selectedAddress = this.preferencesController.getSelectedAddress()
|
const selectedAddress = this.preferencesController.getSelectedAddress()
|
||||||
@ -43,54 +62,66 @@ export default class IncomingTransactionsController {
|
|||||||
const initState = {
|
const initState = {
|
||||||
incomingTransactions: {},
|
incomingTransactions: {},
|
||||||
incomingTxLastFetchedBlocksByNetwork: {
|
incomingTxLastFetchedBlocksByNetwork: {
|
||||||
[ROPSTEN]: null,
|
|
||||||
[RINKEBY]: null,
|
|
||||||
[KOVAN]: null,
|
|
||||||
[GOERLI]: null,
|
[GOERLI]: null,
|
||||||
|
[KOVAN]: null,
|
||||||
[MAINNET]: null,
|
[MAINNET]: null,
|
||||||
}, ...opts.initState,
|
[RINKEBY]: null,
|
||||||
|
[ROPSTEN]: null,
|
||||||
|
},
|
||||||
|
...opts.initState,
|
||||||
}
|
}
|
||||||
this.store = new ObservableStore(initState)
|
this.store = new ObservableStore(initState)
|
||||||
|
|
||||||
this.preferencesController.store.subscribe(pairwise((prevState, currState) => {
|
this.preferencesController.store.subscribe(
|
||||||
const { featureFlags: { showIncomingTransactions: prevShowIncomingTransactions } = {} } = prevState
|
pairwise((prevState, currState) => {
|
||||||
const { featureFlags: { showIncomingTransactions: currShowIncomingTransactions } = {} } = currState
|
const {
|
||||||
|
featureFlags: {
|
||||||
|
showIncomingTransactions: prevShowIncomingTransactions,
|
||||||
|
} = {},
|
||||||
|
} = prevState
|
||||||
|
const {
|
||||||
|
featureFlags: {
|
||||||
|
showIncomingTransactions: currShowIncomingTransactions,
|
||||||
|
} = {},
|
||||||
|
} = currState
|
||||||
|
|
||||||
if (currShowIncomingTransactions === prevShowIncomingTransactions) {
|
if (currShowIncomingTransactions === prevShowIncomingTransactions) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevShowIncomingTransactions && !currShowIncomingTransactions) {
|
if (prevShowIncomingTransactions && !currShowIncomingTransactions) {
|
||||||
this.stop()
|
this.stop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.start()
|
this.start()
|
||||||
}))
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
this.preferencesController.store.subscribe(pairwise(async (prevState, currState) => {
|
this.preferencesController.store.subscribe(
|
||||||
const { selectedAddress: prevSelectedAddress } = prevState
|
pairwise(async (prevState, currState) => {
|
||||||
const { selectedAddress: currSelectedAddress } = currState
|
const { selectedAddress: prevSelectedAddress } = prevState
|
||||||
|
const { selectedAddress: currSelectedAddress } = currState
|
||||||
|
|
||||||
if (currSelectedAddress === prevSelectedAddress) {
|
if (currSelectedAddress === prevSelectedAddress) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await this._update({
|
await this._update({
|
||||||
address: currSelectedAddress,
|
address: currSelectedAddress,
|
||||||
})
|
})
|
||||||
}))
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
this.networkController.on('networkDidChange', async (newType) => {
|
this.networkController.on('networkDidChange', async () => {
|
||||||
const address = this.preferencesController.getSelectedAddress()
|
const address = this.preferencesController.getSelectedAddress()
|
||||||
await this._update({
|
await this._update({
|
||||||
address,
|
address,
|
||||||
networkType: newType,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
start () {
|
start() {
|
||||||
const { featureFlags = {} } = this.preferencesController.store.getState()
|
const { featureFlags = {} } = this.preferencesController.store.getState()
|
||||||
const { showIncomingTransactions } = featureFlags
|
const { showIncomingTransactions } = featureFlags
|
||||||
|
|
||||||
@ -102,33 +133,45 @@ export default class IncomingTransactionsController {
|
|||||||
this.blockTracker.addListener('latest', this._onLatestBlock)
|
this.blockTracker.addListener('latest', this._onLatestBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
stop () {
|
stop() {
|
||||||
this.blockTracker.removeListener('latest', this._onLatestBlock)
|
this.blockTracker.removeListener('latest', this._onLatestBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
async _update ({ address, newBlockNumberDec, networkType } = {}) {
|
async _update({ address, newBlockNumberDec } = {}) {
|
||||||
|
const chainId = this.networkController.getCurrentChainId()
|
||||||
|
if (!etherscanSupportedNetworks.includes(chainId)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const dataForUpdate = await this._getDataForUpdate({ address, newBlockNumberDec, networkType })
|
const dataForUpdate = await this._getDataForUpdate({
|
||||||
await this._updateStateWithNewTxData(dataForUpdate)
|
address,
|
||||||
|
chainId,
|
||||||
|
newBlockNumberDec,
|
||||||
|
})
|
||||||
|
this._updateStateWithNewTxData(dataForUpdate)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error(err)
|
log.error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getDataForUpdate ({ address, newBlockNumberDec, networkType } = {}) {
|
async _getDataForUpdate({ address, chainId, newBlockNumberDec } = {}) {
|
||||||
const {
|
const {
|
||||||
incomingTransactions: currentIncomingTxs,
|
incomingTransactions: currentIncomingTxs,
|
||||||
incomingTxLastFetchedBlocksByNetwork: currentBlocksByNetwork,
|
incomingTxLastFetchedBlocksByNetwork: currentBlocksByNetwork,
|
||||||
} = this.store.getState()
|
} = this.store.getState()
|
||||||
|
|
||||||
const network = networkType || this.getCurrentNetwork()
|
const lastFetchBlockByCurrentNetwork =
|
||||||
const lastFetchBlockByCurrentNetwork = currentBlocksByNetwork[network]
|
currentBlocksByNetwork[CHAIN_ID_TO_TYPE_MAP[chainId]]
|
||||||
let blockToFetchFrom = lastFetchBlockByCurrentNetwork || newBlockNumberDec
|
let blockToFetchFrom = lastFetchBlockByCurrentNetwork || newBlockNumberDec
|
||||||
if (blockToFetchFrom === undefined) {
|
if (blockToFetchFrom === undefined) {
|
||||||
blockToFetchFrom = parseInt(this.blockTracker.getCurrentBlock(), 16)
|
blockToFetchFrom = parseInt(this.blockTracker.getCurrentBlock(), 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { latestIncomingTxBlockNumber, txs: newTxs } = await this._fetchAll(address, blockToFetchFrom, network)
|
const { latestIncomingTxBlockNumber, txs: newTxs } = await this._fetchAll(
|
||||||
|
address,
|
||||||
|
blockToFetchFrom,
|
||||||
|
chainId,
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
latestIncomingTxBlockNumber,
|
latestIncomingTxBlockNumber,
|
||||||
@ -136,17 +179,17 @@ export default class IncomingTransactionsController {
|
|||||||
currentIncomingTxs,
|
currentIncomingTxs,
|
||||||
currentBlocksByNetwork,
|
currentBlocksByNetwork,
|
||||||
fetchedBlockNumber: blockToFetchFrom,
|
fetchedBlockNumber: blockToFetchFrom,
|
||||||
network,
|
chainId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _updateStateWithNewTxData ({
|
_updateStateWithNewTxData({
|
||||||
latestIncomingTxBlockNumber,
|
latestIncomingTxBlockNumber,
|
||||||
newTxs,
|
newTxs,
|
||||||
currentIncomingTxs,
|
currentIncomingTxs,
|
||||||
currentBlocksByNetwork,
|
currentBlocksByNetwork,
|
||||||
fetchedBlockNumber,
|
fetchedBlockNumber,
|
||||||
network,
|
chainId,
|
||||||
}) {
|
}) {
|
||||||
const newLatestBlockHashByNetwork = latestIncomingTxBlockNumber
|
const newLatestBlockHashByNetwork = latestIncomingTxBlockNumber
|
||||||
? parseInt(latestIncomingTxBlockNumber, 10) + 1
|
? parseInt(latestIncomingTxBlockNumber, 10) + 1
|
||||||
@ -161,28 +204,23 @@ export default class IncomingTransactionsController {
|
|||||||
this.store.updateState({
|
this.store.updateState({
|
||||||
incomingTxLastFetchedBlocksByNetwork: {
|
incomingTxLastFetchedBlocksByNetwork: {
|
||||||
...currentBlocksByNetwork,
|
...currentBlocksByNetwork,
|
||||||
[network]: newLatestBlockHashByNetwork,
|
[CHAIN_ID_TO_TYPE_MAP[chainId]]: newLatestBlockHashByNetwork,
|
||||||
},
|
},
|
||||||
incomingTransactions: newIncomingTransactions,
|
incomingTransactions: newIncomingTransactions,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async _fetchAll (address, fromBlock, networkType) {
|
async _fetchAll(address, fromBlock, chainId) {
|
||||||
const fetchedTxResponse = await this._fetchTxs(address, fromBlock, networkType)
|
const fetchedTxResponse = await this._fetchTxs(address, fromBlock, chainId)
|
||||||
return this._processTxFetchResponse(fetchedTxResponse)
|
return this._processTxFetchResponse(fetchedTxResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
async _fetchTxs (address, fromBlock, networkType) {
|
async _fetchTxs(address, fromBlock, chainId) {
|
||||||
let etherscanSubdomain = 'api'
|
const etherscanSubdomain =
|
||||||
const currentNetworkID = NETWORK_TYPE_TO_ID_MAP[networkType]?.networkId
|
chainId === MAINNET_CHAIN_ID
|
||||||
|
? 'api'
|
||||||
|
: `api-${CHAIN_ID_TO_TYPE_MAP[chainId]}`
|
||||||
|
|
||||||
if (!currentNetworkID) {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (networkType !== MAINNET) {
|
|
||||||
etherscanSubdomain = `api-${networkType}`
|
|
||||||
}
|
|
||||||
const apiUrl = `https://${etherscanSubdomain}.etherscan.io`
|
const apiUrl = `https://${etherscanSubdomain}.etherscan.io`
|
||||||
let url = `${apiUrl}/api?module=account&action=txlist&address=${address}&tag=latest&page=1`
|
let url = `${apiUrl}/api?module=account&action=txlist&address=${address}&tag=latest&page=1`
|
||||||
|
|
||||||
@ -195,22 +233,26 @@ export default class IncomingTransactionsController {
|
|||||||
return {
|
return {
|
||||||
...parsedResponse,
|
...parsedResponse,
|
||||||
address,
|
address,
|
||||||
currentNetworkID,
|
chainId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_processTxFetchResponse ({ status, result = [], address, currentNetworkID }) {
|
_processTxFetchResponse({ status, result = [], address, chainId }) {
|
||||||
if (status === '1' && Array.isArray(result) && result.length > 0) {
|
if (status === '1' && Array.isArray(result) && result.length > 0) {
|
||||||
const remoteTxList = {}
|
const remoteTxList = {}
|
||||||
const remoteTxs = []
|
const remoteTxs = []
|
||||||
result.forEach((tx) => {
|
result.forEach((tx) => {
|
||||||
if (!remoteTxList[tx.hash]) {
|
if (!remoteTxList[tx.hash]) {
|
||||||
remoteTxs.push(this._normalizeTxFromEtherscan(tx, currentNetworkID))
|
remoteTxs.push(this._normalizeTxFromEtherscan(tx, chainId))
|
||||||
remoteTxList[tx.hash] = 1
|
remoteTxList[tx.hash] = 1
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const incomingTxs = remoteTxs.filter((tx) => tx.txParams.to && tx.txParams.to.toLowerCase() === address.toLowerCase())
|
const incomingTxs = remoteTxs.filter(
|
||||||
|
(tx) =>
|
||||||
|
tx.txParams.to &&
|
||||||
|
tx.txParams.to.toLowerCase() === address.toLowerCase(),
|
||||||
|
)
|
||||||
incomingTxs.sort((a, b) => (a.time < b.time ? -1 : 1))
|
incomingTxs.sort((a, b) => (a.time < b.time ? -1 : 1))
|
||||||
|
|
||||||
let latestIncomingTxBlockNumber = null
|
let latestIncomingTxBlockNumber = null
|
||||||
@ -218,7 +260,8 @@ export default class IncomingTransactionsController {
|
|||||||
if (
|
if (
|
||||||
tx.blockNumber &&
|
tx.blockNumber &&
|
||||||
(!latestIncomingTxBlockNumber ||
|
(!latestIncomingTxBlockNumber ||
|
||||||
parseInt(latestIncomingTxBlockNumber, 10) < parseInt(tx.blockNumber, 10))
|
parseInt(latestIncomingTxBlockNumber, 10) <
|
||||||
|
parseInt(tx.blockNumber, 10))
|
||||||
) {
|
) {
|
||||||
latestIncomingTxBlockNumber = tx.blockNumber
|
latestIncomingTxBlockNumber = tx.blockNumber
|
||||||
}
|
}
|
||||||
@ -234,13 +277,16 @@ export default class IncomingTransactionsController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_normalizeTxFromEtherscan (txMeta, currentNetworkID) {
|
_normalizeTxFromEtherscan(txMeta, chainId) {
|
||||||
const time = parseInt(txMeta.timeStamp, 10) * 1000
|
const time = parseInt(txMeta.timeStamp, 10) * 1000
|
||||||
const status = txMeta.isError === '0' ? 'confirmed' : 'failed'
|
const status =
|
||||||
|
txMeta.isError === '0'
|
||||||
|
? TRANSACTION_STATUSES.CONFIRMED
|
||||||
|
: TRANSACTION_STATUSES.FAILED
|
||||||
return {
|
return {
|
||||||
blockNumber: txMeta.blockNumber,
|
blockNumber: txMeta.blockNumber,
|
||||||
id: createId(),
|
id: createId(),
|
||||||
metamaskNetworkId: currentNetworkID,
|
metamaskNetworkId: CHAIN_ID_TO_NETWORK_ID_MAP[chainId],
|
||||||
status,
|
status,
|
||||||
time,
|
time,
|
||||||
txParams: {
|
txParams: {
|
||||||
@ -252,12 +298,12 @@ export default class IncomingTransactionsController {
|
|||||||
value: bnToHex(new BN(txMeta.value)),
|
value: bnToHex(new BN(txMeta.value)),
|
||||||
},
|
},
|
||||||
hash: txMeta.hash,
|
hash: txMeta.hash,
|
||||||
transactionCategory: 'incoming',
|
transactionCategory: TRANSACTION_CATEGORIES.INCOMING,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function pairwise (fn) {
|
function pairwise(fn) {
|
||||||
let first = true
|
let first = true
|
||||||
let cache
|
let cache
|
||||||
return (value) => {
|
return (value) => {
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
export const SINGLE_CALL_BALANCES_ADDRESS = '0xb1f8e55c7f64d203c1400b9d8555d050f94adf39'
|
export const SINGLE_CALL_BALANCES_ADDRESS =
|
||||||
export const SINGLE_CALL_BALANCES_ADDRESS_RINKEBY = '0x9f510b19f1ad66f0dcf6e45559fab0d6752c1db7'
|
'0xb1f8e55c7f64d203c1400b9d8555d050f94adf39'
|
||||||
export const SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN = '0xb8e671734ce5c8d7dfbbea5574fa4cf39f7a54a4'
|
export const SINGLE_CALL_BALANCES_ADDRESS_RINKEBY =
|
||||||
export const SINGLE_CALL_BALANCES_ADDRESS_KOVAN = '0xb1d3fbb2f83aecd196f474c16ca5d9cffa0d0ffc'
|
'0x9f510b19f1ad66f0dcf6e45559fab0d6752c1db7'
|
||||||
|
export const SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN =
|
||||||
|
'0xb8e671734ce5c8d7dfbbea5574fa4cf39f7a54a4'
|
||||||
|
export const SINGLE_CALL_BALANCES_ADDRESS_KOVAN =
|
||||||
|
'0xb1d3fbb2f83aecd196f474c16ca5d9cffa0d0ffc'
|
||||||
|
@ -10,7 +10,7 @@ import createInfuraMiddleware from 'eth-json-rpc-infura'
|
|||||||
import BlockTracker from 'eth-block-tracker'
|
import BlockTracker from 'eth-block-tracker'
|
||||||
import * as networkEnums from './enums'
|
import * as networkEnums from './enums'
|
||||||
|
|
||||||
export default function createInfuraClient ({ network, projectId }) {
|
export default function createInfuraClient({ network, projectId }) {
|
||||||
const infuraMiddleware = createInfuraMiddleware({
|
const infuraMiddleware = createInfuraMiddleware({
|
||||||
network,
|
network,
|
||||||
projectId,
|
projectId,
|
||||||
@ -32,7 +32,7 @@ export default function createInfuraClient ({ network, projectId }) {
|
|||||||
return { networkMiddleware, blockTracker }
|
return { networkMiddleware, blockTracker }
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNetworkAndChainIdMiddleware ({ network }) {
|
function createNetworkAndChainIdMiddleware({ network }) {
|
||||||
let chainId
|
let chainId
|
||||||
let netId
|
let netId
|
||||||
|
|
||||||
|
@ -9,16 +9,12 @@ import providerFromMiddleware from 'eth-json-rpc-middleware/providerFromMiddlewa
|
|||||||
import BlockTracker from 'eth-block-tracker'
|
import BlockTracker from 'eth-block-tracker'
|
||||||
|
|
||||||
const inTest = process.env.IN_TEST === 'true'
|
const inTest = process.env.IN_TEST === 'true'
|
||||||
const blockTrackerOpts = inTest
|
const blockTrackerOpts = inTest ? { pollingInterval: 1000 } : {}
|
||||||
? { pollingInterval: 1000 }
|
|
||||||
: {}
|
|
||||||
const getTestMiddlewares = () => {
|
const getTestMiddlewares = () => {
|
||||||
return inTest
|
return inTest ? [createEstimateGasDelayTestMiddleware()] : []
|
||||||
? [createEstimateGasDelayTestMiddleware()]
|
|
||||||
: []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function createJsonRpcClient ({ rpcUrl, chainId }) {
|
export default function createJsonRpcClient({ rpcUrl, chainId }) {
|
||||||
const fetchMiddleware = createFetchMiddleware({ rpcUrl })
|
const fetchMiddleware = createFetchMiddleware({ rpcUrl })
|
||||||
const blockProvider = providerFromMiddleware(fetchMiddleware)
|
const blockProvider = providerFromMiddleware(fetchMiddleware)
|
||||||
const blockTracker = new BlockTracker({
|
const blockTracker = new BlockTracker({
|
||||||
@ -39,7 +35,7 @@ export default function createJsonRpcClient ({ rpcUrl, chainId }) {
|
|||||||
return { networkMiddleware, blockTracker }
|
return { networkMiddleware, blockTracker }
|
||||||
}
|
}
|
||||||
|
|
||||||
function createChainIdMiddleware (chainId) {
|
function createChainIdMiddleware(chainId) {
|
||||||
return (req, res, next, end) => {
|
return (req, res, next, end) => {
|
||||||
if (req.method === 'eth_chainId') {
|
if (req.method === 'eth_chainId') {
|
||||||
res.result = chainId
|
res.result = chainId
|
||||||
@ -53,7 +49,7 @@ function createChainIdMiddleware (chainId) {
|
|||||||
* For use in tests only.
|
* For use in tests only.
|
||||||
* Adds a delay to `eth_estimateGas` calls.
|
* Adds a delay to `eth_estimateGas` calls.
|
||||||
*/
|
*/
|
||||||
function createEstimateGasDelayTestMiddleware () {
|
function createEstimateGasDelayTestMiddleware() {
|
||||||
return createAsyncMiddleware(async (req, _, next) => {
|
return createAsyncMiddleware(async (req, _, next) => {
|
||||||
if (req.method === 'eth_estimateGas') {
|
if (req.method === 'eth_estimateGas') {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import mergeMiddleware from 'json-rpc-engine/src/mergeMiddleware'
|
import mergeMiddleware from 'json-rpc-engine/src/mergeMiddleware'
|
||||||
import createScaffoldMiddleware from 'json-rpc-engine/src/createScaffoldMiddleware'
|
import createScaffoldMiddleware from 'json-rpc-engine/src/createScaffoldMiddleware'
|
||||||
import createWalletSubprovider from 'eth-json-rpc-middleware/wallet'
|
import createWalletSubprovider from 'eth-json-rpc-middleware/wallet'
|
||||||
import { createPendingNonceMiddleware, createPendingTxMiddleware } from './middleware/pending'
|
import {
|
||||||
|
createPendingNonceMiddleware,
|
||||||
|
createPendingTxMiddleware,
|
||||||
|
} from './middleware/pending'
|
||||||
|
|
||||||
export default function createMetamaskMiddleware ({
|
export default function createMetamaskMiddleware({
|
||||||
version,
|
version,
|
||||||
getAccounts,
|
getAccounts,
|
||||||
processTransaction,
|
processTransaction,
|
||||||
|
@ -22,13 +22,7 @@ export const KOVAN_DISPLAY_NAME = 'Kovan'
|
|||||||
export const MAINNET_DISPLAY_NAME = 'Ethereum Mainnet'
|
export const MAINNET_DISPLAY_NAME = 'Ethereum Mainnet'
|
||||||
export const GOERLI_DISPLAY_NAME = 'Goerli'
|
export const GOERLI_DISPLAY_NAME = 'Goerli'
|
||||||
|
|
||||||
export const INFURA_PROVIDER_TYPES = [
|
export const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET, GOERLI]
|
||||||
ROPSTEN,
|
|
||||||
RINKEBY,
|
|
||||||
KOVAN,
|
|
||||||
MAINNET,
|
|
||||||
GOERLI,
|
|
||||||
]
|
|
||||||
|
|
||||||
export const NETWORK_TYPE_TO_ID_MAP = {
|
export const NETWORK_TYPE_TO_ID_MAP = {
|
||||||
[ROPSTEN]: { networkId: ROPSTEN_NETWORK_ID, chainId: ROPSTEN_CHAIN_ID },
|
[ROPSTEN]: { networkId: ROPSTEN_NETWORK_ID, chainId: ROPSTEN_CHAIN_ID },
|
||||||
@ -57,3 +51,17 @@ export const NETWORK_TO_NAME_MAP = {
|
|||||||
[GOERLI_CHAIN_ID]: GOERLI_DISPLAY_NAME,
|
[GOERLI_CHAIN_ID]: GOERLI_DISPLAY_NAME,
|
||||||
[MAINNET_CHAIN_ID]: MAINNET_DISPLAY_NAME,
|
[MAINNET_CHAIN_ID]: MAINNET_DISPLAY_NAME,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const CHAIN_ID_TO_TYPE_MAP = Object.entries(
|
||||||
|
NETWORK_TYPE_TO_ID_MAP,
|
||||||
|
).reduce((chainIdToTypeMap, [networkType, { chainId }]) => {
|
||||||
|
chainIdToTypeMap[chainId] = networkType
|
||||||
|
return chainIdToTypeMap
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
export const CHAIN_ID_TO_NETWORK_ID_MAP = Object.values(
|
||||||
|
NETWORK_TYPE_TO_ID_MAP,
|
||||||
|
).reduce((chainIdToNetworkIdMap, { chainId, networkId }) => {
|
||||||
|
chainIdToNetworkIdMap[chainId] = networkId
|
||||||
|
return chainIdToNetworkIdMap
|
||||||
|
}, {})
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import createAsyncMiddleware from 'json-rpc-engine/src/createAsyncMiddleware'
|
import createAsyncMiddleware from 'json-rpc-engine/src/createAsyncMiddleware'
|
||||||
import { formatTxMetaForRpcResult } from '../util'
|
import { formatTxMetaForRpcResult } from '../util'
|
||||||
|
|
||||||
export function createPendingNonceMiddleware ({ getPendingNonce }) {
|
export function createPendingNonceMiddleware({ getPendingNonce }) {
|
||||||
return createAsyncMiddleware(async (req, res, next) => {
|
return createAsyncMiddleware(async (req, res, next) => {
|
||||||
const { method, params } = req
|
const { method, params } = req
|
||||||
if (method !== 'eth_getTransactionCount') {
|
if (method !== 'eth_getTransactionCount') {
|
||||||
@ -17,7 +17,7 @@ export function createPendingNonceMiddleware ({ getPendingNonce }) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createPendingTxMiddleware ({ getPendingTransactionByHash }) {
|
export function createPendingTxMiddleware({ getPendingTransactionByHash }) {
|
||||||
return createAsyncMiddleware(async (req, res, next) => {
|
return createAsyncMiddleware(async (req, res, next) => {
|
||||||
const { method, params } = req
|
const { method, params } = req
|
||||||
if (method !== 'eth_getTransactionByHash') {
|
if (method !== 'eth_getTransactionByHash') {
|
||||||
|
@ -5,7 +5,10 @@ import ComposedStore from 'obs-store/lib/composed'
|
|||||||
import JsonRpcEngine from 'json-rpc-engine'
|
import JsonRpcEngine from 'json-rpc-engine'
|
||||||
import providerFromEngine from 'eth-json-rpc-middleware/providerFromEngine'
|
import providerFromEngine from 'eth-json-rpc-middleware/providerFromEngine'
|
||||||
import log from 'loglevel'
|
import log from 'loglevel'
|
||||||
import { createSwappableProxy, createEventEmitterProxy } from 'swappable-obj-proxy'
|
import {
|
||||||
|
createSwappableProxy,
|
||||||
|
createEventEmitterProxy,
|
||||||
|
} from 'swappable-obj-proxy'
|
||||||
import EthQuery from 'eth-query'
|
import EthQuery from 'eth-query'
|
||||||
import createMetamaskMiddleware from './createMetamaskMiddleware'
|
import createMetamaskMiddleware from './createMetamaskMiddleware'
|
||||||
import createInfuraClient from './createInfuraClient'
|
import createInfuraClient from './createInfuraClient'
|
||||||
@ -40,8 +43,7 @@ const defaultProviderConfig = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class NetworkController extends EventEmitter {
|
export default class NetworkController extends EventEmitter {
|
||||||
|
constructor(opts = {}) {
|
||||||
constructor (opts = {}) {
|
|
||||||
super()
|
super()
|
||||||
|
|
||||||
// create stores
|
// create stores
|
||||||
@ -72,7 +74,7 @@ export default class NetworkController extends EventEmitter {
|
|||||||
* @throws {Error} if the project ID is not a valid string
|
* @throws {Error} if the project ID is not a valid string
|
||||||
* @return {void}
|
* @return {void}
|
||||||
*/
|
*/
|
||||||
setInfuraProjectId (projectId) {
|
setInfuraProjectId(projectId) {
|
||||||
if (!projectId || typeof projectId !== 'string') {
|
if (!projectId || typeof projectId !== 'string') {
|
||||||
throw new Error('Invalid Infura project ID')
|
throw new Error('Invalid Infura project ID')
|
||||||
}
|
}
|
||||||
@ -80,7 +82,7 @@ export default class NetworkController extends EventEmitter {
|
|||||||
this._infuraProjectId = projectId
|
this._infuraProjectId = projectId
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeProvider (providerParams) {
|
initializeProvider(providerParams) {
|
||||||
this._baseProviderParams = providerParams
|
this._baseProviderParams = providerParams
|
||||||
const { type, rpcUrl, chainId } = this.getProviderConfig()
|
const { type, rpcUrl, chainId } = this.getProviderConfig()
|
||||||
this._configureProvider({ type, rpcUrl, chainId })
|
this._configureProvider({ type, rpcUrl, chainId })
|
||||||
@ -88,41 +90,45 @@ export default class NetworkController extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// return the proxies so the references will always be good
|
// return the proxies so the references will always be good
|
||||||
getProviderAndBlockTracker () {
|
getProviderAndBlockTracker() {
|
||||||
const provider = this._providerProxy
|
const provider = this._providerProxy
|
||||||
const blockTracker = this._blockTrackerProxy
|
const blockTracker = this._blockTrackerProxy
|
||||||
return { provider, blockTracker }
|
return { provider, blockTracker }
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyNetwork () {
|
verifyNetwork() {
|
||||||
// Check network when restoring connectivity:
|
// Check network when restoring connectivity:
|
||||||
if (this.isNetworkLoading()) {
|
if (this.isNetworkLoading()) {
|
||||||
this.lookupNetwork()
|
this.lookupNetwork()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getNetworkState () {
|
getNetworkState() {
|
||||||
return this.networkStore.getState()
|
return this.networkStore.getState()
|
||||||
}
|
}
|
||||||
|
|
||||||
setNetworkState (network) {
|
setNetworkState(network) {
|
||||||
this.networkStore.putState(network)
|
this.networkStore.putState(network)
|
||||||
}
|
}
|
||||||
|
|
||||||
isNetworkLoading () {
|
isNetworkLoading() {
|
||||||
return this.getNetworkState() === 'loading'
|
return this.getNetworkState() === 'loading'
|
||||||
}
|
}
|
||||||
|
|
||||||
lookupNetwork () {
|
lookupNetwork() {
|
||||||
// Prevent firing when provider is not defined.
|
// Prevent firing when provider is not defined.
|
||||||
if (!this._provider) {
|
if (!this._provider) {
|
||||||
log.warn('NetworkController - lookupNetwork aborted due to missing provider')
|
log.warn(
|
||||||
|
'NetworkController - lookupNetwork aborted due to missing provider',
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const chainId = this.getCurrentChainId()
|
const chainId = this.getCurrentChainId()
|
||||||
if (!chainId) {
|
if (!chainId) {
|
||||||
log.warn('NetworkController - lookupNetwork aborted due to missing chainId')
|
log.warn(
|
||||||
|
'NetworkController - lookupNetwork aborted due to missing chainId',
|
||||||
|
)
|
||||||
this.setNetworkState('loading')
|
this.setNetworkState('loading')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -143,12 +149,12 @@ export default class NetworkController extends EventEmitter {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentChainId () {
|
getCurrentChainId() {
|
||||||
const { type, chainId: configChainId } = this.getProviderConfig()
|
const { type, chainId: configChainId } = this.getProviderConfig()
|
||||||
return NETWORK_TYPE_TO_ID_MAP[type]?.chainId || configChainId
|
return NETWORK_TYPE_TO_ID_MAP[type]?.chainId || configChainId
|
||||||
}
|
}
|
||||||
|
|
||||||
setRpcTarget (rpcUrl, chainId, ticker = 'ETH', nickname = '', rpcPrefs) {
|
setRpcTarget(rpcUrl, chainId, ticker = 'ETH', nickname = '', rpcPrefs) {
|
||||||
this.setProviderConfig({
|
this.setProviderConfig({
|
||||||
type: 'rpc',
|
type: 'rpc',
|
||||||
rpcUrl,
|
rpcUrl,
|
||||||
@ -159,26 +165,33 @@ export default class NetworkController extends EventEmitter {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async setProviderType (type, rpcUrl = '', ticker = 'ETH', nickname = '') {
|
async setProviderType(type, rpcUrl = '', ticker = 'ETH', nickname = '') {
|
||||||
assert.notEqual(type, 'rpc', `NetworkController - cannot call "setProviderType" with type 'rpc'. use "setRpcTarget"`)
|
assert.notEqual(
|
||||||
assert(INFURA_PROVIDER_TYPES.includes(type), `NetworkController - Unknown rpc type "${type}"`)
|
type,
|
||||||
|
'rpc',
|
||||||
|
`NetworkController - cannot call "setProviderType" with type 'rpc'. use "setRpcTarget"`,
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
INFURA_PROVIDER_TYPES.includes(type),
|
||||||
|
`NetworkController - Unknown rpc type "${type}"`,
|
||||||
|
)
|
||||||
const { chainId } = NETWORK_TYPE_TO_ID_MAP[type]
|
const { chainId } = NETWORK_TYPE_TO_ID_MAP[type]
|
||||||
this.setProviderConfig({ type, rpcUrl, chainId, ticker, nickname })
|
this.setProviderConfig({ type, rpcUrl, chainId, ticker, nickname })
|
||||||
}
|
}
|
||||||
|
|
||||||
resetConnection () {
|
resetConnection() {
|
||||||
this.setProviderConfig(this.getProviderConfig())
|
this.setProviderConfig(this.getProviderConfig())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the provider config and switches the network.
|
* Sets the provider config and switches the network.
|
||||||
*/
|
*/
|
||||||
setProviderConfig (config) {
|
setProviderConfig(config) {
|
||||||
this.providerStore.updateState(config)
|
this.providerStore.updateState(config)
|
||||||
this._switchNetwork(config)
|
this._switchNetwork(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
getProviderConfig () {
|
getProviderConfig() {
|
||||||
return this.providerStore.getState()
|
return this.providerStore.getState()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,26 +199,28 @@ export default class NetworkController extends EventEmitter {
|
|||||||
// Private
|
// Private
|
||||||
//
|
//
|
||||||
|
|
||||||
_switchNetwork (opts) {
|
_switchNetwork(opts) {
|
||||||
this.setNetworkState('loading')
|
this.setNetworkState('loading')
|
||||||
this._configureProvider(opts)
|
this._configureProvider(opts)
|
||||||
this.emit('networkDidChange', opts.type)
|
this.emit('networkDidChange', opts.type)
|
||||||
}
|
}
|
||||||
|
|
||||||
_configureProvider ({ type, rpcUrl, chainId }) {
|
_configureProvider({ type, rpcUrl, chainId }) {
|
||||||
// infura type-based endpoints
|
// infura type-based endpoints
|
||||||
const isInfura = INFURA_PROVIDER_TYPES.includes(type)
|
const isInfura = INFURA_PROVIDER_TYPES.includes(type)
|
||||||
if (isInfura) {
|
if (isInfura) {
|
||||||
this._configureInfuraProvider(type, this._infuraProjectId)
|
this._configureInfuraProvider(type, this._infuraProjectId)
|
||||||
// url-based rpc endpoints
|
// url-based rpc endpoints
|
||||||
} else if (type === 'rpc') {
|
} else if (type === 'rpc') {
|
||||||
this._configureStandardProvider(rpcUrl, chainId)
|
this._configureStandardProvider(rpcUrl, chainId)
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`NetworkController - _configureProvider - unknown type "${type}"`)
|
throw new Error(
|
||||||
|
`NetworkController - _configureProvider - unknown type "${type}"`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_configureInfuraProvider (type, projectId) {
|
_configureInfuraProvider(type, projectId) {
|
||||||
log.info('NetworkController - configureInfuraProvider', type)
|
log.info('NetworkController - configureInfuraProvider', type)
|
||||||
const networkClient = createInfuraClient({
|
const networkClient = createInfuraClient({
|
||||||
network: type,
|
network: type,
|
||||||
@ -214,14 +229,16 @@ export default class NetworkController extends EventEmitter {
|
|||||||
this._setNetworkClient(networkClient)
|
this._setNetworkClient(networkClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
_configureStandardProvider (rpcUrl, chainId) {
|
_configureStandardProvider(rpcUrl, chainId) {
|
||||||
log.info('NetworkController - configureStandardProvider', rpcUrl)
|
log.info('NetworkController - configureStandardProvider', rpcUrl)
|
||||||
const networkClient = createJsonRpcClient({ rpcUrl, chainId })
|
const networkClient = createJsonRpcClient({ rpcUrl, chainId })
|
||||||
this._setNetworkClient(networkClient)
|
this._setNetworkClient(networkClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
_setNetworkClient ({ networkMiddleware, blockTracker }) {
|
_setNetworkClient({ networkMiddleware, blockTracker }) {
|
||||||
const metamaskMiddleware = createMetamaskMiddleware(this._baseProviderParams)
|
const metamaskMiddleware = createMetamaskMiddleware(
|
||||||
|
this._baseProviderParams,
|
||||||
|
)
|
||||||
const engine = new JsonRpcEngine()
|
const engine = new JsonRpcEngine()
|
||||||
engine.push(metamaskMiddleware)
|
engine.push(metamaskMiddleware)
|
||||||
engine.push(networkMiddleware)
|
engine.push(networkMiddleware)
|
||||||
@ -229,7 +246,7 @@ export default class NetworkController extends EventEmitter {
|
|||||||
this._setProviderAndBlockTracker({ provider, blockTracker })
|
this._setProviderAndBlockTracker({ provider, blockTracker })
|
||||||
}
|
}
|
||||||
|
|
||||||
_setProviderAndBlockTracker ({ provider, blockTracker }) {
|
_setProviderAndBlockTracker({ provider, blockTracker }) {
|
||||||
// update or intialize proxies
|
// update or intialize proxies
|
||||||
if (this._providerProxy) {
|
if (this._providerProxy) {
|
||||||
this._providerProxy.setTarget(provider)
|
this._providerProxy.setTarget(provider)
|
||||||
@ -239,7 +256,9 @@ export default class NetworkController extends EventEmitter {
|
|||||||
if (this._blockTrackerProxy) {
|
if (this._blockTrackerProxy) {
|
||||||
this._blockTrackerProxy.setTarget(blockTracker)
|
this._blockTrackerProxy.setTarget(blockTracker)
|
||||||
} else {
|
} else {
|
||||||
this._blockTrackerProxy = createEventEmitterProxy(blockTracker, { eventFilter: 'skipInternal' })
|
this._blockTrackerProxy = createEventEmitterProxy(blockTracker, {
|
||||||
|
eventFilter: 'skipInternal',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
// set new provider and blockTracker
|
// set new provider and blockTracker
|
||||||
this._provider = provider
|
this._provider = provider
|
||||||
|
@ -2,21 +2,23 @@ import { NETWORK_TO_NAME_MAP } from './enums'
|
|||||||
|
|
||||||
export const getNetworkDisplayName = (key) => NETWORK_TO_NAME_MAP[key]
|
export const getNetworkDisplayName = (key) => NETWORK_TO_NAME_MAP[key]
|
||||||
|
|
||||||
export function formatTxMetaForRpcResult (txMeta) {
|
export function formatTxMetaForRpcResult(txMeta) {
|
||||||
return {
|
return {
|
||||||
'blockHash': txMeta.txReceipt ? txMeta.txReceipt.blockHash : null,
|
blockHash: txMeta.txReceipt ? txMeta.txReceipt.blockHash : null,
|
||||||
'blockNumber': txMeta.txReceipt ? txMeta.txReceipt.blockNumber : null,
|
blockNumber: txMeta.txReceipt ? txMeta.txReceipt.blockNumber : null,
|
||||||
'from': txMeta.txParams.from,
|
from: txMeta.txParams.from,
|
||||||
'gas': txMeta.txParams.gas,
|
gas: txMeta.txParams.gas,
|
||||||
'gasPrice': txMeta.txParams.gasPrice,
|
gasPrice: txMeta.txParams.gasPrice,
|
||||||
'hash': txMeta.hash,
|
hash: txMeta.hash,
|
||||||
'input': txMeta.txParams.data || '0x',
|
input: txMeta.txParams.data || '0x',
|
||||||
'nonce': txMeta.txParams.nonce,
|
nonce: txMeta.txParams.nonce,
|
||||||
'to': txMeta.txParams.to,
|
to: txMeta.txParams.to,
|
||||||
'transactionIndex': txMeta.txReceipt ? txMeta.txReceipt.transactionIndex : null,
|
transactionIndex: txMeta.txReceipt
|
||||||
'value': txMeta.txParams.value || '0x0',
|
? txMeta.txReceipt.transactionIndex
|
||||||
'v': txMeta.v,
|
: null,
|
||||||
'r': txMeta.r,
|
value: txMeta.txParams.value || '0x0',
|
||||||
's': txMeta.s,
|
v: txMeta.v,
|
||||||
|
r: txMeta.r,
|
||||||
|
s: txMeta.s,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,18 +17,17 @@ import log from 'loglevel'
|
|||||||
* state related to onboarding
|
* state related to onboarding
|
||||||
*/
|
*/
|
||||||
export default class OnboardingController {
|
export default class OnboardingController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new controller instance
|
* Creates a new controller instance
|
||||||
*
|
*
|
||||||
* @param {OnboardingOptions} [opts] Controller configuration parameters
|
* @param {OnboardingOptions} [opts] Controller configuration parameters
|
||||||
*/
|
*/
|
||||||
constructor (opts = {}) {
|
constructor(opts = {}) {
|
||||||
const initialTransientState = {
|
const initialTransientState = {
|
||||||
onboardingTabs: {},
|
onboardingTabs: {},
|
||||||
}
|
}
|
||||||
const initState = {
|
const initState = {
|
||||||
seedPhraseBackedUp: true,
|
seedPhraseBackedUp: null,
|
||||||
...opts.initState,
|
...opts.initState,
|
||||||
...initialTransientState,
|
...initialTransientState,
|
||||||
}
|
}
|
||||||
@ -46,7 +45,7 @@ export default class OnboardingController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setSeedPhraseBackedUp (newSeedPhraseBackUpState) {
|
setSeedPhraseBackedUp(newSeedPhraseBackUpState) {
|
||||||
this.store.updateState({
|
this.store.updateState({
|
||||||
seedPhraseBackedUp: newSeedPhraseBackUpState,
|
seedPhraseBackedUp: newSeedPhraseBackUpState,
|
||||||
})
|
})
|
||||||
@ -65,7 +64,9 @@ export default class OnboardingController {
|
|||||||
}
|
}
|
||||||
const onboardingTabs = { ...this.store.getState().onboardingTabs }
|
const onboardingTabs = { ...this.store.getState().onboardingTabs }
|
||||||
if (!onboardingTabs[location] || onboardingTabs[location] !== tabId) {
|
if (!onboardingTabs[location] || onboardingTabs[location] !== tabId) {
|
||||||
log.debug(`Registering onboarding tab at location '${location}' with tabId '${tabId}'`)
|
log.debug(
|
||||||
|
`Registering onboarding tab at location '${location}' with tabId '${tabId}'`,
|
||||||
|
)
|
||||||
onboardingTabs[location] = tabId
|
onboardingTabs[location] = tabId
|
||||||
this.store.updateState({ onboardingTabs })
|
this.store.updateState({ onboardingTabs })
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
export const WALLET_PREFIX = 'wallet_'
|
export const WALLET_PREFIX = 'wallet_'
|
||||||
|
|
||||||
export const HISTORY_STORE_KEY = 'permissionsHistory'
|
export const HISTORY_STORE_KEY = 'permissionsHistory'
|
||||||
@ -23,9 +22,7 @@ export const NOTIFICATION_NAMES = {
|
|||||||
accountsChanged: 'wallet_accountsChanged',
|
accountsChanged: 'wallet_accountsChanged',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LOG_IGNORE_METHODS = [
|
export const LOG_IGNORE_METHODS = ['wallet_sendDomainMetadata']
|
||||||
'wallet_sendDomainMetadata',
|
|
||||||
]
|
|
||||||
|
|
||||||
export const LOG_METHOD_TYPES = {
|
export const LOG_METHOD_TYPES = {
|
||||||
restricted: 'restricted',
|
restricted: 'restricted',
|
||||||
|
@ -24,8 +24,7 @@ import {
|
|||||||
} from './enums'
|
} from './enums'
|
||||||
|
|
||||||
export class PermissionsController {
|
export class PermissionsController {
|
||||||
|
constructor(
|
||||||
constructor (
|
|
||||||
{
|
{
|
||||||
getKeyringAccounts,
|
getKeyringAccounts,
|
||||||
getRestrictedMethods,
|
getRestrictedMethods,
|
||||||
@ -38,7 +37,6 @@ export class PermissionsController {
|
|||||||
restoredPermissions = {},
|
restoredPermissions = {},
|
||||||
restoredState = {},
|
restoredState = {},
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// additional top-level store key set in _initializeMetadataStore
|
// additional top-level store key set in _initializeMetadataStore
|
||||||
this.store = new ObservableStore({
|
this.store = new ObservableStore({
|
||||||
[LOG_STORE_KEY]: restoredState[LOG_STORE_KEY] || [],
|
[LOG_STORE_KEY]: restoredState[LOG_STORE_KEY] || [],
|
||||||
@ -75,8 +73,7 @@ export class PermissionsController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
createMiddleware ({ origin, extensionId }) {
|
createMiddleware({ origin, extensionId }) {
|
||||||
|
|
||||||
if (typeof origin !== 'string' || !origin.length) {
|
if (typeof origin !== 'string' || !origin.length) {
|
||||||
throw new Error('Must provide non-empty string origin.')
|
throw new Error('Must provide non-empty string origin.')
|
||||||
}
|
}
|
||||||
@ -91,20 +88,26 @@ export class PermissionsController {
|
|||||||
|
|
||||||
engine.push(this.permissionsLog.createMiddleware())
|
engine.push(this.permissionsLog.createMiddleware())
|
||||||
|
|
||||||
engine.push(createPermissionsMethodMiddleware({
|
engine.push(
|
||||||
addDomainMetadata: this.addDomainMetadata.bind(this),
|
createPermissionsMethodMiddleware({
|
||||||
getAccounts: this.getAccounts.bind(this, origin),
|
addDomainMetadata: this.addDomainMetadata.bind(this),
|
||||||
getUnlockPromise: () => this._getUnlockPromise(true),
|
getAccounts: this.getAccounts.bind(this, origin),
|
||||||
hasPermission: this.hasPermission.bind(this, origin),
|
getUnlockPromise: () => this._getUnlockPromise(true),
|
||||||
notifyAccountsChanged: this.notifyAccountsChanged.bind(this, origin),
|
hasPermission: this.hasPermission.bind(this, origin),
|
||||||
requestAccountsPermission: this._requestPermissions.bind(
|
notifyAccountsChanged: this.notifyAccountsChanged.bind(this, origin),
|
||||||
this, { origin }, { eth_accounts: {} },
|
requestAccountsPermission: this._requestPermissions.bind(
|
||||||
),
|
this,
|
||||||
}))
|
{ origin },
|
||||||
|
{ eth_accounts: {} },
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
engine.push(this.permissions.providerMiddlewareFunction.bind(
|
engine.push(
|
||||||
this.permissions, { origin },
|
this.permissions.providerMiddlewareFunction.bind(this.permissions, {
|
||||||
))
|
origin,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
return asMiddleware(engine)
|
return asMiddleware(engine)
|
||||||
}
|
}
|
||||||
@ -114,7 +117,7 @@ export class PermissionsController {
|
|||||||
* @param {string} origin - The requesting origin
|
* @param {string} origin - The requesting origin
|
||||||
* @returns {Promise<string>} The permissions request ID
|
* @returns {Promise<string>} The permissions request ID
|
||||||
*/
|
*/
|
||||||
async requestAccountsPermissionWithId (origin) {
|
async requestAccountsPermissionWithId(origin) {
|
||||||
const id = nanoid()
|
const id = nanoid()
|
||||||
this._requestPermissions({ origin }, { eth_accounts: {} }, id)
|
this._requestPermissions({ origin }, { eth_accounts: {} }, id)
|
||||||
return id
|
return id
|
||||||
@ -127,16 +130,19 @@ export class PermissionsController {
|
|||||||
*
|
*
|
||||||
* @param {string} origin - The origin string.
|
* @param {string} origin - The origin string.
|
||||||
*/
|
*/
|
||||||
getAccounts (origin) {
|
getAccounts(origin) {
|
||||||
return new Promise((resolve, _) => {
|
return new Promise((resolve, _) => {
|
||||||
|
|
||||||
const req = { method: 'eth_accounts' }
|
const req = { method: 'eth_accounts' }
|
||||||
const res = {}
|
const res = {}
|
||||||
this.permissions.providerMiddlewareFunction(
|
this.permissions.providerMiddlewareFunction(
|
||||||
{ origin }, req, res, () => undefined, _end,
|
{ origin },
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
() => undefined,
|
||||||
|
_end,
|
||||||
)
|
)
|
||||||
|
|
||||||
function _end () {
|
function _end() {
|
||||||
if (res.error || !Array.isArray(res.result)) {
|
if (res.error || !Array.isArray(res.result)) {
|
||||||
resolve([])
|
resolve([])
|
||||||
} else {
|
} else {
|
||||||
@ -153,7 +159,7 @@ export class PermissionsController {
|
|||||||
* @param {string} permission - The permission to check for.
|
* @param {string} permission - The permission to check for.
|
||||||
* @returns {boolean} Whether the origin has the permission.
|
* @returns {boolean} Whether the origin has the permission.
|
||||||
*/
|
*/
|
||||||
hasPermission (origin, permission) {
|
hasPermission(origin, permission) {
|
||||||
return Boolean(this.permissions.getPermission(origin, permission))
|
return Boolean(this.permissions.getPermission(origin, permission))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +168,7 @@ export class PermissionsController {
|
|||||||
*
|
*
|
||||||
* @returns {Object} identities
|
* @returns {Object} identities
|
||||||
*/
|
*/
|
||||||
_getIdentities () {
|
_getIdentities() {
|
||||||
return this.preferences.getState().identities
|
return this.preferences.getState().identities
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,9 +181,8 @@ export class PermissionsController {
|
|||||||
* @returns {Promise<IOcapLdCapability[]>} A Promise that resolves with the
|
* @returns {Promise<IOcapLdCapability[]>} A Promise that resolves with the
|
||||||
* approved permissions, or rejects with an error.
|
* approved permissions, or rejects with an error.
|
||||||
*/
|
*/
|
||||||
_requestPermissions (domain, permissions, id) {
|
_requestPermissions(domain, permissions, id) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
// rpc-cap assigns an id to the request if there is none, as expected by
|
// rpc-cap assigns an id to the request if there is none, as expected by
|
||||||
// requestUserApproval below
|
// requestUserApproval below
|
||||||
const req = {
|
const req = {
|
||||||
@ -188,10 +193,14 @@ export class PermissionsController {
|
|||||||
const res = {}
|
const res = {}
|
||||||
|
|
||||||
this.permissions.providerMiddlewareFunction(
|
this.permissions.providerMiddlewareFunction(
|
||||||
domain, req, res, () => undefined, _end,
|
domain,
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
() => undefined,
|
||||||
|
_end,
|
||||||
)
|
)
|
||||||
|
|
||||||
function _end (_err) {
|
function _end(_err) {
|
||||||
const err = _err || res.error
|
const err = _err || res.error
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err)
|
reject(err)
|
||||||
@ -211,8 +220,7 @@ export class PermissionsController {
|
|||||||
* @param {Object} approved - The request object approved by the user
|
* @param {Object} approved - The request object approved by the user
|
||||||
* @param {Array} accounts - The accounts to expose, if any
|
* @param {Array} accounts - The accounts to expose, if any
|
||||||
*/
|
*/
|
||||||
async approvePermissionsRequest (approved, accounts) {
|
async approvePermissionsRequest(approved, accounts) {
|
||||||
|
|
||||||
const { id } = approved.metadata
|
const { id } = approved.metadata
|
||||||
const approval = this.pendingApprovals.get(id)
|
const approval = this.pendingApprovals.get(id)
|
||||||
|
|
||||||
@ -222,28 +230,29 @@ export class PermissionsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if (Object.keys(approved.permissions).length === 0) {
|
if (Object.keys(approved.permissions).length === 0) {
|
||||||
|
approval.reject(
|
||||||
approval.reject(ethErrors.rpc.invalidRequest({
|
ethErrors.rpc.invalidRequest({
|
||||||
message: 'Must request at least one permission.',
|
message: 'Must request at least one permission.',
|
||||||
}))
|
}),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// attempt to finalize the request and resolve it,
|
// attempt to finalize the request and resolve it,
|
||||||
// settings caveats as necessary
|
// settings caveats as necessary
|
||||||
approved.permissions = await this.finalizePermissionsRequest(
|
approved.permissions = await this.finalizePermissionsRequest(
|
||||||
approved.permissions, accounts,
|
approved.permissions,
|
||||||
|
accounts,
|
||||||
)
|
)
|
||||||
approval.resolve(approved.permissions)
|
approval.resolve(approved.permissions)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
||||||
// if finalization fails, reject the request
|
// if finalization fails, reject the request
|
||||||
approval.reject(ethErrors.rpc.invalidRequest({
|
approval.reject(
|
||||||
message: err.message, data: err,
|
ethErrors.rpc.invalidRequest({
|
||||||
}))
|
message: err.message,
|
||||||
|
data: err,
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
this._removePendingApproval(id)
|
this._removePendingApproval(id)
|
||||||
@ -256,7 +265,7 @@ export class PermissionsController {
|
|||||||
*
|
*
|
||||||
* @param {string} id - The id of the request rejected by the user
|
* @param {string} id - The id of the request rejected by the user
|
||||||
*/
|
*/
|
||||||
async rejectPermissionsRequest (id) {
|
async rejectPermissionsRequest(id) {
|
||||||
const approval = this.pendingApprovals.get(id)
|
const approval = this.pendingApprovals.get(id)
|
||||||
|
|
||||||
if (!approval) {
|
if (!approval) {
|
||||||
@ -277,8 +286,7 @@ export class PermissionsController {
|
|||||||
* @param {string} origin - The origin to expose the account to.
|
* @param {string} origin - The origin to expose the account to.
|
||||||
* @param {string} account - The new account to expose.
|
* @param {string} account - The new account to expose.
|
||||||
*/
|
*/
|
||||||
async addPermittedAccount (origin, account) {
|
async addPermittedAccount(origin, account) {
|
||||||
|
|
||||||
const domains = this.permissions.getDomains()
|
const domains = this.permissions.getDomains()
|
||||||
if (!domains[origin]) {
|
if (!domains[origin]) {
|
||||||
throw new Error('Unrecognized domain')
|
throw new Error('Unrecognized domain')
|
||||||
@ -294,7 +302,8 @@ export class PermissionsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.permissions.updateCaveatFor(
|
this.permissions.updateCaveatFor(
|
||||||
origin, 'eth_accounts',
|
origin,
|
||||||
|
'eth_accounts',
|
||||||
CAVEAT_NAMES.exposedAccounts,
|
CAVEAT_NAMES.exposedAccounts,
|
||||||
[...oldPermittedAccounts, account],
|
[...oldPermittedAccounts, account],
|
||||||
)
|
)
|
||||||
@ -315,8 +324,7 @@ export class PermissionsController {
|
|||||||
* @param {string} origin - The origin to remove the account from.
|
* @param {string} origin - The origin to remove the account from.
|
||||||
* @param {string} account - The account to remove.
|
* @param {string} account - The account to remove.
|
||||||
*/
|
*/
|
||||||
async removePermittedAccount (origin, account) {
|
async removePermittedAccount(origin, account) {
|
||||||
|
|
||||||
const domains = this.permissions.getDomains()
|
const domains = this.permissions.getDomains()
|
||||||
if (!domains[origin]) {
|
if (!domains[origin]) {
|
||||||
throw new Error('Unrecognized domain')
|
throw new Error('Unrecognized domain')
|
||||||
@ -331,15 +339,16 @@ export class PermissionsController {
|
|||||||
throw new Error('Account is not permitted for origin')
|
throw new Error('Account is not permitted for origin')
|
||||||
}
|
}
|
||||||
|
|
||||||
let newPermittedAccounts = oldPermittedAccounts
|
let newPermittedAccounts = oldPermittedAccounts.filter(
|
||||||
.filter((acc) => acc !== account)
|
(acc) => acc !== account,
|
||||||
|
)
|
||||||
|
|
||||||
if (newPermittedAccounts.length === 0) {
|
if (newPermittedAccounts.length === 0) {
|
||||||
this.removePermissionsFor({ [origin]: ['eth_accounts'] })
|
this.removePermissionsFor({ [origin]: ['eth_accounts'] })
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
this.permissions.updateCaveatFor(
|
this.permissions.updateCaveatFor(
|
||||||
origin, 'eth_accounts',
|
origin,
|
||||||
|
'eth_accounts',
|
||||||
CAVEAT_NAMES.exposedAccounts,
|
CAVEAT_NAMES.exposedAccounts,
|
||||||
newPermittedAccounts,
|
newPermittedAccounts,
|
||||||
)
|
)
|
||||||
@ -358,14 +367,19 @@ export class PermissionsController {
|
|||||||
*
|
*
|
||||||
* @param {string} account - The account to remove.
|
* @param {string} account - The account to remove.
|
||||||
*/
|
*/
|
||||||
async removeAllAccountPermissions (account) {
|
async removeAllAccountPermissions(account) {
|
||||||
this.validatePermittedAccounts([account])
|
this.validatePermittedAccounts([account])
|
||||||
|
|
||||||
const domains = this.permissions.getDomains()
|
const domains = this.permissions.getDomains()
|
||||||
const connectedOrigins = Object.keys(domains)
|
const connectedOrigins = Object.keys(domains).filter((origin) =>
|
||||||
.filter((origin) => this._getPermittedAccounts(origin).includes(account))
|
this._getPermittedAccounts(origin).includes(account),
|
||||||
|
)
|
||||||
|
|
||||||
await Promise.all(connectedOrigins.map((origin) => this.removePermittedAccount(origin, account)))
|
await Promise.all(
|
||||||
|
connectedOrigins.map((origin) =>
|
||||||
|
this.removePermittedAccount(origin, account),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -378,15 +392,13 @@ export class PermissionsController {
|
|||||||
* @param {string[]} requestedAccounts - The accounts to expose, if any.
|
* @param {string[]} requestedAccounts - The accounts to expose, if any.
|
||||||
* @returns {Object} The finalized permissions request object.
|
* @returns {Object} The finalized permissions request object.
|
||||||
*/
|
*/
|
||||||
async finalizePermissionsRequest (requestedPermissions, requestedAccounts) {
|
async finalizePermissionsRequest(requestedPermissions, requestedAccounts) {
|
||||||
|
|
||||||
const finalizedPermissions = cloneDeep(requestedPermissions)
|
const finalizedPermissions = cloneDeep(requestedPermissions)
|
||||||
const finalizedAccounts = cloneDeep(requestedAccounts)
|
const finalizedAccounts = cloneDeep(requestedAccounts)
|
||||||
|
|
||||||
const { eth_accounts: ethAccounts } = finalizedPermissions
|
const { eth_accounts: ethAccounts } = finalizedPermissions
|
||||||
|
|
||||||
if (ethAccounts) {
|
if (ethAccounts) {
|
||||||
|
|
||||||
this.validatePermittedAccounts(finalizedAccounts)
|
this.validatePermittedAccounts(finalizedAccounts)
|
||||||
|
|
||||||
if (!ethAccounts.caveats) {
|
if (!ethAccounts.caveats) {
|
||||||
@ -394,9 +406,11 @@ export class PermissionsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// caveat names are unique, and we will only construct this caveat here
|
// caveat names are unique, and we will only construct this caveat here
|
||||||
ethAccounts.caveats = ethAccounts.caveats.filter((c) => (
|
ethAccounts.caveats = ethAccounts.caveats.filter(
|
||||||
c.name !== CAVEAT_NAMES.exposedAccounts && c.name !== CAVEAT_NAMES.primaryAccountOnly
|
(c) =>
|
||||||
))
|
c.name !== CAVEAT_NAMES.exposedAccounts &&
|
||||||
|
c.name !== CAVEAT_NAMES.primaryAccountOnly,
|
||||||
|
)
|
||||||
|
|
||||||
ethAccounts.caveats.push({
|
ethAccounts.caveats.push({
|
||||||
type: CAVEAT_TYPES.limitResponseLength,
|
type: CAVEAT_TYPES.limitResponseLength,
|
||||||
@ -420,7 +434,7 @@ export class PermissionsController {
|
|||||||
*
|
*
|
||||||
* @param {string[]} accounts - An array of addresses.
|
* @param {string[]} accounts - An array of addresses.
|
||||||
*/
|
*/
|
||||||
validatePermittedAccounts (accounts) {
|
validatePermittedAccounts(accounts) {
|
||||||
if (!Array.isArray(accounts) || accounts.length === 0) {
|
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).')
|
||||||
}
|
}
|
||||||
@ -441,8 +455,7 @@ export class PermissionsController {
|
|||||||
* @param {string} origin - The origin of the domain to notify.
|
* @param {string} origin - The origin of the domain to notify.
|
||||||
* @param {Array<string>} newAccounts - The currently permitted accounts.
|
* @param {Array<string>} newAccounts - The currently permitted accounts.
|
||||||
*/
|
*/
|
||||||
notifyAccountsChanged (origin, newAccounts) {
|
notifyAccountsChanged(origin, newAccounts) {
|
||||||
|
|
||||||
if (typeof origin !== 'string' || !origin) {
|
if (typeof origin !== 'string' || !origin) {
|
||||||
throw new Error(`Invalid origin: '${origin}'`)
|
throw new Error(`Invalid origin: '${origin}'`)
|
||||||
}
|
}
|
||||||
@ -459,9 +472,7 @@ export class PermissionsController {
|
|||||||
// if the accounts changed from the perspective of the dapp,
|
// if the accounts changed from the perspective of the dapp,
|
||||||
// update "last seen" time for the origin and account(s)
|
// update "last seen" time for the origin and account(s)
|
||||||
// exception: no accounts -> no times to update
|
// exception: no accounts -> no times to update
|
||||||
this.permissionsLog.updateAccountsHistory(
|
this.permissionsLog.updateAccountsHistory(origin, newAccounts)
|
||||||
origin, newAccounts,
|
|
||||||
)
|
|
||||||
|
|
||||||
// NOTE:
|
// NOTE:
|
||||||
// we don't check for accounts changing in the notifyAllDomains case,
|
// we don't check for accounts changing in the notifyAllDomains case,
|
||||||
@ -475,17 +486,14 @@ export class PermissionsController {
|
|||||||
* Should only be called after confirming that the permissions exist, to
|
* Should only be called after confirming that the permissions exist, to
|
||||||
* avoid sending unnecessary notifications.
|
* avoid sending unnecessary notifications.
|
||||||
*
|
*
|
||||||
* @param {Object} domains { origin: [permissions] } - The map of domain
|
* @param {Object} domains - The map of domain origins to permissions to remove.
|
||||||
* origins to permissions to remove.
|
* e.g. { origin: [permissions] }
|
||||||
*/
|
*/
|
||||||
removePermissionsFor (domains) {
|
removePermissionsFor(domains) {
|
||||||
|
|
||||||
Object.entries(domains).forEach(([origin, perms]) => {
|
Object.entries(domains).forEach(([origin, perms]) => {
|
||||||
|
|
||||||
this.permissions.removePermissionsFor(
|
this.permissions.removePermissionsFor(
|
||||||
origin,
|
origin,
|
||||||
perms.map((methodName) => {
|
perms.map((methodName) => {
|
||||||
|
|
||||||
if (methodName === 'eth_accounts') {
|
if (methodName === 'eth_accounts') {
|
||||||
this.notifyAccountsChanged(origin, [])
|
this.notifyAccountsChanged(origin, [])
|
||||||
}
|
}
|
||||||
@ -499,7 +507,7 @@ export class PermissionsController {
|
|||||||
/**
|
/**
|
||||||
* Removes all known domains and their related permissions.
|
* Removes all known domains and their related permissions.
|
||||||
*/
|
*/
|
||||||
clearPermissions () {
|
clearPermissions() {
|
||||||
this.permissions.clearDomains()
|
this.permissions.clearDomains()
|
||||||
this._notifyAllDomains({
|
this._notifyAllDomains({
|
||||||
method: NOTIFICATION_NAMES.accountsChanged,
|
method: NOTIFICATION_NAMES.accountsChanged,
|
||||||
@ -517,8 +525,7 @@ export class PermissionsController {
|
|||||||
* @param {string} origin - The origin whose domain metadata to store.
|
* @param {string} origin - The origin whose domain metadata to store.
|
||||||
* @param {Object} metadata - The domain's metadata that will be stored.
|
* @param {Object} metadata - The domain's metadata that will be stored.
|
||||||
*/
|
*/
|
||||||
addDomainMetadata (origin, metadata) {
|
addDomainMetadata(origin, metadata) {
|
||||||
|
|
||||||
const oldMetadataState = this.store.getState()[METADATA_STORE_KEY]
|
const oldMetadataState = this.store.getState()[METADATA_STORE_KEY]
|
||||||
const newMetadataState = { ...oldMetadataState }
|
const newMetadataState = { ...oldMetadataState }
|
||||||
|
|
||||||
@ -541,7 +548,10 @@ export class PermissionsController {
|
|||||||
lastUpdated: Date.now(),
|
lastUpdated: Date.now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!newMetadataState[origin].extensionId && !newMetadataState[origin].host) {
|
if (
|
||||||
|
!newMetadataState[origin].extensionId &&
|
||||||
|
!newMetadataState[origin].host
|
||||||
|
) {
|
||||||
newMetadataState[origin].host = new URL(origin).host
|
newMetadataState[origin].host = new URL(origin).host
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -557,8 +567,7 @@ export class PermissionsController {
|
|||||||
*
|
*
|
||||||
* @param {Object} restoredState - The restored permissions controller state.
|
* @param {Object} restoredState - The restored permissions controller state.
|
||||||
*/
|
*/
|
||||||
_initializeMetadataStore (restoredState) {
|
_initializeMetadataStore(restoredState) {
|
||||||
|
|
||||||
const metadataState = restoredState[METADATA_STORE_KEY] || {}
|
const metadataState = restoredState[METADATA_STORE_KEY] || {}
|
||||||
const newMetadataState = this._trimDomainMetadata(metadataState)
|
const newMetadataState = this._trimDomainMetadata(metadataState)
|
||||||
|
|
||||||
@ -574,8 +583,7 @@ export class PermissionsController {
|
|||||||
* @param {Object} metadataState - The metadata store state object to trim.
|
* @param {Object} metadataState - The metadata store state object to trim.
|
||||||
* @returns {Object} The new metadata state object.
|
* @returns {Object} The new metadata state object.
|
||||||
*/
|
*/
|
||||||
_trimDomainMetadata (metadataState) {
|
_trimDomainMetadata(metadataState) {
|
||||||
|
|
||||||
const newMetadataState = { ...metadataState }
|
const newMetadataState = { ...metadataState }
|
||||||
const origins = Object.keys(metadataState)
|
const origins = Object.keys(metadataState)
|
||||||
const permissionsDomains = this.permissions.getDomains()
|
const permissionsDomains = this.permissions.getDomains()
|
||||||
@ -593,7 +601,7 @@ export class PermissionsController {
|
|||||||
* Replaces the existing domain metadata with the passed-in object.
|
* Replaces the existing domain metadata with the passed-in object.
|
||||||
* @param {Object} newMetadataState - The new metadata to set.
|
* @param {Object} newMetadataState - The new metadata to set.
|
||||||
*/
|
*/
|
||||||
_setDomainMetadata (newMetadataState) {
|
_setDomainMetadata(newMetadataState) {
|
||||||
this.store.updateState({ [METADATA_STORE_KEY]: newMetadataState })
|
this.store.updateState({ [METADATA_STORE_KEY]: newMetadataState })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -603,11 +611,10 @@ export class PermissionsController {
|
|||||||
* @param {string} origin - The origin to obtain permitted accounts for
|
* @param {string} origin - The origin to obtain permitted accounts for
|
||||||
* @returns {Array<string>|null} The list of permitted accounts
|
* @returns {Array<string>|null} The list of permitted accounts
|
||||||
*/
|
*/
|
||||||
_getPermittedAccounts (origin) {
|
_getPermittedAccounts(origin) {
|
||||||
const permittedAccounts = this.permissions
|
const permittedAccounts = this.permissions
|
||||||
.getPermission(origin, 'eth_accounts')
|
.getPermission(origin, 'eth_accounts')
|
||||||
?.caveats
|
?.caveats?.find((caveat) => caveat.name === CAVEAT_NAMES.exposedAccounts)
|
||||||
?.find((caveat) => caveat.name === CAVEAT_NAMES.exposedAccounts)
|
|
||||||
?.value
|
?.value
|
||||||
|
|
||||||
return permittedAccounts || null
|
return permittedAccounts || null
|
||||||
@ -622,8 +629,7 @@ export class PermissionsController {
|
|||||||
*
|
*
|
||||||
* @param {string} account - The newly selected account's address.
|
* @param {string} account - The newly selected account's address.
|
||||||
*/
|
*/
|
||||||
async _handleAccountSelected (account) {
|
async _handleAccountSelected(account) {
|
||||||
|
|
||||||
if (typeof account !== 'string') {
|
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.')
|
||||||
}
|
}
|
||||||
@ -631,20 +637,20 @@ export class PermissionsController {
|
|||||||
const domains = this.permissions.getDomains() || {}
|
const domains = this.permissions.getDomains() || {}
|
||||||
const connectedDomains = Object.entries(domains)
|
const connectedDomains = Object.entries(domains)
|
||||||
.filter(([_, { permissions }]) => {
|
.filter(([_, { permissions }]) => {
|
||||||
const ethAccounts = permissions.find((permission) => permission.parentCapability === 'eth_accounts')
|
const ethAccounts = permissions.find(
|
||||||
const exposedAccounts = ethAccounts
|
(permission) => permission.parentCapability === 'eth_accounts',
|
||||||
?.caveats
|
)
|
||||||
.find((caveat) => caveat.name === 'exposedAccounts')
|
const exposedAccounts = ethAccounts?.caveats.find(
|
||||||
?.value
|
(caveat) => caveat.name === 'exposedAccounts',
|
||||||
|
)?.value
|
||||||
return exposedAccounts?.includes(account)
|
return exposedAccounts?.includes(account)
|
||||||
})
|
})
|
||||||
.map(([domain]) => domain)
|
.map(([domain]) => domain)
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
connectedDomains
|
connectedDomains.map((origin) =>
|
||||||
.map(
|
this._handleConnectedAccountSelected(origin),
|
||||||
(origin) => this._handleConnectedAccountSelected(origin),
|
),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -656,7 +662,7 @@ export class PermissionsController {
|
|||||||
*
|
*
|
||||||
* @param {string} origin - The origin
|
* @param {string} origin - The origin
|
||||||
*/
|
*/
|
||||||
async _handleConnectedAccountSelected (origin) {
|
async _handleConnectedAccountSelected(origin) {
|
||||||
const permittedAccounts = await this.getAccounts(origin)
|
const permittedAccounts = await this.getAccounts(origin)
|
||||||
|
|
||||||
this.notifyAccountsChanged(origin, permittedAccounts)
|
this.notifyAccountsChanged(origin, permittedAccounts)
|
||||||
@ -669,8 +675,7 @@ export class PermissionsController {
|
|||||||
* @param {Function} resolve - The function resolving the pending approval Promise.
|
* @param {Function} resolve - The function resolving the pending approval Promise.
|
||||||
* @param {Function} reject - The function rejecting the pending approval Promise.
|
* @param {Function} reject - The function rejecting the pending approval Promise.
|
||||||
*/
|
*/
|
||||||
_addPendingApproval (id, origin, resolve, reject) {
|
_addPendingApproval(id, origin, resolve, reject) {
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.pendingApprovalOrigins.has(origin) ||
|
this.pendingApprovalOrigins.has(origin) ||
|
||||||
this.pendingApprovals.has(id)
|
this.pendingApprovals.has(id)
|
||||||
@ -688,7 +693,7 @@ export class PermissionsController {
|
|||||||
* Removes the pending approval with the given id.
|
* Removes the pending approval with the given id.
|
||||||
* @param {string} id - The id of the pending approval to remove.
|
* @param {string} id - The id of the pending approval to remove.
|
||||||
*/
|
*/
|
||||||
_removePendingApproval (id) {
|
_removePendingApproval(id) {
|
||||||
const { origin } = this.pendingApprovals.get(id)
|
const { origin } = this.pendingApprovals.get(id)
|
||||||
this.pendingApprovalOrigins.delete(origin)
|
this.pendingApprovalOrigins.delete(origin)
|
||||||
this.pendingApprovals.delete(id)
|
this.pendingApprovals.delete(id)
|
||||||
@ -698,51 +703,54 @@ export class PermissionsController {
|
|||||||
* A convenience method for retrieving a login object
|
* A convenience method for retrieving a login object
|
||||||
* or creating a new one if needed.
|
* or creating a new one if needed.
|
||||||
*
|
*
|
||||||
* @param {string} origin = The origin string representing the domain.
|
* @param {string} origin - The origin string representing the domain.
|
||||||
*/
|
*/
|
||||||
_initializePermissions (restoredState) {
|
_initializePermissions(restoredState) {
|
||||||
|
|
||||||
// these permission requests are almost certainly stale
|
// these permission requests are almost certainly stale
|
||||||
const initState = { ...restoredState, permissionsRequests: [] }
|
const initState = { ...restoredState, permissionsRequests: [] }
|
||||||
|
|
||||||
this.permissions = new RpcCap({
|
this.permissions = new RpcCap(
|
||||||
|
{
|
||||||
|
// Supports passthrough methods:
|
||||||
|
safeMethods: SAFE_METHODS,
|
||||||
|
|
||||||
// Supports passthrough methods:
|
// optional prefix for internal methods
|
||||||
safeMethods: SAFE_METHODS,
|
methodPrefix: WALLET_PREFIX,
|
||||||
|
|
||||||
// optional prefix for internal methods
|
restrictedMethods: this._restrictedMethods,
|
||||||
methodPrefix: WALLET_PREFIX,
|
|
||||||
|
|
||||||
restrictedMethods: this._restrictedMethods,
|
/**
|
||||||
|
* A promise-returning callback used to determine whether to approve
|
||||||
|
* permissions requests or not.
|
||||||
|
*
|
||||||
|
* Currently only returns a boolean, but eventually should return any
|
||||||
|
* specific parameters or amendments to the permissions.
|
||||||
|
*
|
||||||
|
* @param {string} req - The internal rpc-cap user request object.
|
||||||
|
*/
|
||||||
|
requestUserApproval: async (req) => {
|
||||||
|
const {
|
||||||
|
metadata: { id, origin },
|
||||||
|
} = req
|
||||||
|
|
||||||
/**
|
if (this.pendingApprovalOrigins.has(origin)) {
|
||||||
* A promise-returning callback used to determine whether to approve
|
throw ethErrors.rpc.resourceUnavailable(
|
||||||
* permissions requests or not.
|
'Permissions request already pending; please wait.',
|
||||||
*
|
)
|
||||||
* Currently only returns a boolean, but eventually should return any
|
}
|
||||||
* specific parameters or amendments to the permissions.
|
|
||||||
*
|
|
||||||
* @param {string} req - The internal rpc-cap user request object.
|
|
||||||
*/
|
|
||||||
requestUserApproval: async (req) => {
|
|
||||||
const { metadata: { id, origin } } = req
|
|
||||||
|
|
||||||
if (this.pendingApprovalOrigins.has(origin)) {
|
this._showPermissionRequest()
|
||||||
throw ethErrors.rpc.resourceUnavailable(
|
|
||||||
'Permissions request already pending; please wait.',
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
this._showPermissionRequest()
|
return new Promise((resolve, reject) => {
|
||||||
|
this._addPendingApproval(id, origin, resolve, reject)
|
||||||
return new Promise((resolve, reject) => {
|
})
|
||||||
this._addPendingApproval(id, origin, resolve, reject)
|
},
|
||||||
})
|
|
||||||
},
|
},
|
||||||
}, initState)
|
initState,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addInternalMethodPrefix (method) {
|
export function addInternalMethodPrefix(method) {
|
||||||
return WALLET_PREFIX + method
|
return WALLET_PREFIX + method
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,7 @@ import {
|
|||||||
* and permissions-related methods.
|
* and permissions-related methods.
|
||||||
*/
|
*/
|
||||||
export default class PermissionsLogController {
|
export default class PermissionsLogController {
|
||||||
|
constructor({ restrictedMethods, store }) {
|
||||||
constructor ({ restrictedMethods, store }) {
|
|
||||||
this.restrictedMethods = restrictedMethods
|
this.restrictedMethods = restrictedMethods
|
||||||
this.store = store
|
this.store = store
|
||||||
}
|
}
|
||||||
@ -25,7 +24,7 @@ export default class PermissionsLogController {
|
|||||||
*
|
*
|
||||||
* @returns {Array<Object>} The activity log.
|
* @returns {Array<Object>} The activity log.
|
||||||
*/
|
*/
|
||||||
getActivityLog () {
|
getActivityLog() {
|
||||||
return this.store.getState()[LOG_STORE_KEY] || []
|
return this.store.getState()[LOG_STORE_KEY] || []
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,7 +33,7 @@ export default class PermissionsLogController {
|
|||||||
*
|
*
|
||||||
* @param {Array<Object>} logs - The new activity log array.
|
* @param {Array<Object>} logs - The new activity log array.
|
||||||
*/
|
*/
|
||||||
updateActivityLog (logs) {
|
updateActivityLog(logs) {
|
||||||
this.store.updateState({ [LOG_STORE_KEY]: logs })
|
this.store.updateState({ [LOG_STORE_KEY]: logs })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,7 +42,7 @@ export default class PermissionsLogController {
|
|||||||
*
|
*
|
||||||
* @returns {Object} The permissions history log.
|
* @returns {Object} The permissions history log.
|
||||||
*/
|
*/
|
||||||
getHistory () {
|
getHistory() {
|
||||||
return this.store.getState()[HISTORY_STORE_KEY] || {}
|
return this.store.getState()[HISTORY_STORE_KEY] || {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +51,7 @@ export default class PermissionsLogController {
|
|||||||
*
|
*
|
||||||
* @param {Object} history - The new permissions history log object.
|
* @param {Object} history - The new permissions history log object.
|
||||||
*/
|
*/
|
||||||
updateHistory (history) {
|
updateHistory(history) {
|
||||||
this.store.updateState({ [HISTORY_STORE_KEY]: history })
|
this.store.updateState({ [HISTORY_STORE_KEY]: history })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,8 +62,7 @@ export default class PermissionsLogController {
|
|||||||
* @param {string} origin - The origin that the accounts are exposed to.
|
* @param {string} origin - The origin that the accounts are exposed to.
|
||||||
* @param {Array<string>} accounts - The accounts.
|
* @param {Array<string>} accounts - The accounts.
|
||||||
*/
|
*/
|
||||||
updateAccountsHistory (origin, accounts) {
|
updateAccountsHistory(origin, accounts) {
|
||||||
|
|
||||||
if (accounts.length === 0) {
|
if (accounts.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -88,9 +86,8 @@ export default class PermissionsLogController {
|
|||||||
*
|
*
|
||||||
* @returns {JsonRpcEngineMiddleware} The permissions log middleware.
|
* @returns {JsonRpcEngineMiddleware} The permissions log middleware.
|
||||||
*/
|
*/
|
||||||
createMiddleware () {
|
createMiddleware() {
|
||||||
return (req, res, next, _end) => {
|
return (req, res, next, _end) => {
|
||||||
|
|
||||||
let activityEntry, requestedMethods
|
let activityEntry, requestedMethods
|
||||||
const { origin, method } = req
|
const { origin, method } = req
|
||||||
const isInternal = method.startsWith(WALLET_PREFIX)
|
const isInternal = method.startsWith(WALLET_PREFIX)
|
||||||
@ -100,7 +97,6 @@ export default class PermissionsLogController {
|
|||||||
!LOG_IGNORE_METHODS.includes(method) &&
|
!LOG_IGNORE_METHODS.includes(method) &&
|
||||||
(isInternal || this.restrictedMethods.includes(method))
|
(isInternal || this.restrictedMethods.includes(method))
|
||||||
) {
|
) {
|
||||||
|
|
||||||
activityEntry = this.logRequest(req, isInternal)
|
activityEntry = this.logRequest(req, isInternal)
|
||||||
|
|
||||||
if (method === `${WALLET_PREFIX}requestPermissions`) {
|
if (method === `${WALLET_PREFIX}requestPermissions`) {
|
||||||
@ -109,7 +105,6 @@ export default class PermissionsLogController {
|
|||||||
requestedMethods = this.getRequestedMethods(req)
|
requestedMethods = this.getRequestedMethods(req)
|
||||||
}
|
}
|
||||||
} else if (method === 'eth_requestAccounts') {
|
} else if (method === 'eth_requestAccounts') {
|
||||||
|
|
||||||
// eth_requestAccounts is a special case; we need to extract the accounts
|
// eth_requestAccounts is a special case; we need to extract the accounts
|
||||||
// from it
|
// from it
|
||||||
activityEntry = this.logRequest(req, isInternal)
|
activityEntry = this.logRequest(req, isInternal)
|
||||||
@ -122,7 +117,6 @@ export default class PermissionsLogController {
|
|||||||
|
|
||||||
// call next with a return handler for capturing the response
|
// call next with a return handler for capturing the response
|
||||||
next((cb) => {
|
next((cb) => {
|
||||||
|
|
||||||
const time = Date.now()
|
const time = Date.now()
|
||||||
this.logResponse(activityEntry, res, time)
|
this.logResponse(activityEntry, res, time)
|
||||||
|
|
||||||
@ -130,7 +124,10 @@ export default class PermissionsLogController {
|
|||||||
// any permissions or accounts changes will be recorded on the response,
|
// any permissions or accounts changes will be recorded on the response,
|
||||||
// so we only log permissions history here
|
// so we only log permissions history here
|
||||||
this.logPermissionsHistory(
|
this.logPermissionsHistory(
|
||||||
requestedMethods, origin, res.result, time,
|
requestedMethods,
|
||||||
|
origin,
|
||||||
|
res.result,
|
||||||
|
time,
|
||||||
method === 'eth_requestAccounts',
|
method === 'eth_requestAccounts',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -145,13 +142,13 @@ export default class PermissionsLogController {
|
|||||||
* @param {Object} request - The request object.
|
* @param {Object} request - The request object.
|
||||||
* @param {boolean} isInternal - Whether the request is internal.
|
* @param {boolean} isInternal - Whether the request is internal.
|
||||||
*/
|
*/
|
||||||
logRequest (request, isInternal) {
|
logRequest(request, isInternal) {
|
||||||
const activityEntry = {
|
const activityEntry = {
|
||||||
id: request.id,
|
id: request.id,
|
||||||
method: request.method,
|
method: request.method,
|
||||||
methodType: (
|
methodType: isInternal
|
||||||
isInternal ? LOG_METHOD_TYPES.internal : LOG_METHOD_TYPES.restricted
|
? LOG_METHOD_TYPES.internal
|
||||||
),
|
: LOG_METHOD_TYPES.restricted,
|
||||||
origin: request.origin,
|
origin: request.origin,
|
||||||
request: cloneDeep(request),
|
request: cloneDeep(request),
|
||||||
requestTime: Date.now(),
|
requestTime: Date.now(),
|
||||||
@ -171,8 +168,7 @@ export default class PermissionsLogController {
|
|||||||
* @param {Object} response - The response object.
|
* @param {Object} response - The response object.
|
||||||
* @param {number} time - Output from Date.now()
|
* @param {number} time - Output from Date.now()
|
||||||
*/
|
*/
|
||||||
logResponse (entry, response, time) {
|
logResponse(entry, response, time) {
|
||||||
|
|
||||||
if (!entry || !response) {
|
if (!entry || !response) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -188,8 +184,7 @@ export default class PermissionsLogController {
|
|||||||
*
|
*
|
||||||
* @param {Object} entry - The activity log entry.
|
* @param {Object} entry - The activity log entry.
|
||||||
*/
|
*/
|
||||||
commitNewActivity (entry) {
|
commitNewActivity(entry) {
|
||||||
|
|
||||||
const logs = this.getActivityLog()
|
const logs = this.getActivityLog()
|
||||||
|
|
||||||
// add new entry to end of log
|
// add new entry to end of log
|
||||||
@ -212,32 +207,31 @@ export default class PermissionsLogController {
|
|||||||
* @param {string} time - The time of the request, i.e. Date.now().
|
* @param {string} time - The time of the request, i.e. Date.now().
|
||||||
* @param {boolean} isEthRequestAccounts - Whether the permissions request was 'eth_requestAccounts'.
|
* @param {boolean} isEthRequestAccounts - Whether the permissions request was 'eth_requestAccounts'.
|
||||||
*/
|
*/
|
||||||
logPermissionsHistory (
|
logPermissionsHistory(
|
||||||
requestedMethods, origin, result,
|
requestedMethods,
|
||||||
time, isEthRequestAccounts,
|
origin,
|
||||||
|
result,
|
||||||
|
time,
|
||||||
|
isEthRequestAccounts,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
let accounts, newEntries
|
let accounts, newEntries
|
||||||
|
|
||||||
if (isEthRequestAccounts) {
|
if (isEthRequestAccounts) {
|
||||||
|
|
||||||
accounts = result
|
accounts = result
|
||||||
const accountToTimeMap = getAccountToTimeMap(accounts, time)
|
const accountToTimeMap = getAccountToTimeMap(accounts, time)
|
||||||
|
|
||||||
newEntries = {
|
newEntries = {
|
||||||
'eth_accounts': {
|
eth_accounts: {
|
||||||
accounts: accountToTimeMap,
|
accounts: accountToTimeMap,
|
||||||
lastApproved: time,
|
lastApproved: time,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Records new "lastApproved" times for the granted permissions, if any.
|
// Records new "lastApproved" times for the granted permissions, if any.
|
||||||
// Special handling for eth_accounts, in order to record the time the
|
// Special handling for eth_accounts, in order to record the time the
|
||||||
// accounts were last seen or approved by the origin.
|
// accounts were last seen or approved by the origin.
|
||||||
newEntries = result
|
newEntries = result
|
||||||
.map((perm) => {
|
.map((perm) => {
|
||||||
|
|
||||||
if (perm.parentCapability === 'eth_accounts') {
|
if (perm.parentCapability === 'eth_accounts') {
|
||||||
accounts = this.getAccountsFromPermission(perm)
|
accounts = this.getAccountsFromPermission(perm)
|
||||||
}
|
}
|
||||||
@ -245,13 +239,10 @@ export default class PermissionsLogController {
|
|||||||
return perm.parentCapability
|
return perm.parentCapability
|
||||||
})
|
})
|
||||||
.reduce((acc, method) => {
|
.reduce((acc, method) => {
|
||||||
|
|
||||||
// all approved permissions will be included in the response,
|
// all approved permissions will be included in the response,
|
||||||
// not just the newly requested ones
|
// not just the newly requested ones
|
||||||
if (requestedMethods.includes(method)) {
|
if (requestedMethods.includes(method)) {
|
||||||
|
|
||||||
if (method === 'eth_accounts') {
|
if (method === 'eth_accounts') {
|
||||||
|
|
||||||
const accountToTimeMap = getAccountToTimeMap(accounts, time)
|
const accountToTimeMap = getAccountToTimeMap(accounts, time)
|
||||||
|
|
||||||
acc[method] = {
|
acc[method] = {
|
||||||
@ -280,8 +271,7 @@ export default class PermissionsLogController {
|
|||||||
* @param {string} origin - The requesting origin.
|
* @param {string} origin - The requesting origin.
|
||||||
* @param {Object} newEntries - The new entries to commit.
|
* @param {Object} newEntries - The new entries to commit.
|
||||||
*/
|
*/
|
||||||
commitNewHistory (origin, newEntries) {
|
commitNewHistory(origin, newEntries) {
|
||||||
|
|
||||||
// a simple merge updates most permissions
|
// a simple merge updates most permissions
|
||||||
const history = this.getHistory()
|
const history = this.getHistory()
|
||||||
const newOriginHistory = {
|
const newOriginHistory = {
|
||||||
@ -291,19 +281,16 @@ export default class PermissionsLogController {
|
|||||||
|
|
||||||
// eth_accounts requires special handling, because of information
|
// eth_accounts requires special handling, because of information
|
||||||
// we store about the accounts
|
// we store about the accounts
|
||||||
const existingEthAccountsEntry = (
|
const existingEthAccountsEntry =
|
||||||
history[origin] && history[origin].eth_accounts
|
history[origin] && history[origin].eth_accounts
|
||||||
)
|
|
||||||
const newEthAccountsEntry = newEntries.eth_accounts
|
const newEthAccountsEntry = newEntries.eth_accounts
|
||||||
|
|
||||||
if (existingEthAccountsEntry && newEthAccountsEntry) {
|
if (existingEthAccountsEntry && newEthAccountsEntry) {
|
||||||
|
|
||||||
// we may intend to update just the accounts, not the permission
|
// we may intend to update just the accounts, not the permission
|
||||||
// itself
|
// itself
|
||||||
const lastApproved = (
|
const lastApproved =
|
||||||
newEthAccountsEntry.lastApproved ||
|
newEthAccountsEntry.lastApproved ||
|
||||||
existingEthAccountsEntry.lastApproved
|
existingEthAccountsEntry.lastApproved
|
||||||
)
|
|
||||||
|
|
||||||
// merge old and new eth_accounts history entries
|
// merge old and new eth_accounts history entries
|
||||||
newOriginHistory.eth_accounts = {
|
newOriginHistory.eth_accounts = {
|
||||||
@ -326,7 +313,7 @@ export default class PermissionsLogController {
|
|||||||
* @param {Object} request - The request object.
|
* @param {Object} request - The request object.
|
||||||
* @returns {Array<string>} The names of the requested permissions.
|
* @returns {Array<string>} The names of the requested permissions.
|
||||||
*/
|
*/
|
||||||
getRequestedMethods (request) {
|
getRequestedMethods(request) {
|
||||||
if (
|
if (
|
||||||
!request.params ||
|
!request.params ||
|
||||||
!request.params[0] ||
|
!request.params[0] ||
|
||||||
@ -345,20 +332,17 @@ export default class PermissionsLogController {
|
|||||||
* @param {Object} perm - The permissions object.
|
* @param {Object} perm - The permissions object.
|
||||||
* @returns {Array<string>} The permitted accounts.
|
* @returns {Array<string>} The permitted accounts.
|
||||||
*/
|
*/
|
||||||
getAccountsFromPermission (perm) {
|
getAccountsFromPermission(perm) {
|
||||||
|
|
||||||
if (perm.parentCapability !== 'eth_accounts' || !perm.caveats) {
|
if (perm.parentCapability !== 'eth_accounts' || !perm.caveats) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const accounts = new Set()
|
const accounts = new Set()
|
||||||
for (const caveat of perm.caveats) {
|
for (const caveat of perm.caveats) {
|
||||||
|
|
||||||
if (
|
if (
|
||||||
caveat.name === CAVEAT_NAMES.exposedAccounts &&
|
caveat.name === CAVEAT_NAMES.exposedAccounts &&
|
||||||
Array.isArray(caveat.value)
|
Array.isArray(caveat.value)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
for (const value of caveat.value) {
|
for (const value of caveat.value) {
|
||||||
accounts.add(value)
|
accounts.add(value)
|
||||||
}
|
}
|
||||||
@ -377,8 +361,6 @@ export default class PermissionsLogController {
|
|||||||
* @param {number} time - A time, e.g. Date.now().
|
* @param {number} time - A time, e.g. Date.now().
|
||||||
* @returns {Object} A string:number map of addresses to time.
|
* @returns {Object} A string:number map of addresses to time.
|
||||||
*/
|
*/
|
||||||
function getAccountToTimeMap (accounts, time) {
|
function getAccountToTimeMap(accounts, time) {
|
||||||
return accounts.reduce(
|
return accounts.reduce((acc, account) => ({ ...acc, [account]: time }), {})
|
||||||
(acc, account) => ({ ...acc, [account]: time }), {},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import { ethErrors } from 'eth-json-rpc-errors'
|
|||||||
/**
|
/**
|
||||||
* Create middleware for handling certain methods and preprocessing permissions requests.
|
* Create middleware for handling certain methods and preprocessing permissions requests.
|
||||||
*/
|
*/
|
||||||
export default function createPermissionsMethodMiddleware ({
|
export default function createPermissionsMethodMiddleware({
|
||||||
addDomainMetadata,
|
addDomainMetadata,
|
||||||
getAccounts,
|
getAccounts,
|
||||||
getUnlockPromise,
|
getUnlockPromise,
|
||||||
@ -12,26 +12,21 @@ export default function createPermissionsMethodMiddleware ({
|
|||||||
notifyAccountsChanged,
|
notifyAccountsChanged,
|
||||||
requestAccountsPermission,
|
requestAccountsPermission,
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
let isProcessingRequestAccounts = false
|
let isProcessingRequestAccounts = false
|
||||||
|
|
||||||
return createAsyncMiddleware(async (req, res, next) => {
|
return createAsyncMiddleware(async (req, res, next) => {
|
||||||
|
|
||||||
let responseHandler
|
let responseHandler
|
||||||
|
|
||||||
switch (req.method) {
|
switch (req.method) {
|
||||||
|
|
||||||
// Intercepting eth_accounts requests for backwards compatibility:
|
// Intercepting eth_accounts requests for backwards compatibility:
|
||||||
// The getAccounts call below wraps the rpc-cap middleware, and returns
|
// The getAccounts call below wraps the rpc-cap middleware, and returns
|
||||||
// an empty array in case of errors (such as 4100:unauthorized)
|
// an empty array in case of errors (such as 4100:unauthorized)
|
||||||
case 'eth_accounts': {
|
case 'eth_accounts': {
|
||||||
|
|
||||||
res.result = await getAccounts()
|
res.result = await getAccounts()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'eth_requestAccounts': {
|
case 'eth_requestAccounts': {
|
||||||
|
|
||||||
if (isProcessingRequestAccounts) {
|
if (isProcessingRequestAccounts) {
|
||||||
res.error = ethErrors.rpc.resourceUnavailable(
|
res.error = ethErrors.rpc.resourceUnavailable(
|
||||||
'Already processing eth_requestAccounts. Please wait.',
|
'Already processing eth_requestAccounts. Please wait.',
|
||||||
@ -79,7 +74,6 @@ export default function createPermissionsMethodMiddleware ({
|
|||||||
// custom method for getting metadata from the requesting domain,
|
// custom method for getting metadata from the requesting domain,
|
||||||
// sent automatically by the inpage provider when it's initialized
|
// sent automatically by the inpage provider when it's initialized
|
||||||
case 'wallet_sendDomainMetadata': {
|
case 'wallet_sendDomainMetadata': {
|
||||||
|
|
||||||
if (typeof req.domainMetadata?.name === 'string') {
|
if (typeof req.domainMetadata?.name === 'string') {
|
||||||
addDomainMetadata(req.origin, req.domainMetadata)
|
addDomainMetadata(req.origin, req.domainMetadata)
|
||||||
}
|
}
|
||||||
@ -89,11 +83,8 @@ export default function createPermissionsMethodMiddleware ({
|
|||||||
|
|
||||||
// register return handler to send accountsChanged notification
|
// register return handler to send accountsChanged notification
|
||||||
case 'wallet_requestPermissions': {
|
case 'wallet_requestPermissions': {
|
||||||
|
|
||||||
if ('eth_accounts' in req.params?.[0]) {
|
if ('eth_accounts' in req.params?.[0]) {
|
||||||
|
|
||||||
responseHandler = async () => {
|
responseHandler = async () => {
|
||||||
|
|
||||||
if (Array.isArray(res.result)) {
|
if (Array.isArray(res.result)) {
|
||||||
for (const permission of res.result) {
|
for (const permission of res.result) {
|
||||||
if (permission.parentCapability === 'eth_accounts') {
|
if (permission.parentCapability === 'eth_accounts') {
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
export default function getRestrictedMethods ({ getIdentities, getKeyringAccounts }) {
|
export default function getRestrictedMethods({
|
||||||
|
getIdentities,
|
||||||
|
getKeyringAccounts,
|
||||||
|
}) {
|
||||||
return {
|
return {
|
||||||
'eth_accounts': {
|
eth_accounts: {
|
||||||
method: async (_, res, __, end) => {
|
method: async (_, res, __, end) => {
|
||||||
try {
|
try {
|
||||||
const accounts = await getKeyringAccounts()
|
const accounts = await getKeyringAccounts()
|
||||||
@ -10,7 +13,10 @@ export default function getRestrictedMethods ({ getIdentities, getKeyringAccount
|
|||||||
throw new Error(`Missing identity for address ${firstAddress}`)
|
throw new Error(`Missing identity for address ${firstAddress}`)
|
||||||
} else if (!identities[secondAddress]) {
|
} 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) {
|
} else if (
|
||||||
|
identities[firstAddress].lastSelected ===
|
||||||
|
identities[secondAddress].lastSelected
|
||||||
|
) {
|
||||||
return 0
|
return 0
|
||||||
} else if (identities[firstAddress].lastSelected === undefined) {
|
} else if (identities[firstAddress].lastSelected === undefined) {
|
||||||
return 1
|
return 1
|
||||||
@ -18,7 +24,10 @@ export default function getRestrictedMethods ({ getIdentities, getKeyringAccount
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
return identities[secondAddress].lastSelected - identities[firstAddress].lastSelected
|
return (
|
||||||
|
identities[secondAddress].lastSelected -
|
||||||
|
identities[firstAddress].lastSelected
|
||||||
|
)
|
||||||
})
|
})
|
||||||
end()
|
end()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -9,28 +9,27 @@ import { addInternalMethodPrefix } from './permissions'
|
|||||||
import { NETWORK_TYPE_TO_ID_MAP } from './network/enums'
|
import { NETWORK_TYPE_TO_ID_MAP } from './network/enums'
|
||||||
|
|
||||||
export default class PreferencesController {
|
export default class PreferencesController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @typedef {Object} PreferencesController
|
* @typedef {Object} PreferencesController
|
||||||
* @param {Object} opts - Overrides the defaults for the initial state of this.store
|
* @param {Object} opts - Overrides the defaults for the initial state of this.store
|
||||||
* @property {object} store The stored object containing a users preferences, stored in local storage
|
* @property {Object} store The stored object containing a users preferences, stored in local storage
|
||||||
* @property {array} store.frequentRpcList A list of custom rpcs to provide the user
|
* @property {Array} store.frequentRpcList A list of custom rpcs to provide the user
|
||||||
* @property {array} store.tokens The tokens the user wants display in their token lists
|
* @property {Array} store.tokens The tokens the user wants display in their token lists
|
||||||
* @property {object} store.accountTokens The tokens stored per account and then per network type
|
* @property {Object} store.accountTokens The tokens stored per account and then per network type
|
||||||
* @property {object} store.assetImages Contains assets objects related to assets added
|
* @property {Object} store.assetImages Contains assets objects related to assets added
|
||||||
* @property {boolean} store.useBlockie The users preference for blockie identicons within the UI
|
* @property {boolean} store.useBlockie The users preference for blockie identicons within the UI
|
||||||
* @property {boolean} store.useNonceField The users preference for nonce field within the UI
|
* @property {boolean} store.useNonceField The users preference for nonce field within the UI
|
||||||
* @property {object} store.featureFlags A key-boolean map, where keys refer to features and booleans to whether the
|
* @property {Object} store.featureFlags A key-boolean map, where keys refer to features and booleans to whether the
|
||||||
* user wishes to see that feature.
|
* user wishes to see that feature.
|
||||||
*
|
*
|
||||||
* Feature flags can be set by the global function `setPreference(feature, enabled)`, and so should not expose any sensitive behavior.
|
* Feature flags can be set by the global function `setPreference(feature, enabled)`, and so should not expose any sensitive behavior.
|
||||||
* @property {object} store.knownMethodData Contains all data methods known by the user
|
* @property {Object} store.knownMethodData Contains all data methods known by the user
|
||||||
* @property {string} store.currentLocale The preferred language locale key
|
* @property {string} store.currentLocale The preferred language locale key
|
||||||
* @property {string} store.selectedAddress A hex string that matches the currently selected address in the app
|
* @property {string} store.selectedAddress A hex string that matches the currently selected address in the app
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
constructor (opts = {}) {
|
constructor(opts = {}) {
|
||||||
const initState = {
|
const initState = {
|
||||||
frequentRpcListDetail: [],
|
frequentRpcListDetail: [],
|
||||||
accountTokens: {},
|
accountTokens: {},
|
||||||
@ -66,7 +65,8 @@ export default class PreferencesController {
|
|||||||
metaMetricsSendCount: 0,
|
metaMetricsSendCount: 0,
|
||||||
|
|
||||||
// ENS decentralized website resolution
|
// ENS decentralized website resolution
|
||||||
ipfsGateway: 'dweb.link', ...opts.initState,
|
ipfsGateway: 'dweb.link',
|
||||||
|
...opts.initState,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.network = opts.network
|
this.network = opts.network
|
||||||
@ -86,7 +86,7 @@ export default class PreferencesController {
|
|||||||
* Sets the {@code forgottenPassword} state property
|
* Sets the {@code forgottenPassword} state property
|
||||||
* @param {boolean} forgottenPassword - whether or not the user has forgotten their password
|
* @param {boolean} forgottenPassword - whether or not the user has forgotten their password
|
||||||
*/
|
*/
|
||||||
setPasswordForgotten (forgottenPassword) {
|
setPasswordForgotten(forgottenPassword) {
|
||||||
this.store.updateState({ forgottenPassword })
|
this.store.updateState({ forgottenPassword })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ export default class PreferencesController {
|
|||||||
* @param {boolean} val - Whether or not the user prefers blockie indicators
|
* @param {boolean} val - Whether or not the user prefers blockie indicators
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
setUseBlockie (val) {
|
setUseBlockie(val) {
|
||||||
this.store.updateState({ useBlockie: val })
|
this.store.updateState({ useBlockie: val })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ export default class PreferencesController {
|
|||||||
* @param {boolean} val - Whether or not the user prefers to set nonce
|
* @param {boolean} val - Whether or not the user prefers to set nonce
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
setUseNonceField (val) {
|
setUseNonceField(val) {
|
||||||
this.store.updateState({ useNonceField: val })
|
this.store.updateState({ useNonceField: val })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ export default class PreferencesController {
|
|||||||
* @param {boolean} val - Whether or not the user prefers phishing domain protection
|
* @param {boolean} val - Whether or not the user prefers phishing domain protection
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
setUsePhishDetect (val) {
|
setUsePhishDetect(val) {
|
||||||
this.store.updateState({ usePhishDetect: val })
|
this.store.updateState({ usePhishDetect: val })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,14 +124,19 @@ export default class PreferencesController {
|
|||||||
* Setter for the `participateInMetaMetrics` property
|
* Setter for the `participateInMetaMetrics` property
|
||||||
*
|
*
|
||||||
* @param {boolean} bool - Whether or not the user wants to participate in MetaMetrics
|
* @param {boolean} bool - Whether or not the user wants to participate in MetaMetrics
|
||||||
* @returns {string|null} - the string of the new metametrics id, or null if not set
|
* @returns {string|null} the string of the new metametrics id, or null if not set
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
setParticipateInMetaMetrics (bool) {
|
setParticipateInMetaMetrics(bool) {
|
||||||
this.store.updateState({ participateInMetaMetrics: bool })
|
this.store.updateState({ participateInMetaMetrics: bool })
|
||||||
let metaMetricsId = null
|
let metaMetricsId = null
|
||||||
if (bool && !this.store.getState().metaMetricsId) {
|
if (bool && !this.store.getState().metaMetricsId) {
|
||||||
metaMetricsId = bufferToHex(sha3(String(Date.now()) + String(Math.round(Math.random() * Number.MAX_SAFE_INTEGER))))
|
metaMetricsId = bufferToHex(
|
||||||
|
sha3(
|
||||||
|
String(Date.now()) +
|
||||||
|
String(Math.round(Math.random() * Number.MAX_SAFE_INTEGER)),
|
||||||
|
),
|
||||||
|
)
|
||||||
this.store.updateState({ metaMetricsId })
|
this.store.updateState({ metaMetricsId })
|
||||||
} else if (bool === false) {
|
} else if (bool === false) {
|
||||||
this.store.updateState({ metaMetricsId })
|
this.store.updateState({ metaMetricsId })
|
||||||
@ -139,11 +144,11 @@ export default class PreferencesController {
|
|||||||
return metaMetricsId
|
return metaMetricsId
|
||||||
}
|
}
|
||||||
|
|
||||||
getParticipateInMetaMetrics () {
|
getParticipateInMetaMetrics() {
|
||||||
return this.store.getState().participateInMetaMetrics
|
return this.store.getState().participateInMetaMetrics
|
||||||
}
|
}
|
||||||
|
|
||||||
setMetaMetricsSendCount (val) {
|
setMetaMetricsSendCount(val) {
|
||||||
this.store.updateState({ metaMetricsSendCount: val })
|
this.store.updateState({ metaMetricsSendCount: val })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,19 +158,19 @@ export default class PreferencesController {
|
|||||||
* @param {string} type - Indicates the type of first time flow - create or import - the user wishes to follow
|
* @param {string} type - Indicates the type of first time flow - create or import - the user wishes to follow
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
setFirstTimeFlowType (type) {
|
setFirstTimeFlowType(type) {
|
||||||
this.store.updateState({ firstTimeFlowType: type })
|
this.store.updateState({ firstTimeFlowType: type })
|
||||||
}
|
}
|
||||||
|
|
||||||
getSuggestedTokens () {
|
getSuggestedTokens() {
|
||||||
return this.store.getState().suggestedTokens
|
return this.store.getState().suggestedTokens
|
||||||
}
|
}
|
||||||
|
|
||||||
getAssetImages () {
|
getAssetImages() {
|
||||||
return this.store.getState().assetImages
|
return this.store.getState().assetImages
|
||||||
}
|
}
|
||||||
|
|
||||||
addSuggestedERC20Asset (tokenOpts) {
|
addSuggestedERC20Asset(tokenOpts) {
|
||||||
this._validateERC20AssetParams(tokenOpts)
|
this._validateERC20AssetParams(tokenOpts)
|
||||||
const suggested = this.getSuggestedTokens()
|
const suggested = this.getSuggestedTokens()
|
||||||
const { rawAddress, symbol, decimals, image } = tokenOpts
|
const { rawAddress, symbol, decimals, image } = tokenOpts
|
||||||
@ -181,7 +186,7 @@ export default class PreferencesController {
|
|||||||
* @param {string} fourBytePrefix - Four-byte method signature
|
* @param {string} fourBytePrefix - Four-byte method signature
|
||||||
* @param {string} methodData - Corresponding data method
|
* @param {string} methodData - Corresponding data method
|
||||||
*/
|
*/
|
||||||
addKnownMethodData (fourBytePrefix, methodData) {
|
addKnownMethodData(fourBytePrefix, methodData) {
|
||||||
const { knownMethodData } = this.store.getState()
|
const { knownMethodData } = this.store.getState()
|
||||||
knownMethodData[fourBytePrefix] = methodData
|
knownMethodData[fourBytePrefix] = methodData
|
||||||
this.store.updateState({ knownMethodData })
|
this.store.updateState({ knownMethodData })
|
||||||
@ -190,12 +195,12 @@ export default class PreferencesController {
|
|||||||
/**
|
/**
|
||||||
* RPC engine middleware for requesting new asset added
|
* RPC engine middleware for requesting new asset added
|
||||||
*
|
*
|
||||||
* @param req
|
* @param {any} req
|
||||||
* @param res
|
* @param {any} res
|
||||||
* @param {Function} - next
|
* @param {Function} next
|
||||||
* @param {Function} - end
|
* @param {Function} end
|
||||||
*/
|
*/
|
||||||
async requestWatchAsset (req, res, next, end) {
|
async requestWatchAsset(req, res, next, end) {
|
||||||
if (
|
if (
|
||||||
req.method === 'metamask_watchAsset' ||
|
req.method === 'metamask_watchAsset' ||
|
||||||
req.method === addInternalMethodPrefix('watchAsset')
|
req.method === addInternalMethodPrefix('watchAsset')
|
||||||
@ -227,8 +232,10 @@ export default class PreferencesController {
|
|||||||
* @param {string} key - he preferred language locale key
|
* @param {string} key - he preferred language locale key
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
setCurrentLocale (key) {
|
setCurrentLocale(key) {
|
||||||
const textDirection = (['ar', 'dv', 'fa', 'he', 'ku'].includes(key)) ? 'rtl' : 'auto'
|
const textDirection = ['ar', 'dv', 'fa', 'he', 'ku'].includes(key)
|
||||||
|
? 'rtl'
|
||||||
|
: 'auto'
|
||||||
this.store.updateState({
|
this.store.updateState({
|
||||||
currentLocale: key,
|
currentLocale: key,
|
||||||
textDirection,
|
textDirection,
|
||||||
@ -243,7 +250,7 @@ export default class PreferencesController {
|
|||||||
* @param {string[]} addresses - An array of hex addresses
|
* @param {string[]} addresses - An array of hex addresses
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
setAddresses (addresses) {
|
setAddresses(addresses) {
|
||||||
const oldIdentities = this.store.getState().identities
|
const oldIdentities = this.store.getState().identities
|
||||||
const oldAccountTokens = this.store.getState().accountTokens
|
const oldAccountTokens = this.store.getState().accountTokens
|
||||||
|
|
||||||
@ -264,9 +271,9 @@ export default class PreferencesController {
|
|||||||
* Removes an address from state
|
* Removes an address from state
|
||||||
*
|
*
|
||||||
* @param {string} address - A hex address
|
* @param {string} address - A hex address
|
||||||
* @returns {string} - the address that was removed
|
* @returns {string} the address that was removed
|
||||||
*/
|
*/
|
||||||
removeAddress (address) {
|
removeAddress(address) {
|
||||||
const { identities } = this.store.getState()
|
const { identities } = this.store.getState()
|
||||||
const { accountTokens } = this.store.getState()
|
const { accountTokens } = this.store.getState()
|
||||||
if (!identities[address]) {
|
if (!identities[address]) {
|
||||||
@ -291,7 +298,7 @@ export default class PreferencesController {
|
|||||||
* @param {string[]} addresses - An array of hex addresses
|
* @param {string[]} addresses - An array of hex addresses
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
addAddresses (addresses) {
|
addAddresses(addresses) {
|
||||||
const { identities, accountTokens } = this.store.getState()
|
const { identities, accountTokens } = this.store.getState()
|
||||||
addresses.forEach((address) => {
|
addresses.forEach((address) => {
|
||||||
// skip if already exists
|
// skip if already exists
|
||||||
@ -312,10 +319,9 @@ export default class PreferencesController {
|
|||||||
* Removes any unknown identities, and returns the resulting selected address.
|
* Removes any unknown identities, and returns the resulting selected address.
|
||||||
*
|
*
|
||||||
* @param {Array<string>} addresses - known to the vault.
|
* @param {Array<string>} addresses - known to the vault.
|
||||||
* @returns {Promise<string>} - selectedAddress the selected address.
|
* @returns {Promise<string>} selectedAddress the selected address.
|
||||||
*/
|
*/
|
||||||
syncAddresses (addresses) {
|
syncAddresses(addresses) {
|
||||||
|
|
||||||
if (!Array.isArray(addresses) || addresses.length === 0) {
|
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.')
|
||||||
}
|
}
|
||||||
@ -332,7 +338,6 @@ export default class PreferencesController {
|
|||||||
|
|
||||||
// Identities are no longer present.
|
// Identities are no longer present.
|
||||||
if (Object.keys(newlyLost).length > 0) {
|
if (Object.keys(newlyLost).length > 0) {
|
||||||
|
|
||||||
// store lost accounts
|
// store lost accounts
|
||||||
Object.keys(newlyLost).forEach((key) => {
|
Object.keys(newlyLost).forEach((key) => {
|
||||||
lostIdentities[key] = newlyLost[key]
|
lostIdentities[key] = newlyLost[key]
|
||||||
@ -353,7 +358,7 @@ export default class PreferencesController {
|
|||||||
return selected
|
return selected
|
||||||
}
|
}
|
||||||
|
|
||||||
removeSuggestedTokens () {
|
removeSuggestedTokens() {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.store.updateState({ suggestedTokens: {} })
|
this.store.updateState({ suggestedTokens: {} })
|
||||||
resolve({})
|
resolve({})
|
||||||
@ -364,10 +369,10 @@ export default class PreferencesController {
|
|||||||
* Setter for the `selectedAddress` property
|
* Setter for the `selectedAddress` property
|
||||||
*
|
*
|
||||||
* @param {string} _address - A new hex address for an account
|
* @param {string} _address - A new hex address for an account
|
||||||
* @returns {Promise<void>} - Promise resolves with tokens
|
* @returns {Promise<void>} Promise resolves with tokens
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
setSelectedAddress (_address) {
|
setSelectedAddress(_address) {
|
||||||
const address = normalizeAddress(_address)
|
const address = normalizeAddress(_address)
|
||||||
this._updateTokens(address)
|
this._updateTokens(address)
|
||||||
|
|
||||||
@ -385,10 +390,10 @@ export default class PreferencesController {
|
|||||||
/**
|
/**
|
||||||
* Getter for the `selectedAddress` property
|
* Getter for the `selectedAddress` property
|
||||||
*
|
*
|
||||||
* @returns {string} - The hex address for the currently selected account
|
* @returns {string} The hex address for the currently selected account
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
getSelectedAddress () {
|
getSelectedAddress() {
|
||||||
return this.store.getState().selectedAddress
|
return this.store.getState().selectedAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,11 +414,11 @@ export default class PreferencesController {
|
|||||||
*
|
*
|
||||||
* @param {string} rawAddress - Hex address of the token contract. May or may not be a checksum address.
|
* @param {string} rawAddress - Hex address of the token contract. May or may not be a checksum address.
|
||||||
* @param {string} symbol - The symbol of the token
|
* @param {string} symbol - The symbol of the token
|
||||||
* @param {number} decimals - The number of decimals the token uses.
|
* @param {number} decimals - The number of decimals the token uses.
|
||||||
* @returns {Promise<array>} - Promises the new array of AddedToken objects.
|
* @returns {Promise<array>} Promises the new array of AddedToken objects.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async addToken (rawAddress, symbol, decimals, image) {
|
async addToken(rawAddress, symbol, decimals, image) {
|
||||||
const address = normalizeAddress(rawAddress)
|
const address = normalizeAddress(rawAddress)
|
||||||
const newEntry = { address, symbol, decimals }
|
const newEntry = { address, symbol, decimals }
|
||||||
const { tokens } = this.store.getState()
|
const { tokens } = this.store.getState()
|
||||||
@ -437,10 +442,10 @@ export default class PreferencesController {
|
|||||||
* Removes a specified token from the tokens array.
|
* Removes a specified token from the tokens array.
|
||||||
*
|
*
|
||||||
* @param {string} rawAddress - Hex address of the token contract to remove.
|
* @param {string} rawAddress - Hex address of the token contract to remove.
|
||||||
* @returns {Promise<array>} - The new array of AddedToken objects
|
* @returns {Promise<array>} The new array of AddedToken objects
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
removeToken (rawAddress) {
|
removeToken(rawAddress) {
|
||||||
const { tokens } = this.store.getState()
|
const { tokens } = this.store.getState()
|
||||||
const assetImages = this.getAssetImages()
|
const assetImages = this.getAssetImages()
|
||||||
const updatedTokens = tokens.filter((token) => token.address !== rawAddress)
|
const updatedTokens = tokens.filter((token) => token.address !== rawAddress)
|
||||||
@ -452,10 +457,10 @@ export default class PreferencesController {
|
|||||||
/**
|
/**
|
||||||
* A getter for the `tokens` property
|
* A getter for the `tokens` property
|
||||||
*
|
*
|
||||||
* @returns {array} - The current array of AddedToken objects
|
* @returns {Array} The current array of AddedToken objects
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
getTokens () {
|
getTokens() {
|
||||||
return this.store.getState().tokens
|
return this.store.getState().tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -465,9 +470,11 @@ export default class PreferencesController {
|
|||||||
* @param {string} label - the custom label for the account
|
* @param {string} label - the custom label for the account
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
||||||
*/
|
*/
|
||||||
setAccountLabel (account, label) {
|
setAccountLabel(account, label) {
|
||||||
if (!account) {
|
if (!account) {
|
||||||
throw new Error(`setAccountLabel requires a valid address, got ${String(account)}`)
|
throw new Error(
|
||||||
|
`setAccountLabel requires a valid address, got ${String(account)}`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
const address = normalizeAddress(account)
|
const address = normalizeAddress(account)
|
||||||
const { identities } = this.store.getState()
|
const { identities } = this.store.getState()
|
||||||
@ -488,7 +495,7 @@ export default class PreferencesController {
|
|||||||
* @param {Object} [newRpcDetails.rpcPrefs] - Optional RPC preferences, such as the block explorer URL
|
* @param {Object} [newRpcDetails.rpcPrefs] - Optional RPC preferences, such as the block explorer URL
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async updateRpc (newRpcDetails) {
|
async updateRpc(newRpcDetails) {
|
||||||
const rpcList = this.getFrequentRpcListDetail()
|
const rpcList = this.getFrequentRpcListDetail()
|
||||||
const index = rpcList.findIndex((element) => {
|
const index = rpcList.findIndex((element) => {
|
||||||
return element.rpcUrl === newRpcDetails.rpcUrl
|
return element.rpcUrl === newRpcDetails.rpcUrl
|
||||||
@ -497,7 +504,6 @@ export default class PreferencesController {
|
|||||||
const rpcDetail = rpcList[index]
|
const rpcDetail = rpcList[index]
|
||||||
const updatedRpc = { ...rpcDetail, ...newRpcDetails }
|
const updatedRpc = { ...rpcDetail, ...newRpcDetails }
|
||||||
if (rpcDetail.chainId !== updatedRpc.chainId) {
|
if (rpcDetail.chainId !== updatedRpc.chainId) {
|
||||||
|
|
||||||
// When the chainId is changed, associated address book entries should
|
// When the chainId is changed, associated address book entries should
|
||||||
// also be migrated. The address book entries are keyed by the `network` state,
|
// 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
|
// which for custom networks is the chainId with a fallback to the networkId
|
||||||
@ -506,13 +512,17 @@ export default class PreferencesController {
|
|||||||
let addressBookKey = rpcDetail.chainId
|
let addressBookKey = rpcDetail.chainId
|
||||||
if (!addressBookKey) {
|
if (!addressBookKey) {
|
||||||
// We need to find the networkId to determine what these addresses were keyed by
|
// We need to find the networkId to determine what these addresses were keyed by
|
||||||
const provider = new ethers.providers.JsonRpcProvider(rpcDetail.rpcUrl)
|
const provider = new ethers.providers.JsonRpcProvider(
|
||||||
|
rpcDetail.rpcUrl,
|
||||||
|
)
|
||||||
try {
|
try {
|
||||||
addressBookKey = await provider.send('net_version')
|
addressBookKey = await provider.send('net_version')
|
||||||
assert(typeof addressBookKey === 'string')
|
assert(typeof addressBookKey === 'string')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.debug(error)
|
log.debug(error)
|
||||||
log.warn(`Failed to get networkId from ${rpcDetail.rpcUrl}; skipping address book migration`)
|
log.warn(
|
||||||
|
`Failed to get networkId from ${rpcDetail.rpcUrl}; skipping address book migration`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -521,10 +531,12 @@ export default class PreferencesController {
|
|||||||
// on both networks, since we don't know which network each contact is intended for.
|
// 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)
|
const builtInProviderNetworkIds = Object.values(
|
||||||
.map((ids) => ids.networkId)
|
NETWORK_TYPE_TO_ID_MAP,
|
||||||
const otherRpcEntries = rpcList
|
).map((ids) => ids.networkId)
|
||||||
.filter((entry) => entry.rpcUrl !== newRpcDetails.rpcUrl)
|
const otherRpcEntries = rpcList.filter(
|
||||||
|
(entry) => entry.rpcUrl !== newRpcDetails.rpcUrl,
|
||||||
|
)
|
||||||
if (
|
if (
|
||||||
builtInProviderNetworkIds.includes(addressBookKey) ||
|
builtInProviderNetworkIds.includes(addressBookKey) ||
|
||||||
otherRpcEntries.some((entry) => entry.chainId === addressBookKey)
|
otherRpcEntries.some((entry) => entry.chainId === addressBookKey)
|
||||||
@ -532,7 +544,11 @@ export default class PreferencesController {
|
|||||||
duplicate = true
|
duplicate = true
|
||||||
}
|
}
|
||||||
|
|
||||||
this.migrateAddressBookState(addressBookKey, updatedRpc.chainId, duplicate)
|
this.migrateAddressBookState(
|
||||||
|
addressBookKey,
|
||||||
|
updatedRpc.chainId,
|
||||||
|
duplicate,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
rpcList[index] = updatedRpc
|
rpcList[index] = updatedRpc
|
||||||
this.store.updateState({ frequentRpcListDetail: rpcList })
|
this.store.updateState({ frequentRpcListDetail: rpcList })
|
||||||
@ -552,7 +568,13 @@ export default class PreferencesController {
|
|||||||
* @param {Object} [rpcPrefs] - Optional RPC preferences, such as the block explorer URL
|
* @param {Object} [rpcPrefs] - Optional RPC preferences, such as the block explorer URL
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
addToFrequentRpcList (rpcUrl, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) {
|
addToFrequentRpcList(
|
||||||
|
rpcUrl,
|
||||||
|
chainId,
|
||||||
|
ticker = 'ETH',
|
||||||
|
nickname = '',
|
||||||
|
rpcPrefs = {},
|
||||||
|
) {
|
||||||
const rpcList = this.getFrequentRpcListDetail()
|
const rpcList = this.getFrequentRpcListDetail()
|
||||||
|
|
||||||
const index = rpcList.findIndex((element) => {
|
const index = rpcList.findIndex((element) => {
|
||||||
@ -574,10 +596,10 @@ export default class PreferencesController {
|
|||||||
* Removes custom RPC url from state.
|
* Removes custom RPC url from state.
|
||||||
*
|
*
|
||||||
* @param {string} url - The RPC url to remove from frequentRpcList.
|
* @param {string} url - The RPC url to remove from frequentRpcList.
|
||||||
* @returns {Promise<array>} - Promise resolving to updated frequentRpcList.
|
* @returns {Promise<array>} Promise resolving to updated frequentRpcList.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
removeFromFrequentRpcList (url) {
|
removeFromFrequentRpcList(url) {
|
||||||
const rpcList = this.getFrequentRpcListDetail()
|
const rpcList = this.getFrequentRpcListDetail()
|
||||||
const index = rpcList.findIndex((element) => {
|
const index = rpcList.findIndex((element) => {
|
||||||
return element.rpcUrl === url
|
return element.rpcUrl === url
|
||||||
@ -592,10 +614,10 @@ export default class PreferencesController {
|
|||||||
/**
|
/**
|
||||||
* Getter for the `frequentRpcListDetail` property.
|
* Getter for the `frequentRpcListDetail` property.
|
||||||
*
|
*
|
||||||
* @returns {array<array>} - An array of rpc urls.
|
* @returns {array<array>} An array of rpc urls.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
getFrequentRpcListDetail () {
|
getFrequentRpcListDetail() {
|
||||||
return this.store.getState().frequentRpcListDetail
|
return this.store.getState().frequentRpcListDetail
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -604,10 +626,10 @@ export default class PreferencesController {
|
|||||||
*
|
*
|
||||||
* @param {string} feature - A key that corresponds to a UI feature.
|
* @param {string} feature - A key that corresponds to a UI feature.
|
||||||
* @param {boolean} activated - Indicates whether or not the UI feature should be displayed
|
* @param {boolean} activated - Indicates whether or not the UI feature should be displayed
|
||||||
* @returns {Promise<object>} - Promises a new object; the updated featureFlags object.
|
* @returns {Promise<object>} Promises a new object; the updated featureFlags object.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
setFeatureFlag (feature, activated) {
|
setFeatureFlag(feature, activated) {
|
||||||
const currentFeatureFlags = this.store.getState().featureFlags
|
const currentFeatureFlags = this.store.getState().featureFlags
|
||||||
const updatedFeatureFlags = {
|
const updatedFeatureFlags = {
|
||||||
...currentFeatureFlags,
|
...currentFeatureFlags,
|
||||||
@ -624,9 +646,9 @@ export default class PreferencesController {
|
|||||||
* found in the settings page.
|
* found in the settings page.
|
||||||
* @param {string} preference - The preference to enable or disable.
|
* @param {string} preference - The preference to enable or disable.
|
||||||
* @param {boolean} value - Indicates whether or not the preference should be enabled or disabled.
|
* @param {boolean} value - Indicates whether or not the preference should be enabled or disabled.
|
||||||
* @returns {Promise<object>} - Promises a new object; the updated preferences object.
|
* @returns {Promise<object>} Promises a new object; the updated preferences object.
|
||||||
*/
|
*/
|
||||||
setPreference (preference, value) {
|
setPreference(preference, value) {
|
||||||
const currentPreferences = this.getPreferences()
|
const currentPreferences = this.getPreferences()
|
||||||
const updatedPreferences = {
|
const updatedPreferences = {
|
||||||
...currentPreferences,
|
...currentPreferences,
|
||||||
@ -639,9 +661,9 @@ export default class PreferencesController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A getter for the `preferences` property
|
* A getter for the `preferences` property
|
||||||
* @returns {Object} - A key-boolean map of user-selected preferences.
|
* @returns {Object} A key-boolean map of user-selected preferences.
|
||||||
*/
|
*/
|
||||||
getPreferences () {
|
getPreferences() {
|
||||||
return this.store.getState().preferences
|
return this.store.getState().preferences
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -649,25 +671,25 @@ export default class PreferencesController {
|
|||||||
* Sets the completedOnboarding state to true, indicating that the user has completed the
|
* Sets the completedOnboarding state to true, indicating that the user has completed the
|
||||||
* onboarding process.
|
* onboarding process.
|
||||||
*/
|
*/
|
||||||
completeOnboarding () {
|
completeOnboarding() {
|
||||||
this.store.updateState({ completedOnboarding: true })
|
this.store.updateState({ completedOnboarding: true })
|
||||||
return Promise.resolve(true)
|
return Promise.resolve(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A getter for the `ipfsGateway` property
|
* A getter for the `ipfsGateway` property
|
||||||
* @returns {string} - The current IPFS gateway domain
|
* @returns {string} The current IPFS gateway domain
|
||||||
*/
|
*/
|
||||||
getIpfsGateway () {
|
getIpfsGateway() {
|
||||||
return this.store.getState().ipfsGateway
|
return this.store.getState().ipfsGateway
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A setter for the `ipfsGateway` property
|
* A setter for the `ipfsGateway` property
|
||||||
* @param {string} domain - The new IPFS gateway domain
|
* @param {string} domain - The new IPFS gateway domain
|
||||||
* @returns {Promise<string>} - A promise of the update IPFS gateway domain
|
* @returns {Promise<string>} A promise of the update IPFS gateway domain
|
||||||
*/
|
*/
|
||||||
setIpfsGateway (domain) {
|
setIpfsGateway(domain) {
|
||||||
this.store.updateState({ ipfsGateway: domain })
|
this.store.updateState({ ipfsGateway: domain })
|
||||||
return Promise.resolve(domain)
|
return Promise.resolve(domain)
|
||||||
}
|
}
|
||||||
@ -681,7 +703,7 @@ export default class PreferencesController {
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_subscribeProviderType () {
|
_subscribeProviderType() {
|
||||||
this.network.providerStore.subscribe(() => {
|
this.network.providerStore.subscribe(() => {
|
||||||
const { tokens } = this._getTokenRelatedStates()
|
const { tokens } = this._getTokenRelatedStates()
|
||||||
this.store.updateState({ tokens })
|
this.store.updateState({ tokens })
|
||||||
@ -691,11 +713,15 @@ export default class PreferencesController {
|
|||||||
/**
|
/**
|
||||||
* Updates `accountTokens` and `tokens` of current account and network according to it.
|
* Updates `accountTokens` and `tokens` of current account and network according to it.
|
||||||
*
|
*
|
||||||
* @param {array} tokens - Array of tokens to be updated.
|
* @param {Array} tokens - Array of tokens to be updated.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_updateAccountTokens (tokens, assetImages) {
|
_updateAccountTokens(tokens, assetImages) {
|
||||||
const { accountTokens, providerType, selectedAddress } = this._getTokenRelatedStates()
|
const {
|
||||||
|
accountTokens,
|
||||||
|
providerType,
|
||||||
|
selectedAddress,
|
||||||
|
} = this._getTokenRelatedStates()
|
||||||
accountTokens[selectedAddress][providerType] = tokens
|
accountTokens[selectedAddress][providerType] = tokens
|
||||||
this.store.updateState({ accountTokens, tokens, assetImages })
|
this.store.updateState({ accountTokens, tokens, assetImages })
|
||||||
}
|
}
|
||||||
@ -706,7 +732,7 @@ export default class PreferencesController {
|
|||||||
* @param {string} selectedAddress - Account address to be updated with.
|
* @param {string} selectedAddress - Account address to be updated with.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_updateTokens (selectedAddress) {
|
_updateTokens(selectedAddress) {
|
||||||
const { tokens } = this._getTokenRelatedStates(selectedAddress)
|
const { tokens } = this._getTokenRelatedStates(selectedAddress)
|
||||||
this.store.updateState({ tokens })
|
this.store.updateState({ tokens })
|
||||||
}
|
}
|
||||||
@ -714,11 +740,11 @@ export default class PreferencesController {
|
|||||||
/**
|
/**
|
||||||
* A getter for `tokens` and `accountTokens` related states.
|
* A getter for `tokens` and `accountTokens` related states.
|
||||||
*
|
*
|
||||||
* @param {string} [selectedAddress] A new hex address for an account
|
* @param {string} [selectedAddress] - A new hex address for an account
|
||||||
* @returns {Object.<array, object, string, string>} - States to interact with tokens in `accountTokens`
|
* @returns {Object.<array, object, string, string>} States to interact with tokens in `accountTokens`
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_getTokenRelatedStates (selectedAddress) {
|
_getTokenRelatedStates(selectedAddress) {
|
||||||
const { accountTokens } = this.store.getState()
|
const { accountTokens } = this.store.getState()
|
||||||
if (!selectedAddress) {
|
if (!selectedAddress) {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
@ -738,11 +764,11 @@ export default class PreferencesController {
|
|||||||
/**
|
/**
|
||||||
* Handle the suggestion of an ERC20 asset through `watchAsset`
|
* Handle the suggestion of an ERC20 asset through `watchAsset`
|
||||||
* *
|
* *
|
||||||
* @param {Promise} promise - Promise according to addition of ERC20 token
|
* @param {Object} tokenMetadata - Token metadata
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async _handleWatchAssetERC20 (options) {
|
async _handleWatchAssetERC20(tokenMetadata) {
|
||||||
const { address, symbol, decimals, image } = options
|
const { address, symbol, decimals, image } = tokenMetadata
|
||||||
const rawAddress = address
|
const rawAddress = address
|
||||||
try {
|
try {
|
||||||
this._validateERC20AssetParams({ rawAddress, symbol, decimals })
|
this._validateERC20AssetParams({ rawAddress, symbol, decimals })
|
||||||
@ -752,7 +778,9 @@ export default class PreferencesController {
|
|||||||
const tokenOpts = { rawAddress, decimals, symbol, image }
|
const tokenOpts = { rawAddress, decimals, symbol, image }
|
||||||
this.addSuggestedERC20Asset(tokenOpts)
|
this.addSuggestedERC20Asset(tokenOpts)
|
||||||
return this.openPopup().then(() => {
|
return this.openPopup().then(() => {
|
||||||
const tokenAddresses = this.getTokens().filter((token) => token.address === normalizeAddress(rawAddress))
|
const tokenAddresses = this.getTokens().filter(
|
||||||
|
(token) => token.address === normalizeAddress(rawAddress),
|
||||||
|
)
|
||||||
return tokenAddresses.length > 0
|
return tokenAddresses.length > 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -765,17 +793,21 @@ export default class PreferencesController {
|
|||||||
* doesn't fulfill requirements
|
* doesn't fulfill requirements
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_validateERC20AssetParams (opts) {
|
_validateERC20AssetParams(opts) {
|
||||||
const { rawAddress, symbol, decimals } = opts
|
const { rawAddress, symbol, decimals } = opts
|
||||||
if (!rawAddress || !symbol || typeof decimals === 'undefined') {
|
if (!rawAddress || !symbol || typeof decimals === 'undefined') {
|
||||||
throw new Error(`Cannot suggest token without address, symbol, and decimals`)
|
throw new Error(
|
||||||
|
`Cannot suggest token without address, symbol, and decimals`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (!(symbol.length < 7)) {
|
if (!(symbol.length < 7)) {
|
||||||
throw new Error(`Invalid symbol ${symbol} more than six characters`)
|
throw new Error(`Invalid symbol ${symbol} more than six characters`)
|
||||||
}
|
}
|
||||||
const numDecimals = parseInt(decimals, 10)
|
const numDecimals = parseInt(decimals, 10)
|
||||||
if (isNaN(numDecimals) || numDecimals > 36 || numDecimals < 0) {
|
if (isNaN(numDecimals) || numDecimals > 36 || numDecimals < 0) {
|
||||||
throw new Error(`Invalid decimals ${decimals} must be at least 0, and not over 36`)
|
throw new Error(
|
||||||
|
`Invalid decimals ${decimals} must be at least 0, and not over 36`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (!isValidAddress(rawAddress)) {
|
if (!isValidAddress(rawAddress)) {
|
||||||
throw new Error(`Invalid address ${rawAddress}`)
|
throw new Error(`Invalid address ${rawAddress}`)
|
||||||
|
@ -2,7 +2,7 @@ import { ethers } from 'ethers'
|
|||||||
import log from 'loglevel'
|
import log from 'loglevel'
|
||||||
import BigNumber from 'bignumber.js'
|
import BigNumber from 'bignumber.js'
|
||||||
import ObservableStore from 'obs-store'
|
import ObservableStore from 'obs-store'
|
||||||
import { mapValues } from 'lodash'
|
import { mapValues, cloneDeep } from 'lodash'
|
||||||
import abi from 'human-standard-token-abi'
|
import abi from 'human-standard-token-abi'
|
||||||
import { calcTokenAmount } from '../../../ui/app/helpers/utils/token-util'
|
import { calcTokenAmount } from '../../../ui/app/helpers/utils/token-util'
|
||||||
import { calcGasTotal } from '../../../ui/app/pages/send/send.utils'
|
import { calcGasTotal } from '../../../ui/app/pages/send/send.utils'
|
||||||
@ -28,17 +28,14 @@ const MAX_GAS_LIMIT = 2500000
|
|||||||
// 3 seems to be an appropriate balance of giving users the time they need when MetaMask is not left idle, and turning polling off when it is.
|
// 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
|
||||||
|
|
||||||
function calculateGasEstimateWithRefund (maxGas = MAX_GAS_LIMIT, estimatedRefund = 0, estimatedGas = 0) {
|
function calculateGasEstimateWithRefund(
|
||||||
const maxGasMinusRefund = new BigNumber(
|
maxGas = MAX_GAS_LIMIT,
|
||||||
maxGas,
|
estimatedRefund = 0,
|
||||||
10,
|
estimatedGas = 0,
|
||||||
)
|
) {
|
||||||
.minus(estimatedRefund, 10)
|
const maxGasMinusRefund = new BigNumber(maxGas, 10).minus(estimatedRefund, 10)
|
||||||
|
|
||||||
const gasEstimateWithRefund = maxGasMinusRefund.lt(
|
const gasEstimateWithRefund = maxGasMinusRefund.lt(estimatedGas, 16)
|
||||||
estimatedGas,
|
|
||||||
16,
|
|
||||||
)
|
|
||||||
? maxGasMinusRefund.toString(16)
|
? maxGasMinusRefund.toString(16)
|
||||||
: estimatedGas
|
: estimatedGas
|
||||||
|
|
||||||
@ -68,7 +65,7 @@ const initialState = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class SwapsController {
|
export default class SwapsController {
|
||||||
constructor ({
|
constructor({
|
||||||
getBufferedGasLimit,
|
getBufferedGasLimit,
|
||||||
networkController,
|
networkController,
|
||||||
provider,
|
provider,
|
||||||
@ -108,18 +105,26 @@ export default class SwapsController {
|
|||||||
// that quotes will no longer be available after 1 or 2 minutes. When fetchAndSetQuotes is first called it, receives fetch that parameters are stored in
|
// that quotes will no longer be available after 1 or 2 minutes. When fetchAndSetQuotes is first called it, receives fetch that parameters are stored in
|
||||||
// state. These stored parameters are used on subsequent calls made during polling.
|
// state. These stored parameters are used on subsequent calls made during polling.
|
||||||
// Note: we stop polling after 3 requests, until new quotes are explicitly asked for. The logic that enforces that maximum is in the body of fetchAndSetQuotes
|
// Note: we stop polling after 3 requests, until new quotes are explicitly asked for. The logic that enforces that maximum is in the body of fetchAndSetQuotes
|
||||||
pollForNewQuotes () {
|
pollForNewQuotes() {
|
||||||
this.pollingTimeout = setTimeout(() => {
|
this.pollingTimeout = setTimeout(() => {
|
||||||
const { swapsState } = this.store.getState()
|
const { swapsState } = this.store.getState()
|
||||||
this.fetchAndSetQuotes(swapsState.fetchParams, swapsState.fetchParams.metaData, true)
|
this.fetchAndSetQuotes(
|
||||||
|
swapsState.fetchParams,
|
||||||
|
swapsState.fetchParams?.metaData,
|
||||||
|
true,
|
||||||
|
)
|
||||||
}, QUOTE_POLLING_INTERVAL)
|
}, QUOTE_POLLING_INTERVAL)
|
||||||
}
|
}
|
||||||
|
|
||||||
stopPollingForQuotes () {
|
stopPollingForQuotes() {
|
||||||
clearTimeout(this.pollingTimeout)
|
clearTimeout(this.pollingTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchAndSetQuotes (fetchParams, fetchParamsMetaData = {}, isPolledRequest) {
|
async fetchAndSetQuotes(
|
||||||
|
fetchParams,
|
||||||
|
fetchParamsMetaData = {},
|
||||||
|
isPolledRequest,
|
||||||
|
) {
|
||||||
if (!fetchParams) {
|
if (!fetchParams) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -150,7 +155,10 @@ export default class SwapsController {
|
|||||||
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) {
|
if (
|
||||||
|
fetchParams.sourceToken !== ETH_SWAPS_TOKEN_ADDRESS &&
|
||||||
|
Object.values(newQuotes).length
|
||||||
|
) {
|
||||||
const allowance = await this._getERC20Allowance(
|
const allowance = await this._getERC20Allowance(
|
||||||
fetchParams.sourceToken,
|
fetchParams.sourceToken,
|
||||||
fetchParams.fromAddress,
|
fetchParams.fromAddress,
|
||||||
@ -167,7 +175,9 @@ export default class SwapsController {
|
|||||||
approvalNeeded: null,
|
approvalNeeded: null,
|
||||||
}))
|
}))
|
||||||
} else if (!isPolledRequest) {
|
} else if (!isPolledRequest) {
|
||||||
const { gasLimit: approvalGas } = await this.timedoutGasReturn(Object.values(newQuotes)[0].approvalNeeded)
|
const { gasLimit: approvalGas } = await this.timedoutGasReturn(
|
||||||
|
Object.values(newQuotes)[0].approvalNeeded,
|
||||||
|
)
|
||||||
|
|
||||||
newQuotes = mapValues(newQuotes, (quote) => ({
|
newQuotes = mapValues(newQuotes, (quote) => ({
|
||||||
...quote,
|
...quote,
|
||||||
@ -190,13 +200,12 @@ export default class SwapsController {
|
|||||||
if (Object.values(newQuotes).length === 0) {
|
if (Object.values(newQuotes).length === 0) {
|
||||||
this.setSwapsErrorKey(QUOTES_NOT_AVAILABLE_ERROR)
|
this.setSwapsErrorKey(QUOTES_NOT_AVAILABLE_ERROR)
|
||||||
} else {
|
} else {
|
||||||
const topQuoteData = await this._findTopQuoteAndCalculateSavings(newQuotes)
|
const [
|
||||||
|
_topAggId,
|
||||||
if (topQuoteData.topAggId) {
|
quotesWithSavingsAndFeeData,
|
||||||
topAggId = topQuoteData.topAggId
|
] = await this._findTopQuoteAndCalculateSavings(newQuotes)
|
||||||
newQuotes[topAggId].isBestQuote = topQuoteData.isBest
|
topAggId = _topAggId
|
||||||
newQuotes[topAggId].savings = topQuoteData.savings
|
newQuotes = quotesWithSavingsAndFeeData
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a newer call has been made, don't update state with old information
|
// If a newer call has been made, don't update state with old information
|
||||||
@ -235,32 +244,34 @@ export default class SwapsController {
|
|||||||
return [newQuotes, topAggId]
|
return [newQuotes, topAggId]
|
||||||
}
|
}
|
||||||
|
|
||||||
safeRefetchQuotes () {
|
safeRefetchQuotes() {
|
||||||
const { swapsState } = this.store.getState()
|
const { swapsState } = this.store.getState()
|
||||||
if (!this.pollingTimeout && swapsState.fetchParams) {
|
if (!this.pollingTimeout && swapsState.fetchParams) {
|
||||||
this.fetchAndSetQuotes(swapsState.fetchParams)
|
this.fetchAndSetQuotes(swapsState.fetchParams)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelectedQuoteAggId (selectedAggId) {
|
setSelectedQuoteAggId(selectedAggId) {
|
||||||
const { swapsState } = this.store.getState()
|
const { swapsState } = this.store.getState()
|
||||||
this.store.updateState({ swapsState: { ...swapsState, selectedAggId } })
|
this.store.updateState({ swapsState: { ...swapsState, selectedAggId } })
|
||||||
}
|
}
|
||||||
|
|
||||||
setSwapsTokens (tokens) {
|
setSwapsTokens(tokens) {
|
||||||
const { swapsState } = this.store.getState()
|
const { swapsState } = this.store.getState()
|
||||||
this.store.updateState({ swapsState: { ...swapsState, tokens } })
|
this.store.updateState({ swapsState: { ...swapsState, tokens } })
|
||||||
}
|
}
|
||||||
|
|
||||||
setSwapsErrorKey (errorKey) {
|
setSwapsErrorKey(errorKey) {
|
||||||
const { swapsState } = this.store.getState()
|
const { swapsState } = this.store.getState()
|
||||||
this.store.updateState({ swapsState: { ...swapsState, errorKey } })
|
this.store.updateState({ swapsState: { ...swapsState, errorKey } })
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllQuotesWithGasEstimates (quotes) {
|
async getAllQuotesWithGasEstimates(quotes) {
|
||||||
const quoteGasData = await Promise.all(
|
const quoteGasData = await Promise.all(
|
||||||
Object.values(quotes).map(async (quote) => {
|
Object.values(quotes).map(async (quote) => {
|
||||||
const { gasLimit, simulationFails } = await this.timedoutGasReturn(quote.trade)
|
const { gasLimit, simulationFails } = await this.timedoutGasReturn(
|
||||||
|
quote.trade,
|
||||||
|
)
|
||||||
return [gasLimit, simulationFails, quote.aggregator]
|
return [gasLimit, simulationFails, quote.aggregator]
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -268,7 +279,11 @@ export default class SwapsController {
|
|||||||
const newQuotes = {}
|
const newQuotes = {}
|
||||||
quoteGasData.forEach(([gasLimit, simulationFails, aggId]) => {
|
quoteGasData.forEach(([gasLimit, simulationFails, aggId]) => {
|
||||||
if (gasLimit && !simulationFails) {
|
if (gasLimit && !simulationFails) {
|
||||||
const gasEstimateWithRefund = calculateGasEstimateWithRefund(quotes[aggId].maxGas, quotes[aggId].estimatedRefund, gasLimit)
|
const gasEstimateWithRefund = calculateGasEstimateWithRefund(
|
||||||
|
quotes[aggId].maxGas,
|
||||||
|
quotes[aggId].estimatedRefund,
|
||||||
|
gasLimit,
|
||||||
|
)
|
||||||
|
|
||||||
newQuotes[aggId] = {
|
newQuotes[aggId] = {
|
||||||
...quotes[aggId],
|
...quotes[aggId],
|
||||||
@ -285,7 +300,7 @@ export default class SwapsController {
|
|||||||
return newQuotes
|
return newQuotes
|
||||||
}
|
}
|
||||||
|
|
||||||
timedoutGasReturn (tradeTxParams) {
|
timedoutGasReturn(tradeTxParams) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
let gasTimedOut = false
|
let gasTimedOut = false
|
||||||
|
|
||||||
@ -321,7 +336,7 @@ export default class SwapsController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async setInitialGasEstimate (initialAggId) {
|
async setInitialGasEstimate(initialAggId) {
|
||||||
const { swapsState } = this.store.getState()
|
const { swapsState } = this.store.getState()
|
||||||
|
|
||||||
const quoteToUpdate = { ...swapsState.quotes[initialAggId] }
|
const quoteToUpdate = { ...swapsState.quotes[initialAggId] }
|
||||||
@ -332,64 +347,73 @@ export default class SwapsController {
|
|||||||
} = await this.timedoutGasReturn(quoteToUpdate.trade)
|
} = await this.timedoutGasReturn(quoteToUpdate.trade)
|
||||||
|
|
||||||
if (newGasEstimate && !simulationFails) {
|
if (newGasEstimate && !simulationFails) {
|
||||||
const gasEstimateWithRefund = calculateGasEstimateWithRefund(quoteToUpdate.maxGas, quoteToUpdate.estimatedRefund, newGasEstimate)
|
const gasEstimateWithRefund = calculateGasEstimateWithRefund(
|
||||||
|
quoteToUpdate.maxGas,
|
||||||
|
quoteToUpdate.estimatedRefund,
|
||||||
|
newGasEstimate,
|
||||||
|
)
|
||||||
|
|
||||||
quoteToUpdate.gasEstimate = newGasEstimate
|
quoteToUpdate.gasEstimate = newGasEstimate
|
||||||
quoteToUpdate.gasEstimateWithRefund = gasEstimateWithRefund
|
quoteToUpdate.gasEstimateWithRefund = gasEstimateWithRefund
|
||||||
}
|
}
|
||||||
|
|
||||||
this.store.updateState({
|
this.store.updateState({
|
||||||
swapsState: { ...swapsState, quotes: { ...swapsState.quotes, [initialAggId]: quoteToUpdate } },
|
swapsState: {
|
||||||
|
...swapsState,
|
||||||
|
quotes: { ...swapsState.quotes, [initialAggId]: quoteToUpdate },
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setApproveTxId (approveTxId) {
|
setApproveTxId(approveTxId) {
|
||||||
const { swapsState } = this.store.getState()
|
const { swapsState } = this.store.getState()
|
||||||
this.store.updateState({ swapsState: { ...swapsState, approveTxId } })
|
this.store.updateState({ swapsState: { ...swapsState, approveTxId } })
|
||||||
}
|
}
|
||||||
|
|
||||||
setTradeTxId (tradeTxId) {
|
setTradeTxId(tradeTxId) {
|
||||||
const { swapsState } = this.store.getState()
|
const { swapsState } = this.store.getState()
|
||||||
this.store.updateState({ swapsState: { ...swapsState, tradeTxId } })
|
this.store.updateState({ swapsState: { ...swapsState, tradeTxId } })
|
||||||
}
|
}
|
||||||
|
|
||||||
setQuotesLastFetched (quotesLastFetched) {
|
setQuotesLastFetched(quotesLastFetched) {
|
||||||
const { swapsState } = this.store.getState()
|
const { swapsState } = this.store.getState()
|
||||||
this.store.updateState({ swapsState: { ...swapsState, quotesLastFetched } })
|
this.store.updateState({ swapsState: { ...swapsState, quotesLastFetched } })
|
||||||
}
|
}
|
||||||
|
|
||||||
setSwapsTxGasPrice (gasPrice) {
|
setSwapsTxGasPrice(gasPrice) {
|
||||||
const { swapsState } = this.store.getState()
|
const { swapsState } = this.store.getState()
|
||||||
this.store.updateState({
|
this.store.updateState({
|
||||||
swapsState: { ...swapsState, customGasPrice: gasPrice },
|
swapsState: { ...swapsState, customGasPrice: gasPrice },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setSwapsTxGasLimit (gasLimit) {
|
setSwapsTxGasLimit(gasLimit) {
|
||||||
const { swapsState } = this.store.getState()
|
const { swapsState } = this.store.getState()
|
||||||
this.store.updateState({
|
this.store.updateState({
|
||||||
swapsState: { ...swapsState, customMaxGas: gasLimit },
|
swapsState: { ...swapsState, customMaxGas: gasLimit },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setCustomApproveTxData (data) {
|
setCustomApproveTxData(data) {
|
||||||
const { swapsState } = this.store.getState()
|
const { swapsState } = this.store.getState()
|
||||||
this.store.updateState({
|
this.store.updateState({
|
||||||
swapsState: { ...swapsState, customApproveTxData: data },
|
swapsState: { ...swapsState, customApproveTxData: data },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setBackgroundSwapRouteState (routeState) {
|
setBackgroundSwapRouteState(routeState) {
|
||||||
const { swapsState } = this.store.getState()
|
const { swapsState } = this.store.getState()
|
||||||
this.store.updateState({ swapsState: { ...swapsState, routeState } })
|
this.store.updateState({ swapsState: { ...swapsState, routeState } })
|
||||||
}
|
}
|
||||||
|
|
||||||
setSwapsLiveness (swapsFeatureIsLive) {
|
setSwapsLiveness(swapsFeatureIsLive) {
|
||||||
const { swapsState } = this.store.getState()
|
const { swapsState } = this.store.getState()
|
||||||
this.store.updateState({ swapsState: { ...swapsState, swapsFeatureIsLive } })
|
this.store.updateState({
|
||||||
|
swapsState: { ...swapsState, swapsFeatureIsLive },
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
resetPostFetchState () {
|
resetPostFetchState() {
|
||||||
const { swapsState } = this.store.getState()
|
const { swapsState } = this.store.getState()
|
||||||
|
|
||||||
this.store.updateState({
|
this.store.updateState({
|
||||||
@ -403,21 +427,25 @@ export default class SwapsController {
|
|||||||
clearTimeout(this.pollingTimeout)
|
clearTimeout(this.pollingTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
resetSwapsState () {
|
resetSwapsState() {
|
||||||
const { swapsState } = this.store.getState()
|
const { swapsState } = this.store.getState()
|
||||||
|
|
||||||
this.store.updateState({
|
this.store.updateState({
|
||||||
swapsState: { ...initialState.swapsState, tokens: swapsState.tokens, swapsFeatureIsLive: swapsState.swapsFeatureIsLive },
|
swapsState: {
|
||||||
|
...initialState.swapsState,
|
||||||
|
tokens: swapsState.tokens,
|
||||||
|
swapsFeatureIsLive: swapsState.swapsFeatureIsLive,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
clearTimeout(this.pollingTimeout)
|
clearTimeout(this.pollingTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getEthersGasPrice () {
|
async _getEthersGasPrice() {
|
||||||
const ethersGasPrice = await this.ethersProvider.getGasPrice()
|
const ethersGasPrice = await this.ethersProvider.getGasPrice()
|
||||||
return ethersGasPrice.toHexString()
|
return ethersGasPrice.toHexString()
|
||||||
}
|
}
|
||||||
|
|
||||||
async _findTopQuoteAndCalculateSavings (quotes = {}) {
|
async _findTopQuoteAndCalculateSavings(quotes = {}) {
|
||||||
const tokenConversionRates = this.tokenRatesStore.getState()
|
const tokenConversionRates = this.tokenRatesStore.getState()
|
||||||
.contractExchangeRates
|
.contractExchangeRates
|
||||||
const {
|
const {
|
||||||
@ -429,15 +457,14 @@ export default class SwapsController {
|
|||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const usedGasPrice = customGasPrice || await this._getEthersGasPrice()
|
const newQuotes = cloneDeep(quotes)
|
||||||
|
|
||||||
let topAggId = ''
|
const usedGasPrice = customGasPrice || (await this._getEthersGasPrice())
|
||||||
let ethTradeValueOfBestQuote = null
|
|
||||||
let ethFeeForBestQuote = null
|
|
||||||
const allEthTradeValues = []
|
|
||||||
const allEthFees = []
|
|
||||||
|
|
||||||
Object.values(quotes).forEach((quote) => {
|
let topAggId = null
|
||||||
|
let overallValueOfBestQuoteForSorting = null
|
||||||
|
|
||||||
|
Object.values(newQuotes).forEach((quote) => {
|
||||||
const {
|
const {
|
||||||
aggregator,
|
aggregator,
|
||||||
approvalNeeded,
|
approvalNeeded,
|
||||||
@ -449,6 +476,7 @@ export default class SwapsController {
|
|||||||
sourceAmount,
|
sourceAmount,
|
||||||
sourceToken,
|
sourceToken,
|
||||||
trade,
|
trade,
|
||||||
|
fee: metaMaskFee,
|
||||||
} = quote
|
} = quote
|
||||||
|
|
||||||
const tradeGasLimitForCalculation = gasEstimate
|
const tradeGasLimitForCalculation = gasEstimate
|
||||||
@ -468,8 +496,10 @@ export default class SwapsController {
|
|||||||
// It always includes any external fees charged by the quote source. In
|
// It always includes any external fees charged by the quote source. In
|
||||||
// addition, if the source asset is ETH, trade.value includes the amount
|
// addition, if the source asset is ETH, trade.value includes the amount
|
||||||
// of swapped ETH.
|
// of swapped ETH.
|
||||||
const totalWeiCost = new BigNumber(gasTotalInWeiHex, 16)
|
const totalWeiCost = new BigNumber(gasTotalInWeiHex, 16).plus(
|
||||||
.plus(trade.value, 16)
|
trade.value,
|
||||||
|
16,
|
||||||
|
)
|
||||||
|
|
||||||
const totalEthCost = conversionUtil(totalWeiCost, {
|
const totalEthCost = conversionUtil(totalWeiCost, {
|
||||||
fromCurrency: 'ETH',
|
fromCurrency: 'ETH',
|
||||||
@ -482,81 +512,122 @@ export default class SwapsController {
|
|||||||
// The total fee is aggregator/exchange fees plus gas fees.
|
// The total fee is aggregator/exchange fees plus gas fees.
|
||||||
// If the swap is from ETH, subtract the sourceAmount from the total cost.
|
// If the swap is from ETH, subtract the sourceAmount from the total cost.
|
||||||
// Otherwise, the total fee is simply trade.value plus gas fees.
|
// Otherwise, the total fee is simply trade.value plus gas fees.
|
||||||
const ethFee = sourceToken === ETH_SWAPS_TOKEN_ADDRESS
|
const ethFee =
|
||||||
? conversionUtil(
|
sourceToken === ETH_SWAPS_TOKEN_ADDRESS
|
||||||
totalWeiCost.minus(sourceAmount, 10), // sourceAmount is in wei
|
? conversionUtil(
|
||||||
{
|
totalWeiCost.minus(sourceAmount, 10), // sourceAmount is in wei
|
||||||
fromCurrency: 'ETH',
|
{
|
||||||
fromDenomination: 'WEI',
|
fromCurrency: 'ETH',
|
||||||
toDenomination: 'ETH',
|
fromDenomination: 'WEI',
|
||||||
fromNumericBase: 'BN',
|
toDenomination: 'ETH',
|
||||||
numberOfDecimals: 6,
|
fromNumericBase: 'BN',
|
||||||
},
|
numberOfDecimals: 6,
|
||||||
)
|
},
|
||||||
: totalEthCost
|
)
|
||||||
|
: totalEthCost
|
||||||
|
|
||||||
|
const decimalAdjustedDestinationAmount = calcTokenAmount(
|
||||||
|
destinationAmount,
|
||||||
|
destinationTokenInfo.decimals,
|
||||||
|
)
|
||||||
|
|
||||||
|
const tokenPercentageOfPreFeeDestAmount = new BigNumber(100, 10)
|
||||||
|
.minus(metaMaskFee, 10)
|
||||||
|
.div(100)
|
||||||
|
const destinationAmountBeforeMetaMaskFee = decimalAdjustedDestinationAmount.div(
|
||||||
|
tokenPercentageOfPreFeeDestAmount,
|
||||||
|
)
|
||||||
|
const metaMaskFeeInTokens = destinationAmountBeforeMetaMaskFee.minus(
|
||||||
|
decimalAdjustedDestinationAmount,
|
||||||
|
)
|
||||||
|
|
||||||
const tokenConversionRate = tokenConversionRates[destinationToken]
|
const tokenConversionRate = tokenConversionRates[destinationToken]
|
||||||
const ethValueOfTrade =
|
const conversionRateForSorting = tokenConversionRate || 1
|
||||||
destinationToken === ETH_SWAPS_TOKEN_ADDRESS
|
|
||||||
? calcTokenAmount(destinationAmount, 18).minus(totalEthCost, 10)
|
|
||||||
: new BigNumber(tokenConversionRate || 1, 10)
|
|
||||||
.times(
|
|
||||||
calcTokenAmount(
|
|
||||||
destinationAmount,
|
|
||||||
destinationTokenInfo.decimals,
|
|
||||||
),
|
|
||||||
10,
|
|
||||||
)
|
|
||||||
.minus(tokenConversionRate ? totalEthCost : 0, 10)
|
|
||||||
|
|
||||||
// collect values for savings calculation
|
const ethValueOfTokens = decimalAdjustedDestinationAmount.times(
|
||||||
allEthTradeValues.push(ethValueOfTrade)
|
conversionRateForSorting,
|
||||||
allEthFees.push(ethFee)
|
10,
|
||||||
|
)
|
||||||
|
|
||||||
|
const conversionRateForCalculations =
|
||||||
|
destinationToken === ETH_SWAPS_TOKEN_ADDRESS ? 1 : tokenConversionRate
|
||||||
|
|
||||||
|
const overallValueOfQuoteForSorting =
|
||||||
|
conversionRateForCalculations === undefined
|
||||||
|
? ethValueOfTokens
|
||||||
|
: ethValueOfTokens.minus(ethFee, 10)
|
||||||
|
|
||||||
|
quote.ethFee = ethFee.toString(10)
|
||||||
|
|
||||||
|
if (conversionRateForCalculations !== undefined) {
|
||||||
|
quote.ethValueOfTokens = ethValueOfTokens.toString(10)
|
||||||
|
quote.overallValueOfQuote = overallValueOfQuoteForSorting.toString(10)
|
||||||
|
quote.metaMaskFeeInEth = metaMaskFeeInTokens
|
||||||
|
.times(conversionRateForCalculations)
|
||||||
|
.toString(10)
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
ethTradeValueOfBestQuote === null ||
|
overallValueOfBestQuoteForSorting === null ||
|
||||||
ethValueOfTrade.gt(ethTradeValueOfBestQuote)
|
overallValueOfQuoteForSorting.gt(overallValueOfBestQuoteForSorting)
|
||||||
) {
|
) {
|
||||||
topAggId = aggregator
|
topAggId = aggregator
|
||||||
ethTradeValueOfBestQuote = ethValueOfTrade
|
overallValueOfBestQuoteForSorting = overallValueOfQuoteForSorting
|
||||||
ethFeeForBestQuote = ethFee
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const isBest =
|
const isBest =
|
||||||
quotes[topAggId].destinationToken === ETH_SWAPS_TOKEN_ADDRESS ||
|
newQuotes[topAggId].destinationToken === ETH_SWAPS_TOKEN_ADDRESS ||
|
||||||
Boolean(tokenConversionRates[quotes[topAggId]?.destinationToken])
|
Boolean(tokenConversionRates[newQuotes[topAggId]?.destinationToken])
|
||||||
|
|
||||||
let savings = null
|
let savings = null
|
||||||
|
|
||||||
if (isBest) {
|
if (isBest) {
|
||||||
|
const bestQuote = newQuotes[topAggId]
|
||||||
|
|
||||||
savings = {}
|
savings = {}
|
||||||
|
|
||||||
|
const {
|
||||||
|
ethFee: medianEthFee,
|
||||||
|
metaMaskFeeInEth: medianMetaMaskFee,
|
||||||
|
ethValueOfTokens: medianEthValueOfTokens,
|
||||||
|
} = getMedianEthValueQuote(Object.values(newQuotes))
|
||||||
|
|
||||||
// Performance savings are calculated as:
|
// Performance savings are calculated as:
|
||||||
// valueForBestTrade - medianValueOfAllTrades
|
// (ethValueOfTokens for the best trade) - (ethValueOfTokens for the media trade)
|
||||||
savings.performance = ethTradeValueOfBestQuote.minus(
|
savings.performance = new BigNumber(bestQuote.ethValueOfTokens, 10).minus(
|
||||||
getMedian(allEthTradeValues),
|
medianEthValueOfTokens,
|
||||||
10,
|
10,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Performance savings are calculated as:
|
// Fee savings are calculated as:
|
||||||
// medianFeeOfAllTrades - feeForBestTrade
|
// (fee for the median trade) - (fee for the best trade)
|
||||||
savings.fee = getMedian(allEthFees).minus(
|
savings.fee = new BigNumber(medianEthFee).minus(bestQuote.ethFee, 10)
|
||||||
ethFeeForBestQuote,
|
|
||||||
10,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Total savings are the sum of performance and fee savings
|
savings.metaMaskFee = bestQuote.metaMaskFeeInEth
|
||||||
savings.total = savings.performance.plus(savings.fee, 10).toString(10)
|
|
||||||
|
// 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.performance = savings.performance.toString(10)
|
||||||
savings.fee = savings.fee.toString(10)
|
savings.fee = savings.fee.toString(10)
|
||||||
|
savings.medianMetaMaskFee = medianMetaMaskFee
|
||||||
|
|
||||||
|
newQuotes[topAggId].isBestQuote = true
|
||||||
|
newQuotes[topAggId].savings = savings
|
||||||
}
|
}
|
||||||
|
|
||||||
return { topAggId, isBest, savings }
|
return [topAggId, newQuotes]
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getERC20Allowance (contractAddress, walletAddress) {
|
async _getERC20Allowance(contractAddress, walletAddress) {
|
||||||
const contract = new ethers.Contract(
|
const contract = new ethers.Contract(
|
||||||
contractAddress, abi, this.ethersProvider,
|
contractAddress,
|
||||||
|
abi,
|
||||||
|
this.ethersProvider,
|
||||||
)
|
)
|
||||||
return await contract.allowance(walletAddress, METASWAP_ADDRESS)
|
return await contract.allowance(walletAddress, METASWAP_ADDRESS)
|
||||||
}
|
}
|
||||||
@ -569,7 +640,7 @@ export default class SwapsController {
|
|||||||
* If the browser goes offline, the interval is cleared and swaps are disabled
|
* If the browser goes offline, the interval is cleared and swaps are disabled
|
||||||
* until the value can be fetched again.
|
* until the value can be fetched again.
|
||||||
*/
|
*/
|
||||||
_setupSwapsLivenessFetching () {
|
_setupSwapsLivenessFetching() {
|
||||||
const TEN_MINUTES_MS = 10 * 60 * 1000
|
const TEN_MINUTES_MS = 10 * 60 * 1000
|
||||||
let intervalId = null
|
let intervalId = null
|
||||||
|
|
||||||
@ -577,7 +648,10 @@ export default class SwapsController {
|
|||||||
if (window.navigator.onLine && intervalId === null) {
|
if (window.navigator.onLine && intervalId === null) {
|
||||||
// Set the interval first to prevent race condition between listener and
|
// Set the interval first to prevent race condition between listener and
|
||||||
// initial call to this function.
|
// initial call to this function.
|
||||||
intervalId = setInterval(this._fetchAndSetSwapsLiveness.bind(this), TEN_MINUTES_MS)
|
intervalId = setInterval(
|
||||||
|
this._fetchAndSetSwapsLiveness.bind(this),
|
||||||
|
TEN_MINUTES_MS,
|
||||||
|
)
|
||||||
this._fetchAndSetSwapsLiveness()
|
this._fetchAndSetSwapsLiveness()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -608,7 +682,7 @@ export default class SwapsController {
|
|||||||
* Only updates state if the fetched/computed flag value differs from current
|
* Only updates state if the fetched/computed flag value differs from current
|
||||||
* state.
|
* state.
|
||||||
*/
|
*/
|
||||||
async _fetchAndSetSwapsLiveness () {
|
async _fetchAndSetSwapsLiveness() {
|
||||||
const { swapsState } = this.store.getState()
|
const { swapsState } = this.store.getState()
|
||||||
const { swapsFeatureIsLive: oldSwapsFeatureIsLive } = swapsState
|
const { swapsFeatureIsLive: oldSwapsFeatureIsLive } = swapsState
|
||||||
let swapsFeatureIsLive = false
|
let swapsFeatureIsLive = false
|
||||||
@ -637,7 +711,9 @@ export default class SwapsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!successfullyFetched) {
|
if (!successfullyFetched) {
|
||||||
log.error('Failed to fetch swaps feature flag 3 times. Setting to false and trying again next interval.')
|
log.error(
|
||||||
|
'Failed to fetch swaps feature flag 3 times. Setting to false and trying again next interval.',
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (swapsFeatureIsLive !== oldSwapsFeatureIsLive) {
|
if (swapsFeatureIsLive !== oldSwapsFeatureIsLive) {
|
||||||
@ -647,36 +723,123 @@ export default class SwapsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the median of a sample of BigNumber values.
|
* Calculates the median overallValueOfQuote of a sample of quotes.
|
||||||
*
|
*
|
||||||
* @param {import('bignumber.js').BigNumber[]} values - A sample of BigNumber
|
* @param {Array} quotes - A sample of quote objects with overallValueOfQuote, ethFee, metaMaskFeeInEth, and ethValueOfTokens properties
|
||||||
* values. The array will be sorted in place.
|
* @returns {Object} An object with the ethValueOfTokens, ethFee, and metaMaskFeeInEth of the quote with the median overallValueOfQuote
|
||||||
* @returns {import('bignumber.js').BigNumber} The median of the sample.
|
|
||||||
*/
|
*/
|
||||||
function getMedian (values) {
|
function getMedianEthValueQuote(_quotes) {
|
||||||
if (!Array.isArray(values) || values.length === 0) {
|
if (!Array.isArray(_quotes) || _quotes.length === 0) {
|
||||||
throw new Error('Expected non-empty array param.')
|
throw new Error('Expected non-empty array param.')
|
||||||
}
|
}
|
||||||
|
|
||||||
values.sort((a, b) => {
|
const quotes = [..._quotes]
|
||||||
if (a.equals(b)) {
|
|
||||||
|
quotes.sort((quoteA, quoteB) => {
|
||||||
|
const overallValueOfQuoteA = new BigNumber(quoteA.overallValueOfQuote, 10)
|
||||||
|
const overallValueOfQuoteB = new BigNumber(quoteB.overallValueOfQuote, 10)
|
||||||
|
if (overallValueOfQuoteA.equals(overallValueOfQuoteB)) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return a.lessThan(b) ? -1 : 1
|
return overallValueOfQuoteA.lessThan(overallValueOfQuoteB) ? -1 : 1
|
||||||
})
|
})
|
||||||
|
|
||||||
if (values.length % 2 === 1) {
|
if (quotes.length % 2 === 1) {
|
||||||
// return middle value
|
// return middle values
|
||||||
return values[(values.length - 1) / 2]
|
const medianOverallValue =
|
||||||
|
quotes[(quotes.length - 1) / 2].overallValueOfQuote
|
||||||
|
const quotesMatchingMedianQuoteValue = quotes.filter(
|
||||||
|
(quote) => medianOverallValue === quote.overallValueOfQuote,
|
||||||
|
)
|
||||||
|
return meansOfQuotesFeesAndValue(quotesMatchingMedianQuoteValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
// return mean of middle two values
|
// return mean of middle two values
|
||||||
const upperIndex = values.length / 2
|
const upperIndex = quotes.length / 2
|
||||||
return values[upperIndex]
|
const lowerIndex = upperIndex - 1
|
||||||
.plus(values[upperIndex - 1])
|
|
||||||
.dividedBy(2)
|
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)
|
||||||
|
.plus(feesAndValueAtLowerIndex.ethFee, 10)
|
||||||
|
.dividedBy(2)
|
||||||
|
.toString(10),
|
||||||
|
metaMaskFeeInEth: new BigNumber(
|
||||||
|
feesAndValueAtUpperIndex.metaMaskFeeInEth,
|
||||||
|
10,
|
||||||
|
)
|
||||||
|
.plus(feesAndValueAtLowerIndex.metaMaskFeeInEth, 10)
|
||||||
|
.dividedBy(2)
|
||||||
|
.toString(10),
|
||||||
|
ethValueOfTokens: new BigNumber(
|
||||||
|
feesAndValueAtUpperIndex.ethValueOfTokens,
|
||||||
|
10,
|
||||||
|
)
|
||||||
|
.plus(feesAndValueAtLowerIndex.ethValueOfTokens, 10)
|
||||||
|
.dividedBy(2)
|
||||||
|
.toString(10),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the arithmetic mean for each of three properties - ethFee, metaMaskFeeInEth and ethValueOfTokens - across
|
||||||
|
* an array of objects containing those properties.
|
||||||
|
*
|
||||||
|
* @param {Array} quotes - A sample of quote objects with overallValueOfQuote, ethFee, metaMaskFeeInEth and
|
||||||
|
* ethValueOfTokens properties
|
||||||
|
* @returns {Object} An object with the arithmetic mean each of the ethFee, metaMaskFeeInEth and ethValueOfTokens of
|
||||||
|
* the passed quote objects
|
||||||
|
*/
|
||||||
|
function meansOfQuotesFeesAndValue(quotes) {
|
||||||
|
const feeAndValueSumsAsBigNumbers = quotes.reduce(
|
||||||
|
(feeAndValueSums, quote) => ({
|
||||||
|
ethFee: feeAndValueSums.ethFee.plus(quote.ethFee, 10),
|
||||||
|
metaMaskFeeInEth: feeAndValueSums.metaMaskFeeInEth.plus(
|
||||||
|
quote.metaMaskFeeInEth,
|
||||||
|
10,
|
||||||
|
),
|
||||||
|
ethValueOfTokens: feeAndValueSums.ethValueOfTokens.plus(
|
||||||
|
quote.ethValueOfTokens,
|
||||||
|
10,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
ethFee: new BigNumber(0, 10),
|
||||||
|
metaMaskFeeInEth: new BigNumber(0, 10),
|
||||||
|
ethValueOfTokens: new BigNumber(0, 10),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
ethFee: feeAndValueSumsAsBigNumbers.ethFee
|
||||||
|
.div(quotes.length, 10)
|
||||||
|
.toString(10),
|
||||||
|
metaMaskFeeInEth: feeAndValueSumsAsBigNumbers.metaMaskFeeInEth
|
||||||
|
.div(quotes.length, 10)
|
||||||
|
.toString(10),
|
||||||
|
ethValueOfTokens: feeAndValueSumsAsBigNumbers.ethValueOfTokens
|
||||||
|
.div(quotes.length, 10)
|
||||||
|
.toString(10),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const utils = {
|
export const utils = {
|
||||||
getMedian,
|
getMedianEthValueQuote,
|
||||||
|
meansOfQuotesFeesAndValue,
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ import createMetamaskMiddleware from './network/createMetamaskMiddleware'
|
|||||||
const SYNC_TIMEOUT = 60 * 1000 // one minute
|
const SYNC_TIMEOUT = 60 * 1000 // one minute
|
||||||
|
|
||||||
export default class ThreeBoxController {
|
export default class ThreeBoxController {
|
||||||
constructor (opts = {}) {
|
constructor(opts = {}) {
|
||||||
const {
|
const {
|
||||||
preferencesController,
|
preferencesController,
|
||||||
keyringController,
|
keyringController,
|
||||||
@ -41,16 +41,22 @@ export default class ThreeBoxController {
|
|||||||
const accounts = await this.keyringController.getAccounts()
|
const accounts = await this.keyringController.getAccounts()
|
||||||
|
|
||||||
if (isUnlocked && accounts[0]) {
|
if (isUnlocked && accounts[0]) {
|
||||||
const appKeyAddress = await this.keyringController.getAppKeyAddress(accounts[0], 'wallet://3box.metamask.io')
|
const appKeyAddress = await this.keyringController.getAppKeyAddress(
|
||||||
|
accounts[0],
|
||||||
|
'wallet://3box.metamask.io',
|
||||||
|
)
|
||||||
return [appKeyAddress]
|
return [appKeyAddress]
|
||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
},
|
},
|
||||||
processPersonalMessage: async (msgParams) => {
|
processPersonalMessage: async (msgParams) => {
|
||||||
const accounts = await this.keyringController.getAccounts()
|
const accounts = await this.keyringController.getAccounts()
|
||||||
return keyringController.signPersonalMessage({ ...msgParams, from: accounts[0] }, {
|
return keyringController.signPersonalMessage(
|
||||||
withAppKeyOrigin: 'wallet://3box.metamask.io',
|
{ ...msgParams, from: accounts[0] },
|
||||||
})
|
{
|
||||||
|
withAppKeyOrigin: 'wallet://3box.metamask.io',
|
||||||
|
},
|
||||||
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -65,14 +71,16 @@ export default class ThreeBoxController {
|
|||||||
}
|
}
|
||||||
this.store = new ObservableStore(initState)
|
this.store = new ObservableStore(initState)
|
||||||
this.registeringUpdates = false
|
this.registeringUpdates = false
|
||||||
this.lastMigration = migrations.sort((a, b) => a.version - b.version).slice(-1)[0]
|
this.lastMigration = migrations
|
||||||
|
.sort((a, b) => a.version - b.version)
|
||||||
|
.slice(-1)[0]
|
||||||
|
|
||||||
if (initState.threeBoxSyncingAllowed) {
|
if (initState.threeBoxSyncingAllowed) {
|
||||||
this.init()
|
this.init()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async init () {
|
async init() {
|
||||||
const accounts = await this.keyringController.getAccounts()
|
const accounts = await this.keyringController.getAccounts()
|
||||||
this.address = accounts[0]
|
this.address = accounts[0]
|
||||||
if (this.address && !(this.box && this.store.getState().threeBoxSynced)) {
|
if (this.address && !(this.box && this.store.getState().threeBoxSynced)) {
|
||||||
@ -80,7 +88,7 @@ export default class ThreeBoxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _update3Box () {
|
async _update3Box() {
|
||||||
try {
|
try {
|
||||||
const { threeBoxSyncingAllowed, threeBoxSynced } = this.store.getState()
|
const { threeBoxSyncingAllowed, threeBoxSynced } = this.store.getState()
|
||||||
if (threeBoxSyncingAllowed && threeBoxSynced) {
|
if (threeBoxSyncingAllowed && threeBoxSynced) {
|
||||||
@ -99,7 +107,7 @@ export default class ThreeBoxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_createProvider (providerOpts) {
|
_createProvider(providerOpts) {
|
||||||
const metamaskMiddleware = createMetamaskMiddleware(providerOpts)
|
const metamaskMiddleware = createMetamaskMiddleware(providerOpts)
|
||||||
const engine = new JsonRpcEngine()
|
const engine = new JsonRpcEngine()
|
||||||
engine.push(createOriginMiddleware({ origin: '3Box' }))
|
engine.push(createOriginMiddleware({ origin: '3Box' }))
|
||||||
@ -108,7 +116,7 @@ export default class ThreeBoxController {
|
|||||||
return provider
|
return provider
|
||||||
}
|
}
|
||||||
|
|
||||||
_waitForOnSyncDone () {
|
_waitForOnSyncDone() {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.box.onSyncDone(() => {
|
this.box.onSyncDone(() => {
|
||||||
log.debug('3Box box sync done')
|
log.debug('3Box box sync done')
|
||||||
@ -117,9 +125,12 @@ export default class ThreeBoxController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async new3Box () {
|
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')
|
this.address = await this.keyringController.getAppKeyAddress(
|
||||||
|
accounts[0],
|
||||||
|
'wallet://3box.metamask.io',
|
||||||
|
)
|
||||||
let backupExists
|
let backupExists
|
||||||
try {
|
try {
|
||||||
const threeBoxConfig = await Box.getConfig(this.address)
|
const threeBoxConfig = await Box.getConfig(this.address)
|
||||||
@ -170,20 +181,22 @@ export default class ThreeBoxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLastUpdated () {
|
async getLastUpdated() {
|
||||||
const res = await this.space.private.get('metamaskBackup')
|
const res = await this.space.private.get('metamaskBackup')
|
||||||
const parsedRes = JSON.parse(res || '{}')
|
const parsedRes = JSON.parse(res || '{}')
|
||||||
return parsedRes.lastUpdated
|
return parsedRes.lastUpdated
|
||||||
}
|
}
|
||||||
|
|
||||||
async migrateBackedUpState (backedUpState) {
|
async migrateBackedUpState(backedUpState) {
|
||||||
const migrator = new Migrator({ migrations })
|
const migrator = new Migrator({ migrations })
|
||||||
const { preferences, addressBook } = JSON.parse(backedUpState)
|
const { preferences, addressBook } = JSON.parse(backedUpState)
|
||||||
const formattedStateBackup = {
|
const formattedStateBackup = {
|
||||||
PreferencesController: preferences,
|
PreferencesController: preferences,
|
||||||
AddressBookController: addressBook,
|
AddressBookController: addressBook,
|
||||||
}
|
}
|
||||||
const initialMigrationState = migrator.generateInitialState(formattedStateBackup)
|
const initialMigrationState = migrator.generateInitialState(
|
||||||
|
formattedStateBackup,
|
||||||
|
)
|
||||||
const migratedState = await migrator.migrateData(initialMigrationState)
|
const migratedState = await migrator.migrateData(initialMigrationState)
|
||||||
return {
|
return {
|
||||||
preferences: migratedState.data.PreferencesController,
|
preferences: migratedState.data.PreferencesController,
|
||||||
@ -191,31 +204,30 @@ export default class ThreeBoxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async restoreFromThreeBox () {
|
async restoreFromThreeBox() {
|
||||||
const backedUpState = await this.space.private.get('metamaskBackup')
|
const backedUpState = await this.space.private.get('metamaskBackup')
|
||||||
const {
|
const { preferences, addressBook } = await this.migrateBackedUpState(
|
||||||
preferences,
|
backedUpState,
|
||||||
addressBook,
|
)
|
||||||
} = await this.migrateBackedUpState(backedUpState)
|
|
||||||
this.store.updateState({ threeBoxLastUpdated: backedUpState.lastUpdated })
|
this.store.updateState({ threeBoxLastUpdated: backedUpState.lastUpdated })
|
||||||
preferences && this.preferencesController.store.updateState(preferences)
|
preferences && this.preferencesController.store.updateState(preferences)
|
||||||
addressBook && this.addressBookController.update(addressBook, true)
|
addressBook && this.addressBookController.update(addressBook, true)
|
||||||
this.setShowRestorePromptToFalse()
|
this.setShowRestorePromptToFalse()
|
||||||
}
|
}
|
||||||
|
|
||||||
turnThreeBoxSyncingOn () {
|
turnThreeBoxSyncingOn() {
|
||||||
this._registerUpdates()
|
this._registerUpdates()
|
||||||
}
|
}
|
||||||
|
|
||||||
turnThreeBoxSyncingOff () {
|
turnThreeBoxSyncingOff() {
|
||||||
this.box.logout()
|
this.box.logout()
|
||||||
}
|
}
|
||||||
|
|
||||||
setShowRestorePromptToFalse () {
|
setShowRestorePromptToFalse() {
|
||||||
this.store.updateState({ showRestorePrompt: false })
|
this.store.updateState({ showRestorePrompt: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
setThreeBoxSyncingPermission (newThreeboxSyncingState) {
|
setThreeBoxSyncingPermission(newThreeboxSyncingState) {
|
||||||
if (this.store.getState().threeBoxDisabled) {
|
if (this.store.getState().threeBoxDisabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -232,11 +244,11 @@ export default class ThreeBoxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getThreeBoxSyncingState () {
|
getThreeBoxSyncingState() {
|
||||||
return this.store.getState().threeBoxSyncingAllowed
|
return this.store.getState().threeBoxSyncingAllowed
|
||||||
}
|
}
|
||||||
|
|
||||||
_registerUpdates () {
|
_registerUpdates() {
|
||||||
if (!this.registeringUpdates) {
|
if (!this.registeringUpdates) {
|
||||||
const updatePreferences = this._update3Box.bind(this)
|
const updatePreferences = this._update3Box.bind(this)
|
||||||
this.preferencesController.store.subscribe(updatePreferences)
|
this.preferencesController.store.subscribe(updatePreferences)
|
||||||
|
@ -11,13 +11,12 @@ const DEFAULT_INTERVAL = 180 * 1000
|
|||||||
* rates based on a user's current token list
|
* rates based on a user's current token list
|
||||||
*/
|
*/
|
||||||
export default class TokenRatesController {
|
export default class TokenRatesController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a TokenRatesController
|
* Creates a TokenRatesController
|
||||||
*
|
*
|
||||||
* @param {Object} [config] - Options to configure controller
|
* @param {Object} [config] - Options to configure controller
|
||||||
*/
|
*/
|
||||||
constructor ({ currency, preferences } = {}) {
|
constructor({ currency, preferences } = {}) {
|
||||||
this.store = new ObservableStore()
|
this.store = new ObservableStore()
|
||||||
this.currency = currency
|
this.currency = currency
|
||||||
this.preferences = preferences
|
this.preferences = preferences
|
||||||
@ -26,21 +25,32 @@ export default class TokenRatesController {
|
|||||||
/**
|
/**
|
||||||
* Updates exchange rates for all tokens
|
* Updates exchange rates for all tokens
|
||||||
*/
|
*/
|
||||||
async updateExchangeRates () {
|
async updateExchangeRates() {
|
||||||
const contractExchangeRates = {}
|
const contractExchangeRates = {}
|
||||||
const nativeCurrency = this.currency ? this.currency.state.nativeCurrency.toLowerCase() : 'eth'
|
const nativeCurrency = this.currency
|
||||||
|
? this.currency.state.nativeCurrency.toLowerCase()
|
||||||
|
: 'eth'
|
||||||
const pairs = this._tokens.map((token) => token.address).join(',')
|
const pairs = this._tokens.map((token) => token.address).join(',')
|
||||||
const query = `contract_addresses=${pairs}&vs_currencies=${nativeCurrency}`
|
const query = `contract_addresses=${pairs}&vs_currencies=${nativeCurrency}`
|
||||||
if (this._tokens.length > 0) {
|
if (this._tokens.length > 0) {
|
||||||
try {
|
try {
|
||||||
const response = await window.fetch(`https://api.coingecko.com/api/v3/simple/token_price/ethereum?${query}`)
|
const response = await window.fetch(
|
||||||
|
`https://api.coingecko.com/api/v3/simple/token_price/ethereum?${query}`,
|
||||||
|
)
|
||||||
const prices = await response.json()
|
const prices = await response.json()
|
||||||
this._tokens.forEach((token) => {
|
this._tokens.forEach((token) => {
|
||||||
const price = prices[token.address.toLowerCase()] || prices[ethUtil.toChecksumAddress(token.address)]
|
const price =
|
||||||
contractExchangeRates[normalizeAddress(token.address)] = price ? price[nativeCurrency] : 0
|
prices[token.address.toLowerCase()] ||
|
||||||
|
prices[ethUtil.toChecksumAddress(token.address)]
|
||||||
|
contractExchangeRates[normalizeAddress(token.address)] = price
|
||||||
|
? price[nativeCurrency]
|
||||||
|
: 0
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.warn(`MetaMask - TokenRatesController exchange rate fetch failed.`, error)
|
log.warn(
|
||||||
|
`MetaMask - TokenRatesController exchange rate fetch failed.`,
|
||||||
|
error,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.store.putState({ contractExchangeRates })
|
this.store.putState({ contractExchangeRates })
|
||||||
@ -50,7 +60,7 @@ export default class TokenRatesController {
|
|||||||
/**
|
/**
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
set preferences (preferences) {
|
set preferences(preferences) {
|
||||||
this._preferences && this._preferences.unsubscribe()
|
this._preferences && this._preferences.unsubscribe()
|
||||||
if (!preferences) {
|
if (!preferences) {
|
||||||
return
|
return
|
||||||
@ -65,13 +75,13 @@ export default class TokenRatesController {
|
|||||||
/**
|
/**
|
||||||
* @type {Array}
|
* @type {Array}
|
||||||
*/
|
*/
|
||||||
set tokens (tokens) {
|
set tokens(tokens) {
|
||||||
this._tokens = tokens
|
this._tokens = tokens
|
||||||
this.updateExchangeRates()
|
this.updateExchangeRates()
|
||||||
}
|
}
|
||||||
/* eslint-enable accessor-pairs */
|
/* eslint-enable accessor-pairs */
|
||||||
|
|
||||||
start (interval = DEFAULT_INTERVAL) {
|
start(interval = DEFAULT_INTERVAL) {
|
||||||
this._handle && clearInterval(this._handle)
|
this._handle && clearInterval(this._handle)
|
||||||
if (!interval) {
|
if (!interval) {
|
||||||
return
|
return
|
||||||
@ -82,7 +92,7 @@ export default class TokenRatesController {
|
|||||||
this.updateExchangeRates()
|
this.updateExchangeRates()
|
||||||
}
|
}
|
||||||
|
|
||||||
stop () {
|
stop() {
|
||||||
this._handle && clearInterval(this._handle)
|
this._handle && clearInterval(this._handle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
const TRANSACTION_TYPE_CANCEL = 'cancel'
|
|
||||||
const TRANSACTION_TYPE_RETRY = 'retry'
|
|
||||||
const TRANSACTION_TYPE_STANDARD = 'standard'
|
|
||||||
|
|
||||||
const TRANSACTION_STATUS_APPROVED = 'approved'
|
|
||||||
const TRANSACTION_STATUS_CONFIRMED = 'confirmed'
|
|
||||||
|
|
||||||
export {
|
|
||||||
TRANSACTION_TYPE_CANCEL,
|
|
||||||
TRANSACTION_TYPE_RETRY,
|
|
||||||
TRANSACTION_TYPE_STANDARD,
|
|
||||||
TRANSACTION_STATUS_APPROVED,
|
|
||||||
TRANSACTION_STATUS_CONFIRMED,
|
|
||||||
}
|
|
@ -9,29 +9,24 @@ import { ethers } from 'ethers'
|
|||||||
import NonceTracker from 'nonce-tracker'
|
import NonceTracker from 'nonce-tracker'
|
||||||
import log from 'loglevel'
|
import log from 'loglevel'
|
||||||
import BigNumber from 'bignumber.js'
|
import BigNumber from 'bignumber.js'
|
||||||
import {
|
|
||||||
TOKEN_METHOD_APPROVE,
|
|
||||||
TOKEN_METHOD_TRANSFER,
|
|
||||||
TOKEN_METHOD_TRANSFER_FROM,
|
|
||||||
SEND_ETHER_ACTION_KEY,
|
|
||||||
DEPLOY_CONTRACT_ACTION_KEY,
|
|
||||||
CONTRACT_INTERACTION_KEY,
|
|
||||||
SWAP,
|
|
||||||
} from '../../../../ui/app/helpers/constants/transactions'
|
|
||||||
import cleanErrorStack from '../../lib/cleanErrorStack'
|
import cleanErrorStack from '../../lib/cleanErrorStack'
|
||||||
import { hexToBn, bnToHex, BnMultiplyByFraction } from '../../lib/util'
|
import {
|
||||||
|
hexToBn,
|
||||||
|
bnToHex,
|
||||||
|
BnMultiplyByFraction,
|
||||||
|
addHexPrefix,
|
||||||
|
} from '../../lib/util'
|
||||||
import { TRANSACTION_NO_CONTRACT_ERROR_KEY } from '../../../../ui/app/helpers/constants/error-keys'
|
import { TRANSACTION_NO_CONTRACT_ERROR_KEY } from '../../../../ui/app/helpers/constants/error-keys'
|
||||||
import { getSwapsTokensReceivedFromTxMeta } from '../../../../ui/app/pages/swaps/swaps.util'
|
import { getSwapsTokensReceivedFromTxMeta } from '../../../../ui/app/pages/swaps/swaps.util'
|
||||||
|
import {
|
||||||
|
TRANSACTION_CATEGORIES,
|
||||||
|
TRANSACTION_STATUSES,
|
||||||
|
TRANSACTION_TYPES,
|
||||||
|
} from '../../../../shared/constants/transaction'
|
||||||
import TransactionStateManager from './tx-state-manager'
|
import TransactionStateManager from './tx-state-manager'
|
||||||
import TxGasUtil from './tx-gas-utils'
|
import TxGasUtil from './tx-gas-utils'
|
||||||
import PendingTransactionTracker from './pending-tx-tracker'
|
import PendingTransactionTracker from './pending-tx-tracker'
|
||||||
import * as txUtils from './lib/util'
|
import * as txUtils from './lib/util'
|
||||||
import {
|
|
||||||
TRANSACTION_TYPE_CANCEL,
|
|
||||||
TRANSACTION_TYPE_RETRY,
|
|
||||||
TRANSACTION_TYPE_STANDARD,
|
|
||||||
TRANSACTION_STATUS_APPROVED,
|
|
||||||
} from './enums'
|
|
||||||
|
|
||||||
const hstInterface = new ethers.utils.Interface(abi)
|
const hstInterface = new ethers.utils.Interface(abi)
|
||||||
|
|
||||||
@ -53,20 +48,20 @@ const MAX_MEMSTORE_TX_LIST_SIZE = 100 // Number of transactions (by unique nonce
|
|||||||
calculating nonces
|
calculating nonces
|
||||||
|
|
||||||
@class
|
@class
|
||||||
@param {Object} - opts
|
@param {Object} opts
|
||||||
@param {Object} opts.initState - initial transaction list default is an empty array
|
@param {Object} opts.initState - initial transaction list default is an empty array
|
||||||
@param {Object} opts.networkStore - an observable store for network number
|
@param {Object} opts.networkStore - an observable store for network number
|
||||||
@param {Object} opts.blockTracker - An instance of eth-blocktracker
|
@param {Object} opts.blockTracker - An instance of eth-blocktracker
|
||||||
@param {Object} opts.provider - A network provider.
|
@param {Object} opts.provider - A network provider.
|
||||||
@param {Function} opts.signTransaction - function the signs an ethereumjs-tx
|
@param {Function} opts.signTransaction - function the signs an ethereumjs-tx
|
||||||
@param {Object} opts.getPermittedAccounts - get accounts that an origin has permissions for
|
@param {Object} opts.getPermittedAccounts - get accounts that an origin has permissions for
|
||||||
@param {Function} opts.signTransaction - ethTx signer that returns a rawTx
|
@param {Function} opts.signTransaction - ethTx signer that returns a rawTx
|
||||||
@param {number} [opts.txHistoryLimit] - number *optional* for limiting how many transactions are in state
|
@param {number} [opts.txHistoryLimit] - number *optional* for limiting how many transactions are in state
|
||||||
@param {Object} opts.preferencesStore
|
@param {Object} opts.preferencesStore
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default class TransactionController extends EventEmitter {
|
export default class TransactionController extends EventEmitter {
|
||||||
constructor (opts) {
|
constructor(opts) {
|
||||||
super()
|
super()
|
||||||
this.networkStore = opts.networkStore || new ObservableStore({})
|
this.networkStore = opts.networkStore || new ObservableStore({})
|
||||||
this._getCurrentChainId = opts.getCurrentChainId
|
this._getCurrentChainId = opts.getCurrentChainId
|
||||||
@ -95,8 +90,12 @@ export default class TransactionController extends EventEmitter {
|
|||||||
this.nonceTracker = new NonceTracker({
|
this.nonceTracker = new NonceTracker({
|
||||||
provider: this.provider,
|
provider: this.provider,
|
||||||
blockTracker: this.blockTracker,
|
blockTracker: this.blockTracker,
|
||||||
getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
|
getPendingTransactions: this.txStateManager.getPendingTransactions.bind(
|
||||||
getConfirmedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager),
|
this.txStateManager,
|
||||||
|
),
|
||||||
|
getConfirmedTransactions: this.txStateManager.getConfirmedTransactions.bind(
|
||||||
|
this.txStateManager,
|
||||||
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
this.pendingTxTracker = new PendingTransactionTracker({
|
this.pendingTxTracker = new PendingTransactionTracker({
|
||||||
@ -109,7 +108,9 @@ export default class TransactionController extends EventEmitter {
|
|||||||
return [...pending, ...approved]
|
return [...pending, ...approved]
|
||||||
},
|
},
|
||||||
approveTransaction: this.approveTransaction.bind(this),
|
approveTransaction: this.approveTransaction.bind(this),
|
||||||
getCompletedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager),
|
getCompletedTransactions: this.txStateManager.getConfirmedTransactions.bind(
|
||||||
|
this.txStateManager,
|
||||||
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
this.txStateManager.store.subscribe(() => this.emit('update:badge'))
|
this.txStateManager.store.subscribe(() => this.emit('update:badge'))
|
||||||
@ -132,7 +133,7 @@ export default class TransactionController extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @returns {number} The numerical chainId.
|
* @returns {number} The numerical chainId.
|
||||||
*/
|
*/
|
||||||
getChainId () {
|
getChainId() {
|
||||||
const networkState = this.networkStore.getState()
|
const networkState = this.networkStore.getState()
|
||||||
const chainId = this._getCurrentChainId()
|
const chainId = this._getCurrentChainId()
|
||||||
const integerChainId = parseInt(chainId, 16)
|
const integerChainId = parseInt(chainId, 16)
|
||||||
@ -146,7 +147,7 @@ export default class TransactionController extends EventEmitter {
|
|||||||
Adds a tx to the txlist
|
Adds a tx to the txlist
|
||||||
@emits ${txMeta.id}:unapproved
|
@emits ${txMeta.id}:unapproved
|
||||||
*/
|
*/
|
||||||
addTx (txMeta) {
|
addTx(txMeta) {
|
||||||
this.txStateManager.addTx(txMeta)
|
this.txStateManager.addTx(txMeta)
|
||||||
this.emit(`${txMeta.id}:unapproved`, txMeta)
|
this.emit(`${txMeta.id}:unapproved`, txMeta)
|
||||||
}
|
}
|
||||||
@ -155,37 +156,62 @@ export default class TransactionController extends EventEmitter {
|
|||||||
Wipes the transactions for a given account
|
Wipes the transactions for a given account
|
||||||
@param {string} address - hex string of the from address for txs being removed
|
@param {string} address - hex string of the from address for txs being removed
|
||||||
*/
|
*/
|
||||||
wipeTransactions (address) {
|
wipeTransactions(address) {
|
||||||
this.txStateManager.wipeTransactions(address)
|
this.txStateManager.wipeTransactions(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new unapproved transaction to the pipeline
|
* Add a new unapproved transaction to the pipeline
|
||||||
*
|
*
|
||||||
* @returns {Promise<string>} - the hash of the transaction after being submitted to the network
|
* @returns {Promise<string>} the hash of the transaction after being submitted to the network
|
||||||
* @param {Object} txParams - txParams for the transaction
|
* @param {Object} txParams - txParams for the transaction
|
||||||
* @param {Object} opts - with the key origin to put the origin on the txMeta
|
* @param {Object} opts - with the key origin to put the origin on the txMeta
|
||||||
*/
|
*/
|
||||||
async newUnapprovedTransaction (txParams, opts = {}) {
|
async newUnapprovedTransaction(txParams, opts = {}) {
|
||||||
|
log.debug(
|
||||||
|
`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`,
|
||||||
|
)
|
||||||
|
|
||||||
log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`)
|
const initialTxMeta = await this.addUnapprovedTransaction(
|
||||||
|
txParams,
|
||||||
const initialTxMeta = await this.addUnapprovedTransaction(txParams, opts.origin)
|
opts.origin,
|
||||||
|
)
|
||||||
|
|
||||||
// listen for tx completion (success, fail)
|
// listen for tx completion (success, fail)
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => {
|
this.txStateManager.once(
|
||||||
switch (finishedTxMeta.status) {
|
`${initialTxMeta.id}:finished`,
|
||||||
case 'submitted':
|
(finishedTxMeta) => {
|
||||||
return resolve(finishedTxMeta.hash)
|
switch (finishedTxMeta.status) {
|
||||||
case 'rejected':
|
case TRANSACTION_STATUSES.SUBMITTED:
|
||||||
return reject(cleanErrorStack(ethErrors.provider.userRejectedRequest('MetaMask Tx Signature: User denied transaction signature.')))
|
return resolve(finishedTxMeta.hash)
|
||||||
case 'failed':
|
case TRANSACTION_STATUSES.REJECTED:
|
||||||
return reject(cleanErrorStack(ethErrors.rpc.internal(finishedTxMeta.err.message)))
|
return reject(
|
||||||
default:
|
cleanErrorStack(
|
||||||
return reject(cleanErrorStack(ethErrors.rpc.internal(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`)))
|
ethErrors.provider.userRejectedRequest(
|
||||||
}
|
'MetaMask Tx Signature: User denied transaction signature.',
|
||||||
})
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
case TRANSACTION_STATUSES.FAILED:
|
||||||
|
return reject(
|
||||||
|
cleanErrorStack(
|
||||||
|
ethErrors.rpc.internal(finishedTxMeta.err.message),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return reject(
|
||||||
|
cleanErrorStack(
|
||||||
|
ethErrors.rpc.internal(
|
||||||
|
`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(
|
||||||
|
finishedTxMeta.txParams,
|
||||||
|
)}`,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,8 +221,7 @@ export default class TransactionController extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @returns {txMeta}
|
* @returns {txMeta}
|
||||||
*/
|
*/
|
||||||
async addUnapprovedTransaction (txParams, origin) {
|
async addUnapprovedTransaction(txParams, origin) {
|
||||||
|
|
||||||
// validate
|
// validate
|
||||||
const normalizedTxParams = txUtils.normalizeTxParams(txParams)
|
const normalizedTxParams = txUtils.normalizeTxParams(txParams)
|
||||||
|
|
||||||
@ -210,7 +235,7 @@ export default class TransactionController extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
let txMeta = this.txStateManager.generateTxMeta({
|
let txMeta = this.txStateManager.generateTxMeta({
|
||||||
txParams: normalizedTxParams,
|
txParams: normalizedTxParams,
|
||||||
type: TRANSACTION_TYPE_STANDARD,
|
type: TRANSACTION_TYPES.STANDARD,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (origin === 'metamask') {
|
if (origin === 'metamask') {
|
||||||
@ -236,12 +261,15 @@ export default class TransactionController extends EventEmitter {
|
|||||||
|
|
||||||
txMeta.origin = origin
|
txMeta.origin = origin
|
||||||
|
|
||||||
const { transactionCategory, getCodeResponse } = await this._determineTransactionCategory(txParams)
|
const {
|
||||||
|
transactionCategory,
|
||||||
|
getCodeResponse,
|
||||||
|
} = await this._determineTransactionCategory(txParams)
|
||||||
txMeta.transactionCategory = transactionCategory
|
txMeta.transactionCategory = transactionCategory
|
||||||
|
|
||||||
// ensure value
|
// ensure value
|
||||||
txMeta.txParams.value = txMeta.txParams.value
|
txMeta.txParams.value = txMeta.txParams.value
|
||||||
? ethUtil.addHexPrefix(txMeta.txParams.value)
|
? addHexPrefix(txMeta.txParams.value)
|
||||||
: '0x0'
|
: '0x0'
|
||||||
|
|
||||||
this.addTx(txMeta)
|
this.addTx(txMeta)
|
||||||
@ -267,11 +295,14 @@ export default class TransactionController extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
* Adds the tx gas defaults: gas && gasPrice
|
* Adds the tx gas defaults: gas && gasPrice
|
||||||
* @param {Object} txMeta - the txMeta object
|
* @param {Object} txMeta - the txMeta object
|
||||||
* @returns {Promise<object>} - resolves with txMeta
|
* @returns {Promise<object>} resolves with txMeta
|
||||||
*/
|
*/
|
||||||
async addTxGasDefaults (txMeta, getCodeResponse) {
|
async addTxGasDefaults(txMeta, getCodeResponse) {
|
||||||
const defaultGasPrice = await this._getDefaultGasPrice(txMeta)
|
const defaultGasPrice = await this._getDefaultGasPrice(txMeta)
|
||||||
const { gasLimit: defaultGasLimit, simulationFails } = await this._getDefaultGasLimit(txMeta, getCodeResponse)
|
const {
|
||||||
|
gasLimit: defaultGasLimit,
|
||||||
|
simulationFails,
|
||||||
|
} = await this._getDefaultGasLimit(txMeta, getCodeResponse)
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
txMeta = this.txStateManager.getTx(txMeta.id)
|
txMeta = this.txStateManager.getTx(txMeta.id)
|
||||||
@ -292,13 +323,13 @@ export default class TransactionController extends EventEmitter {
|
|||||||
* @param {Object} txMeta - The txMeta object
|
* @param {Object} txMeta - The txMeta object
|
||||||
* @returns {Promise<string|undefined>} The default gas price
|
* @returns {Promise<string|undefined>} The default gas price
|
||||||
*/
|
*/
|
||||||
async _getDefaultGasPrice (txMeta) {
|
async _getDefaultGasPrice(txMeta) {
|
||||||
if (txMeta.txParams.gasPrice) {
|
if (txMeta.txParams.gasPrice) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
const gasPrice = await this.query.gasPrice()
|
const gasPrice = await this.query.gasPrice()
|
||||||
|
|
||||||
return ethUtil.addHexPrefix(gasPrice.toString(16))
|
return addHexPrefix(gasPrice.toString(16))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -307,16 +338,18 @@ export default class TransactionController extends EventEmitter {
|
|||||||
* @param {string} getCodeResponse - The transaction category code response, used for debugging purposes
|
* @param {string} getCodeResponse - The transaction category code response, used for debugging purposes
|
||||||
* @returns {Promise<Object>} Object containing the default gas limit, or the simulation failure object
|
* @returns {Promise<Object>} Object containing the default gas limit, or the simulation failure object
|
||||||
*/
|
*/
|
||||||
async _getDefaultGasLimit (txMeta, getCodeResponse) {
|
async _getDefaultGasLimit(txMeta, getCodeResponse) {
|
||||||
if (txMeta.txParams.gas) {
|
if (txMeta.txParams.gas) {
|
||||||
return {}
|
return {}
|
||||||
} else if (
|
} else if (
|
||||||
txMeta.txParams.to &&
|
txMeta.txParams.to &&
|
||||||
txMeta.transactionCategory === SEND_ETHER_ACTION_KEY
|
txMeta.transactionCategory === TRANSACTION_CATEGORIES.SENT_ETHER
|
||||||
) {
|
) {
|
||||||
// if there's data in the params, but there's no contract code, it's not a valid transaction
|
// if there's data in the params, but there's no contract code, it's not a valid transaction
|
||||||
if (txMeta.txParams.data) {
|
if (txMeta.txParams.data) {
|
||||||
const err = new Error('TxGasUtil - Trying to call a function on a non-contract address')
|
const err = new Error(
|
||||||
|
'TxGasUtil - Trying to call a function on a non-contract address',
|
||||||
|
)
|
||||||
// set error key so ui can display localized error message
|
// set error key so ui can display localized error message
|
||||||
err.errorKey = TRANSACTION_NO_CONTRACT_ERROR_KEY
|
err.errorKey = TRANSACTION_NO_CONTRACT_ERROR_KEY
|
||||||
|
|
||||||
@ -329,10 +362,17 @@ export default class TransactionController extends EventEmitter {
|
|||||||
return { gasLimit: SIMPLE_GAS_COST }
|
return { gasLimit: SIMPLE_GAS_COST }
|
||||||
}
|
}
|
||||||
|
|
||||||
const { blockGasLimit, estimatedGasHex, simulationFails } = await this.txGasUtil.analyzeGasUsage(txMeta)
|
const {
|
||||||
|
blockGasLimit,
|
||||||
|
estimatedGasHex,
|
||||||
|
simulationFails,
|
||||||
|
} = await this.txGasUtil.analyzeGasUsage(txMeta)
|
||||||
|
|
||||||
// add additional gas buffer to our estimation for safety
|
// add additional gas buffer to our estimation for safety
|
||||||
const gasLimit = this.txGasUtil.addGasBuffer(ethUtil.addHexPrefix(estimatedGasHex), blockGasLimit)
|
const gasLimit = this.txGasUtil.addGasBuffer(
|
||||||
|
addHexPrefix(estimatedGasHex),
|
||||||
|
blockGasLimit,
|
||||||
|
)
|
||||||
return { gasLimit, simulationFails }
|
return { gasLimit, simulationFails }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,12 +384,14 @@ export default class TransactionController extends EventEmitter {
|
|||||||
* @param {string} [customGasPrice] - the hex value to use for the cancel transaction
|
* @param {string} [customGasPrice] - the hex value to use for the cancel transaction
|
||||||
* @returns {txMeta}
|
* @returns {txMeta}
|
||||||
*/
|
*/
|
||||||
async createCancelTransaction (originalTxId, customGasPrice) {
|
async createCancelTransaction(originalTxId, customGasPrice) {
|
||||||
const originalTxMeta = this.txStateManager.getTx(originalTxId)
|
const originalTxMeta = this.txStateManager.getTx(originalTxId)
|
||||||
const { txParams } = originalTxMeta
|
const { txParams } = originalTxMeta
|
||||||
const { gasPrice: lastGasPrice, from, nonce } = txParams
|
const { gasPrice: lastGasPrice, from, nonce } = txParams
|
||||||
|
|
||||||
const newGasPrice = customGasPrice || bnToHex(BnMultiplyByFraction(hexToBn(lastGasPrice), 11, 10))
|
const newGasPrice =
|
||||||
|
customGasPrice ||
|
||||||
|
bnToHex(BnMultiplyByFraction(hexToBn(lastGasPrice), 11, 10))
|
||||||
const newTxMeta = this.txStateManager.generateTxMeta({
|
const newTxMeta = this.txStateManager.generateTxMeta({
|
||||||
txParams: {
|
txParams: {
|
||||||
from,
|
from,
|
||||||
@ -361,8 +403,8 @@ export default class TransactionController extends EventEmitter {
|
|||||||
},
|
},
|
||||||
lastGasPrice,
|
lastGasPrice,
|
||||||
loadingDefaults: false,
|
loadingDefaults: false,
|
||||||
status: TRANSACTION_STATUS_APPROVED,
|
status: TRANSACTION_STATUSES.APPROVED,
|
||||||
type: TRANSACTION_TYPE_CANCEL,
|
type: TRANSACTION_TYPES.CANCEL,
|
||||||
})
|
})
|
||||||
|
|
||||||
this.addTx(newTxMeta)
|
this.addTx(newTxMeta)
|
||||||
@ -380,12 +422,14 @@ export default class TransactionController extends EventEmitter {
|
|||||||
* @param {string} [customGasLimit] - The new custom gas limt, in hex
|
* @param {string} [customGasLimit] - The new custom gas limt, in hex
|
||||||
* @returns {txMeta}
|
* @returns {txMeta}
|
||||||
*/
|
*/
|
||||||
async createSpeedUpTransaction (originalTxId, customGasPrice, customGasLimit) {
|
async createSpeedUpTransaction(originalTxId, customGasPrice, customGasLimit) {
|
||||||
const originalTxMeta = this.txStateManager.getTx(originalTxId)
|
const originalTxMeta = this.txStateManager.getTx(originalTxId)
|
||||||
const { txParams } = originalTxMeta
|
const { txParams } = originalTxMeta
|
||||||
const { gasPrice: lastGasPrice } = txParams
|
const { gasPrice: lastGasPrice } = txParams
|
||||||
|
|
||||||
const newGasPrice = customGasPrice || bnToHex(BnMultiplyByFraction(hexToBn(lastGasPrice), 11, 10))
|
const newGasPrice =
|
||||||
|
customGasPrice ||
|
||||||
|
bnToHex(BnMultiplyByFraction(hexToBn(lastGasPrice), 11, 10))
|
||||||
|
|
||||||
const newTxMeta = this.txStateManager.generateTxMeta({
|
const newTxMeta = this.txStateManager.generateTxMeta({
|
||||||
txParams: {
|
txParams: {
|
||||||
@ -394,8 +438,8 @@ export default class TransactionController extends EventEmitter {
|
|||||||
},
|
},
|
||||||
lastGasPrice,
|
lastGasPrice,
|
||||||
loadingDefaults: false,
|
loadingDefaults: false,
|
||||||
status: TRANSACTION_STATUS_APPROVED,
|
status: TRANSACTION_STATUSES.APPROVED,
|
||||||
type: TRANSACTION_TYPE_RETRY,
|
type: TRANSACTION_TYPES.RETRY,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (customGasLimit) {
|
if (customGasLimit) {
|
||||||
@ -411,7 +455,7 @@ export default class TransactionController extends EventEmitter {
|
|||||||
updates the txMeta in the txStateManager
|
updates the txMeta in the txStateManager
|
||||||
@param {Object} txMeta - the updated txMeta
|
@param {Object} txMeta - the updated txMeta
|
||||||
*/
|
*/
|
||||||
async updateTransaction (txMeta) {
|
async updateTransaction(txMeta) {
|
||||||
this.txStateManager.updateTx(txMeta, 'confTx: user updated transaction')
|
this.txStateManager.updateTx(txMeta, 'confTx: user updated transaction')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -419,7 +463,7 @@ export default class TransactionController extends EventEmitter {
|
|||||||
updates and approves the transaction
|
updates and approves the transaction
|
||||||
@param {Object} txMeta
|
@param {Object} txMeta
|
||||||
*/
|
*/
|
||||||
async updateAndApproveTransaction (txMeta) {
|
async updateAndApproveTransaction(txMeta) {
|
||||||
this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction')
|
this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction')
|
||||||
await this.approveTransaction(txMeta.id)
|
await this.approveTransaction(txMeta.id)
|
||||||
}
|
}
|
||||||
@ -432,7 +476,7 @@ export default class TransactionController extends EventEmitter {
|
|||||||
if any of these steps fails the tx status will be set to failed
|
if any of these steps fails the tx status will be set to failed
|
||||||
@param {number} txId - the tx's Id
|
@param {number} txId - the tx's Id
|
||||||
*/
|
*/
|
||||||
async approveTransaction (txId) {
|
async approveTransaction(txId) {
|
||||||
// TODO: Move this safety out of this function.
|
// TODO: Move this safety out of this function.
|
||||||
// Since this transaction is async,
|
// Since this transaction is async,
|
||||||
// we need to keep track of what is currently being signed,
|
// we need to keep track of what is currently being signed,
|
||||||
@ -456,10 +500,13 @@ export default class TransactionController extends EventEmitter {
|
|||||||
// add nonce to txParams
|
// add nonce to txParams
|
||||||
// if txMeta has lastGasPrice then it is a retry at same nonce with higher
|
// if txMeta has lastGasPrice then it is a retry at same nonce with higher
|
||||||
// gas price transaction and their for the nonce should not be calculated
|
// gas price transaction and their for the nonce should not be calculated
|
||||||
const nonce = txMeta.lastGasPrice ? txMeta.txParams.nonce : nonceLock.nextNonce
|
const nonce = txMeta.lastGasPrice
|
||||||
const customOrNonce = (customNonceValue === 0) ? customNonceValue : customNonceValue || nonce
|
? txMeta.txParams.nonce
|
||||||
|
: nonceLock.nextNonce
|
||||||
|
const customOrNonce =
|
||||||
|
customNonceValue === 0 ? customNonceValue : customNonceValue || nonce
|
||||||
|
|
||||||
txMeta.txParams.nonce = ethUtil.addHexPrefix(customOrNonce.toString(16))
|
txMeta.txParams.nonce = addHexPrefix(customOrNonce.toString(16))
|
||||||
// add nonce debugging information to txMeta
|
// add nonce debugging information to txMeta
|
||||||
txMeta.nonceDetails = nonceLock.nonceDetails
|
txMeta.nonceDetails = nonceLock.nonceDetails
|
||||||
if (customNonceValue) {
|
if (customNonceValue) {
|
||||||
@ -492,9 +539,9 @@ export default class TransactionController extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
adds the chain id and signs the transaction and set the status to signed
|
adds the chain id and signs the transaction and set the status to signed
|
||||||
@param {number} txId - the tx's Id
|
@param {number} txId - the tx's Id
|
||||||
@returns {string} - rawTx
|
@returns {string} rawTx
|
||||||
*/
|
*/
|
||||||
async signTransaction (txId) {
|
async signTransaction(txId) {
|
||||||
const txMeta = this.txStateManager.getTx(txId)
|
const txMeta = this.txStateManager.getTx(txId)
|
||||||
// add network/chain id
|
// add network/chain id
|
||||||
const chainId = this.getChainId()
|
const chainId = this.getChainId()
|
||||||
@ -510,7 +557,10 @@ export default class TransactionController extends EventEmitter {
|
|||||||
txMeta.s = ethUtil.bufferToHex(ethTx.s)
|
txMeta.s = ethUtil.bufferToHex(ethTx.s)
|
||||||
txMeta.v = ethUtil.bufferToHex(ethTx.v)
|
txMeta.v = ethUtil.bufferToHex(ethTx.v)
|
||||||
|
|
||||||
this.txStateManager.updateTx(txMeta, 'transactions#signTransaction: add r, s, v values')
|
this.txStateManager.updateTx(
|
||||||
|
txMeta,
|
||||||
|
'transactions#signTransaction: add r, s, v values',
|
||||||
|
)
|
||||||
|
|
||||||
// set state to signed
|
// set state to signed
|
||||||
this.txStateManager.setTxStatusSigned(txMeta.id)
|
this.txStateManager.setTxStatusSigned(txMeta.id)
|
||||||
@ -524,10 +574,10 @@ export default class TransactionController extends EventEmitter {
|
|||||||
@param {string} rawTx - the hex string of the serialized signed transaction
|
@param {string} rawTx - the hex string of the serialized signed transaction
|
||||||
@returns {Promise<void>}
|
@returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async publishTransaction (txId, rawTx) {
|
async publishTransaction(txId, rawTx) {
|
||||||
const txMeta = this.txStateManager.getTx(txId)
|
const txMeta = this.txStateManager.getTx(txId)
|
||||||
txMeta.rawTx = rawTx
|
txMeta.rawTx = rawTx
|
||||||
if (txMeta.transactionCategory === SWAP) {
|
if (txMeta.transactionCategory === TRANSACTION_CATEGORIES.SWAP) {
|
||||||
const preTxBalance = await this.query.getBalance(txMeta.txParams.from)
|
const preTxBalance = await this.query.getBalance(txMeta.txParams.from)
|
||||||
txMeta.preTxBalance = preTxBalance.toString(16)
|
txMeta.preTxBalance = preTxBalance.toString(16)
|
||||||
}
|
}
|
||||||
@ -537,8 +587,8 @@ export default class TransactionController extends EventEmitter {
|
|||||||
txHash = await this.query.sendRawTransaction(rawTx)
|
txHash = await this.query.sendRawTransaction(rawTx)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.message.toLowerCase().includes('known transaction')) {
|
if (error.message.toLowerCase().includes('known transaction')) {
|
||||||
txHash = ethUtil.sha3(ethUtil.addHexPrefix(rawTx)).toString('hex')
|
txHash = ethUtil.sha3(addHexPrefix(rawTx)).toString('hex')
|
||||||
txHash = ethUtil.addHexPrefix(txHash)
|
txHash = addHexPrefix(txHash)
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
@ -554,7 +604,7 @@ export default class TransactionController extends EventEmitter {
|
|||||||
* @param {number} txId - The tx's ID
|
* @param {number} txId - The tx's ID
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async confirmTransaction (txId, txReceipt) {
|
async confirmTransaction(txId, txReceipt) {
|
||||||
// get the txReceipt before marking the transaction confirmed
|
// get the txReceipt before marking the transaction confirmed
|
||||||
// to ensure the receipt is gotten before the ui revives the tx
|
// to ensure the receipt is gotten before the ui revives the tx
|
||||||
const txMeta = this.txStateManager.getTx(txId)
|
const txMeta = this.txStateManager.getTx(txId)
|
||||||
@ -566,9 +616,10 @@ export default class TransactionController extends EventEmitter {
|
|||||||
try {
|
try {
|
||||||
// It seems that sometimes the numerical values being returned from
|
// It seems that sometimes the numerical values being returned from
|
||||||
// this.query.getTransactionReceipt are BN instances and not strings.
|
// this.query.getTransactionReceipt are BN instances and not strings.
|
||||||
const gasUsed = typeof txReceipt.gasUsed === 'string'
|
const gasUsed =
|
||||||
? txReceipt.gasUsed
|
typeof txReceipt.gasUsed === 'string'
|
||||||
: txReceipt.gasUsed.toString(16)
|
? txReceipt.gasUsed
|
||||||
|
: txReceipt.gasUsed.toString(16)
|
||||||
|
|
||||||
txMeta.txReceipt = {
|
txMeta.txReceipt = {
|
||||||
...txReceipt,
|
...txReceipt,
|
||||||
@ -577,9 +628,12 @@ export default class TransactionController extends EventEmitter {
|
|||||||
this.txStateManager.setTxStatusConfirmed(txId)
|
this.txStateManager.setTxStatusConfirmed(txId)
|
||||||
this._markNonceDuplicatesDropped(txId)
|
this._markNonceDuplicatesDropped(txId)
|
||||||
|
|
||||||
this.txStateManager.updateTx(txMeta, 'transactions#confirmTransaction - add txReceipt')
|
this.txStateManager.updateTx(
|
||||||
|
txMeta,
|
||||||
|
'transactions#confirmTransaction - add txReceipt',
|
||||||
|
)
|
||||||
|
|
||||||
if (txMeta.transactionCategory === SWAP) {
|
if (txMeta.transactionCategory === TRANSACTION_CATEGORIES.SWAP) {
|
||||||
const postTxBalance = await this.query.getBalance(txMeta.txParams.from)
|
const postTxBalance = await this.query.getBalance(txMeta.txParams.from)
|
||||||
const latestTxMeta = this.txStateManager.getTx(txId)
|
const latestTxMeta = this.txStateManager.getTx(txId)
|
||||||
|
|
||||||
@ -589,11 +643,13 @@ export default class TransactionController extends EventEmitter {
|
|||||||
|
|
||||||
latestTxMeta.postTxBalance = postTxBalance.toString(16)
|
latestTxMeta.postTxBalance = postTxBalance.toString(16)
|
||||||
|
|
||||||
this.txStateManager.updateTx(latestTxMeta, 'transactions#confirmTransaction - add postTxBalance')
|
this.txStateManager.updateTx(
|
||||||
|
latestTxMeta,
|
||||||
|
'transactions#confirmTransaction - add postTxBalance',
|
||||||
|
)
|
||||||
|
|
||||||
this._trackSwapsMetrics(latestTxMeta, approvalTxMeta)
|
this._trackSwapsMetrics(latestTxMeta, approvalTxMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error(err)
|
log.error(err)
|
||||||
}
|
}
|
||||||
@ -604,7 +660,7 @@ export default class TransactionController extends EventEmitter {
|
|||||||
@param {number} txId - the tx's Id
|
@param {number} txId - the tx's Id
|
||||||
@returns {Promise<void>}
|
@returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async cancelTransaction (txId) {
|
async cancelTransaction(txId) {
|
||||||
this.txStateManager.setTxStatusRejected(txId)
|
this.txStateManager.setTxStatusRejected(txId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -613,7 +669,7 @@ export default class TransactionController extends EventEmitter {
|
|||||||
@param {number} txId - the tx's Id
|
@param {number} txId - the tx's Id
|
||||||
@param {string} txHash - the hash for the txMeta
|
@param {string} txHash - the hash for the txMeta
|
||||||
*/
|
*/
|
||||||
setTxHash (txId, txHash) {
|
setTxHash(txId, txHash) {
|
||||||
// Add the tx hash to the persisted meta-tx object
|
// Add the tx hash to the persisted meta-tx object
|
||||||
const txMeta = this.txStateManager.getTx(txId)
|
const txMeta = this.txStateManager.getTx(txId)
|
||||||
txMeta.hash = txHash
|
txMeta.hash = txHash
|
||||||
@ -624,32 +680,35 @@ export default class TransactionController extends EventEmitter {
|
|||||||
// PRIVATE METHODS
|
// PRIVATE METHODS
|
||||||
//
|
//
|
||||||
/** maps methods for convenience*/
|
/** maps methods for convenience*/
|
||||||
_mapMethods () {
|
_mapMethods() {
|
||||||
|
/** @returns {Object} the state in transaction controller */
|
||||||
/** @returns {Object} - the state in transaction controller */
|
|
||||||
this.getState = () => this.memStore.getState()
|
this.getState = () => this.memStore.getState()
|
||||||
|
|
||||||
/** @returns {string|number} - the network number stored in networkStore */
|
/** @returns {string|number} the network number stored in networkStore */
|
||||||
this.getNetwork = () => this.networkStore.getState()
|
this.getNetwork = () => this.networkStore.getState()
|
||||||
|
|
||||||
/** @returns {string} - the user selected address */
|
/** @returns {string} the user selected address */
|
||||||
this.getSelectedAddress = () => this.preferencesStore.getState().selectedAddress
|
this.getSelectedAddress = () =>
|
||||||
|
this.preferencesStore.getState().selectedAddress
|
||||||
|
|
||||||
/** @returns {array} - transactions whos status is unapproved */
|
/** @returns {Array} transactions whos status is unapproved */
|
||||||
this.getUnapprovedTxCount = () => Object.keys(this.txStateManager.getUnapprovedTxList()).length
|
this.getUnapprovedTxCount = () =>
|
||||||
|
Object.keys(this.txStateManager.getUnapprovedTxList()).length
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@returns {number} - number of transactions that have the status submitted
|
@returns {number} number of transactions that have the status submitted
|
||||||
@param {string} account - hex prefixed account
|
@param {string} account - hex prefixed account
|
||||||
*/
|
*/
|
||||||
this.getPendingTxCount = (account) => this.txStateManager.getPendingTransactions(account).length
|
this.getPendingTxCount = (account) =>
|
||||||
|
this.txStateManager.getPendingTransactions(account).length
|
||||||
|
|
||||||
/** see txStateManager */
|
/** see txStateManager */
|
||||||
this.getFilteredTxList = (opts) => this.txStateManager.getFilteredTxList(opts)
|
this.getFilteredTxList = (opts) =>
|
||||||
|
this.txStateManager.getFilteredTxList(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// called once on startup
|
// called once on startup
|
||||||
async _updatePendingTxsAfterFirstBlock () {
|
async _updatePendingTxsAfterFirstBlock() {
|
||||||
// wait for first block so we know we're ready
|
// wait for first block so we know we're ready
|
||||||
await this.blockTracker.getLatestBlock()
|
await this.blockTracker.getLatestBlock()
|
||||||
// get status update for all pending transactions (for the current network)
|
// get status update for all pending transactions (for the current network)
|
||||||
@ -662,49 +721,78 @@ export default class TransactionController extends EventEmitter {
|
|||||||
transition txMetas to a failed state or try to redo those tasks.
|
transition txMetas to a failed state or try to redo those tasks.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
_onBootCleanUp () {
|
_onBootCleanUp() {
|
||||||
this.txStateManager.getFilteredTxList({
|
this.txStateManager
|
||||||
status: 'unapproved',
|
.getFilteredTxList({
|
||||||
loadingDefaults: true,
|
status: TRANSACTION_STATUSES.UNAPPROVED,
|
||||||
}).forEach((tx) => {
|
loadingDefaults: true,
|
||||||
|
})
|
||||||
|
.forEach((tx) => {
|
||||||
|
this.addTxGasDefaults(tx)
|
||||||
|
.then((txMeta) => {
|
||||||
|
txMeta.loadingDefaults = false
|
||||||
|
this.txStateManager.updateTx(
|
||||||
|
txMeta,
|
||||||
|
'transactions: gas estimation for tx on boot',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
const txMeta = this.txStateManager.getTx(tx.id)
|
||||||
|
txMeta.loadingDefaults = false
|
||||||
|
this.txStateManager.updateTx(
|
||||||
|
txMeta,
|
||||||
|
'failed to estimate gas during boot cleanup.',
|
||||||
|
)
|
||||||
|
this.txStateManager.setTxStatusFailed(txMeta.id, error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
this.addTxGasDefaults(tx)
|
this.txStateManager
|
||||||
.then((txMeta) => {
|
.getFilteredTxList({
|
||||||
txMeta.loadingDefaults = false
|
status: TRANSACTION_STATUSES.APPROVED,
|
||||||
this.txStateManager.updateTx(txMeta, 'transactions: gas estimation for tx on boot')
|
})
|
||||||
}).catch((error) => {
|
.forEach((txMeta) => {
|
||||||
const txMeta = this.txStateManager.getTx(tx.id)
|
const txSignError = new Error(
|
||||||
txMeta.loadingDefaults = false
|
'Transaction found as "approved" during boot - possibly stuck during signing',
|
||||||
this.txStateManager.updateTx(txMeta, 'failed to estimate gas during boot cleanup.')
|
)
|
||||||
this.txStateManager.setTxStatusFailed(txMeta.id, error)
|
this.txStateManager.setTxStatusFailed(txMeta.id, txSignError)
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
this.txStateManager.getFilteredTxList({
|
|
||||||
status: TRANSACTION_STATUS_APPROVED,
|
|
||||||
}).forEach((txMeta) => {
|
|
||||||
const txSignError = new Error('Transaction found as "approved" during boot - possibly stuck during signing')
|
|
||||||
this.txStateManager.setTxStatusFailed(txMeta.id, txSignError)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
is called in constructor applies the listeners for pendingTxTracker txStateManager
|
is called in constructor applies the listeners for pendingTxTracker txStateManager
|
||||||
and blockTracker
|
and blockTracker
|
||||||
*/
|
*/
|
||||||
_setupListeners () {
|
_setupListeners() {
|
||||||
this.txStateManager.on('tx:status-update', this.emit.bind(this, 'tx:status-update'))
|
this.txStateManager.on(
|
||||||
|
'tx:status-update',
|
||||||
|
this.emit.bind(this, 'tx:status-update'),
|
||||||
|
)
|
||||||
this._setupBlockTrackerListener()
|
this._setupBlockTrackerListener()
|
||||||
this.pendingTxTracker.on('tx:warning', (txMeta) => {
|
this.pendingTxTracker.on('tx:warning', (txMeta) => {
|
||||||
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning')
|
this.txStateManager.updateTx(
|
||||||
|
txMeta,
|
||||||
|
'transactions/pending-tx-tracker#event: tx:warning',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager))
|
this.pendingTxTracker.on(
|
||||||
this.pendingTxTracker.on('tx:confirmed', (txId, transactionReceipt) => this.confirmTransaction(txId, transactionReceipt))
|
'tx:failed',
|
||||||
this.pendingTxTracker.on('tx:dropped', this.txStateManager.setTxStatusDropped.bind(this.txStateManager))
|
this.txStateManager.setTxStatusFailed.bind(this.txStateManager),
|
||||||
|
)
|
||||||
|
this.pendingTxTracker.on('tx:confirmed', (txId, transactionReceipt) =>
|
||||||
|
this.confirmTransaction(txId, transactionReceipt),
|
||||||
|
)
|
||||||
|
this.pendingTxTracker.on(
|
||||||
|
'tx:dropped',
|
||||||
|
this.txStateManager.setTxStatusDropped.bind(this.txStateManager),
|
||||||
|
)
|
||||||
this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => {
|
this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => {
|
||||||
if (!txMeta.firstRetryBlockNumber) {
|
if (!txMeta.firstRetryBlockNumber) {
|
||||||
txMeta.firstRetryBlockNumber = latestBlockNumber
|
txMeta.firstRetryBlockNumber = latestBlockNumber
|
||||||
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:block-update')
|
this.txStateManager.updateTx(
|
||||||
|
txMeta,
|
||||||
|
'transactions/pending-tx-tracker#event: tx:block-update',
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.pendingTxTracker.on('tx:retry', (txMeta) => {
|
this.pendingTxTracker.on('tx:retry', (txMeta) => {
|
||||||
@ -712,7 +800,10 @@ export default class TransactionController extends EventEmitter {
|
|||||||
txMeta.retryCount = 0
|
txMeta.retryCount = 0
|
||||||
}
|
}
|
||||||
txMeta.retryCount += 1
|
txMeta.retryCount += 1
|
||||||
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:retry')
|
this.txStateManager.updateTx(
|
||||||
|
txMeta,
|
||||||
|
'transactions/pending-tx-tracker#event: tx:retry',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -720,7 +811,7 @@ export default class TransactionController extends EventEmitter {
|
|||||||
Returns a "type" for a transaction out of the following list: simpleSend, tokenTransfer, tokenApprove,
|
Returns a "type" for a transaction out of the following list: simpleSend, tokenTransfer, tokenApprove,
|
||||||
contractDeployment, contractMethodCall
|
contractDeployment, contractMethodCall
|
||||||
*/
|
*/
|
||||||
async _determineTransactionCategory (txParams) {
|
async _determineTransactionCategory(txParams) {
|
||||||
const { data, to } = txParams
|
const { data, to } = txParams
|
||||||
let name
|
let name
|
||||||
try {
|
try {
|
||||||
@ -730,16 +821,16 @@ export default class TransactionController extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tokenMethodName = [
|
const tokenMethodName = [
|
||||||
TOKEN_METHOD_APPROVE,
|
TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE,
|
||||||
TOKEN_METHOD_TRANSFER,
|
TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER,
|
||||||
TOKEN_METHOD_TRANSFER_FROM,
|
TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER_FROM,
|
||||||
].find((methodName) => methodName === name && name.toLowerCase())
|
].find((methodName) => methodName === name && name.toLowerCase())
|
||||||
|
|
||||||
let result
|
let result
|
||||||
if (txParams.data && tokenMethodName) {
|
if (txParams.data && tokenMethodName) {
|
||||||
result = tokenMethodName
|
result = tokenMethodName
|
||||||
} else if (txParams.data && !to) {
|
} else if (txParams.data && !to) {
|
||||||
result = DEPLOY_CONTRACT_ACTION_KEY
|
result = TRANSACTION_CATEGORIES.DEPLOY_CONTRACT
|
||||||
}
|
}
|
||||||
|
|
||||||
let code
|
let code
|
||||||
@ -753,7 +844,9 @@ export default class TransactionController extends EventEmitter {
|
|||||||
|
|
||||||
const codeIsEmpty = !code || code === '0x' || code === '0x0'
|
const codeIsEmpty = !code || code === '0x' || code === '0x0'
|
||||||
|
|
||||||
result = codeIsEmpty ? SEND_ETHER_ACTION_KEY : CONTRACT_INTERACTION_KEY
|
result = codeIsEmpty
|
||||||
|
? TRANSACTION_CATEGORIES.SENT_ETHER
|
||||||
|
: TRANSACTION_CATEGORIES.CONTRACT_INTERACTION
|
||||||
}
|
}
|
||||||
|
|
||||||
return { transactionCategory: result, getCodeResponse: code }
|
return { transactionCategory: result, getCodeResponse: code }
|
||||||
@ -765,7 +858,7 @@ export default class TransactionController extends EventEmitter {
|
|||||||
|
|
||||||
@param {number} txId - the txId of the transaction that has been confirmed in a block
|
@param {number} txId - the txId of the transaction that has been confirmed in a block
|
||||||
*/
|
*/
|
||||||
_markNonceDuplicatesDropped (txId) {
|
_markNonceDuplicatesDropped(txId) {
|
||||||
// get the confirmed transactions nonce and from address
|
// get the confirmed transactions nonce and from address
|
||||||
const txMeta = this.txStateManager.getTx(txId)
|
const txMeta = this.txStateManager.getTx(txId)
|
||||||
const { nonce, from } = txMeta.txParams
|
const { nonce, from } = txMeta.txParams
|
||||||
@ -779,12 +872,15 @@ export default class TransactionController extends EventEmitter {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
otherTxMeta.replacedBy = txMeta.hash
|
otherTxMeta.replacedBy = txMeta.hash
|
||||||
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:confirmed reference to confirmed txHash with same nonce')
|
this.txStateManager.updateTx(
|
||||||
|
txMeta,
|
||||||
|
'transactions/pending-tx-tracker#event: tx:confirmed reference to confirmed txHash with same nonce',
|
||||||
|
)
|
||||||
this.txStateManager.setTxStatusDropped(otherTxMeta.id)
|
this.txStateManager.setTxStatusDropped(otherTxMeta.id)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
_setupBlockTrackerListener () {
|
_setupBlockTrackerListener() {
|
||||||
let listenersAreActive = false
|
let listenersAreActive = false
|
||||||
const latestBlockHandler = this._onLatestBlock.bind(this)
|
const latestBlockHandler = this._onLatestBlock.bind(this)
|
||||||
const { blockTracker, txStateManager } = this
|
const { blockTracker, txStateManager } = this
|
||||||
@ -792,7 +888,7 @@ export default class TransactionController extends EventEmitter {
|
|||||||
txStateManager.on('tx:status-update', updateSubscription)
|
txStateManager.on('tx:status-update', updateSubscription)
|
||||||
updateSubscription()
|
updateSubscription()
|
||||||
|
|
||||||
function updateSubscription () {
|
function updateSubscription() {
|
||||||
const pendingTxs = txStateManager.getPendingTransactions()
|
const pendingTxs = txStateManager.getPendingTransactions()
|
||||||
if (!listenersAreActive && pendingTxs.length > 0) {
|
if (!listenersAreActive && pendingTxs.length > 0) {
|
||||||
blockTracker.on('latest', latestBlockHandler)
|
blockTracker.on('latest', latestBlockHandler)
|
||||||
@ -804,7 +900,7 @@ export default class TransactionController extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onLatestBlock (blockNumber) {
|
async _onLatestBlock(blockNumber) {
|
||||||
try {
|
try {
|
||||||
await this.pendingTxTracker.updatePendingTxs()
|
await this.pendingTxTracker.updatePendingTxs()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -820,13 +916,15 @@ export default class TransactionController extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
Updates the memStore in transaction controller
|
Updates the memStore in transaction controller
|
||||||
*/
|
*/
|
||||||
_updateMemstore () {
|
_updateMemstore() {
|
||||||
const unapprovedTxs = this.txStateManager.getUnapprovedTxList()
|
const unapprovedTxs = this.txStateManager.getUnapprovedTxList()
|
||||||
const currentNetworkTxList = this.txStateManager.getTxList(MAX_MEMSTORE_TX_LIST_SIZE)
|
const currentNetworkTxList = this.txStateManager.getTxList(
|
||||||
|
MAX_MEMSTORE_TX_LIST_SIZE,
|
||||||
|
)
|
||||||
this.memStore.updateState({ unapprovedTxs, currentNetworkTxList })
|
this.memStore.updateState({ unapprovedTxs, currentNetworkTxList })
|
||||||
}
|
}
|
||||||
|
|
||||||
_trackSwapsMetrics (txMeta, approvalTxMeta) {
|
_trackSwapsMetrics(txMeta, approvalTxMeta) {
|
||||||
if (this._getParticipateInMetrics() && txMeta.swapMetaData) {
|
if (this._getParticipateInMetrics() && txMeta.swapMetaData) {
|
||||||
if (txMeta.txReceipt.status === '0x0') {
|
if (txMeta.txReceipt.status === '0x0') {
|
||||||
this._trackMetaMetricsEvent({
|
this._trackMetaMetricsEvent({
|
||||||
@ -851,19 +949,18 @@ export default class TransactionController extends EventEmitter {
|
|||||||
approvalTxMeta,
|
approvalTxMeta,
|
||||||
)
|
)
|
||||||
|
|
||||||
const quoteVsExecutionRatio = `${
|
const quoteVsExecutionRatio = `${new BigNumber(tokensReceived, 10)
|
||||||
(new BigNumber(tokensReceived, 10))
|
.div(txMeta.swapMetaData.token_to_amount, 10)
|
||||||
.div(txMeta.swapMetaData.token_to_amount, 10)
|
.times(100)
|
||||||
.times(100)
|
.round(2)}%`
|
||||||
.round(2)
|
|
||||||
}%`
|
|
||||||
|
|
||||||
const estimatedVsUsedGasRatio = `${
|
const estimatedVsUsedGasRatio = `${new BigNumber(
|
||||||
(new BigNumber(txMeta.txReceipt.gasUsed, 16))
|
txMeta.txReceipt.gasUsed,
|
||||||
.div(txMeta.swapMetaData.estimated_gas, 10)
|
16,
|
||||||
.times(100)
|
)
|
||||||
.round(2)
|
.div(txMeta.swapMetaData.estimated_gas, 10)
|
||||||
}%`
|
.times(100)
|
||||||
|
.round(2)}%`
|
||||||
|
|
||||||
this._trackMetaMetricsEvent({
|
this._trackMetaMetricsEvent({
|
||||||
event: 'Swap Completed',
|
event: 'Swap Completed',
|
||||||
|
@ -3,13 +3,13 @@ import { cloneDeep } from 'lodash'
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
converts non-initial history entries into diffs
|
converts non-initial history entries into diffs
|
||||||
@param {array} longHistory
|
@param {Array} longHistory
|
||||||
@returns {array}
|
@returns {Array}
|
||||||
*/
|
*/
|
||||||
export function migrateFromSnapshotsToDiffs (longHistory) {
|
export function migrateFromSnapshotsToDiffs(longHistory) {
|
||||||
return (
|
return (
|
||||||
longHistory
|
longHistory
|
||||||
// convert non-initial history entries into diffs
|
// convert non-initial history entries into diffs
|
||||||
.map((entry, index) => {
|
.map((entry, index) => {
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
return entry
|
return entry
|
||||||
@ -29,9 +29,9 @@ export function migrateFromSnapshotsToDiffs (longHistory) {
|
|||||||
@param {Object} previousState - the previous state of the object
|
@param {Object} previousState - the previous state of the object
|
||||||
@param {Object} newState - the update object
|
@param {Object} newState - the update object
|
||||||
@param {string} [note] - a optional note for the state change
|
@param {string} [note] - a optional note for the state change
|
||||||
@returns {array}
|
@returns {Array}
|
||||||
*/
|
*/
|
||||||
export function generateHistoryEntry (previousState, newState, note) {
|
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
|
// Add a note to the first op, since it breaks if we append it to the entry
|
||||||
if (entry[0]) {
|
if (entry[0]) {
|
||||||
@ -48,9 +48,11 @@ export function generateHistoryEntry (previousState, newState, note) {
|
|||||||
Recovers previous txMeta state obj
|
Recovers previous txMeta state obj
|
||||||
@returns {Object}
|
@returns {Object}
|
||||||
*/
|
*/
|
||||||
export function replayHistory (_shortHistory) {
|
export function replayHistory(_shortHistory) {
|
||||||
const shortHistory = cloneDeep(_shortHistory)
|
const shortHistory = cloneDeep(_shortHistory)
|
||||||
return shortHistory.reduce((val, entry) => jsonDiffer.applyPatch(val, entry).newDocument)
|
return shortHistory.reduce(
|
||||||
|
(val, entry) => jsonDiffer.applyPatch(val, entry).newDocument,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,7 +60,7 @@ export function replayHistory (_shortHistory) {
|
|||||||
* @param {Object} txMeta - the tx metadata object
|
* @param {Object} txMeta - the tx metadata object
|
||||||
* @returns {Object} a deep clone without history
|
* @returns {Object} a deep clone without history
|
||||||
*/
|
*/
|
||||||
export function snapshotFromTxMeta (txMeta) {
|
export function snapshotFromTxMeta(txMeta) {
|
||||||
const shallow = { ...txMeta }
|
const shallow = { ...txMeta }
|
||||||
delete shallow.history
|
delete shallow.history
|
||||||
return cloneDeep(shallow)
|
return cloneDeep(shallow)
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import { addHexPrefix, isValidAddress } from 'ethereumjs-util'
|
import { isValidAddress } from 'ethereumjs-util'
|
||||||
|
import { addHexPrefix } from '../../../lib/util'
|
||||||
|
import { TRANSACTION_STATUSES } from '../../../../../shared/constants/transaction'
|
||||||
|
|
||||||
const normalizers = {
|
const normalizers = {
|
||||||
from: (from) => addHexPrefix(from),
|
from: (from) => addHexPrefix(from),
|
||||||
to: (to, lowerCase) => (lowerCase ? addHexPrefix(to).toLowerCase() : addHexPrefix(to)),
|
to: (to, lowerCase) =>
|
||||||
|
lowerCase ? addHexPrefix(to).toLowerCase() : addHexPrefix(to),
|
||||||
nonce: (nonce) => addHexPrefix(nonce),
|
nonce: (nonce) => addHexPrefix(nonce),
|
||||||
value: (value) => addHexPrefix(value),
|
value: (value) => addHexPrefix(value),
|
||||||
data: (data) => addHexPrefix(data),
|
data: (data) => addHexPrefix(data),
|
||||||
@ -17,7 +20,7 @@ const normalizers = {
|
|||||||
* Default: true
|
* Default: true
|
||||||
* @returns {Object} the normalized tx params
|
* @returns {Object} the normalized tx params
|
||||||
*/
|
*/
|
||||||
export function normalizeTxParams (txParams, lowerCase = true) {
|
export function normalizeTxParams(txParams, lowerCase = true) {
|
||||||
// apply only keys in the normalizers
|
// apply only keys in the normalizers
|
||||||
const normalizedTxParams = {}
|
const normalizedTxParams = {}
|
||||||
for (const key in normalizers) {
|
for (const key in normalizers) {
|
||||||
@ -33,17 +36,21 @@ export function normalizeTxParams (txParams, lowerCase = true) {
|
|||||||
* @param {Object} txParams - the tx params
|
* @param {Object} txParams - the tx params
|
||||||
* @throws {Error} if the tx params contains invalid fields
|
* @throws {Error} if the tx params contains invalid fields
|
||||||
*/
|
*/
|
||||||
export function validateTxParams (txParams) {
|
export function validateTxParams(txParams) {
|
||||||
validateFrom(txParams)
|
validateFrom(txParams)
|
||||||
validateRecipient(txParams)
|
validateRecipient(txParams)
|
||||||
if ('value' in txParams) {
|
if ('value' in txParams) {
|
||||||
const value = txParams.value.toString()
|
const value = txParams.value.toString()
|
||||||
if (value.includes('-')) {
|
if (value.includes('-')) {
|
||||||
throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`)
|
throw new Error(
|
||||||
|
`Invalid transaction value of ${txParams.value} not a positive number.`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.includes('.')) {
|
if (value.includes('.')) {
|
||||||
throw new Error(`Invalid transaction value of ${txParams.value} number must be in wei`)
|
throw new Error(
|
||||||
|
`Invalid transaction value of ${txParams.value} number must be in wei`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,7 +60,7 @@ export function validateTxParams (txParams) {
|
|||||||
* @param {Object} txParams
|
* @param {Object} txParams
|
||||||
* @throws {Error} if the from address isn't valid
|
* @throws {Error} if the from address isn't valid
|
||||||
*/
|
*/
|
||||||
export function validateFrom (txParams) {
|
export function validateFrom(txParams) {
|
||||||
if (!(typeof txParams.from === 'string')) {
|
if (!(typeof txParams.from === 'string')) {
|
||||||
throw new Error(`Invalid from address ${txParams.from} not a string`)
|
throw new Error(`Invalid from address ${txParams.from} not a string`)
|
||||||
}
|
}
|
||||||
@ -68,7 +75,7 @@ export function validateFrom (txParams) {
|
|||||||
* @returns {Object} the tx params
|
* @returns {Object} the tx params
|
||||||
* @throws {Error} if the recipient is invalid OR there isn't tx data
|
* @throws {Error} if the recipient is invalid OR there isn't tx data
|
||||||
*/
|
*/
|
||||||
export function validateRecipient (txParams) {
|
export function validateRecipient(txParams) {
|
||||||
if (txParams.to === '0x' || txParams.to === null) {
|
if (txParams.to === '0x' || txParams.to === null) {
|
||||||
if (txParams.data) {
|
if (txParams.data) {
|
||||||
delete txParams.to
|
delete txParams.to
|
||||||
@ -85,11 +92,11 @@ export function validateRecipient (txParams) {
|
|||||||
* Returns a list of final states
|
* Returns a list of final states
|
||||||
* @returns {string[]} the states that can be considered final states
|
* @returns {string[]} the states that can be considered final states
|
||||||
*/
|
*/
|
||||||
export function getFinalStates () {
|
export function getFinalStates() {
|
||||||
return [
|
return [
|
||||||
'rejected', // the user has responded no!
|
TRANSACTION_STATUSES.REJECTED, // the user has responded no!
|
||||||
'confirmed', // the tx has been included in a block.
|
TRANSACTION_STATUSES.CONFIRMED, // the tx has been included in a block.
|
||||||
'failed', // the tx failed for some reason, included on tx data.
|
TRANSACTION_STATUSES.FAILED, // the tx failed for some reason, included on tx data.
|
||||||
'dropped', // the tx nonce was already used
|
TRANSACTION_STATUSES.DROPPED, // the tx nonce was already used
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import EventEmitter from 'safe-event-emitter'
|
import EventEmitter from 'safe-event-emitter'
|
||||||
import log from 'loglevel'
|
import log from 'loglevel'
|
||||||
import EthQuery from 'ethjs-query'
|
import EthQuery from 'ethjs-query'
|
||||||
|
import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
||||||
@ -11,15 +12,14 @@ import EthQuery from 'ethjs-query'
|
|||||||
<br>
|
<br>
|
||||||
@param {Object} config - non optional configuration object consists of:
|
@param {Object} config - non optional configuration object consists of:
|
||||||
@param {Object} config.provider - A network provider.
|
@param {Object} config.provider - A network provider.
|
||||||
@param {Object} config.nonceTracker see nonce tracker
|
@param {Object} config.nonceTracker - see nonce tracker
|
||||||
@param {function} config.getPendingTransactions a function for getting an array of transactions,
|
@param {Function} config.getPendingTransactions - a function for getting an array of transactions,
|
||||||
@param {function} config.publishTransaction a async function for publishing raw transactions,
|
@param {Function} config.publishTransaction - a async function for publishing raw transactions,
|
||||||
|
|
||||||
@class
|
@class
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default class PendingTransactionTracker extends EventEmitter {
|
export default class PendingTransactionTracker extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We wait this many blocks before emitting a 'tx:dropped' event
|
* We wait this many blocks before emitting a 'tx:dropped' event
|
||||||
*
|
*
|
||||||
@ -37,9 +37,9 @@ export default class PendingTransactionTracker extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
droppedBlocksBufferByHash = new Map()
|
droppedBlocksBufferByHash = new Map()
|
||||||
|
|
||||||
constructor (config) {
|
constructor(config) {
|
||||||
super()
|
super()
|
||||||
this.query = config.query || (new EthQuery(config.provider))
|
this.query = config.query || new EthQuery(config.provider)
|
||||||
this.nonceTracker = config.nonceTracker
|
this.nonceTracker = config.nonceTracker
|
||||||
this.getPendingTransactions = config.getPendingTransactions
|
this.getPendingTransactions = config.getPendingTransactions
|
||||||
this.getCompletedTransactions = config.getCompletedTransactions
|
this.getCompletedTransactions = config.getCompletedTransactions
|
||||||
@ -51,14 +51,18 @@ export default class PendingTransactionTracker extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
checks the network for signed txs and releases the nonce global lock if it is
|
checks the network for signed txs and releases the nonce global lock if it is
|
||||||
*/
|
*/
|
||||||
async updatePendingTxs () {
|
async updatePendingTxs() {
|
||||||
// in order to keep the nonceTracker accurate we block it while updating pending transactions
|
// 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 {
|
try {
|
||||||
const pendingTxs = this.getPendingTransactions()
|
const pendingTxs = this.getPendingTransactions()
|
||||||
await Promise.all(pendingTxs.map((txMeta) => this._checkPendingTx(txMeta)))
|
await Promise.all(
|
||||||
|
pendingTxs.map((txMeta) => this._checkPendingTx(txMeta)),
|
||||||
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error('PendingTransactionTracker - Error updating pending transactions')
|
log.error(
|
||||||
|
'PendingTransactionTracker - Error updating pending transactions',
|
||||||
|
)
|
||||||
log.error(err)
|
log.error(err)
|
||||||
}
|
}
|
||||||
nonceGlobalLock.releaseLock()
|
nonceGlobalLock.releaseLock()
|
||||||
@ -70,7 +74,7 @@ export default class PendingTransactionTracker extends EventEmitter {
|
|||||||
* @emits tx:warning
|
* @emits tx:warning
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async resubmitPendingTxs (blockNumber) {
|
async resubmitPendingTxs(blockNumber) {
|
||||||
const pending = this.getPendingTransactions()
|
const pending = this.getPendingTransactions()
|
||||||
if (!pending.length) {
|
if (!pending.length) {
|
||||||
return
|
return
|
||||||
@ -79,18 +83,20 @@ export default class PendingTransactionTracker extends EventEmitter {
|
|||||||
try {
|
try {
|
||||||
await this._resubmitTx(txMeta, blockNumber)
|
await this._resubmitTx(txMeta, blockNumber)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMessage = err.value?.message?.toLowerCase() || err.message.toLowerCase()
|
const errorMessage =
|
||||||
const isKnownTx = (
|
err.value?.message?.toLowerCase() || err.message.toLowerCase()
|
||||||
|
const isKnownTx =
|
||||||
// geth
|
// geth
|
||||||
errorMessage.includes('replacement transaction underpriced') ||
|
errorMessage.includes('replacement transaction underpriced') ||
|
||||||
errorMessage.includes('known transaction') ||
|
errorMessage.includes('known transaction') ||
|
||||||
// parity
|
// parity
|
||||||
errorMessage.includes('gas price too low to replace') ||
|
errorMessage.includes('gas price too low to replace') ||
|
||||||
errorMessage.includes('transaction with the same hash was already imported') ||
|
errorMessage.includes(
|
||||||
|
'transaction with the same hash was already imported',
|
||||||
|
) ||
|
||||||
// other
|
// other
|
||||||
errorMessage.includes('gateway timeout') ||
|
errorMessage.includes('gateway timeout') ||
|
||||||
errorMessage.includes('nonce too low')
|
errorMessage.includes('nonce too low')
|
||||||
)
|
|
||||||
// ignore resubmit warnings, return early
|
// ignore resubmit warnings, return early
|
||||||
if (isKnownTx) {
|
if (isKnownTx) {
|
||||||
return
|
return
|
||||||
@ -117,13 +123,16 @@ export default class PendingTransactionTracker extends EventEmitter {
|
|||||||
* @emits tx:retry
|
* @emits tx:retry
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _resubmitTx (txMeta, latestBlockNumber) {
|
async _resubmitTx(txMeta, latestBlockNumber) {
|
||||||
if (!txMeta.firstRetryBlockNumber) {
|
if (!txMeta.firstRetryBlockNumber) {
|
||||||
this.emit('tx:block-update', txMeta, latestBlockNumber)
|
this.emit('tx:block-update', txMeta, latestBlockNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstRetryBlockNumber = txMeta.firstRetryBlockNumber || latestBlockNumber
|
const firstRetryBlockNumber =
|
||||||
const txBlockDistance = Number.parseInt(latestBlockNumber, 16) - Number.parseInt(firstRetryBlockNumber, 16)
|
txMeta.firstRetryBlockNumber || latestBlockNumber
|
||||||
|
const txBlockDistance =
|
||||||
|
Number.parseInt(latestBlockNumber, 16) -
|
||||||
|
Number.parseInt(firstRetryBlockNumber, 16)
|
||||||
|
|
||||||
const retryCount = txMeta.retryCount || 0
|
const retryCount = txMeta.retryCount || 0
|
||||||
|
|
||||||
@ -155,19 +164,21 @@ export default class PendingTransactionTracker extends EventEmitter {
|
|||||||
* @emits tx:warning
|
* @emits tx:warning
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _checkPendingTx (txMeta) {
|
async _checkPendingTx(txMeta) {
|
||||||
const txHash = txMeta.hash
|
const txHash = txMeta.hash
|
||||||
const txId = txMeta.id
|
const txId = txMeta.id
|
||||||
|
|
||||||
// Only check submitted txs
|
// Only check submitted txs
|
||||||
if (txMeta.status !== 'submitted') {
|
if (txMeta.status !== TRANSACTION_STATUSES.SUBMITTED) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// extra check in case there was an uncaught error during the
|
// extra check in case there was an uncaught error during the
|
||||||
// signature and submission process
|
// signature and submission process
|
||||||
if (!txHash) {
|
if (!txHash) {
|
||||||
const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.')
|
const noTxHashErr = new Error(
|
||||||
|
'We had an error while submitting this transaction, please try again.',
|
||||||
|
)
|
||||||
noTxHashErr.name = 'NoTxHashError'
|
noTxHashErr.name = 'NoTxHashError'
|
||||||
this.emit('tx:failed', txId, noTxHashErr)
|
this.emit('tx:failed', txId, noTxHashErr)
|
||||||
|
|
||||||
@ -206,8 +217,11 @@ export default class PendingTransactionTracker extends EventEmitter {
|
|||||||
* @returns {Promise<boolean>}
|
* @returns {Promise<boolean>}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _checkIfTxWasDropped (txMeta) {
|
async _checkIfTxWasDropped(txMeta) {
|
||||||
const { hash: txHash, txParams: { nonce, from } } = txMeta
|
const {
|
||||||
|
hash: txHash,
|
||||||
|
txParams: { nonce, from },
|
||||||
|
} = txMeta
|
||||||
const networkNextNonce = await this.query.getTransactionCount(from)
|
const networkNextNonce = await this.query.getTransactionCount(from)
|
||||||
|
|
||||||
if (parseInt(nonce, 16) >= networkNextNonce.toNumber()) {
|
if (parseInt(nonce, 16) >= networkNextNonce.toNumber()) {
|
||||||
@ -235,14 +249,16 @@ export default class PendingTransactionTracker extends EventEmitter {
|
|||||||
* @returns {Promise<boolean>}
|
* @returns {Promise<boolean>}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _checkIfNonceIsTaken (txMeta) {
|
async _checkIfNonceIsTaken(txMeta) {
|
||||||
const address = txMeta.txParams.from
|
const address = txMeta.txParams.from
|
||||||
const completed = this.getCompletedTransactions(address)
|
const completed = this.getCompletedTransactions(address)
|
||||||
return completed.some(
|
return completed.some(
|
||||||
// This is called while the transaction is in-flight, so it is possible that the
|
// 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
|
// list of completed transactions now includes the transaction we were looking at
|
||||||
// and if that is the case, don't consider the transaction to have taken its own nonce
|
// and if that is the case, don't consider the transaction to have taken its own nonce
|
||||||
(other) => !(other.id === txMeta.id) && other.txParams.nonce === txMeta.txParams.nonce,
|
(other) =>
|
||||||
|
!(other.id === txMeta.id) &&
|
||||||
|
other.txParams.nonce === txMeta.txParams.nonce,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,7 @@ and used to do things like calculate gas of a tx.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export default class TxGasUtil {
|
export default class TxGasUtil {
|
||||||
|
constructor(provider) {
|
||||||
constructor (provider) {
|
|
||||||
this.query = new EthQuery(provider)
|
this.query = new EthQuery(provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +28,7 @@ export default class TxGasUtil {
|
|||||||
@param {Object} txMeta - the txMeta object
|
@param {Object} txMeta - the txMeta object
|
||||||
@returns {GasAnalysisResult} The result of the gas analysis
|
@returns {GasAnalysisResult} The result of the gas analysis
|
||||||
*/
|
*/
|
||||||
async analyzeGasUsage (txMeta) {
|
async analyzeGasUsage(txMeta) {
|
||||||
const block = await this.query.getBlockByNumber('latest', false)
|
const block = await this.query.getBlockByNumber('latest', false)
|
||||||
|
|
||||||
// fallback to block gasLimit
|
// fallback to block gasLimit
|
||||||
@ -54,9 +53,9 @@ export default class TxGasUtil {
|
|||||||
/**
|
/**
|
||||||
Estimates the tx's gas usage
|
Estimates the tx's gas usage
|
||||||
@param {Object} txMeta - the txMeta object
|
@param {Object} txMeta - the txMeta object
|
||||||
@returns {string} - the estimated gas limit as a hex string
|
@returns {string} the estimated gas limit as a hex string
|
||||||
*/
|
*/
|
||||||
async estimateTxGas (txMeta) {
|
async estimateTxGas(txMeta) {
|
||||||
const { txParams } = txMeta
|
const { txParams } = txMeta
|
||||||
|
|
||||||
// estimate tx gas requirements
|
// estimate tx gas requirements
|
||||||
@ -68,9 +67,9 @@ export default class TxGasUtil {
|
|||||||
|
|
||||||
@param {string} initialGasLimitHex - the initial gas limit to add the buffer too
|
@param {string} initialGasLimitHex - the initial gas limit to add the buffer too
|
||||||
@param {string} blockGasLimitHex - the block gas limit
|
@param {string} blockGasLimitHex - the block gas limit
|
||||||
@returns {string} - the buffered gas limit as a hex string
|
@returns {string} the buffered gas limit as a hex string
|
||||||
*/
|
*/
|
||||||
addGasBuffer (initialGasLimitHex, blockGasLimitHex, multiplier = 1.5) {
|
addGasBuffer(initialGasLimitHex, blockGasLimitHex, multiplier = 1.5) {
|
||||||
const initialGasLimitBn = hexToBn(initialGasLimitHex)
|
const initialGasLimitBn = hexToBn(initialGasLimitHex)
|
||||||
const blockGasLimitBn = hexToBn(blockGasLimitHex)
|
const blockGasLimitBn = hexToBn(blockGasLimitHex)
|
||||||
const upperGasLimitBn = blockGasLimitBn.muln(0.9)
|
const upperGasLimitBn = blockGasLimitBn.muln(0.9)
|
||||||
@ -88,11 +87,19 @@ export default class TxGasUtil {
|
|||||||
return bnToHex(upperGasLimitBn)
|
return bnToHex(upperGasLimitBn)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBufferedGasLimit (txMeta, multiplier) {
|
async getBufferedGasLimit(txMeta, multiplier) {
|
||||||
const { blockGasLimit, estimatedGasHex, simulationFails } = await this.analyzeGasUsage(txMeta)
|
const {
|
||||||
|
blockGasLimit,
|
||||||
|
estimatedGasHex,
|
||||||
|
simulationFails,
|
||||||
|
} = await this.analyzeGasUsage(txMeta)
|
||||||
|
|
||||||
// add additional gas buffer to our estimation for safety
|
// add additional gas buffer to our estimation for safety
|
||||||
const gasLimit = this.addGasBuffer(ethUtil.addHexPrefix(estimatedGasHex), blockGasLimit, multiplier)
|
const gasLimit = this.addGasBuffer(
|
||||||
|
ethUtil.addHexPrefix(estimatedGasHex),
|
||||||
|
blockGasLimit,
|
||||||
|
multiplier,
|
||||||
|
)
|
||||||
return { gasLimit, simulationFails }
|
return { gasLimit, simulationFails }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,57 +2,55 @@ import EventEmitter from 'safe-event-emitter'
|
|||||||
import ObservableStore from 'obs-store'
|
import ObservableStore from 'obs-store'
|
||||||
import log from 'loglevel'
|
import log from 'loglevel'
|
||||||
import createId from '../../lib/random-id'
|
import createId from '../../lib/random-id'
|
||||||
import { generateHistoryEntry, replayHistory, snapshotFromTxMeta } from './lib/tx-state-history-helpers'
|
import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction'
|
||||||
|
import {
|
||||||
|
generateHistoryEntry,
|
||||||
|
replayHistory,
|
||||||
|
snapshotFromTxMeta,
|
||||||
|
} from './lib/tx-state-history-helpers'
|
||||||
import { getFinalStates, normalizeTxParams } from './lib/util'
|
import { getFinalStates, normalizeTxParams } from './lib/util'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
TransactionStateManager is responsible for the state of a transaction and
|
* TransactionStatuses reimported from the shared transaction constants file
|
||||||
storing the transaction
|
* @typedef {import('../../../../shared/constants/transaction').TransactionStatuses} TransactionStatuses
|
||||||
it also has some convenience methods for finding subsets of transactions
|
*/
|
||||||
*
|
|
||||||
*STATUS METHODS
|
/**
|
||||||
<br>statuses:
|
* TransactionStateManager is responsible for the state of a transaction and
|
||||||
<br> - `'unapproved'` the user has not responded
|
* storing the transaction. It also has some convenience methods for finding
|
||||||
<br> - `'rejected'` the user has responded no!
|
* subsets of transactions.
|
||||||
<br> - `'approved'` the user has approved the tx
|
* @param {Object} opts
|
||||||
<br> - `'signed'` the tx is signed
|
* @param {Object} [opts.initState={ transactions: [] }] - initial transactions list with the key transaction {Array}
|
||||||
<br> - `'submitted'` the tx is sent to a server
|
* @param {number} [opts.txHistoryLimit] - limit for how many finished
|
||||||
<br> - `'confirmed'` the tx has been included in a block.
|
* transactions can hang around in state
|
||||||
<br> - `'failed'` the tx failed for some reason, included on tx data.
|
* @param {Function} opts.getNetwork - return network number
|
||||||
<br> - `'dropped'` the tx nonce was already used
|
* @class
|
||||||
@param {Object} opts
|
*/
|
||||||
@param {Object} [opts.initState={ transactions: [] }] initial transactions list with the key transaction {array}
|
|
||||||
@param {number} [opts.txHistoryLimit] limit for how many finished
|
|
||||||
transactions can hang around in state
|
|
||||||
@param {function} opts.getNetwork return network number
|
|
||||||
@class
|
|
||||||
*/
|
|
||||||
export default class TransactionStateManager extends EventEmitter {
|
export default class TransactionStateManager extends EventEmitter {
|
||||||
constructor ({ initState, txHistoryLimit, getNetwork }) {
|
constructor({ initState, txHistoryLimit, getNetwork }) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this.store = new ObservableStore(
|
this.store = new ObservableStore({ transactions: [], ...initState })
|
||||||
{ transactions: [], ...initState },
|
|
||||||
)
|
|
||||||
this.txHistoryLimit = txHistoryLimit
|
this.txHistoryLimit = txHistoryLimit
|
||||||
this.getNetwork = getNetwork
|
this.getNetwork = getNetwork
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param {Object} opts - the object to use when overwriting defaults
|
* @param {Object} opts - the object to use when overwriting defaults
|
||||||
@returns {txMeta} - the default txMeta object
|
* @returns {txMeta} the default txMeta object
|
||||||
*/
|
*/
|
||||||
generateTxMeta (opts) {
|
generateTxMeta(opts) {
|
||||||
const netId = this.getNetwork()
|
const netId = this.getNetwork()
|
||||||
if (netId === 'loading') {
|
if (netId === 'loading') {
|
||||||
throw new Error('MetaMask is having trouble connecting to the network')
|
throw new Error('MetaMask is having trouble connecting to the network')
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
id: createId(),
|
id: createId(),
|
||||||
time: (new Date()).getTime(),
|
time: new Date().getTime(),
|
||||||
status: 'unapproved',
|
status: TRANSACTION_STATUSES.UNAPPROVED,
|
||||||
metamaskNetworkId: netId,
|
metamaskNetworkId: netId,
|
||||||
loadingDefaults: true, ...opts,
|
loadingDefaults: true,
|
||||||
|
...opts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,10 +59,10 @@ export default class TransactionStateManager extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* The list is iterated backwards as new transactions are pushed onto it.
|
* The list is iterated backwards as new transactions are pushed onto it.
|
||||||
*
|
*
|
||||||
* @param {number} [limit] a limit for the number of transactions to return
|
* @param {number} [limit] - a limit for the number of transactions to return
|
||||||
* @returns {Object[]} The {@code txMeta}s, filtered to the current network
|
* @returns {Object[]} The {@code txMeta}s, filtered to the current network
|
||||||
*/
|
*/
|
||||||
getTxList (limit) {
|
getTxList(limit) {
|
||||||
const network = this.getNetwork()
|
const network = this.getNetwork()
|
||||||
const fullTxList = this.getFullTxList()
|
const fullTxList = this.getFullTxList()
|
||||||
|
|
||||||
@ -93,17 +91,20 @@ export default class TransactionStateManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@returns {array} - of all the txMetas in store
|
* @returns {Array} of all the txMetas in store
|
||||||
*/
|
*/
|
||||||
getFullTxList () {
|
getFullTxList() {
|
||||||
return this.store.getState().transactions
|
return this.store.getState().transactions
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@returns {array} - the tx list whose status is unapproved
|
* @returns {Array} the tx list with unapproved status
|
||||||
*/
|
*/
|
||||||
getUnapprovedTxList () {
|
getUnapprovedTxList() {
|
||||||
const txList = this.getTxsByMetaData('status', 'unapproved')
|
const txList = this.getTxsByMetaData(
|
||||||
|
'status',
|
||||||
|
TRANSACTION_STATUSES.UNAPPROVED,
|
||||||
|
)
|
||||||
return txList.reduce((result, tx) => {
|
return txList.reduce((result, tx) => {
|
||||||
result[tx.id] = tx
|
result[tx.id] = tx
|
||||||
return result
|
return result
|
||||||
@ -111,12 +112,12 @@ export default class TransactionStateManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param [address] {string} - hex prefixed address to sort the txMetas for [optional]
|
* @param {string} [address] - hex prefixed address to sort the txMetas for [optional]
|
||||||
@returns {array} - the tx list whose status is approved if no address is provide
|
* @returns {Array} the tx list with approved status if no address is provide
|
||||||
returns all txMetas who's status is approved for the current network
|
* returns all txMetas with approved statuses for the current network
|
||||||
*/
|
*/
|
||||||
getApprovedTransactions (address) {
|
getApprovedTransactions(address) {
|
||||||
const opts = { status: 'approved' }
|
const opts = { status: TRANSACTION_STATUSES.APPROVED }
|
||||||
if (address) {
|
if (address) {
|
||||||
opts.from = address
|
opts.from = address
|
||||||
}
|
}
|
||||||
@ -124,12 +125,12 @@ export default class TransactionStateManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param [address] {string} - hex prefixed address to sort the txMetas for [optional]
|
* @param {string} [address] - hex prefixed address to sort the txMetas for [optional]
|
||||||
@returns {array} - the tx list whose status is submitted if no address is provide
|
* @returns {Array} the tx list submitted status if no address is provide
|
||||||
returns all txMetas who's status is submitted for the current network
|
* returns all txMetas with submitted statuses for the current network
|
||||||
*/
|
*/
|
||||||
getPendingTransactions (address) {
|
getPendingTransactions(address) {
|
||||||
const opts = { status: 'submitted' }
|
const opts = { status: TRANSACTION_STATUSES.SUBMITTED }
|
||||||
if (address) {
|
if (address) {
|
||||||
opts.from = address
|
opts.from = address
|
||||||
}
|
}
|
||||||
@ -137,12 +138,12 @@ export default class TransactionStateManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param [address] {string} - hex prefixed address to sort the txMetas for [optional]
|
@param {string} [address] - hex prefixed address to sort the txMetas for [optional]
|
||||||
@returns {array} - the tx list whose status is confirmed if no address is provide
|
@returns {Array} the tx list whose status is confirmed if no address is provide
|
||||||
returns all txMetas who's status is confirmed for the current network
|
returns all txMetas who's status is confirmed for the current network
|
||||||
*/
|
*/
|
||||||
getConfirmedTransactions (address) {
|
getConfirmedTransactions(address) {
|
||||||
const opts = { status: 'confirmed' }
|
const opts = { status: TRANSACTION_STATUSES.CONFIRMED }
|
||||||
if (address) {
|
if (address) {
|
||||||
opts.from = address
|
opts.from = address
|
||||||
}
|
}
|
||||||
@ -150,15 +151,15 @@ export default class TransactionStateManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Adds the txMeta to the list of transactions in the store.
|
* Adds the txMeta to the list of transactions in the store.
|
||||||
if the list is over txHistoryLimit it will remove a transaction that
|
* if the list is over txHistoryLimit it will remove a transaction that
|
||||||
is in its final state
|
* is in its final state.
|
||||||
it will also add the key `history` to the txMeta with the snap shot of the original
|
* it will also add the key `history` to the txMeta with the snap shot of
|
||||||
object
|
* the original object
|
||||||
@param {Object} txMeta
|
* @param {Object} txMeta
|
||||||
@returns {Object} - the txMeta
|
* @returns {Object} the txMeta
|
||||||
*/
|
*/
|
||||||
addTx (txMeta) {
|
addTx(txMeta) {
|
||||||
// normalize and validate txParams if present
|
// normalize and validate txParams if present
|
||||||
if (txMeta.txParams) {
|
if (txMeta.txParams) {
|
||||||
txMeta.txParams = this.normalizeAndValidateTxParams(txMeta.txParams)
|
txMeta.txParams = this.normalizeAndValidateTxParams(txMeta.txParams)
|
||||||
@ -193,8 +194,9 @@ export default class TransactionStateManager extends EventEmitter {
|
|||||||
transactions.splice(index, 1)
|
transactions.splice(index, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const newTxIndex = transactions
|
const newTxIndex = transactions.findIndex(
|
||||||
.findIndex((currentTxMeta) => currentTxMeta.time > txMeta.time)
|
(currentTxMeta) => currentTxMeta.time > txMeta.time,
|
||||||
|
)
|
||||||
|
|
||||||
newTxIndex === -1
|
newTxIndex === -1
|
||||||
? transactions.push(txMeta)
|
? transactions.push(txMeta)
|
||||||
@ -204,21 +206,21 @@ export default class TransactionStateManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param {number} txId
|
* @param {number} txId
|
||||||
@returns {Object} - the txMeta who matches the given id if none found
|
* @returns {Object} the txMeta who matches the given id if none found
|
||||||
for the network returns undefined
|
* for the network returns undefined
|
||||||
*/
|
*/
|
||||||
getTx (txId) {
|
getTx(txId) {
|
||||||
const txMeta = this.getTxsByMetaData('id', txId)[0]
|
const txMeta = this.getTxsByMetaData('id', txId)[0]
|
||||||
return txMeta
|
return txMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
updates the txMeta in the list and adds a history entry
|
* updates the txMeta in the list and adds a history entry
|
||||||
@param {Object} txMeta - the txMeta to update
|
* @param {Object} txMeta - the txMeta to update
|
||||||
@param {string} [note] - a note about the update for history
|
* @param {string} [note] - a note about the update for history
|
||||||
*/
|
*/
|
||||||
updateTx (txMeta, note) {
|
updateTx(txMeta, note) {
|
||||||
// normalize and validate txParams if present
|
// normalize and validate txParams if present
|
||||||
if (txMeta.txParams) {
|
if (txMeta.txParams) {
|
||||||
txMeta.txParams = this.normalizeAndValidateTxParams(txMeta.txParams)
|
txMeta.txParams = this.normalizeAndValidateTxParams(txMeta.txParams)
|
||||||
@ -243,12 +245,12 @@ export default class TransactionStateManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
merges txParams obj onto txMeta.txParams
|
* merges txParams obj onto txMeta.txParams use extend to ensure
|
||||||
use extend to ensure that all fields are filled
|
* that all fields are filled
|
||||||
@param {number} txId - the id of the txMeta
|
* @param {number} txId - the id of the txMeta
|
||||||
@param {Object} txParams - the updated txParams
|
* @param {Object} txParams - the updated txParams
|
||||||
*/
|
*/
|
||||||
updateTxParams (txId, txParams) {
|
updateTxParams(txId, txParams) {
|
||||||
const txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
txMeta.txParams = { ...txMeta.txParams, ...txParams }
|
txMeta.txParams = { ...txMeta.txParams, ...txParams }
|
||||||
this.updateTx(txMeta, `txStateManager#updateTxParams`)
|
this.updateTx(txMeta, `txStateManager#updateTxParams`)
|
||||||
@ -258,7 +260,7 @@ export default class TransactionStateManager extends EventEmitter {
|
|||||||
* normalize and validate txParams members
|
* normalize and validate txParams members
|
||||||
* @param {Object} txParams - txParams
|
* @param {Object} txParams - txParams
|
||||||
*/
|
*/
|
||||||
normalizeAndValidateTxParams (txParams) {
|
normalizeAndValidateTxParams(txParams) {
|
||||||
if (typeof txParams.data === 'undefined') {
|
if (typeof txParams.data === 'undefined') {
|
||||||
delete txParams.data
|
delete txParams.data
|
||||||
}
|
}
|
||||||
@ -269,22 +271,26 @@ export default class TransactionStateManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
validates txParams members by type
|
* validates txParams members by type
|
||||||
@param {Object} txParams - txParams to validate
|
* @param {Object} txParams - txParams to validate
|
||||||
*/
|
*/
|
||||||
validateTxParams (txParams) {
|
validateTxParams(txParams) {
|
||||||
Object.keys(txParams).forEach((key) => {
|
Object.keys(txParams).forEach((key) => {
|
||||||
const value = txParams[key]
|
const value = txParams[key]
|
||||||
// validate types
|
// validate types
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'chainId':
|
case 'chainId':
|
||||||
if (typeof value !== 'number' && typeof value !== 'string') {
|
if (typeof value !== 'number' && typeof value !== 'string') {
|
||||||
throw new Error(`${key} in txParams is not a Number or hex string. got: (${value})`)
|
throw new Error(
|
||||||
|
`${key} in txParams is not a Number or hex string. got: (${value})`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
if (typeof value !== 'string') {
|
if (typeof value !== 'string') {
|
||||||
throw new Error(`${key} in txParams is not a string. got: (${value})`)
|
throw new Error(
|
||||||
|
`${key} in txParams is not a string. got: (${value})`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -301,8 +307,8 @@ export default class TransactionStateManager extends EventEmitter {
|
|||||||
}<br></code>
|
}<br></code>
|
||||||
optionally the values of the keys can be functions for situations like where
|
optionally the values of the keys can be functions for situations like where
|
||||||
you want all but one status.
|
you want all but one status.
|
||||||
@param [initialList=this.getTxList()]
|
@param {Array} [initialList=this.getTxList()]
|
||||||
@returns {array} - array of txMeta with all
|
@returns {Array} array of txMeta with all
|
||||||
options matching
|
options matching
|
||||||
*/
|
*/
|
||||||
/*
|
/*
|
||||||
@ -319,7 +325,7 @@ export default class TransactionStateManager extends EventEmitter {
|
|||||||
or for filtering for all txs from one account
|
or for filtering for all txs from one account
|
||||||
and that have been 'confirmed'
|
and that have been 'confirmed'
|
||||||
*/
|
*/
|
||||||
getFilteredTxList (opts, initialList) {
|
getFilteredTxList(opts, initialList) {
|
||||||
let filteredTxList = initialList
|
let filteredTxList = initialList
|
||||||
Object.keys(opts).forEach((key) => {
|
Object.keys(opts).forEach((key) => {
|
||||||
filteredTxList = this.getTxsByMetaData(key, opts[key], filteredTxList)
|
filteredTxList = this.getTxsByMetaData(key, opts[key], filteredTxList)
|
||||||
@ -328,14 +334,13 @@ export default class TransactionStateManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param {string} key - the key to check
|
||||||
@param {string} key - the key to check
|
* @param {any} value - the value your looking for can also be a function that returns a bool
|
||||||
@param value - the value your looking for can also be a function that returns a bool
|
* @param {Array} [txList=this.getTxList()] - the list to search. default is the txList
|
||||||
@param [txList=this.getTxList()] {array} - the list to search. default is the txList
|
* from txStateManager#getTxList
|
||||||
from txStateManager#getTxList
|
* @returns {Array} a list of txMetas who matches the search params
|
||||||
@returns {array} - a list of txMetas who matches the search params
|
*/
|
||||||
*/
|
getTxsByMetaData(key, value, txList = this.getTxList()) {
|
||||||
getTxsByMetaData (key, value, txList = this.getTxList()) {
|
|
||||||
const filter = typeof value === 'function' ? value : (v) => v === value
|
const filter = typeof value === 'function' ? value : (v) => v === value
|
||||||
|
|
||||||
return txList.filter((txMeta) => {
|
return txList.filter((txMeta) => {
|
||||||
@ -349,82 +354,81 @@ export default class TransactionStateManager extends EventEmitter {
|
|||||||
// get::set status
|
// get::set status
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param {number} txId - the txMeta Id
|
* @param {number} txId - the txMeta Id
|
||||||
@returns {string} - the status of the tx.
|
* @returns {string} the status of the tx.
|
||||||
*/
|
*/
|
||||||
getTxStatus (txId) {
|
getTxStatus(txId) {
|
||||||
const txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
return txMeta.status
|
return txMeta.status
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'rejected'.
|
* Update the status of the tx to 'rejected'.
|
||||||
@param {number} txId - the txMeta Id
|
* @param {number} txId - the txMeta Id
|
||||||
*/
|
*/
|
||||||
setTxStatusRejected (txId) {
|
setTxStatusRejected(txId) {
|
||||||
this._setTxStatus(txId, 'rejected')
|
this._setTxStatus(txId, 'rejected')
|
||||||
this._removeTx(txId)
|
this._removeTx(txId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'unapproved'.
|
* Update the status of the tx to 'unapproved'.
|
||||||
@param {number} txId - the txMeta Id
|
* @param {number} txId - the txMeta Id
|
||||||
*/
|
*/
|
||||||
setTxStatusUnapproved (txId) {
|
setTxStatusUnapproved(txId) {
|
||||||
this._setTxStatus(txId, 'unapproved')
|
this._setTxStatus(txId, TRANSACTION_STATUSES.UNAPPROVED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'approved'.
|
* Update the status of the tx to 'approved'.
|
||||||
@param {number} txId - the txMeta Id
|
* @param {number} txId - the txMeta Id
|
||||||
*/
|
*/
|
||||||
setTxStatusApproved (txId) {
|
setTxStatusApproved(txId) {
|
||||||
this._setTxStatus(txId, 'approved')
|
this._setTxStatus(txId, TRANSACTION_STATUSES.APPROVED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'signed'.
|
* Update the status of the tx to 'signed'.
|
||||||
@param {number} txId - the txMeta Id
|
* @param {number} txId - the txMeta Id
|
||||||
*/
|
*/
|
||||||
setTxStatusSigned (txId) {
|
setTxStatusSigned(txId) {
|
||||||
this._setTxStatus(txId, 'signed')
|
this._setTxStatus(txId, TRANSACTION_STATUSES.SIGNED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'submitted'.
|
* Update the status of the tx to 'submitted' and add a time stamp
|
||||||
and add a time stamp for when it was called
|
* for when it was called
|
||||||
@param {number} txId - the txMeta Id
|
* @param {number} txId - the txMeta Id
|
||||||
*/
|
*/
|
||||||
setTxStatusSubmitted (txId) {
|
setTxStatusSubmitted(txId) {
|
||||||
const txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
txMeta.submittedTime = (new Date()).getTime()
|
txMeta.submittedTime = new Date().getTime()
|
||||||
this.updateTx(txMeta, 'txStateManager - add submitted time stamp')
|
this.updateTx(txMeta, 'txStateManager - add submitted time stamp')
|
||||||
this._setTxStatus(txId, 'submitted')
|
this._setTxStatus(txId, TRANSACTION_STATUSES.SUBMITTED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'confirmed'.
|
* Update the status of the tx to 'confirmed'.
|
||||||
@param {number} txId - the txMeta Id
|
* @param {number} txId - the txMeta Id
|
||||||
*/
|
*/
|
||||||
setTxStatusConfirmed (txId) {
|
setTxStatusConfirmed(txId) {
|
||||||
this._setTxStatus(txId, 'confirmed')
|
this._setTxStatus(txId, TRANSACTION_STATUSES.CONFIRMED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'dropped'.
|
* Update the status of the tx to 'dropped'.
|
||||||
@param {number} txId - the txMeta Id
|
* @param {number} txId - the txMeta Id
|
||||||
*/
|
*/
|
||||||
setTxStatusDropped (txId) {
|
setTxStatusDropped(txId) {
|
||||||
this._setTxStatus(txId, 'dropped')
|
this._setTxStatus(txId, TRANSACTION_STATUSES.DROPPED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'failed'.
|
* Updates the status of the tx to 'failed' and put the error on the txMeta
|
||||||
and put the error on the txMeta
|
* @param {number} txId - the txMeta Id
|
||||||
@param {number} txId - the txMeta Id
|
* @param {erroObject} err - error object
|
||||||
@param {erroObject} err - error object
|
*/
|
||||||
*/
|
setTxStatusFailed(txId, err) {
|
||||||
setTxStatusFailed (txId, err) {
|
|
||||||
const error = err || new Error('Internal metamask failure')
|
const error = err || new Error('Internal metamask failure')
|
||||||
|
|
||||||
const txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
@ -434,48 +438,45 @@ export default class TransactionStateManager extends EventEmitter {
|
|||||||
stack: error.stack,
|
stack: error.stack,
|
||||||
}
|
}
|
||||||
this.updateTx(txMeta, 'transactions:tx-state-manager#fail - add error')
|
this.updateTx(txMeta, 'transactions:tx-state-manager#fail - add error')
|
||||||
this._setTxStatus(txId, 'failed')
|
this._setTxStatus(txId, TRANSACTION_STATUSES.FAILED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Removes transaction from the given address for the current network
|
* Removes transaction from the given address for the current network
|
||||||
from the txList
|
* from the txList
|
||||||
@param {string} address - hex string of the from address on the txParams to remove
|
* @param {string} address - hex string of the from address on the txParams
|
||||||
*/
|
* to remove
|
||||||
wipeTransactions (address) {
|
*/
|
||||||
|
wipeTransactions(address) {
|
||||||
// network only tx
|
// network only tx
|
||||||
const txs = this.getFullTxList()
|
const txs = this.getFullTxList()
|
||||||
const network = this.getNetwork()
|
const network = this.getNetwork()
|
||||||
|
|
||||||
// Filter out the ones from the current account and network
|
// Filter out the ones from the current account and network
|
||||||
const otherAccountTxs = txs.filter((txMeta) => !(txMeta.txParams.from === address && txMeta.metamaskNetworkId === network))
|
const otherAccountTxs = txs.filter(
|
||||||
|
(txMeta) =>
|
||||||
|
!(
|
||||||
|
txMeta.txParams.from === address &&
|
||||||
|
txMeta.metamaskNetworkId === network
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
this._saveTxList(otherAccountTxs)
|
this._saveTxList(otherAccountTxs)
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// PRIVATE METHODS
|
// PRIVATE METHODS
|
||||||
//
|
//
|
||||||
|
|
||||||
// STATUS METHODS
|
|
||||||
// statuses:
|
|
||||||
// - `'unapproved'` the user has not responded
|
|
||||||
// - `'rejected'` the user has responded no!
|
|
||||||
// - `'approved'` the user has approved the tx
|
|
||||||
// - `'signed'` the tx is signed
|
|
||||||
// - `'submitted'` the tx is sent to a server
|
|
||||||
// - `'confirmed'` the tx has been included in a block.
|
|
||||||
// - `'failed'` the tx failed for some reason, included on tx data.
|
|
||||||
// - `'dropped'` the tx nonce was already used
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param {number} txId - the txMeta Id
|
* @param {number} txId - the txMeta Id
|
||||||
@param {string} status - the status to set on the txMeta
|
* @param {TransactionStatuses[keyof TransactionStatuses]} status - the status to set on the txMeta
|
||||||
@emits tx:status-update - passes txId and status
|
* @emits tx:status-update - passes txId and status
|
||||||
@emits ${txMeta.id}:finished - if it is a finished state. Passes the txMeta
|
* @emits ${txMeta.id}:finished - if it is a finished state. Passes the txMeta
|
||||||
@emits update:badge
|
* @emits update:badge
|
||||||
*/
|
*/
|
||||||
_setTxStatus (txId, status) {
|
_setTxStatus(txId, status) {
|
||||||
const txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
|
|
||||||
if (!txMeta) {
|
if (!txMeta) {
|
||||||
@ -487,7 +488,13 @@ export default class TransactionStateManager extends EventEmitter {
|
|||||||
this.updateTx(txMeta, `txStateManager: setting status to ${status}`)
|
this.updateTx(txMeta, `txStateManager: setting status to ${status}`)
|
||||||
this.emit(`${txMeta.id}:${status}`, txId)
|
this.emit(`${txMeta.id}:${status}`, txId)
|
||||||
this.emit(`tx:status-update`, txId, status)
|
this.emit(`tx:status-update`, txId, status)
|
||||||
if (['submitted', 'rejected', 'failed'].includes(status)) {
|
if (
|
||||||
|
[
|
||||||
|
TRANSACTION_STATUSES.SUBMITTED,
|
||||||
|
TRANSACTION_STATUSES.REJECTED,
|
||||||
|
TRANSACTION_STATUSES.FAILED,
|
||||||
|
].includes(status)
|
||||||
|
) {
|
||||||
this.emit(`${txMeta.id}:finished`, txMeta)
|
this.emit(`${txMeta.id}:finished`, txMeta)
|
||||||
}
|
}
|
||||||
this.emit('update:badge')
|
this.emit('update:badge')
|
||||||
@ -497,15 +504,14 @@ export default class TransactionStateManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Saves the new/updated txList.
|
* Saves the new/updated txList. Intended only for internal use
|
||||||
@param {array} transactions - the list of transactions to save
|
* @param {Array} transactions - the list of transactions to save
|
||||||
*/
|
*/
|
||||||
// Function is intended only for internal use
|
_saveTxList(transactions) {
|
||||||
_saveTxList (transactions) {
|
|
||||||
this.store.updateState({ transactions })
|
this.store.updateState({ transactions })
|
||||||
}
|
}
|
||||||
|
|
||||||
_removeTx (txId) {
|
_removeTx(txId) {
|
||||||
const transactionList = this.getFullTxList()
|
const transactionList = this.getFullTxList()
|
||||||
this._saveTxList(transactionList.filter((txMeta) => txMeta.id !== txId))
|
this._saveTxList(transactionList.filter((txMeta) => txMeta.id !== txId))
|
||||||
}
|
}
|
||||||
@ -513,10 +519,11 @@ export default class TransactionStateManager extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
* Filters out the unapproved transactions
|
* Filters out the unapproved transactions
|
||||||
*/
|
*/
|
||||||
|
clearUnapprovedTxs() {
|
||||||
clearUnapprovedTxs () {
|
|
||||||
const transactions = this.getFullTxList()
|
const transactions = this.getFullTxList()
|
||||||
const nonUnapprovedTxs = transactions.filter((tx) => tx.status !== 'unapproved')
|
const nonUnapprovedTxs = transactions.filter(
|
||||||
|
(tx) => tx.status !== TRANSACTION_STATUSES.UNAPPROVED,
|
||||||
|
)
|
||||||
this._saveTxList(nonUnapprovedTxs)
|
this._saveTxList(nonUnapprovedTxs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} FirstTimeState
|
* @typedef {Object} FirstTimeState
|
||||||
* @property {Object} config Initial configuration parameters
|
* @property {Object} config Initial configuration parameters
|
||||||
|
@ -60,13 +60,13 @@ initProvider({
|
|||||||
// TODO:deprecate:2020
|
// TODO:deprecate:2020
|
||||||
// Setup web3
|
// Setup web3
|
||||||
|
|
||||||
if (typeof window.web3 !== 'undefined') {
|
if (typeof window.web3 === 'undefined') {
|
||||||
throw new Error(`MetaMask detected another web3.
|
// proxy web3, assign to window, and set up site auto reload
|
||||||
|
setupWeb3(log)
|
||||||
|
} else {
|
||||||
|
log.warn(`MetaMask detected another web3.
|
||||||
MetaMask will not work reliably with another web3 extension.
|
MetaMask will not work reliably with another web3 extension.
|
||||||
This usually happens if you have two MetaMasks installed,
|
This usually happens if you have two MetaMasks installed,
|
||||||
or MetaMask and another web3 extension. Please remove one
|
or MetaMask and another web3 extension. Please remove one
|
||||||
and try again.`)
|
and try again.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// proxy web3, assign to window, and set up site auto reload
|
|
||||||
setupWeb3(log)
|
|
||||||
|
@ -5,14 +5,13 @@ import ObservableStore from 'obs-store'
|
|||||||
* structure of child stores based on configuration
|
* structure of child stores based on configuration
|
||||||
*/
|
*/
|
||||||
export default class ComposableObservableStore extends ObservableStore {
|
export default class ComposableObservableStore extends ObservableStore {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new store
|
* Create a new store
|
||||||
*
|
*
|
||||||
* @param {Object} [initState] - The initial store state
|
* @param {Object} [initState] - The initial store state
|
||||||
* @param {Object} [config] - Map of internal state keys to child stores
|
* @param {Object} [config] - Map of internal state keys to child stores
|
||||||
*/
|
*/
|
||||||
constructor (initState, config) {
|
constructor(initState, config) {
|
||||||
super(initState)
|
super(initState)
|
||||||
this.updateStructure(config)
|
this.updateStructure(config)
|
||||||
}
|
}
|
||||||
@ -22,7 +21,7 @@ export default class ComposableObservableStore extends ObservableStore {
|
|||||||
*
|
*
|
||||||
* @param {Object} [config] - Map of internal state keys to child stores
|
* @param {Object} [config] - Map of internal state keys to child stores
|
||||||
*/
|
*/
|
||||||
updateStructure (config) {
|
updateStructure(config) {
|
||||||
this.config = config
|
this.config = config
|
||||||
this.removeAllListeners()
|
this.removeAllListeners()
|
||||||
for (const key in config) {
|
for (const key in config) {
|
||||||
@ -38,14 +37,16 @@ export default class ComposableObservableStore extends ObservableStore {
|
|||||||
* Merges all child store state into a single object rather than
|
* Merges all child store state into a single object rather than
|
||||||
* returning an object keyed by child store class name
|
* returning an object keyed by child store class name
|
||||||
*
|
*
|
||||||
* @returns {Object} - Object containing merged child store state
|
* @returns {Object} Object containing merged child store state
|
||||||
*/
|
*/
|
||||||
getFlatState () {
|
getFlatState() {
|
||||||
let flatState = {}
|
let flatState = {}
|
||||||
for (const key in this.config) {
|
for (const key in this.config) {
|
||||||
if (Object.prototype.hasOwnProperty.call(this.config, key)) {
|
if (Object.prototype.hasOwnProperty.call(this.config, key)) {
|
||||||
const controller = this.config[key]
|
const controller = this.config[key]
|
||||||
const state = controller.getState ? controller.getState() : controller.state
|
const state = controller.getState
|
||||||
|
? controller.getState()
|
||||||
|
: controller.state
|
||||||
flatState = { ...flatState, ...state }
|
flatState = { ...flatState, ...state }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,12 @@ import log from 'loglevel'
|
|||||||
import pify from 'pify'
|
import pify from 'pify'
|
||||||
import Web3 from 'web3'
|
import Web3 from 'web3'
|
||||||
import SINGLE_CALL_BALANCES_ABI from 'single-call-balance-checker-abi'
|
import SINGLE_CALL_BALANCES_ABI from 'single-call-balance-checker-abi'
|
||||||
import { MAINNET_CHAIN_ID, RINKEBY_CHAIN_ID, ROPSTEN_CHAIN_ID, KOVAN_CHAIN_ID } from '../controllers/network/enums'
|
import {
|
||||||
|
MAINNET_CHAIN_ID,
|
||||||
|
RINKEBY_CHAIN_ID,
|
||||||
|
ROPSTEN_CHAIN_ID,
|
||||||
|
KOVAN_CHAIN_ID,
|
||||||
|
} from '../controllers/network/enums'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SINGLE_CALL_BALANCES_ADDRESS,
|
SINGLE_CALL_BALANCES_ADDRESS,
|
||||||
@ -42,14 +47,13 @@ import { bnToHex } from './util'
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export default class AccountTracker {
|
export default class AccountTracker {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object} opts - Options for initializing the controller
|
* @param {Object} opts - Options for initializing the controller
|
||||||
* @param {Object} opts.provider - An EIP-1193 provider instance that uses the current global network
|
* @param {Object} opts.provider - An EIP-1193 provider instance that uses the current global network
|
||||||
* @param {Object} opts.blockTracker - A block tracker, which emits events for each new block
|
* @param {Object} opts.blockTracker - A block tracker, which emits events for each new block
|
||||||
* @param {Function} opts.getCurrentChainId - A function that returns the `chainId` for the current global network
|
* @param {Function} opts.getCurrentChainId - A function that returns the `chainId` for the current global network
|
||||||
*/
|
*/
|
||||||
constructor (opts = {}) {
|
constructor(opts = {}) {
|
||||||
const initState = {
|
const initState = {
|
||||||
accounts: {},
|
accounts: {},
|
||||||
currentBlockGasLimit: '',
|
currentBlockGasLimit: '',
|
||||||
@ -71,7 +75,7 @@ export default class AccountTracker {
|
|||||||
this.web3 = new Web3(this._provider)
|
this.web3 = new Web3(this._provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
start () {
|
start() {
|
||||||
// remove first to avoid double add
|
// remove first to avoid double add
|
||||||
this._blockTracker.removeListener('latest', this._updateForBlock)
|
this._blockTracker.removeListener('latest', this._updateForBlock)
|
||||||
// add listener
|
// add listener
|
||||||
@ -80,7 +84,7 @@ export default class AccountTracker {
|
|||||||
this._updateAccounts()
|
this._updateAccounts()
|
||||||
}
|
}
|
||||||
|
|
||||||
stop () {
|
stop() {
|
||||||
// remove listener
|
// remove listener
|
||||||
this._blockTracker.removeListener('latest', this._updateForBlock)
|
this._blockTracker.removeListener('latest', this._updateForBlock)
|
||||||
}
|
}
|
||||||
@ -92,11 +96,11 @@ export default class AccountTracker {
|
|||||||
* Once this AccountTracker's accounts are up to date with those referenced by the passed addresses, each
|
* Once this AccountTracker's accounts are up to date with those referenced by the passed addresses, each
|
||||||
* of these accounts are given an updated balance via EthQuery.
|
* of these accounts are given an updated balance via EthQuery.
|
||||||
*
|
*
|
||||||
* @param {array} address - The array of hex addresses for accounts with which this AccountTracker's accounts should be
|
* @param {Array} address - The array of hex addresses for accounts with which this AccountTracker's accounts should be
|
||||||
* in sync
|
* in sync
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
syncWithAddresses (addresses) {
|
syncWithAddresses(addresses) {
|
||||||
const { accounts } = this.store.getState()
|
const { accounts } = this.store.getState()
|
||||||
const locals = Object.keys(accounts)
|
const locals = Object.keys(accounts)
|
||||||
|
|
||||||
@ -122,10 +126,10 @@ export default class AccountTracker {
|
|||||||
* Adds new addresses to track the balances of
|
* Adds new addresses to track the balances of
|
||||||
* given a balance as long this._currentBlockNumber is defined.
|
* given a balance as long this._currentBlockNumber is defined.
|
||||||
*
|
*
|
||||||
* @param {array} addresses - An array of hex addresses of new accounts to track
|
* @param {Array} addresses - An array of hex addresses of new accounts to track
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
addAccounts (addresses) {
|
addAccounts(addresses) {
|
||||||
const { accounts } = this.store.getState()
|
const { accounts } = this.store.getState()
|
||||||
// add initial state for addresses
|
// add initial state for addresses
|
||||||
addresses.forEach((address) => {
|
addresses.forEach((address) => {
|
||||||
@ -143,10 +147,10 @@ export default class AccountTracker {
|
|||||||
/**
|
/**
|
||||||
* Removes accounts from being tracked
|
* Removes accounts from being tracked
|
||||||
*
|
*
|
||||||
* @param {array} an - array of hex addresses to stop tracking
|
* @param {Array} an - array of hex addresses to stop tracking
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
removeAccount (addresses) {
|
removeAccount(addresses) {
|
||||||
const { accounts } = this.store.getState()
|
const { accounts } = this.store.getState()
|
||||||
// remove each state object
|
// remove each state object
|
||||||
addresses.forEach((address) => {
|
addresses.forEach((address) => {
|
||||||
@ -160,7 +164,7 @@ export default class AccountTracker {
|
|||||||
* Removes all addresses and associated balances
|
* Removes all addresses and associated balances
|
||||||
*/
|
*/
|
||||||
|
|
||||||
clearAccounts () {
|
clearAccounts() {
|
||||||
this.store.updateState({ accounts: {} })
|
this.store.updateState({ accounts: {} })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,7 +177,7 @@ export default class AccountTracker {
|
|||||||
* @fires 'block' The updated state, if all account updates are successful
|
* @fires 'block' The updated state, if all account updates are successful
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async _updateForBlock (blockNumber) {
|
async _updateForBlock(blockNumber) {
|
||||||
this._currentBlockNumber = blockNumber
|
this._currentBlockNumber = blockNumber
|
||||||
|
|
||||||
// block gasLimit polling shouldn't be in account-tracker shouldn't be here...
|
// block gasLimit polling shouldn't be in account-tracker shouldn't be here...
|
||||||
@ -195,29 +199,41 @@ export default class AccountTracker {
|
|||||||
* balanceChecker is deployed on main eth (test)nets and requires a single call
|
* balanceChecker is deployed on main eth (test)nets and requires a single call
|
||||||
* for all other networks, calls this._updateAccount for each account in this.store
|
* for all other networks, calls this._updateAccount for each account in this.store
|
||||||
*
|
*
|
||||||
* @returns {Promise} - after all account balances updated
|
* @returns {Promise} after all account balances updated
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async _updateAccounts () {
|
async _updateAccounts() {
|
||||||
const { accounts } = this.store.getState()
|
const { accounts } = this.store.getState()
|
||||||
const addresses = Object.keys(accounts)
|
const addresses = Object.keys(accounts)
|
||||||
const chainId = this.getCurrentChainId()
|
const chainId = this.getCurrentChainId()
|
||||||
|
|
||||||
switch (chainId) {
|
switch (chainId) {
|
||||||
case MAINNET_CHAIN_ID:
|
case MAINNET_CHAIN_ID:
|
||||||
await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS)
|
await this._updateAccountsViaBalanceChecker(
|
||||||
|
addresses,
|
||||||
|
SINGLE_CALL_BALANCES_ADDRESS,
|
||||||
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
case RINKEBY_CHAIN_ID:
|
case RINKEBY_CHAIN_ID:
|
||||||
await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_RINKEBY)
|
await this._updateAccountsViaBalanceChecker(
|
||||||
|
addresses,
|
||||||
|
SINGLE_CALL_BALANCES_ADDRESS_RINKEBY,
|
||||||
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
case ROPSTEN_CHAIN_ID:
|
case ROPSTEN_CHAIN_ID:
|
||||||
await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN)
|
await this._updateAccountsViaBalanceChecker(
|
||||||
|
addresses,
|
||||||
|
SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN,
|
||||||
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
case KOVAN_CHAIN_ID:
|
case KOVAN_CHAIN_ID:
|
||||||
await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_KOVAN)
|
await this._updateAccountsViaBalanceChecker(
|
||||||
|
addresses,
|
||||||
|
SINGLE_CALL_BALANCES_ADDRESS_KOVAN,
|
||||||
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -230,10 +246,10 @@ export default class AccountTracker {
|
|||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {string} address - A hex address of a the account to be updated
|
* @param {string} address - A hex address of a the account to be updated
|
||||||
* @returns {Promise} - after the account balance is updated
|
* @returns {Promise} after the account balance is updated
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async _updateAccount (address) {
|
async _updateAccount(address) {
|
||||||
// query balance
|
// query balance
|
||||||
const balance = await this._query.getBalance(address)
|
const balance = await this._query.getBalance(address)
|
||||||
const result = { address, balance }
|
const result = { address, balance }
|
||||||
@ -252,15 +268,20 @@ export default class AccountTracker {
|
|||||||
* @param {*} addresses
|
* @param {*} addresses
|
||||||
* @param {*} deployedContractAddress
|
* @param {*} deployedContractAddress
|
||||||
*/
|
*/
|
||||||
async _updateAccountsViaBalanceChecker (addresses, deployedContractAddress) {
|
async _updateAccountsViaBalanceChecker(addresses, deployedContractAddress) {
|
||||||
const { accounts } = this.store.getState()
|
const { accounts } = this.store.getState()
|
||||||
this.web3.setProvider(this._provider)
|
this.web3.setProvider(this._provider)
|
||||||
const ethContract = this.web3.eth.contract(SINGLE_CALL_BALANCES_ABI).at(deployedContractAddress)
|
const ethContract = this.web3.eth
|
||||||
|
.contract(SINGLE_CALL_BALANCES_ABI)
|
||||||
|
.at(deployedContractAddress)
|
||||||
const ethBalance = ['0x0']
|
const ethBalance = ['0x0']
|
||||||
|
|
||||||
ethContract.balances(addresses, ethBalance, (error, result) => {
|
ethContract.balances(addresses, ethBalance, (error, result) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
log.warn(`MetaMask - Account Tracker single call balance fetch failed`, error)
|
log.warn(
|
||||||
|
`MetaMask - Account Tracker single call balance fetch failed`,
|
||||||
|
error,
|
||||||
|
)
|
||||||
Promise.all(addresses.map(this._updateAccount.bind(this)))
|
Promise.all(addresses.map(this._updateAccount.bind(this)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -271,5 +292,4 @@ export default class AccountTracker {
|
|||||||
this.store.updateState({ accounts })
|
this.store.updateState({ accounts })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
* Gives the caller a url at which the user can acquire eth, depending on the network they are in
|
* Gives the caller a url at which the user can acquire eth, depending on the network they are in
|
||||||
*
|
*
|
||||||
* @param {Object} opts - Options required to determine the correct url
|
* @param {Object} opts - Options required to determine the correct url
|
||||||
* @param {string} opts.network The network for which to return a url
|
* @param {string} opts.network - The network for which to return a url
|
||||||
* @param {string} opts.address The address the bought ETH should be sent to. Only relevant if network === '1'.
|
* @param {string} opts.address - The address the bought ETH should be sent to. Only relevant if network === '1'.
|
||||||
* @returns {string|undefined} - The url at which the user can access ETH, while in the given network. If the passed
|
* @returns {string|undefined} The url at which the user can access ETH, while in the given network. If the passed
|
||||||
* network does not match any of the specified cases, or if no network is given, returns undefined.
|
* network does not match any of the specified cases, or if no network is given, returns undefined.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export default function getBuyEthUrl ({ network, address, service }) {
|
export default function getBuyEthUrl({ network, address, service }) {
|
||||||
// default service by network if not specified
|
// default service by network if not specified
|
||||||
if (!service) {
|
if (!service) {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
@ -33,7 +33,7 @@ export default function getBuyEthUrl ({ network, address, service }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDefaultServiceForNetwork (network) {
|
function getDefaultServiceForNetwork(network) {
|
||||||
switch (network) {
|
switch (network) {
|
||||||
case '1':
|
case '1':
|
||||||
return 'wyre'
|
return 'wyre'
|
||||||
@ -46,6 +46,8 @@ function getDefaultServiceForNetwork (network) {
|
|||||||
case '5':
|
case '5':
|
||||||
return 'goerli-faucet'
|
return 'goerli-faucet'
|
||||||
default:
|
default:
|
||||||
throw new Error(`No default cryptocurrency exchange or faucet for networkId: "${network}"`)
|
throw new Error(
|
||||||
|
`No default cryptocurrency exchange or faucet for networkId: "${network}"`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
* Returns error without stack trace for better UI display
|
* Returns error without stack trace for better UI display
|
||||||
* @param {Error} err - error
|
* @param {Error} err - error
|
||||||
* @returns {Error} - Error with clean stack trace.
|
* @returns {Error} Error with clean stack trace.
|
||||||
*/
|
*/
|
||||||
export default function cleanErrorStack (err) {
|
export default function cleanErrorStack(err) {
|
||||||
let { name } = err
|
let { name } = err
|
||||||
name = (name === undefined) ? 'Error' : String(name)
|
name = name === undefined ? 'Error' : String(name)
|
||||||
|
|
||||||
let msg = err.message
|
let msg = err.message
|
||||||
msg = (msg === undefined) ? '' : String(msg)
|
msg = msg === undefined ? '' : String(msg)
|
||||||
|
|
||||||
if (name === '') {
|
if (name === '') {
|
||||||
err.stack = err.message
|
err.stack = err.message
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user