aboutsummaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
authorThomas <thomas.b.huang@gmail.com>2018-05-08 05:30:43 +0800
committerThomas <thomas.b.huang@gmail.com>2018-05-08 05:30:43 +0800
commit8250fd9d6daae3bcef17d8de4e3f2b22ac58ce5b (patch)
tree7deae9281915f1499883fc1eb71b6fb62717dd42 /app
parent6bf6589c440330d0089da4012709b831c722945a (diff)
parentcaf5a6c15c3375d9d64116d80d87eb064e955e59 (diff)
downloadtangerine-wallet-browser-8250fd9d6daae3bcef17d8de4e3f2b22ac58ce5b.tar.gz
tangerine-wallet-browser-8250fd9d6daae3bcef17d8de4e3f2b22ac58ce5b.tar.zst
tangerine-wallet-browser-8250fd9d6daae3bcef17d8de4e3f2b22ac58ce5b.zip
Merge branch 'develop' into testing
Diffstat (limited to 'app')
-rw-r--r--app/_locales/en/messages.json33
-rw-r--r--app/_locales/sl/messages.json8
-rw-r--r--app/images/copy-to-clipboard.svg24
-rw-r--r--app/images/download.svg41
-rw-r--r--app/images/warning.svg22
-rw-r--r--app/manifest.json2
-rw-r--r--app/scripts/background.js135
-rw-r--r--app/scripts/config.js79
-rw-r--r--app/scripts/contentscript.js1
-rw-r--r--app/scripts/controllers/balance.js56
-rw-r--r--app/scripts/controllers/blacklist.js53
-rw-r--r--app/scripts/controllers/network/enums.js56
-rw-r--r--app/scripts/controllers/network/index.js2
-rw-r--r--app/scripts/controllers/network/network.js (renamed from app/scripts/controllers/network.js)29
-rw-r--r--app/scripts/controllers/network/util.js65
-rw-r--r--app/scripts/controllers/preferences.js4
-rw-r--r--app/scripts/controllers/recent-blocks.js68
-rw-r--r--app/scripts/controllers/token-rates.js8
-rw-r--r--app/scripts/controllers/transactions/README.md92
-rw-r--r--app/scripts/controllers/transactions/index.js (renamed from app/scripts/controllers/transactions.js)345
-rw-r--r--app/scripts/controllers/transactions/lib/tx-state-history-helper.js (renamed from app/scripts/lib/tx-state-history-helper.js)27
-rw-r--r--app/scripts/controllers/transactions/lib/util.js99
-rw-r--r--app/scripts/controllers/transactions/nonce-tracker.js (renamed from app/scripts/lib/nonce-tracker.js)44
-rw-r--r--app/scripts/controllers/transactions/pending-tx-tracker.js (renamed from app/scripts/lib/pending-tx-tracker.js)77
-rw-r--r--app/scripts/controllers/transactions/tx-gas-utils.js (renamed from app/scripts/lib/tx-gas-utils.js)36
-rw-r--r--app/scripts/controllers/transactions/tx-state-manager.js (renamed from app/scripts/lib/tx-state-manager.js)209
-rw-r--r--app/scripts/first-time-state.js3
-rw-r--r--app/scripts/lib/account-tracker.js81
-rw-r--r--app/scripts/lib/config-manager.js45
-rw-r--r--app/scripts/lib/extractEthjsErrorMessage.js23
-rw-r--r--app/scripts/lib/getObjStructure.js17
-rw-r--r--app/scripts/lib/message-manager.js137
-rw-r--r--app/scripts/lib/notification-manager.js44
-rw-r--r--app/scripts/lib/pending-balance-calculator.js38
-rw-r--r--app/scripts/lib/personal-message-manager.js142
-rw-r--r--app/scripts/lib/seed-phrase-verifier.js18
-rw-r--r--app/scripts/lib/setupRaven.js61
-rw-r--r--app/scripts/lib/typed-message-manager.js137
-rw-r--r--app/scripts/metamask-controller.js464
-rw-r--r--app/scripts/migrations/018.js2
40 files changed, 2245 insertions, 582 deletions
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 3b20ab49a..a40c2635c 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -98,6 +98,9 @@
"clickCopy": {
"message": "Click to Copy"
},
+ "close": {
+ "message": "Close"
+ },
"confirm": {
"message": "Confirm"
},
@@ -259,6 +262,9 @@
"enterPasswordConfirm": {
"message": "Enter your password to confirm"
},
+ "enterPasswordContinue": {
+ "message": "Enter password to continue"
+ },
"passwordNotLongEnough": {
"message": "Password not long enough"
},
@@ -331,6 +337,9 @@
"gasPriceRequired": {
"message": "Gas Price Required"
},
+ "generatingTransaction": {
+ "message": "Generating transaction"
+ },
"getEther": {
"message": "Get Ether"
},
@@ -476,6 +485,9 @@
"metamaskDescription": {
"message": "MetaMask is a secure identity vault for Ethereum."
},
+ "metamaskSeedWords": {
+ "message": "MetaMask Seed Words"
+ },
"min": {
"message": "Minimum"
},
@@ -549,6 +561,9 @@
"message": "or",
"description": "choice between creating or importing a new account"
},
+ "password": {
+ "message": "Password"
+ },
"passwordCorrect": {
"message": "Please make sure your password is correct."
},
@@ -634,8 +649,17 @@
"revealSeedWords": {
"message": "Reveal Seed Words"
},
+ "revealSeedWordsTitle": {
+ "message": "Seed Phrase"
+ },
+ "revealSeedWordsDescription": {
+ "message": "If you ever change browsers or move computers, you will need this seed phrase to access your accounts. Save them somewhere safe and secret."
+ },
+ "revealSeedWordsWarningTitle": {
+ "message": "DO NOT share this phrase with anyone!"
+ },
"revealSeedWordsWarning": {
- "message": "Do not recover your seed words in a public place! These words can be used to steal all your accounts."
+ "message": "These words can be used to steal all your accounts."
},
"revert": {
"message": "Revert"
@@ -677,6 +701,9 @@
"reprice_subtitle": {
"message": "Increase your gas price to attempt to overwrite and speed up your transaction"
},
+ "saveAsCsvFile": {
+ "message": "Save as CSV File"
+ },
"saveAsFile": {
"message": "Save as File",
"description": "Account export process"
@@ -909,7 +936,7 @@
"youSign": {
"message": "You are signing"
},
- "generatingTransaction": {
- "message": "Generating transaction"
+ "yourPrivateSeedPhrase": {
+ "message": "Your private seed phrase"
}
}
diff --git a/app/_locales/sl/messages.json b/app/_locales/sl/messages.json
index b089f3476..25bd0bcbb 100644
--- a/app/_locales/sl/messages.json
+++ b/app/_locales/sl/messages.json
@@ -181,7 +181,7 @@
"message": "DEN je vaša šifrirana shramba v MetaMasku."
},
"deposit": {
- "message": "Vplačilo"
+ "message": "Vplačaj"
},
"depositBTC": {
"message": "Vplačajte vaš BTC na spodnji naslov:"
@@ -507,10 +507,10 @@
"message": "Ni se začelo"
},
"oldUI": {
- "message": "Starejši uporabniški vmesnik"
+ "message": "Star UI"
},
"oldUIMessage": {
- "message": "Vrnili ste se v starejši uporabniški vmesnik. V novega se lahko vrnete z možnostjo v spustnem meniju v zgornjem desnem kotu."
+ "message": "Vrnili ste se v star uporabniški vmesnik. V novega se lahko vrnete z možnostjo v spustnem meniju v zgornjem desnem kotu."
},
"or": {
"message": "ali",
@@ -759,7 +759,7 @@
"message": "Vpišite vaše geslo"
},
"uiWelcome": {
- "message": "Dobrodošli v novem uporabniškem vmesniku (Beta)"
+ "message": "Dobrodošli v nov UI (Beta)"
},
"uiWelcomeMessage": {
"message": "Zdaj uporabljate novi MetaMask uporabniški vmesnik. Razglejte se, preizkusite nove funkcije, kot so pošiljanje žetonov, in nas obvestite, če imate kakšne težave."
diff --git a/app/images/copy-to-clipboard.svg b/app/images/copy-to-clipboard.svg
new file mode 100644
index 000000000..c67c2aa84
--- /dev/null
+++ b/app/images/copy-to-clipboard.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="18px" height="17px" viewBox="0 0 18 17" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: sketchtool 49.3 (51167) - http://www.bohemiancoding.com/sketch -->
+ <title>374E58A5-C29E-4921-83E7-889FA06D6408</title>
+ <desc>Created with sketchtool.</desc>
+ <defs></defs>
+ <g id="Reveal-Seedphrase" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g id="Seed-phrase-2" transform="translate(-39.000000, -379.000000)">
+ <g id="Group-2">
+ <g id="Group-8" transform="translate(16.000000, 248.000000)">
+ <g id="Group-6" transform="translate(23.336478, 120.000000)">
+ <g id="Group-5" transform="translate(0.408805, 11.000000)">
+ <g id="copy-to-clipboard">
+ <rect id="Rectangle-18" stroke="#3098DC" stroke-width="2" x="1" y="1" width="12.0220126" height="12"></rect>
+ <rect id="Rectangle-18-Copy-2" fill="#FFFFFF" x="2.1572327" y="2" width="14.0220126" height="14"></rect>
+ <rect id="Rectangle-18-Copy" stroke="#3098DC" stroke-width="2" x="4.23584906" y="4" width="12.0220126" height="12"></rect>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/images/download.svg b/app/images/download.svg
index 137a1190e..b55066414 100644
--- a/app/images/download.svg
+++ b/app/images/download.svg
@@ -1,15 +1,26 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- width="24.088px" height="24px" viewBox="138.01 0 24.088 24" enable-background="new 138.01 0 24.088 24" xml:space="preserve" fill="#F7861C">
-<g>
- <polygon fill="#F7861C" points="157.551,17.075 156.55,17.075 156.55,19.149 142.569,19.149 142.569,17.075 141.568,17.075
- 141.568,20.145 141.955,20.145 141.955,20.15 157.006,20.15 157.006,20.145 157.551,20.145 "/>
- <polygon fill="#F7861C" points="152.555,10.275 152.555,11.26 152.555,11.268 151.562,11.268 151.562,12.252 150.565,12.252
- 150.565,4.171 149.564,4.171 149.564,12.236 148.564,12.236 148.564,11.252 147.564,11.252 147.564,11.236 147.564,11.221
- 147.564,10.236 146.563,10.236 146.563,11.221 146.563,11.236 146.563,12.221 147.563,12.221 147.563,12.236 147.563,12.252
- 147.563,13.236 148.563,13.236 148.563,14.221 149.564,14.221 149.564,15.725 150.565,15.725 150.565,14.236 151.563,14.236
- 151.563,13.252 152.563,13.252 152.563,12.268 152.563,12.26 153.556,12.26 153.556,11.275 153.556,11.26 153.556,10.275 "/>
-</g>
-</svg>
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="20px" height="18px" viewBox="0 0 20 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: sketchtool 49.3 (51167) - http://www.bohemiancoding.com/sketch -->
+ <title>50559280-0739-419A-8E87-3CDD16A6996A</title>
+ <desc>Created with sketchtool.</desc>
+ <defs></defs>
+ <g id="Reveal-Seedphrase" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g id="Seed-phrase-2" transform="translate(-212.000000, -379.000000)" stroke="#259DE5" stroke-width="2">
+ <g id="Group-2">
+ <g id="Group-8" transform="translate(16.000000, 248.000000)">
+ <g id="Group-6" transform="translate(23.336478, 120.000000)">
+ <g id="Group-3" transform="translate(174.000000, 11.000000)">
+ <g id="Group-4">
+ <g id="download">
+ <polyline id="Path-5" points="0 11 0 17 17 17 17 11"></polyline>
+ <path d="M8.5,0 L8.5,11" id="Path-6"></path>
+ <polyline id="Path-7" points="3.1875 7 8.5 11 13.8125 7"></polyline>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/images/warning.svg b/app/images/warning.svg
new file mode 100644
index 000000000..9c8d697d7
--- /dev/null
+++ b/app/images/warning.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="33px" height="32px" viewBox="0 0 33 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 49.3 (51167) - http://www.bohemiancoding.com/sketch -->
+ <title>Group 7</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Reveal-Seedphrase" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g id="Seed-phrase-2" transform="translate(-29.000000, -155.000000)">
+ <g id="Group-2" transform="translate(0.000000, 132.000000)">
+ <g id="Group" transform="translate(28.000000, 19.000000)">
+ <g id="Group-19-Copy-2" transform="translate(0.000000, 3.000000)">
+ <g id="Group-7">
+ <path d="M20.1321134,3.85444772 L32.5721829,26.6020033 C33.367162,28.0556794 32.8331826,29.8785746 31.3795065,30.6735537 C30.9381289,30.9149321 30.4431378,31.0414403 29.9400695,31.0414403 L5.05993054,31.0414403 C3.40307629,31.0414403 2.05993054,29.6982946 2.05993054,28.0414403 C2.05993054,27.538372 2.18643873,27.0433809 2.42781712,26.6020033 L14.8678866,3.85444772 C15.6628657,2.40077162 17.4857609,1.86679221 18.939437,2.66177133 C19.442875,2.93708896 19.8567958,3.35100977 20.1321134,3.85444772 Z" id="Triangle-2-Copy" stroke="#FF001F" stroke-width="2"></path>
+ <rect id="Rectangle-5" fill="#FF001F" x="16" y="9" width="3" height="13"></rect>
+ <rect id="Rectangle-5-Copy" fill="#FF001F" x="16" y="24" width="3" height="3"></rect>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/manifest.json b/app/manifest.json
index dc46f1ca4..3e5eed205 100644
--- a/app/manifest.json
+++ b/app/manifest.json
@@ -1,7 +1,7 @@
{
"name": "__MSG_appName__",
"short_name": "__MSG_appName__",
- "version": "4.5.5",
+ "version": "4.6.0",
"manifest_version": 2,
"author": "https://metamask.io",
"description": "__MSG_appDescription__",
diff --git a/app/scripts/background.js b/app/scripts/background.js
index 6550e8944..69d549c85 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -1,3 +1,7 @@
+/**
+ * @file The entry point for the web extension singleton process.
+ */
+
const urlUtil = require('url')
const endOfStream = require('end-of-stream')
const pump = require('pump')
@@ -61,6 +65,90 @@ initialize().catch(log.error)
// setup metamask mesh testing container
setupMetamaskMeshMetrics()
+/**
+ * An object representing a transaction, in whatever state it is in.
+ * @typedef TransactionMeta
+ *
+ * @property {number} id - An internally unique tx identifier.
+ * @property {number} time - Time the tx was first suggested, in unix epoch time (ms).
+ * @property {string} status - The current transaction status (unapproved, signed, submitted, dropped, failed, rejected), as defined in `tx-state-manager.js`.
+ * @property {string} metamaskNetworkId - The transaction's network ID, used for EIP-155 compliance.
+ * @property {boolean} loadingDefaults - TODO: Document
+ * @property {Object} txParams - The tx params as passed to the network provider.
+ * @property {Object[]} history - A history of mutations to this TransactionMeta object.
+ * @property {boolean} gasPriceSpecified - True if the suggesting dapp specified a gas price, prevents auto-estimation.
+ * @property {boolean} gasLimitSpecified - True if the suggesting dapp specified a gas limit, prevents auto-estimation.
+ * @property {string} estimatedGas - A hex string represented the estimated gas limit required to complete the transaction.
+ * @property {string} origin - A string representing the interface that suggested the transaction.
+ * @property {Object} nonceDetails - A metadata object containing information used to derive the suggested nonce, useful for debugging nonce issues.
+ * @property {string} rawTx - A hex string of the final signed transaction, ready to submit to the network.
+ * @property {string} hash - A hex string of the transaction hash, used to identify the transaction on the network.
+ * @property {number} submittedTime - The time the transaction was submitted to the network, in Unix epoch time (ms).
+ */
+
+/**
+ * The data emitted from the MetaMaskController.store EventEmitter, also used to initialize the MetaMaskController. Available in UI on React state as state.metamask.
+ * @typedef MetaMaskState
+ * @property {boolean} isInitialized - Whether the first vault has been created.
+ * @property {boolean} isUnlocked - Whether the vault is currently decrypted and accounts are available for selection.
+ * @property {boolean} isAccountMenuOpen - Represents whether the main account selection UI is currently displayed.
+ * @property {boolean} isMascara - True if the current context is the extensionless MetaMascara project.
+ * @property {boolean} isPopup - Returns true if the current view is an externally-triggered notification.
+ * @property {string} rpcTarget - DEPRECATED - The URL of the current RPC provider.
+ * @property {Object} identities - An object matching lower-case hex addresses to Identity objects with "address" and "name" (nickname) keys.
+ * @property {Object} unapprovedTxs - An object mapping transaction hashes to unapproved transactions.
+ * @property {boolean} noActiveNotices - False if there are notices the user should confirm before using the application.
+ * @property {Array} frequentRpcList - A list of frequently used RPCs, including custom user-provided ones.
+ * @property {Array} addressBook - A list of previously sent to addresses.
+ * @property {address} selectedTokenAddress - Used to indicate if a token is globally selected. Should be deprecated in favor of UI-centric token selection.
+ * @property {Object} tokenExchangeRates - Info about current token prices.
+ * @property {Array} tokens - Tokens held by the current user, including their balances.
+ * @property {Object} send - TODO: Document
+ * @property {Object} coinOptions - TODO: Document
+ * @property {boolean} useBlockie - Indicates preferred user identicon format. True for blockie, false for Jazzicon.
+ * @property {Object} featureFlags - An object for optional feature flags.
+ * @property {string} networkEndpointType - TODO: Document
+ * @property {boolean} isRevealingSeedWords - True if seed words are currently being recovered, and should be shown to user.
+ * @property {boolean} welcomeScreen - True if welcome screen should be shown.
+ * @property {string} currentLocale - A locale string matching the user's preferred display language.
+ * @property {Object} provider - The current selected network provider.
+ * @property {string} provider.rpcTarget - The address for the RPC API, if using an RPC API.
+ * @property {string} provider.type - An identifier for the type of network selected, allows MetaMask to use custom provider strategies for known networks.
+ * @property {string} network - A stringified number of the current network ID.
+ * @property {Object} accounts - An object mapping lower-case hex addresses to objects with "balance" and "address" keys, both storing hex string values.
+ * @property {hex} currentBlockGasLimit - The most recently seen block gas limit, in a lower case hex prefixed string.
+ * @property {TransactionMeta[]} selectedAddressTxList - An array of transactions associated with the currently selected account.
+ * @property {Object} unapprovedMsgs - An object of messages associated with the currently selected account, mapping a unique ID to the options.
+ * @property {number} unapprovedMsgCount - The number of messages in unapprovedMsgs.
+ * @property {Object} unapprovedPersonalMsgs - An object of messages associated with the currently selected account, mapping a unique ID to the options.
+ * @property {number} unapprovedPersonalMsgCount - The number of messages in unapprovedPersonalMsgs.
+ * @property {Object} unapprovedTypedMsgs - An object of messages associated with the currently selected account, mapping a unique ID to the options.
+ * @property {number} unapprovedTypedMsgCount - The number of messages in unapprovedTypedMsgs.
+ * @property {string[]} keyringTypes - An array of unique keyring identifying strings, representing available strategies for creating accounts.
+ * @property {Keyring[]} keyrings - An array of keyring descriptions, summarizing the accounts that are available for use, and what keyrings they belong to.
+ * @property {Object} computedBalances - Maps accounts to their balances, accounting for balance changes from pending transactions.
+ * @property {string} currentAccountTab - A view identifying string for displaying the current displayed view, allows user to have a preferred tab in the old UI (between tokens and history).
+ * @property {string} selectedAddress - A lower case hex string of the currently selected address.
+ * @property {string} currentCurrency - A string identifying the user's preferred display currency, for use in showing conversion rates.
+ * @property {number} conversionRate - A number representing the current exchange rate from the user's preferred currency to Ether.
+ * @property {number} conversionDate - A unix epoch date (ms) for the time the current conversion rate was last retrieved.
+ * @property {Object} infuraNetworkStatus - An object of infura network status checks.
+ * @property {Block[]} recentBlocks - An array of recent blocks, used to calculate an effective but cheap gas price.
+ * @property {Array} shapeShiftTxList - An array of objects describing shapeshift exchange attempts.
+ * @property {Array} lostAccounts - TODO: Remove this feature. A leftover from the version-3 migration where our seed-phrase library changed to fix a bug where some accounts were mis-generated, but we recovered the old accounts as "lost" instead of losing them.
+ * @property {boolean} forgottenPassword - Returns true if the user has initiated the password recovery screen, is recovering from seed phrase.
+ */
+
+/**
+ * @typedef VersionedData
+ * @property {MetaMaskState} data - The data emitted from MetaMask controller, or used to initialize it.
+ * @property {Number} version - The latest migration version that has been run.
+ */
+
+/**
+ * Initializes the MetaMask controller, and sets up all platform configuration.
+ * @returns {Promise} Setup complete.
+ */
async function initialize () {
const initState = await loadStateFromPersistence()
const initLangCode = await getFirstPreferredLangCode()
@@ -72,6 +160,11 @@ async function initialize () {
// State and Persistence
//
+/**
+ * Loads any stored data, prioritizing the latest storage strategy.
+ * Migrates that data schema in case it was last loaded on an older version.
+ * @returns {Promise<MetaMaskState>} Last data emitted from previous instance of MetaMask.
+ */
async function loadStateFromPersistence () {
// migrations
const migrator = new Migrator({ migrations })
@@ -134,6 +227,16 @@ async function loadStateFromPersistence () {
return versionedData.data
}
+/**
+ * Initializes the MetaMask Controller with any initial state and default language.
+ * Configures platform-specific error reporting strategy.
+ * Streams emitted state updates to platform-specific storage strategy.
+ * Creates platform listeners for new Dapps/Contexts, and sets up their data connections to 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.
+ * @returns {Promise} After setup is complete.
+ */
function setupController (initState, initLangCode) {
//
// MetaMask Controller
@@ -158,7 +261,11 @@ function setupController (initState, initLangCode) {
controller.txController.on(`tx:status-update`, (txId, status) => {
if (status !== 'failed') return
const txMeta = controller.txController.txStateManager.getTx(txId)
- reportFailedTxToSentry({ raven, txMeta })
+ try {
+ reportFailedTxToSentry({ raven, txMeta })
+ } catch (e) {
+ console.error(e)
+ }
})
// setup state persistence
@@ -172,6 +279,11 @@ function setupController (initState, initLangCode) {
}
)
+ /**
+ * Assigns the given state to the versioned object (with metadata), and returns that.
+ * @param {Object} state - The state object as emitted by the MetaMaskController.
+ * @returns {VersionedData} The state object wrapped in an object that includes a metadata key.
+ */
function versionifyData (state) {
versionedData.data = state
return versionedData
@@ -208,6 +320,18 @@ function setupController (initState, initLangCode) {
return popupIsOpen || Boolean(Object.keys(openMetamaskTabsIDs).length) || notificationIsOpen
}
+ /**
+ * A runtime.Port object, as provided by the browser:
+ * @link https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/runtime/Port
+ * @typedef Port
+ * @type Object
+ */
+
+ /**
+ * Connects a Port to the MetaMask controller via a multiplexed duplex stream.
+ * This method identifies trusted (MetaMask) interfaces, and connects them differently from untrusted (web pages).
+ * @param {Port} remotePort - The port provided by a new context.
+ */
function connectRemote (remotePort) {
const processName = remotePort.name
const isMetaMaskInternalProcess = metamaskInternalProcessHash[processName]
@@ -261,7 +385,10 @@ function setupController (initState, initLangCode) {
controller.messageManager.on('updateBadge', updateBadge)
controller.personalMessageManager.on('updateBadge', updateBadge)
- // plugin badge text
+ /**
+ * Updates the Web Extension's "badge" number, on the little fox in the toolbar.
+ * The number reflects the current number of pending transactions or message signatures needing user approval.
+ */
function updateBadge () {
var label = ''
var unapprovedTxCount = controller.txController.getUnapprovedTxCount()
@@ -283,7 +410,9 @@ function setupController (initState, initLangCode) {
// Etc...
//
-// popup trigger
+/**
+ * Opens the browser popup for user confirmation
+ */
function triggerUi () {
extension.tabs.query({ active: true }, tabs => {
const currentlyActiveMetamaskTab = Boolean(tabs.find(tab => openMetamaskTabsIDs[tab.id]))
diff --git a/app/scripts/config.js b/app/scripts/config.js
deleted file mode 100644
index e6f70ca2b..000000000
--- a/app/scripts/config.js
+++ /dev/null
@@ -1,79 +0,0 @@
-const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask'
-const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask'
-const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask'
-const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask'
-const LOCALHOST_RPC_URL = 'http://localhost:8545'
-
-const MAINET_RPC_URL_BETA = 'https://mainnet.infura.io/metamask2'
-const ROPSTEN_RPC_URL_BETA = 'https://ropsten.infura.io/metamask2'
-const KOVAN_RPC_URL_BETA = 'https://kovan.infura.io/metamask2'
-const RINKEBY_RPC_URL_BETA = 'https://rinkeby.infura.io/metamask2'
-
-const DEFAULT_RPC = 'rinkeby'
-const OLD_UI_NETWORK_TYPE = 'network'
-const BETA_UI_NETWORK_TYPE = 'networkBeta'
-
-global.METAMASK_DEBUG = process.env.METAMASK_DEBUG
-
-/**
- * @typedef {Object} UrlConfig
- * @property {string} localhost URL of local RPC provider
- * @property {string} mainnet URL of mainnet RPC provider
- * @property {string} ropsten URL of Ropsten testnet RPC provider
- * @property {string} kovan URL of Kovan testnet RPC provider
- * @property {string} rinkeby URL of Rinkeby testnet RPC provider
- */
-
-/**
- * @typedef {Object} NameConfig
- * @property {string} 3 URL of local RPC provider
- * @property {string} 4 URL of mainnet RPC provider
- * @property {string} 42 URL of Ropsten testnet RPC provider
- */
-
-/**
- * @typedef {Object} EnumConfig
- * @property {string} DEFAULT_RPC Default network provider URL
- * @property {string} OLD_UI_NETWORK_TYPE Network associated with old UI
- * @property {string} BETA_UI_NETWORK_TYPE Network associated with new UI
- */
-
-/**
- * @typedef {Object} Config
- * @property {UrlConfig} network Network configuration parameters
- * @property {UrlConfig} networkBeta Beta UI network configuration parameters
- * @property {NameConfig} networkNames Network name configuration parameters
- * @property {EnumConfig} enums Application-wide string constants
- */
-
-/**
- * @type {Config}
- **/
-const config = {
- network: {
- localhost: LOCALHOST_RPC_URL,
- mainnet: MAINET_RPC_URL,
- ropsten: ROPSTEN_RPC_URL,
- kovan: KOVAN_RPC_URL,
- rinkeby: RINKEBY_RPC_URL,
- },
- networkBeta: {
- localhost: LOCALHOST_RPC_URL,
- mainnet: MAINET_RPC_URL_BETA,
- ropsten: ROPSTEN_RPC_URL_BETA,
- kovan: KOVAN_RPC_URL_BETA,
- rinkeby: RINKEBY_RPC_URL_BETA,
- },
- networkNames: {
- 3: 'Ropsten',
- 4: 'Rinkeby',
- 42: 'Kovan',
- },
- enums: {
- DEFAULT_RPC,
- OLD_UI_NETWORK_TYPE,
- BETA_UI_NETWORK_TYPE,
- },
-}
-
-module.exports = config
diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js
index dbf1c6d4c..ddf1a9432 100644
--- a/app/scripts/contentscript.js
+++ b/app/scripts/contentscript.js
@@ -174,6 +174,7 @@ function blacklistedDomainCheck () {
'uscourts.gov',
'dropbox.com',
'webbyawards.com',
+ 'cdn.shopify.com/s/javascripts/tricorder/xtld-read-only-frame.html',
]
var currentUrl = window.location.href
var currentRegex
diff --git a/app/scripts/controllers/balance.js b/app/scripts/controllers/balance.js
index f83f294cc..86619fce1 100644
--- a/app/scripts/controllers/balance.js
+++ b/app/scripts/controllers/balance.js
@@ -4,6 +4,24 @@ const BN = require('ethereumjs-util').BN
class BalanceController {
+ /**
+ * Controller responsible for storing and updating an account's balance.
+ *
+ * @typedef {Object} BalanceController
+ * @param {Object} opts Initialize various properties of the class.
+ * @property {string} address A base 16 hex string. The account address which has the balance managed by this
+ * BalanceController.
+ * @property {AccountTracker} accountTracker Stores and updates the users accounts
+ * for which this BalanceController manages balance.
+ * @property {TransactionController} txController Stores, tracks and manages transactions. Here used to create a listener for
+ * transaction updates.
+ * @property {BlockTracker} blockTracker Tracks updates to blocks. On new blocks, this BalanceController updates its balance
+ * @property {Object} store The store for the ethBalance
+ * @property {string} store.ethBalance A base 16 hex string. The balance for the current account.
+ * @property {PendingBalanceCalculator} balanceCalc Used to calculate the accounts balance with possible pending
+ * transaction costs taken into account.
+ *
+ */
constructor (opts = {}) {
this._validateParams(opts)
const { address, accountTracker, txController, blockTracker } = opts
@@ -26,6 +44,11 @@ class BalanceController {
this._registerUpdates()
}
+ /**
+ * Updates the ethBalance property to the current pending balance
+ *
+ * @returns {Promise<void>} Promises undefined
+ */
async updateBalance () {
const balance = await this.balanceCalc.getBalance()
this.store.updateState({
@@ -33,6 +56,15 @@ class BalanceController {
})
}
+ /**
+ * Sets up listeners and subscriptions which should trigger an update of ethBalance. These updates include:
+ * - when a transaction changes state to 'submitted', 'confirmed' or 'failed'
+ * - when the current account changes (i.e. a new account is selected)
+ * - when there is a block update
+ *
+ * @private
+ *
+ */
_registerUpdates () {
const update = this.updateBalance.bind(this)
@@ -51,6 +83,14 @@ class BalanceController {
this.blockTracker.on('block', update)
}
+ /**
+ * Gets the balance, as a base 16 hex string, of the account at this BalanceController's current address.
+ * If the current account has no balance, returns undefined.
+ *
+ * @returns {Promise<BN|void>} Promises a BN with a value equal to the balance of the current account, or undefined
+ * if the current account has no balance
+ *
+ */
async _getBalance () {
const { accounts } = this.accountTracker.store.getState()
const entry = accounts[this.address]
@@ -58,6 +98,14 @@ class BalanceController {
return balance ? new BN(balance.substring(2), 16) : undefined
}
+ /**
+ * Gets the pending transactions (i.e. those with a 'submitted' status). These are accessed from the
+ * TransactionController passed to this BalanceController during construction.
+ *
+ * @private
+ * @returns {Promise<array>} Promises an array of transaction objects.
+ *
+ */
async _getPendingTransactions () {
const pending = this.txController.getFilteredTxList({
from: this.address,
@@ -67,6 +115,14 @@ class BalanceController {
return pending
}
+ /**
+ * Validates that the passed options have all required properties.
+ *
+ * @param {Object} opts The options object to validate
+ * @throws {string} Throw a custom error indicating that address, accountTracker, txController and blockTracker are
+ * missing and at least one is required
+ *
+ */
_validateParams (opts) {
const { address, accountTracker, txController, blockTracker } = opts
if (!address || !accountTracker || !txController || !blockTracker) {
diff --git a/app/scripts/controllers/blacklist.js b/app/scripts/controllers/blacklist.js
index d965f80b8..f100c4525 100644
--- a/app/scripts/controllers/blacklist.js
+++ b/app/scripts/controllers/blacklist.js
@@ -10,6 +10,22 @@ const POLLING_INTERVAL = 4 * 60 * 1000
class BlacklistController {
+ /**
+ * Responsible for polling for and storing an up to date 'eth-phishing-detect' config.json file, while
+ * exposing a method that can check whether a given url is a phishing attempt. The 'eth-phishing-detect'
+ * config.json file contains a fuzzylist, whitelist and blacklist.
+ *
+ *
+ * @typedef {Object} BlacklistController
+ * @param {object} opts Overrides the defaults for the initial state of this.store
+ * @property {object} store The the store of the current phishing config
+ * @property {object} store.phishing Contains fuzzylist, whitelist and blacklist arrays. @see
+ * {@link https://github.com/MetaMask/eth-phishing-detect/blob/master/src/config.json}
+ * @property {object} _phishingDetector The PhishingDetector instantiated by passing store.phishing to
+ * PhishingDetector.
+ * @property {object} _phishingUpdateIntervalRef Id of the interval created to periodically update the blacklist
+ *
+ */
constructor (opts = {}) {
const initState = extend({
phishing: PHISHING_DETECTION_CONFIG,
@@ -22,16 +38,28 @@ class BlacklistController {
this._phishingUpdateIntervalRef = null
}
- //
- // PUBLIC METHODS
- //
-
+ /**
+ * Given a url, returns the result of checking if that url is in the store.phishing blacklist
+ *
+ * @param {string} hostname The hostname portion of a url; the one that will be checked against the white and
+ * blacklists of store.phishing
+ * @returns {boolean} Whether or not the passed hostname is on our phishing blacklist
+ *
+ */
checkForPhishing (hostname) {
if (!hostname) return false
const { result } = this._phishingDetector.check(hostname)
return result
}
+ /**
+ * Queries `https://api.infura.io/v2/blacklist` for an updated blacklist config. This is passed to this._phishingDetector
+ * to update our phishing detector instance, and is updated in the store. The new phishing config is returned
+ *
+ *
+ * @returns {Promise<object>} Promises the updated blacklist config for the phishingDetector
+ *
+ */
async updatePhishingList () {
const response = await fetch('https://api.infura.io/v2/blacklist')
const phishing = await response.json()
@@ -40,6 +68,11 @@ class BlacklistController {
return phishing
}
+ /**
+ * Initiates the updating of the local blacklist at a set interval. The update is done via this.updatePhishingList().
+ * Also, this method store a reference to that interval at this._phishingUpdateIntervalRef
+ *
+ */
scheduleUpdates () {
if (this._phishingUpdateIntervalRef) return
this.updatePhishingList().catch(log.warn)
@@ -48,10 +81,14 @@ class BlacklistController {
}, POLLING_INTERVAL)
}
- //
- // PRIVATE METHODS
- //
-
+ /**
+ * Sets this._phishingDetector to a new PhishingDetector instance.
+ * @see {@link https://github.com/MetaMask/eth-phishing-detect}
+ *
+ * @private
+ * @param {object} config A config object like that found at {@link https://github.com/MetaMask/eth-phishing-detect/blob/master/src/config.json}
+ *
+ */
_setupPhishingDetector (config) {
this._phishingDetector = new PhishingDetector(config)
}
diff --git a/app/scripts/controllers/network/enums.js b/app/scripts/controllers/network/enums.js
new file mode 100644
index 000000000..4f29e301b
--- /dev/null
+++ b/app/scripts/controllers/network/enums.js
@@ -0,0 +1,56 @@
+const ROPSTEN = 'ropsten'
+const RINKEBY = 'rinkeby'
+const KOVAN = 'kovan'
+const MAINNET = 'mainnet'
+const LOCALHOST = 'localhost'
+
+const ROPSTEN_CODE = 3
+const RINKEYBY_CODE = 4
+const KOVAN_CODE = 42
+
+const ROPSTEN_DISPLAY_NAME = 'Ropsten'
+const RINKEBY_DISPLAY_NAME = 'Rinkeby'
+const KOVAN_DISPLAY_NAME = 'Kovan'
+const MAINNET_DISPLAY_NAME = 'Main Ethereum Network'
+
+const MAINNET_RPC_URL = 'https://mainnet.infura.io/metamask'
+const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask'
+const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask'
+const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask'
+const LOCALHOST_RPC_URL = 'http://localhost:8545'
+
+const MAINNET_RPC_URL_BETA = 'https://mainnet.infura.io/metamask2'
+const ROPSTEN_RPC_URL_BETA = 'https://ropsten.infura.io/metamask2'
+const KOVAN_RPC_URL_BETA = 'https://kovan.infura.io/metamask2'
+const RINKEBY_RPC_URL_BETA = 'https://rinkeby.infura.io/metamask2'
+
+const DEFAULT_NETWORK = 'rinkeby'
+const OLD_UI_NETWORK_TYPE = 'network'
+const BETA_UI_NETWORK_TYPE = 'networkBeta'
+
+module.exports = {
+ ROPSTEN,
+ RINKEBY,
+ KOVAN,
+ MAINNET,
+ LOCALHOST,
+ ROPSTEN_CODE,
+ RINKEYBY_CODE,
+ KOVAN_CODE,
+ ROPSTEN_DISPLAY_NAME,
+ RINKEBY_DISPLAY_NAME,
+ KOVAN_DISPLAY_NAME,
+ MAINNET_DISPLAY_NAME,
+ MAINNET_RPC_URL,
+ ROPSTEN_RPC_URL,
+ KOVAN_RPC_URL,
+ RINKEBY_RPC_URL,
+ LOCALHOST_RPC_URL,
+ MAINNET_RPC_URL_BETA,
+ ROPSTEN_RPC_URL_BETA,
+ KOVAN_RPC_URL_BETA,
+ RINKEBY_RPC_URL_BETA,
+ DEFAULT_NETWORK,
+ OLD_UI_NETWORK_TYPE,
+ BETA_UI_NETWORK_TYPE,
+}
diff --git a/app/scripts/controllers/network/index.js b/app/scripts/controllers/network/index.js
new file mode 100644
index 000000000..fb095bf33
--- /dev/null
+++ b/app/scripts/controllers/network/index.js
@@ -0,0 +1,2 @@
+const NetworkController = require('./network')
+module.exports = NetworkController
diff --git a/app/scripts/controllers/network.js b/app/scripts/controllers/network/network.js
index 45574e673..2f5b81cd2 100644
--- a/app/scripts/controllers/network.js
+++ b/app/scripts/controllers/network/network.js
@@ -1,17 +1,24 @@
const assert = require('assert')
const EventEmitter = require('events')
const createMetamaskProvider = require('web3-provider-engine/zero.js')
-const SubproviderFromProvider = require('web3-provider-engine/subproviders/web3.js')
+const SubproviderFromProvider = require('web3-provider-engine/subproviders/provider.js')
const createInfuraProvider = require('eth-json-rpc-infura/src/createProvider')
const ObservableStore = require('obs-store')
const ComposedStore = require('obs-store/lib/composed')
const extend = require('xtend')
const EthQuery = require('eth-query')
-const createEventEmitterProxy = require('../lib/events-proxy.js')
-const networkConfig = require('../config.js')
+const createEventEmitterProxy = require('../../lib/events-proxy.js')
const log = require('loglevel')
-const { OLD_UI_NETWORK_TYPE, DEFAULT_RPC } = networkConfig.enums
-const INFURA_PROVIDER_TYPES = ['ropsten', 'rinkeby', 'kovan', 'mainnet']
+const {
+ ROPSTEN,
+ RINKEBY,
+ KOVAN,
+ MAINNET,
+ OLD_UI_NETWORK_TYPE,
+ DEFAULT_NETWORK,
+} = require('./enums')
+const { getNetworkEndpoints } = require('./util')
+const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET]
module.exports = class NetworkController extends EventEmitter {
@@ -19,8 +26,8 @@ module.exports = class NetworkController extends EventEmitter {
super()
this._networkEndpointVersion = OLD_UI_NETWORK_TYPE
- this._networkEndpoints = this.getNetworkEndpoints(OLD_UI_NETWORK_TYPE)
- this._defaultRpc = this._networkEndpoints[DEFAULT_RPC]
+ this._networkEndpoints = getNetworkEndpoints(OLD_UI_NETWORK_TYPE)
+ this._defaultRpc = this._networkEndpoints[DEFAULT_NETWORK]
config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider)
this.networkStore = new ObservableStore('loading')
@@ -37,17 +44,13 @@ module.exports = class NetworkController extends EventEmitter {
}
this._networkEndpointVersion = version
- this._networkEndpoints = this.getNetworkEndpoints(version)
- this._defaultRpc = this._networkEndpoints[DEFAULT_RPC]
+ this._networkEndpoints = getNetworkEndpoints(version)
+ this._defaultRpc = this._networkEndpoints[DEFAULT_NETWORK]
const { type } = this.getProviderConfig()
return this.setProviderType(type, true)
}
- getNetworkEndpoints (version = OLD_UI_NETWORK_TYPE) {
- return networkConfig[version]
- }
-
initializeProvider (_providerParams) {
this._baseProviderParams = _providerParams
const { type, rpcTarget } = this.providerStore.getState()
diff --git a/app/scripts/controllers/network/util.js b/app/scripts/controllers/network/util.js
new file mode 100644
index 000000000..4f38ccda4
--- /dev/null
+++ b/app/scripts/controllers/network/util.js
@@ -0,0 +1,65 @@
+const {
+ ROPSTEN,
+ RINKEBY,
+ KOVAN,
+ MAINNET,
+ LOCALHOST,
+ ROPSTEN_CODE,
+ RINKEYBY_CODE,
+ KOVAN_CODE,
+ ROPSTEN_DISPLAY_NAME,
+ RINKEBY_DISPLAY_NAME,
+ KOVAN_DISPLAY_NAME,
+ MAINNET_DISPLAY_NAME,
+ MAINNET_RPC_URL,
+ ROPSTEN_RPC_URL,
+ KOVAN_RPC_URL,
+ RINKEBY_RPC_URL,
+ LOCALHOST_RPC_URL,
+ MAINNET_RPC_URL_BETA,
+ ROPSTEN_RPC_URL_BETA,
+ KOVAN_RPC_URL_BETA,
+ RINKEBY_RPC_URL_BETA,
+ OLD_UI_NETWORK_TYPE,
+ BETA_UI_NETWORK_TYPE,
+} = require('./enums')
+
+const networkToNameMap = {
+ [ROPSTEN]: ROPSTEN_DISPLAY_NAME,
+ [RINKEBY]: RINKEBY_DISPLAY_NAME,
+ [KOVAN]: KOVAN_DISPLAY_NAME,
+ [MAINNET]: MAINNET_DISPLAY_NAME,
+ [ROPSTEN_CODE]: ROPSTEN_DISPLAY_NAME,
+ [RINKEYBY_CODE]: RINKEBY_DISPLAY_NAME,
+ [KOVAN_CODE]: KOVAN_DISPLAY_NAME,
+}
+
+const networkEndpointsMap = {
+ [OLD_UI_NETWORK_TYPE]: {
+ [LOCALHOST]: LOCALHOST_RPC_URL,
+ [MAINNET]: MAINNET_RPC_URL,
+ [ROPSTEN]: ROPSTEN_RPC_URL,
+ [KOVAN]: KOVAN_RPC_URL,
+ [RINKEBY]: RINKEBY_RPC_URL,
+ },
+ [BETA_UI_NETWORK_TYPE]: {
+ [LOCALHOST]: LOCALHOST_RPC_URL,
+ [MAINNET]: MAINNET_RPC_URL_BETA,
+ [ROPSTEN]: ROPSTEN_RPC_URL_BETA,
+ [KOVAN]: KOVAN_RPC_URL_BETA,
+ [RINKEBY]: RINKEBY_RPC_URL_BETA,
+ },
+}
+
+const getNetworkDisplayName = key => networkToNameMap[key]
+
+const getNetworkEndpoints = (networkType = OLD_UI_NETWORK_TYPE) => {
+ return {
+ ...networkEndpointsMap[networkType],
+ }
+}
+
+module.exports = {
+ getNetworkDisplayName,
+ getNetworkEndpoints,
+}
diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js
index d4d508026..1d3308d36 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -8,8 +8,8 @@ class PreferencesController {
*
* @typedef {Object} PreferencesController
* @param {object} opts Overrides the defaults for the initial state of this.store
- * @property {object} store The an object containing a users preferences, stored in local storage
- * @property {array} store.frequentRpcList A list of custom rpcs to provide the user
+ * @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 {string} store.currentAccountTab Indicates the selected tab in the ui
* @property {array} store.tokens The tokens the user wants display in their token lists
* @property {boolean} store.useBlockie The users preference for blockie identicons within the UI
diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js
index 0c1ee4e38..1377c1ba9 100644
--- a/app/scripts/controllers/recent-blocks.js
+++ b/app/scripts/controllers/recent-blocks.js
@@ -6,6 +6,23 @@ const log = require('loglevel')
class RecentBlocksController {
+ /**
+ * Controller responsible for storing, updating and managing the recent history of blocks. Blocks are back filled
+ * upon the controller's construction and then the list is updated when the given block tracker gets a 'block' event
+ * (indicating that there is a new block to process).
+ *
+ * @typedef {Object} RecentBlocksController
+ * @param {object} opts Contains objects necessary for tracking blocks and querying the blockchain
+ * @param {BlockTracker} opts.blockTracker Contains objects necessary for tracking blocks and querying the blockchain
+ * @param {BlockTracker} opts.provider The provider used to create a new EthQuery instance.
+ * @property {BlockTracker} blockTracker Points to the passed BlockTracker. On RecentBlocksController construction,
+ * listens for 'block' events so that new blocks can be processed and added to storage.
+ * @property {EthQuery} ethQuery Points to the EthQuery instance created with the passed provider
+ * @property {number} historyLength The maximum length of blocks to track
+ * @property {object} store Stores the recentBlocks
+ * @property {array} store.recentBlocks Contains all recent blocks, up to a total that is equal to this.historyLength
+ *
+ */
constructor (opts = {}) {
const { blockTracker, provider } = opts
this.blockTracker = blockTracker
@@ -21,12 +38,23 @@ class RecentBlocksController {
this.backfill()
}
+ /**
+ * Sets store.recentBlocks to an empty array
+ *
+ */
resetState () {
this.store.updateState({
recentBlocks: [],
})
}
+ /**
+ * Receives a new block and modifies it with this.mapTransactionsToPrices. Then adds that block to the recentBlocks
+ * array in storage. If the recentBlocks array contains the maximum number of blocks, the oldest block is removed.
+ *
+ * @param {object} newBlock The new block to modify and add to the recentBlocks array
+ *
+ */
processBlock (newBlock) {
const block = this.mapTransactionsToPrices(newBlock)
@@ -40,6 +68,15 @@ class RecentBlocksController {
this.store.updateState(state)
}
+ /**
+ * Receives a new block and modifies it with this.mapTransactionsToPrices. Adds that block to the recentBlocks
+ * array in storage, but only if the recentBlocks array contains fewer than the maximum permitted.
+ *
+ * Unlike this.processBlock, backfillBlock adds the modified new block to the beginning of the recent block array.
+ *
+ * @param {object} newBlock The new block to modify and add to the beginning of the recentBlocks array
+ *
+ */
backfillBlock (newBlock) {
const block = this.mapTransactionsToPrices(newBlock)
@@ -52,6 +89,14 @@ class RecentBlocksController {
this.store.updateState(state)
}
+ /**
+ * Receives a block and gets the gasPrice of each of its transactions. These gas prices are added to the block at a
+ * new property, and the block's transactions are removed.
+ *
+ * @param {object} newBlock The block to modify. It's transaction array will be replaced by a gasPrices array.
+ * @returns {object} The modified block.
+ *
+ */
mapTransactionsToPrices (newBlock) {
const block = extend(newBlock, {
gasPrices: newBlock.transactions.map((tx) => {
@@ -62,6 +107,16 @@ class RecentBlocksController {
return block
}
+ /**
+ * On this.blockTracker's first 'block' event after this RecentBlocksController's instantiation, the store.recentBlocks
+ * array is populated with this.historyLength number of blocks. The block number of the this.blockTracker's first
+ * 'block' event is used to iteratively generate all the numbers of the previous blocks, which are obtained by querying
+ * the blockchain. These blocks are backfilled so that the recentBlocks array is ordered from oldest to newest.
+ *
+ * Each iteration over the block numbers is delayed by 100 milliseconds.
+ *
+ * @returns {Promise<void>} Promises undefined
+ */
async backfill() {
this.blockTracker.once('block', async (block) => {
let blockNum = block.number
@@ -90,12 +145,25 @@ class RecentBlocksController {
})
}
+ /**
+ * A helper for this.backfill. Provides an easy way to ensure a 100 millisecond delay using await
+ *
+ * @returns {Promise<void>} Promises undefined
+ *
+ */
async wait () {
return new Promise((resolve) => {
setTimeout(resolve, 100)
})
}
+ /**
+ * Uses EthQuery to get a block that has a given block number.
+ *
+ * @param {number} number The number of the block to get
+ * @returns {Promise<object>} Promises A block with the passed number
+ *
+ */
async getBlockByNumber (number) {
const bn = new BN(number)
return new Promise((resolve, reject) => {
diff --git a/app/scripts/controllers/token-rates.js b/app/scripts/controllers/token-rates.js
index 28409ea10..87d716aa6 100644
--- a/app/scripts/controllers/token-rates.js
+++ b/app/scripts/controllers/token-rates.js
@@ -1,4 +1,5 @@
const ObservableStore = require('obs-store')
+const { warn } = require('loglevel')
// By default, poll every 3 minutes
const DEFAULT_INTERVAL = 180 * 1000
@@ -39,10 +40,13 @@ class TokenRatesController {
*/
async fetchExchangeRate (address) {
try {
- const response = await fetch(`https://exchanges.balanc3.net/prices?from=${address}&to=ETH&autoConversion=false&summaryOnly=true`)
+ const response = await fetch(`https://metamask.balanc3.net/prices?from=${address}&to=ETH&autoConversion=false&summaryOnly=true`)
const json = await response.json()
return json && json.length ? json[0].averagePrice : 0
- } catch (error) { }
+ } catch (error) {
+ warn(`MetaMask - TokenRatesController exchange rate fetch failed for ${address}.`, error)
+ return 0
+ }
}
/**
diff --git a/app/scripts/controllers/transactions/README.md b/app/scripts/controllers/transactions/README.md
new file mode 100644
index 000000000..b414762dc
--- /dev/null
+++ b/app/scripts/controllers/transactions/README.md
@@ -0,0 +1,92 @@
+# Transaction Controller
+
+Transaction Controller is an aggregate of sub-controllers and trackers
+exposed to the MetaMask controller.
+
+- txStateManager
+ responsible for the state of a transaction and
+ storing the transaction
+- pendingTxTracker
+ watching blocks for transactions to be include
+ and emitting confirmed events
+- txGasUtil
+ gas calculations and safety buffering
+- nonceTracker
+ calculating nonces
+
+## Flow diagram of processing a transaction
+
+![transaction-flow](../../../../docs/transaction-flow.png)
+
+## txMeta's & txParams
+
+A txMeta is the "meta" object it has all the random bits of info we need about a transaction on it. txParams are sacred every thing on txParams gets signed so it must
+be a valid key and be hex prefixed except for the network number. Extra stuff must go on the txMeta!
+
+Here is a txMeta too look at:
+
+```js
+txMeta = {
+ "id": 2828415030114568, // unique id for this txMeta used for look ups
+ "time": 1524094064821, // time of creation
+ "status": "confirmed",
+ "metamaskNetworkId": "1524091532133", //the network id for the transaction
+ "loadingDefaults": false, // used to tell the ui when we are done calculatyig gass defaults
+ "txParams": { // the txParams object
+ "from": "0x8acce2391c0d510a6c5e5d8f819a678f79b7e675",
+ "to": "0x8acce2391c0d510a6c5e5d8f819a678f79b7e675",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0c",
+ "nonce": "0x0"
+ },
+ "history": [{ //debug
+ "id": 2828415030114568,
+ "time": 1524094064821,
+ "status": "unapproved",
+ "metamaskNetworkId": "1524091532133",
+ "loadingDefaults": true,
+ "txParams": {
+ "from": "0x8acce2391c0d510a6c5e5d8f819a678f79b7e675",
+ "to": "0x8acce2391c0d510a6c5e5d8f819a678f79b7e675",
+ "value": "0x0"
+ }
+ },
+ [
+ {
+ "op": "add",
+ "path": "/txParams/gasPrice",
+ "value": "0x3b9aca00"
+ },
+ ...], // I've removed most of history for this
+ "gasPriceSpecified": false, //whether or not the user/dapp has specified gasPrice
+ "gasLimitSpecified": false, //whether or not the user/dapp has specified gas
+ "estimatedGas": "5208",
+ "origin": "MetaMask", //debug
+ "nonceDetails": {
+ "params": {
+ "highestLocallyConfirmed": 0,
+ "highestSuggested": 0,
+ "nextNetworkNonce": 0
+ },
+ "local": {
+ "name": "local",
+ "nonce": 0,
+ "details": {
+ "startPoint": 0,
+ "highest": 0
+ }
+ },
+ "network": {
+ "name": "network",
+ "nonce": 0,
+ "details": {
+ "baseCount": 0
+ }
+ }
+ },
+ "rawTx": "0xf86980843b9aca00827b0c948acce2391c0d510a6c5e5d8f819a678f79b7e67580808602c5b5de66eea05c01a320b96ac730cb210ca56d2cb71fa360e1fc2c21fa5cf333687d18eb323fa02ed05987a6e5fd0f2459fcff80710b76b83b296454ad9a37594a0ccb4643ea90", // used for rebroadcast
+ "hash": "0xa45ba834b97c15e6ff4ed09badd04ecd5ce884b455eb60192cdc73bcc583972a",
+ "submittedTime": 1524094077902 // time of the attempt to submit the raw tx to the network, used in the ui to show the retry button
+}
+```
diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions/index.js
index c8211ebd7..a1588cfef 100644
--- a/app/scripts/controllers/transactions.js
+++ b/app/scripts/controllers/transactions/index.js
@@ -3,28 +3,42 @@ const ObservableStore = require('obs-store')
const ethUtil = require('ethereumjs-util')
const Transaction = require('ethereumjs-tx')
const EthQuery = require('ethjs-query')
-const TransactionStateManager = require('../lib/tx-state-manager')
-const TxGasUtil = require('../lib/tx-gas-utils')
-const PendingTransactionTracker = require('../lib/pending-tx-tracker')
-const NonceTracker = require('../lib/nonce-tracker')
+const TransactionStateManager = require('./tx-state-manager')
+const TxGasUtil = require('./tx-gas-utils')
+const PendingTransactionTracker = require('./pending-tx-tracker')
+const NonceTracker = require('./nonce-tracker')
+const txUtils = require('./lib/util')
const log = require('loglevel')
-/*
+/**
Transaction Controller is an aggregate of sub-controllers and trackers
composing them in a way to be exposed to the metamask controller
- - txStateManager
+ <br>- txStateManager
responsible for the state of a transaction and
storing the transaction
- - pendingTxTracker
+ <br>- pendingTxTracker
watching blocks for transactions to be include
and emitting confirmed events
- - txGasUtil
+ <br>- txGasUtil
gas calculations and safety buffering
- - nonceTracker
+ <br>- nonceTracker
calculating nonces
+
+
+ @class
+ @param {object} - opts
+ @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.blockTracker - An instance of eth-blocktracker
+ @param {Object} opts.provider - A network provider.
+ @param {Function} opts.signTransaction - function the signs an ethereumjs-tx
+ @param {Function} [opts.getGasPrice] - optional gas price calculator
+ @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 {Object} opts.preferencesStore
*/
-module.exports = class TransactionController extends EventEmitter {
+class TransactionController extends EventEmitter {
constructor (opts) {
super()
this.networkStore = opts.networkStore || new ObservableStore({})
@@ -38,45 +52,19 @@ module.exports = class TransactionController extends EventEmitter {
this.query = new EthQuery(this.provider)
this.txGasUtil = new TxGasUtil(this.provider)
+ this._mapMethods()
this.txStateManager = new TransactionStateManager({
initState: opts.initState,
txHistoryLimit: opts.txHistoryLimit,
getNetwork: this.getNetwork.bind(this),
})
-
- this.txStateManager.getFilteredTxList({
- status: 'unapproved',
- loadingDefaults: true,
- }).forEach((tx) => {
- this.addTxDefaults(tx)
- .then((txMeta) => {
- txMeta.loadingDefaults = false
- this.txStateManager.updateTx(txMeta, 'transactions: gas estimation for tx on boot')
- }).catch((error) => {
- this.txStateManager.setTxStatusFailed(tx.id, error)
- })
- })
-
- this.txStateManager.getFilteredTxList({
- status: 'approved',
- }).forEach((txMeta) => {
- const txSignError = new Error('Transaction found as "approved" during boot - possibly stuck during signing')
- this.txStateManager.setTxStatusFailed(txMeta.id, txSignError)
- })
-
+ this._onBootCleanUp()
this.store = this.txStateManager.store
- this.txStateManager.on('tx:status-update', this.emit.bind(this, 'tx:status-update'))
this.nonceTracker = new NonceTracker({
provider: this.provider,
getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
- getConfirmedTransactions: (address) => {
- return this.txStateManager.getFilteredTxList({
- from: address,
- status: 'confirmed',
- err: undefined,
- })
- },
+ getConfirmedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager),
})
this.pendingTxTracker = new PendingTransactionTracker({
@@ -88,60 +76,14 @@ module.exports = class TransactionController extends EventEmitter {
})
this.txStateManager.store.subscribe(() => this.emit('update:badge'))
-
- this.pendingTxTracker.on('tx:warning', (txMeta) => {
- this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning')
- })
- this.pendingTxTracker.on('tx:confirmed', (txId) => this._markNonceDuplicatesDropped(txId))
- this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager))
- this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => {
- if (!txMeta.firstRetryBlockNumber) {
- txMeta.firstRetryBlockNumber = latestBlockNumber
- this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:block-update')
- }
- })
- this.pendingTxTracker.on('tx:retry', (txMeta) => {
- if (!('retryCount' in txMeta)) txMeta.retryCount = 0
- txMeta.retryCount++
- this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:retry')
- })
-
- this.blockTracker.on('block', this.pendingTxTracker.checkForTxInBlock.bind(this.pendingTxTracker))
- // this is a little messy but until ethstore has been either
- // removed or redone this is to guard against the race condition
- this.blockTracker.on('latest', this.pendingTxTracker.resubmitPendingTxs.bind(this.pendingTxTracker))
- this.blockTracker.on('sync', this.pendingTxTracker.queryPendingTxs.bind(this.pendingTxTracker))
+ this._setupListners()
// memstore is computed from a few different stores
this._updateMemstore()
this.txStateManager.store.subscribe(() => this._updateMemstore())
this.networkStore.subscribe(() => this._updateMemstore())
this.preferencesStore.subscribe(() => this._updateMemstore())
}
-
- getState () {
- return this.memStore.getState()
- }
-
- getNetwork () {
- return this.networkStore.getState()
- }
-
- getSelectedAddress () {
- return this.preferencesStore.getState().selectedAddress
- }
-
- getUnapprovedTxCount () {
- return Object.keys(this.txStateManager.getUnapprovedTxList()).length
- }
-
- getPendingTxCount (account) {
- return this.txStateManager.getPendingTransactions(account).length
- }
-
- getFilteredTxList (opts) {
- return this.txStateManager.getFilteredTxList(opts)
- }
-
+ /** @returns {number} the chainId*/
getChainId () {
const networkState = this.networkStore.getState()
const getChainId = parseInt(networkState)
@@ -152,16 +94,45 @@ module.exports = class TransactionController extends EventEmitter {
}
}
+/**
+ Adds a tx to the txlist
+ @emits ${txMeta.id}:unapproved
+*/
+ addTx (txMeta) {
+ this.txStateManager.addTx(txMeta)
+ this.emit(`${txMeta.id}:unapproved`, txMeta)
+ }
+
+ /**
+ Wipes the transactions for a given account
+ @param {string} address - hex string of the from address for txs being removed
+ */
wipeTransactions (address) {
this.txStateManager.wipeTransactions(address)
}
- // Adds a tx to the txlist
- addTx (txMeta) {
- this.txStateManager.addTx(txMeta)
- this.emit(`${txMeta.id}:unapproved`, txMeta)
+ /**
+ Check if a txMeta in the list with the same nonce has been confirmed in a block
+ if the txParams dont have a nonce will return false
+ @returns {boolean} weather the nonce has been used in a transaction confirmed in a block
+ @param {object} txMeta - the txMeta object
+ */
+ async isNonceTaken (txMeta) {
+ const { from, nonce } = txMeta.txParams
+ if ('nonce' in txMeta.txParams) {
+ const sameNonceTxList = this.txStateManager.getFilteredTxList({from, nonce, status: 'confirmed'})
+ return (sameNonceTxList.length >= 1)
+ }
+ return false
}
+ /**
+ add a new unapproved transaction to the pipeline
+
+ @returns {Promise<string>} the hash of the transaction after being submitted to the network
+ @param txParams {object} - txParams for the transaction
+ @param opts {object} - with the key origin to put the origin on the txMeta
+ */
async newUnapprovedTransaction (txParams, opts = {}) {
log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`)
const initialTxMeta = await this.addUnapprovedTransaction(txParams)
@@ -184,17 +155,24 @@ module.exports = class TransactionController extends EventEmitter {
})
}
+ /**
+ Validates and generates a txMeta with defaults and puts it in txStateManager
+ store
+
+ @returns {txMeta}
+ */
+
async addUnapprovedTransaction (txParams) {
// validate
- const normalizedTxParams = this._normalizeTxParams(txParams)
- this._validateTxParams(normalizedTxParams)
+ const normalizedTxParams = txUtils.normalizeTxParams(txParams)
+ txUtils.validateTxParams(normalizedTxParams)
// construct txMeta
let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams })
this.addTx(txMeta)
this.emit('newUnapprovedTx', txMeta)
// add default tx params
try {
- txMeta = await this.addTxDefaults(txMeta)
+ txMeta = await this.addTxGasDefaults(txMeta)
} catch (error) {
console.log(error)
this.txStateManager.setTxStatusFailed(txMeta.id, error)
@@ -206,21 +184,33 @@ module.exports = class TransactionController extends EventEmitter {
return txMeta
}
-
- async addTxDefaults (txMeta) {
+/**
+ adds the tx gas defaults: gas && gasPrice
+ @param txMeta {Object} - the txMeta object
+ @returns {Promise<object>} resolves with txMeta
+*/
+ async addTxGasDefaults (txMeta) {
const txParams = txMeta.txParams
// ensure value
+ txParams.value = txParams.value ? ethUtil.addHexPrefix(txParams.value) : '0x0'
txMeta.gasPriceSpecified = Boolean(txParams.gasPrice)
let gasPrice = txParams.gasPrice
if (!gasPrice) {
gasPrice = this.getGasPrice ? this.getGasPrice() : await this.query.gasPrice()
}
txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16))
- txParams.value = txParams.value || '0x0'
// set gasLimit
return await this.txGasUtil.analyzeGasUsage(txMeta)
}
+ /**
+ Creates a new txMeta with the same txParams as the original
+ to allow the user to resign the transaction with a higher gas values
+ @param originalTxId {number} - the id of the txMeta that
+ you want to attempt to retry
+ @return {txMeta}
+ */
+
async retryTransaction (originalTxId) {
const originalTxMeta = this.txStateManager.getTx(originalTxId)
const lastGasPrice = originalTxMeta.txParams.gasPrice
@@ -234,15 +224,31 @@ module.exports = class TransactionController extends EventEmitter {
return txMeta
}
+ /**
+ updates the txMeta in the txStateManager
+ @param txMeta {Object} - the updated txMeta
+ */
async updateTransaction (txMeta) {
this.txStateManager.updateTx(txMeta, 'confTx: user updated transaction')
}
+ /**
+ updates and approves the transaction
+ @param txMeta {Object}
+ */
async updateAndApproveTransaction (txMeta) {
this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction')
await this.approveTransaction(txMeta.id)
}
+ /**
+ sets the tx status to approved
+ auto fills the nonce
+ signs the transaction
+ publishes the transaction
+ if any of these steps fails the tx status will be set to failed
+ @param txId {number} - the tx's Id
+ */
async approveTransaction (txId) {
let nonceLock
try {
@@ -274,7 +280,11 @@ module.exports = class TransactionController extends EventEmitter {
throw err
}
}
-
+ /**
+ adds the chain id and signs the transaction and set the status to signed
+ @param txId {number} - the tx's Id
+ @returns - rawTx {string}
+ */
async signTransaction (txId) {
const txMeta = this.txStateManager.getTx(txId)
// add network/chain id
@@ -290,6 +300,12 @@ module.exports = class TransactionController extends EventEmitter {
return rawTx
}
+ /**
+ publishes the raw tx and sets the txMeta to submitted
+ @param txId {number} - the tx's Id
+ @param rawTx {string} - the hex string of the serialized signed transaction
+ @returns {Promise<void>}
+ */
async publishTransaction (txId, rawTx) {
const txMeta = this.txStateManager.getTx(txId)
txMeta.rawTx = rawTx
@@ -299,11 +315,20 @@ module.exports = class TransactionController extends EventEmitter {
this.txStateManager.setTxStatusSubmitted(txId)
}
+ /**
+ Convenience method for the ui thats sets the transaction to rejected
+ @param txId {number} - the tx's Id
+ @returns {Promise<void>}
+ */
async cancelTransaction (txId) {
this.txStateManager.setTxStatusRejected(txId)
}
- // receives a txHash records the tx as signed
+ /**
+ Sets the txHas on the txMeta
+ @param txId {number} - the tx's Id
+ @param txHash {string} - the hash for the txMeta
+ */
setTxHash (txId, txHash) {
// Add the tx hash to the persisted meta-tx object
const txMeta = this.txStateManager.getTx(txId)
@@ -314,63 +339,92 @@ module.exports = class TransactionController extends EventEmitter {
//
// PRIVATE METHODS
//
+ /** maps methods for convenience*/
+ _mapMethods () {
+ /** @returns the state in transaction controller */
+ this.getState = () => this.memStore.getState()
+ /** @returns the network number stored in networkStore */
+ this.getNetwork = () => this.networkStore.getState()
+ /** @returns the user selected address */
+ this.getSelectedAddress = () => this.preferencesStore.getState().selectedAddress
+ /** Returns an array of transactions whos status is unapproved */
+ this.getUnapprovedTxCount = () => Object.keys(this.txStateManager.getUnapprovedTxList()).length
+ /**
+ @returns a number that represents how many transactions have the status submitted
+ @param account {String} - hex prefixed account
+ */
+ this.getPendingTxCount = (account) => this.txStateManager.getPendingTransactions(account).length
+ /** see txStateManager */
+ this.getFilteredTxList = (opts) => this.txStateManager.getFilteredTxList(opts)
+ }
- _normalizeTxParams (txParams) {
- // functions that handle normalizing of that key in txParams
- const whiteList = {
- from: from => ethUtil.addHexPrefix(from).toLowerCase(),
- to: to => ethUtil.addHexPrefix(txParams.to).toLowerCase(),
- nonce: nonce => ethUtil.addHexPrefix(nonce),
- value: value => ethUtil.addHexPrefix(value),
- data: data => ethUtil.addHexPrefix(data),
- gas: gas => ethUtil.addHexPrefix(gas),
- gasPrice: gasPrice => ethUtil.addHexPrefix(gasPrice),
- }
+ /**
+ If transaction controller was rebooted with transactions that are uncompleted
+ in steps of the transaction signing or user confirmation process it will either
+ transition txMetas to a failed state or try to redo those tasks.
+ */
- // apply only keys in the whiteList
- const normalizedTxParams = {}
- Object.keys(whiteList).forEach((key) => {
- if (txParams[key]) normalizedTxParams[key] = whiteList[key](txParams[key])
+ _onBootCleanUp () {
+ this.txStateManager.getFilteredTxList({
+ status: 'unapproved',
+ loadingDefaults: true,
+ }).forEach((tx) => {
+ this.addTxGasDefaults(tx)
+ .then((txMeta) => {
+ txMeta.loadingDefaults = false
+ this.txStateManager.updateTx(txMeta, 'transactions: gas estimation for tx on boot')
+ }).catch((error) => {
+ this.txStateManager.setTxStatusFailed(tx.id, error)
+ })
})
- return normalizedTxParams
+ this.txStateManager.getFilteredTxList({
+ status: 'approved',
+ }).forEach((txMeta) => {
+ const txSignError = new Error('Transaction found as "approved" during boot - possibly stuck during signing')
+ this.txStateManager.setTxStatusFailed(txMeta.id, txSignError)
+ })
}
- _validateTxParams (txParams) {
- this._validateFrom(txParams)
- this._validateRecipient(txParams)
- if ('value' in txParams) {
- const value = txParams.value.toString()
- if (value.includes('-')) {
- throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`)
+ /**
+ is called in constructor applies the listeners for pendingTxTracker txStateManager
+ and blockTracker
+ */
+ _setupListners () {
+ this.txStateManager.on('tx:status-update', this.emit.bind(this, 'tx:status-update'))
+ this.pendingTxTracker.on('tx:warning', (txMeta) => {
+ this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning')
+ })
+ this.pendingTxTracker.on('tx:confirmed', (txId) => this.txStateManager.setTxStatusConfirmed(txId))
+ this.pendingTxTracker.on('tx:confirmed', (txId) => this._markNonceDuplicatesDropped(txId))
+ this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager))
+ this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => {
+ if (!txMeta.firstRetryBlockNumber) {
+ txMeta.firstRetryBlockNumber = latestBlockNumber
+ this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:block-update')
}
+ })
+ this.pendingTxTracker.on('tx:retry', (txMeta) => {
+ if (!('retryCount' in txMeta)) txMeta.retryCount = 0
+ txMeta.retryCount++
+ this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:retry')
+ })
- if (value.includes('.')) {
- throw new Error(`Invalid transaction value of ${txParams.value} number must be in wei`)
- }
- }
- }
+ this.blockTracker.on('block', this.pendingTxTracker.checkForTxInBlock.bind(this.pendingTxTracker))
+ // this is a little messy but until ethstore has been either
+ // removed or redone this is to guard against the race condition
+ this.blockTracker.on('latest', this.pendingTxTracker.resubmitPendingTxs.bind(this.pendingTxTracker))
+ this.blockTracker.on('sync', this.pendingTxTracker.queryPendingTxs.bind(this.pendingTxTracker))
- _validateFrom (txParams) {
- if ( !(typeof txParams.from === 'string') ) throw new Error(`Invalid from address ${txParams.from} not a string`)
- if (!ethUtil.isValidAddress(txParams.from)) throw new Error('Invalid from address')
}
- _validateRecipient (txParams) {
- if (txParams.to === '0x' || txParams.to === null ) {
- if (txParams.data) {
- delete txParams.to
- } else {
- throw new Error('Invalid recipient address')
- }
- } else if ( txParams.to !== undefined && !ethUtil.isValidAddress(txParams.to) ) {
- throw new Error('Invalid recipient address')
- }
- return txParams
- }
+ /**
+ Sets other txMeta statuses to dropped if the txMeta that has been confirmed has other transactions
+ in the list have the same nonce
+ @param txId {Number} - the txId of the transaction that has been confirmed in a block
+ */
_markNonceDuplicatesDropped (txId) {
- this.txStateManager.setTxStatusConfirmed(txId)
// get the confirmed transactions nonce and from address
const txMeta = this.txStateManager.getTx(txId)
const { nonce, from } = txMeta.txParams
@@ -385,6 +439,9 @@ module.exports = class TransactionController extends EventEmitter {
})
}
+ /**
+ Updates the memStore in transaction controller
+ */
_updateMemstore () {
const unapprovedTxs = this.txStateManager.getUnapprovedTxList()
const selectedAddressTxList = this.txStateManager.getFilteredTxList({
@@ -394,3 +451,5 @@ module.exports = class TransactionController extends EventEmitter {
this.memStore.updateState({ unapprovedTxs, selectedAddressTxList })
}
}
+
+module.exports = TransactionController
diff --git a/app/scripts/lib/tx-state-history-helper.js b/app/scripts/controllers/transactions/lib/tx-state-history-helper.js
index 94c7b6792..59a4b562c 100644
--- a/app/scripts/lib/tx-state-history-helper.js
+++ b/app/scripts/controllers/transactions/lib/tx-state-history-helper.js
@@ -1,6 +1,6 @@
const jsonDiffer = require('fast-json-patch')
const clone = require('clone')
-
+/** @module*/
module.exports = {
generateHistoryEntry,
replayHistory,
@@ -8,7 +8,11 @@ module.exports = {
migrateFromSnapshotsToDiffs,
}
-
+/**
+ converts non-initial history entries into diffs
+ @param longHistory {array}
+ @returns {array}
+*/
function migrateFromSnapshotsToDiffs (longHistory) {
return (
longHistory
@@ -20,6 +24,17 @@ function migrateFromSnapshotsToDiffs (longHistory) {
)
}
+/**
+ generates an array of history objects sense the previous state.
+ The object has the keys opp(the operation preformed),
+ path(the key and if a nested object then each key will be seperated with a `/`)
+ value
+ with the first entry having the note
+ @param previousState {object} - the previous state of the object
+ @param newState {object} - the update object
+ @param note {string} - a optional note for the state change
+ @reurns {array}
+*/
function generateHistoryEntry (previousState, newState, note) {
const entry = jsonDiffer.compare(previousState, newState)
// Add a note to the first op, since it breaks if we append it to the entry
@@ -27,11 +42,19 @@ function generateHistoryEntry (previousState, newState, note) {
return entry
}
+/**
+ Recovers previous txMeta state obj
+ @return {object}
+*/
function replayHistory (_shortHistory) {
const shortHistory = clone(_shortHistory)
return shortHistory.reduce((val, entry) => jsonDiffer.applyPatch(val, entry).newDocument)
}
+/**
+ @param txMeta {Object}
+ @returns {object} a clone object of the txMeta with out history
+*/
function snapshotFromTxMeta (txMeta) {
// create txMeta snapshot for history
const snapshot = clone(txMeta)
diff --git a/app/scripts/controllers/transactions/lib/util.js b/app/scripts/controllers/transactions/lib/util.js
new file mode 100644
index 000000000..84f7592a0
--- /dev/null
+++ b/app/scripts/controllers/transactions/lib/util.js
@@ -0,0 +1,99 @@
+const {
+ addHexPrefix,
+ isValidAddress,
+} = require('ethereumjs-util')
+
+/**
+@module
+*/
+module.exports = {
+ normalizeTxParams,
+ validateTxParams,
+ validateFrom,
+ validateRecipient,
+ getFinalStates,
+}
+
+
+// functions that handle normalizing of that key in txParams
+const normalizers = {
+ from: from => addHexPrefix(from).toLowerCase(),
+ to: to => addHexPrefix(to).toLowerCase(),
+ nonce: nonce => addHexPrefix(nonce),
+ value: value => addHexPrefix(value),
+ data: data => addHexPrefix(data),
+ gas: gas => addHexPrefix(gas),
+ gasPrice: gasPrice => addHexPrefix(gasPrice),
+}
+
+ /**
+ normalizes txParams
+ @param txParams {object}
+ @returns {object} normalized txParams
+ */
+function normalizeTxParams (txParams) {
+ // apply only keys in the normalizers
+ const normalizedTxParams = {}
+ for (const key in normalizers) {
+ if (txParams[key]) normalizedTxParams[key] = normalizers[key](txParams[key])
+ }
+ return normalizedTxParams
+}
+
+ /**
+ validates txParams
+ @param txParams {object}
+ */
+function validateTxParams (txParams) {
+ validateFrom(txParams)
+ validateRecipient(txParams)
+ if ('value' in txParams) {
+ const value = txParams.value.toString()
+ if (value.includes('-')) {
+ throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`)
+ }
+
+ if (value.includes('.')) {
+ throw new Error(`Invalid transaction value of ${txParams.value} number must be in wei`)
+ }
+ }
+}
+
+ /**
+ validates the from field in txParams
+ @param txParams {object}
+ */
+function validateFrom (txParams) {
+ if (!(typeof txParams.from === 'string')) throw new Error(`Invalid from address ${txParams.from} not a string`)
+ if (!isValidAddress(txParams.from)) throw new Error('Invalid from address')
+}
+
+ /**
+ validates the to field in txParams
+ @param txParams {object}
+ */
+function validateRecipient (txParams) {
+ if (txParams.to === '0x' || txParams.to === null) {
+ if (txParams.data) {
+ delete txParams.to
+ } else {
+ throw new Error('Invalid recipient address')
+ }
+ } else if (txParams.to !== undefined && !isValidAddress(txParams.to)) {
+ throw new Error('Invalid recipient address')
+ }
+ return txParams
+}
+
+ /**
+ @returns an {array} of states that can be considered final
+ */
+function getFinalStates () {
+ return [
+ 'rejected', // the user has responded no!
+ '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
+ ]
+}
+
diff --git a/app/scripts/lib/nonce-tracker.js b/app/scripts/controllers/transactions/nonce-tracker.js
index 5b1cd7f43..f8cdc5523 100644
--- a/app/scripts/lib/nonce-tracker.js
+++ b/app/scripts/controllers/transactions/nonce-tracker.js
@@ -1,7 +1,15 @@
const EthQuery = require('ethjs-query')
const assert = require('assert')
const Mutex = require('await-semaphore').Mutex
-
+/**
+ @param opts {Object}
+ @param {Object} opts.provider a ethereum provider
+ @param {Function} opts.getPendingTransactions a function that returns an array of txMeta
+ whosee status is `submitted`
+ @param {Function} opts.getConfirmedTransactions a function that returns an array of txMeta
+ whose status is `confirmed`
+ @class
+*/
class NonceTracker {
constructor ({ provider, getPendingTransactions, getConfirmedTransactions }) {
@@ -12,6 +20,9 @@ class NonceTracker {
this.lockMap = {}
}
+ /**
+ @returns {Promise<Object>} with the key releaseLock (the gloabl mutex)
+ */
async getGlobalLock () {
const globalMutex = this._lookupMutex('global')
// await global mutex free
@@ -19,8 +30,20 @@ class NonceTracker {
return { releaseLock }
}
- // releaseLock must be called
- // releaseLock must be called after adding signed tx to pending transactions (or discarding)
+ /**
+ * @typedef NonceDetails
+ * @property {number} highestLocallyConfirmed - A hex string of the highest nonce on a confirmed transaction.
+ * @property {number} nextNetworkNonce - The next nonce suggested by the eth_getTransactionCount method.
+ * @property {number} highetSuggested - The maximum between the other two, the number returned.
+ */
+
+ /**
+ this will return an object with the `nextNonce` `nonceDetails` of type NonceDetails, and the releaseLock
+ Note: releaseLock must be called after adding a signed tx to pending transactions (or discarding).
+
+ @param address {string} the hex string for the address whose nonce we are calculating
+ @returns {Promise<NonceDetails>}
+ */
async getNonceLock (address) {
// await global mutex free
await this._globalMutexFree()
@@ -123,6 +146,17 @@ class NonceTracker {
return highestNonce
}
+ /**
+ @typedef {object} highestContinuousFrom
+ @property {string} - name the name for how the nonce was calculated based on the data used
+ @property {number} - nonce the next suggested nonce
+ @property {object} - details the provided starting nonce that was used (for debugging)
+ */
+ /**
+ @param txList {array} - list of txMeta's
+ @param startPoint {number} - the highest known locally confirmed nonce
+ @returns {highestContinuousFrom}
+ */
_getHighestContinuousFrom (txList, startPoint) {
const nonces = txList.map((txMeta) => {
const nonce = txMeta.txParams.nonce
@@ -140,6 +174,10 @@ class NonceTracker {
// this is a hotfix for the fact that the blockTracker will
// change when the network changes
+
+ /**
+ @returns {Object} the current blockTracker
+ */
_getBlockTracker () {
return this.provider._blockTracker
}
diff --git a/app/scripts/lib/pending-tx-tracker.js b/app/scripts/controllers/transactions/pending-tx-tracker.js
index e8869e6b8..6e2fcb40b 100644
--- a/app/scripts/lib/pending-tx-tracker.js
+++ b/app/scripts/controllers/transactions/pending-tx-tracker.js
@@ -1,23 +1,24 @@
const EventEmitter = require('events')
+const log = require('loglevel')
const EthQuery = require('ethjs-query')
-/*
-
- Utility class for tracking the transactions as they
- go from a pending state to a confirmed (mined in a block) state
+/**
+ Event emitter utility class for tracking the transactions as they<br>
+ go from a pending state to a confirmed (mined in a block) state<br>
+<br>
As well as continues broadcast while in the pending state
+<br>
+@param config {object} - non optional configuration object consists of:
+ @param {Object} config.provider - A network provider.
+ @param {Object} config.nonceTracker see nonce tracker
+ @param {function} config.getPendingTransactions a function for getting an array of transactions,
+ @param {function} config.publishTransaction a async function for publishing raw transactions,
- ~config is not optional~
- requires a: {
- provider: //,
- nonceTracker: //see nonce tracker,
- getPendingTransactions: //() a function for getting an array of transactions,
- publishTransaction: //(rawTx) a async function for publishing raw transactions,
- }
+@class
*/
-module.exports = class PendingTransactionTracker extends EventEmitter {
+class PendingTransactionTracker extends EventEmitter {
constructor (config) {
super()
this.query = new EthQuery(config.provider)
@@ -29,8 +30,13 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
this._checkPendingTxs()
}
- // checks if a signed tx is in a block and
- // if included sets the tx status as 'confirmed'
+ /**
+ checks if a signed tx is in a block and
+ if it is included emits tx status as 'confirmed'
+ @param block {object}, a full block
+ @emits tx:confirmed
+ @emits tx:failed
+ */
checkForTxInBlock (block) {
const signedTxList = this.getPendingTransactions()
if (!signedTxList.length) return
@@ -52,6 +58,11 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
})
}
+ /**
+ asks the network for the transaction to see if a block number is included on it
+ if we have skipped/missed blocks
+ @param object - oldBlock newBlock
+ */
queryPendingTxs ({ oldBlock, newBlock }) {
// check pending transactions on start
if (!oldBlock) {
@@ -63,7 +74,11 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
if (diff > 1) this._checkPendingTxs()
}
-
+ /**
+ Will resubmit any transactions who have not been confirmed in a block
+ @param block {object} - a block object
+ @emits tx:warning
+ */
resubmitPendingTxs (block) {
const pending = this.getPendingTransactions()
// only try resubmitting if their are transactions to resubmit
@@ -100,6 +115,13 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
}))
}
+ /**
+ resubmits the individual txMeta used in resubmitPendingTxs
+ @param txMeta {Object} - txMeta object
+ @param latestBlockNumber {string} - hex string for the latest block number
+ @emits tx:retry
+ @returns txHash {string}
+ */
async _resubmitTx (txMeta, latestBlockNumber) {
if (!txMeta.firstRetryBlockNumber) {
this.emit('tx:block-update', txMeta, latestBlockNumber)
@@ -123,7 +145,13 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
this.emit('tx:retry', txMeta)
return txHash
}
-
+ /**
+ Ask the network for the transaction to see if it has been include in a block
+ @param txMeta {Object} - the txMeta object
+ @emits tx:failed
+ @emits tx:confirmed
+ @emits tx:warning
+ */
async _checkPendingTx (txMeta) {
const txHash = txMeta.hash
const txId = txMeta.id
@@ -162,8 +190,9 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
}
}
- // checks the network for signed txs and
- // if confirmed sets the tx status as 'confirmed'
+ /**
+ checks the network for signed txs and releases the nonce global lock if it is
+ */
async _checkPendingTxs () {
const signedTxList = this.getPendingTransactions()
// in order to keep the nonceTracker accurate we block it while updating pending transactions
@@ -171,12 +200,17 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
try {
await Promise.all(signedTxList.map((txMeta) => this._checkPendingTx(txMeta)))
} catch (err) {
- console.error('PendingTransactionWatcher - Error updating pending transactions')
- console.error(err)
+ log.error('PendingTransactionWatcher - Error updating pending transactions')
+ log.error(err)
}
nonceGlobalLock.releaseLock()
}
+ /**
+ checks to see if a confirmed txMeta has the same nonce
+ @param txMeta {Object} - txMeta object
+ @returns {boolean}
+ */
async _checkIfNonceIsTaken (txMeta) {
const address = txMeta.txParams.from
const completed = this.getCompletedTransactions(address)
@@ -185,5 +219,6 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
})
return sameNonce.length > 0
}
-
}
+
+module.exports = PendingTransactionTracker
diff --git a/app/scripts/lib/tx-gas-utils.js b/app/scripts/controllers/transactions/tx-gas-utils.js
index c579e462a..36b5cdbc9 100644
--- a/app/scripts/lib/tx-gas-utils.js
+++ b/app/scripts/controllers/transactions/tx-gas-utils.js
@@ -3,22 +3,27 @@ const {
hexToBn,
BnMultiplyByFraction,
bnToHex,
-} = require('./util')
+} = require('../../lib/util')
const { addHexPrefix } = require('ethereumjs-util')
const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send.
-/*
-tx-utils are utility methods for Transaction manager
+/**
+tx-gas-utils are gas utility methods for Transaction manager
its passed ethquery
and used to do things like calculate gas of a tx.
+@param {Object} provider - A network provider.
*/
-module.exports = class TxGasUtil {
+class TxGasUtil {
constructor (provider) {
this.query = new EthQuery(provider)
}
+ /**
+ @param txMeta {Object} - the txMeta object
+ @returns {object} the txMeta object with the gas written to the txParams
+ */
async analyzeGasUsage (txMeta) {
const block = await this.query.getBlockByNumber('latest', true)
let estimatedGasHex
@@ -38,6 +43,12 @@ module.exports = class TxGasUtil {
return txMeta
}
+ /**
+ Estimates the tx's gas usage
+ @param txMeta {Object} - the txMeta object
+ @param blockGasLimitHex {string} - hex string of the block's gas limit
+ @returns {string} the estimated gas limit as a hex string
+ */
async estimateTxGas (txMeta, blockGasLimitHex) {
const txParams = txMeta.txParams
@@ -70,6 +81,12 @@ module.exports = class TxGasUtil {
return await this.query.estimateGas(txParams)
}
+ /**
+ Writes the gas on the txParams in the txMeta
+ @param txMeta {Object} - the txMeta object to write to
+ @param blockGasLimitHex {string} - the block gas limit hex
+ @param estimatedGasHex {string} - the estimated gas hex
+ */
setTxGas (txMeta, blockGasLimitHex, estimatedGasHex) {
txMeta.estimatedGas = addHexPrefix(estimatedGasHex)
const txParams = txMeta.txParams
@@ -87,6 +104,13 @@ module.exports = class TxGasUtil {
return
}
+ /**
+ Adds a gas buffer with out exceeding the block gas limit
+
+ @param initialGasLimitHex {string} - the initial gas limit to add the buffer too
+ @param blockGasLimitHex {string} - the block gas limit
+ @returns {string} the buffered gas limit as a hex string
+ */
addGasBuffer (initialGasLimitHex, blockGasLimitHex) {
const initialGasLimitBn = hexToBn(initialGasLimitHex)
const blockGasLimitBn = hexToBn(blockGasLimitHex)
@@ -100,4 +124,6 @@ module.exports = class TxGasUtil {
// otherwise use blockGasLimit
return bnToHex(upperGasLimitBn)
}
-} \ No newline at end of file
+}
+
+module.exports = TxGasUtil \ No newline at end of file
diff --git a/app/scripts/lib/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js
index c6d10ee62..380214c1d 100644
--- a/app/scripts/lib/tx-state-manager.js
+++ b/app/scripts/controllers/transactions/tx-state-manager.js
@@ -1,22 +1,33 @@
const extend = require('xtend')
const EventEmitter = require('events')
const ObservableStore = require('obs-store')
-const createId = require('./random-id')
const ethUtil = require('ethereumjs-util')
-const txStateHistoryHelper = require('./tx-state-history-helper')
-
-// 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
-
-module.exports = class TransactionStateManager extends EventEmitter {
+const txStateHistoryHelper = require('./lib/tx-state-history-helper')
+const createId = require('../../lib/random-id')
+const { getFinalStates } = require('./lib/util')
+/**
+ TransactionStateManager is responsible for the state of a transaction and
+ storing the transaction
+ it also has some convenience methods for finding subsets of transactions
+ *
+ *STATUS METHODS
+ <br>statuses:
+ <br> - `'unapproved'` the user has not responded
+ <br> - `'rejected'` the user has responded no!
+ <br> - `'approved'` the user has approved the tx
+ <br> - `'signed'` the tx is signed
+ <br> - `'submitted'` the tx is sent to a server
+ <br> - `'confirmed'` the tx has been included in a block.
+ <br> - `'failed'` the tx failed for some reason, included on tx data.
+ <br> - `'dropped'` the tx nonce was already used
+ @param opts {object}
+ @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
+*/
+class TransactionStateManager extends EventEmitter {
constructor ({ initState, txHistoryLimit, getNetwork }) {
super()
@@ -28,6 +39,10 @@ module.exports = class TransactionStateManager extends EventEmitter {
this.getNetwork = getNetwork
}
+ /**
+ @param opts {object} - the object to use when overwriting defaults
+ @returns {txMeta} the default txMeta object
+ */
generateTxMeta (opts) {
return extend({
id: createId(),
@@ -38,17 +53,25 @@ module.exports = class TransactionStateManager extends EventEmitter {
}, opts)
}
+ /**
+ @returns {array} of txMetas that have been filtered for only the current network
+ */
getTxList () {
const network = this.getNetwork()
const fullTxList = this.getFullTxList()
return fullTxList.filter((txMeta) => txMeta.metamaskNetworkId === network)
}
+ /**
+ @returns {array} of all the txMetas in store
+ */
getFullTxList () {
return this.store.getState().transactions
}
- // Returns the tx list
+ /**
+ @returns {array} the tx list whos status is unapproved
+ */
getUnapprovedTxList () {
const txList = this.getTxsByMetaData('status', 'unapproved')
return txList.reduce((result, tx) => {
@@ -57,18 +80,37 @@ module.exports = class TransactionStateManager extends EventEmitter {
}, {})
}
+ /**
+ @param [address] {string} - hex prefixed address to sort the txMetas for [optional]
+ @returns {array} the tx list whos status is submitted if no address is provide
+ returns all txMetas who's status is submitted for the current network
+ */
getPendingTransactions (address) {
const opts = { status: 'submitted' }
if (address) opts.from = address
return this.getFilteredTxList(opts)
}
+ /**
+ @param [address] {string} - hex prefixed address to sort the txMetas for [optional]
+ @returns {array} the tx list whos status is confirmed if no address is provide
+ returns all txMetas who's status is confirmed for the current network
+ */
getConfirmedTransactions (address) {
const opts = { status: 'confirmed' }
if (address) opts.from = address
return this.getFilteredTxList(opts)
}
+ /**
+ Adds the txMeta to the list of transactions in the store.
+ if the list is over txHistoryLimit it will remove a transaction that
+ is in its final state
+ it will allso add the key `history` to the txMeta with the snap shot of the original
+ object
+ @param txMeta {Object}
+ @returns {object} the txMeta
+ */
addTx (txMeta) {
this.once(`${txMeta.id}:signed`, function (txId) {
this.removeAllListeners(`${txMeta.id}:rejected`)
@@ -92,7 +134,9 @@ module.exports = class TransactionStateManager extends EventEmitter {
// or rejected tx's.
// not tx's that are pending or unapproved
if (txCount > txHistoryLimit - 1) {
- let index = transactions.findIndex((metaTx) => metaTx.status === 'confirmed' || metaTx.status === 'rejected')
+ const index = transactions.findIndex((metaTx) => {
+ return getFinalStates().includes(metaTx.status)
+ })
if (index !== -1) {
transactions.splice(index, 1)
}
@@ -101,12 +145,21 @@ module.exports = class TransactionStateManager extends EventEmitter {
this._saveTxList(transactions)
return txMeta
}
- // gets tx by Id and returns it
+ /**
+ @param txId {number}
+ @returns {object} the txMeta who matches the given id if none found
+ for the network returns undefined
+ */
getTx (txId) {
const txMeta = this.getTxsByMetaData('id', txId)[0]
return txMeta
}
+ /**
+ updates the txMeta in the list and adds a history entry
+ @param txMeta {Object} - the txMeta to update
+ @param [note] {string} - a not about the update for history
+ */
updateTx (txMeta, note) {
// validate txParams
if (txMeta.txParams) {
@@ -134,16 +187,23 @@ module.exports = class TransactionStateManager extends EventEmitter {
}
- // merges txParams obj onto txData.txParams
- // use extend to ensure that all fields are filled
+ /**
+ merges txParams obj onto txMeta.txParams
+ use extend to ensure that all fields are filled
+ @param txId {number} - the id of the txMeta
+ @param txParams {object} - the updated txParams
+ */
updateTxParams (txId, txParams) {
const txMeta = this.getTx(txId)
txMeta.txParams = extend(txMeta.txParams, txParams)
this.updateTx(txMeta, `txStateManager#updateTxParams`)
}
- // validates txParams members by type
- validateTxParams(txParams) {
+ /**
+ validates txParams members by type
+ @param txParams {object} - txParams to validate
+ */
+ validateTxParams (txParams) {
Object.keys(txParams).forEach((key) => {
const value = txParams[key]
// validate types
@@ -159,17 +219,19 @@ module.exports = class TransactionStateManager extends EventEmitter {
})
}
-/*
- Takes an object of fields to search for eg:
- let thingsToLookFor = {
- to: '0x0..',
- from: '0x0..',
- status: 'signed',
- err: undefined,
- }
- and returns a list of tx with all
+/**
+ @param opts {object} - an object of fields to search for eg:<br>
+ let <code>thingsToLookFor = {<br>
+ to: '0x0..',<br>
+ from: '0x0..',<br>
+ status: 'signed',<br>
+ err: undefined,<br>
+ }<br></code>
+ @param [initialList=this.getTxList()]
+ @returns a {array} of txMeta with all
options matching
-
+ */
+ /*
****************HINT****************
| `err: undefined` is like looking |
| for a tx with no err |
@@ -190,10 +252,17 @@ module.exports = class TransactionStateManager extends EventEmitter {
})
return filteredTxList
}
+ /**
+ @param key {string} - the key to check
+ @param value - the value your looking for
+ @param [txList=this.getTxList()] {array} - the list to search. default is the txList
+ from txStateManager#getTxList
+ @returns {array} a list of txMetas who matches the search params
+ */
getTxsByMetaData (key, value, txList = this.getTxList()) {
return txList.filter((txMeta) => {
- if (txMeta.txParams[key]) {
+ if (key in txMeta.txParams) {
return txMeta.txParams[key] === value
} else {
return txMeta[key] === value
@@ -203,33 +272,51 @@ module.exports = class TransactionStateManager extends EventEmitter {
// get::set status
- // should return the status of the tx.
+ /**
+ @param txId {number} - the txMeta Id
+ @return {string} the status of the tx.
+ */
getTxStatus (txId) {
const txMeta = this.getTx(txId)
return txMeta.status
}
- // should update the status of the tx to 'rejected'.
+ /**
+ should update the status of the tx to 'rejected'.
+ @param txId {number} - the txMeta Id
+ */
setTxStatusRejected (txId) {
this._setTxStatus(txId, 'rejected')
}
- // should update the status of the tx to 'unapproved'.
+ /**
+ should update the status of the tx to 'unapproved'.
+ @param txId {number} - the txMeta Id
+ */
setTxStatusUnapproved (txId) {
this._setTxStatus(txId, 'unapproved')
}
- // should update the status of the tx to 'approved'.
+ /**
+ should update the status of the tx to 'approved'.
+ @param txId {number} - the txMeta Id
+ */
setTxStatusApproved (txId) {
this._setTxStatus(txId, 'approved')
}
- // should update the status of the tx to 'signed'.
+ /**
+ should update the status of the tx to 'signed'.
+ @param txId {number} - the txMeta Id
+ */
setTxStatusSigned (txId) {
this._setTxStatus(txId, 'signed')
}
- // should update the status of the tx to 'submitted'.
- // and add a time stamp for when it was called
+ /**
+ should update the status of the tx to 'submitted'.
+ and add a time stamp for when it was called
+ @param txId {number} - the txMeta Id
+ */
setTxStatusSubmitted (txId) {
const txMeta = this.getTx(txId)
txMeta.submittedTime = (new Date()).getTime()
@@ -237,17 +324,29 @@ module.exports = class TransactionStateManager extends EventEmitter {
this._setTxStatus(txId, 'submitted')
}
- // should update the status of the tx to 'confirmed'.
+ /**
+ should update the status of the tx to 'confirmed'.
+ @param txId {number} - the txMeta Id
+ */
setTxStatusConfirmed (txId) {
this._setTxStatus(txId, 'confirmed')
}
- // should update the status dropped
+ /**
+ should update the status of the tx to 'dropped'.
+ @param txId {number} - the txMeta Id
+ */
setTxStatusDropped (txId) {
this._setTxStatus(txId, 'dropped')
}
+ /**
+ should update the status of the tx to 'failed'.
+ and put the error on the txMeta
+ @param txId {number} - the txMeta Id
+ @param err {erroObject} - error object
+ */
setTxStatusFailed (txId, err) {
const txMeta = this.getTx(txId)
txMeta.err = {
@@ -258,6 +357,11 @@ module.exports = class TransactionStateManager extends EventEmitter {
this._setTxStatus(txId, 'failed')
}
+ /**
+ Removes transaction from the given address for the current network
+ from the txList
+ @param address {string} - hex string of the from address on the txParams to remove
+ */
wipeTransactions (address) {
// network only tx
const txs = this.getFullTxList()
@@ -273,9 +377,8 @@ module.exports = class TransactionStateManager extends EventEmitter {
// PRIVATE METHODS
//
- // Should find the tx in the tx list and
- // update it.
- // should set the status in txData
+ // STATUS METHODS
+ // statuses:
// - `'unapproved'` the user has not responded
// - `'rejected'` the user has responded no!
// - `'approved'` the user has approved the tx
@@ -283,6 +386,15 @@ module.exports = class TransactionStateManager extends EventEmitter {
// - `'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 txId {number} - the txMeta Id
+ @param status {string} - the status to set on the txMeta
+ @emits tx:status-update - passes txId and status
+ @emits ${txMeta.id}:finished - if it is a finished state. Passes the txMeta
+ @emits update:badge
+ */
_setTxStatus (txId, status) {
const txMeta = this.getTx(txId)
txMeta.status = status
@@ -295,9 +407,14 @@ module.exports = class TransactionStateManager extends EventEmitter {
this.emit('update:badge')
}
- // Saves the new/updated txList.
+ /**
+ Saves the new/updated txList.
+ @param transactions {array} - the list of transactions to save
+ */
// Function is intended only for internal use
_saveTxList (transactions) {
this.store.updateState({ transactions })
}
}
+
+module.exports = TransactionStateManager
diff --git a/app/scripts/first-time-state.js b/app/scripts/first-time-state.js
index 144534f43..c49d89288 100644
--- a/app/scripts/first-time-state.js
+++ b/app/scripts/first-time-state.js
@@ -1,6 +1,7 @@
// test and development environment variables
const env = process.env.METAMASK_ENV
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
+const { DEFAULT_NETWORK, MAINNET } = require('./controllers/network/enums')
/**
* @typedef {Object} FirstTimeState
@@ -15,7 +16,7 @@ const initialState = {
config: {},
NetworkController: {
provider: {
- type: (METAMASK_DEBUG || env === 'test') ? 'rinkeby' : 'mainnet',
+ type: (METAMASK_DEBUG || env === 'test') ? DEFAULT_NETWORK : MAINNET,
},
},
}
diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js
index 8c3dd8c71..0f7b3d865 100644
--- a/app/scripts/lib/account-tracker.js
+++ b/app/scripts/lib/account-tracker.js
@@ -16,6 +16,24 @@ function noop () {}
class AccountTracker extends EventEmitter {
+ /**
+ * This module is responsible for tracking any number of accounts and caching their current balances & transaction
+ * counts.
+ *
+ * It also tracks transaction hashes, and checks their inclusion status on each new block.
+ *
+ * @typedef {Object} AccountTracker
+ * @param {Object} opts Initialize various properties of the class.
+ * @property {Object} store The stored object containing all accounts to track, as well as the current block's gas limit.
+ * @property {Object} store.accounts The accounts currently stored in this AccountTracker
+ * @property {string} store.currentBlockGasLimit A hex string indicating the gas limit of the current block
+ * @property {Object} _provider A provider needed to create the EthQuery instance used within this AccountTracker.
+ * @property {EthQuery} _query An EthQuery instance used to access account information from the blockchain
+ * @property {BlockTracker} _blockTracker A BlockTracker instance. Needed to ensure that accounts and their info updates
+ * when a new block is created.
+ * @property {Object} _currentBlockNumber Reference to a property on the _blockTracker: the number (i.e. an id) of the the current block
+ *
+ */
constructor (opts = {}) {
super()
@@ -34,10 +52,17 @@ class AccountTracker extends EventEmitter {
this._currentBlockNumber = this._blockTracker.currentBlock
}
- //
- // public
- //
-
+ /**
+ * Ensures that the locally stored accounts are in sync with a set of accounts stored externally to this
+ * AccountTracker.
+ *
+ * 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.
+ *
+ * @param {array} address The array of hex addresses for accounts with which this AccountTracker's accounts should be
+ * in sync
+ *
+ */
syncWithAddresses (addresses) {
const accounts = this.store.getState().accounts
const locals = Object.keys(accounts)
@@ -61,6 +86,13 @@ class AccountTracker extends EventEmitter {
this._updateAccounts()
}
+ /**
+ * Adds a new address to this AccountTracker's accounts object, which points to an empty object. This object will be
+ * given a balance as long this._currentBlockNumber is defined.
+ *
+ * @param {string} address A hex address of a new account to store in this AccountTracker's accounts object
+ *
+ */
addAccount (address) {
const accounts = this.store.getState().accounts
accounts[address] = {}
@@ -69,16 +101,27 @@ class AccountTracker extends EventEmitter {
this._updateAccount(address)
}
+ /**
+ * Removes an account from this AccountTracker's accounts object
+ *
+ * @param {string} address A hex address of a the account to remove
+ *
+ */
removeAccount (address) {
const accounts = this.store.getState().accounts
delete accounts[address]
this.store.updateState({ accounts })
}
- //
- // private
- //
-
+ /**
+ * Given a block, updates this AccountTracker's currentBlockGasLimit, and then updates each local account's balance
+ * via EthQuery
+ *
+ * @private
+ * @param {object} block Data about the block that contains the data to update to.
+ * @fires 'block' The updated state, if all account updates are successful
+ *
+ */
_updateForBlock (block) {
this._currentBlockNumber = block.number
const currentBlockGasLimit = block.gasLimit
@@ -93,12 +136,26 @@ class AccountTracker extends EventEmitter {
})
}
+ /**
+ * Calls this._updateAccount for each account in this.store
+ *
+ * @param {Function} cb A callback to pass to this._updateAccount, called after each account is successfully updated
+ *
+ */
_updateAccounts (cb = noop) {
const accounts = this.store.getState().accounts
const addresses = Object.keys(accounts)
async.each(addresses, this._updateAccount.bind(this), cb)
}
+ /**
+ * Updates the current balance of an account. Gets an updated balance via this._getAccount.
+ *
+ * @private
+ * @param {string} address A hex address of a the account to be updated
+ * @param {Function} cb A callback to call once the account at address is successfully update
+ *
+ */
_updateAccount (address, cb = noop) {
this._getAccount(address, (err, result) => {
if (err) return cb(err)
@@ -113,6 +170,14 @@ class AccountTracker extends EventEmitter {
})
}
+ /**
+ * Gets the current balance of an account via EthQuery.
+ *
+ * @private
+ * @param {string} address A hex address of a the account to query
+ * @param {Function} cb A callback to call once the account at address is successfully update
+ *
+ */
_getAccount (address, cb = noop) {
const query = this._query
async.parallel({
diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js
index 63d27c40e..221746467 100644
--- a/app/scripts/lib/config-manager.js
+++ b/app/scripts/lib/config-manager.js
@@ -1,12 +1,11 @@
const ethUtil = require('ethereumjs-util')
const normalize = require('eth-sig-util').normalize
-const MetamaskConfig = require('../config.js')
-
-
-const MAINNET_RPC = MetamaskConfig.network.mainnet
-const ROPSTEN_RPC = MetamaskConfig.network.ropsten
-const KOVAN_RPC = MetamaskConfig.network.kovan
-const RINKEBY_RPC = MetamaskConfig.network.rinkeby
+const {
+ MAINNET_RPC_URL,
+ ROPSTEN_RPC_URL,
+ KOVAN_RPC_URL,
+ RINKEBY_RPC_URL,
+} = require('../controllers/network/enums')
/* The config-manager is a convenience object
* wrapping a pojo-migrator.
@@ -102,6 +101,7 @@ ConfigManager.prototype.setShowSeedWords = function (should) {
this.setData(data)
}
+
ConfigManager.prototype.getShouldShowSeedWords = function () {
var data = this.getData()
return data.showSeedWords
@@ -117,27 +117,6 @@ ConfigManager.prototype.getSeedWords = function () {
var data = this.getData()
return data.seedWords
}
-
-/**
- * Called to set the isRevealingSeedWords flag. This happens only when the user chooses to reveal
- * the seed words and not during the first time flow.
- * @param {boolean} reveal - Value to set the isRevealingSeedWords flag.
- */
-ConfigManager.prototype.setIsRevealingSeedWords = function (reveal = false) {
- const data = this.getData()
- data.isRevealingSeedWords = reveal
- this.setData(data)
-}
-
-/**
- * Returns the isRevealingSeedWords flag.
- * @returns {boolean|undefined}
- */
-ConfigManager.prototype.getIsRevealingSeedWords = function () {
- const data = this.getData()
- return data.isRevealingSeedWords
-}
-
ConfigManager.prototype.setRpcTarget = function (rpcUrl) {
var config = this.getConfig()
config.provider = {
@@ -174,19 +153,19 @@ ConfigManager.prototype.getCurrentRpcAddress = function () {
switch (provider.type) {
case 'mainnet':
- return MAINNET_RPC
+ return MAINNET_RPC_URL
case 'ropsten':
- return ROPSTEN_RPC
+ return ROPSTEN_RPC_URL
case 'kovan':
- return KOVAN_RPC
+ return KOVAN_RPC_URL
case 'rinkeby':
- return RINKEBY_RPC
+ return RINKEBY_RPC_URL
default:
- return provider && provider.rpcTarget ? provider.rpcTarget : RINKEBY_RPC
+ return provider && provider.rpcTarget ? provider.rpcTarget : RINKEBY_RPC_URL
}
}
diff --git a/app/scripts/lib/extractEthjsErrorMessage.js b/app/scripts/lib/extractEthjsErrorMessage.js
index bac541735..0f100756f 100644
--- a/app/scripts/lib/extractEthjsErrorMessage.js
+++ b/app/scripts/lib/extractEthjsErrorMessage.js
@@ -4,17 +4,18 @@ const errorLabelPrefix = 'Error: '
module.exports = extractEthjsErrorMessage
-//
-// ethjs-rpc provides overly verbose error messages
-// if we detect this type of message, we extract the important part
-// Below is an example input and output
-//
-// Error: [ethjs-rpc] rpc error with payload {"id":3947817945380,"jsonrpc":"2.0","params":["0xf8eb8208708477359400830398539406012c8cf97bead5deae237070f9587f8e7a266d80b8843d7d3f5a0000000000000000000000000000000000000000000000000000000000081d1a000000000000000000000000000000000000000000000000001ff973cafa800000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000000000003f48025a04c32a9b630e0d9e7ff361562d850c86b7a884908135956a7e4a336fa0300d19ca06830776423f25218e8d19b267161db526e66895567147015b1f3fc47aef9a3c7"],"method":"eth_sendRawTransaction"} Error: replacement transaction underpriced
-//
-// Transaction Failed: replacement transaction underpriced
-//
-
-
+/**
+ * Extracts the important part of an ethjs-rpc error message. If the passed error is not an isEthjsRpcError, the error
+ * is returned unchanged.
+ *
+ * @param {string} errorMessage The error message to parse
+ * @returns {string} Returns an error message, either the same as was passed, or the ending message portion of an isEthjsRpcError
+ *
+ * @example
+ * // returns 'Transaction Failed: replacement transaction underpriced'
+ * extractEthjsErrorMessage(`Error: [ethjs-rpc] rpc error with payload {"id":3947817945380,"jsonrpc":"2.0","params":["0xf8eb8208708477359400830398539406012c8cf97bead5deae237070f9587f8e7a266d80b8843d7d3f5a0000000000000000000000000000000000000000000000000000000000081d1a000000000000000000000000000000000000000000000000001ff973cafa800000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000000000003f48025a04c32a9b630e0d9e7ff361562d850c86b7a884908135956a7e4a336fa0300d19ca06830776423f25218e8d19b267161db526e66895567147015b1f3fc47aef9a3c7"],"method":"eth_sendRawTransaction"} Error: replacement transaction underpriced`)
+ *
+*/
function extractEthjsErrorMessage(errorMessage) {
const isEthjsRpcError = errorMessage.includes(ethJsRpcSlug)
if (isEthjsRpcError) {
diff --git a/app/scripts/lib/getObjStructure.js b/app/scripts/lib/getObjStructure.js
index 3db389507..52250d3fb 100644
--- a/app/scripts/lib/getObjStructure.js
+++ b/app/scripts/lib/getObjStructure.js
@@ -14,6 +14,15 @@ module.exports = getObjStructure
// }
// }
+/**
+ * Creates an object that represents the structure of the given object. It replaces all values with the result of their
+ * type.
+ *
+ * @param {object} obj The object for which a 'structure' will be returned. Usually a plain object and not a class.
+ * @returns {object} The "mapped" version of a deep clone of the passed object, with each non-object property value
+ * replaced with the javascript type of that value.
+ *
+ */
function getObjStructure(obj) {
const structure = clone(obj)
return deepMap(structure, (value) => {
@@ -21,6 +30,14 @@ function getObjStructure(obj) {
})
}
+/**
+ * Modifies all the properties and deeply nested of a passed object. Iterates recursively over all nested objects and
+ * their properties, and covers the entire depth of the object. At each property value which is not an object is modified.
+ *
+ * @param {object} target The object to modify
+ * @param {Function} visit The modifier to apply to each non-object property value
+ * @returns {object} The modified object
+ */
function deepMap(target = {}, visit) {
Object.entries(target).forEach(([key, value]) => {
if (typeof value === 'object' && value !== null) {
diff --git a/app/scripts/lib/message-manager.js b/app/scripts/lib/message-manager.js
index f52e048e0..901367f04 100644
--- a/app/scripts/lib/message-manager.js
+++ b/app/scripts/lib/message-manager.js
@@ -3,8 +3,37 @@ const ObservableStore = require('obs-store')
const ethUtil = require('ethereumjs-util')
const createId = require('./random-id')
+/**
+ * Represents, and contains data about, an 'eth_sign' type signature request. These are created when a signature for
+ * an eth_sign call is requested.
+ *
+ * @see {@link https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign}
+ *
+ * @typedef {Object} Message
+ * @property {number} id An id to track and identify the message object
+ * @property {Object} msgParams The parameters to pass to the eth_sign method once the signature request is approved.
+ * @property {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask.
+ * @property {string} msgParams.data A hex string conversion of the raw buffer data of the signature request
+ * @property {number} time The epoch time at which the this message was created
+ * @property {string} status Indicates whether the signature request is 'unapproved', 'approved', 'signed' or 'rejected'
+ * @property {string} type The json-prc signing method for which a signature request has been made. A 'Message' with
+ * always have a 'eth_sign' type.
+ *
+ */
module.exports = class MessageManager extends EventEmitter {
+
+ /**
+ * Controller in charge of managing - storing, adding, removing, updating - Messages.
+ *
+ * @typedef {Object} MessageManager
+ * @param {Object} opts @deprecated
+ * @property {Object} memStore The observable store where Messages are saved.
+ * @property {Object} memStore.unapprovedMsgs A collection of all Messages in the 'unapproved' state
+ * @property {number} memStore.unapprovedMsgCount The count of all Messages in this.memStore.unapprobedMsgs
+ * @property {array} messages Holds all messages that have been created by this MessageManager
+ *
+ */
constructor (opts) {
super()
this.memStore = new ObservableStore({
@@ -14,15 +43,35 @@ module.exports = class MessageManager extends EventEmitter {
this.messages = []
}
+ /**
+ * A getter for the number of 'unapproved' Messages in this.messages
+ *
+ * @returns {number} The number of 'unapproved' Messages in this.messages
+ *
+ */
get unapprovedMsgCount () {
return Object.keys(this.getUnapprovedMsgs()).length
}
+ /**
+ * A getter for the 'unapproved' Messages in this.messages
+ *
+ * @returns {Object} An index of Message ids to Messages, for all 'unapproved' Messages in this.messages
+ *
+ */
getUnapprovedMsgs () {
return this.messages.filter(msg => msg.status === 'unapproved')
.reduce((result, msg) => { result[msg.id] = msg; return result }, {})
}
+ /**
+ * Creates a new Message with an 'unapproved' status using the passed msgParams. this.addMsg is called to add the
+ * new Message to this.messages, and to save the unapproved Messages from that list to this.memStore.
+ *
+ * @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
+ * @returns {number} The id of the newly created message.
+ *
+ */
addUnapprovedMessage (msgParams) {
msgParams.data = normalizeMsgData(msgParams.data)
// create txData obj with parameters and meta data
@@ -42,24 +91,61 @@ module.exports = class MessageManager extends EventEmitter {
return msgId
}
+ /**
+ * Adds a passed Message to this.messages, and calls this._saveMsgList() to save the unapproved Messages from that
+ * list to this.memStore.
+ *
+ * @param {Message} msg The Message to add to this.messages
+ *
+ */
addMsg (msg) {
this.messages.push(msg)
this._saveMsgList()
}
+ /**
+ * Returns a specified Message.
+ *
+ * @param {number} msgId The id of the Message to get
+ * @returns {Message|undefined} The Message with the id that matches the passed msgId, or undefined if no Message has that id.
+ *
+ */
getMsg (msgId) {
return this.messages.find(msg => msg.id === msgId)
}
+ /**
+ * Approves a Message. Sets the message status via a call to this.setMsgStatusApproved, and returns a promise with
+ * any the message params modified for proper signing.
+ *
+ * @param {Object} msgParams The msgParams to be used when eth_sign is called, plus data added by MetaMask.
+ * @param {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask.
+ * @returns {Promise<object>} Promises the msgParams object with metamaskId removed.
+ *
+ */
approveMessage (msgParams) {
this.setMsgStatusApproved(msgParams.metamaskId)
return this.prepMsgForSigning(msgParams)
}
+ /**
+ * Sets a Message status to 'approved' via a call to this._setMsgStatus.
+ *
+ * @param {number} msgId The id of the Message to approve.
+ *
+ */
setMsgStatusApproved (msgId) {
this._setMsgStatus(msgId, 'approved')
}
+ /**
+ * Sets a Message status to 'signed' via a call to this._setMsgStatus and updates that Message in this.messages by
+ * adding the raw signature data of the signature request to the Message
+ *
+ * @param {number} msgId The id of the Message to sign.
+ * @param {buffer} rawSig The raw data of the signature request
+ *
+ */
setMsgStatusSigned (msgId, rawSig) {
const msg = this.getMsg(msgId)
msg.rawSig = rawSig
@@ -67,19 +153,40 @@ module.exports = class MessageManager extends EventEmitter {
this._setMsgStatus(msgId, 'signed')
}
+ /**
+ * Removes the metamaskId property from passed msgParams and returns a promise which resolves the updated msgParams
+ *
+ * @param {Object} msgParams The msgParams to modify
+ * @returns {Promise<object>} Promises the msgParams with the metamaskId property removed
+ *
+ */
prepMsgForSigning (msgParams) {
delete msgParams.metamaskId
return Promise.resolve(msgParams)
}
+ /**
+ * Sets a Message status to 'rejected' via a call to this._setMsgStatus.
+ *
+ * @param {number} msgId The id of the Message to reject.
+ *
+ */
rejectMsg (msgId) {
this._setMsgStatus(msgId, 'rejected')
}
- //
- // PRIVATE METHODS
- //
-
+ /**
+ * Updates the status of a Message in this.messages via a call to this._updateMsg
+ *
+ * @private
+ * @param {number} msgId The id of the Message to update.
+ * @param {string} status The new status of the Message.
+ * @throws A 'MessageManager - Message not found for id: "${msgId}".' if there is no Message in this.messages with an
+ * id equal to the passed msgId
+ * @fires An event with a name equal to `${msgId}:${status}`. The Message is also fired.
+ * @fires If status is 'rejected' or 'signed', an event with a name equal to `${msgId}:finished` is fired along with the message
+ *
+ */
_setMsgStatus (msgId, status) {
const msg = this.getMsg(msgId)
if (!msg) throw new Error('MessageManager - Message not found for id: "${msgId}".')
@@ -91,6 +198,14 @@ module.exports = class MessageManager extends EventEmitter {
}
}
+ /**
+ * Sets a Message in this.messages to the passed Message if the ids are equal. Then saves the unapprovedMsg list to
+ * storage via this._saveMsgList
+ *
+ * @private
+ * @param {msg} Message A Message that will replace an existing Message (with the same id) in this.messages
+ *
+ */
_updateMsg (msg) {
const index = this.messages.findIndex((message) => message.id === msg.id)
if (index !== -1) {
@@ -99,6 +214,13 @@ module.exports = class MessageManager extends EventEmitter {
this._saveMsgList()
}
+ /**
+ * Saves the unapproved messages, and their count, to this.memStore
+ *
+ * @private
+ * @fires 'updateBadge'
+ *
+ */
_saveMsgList () {
const unapprovedMsgs = this.getUnapprovedMsgs()
const unapprovedMsgCount = Object.keys(unapprovedMsgs).length
@@ -108,6 +230,13 @@ module.exports = class MessageManager extends EventEmitter {
}
+/**
+ * A helper function that converts raw buffer data to a hex, or just returns the data if it is already formatted as a hex.
+ *
+ * @param {any} data The buffer data to convert to a hex
+ * @returns {string} A hex string conversion of the buffer data
+ *
+ */
function normalizeMsgData (data) {
if (data.slice(0, 2) === '0x') {
// data is already hex
diff --git a/app/scripts/lib/notification-manager.js b/app/scripts/lib/notification-manager.js
index 1fcb7cf69..5dfb42078 100644
--- a/app/scripts/lib/notification-manager.js
+++ b/app/scripts/lib/notification-manager.js
@@ -5,10 +5,18 @@ const width = 360
class NotificationManager {
- //
- // Public
- //
+ /**
+ * A collection of methods for controlling the showing and hiding of the notification popup.
+ *
+ * @typedef {Object} NotificationManager
+ *
+ */
+ /**
+ * Either brings an existing MetaMask notification window into focus, or creates a new notification window. New
+ * notification windows are given a 'popup' type.
+ *
+ */
showPopup () {
this._getPopup((err, popup) => {
if (err) throw err
@@ -29,6 +37,10 @@ class NotificationManager {
})
}
+ /**
+ * Closes a MetaMask notification if it window exists.
+ *
+ */
closePopup () {
// closes notification popup
this._getPopup((err, popup) => {
@@ -38,10 +50,14 @@ class NotificationManager {
})
}
- //
- // Private
- //
-
+ /**
+ * Checks all open MetaMask windows, and returns the first one it finds that is a notification window (i.e. has the
+ * type 'popup')
+ *
+ * @private
+ * @param {Function} cb A node style callback that to whcih the found notification window will be passed.
+ *
+ */
_getPopup (cb) {
this._getWindows((err, windows) => {
if (err) throw err
@@ -49,6 +65,13 @@ class NotificationManager {
})
}
+ /**
+ * Returns all open MetaMask windows.
+ *
+ * @private
+ * @param {Function} cb A node style callback that to which the windows will be passed.
+ *
+ */
_getWindows (cb) {
// Ignore in test environment
if (!extension.windows) {
@@ -60,6 +83,13 @@ class NotificationManager {
})
}
+ /**
+ * Given an array of windows, returns the first that has a 'popup' type, or null if no such window exists.
+ *
+ * @private
+ * @param {array} windows An array of objects containing data about the open MetaMask extension windows.
+ *
+ */
_getPopupIn (windows) {
return windows ? windows.find((win) => {
// Returns notification popup
diff --git a/app/scripts/lib/pending-balance-calculator.js b/app/scripts/lib/pending-balance-calculator.js
index 6ae526463..0f1dc19a9 100644
--- a/app/scripts/lib/pending-balance-calculator.js
+++ b/app/scripts/lib/pending-balance-calculator.js
@@ -3,16 +3,28 @@ const normalize = require('eth-sig-util').normalize
class PendingBalanceCalculator {
- // Must be initialized with two functions:
- // getBalance => Returns a promise of a BN of the current balance in Wei
- // getPendingTransactions => Returns an array of TxMeta Objects,
- // which have txParams properties, which include value, gasPrice, and gas,
- // all in a base=16 hex format.
+ /**
+ * Used for calculating a users "pending balance": their current balance minus the total possible cost of all their
+ * pending transactions.
+ *
+ * @typedef {Object} PendingBalanceCalculator
+ * @param {Function} getBalance Returns a promise of a BN of the current balance in Wei
+ * @param {Function} getPendingTransactions Returns an array of TxMeta Objects, which have txParams properties,
+ * which include value, gasPrice, and gas, all in a base=16 hex format.
+ *
+ */
constructor ({ getBalance, getPendingTransactions }) {
this.getPendingTransactions = getPendingTransactions
this.getNetworkBalance = getBalance
}
+ /**
+ * Returns the users "pending balance": their current balance minus the total possible cost of all their
+ * pending transactions.
+ *
+ * @returns {Promise<string>} Promises a base 16 hex string that contains the user's "pending balance"
+ *
+ */
async getBalance () {
const results = await Promise.all([
this.getNetworkBalance(),
@@ -29,6 +41,15 @@ class PendingBalanceCalculator {
return `0x${balance.sub(pendingValue).toString(16)}`
}
+ /**
+ * Calculates the maximum possible cost of a single transaction, based on the value, gas price and gas limit.
+ *
+ * @param {object} tx Contains all that data about a transaction.
+ * @property {object} tx.txParams Contains data needed to calculate the maximum cost of the transaction: gas,
+ * gasLimit and value.
+ *
+ * @returns {string} Returns a base 16 hex string that contains the maximum possible cost of the transaction.
+ */
calculateMaxCost (tx) {
const txValue = tx.txParams.value
const value = this.hexToBn(txValue)
@@ -42,6 +63,13 @@ class PendingBalanceCalculator {
return value.add(gasCost)
}
+ /**
+ * Converts a hex string to a BN object
+ *
+ * @param {string} hex A number represented as a hex string
+ * @returns {Object} A BN object
+ *
+ */
hexToBn (hex) {
return new BN(normalize(hex).substring(2), 16)
}
diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js
index 43a7d0b42..e96ced1f2 100644
--- a/app/scripts/lib/personal-message-manager.js
+++ b/app/scripts/lib/personal-message-manager.js
@@ -5,8 +5,37 @@ const createId = require('./random-id')
const hexRe = /^[0-9A-Fa-f]+$/g
const log = require('loglevel')
+/**
+ * Represents, and contains data about, an 'personal_sign' type signature request. These are created when a
+ * signature for an personal_sign call is requested.
+ *
+ * @see {@link https://web3js.readthedocs.io/en/1.0/web3-eth-personal.html#sign}
+ *
+ * @typedef {Object} PersonalMessage
+ * @property {number} id An id to track and identify the message object
+ * @property {Object} msgParams The parameters to pass to the personal_sign method once the signature request is
+ * approved.
+ * @property {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask.
+ * @property {string} msgParams.data A hex string conversion of the raw buffer data of the signature request
+ * @property {number} time The epoch time at which the this message was created
+ * @property {string} status Indicates whether the signature request is 'unapproved', 'approved', 'signed' or 'rejected'
+ * @property {string} type The json-prc signing method for which a signature request has been made. A 'Message' will
+ * always have a 'personal_sign' type.
+ *
+ */
module.exports = class PersonalMessageManager extends EventEmitter {
+ /**
+ * Controller in charge of managing - storing, adding, removing, updating - PersonalMessage.
+ *
+ * @typedef {Object} PersonalMessageManager
+ * @param {Object} opts @deprecated
+ * @property {Object} memStore The observable store where PersonalMessage are saved with persistance.
+ * @property {Object} memStore.unapprovedPersonalMsgs A collection of all PersonalMessages in the 'unapproved' state
+ * @property {number} memStore.unapprovedPersonalMsgCount The count of all PersonalMessages in this.memStore.unapprobedMsgs
+ * @property {array} messages Holds all messages that have been created by this PersonalMessageManager
+ *
+ */
constructor (opts) {
super()
this.memStore = new ObservableStore({
@@ -16,15 +45,37 @@ module.exports = class PersonalMessageManager extends EventEmitter {
this.messages = []
}
+ /**
+ * A getter for the number of 'unapproved' PersonalMessages in this.messages
+ *
+ * @returns {number} The number of 'unapproved' PersonalMessages in this.messages
+ *
+ */
get unapprovedPersonalMsgCount () {
return Object.keys(this.getUnapprovedMsgs()).length
}
+ /**
+ * A getter for the 'unapproved' PersonalMessages in this.messages
+ *
+ * @returns {Object} An index of PersonalMessage ids to PersonalMessages, for all 'unapproved' PersonalMessages in
+ * this.messages
+ *
+ */
getUnapprovedMsgs () {
return this.messages.filter(msg => msg.status === 'unapproved')
.reduce((result, msg) => { result[msg.id] = msg; return result }, {})
}
+ /**
+ * Creates a new PersonalMessage with an 'unapproved' status using the passed msgParams. this.addMsg is called to add
+ * the new PersonalMessage to this.messages, and to save the unapproved PersonalMessages from that list to
+ * this.memStore.
+ *
+ * @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
+ * @returns {number} The id of the newly created PersonalMessage.
+ *
+ */
addUnapprovedMessage (msgParams) {
log.debug(`PersonalMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`)
msgParams.data = this.normalizeMsgData(msgParams.data)
@@ -45,24 +96,62 @@ module.exports = class PersonalMessageManager extends EventEmitter {
return msgId
}
+ /**
+ * Adds a passed PersonalMessage to this.messages, and calls this._saveMsgList() to save the unapproved PersonalMessages from that
+ * list to this.memStore.
+ *
+ * @param {Message} msg The PersonalMessage to add to this.messages
+ *
+ */
addMsg (msg) {
this.messages.push(msg)
this._saveMsgList()
}
+ /**
+ * Returns a specified PersonalMessage.
+ *
+ * @param {number} msgId The id of the PersonalMessage to get
+ * @returns {PersonalMessage|undefined} The PersonalMessage with the id that matches the passed msgId, or undefined
+ * if no PersonalMessage has that id.
+ *
+ */
getMsg (msgId) {
return this.messages.find(msg => msg.id === msgId)
}
+ /**
+ * Approves a PersonalMessage. Sets the message status via a call to this.setMsgStatusApproved, and returns a promise
+ * with any the message params modified for proper signing.
+ *
+ * @param {Object} msgParams The msgParams to be used when eth_sign is called, plus data added by MetaMask.
+ * @param {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask.
+ * @returns {Promise<object>} Promises the msgParams object with metamaskId removed.
+ *
+ */
approveMessage (msgParams) {
this.setMsgStatusApproved(msgParams.metamaskId)
return this.prepMsgForSigning(msgParams)
}
+ /**
+ * Sets a PersonalMessage status to 'approved' via a call to this._setMsgStatus.
+ *
+ * @param {number} msgId The id of the PersonalMessage to approve.
+ *
+ */
setMsgStatusApproved (msgId) {
this._setMsgStatus(msgId, 'approved')
}
+ /**
+ * Sets a PersonalMessage status to 'signed' via a call to this._setMsgStatus and updates that PersonalMessage in
+ * this.messages by adding the raw signature data of the signature request to the PersonalMessage
+ *
+ * @param {number} msgId The id of the PersonalMessage to sign.
+ * @param {buffer} rawSig The raw data of the signature request
+ *
+ */
setMsgStatusSigned (msgId, rawSig) {
const msg = this.getMsg(msgId)
msg.rawSig = rawSig
@@ -70,19 +159,41 @@ module.exports = class PersonalMessageManager extends EventEmitter {
this._setMsgStatus(msgId, 'signed')
}
+ /**
+ * Removes the metamaskId property from passed msgParams and returns a promise which resolves the updated msgParams
+ *
+ * @param {Object} msgParams The msgParams to modify
+ * @returns {Promise<object>} Promises the msgParams with the metamaskId property removed
+ *
+ */
prepMsgForSigning (msgParams) {
delete msgParams.metamaskId
return Promise.resolve(msgParams)
}
+ /**
+ * Sets a PersonalMessage status to 'rejected' via a call to this._setMsgStatus.
+ *
+ * @param {number} msgId The id of the PersonalMessage to reject.
+ *
+ */
rejectMsg (msgId) {
this._setMsgStatus(msgId, 'rejected')
}
- //
- // PRIVATE METHODS
- //
-
+ /**
+ * Updates the status of a PersonalMessage in this.messages via a call to this._updateMsg
+ *
+ * @private
+ * @param {number} msgId The id of the PersonalMessage to update.
+ * @param {string} status The new status of the PersonalMessage.
+ * @throws A 'PersonalMessageManager - PersonalMessage not found for id: "${msgId}".' if there is no PersonalMessage
+ * in this.messages with an id equal to the passed msgId
+ * @fires An event with a name equal to `${msgId}:${status}`. The PersonalMessage is also fired.
+ * @fires If status is 'rejected' or 'signed', an event with a name equal to `${msgId}:finished` is fired along
+ * with the PersonalMessage
+ *
+ */
_setMsgStatus (msgId, status) {
const msg = this.getMsg(msgId)
if (!msg) throw new Error('PersonalMessageManager - Message not found for id: "${msgId}".')
@@ -94,6 +205,15 @@ module.exports = class PersonalMessageManager extends EventEmitter {
}
}
+ /**
+ * Sets a PersonalMessage in this.messages to the passed PersonalMessage if the ids are equal. Then saves the
+ * unapprovedPersonalMsgs index to storage via this._saveMsgList
+ *
+ * @private
+ * @param {msg} PersonalMessage A PersonalMessage that will replace an existing PersonalMessage (with the same
+ * id) in this.messages
+ *
+ */
_updateMsg (msg) {
const index = this.messages.findIndex((message) => message.id === msg.id)
if (index !== -1) {
@@ -102,6 +222,13 @@ module.exports = class PersonalMessageManager extends EventEmitter {
this._saveMsgList()
}
+ /**
+ * Saves the unapproved PersonalMessages, and their count, to this.memStore
+ *
+ * @private
+ * @fires 'updateBadge'
+ *
+ */
_saveMsgList () {
const unapprovedPersonalMsgs = this.getUnapprovedMsgs()
const unapprovedPersonalMsgCount = Object.keys(unapprovedPersonalMsgs).length
@@ -109,6 +236,13 @@ module.exports = class PersonalMessageManager extends EventEmitter {
this.emit('updateBadge')
}
+ /**
+ * A helper function that converts raw buffer data to a hex, or just returns the data if it is already formatted as a hex.
+ *
+ * @param {any} data The buffer data to convert to a hex
+ * @returns {string} A hex string conversion of the buffer data
+ *
+ */
normalizeMsgData (data) {
try {
const stripped = ethUtil.stripHexPrefix(data)
diff --git a/app/scripts/lib/seed-phrase-verifier.js b/app/scripts/lib/seed-phrase-verifier.js
index 7ba712c0d..3b5afb800 100644
--- a/app/scripts/lib/seed-phrase-verifier.js
+++ b/app/scripts/lib/seed-phrase-verifier.js
@@ -3,11 +3,19 @@ const log = require('loglevel')
const seedPhraseVerifier = {
- // Verifies if the seed words can restore the accounts.
- //
- // The seed words can recreate the primary keyring and the accounts belonging to it.
- // The created accounts in the primary keyring are always the same.
- // The keyring always creates the accounts in the same sequence.
+ /**
+ * Verifies if the seed words can restore the accounts.
+ *
+ * Key notes:
+ * - The seed words can recreate the primary keyring and the accounts belonging to it.
+ * - The created accounts in the primary keyring are always the same.
+ * - The keyring always creates the accounts in the same sequence.
+ *
+ * @param {array} createdAccounts The accounts to restore
+ * @param {string} seedWords The seed words to verify
+ * @returns {Promise<void>} Promises undefined
+ *
+ */
verifyAccounts (createdAccounts, seedWords) {
return new Promise((resolve, reject) => {
diff --git a/app/scripts/lib/setupRaven.js b/app/scripts/lib/setupRaven.js
index 9ec9a256f..b1b67f771 100644
--- a/app/scripts/lib/setupRaven.js
+++ b/app/scripts/lib/setupRaven.js
@@ -23,22 +23,16 @@ function setupRaven(opts) {
release,
transport: function(opts) {
const report = opts.data
- // simplify certain complex error messages
- report.exception.values.forEach(item => {
- let errorMessage = item.value
- // simplify ethjs error messages
- errorMessage = extractEthjsErrorMessage(errorMessage)
- // simplify 'Transaction Failed: known transaction'
- if (errorMessage.indexOf('Transaction Failed: known transaction') === 0) {
- // cut the hash from the error message
- errorMessage = 'Transaction Failed: known transaction'
- }
- // finalize
- item.value = errorMessage
- })
-
- // modify report urls
- rewriteReportUrls(report)
+ try {
+ // handle error-like non-error exceptions
+ nonErrorException(report)
+ // simplify certain complex error messages (e.g. Ethjs)
+ simplifyErrorMessages(report)
+ // modify report urls
+ rewriteReportUrls(report)
+ } catch (err) {
+ console.warn(err)
+ }
// make request normally
client._makeRequest(opts)
},
@@ -48,15 +42,42 @@ function setupRaven(opts) {
return Raven
}
+function nonErrorException(report) {
+ // handle errors that lost their error-ness in serialization
+ if (report.message.includes('Non-Error exception captured with keys: message')) {
+ if (!(report.extra && report.extra.__serialized__)) return
+ report.message = `Non-Error Exception: ${report.extra.__serialized__.message}`
+ }
+}
+
+function simplifyErrorMessages(report) {
+ if (report.exception && report.exception.values) {
+ report.exception.values.forEach(item => {
+ let errorMessage = item.value
+ // simplify ethjs error messages
+ errorMessage = extractEthjsErrorMessage(errorMessage)
+ // simplify 'Transaction Failed: known transaction'
+ if (errorMessage.indexOf('Transaction Failed: known transaction') === 0) {
+ // cut the hash from the error message
+ errorMessage = 'Transaction Failed: known transaction'
+ }
+ // finalize
+ item.value = errorMessage
+ })
+ }
+}
+
function rewriteReportUrls(report) {
// update request url
report.request.url = toMetamaskUrl(report.request.url)
// update exception stack trace
- report.exception.values.forEach(item => {
- item.stacktrace.frames.forEach(frame => {
- frame.filename = toMetamaskUrl(frame.filename)
+ if (report.exception && report.exception.values) {
+ report.exception.values.forEach(item => {
+ item.stacktrace.frames.forEach(frame => {
+ frame.filename = toMetamaskUrl(frame.filename)
+ })
})
- })
+ }
}
function toMetamaskUrl(origUrl) {
diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js
index 60042155e..c58921610 100644
--- a/app/scripts/lib/typed-message-manager.js
+++ b/app/scripts/lib/typed-message-manager.js
@@ -5,7 +5,36 @@ const assert = require('assert')
const sigUtil = require('eth-sig-util')
const log = require('loglevel')
+/**
+ * Represents, and contains data about, an 'eth_signTypedData' type signature request. These are created when a
+ * signature for an eth_signTypedData call is requested.
+ *
+ * @typedef {Object} TypedMessage
+ * @property {number} id An id to track and identify the message object
+ * @property {Object} msgParams The parameters to pass to the eth_signTypedData method once the signature request is
+ * approved.
+ * @property {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask.
+ * @property {Object} msgParams.from The address that is making the signature request.
+ * @property {string} msgParams.data A hex string conversion of the raw buffer data of the signature request
+ * @property {number} time The epoch time at which the this message was created
+ * @property {string} status Indicates whether the signature request is 'unapproved', 'approved', 'signed' or 'rejected'
+ * @property {string} type The json-prc signing method for which a signature request has been made. A 'Message' will
+ * always have a 'eth_signTypedData' type.
+ *
+ */
+
module.exports = class TypedMessageManager extends EventEmitter {
+ /**
+ * Controller in charge of managing - storing, adding, removing, updating - TypedMessage.
+ *
+ * @typedef {Object} TypedMessage
+ * @param {Object} opts @deprecated
+ * @property {Object} memStore The observable store where TypedMessage are saved.
+ * @property {Object} memStore.unapprovedTypedMessages A collection of all TypedMessages in the 'unapproved' state
+ * @property {number} memStore.unapprovedTypedMessagesCount The count of all TypedMessages in this.memStore.unapprobedMsgs
+ * @property {array} messages Holds all messages that have been created by this TypedMessage
+ *
+ */
constructor (opts) {
super()
this.memStore = new ObservableStore({
@@ -15,15 +44,37 @@ module.exports = class TypedMessageManager extends EventEmitter {
this.messages = []
}
+ /**
+ * A getter for the number of 'unapproved' TypedMessages in this.messages
+ *
+ * @returns {number} The number of 'unapproved' TypedMessages in this.messages
+ *
+ */
get unapprovedTypedMessagesCount () {
return Object.keys(this.getUnapprovedMsgs()).length
}
+ /**
+ * A getter for the 'unapproved' TypedMessages in this.messages
+ *
+ * @returns {Object} An index of TypedMessage ids to TypedMessages, for all 'unapproved' TypedMessages in
+ * this.messages
+ *
+ */
getUnapprovedMsgs () {
return this.messages.filter(msg => msg.status === 'unapproved')
.reduce((result, msg) => { result[msg.id] = msg; return result }, {})
}
+ /**
+ * Creates a new TypedMessage with an 'unapproved' status using the passed msgParams. this.addMsg is called to add
+ * the new TypedMessage to this.messages, and to save the unapproved TypedMessages from that list to
+ * this.memStore. Before any of this is done, msgParams are validated
+ *
+ * @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
+ * @returns {number} The id of the newly created TypedMessage.
+ *
+ */
addUnapprovedMessage (msgParams) {
this.validateParams(msgParams)
@@ -45,6 +96,12 @@ module.exports = class TypedMessageManager extends EventEmitter {
return msgId
}
+ /**
+ * Helper method for this.addUnapprovedMessage. Validates that the passed params have the required properties.
+ *
+ * @param {Object} params The params to validate
+ *
+ */
validateParams (params) {
assert.equal(typeof params, 'object', 'Params should ben an object.')
assert.ok('data' in params, 'Params must include a data field.')
@@ -56,24 +113,62 @@ module.exports = class TypedMessageManager extends EventEmitter {
}, 'Expected EIP712 typed data')
}
+ /**
+ * Adds a passed TypedMessage to this.messages, and calls this._saveMsgList() to save the unapproved TypedMessages from that
+ * list to this.memStore.
+ *
+ * @param {Message} msg The TypedMessage to add to this.messages
+ *
+ */
addMsg (msg) {
this.messages.push(msg)
this._saveMsgList()
}
+ /**
+ * Returns a specified TypedMessage.
+ *
+ * @param {number} msgId The id of the TypedMessage to get
+ * @returns {TypedMessage|undefined} The TypedMessage with the id that matches the passed msgId, or undefined
+ * if no TypedMessage has that id.
+ *
+ */
getMsg (msgId) {
return this.messages.find(msg => msg.id === msgId)
}
+ /**
+ * Approves a TypedMessage. Sets the message status via a call to this.setMsgStatusApproved, and returns a promise
+ * with any the message params modified for proper signing.
+ *
+ * @param {Object} msgParams The msgParams to be used when eth_sign is called, plus data added by MetaMask.
+ * @param {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask.
+ * @returns {Promise<object>} Promises the msgParams object with metamaskId removed.
+ *
+ */
approveMessage (msgParams) {
this.setMsgStatusApproved(msgParams.metamaskId)
return this.prepMsgForSigning(msgParams)
}
+ /**
+ * Sets a TypedMessage status to 'approved' via a call to this._setMsgStatus.
+ *
+ * @param {number} msgId The id of the TypedMessage to approve.
+ *
+ */
setMsgStatusApproved (msgId) {
this._setMsgStatus(msgId, 'approved')
}
+ /**
+ * Sets a TypedMessage status to 'signed' via a call to this._setMsgStatus and updates that TypedMessage in
+ * this.messages by adding the raw signature data of the signature request to the TypedMessage
+ *
+ * @param {number} msgId The id of the TypedMessage to sign.
+ * @param {buffer} rawSig The raw data of the signature request
+ *
+ */
setMsgStatusSigned (msgId, rawSig) {
const msg = this.getMsg(msgId)
msg.rawSig = rawSig
@@ -81,11 +176,24 @@ module.exports = class TypedMessageManager extends EventEmitter {
this._setMsgStatus(msgId, 'signed')
}
+ /**
+ * Removes the metamaskId property from passed msgParams and returns a promise which resolves the updated msgParams
+ *
+ * @param {Object} msgParams The msgParams to modify
+ * @returns {Promise<object>} Promises the msgParams with the metamaskId property removed
+ *
+ */
prepMsgForSigning (msgParams) {
delete msgParams.metamaskId
return Promise.resolve(msgParams)
}
+ /**
+ * Sets a TypedMessage status to 'rejected' via a call to this._setMsgStatus.
+ *
+ * @param {number} msgId The id of the TypedMessage to reject.
+ *
+ */
rejectMsg (msgId) {
this._setMsgStatus(msgId, 'rejected')
}
@@ -94,6 +202,19 @@ module.exports = class TypedMessageManager extends EventEmitter {
// PRIVATE METHODS
//
+ /**
+ * Updates the status of a TypedMessage in this.messages via a call to this._updateMsg
+ *
+ * @private
+ * @param {number} msgId The id of the TypedMessage to update.
+ * @param {string} status The new status of the TypedMessage.
+ * @throws A 'TypedMessageManager - TypedMessage not found for id: "${msgId}".' if there is no TypedMessage
+ * in this.messages with an id equal to the passed msgId
+ * @fires An event with a name equal to `${msgId}:${status}`. The TypedMessage is also fired.
+ * @fires If status is 'rejected' or 'signed', an event with a name equal to `${msgId}:finished` is fired along
+ * with the TypedMessage
+ *
+ */
_setMsgStatus (msgId, status) {
const msg = this.getMsg(msgId)
if (!msg) throw new Error('TypedMessageManager - Message not found for id: "${msgId}".')
@@ -105,6 +226,15 @@ module.exports = class TypedMessageManager extends EventEmitter {
}
}
+ /**
+ * Sets a TypedMessage in this.messages to the passed TypedMessage if the ids are equal. Then saves the
+ * unapprovedTypedMsgs index to storage via this._saveMsgList
+ *
+ * @private
+ * @param {msg} TypedMessage A TypedMessage that will replace an existing TypedMessage (with the same
+ * id) in this.messages
+ *
+ */
_updateMsg (msg) {
const index = this.messages.findIndex((message) => message.id === msg.id)
if (index !== -1) {
@@ -113,6 +243,13 @@ module.exports = class TypedMessageManager extends EventEmitter {
this._saveMsgList()
}
+ /**
+ * Saves the unapproved TypedMessages, and their count, to this.memStore
+ *
+ * @private
+ * @fires 'updateBadge'
+ *
+ */
_saveMsgList () {
const unapprovedTypedMessages = this.getUnapprovedMsgs()
const unapprovedTypedMessagesCount = Object.keys(unapprovedTypedMessages).length
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 782bc50ac..a90acb4d5 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -263,6 +263,7 @@ module.exports = class MetamaskController extends EventEmitter {
/**
* Constructor helper: initialize a public config store.
+ * This store is used to make some config info available to Dapps synchronously.
*/
initPublicConfigStore () {
// get init state
@@ -308,15 +309,16 @@ module.exports = class MetamaskController extends EventEmitter {
lostAccounts: this.configManager.getLostAccounts(),
seedWords: this.configManager.getSeedWords(),
forgottenPassword: this.configManager.getPasswordForgotten(),
- isRevealingSeedWords: Boolean(this.configManager.getIsRevealingSeedWords()),
},
}
}
/**
- * Returns an api-object which is consumed by the UI
+ * Returns an Object containing API Callback Functions.
+ * These functions are the interface for the UI.
+ * The API object can be transmitted over a stream with dnode.
*
- * @returns {Object}
+ * @returns {Object} Object containing API functions.
*/
getApi () {
const keyringController = this.keyringController
@@ -348,7 +350,6 @@ module.exports = class MetamaskController extends EventEmitter {
clearSeedWordCache: this.clearSeedWordCache.bind(this),
resetAccount: nodeify(this.resetAccount, this),
importAccountWithStrategy: this.importAccountWithStrategy.bind(this),
- setIsRevealingSeedWords: this.configManager.setIsRevealingSeedWords.bind(this.configManager),
// vault management
submitPassword: nodeify(keyringController.submitPassword, keyringController),
@@ -381,6 +382,7 @@ module.exports = class MetamaskController extends EventEmitter {
updateTransaction: nodeify(txController.updateTransaction, txController),
updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController),
retryTransaction: nodeify(this.retryTransaction, this),
+ isNonceTaken: nodeify(txController.isNonceTaken, txController),
// messageManager
signMessage: nodeify(this.signMessage, this),
@@ -407,16 +409,18 @@ module.exports = class MetamaskController extends EventEmitter {
//=============================================================================
/**
- * Creates a new Vault(?) and create a new keychain(?)
+ * Creates a new Vault and create a new keychain.
*
- * A vault is ...
+ * A vault, or KeyringController, is a controller that contains
+ * many different account strategies, currently called Keyrings.
+ * Creating it new means wiping all previous keyrings.
*
- * A keychain is ...
+ * A keychain, or keyring, controls many accounts with a single backup and signing strategy.
+ * For example, a mnemonic phrase can generate many accounts, and is a keyring.
*
+ * @param {string} password
*
- * @param {} password
- *
- * @returns {} vault
+ * @returns {Object} vault
*/
async createNewVaultAndKeychain (password) {
const release = await this.createVaultMutex.acquire()
@@ -442,7 +446,7 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
- * Create a new Vault and restore an existent keychain
+ * Create a new Vault and restore an existent keyring.
* @param {} password
* @param {} seed
*/
@@ -460,10 +464,16 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
+ * @type Identity
+ * @property {string} name - The account nickname.
+ * @property {string} address - The account's ethereum address, in lower case.
+ * @property {boolean} mayBeFauceting - Whether this account is currently
+ * receiving funds from our automatic Ropsten faucet.
+ */
+
+ /**
* Retrieves the first Identiy from the passed Vault and selects the related address
*
- * An Identity is ...
- *
* @param {} vault
*/
selectFirstIdentity (vault) {
@@ -472,12 +482,12 @@ module.exports = class MetamaskController extends EventEmitter {
this.preferencesController.setSelectedAddress(address)
}
- // ?
- // Opinionated Keyring Management
+ //
+ // Account Management
//
/**
- * Adds a new account to ...
+ * Adds a new account to the default (first) HD seed phrase Keyring.
*
* @returns {} keyState
*/
@@ -507,6 +517,8 @@ module.exports = class MetamaskController extends EventEmitter {
*
* Used when creating a first vault, to allow confirmation.
* Also used when revealing the seed words in the confirmation view.
+ *
+ * @param {Function} cb - A callback called on completion.
*/
placeSeedWords (cb) {
@@ -526,6 +538,8 @@ module.exports = class MetamaskController extends EventEmitter {
* Validity: seed phrase restores the accounts belonging to the current vault.
*
* Called when the first account is created and on unlocking the vault.
+ *
+ * @returns {Promise<string>} Seed phrase to be confirmed by the user.
*/
async verifySeedPhrase () {
@@ -556,6 +570,7 @@ module.exports = class MetamaskController extends EventEmitter {
*
* The seed phrase remains available in the background process.
*
+ * @param {function} cb Callback function called with the current address.
*/
clearSeedWordCache (cb) {
this.configManager.setSeedWords(null)
@@ -563,9 +578,13 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
- * ?
+ * Clears the transaction history, to allow users to force-reset their nonces.
+ * Mostly used in development environments, when networks are restarted with
+ * the same network ID.
+ *
+ * @returns Promise<string> The current selected address.
*/
- async resetAccount (cb) {
+ async resetAccount () {
const selectedAddress = this.preferencesController.getSelectedAddress()
this.txController.wipeTransactions(selectedAddress)
@@ -577,11 +596,13 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
- * Imports an account ... ?
+ * Imports an account with the specified import strategy.
+ * These are defined in app/scripts/account-import-strategies
+ * Each strategy represents a different way of serializing an Ethereum key pair.
*
- * @param {} strategy
- * @param {} args
- * @param {} cb
+ * @param {string} strategy - A unique identifier for an account import strategy.
+ * @param {any} args - The data required by that strategy to import an account.
+ * @param {Function} cb - A callback function called with a state update on success.
*/
importAccountWithStrategy (strategy, args, cb) {
accountImporter.importAccount(strategy, args)
@@ -595,13 +616,42 @@ module.exports = class MetamaskController extends EventEmitter {
}
// ---------------------------------------------------------------------------
- // Identity Management (sign)
+ // Identity Management (signature operations)
+
+ // eth_sign methods:
+
+ /**
+ * Called when a Dapp uses the eth_sign method, to request user approval.
+ * eth_sign is a pure signature of arbitrary data. It is on a deprecation
+ * path, since this data can be a transaction, or can leak private key
+ * information.
+ *
+ * @param {Object} msgParams - The params passed to eth_sign.
+ * @param {Function} cb = The callback function called with the signature.
+ */
+ newUnsignedMessage (msgParams, cb) {
+ const msgId = this.messageManager.addUnapprovedMessage(msgParams)
+ this.sendUpdate()
+ this.opts.showUnconfirmedMessage()
+ this.messageManager.once(`${msgId}:finished`, (data) => {
+ switch (data.status) {
+ case 'signed':
+ return cb(null, data.rawSig)
+ case 'rejected':
+ return cb(new Error('MetaMask Message Signature: User denied message signature.'))
+ default:
+ return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
+ }
+ })
+ }
/**
- * @param {} msgParams
- * @param {} cb
+ * Signifies user intent to complete an eth_sign method.
+ *
+ * @param {Object} msgParams The params passed to eth_call.
+ * @returns {Promise<Object>} Full state update.
*/
- signMessage (msgParams, cb) {
+ signMessage (msgParams) {
log.info('MetaMaskController - signMessage')
const msgId = msgParams.metamaskId
@@ -620,14 +670,37 @@ module.exports = class MetamaskController extends EventEmitter {
})
}
- // Prefixed Style Message Signing Methods:
+ /**
+ * Used to cancel a message submitted via eth_sign.
+ *
+ * @param {string} msgId - The id of the message to cancel.
+ */
+ cancelMessage (msgId, cb) {
+ const messageManager = this.messageManager
+ messageManager.rejectMsg(msgId)
+ if (cb && typeof cb === 'function') {
+ cb(null, this.getState())
+ }
+ }
+
+ // personal_sign methods:
/**
+ * Called when a dapp uses the personal_sign method.
+ * This is identical to the Geth eth_sign method, and may eventually replace
+ * eth_sign.
*
- * @param {} msgParams
- * @param {} cb
+ * We currently define our eth_sign and personal_sign mostly for legacy Dapps.
+ *
+ * @param {Object} msgParams - The params of the message to sign & return to the Dapp.
+ * @param {Function} cb - The callback function called with the signature.
+ * Passed back to the requesting Dapp.
*/
- approvePersonalMessage (msgParams, cb) {
+ newUnsignedPersonalMessage (msgParams, cb) {
+ if (!msgParams.from) {
+ return cb(new Error('MetaMask Message Signature: from field is required.'))
+ }
+
const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams)
this.sendUpdate()
this.opts.showUnconfirmedMessage()
@@ -636,7 +709,7 @@ module.exports = class MetamaskController extends EventEmitter {
case 'signed':
return cb(null, data.rawSig)
case 'rejected':
- return cb(new Error('MetaMask Message Signature: User denied transaction signature.'))
+ return cb(new Error('MetaMask Message Signature: User denied message signature.'))
default:
return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
}
@@ -644,7 +717,11 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
- * @param {} msgParams
+ * Signifies a user's approval to sign a personal_sign message in queue.
+ * Triggers signing, and the callback function from newUnsignedPersonalMessage.
+ *
+ * @param {Object} msgParams - The params of the message to sign & return to the Dapp.
+ * @returns {Promise<Object>} - A full state update.
*/
signPersonalMessage (msgParams) {
log.info('MetaMaskController - signPersonalMessage')
@@ -665,7 +742,54 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
- * @param {} msgParams
+ * Used to cancel a personal_sign type message.
+ * @param {string} msgId - The ID of the message to cancel.
+ * @param {Function} cb - The callback function called with a full state update.
+ */
+ cancelPersonalMessage (msgId, cb) {
+ const messageManager = this.personalMessageManager
+ messageManager.rejectMsg(msgId)
+ if (cb && typeof cb === 'function') {
+ cb(null, this.getState())
+ }
+ }
+
+ // eth_signTypedData methods
+
+ /**
+ * Called when a dapp uses the eth_signTypedData method, per EIP 712.
+ *
+ * @param {Object} msgParams - The params passed to eth_signTypedData.
+ * @param {Function} cb - The callback function, called with the signature.
+ */
+ newUnsignedTypedMessage (msgParams, cb) {
+ let msgId
+ try {
+ msgId = this.typedMessageManager.addUnapprovedMessage(msgParams)
+ this.sendUpdate()
+ this.opts.showUnconfirmedMessage()
+ } catch (e) {
+ return cb(e)
+ }
+
+ this.typedMessageManager.once(`${msgId}:finished`, (data) => {
+ switch (data.status) {
+ case 'signed':
+ return cb(null, data.rawSig)
+ case 'rejected':
+ return cb(new Error('MetaMask Message Signature: User denied message signature.'))
+ default:
+ return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
+ }
+ })
+ }
+
+ /**
+ * The method for a user approving a call to eth_signTypedData, per EIP 712.
+ * Triggers the callback in newUnsignedTypedMessage.
+ *
+ * @param {Object} msgParams - The params passed to eth_signTypedData.
+ * @returns {Object} Full state update.
*/
signTypedMessage (msgParams) {
log.info('MetaMaskController - signTypedMessage')
@@ -685,12 +809,30 @@ module.exports = class MetamaskController extends EventEmitter {
})
}
+ /**
+ * Used to cancel a eth_signTypedData type message.
+ * @param {string} msgId - The ID of the message to cancel.
+ * @param {Function} cb - The callback function called with a full state update.
+ */
+ cancelTypedMessage (msgId, cb) {
+ const messageManager = this.typedMessageManager
+ messageManager.rejectMsg(msgId)
+ if (cb && typeof cb === 'function') {
+ cb(null, this.getState())
+ }
+ }
+
// ---------------------------------------------------------------------------
- // Account Restauration
+ // MetaMask Version 3 Migration Account Restauration Methods
/**
- * ?
+ * A legacy method (probably dead code) that was used when we swapped out our
+ * key management library that we depended on.
*
+ * Described in:
+ * https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd
+ *
+ * @deprecated
* @param {} migratorOutput
*/
restoreOldVaultAccounts (migratorOutput) {
@@ -700,8 +842,26 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
- * ?
+ * A legacy method used to record user confirmation that they understand
+ * that some of their accounts have been recovered but should be backed up.
+ *
+ * @deprecated
+ * @param {Function} cb - A callback function called with a full state update.
+ */
+ markAccountsFound (cb) {
+ this.configManager.setLostAccounts([])
+ this.sendUpdate()
+ cb(null, this.getState())
+ }
+
+ /**
+ * A legacy method (probably dead code) that was used when we swapped out our
+ * key management library that we depended on.
*
+ * Described in:
+ * https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd
+ *
+ * @deprecated
* @param {} migratorOutput
*/
restoreOldLostAccounts (migratorOutput) {
@@ -714,12 +874,23 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
- * Import (lost) Accounts
+ * An account object
+ * @typedef Account
+ * @property string privateKey - The private key of the account.
+ */
+
+ /**
+ * Probably no longer needed, related to the Version 3 migration.
+ * Imports a hash of accounts to private keys into the vault.
*
- * @param {Object} {lostAccounts} @Array accounts <{ address, privateKey }>
+ * Described in:
+ * https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd
*
* Uses the array's private keys to create a new Simple Key Pair keychain
* and add it to the keyring controller.
+ * @deprecated
+ * @param {Account[]} lostAccounts -
+ * @returns {Keyring[]} An array of the restored keyrings.
*/
importLostAccounts ({ lostAccounts }) {
const privKeys = lostAccounts.map(acct => acct.privateKey)
@@ -733,113 +904,37 @@ module.exports = class MetamaskController extends EventEmitter {
// END (VAULT / KEYRING RELATED METHODS)
//=============================================================================
-//
-
-//=============================================================================
-// MESSAGES
-//=============================================================================
-
+ /**
+ * Allows a user to try to speed up a transaction by retrying it
+ * with higher gas.
+ *
+ * @param {string} txId - The ID of the transaction to speed up.
+ * @param {Function} cb - The callback function called with a full state update.
+ */
async retryTransaction (txId, cb) {
await this.txController.retryTransaction(txId)
const state = await this.getState()
return state
}
+//=============================================================================
+// PASSWORD MANAGEMENT
+//=============================================================================
- newUnsignedMessage (msgParams, cb) {
- const msgId = this.messageManager.addUnapprovedMessage(msgParams)
- this.sendUpdate()
- this.opts.showUnconfirmedMessage()
- this.messageManager.once(`${msgId}:finished`, (data) => {
- switch (data.status) {
- case 'signed':
- return cb(null, data.rawSig)
- case 'rejected':
- return cb(new Error('MetaMask Message Signature: User denied message signature.'))
- default:
- return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
- }
- })
- }
-
- newUnsignedPersonalMessage (msgParams, cb) {
- if (!msgParams.from) {
- return cb(new Error('MetaMask Message Signature: from field is required.'))
- }
-
- const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams)
- this.sendUpdate()
- this.opts.showUnconfirmedMessage()
- this.personalMessageManager.once(`${msgId}:finished`, (data) => {
- switch (data.status) {
- case 'signed':
- return cb(null, data.rawSig)
- case 'rejected':
- return cb(new Error('MetaMask Message Signature: User denied message signature.'))
- default:
- return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
- }
- })
- }
-
- newUnsignedTypedMessage (msgParams, cb) {
- let msgId
- try {
- msgId = this.typedMessageManager.addUnapprovedMessage(msgParams)
- this.sendUpdate()
- this.opts.showUnconfirmedMessage()
- } catch (e) {
- return cb(e)
- }
-
- this.typedMessageManager.once(`${msgId}:finished`, (data) => {
- switch (data.status) {
- case 'signed':
- return cb(null, data.rawSig)
- case 'rejected':
- return cb(new Error('MetaMask Message Signature: User denied message signature.'))
- default:
- return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
- }
- })
- }
-
- cancelMessage (msgId, cb) {
- const messageManager = this.messageManager
- messageManager.rejectMsg(msgId)
- if (cb && typeof cb === 'function') {
- cb(null, this.getState())
- }
- }
-
- cancelPersonalMessage (msgId, cb) {
- const messageManager = this.personalMessageManager
- messageManager.rejectMsg(msgId)
- if (cb && typeof cb === 'function') {
- cb(null, this.getState())
- }
- }
-
- cancelTypedMessage (msgId, cb) {
- const messageManager = this.typedMessageManager
- messageManager.rejectMsg(msgId)
- if (cb && typeof cb === 'function') {
- cb(null, this.getState())
- }
- }
-
- markAccountsFound (cb) {
- this.configManager.setLostAccounts([])
- this.sendUpdate()
- cb(null, this.getState())
- }
-
+ /**
+ * Allows a user to begin the seed phrase recovery process.
+ * @param {Function} cb - A callback function called when complete.
+ */
markPasswordForgotten(cb) {
this.configManager.setPasswordForgotten(true)
this.sendUpdate()
cb()
}
+ /**
+ * Allows a user to end the seed phrase recovery process.
+ * @param {Function} cb - A callback function called when complete.
+ */
unMarkPasswordForgotten(cb) {
this.configManager.setPasswordForgotten(false)
this.sendUpdate()
@@ -850,6 +945,13 @@ module.exports = class MetamaskController extends EventEmitter {
// SETUP
//=============================================================================
+ /**
+ * Used to create a multiplexed stream for connecting to an untrusted context
+ * like a Dapp or other extension.
+ * @param {*} connectionStream - The Duplex stream to connect to.
+ * @param {string} originDomain - The domain requesting the stream, which
+ * may trigger a blacklist reload.
+ */
setupUntrustedCommunication (connectionStream, originDomain) {
// Check if new connection is blacklisted
if (this.blacklistController.checkForPhishing(originDomain)) {
@@ -865,6 +967,16 @@ module.exports = class MetamaskController extends EventEmitter {
this.setupPublicConfig(mux.createStream('publicConfig'))
}
+ /**
+ * Used to create a multiplexed stream for connecting to a trusted context,
+ * like our own user interfaces, which have the provider APIs, but also
+ * receive the exported API from this controller, which includes trusted
+ * functions, like the ability to approve transactions or sign messages.
+ *
+ * @param {*} connectionStream - The duplex stream to connect to.
+ * @param {string} originDomain - The domain requesting the connection,
+ * used in logging and error reporting.
+ */
setupTrustedCommunication (connectionStream, originDomain) {
// setup multiplexing
const mux = setupMultiplex(connectionStream)
@@ -873,12 +985,25 @@ module.exports = class MetamaskController extends EventEmitter {
this.setupProviderConnection(mux.createStream('provider'), originDomain)
}
+ /**
+ * Called when we detect a suspicious domain. Requests the browser redirects
+ * to our anti-phishing page.
+ *
+ * @private
+ * @param {*} connectionStream - The duplex stream to the per-page script,
+ * for sending the reload attempt to.
+ * @param {string} hostname - The URL that triggered the suspicion.
+ */
sendPhishingWarning (connectionStream, hostname) {
const mux = setupMultiplex(connectionStream)
const phishingStream = mux.createStream('phishing')
phishingStream.write({ hostname })
}
+ /**
+ * A method for providing our API over a stream using Dnode.
+ * @param {*} outStream - The stream to provide our API over.
+ */
setupControllerConnection (outStream) {
const api = this.getApi()
const dnode = Dnode(api)
@@ -897,6 +1022,11 @@ module.exports = class MetamaskController extends EventEmitter {
})
}
+ /**
+ * A method for serving our ethereum provider over a given stream.
+ * @param {*} outStream - The stream to provide over.
+ * @param {string} origin - The URI of the requesting resource.
+ */
setupProviderConnection (outStream, origin) {
// setup json rpc engine stack
const engine = new RpcEngine()
@@ -926,6 +1056,16 @@ module.exports = class MetamaskController extends EventEmitter {
)
}
+ /**
+ * A method for providing our public config info over a stream.
+ * This includes info we like to be synchronous if possible, like
+ * the current selected account, and network ID.
+ *
+ * Since synchronous methods have been deprecated in web3,
+ * this is a good candidate for deprecation.
+ *
+ * @param {*} outStream - The stream to provide public config over.
+ */
setupPublicConfig (outStream) {
pump(
asStream(this.publicConfigStore),
@@ -936,10 +1076,21 @@ module.exports = class MetamaskController extends EventEmitter {
)
}
+ /**
+ * A method for emitting the full MetaMask state to all registered listeners.
+ * @private
+ */
privateSendUpdate () {
this.emit('update', this.getState())
}
+ /**
+ * A method for estimating a good gas price at recent prices.
+ * Returns the lowest price that would have been included in
+ * 50% of recent blocks.
+ *
+ * @returns {string} A hex representation of the suggested wei gas price.
+ */
getGasPrice () {
const { recentBlocksController } = this
const { recentBlocks } = recentBlocksController.store.getState()
@@ -973,6 +1124,11 @@ module.exports = class MetamaskController extends EventEmitter {
// Log blocks
+ /**
+ * A method for setting the user's preferred display currency.
+ * @param {string} currencyCode - The code of the preferred currency.
+ * @param {Function} cb - A callback function returning currency info.
+ */
setCurrentCurrency (currencyCode, cb) {
try {
this.currencyController.setCurrentCurrency(currencyCode)
@@ -988,6 +1144,13 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
+ /**
+ * A method for forwarding the user to the easiest way to obtain ether,
+ * or the network "gas" currency, for the current selected network.
+ *
+ * @param {string} address - The address to fund.
+ * @param {string} amount - The amount of ether desired, as a base 10 string.
+ */
buyEth (address, amount) {
if (!amount) amount = '5'
const network = this.networkController.getNetworkState()
@@ -995,18 +1158,33 @@ module.exports = class MetamaskController extends EventEmitter {
if (url) this.platform.openWindow({ url })
}
+ /**
+ * A method for triggering a shapeshift currency transfer.
+ * @param {string} depositAddress - The address to deposit to.
+ * @property {string} depositType - An abbreviation of the type of crypto currency to be deposited.
+ */
createShapeShiftTx (depositAddress, depositType) {
this.shapeshiftController.createShapeShiftTx(depositAddress, depositType)
}
// network
- async setCustomRpc (rpcTarget, rpcList) {
+ /**
+ * A method for selecting a custom URL for an ethereum RPC provider.
+ * @param {string} rpcTarget - A URL for a valid Ethereum RPC API.
+ * @returns {Promise<String>} - The RPC Target URL confirmed.
+ */
+ async setCustomRpc (rpcTarget) {
this.networkController.setRpcTarget(rpcTarget)
await this.preferencesController.updateFrequentRpcList(rpcTarget)
return rpcTarget
}
+ /**
+ * Sets whether or not to use the blockie identicon format.
+ * @param {boolean} val - True for bockie, false for jazzicon.
+ * @param {Function} cb - A callback function called when complete.
+ */
setUseBlockie (val, cb) {
try {
this.preferencesController.setUseBlockie(val)
@@ -1016,6 +1194,11 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
+ /**
+ * A method for setting a user's current locale, affecting the language rendered.
+ * @param {string} key - Locale identifier.
+ * @param {Function} cb - A callback function called when complete.
+ */
setCurrentLocale (key, cb) {
try {
this.preferencesController.setCurrentLocale(key)
@@ -1025,6 +1208,11 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
+ /**
+ * A method for initializing storage the first time.
+ * @param {Object} initState - The default state to initialize with.
+ * @private
+ */
recordFirstTimeInfo (initState) {
if (!('firstTimeInfo' in initState)) {
initState.firstTimeInfo = {
@@ -1034,11 +1222,21 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
+ /**
+ * A method for recording whether the MetaMask user interface is open or not.
+ * @private
+ * @param {boolean} open
+ */
set isClientOpen (open) {
this._isClientOpen = open
this.isClientOpenAndUnlocked = this.getState().isUnlocked && open
}
+ /**
+ * A method for activating the retrieval of price data, which should only be fetched when the UI is visible.
+ * @private
+ * @param {boolean} active - True if price data should be getting fetched.
+ */
set isClientOpenAndUnlocked (active) {
this.tokenRatesController.isActive = active
}
diff --git a/app/scripts/migrations/018.js b/app/scripts/migrations/018.js
index bea1fe3da..ffbf24a4b 100644
--- a/app/scripts/migrations/018.js
+++ b/app/scripts/migrations/018.js
@@ -7,7 +7,7 @@ This migration updates "transaction state history" to diffs style
*/
const clone = require('clone')
-const txStateHistoryHelper = require('../lib/tx-state-history-helper')
+const txStateHistoryHelper = require('../controllers/transactions/lib/tx-state-history-helper')
module.exports = {