1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-26 04:20:53 +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
## 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
- [#6389](https://github.com/MetaMask/metamask-extension/pull/6389): Fix display of gas chart on ethereum networks
@ -21,7 +47,7 @@
- [#6302](https://github.com/MetaMask/metamask-extension/pull/6302): Replaces the coinbase link in the deposit modal with one for wyre
- [#6307](https://github.com/MetaMask/metamask-extension/pull/6307): Centre the notification in the current window
- [#6312](https://github.com/MetaMask/metamask-extension/pull/6312): Fixes popups not showing when screen size is odd
- [#6326](https://github.com/MetaMask/metamask-extension/pull/6326): Fix oversized loading overlay on gas customization modal.
- [#6326](https://github.com/MetaMask/metamask-extension/pull/6326): Fix oversized loading overlay on gas customization modal.
- [#6330](https://github.com/MetaMask/metamask-extension/pull/6330): Stop reloading dapps on network change allowing dapps to decide if it should refresh or not
- [#6332](https://github.com/MetaMask/metamask-extension/pull/6332): Enable mobile sync
- [#6333](https://github.com/MetaMask/metamask-extension/pull/6333): Redesign of the settings screen

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,8 +20,9 @@ const {
KOVAN,
MAINNET,
LOCALHOST,
GOERLI,
} = 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 METAMASK_DEBUG = process.env.METAMASK_DEBUG
@ -140,10 +141,10 @@ module.exports = class NetworkController extends EventEmitter {
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(INFURA_PROVIDER_TYPES.includes(type) || type === LOCALHOST, `NetworkController - Unknown rpc type "${type}"`)
const providerConfig = { type }
const providerConfig = { type, rpcTarget, ticker, nickname }
this.providerConfig = providerConfig
}

View File

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

View File

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

View File

@ -28,16 +28,16 @@ class TokenRatesController {
async updateExchangeRates () {
if (!this.isActive) { return }
const contractExchangeRates = {}
const nativeCurrency = this.currency ? this.currency.getState().nativeCurrency.toUpperCase() : 'ETH'
const pairs = this._tokens.map(token => `pairs[]=${token.address}/${nativeCurrency}`)
const query = pairs.join('&')
const nativeCurrency = this.currency ? this.currency.getState().nativeCurrency.toLowerCase() : 'eth'
const pairs = this._tokens.map(token => token.address).join(',')
const query = `contract_addresses=${pairs}&vs_currencies=${nativeCurrency}`
if (this._tokens.length > 0) {
try {
const response = await fetch(`https://exchanges.balanc3.net/pie?${query}&autoConversion=false`)
const { prices = [] } = await response.json()
prices.forEach(({ pair, price }) => {
const address = pair.split('/')[0]
contractExchangeRates[normalizeAddress(address)] = typeof price === 'number' ? price : 0
const response = await fetch(`https://api.coingecko.com/api/v3/simple/token_price/ethereum?${query}`)
const prices = await response.json()
this._tokens.forEach(token => {
const price = prices[token.address.toLowerCase()]
contractExchangeRates[normalizeAddress(token.address)] = price ? price[nativeCurrency] : 0
})
} catch (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
})
inpageProvider.publicConfigStore.subscribe(function (state) {
if (state.onboardingcomplete) {
window.postMessage('onboardingcomplete', '*')
}
})
// need to make sure we aren't affected by overlapping namespaces
// and that we dont affect the app with our namespace
// 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/'
case 'kovan-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}"`)
}
@ -42,6 +44,8 @@ function getDefaultServiceForNetwork (network) {
return 'rinkeby-faucet'
case '42':
return 'kovan-faucet'
case '5':
return 'goerli-faucet'
}
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 = {
selectedAddress: memState.isUnlocked ? memState.selectedAddress : undefined,
networkVersion: memState.network,
onboardingcomplete: memState.completedOnboarding,
}
return result
}

View File

@ -230,7 +230,8 @@
"kovan": "ok",
"mainnet": "ok",
"rinkeby": "ok",
"ropsten": "ok"
"ropsten": "ok",
"goerli": "ok"
},
"lostAccounts": []
},
@ -320,4 +321,4 @@
"toSmartContract": false,
"fetchingData": false
}
}
}

View File

@ -704,7 +704,8 @@
"mainnet": "ok",
"ropsten": "ok",
"kovan": "ok",
"rinkeby": "ok"
"rinkeby": "ok",
"goerli": "ok"
},
"shapeShiftTxList": [],
"lostAccounts": []
@ -735,4 +736,4 @@
"os": "mac"
},
"browser": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36"
}
}

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.
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
@ -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).
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!

View File

@ -2,6 +2,13 @@
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
We try to ensure certain criteria are met before deploying:

103
package-lock.json generated
View File

@ -9759,8 +9759,8 @@
}
},
"eth-contract-metadata": {
"version": "github:MetaMask/eth-contract-metadata#92e7d1442c7585bfd24e50a0fda78df11dedadfe",
"from": "github:MetaMask/eth-contract-metadata#92e7d1442c7585bfd24e50a0fda78df11dedadfe"
"version": "github:MetaMask/eth-contract-metadata#41a14e8004bdd37eaba5af5f2bb1fc4f4ff7063f",
"from": "github:MetaMask/eth-contract-metadata#41a14e8004bdd37eaba5af5f2bb1fc4f4ff7063f"
},
"eth-ens-namehash": {
"version": "2.0.8",
@ -9806,6 +9806,31 @@
"requires": {
"bn.js": "^4.11.8",
"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",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
"bn.js": "^4.11.8",
"ethereumjs-util": "^6.0.0"
"bn.js": "^4.11.8"
}
},
"ethereumjs-util": {
@ -9896,6 +9920,31 @@
"requires": {
"bn.js": "^4.11.8",
"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",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
"bn.js": "^4.11.8",
"ethereumjs-util": "^6.0.0"
"bn.js": "^4.11.8"
}
},
"ethereumjs-util": {
@ -10076,6 +10124,33 @@
"requires": {
"bn.js": "^4.11.8",
"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",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
"bn.js": "^4.11.8",
"ethereumjs-util": "^6.0.0"
"bn.js": "^4.11.8"
}
},
"ethereumjs-util": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz",
"integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==",
"dev": true,
"requires": {
"bn.js": "^4.11.0",
"create-hash": "^1.1.2",
@ -10192,8 +10267,7 @@
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
"bn.js": "^4.11.8",
"ethereumjs-util": "^6.0.0"
"bn.js": "^4.11.8"
}
},
"ethereumjs-util": {
@ -10324,9 +10398,9 @@
}
},
"eth-method-registry": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/eth-method-registry/-/eth-method-registry-1.0.0.tgz",
"integrity": "sha1-8Ij3Wdad6f3BK3EEm83GiKMoOLY=",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/eth-method-registry/-/eth-method-registry-1.2.0.tgz",
"integrity": "sha512-m+nphH4kOxz5KTvQ+BeIKVggxAul1sp4Ev09lfxRXIEHM1t/6NQEtaErL5ddTDFXXFVtTiW8uC9edTVUTnBZNg==",
"requires": {
"ethjs": "^0.3.0"
},
@ -10502,8 +10576,7 @@
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
"bn.js": "^4.11.8",
"ethereumjs-util": "^6.0.0"
"bn.js": "^4.11.8"
}
},
"ethereumjs-util": {

View File

@ -17,6 +17,8 @@
"test:integration:build": "gulp build:scss",
"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: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: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",
@ -82,14 +84,14 @@
"ensnare": "^1.0.0",
"eth-bin-to-ops": "^1.0.1",
"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-hd-keyring": "^1.2.2",
"eth-json-rpc-filters": "^3.0.1",
"eth-json-rpc-infura": "^3.0.0",
"eth-keyring-controller": "^3.3.1",
"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-query": "^2.1.2",
"eth-sig-util": "^2.0.2",

View File

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

View File

@ -123,7 +123,7 @@ describe('MetaMask', function () {
})
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()
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')
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()
await timeout(1000)
confirmSigHeadline = await queryAsync($, '.request-signature__headline')
@ -53,7 +53,7 @@ async function runConfirmSigRequestsTest (assert, done) {
confirmSigRowValue = await queryAsync($, '.request-signature__row-value')
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()
await timeout(1000)
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[1].textContent, '1337')
confirmSigSignButton = await queryAsync($, 'button.btn-primary.btn--large')
confirmSigSignButton = await queryAsync($, 'button.btn-secondary.btn--large')
confirmSigSignButton[0].click()
await timeout(2000)

View File

@ -43,16 +43,13 @@ async function runSendFlowTest (assert, done) {
selectState.val('send new ui')
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')
sendScreenButton[1].click()
const sendTitle = await queryAsync($, '.page-container__title')
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')
assert.ok(sendFromField[0], 'send screen has a from field')
@ -72,7 +69,7 @@ async function runSendFlowTest (assert, done) {
const sendToAccountAddress = sendToFieldInput.val()
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()
const sendAmountFieldInput = await findAsync(sendAmountField, '.unit-input__input')
@ -88,7 +85,7 @@ async function runSendFlowTest (assert, done) {
errorMessage = $('.send-v2__error')
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')
sendButton[0].click()
await timeout()
@ -115,14 +112,14 @@ async function runSendFlowTest (assert, done) {
sendToFieldInputInEdit[0].focus()
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()
const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('.unit-input__input')
sendAmountFieldInputInEdit.val('1.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')
selectState.val('send new ui')

View File

@ -4,7 +4,7 @@ const InfuraController = require('../../../../app/scripts/controllers/infura')
describe('infura-controller', function () {
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 () {
infuraController = new InfuraController()
@ -58,5 +58,15 @@ describe('infura-controller', function () {
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',
expected: 'Main Ethereum Network',
}, {
input: 'goerli',
expected: 'Goerli',
},
]

View File

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

View File

@ -23,4 +23,8 @@ describe('Etherscan Network Prefix', () => {
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 {
font-size: 0.75rem;
@extend %small-link;
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_LIMIT_DEC,
MIN_GAS_PRICE_GWEI,
} = require('../send/send.constants')
} = require('../../../pages/send/send.constants')
const {
isBalanceSufficient,
} = require('../send/send.utils')
} = require('../../../pages/send/send.utils')
const {
conversionUtil,
@ -47,7 +47,7 @@ const {
const {
getGasPrice,
getGasLimit,
} = require('../send/send.selectors')
} = require('../../../pages/send/send.selectors')
function mapStateToProps (state) {
const selectedToken = getSelectedToken(state)
@ -382,7 +382,7 @@ CustomizeGasModal.prototype.render = function () {
onClick: this.props.hideModal,
}, [this.context.t('cancel')]),
h(Button, {
type: 'primary',
type: 'secondary',
className: 'send-v2__customize-gas__save',
onClick: () => !error && this.save(newGasPrice, gasLimit, gasTotal),
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(
DropdownMenuItem,
{
@ -285,6 +307,10 @@ NetworkDropdown.prototype.getNetworkName = function () {
name = this.context.t('kovan')
} else if (providerName === '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 {
name = provider.nickname || this.context.t('unknownNetwork')
}

View File

@ -62,7 +62,7 @@ describe('Network Dropdown', () => {
})
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', () => {
@ -82,16 +82,20 @@ describe('Network Dropdown', () => {
})
it('checks background color for fifth NetworkDropdownIcon', () => {
assert.equal(wrapper.find(NetworkDropdownIcon).at(4).prop('innerBorder'), '1px solid #9b9b9b')
})
it('checks dropdown for frequestRPCList from state ', () => {
assert.equal(wrapper.find(DropdownMenuItem).at(5).text(), '✓http://localhost:7545')
assert.equal(wrapper.find(NetworkDropdownIcon).at(4).prop('backgroundColor'), '#3099f2') // Goerli Blue
})
it('checks background color for sixth NetworkDropdownIcon', () => {
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 ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
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 { isValidENSAddress } = require('../../helpers/utils/util')

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@ describe('Modal Component', () => {
assert.equal(wrapper.find('.modal-container').length, 1)
const buttons = wrapper.find(Button)
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', () => {
@ -38,7 +38,7 @@ describe('Modal Component', () => {
cancelButton.simulate('click')
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(handleSubmit.callCount, 0)
submitButton.simulate('click')

View File

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

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import BigNumber from 'bignumber.js'
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 {
@ -128,7 +128,7 @@ export default class CustomizeGas extends Component {
{ t('cancel') }
</Button>
<Button
type="primary"
type="secondary"
className="customize-gas__save"
onClick={() => {
metricsEvent({

View File

@ -119,7 +119,7 @@ DepositEtherModal.prototype.renderRow = function ({
!hideButton && h('div.deposit-ether-modal__buy-row__button', [
h(Button, {
type: 'primary',
type: 'secondary',
className: 'deposit-ether-modal__deposit-button',
large: true,
onClick: onButtonClick,
@ -133,7 +133,7 @@ DepositEtherModal.prototype.render = function () {
const { network, toWyre, toCoinSwitch, address, toFaucet } = this.props
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)
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,
}, []),
h('button.btn-clear.edit-account-name-modal-save-button.allcaps', {
h('button.button.btn-secondary.edit-account-name-modal-save-button.allcaps', {
onClick: () => {
if (this.state.inputText.length !== 0) {
setAccountLabel(identity.address, this.state.inputText)

View File

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

View File

@ -67,12 +67,12 @@ HideTokenConfirmationModal.prototype.render = function () {
]),
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(),
}, [
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),
}, [
this.context.t('hide'),

View File

@ -37,11 +37,11 @@ class NotificationModal extends Component {
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,
}, 'Cancel'),
showConfirmButton && h('div.btn-clear.notification-modal__buttons__btn', {
showConfirmButton && h('div.button.btn-secondary.notification-modal__buttons__btn', {
onClick: () => {
onConfirm()
hideModal()

View File

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

View File

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

View File

@ -50,6 +50,9 @@ Network.prototype.render = function () {
} else if (providerName === 'rinkeby') {
hoverText = context.t('rinkeby')
iconName = 'rinkeby-test-network'
} else if (providerName === 'goerli') {
hoverText = context.t('goerli')
iconName = 'goerli-test-network'
} else {
hoverText = providerId
iconName = 'private-network'
@ -63,6 +66,7 @@ Network.prototype.render = function () {
'ropsten-test-network': providerName === 'ropsten' || parseInt(networkNumber) === 3,
'kovan-test-network': providerName === 'kovan',
'rinkeby-test-network': providerName === 'rinkeby',
'goerli-test-network': providerName === 'goerli',
}),
title: hoverText,
onClick: (event) => {
@ -113,33 +117,34 @@ Network.prototype.render = function () {
h('.network-name', context.t('rinkeby')),
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:
return h('.network-indicator', [
networkNumber === 'loading'
? h('span.pointer.network-indicator', {
style: {
display: 'flex',
alignItems: 'center',
flexDirection: 'row',
},
? h('span.pointer.network-loading-spinner', {
onClick: (event) => this.props.onClick(event),
}, [
h('img', {
title: context.t('attemptingConnect'),
style: {
width: '27px',
},
src: 'images/loading.svg',
}),
])
: h('i.fa.fa-question-circle.fa-lg', {
style: {
margin: '10px',
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'),
])
}

View File

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

View File

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

View File

@ -15,7 +15,7 @@ import {
setCustomGasLimit,
} from '../../../ducks/gas/gas.duck'
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 { metamask: { knownMethodData, accounts } } = state

View File

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

View File

@ -190,7 +190,7 @@ WalletView.prototype.render = function () {
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 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 {
static propTypes = {

View File

@ -2,7 +2,7 @@ import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
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', () => {
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_PRIMARY = 'btn-primary'
const CLASSNAME_SECONDARY = 'btn-secondary'
const CLASSNAME_CONFIRM = 'btn-confirm'
const CLASSNAME_CONFIRM = 'btn-primary'
const CLASSNAME_RAISED = 'btn-raised'
const CLASSNAME_LARGE = 'btn--large'
const CLASSNAME_FIRST_TIME = 'btn--first-time'
@ -14,6 +14,11 @@ const typeHash = {
default: CLASSNAME_DEFAULT,
primary: CLASSNAME_PRIMARY,
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,
raised: CLASSNAME_RAISED,
'first-time': CLASSNAME_FIRST_TIME,
@ -38,7 +43,7 @@ export default class Button extends Component {
<button
className={classnames(
'button',
typeHash[type],
typeHash[type] || CLASSNAME_DEFAULT,
large && CLASSNAME_LARGE,
className
)}

View File

@ -2,57 +2,70 @@ import React from 'react'
import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
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)
.add('primary', () =>
.add('Button - Primary', () =>
<Button
onClick={action('clicked')}
type="primary"
disabled={boolean('disabled', false)}
>
{text('text', 'Click me')}
</Button>
)
.add('secondary', () =>
.add('Button - Secondary', () =>
<Button
onClick={action('clicked')}
type="secondary"
disabled={boolean('disabled', false)}
>
{text('text', 'Click me')}
</Button>
)
.add('default', () => (
.add('Button - Default', () =>
<Button
onClick={action('clicked')}
type="default"
disabled={boolean('disabled', false)}
>
{text('text', 'Click me')}
</Button>
))
.add('large primary', () => (
)
.add('Button - Warning', () =>
<Button
onClick={action('clicked')}
type="primary"
large
type="warning"
disabled={boolean('disabled', false)}
>
{text('text', 'Click me')}
</Button>
))
.add('large secondary', () => (
)
.add('Button - Danger', () =>
<Button
onClick={action('clicked')}
type="secondary"
large
type="danger"
disabled={boolean('disabled', false)}
>
{text('text', 'Click me')}
</Button>
))
.add('large default', () => (
)
.add('Button - Danger Primary', () =>
<Button
onClick={action('clicked')}
type="default"
large
type="danger-primary"
disabled={boolean('disabled', false)}
>
{text('text', 'Click me')}
</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;
flex: 0 0 auto;
.btn-default,
.btn-confirm {
font-size: 1rem;
}
header {
display: flex;
flex-flow: row;
@ -86,9 +81,6 @@
}
&__footer-button {
height: 55px;
font-size: 1rem;
text-transform: uppercase;
margin-right: 16px;
&:last-of-type {

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
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

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';

View File

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

View File

@ -29,6 +29,10 @@
&.rinkeby-test-network .menu-icon-circle div {
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 {
@ -48,6 +52,11 @@
font-size: 12px;
padding: 0 4px;
}
.fa-question-circle {
margin: 0 4px 0 6px;
font-size: 1rem;
}
}
.network-name {
@ -165,5 +174,22 @@
}
.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 {
margin: 14.5px 18px 0px;
margin: 8px 18px 0px;
position: relative;
display: flex;
flex-flow: row;
@ -592,8 +592,8 @@
flex: 0 0 auto;
}
&__from-dropdown {
height: 73px;
&__from-dropdown,
&__asset-dropdown {
width: 100%;
border: 1px solid $alto;
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 {
position: relative;

View File

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

View File

@ -403,3 +403,40 @@
font-weight: 400;
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;
$alabaster: #fafafa;
$silver-chalice: #aeaeae;
$curious-blue: #2f9ae0;
$curious-blue: #037DD6;
$concrete: #f3f3f3;
$tundora: #4d4d4d;
$nile-blue: #1b344d;
@ -93,3 +93,19 @@ $break-large: 576px;
$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
case actions.SET_SELECTED_TOKEN:
return extend(metamaskState, {
newState = extend(metamaskState, {
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:
const account = action.value.account

View File

@ -10,4 +10,5 @@ export const NETWORK_TYPES = {
MAINNET: 'mainnet',
RINKEBY: 'rinkeby',
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_RPC_NETWORK_ID = 'networkId'
const METAMETRICS_CUSTOM_RPC_CHAIN_ID = 'chainId'
const METAMETRICS_CUSTOM_GAS_CHANGED = 'gasChanged'
const METAMETRICS_CUSTOM_NETWORK = 'network'
const METAMETRICS_CUSTOM_ENVIRONMENT_TYPE = 'environmentType'
@ -43,6 +44,7 @@ const customVariableNameIdMap = {
[METAMETRICS_CUSTOM_RPC_CHAIN_ID]: 2,
[METAMETRICS_CUSTOM_ERROR_FIELD]: 1,
[METAMETRICS_CUSTOM_ERROR_MESSAGE]: 2,
[METAMETRICS_CUSTOM_GAS_CHANGED]: 1,
}
const customDimensionsNameIdMap = {

View File

@ -30,6 +30,21 @@ export function getTokenData (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 })
/**
@ -43,7 +58,16 @@ const registry = new MethodRegistry({ provider: global.ethereumProvider })
const fourBytePrefix = prefixedData.slice(0, 10)
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) {
return {}
@ -57,8 +81,8 @@ const registry = new MethodRegistry({ provider: global.ethereumProvider })
}
} catch (error) {
log.error(error)
const contractData = getTokenData(data)
const { name } = contractData || {}
const tokenData = getTokenData(data)
const { name } = tokenData || {}
return { name }
}

View File

@ -18,6 +18,7 @@
}
&__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') }
</Button>
<Button
type="primary"
type="secondary"
large
className="page-container__footer-button"
onClick={() => {

View File

@ -18,7 +18,7 @@ const mapStateToProps = ({ metamask }) => {
const mapDispatchToProps = dispatch => {
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()),
}
}

View File

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

View File

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

View File

@ -30,7 +30,7 @@ export default class ConfirmSendEther extends Component {
return (
<ConfirmTransactionBase
action={this.context.t('confirm')}
actionKey={'confirm'}
hideData={hideData}
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 { getEnvironmentType } from '../../../../app/scripts/lib/util'
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 {
INSUFFICIENT_FUNDS_ERROR_KEY,
@ -64,7 +64,7 @@ export default class ConfirmTransactionBase extends Component {
updateGasAndCalculate: PropTypes.func,
customGas: PropTypes.object,
// Component props
action: PropTypes.string,
actionKey: PropTypes.string,
contentComponent: PropTypes.node,
dataComponent: PropTypes.node,
detailsComponent: PropTypes.node,
@ -159,7 +159,7 @@ export default class ConfirmTransactionBase extends Component {
}
handleEditGas () {
const { onEditGas, showCustomizeGasModal, action, txData: { origin }, methodData = {} } = this.props
const { onEditGas, showCustomizeGasModal, actionKey, txData: { origin }, methodData = {} } = this.props
this.context.metricsEvent({
eventOpts: {
@ -169,7 +169,7 @@ export default class ConfirmTransactionBase extends Component {
},
customVariables: {
recipientKnown: null,
functionType: action || getMethodName(methodData.name) || this.context.t('contractInteraction'),
functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction',
origin,
},
})
@ -292,7 +292,7 @@ export default class ConfirmTransactionBase extends Component {
}
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({
eventOpts: {
@ -302,7 +302,7 @@ export default class ConfirmTransactionBase extends Component {
},
customVariables: {
recipientKnown: null,
functionType: action || getMethodName(methodData.name) || this.context.t('contractInteraction'),
functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction',
origin,
},
})
@ -331,7 +331,7 @@ export default class ConfirmTransactionBase extends Component {
handleCancel () {
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) {
metricsEvent({
@ -342,7 +342,7 @@ export default class ConfirmTransactionBase extends Component {
},
customVariables: {
recipientKnown: null,
functionType: action || getMethodName(methodData.name) || this.context.t('contractInteraction'),
functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction',
origin,
},
})
@ -358,7 +358,7 @@ export default class ConfirmTransactionBase extends Component {
handleSubmit () {
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
if (submitting) {
@ -377,7 +377,7 @@ export default class ConfirmTransactionBase extends Component {
},
customVariables: {
recipientKnown: null,
functionType: action || getMethodName(methodData.name) || this.context.t('contractInteraction'),
functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction',
origin,
},
})
@ -517,7 +517,7 @@ export default class ConfirmTransactionBase extends Component {
valid: propsValid = true,
errorMessage,
errorKey: propsErrorKey,
action,
actionKey,
title,
subtitle,
hideSubtitle,
@ -543,7 +543,7 @@ export default class ConfirmTransactionBase extends Component {
toName={toName}
toAddress={toAddress}
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}
titleComponent={this.renderTitleComponent()}
subtitle={subtitle}

View File

@ -14,9 +14,9 @@ import {
GAS_LIMIT_TOO_LOW_ERROR_KEY,
} from '../../helpers/constants/error-keys'
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 { 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 {getMetaMaskAccounts, getAdvancedInlineGasShown, preferencesSelector, getIsMainnet} from '../../selectors/selectors'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -36,6 +36,20 @@ export default class ImportWithSeedPhrase extends PureComponent {
.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) {
let seedPhraseError = ''
@ -172,6 +186,10 @@ export default class ImportWithSeedPhrase extends PureComponent {
action: 'Import Seed Phrase',
name: 'Go Back from Onboarding Import',
},
customVariables: {
errorLabel: 'Seed Phrase Error',
errorMessage: seedPhraseError,
},
})
this.props.history.push(INITIALIZE_SELECT_ACTION_ROUTE)
}}
@ -243,7 +261,7 @@ export default class ImportWithSeedPhrase extends PureComponent {
</span>
</div>
<Button
type="confirm"
type="primary"
className="first-time-flow__button"
disabled={!this.isValid() || !termsChecked}
onClick={this.handleImport}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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