aboutsummaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
authorAlexander Tseung <alextsg@gmail.com>2018-01-30 10:22:52 +0800
committerAlexander Tseung <alextsg@gmail.com>2018-01-30 10:22:52 +0800
commitecc39c5a7abd8c8794d5565c1bc7d213d3514d61 (patch)
tree746f0a2bada5d8cd6636789d3c498c1ef13901fb /app
parentd905b86ba775aad888d1dfd22257958fd9415909 (diff)
parentb05d21b1ba308bdb5b758d53dd79593a7a2bf26e (diff)
downloadtangerine-wallet-browser-ecc39c5a7abd8c8794d5565c1bc7d213d3514d61.tar.gz
tangerine-wallet-browser-ecc39c5a7abd8c8794d5565c1bc7d213d3514d61.tar.zst
tangerine-wallet-browser-ecc39c5a7abd8c8794d5565c1bc7d213d3514d61.zip
Merge branch 'uat' of https://github.com/MetaMask/metamask-extension into cb-254
Diffstat (limited to 'app')
-rw-r--r--app/_locales/ko/messages.json10
-rw-r--r--app/images/coinbase logo.pngbin0 -> 9775 bytes
-rw-r--r--app/images/metamask-fox.svg128
-rw-r--r--app/images/popout.svg21
-rw-r--r--app/images/shapeshift logo.pngbin0 -> 17537 bytes
-rw-r--r--app/manifest.json2
-rw-r--r--app/scripts/config.js22
-rw-r--r--app/scripts/contentscript.js12
-rw-r--r--app/scripts/controllers/blacklist.js1
-rw-r--r--app/scripts/controllers/network.js104
-rw-r--r--app/scripts/controllers/preferences.js8
-rw-r--r--app/scripts/controllers/recent-blocks.js110
-rw-r--r--app/scripts/controllers/transactions.js46
-rw-r--r--app/scripts/lib/account-tracker.js2
-rw-r--r--app/scripts/lib/pending-tx-tracker.js10
-rw-r--r--app/scripts/lib/tx-gas-utils.js57
-rw-r--r--app/scripts/lib/tx-state-manager.js6
-rw-r--r--app/scripts/metamask-controller.js100
-rw-r--r--app/scripts/notice-controller.js2
-rw-r--r--app/scripts/popup.js13
20 files changed, 579 insertions, 75 deletions
diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json
new file mode 100644
index 000000000..c58af4b80
--- /dev/null
+++ b/app/_locales/ko/messages.json
@@ -0,0 +1,10 @@
+{
+ "appName": {
+ "message": "MetaMask",
+ "description": "The name of the application"
+ },
+ "appDescription": {
+ "message": "이더리움 계좌 관리",
+ "description": "The description of the application"
+ }
+}
diff --git a/app/images/coinbase logo.png b/app/images/coinbase logo.png
new file mode 100644
index 000000000..a23d7926d
--- /dev/null
+++ b/app/images/coinbase logo.png
Binary files differ
diff --git a/app/images/metamask-fox.svg b/app/images/metamask-fox.svg
new file mode 100644
index 000000000..f3c24f79e
--- /dev/null
+++ b/app/images/metamask-fox.svg
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns:ev="http://www.w3.org/2001/xml-events"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 318.6 318.6"
+ style="enable-background:new 0 0 318.6 318.6;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:#161616;stroke:#161616;}
+ .st1{fill:#E4761B;stroke:#E4761B;stroke-linecap:round;stroke-linejoin:round;}
+ .st2{fill:#763D16;stroke:#763D16;stroke-linecap:round;stroke-linejoin:round;}
+ .st3{fill:#F6851B;stroke:#F6851B;stroke-linecap:round;stroke-linejoin:round;}
+ .st4{fill:#E2761B;stroke:#E2761B;stroke-linecap:round;stroke-linejoin:round;}
+ .st5{fill:#CD6116;stroke:#CD6116;stroke-linecap:round;stroke-linejoin:round;}
+ .st6{fill:#C0AD9E;stroke:#C0AD9E;stroke-linecap:round;stroke-linejoin:round;}
+ .st7{fill:#D7C1B3;stroke:#D7C1B3;stroke-linecap:round;stroke-linejoin:round;}
+ .st8{fill:#E4751F;stroke:#E4751F;stroke-linecap:round;stroke-linejoin:round;}
+ .st9{fill:#233447;stroke:#233447;stroke-linecap:round;stroke-linejoin:round;}
+ .st10{fill:#161616;stroke:#161616;stroke-linecap:round;stroke-linejoin:round;}
+</style>
+<polygon class="st0" points="277.3,145.6 272.3,142 280.3,134.7 274.2,129.9 282.2,123.8 276.9,119.8 285.3,79 272.7,41.1
+ 191.6,71.4 124.1,71.4 43,41.1 30.4,79 38.9,119.8 33.5,123.8 41.5,129.9 35.4,134.7 43.4,142 38.4,145.6 49.9,159.1 32.5,213.3
+ 48.6,268.6 105.3,253 116.3,262 138.7,277.5 177,277.5 199.4,262 210.4,253 267.1,268.6 283.3,213.3 265.8,159.1 "/>
+<g>
+ <polygon class="st1" points="105.3,253 48.6,268.6 32.5,213.3 "/>
+ <polygon class="st1" points="283.3,213.3 267.1,268.6 210.4,253 "/>
+ <polygon class="st2" points="265.8,159.1 213.5,143.8 231.8,139 "/>
+ <polygon class="st2" points="49.9,159.1 84,139 102.2,143.8 "/>
+ <polygon class="st2" points="43.4,142 41.5,129.9 84,139 "/>
+ <polygon class="st2" points="272.3,142 231.8,139 274.2,129.9 "/>
+ <polygon class="st2" points="272.3,142 265.8,159.1 231.8,139 "/>
+ <polygon class="st2" points="43.4,142 84,139 49.9,159.1 "/>
+ <polygon class="st2" points="231.8,139 276.9,119.8 274.2,129.9 "/>
+ <polygon class="st2" points="84,139 41.5,129.9 38.9,119.8 "/>
+ <polygon class="st3" points="124.1,71.4 191.6,71.4 176.5,112.5 "/>
+ <polygon class="st3" points="176.5,112.5 139.2,112.5 124.1,71.4 "/>
+ <polygon class="st2" points="276.9,119.8 231.8,139 231,87.4 "/>
+ <polygon class="st2" points="102.2,143.8 84,139 84.7,87.4 "/>
+ <polygon class="st2" points="84.7,87.4 84,139 38.9,119.8 "/>
+ <polygon class="st2" points="231,87.4 231.8,139 213.5,143.8 "/>
+ <polygon class="st1" points="139.2,112.5 43,41.1 124.1,71.4 "/>
+ <polygon class="st4" points="272.7,41.1 176.5,112.5 191.6,71.4 "/>
+ <polygon class="st1" points="210.4,253 236.9,213.3 283.3,213.3 "/>
+ <polygon class="st1" points="32.5,213.3 78.9,213.3 105.3,253 "/>
+ <polygon class="st3" points="229.3,167.7 283.3,213.3 236.9,213.3 "/>
+ <polygon class="st3" points="86.4,167.7 32.5,213.3 49.9,159.1 "/>
+ <polygon class="st3" points="78.9,213.3 32.5,213.3 86.4,167.7 "/>
+ <polygon class="st3" points="229.3,167.7 265.8,159.1 283.3,213.3 "/>
+ <polygon class="st2" points="84.7,87.4 139.2,112.5 102.2,143.8 "/>
+ <polygon class="st2" points="213.5,143.8 176.5,112.5 231,87.4 "/>
+ <polygon class="st2" points="265.8,159.1 272.3,142 277.3,145.6 "/>
+ <polygon class="st2" points="49.9,159.1 38.4,145.6 43.4,142 "/>
+ <polygon class="st2" points="272.3,142 274.2,129.9 280.3,134.7 "/>
+ <polygon class="st2" points="43.4,142 35.4,134.7 41.5,129.9 "/>
+ <polygon class="st2" points="33.5,123.8 38.9,119.8 41.5,129.9 "/>
+ <polygon class="st2" points="282.2,123.8 274.2,129.9 276.9,119.8 "/>
+ <polygon class="st3" points="49.9,159.1 102.2,143.8 86.4,167.7 "/>
+ <polygon class="st3" points="265.8,159.1 229.3,167.7 213.5,143.8 "/>
+ <polygon class="st2" points="38.9,119.8 30.4,79 84.7,87.4 "/>
+ <polygon class="st2" points="231,87.4 285.3,79 276.9,119.8 "/>
+ <polygon class="st1" points="102.2,143.8 139.2,112.5 142.6,170.2 "/>
+ <polygon class="st1" points="213.5,143.8 229.3,167.7 173.1,170.2 "/>
+ <polygon class="st1" points="173.1,170.2 176.5,112.5 213.5,143.8 "/>
+ <polygon class="st1" points="142.6,170.2 86.4,167.7 102.2,143.8 "/>
+ <polygon class="st2" points="272.7,41.1 285.3,79 231,87.4 "/>
+ <polygon class="st2" points="43,41.1 139.2,112.5 84.7,87.4 "/>
+ <polygon class="st2" points="231,87.4 176.5,112.5 272.7,41.1 "/>
+ <polygon class="st2" points="84.7,87.4 30.4,79 43,41.1 "/>
+ <polygon class="st5" points="105.3,253 78.9,213.3 110,213.7 "/>
+ <polygon class="st5" points="210.4,253 205.7,213.7 236.9,213.3 "/>
+ <polygon class="st3" points="173.1,170.2 142.6,170.2 139.2,112.5 "/>
+ <polygon class="st3" points="139.2,112.5 176.5,112.5 173.1,170.2 "/>
+ <polygon class="st6" points="116.3,262 105.3,253 136.8,267.9 "/>
+ <polygon class="st6" points="178.9,267.9 210.4,253 199.4,262 "/>
+ <polygon class="st7" points="136.6,258.6 136.8,267.9 105.3,253 "/>
+ <polygon class="st7" points="179.2,258.6 210.4,253 178.9,267.9 "/>
+ <polygon class="st3" points="86.4,167.7 110,213.7 78.9,213.3 "/>
+ <polygon class="st3" points="236.9,213.3 205.7,213.7 229.3,167.7 "/>
+ <polygon class="st8" points="86.4,167.7 109.2,190.8 110,213.7 "/>
+ <polygon class="st8" points="229.3,167.7 205.7,213.7 206.6,190.8 "/>
+ <polygon class="st7" points="105.3,253 139.2,236.5 136.6,258.6 "/>
+ <polygon class="st7" points="210.4,253 179.2,258.6 176.5,236.5 "/>
+ <polygon class="st1" points="139.2,236.5 105.3,253 110,213.7 "/>
+ <polygon class="st1" points="176.5,236.5 205.7,213.7 210.4,253 "/>
+ <polygon class="st5" points="173.1,170.2 229.3,167.7 206.6,190.8 "/>
+ <polygon class="st5" points="109.2,190.8 86.4,167.7 142.6,170.2 "/>
+ <polygon class="st5" points="142.6,170.2 129.1,181.7 109.2,190.8 "/>
+ <polygon class="st5" points="206.6,190.8 186.6,181.7 173.1,170.2 "/>
+ <polygon class="st3" points="205.7,213.7 178.3,199.1 206.6,190.8 "/>
+ <polygon class="st3" points="110,213.7 109.2,190.8 137.4,199.1 "/>
+ <polygon class="st9" points="137.4,199.1 109.2,190.8 129.1,181.7 "/>
+ <polygon class="st9" points="178.3,199.1 186.6,181.7 206.6,190.8 "/>
+ <polygon class="st5" points="186.6,181.7 178.3,199.1 173.1,170.2 "/>
+ <polygon class="st5" points="129.1,181.7 142.6,170.2 137.4,199.1 "/>
+ <polygon class="st6" points="199.4,262 177,277.5 178.9,267.9 "/>
+ <polygon class="st6" points="136.8,267.9 138.7,277.5 116.3,262 "/>
+ <polygon class="st4" points="178.3,199.1 171.8,188.4 173.1,170.2 "/>
+ <polygon class="st8" points="137.4,199.1 142.6,170.2 143.9,188.4 "/>
+ <polygon class="st3" points="173.1,170.2 171.8,188.4 143.9,188.4 "/>
+ <polygon class="st3" points="143.9,188.4 142.6,170.2 173.1,170.2 "/>
+ <polygon class="st3" points="178.3,199.1 205.7,213.7 176.5,236.5 "/>
+ <polygon class="st3" points="139.2,236.5 110,213.7 137.4,199.1 "/>
+ <polygon class="st3" points="137.4,199.1 144,233.2 139.2,236.5 "/>
+ <polygon class="st3" points="176.5,236.5 171.7,233.2 178.3,199.1 "/>
+ <polygon class="st8" points="171.8,188.4 178.3,199.1 171.7,233.2 "/>
+ <polygon class="st8" points="143.9,188.4 144,233.2 137.4,199.1 "/>
+ <polygon class="st3" points="143.9,188.4 171.8,188.4 171.7,233.2 "/>
+ <polygon class="st3" points="171.7,233.2 144,233.2 143.9,188.4 "/>
+ <polygon class="st6" points="179.2,258.6 178.9,267.9 177,277.5 "/>
+ <polygon class="st6" points="138.7,277.5 136.8,267.9 136.6,258.6 "/>
+ <polygon class="st6" points="136.6,258.6 139,256.4 138.7,277.5 "/>
+ <polygon class="st6" points="177,277.5 176.7,256.4 179.2,258.6 "/>
+ <polygon class="st6" points="138.7,277.5 139,256.4 176.7,256.4 "/>
+ <polygon class="st6" points="176.7,256.4 177,277.5 138.7,277.5 "/>
+ <polygon class="st10" points="176.5,236.5 179.2,258.6 176.7,256.4 "/>
+ <polygon class="st10" points="139,256.4 136.6,258.6 139.2,236.5 "/>
+ <polygon class="st10" points="139.2,236.5 140.7,241.2 139,256.4 "/>
+ <polygon class="st10" points="176.7,256.4 175,241.2 176.5,236.5 "/>
+ <polygon class="st10" points="143.7,237.7 140.7,241.2 139.2,236.5 "/>
+ <polygon class="st10" points="176.5,236.5 175,241.2 172,237.7 "/>
+ <polygon class="st10" points="172,237.7 171.7,233.2 176.5,236.5 "/>
+ <polygon class="st10" points="139.2,236.5 144,233.2 143.7,237.7 "/>
+ <polygon class="st10" points="171.7,233.2 172,237.7 143.7,237.7 "/>
+ <polygon class="st10" points="143.7,237.7 144,233.2 171.7,233.2 "/>
+ <polygon class="st10" points="140.7,241.2 175,241.2 176.7,256.4 "/>
+ <polygon class="st10" points="176.7,256.4 139,256.4 140.7,241.2 "/>
+ <polygon class="st10" points="140.7,241.2 143.7,237.7 172,237.7 "/>
+ <polygon class="st10" points="172,237.7 175,241.2 140.7,241.2 "/>
+</g>
+</svg>
diff --git a/app/images/popout.svg b/app/images/popout.svg
new file mode 100644
index 000000000..760fe4379
--- /dev/null
+++ b/app/images/popout.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
+ <title>popout</title>
+ <desc>Created with Sketch.</desc>
+ <defs>
+ <polygon id="path-1" points="-0.00035 0 10.9999 0 10.9999 10.9997 -0.00035 10.9997"></polygon>
+ </defs>
+ <g id="MetaMascara-Mobile---structured-TOKEN" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(-327.000000, -96.000000)">
+ <g id="popout" transform="translate(327.000000, 96.000000)">
+ <g id="Group-3" transform="translate(11.000000, 0.000000)">
+ <mask id="mask-2" fill="white">
+ <use xlink:href="#path-1"></use>
+ </mask>
+ <g id="Clip-2"></g>
+ <path d="M10.9229,0.6177 C10.8209,0.3737 10.6269,0.1787 10.3819,0.0767 C10.2599,0.0267 10.1309,-0.0003 9.9999,-0.0003 L3.9999,-0.0003 C3.4479,-0.0003 2.9999,0.4477 2.9999,0.9997 C2.9999,1.5527 3.4479,1.9997 3.9999,1.9997 L7.5859,1.9997 L0.2929,9.2927 C-0.0981,9.6837 -0.0981,10.3167 0.2929,10.7067 C0.4879,10.9027 0.7439,10.9997 0.9999,10.9997 C1.2559,10.9997 1.5119,10.9027 1.7069,10.7067 L8.9999,3.4137 L8.9999,6.9997 C8.9999,7.5527 9.4479,7.9997 9.9999,7.9997 C10.5519,7.9997 10.9999,7.5527 10.9999,6.9997 L10.9999,0.9997 C10.9999,0.8697 10.9739,0.7407 10.9229,0.6177" id="Fill-1" fill="#4A4A4A" mask="url(#mask-2)"></path>
+ </g>
+ <path d="M19,10 C18.448,10 18,10.448 18,11 L18,19 C18,19.551 17.551,20 17,20 L3,20 C2.449,20 2,19.551 2,19 L2,5 C2,4.449 2.449,4 3,4 L11,4 C11.552,4 12,3.552 12,3 C12,2.448 11.552,2 11,2 L3,2 C1.346,2 0,3.346 0,5 L0,19 C0,20.654 1.346,22 3,22 L17,22 C18.654,22 20,20.654 20,19 L20,11 C20,10.448 19.552,10 19,10" id="Fill-4" fill="#4A4A4A"></path>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/images/shapeshift logo.png b/app/images/shapeshift logo.png
new file mode 100644
index 000000000..ac8faba5b
--- /dev/null
+++ b/app/images/shapeshift logo.png
Binary files differ
diff --git a/app/manifest.json b/app/manifest.json
index ff595c717..d65374330 100644
--- a/app/manifest.json
+++ b/app/manifest.json
@@ -1,7 +1,7 @@
{
"name": "MetaMask",
"short_name": "Metamask",
- "version": "4.0.4",
+ "version": "4.0.9",
"manifest_version": 2,
"author": "https://metamask.io",
"description": "Ethereum Browser Extension",
diff --git a/app/scripts/config.js b/app/scripts/config.js
index 1d4ff7c0d..74c5b576e 100644
--- a/app/scripts/config.js
+++ b/app/scripts/config.js
@@ -4,6 +4,15 @@ 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 = 'GULP_METAMASK_DEBUG'
module.exports = {
@@ -14,9 +23,22 @@ module.exports = {
kovan: KOVAN_RPC_URL,
rinkeby: RINKEBY_RPC_URL,
},
+ // Used for beta UI
+ 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,
+ },
}
diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js
index ffbbc73cc..2ed7c87b6 100644
--- a/app/scripts/contentscript.js
+++ b/app/scripts/contentscript.js
@@ -96,7 +96,7 @@ function logStreamDisconnectWarning (remoteLabel, err) {
}
function shouldInjectWeb3 () {
- return doctypeCheck() || suffixCheck()
+ return doctypeCheck() && suffixCheck() && documentElementCheck()
}
function doctypeCheck () {
@@ -104,7 +104,7 @@ function doctypeCheck () {
if (doctype) {
return doctype.name === 'html'
} else {
- return false
+ return true
}
}
@@ -121,6 +121,14 @@ function suffixCheck () {
return true
}
+function documentElementCheck () {
+ var documentElement = document.documentElement.nodeName
+ if (documentElement) {
+ return documentElement.toLowerCase() === 'html'
+ }
+ return true
+}
+
function redirectToPhishingWarning () {
console.log('MetaMask - redirecting to phishing warning')
window.location.href = 'https://metamask.io/phishing.html'
diff --git a/app/scripts/controllers/blacklist.js b/app/scripts/controllers/blacklist.js
index dd671943f..33c31dab9 100644
--- a/app/scripts/controllers/blacklist.js
+++ b/app/scripts/controllers/blacklist.js
@@ -57,3 +57,4 @@ class BlacklistController {
}
module.exports = BlacklistController
+
diff --git a/app/scripts/controllers/network.js b/app/scripts/controllers/network.js
index 045bfcc5d..617456cd7 100644
--- a/app/scripts/controllers/network.js
+++ b/app/scripts/controllers/network.js
@@ -1,18 +1,26 @@
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 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 RPC_ADDRESS_LIST = require('../config.js').network
-const DEFAULT_RPC = RPC_ADDRESS_LIST['rinkeby']
+const networkConfig = require('../config.js')
+const { OLD_UI_NETWORK_TYPE, DEFAULT_RPC } = networkConfig.enums
+const INFURA_PROVIDER_TYPES = ['ropsten', 'rinkeby', 'kovan', 'mainnet']
module.exports = class NetworkController extends EventEmitter {
constructor (config) {
super()
+
+ this._networkEndpointVersion = OLD_UI_NETWORK_TYPE
+ this._networkEndpoints = this.getNetworkEndpoints(OLD_UI_NETWORK_TYPE)
+ this._defaultRpc = this._networkEndpoints[DEFAULT_RPC]
+
config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider)
this.networkStore = new ObservableStore('loading')
this.providerStore = new ObservableStore(config.provider)
@@ -22,10 +30,32 @@ module.exports = class NetworkController extends EventEmitter {
this.on('networkDidChange', this.lookupNetwork)
}
+ async setNetworkEndpoints (version) {
+ if (version === this._networkEndpointVersion) {
+ return
+ }
+
+ this._networkEndpointVersion = version
+ this._networkEndpoints = this.getNetworkEndpoints(version)
+ this._defaultRpc = this._networkEndpoints[DEFAULT_RPC]
+ const { type } = this.getProviderConfig()
+
+ return this.setProviderType(type, true)
+ }
+
+ getNetworkEndpoints (version = OLD_UI_NETWORK_TYPE) {
+ return networkConfig[version]
+ }
+
initializeProvider (_providerParams) {
this._baseProviderParams = _providerParams
- const rpcUrl = this.getCurrentRpcAddress()
- this._configureStandardProvider({ rpcUrl })
+ const { type, rpcTarget } = this.providerStore.getState()
+ // map rpcTarget to rpcUrl
+ const opts = {
+ type,
+ rpcUrl: rpcTarget,
+ }
+ this._configureProvider(opts)
this._proxy.on('block', this._logBlock.bind(this))
this._proxy.on('error', this.verifyNetwork.bind(this))
this.ethQuery = new EthQuery(this._proxy)
@@ -76,14 +106,17 @@ module.exports = class NetworkController extends EventEmitter {
return this.getRpcAddressForType(provider.type)
}
- async setProviderType (type) {
+ async setProviderType (type, forceUpdate = false) {
assert(type !== 'rpc', `NetworkController.setProviderType - cannot connect by type "rpc"`)
// skip if type already matches
- if (type === this.getProviderConfig().type) return
+ if (type === this.getProviderConfig().type && !forceUpdate) {
+ return
+ }
+
const rpcTarget = this.getRpcAddressForType(type)
assert(rpcTarget, `NetworkController - unknown rpc address for type "${type}"`)
this.providerStore.updateState({ type, rpcTarget })
- this._switchNetwork({ rpcUrl: rpcTarget })
+ this._switchNetwork({ type })
}
getProviderConfig () {
@@ -91,22 +124,67 @@ module.exports = class NetworkController extends EventEmitter {
}
getRpcAddressForType (type, provider = this.getProviderConfig()) {
- if (RPC_ADDRESS_LIST[type]) return RPC_ADDRESS_LIST[type]
- return provider && provider.rpcTarget ? provider.rpcTarget : DEFAULT_RPC
+ if (this._networkEndpoints[type]) {
+ return this._networkEndpoints[type]
+ }
+
+ return provider && provider.rpcTarget ? provider.rpcTarget : this._defaultRpc
}
//
// Private
//
- _switchNetwork (providerParams) {
+ _switchNetwork (opts) {
this.setNetworkState('loading')
- this._configureStandardProvider(providerParams)
+ this._configureProvider(opts)
this.emit('networkDidChange')
}
- _configureStandardProvider (_providerParams) {
- const providerParams = extend(this._baseProviderParams, _providerParams)
+ _configureProvider (opts) {
+ // type-based rpc endpoints
+ const { type } = opts
+ if (type) {
+ // type-based infura rpc endpoints
+ const isInfura = INFURA_PROVIDER_TYPES.includes(type)
+ opts.rpcUrl = this.getRpcAddressForType(type)
+ if (isInfura) {
+ this._configureInfuraProvider(opts)
+ // other type-based rpc endpoints
+ } else {
+ this._configureStandardProvider(opts)
+ }
+ // url-based rpc endpoints
+ } else {
+ this._configureStandardProvider(opts)
+ }
+ }
+
+ _configureInfuraProvider (opts) {
+ log.info('_configureInfuraProvider', opts)
+ const infuraProvider = createInfuraProvider({
+ network: opts.type,
+ })
+ const infuraSubprovider = new SubproviderFromProvider(infuraProvider)
+ const providerParams = extend(this._baseProviderParams, {
+ rpcUrl: opts.rpcUrl,
+ engineParams: {
+ pollingInterval: 8000,
+ blockTrackerProvider: infuraProvider,
+ },
+ dataSubprovider: infuraSubprovider,
+ })
+ const provider = createMetamaskProvider(providerParams)
+ this._setProvider(provider)
+ }
+
+ _configureStandardProvider ({ rpcUrl }) {
+ const providerParams = extend(this._baseProviderParams, {
+ rpcUrl,
+ engineParams: {
+ pollingInterval: 8000,
+ },
+ })
const provider = createMetamaskProvider(providerParams)
this._setProvider(provider)
}
diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js
index de9006044..39d15fd83 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -36,22 +36,24 @@ class PreferencesController {
return this.store.getState().selectedAddress
}
- addToken (rawAddress, symbol, decimals) {
+ async addToken (rawAddress, symbol, decimals) {
const address = normalizeAddress(rawAddress)
const newEntry = { address, symbol, decimals }
const tokens = this.store.getState().tokens
- const previousIndex = tokens.find((token, index) => {
+ const previousEntry = tokens.find((token, index) => {
return token.address === address
})
+ const previousIndex = tokens.indexOf(previousEntry)
- if (previousIndex) {
+ if (previousEntry) {
tokens[previousIndex] = newEntry
} else {
tokens.push(newEntry)
}
this.store.updateState({ tokens })
+
return Promise.resolve(tokens)
}
diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js
new file mode 100644
index 000000000..4ae3810eb
--- /dev/null
+++ b/app/scripts/controllers/recent-blocks.js
@@ -0,0 +1,110 @@
+const ObservableStore = require('obs-store')
+const extend = require('xtend')
+const BN = require('ethereumjs-util').BN
+const EthQuery = require('eth-query')
+
+class RecentBlocksController {
+
+ constructor (opts = {}) {
+ const { blockTracker, provider } = opts
+ this.blockTracker = blockTracker
+ this.ethQuery = new EthQuery(provider)
+ this.historyLength = opts.historyLength || 40
+
+ const initState = extend({
+ recentBlocks: [],
+ }, opts.initState)
+ this.store = new ObservableStore(initState)
+
+ this.blockTracker.on('block', this.processBlock.bind(this))
+ this.backfill()
+ }
+
+ resetState () {
+ this.store.updateState({
+ recentBlocks: [],
+ })
+ }
+
+ processBlock (newBlock) {
+ const block = this.mapTransactionsToPrices(newBlock)
+
+ const state = this.store.getState()
+ state.recentBlocks.push(block)
+
+ while (state.recentBlocks.length > this.historyLength) {
+ state.recentBlocks.shift()
+ }
+
+ this.store.updateState(state)
+ }
+
+ backfillBlock (newBlock) {
+ const block = this.mapTransactionsToPrices(newBlock)
+
+ const state = this.store.getState()
+
+ if (state.recentBlocks.length < this.historyLength) {
+ state.recentBlocks.unshift(block)
+ }
+
+ this.store.updateState(state)
+ }
+
+ mapTransactionsToPrices (newBlock) {
+ const block = extend(newBlock, {
+ gasPrices: newBlock.transactions.map((tx) => {
+ return tx.gasPrice
+ }),
+ })
+ delete block.transactions
+ return block
+ }
+
+ async backfill() {
+ this.blockTracker.once('block', async (block) => {
+ let blockNum = block.number
+ let recentBlocks
+ let state = this.store.getState()
+ recentBlocks = state.recentBlocks
+
+ while (recentBlocks.length < this.historyLength) {
+ try {
+ let blockNumBn = new BN(blockNum.substr(2), 16)
+ const newNum = blockNumBn.subn(1).toString(10)
+ const newBlock = await this.getBlockByNumber(newNum)
+
+ if (newBlock) {
+ this.backfillBlock(newBlock)
+ blockNum = newBlock.number
+ }
+
+ state = this.store.getState()
+ recentBlocks = state.recentBlocks
+ } catch (e) {
+ log.error(e)
+ }
+ await this.wait()
+ }
+ })
+ }
+
+ async wait () {
+ return new Promise((resolve) => {
+ setTimeout(resolve, 100)
+ })
+ }
+
+ async getBlockByNumber (number) {
+ const bn = new BN(number)
+ return new Promise((resolve, reject) => {
+ this.ethQuery.getBlockByNumber('0x' + bn.toString(16), true, (err, block) => {
+ if (err) reject(err)
+ resolve(block)
+ })
+ })
+ }
+
+}
+
+module.exports = RecentBlocksController
diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js
index ce709bd28..290444bbb 100644
--- a/app/scripts/controllers/transactions.js
+++ b/app/scripts/controllers/transactions.js
@@ -32,6 +32,7 @@ module.exports = class TransactionController extends EventEmitter {
this.provider = opts.provider
this.blockTracker = opts.blockTracker
this.signEthTx = opts.signTransaction
+ this.getGasPrice = opts.getGasPrice
this.memStore = new ObservableStore({})
this.query = new EthQuery(this.provider)
@@ -59,7 +60,6 @@ module.exports = class TransactionController extends EventEmitter {
this.pendingTxTracker = new PendingTransactionTracker({
provider: this.provider,
nonceTracker: this.nonceTracker,
- retryTimePeriod: 86400000, // Retry 3500 blocks, or about 1 day.
publishTransaction: (rawTx) => this.query.sendRawTransaction(rawTx),
getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
getCompletedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager),
@@ -138,18 +138,19 @@ module.exports = class TransactionController extends EventEmitter {
async newUnapprovedTransaction (txParams) {
log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`)
- const txMeta = await this.addUnapprovedTransaction(txParams)
- this.emit('newUnapprovedTx', txMeta)
+ const initialTxMeta = await this.addUnapprovedTransaction(txParams)
// listen for tx completion (success, fail)
return new Promise((resolve, reject) => {
- this.txStateManager.once(`${txMeta.id}:finished`, (completedTx) => {
- switch (completedTx.status) {
+ this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => {
+ switch (finishedTxMeta.status) {
case 'submitted':
- return resolve(completedTx.hash)
+ return resolve(finishedTxMeta.hash)
case 'rejected':
return reject(new Error('MetaMask Tx Signature: User denied transaction signature.'))
+ case 'failed':
+ return reject(new Error(finishedTxMeta.err.message))
default:
- return reject(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(completedTx.txParams)}`))
+ return reject(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`))
}
})
})
@@ -165,11 +166,16 @@ module.exports = class TransactionController extends EventEmitter {
status: 'unapproved',
metamaskNetworkId: this.getNetwork(),
txParams: txParams,
+ loadingDefaults: true,
}
+ this.addTx(txMeta)
+ this.emit('newUnapprovedTx', txMeta)
// add default tx params
await this.addTxDefaults(txMeta)
+
+ txMeta.loadingDefaults = false
// save txMeta
- this.addTx(txMeta)
+ this.txStateManager.updateTx(txMeta)
return txMeta
}
@@ -177,13 +183,28 @@ module.exports = class TransactionController extends EventEmitter {
const txParams = txMeta.txParams
// ensure value
txMeta.gasPriceSpecified = Boolean(txParams.gasPrice)
- const gasPrice = txParams.gasPrice || await this.query.gasPrice()
+ txMeta.nonceSpecified = Boolean(txParams.nonce)
+ 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)
}
+ async retryTransaction (txId) {
+ this.txStateManager.setTxStatusUnapproved(txId)
+ const txMeta = this.txStateManager.getTx(txId)
+ txMeta.lastGasPrice = txMeta.txParams.gasPrice
+ this.txStateManager.updateTx(txMeta, 'retryTransaction: manual retry')
+ }
+
+ async updateTransaction (txMeta) {
+ this.txStateManager.updateTx(txMeta, 'confTx: user updated transaction')
+ }
+
async updateAndApproveTransaction (txMeta) {
this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction')
await this.approveTransaction(txMeta.id)
@@ -200,7 +221,12 @@ module.exports = class TransactionController extends EventEmitter {
// wait for a nonce
nonceLock = await this.nonceTracker.getNonceLock(fromAddress)
// add nonce to txParams
- txMeta.txParams.nonce = ethUtil.addHexPrefix(nonceLock.nextNonce.toString(16))
+ const nonce = txMeta.nonceSpecified ? txMeta.txParams.nonce : nonceLock.nextNonce
+ if (nonce > nonceLock.nextNonce) {
+ const message = `Specified nonce may not be larger than account's next valid nonce.`
+ throw new Error(message)
+ }
+ txMeta.txParams.nonce = ethUtil.addHexPrefix(nonce.toString(16))
// add nonce debugging information to txMeta
txMeta.nonceDetails = nonceLock.nonceDetails
this.txStateManager.updateTx(txMeta, 'transactions#approveTransaction')
diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js
index ce6642150..8c3dd8c71 100644
--- a/app/scripts/lib/account-tracker.js
+++ b/app/scripts/lib/account-tracker.js
@@ -117,8 +117,6 @@ class AccountTracker extends EventEmitter {
const query = this._query
async.parallel({
balance: query.getBalance.bind(query, address),
- nonce: query.getTransactionCount.bind(query, address),
- code: query.getCode.bind(query, address),
}, cb)
}
diff --git a/app/scripts/lib/pending-tx-tracker.js b/app/scripts/lib/pending-tx-tracker.js
index dc6e526fd..e8869e6b8 100644
--- a/app/scripts/lib/pending-tx-tracker.js
+++ b/app/scripts/lib/pending-tx-tracker.js
@@ -23,7 +23,6 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
this.query = new EthQuery(config.provider)
this.nonceTracker = config.nonceTracker
// default is one day
- this.retryTimePeriod = config.retryTimePeriod || 86400000
this.getPendingTransactions = config.getPendingTransactions
this.getCompletedTransactions = config.getCompletedTransactions
this.publishTransaction = config.publishTransaction
@@ -106,12 +105,6 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
this.emit('tx:block-update', txMeta, latestBlockNumber)
}
- if (Date.now() > txMeta.time + this.retryTimePeriod) {
- const hours = (this.retryTimePeriod / 3.6e+6).toFixed(1)
- const err = new Error(`Gave up submitting after ${hours} hours.`)
- return this.emit('tx:failed', txMeta.id, err)
- }
-
const firstRetryBlockNumber = txMeta.firstRetryBlockNumber || latestBlockNumber
const txBlockDistance = Number.parseInt(latestBlockNumber, 16) - Number.parseInt(firstRetryBlockNumber, 16)
@@ -185,7 +178,8 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
}
async _checkIfNonceIsTaken (txMeta) {
- const completed = this.getCompletedTransactions()
+ const address = txMeta.txParams.from
+ const completed = this.getCompletedTransactions(address)
const sameNonce = completed.filter((otherMeta) => {
return otherMeta.txParams.nonce === txMeta.txParams.nonce
})
diff --git a/app/scripts/lib/tx-gas-utils.js b/app/scripts/lib/tx-gas-utils.js
index 7e72ea71d..e80e0467e 100644
--- a/app/scripts/lib/tx-gas-utils.js
+++ b/app/scripts/lib/tx-gas-utils.js
@@ -4,6 +4,7 @@ const {
BnMultiplyByFraction,
bnToHex,
} = require('./util')
+const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send.
/*
tx-utils are utility methods for Transaction manager
@@ -22,7 +23,11 @@ module.exports = class txProvideUtil {
try {
estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit)
} catch (err) {
- if (err.message.includes('Transaction execution error.')) {
+ const simulationFailed = (
+ err.message.includes('Transaction execution error.') ||
+ err.message.includes('gas required exceeds allowance or always failing transaction')
+ )
+ if (simulationFailed) {
txMeta.simulationFails = true
return txMeta
}
@@ -33,14 +38,30 @@ module.exports = class txProvideUtil {
async estimateTxGas (txMeta, blockGasLimitHex) {
const txParams = txMeta.txParams
+
// check if gasLimit is already specified
txMeta.gasLimitSpecified = Boolean(txParams.gas)
- // if not, fallback to block gasLimit
- if (!txMeta.gasLimitSpecified) {
- const blockGasLimitBN = hexToBn(blockGasLimitHex)
- const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20)
- txParams.gas = bnToHex(saferGasLimitBN)
+
+ // if it is, use that value
+ if (txMeta.gasLimitSpecified) {
+ return txParams.gas
}
+
+ // if recipient has no code, gas is 21k max:
+ const recipient = txParams.to
+ const hasRecipient = Boolean(recipient)
+ const code = await this.query.getCode(recipient)
+ if (hasRecipient && (!code || code === '0x')) {
+ txParams.gas = SIMPLE_GAS_COST
+ txMeta.simpleSend = true // Prevents buffer addition
+ return SIMPLE_GAS_COST
+ }
+
+ // if not, fall back to block gasLimit
+ const blockGasLimitBN = hexToBn(blockGasLimitHex)
+ const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20)
+ txParams.gas = bnToHex(saferGasLimitBN)
+
// run tx
return await this.query.estimateGas(txParams)
}
@@ -51,7 +72,7 @@ module.exports = class txProvideUtil {
// if gasLimit was specified and doesnt OOG,
// use original specified amount
- if (txMeta.gasLimitSpecified) {
+ if (txMeta.gasLimitSpecified || txMeta.simpleSend) {
txMeta.estimatedGas = txParams.gas
return
}
@@ -77,8 +98,26 @@ module.exports = class txProvideUtil {
}
async validateTxParams (txParams) {
- if (('value' in txParams) && txParams.value.indexOf('-') === 0) {
- throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`)
+ 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.`)
+ }
+
+ if (value.includes('.')) {
+ throw new Error(`Invalid transaction value of ${txParams.value} number must be in wei`)
+ }
+ }
+ }
+ validateRecipient (txParams) {
+ if (txParams.to === '0x') {
+ if (txParams.data) {
+ delete txParams.to
+ } else {
+ throw new Error('Invalid recipient address')
+ }
}
+ return txParams
}
}
diff --git a/app/scripts/lib/tx-state-manager.js b/app/scripts/lib/tx-state-manager.js
index 0fd6bed4b..a8ef39891 100644
--- a/app/scripts/lib/tx-state-manager.js
+++ b/app/scripts/lib/tx-state-manager.js
@@ -187,6 +187,10 @@ module.exports = class TransactionStateManger extends EventEmitter {
this._setTxStatus(txId, 'rejected')
}
+ // should update the status of the tx to 'unapproved'.
+ setTxStatusUnapproved (txId) {
+ this._setTxStatus(txId, 'unapproved')
+ }
// should update the status of the tx to 'approved'.
setTxStatusApproved (txId) {
this._setTxStatus(txId, 'approved')
@@ -236,7 +240,7 @@ module.exports = class TransactionStateManger extends EventEmitter {
txMeta.status = status
this.emit(`${txMeta.id}:${status}`, txId)
this.emit(`tx:status-update`, txId, status)
- if (status === 'submitted' || status === 'rejected') {
+ if (['submitted', 'rejected', 'failed'].includes(status)) {
this.emit(`${txMeta.id}:finished`, txMeta)
}
this.updateTx(txMeta, `txStateManager: setting status to ${status}`)
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 018eb2c76..962516af6 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -5,7 +5,6 @@ const Dnode = require('dnode')
const ObservableStore = require('obs-store')
const asStream = require('obs-store/lib/asStream')
const AccountTracker = require('./lib/account-tracker')
-const EthQuery = require('eth-query')
const RpcEngine = require('json-rpc-engine')
const debounce = require('debounce')
const createEngineStream = require('json-rpc-middleware-stream/engineStream')
@@ -23,6 +22,7 @@ const ShapeShiftController = require('./controllers/shapeshift')
const AddressBookController = require('./controllers/address-book')
const InfuraController = require('./controllers/infura')
const BlacklistController = require('./controllers/blacklist')
+const RecentBlocksController = require('./controllers/recent-blocks')
const MessageManager = require('./lib/message-manager')
const PersonalMessageManager = require('./lib/personal-message-manager')
const TypedMessageManager = require('./lib/typed-message-manager')
@@ -34,13 +34,15 @@ const accountImporter = require('./account-import-strategies')
const getBuyEthUrl = require('./lib/buy-eth-url')
const Mutex = require('await-semaphore').Mutex
const version = require('../manifest.json').version
+const BN = require('ethereumjs-util').BN
+const GWEI_BN = new BN('1000000000')
+const percentile = require('percentile')
module.exports = class MetamaskController extends EventEmitter {
constructor (opts) {
super()
-
this.sendUpdate = debounce(this.privateSendUpdate.bind(this), 200)
this.opts = opts
@@ -91,8 +93,11 @@ module.exports = class MetamaskController extends EventEmitter {
this.provider = this.initializeProvider()
this.blockTracker = this.provider._blockTracker
- // eth data query tools
- this.ethQuery = new EthQuery(this.provider)
+ this.recentBlocksController = new RecentBlocksController({
+ blockTracker: this.blockTracker,
+ provider: this.provider,
+ })
+
// account tracker watches balances, nonces, and any code at their address.
this.accountTracker = new AccountTracker({
provider: this.provider,
@@ -133,7 +138,7 @@ module.exports = class MetamaskController extends EventEmitter {
signTransaction: this.keyringController.signTransaction.bind(this.keyringController),
provider: this.provider,
blockTracker: this.blockTracker,
- ethQuery: this.ethQuery,
+ getGasPrice: this.getGasPrice.bind(this),
})
this.txController.on('newUnapprovedTx', opts.showUnapprovedTx.bind(opts))
@@ -196,25 +201,30 @@ module.exports = class MetamaskController extends EventEmitter {
this.blacklistController.store.subscribe((state) => {
this.store.updateState({ BlacklistController: state })
})
+ this.recentBlocksController.store.subscribe((state) => {
+ this.store.updateState({ RecentBlocks: state })
+ })
this.infuraController.store.subscribe((state) => {
this.store.updateState({ InfuraController: state })
})
// manual mem state subscriptions
- this.networkController.store.subscribe(this.sendUpdate.bind(this))
- this.accountTracker.store.subscribe(this.sendUpdate.bind(this))
- this.txController.memStore.subscribe(this.sendUpdate.bind(this))
- this.balancesController.store.subscribe(this.sendUpdate.bind(this))
- this.messageManager.memStore.subscribe(this.sendUpdate.bind(this))
- this.personalMessageManager.memStore.subscribe(this.sendUpdate.bind(this))
- this.typedMessageManager.memStore.subscribe(this.sendUpdate.bind(this))
- this.keyringController.memStore.subscribe(this.sendUpdate.bind(this))
- this.preferencesController.store.subscribe(this.sendUpdate.bind(this))
- this.addressBookController.store.subscribe(this.sendUpdate.bind(this))
- this.currencyController.store.subscribe(this.sendUpdate.bind(this))
- this.noticeController.memStore.subscribe(this.sendUpdate.bind(this))
- this.shapeshiftController.store.subscribe(this.sendUpdate.bind(this))
- this.infuraController.store.subscribe(this.sendUpdate.bind(this))
+ const sendUpdate = this.sendUpdate.bind(this)
+ this.networkController.store.subscribe(sendUpdate)
+ this.accountTracker.store.subscribe(sendUpdate)
+ this.txController.memStore.subscribe(sendUpdate)
+ this.balancesController.store.subscribe(sendUpdate)
+ this.messageManager.memStore.subscribe(sendUpdate)
+ this.personalMessageManager.memStore.subscribe(sendUpdate)
+ this.typedMessageManager.memStore.subscribe(sendUpdate)
+ this.keyringController.memStore.subscribe(sendUpdate)
+ this.preferencesController.store.subscribe(sendUpdate)
+ this.recentBlocksController.store.subscribe(sendUpdate)
+ this.addressBookController.store.subscribe(sendUpdate)
+ this.currencyController.store.subscribe(sendUpdate)
+ this.noticeController.memStore.subscribe(sendUpdate)
+ this.shapeshiftController.store.subscribe(sendUpdate)
+ this.infuraController.store.subscribe(sendUpdate)
}
//
@@ -298,6 +308,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.currencyController.store.getState(),
this.noticeController.memStore.getState(),
this.infuraController.store.getState(),
+ this.recentBlocksController.store.getState(),
// config manager
this.configManager.getConfig(),
this.shapeshiftController.store.getState(),
@@ -342,6 +353,7 @@ module.exports = class MetamaskController extends EventEmitter {
submitPassword: nodeify(keyringController.submitPassword, keyringController),
// network management
+ setNetworkEndpoints: nodeify(networkController.setNetworkEndpoints, networkController),
setProviderType: nodeify(networkController.setProviderType, networkController),
setCustomRpc: nodeify(this.setCustomRpc, this),
@@ -365,7 +377,9 @@ module.exports = class MetamaskController extends EventEmitter {
// txController
cancelTransaction: nodeify(txController.cancelTransaction, txController),
+ updateTransaction: nodeify(txController.updateTransaction, txController),
updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController),
+ retryTransaction: nodeify(this.retryTransaction, this),
// messageManager
signMessage: nodeify(this.signMessage, this),
@@ -475,6 +489,33 @@ module.exports = class MetamaskController extends EventEmitter {
this.emit('update', this.getState())
}
+ getGasPrice () {
+ const { recentBlocksController } = this
+ const { recentBlocks } = recentBlocksController.store.getState()
+
+ // Return 1 gwei if no blocks have been observed:
+ if (recentBlocks.length === 0) {
+ return '0x' + GWEI_BN.toString(16)
+ }
+
+ const lowestPrices = recentBlocks.map((block) => {
+ if (!block.gasPrices || block.gasPrices.length < 1) {
+ return GWEI_BN
+ }
+ return block.gasPrices
+ .map(hexPrefix => hexPrefix.substr(2))
+ .map(hex => new BN(hex, 16))
+ .sort((a, b) => {
+ return a.gt(b) ? 1 : -1
+ })[0]
+ })
+ .map(number => number.div(GWEI_BN).toNumber())
+
+ const percentileNum = percentile(50, lowestPrices)
+ const percentileNumBn = new BN(percentileNum)
+ return '0x' + percentileNumBn.mul(GWEI_BN).toString(16)
+ }
+
//
// Vault Management
//
@@ -504,10 +545,15 @@ module.exports = class MetamaskController extends EventEmitter {
async createNewVaultAndRestore (password, seed) {
const release = await this.createVaultMutex.acquire()
- const vault = await this.keyringController.createNewVaultAndRestore(password, seed)
- this.selectFirstIdentity(vault)
- release()
- return vault
+ try {
+ const vault = await this.keyringController.createNewVaultAndRestore(password, seed)
+ this.selectFirstIdentity(vault)
+ release()
+ return vault
+ } catch (err) {
+ release()
+ throw err
+ }
}
selectFirstIdentity (vault) {
@@ -576,6 +622,14 @@ module.exports = class MetamaskController extends EventEmitter {
//
// Identity Management
//
+ //
+
+ async retryTransaction (txId, cb) {
+ await this.txController.retryTransaction(txId)
+ const state = await this.getState()
+ return state
+ }
+
newUnsignedMessage (msgParams, cb) {
const msgId = this.messageManager.addUnapprovedMessage(msgParams)
diff --git a/app/scripts/notice-controller.js b/app/scripts/notice-controller.js
index db2b8c4f4..14a63eae7 100644
--- a/app/scripts/notice-controller.js
+++ b/app/scripts/notice-controller.js
@@ -77,7 +77,7 @@ module.exports = class NoticeController extends EventEmitter {
return uniqBy(oldNotices.concat(newNotices), 'id')
}
- _filterNotices(notices) {
+ _filterNotices (notices) {
return notices.filter((newNotice) => {
if ('version' in newNotice) {
const satisfied = semver.satisfies(this.version, newNotice.version)
diff --git a/app/scripts/popup.js b/app/scripts/popup.js
index d0952af6a..1a2e425dc 100644
--- a/app/scripts/popup.js
+++ b/app/scripts/popup.js
@@ -26,8 +26,17 @@ const container = document.getElementById('app-content')
startPopup({ container, connectionStream }, (err, store) => {
if (err) return displayCriticalError(err)
- let betaUIState = store.getState().metamask.featureFlags.betaUI
- let css = betaUIState ? NewMetaMaskUiCss() : OldMetaMaskUiCss()
+ // Code commented out until we begin auto adding users to NewUI
+ // const { isMascara, identities = {}, featureFlags = {} } = store.getState().metamask
+ // const firstTime = Object.keys(identities).length === 0
+ const { isMascara, featureFlags = {} } = store.getState().metamask
+ let betaUIState = featureFlags.betaUI
+
+ // Code commented out until we begin auto adding users to NewUI
+ // const useBetaCss = isMascara || firstTime || betaUIState
+ const useBetaCss = isMascara || betaUIState
+
+ let css = useBetaCss ? NewMetaMaskUiCss() : OldMetaMaskUiCss()
let deleteInjectedCss = injectCss(css)
let newBetaUIState