1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-26 12:29:06 +01:00

Merge pull request #6484 from MetaMask/develop

Update master branch with develop (v6.4.0)
This commit is contained in:
Dan Finlay 2019-04-24 12:25:39 -07:00 committed by GitHub
commit 87d5be9081
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
233 changed files with 2003 additions and 649 deletions

View File

@ -2,6 +2,32 @@
## Current Develop Branch ## Current Develop Branch
## 6.4.0 Wed Apr 17 2019
- [#6445](https://github.com/MetaMask/metamask-extension/pull/6445): * Move send to pages/
- [#6470](https://github.com/MetaMask/metamask-extension/pull/6470): update publishing.md with dev diagram
- [#6403](https://github.com/MetaMask/metamask-extension/pull/6403): Update to eth-method-registry@1.2.0
- [#6468](https://github.com/MetaMask/metamask-extension/pull/6468): Fix switcher height when Custom RPC is selected or loading
- [#6459](https://github.com/MetaMask/metamask-extension/pull/6459): feature: add Goerli support
- [#6444](https://github.com/MetaMask/metamask-extension/pull/6444): Fixes #6321 & #6421 - Add Localhost 8545 for network dropdown names
- [#6454](https://github.com/MetaMask/metamask-extension/pull/6454): Bump eth-contract-metadata
- [#6448](https://github.com/MetaMask/metamask-extension/pull/6448): Remove unneeded array cloning in getSendToAccounts selector
- [#6056](https://github.com/MetaMask/metamask-extension/pull/6056): repeated getSelectedAddress() func send.selectors.js removed
- [#6422](https://github.com/MetaMask/metamask-extension/pull/6422): Added Chrome limited site access solution doc
- [#6424](https://github.com/MetaMask/metamask-extension/pull/6424): feature: switch token pricing to CoinGecko API
- [#6428](https://github.com/MetaMask/metamask-extension/pull/6428): Don't inject web3 on sharefile.com
- [#6417](https://github.com/MetaMask/metamask-extension/pull/6417): Metrics updates
- [#6420](https://github.com/MetaMask/metamask-extension/pull/6420): Fix links to MetamaskInpageProvider in porting_to_new_environment.md
- [#6362](https://github.com/MetaMask/metamask-extension/pull/6362): Remove broken image walkthrough from metamaskbot comment
- [#6401](https://github.com/MetaMask/metamask-extension/pull/6401): metamask-controller - use improved provider-as-middleware utility
- [#6406](https://github.com/MetaMask/metamask-extension/pull/6406): remove user actions controller
- [#6399](https://github.com/MetaMask/metamask-extension/pull/6399): doc - publishing - typo fix
- [#6396](https://github.com/MetaMask/metamask-extension/pull/6396): pin eth-contract-metadata to last commit hash
- [#6397](https://github.com/MetaMask/metamask-extension/pull/6397): Change coinbase to wyre
- [#6395](https://github.com/MetaMask/metamask-extension/pull/6395): bump ledger and trezor keyring
- [#6389](https://github.com/MetaMask/metamask-extension/pull/6389): Fix display of gas chart on Ethereum networks
- [#6382](https://github.com/MetaMask/metamask-extension/pull/6382): Remove NoticeController
## 6.3.2 Mon Apr 8 2019 ## 6.3.2 Mon Apr 8 2019
- [#6389](https://github.com/MetaMask/metamask-extension/pull/6389): Fix display of gas chart on ethereum networks - [#6389](https://github.com/MetaMask/metamask-extension/pull/6389): Fix display of gas chart on ethereum networks

View File

@ -139,6 +139,9 @@
"approved": { "approved": {
"message": "Approved" "message": "Approved"
}, },
"asset": {
"message": "Asset"
},
"attemptingConnect": { "attemptingConnect": {
"message": "Attempting to connect to blockchain." "message": "Attempting to connect to blockchain."
}, },
@ -308,6 +311,12 @@
"connectingToRinkeby": { "connectingToRinkeby": {
"message": "Connecting to Rinkeby Test Network" "message": "Connecting to Rinkeby Test Network"
}, },
"connectingToLocalhost": {
"message": "Connecting to Localhost 8545"
},
"connectingToGoerli": {
"message": "Connecting to Goerli Test Network"
},
"connectingToUnknown": { "connectingToUnknown": {
"message": "Connecting to Unknown Network" "message": "Connecting to Unknown Network"
}, },
@ -1226,6 +1235,9 @@
"ropsten": { "ropsten": {
"message": "Ropsten Test Network" "message": "Ropsten Test Network"
}, },
"goerli": {
"message": "Goerli Test Network"
},
"rpc": { "rpc": {
"message": "Custom RPC" "message": "Custom RPC"
}, },
@ -1342,6 +1354,9 @@
"selectAnAccountHelp": { "selectAnAccountHelp": {
"message": "Select the account to view in MetaMask" "message": "Select the account to view in MetaMask"
}, },
"selectAnAsset": {
"message": "Select an Asset"
},
"selectAHigherGasFee": { "selectAHigherGasFee": {
"message": "Select a higher gas fee to accelerate the processing of your transaction.*" "message": "Select a higher gas fee to accelerate the processing of your transaction.*"
}, },

View File

@ -1,7 +1,7 @@
{ {
"name": "__MSG_appName__", "name": "__MSG_appName__",
"short_name": "__MSG_appName__", "short_name": "__MSG_appName__",
"version": "6.3.2", "version": "6.4.0",
"manifest_version": 2, "manifest_version": 2,
"author": "https://metamask.io", "author": "https://metamask.io",
"description": "__MSG_appDescription__", "description": "__MSG_appDescription__",

View File

@ -275,6 +275,7 @@ function blacklistedDomainCheck () {
'harbourair.com', 'harbourair.com',
'ani.gamer.com.tw', 'ani.gamer.com.tw',
'blueskybooking.com', 'blueskybooking.com',
'sharefile.com',
] ]
const currentUrl = window.location.href const currentUrl = window.location.href
let currentRegex let currentRegex

View File

@ -49,6 +49,10 @@ function createNetworkAndChainIdMiddleware ({ network }) {
netId = '42' netId = '42'
chainId = '0x2a' chainId = '0x2a'
break break
case 'goerli':
netId = '5'
chainId = '0x05'
break
default: default:
throw new Error(`createInfuraClient - unknown network "${network}"`) throw new Error(`createInfuraClient - unknown network "${network}"`)
} }

View File

@ -3,16 +3,19 @@ const RINKEBY = 'rinkeby'
const KOVAN = 'kovan' const KOVAN = 'kovan'
const MAINNET = 'mainnet' const MAINNET = 'mainnet'
const LOCALHOST = 'localhost' const LOCALHOST = 'localhost'
const GOERLI = 'goerli'
const MAINNET_CODE = 1 const MAINNET_CODE = 1
const ROPSTEN_CODE = 3 const ROPSTEN_CODE = 3
const RINKEYBY_CODE = 4 const RINKEYBY_CODE = 4
const KOVAN_CODE = 42 const KOVAN_CODE = 42
const GOERLI_CODE = 5
const ROPSTEN_DISPLAY_NAME = 'Ropsten' const ROPSTEN_DISPLAY_NAME = 'Ropsten'
const RINKEBY_DISPLAY_NAME = 'Rinkeby' const RINKEBY_DISPLAY_NAME = 'Rinkeby'
const KOVAN_DISPLAY_NAME = 'Kovan' const KOVAN_DISPLAY_NAME = 'Kovan'
const MAINNET_DISPLAY_NAME = 'Main Ethereum Network' const MAINNET_DISPLAY_NAME = 'Main Ethereum Network'
const GOERLI_DISPLAY_NAME = 'Goerli'
module.exports = { module.exports = {
ROPSTEN, ROPSTEN,
@ -20,12 +23,15 @@ module.exports = {
KOVAN, KOVAN,
MAINNET, MAINNET,
LOCALHOST, LOCALHOST,
GOERLI,
MAINNET_CODE, MAINNET_CODE,
ROPSTEN_CODE, ROPSTEN_CODE,
RINKEYBY_CODE, RINKEYBY_CODE,
KOVAN_CODE, KOVAN_CODE,
GOERLI_CODE,
ROPSTEN_DISPLAY_NAME, ROPSTEN_DISPLAY_NAME,
RINKEBY_DISPLAY_NAME, RINKEBY_DISPLAY_NAME,
KOVAN_DISPLAY_NAME, KOVAN_DISPLAY_NAME,
MAINNET_DISPLAY_NAME, MAINNET_DISPLAY_NAME,
GOERLI_DISPLAY_NAME,
} }

View File

@ -20,8 +20,9 @@ const {
KOVAN, KOVAN,
MAINNET, MAINNET,
LOCALHOST, LOCALHOST,
GOERLI,
} = require('./enums') } = require('./enums')
const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET] const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET, GOERLI]
const env = process.env.METAMASK_ENV const env = process.env.METAMASK_ENV
const METAMASK_DEBUG = process.env.METAMASK_DEBUG const METAMASK_DEBUG = process.env.METAMASK_DEBUG
@ -140,10 +141,10 @@ module.exports = class NetworkController extends EventEmitter {
this.providerConfig = providerConfig this.providerConfig = providerConfig
} }
async setProviderType (type) { async setProviderType (type, rpcTarget = '', ticker = 'ETH', nickname = '') {
assert.notEqual(type, 'rpc', `NetworkController - cannot call "setProviderType" with type 'rpc'. use "setRpcTarget"`) assert.notEqual(type, 'rpc', `NetworkController - cannot call "setProviderType" with type 'rpc'. use "setRpcTarget"`)
assert(INFURA_PROVIDER_TYPES.includes(type) || type === LOCALHOST, `NetworkController - Unknown rpc type "${type}"`) assert(INFURA_PROVIDER_TYPES.includes(type) || type === LOCALHOST, `NetworkController - Unknown rpc type "${type}"`)
const providerConfig = { type } const providerConfig = { type, rpcTarget, ticker, nickname }
this.providerConfig = providerConfig this.providerConfig = providerConfig
} }

View File

@ -3,13 +3,16 @@ const {
RINKEBY, RINKEBY,
KOVAN, KOVAN,
MAINNET, MAINNET,
GOERLI,
ROPSTEN_CODE, ROPSTEN_CODE,
RINKEYBY_CODE, RINKEYBY_CODE,
KOVAN_CODE, KOVAN_CODE,
GOERLI_CODE,
ROPSTEN_DISPLAY_NAME, ROPSTEN_DISPLAY_NAME,
RINKEBY_DISPLAY_NAME, RINKEBY_DISPLAY_NAME,
KOVAN_DISPLAY_NAME, KOVAN_DISPLAY_NAME,
MAINNET_DISPLAY_NAME, MAINNET_DISPLAY_NAME,
GOERLI_DISPLAY_NAME,
} = require('./enums') } = require('./enums')
const networkToNameMap = { const networkToNameMap = {
@ -17,9 +20,11 @@ const networkToNameMap = {
[RINKEBY]: RINKEBY_DISPLAY_NAME, [RINKEBY]: RINKEBY_DISPLAY_NAME,
[KOVAN]: KOVAN_DISPLAY_NAME, [KOVAN]: KOVAN_DISPLAY_NAME,
[MAINNET]: MAINNET_DISPLAY_NAME, [MAINNET]: MAINNET_DISPLAY_NAME,
[GOERLI]: GOERLI_DISPLAY_NAME,
[ROPSTEN_CODE]: ROPSTEN_DISPLAY_NAME, [ROPSTEN_CODE]: ROPSTEN_DISPLAY_NAME,
[RINKEYBY_CODE]: RINKEBY_DISPLAY_NAME, [RINKEYBY_CODE]: RINKEBY_DISPLAY_NAME,
[KOVAN_CODE]: KOVAN_DISPLAY_NAME, [KOVAN_CODE]: KOVAN_DISPLAY_NAME,
[GOERLI_CODE]: GOERLI_DISPLAY_NAME,
} }
const getNetworkDisplayName = key => networkToNameMap[key] const getNetworkDisplayName = key => networkToNameMap[key]

View File

@ -8,8 +8,9 @@ const {
RINKEBY, RINKEBY,
KOVAN, KOVAN,
MAINNET, MAINNET,
GOERLI,
} = require('./network/enums') } = require('./network/enums')
const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET] const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET, GOERLI]
class RecentBlocksController { class RecentBlocksController {

View File

@ -28,16 +28,16 @@ class TokenRatesController {
async updateExchangeRates () { async updateExchangeRates () {
if (!this.isActive) { return } if (!this.isActive) { return }
const contractExchangeRates = {} const contractExchangeRates = {}
const nativeCurrency = this.currency ? this.currency.getState().nativeCurrency.toUpperCase() : 'ETH' const nativeCurrency = this.currency ? this.currency.getState().nativeCurrency.toLowerCase() : 'eth'
const pairs = this._tokens.map(token => `pairs[]=${token.address}/${nativeCurrency}`) const pairs = this._tokens.map(token => token.address).join(',')
const query = pairs.join('&') const query = `contract_addresses=${pairs}&vs_currencies=${nativeCurrency}`
if (this._tokens.length > 0) { if (this._tokens.length > 0) {
try { try {
const response = await fetch(`https://exchanges.balanc3.net/pie?${query}&autoConversion=false`) const response = await fetch(`https://api.coingecko.com/api/v3/simple/token_price/ethereum?${query}`)
const { prices = [] } = await response.json() const prices = await response.json()
prices.forEach(({ pair, price }) => { this._tokens.forEach(token => {
const address = pair.split('/')[0] const price = prices[token.address.toLowerCase()]
contractExchangeRates[normalizeAddress(address)] = typeof price === 'number' ? price : 0 contractExchangeRates[normalizeAddress(token.address)] = price ? price[nativeCurrency] : 0
}) })
} catch (error) { } catch (error) {
log.warn(`MetaMask - TokenRatesController exchange rate fetch failed.`, error) log.warn(`MetaMask - TokenRatesController exchange rate fetch failed.`, error)

View File

@ -218,6 +218,12 @@ inpageProvider.publicConfigStore.subscribe(function (state) {
web3.eth.defaultAccount = state.selectedAddress web3.eth.defaultAccount = state.selectedAddress
}) })
inpageProvider.publicConfigStore.subscribe(function (state) {
if (state.onboardingcomplete) {
window.postMessage('onboardingcomplete', '*')
}
})
// need to make sure we aren't affected by overlapping namespaces // need to make sure we aren't affected by overlapping namespaces
// and that we dont affect the app with our namespace // and that we dont affect the app with our namespace
// mostly a fix for web3's BigNumber if AMD's "define" is defined... // mostly a fix for web3's BigNumber if AMD's "define" is defined...

View File

@ -28,6 +28,8 @@ function getBuyEthUrl ({ network, amount, address, service }) {
return 'https://www.rinkeby.io/' return 'https://www.rinkeby.io/'
case 'kovan-faucet': case 'kovan-faucet':
return 'https://github.com/kovan-testnet/faucet' return 'https://github.com/kovan-testnet/faucet'
case 'goerli-faucet':
return 'https://goerli-faucet.slock.it/'
} }
throw new Error(`Unknown cryptocurrency exchange or faucet: "${service}"`) throw new Error(`Unknown cryptocurrency exchange or faucet: "${service}"`)
} }
@ -42,6 +44,8 @@ function getDefaultServiceForNetwork (network) {
return 'rinkeby-faucet' return 'rinkeby-faucet'
case '42': case '42':
return 'kovan-faucet' return 'kovan-faucet'
case '5':
return 'goerli-faucet'
} }
throw new Error(`No default cryptocurrency exchange or faucet for networkId: "${network}"`) throw new Error(`No default cryptocurrency exchange or faucet for networkId: "${network}"`)
} }

View File

@ -320,6 +320,7 @@ module.exports = class MetamaskController extends EventEmitter {
const result = { const result = {
selectedAddress: memState.isUnlocked ? memState.selectedAddress : undefined, selectedAddress: memState.isUnlocked ? memState.selectedAddress : undefined,
networkVersion: memState.network, networkVersion: memState.network,
onboardingcomplete: memState.completedOnboarding,
} }
return result return result
} }

View File

@ -230,7 +230,8 @@
"kovan": "ok", "kovan": "ok",
"mainnet": "ok", "mainnet": "ok",
"rinkeby": "ok", "rinkeby": "ok",
"ropsten": "ok" "ropsten": "ok",
"goerli": "ok"
}, },
"lostAccounts": [] "lostAccounts": []
}, },

View File

@ -704,7 +704,8 @@
"mainnet": "ok", "mainnet": "ok",
"ropsten": "ok", "ropsten": "ok",
"kovan": "ok", "kovan": "ok",
"rinkeby": "ok" "rinkeby": "ok",
"goerli": "ok"
}, },
"shapeShiftTxList": [], "shapeShiftTxList": [],
"lostAccounts": [] "lostAccounts": []

View File

@ -0,0 +1,5 @@
# Google Chrome/Brave Limited Site Access for Extensions
Problem: MetaMask doesn't work with limited site access enabled under Chrome's extensions.
Solution: In addition to the site you wish to whitelist, you must add 'api.infura.io' as another domain, so the MetaMask extension is authorized to make RPC calls to Infura.

View File

@ -10,7 +10,7 @@ The `metamask-background` describes the file at `app/scripts/background.js`, whi
When a new site is visited, the WebExtension creates a new `ContentScript` in that page's context, which can be seen at `app/scripts/contentscript.js`. This script represents a per-page setup process, which creates the per-page `web3` api, connects it to the background script via the Port API (wrapped in a [stream abstraction](https://github.com/substack/stream-handbook)), and injected into the DOM before anything loads. When a new site is visited, the WebExtension creates a new `ContentScript` in that page's context, which can be seen at `app/scripts/contentscript.js`. This script represents a per-page setup process, which creates the per-page `web3` api, connects it to the background script via the Port API (wrapped in a [stream abstraction](https://github.com/substack/stream-handbook)), and injected into the DOM before anything loads.
The most confusing part about porting MetaMask to a new platform is the way we provide the Web3 API over a series of streams between contexts. Once you understand how we create the [InpageProvider](../app/scripts/lib/inpage-provider.js) in the [inpage.js script](../app/scripts/inpage.js), you will be able to understand how the [port-stream](../app/scripts/lib/port-stream.js) is just a thin wrapper around the [postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage), and a similar stream API can be wrapped around any communication channel to communicate with the `MetaMaskController` via its `setupUntrustedCommunication(stream, domain)` method. The most confusing part about porting MetaMask to a new platform is the way we provide the Web3 API over a series of streams between contexts. Once you understand how we create the [MetamaskInpageProvider](https://github.com/MetaMask/metamask-inpage-provider/blob/master/index.js) in the [inpage.js script](../app/scripts/inpage.js), you will be able to understand how the [port-stream](../app/scripts/lib/port-stream.js) is just a thin wrapper around the [postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage), and a similar stream API can be wrapped around any communication channel to communicate with the `MetaMaskController` via its `setupUntrustedCommunication(stream, domain)` method.
### The MetaMask Controller ### The MetaMask Controller
@ -89,7 +89,7 @@ MetaMask has two kinds of [duplex stream APIs](https://github.com/substack/strea
If you are making a MetaMask-powered browser for a new platform, one of the trickiest tasks will be injecting the Web3 API into websites that are visited. On WebExtensions, we actually have to pipe data through a total of three JS contexts just to let sites talk to our background process (site -> contentscript -> background). If you are making a MetaMask-powered browser for a new platform, one of the trickiest tasks will be injecting the Web3 API into websites that are visited. On WebExtensions, we actually have to pipe data through a total of three JS contexts just to let sites talk to our background process (site -> contentscript -> background).
To see how we do that, you can refer to the [inpage script](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/inpage.js) that we inject into every website. There you can see it creates a multiplex stream to the background, and uses it to initialize what we call the [inpage-provider](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/lib/inpage-provider.js), which you can see stubs a few methods out, but mostly just passes calls to `sendAsync` through the stream it's passed! That's really all the magic that's needed to create a web3-like API in a remote context, once you have a stream to MetaMask available. To see how we do that, you can refer to the [inpage script](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/inpage.js) that we inject into every website. There you can see it creates a multiplex stream to the background, and uses it to initialize what we call the [MetamaskInpageProvider](https://github.com/MetaMask/metamask-inpage-provider/blob/master/index.js), which you can see stubs a few methods out, but mostly just passes calls to `sendAsync` through the stream it's passed! That's really all the magic that's needed to create a web3-like API in a remote context, once you have a stream to MetaMask available.
In `inpage.js` you can see we create a `PortStream`, that's just a class we use to wrap WebExtension ports as streams, so we can reuse our favorite stream abstraction over the more irregular API surface of the WebExtension. In a new platform, you will probably need to construct this stream differently. The key is that you need to construct a stream that talks from the site context to the background. Once you have that set up, it works like magic! In `inpage.js` you can see we create a `PortStream`, that's just a class we use to wrap WebExtension ports as streams, so we can reuse our favorite stream abstraction over the more irregular API surface of the WebExtension. In a new platform, you will probably need to construct this stream differently. The key is that you need to construct a stream that talks from the site context to the background. Once you have that set up, it works like magic!

View File

@ -2,6 +2,13 @@
When publishing a new version of MetaMask, we follow this procedure: When publishing a new version of MetaMask, we follow this procedure:
## Overview
The below diagram outlines our process for design, development, and release. Building MetaMask is a community affair, and many steps of the process invite participation from external contributors as indicated. All QA, code review, and release of new versions is done by members of the core MetaMask team.
<img width="664" alt="mm-dev-process" src="https://user-images.githubusercontent.com/1016190/56308059-36906000-60fb-11e9-8e61-6655bca0c54f.png">
## Preparation ## Preparation
We try to ensure certain criteria are met before deploying: We try to ensure certain criteria are met before deploying:

103
package-lock.json generated
View File

@ -9759,8 +9759,8 @@
} }
}, },
"eth-contract-metadata": { "eth-contract-metadata": {
"version": "github:MetaMask/eth-contract-metadata#92e7d1442c7585bfd24e50a0fda78df11dedadfe", "version": "github:MetaMask/eth-contract-metadata#41a14e8004bdd37eaba5af5f2bb1fc4f4ff7063f",
"from": "github:MetaMask/eth-contract-metadata#92e7d1442c7585bfd24e50a0fda78df11dedadfe" "from": "github:MetaMask/eth-contract-metadata#41a14e8004bdd37eaba5af5f2bb1fc4f4ff7063f"
}, },
"eth-ens-namehash": { "eth-ens-namehash": {
"version": "2.0.8", "version": "2.0.8",
@ -9806,6 +9806,31 @@
"requires": { "requires": {
"bn.js": "^4.11.8", "bn.js": "^4.11.8",
"ethereumjs-util": "^6.0.0" "ethereumjs-util": "^6.0.0"
},
"dependencies": {
"ethereumjs-util": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.1.0.tgz",
"integrity": "sha512-URESKMFbDeJxnAxPppnk2fN6Y3BIatn9fwn76Lm8bQlt+s52TpG8dN9M66MLPuRAiAOIqL3dfwqWJf0sd0fL0Q==",
"requires": {
"bn.js": "^4.11.0",
"create-hash": "^1.1.2",
"ethjs-util": "0.1.6",
"keccak": "^1.0.2",
"rlp": "^2.0.0",
"safe-buffer": "^5.1.1",
"secp256k1": "^3.0.1"
}
}
}
},
"ethjs-util": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz",
"integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==",
"requires": {
"is-hex-prefixed": "1.0.0",
"strip-hex-prefix": "1.0.0"
} }
} }
} }
@ -9814,8 +9839,7 @@
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799", "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": { "requires": {
"bn.js": "^4.11.8", "bn.js": "^4.11.8"
"ethereumjs-util": "^6.0.0"
} }
}, },
"ethereumjs-util": { "ethereumjs-util": {
@ -9896,6 +9920,31 @@
"requires": { "requires": {
"bn.js": "^4.11.8", "bn.js": "^4.11.8",
"ethereumjs-util": "^6.0.0" "ethereumjs-util": "^6.0.0"
},
"dependencies": {
"ethereumjs-util": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.1.0.tgz",
"integrity": "sha512-URESKMFbDeJxnAxPppnk2fN6Y3BIatn9fwn76Lm8bQlt+s52TpG8dN9M66MLPuRAiAOIqL3dfwqWJf0sd0fL0Q==",
"requires": {
"bn.js": "^4.11.0",
"create-hash": "^1.1.2",
"ethjs-util": "0.1.6",
"keccak": "^1.0.2",
"rlp": "^2.0.0",
"safe-buffer": "^5.1.1",
"secp256k1": "^3.0.1"
}
}
}
},
"ethjs-util": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz",
"integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==",
"requires": {
"is-hex-prefixed": "1.0.0",
"strip-hex-prefix": "1.0.0"
} }
} }
} }
@ -9904,8 +9953,7 @@
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215", "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": { "requires": {
"bn.js": "^4.11.8", "bn.js": "^4.11.8"
"ethereumjs-util": "^6.0.0"
} }
}, },
"ethereumjs-util": { "ethereumjs-util": {
@ -10076,6 +10124,33 @@
"requires": { "requires": {
"bn.js": "^4.11.8", "bn.js": "^4.11.8",
"ethereumjs-util": "^6.0.0" "ethereumjs-util": "^6.0.0"
},
"dependencies": {
"ethereumjs-util": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.1.0.tgz",
"integrity": "sha512-URESKMFbDeJxnAxPppnk2fN6Y3BIatn9fwn76Lm8bQlt+s52TpG8dN9M66MLPuRAiAOIqL3dfwqWJf0sd0fL0Q==",
"dev": true,
"requires": {
"bn.js": "^4.11.0",
"create-hash": "^1.1.2",
"ethjs-util": "0.1.6",
"keccak": "^1.0.2",
"rlp": "^2.0.0",
"safe-buffer": "^5.1.1",
"secp256k1": "^3.0.1"
}
}
}
},
"ethjs-util": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz",
"integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==",
"dev": true,
"requires": {
"is-hex-prefixed": "1.0.0",
"strip-hex-prefix": "1.0.0"
} }
} }
} }
@ -10084,14 +10159,14 @@
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799", "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": { "requires": {
"bn.js": "^4.11.8", "bn.js": "^4.11.8"
"ethereumjs-util": "^6.0.0"
} }
}, },
"ethereumjs-util": { "ethereumjs-util": {
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz",
"integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==",
"dev": true,
"requires": { "requires": {
"bn.js": "^4.11.0", "bn.js": "^4.11.0",
"create-hash": "^1.1.2", "create-hash": "^1.1.2",
@ -10192,8 +10267,7 @@
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215", "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": { "requires": {
"bn.js": "^4.11.8", "bn.js": "^4.11.8"
"ethereumjs-util": "^6.0.0"
} }
}, },
"ethereumjs-util": { "ethereumjs-util": {
@ -10324,9 +10398,9 @@
} }
}, },
"eth-method-registry": { "eth-method-registry": {
"version": "1.0.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/eth-method-registry/-/eth-method-registry-1.0.0.tgz", "resolved": "https://registry.npmjs.org/eth-method-registry/-/eth-method-registry-1.2.0.tgz",
"integrity": "sha1-8Ij3Wdad6f3BK3EEm83GiKMoOLY=", "integrity": "sha512-m+nphH4kOxz5KTvQ+BeIKVggxAul1sp4Ev09lfxRXIEHM1t/6NQEtaErL5ddTDFXXFVtTiW8uC9edTVUTnBZNg==",
"requires": { "requires": {
"ethjs": "^0.3.0" "ethjs": "^0.3.0"
}, },
@ -10502,8 +10576,7 @@
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215", "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": { "requires": {
"bn.js": "^4.11.8", "bn.js": "^4.11.8"
"ethereumjs-util": "^6.0.0"
} }
}, },
"ethereumjs-util": { "ethereumjs-util": {

View File

@ -17,6 +17,8 @@
"test:integration:build": "gulp build:scss", "test:integration:build": "gulp build:scss",
"test:e2e:drizzle:beta": "SELENIUM_BROWSER=chrome test/e2e/beta/run-drizzle.sh", "test:e2e:drizzle:beta": "SELENIUM_BROWSER=chrome test/e2e/beta/run-drizzle.sh",
"test:e2e:chrome": "SELENIUM_BROWSER=chrome test/e2e/beta/run-all.sh", "test:e2e:chrome": "SELENIUM_BROWSER=chrome test/e2e/beta/run-all.sh",
"test:web3:chrome": "SELENIUM_BROWSER=chrome test/e2e/beta/run-web3.sh",
"test:web3:firefox": "SELENIUM_BROWSER=firefox test/e2e/beta/run-web3.sh",
"test:e2e:firefox": "SELENIUM_BROWSER=firefox test/e2e/beta/run-all.sh", "test:e2e:firefox": "SELENIUM_BROWSER=firefox test/e2e/beta/run-all.sh",
"test:screens": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:screens:run'", "test:screens": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:screens:run'",
"test:screens:run": "node test/screens/new-ui.js", "test:screens:run": "node test/screens/new-ui.js",
@ -82,14 +84,14 @@
"ensnare": "^1.0.0", "ensnare": "^1.0.0",
"eth-bin-to-ops": "^1.0.1", "eth-bin-to-ops": "^1.0.1",
"eth-block-tracker": "^4.1.0", "eth-block-tracker": "^4.1.0",
"eth-contract-metadata": "github:MetaMask/eth-contract-metadata#92e7d1442c7585bfd24e50a0fda78df11dedadfe", "eth-contract-metadata": "github:MetaMask/eth-contract-metadata#41a14e8004bdd37eaba5af5f2bb1fc4f4ff7063f",
"eth-ens-namehash": "^2.0.8", "eth-ens-namehash": "^2.0.8",
"eth-hd-keyring": "^1.2.2", "eth-hd-keyring": "^1.2.2",
"eth-json-rpc-filters": "^3.0.1", "eth-json-rpc-filters": "^3.0.1",
"eth-json-rpc-infura": "^3.0.0", "eth-json-rpc-infura": "^3.0.0",
"eth-keyring-controller": "^3.3.1", "eth-keyring-controller": "^3.3.1",
"eth-ledger-bridge-keyring": "^0.2.0", "eth-ledger-bridge-keyring": "^0.2.0",
"eth-method-registry": "^1.0.0", "eth-method-registry": "^1.2.0",
"eth-phishing-detect": "^1.1.4", "eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2", "eth-query": "^2.1.2",
"eth-sig-util": "^2.0.2", "eth-sig-util": "^2.0.2",

View File

@ -54,7 +54,8 @@
"mainnet": "degraded", "mainnet": "degraded",
"ropsten": "ok", "ropsten": "ok",
"kovan": "ok", "kovan": "ok",
"rinkeby": "ok" "rinkeby": "ok",
"goerli": "ok"
} }
}, },
"BlacklistController": { "BlacklistController": {

View File

@ -123,7 +123,7 @@ describe('MetaMask', function () {
}) })
it('clicks the "I agree" option on the metametrics opt-in screen', async () => { it('clicks the "I agree" option on the metametrics opt-in screen', async () => {
const optOutButton = await findElement(driver, By.css('.btn-confirm')) const optOutButton = await findElement(driver, By.css('.btn-primary'))
optOutButton.click() optOutButton.click()
await delay(largeDelayMs) await delay(largeDelayMs)
}) })

9
test/e2e/beta/run-web3.sh Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -e
set -u
set -o pipefail
export PATH="$PATH:./node_modules/.bin"
shell-parallel -s 'static-server test/web3 --port 8080' -x 'sleep 5 && mocha test/e2e/beta/web3.spec'

365
test/e2e/beta/web3.spec.js Normal file
View File

@ -0,0 +1,365 @@
const path = require('path')
const assert = require('assert')
const webdriver = require('selenium-webdriver')
const { By } = webdriver
const {
delay,
buildChromeWebDriver,
buildFirefoxWebdriver,
installWebExt,
getExtensionIdChrome,
getExtensionIdFirefox,
} = require('../func')
const {
checkBrowserForConsoleErrors,
closeAllWindowHandlesExcept,
findElement,
findElements,
openNewPage,
switchToWindowWithTitle,
verboseReportOnFailure,
waitUntilXWindowHandles,
} = require('./helpers')
const fetchMockResponses = require('./fetch-mocks.js')
describe('Using MetaMask with an existing account', function () {
let extensionId
let driver
const testSeedPhrase = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress'
const regularDelayMs = 1000
const largeDelayMs = regularDelayMs * 2
const button = async (x) => {
const buttoncheck = x
await buttoncheck.click()
await delay(largeDelayMs)
const [results] = await findElements(driver, By.css('#results'))
const resulttext = await results.getText()
var parsedData = JSON.parse(resulttext)
return (parsedData)
}
this.timeout(0)
this.bail(true)
before(async function () {
let extensionUrl
switch (process.env.SELENIUM_BROWSER) {
case 'chrome': {
const extensionPath = path.resolve('dist/chrome')
driver = buildChromeWebDriver(extensionPath)
extensionId = await getExtensionIdChrome(driver)
await delay(regularDelayMs)
extensionUrl = `chrome-extension://${extensionId}/home.html`
break
}
case 'firefox': {
const extensionPath = path.resolve('dist/firefox')
driver = buildFirefoxWebdriver()
await installWebExt(driver, extensionPath)
await delay(regularDelayMs)
extensionId = await getExtensionIdFirefox(driver)
extensionUrl = `moz-extension://${extensionId}/home.html`
break
}
}
// Depending on the state of the application built into the above directory (extPath) and the value of
// METAMASK_DEBUG we will see different post-install behaviour and possibly some extra windows. Here we
// are closing any extraneous windows to reset us to a single window before continuing.
const [tab1] = await driver.getAllWindowHandles()
await closeAllWindowHandlesExcept(driver, [tab1])
await driver.switchTo().window(tab1)
await driver.get(extensionUrl)
})
beforeEach(async function () {
await driver.executeScript(
'window.origFetch = window.fetch.bind(window);' +
'window.fetch = ' +
'(...args) => { ' +
'if (args[0] === "https://ethgasstation.info/json/ethgasAPI.json") { return ' +
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasBasic + '\')) }); } else if ' +
'(args[0] === "https://ethgasstation.info/json/predictTable.json") { return ' +
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasPredictTable + '\')) }); } else if ' +
'(args[0].match(/chromeextensionmm/)) { return ' +
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.metametrics + '\')) }); } else if ' +
'(args[0] === "https://dev.blockscale.net/api/gasexpress.json") { return ' +
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.gasExpress + '\')) }); } ' +
'return window.origFetch(...args); };' +
'function cancelInfuraRequest(requestDetails) {' +
'console.log("Canceling: " + requestDetails.url);' +
'return {' +
'cancel: true' +
'};' +
' }' +
'window.chrome && window.chrome.webRequest && window.chrome.webRequest.onBeforeRequest.addListener(' +
'cancelInfuraRequest,' +
'{urls: ["https://*.infura.io/*"]},' +
'["blocking"]' +
');'
)
})
afterEach(async function () {
if (process.env.SELENIUM_BROWSER === 'chrome') {
const errors = await checkBrowserForConsoleErrors(driver)
if (errors.length) {
const errorReports = errors.map(err => err.message)
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
console.error(new Error(errorMessage))
}
}
if (this.currentTest.state === 'failed') {
await verboseReportOnFailure(driver, this.currentTest)
}
})
after(async function () {
await driver.quit()
})
describe('First time flow starting from an existing seed phrase', () => {
it('clicks the continue button on the welcome screen', async () => {
await findElement(driver, By.css('.welcome-page__header'))
const welcomeScreenBtn = await findElement(driver, By.css('.first-time-flow__button'))
welcomeScreenBtn.click()
await delay(largeDelayMs)
})
it('clicks the "Import Wallet" option', async () => {
const customRpcButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Import Wallet')]`))
customRpcButton.click()
await delay(largeDelayMs)
})
it('clicks the "No thanks" option on the metametrics opt-in screen', async () => {
const optOutButton = await findElement(driver, By.css('.btn-default'))
optOutButton.click()
await delay(largeDelayMs)
})
it('imports a seed phrase', async () => {
const [seedTextArea] = await findElements(driver, By.css('textarea.first-time-flow__textarea'))
await seedTextArea.sendKeys(testSeedPhrase)
await delay(regularDelayMs)
const [password] = await findElements(driver, By.id('password'))
await password.sendKeys('correct horse battery staple')
const [confirmPassword] = await findElements(driver, By.id('confirm-password'))
confirmPassword.sendKeys('correct horse battery staple')
const tosCheckBox = await findElement(driver, By.css('.first-time-flow__checkbox'))
await tosCheckBox.click()
const [importButton] = await findElements(driver, By.xpath(`//button[contains(text(), 'Import')]`))
await importButton.click()
await delay(regularDelayMs)
})
it('clicks through the success screen', async () => {
await findElement(driver, By.xpath(`//div[contains(text(), 'Congratulations')]`))
const doneButton = await findElement(driver, By.css('button.first-time-flow__button'))
await doneButton.click()
await delay(regularDelayMs)
})
})
describe('opens dapp', () => {
it('switches to mainnet', async () => {
const networkDropdown = await findElement(driver, By.css('.network-name'))
await networkDropdown.click()
await delay(regularDelayMs)
const [mainnet] = await findElements(driver, By.xpath(`//span[contains(text(), 'Main Ethereum Network')]`))
await mainnet.click()
await delay(largeDelayMs * 2)
})
it('', async () => {
await openNewPage(driver, 'http://127.0.0.1:8080/')
await delay(regularDelayMs)
await waitUntilXWindowHandles(driver, 3)
const windowHandles = await driver.getAllWindowHandles()
const extension = windowHandles[0]
const popup = await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles)
const dapp = windowHandles.find(handle => handle !== extension && handle !== popup)
await delay(regularDelayMs)
const approveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`))
await approveButton.click()
await driver.switchTo().window(dapp)
await delay(regularDelayMs)
})
})
describe('testing web3 methods', async () => {
it('testing hexa methods', async () => {
var List = await driver.findElements(By.className('hexaNumberMethods'))
for (let i = 0; i < List.length; i++) {
try {
var parsedData = await button(List[i])
console.log(parsedData)
var result = parseInt(parsedData.result, 16)
assert.equal((typeof result === 'number'), true)
await delay(regularDelayMs)
} catch (err) {
console.log(err)
assert(false)
}
}
})
it('testing booleanMethods', async () => {
var List = await driver.findElements(By.className('booleanMethods'))
for (let i = 0; i < List.length; i++) {
try {
var parsedData = await button(List[i])
console.log(parsedData)
var result = parsedData.result
assert.equal(result, false)
await delay(regularDelayMs)
} catch (err) {
console.log(err)
assert(false)
}
}
})
it('testing transactionMethods', async () => {
var List = await driver.findElements(By.className('transactionMethods'))
for (let i = 0; i < List.length; i++) {
try {
var parsedData = await button(List[i])
console.log(parsedData.result.blockHash)
var result = []
result.push(parseInt(parsedData.result.blockHash, 16))
result.push(parseInt(parsedData.result.blockNumber, 16))
result.push(parseInt(parsedData.result.gas, 16))
result.push(parseInt(parsedData.result.gasPrice, 16))
result.push(parseInt(parsedData.result.hash, 16))
result.push(parseInt(parsedData.result.input, 16))
result.push(parseInt(parsedData.result.nonce, 16))
result.push(parseInt(parsedData.result.r, 16))
result.push(parseInt(parsedData.result.s, 16))
result.push(parseInt(parsedData.result.v, 16))
result.push(parseInt(parsedData.result.to, 16))
result.push(parseInt(parsedData.result.value, 16))
result.forEach((value) => {
assert.equal((typeof value === 'number'), true)
})
} catch (err) {
console.log(err)
assert(false)
}
}
})
it('testing blockMethods', async () => {
var List = await driver.findElements(By.className('blockMethods'))
for (let i = 0; i < List.length; i++) {
try {
var parsedData = await button(List[i])
console.log(JSON.stringify(parsedData) + i)
console.log(parsedData.result.parentHash)
var result = parseInt(parsedData.result.parentHash, 16)
assert.equal((typeof result === 'number'), true)
await delay(regularDelayMs)
} catch (err) {
console.log(err)
assert(false)
}
}
})
it('testing methods', async () => {
var List = await driver.findElements(By.className('methods'))
var parsedData
var result
for (let i = 0; i < List.length; i++) {
try {
if (i === 2) {
parsedData = await button(List[i])
console.log(parsedData.result.blockHash)
result = parseInt(parsedData.result.blockHash, 16)
assert.equal((typeof result === 'number' || (result === 0)), true)
await delay(regularDelayMs)
} else {
parsedData = await button(List[i])
console.log(parsedData.result)
result = parseInt(parsedData.result, 16)
assert.equal((typeof result === 'number' || (result === 0)), true)
await delay(regularDelayMs)
}
} catch (err) {
console.log(err)
assert(false)
}
}
})
})
})

View File

@ -44,7 +44,7 @@ async function runConfirmSigRequestsTest (assert, done) {
let confirmSigRowValue = await queryAsync($, '.request-signature__row-value') let confirmSigRowValue = await queryAsync($, '.request-signature__row-value')
assert.equal(confirmSigRowValue[0].textContent, '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0') assert.equal(confirmSigRowValue[0].textContent, '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0')
let confirmSigSignButton = await queryAsync($, 'button.btn-primary.btn--large') let confirmSigSignButton = await queryAsync($, 'button.btn-secondary.btn--large')
confirmSigSignButton[0].click() confirmSigSignButton[0].click()
await timeout(1000) await timeout(1000)
confirmSigHeadline = await queryAsync($, '.request-signature__headline') confirmSigHeadline = await queryAsync($, '.request-signature__headline')
@ -53,7 +53,7 @@ async function runConfirmSigRequestsTest (assert, done) {
confirmSigRowValue = await queryAsync($, '.request-signature__row-value') confirmSigRowValue = await queryAsync($, '.request-signature__row-value')
assert.ok(confirmSigRowValue[0].textContent.match(/^#\sTerms\sof\sUse/)) assert.ok(confirmSigRowValue[0].textContent.match(/^#\sTerms\sof\sUse/))
confirmSigSignButton = await queryAsync($, 'button.btn-primary.btn--large') confirmSigSignButton = await queryAsync($, 'button.btn-secondary.btn--large')
confirmSigSignButton[0].click() confirmSigSignButton[0].click()
await timeout(1000) await timeout(1000)
confirmSigHeadline = await queryAsync($, '.request-signature__headline') confirmSigHeadline = await queryAsync($, '.request-signature__headline')
@ -63,7 +63,7 @@ async function runConfirmSigRequestsTest (assert, done) {
assert.equal(confirmSigRowValue[0].textContent, 'Hi, Alice!') assert.equal(confirmSigRowValue[0].textContent, 'Hi, Alice!')
assert.equal(confirmSigRowValue[1].textContent, '1337') assert.equal(confirmSigRowValue[1].textContent, '1337')
confirmSigSignButton = await queryAsync($, 'button.btn-primary.btn--large') confirmSigSignButton = await queryAsync($, 'button.btn-secondary.btn--large')
confirmSigSignButton[0].click() confirmSigSignButton[0].click()
await timeout(2000) await timeout(2000)

View File

@ -43,16 +43,13 @@ async function runSendFlowTest (assert, done) {
selectState.val('send new ui') selectState.val('send new ui')
reactTriggerChange(selectState[0]) reactTriggerChange(selectState[0])
const sendScreenButton = await queryAsync($, 'button.btn-primary.transaction-view-balance__button') const sendScreenButton = await queryAsync($, 'button.btn-secondary.transaction-view-balance__button')
assert.ok(sendScreenButton[1], 'send screen button present') assert.ok(sendScreenButton[1], 'send screen button present')
sendScreenButton[1].click() sendScreenButton[1].click()
const sendTitle = await queryAsync($, '.page-container__title') const sendTitle = await queryAsync($, '.page-container__title')
assert.equal(sendTitle[0].textContent, 'Send ETH', 'Send screen title is correct') assert.equal(sendTitle[0].textContent, 'Send ETH', 'Send screen title is correct')
const sendCopy = await queryAsync($, '.page-container__subtitle')
assert.equal(sendCopy[0].textContent, 'Only send ETH to an Ethereum address.', 'Send screen has copy')
const sendFromField = await queryAsync($, '.send-v2__form-field') const sendFromField = await queryAsync($, '.send-v2__form-field')
assert.ok(sendFromField[0], 'send screen has a from field') assert.ok(sendFromField[0], 'send screen has a from field')
@ -72,7 +69,7 @@ async function runSendFlowTest (assert, done) {
const sendToAccountAddress = sendToFieldInput.val() const sendToAccountAddress = sendToFieldInput.val()
assert.equal(sendToAccountAddress, '0x2f8D4a878cFA04A6E60D46362f5644DeAb66572D', 'send to dropdown selects the correct address') assert.equal(sendToAccountAddress, '0x2f8D4a878cFA04A6E60D46362f5644DeAb66572D', 'send to dropdown selects the correct address')
const sendAmountField = await queryAsync($, '.send-v2__form-row:eq(2)') const sendAmountField = await queryAsync($, '.send-v2__form-row:eq(3)')
sendAmountField.find('.unit-input')[0].click() sendAmountField.find('.unit-input')[0].click()
const sendAmountFieldInput = await findAsync(sendAmountField, '.unit-input__input') const sendAmountFieldInput = await findAsync(sendAmountField, '.unit-input__input')
@ -88,7 +85,7 @@ async function runSendFlowTest (assert, done) {
errorMessage = $('.send-v2__error') errorMessage = $('.send-v2__error')
assert.equal(errorMessage.length, 0, 'send should stop rendering amount error message after amount is corrected') assert.equal(errorMessage.length, 0, 'send should stop rendering amount error message after amount is corrected')
const sendButton = await queryAsync($, 'button.btn-primary.btn--large.page-container__footer-button') const sendButton = await queryAsync($, 'button.btn-secondary.btn--large.page-container__footer-button')
assert.equal(sendButton[0].textContent, 'Next', 'next button rendered') assert.equal(sendButton[0].textContent, 'Next', 'next button rendered')
sendButton[0].click() sendButton[0].click()
await timeout() await timeout()
@ -115,14 +112,14 @@ async function runSendFlowTest (assert, done) {
sendToFieldInputInEdit[0].focus() sendToFieldInputInEdit[0].focus()
sendToFieldInputInEdit.val('0xd85a4b6a394794842887b8284293d69163007bbb') sendToFieldInputInEdit.val('0xd85a4b6a394794842887b8284293d69163007bbb')
const sendAmountFieldInEdit = await queryAsync($, '.send-v2__form-row:eq(2)') const sendAmountFieldInEdit = await queryAsync($, '.send-v2__form-row:eq(3)')
sendAmountFieldInEdit.find('.unit-input')[0].click() sendAmountFieldInEdit.find('.unit-input')[0].click()
const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('.unit-input__input') const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('.unit-input__input')
sendAmountFieldInputInEdit.val('1.0') sendAmountFieldInputInEdit.val('1.0')
reactTriggerChange(sendAmountFieldInputInEdit[0]) reactTriggerChange(sendAmountFieldInputInEdit[0])
const sendButtonInEdit = await queryAsync($, '.btn-primary.btn--large.page-container__footer-button') const sendButtonInEdit = await queryAsync($, '.btn-secondary.btn--large.page-container__footer-button')
assert.equal(sendButtonInEdit[0].textContent, 'Next', 'next button in edit rendered') assert.equal(sendButtonInEdit[0].textContent, 'Next', 'next button in edit rendered')
selectState.val('send new ui') selectState.val('send new ui')

View File

@ -4,7 +4,7 @@ const InfuraController = require('../../../../app/scripts/controllers/infura')
describe('infura-controller', function () { describe('infura-controller', function () {
let infuraController, sandbox, networkStatus let infuraController, sandbox, networkStatus
const response = {'mainnet': 'degraded', 'ropsten': 'ok', 'kovan': 'ok', 'rinkeby': 'down'} const response = {'mainnet': 'degraded', 'ropsten': 'ok', 'kovan': 'ok', 'rinkeby': 'down', 'goerli': 'ok'}
before(async function () { before(async function () {
infuraController = new InfuraController() infuraController = new InfuraController()
@ -58,5 +58,15 @@ describe('infura-controller', function () {
assert.equal(networkStatus.rinkeby, 'down') assert.equal(networkStatus.rinkeby, 'down')
}) })
}) })
describe('Goerli', function () {
it('should have Goerli', function () {
assert.equal(Object.keys(networkStatus)[4], 'goerli')
})
it('should have a value for Goerli status', function () {
assert.equal(networkStatus.goerli, 'ok')
})
})
}) })
}) })

View File

@ -92,6 +92,9 @@ describe('Network utils', () => {
}, { }, {
input: 'mainnet', input: 'mainnet',
expected: 'Main Ethereum Network', expected: 'Main Ethereum Network',
}, {
input: 'goerli',
expected: 'Goerli',
}, },
] ]

View File

@ -4,6 +4,7 @@ const {
ROPSTEN_CODE, ROPSTEN_CODE,
RINKEYBY_CODE, RINKEYBY_CODE,
KOVAN_CODE, KOVAN_CODE,
GOERLI_CODE,
} = require('../../../../../app/scripts/controllers/network/enums') } = require('../../../../../app/scripts/controllers/network/enums')
const KeyringController = require('eth-keyring-controller') const KeyringController = require('eth-keyring-controller')
@ -27,14 +28,14 @@ describe('Recipient Blacklist Checker', function () {
describe('#checkAccount', function () { describe('#checkAccount', function () {
it('does not fail on test networks', function () { it('does not fail on test networks', function () {
let callCount = 0 let callCount = 0
const networks = [ROPSTEN_CODE, RINKEYBY_CODE, KOVAN_CODE] const networks = [ROPSTEN_CODE, RINKEYBY_CODE, KOVAN_CODE, GOERLI_CODE]
for (const networkId in networks) { for (const networkId in networks) {
publicAccounts.forEach((account) => { publicAccounts.forEach((account) => {
recipientBlackListChecker.checkAccount(networkId, account) recipientBlackListChecker.checkAccount(networkId, account)
callCount++ callCount++
}) })
} }
assert.equal(callCount, 30) assert.equal(callCount, 40)
}) })
it('fails on mainnet', function () { it('fails on mainnet', function () {

View File

@ -23,4 +23,8 @@ describe('Etherscan Network Prefix', () => {
assert.equal(etherscanNetworkPrefix(42), 'kovan.') assert.equal(etherscanNetworkPrefix(42), 'kovan.')
}) })
it('returs goerli as prefix for networkId of 5', () => {
assert.equal(etherscanNetworkPrefix(5), 'goerli.')
})
}) })

105
test/web3/index.html Normal file
View File

@ -0,0 +1,105 @@
<html>
<head>
<title>Web3 Test Dapp</title>
</head>
<body>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;">hexaNumberMethods</div>
<div style="display: flex;">
<button id="eth_blockNumber" class="hexaNumberMethods">eth_blockNumber</button>
<button id="eth_gasPrice" class="hexaNumberMethods">eth_gasPrice</button>
<button id="eth_newBlockFilter" class="hexaNumberMethods">eth_newBlockFilter</button>
<button id="eth_newPendingTransactionFilter" class="hexaNumberMethods">
eth_newPendingTransactionFilter
</button>
<button id="eth_getUncleCountByBlockHash" class="hexaNumberMethods">
eth_getUncleCountByBlockHash
</button>
<button id="eth_getBlockTransactionCountByHash" class="hexaNumberMethods">
getBlockTransactionCountByHash
</button>
</div>
<div style="display: flex ;">
<button id="eth_getTransactionCount" class="hexaNumberMethods">eth_getTransactionCount</button>
<button id="eth_getBalance" class="hexaNumberMethods">eth_getBalance</button>
<button id="eth_estimateGas" class="hexaNumberMethods">eth_estimateGas</button>
</div>
<div style="display: flex ;">
<button id="eth_getUncleCountByBlockNumber" class="hexaNumberMethods">
eth_getUncleCountByBlockNumber
</button>
<button id='eth_getBlockTransactionCountByNumber' class="hexaNumberMethods">
eth_getBlockTransactionCountByNumber
</button>
<button id="eth_protocolVersion" class="hexaNumberMethods">eth_protocolVersion</button>
<button id="eth_getCode" class="hexaNumberMethods">eth_getCode</button>
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;">booleanMethods</div>
<div style="display: flex ;">
<button id="eth_uninstallFilter" class = 'booleanMethods'>eth_uninstallFilter</button>
<button id="eth_mining" class = 'booleanMethods'>eth_mining</button>
<button id="eth_syncing" class = 'booleanMethods'>eth_syncing</button>
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;" >transactionMethods</div>
<div style="display: flex ;">
<button id="eth_getTransactionByHash" class='transactionMethods'>eth_getTransactionByHash</button>
<button id="eth_getTransactionByBlockHashAndIndex" class = 'transactionMethods'>
eth_getTransactionByBlockHashAndIndex
</button>
<button id="eth_getTransactionByBlockNumberAndIndex" class="transactionMethods">
eth_getTransactionByBlockNumberAndIndex
</button>
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;">blockMethods</div>
<div style="display: flex ;">
<button id="eth_getUncleByBlockHashAndIndex" class="blockMethods">
eth_getUncleByBlockHashAndIndex
</button>
<button id="eth_getBlockByHash" class="blockMethods">eth_getBlockByHash</button>
</div>
<div style="display: flex ;">
<button id="eth_getBlockByNumber" class="blockMethods">eth_getBlockByNumber</button>
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;">Methods</div>
<div style="display: flex ;">
<button id="eth_call" class = 'methods'>eth_call</button>
<button id="eth_getStorageAt" class="methods">eth_getStorageAt</button>
<button id="eth_getTransactionReceipt" class="methods">
eth_getTransactionReceipt
</button>
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div id='results'></div>
</div>
</div>
<script src="schema.js"></script>
<script src="web3.js"></script>
</body>
</html>

209
test/web3/schema.js Normal file
View File

@ -0,0 +1,209 @@
/* eslint no-unused-vars: 0 */
var params = {
// diffrent params used in the methods
param: [],
blockHashParams: '0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35',
filterParams: ['0xfe704947a3cd3ca12541458a4321c869'],
transactionHashParams: [
'0xbb3a336e3f823ec18197f1e13ee875700f08f03e2cab75f0d0b118dabb44cba0',
],
blockHashAndIndexParams: [
'0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35',
'0x0',
],
uncleByBlockNumberAndIndexParams: ['0x29c', '0x0'],
blockParameterParams: '0x5bad55',
data: '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675',
addressParams: '0xc94770007dda54cF92009BFF0dE90c06F603a09f',
getStorageAtParams: [
'0x295a70b2de5e3953354a6a8344e616ed314d7251',
'0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9',
'0x65a8db',
],
getCodeParams: ['0x06012c8cf97bead5deae237070f9587f8e7a266d', '0x65a8db'],
estimateTransaction: {
from: '0xb60e8dd61c5d32be8058bb8eb970870f07233155',
to: '0xd46e8dd67c5d32be8058bb8eb970870f07244567',
gas: '0x76c0',
gasPrice: '0x9184e72a000',
value: '0x9184e72a',
data: '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675',
},
filterGetLogs: [{'blockHash': '0x7c5a35e9cb3e8ae0e221ab470abae9d446c3a5626ce6689fc777dcffcab52c70', 'topics': ['0x241ea03ca20251805084d27d4440371c34a0b85ff108f6bb5611248f73818b80']}],
block: {
__required: [],
number: 'Q',
hash: 'D32',
parentHash: 'D32',
nonce: 'D',
sha3Uncles: 'D',
logsBloom: 'D',
transactionsRoot: 'D',
stateRoot: 'D',
receiptsRoot: 'D',
miner: 'D',
difficulty: 'Q',
totalDifficulty: 'Q',
extraData: 'D',
size: 'Q',
gasLimit: 'Q',
gasUsed: 'Q',
timestamp: 'Q',
transactions: ['DATA|Transaction'],
uncles: ['D'],
},
transaction: {
__required: [],
hash: 'D32',
nonce: 'Q',
blockHash: 'D32',
blockNumber: 'Q',
transactionIndex: 'Q',
from: 'D20',
to: 'D20',
value: 'Q',
gasPrice: 'Q',
gas: 'Q',
input: 'D',
},
receipt: {
__required: [],
transactionHash: 'D32',
transactionIndex: 'Q',
blockHash: 'D32',
blockNumber: 'Q',
cumulativeGasUsed: 'Q',
gasUsed: 'Q',
contractAddress: 'D20',
logs: ['FilterChange'],
},
filterChange: {
__required: [],
removed: 'B',
logIndex: 'Q',
transactionIndex: 'Q',
transactionHash: 'D32',
blockHash: 'D32',
blockNumber: 'Q',
address: 'D20',
data: 'Array|DATA',
topics: ['D'],
},
}
var methods = {
hexaNumberMethods: {
// these are the methods which have output in the form of hexa decimal numbers
eth_blockNumber: ['eth_blockNumber', params.param, 'Q'],
eth_gasPrice: ['eth_gasPrice', params.param, 'Q'],
eth_newBlockFilter: ['eth_newBlockFilter', params.param, 'Q'],
eth_newPendingTransactionFilter: [
'eth_newPendingTransactionFilter',
params.param,
'Q',
],
eth_getUncleCountByBlockHash: [
'eth_getUncleCountByBlockHash',
[params.blockHashParams],
'Q',
1,
],
eth_getBlockTransactionCountByHash: [
'eth_getBlockTransactionCountByHash',
[params.blockHashParams],
'Q',
1,
],
eth_getTransactionCount: [
'eth_getTransactionCount',
[params.addressParams, params.blockParameterParams],
'Q',
1,
2,
],
eth_getBalance: ['eth_getBalance', [params.addressParams, 'latest'], 'Q', 1, 2],
eth_estimateGas: ['eth_estimateGas', [params.estimateTransaction], 'Q', 1],
eth_getUncleCountByBlockNumber: [
'eth_getUncleCountByBlockNumber',
[params.blockParameterParams],
'Q',
1,
],
eth_getBlockTransactionCountByNumber: [
'eth_getBlockTransactionCountByNumber',
['latest'],
'Q',
1,
],
eth_protocolVersion: ['eth_protocolVersion', params.param, 'S'],
eth_getCode: ['eth_getCode', params.getCodeParams, 'D', 1, 2],
},
booleanMethods: {
// these are the methods which have output in the form of boolean
eth_uninstallFilter: ['eth_uninstallFilter', params.filterParams, 'B', 1],
eth_mining: ['eth_mining', params.param, 'B'],
eth_syncing: ['eth_syncing', params.param, 'B|EthSyncing'],
},
transactionMethods: {
// these are the methods which have output in the form of transaction object
eth_getTransactionByHash: [
'eth_getTransactionByHash',
params.transactionHashParams,
params.transaction,
1,
],
eth_getTransactionByBlockHashAndIndex: [
'eth_getTransactionByBlockHashAndIndex',
params.blockHashAndIndexParams,
params.transaction,
2,
],
eth_getTransactionByBlockNumberAndIndex: [
'eth_getTransactionByBlockNumberAndIndex',
[params.blockParameterParams, '0x0'],
params.transaction,
2,
],
},
blockMethods: {
// these are the methods which have output in the form of a block
eth_getUncleByBlockNumberAndIndex: [
'eth_getUncleByBlockNumberAndIndex',
params.uncleByBlockNumberAndIndexParams,
params.block,
2,
],
eth_getBlockByHash: [
'eth_getBlockByHash',
[params.params, false],
params.block,
2,
],
eth_getBlockByNumber: [
'eth_getBlockByNumber',
[params.blockParameterParams, false],
params.block,
2,
],
},
methods: {
// these are the methods which have output in the form of bytes data
eth_call: ['eth_call', [params.estimateTransaction, 'latest'], 'D', 1, 2],
eth_getStorageAt: ['eth_getStorageAt', params.getStorageAtParams, 'D', 2, 2],
eth_getTransactionReceipt: [
'eth_getTransactionReceipt',
params.transactionHashParams,
params.receipt,
1,
],
},
}

34
test/web3/web3.js Normal file
View File

@ -0,0 +1,34 @@
/* eslint no-undef: 0 */
var json = methods
web3.currentProvider.enable().then(() => {
Object.keys(json).forEach(methodGroupKey => {
console.log(methodGroupKey)
const methodGroup = json[methodGroupKey]
console.log(methodGroup)
Object.keys(methodGroup).forEach(methodKey => {
const methodButton = document.getElementById(methodKey)
methodButton.addEventListener('click', function (event) {
window.ethereum.sendAsync({
method: methodKey,
params: methodGroup[methodKey][1],
}, function (err, result) {
if (err) {
console.log(err)
console.log(methodKey)
} else {
document.getElementById('results').innerHTML = JSON.stringify(result)
}
})
})
})
})
})

View File

@ -17,10 +17,7 @@
} }
&__button { &__button {
font-size: 0.75rem; @extend %small-link;
margin: 1rem; margin: 1rem;
text-transform: uppercase;
color: $curious-blue;
cursor: pointer;
} }
} }

View File

@ -1,69 +0,0 @@
const Component = require('react').Component
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../store/actions')
CoinbaseForm.contextTypes = {
t: PropTypes.func,
}
module.exports = connect(mapStateToProps)(CoinbaseForm)
function mapStateToProps (state) {
return {
warning: state.appState.warning,
}
}
inherits(CoinbaseForm, Component)
function CoinbaseForm () {
Component.call(this)
}
CoinbaseForm.prototype.render = function () {
var props = this.props
return h('.flex-column', {
style: {
marginTop: '35px',
padding: '25px',
width: '100%',
},
}, [
h('.flex-row', {
style: {
justifyContent: 'space-around',
margin: '33px',
marginTop: '0px',
},
}, [
h('button.btn-green', {
onClick: this.toCoinbase.bind(this),
}, this.context.t('continueToCoinbase')),
h('button.btn-red', {
onClick: () => props.dispatch(actions.goHome()),
}, this.context.t('cancel')),
]),
])
}
CoinbaseForm.prototype.toCoinbase = function () {
const props = this.props
const address = props.buyView.buyAddress
props.dispatch(actions.buyEth({ network: '1', address, amount: 0 }))
}
CoinbaseForm.prototype.renderLoading = function () {
return h('img', {
style: {
width: '27px',
marginRight: '-27px',
},
src: 'images/loading.svg',
})
}

View File

@ -18,11 +18,11 @@ const {
MIN_GAS_PRICE_DEC, MIN_GAS_PRICE_DEC,
MIN_GAS_LIMIT_DEC, MIN_GAS_LIMIT_DEC,
MIN_GAS_PRICE_GWEI, MIN_GAS_PRICE_GWEI,
} = require('../send/send.constants') } = require('../../../pages/send/send.constants')
const { const {
isBalanceSufficient, isBalanceSufficient,
} = require('../send/send.utils') } = require('../../../pages/send/send.utils')
const { const {
conversionUtil, conversionUtil,
@ -47,7 +47,7 @@ const {
const { const {
getGasPrice, getGasPrice,
getGasLimit, getGasLimit,
} = require('../send/send.selectors') } = require('../../../pages/send/send.selectors')
function mapStateToProps (state) { function mapStateToProps (state) {
const selectedToken = getSelectedToken(state) const selectedToken = getSelectedToken(state)
@ -382,7 +382,7 @@ CustomizeGasModal.prototype.render = function () {
onClick: this.props.hideModal, onClick: this.props.hideModal,
}, [this.context.t('cancel')]), }, [this.context.t('cancel')]),
h(Button, { h(Button, {
type: 'primary', type: 'secondary',
className: 'send-v2__customize-gas__save', className: 'send-v2__customize-gas__save',
onClick: () => !error && this.save(newGasPrice, gasLimit, gasTotal), onClick: () => !error && this.save(newGasPrice, gasLimit, gasTotal),
disabled: error, disabled: error,

View File

@ -204,6 +204,28 @@ NetworkDropdown.prototype.render = function () {
] ]
), ),
h(
DropdownMenuItem,
{
key: 'goerli',
closeMenu: () => this.props.hideNetworkDropdown(),
onClick: () => this.handleClick('goerli'),
style: dropdownMenuItemStyle,
},
[
providerType === 'goerli' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'),
h(NetworkDropdownIcon, {
backgroundColor: '#3099f2', // $dodger-blue
isSelected: providerType === 'goerli',
}),
h('span.network-name-item', {
style: {
color: providerType === 'goerli' ? '#ffffff' : '#9b9b9b',
},
}, this.context.t('goerli')),
]
),
h( h(
DropdownMenuItem, DropdownMenuItem,
{ {
@ -285,6 +307,10 @@ NetworkDropdown.prototype.getNetworkName = function () {
name = this.context.t('kovan') name = this.context.t('kovan')
} else if (providerName === 'rinkeby') { } else if (providerName === 'rinkeby') {
name = this.context.t('rinkeby') name = this.context.t('rinkeby')
} else if (providerName === 'localhost') {
name = this.context.t('localhost')
} else if (providerName === 'goerli') {
name = this.context.t('goerli')
} else { } else {
name = provider.nickname || this.context.t('unknownNetwork') name = provider.nickname || this.context.t('unknownNetwork')
} }

View File

@ -62,7 +62,7 @@ describe('Network Dropdown', () => {
}) })
it('renders 7 DropDownMenuItems ', () => { it('renders 7 DropDownMenuItems ', () => {
assert.equal(wrapper.find(DropdownMenuItem).length, 7) assert.equal(wrapper.find(DropdownMenuItem).length, 8)
}) })
it('checks background color for first NetworkDropdownIcon', () => { it('checks background color for first NetworkDropdownIcon', () => {
@ -82,16 +82,20 @@ describe('Network Dropdown', () => {
}) })
it('checks background color for fifth NetworkDropdownIcon', () => { it('checks background color for fifth NetworkDropdownIcon', () => {
assert.equal(wrapper.find(NetworkDropdownIcon).at(4).prop('innerBorder'), '1px solid #9b9b9b') assert.equal(wrapper.find(NetworkDropdownIcon).at(4).prop('backgroundColor'), '#3099f2') // Goerli Blue
})
it('checks dropdown for frequestRPCList from state ', () => {
assert.equal(wrapper.find(DropdownMenuItem).at(5).text(), '✓http://localhost:7545')
}) })
it('checks background color for sixth NetworkDropdownIcon', () => { it('checks background color for sixth NetworkDropdownIcon', () => {
assert.equal(wrapper.find(NetworkDropdownIcon).at(5).prop('innerBorder'), '1px solid #9b9b9b') assert.equal(wrapper.find(NetworkDropdownIcon).at(5).prop('innerBorder'), '1px solid #9b9b9b')
}) })
it('checks dropdown for frequestRPCList from state ', () => {
assert.equal(wrapper.find(DropdownMenuItem).at(6).text(), '✓http://localhost:7545')
})
it('checks background color for seventh NetworkDropdownIcon', () => {
assert.equal(wrapper.find(NetworkDropdownIcon).at(6).prop('innerBorder'), '1px solid #9b9b9b')
})
}) })
}) })

View File

@ -10,7 +10,7 @@ const networkMap = require('ethjs-ens/lib/network-map.json')
const ensRE = /.+\..+$/ const ensRE = /.+\..+$/
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
const connect = require('react-redux').connect const connect = require('react-redux').connect
const ToAutoComplete = require('./send/to-autocomplete').default const ToAutoComplete = require('../../pages/send/to-autocomplete').default
const log = require('loglevel') const log = require('loglevel')
const { isValidENSAddress } = require('../../helpers/utils/util') const { isValidENSAddress } = require('../../helpers/utils/util')

View File

@ -63,7 +63,7 @@ import {
import { import {
calcGasTotal, calcGasTotal,
isBalanceSufficient, isBalanceSufficient,
} from '../../send/send.utils' } from '../../../../pages/send/send.utils'
import { addHexPrefix } from 'ethereumjs-util' import { addHexPrefix } from 'ethereumjs-util'
import { getAdjacentGasPrices, extrapolateY } from '../gas-price-chart/gas-price-chart.utils' import { getAdjacentGasPrices, extrapolateY } from '../gas-price-chart/gas-price-chart.utils'

View File

@ -65,6 +65,7 @@
.gas-price-button-group--small { .gas-price-button-group--small {
display: flex; display: flex;
justify-content: stretch; justify-content: stretch;
height: 54px;
@media screen and (max-width: $break-small) { @media screen and (max-width: $break-small) {
max-width: 260px; max-width: 260px;
@ -80,10 +81,14 @@
&__label { &__label {
font-weight: 500; font-weight: 500;
line-height: 16px;
padding-bottom: 4px;
} }
&__primary-currency { &__primary-currency {
font-size: 12px; font-size: 12px;
line-height: 12px;
padding-bottom: 2px;
@media screen and (max-width: 575px) { @media screen and (max-width: 575px) {
font-size: 10px; font-size: 10px;
@ -92,6 +97,8 @@
&__secondary-currency { &__secondary-currency {
font-size: 12px; font-size: 12px;
line-height: 12px;
padding-bottom: 2px;
@media screen and (max-width: 575px) { @media screen and (max-width: 575px) {
font-size: 10px; font-size: 10px;
@ -99,19 +106,13 @@
} }
&__loading-container { &__loading-container {
height: 78px; height: 54px;
} }
.button-group__button, .button-group__button--active { .button-group__button, .button-group__button--active {
height: 78px;
background: white; background: white;
color: $scorpion; color: $scorpion;
padding-top: 9px; padding: 0 4px;
padding-left: 8.5px;
@media screen and (max-width: $break-small) {
padding-left: 4px;
}
div { div {
display: flex; display: flex;

View File

@ -45,6 +45,10 @@ export default class LoadingNetworkScreen extends PureComponent {
name = this.context.t('connectingToKovan') name = this.context.t('connectingToKovan')
} else if (providerName === 'rinkeby') { } else if (providerName === 'rinkeby') {
name = this.context.t('connectingToRinkeby') name = this.context.t('connectingToRinkeby')
} else if (providerName === 'localhost') {
name = this.context.t('connectingToLocalhost')
} else if (providerName === 'goerli') {
name = this.context.t('connectingToGoerli')
} else { } else {
name = this.context.t('connectingTo', [providerId]) name = this.context.t('connectingTo', [providerId])
} }

View File

@ -20,7 +20,7 @@ export default class Modal extends PureComponent {
} }
static defaultProps = { static defaultProps = {
submitType: 'primary', submitType: 'secondary',
cancelType: 'default', cancelType: 'default',
} }

View File

@ -12,7 +12,7 @@ describe('Modal Component', () => {
assert.equal(wrapper.find('.modal-container').length, 1) assert.equal(wrapper.find('.modal-container').length, 1)
const buttons = wrapper.find(Button) const buttons = wrapper.find(Button)
assert.equal(buttons.length, 1) assert.equal(buttons.length, 1)
assert.equal(buttons.at(0).props().type, 'primary') assert.equal(buttons.at(0).props().type, 'secondary')
}) })
it('should render a modal with a cancel and a submit button', () => { it('should render a modal with a cancel and a submit button', () => {
@ -38,7 +38,7 @@ describe('Modal Component', () => {
cancelButton.simulate('click') cancelButton.simulate('click')
assert.equal(handleCancel.callCount, 1) assert.equal(handleCancel.callCount, 1)
assert.equal(submitButton.props().type, 'primary') assert.equal(submitButton.props().type, 'secondary')
assert.equal(submitButton.props().children, 'Submit') assert.equal(submitButton.props().children, 'Submit')
assert.equal(handleSubmit.callCount, 0) assert.equal(handleSubmit.callCount, 0)
submitButton.simulate('click') submitButton.simulate('click')

View File

@ -84,7 +84,7 @@ AccountDetailsModal.prototype.render = function () {
h('div.account-modal-divider'), h('div.account-modal-divider'),
h(Button, { h(Button, {
type: 'primary', type: 'secondary',
className: 'account-modal__button', className: 'account-modal__button',
onClick: () => global.platform.openWindow({ url: genAccountLink(address, network) }), onClick: () => global.platform.openWindow({ url: genAccountLink(address, network) }),
}, this.context.t('etherscanView')), }, this.context.t('etherscanView')),
@ -92,7 +92,7 @@ AccountDetailsModal.prototype.render = function () {
// Holding on redesign for Export Private Key functionality // Holding on redesign for Export Private Key functionality
exportPrivateKeyFeatureEnabled ? h(Button, { exportPrivateKeyFeatureEnabled ? h(Button, {
type: 'primary', type: 'secondary',
className: 'account-modal__button', className: 'account-modal__button',
onClick: () => showExportPrivateKeyModal(), onClick: () => showExportPrivateKeyModal(),
}, this.context.t('exportPrivateKey')) : null, }, this.context.t('exportPrivateKey')) : null,

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import GasModalCard from '../../customize-gas-modal/gas-modal-card' import GasModalCard from '../../customize-gas-modal/gas-modal-card'
import { MIN_GAS_PRICE_GWEI } from '../../send/send.constants' import { MIN_GAS_PRICE_GWEI } from '../../../../pages/send/send.constants'
import Button from '../../../ui/button' import Button from '../../../ui/button'
import { import {
@ -128,7 +128,7 @@ export default class CustomizeGas extends Component {
{ t('cancel') } { t('cancel') }
</Button> </Button>
<Button <Button
type="primary" type="secondary"
className="customize-gas__save" className="customize-gas__save"
onClick={() => { onClick={() => {
metricsEvent({ metricsEvent({

View File

@ -119,7 +119,7 @@ DepositEtherModal.prototype.renderRow = function ({
!hideButton && h('div.deposit-ether-modal__buy-row__button', [ !hideButton && h('div.deposit-ether-modal__buy-row__button', [
h(Button, { h(Button, {
type: 'primary', type: 'secondary',
className: 'deposit-ether-modal__deposit-button', className: 'deposit-ether-modal__deposit-button',
large: true, large: true,
onClick: onButtonClick, onClick: onButtonClick,
@ -133,7 +133,7 @@ DepositEtherModal.prototype.render = function () {
const { network, toWyre, toCoinSwitch, address, toFaucet } = this.props const { network, toWyre, toCoinSwitch, address, toFaucet } = this.props
const { buyingWithShapeshift } = this.state const { buyingWithShapeshift } = this.state
const isTestNetwork = ['3', '4', '42'].find(n => n === network) const isTestNetwork = ['3', '4', '5', '42'].find(n => n === network)
const networkName = getNetworkDisplayName(network) const networkName = getNetworkDisplayName(network)
return h('div.page-container.page-container--full-width.page-container--full-height', {}, [ return h('div.page-container.page-container--full-width.page-container--full-height', {}, [

View File

@ -66,7 +66,7 @@ EditAccountNameModal.prototype.render = function () {
value: this.state.inputText, value: this.state.inputText,
}, []), }, []),
h('button.btn-clear.edit-account-name-modal-save-button.allcaps', { h('button.button.btn-secondary.edit-account-name-modal-save-button.allcaps', {
onClick: () => { onClick: () => {
if (this.state.inputText.length !== 0) { if (this.state.inputText.length !== 0) {
setAccountLabel(identity.address, this.state.inputText) setAccountLabel(identity.address, this.state.inputText)

View File

@ -110,14 +110,14 @@ ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, password,
(privateKey (privateKey
? ( ? (
h(Button, { h(Button, {
type: 'primary', type: 'secondary',
large: true, large: true,
className: 'export-private-key__button', className: 'export-private-key__button',
onClick: () => hideModal(), onClick: () => hideModal(),
}, this.context.t('done')) }, this.context.t('done'))
) : ( ) : (
h(Button, { h(Button, {
type: 'primary', type: 'secondary',
large: true, large: true,
className: 'export-private-key__button', className: 'export-private-key__button',
onClick: () => this.exportAccountAndGetPrivateKey(this.state.password, address), onClick: () => this.exportAccountAndGetPrivateKey(this.state.password, address),

View File

@ -67,12 +67,12 @@ HideTokenConfirmationModal.prototype.render = function () {
]), ]),
h('div.hide-token-confirmation__buttons', {}, [ h('div.hide-token-confirmation__buttons', {}, [
h('button.btn-cancel.hide-token-confirmation__button.allcaps', { h('button.btn-default.hide-token-confirmation__button.btn--large', {
onClick: () => hideModal(), onClick: () => hideModal(),
}, [ }, [
this.context.t('cancel'), this.context.t('cancel'),
]), ]),
h('button.btn-clear.hide-token-confirmation__button.allcaps', { h('button.btn-secondary.hide-token-confirmation__button.btn--large', {
onClick: () => hideToken(address), onClick: () => hideToken(address),
}, [ }, [
this.context.t('hide'), this.context.t('hide'),

View File

@ -37,11 +37,11 @@ class NotificationModal extends Component {
showButtons && h('div.notification-modal__buttons', [ showButtons && h('div.notification-modal__buttons', [
showCancelButton && h('div.btn-cancel.notification-modal__buttons__btn', { showCancelButton && h('div.btn-default.notification-modal__buttons__btn', {
onClick: hideModal, onClick: hideModal,
}, 'Cancel'), }, 'Cancel'),
showConfirmButton && h('div.btn-clear.notification-modal__buttons__btn', { showConfirmButton && h('div.button.btn-secondary.notification-modal__buttons__btn', {
onClick: () => { onClick: () => {
onConfirm() onConfirm()
hideModal() hideModal()

View File

@ -26,6 +26,10 @@
&--rinkeby { &--rinkeby {
background-color: lighten($tulip-tree, 35%); background-color: lighten($tulip-tree, 35%);
} }
&--goerli {
background-color: lighten($dodger-blue, 35%);
}
} }
&__name { &__name {
@ -53,5 +57,9 @@
&--rinkeby { &--rinkeby {
background-color: $tulip-tree; background-color: $tulip-tree;
} }
&--goerli {
background-color: $dodger-blue;
}
} }
} }

View File

@ -6,12 +6,14 @@ import {
ROPSTEN_CODE, ROPSTEN_CODE,
RINKEYBY_CODE, RINKEYBY_CODE,
KOVAN_CODE, KOVAN_CODE,
GOERLI_CODE,
} from '../../../../../app/scripts/controllers/network/enums' } from '../../../../../app/scripts/controllers/network/enums'
const networkToClassHash = { const networkToClassHash = {
[MAINNET_CODE]: 'mainnet', [MAINNET_CODE]: 'mainnet',
[ROPSTEN_CODE]: 'ropsten', [ROPSTEN_CODE]: 'ropsten',
[RINKEYBY_CODE]: 'rinkeby', [RINKEYBY_CODE]: 'rinkeby',
[GOERLI_CODE]: 'goerli',
[KOVAN_CODE]: 'kovan', [KOVAN_CODE]: 'kovan',
} }

View File

@ -50,6 +50,9 @@ Network.prototype.render = function () {
} else if (providerName === 'rinkeby') { } else if (providerName === 'rinkeby') {
hoverText = context.t('rinkeby') hoverText = context.t('rinkeby')
iconName = 'rinkeby-test-network' iconName = 'rinkeby-test-network'
} else if (providerName === 'goerli') {
hoverText = context.t('goerli')
iconName = 'goerli-test-network'
} else { } else {
hoverText = providerId hoverText = providerId
iconName = 'private-network' iconName = 'private-network'
@ -63,6 +66,7 @@ Network.prototype.render = function () {
'ropsten-test-network': providerName === 'ropsten' || parseInt(networkNumber) === 3, 'ropsten-test-network': providerName === 'ropsten' || parseInt(networkNumber) === 3,
'kovan-test-network': providerName === 'kovan', 'kovan-test-network': providerName === 'kovan',
'rinkeby-test-network': providerName === 'rinkeby', 'rinkeby-test-network': providerName === 'rinkeby',
'goerli-test-network': providerName === 'goerli',
}), }),
title: hoverText, title: hoverText,
onClick: (event) => { onClick: (event) => {
@ -113,33 +117,34 @@ Network.prototype.render = function () {
h('.network-name', context.t('rinkeby')), h('.network-name', context.t('rinkeby')),
h('i.fa.fa-chevron-down.fa-lg.network-caret'), h('i.fa.fa-chevron-down.fa-lg.network-caret'),
]) ])
case 'goerli-test-network':
return h('.network-indicator', [
h(NetworkDropdownIcon, {
backgroundColor: '#3099f2', // $dodger-blue
nonSelectBackgroundColor: '#ecb23e',
loading: networkNumber === 'loading',
}),
h('.network-name', context.t('goerli')),
h('i.fa.fa-chevron-down.fa-lg.network-caret'),
])
default: default:
return h('.network-indicator', [ return h('.network-indicator', [
networkNumber === 'loading' networkNumber === 'loading'
? h('span.pointer.network-indicator', { ? h('span.pointer.network-loading-spinner', {
style: {
display: 'flex',
alignItems: 'center',
flexDirection: 'row',
},
onClick: (event) => this.props.onClick(event), onClick: (event) => this.props.onClick(event),
}, [ }, [
h('img', { h('img', {
title: context.t('attemptingConnect'), title: context.t('attemptingConnect'),
style: {
width: '27px',
},
src: 'images/loading.svg', src: 'images/loading.svg',
}), }),
]) ])
: h('i.fa.fa-question-circle.fa-lg', { : h('i.fa.fa-question-circle.fa-lg', {
style: { style: {
margin: '10px',
color: 'rgb(125, 128, 130)', color: 'rgb(125, 128, 130)',
}, },
}), }),
h('.network-name', providerNick || context.t('privateNetwork')), h('.network-name', providerName === 'localhost' ? context.t('localhost') : providerNick || context.t('privateNetwork')),
h('i.fa.fa-chevron-down.fa-lg.network-caret'), h('i.fa.fa-chevron-down.fa-lg.network-caret'),
]) ])
} }

View File

@ -245,7 +245,7 @@ ShapeshiftForm.prototype.render = function () {
]), ]),
!depositAddress && h(Button, { !depositAddress && h(Button, {
type: 'primary', type: 'secondary',
large: true, large: true,
className: `${btnClass} shapeshift-form__shapeshift-buy-btn`, className: `${btnClass} shapeshift-form__shapeshift-buy-btn`,
disabled: !token, disabled: !token,

View File

@ -311,7 +311,7 @@ SignatureRequest.prototype.renderFooter = function () {
}, },
}, this.context.t('cancel')), }, this.context.t('cancel')),
h(Button, { h(Button, {
type: 'primary', type: 'secondary',
large: true, large: true,
className: 'request-signature__footer__sign-button', className: 'request-signature__footer__sign-button',
onClick: event => { onClick: event => {

View File

@ -15,7 +15,7 @@ import {
setCustomGasLimit, setCustomGasLimit,
} from '../../../ducks/gas/gas.duck' } from '../../../ducks/gas/gas.duck'
import { getIsMainnet, preferencesSelector, getSelectedAddress, conversionRateSelector } from '../../../selectors/selectors' import { getIsMainnet, preferencesSelector, getSelectedAddress, conversionRateSelector } from '../../../selectors/selectors'
import { isBalanceSufficient } from '../send/send.utils' import { isBalanceSufficient } from '../../../pages/send/send.utils'
const mapStateToProps = (state, ownProps) => { const mapStateToProps = (state, ownProps) => {
const { metamask: { knownMethodData, accounts } } = state const { metamask: { knownMethodData, accounts } } = state

View File

@ -87,7 +87,7 @@ export default class TransactionViewBalance extends PureComponent {
{ {
!selectedToken && ( !selectedToken && (
<Button <Button
type="primary" type="secondary"
className="transaction-view-balance__button" className="transaction-view-balance__button"
onClick={() => { onClick={() => {
metricsEvent({ metricsEvent({
@ -105,14 +105,14 @@ export default class TransactionViewBalance extends PureComponent {
) )
} }
<Button <Button
type="primary" type="secondary"
className="transaction-view-balance__button" className="transaction-view-balance__button"
onClick={() => { onClick={() => {
metricsEvent({ metricsEvent({
eventOpts: { eventOpts: {
category: 'Navigation', category: 'Navigation',
action: 'Home', action: 'Home',
name: 'Clicked Send', name: selectedToken ? 'Clicked Send: Token' : 'Clicked Send: Eth',
}, },
}) })
history.push(SEND_ROUTE) history.push(SEND_ROUTE)

View File

@ -190,7 +190,7 @@ WalletView.prototype.render = function () {
identities[selectedAddress].name, identities[selectedAddress].name,
]), ]),
h('button.btn-clear.wallet-view__details-button.allcaps', this.context.t('details')), h('button.btn-secondary.wallet-view__details-button', this.context.t('details')),
]), ]),
]), ]),

View File

@ -1,6 +1,6 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import AccountListItem from '../../app/send/account-list-item/account-list-item.component' import AccountListItem from '../../../pages/send/account-list-item/account-list-item.component'
export default class AccountDropdownMini extends PureComponent { export default class AccountDropdownMini extends PureComponent {
static propTypes = { static propTypes = {

View File

@ -2,7 +2,7 @@ import React from 'react'
import assert from 'assert' import assert from 'assert'
import { shallow } from 'enzyme' import { shallow } from 'enzyme'
import AccountDropdownMini from '../account-dropdown-mini.component' import AccountDropdownMini from '../account-dropdown-mini.component'
import AccountListItem from '../../../app/send/account-list-item/account-list-item.component' import AccountListItem from '../../../../pages/send/account-list-item/account-list-item.component'
describe('AccountDropdownMini', () => { describe('AccountDropdownMini', () => {
it('should render an account with an icon', () => { it('should render an account with an icon', () => {

View File

@ -5,7 +5,7 @@ import classnames from 'classnames'
const CLASSNAME_DEFAULT = 'btn-default' const CLASSNAME_DEFAULT = 'btn-default'
const CLASSNAME_PRIMARY = 'btn-primary' const CLASSNAME_PRIMARY = 'btn-primary'
const CLASSNAME_SECONDARY = 'btn-secondary' const CLASSNAME_SECONDARY = 'btn-secondary'
const CLASSNAME_CONFIRM = 'btn-confirm' const CLASSNAME_CONFIRM = 'btn-primary'
const CLASSNAME_RAISED = 'btn-raised' const CLASSNAME_RAISED = 'btn-raised'
const CLASSNAME_LARGE = 'btn--large' const CLASSNAME_LARGE = 'btn--large'
const CLASSNAME_FIRST_TIME = 'btn--first-time' const CLASSNAME_FIRST_TIME = 'btn--first-time'
@ -14,6 +14,11 @@ const typeHash = {
default: CLASSNAME_DEFAULT, default: CLASSNAME_DEFAULT,
primary: CLASSNAME_PRIMARY, primary: CLASSNAME_PRIMARY,
secondary: CLASSNAME_SECONDARY, secondary: CLASSNAME_SECONDARY,
warning: 'btn-warning',
danger: 'btn-danger',
'danger-primary': 'btn-danger-primary',
link: 'btn-link',
// TODO: Legacy button type to be deprecated
confirm: CLASSNAME_CONFIRM, confirm: CLASSNAME_CONFIRM,
raised: CLASSNAME_RAISED, raised: CLASSNAME_RAISED,
'first-time': CLASSNAME_FIRST_TIME, 'first-time': CLASSNAME_FIRST_TIME,
@ -38,7 +43,7 @@ export default class Button extends Component {
<button <button
className={classnames( className={classnames(
'button', 'button',
typeHash[type], typeHash[type] || CLASSNAME_DEFAULT,
large && CLASSNAME_LARGE, large && CLASSNAME_LARGE,
className className
)} )}

View File

@ -2,57 +2,70 @@ import React from 'react'
import { storiesOf } from '@storybook/react' import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions' import { action } from '@storybook/addon-actions'
import Button from '.' import Button from '.'
import { text } from '@storybook/addon-knobs/react' import { text, boolean } from '@storybook/addon-knobs/react'
// ', 'secondary', 'default', 'warning', 'danger', 'danger-primary', 'link'], 'primary')}
storiesOf('Button', module) storiesOf('Button', module)
.add('primary', () => .add('Button - Primary', () =>
<Button <Button
onClick={action('clicked')} onClick={action('clicked')}
type="primary" type="primary"
disabled={boolean('disabled', false)}
> >
{text('text', 'Click me')} {text('text', 'Click me')}
</Button> </Button>
) )
.add('secondary', () => .add('Button - Secondary', () =>
<Button <Button
onClick={action('clicked')} onClick={action('clicked')}
type="secondary" type="secondary"
disabled={boolean('disabled', false)}
> >
{text('text', 'Click me')} {text('text', 'Click me')}
</Button> </Button>
) )
.add('default', () => ( .add('Button - Default', () =>
<Button <Button
onClick={action('clicked')} onClick={action('clicked')}
type="default" type="default"
disabled={boolean('disabled', false)}
> >
{text('text', 'Click me')} {text('text', 'Click me')}
</Button> </Button>
)) )
.add('large primary', () => ( .add('Button - Warning', () =>
<Button <Button
onClick={action('clicked')} onClick={action('clicked')}
type="primary" type="warning"
large disabled={boolean('disabled', false)}
> >
{text('text', 'Click me')} {text('text', 'Click me')}
</Button> </Button>
)) )
.add('large secondary', () => ( .add('Button - Danger', () =>
<Button <Button
onClick={action('clicked')} onClick={action('clicked')}
type="secondary" type="danger"
large disabled={boolean('disabled', false)}
> >
{text('text', 'Click me')} {text('text', 'Click me')}
</Button> </Button>
)) )
.add('large default', () => ( .add('Button - Danger Primary', () =>
<Button <Button
onClick={action('clicked')} onClick={action('clicked')}
type="default" type="danger-primary"
large disabled={boolean('disabled', false)}
> >
{text('text', 'Click me')} {text('text', 'Click me')}
</Button> </Button>
)) )
.add('Button - Link', () =>
<Button
onClick={action('clicked')}
type="link"
disabled={boolean('disabled', false)}
>
{text('text', 'Click me')}
</Button>
)

View File

@ -0,0 +1,244 @@
/*
Buttons
*/
$hover-secondary: #B0D7F2;
$hover-default: #B3B3B3;
$hover-confirm: #0372C3;
$hover-red: #FEB6BF;
$hover-red-primary: #C72837;
$hover-orange: #FFD3B5;
%button {
@include h6;
font-weight: 500;
font-family: Roboto, Arial;
line-height: 1.25rem;
padding: .75rem 1rem;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
border-radius: 6px;
width: 100%;
outline: none;
transition: border-color .3s ease, background-color .3s ease;
&--disabled,
&[disabled] {
cursor: auto;
opacity: .5;
pointer-events: none;
}
}
%link {
@include h4;
color: $Blue-500;
line-height: 1.25rem;
cursor: pointer;
background-color: transparent;
&:hover {
color: $Blue-400;
}
&:active {
color: $Blue-600;
}
&--disabled,
&[disabled] {
cursor: auto;
opacity: 1;
pointer-events: none;
color: $hover-secondary;
}
}
%small-link {
@extend %link;
@include h6;
}
.button {
@extend %button;
}
.btn-secondary {
color: $Blue-500;
border: 2px solid $hover-secondary;
&:hover {
border-color: $Blue-500;
}
&:active {
background: $Blue-000;
border-color: $Blue-500;
}
&--disabled,
&[disabled] {
opacity: 1;
color: $hover-secondary;
}
}
.btn-warning {
color: $Orange-500;
border: 2px solid $hover-orange;
&:hover {
border-color: $Orange-500;
}
&:active {
background: $Orange-000;
border-color: $Orange-500;
}
&--disabled,
&[disabled] {
opacity: 1;
color: $hover-orange;
}
}
.btn-danger {
color: $Red-500;
border: 2px solid $hover-red;
&:hover {
border-color: $Red-500;
}
&:active {
background: $Red-000;
border-color: $Red-500;
}
&--disabled,
&[disabled] {
opacity: 1;
color: $hover-red;
}
}
.btn-danger-primary {
color: $white;
border: 2px solid $Red-500;
background-color: $Red-500;
&:hover {
border-color: $hover-red-primary;
background-color: $hover-red-primary;
}
&:active {
background: $Red-600;
border-color: $Red-600;
}
&--disabled,
&[disabled] {
opacity: 1;
border-color: $hover-red;
background-color: $hover-red;
}
}
.btn-default {
color: $Grey-500;
border: 2px solid $hover-default;
&:hover {
border-color: $Grey-500;
}
&:active {
background: #FBFBFC;
border-color: $Grey-500;
}
&--disabled,
&[disabled] {
opacity: 1;
color: $hover-default;
}
}
.btn-primary {
color: $white;
border: 2px solid $Blue-500;
background-color: $Blue-500;
&:hover {
border-color: $hover-confirm;
background-color: $hover-confirm;
}
&:active {
background: $Blue-600;
border-color: $Blue-600;
}
&--disabled,
&[disabled] {
border-color: $hover-secondary;
background-color: $hover-secondary;
}
}
.btn-link {
@extend %link;
}
.btn--large {
min-height: 54px;
}
/**
All Buttons styles are deviations from design guide
*/
.btn-raised {
color: $curious-blue;
background-color: $white;
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.08);
padding: 6px;
height: initial;
min-height: initial;
width: initial;
min-width: initial;
}
.btn--first-time {
height: 54px;
width: 198px;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .14);
color: $white;
font-size: 1.25rem;
font-weight: 500;
transition: 200ms ease-in-out;
background-color: rgba(247, 134, 28, .9);
border-radius: 0;
}
button[disabled],
input[type="submit"][disabled] {
cursor: not-allowed;
opacity: .5;
}
button.primary {
padding: 8px 12px;
background: #f7861c;
box-shadow: 0 3px 6px rgba(247, 134, 28, .36);
color: $white;
font-size: 1.1em;
font-family: Roboto;
text-transform: uppercase;
}

View File

@ -55,11 +55,6 @@
border-top: 1px solid $geyser; border-top: 1px solid $geyser;
flex: 0 0 auto; flex: 0 0 auto;
.btn-default,
.btn-confirm {
font-size: 1rem;
}
header { header {
display: flex; display: flex;
flex-flow: row; flex-flow: row;
@ -86,9 +81,6 @@
} }
&__footer-button { &__footer-button {
height: 55px;
font-size: 1rem;
text-transform: uppercase;
margin-right: 16px; margin-right: 16px;
&:last-of-type { &:last-of-type {

View File

@ -45,7 +45,7 @@ export default class PageContainerFooter extends Component {
</Button>} </Button>}
<Button <Button
type={submitButtonType || 'primary'} type={submitButtonType || 'secondary'}
large large
className="page-container__footer-button" className="page-container__footer-button"
disabled={disabled} disabled={disabled}

View File

@ -7,7 +7,7 @@
border-radius: 4px; border-radius: 4px;
background-color: #fff; background-color: #fff;
color: #4d4d4d; color: #4d4d4d;
font-size: 1rem; font-size: 16px;
padding: 8px 10px; padding: 8px 10px;
position: relative; position: relative;
@ -29,6 +29,8 @@
&__inputs { &__inputs {
flex: 1 0 auto; flex: 1 0 auto;
display: flex;
flex-flow: column nowrap;
} }
&__input { &__input {
@ -38,15 +40,20 @@
border: none; border: none;
outline: 0 !important; outline: 0 !important;
max-width: 22ch; max-width: 22ch;
height: 16px;
line-height: 18px;
} }
&__input-container { &__input-container {
display: flex; display: flex;
align-items: center; align-items: flex-start;
padding-bottom: 4px;
} }
&__suffix { &__suffix {
margin-left: 3px; margin-left: 3px;
font-size: 1rem;
line-height: 1rem;
} }
&--error { &--error {

View File

@ -1,7 +1,7 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
import { removeLeadingZeroes } from '../../app/send/send.utils' import { removeLeadingZeroes } from '../../../pages/send/send.utils'
/** /**
* Component that attaches a suffix or unit of measurement trailing user input, ex. 'ETH'. Also * Component that attaches a suffix or unit of measurement trailing user input, ex. 'ETH'. Also

View File

@ -1,230 +0,0 @@
/*
Buttons
*/
.button {
min-height: 44px;
background: $white;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
border-radius: 4px;
font-size: 14px;
font-weight: 400;
transition: border-color .3s ease;
padding: 0 16px;
min-width: 140px;
width: 100%;
text-transform: uppercase;
outline: none;
font-family: Roboto;
&--disabled,
&[disabled] {
cursor: auto;
opacity: .5;
pointer-events: none;
}
}
.btn-primary {
color: $curious-blue;
border: 2px solid $spindle;
&:active {
background: $zumthor;
border-color: $curious-blue;
}
&:hover {
border-color: $curious-blue;
}
}
.btn-secondary {
color: $monzo;
border: 2px solid lighten($monzo, 40%);
&:active {
background: lighten($monzo, 55%);
border-color: $monzo;
}
&:hover {
border-color: $monzo;
}
}
.btn-default {
color: $scorpion;
border: 2px solid $dusty-gray;
&:active {
background: $gallery;
border-color: $dusty-gray;
}
&:hover {
border-color: $scorpion;
}
}
.btn-confirm {
color: $white;
border: 2px solid $curious-blue;
background-color: $curious-blue;
}
.btn-raised {
color: $curious-blue;
background-color: $white;
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.08);
padding: 6px;
height: initial;
min-height: initial;
width: initial;
min-width: initial;
}
.btn--first-time {
height: 54px;
width: 198px;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .14);
color: $white;
font-size: 1.25rem;
font-weight: 500;
transition: 200ms ease-in-out;
background-color: rgba(247, 134, 28, .9);
border-radius: 0;
}
.btn--large {
min-height: 54px;
}
.btn-green {
background-color: #02c9b1; // TODO: reusable color in colors.css
}
.btn-clear {
background: $white;
text-align: center;
padding: .8rem 1rem;
color: $curious-blue;
border: 2px solid $spindle;
border-radius: 4px;
font-size: .85rem;
font-weight: 400;
transition: border-color .3s ease;
&:hover {
border-color: $curious-blue;
}
&--disabled,
&[disabled] {
cursor: auto;
opacity: .5;
pointer-events: none;
}
}
.btn-cancel {
background: $white;
text-align: center;
padding: .9rem 1rem;
color: $scorpion;
border: 2px solid $dusty-gray;
border-radius: 4px;
font-size: .85rem;
font-weight: 400;
transition: border-color .3s ease;
width: 100%;
&:hover {
border-color: $scorpion;
}
}
// No longer used in flat design, remove when modal buttons done
// div.wallet-btn {
// border: 1px solid rgb(91, 93, 103);
// border-radius: 2px;
// height: 30px;
// width: 75px;
// font-size: 0.8em;
// text-align: center;
// line-height: 25px;
// }
// .btn-red {
// background: rgba(254, 35, 17, 1);
// box-shadow: 0px 3px 6px rgba(254, 35, 17, 0.36);
// }
button[disabled],
input[type="submit"][disabled] {
cursor: not-allowed;
opacity: .5;
// background: rgba(197, 197, 197, 1);
// box-shadow: 0 3px 6px rgba(197, 197, 197, .36);
}
// button.spaced {
// margin: 2px;
// }
// button:not([disabled]):hover, input[type="submit"]:not([disabled]):hover {
// transform: scale(1.1);
// }
// button:not([disabled]):active, input[type="submit"]:not([disabled]):active {
// transform: scale(0.95);
// }
button.primary {
padding: 8px 12px;
background: #f7861c;
box-shadow: 0 3px 6px rgba(247, 134, 28, .36);
color: $white;
font-size: 1.1em;
font-family: Roboto;
text-transform: uppercase;
}
.btn-light {
padding: 8px 12px;
// background: #FFFFFF; // $bg-white
box-shadow: 0 3px 6px rgba(247, 134, 28, .36);
color: #585d67; // TODO: make reusable light button color
font-size: 1.1em;
font-family: Roboto;
text-transform: uppercase;
text-align: center;
line-height: 20px;
border-radius: 2px;
border: 1px solid #979797; // #TODO: make reusable light border color
opacity: .5;
}
// TODO: cleanup: not used anywhere
button.btn-thin {
border: 1px solid;
border-color: #4d4d4d;
color: #4d4d4d;
background: rgb(255, 174, 41);
border-radius: 4px;
min-width: 200px;
margin: 12px 0;
padding: 6px;
font-size: 13px;
}
.btn-tertiary {
border: 1px solid transparent;
border-radius: 2px;
background-color: transparent;
font-size: 16px;
line-height: 24px;
padding: 16px 42px;
}

View File

@ -1,4 +1,4 @@
@import './buttons.scss'; @import '../../../components/ui/button/buttons';
@import './footer.scss'; @import './footer.scss';

View File

@ -538,6 +538,8 @@
} }
&__button { &__button {
@include paragraph;
@extend %button;
width: 141px; width: 141px;
margin: 0 5px; margin: 0 5px;
} }

View File

@ -29,6 +29,10 @@
&.rinkeby-test-network .menu-icon-circle div { &.rinkeby-test-network .menu-icon-circle div {
background-color: rgba(235, 179, 63, .7) !important; background-color: rgba(235, 179, 63, .7) !important;
} }
&.goerli-test-network .menu-icon-circle div {
background-color: rgba(48, 153, 242, .7) !important;
}
} }
.dropdown-menu-item { .dropdown-menu-item {
@ -48,6 +52,11 @@
font-size: 12px; font-size: 12px;
padding: 0 4px; padding: 0 4px;
} }
.fa-question-circle {
margin: 0 4px 0 6px;
font-size: 1rem;
}
} }
.network-name { .network-name {
@ -165,5 +174,22 @@
} }
.network-caret { .network-caret {
margin: 0 8px 2px; margin: 0 8px;
}
.network-loading-spinner {
display: flex;
flex-flow: row nowrap;
align-items: center;
position: relative;
height: 16px;
width: 16px;
margin-left: 5px;
img {
height: 26px;
position: absolute;
top: -5px;
left: -6px;
}
} }

View File

@ -549,7 +549,7 @@
} }
&__form-row { &__form-row {
margin: 14.5px 18px 0px; margin: 8px 18px 0px;
position: relative; position: relative;
display: flex; display: flex;
flex-flow: row; flex-flow: row;
@ -592,8 +592,8 @@
flex: 0 0 auto; flex: 0 0 auto;
} }
&__from-dropdown { &__from-dropdown,
height: 73px; &__asset-dropdown {
width: 100%; width: 100%;
border: 1px solid $alto; border: 1px solid $alto;
border-radius: 4px; border-radius: 4px;
@ -628,6 +628,112 @@
} }
} }
&__from-dropdown {
height: 73px;
}
&__asset-dropdown {
height: 54px;
border: none;
&__asset {
display: flex;
flex-flow: row nowrap;
align-items: center;
padding: 0 8px;
cursor: pointer;
&:hover {
background-color: rgba($alto, 0.2);
}
}
&__asset-icon {
.identicon {
border: 1px solid $alto;
}
}
&__asset-data {
display: flex;
flex-flow: column nowrap;
margin-left: 8px;
}
&__symbol {
font-size: 16px;
margin-bottom: 2px;
}
&__name {
display: flex;
flex-flow: row nowrap;
font-size: 12px;
&__label {
margin-right: .25rem;
}
}
&__close-area {
z-index: 2000;
}
&__list {
z-index: 2050;
position: absolute;
height: 220px;
width: 100%;
border: 1px solid $geyser;
border-radius: 4px;
background-color: $white;
box-shadow: 0 3px 6px 0 rgba(0 ,0 ,0 ,.11);
top: 65px;
left: 0;
box-sizing: content-box;
overflow-y: scroll;
margin-top: 0;
padding: 4px 0;
.send-v2__asset-dropdown__asset {
padding: 8px;
}
}
&__input-wrapper {
border: 1px solid $alto;
border-radius: 4px;
height: 100%;
&--opened {
position: relative;
z-index: 2050;
}
.send-v2__asset-dropdown__asset {
height: 100%;
&:hover {
background-color: $white;
}
}
}
&__input {
z-index: 1025;
position: relative;
height: 54px;
width: 100%;
border: none;
border-radius: 4px;
background-color: $white;
color: $tundora;
padding: 10px;
font-family: Roboto;
font-size: 16px;
line-height: 21px;
}
}
&__to-autocomplete { &__to-autocomplete {
position: relative; position: relative;

View File

@ -18,6 +18,7 @@ body {
height: 100%; height: 100%;
margin: 0; margin: 0;
padding: 0; padding: 0;
font-size: 16px;
@media screen and (max-width: $break-small) { @media screen and (max-width: $break-small) {
overflow-y: overlay; overflow-y: overlay;

View File

@ -403,3 +403,40 @@
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;
} }
@mixin fontScale($weight: 400, $size: 1rem) {
font-weight: $weight;
font-size: $size;
}
@mixin h1($weight: 400, $size: 2.5rem){
@include fontScale($weight, $size);
}
@mixin h2($weight: 400, $size: 2rem){
@include fontScale($weight, $size);
}
@mixin h3($weight: 400, $size: 1.5rem){
@include fontScale($weight, $size);
}
@mixin h4($weight: 400, $size: 1.125rem){
@include fontScale($weight, $size);
}
@mixin h5($weight: 400, $size: 1rem){
@include fontScale($weight, $size);
}
@mixin h6($weight: 400, $size: .875rem){
@include fontScale($weight, $size);
}
@mixin h7($weight: 400, $size: .75rem){
@include fontScale($weight, $size);
}
@mixin paragraph($weight: 400, $size: 1rem){
@include fontScale($weight, $size);
}

View File

@ -26,7 +26,7 @@ $dusty-gray: #9b9b9b;
$alto: #dedede; $alto: #dedede;
$alabaster: #fafafa; $alabaster: #fafafa;
$silver-chalice: #aeaeae; $silver-chalice: #aeaeae;
$curious-blue: #2f9ae0; $curious-blue: #037DD6;
$concrete: #f3f3f3; $concrete: #f3f3f3;
$tundora: #4d4d4d; $tundora: #4d4d4d;
$nile-blue: #1b344d; $nile-blue: #1b344d;
@ -93,3 +93,19 @@ $break-large: 576px;
$primary-font-type: Roboto; $primary-font-type: Roboto;
$Blue-000: #eaf6ff;
$Blue-400: #1098fc;
$Blue-500: #037DD6;
$Blue-600: #0260a4;
$Grey-000: #f2f3f4;
$Grey-500: #6A737D;
$Red-000: #fcf2f3;
$Red-500: #D73A49;
$Red-600: #b92534;
$Orange-000: #fef5ef;
$Orange-500: #F66A0A;

View File

@ -154,9 +154,26 @@ function reduceMetamask (state, action) {
return newState return newState
case actions.SET_SELECTED_TOKEN: case actions.SET_SELECTED_TOKEN:
return extend(metamaskState, { newState = extend(metamaskState, {
selectedTokenAddress: action.value, selectedTokenAddress: action.value,
}) })
const newSend = extend(metamaskState.send)
if (metamaskState.send.editingTransactionId && !action.value) {
delete newSend.token
const unapprovedTx = newState.unapprovedTxs[newSend.editingTransactionId] || {}
const txParams = unapprovedTx.txParams || {}
newState.unapprovedTxs = extend(newState.unapprovedTxs, {
[newSend.editingTransactionId]: extend(unapprovedTx, {
txParams: extend(txParams, { data: '' }),
}),
})
newSend.tokenBalance = null
newSend.balance = '0'
}
newState.send = newSend
return newState
case actions.SET_ACCOUNT_LABEL: case actions.SET_ACCOUNT_LABEL:
const account = action.value.account const account = action.value.account

View File

@ -10,4 +10,5 @@ export const NETWORK_TYPES = {
MAINNET: 'mainnet', MAINNET: 'mainnet',
RINKEBY: 'rinkeby', RINKEBY: 'rinkeby',
ROPSTEN: 'ropsten', ROPSTEN: 'ropsten',
GOERLI: 'goerli',
} }

View File

@ -23,6 +23,7 @@ const METAMETRICS_CUSTOM_ERROR_FIELD = 'errorField'
const METAMETRICS_CUSTOM_ERROR_MESSAGE = 'errorMessage' const METAMETRICS_CUSTOM_ERROR_MESSAGE = 'errorMessage'
const METAMETRICS_CUSTOM_RPC_NETWORK_ID = 'networkId' const METAMETRICS_CUSTOM_RPC_NETWORK_ID = 'networkId'
const METAMETRICS_CUSTOM_RPC_CHAIN_ID = 'chainId' const METAMETRICS_CUSTOM_RPC_CHAIN_ID = 'chainId'
const METAMETRICS_CUSTOM_GAS_CHANGED = 'gasChanged'
const METAMETRICS_CUSTOM_NETWORK = 'network' const METAMETRICS_CUSTOM_NETWORK = 'network'
const METAMETRICS_CUSTOM_ENVIRONMENT_TYPE = 'environmentType' const METAMETRICS_CUSTOM_ENVIRONMENT_TYPE = 'environmentType'
@ -43,6 +44,7 @@ const customVariableNameIdMap = {
[METAMETRICS_CUSTOM_RPC_CHAIN_ID]: 2, [METAMETRICS_CUSTOM_RPC_CHAIN_ID]: 2,
[METAMETRICS_CUSTOM_ERROR_FIELD]: 1, [METAMETRICS_CUSTOM_ERROR_FIELD]: 1,
[METAMETRICS_CUSTOM_ERROR_MESSAGE]: 2, [METAMETRICS_CUSTOM_ERROR_MESSAGE]: 2,
[METAMETRICS_CUSTOM_GAS_CHANGED]: 1,
} }
const customDimensionsNameIdMap = { const customDimensionsNameIdMap = {

View File

@ -30,6 +30,21 @@ export function getTokenData (data = '') {
return abiDecoder.decodeMethod(data) return abiDecoder.decodeMethod(data)
} }
async function getMethodFrom4Byte (fourBytePrefix) {
const fourByteResponse = (await fetch(`https://www.4byte.directory/api/v1/signatures/?hex_signature=${fourBytePrefix}`, {
referrerPolicy: 'no-referrer-when-downgrade',
body: null,
method: 'GET',
mode: 'cors',
})).json()
if (fourByteResponse.count === 1) {
return fourByteResponse.results[0].text_signature
} else {
return null
}
}
const registry = new MethodRegistry({ provider: global.ethereumProvider }) const registry = new MethodRegistry({ provider: global.ethereumProvider })
/** /**
@ -43,7 +58,16 @@ const registry = new MethodRegistry({ provider: global.ethereumProvider })
const fourBytePrefix = prefixedData.slice(0, 10) const fourBytePrefix = prefixedData.slice(0, 10)
try { try {
const sig = await registry.lookup(fourBytePrefix) const fourByteSig = getMethodFrom4Byte(fourBytePrefix).catch((e) => {
log.error(e)
return null
})
let sig = await registry.lookup(fourBytePrefix)
if (!sig) {
sig = await fourByteSig
}
if (!sig) { if (!sig) {
return {} return {}
@ -57,8 +81,8 @@ const registry = new MethodRegistry({ provider: global.ethereumProvider })
} }
} catch (error) { } catch (error) {
log.error(error) log.error(error)
const contractData = getTokenData(data) const tokenData = getTokenData(data)
const { name } = contractData || {} const { name } = tokenData || {}
return { name } return { name }
} }

View File

@ -18,6 +18,7 @@
} }
&__link { &__link {
color: $curious-blue; @extend %link;
margin-top: .5rem;
} }
} }

View File

@ -103,7 +103,7 @@ export default class ConfirmAddSuggestedToken extends Component {
{ this.context.t('cancel') } { this.context.t('cancel') }
</Button> </Button>
<Button <Button
type="primary" type="secondary"
large large
className="page-container__footer-button" className="page-container__footer-button"
onClick={() => { onClick={() => {

View File

@ -18,7 +18,7 @@ const mapStateToProps = ({ metamask }) => {
const mapDispatchToProps = dispatch => { const mapDispatchToProps = dispatch => {
return { return {
addToken: ({address, symbol, decimals, image}) => dispatch(addToken(address, symbol, decimals, image)), addToken: ({address, symbol, decimals, image}) => dispatch(addToken(address, symbol, Number(decimals), image)),
removeSuggestedTokens: () => dispatch(removeSuggestedTokens()), removeSuggestedTokens: () => dispatch(removeSuggestedTokens()),
} }
} }

View File

@ -96,7 +96,7 @@ export default class ConfirmAddToken extends Component {
{ this.context.t('back') } { this.context.t('back') }
</Button> </Button>
<Button <Button
type="primary" type="secondary"
large large
className="page-container__footer-button" className="page-container__footer-button"
onClick={() => { onClick={() => {

View File

@ -56,7 +56,7 @@ export default class ConfirmDeployContract extends Component {
render () { render () {
return ( return (
<ConfirmTransactionBase <ConfirmTransactionBase
action={this.context.t('contractDeployment')} actionKey={'contractDeployment'}
dataComponent={this.renderData()} dataComponent={this.renderData()}
/> />
) )

View File

@ -30,7 +30,7 @@ export default class ConfirmSendEther extends Component {
return ( return (
<ConfirmTransactionBase <ConfirmTransactionBase
action={this.context.t('confirm')} actionKey={'confirm'}
hideData={hideData} hideData={hideData}
onEdit={confirmTransactionData => this.handleEdit(confirmTransactionData)} onEdit={confirmTransactionData => this.handleEdit(confirmTransactionData)}
/> />

View File

@ -4,7 +4,7 @@ import PropTypes from 'prop-types'
import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../app/scripts/lib/enums' import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../app/scripts/lib/enums'
import { getEnvironmentType } from '../../../../app/scripts/lib/util' import { getEnvironmentType } from '../../../../app/scripts/lib/util'
import ConfirmPageContainer, { ConfirmDetailRow } from '../../components/app/confirm-page-container' import ConfirmPageContainer, { ConfirmDetailRow } from '../../components/app/confirm-page-container'
import { isBalanceSufficient } from '../../components/app/send/send.utils' import { isBalanceSufficient } from '../send/send.utils'
import { DEFAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE } from '../../helpers/constants/routes' import { DEFAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE } from '../../helpers/constants/routes'
import { import {
INSUFFICIENT_FUNDS_ERROR_KEY, INSUFFICIENT_FUNDS_ERROR_KEY,
@ -64,7 +64,7 @@ export default class ConfirmTransactionBase extends Component {
updateGasAndCalculate: PropTypes.func, updateGasAndCalculate: PropTypes.func,
customGas: PropTypes.object, customGas: PropTypes.object,
// Component props // Component props
action: PropTypes.string, actionKey: PropTypes.string,
contentComponent: PropTypes.node, contentComponent: PropTypes.node,
dataComponent: PropTypes.node, dataComponent: PropTypes.node,
detailsComponent: PropTypes.node, detailsComponent: PropTypes.node,
@ -159,7 +159,7 @@ export default class ConfirmTransactionBase extends Component {
} }
handleEditGas () { handleEditGas () {
const { onEditGas, showCustomizeGasModal, action, txData: { origin }, methodData = {} } = this.props const { onEditGas, showCustomizeGasModal, actionKey, txData: { origin }, methodData = {} } = this.props
this.context.metricsEvent({ this.context.metricsEvent({
eventOpts: { eventOpts: {
@ -169,7 +169,7 @@ export default class ConfirmTransactionBase extends Component {
}, },
customVariables: { customVariables: {
recipientKnown: null, recipientKnown: null,
functionType: action || getMethodName(methodData.name) || this.context.t('contractInteraction'), functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction',
origin, origin,
}, },
}) })
@ -292,7 +292,7 @@ export default class ConfirmTransactionBase extends Component {
} }
handleEdit () { handleEdit () {
const { txData, tokenData, tokenProps, onEdit, action, txData: { origin }, methodData = {} } = this.props const { txData, tokenData, tokenProps, onEdit, actionKey, txData: { origin }, methodData = {} } = this.props
this.context.metricsEvent({ this.context.metricsEvent({
eventOpts: { eventOpts: {
@ -302,7 +302,7 @@ export default class ConfirmTransactionBase extends Component {
}, },
customVariables: { customVariables: {
recipientKnown: null, recipientKnown: null,
functionType: action || getMethodName(methodData.name) || this.context.t('contractInteraction'), functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction',
origin, origin,
}, },
}) })
@ -331,7 +331,7 @@ export default class ConfirmTransactionBase extends Component {
handleCancel () { handleCancel () {
const { metricsEvent } = this.context const { metricsEvent } = this.context
const { onCancel, txData, cancelTransaction, history, clearConfirmTransaction, action, txData: { origin }, methodData = {} } = this.props const { onCancel, txData, cancelTransaction, history, clearConfirmTransaction, actionKey, txData: { origin }, methodData = {} } = this.props
if (onCancel) { if (onCancel) {
metricsEvent({ metricsEvent({
@ -342,7 +342,7 @@ export default class ConfirmTransactionBase extends Component {
}, },
customVariables: { customVariables: {
recipientKnown: null, recipientKnown: null,
functionType: action || getMethodName(methodData.name) || this.context.t('contractInteraction'), functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction',
origin, origin,
}, },
}) })
@ -358,7 +358,7 @@ export default class ConfirmTransactionBase extends Component {
handleSubmit () { handleSubmit () {
const { metricsEvent } = this.context const { metricsEvent } = this.context
const { txData: { origin }, sendTransaction, clearConfirmTransaction, txData, history, onSubmit, action, metaMetricsSendCount = 0, setMetaMetricsSendCount, methodData = {} } = this.props const { txData: { origin }, sendTransaction, clearConfirmTransaction, txData, history, onSubmit, actionKey, metaMetricsSendCount = 0, setMetaMetricsSendCount, methodData = {} } = this.props
const { submitting } = this.state const { submitting } = this.state
if (submitting) { if (submitting) {
@ -377,7 +377,7 @@ export default class ConfirmTransactionBase extends Component {
}, },
customVariables: { customVariables: {
recipientKnown: null, recipientKnown: null,
functionType: action || getMethodName(methodData.name) || this.context.t('contractInteraction'), functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction',
origin, origin,
}, },
}) })
@ -517,7 +517,7 @@ export default class ConfirmTransactionBase extends Component {
valid: propsValid = true, valid: propsValid = true,
errorMessage, errorMessage,
errorKey: propsErrorKey, errorKey: propsErrorKey,
action, actionKey,
title, title,
subtitle, subtitle,
hideSubtitle, hideSubtitle,
@ -543,7 +543,7 @@ export default class ConfirmTransactionBase extends Component {
toName={toName} toName={toName}
toAddress={toAddress} toAddress={toAddress}
showEdit={onEdit && !isTxReprice} showEdit={onEdit && !isTxReprice}
action={action || getMethodName(name) || this.context.t('contractInteraction')} action={actionKey && this.context.t(actionKey) || getMethodName(name) || this.context.t('contractInteraction')}
title={title} title={title}
titleComponent={this.renderTitleComponent()} titleComponent={this.renderTitleComponent()}
subtitle={subtitle} subtitle={subtitle}

View File

@ -14,9 +14,9 @@ import {
GAS_LIMIT_TOO_LOW_ERROR_KEY, GAS_LIMIT_TOO_LOW_ERROR_KEY,
} from '../../helpers/constants/error-keys' } from '../../helpers/constants/error-keys'
import { getHexGasTotal } from '../../helpers/utils/confirm-tx.util' import { getHexGasTotal } from '../../helpers/utils/confirm-tx.util'
import { isBalanceSufficient, calcGasTotal } from '../../components/app/send/send.utils' import { isBalanceSufficient, calcGasTotal } from '../send/send.utils'
import { conversionGreaterThan } from '../../helpers/utils/conversion-util' import { conversionGreaterThan } from '../../helpers/utils/conversion-util'
import { MIN_GAS_LIMIT_DEC } from '../../components/app/send/send.constants' import { MIN_GAS_LIMIT_DEC } from '../send/send.constants'
import { checksumAddress, addressSlicer, valuesFor } from '../../helpers/utils/util' import { checksumAddress, addressSlicer, valuesFor } from '../../helpers/utils/util'
import {getMetaMaskAccounts, getAdvancedInlineGasShown, preferencesSelector, getIsMainnet} from '../../selectors/selectors' import {getMetaMaskAccounts, getAdvancedInlineGasShown, preferencesSelector, getIsMainnet} from '../../selectors/selectors'

View File

@ -152,7 +152,7 @@ class AccountList extends Component {
}, [this.context.t('cancel')]), }, [this.context.t('cancel')]),
h(Button, { h(Button, {
type: 'confirm', type: 'primary',
large: true, large: true,
className: 'new-account-connect-form__button unlock', className: 'new-account-connect-form__button unlock',
disabled, disabled,

View File

@ -46,7 +46,7 @@ class ConnectScreen extends Component {
this.renderConnectToTrezorButton(), this.renderConnectToTrezorButton(),
]), ]),
h(Button, { h(Button, {
type: 'confirm', type: 'primary',
large: true, large: true,
className: 'hw-connect__connect-btn', className: 'hw-connect__connect-btn',
onClick: this.connect, onClick: this.connect,

View File

@ -61,7 +61,7 @@ class JsonImportSubview extends Component {
}, [this.context.t('cancel')]), }, [this.context.t('cancel')]),
h(Button, { h(Button, {
type: 'primary', type: 'secondary',
large: true, large: true,
className: 'new-account-create-form__button', className: 'new-account-create-form__button',
onClick: () => this.createNewKeychain(), onClick: () => this.createNewKeychain(),

View File

@ -75,7 +75,7 @@ PrivateKeyImportView.prototype.render = function () {
}, [this.context.t('cancel')]), }, [this.context.t('cancel')]),
h(Button, { h(Button, {
type: 'primary', type: 'secondary',
large: true, large: true,
className: 'new-account-create-form__button', className: 'new-account-create-form__button',
onClick: () => this.createNewKeychain(), onClick: () => this.createNewKeychain(),

View File

@ -47,7 +47,7 @@ class NewAccountCreateForm extends Component {
}, [this.context.t('cancel')]), }, [this.context.t('cancel')]),
h(Button, { h(Button, {
type: 'primary', type: 'secondary',
large: true, large: true,
className: 'new-account-create-form__button', className: 'new-account-create-form__button',
onClick: () => { onClick: () => {

View File

@ -36,6 +36,20 @@ export default class ImportWithSeedPhrase extends PureComponent {
.join(' ') .join(' ')
} }
componentWillMount () {
window.onbeforeunload = () => this.context.metricsEvent({
eventOpts: {
category: 'Onboarding',
action: 'Import Seed Phrase',
name: 'Close window on import screen',
},
customVariables: {
errorLabel: 'Seed Phrase Error',
errorMessage: this.state.seedPhraseError,
},
})
}
handleSeedPhraseChange (seedPhrase) { handleSeedPhraseChange (seedPhrase) {
let seedPhraseError = '' let seedPhraseError = ''
@ -172,6 +186,10 @@ export default class ImportWithSeedPhrase extends PureComponent {
action: 'Import Seed Phrase', action: 'Import Seed Phrase',
name: 'Go Back from Onboarding Import', name: 'Go Back from Onboarding Import',
}, },
customVariables: {
errorLabel: 'Seed Phrase Error',
errorMessage: seedPhraseError,
},
}) })
this.props.history.push(INITIALIZE_SELECT_ACTION_ROUTE) this.props.history.push(INITIALIZE_SELECT_ACTION_ROUTE)
}} }}
@ -243,7 +261,7 @@ export default class ImportWithSeedPhrase extends PureComponent {
</span> </span>
</div> </div>
<Button <Button
type="confirm" type="primary"
className="first-time-flow__button" className="first-time-flow__button"
disabled={!this.isValid() || !termsChecked} disabled={!this.isValid() || !termsChecked}
onClick={this.handleImport} onClick={this.handleImport}

View File

@ -211,7 +211,7 @@ export default class NewAccount extends PureComponent {
</span> </span>
</div> </div>
<Button <Button
type="confirm" type="primary"
className="first-time-flow__button" className="first-time-flow__button"
disabled={!this.isValid() || !termsChecked} disabled={!this.isValid() || !termsChecked}
onClick={this.handleCreate} onClick={this.handleCreate}

View File

@ -34,7 +34,7 @@ export default class UniqueImageScreen extends PureComponent {
{ t('protectYourKeysMessage2') } { t('protectYourKeysMessage2') }
</div> </div>
<Button <Button
type="confirm" type="primary"
className="first-time-flow__button" className="first-time-flow__button"
onClick={() => { onClick={() => {
this.context.metricsEvent({ this.context.metricsEvent({

View File

@ -71,7 +71,7 @@ export default class EndOfFlowScreen extends PureComponent {
</a>. </a>.
</div> </div>
<Button <Button
type="confirm" type="primary"
className="first-time-flow__button" className="first-time-flow__button"
onClick={async () => { onClick={async () => {
await completeOnboarding() await completeOnboarding()

View File

@ -149,7 +149,7 @@ export default class MetaMetricsOptIn extends Component {
}) })
}} }}
submitText={'I agree'} submitText={'I agree'}
submitButtonType={'confirm'} submitButtonType={'primary'}
disabled={false} disabled={false}
/> />
<div className="metametrics-opt-in__bottom-text"> <div className="metametrics-opt-in__bottom-text">

View File

@ -142,7 +142,7 @@ export default class ConfirmSeedPhrase extends PureComponent {
} }
</div> </div>
<Button <Button
type="confirm" type="primary"
className="first-time-flow__button" className="first-time-flow__button"
onClick={this.handleSubmit} onClick={this.handleSubmit}
disabled={!this.isValid()} disabled={!this.isValid()}

View File

@ -130,7 +130,7 @@ export default class RevealSeedPhrase extends PureComponent {
</div> </div>
</div> </div>
<Button <Button
type="confirm" type="primary"
className="first-time-flow__button" className="first-time-flow__button"
onClick={this.handleNext} onClick={this.handleNext}
disabled={!isShowingSeedPhrase} disabled={!isShowingSeedPhrase}

Some files were not shown because too many files have changed in this diff Show More