1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-22 17:33:23 +01:00
This commit is contained in:
Alexander Tseung 2018-01-29 18:22:52 -08:00
commit ecc39c5a7a
111 changed files with 6104 additions and 2427 deletions

View File

@ -2,6 +2,42 @@
## Current Master
## 3.13.5 2018-1-16
- Estimating gas limit for simple ether sends now faster & cheaper, by avoiding VM usage on recipients with no code.
- Add an extra px to address for Firefox clipping.
- Fix Firefox scrollbar.
- Open metamask popup for transaction confirmation before gas estimation finishes and add a loading screen over transaction confirmation.
- Fix bug that prevented eth_signTypedData from signing bytes.
- Further improve gas price estimation.
## 3.13.4 2018-1-9
- Remove recipient field if application initializes a tx with an empty string, or 0x, and tx data. Throw an error with the same condition, but without tx data.
- Improve gas price suggestion to be closer to the lowest that will be accepted.
- Throw an error if a application tries to submit a tx whose value is a decimal, and inform that it should be in wei.
- Fix bug that prevented updating custom token details.
- No longer mark long-pending transactions as failed, since we now have button to retry with higher gas.
- Fix rounding error when specifying an ether amount that has too much precision.
- Fix bug where incorrectly inputting seed phrase would prevent any future attempts from succeeding.
## 3.13.3 2017-12-14
- Show tokens that are held that have no balance.
- Reduce load on Infura by using a new block polling endpoint.
## 3.13.2 2017-12-9
- Reduce new block polling interval to 8000 ms, to ease server load.
## 3.13.1 2017-12-7
- Allow Dapps to specify a transaction nonce, allowing dapps to propose resubmit and force-cancel transactions.
## 3.13.0 2017-12-7
- Allow resubmitting transactions that are taking long to complete.
## 3.12.1 2017-11-29
- Fix bug where a user could be shown two different seed phrases.

View File

@ -1,10 +1,10 @@
# MetaMask Plugin
# MetaMask Browser Extension
[![Build Status](https://circleci.com/gh/MetaMask/metamask-extension.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-extension) [![Coverage Status](https://coveralls.io/repos/github/MetaMask/metamask-extension/badge.svg?branch=master)](https://coveralls.io/github/MetaMask/metamask-extension?branch=master) [![Greenkeeper badge](https://badges.greenkeeper.io/MetaMask/metamask-extension.svg)](https://greenkeeper.io/) [![Stories in Ready](https://badge.waffle.io/MetaMask/metamask-extension.png?label=in%20progress&title=waffle.io)](http://waffle.io/MetaMask/metamask-extension)
## Support
If you're a user seeking support, [here is our support site](http://metamask.consensyssupport.happyfox.com).
If you're a user seeking support, [here is our support site](https://metamask.helpscoutdocs.com/).
## Developing Compatible Dapps

View File

@ -0,0 +1,10 @@
{
"appName": {
"message": "MetaMask",
"description": "The name of the application"
},
"appDescription": {
"message": "이더리움 계좌 관리",
"description": "The description of the application"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

128
app/images/metamask-fox.svg Normal file
View File

@ -0,0 +1,128 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 318.6 318.6"
style="enable-background:new 0 0 318.6 318.6;" xml:space="preserve">
<style type="text/css">
.st0{fill:#161616;stroke:#161616;}
.st1{fill:#E4761B;stroke:#E4761B;stroke-linecap:round;stroke-linejoin:round;}
.st2{fill:#763D16;stroke:#763D16;stroke-linecap:round;stroke-linejoin:round;}
.st3{fill:#F6851B;stroke:#F6851B;stroke-linecap:round;stroke-linejoin:round;}
.st4{fill:#E2761B;stroke:#E2761B;stroke-linecap:round;stroke-linejoin:round;}
.st5{fill:#CD6116;stroke:#CD6116;stroke-linecap:round;stroke-linejoin:round;}
.st6{fill:#C0AD9E;stroke:#C0AD9E;stroke-linecap:round;stroke-linejoin:round;}
.st7{fill:#D7C1B3;stroke:#D7C1B3;stroke-linecap:round;stroke-linejoin:round;}
.st8{fill:#E4751F;stroke:#E4751F;stroke-linecap:round;stroke-linejoin:round;}
.st9{fill:#233447;stroke:#233447;stroke-linecap:round;stroke-linejoin:round;}
.st10{fill:#161616;stroke:#161616;stroke-linecap:round;stroke-linejoin:round;}
</style>
<polygon class="st0" points="277.3,145.6 272.3,142 280.3,134.7 274.2,129.9 282.2,123.8 276.9,119.8 285.3,79 272.7,41.1
191.6,71.4 124.1,71.4 43,41.1 30.4,79 38.9,119.8 33.5,123.8 41.5,129.9 35.4,134.7 43.4,142 38.4,145.6 49.9,159.1 32.5,213.3
48.6,268.6 105.3,253 116.3,262 138.7,277.5 177,277.5 199.4,262 210.4,253 267.1,268.6 283.3,213.3 265.8,159.1 "/>
<g>
<polygon class="st1" points="105.3,253 48.6,268.6 32.5,213.3 "/>
<polygon class="st1" points="283.3,213.3 267.1,268.6 210.4,253 "/>
<polygon class="st2" points="265.8,159.1 213.5,143.8 231.8,139 "/>
<polygon class="st2" points="49.9,159.1 84,139 102.2,143.8 "/>
<polygon class="st2" points="43.4,142 41.5,129.9 84,139 "/>
<polygon class="st2" points="272.3,142 231.8,139 274.2,129.9 "/>
<polygon class="st2" points="272.3,142 265.8,159.1 231.8,139 "/>
<polygon class="st2" points="43.4,142 84,139 49.9,159.1 "/>
<polygon class="st2" points="231.8,139 276.9,119.8 274.2,129.9 "/>
<polygon class="st2" points="84,139 41.5,129.9 38.9,119.8 "/>
<polygon class="st3" points="124.1,71.4 191.6,71.4 176.5,112.5 "/>
<polygon class="st3" points="176.5,112.5 139.2,112.5 124.1,71.4 "/>
<polygon class="st2" points="276.9,119.8 231.8,139 231,87.4 "/>
<polygon class="st2" points="102.2,143.8 84,139 84.7,87.4 "/>
<polygon class="st2" points="84.7,87.4 84,139 38.9,119.8 "/>
<polygon class="st2" points="231,87.4 231.8,139 213.5,143.8 "/>
<polygon class="st1" points="139.2,112.5 43,41.1 124.1,71.4 "/>
<polygon class="st4" points="272.7,41.1 176.5,112.5 191.6,71.4 "/>
<polygon class="st1" points="210.4,253 236.9,213.3 283.3,213.3 "/>
<polygon class="st1" points="32.5,213.3 78.9,213.3 105.3,253 "/>
<polygon class="st3" points="229.3,167.7 283.3,213.3 236.9,213.3 "/>
<polygon class="st3" points="86.4,167.7 32.5,213.3 49.9,159.1 "/>
<polygon class="st3" points="78.9,213.3 32.5,213.3 86.4,167.7 "/>
<polygon class="st3" points="229.3,167.7 265.8,159.1 283.3,213.3 "/>
<polygon class="st2" points="84.7,87.4 139.2,112.5 102.2,143.8 "/>
<polygon class="st2" points="213.5,143.8 176.5,112.5 231,87.4 "/>
<polygon class="st2" points="265.8,159.1 272.3,142 277.3,145.6 "/>
<polygon class="st2" points="49.9,159.1 38.4,145.6 43.4,142 "/>
<polygon class="st2" points="272.3,142 274.2,129.9 280.3,134.7 "/>
<polygon class="st2" points="43.4,142 35.4,134.7 41.5,129.9 "/>
<polygon class="st2" points="33.5,123.8 38.9,119.8 41.5,129.9 "/>
<polygon class="st2" points="282.2,123.8 274.2,129.9 276.9,119.8 "/>
<polygon class="st3" points="49.9,159.1 102.2,143.8 86.4,167.7 "/>
<polygon class="st3" points="265.8,159.1 229.3,167.7 213.5,143.8 "/>
<polygon class="st2" points="38.9,119.8 30.4,79 84.7,87.4 "/>
<polygon class="st2" points="231,87.4 285.3,79 276.9,119.8 "/>
<polygon class="st1" points="102.2,143.8 139.2,112.5 142.6,170.2 "/>
<polygon class="st1" points="213.5,143.8 229.3,167.7 173.1,170.2 "/>
<polygon class="st1" points="173.1,170.2 176.5,112.5 213.5,143.8 "/>
<polygon class="st1" points="142.6,170.2 86.4,167.7 102.2,143.8 "/>
<polygon class="st2" points="272.7,41.1 285.3,79 231,87.4 "/>
<polygon class="st2" points="43,41.1 139.2,112.5 84.7,87.4 "/>
<polygon class="st2" points="231,87.4 176.5,112.5 272.7,41.1 "/>
<polygon class="st2" points="84.7,87.4 30.4,79 43,41.1 "/>
<polygon class="st5" points="105.3,253 78.9,213.3 110,213.7 "/>
<polygon class="st5" points="210.4,253 205.7,213.7 236.9,213.3 "/>
<polygon class="st3" points="173.1,170.2 142.6,170.2 139.2,112.5 "/>
<polygon class="st3" points="139.2,112.5 176.5,112.5 173.1,170.2 "/>
<polygon class="st6" points="116.3,262 105.3,253 136.8,267.9 "/>
<polygon class="st6" points="178.9,267.9 210.4,253 199.4,262 "/>
<polygon class="st7" points="136.6,258.6 136.8,267.9 105.3,253 "/>
<polygon class="st7" points="179.2,258.6 210.4,253 178.9,267.9 "/>
<polygon class="st3" points="86.4,167.7 110,213.7 78.9,213.3 "/>
<polygon class="st3" points="236.9,213.3 205.7,213.7 229.3,167.7 "/>
<polygon class="st8" points="86.4,167.7 109.2,190.8 110,213.7 "/>
<polygon class="st8" points="229.3,167.7 205.7,213.7 206.6,190.8 "/>
<polygon class="st7" points="105.3,253 139.2,236.5 136.6,258.6 "/>
<polygon class="st7" points="210.4,253 179.2,258.6 176.5,236.5 "/>
<polygon class="st1" points="139.2,236.5 105.3,253 110,213.7 "/>
<polygon class="st1" points="176.5,236.5 205.7,213.7 210.4,253 "/>
<polygon class="st5" points="173.1,170.2 229.3,167.7 206.6,190.8 "/>
<polygon class="st5" points="109.2,190.8 86.4,167.7 142.6,170.2 "/>
<polygon class="st5" points="142.6,170.2 129.1,181.7 109.2,190.8 "/>
<polygon class="st5" points="206.6,190.8 186.6,181.7 173.1,170.2 "/>
<polygon class="st3" points="205.7,213.7 178.3,199.1 206.6,190.8 "/>
<polygon class="st3" points="110,213.7 109.2,190.8 137.4,199.1 "/>
<polygon class="st9" points="137.4,199.1 109.2,190.8 129.1,181.7 "/>
<polygon class="st9" points="178.3,199.1 186.6,181.7 206.6,190.8 "/>
<polygon class="st5" points="186.6,181.7 178.3,199.1 173.1,170.2 "/>
<polygon class="st5" points="129.1,181.7 142.6,170.2 137.4,199.1 "/>
<polygon class="st6" points="199.4,262 177,277.5 178.9,267.9 "/>
<polygon class="st6" points="136.8,267.9 138.7,277.5 116.3,262 "/>
<polygon class="st4" points="178.3,199.1 171.8,188.4 173.1,170.2 "/>
<polygon class="st8" points="137.4,199.1 142.6,170.2 143.9,188.4 "/>
<polygon class="st3" points="173.1,170.2 171.8,188.4 143.9,188.4 "/>
<polygon class="st3" points="143.9,188.4 142.6,170.2 173.1,170.2 "/>
<polygon class="st3" points="178.3,199.1 205.7,213.7 176.5,236.5 "/>
<polygon class="st3" points="139.2,236.5 110,213.7 137.4,199.1 "/>
<polygon class="st3" points="137.4,199.1 144,233.2 139.2,236.5 "/>
<polygon class="st3" points="176.5,236.5 171.7,233.2 178.3,199.1 "/>
<polygon class="st8" points="171.8,188.4 178.3,199.1 171.7,233.2 "/>
<polygon class="st8" points="143.9,188.4 144,233.2 137.4,199.1 "/>
<polygon class="st3" points="143.9,188.4 171.8,188.4 171.7,233.2 "/>
<polygon class="st3" points="171.7,233.2 144,233.2 143.9,188.4 "/>
<polygon class="st6" points="179.2,258.6 178.9,267.9 177,277.5 "/>
<polygon class="st6" points="138.7,277.5 136.8,267.9 136.6,258.6 "/>
<polygon class="st6" points="136.6,258.6 139,256.4 138.7,277.5 "/>
<polygon class="st6" points="177,277.5 176.7,256.4 179.2,258.6 "/>
<polygon class="st6" points="138.7,277.5 139,256.4 176.7,256.4 "/>
<polygon class="st6" points="176.7,256.4 177,277.5 138.7,277.5 "/>
<polygon class="st10" points="176.5,236.5 179.2,258.6 176.7,256.4 "/>
<polygon class="st10" points="139,256.4 136.6,258.6 139.2,236.5 "/>
<polygon class="st10" points="139.2,236.5 140.7,241.2 139,256.4 "/>
<polygon class="st10" points="176.7,256.4 175,241.2 176.5,236.5 "/>
<polygon class="st10" points="143.7,237.7 140.7,241.2 139.2,236.5 "/>
<polygon class="st10" points="176.5,236.5 175,241.2 172,237.7 "/>
<polygon class="st10" points="172,237.7 171.7,233.2 176.5,236.5 "/>
<polygon class="st10" points="139.2,236.5 144,233.2 143.7,237.7 "/>
<polygon class="st10" points="171.7,233.2 172,237.7 143.7,237.7 "/>
<polygon class="st10" points="143.7,237.7 144,233.2 171.7,233.2 "/>
<polygon class="st10" points="140.7,241.2 175,241.2 176.7,256.4 "/>
<polygon class="st10" points="176.7,256.4 139,256.4 140.7,241.2 "/>
<polygon class="st10" points="140.7,241.2 143.7,237.7 172,237.7 "/>
<polygon class="st10" points="172,237.7 175,241.2 140.7,241.2 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.6 KiB

21
app/images/popout.svg Normal file
View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
<title>popout</title>
<desc>Created with Sketch.</desc>
<defs>
<polygon id="path-1" points="-0.00035 0 10.9999 0 10.9999 10.9997 -0.00035 10.9997"></polygon>
</defs>
<g id="MetaMascara-Mobile---structured-TOKEN" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(-327.000000, -96.000000)">
<g id="popout" transform="translate(327.000000, 96.000000)">
<g id="Group-3" transform="translate(11.000000, 0.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="Clip-2"></g>
<path d="M10.9229,0.6177 C10.8209,0.3737 10.6269,0.1787 10.3819,0.0767 C10.2599,0.0267 10.1309,-0.0003 9.9999,-0.0003 L3.9999,-0.0003 C3.4479,-0.0003 2.9999,0.4477 2.9999,0.9997 C2.9999,1.5527 3.4479,1.9997 3.9999,1.9997 L7.5859,1.9997 L0.2929,9.2927 C-0.0981,9.6837 -0.0981,10.3167 0.2929,10.7067 C0.4879,10.9027 0.7439,10.9997 0.9999,10.9997 C1.2559,10.9997 1.5119,10.9027 1.7069,10.7067 L8.9999,3.4137 L8.9999,6.9997 C8.9999,7.5527 9.4479,7.9997 9.9999,7.9997 C10.5519,7.9997 10.9999,7.5527 10.9999,6.9997 L10.9999,0.9997 C10.9999,0.8697 10.9739,0.7407 10.9229,0.6177" id="Fill-1" fill="#4A4A4A" mask="url(#mask-2)"></path>
</g>
<path d="M19,10 C18.448,10 18,10.448 18,11 L18,19 C18,19.551 17.551,20 17,20 L3,20 C2.449,20 2,19.551 2,19 L2,5 C2,4.449 2.449,4 3,4 L11,4 C11.552,4 12,3.552 12,3 C12,2.448 11.552,2 11,2 L3,2 C1.346,2 0,3.346 0,5 L0,19 C0,20.654 1.346,22 3,22 L17,22 C18.654,22 20,20.654 20,19 L20,11 C20,10.448 19.552,10 19,10" id="Fill-4" fill="#4A4A4A"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -1,7 +1,7 @@
{
"name": "MetaMask",
"short_name": "Metamask",
"version": "4.0.4",
"version": "4.0.9",
"manifest_version": 2,
"author": "https://metamask.io",
"description": "Ethereum Browser Extension",

View File

@ -4,6 +4,15 @@ const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask'
const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask'
const LOCALHOST_RPC_URL = 'http://localhost:8545'
const MAINET_RPC_URL_BETA = 'https://mainnet.infura.io/metamask2'
const ROPSTEN_RPC_URL_BETA = 'https://ropsten.infura.io/metamask2'
const KOVAN_RPC_URL_BETA = 'https://kovan.infura.io/metamask2'
const RINKEBY_RPC_URL_BETA = 'https://rinkeby.infura.io/metamask2'
const DEFAULT_RPC = 'rinkeby'
const OLD_UI_NETWORK_TYPE = 'network'
const BETA_UI_NETWORK_TYPE = 'networkBeta'
global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
module.exports = {
@ -14,9 +23,22 @@ module.exports = {
kovan: KOVAN_RPC_URL,
rinkeby: RINKEBY_RPC_URL,
},
// Used for beta UI
networkBeta: {
localhost: LOCALHOST_RPC_URL,
mainnet: MAINET_RPC_URL_BETA,
ropsten: ROPSTEN_RPC_URL_BETA,
kovan: KOVAN_RPC_URL_BETA,
rinkeby: RINKEBY_RPC_URL_BETA,
},
networkNames: {
3: 'Ropsten',
4: 'Rinkeby',
42: 'Kovan',
},
enums: {
DEFAULT_RPC,
OLD_UI_NETWORK_TYPE,
BETA_UI_NETWORK_TYPE,
},
}

View File

@ -96,7 +96,7 @@ function logStreamDisconnectWarning (remoteLabel, err) {
}
function shouldInjectWeb3 () {
return doctypeCheck() || suffixCheck()
return doctypeCheck() && suffixCheck() && documentElementCheck()
}
function doctypeCheck () {
@ -104,7 +104,7 @@ function doctypeCheck () {
if (doctype) {
return doctype.name === 'html'
} else {
return false
return true
}
}
@ -121,6 +121,14 @@ function suffixCheck () {
return true
}
function documentElementCheck () {
var documentElement = document.documentElement.nodeName
if (documentElement) {
return documentElement.toLowerCase() === 'html'
}
return true
}
function redirectToPhishingWarning () {
console.log('MetaMask - redirecting to phishing warning')
window.location.href = 'https://metamask.io/phishing.html'

View File

@ -57,3 +57,4 @@ class BlacklistController {
}
module.exports = BlacklistController

View File

@ -1,18 +1,26 @@
const assert = require('assert')
const EventEmitter = require('events')
const createMetamaskProvider = require('web3-provider-engine/zero.js')
const SubproviderFromProvider = require('web3-provider-engine/subproviders/web3.js')
const createInfuraProvider = require('eth-json-rpc-infura/src/createProvider')
const ObservableStore = require('obs-store')
const ComposedStore = require('obs-store/lib/composed')
const extend = require('xtend')
const EthQuery = require('eth-query')
const createEventEmitterProxy = require('../lib/events-proxy.js')
const RPC_ADDRESS_LIST = require('../config.js').network
const DEFAULT_RPC = RPC_ADDRESS_LIST['rinkeby']
const networkConfig = require('../config.js')
const { OLD_UI_NETWORK_TYPE, DEFAULT_RPC } = networkConfig.enums
const INFURA_PROVIDER_TYPES = ['ropsten', 'rinkeby', 'kovan', 'mainnet']
module.exports = class NetworkController extends EventEmitter {
constructor (config) {
super()
this._networkEndpointVersion = OLD_UI_NETWORK_TYPE
this._networkEndpoints = this.getNetworkEndpoints(OLD_UI_NETWORK_TYPE)
this._defaultRpc = this._networkEndpoints[DEFAULT_RPC]
config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider)
this.networkStore = new ObservableStore('loading')
this.providerStore = new ObservableStore(config.provider)
@ -22,10 +30,32 @@ module.exports = class NetworkController extends EventEmitter {
this.on('networkDidChange', this.lookupNetwork)
}
async setNetworkEndpoints (version) {
if (version === this._networkEndpointVersion) {
return
}
this._networkEndpointVersion = version
this._networkEndpoints = this.getNetworkEndpoints(version)
this._defaultRpc = this._networkEndpoints[DEFAULT_RPC]
const { type } = this.getProviderConfig()
return this.setProviderType(type, true)
}
getNetworkEndpoints (version = OLD_UI_NETWORK_TYPE) {
return networkConfig[version]
}
initializeProvider (_providerParams) {
this._baseProviderParams = _providerParams
const rpcUrl = this.getCurrentRpcAddress()
this._configureStandardProvider({ rpcUrl })
const { type, rpcTarget } = this.providerStore.getState()
// map rpcTarget to rpcUrl
const opts = {
type,
rpcUrl: rpcTarget,
}
this._configureProvider(opts)
this._proxy.on('block', this._logBlock.bind(this))
this._proxy.on('error', this.verifyNetwork.bind(this))
this.ethQuery = new EthQuery(this._proxy)
@ -76,14 +106,17 @@ module.exports = class NetworkController extends EventEmitter {
return this.getRpcAddressForType(provider.type)
}
async setProviderType (type) {
async setProviderType (type, forceUpdate = false) {
assert(type !== 'rpc', `NetworkController.setProviderType - cannot connect by type "rpc"`)
// skip if type already matches
if (type === this.getProviderConfig().type) return
if (type === this.getProviderConfig().type && !forceUpdate) {
return
}
const rpcTarget = this.getRpcAddressForType(type)
assert(rpcTarget, `NetworkController - unknown rpc address for type "${type}"`)
this.providerStore.updateState({ type, rpcTarget })
this._switchNetwork({ rpcUrl: rpcTarget })
this._switchNetwork({ type })
}
getProviderConfig () {
@ -91,22 +124,67 @@ module.exports = class NetworkController extends EventEmitter {
}
getRpcAddressForType (type, provider = this.getProviderConfig()) {
if (RPC_ADDRESS_LIST[type]) return RPC_ADDRESS_LIST[type]
return provider && provider.rpcTarget ? provider.rpcTarget : DEFAULT_RPC
if (this._networkEndpoints[type]) {
return this._networkEndpoints[type]
}
return provider && provider.rpcTarget ? provider.rpcTarget : this._defaultRpc
}
//
// Private
//
_switchNetwork (providerParams) {
_switchNetwork (opts) {
this.setNetworkState('loading')
this._configureStandardProvider(providerParams)
this._configureProvider(opts)
this.emit('networkDidChange')
}
_configureStandardProvider (_providerParams) {
const providerParams = extend(this._baseProviderParams, _providerParams)
_configureProvider (opts) {
// type-based rpc endpoints
const { type } = opts
if (type) {
// type-based infura rpc endpoints
const isInfura = INFURA_PROVIDER_TYPES.includes(type)
opts.rpcUrl = this.getRpcAddressForType(type)
if (isInfura) {
this._configureInfuraProvider(opts)
// other type-based rpc endpoints
} else {
this._configureStandardProvider(opts)
}
// url-based rpc endpoints
} else {
this._configureStandardProvider(opts)
}
}
_configureInfuraProvider (opts) {
log.info('_configureInfuraProvider', opts)
const infuraProvider = createInfuraProvider({
network: opts.type,
})
const infuraSubprovider = new SubproviderFromProvider(infuraProvider)
const providerParams = extend(this._baseProviderParams, {
rpcUrl: opts.rpcUrl,
engineParams: {
pollingInterval: 8000,
blockTrackerProvider: infuraProvider,
},
dataSubprovider: infuraSubprovider,
})
const provider = createMetamaskProvider(providerParams)
this._setProvider(provider)
}
_configureStandardProvider ({ rpcUrl }) {
const providerParams = extend(this._baseProviderParams, {
rpcUrl,
engineParams: {
pollingInterval: 8000,
},
})
const provider = createMetamaskProvider(providerParams)
this._setProvider(provider)
}

View File

@ -36,22 +36,24 @@ class PreferencesController {
return this.store.getState().selectedAddress
}
addToken (rawAddress, symbol, decimals) {
async addToken (rawAddress, symbol, decimals) {
const address = normalizeAddress(rawAddress)
const newEntry = { address, symbol, decimals }
const tokens = this.store.getState().tokens
const previousIndex = tokens.find((token, index) => {
const previousEntry = tokens.find((token, index) => {
return token.address === address
})
const previousIndex = tokens.indexOf(previousEntry)
if (previousIndex) {
if (previousEntry) {
tokens[previousIndex] = newEntry
} else {
tokens.push(newEntry)
}
this.store.updateState({ tokens })
return Promise.resolve(tokens)
}

View File

@ -0,0 +1,110 @@
const ObservableStore = require('obs-store')
const extend = require('xtend')
const BN = require('ethereumjs-util').BN
const EthQuery = require('eth-query')
class RecentBlocksController {
constructor (opts = {}) {
const { blockTracker, provider } = opts
this.blockTracker = blockTracker
this.ethQuery = new EthQuery(provider)
this.historyLength = opts.historyLength || 40
const initState = extend({
recentBlocks: [],
}, opts.initState)
this.store = new ObservableStore(initState)
this.blockTracker.on('block', this.processBlock.bind(this))
this.backfill()
}
resetState () {
this.store.updateState({
recentBlocks: [],
})
}
processBlock (newBlock) {
const block = this.mapTransactionsToPrices(newBlock)
const state = this.store.getState()
state.recentBlocks.push(block)
while (state.recentBlocks.length > this.historyLength) {
state.recentBlocks.shift()
}
this.store.updateState(state)
}
backfillBlock (newBlock) {
const block = this.mapTransactionsToPrices(newBlock)
const state = this.store.getState()
if (state.recentBlocks.length < this.historyLength) {
state.recentBlocks.unshift(block)
}
this.store.updateState(state)
}
mapTransactionsToPrices (newBlock) {
const block = extend(newBlock, {
gasPrices: newBlock.transactions.map((tx) => {
return tx.gasPrice
}),
})
delete block.transactions
return block
}
async backfill() {
this.blockTracker.once('block', async (block) => {
let blockNum = block.number
let recentBlocks
let state = this.store.getState()
recentBlocks = state.recentBlocks
while (recentBlocks.length < this.historyLength) {
try {
let blockNumBn = new BN(blockNum.substr(2), 16)
const newNum = blockNumBn.subn(1).toString(10)
const newBlock = await this.getBlockByNumber(newNum)
if (newBlock) {
this.backfillBlock(newBlock)
blockNum = newBlock.number
}
state = this.store.getState()
recentBlocks = state.recentBlocks
} catch (e) {
log.error(e)
}
await this.wait()
}
})
}
async wait () {
return new Promise((resolve) => {
setTimeout(resolve, 100)
})
}
async getBlockByNumber (number) {
const bn = new BN(number)
return new Promise((resolve, reject) => {
this.ethQuery.getBlockByNumber('0x' + bn.toString(16), true, (err, block) => {
if (err) reject(err)
resolve(block)
})
})
}
}
module.exports = RecentBlocksController

View File

@ -32,6 +32,7 @@ module.exports = class TransactionController extends EventEmitter {
this.provider = opts.provider
this.blockTracker = opts.blockTracker
this.signEthTx = opts.signTransaction
this.getGasPrice = opts.getGasPrice
this.memStore = new ObservableStore({})
this.query = new EthQuery(this.provider)
@ -59,7 +60,6 @@ module.exports = class TransactionController extends EventEmitter {
this.pendingTxTracker = new PendingTransactionTracker({
provider: this.provider,
nonceTracker: this.nonceTracker,
retryTimePeriod: 86400000, // Retry 3500 blocks, or about 1 day.
publishTransaction: (rawTx) => this.query.sendRawTransaction(rawTx),
getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
getCompletedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager),
@ -138,18 +138,19 @@ module.exports = class TransactionController extends EventEmitter {
async newUnapprovedTransaction (txParams) {
log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`)
const txMeta = await this.addUnapprovedTransaction(txParams)
this.emit('newUnapprovedTx', txMeta)
const initialTxMeta = await this.addUnapprovedTransaction(txParams)
// listen for tx completion (success, fail)
return new Promise((resolve, reject) => {
this.txStateManager.once(`${txMeta.id}:finished`, (completedTx) => {
switch (completedTx.status) {
this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => {
switch (finishedTxMeta.status) {
case 'submitted':
return resolve(completedTx.hash)
return resolve(finishedTxMeta.hash)
case 'rejected':
return reject(new Error('MetaMask Tx Signature: User denied transaction signature.'))
case 'failed':
return reject(new Error(finishedTxMeta.err.message))
default:
return reject(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(completedTx.txParams)}`))
return reject(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`))
}
})
})
@ -165,11 +166,16 @@ module.exports = class TransactionController extends EventEmitter {
status: 'unapproved',
metamaskNetworkId: this.getNetwork(),
txParams: txParams,
loadingDefaults: true,
}
this.addTx(txMeta)
this.emit('newUnapprovedTx', txMeta)
// add default tx params
await this.addTxDefaults(txMeta)
txMeta.loadingDefaults = false
// save txMeta
this.addTx(txMeta)
this.txStateManager.updateTx(txMeta)
return txMeta
}
@ -177,13 +183,28 @@ module.exports = class TransactionController extends EventEmitter {
const txParams = txMeta.txParams
// ensure value
txMeta.gasPriceSpecified = Boolean(txParams.gasPrice)
const gasPrice = txParams.gasPrice || await this.query.gasPrice()
txMeta.nonceSpecified = Boolean(txParams.nonce)
let gasPrice = txParams.gasPrice
if (!gasPrice) {
gasPrice = this.getGasPrice ? this.getGasPrice() : await this.query.gasPrice()
}
txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16))
txParams.value = txParams.value || '0x0'
// set gasLimit
return await this.txGasUtil.analyzeGasUsage(txMeta)
}
async retryTransaction (txId) {
this.txStateManager.setTxStatusUnapproved(txId)
const txMeta = this.txStateManager.getTx(txId)
txMeta.lastGasPrice = txMeta.txParams.gasPrice
this.txStateManager.updateTx(txMeta, 'retryTransaction: manual retry')
}
async updateTransaction (txMeta) {
this.txStateManager.updateTx(txMeta, 'confTx: user updated transaction')
}
async updateAndApproveTransaction (txMeta) {
this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction')
await this.approveTransaction(txMeta.id)
@ -200,7 +221,12 @@ module.exports = class TransactionController extends EventEmitter {
// wait for a nonce
nonceLock = await this.nonceTracker.getNonceLock(fromAddress)
// add nonce to txParams
txMeta.txParams.nonce = ethUtil.addHexPrefix(nonceLock.nextNonce.toString(16))
const nonce = txMeta.nonceSpecified ? txMeta.txParams.nonce : nonceLock.nextNonce
if (nonce > nonceLock.nextNonce) {
const message = `Specified nonce may not be larger than account's next valid nonce.`
throw new Error(message)
}
txMeta.txParams.nonce = ethUtil.addHexPrefix(nonce.toString(16))
// add nonce debugging information to txMeta
txMeta.nonceDetails = nonceLock.nonceDetails
this.txStateManager.updateTx(txMeta, 'transactions#approveTransaction')

View File

@ -117,8 +117,6 @@ class AccountTracker extends EventEmitter {
const query = this._query
async.parallel({
balance: query.getBalance.bind(query, address),
nonce: query.getTransactionCount.bind(query, address),
code: query.getCode.bind(query, address),
}, cb)
}

View File

@ -23,7 +23,6 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
this.query = new EthQuery(config.provider)
this.nonceTracker = config.nonceTracker
// default is one day
this.retryTimePeriod = config.retryTimePeriod || 86400000
this.getPendingTransactions = config.getPendingTransactions
this.getCompletedTransactions = config.getCompletedTransactions
this.publishTransaction = config.publishTransaction
@ -106,12 +105,6 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
this.emit('tx:block-update', txMeta, latestBlockNumber)
}
if (Date.now() > txMeta.time + this.retryTimePeriod) {
const hours = (this.retryTimePeriod / 3.6e+6).toFixed(1)
const err = new Error(`Gave up submitting after ${hours} hours.`)
return this.emit('tx:failed', txMeta.id, err)
}
const firstRetryBlockNumber = txMeta.firstRetryBlockNumber || latestBlockNumber
const txBlockDistance = Number.parseInt(latestBlockNumber, 16) - Number.parseInt(firstRetryBlockNumber, 16)
@ -185,7 +178,8 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
}
async _checkIfNonceIsTaken (txMeta) {
const completed = this.getCompletedTransactions()
const address = txMeta.txParams.from
const completed = this.getCompletedTransactions(address)
const sameNonce = completed.filter((otherMeta) => {
return otherMeta.txParams.nonce === txMeta.txParams.nonce
})

View File

@ -4,6 +4,7 @@ const {
BnMultiplyByFraction,
bnToHex,
} = require('./util')
const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send.
/*
tx-utils are utility methods for Transaction manager
@ -22,7 +23,11 @@ module.exports = class txProvideUtil {
try {
estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit)
} catch (err) {
if (err.message.includes('Transaction execution error.')) {
const simulationFailed = (
err.message.includes('Transaction execution error.') ||
err.message.includes('gas required exceeds allowance or always failing transaction')
)
if (simulationFailed) {
txMeta.simulationFails = true
return txMeta
}
@ -33,14 +38,30 @@ module.exports = class txProvideUtil {
async estimateTxGas (txMeta, blockGasLimitHex) {
const txParams = txMeta.txParams
// check if gasLimit is already specified
txMeta.gasLimitSpecified = Boolean(txParams.gas)
// if not, fallback to block gasLimit
if (!txMeta.gasLimitSpecified) {
// if it is, use that value
if (txMeta.gasLimitSpecified) {
return txParams.gas
}
// if recipient has no code, gas is 21k max:
const recipient = txParams.to
const hasRecipient = Boolean(recipient)
const code = await this.query.getCode(recipient)
if (hasRecipient && (!code || code === '0x')) {
txParams.gas = SIMPLE_GAS_COST
txMeta.simpleSend = true // Prevents buffer addition
return SIMPLE_GAS_COST
}
// if not, fall back to block gasLimit
const blockGasLimitBN = hexToBn(blockGasLimitHex)
const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20)
txParams.gas = bnToHex(saferGasLimitBN)
}
// run tx
return await this.query.estimateGas(txParams)
}
@ -51,7 +72,7 @@ module.exports = class txProvideUtil {
// if gasLimit was specified and doesnt OOG,
// use original specified amount
if (txMeta.gasLimitSpecified) {
if (txMeta.gasLimitSpecified || txMeta.simpleSend) {
txMeta.estimatedGas = txParams.gas
return
}
@ -77,8 +98,26 @@ module.exports = class txProvideUtil {
}
async validateTxParams (txParams) {
if (('value' in txParams) && txParams.value.indexOf('-') === 0) {
this.validateRecipient(txParams)
if ('value' in txParams) {
const value = txParams.value.toString()
if (value.includes('-')) {
throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`)
}
if (value.includes('.')) {
throw new Error(`Invalid transaction value of ${txParams.value} number must be in wei`)
}
}
}
validateRecipient (txParams) {
if (txParams.to === '0x') {
if (txParams.data) {
delete txParams.to
} else {
throw new Error('Invalid recipient address')
}
}
return txParams
}
}

View File

@ -187,6 +187,10 @@ module.exports = class TransactionStateManger extends EventEmitter {
this._setTxStatus(txId, 'rejected')
}
// should update the status of the tx to 'unapproved'.
setTxStatusUnapproved (txId) {
this._setTxStatus(txId, 'unapproved')
}
// should update the status of the tx to 'approved'.
setTxStatusApproved (txId) {
this._setTxStatus(txId, 'approved')
@ -236,7 +240,7 @@ module.exports = class TransactionStateManger extends EventEmitter {
txMeta.status = status
this.emit(`${txMeta.id}:${status}`, txId)
this.emit(`tx:status-update`, txId, status)
if (status === 'submitted' || status === 'rejected') {
if (['submitted', 'rejected', 'failed'].includes(status)) {
this.emit(`${txMeta.id}:finished`, txMeta)
}
this.updateTx(txMeta, `txStateManager: setting status to ${status}`)

View File

@ -5,7 +5,6 @@ const Dnode = require('dnode')
const ObservableStore = require('obs-store')
const asStream = require('obs-store/lib/asStream')
const AccountTracker = require('./lib/account-tracker')
const EthQuery = require('eth-query')
const RpcEngine = require('json-rpc-engine')
const debounce = require('debounce')
const createEngineStream = require('json-rpc-middleware-stream/engineStream')
@ -23,6 +22,7 @@ const ShapeShiftController = require('./controllers/shapeshift')
const AddressBookController = require('./controllers/address-book')
const InfuraController = require('./controllers/infura')
const BlacklistController = require('./controllers/blacklist')
const RecentBlocksController = require('./controllers/recent-blocks')
const MessageManager = require('./lib/message-manager')
const PersonalMessageManager = require('./lib/personal-message-manager')
const TypedMessageManager = require('./lib/typed-message-manager')
@ -34,13 +34,15 @@ const accountImporter = require('./account-import-strategies')
const getBuyEthUrl = require('./lib/buy-eth-url')
const Mutex = require('await-semaphore').Mutex
const version = require('../manifest.json').version
const BN = require('ethereumjs-util').BN
const GWEI_BN = new BN('1000000000')
const percentile = require('percentile')
module.exports = class MetamaskController extends EventEmitter {
constructor (opts) {
super()
this.sendUpdate = debounce(this.privateSendUpdate.bind(this), 200)
this.opts = opts
@ -91,8 +93,11 @@ module.exports = class MetamaskController extends EventEmitter {
this.provider = this.initializeProvider()
this.blockTracker = this.provider._blockTracker
// eth data query tools
this.ethQuery = new EthQuery(this.provider)
this.recentBlocksController = new RecentBlocksController({
blockTracker: this.blockTracker,
provider: this.provider,
})
// account tracker watches balances, nonces, and any code at their address.
this.accountTracker = new AccountTracker({
provider: this.provider,
@ -133,7 +138,7 @@ module.exports = class MetamaskController extends EventEmitter {
signTransaction: this.keyringController.signTransaction.bind(this.keyringController),
provider: this.provider,
blockTracker: this.blockTracker,
ethQuery: this.ethQuery,
getGasPrice: this.getGasPrice.bind(this),
})
this.txController.on('newUnapprovedTx', opts.showUnapprovedTx.bind(opts))
@ -196,25 +201,30 @@ module.exports = class MetamaskController extends EventEmitter {
this.blacklistController.store.subscribe((state) => {
this.store.updateState({ BlacklistController: state })
})
this.recentBlocksController.store.subscribe((state) => {
this.store.updateState({ RecentBlocks: state })
})
this.infuraController.store.subscribe((state) => {
this.store.updateState({ InfuraController: state })
})
// manual mem state subscriptions
this.networkController.store.subscribe(this.sendUpdate.bind(this))
this.accountTracker.store.subscribe(this.sendUpdate.bind(this))
this.txController.memStore.subscribe(this.sendUpdate.bind(this))
this.balancesController.store.subscribe(this.sendUpdate.bind(this))
this.messageManager.memStore.subscribe(this.sendUpdate.bind(this))
this.personalMessageManager.memStore.subscribe(this.sendUpdate.bind(this))
this.typedMessageManager.memStore.subscribe(this.sendUpdate.bind(this))
this.keyringController.memStore.subscribe(this.sendUpdate.bind(this))
this.preferencesController.store.subscribe(this.sendUpdate.bind(this))
this.addressBookController.store.subscribe(this.sendUpdate.bind(this))
this.currencyController.store.subscribe(this.sendUpdate.bind(this))
this.noticeController.memStore.subscribe(this.sendUpdate.bind(this))
this.shapeshiftController.store.subscribe(this.sendUpdate.bind(this))
this.infuraController.store.subscribe(this.sendUpdate.bind(this))
const sendUpdate = this.sendUpdate.bind(this)
this.networkController.store.subscribe(sendUpdate)
this.accountTracker.store.subscribe(sendUpdate)
this.txController.memStore.subscribe(sendUpdate)
this.balancesController.store.subscribe(sendUpdate)
this.messageManager.memStore.subscribe(sendUpdate)
this.personalMessageManager.memStore.subscribe(sendUpdate)
this.typedMessageManager.memStore.subscribe(sendUpdate)
this.keyringController.memStore.subscribe(sendUpdate)
this.preferencesController.store.subscribe(sendUpdate)
this.recentBlocksController.store.subscribe(sendUpdate)
this.addressBookController.store.subscribe(sendUpdate)
this.currencyController.store.subscribe(sendUpdate)
this.noticeController.memStore.subscribe(sendUpdate)
this.shapeshiftController.store.subscribe(sendUpdate)
this.infuraController.store.subscribe(sendUpdate)
}
//
@ -298,6 +308,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.currencyController.store.getState(),
this.noticeController.memStore.getState(),
this.infuraController.store.getState(),
this.recentBlocksController.store.getState(),
// config manager
this.configManager.getConfig(),
this.shapeshiftController.store.getState(),
@ -342,6 +353,7 @@ module.exports = class MetamaskController extends EventEmitter {
submitPassword: nodeify(keyringController.submitPassword, keyringController),
// network management
setNetworkEndpoints: nodeify(networkController.setNetworkEndpoints, networkController),
setProviderType: nodeify(networkController.setProviderType, networkController),
setCustomRpc: nodeify(this.setCustomRpc, this),
@ -365,7 +377,9 @@ module.exports = class MetamaskController extends EventEmitter {
// txController
cancelTransaction: nodeify(txController.cancelTransaction, txController),
updateTransaction: nodeify(txController.updateTransaction, txController),
updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController),
retryTransaction: nodeify(this.retryTransaction, this),
// messageManager
signMessage: nodeify(this.signMessage, this),
@ -475,6 +489,33 @@ module.exports = class MetamaskController extends EventEmitter {
this.emit('update', this.getState())
}
getGasPrice () {
const { recentBlocksController } = this
const { recentBlocks } = recentBlocksController.store.getState()
// Return 1 gwei if no blocks have been observed:
if (recentBlocks.length === 0) {
return '0x' + GWEI_BN.toString(16)
}
const lowestPrices = recentBlocks.map((block) => {
if (!block.gasPrices || block.gasPrices.length < 1) {
return GWEI_BN
}
return block.gasPrices
.map(hexPrefix => hexPrefix.substr(2))
.map(hex => new BN(hex, 16))
.sort((a, b) => {
return a.gt(b) ? 1 : -1
})[0]
})
.map(number => number.div(GWEI_BN).toNumber())
const percentileNum = percentile(50, lowestPrices)
const percentileNumBn = new BN(percentileNum)
return '0x' + percentileNumBn.mul(GWEI_BN).toString(16)
}
//
// Vault Management
//
@ -504,10 +545,15 @@ module.exports = class MetamaskController extends EventEmitter {
async createNewVaultAndRestore (password, seed) {
const release = await this.createVaultMutex.acquire()
try {
const vault = await this.keyringController.createNewVaultAndRestore(password, seed)
this.selectFirstIdentity(vault)
release()
return vault
} catch (err) {
release()
throw err
}
}
selectFirstIdentity (vault) {
@ -576,6 +622,14 @@ module.exports = class MetamaskController extends EventEmitter {
//
// Identity Management
//
//
async retryTransaction (txId, cb) {
await this.txController.retryTransaction(txId)
const state = await this.getState()
return state
}
newUnsignedMessage (msgParams, cb) {
const msgId = this.messageManager.addUnapprovedMessage(msgParams)

View File

@ -77,7 +77,7 @@ module.exports = class NoticeController extends EventEmitter {
return uniqBy(oldNotices.concat(newNotices), 'id')
}
_filterNotices(notices) {
_filterNotices (notices) {
return notices.filter((newNotice) => {
if ('version' in newNotice) {
const satisfied = semver.satisfies(this.version, newNotice.version)

View File

@ -26,8 +26,17 @@ const container = document.getElementById('app-content')
startPopup({ container, connectionStream }, (err, store) => {
if (err) return displayCriticalError(err)
let betaUIState = store.getState().metamask.featureFlags.betaUI
let css = betaUIState ? NewMetaMaskUiCss() : OldMetaMaskUiCss()
// Code commented out until we begin auto adding users to NewUI
// const { isMascara, identities = {}, featureFlags = {} } = store.getState().metamask
// const firstTime = Object.keys(identities).length === 0
const { isMascara, featureFlags = {} } = store.getState().metamask
let betaUIState = featureFlags.betaUI
// Code commented out until we begin auto adding users to NewUI
// const useBetaCss = isMascara || firstTime || betaUIState
const useBetaCss = isMascara || betaUIState
let css = useBetaCss ? NewMetaMaskUiCss() : OldMetaMaskUiCss()
let deleteInjectedCss = injectCss(css)
let newBetaUIState

View File

@ -8,6 +8,7 @@
"frequentRpcList": [],
"unapprovedTxs": {},
"currentCurrency": "USD",
"featureFlags": {"betaUI": true},
"conversionRate": 12.7527416,
"conversionDate": 1487624341,
"noActiveNotices": false,

View File

@ -0,0 +1,739 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"isMascara": false,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
"address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"name": "Account 1"
}
},
"unapprovedTxs": {},
"noActiveNotices": true,
"frequentRpcList": [
"http://192.168.1.34:7545/"
],
"addressBook": [],
"tokenExchangeRates": {},
"coinOptions": {},
"provider": {
"type": "mainnet",
"rpcTarget": "https://mainnet.infura.io/metamask"
},
"network": "1",
"accounts": {
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
"code": "0x",
"balance": "0x1b3f641ed0c2f62",
"nonce": "0x35",
"address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
}
},
"currentBlockGasLimit": "0x66df83",
"selectedAddressTxList": [
{
"id": 3516145537630216,
"time": 1512615655535,
"status": "submitted",
"metamaskNetworkId": "1",
"txParams": {
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"value": "0x16345785d8a0000",
"gasPrice": "0xc1b710800",
"gas": "0x7b0c",
"nonce": "0x35",
"chainId": "0x1"
},
"gasPriceSpecified": false,
"gasLimitSpecified": false,
"estimatedGas": "5208",
"history": [
{
"id": 3516145537630216,
"time": 1512615655535,
"status": "unapproved",
"metamaskNetworkId": "1",
"txParams": {
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"value": "0x16345785d8a0000",
"gasPrice": "0xe6f7cec00",
"gas": "0x7b0c"
},
"gasPriceSpecified": false,
"gasLimitSpecified": false,
"estimatedGas": "5208"
},
[
{
"op": "replace",
"path": "/txParams/gasPrice",
"value": "0xc1b710800",
"note": "confTx: user approved transaction"
}
],
[
{
"op": "replace",
"path": "/status",
"value": "approved",
"note": "txStateManager: setting status to approved"
}
],
[
{
"op": "add",
"path": "/txParams/nonce",
"value": "0x35",
"note": "transactions#approveTransaction"
},
{
"op": "add",
"path": "/nonceDetails",
"value": {
"params": {
"highestLocalNonce": 53,
"highestSuggested": 53,
"nextNetworkNonce": 53
},
"local": {
"name": "local",
"nonce": 53,
"details": {
"startPoint": 53,
"highest": 53
}
},
"network": {
"name": "network",
"nonce": 53,
"details": {
"baseCount": 53
}
}
}
}
],
[
{
"op": "add",
"path": "/txParams/chainId",
"value": "0x1",
"note": "txStateManager: setting status to signed"
},
{
"op": "replace",
"path": "/status",
"value": "signed"
}
],
[
{
"op": "add",
"path": "/rawTx",
"value": "0xf86c35850c1b710800827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a0f5142ba79a13ca7ec65548953017edafb217803244bbf9821d9ad077d89921e9a03afcb614169c90be9905d5b469d06984825c76675d3a535937cdb8f2ad1c0a95",
"note": "transactions#publishTransaction"
}
],
[
{
"op": "add",
"path": "/hash",
"value": "0x7ce19c0d128ca11293b44a4e6d3cc9063665c00ea8c8eb400f548e132c147353",
"note": "transactions#setTxHash"
}
],
[
{
"op": "replace",
"path": "/status",
"value": "submitted",
"note": "txStateManager: setting status to submitted"
}
],
[
{
"op": "add",
"path": "/firstRetryBlockNumber",
"value": "0x478ab3",
"note": "transactions/pending-tx-tracker#event: tx:block-update"
}
]
],
"nonceDetails": {
"params": {
"highestLocalNonce": 53,
"highestSuggested": 53,
"nextNetworkNonce": 53
},
"local": {
"name": "local",
"nonce": 53,
"details": {
"startPoint": 53,
"highest": 53
}
},
"network": {
"name": "network",
"nonce": 53,
"details": {
"baseCount": 53
}
}
},
"rawTx": "0xf86c35850c1b710800827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a0f5142ba79a13ca7ec65548953017edafb217803244bbf9821d9ad077d89921e9a03afcb614169c90be9905d5b469d06984825c76675d3a535937cdb8f2ad1c0a95",
"hash": "0x7ce19c0d128ca11293b44a4e6d3cc9063665c00ea8c8eb400f548e132c147353",
"firstRetryBlockNumber": "0x478ab3"
},
{
"id": 3516145537630211,
"time": 1512613432658,
"status": "confirmed",
"metamaskNetworkId": "1",
"txParams": {
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"value": "0x16345785d8a0000",
"gasPrice": "0xba43b7400",
"gas": "0x7b0c",
"nonce": "0x34",
"chainId": "0x1"
},
"gasPriceSpecified": false,
"gasLimitSpecified": false,
"estimatedGas": "5208",
"history": [
{
"id": 3516145537630211,
"time": 1512613432658,
"status": "unapproved",
"metamaskNetworkId": "1",
"txParams": {
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"value": "0x16345785d8a0000",
"gasPrice": "0xdf8475800",
"gas": "0x7b0c"
},
"gasPriceSpecified": false,
"gasLimitSpecified": false,
"estimatedGas": "5208"
},
[
{
"op": "replace",
"path": "/txParams/gasPrice",
"value": "0xba43b7400",
"note": "confTx: user approved transaction"
}
],
[
{
"op": "replace",
"path": "/status",
"value": "approved",
"note": "txStateManager: setting status to approved"
}
],
[
{
"op": "add",
"path": "/txParams/nonce",
"value": "0x34",
"note": "transactions#approveTransaction"
},
{
"op": "add",
"path": "/nonceDetails",
"value": {
"params": {
"highestLocalNonce": 52,
"highestSuggested": 52,
"nextNetworkNonce": 52
},
"local": {
"name": "local",
"nonce": 52,
"details": {
"startPoint": 52,
"highest": 52
}
},
"network": {
"name": "network",
"nonce": 52,
"details": {
"baseCount": 52
}
}
}
}
],
[
{
"op": "add",
"path": "/txParams/chainId",
"value": "0x1",
"note": "txStateManager: setting status to signed"
},
{
"op": "replace",
"path": "/status",
"value": "signed"
}
],
[
{
"op": "add",
"path": "/rawTx",
"value": "0xf86c34850ba43b7400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a073a4afdb8e8ad32b0cf9039af56c66baffd60d30e75cee5c1b783208824eafb8a0021ca6c1714a2c71281333ab77f776d3514348ab77967280fca8a5b4be44285e",
"note": "transactions#publishTransaction"
}
],
[
{
"op": "add",
"path": "/hash",
"value": "0x5c98409883fdfd3cd24058a83b91470da6c40ffae41a40eb90d7dee0b837d26d",
"note": "transactions#setTxHash"
}
],
[
{
"op": "replace",
"path": "/status",
"value": "submitted",
"note": "txStateManager: setting status to submitted"
}
],
[
{
"op": "add",
"path": "/firstRetryBlockNumber",
"value": "0x478a2c",
"note": "transactions/pending-tx-tracker#event: tx:block-update"
}
],
[
{
"op": "replace",
"path": "/status",
"value": "confirmed",
"note": "txStateManager: setting status to confirmed"
}
]
],
"nonceDetails": {
"params": {
"highestLocalNonce": 52,
"highestSuggested": 52,
"nextNetworkNonce": 52
},
"local": {
"name": "local",
"nonce": 52,
"details": {
"startPoint": 52,
"highest": 52
}
},
"network": {
"name": "network",
"nonce": 52,
"details": {
"baseCount": 52
}
}
},
"rawTx": "0xf86c34850ba43b7400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a073a4afdb8e8ad32b0cf9039af56c66baffd60d30e75cee5c1b783208824eafb8a0021ca6c1714a2c71281333ab77f776d3514348ab77967280fca8a5b4be44285e",
"hash": "0x5c98409883fdfd3cd24058a83b91470da6c40ffae41a40eb90d7dee0b837d26d",
"firstRetryBlockNumber": "0x478a2c"
},
{
"id": 3516145537630210,
"time": 1512612826136,
"status": "confirmed",
"metamaskNetworkId": "1",
"txParams": {
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"value": "0x16345785d8a0000",
"gasPrice": "0xa7a358200",
"gas": "0x7b0c",
"nonce": "0x33",
"chainId": "0x1"
},
"gasPriceSpecified": false,
"gasLimitSpecified": false,
"estimatedGas": "5208",
"history": [
{
"id": 3516145537630210,
"time": 1512612826136,
"status": "unapproved",
"metamaskNetworkId": "1",
"txParams": {
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"value": "0x16345785d8a0000",
"gasPrice": "0xba43b7400",
"gas": "0x7b0c"
},
"gasPriceSpecified": false,
"gasLimitSpecified": false,
"estimatedGas": "5208"
},
[
{
"op": "replace",
"path": "/txParams/gasPrice",
"value": "0xa7a358200",
"note": "confTx: user approved transaction"
}
],
[
{
"op": "replace",
"path": "/status",
"value": "approved",
"note": "txStateManager: setting status to approved"
}
],
[
{
"op": "add",
"path": "/txParams/nonce",
"value": "0x33",
"note": "transactions#approveTransaction"
},
{
"op": "add",
"path": "/nonceDetails",
"value": {
"params": {
"highestLocalNonce": 0,
"highestSuggested": 51,
"nextNetworkNonce": 51
},
"local": {
"name": "local",
"nonce": 51,
"details": {
"startPoint": 51,
"highest": 51
}
},
"network": {
"name": "network",
"nonce": 51,
"details": {
"baseCount": 51
}
}
}
}
],
[
{
"op": "add",
"path": "/txParams/chainId",
"value": "0x1",
"note": "txStateManager: setting status to signed"
},
{
"op": "replace",
"path": "/status",
"value": "signed"
}
],
[
{
"op": "add",
"path": "/rawTx",
"value": "0xf86c33850a7a358200827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a0021a8cd6c10208cc593e22af53637e5d127cee5cc6f9443a3e758a02afff1d7ca025f7420e974d3f2c668c165040987c72543a8e709bfea3528a62836a6ced9ce8",
"note": "transactions#publishTransaction"
}
],
[
{
"op": "add",
"path": "/hash",
"value": "0x289772800898bc9cd414530d8581c0da257a9055e4aaaa6d10d92d700bfbd044",
"note": "transactions#setTxHash"
}
],
[
{
"op": "replace",
"path": "/status",
"value": "submitted",
"note": "txStateManager: setting status to submitted"
}
],
[
{
"op": "add",
"path": "/firstRetryBlockNumber",
"value": "0x478a04",
"note": "transactions/pending-tx-tracker#event: tx:block-update"
}
],
[
{
"op": "replace",
"path": "/status",
"value": "confirmed",
"note": "txStateManager: setting status to confirmed"
}
]
],
"nonceDetails": {
"params": {
"highestLocalNonce": 0,
"highestSuggested": 51,
"nextNetworkNonce": 51
},
"local": {
"name": "local",
"nonce": 51,
"details": {
"startPoint": 51,
"highest": 51
}
},
"network": {
"name": "network",
"nonce": 51,
"details": {
"baseCount": 51
}
}
},
"rawTx": "0xf86c33850a7a358200827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a0021a8cd6c10208cc593e22af53637e5d127cee5cc6f9443a3e758a02afff1d7ca025f7420e974d3f2c668c165040987c72543a8e709bfea3528a62836a6ced9ce8",
"hash": "0x289772800898bc9cd414530d8581c0da257a9055e4aaaa6d10d92d700bfbd044",
"firstRetryBlockNumber": "0x478a04"
},
{
"id": 3516145537630209,
"time": 1512612809252,
"status": "failed",
"metamaskNetworkId": "1",
"txParams": {
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"value": "0x16345785d8a0000",
"gasPrice": "0x77359400",
"gas": "0x7b0c",
"nonce": "0x33",
"chainId": "0x1"
},
"gasPriceSpecified": false,
"gasLimitSpecified": false,
"estimatedGas": "5208",
"history": [
{
"id": 3516145537630209,
"time": 1512612809252,
"status": "unapproved",
"metamaskNetworkId": "1",
"txParams": {
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"value": "0x16345785d8a0000",
"gasPrice": "0xba43b7400",
"gas": "0x7b0c"
},
"gasPriceSpecified": false,
"gasLimitSpecified": false,
"estimatedGas": "5208"
},
[
{
"op": "replace",
"path": "/txParams/gasPrice",
"value": "0x77359400",
"note": "confTx: user approved transaction"
}
],
[
{
"op": "replace",
"path": "/status",
"value": "approved",
"note": "txStateManager: setting status to approved"
}
],
[
{
"op": "add",
"path": "/txParams/nonce",
"value": "0x33",
"note": "transactions#approveTransaction"
},
{
"op": "add",
"path": "/nonceDetails",
"value": {
"params": {
"highestLocalNonce": 0,
"highestSuggested": 51,
"nextNetworkNonce": 51
},
"local": {
"name": "local",
"nonce": 51,
"details": {
"startPoint": 51,
"highest": 51
}
},
"network": {
"name": "network",
"nonce": 51,
"details": {
"baseCount": 51
}
}
}
}
],
[
{
"op": "add",
"path": "/txParams/chainId",
"value": "0x1",
"note": "txStateManager: setting status to signed"
},
{
"op": "replace",
"path": "/status",
"value": "signed"
}
],
[
{
"op": "add",
"path": "/rawTx",
"value": "0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7",
"note": "transactions#publishTransaction"
}
],
[
{
"op": "add",
"path": "/err",
"value": {
"message": "Error: [ethjs-rpc] rpc error with payload {\"id\":7801900228852,\"jsonrpc\":\"2.0\",\"params\":[\"0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7\"],\"method\":\"eth_sendRawTransaction\"} Error: transaction underpriced",
"stack": "Error: [ethjs-rpc] rpc error with payload {\"id\":7801900228852,\"jsonrpc\":\"2.0\",\"params\":[\"0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7\"],\"method\":\"eth_sendRawTransaction\"} Error: transaction underpriced\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:60327:26\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:88030:9\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16678:16\n at replenish (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16522:25)\n at iterateeCallback (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16512:17)\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16694:16\n at resultObj.id (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:88012:9)\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16813:16\n at replenish (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16527:17)\n at iterateeCallback (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16512:17)"
}
}
],
[
{
"op": "replace",
"path": "/status",
"value": "failed",
"note": "txStateManager: setting status to failed"
}
]
],
"nonceDetails": {
"params": {
"highestLocalNonce": 0,
"highestSuggested": 51,
"nextNetworkNonce": 51
},
"local": {
"name": "local",
"nonce": 51,
"details": {
"startPoint": 51,
"highest": 51
}
},
"network": {
"name": "network",
"nonce": 51,
"details": {
"baseCount": 51
}
}
},
"rawTx": "0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7",
"err": {
"message": "Error: [ethjs-rpc] rpc error with payload {\"id\":7801900228852,\"jsonrpc\":\"2.0\",\"params\":[\"0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7\"],\"method\":\"eth_sendRawTransaction\"} Error: transaction underpriced",
"stack": "Error: [ethjs-rpc] rpc error with payload {\"id\":7801900228852,\"jsonrpc\":\"2.0\",\"params\":[\"0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7\"],\"method\":\"eth_sendRawTransaction\"} Error: transaction underpriced\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:60327:26\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:88030:9\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16678:16\n at replenish (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16522:25)\n at iterateeCallback (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16512:17)\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16694:16\n at resultObj.id (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:88012:9)\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16813:16\n at replenish (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16527:17)\n at iterateeCallback (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16512:17)"
}
}
],
"unapprovedMsgs": {},
"unapprovedMsgCount": 0,
"unapprovedPersonalMsgs": {},
"unapprovedPersonalMsgCount": 0,
"unapprovedTypedMessages": {},
"unapprovedTypedMessagesCount": 0,
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
],
"keyrings": [
{
"type": "HD Key Tree",
"accounts": [
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
]
}
],
"computedBalances": {},
"currentAccountTab": "history",
"tokens": [
{
"address": "0x0d8775f648430679a709e98d2b0cb6250d2887ef",
"symbol": "BAT",
"decimals": "18"
}
],
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"currentCurrency": "usd",
"conversionRate": 418.62,
"conversionDate": 1512615622,
"infuraNetworkStatus": {
"mainnet": "ok",
"ropsten": "ok",
"kovan": "ok",
"rinkeby": "ok"
},
"shapeShiftTxList": [],
"lostAccounts": []
},
"appState": {
"shouldClose": true,
"menuOpen": false,
"currentView": {
"name": "accountDetail",
"context": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
},
"accountDetail": {
"subview": "transactions",
"accountExport": "none",
"privateKey": ""
},
"transForward": false,
"isLoading": false,
"warning": null,
"forgottenPassword": false,
"scrollToBottom": false
},
"identities": {},
"version": "3.12.1",
"platform": {
"arch": "x86-64",
"nacl_arch": "x86-64",
"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

@ -4,8 +4,8 @@ metamascara:
ports:
- "9001"
environment:
MASCARA_ORIGIN: "https://zero.metamask.io"
MASCARA_ORIGIN: "https://wallet.metamask.io"
VIRTUAL_PORT: "9001"
VIRTUAL_HOST: "zero.metamask.io"
LETSENCRYPT_HOST: "zero.metamask.io"
VIRTUAL_HOST: "wallet.metamask.io"
LETSENCRYPT_HOST: "wallet.metamask.io"
LETSENCRYPT_EMAIL: "admin@metamask.io"

View File

@ -19,10 +19,14 @@ var manifest = require('./app/manifest.json')
var gulpif = require('gulp-if')
var replace = require('gulp-replace')
var mkdirp = require('mkdirp')
var asyncEach = require('async/each')
var exec = require('child_process').exec
var sass = require('gulp-sass')
var autoprefixer = require('gulp-autoprefixer')
var gulpStylelint = require('gulp-stylelint')
var stylefmt = require('gulp-stylefmt')
var uglify = require('gulp-uglify-es').default
var babel = require('gulp-babel')
var disableDebugTools = gutil.env.disableDebugTools
@ -159,6 +163,18 @@ gulp.task('copy:watch', function(){
gulp.watch(['./app/{_locales,images}/*', './app/scripts/chromereload.js', './app/*.{html,json}'], gulp.series('copy'))
})
// record deps
gulp.task('deps', function (cb) {
exec('npm ls', (err, stdoutOutput, stderrOutput) => {
if (err) return cb(err)
const browsers = ['firefox','chrome','edge','opera']
asyncEach(browsers, (target, done) => {
fs.writeFile(`./dist/${target}/deps.txt`, stdoutOutput, done)
}, cb)
})
})
// lint js
gulp.task('lint', function () {
@ -232,8 +248,18 @@ var jsDevStrings = jsFiles.map(jsFile => `dev:js:${jsFile}`)
var jsBuildStrings = jsFiles.map(jsFile => `build:js:${jsFile}`)
jsFiles.forEach((jsFile) => {
gulp.task(`dev:js:${jsFile}`, bundleTask({ watch: true, label: jsFile, filename: `${jsFile}.js` }))
gulp.task(`build:js:${jsFile}`, bundleTask({ watch: false, label: jsFile, filename: `${jsFile}.js` }))
gulp.task(`dev:js:${jsFile}`, bundleTask({
watch: true,
label: jsFile,
filename: `${jsFile}.js`,
isBuild: false
}))
gulp.task(`build:js:${jsFile}`, bundleTask({
watch: false,
label: jsFile,
filename: `${jsFile}.js`,
isBuild: true
}))
})
// inpage must be built before all other scripts:
@ -267,12 +293,18 @@ gulp.task('zip:edge', zipTask('edge'))
gulp.task('zip:opera', zipTask('opera'))
gulp.task('zip', gulp.parallel('zip:chrome', 'zip:firefox', 'zip:edge', 'zip:opera'))
// set env var for production
gulp.task('apply-prod-environment', function(done) {
process.env.NODE_ENV = 'production'
done()
});
// high level tasks
gulp.task('dev', gulp.series('build:scss', 'dev:js', 'copy', gulp.parallel('watch:scss', 'copy:watch', 'dev:reload')))
gulp.task('build', gulp.series('clean', 'build:scss', gulp.parallel('build:js', 'copy')))
gulp.task('dist', gulp.series('build', 'zip'))
gulp.task('build', gulp.series('clean', 'build:scss', gulp.parallel('build:js', 'copy', 'deps')))
gulp.task('dist', gulp.series('apply-prod-environment', 'build', 'zip'))
// task generators
@ -365,7 +397,6 @@ function bundleTask(opts) {
throw err
}
})
// convert bundle stream to gulp vinyl stream
.pipe(source(opts.filename))
// inject variables into bundle
@ -375,6 +406,8 @@ function bundleTask(opts) {
// sourcemaps
// loads map from browserify file
.pipe(gulpif(debug, sourcemaps.init({ loadMaps: true })))
// Minification
.pipe(gulpif(opts.isBuild, uglify()))
// writes .map file
.pipe(gulpif(debug, sourcemaps.write('./')))
// write completed bundles

View File

@ -2,6 +2,7 @@ const path = require('path')
const express = require('express')
const createBundle = require('./util').createBundle
const serveBundle = require('./util').serveBundle
const compression = require('compression')
module.exports = createMetamascaraServer
@ -16,6 +17,8 @@ function createMetamascaraServer () {
// serve bundles
const server = express()
server.use(compression())
// ui window
serveBundle(server, '/ui.js', uiBundle)
server.use(express.static(path.join(__dirname, '/../ui/'), { setHeaders: (res) => res.set('X-Frame-Options', 'DENY') }))

View File

@ -23,7 +23,9 @@ function createBundle (entryPoint) {
cache: {},
packageCache: {},
plugin: [watchify],
}).transform('babelify')
})
.transform('babelify')
.transform('uglifyify', { global: true })
bundler.on('update', bundle)
bundle()

View File

@ -5,6 +5,8 @@ import { createNewVaultAndKeychain } from '../../../../ui/app/actions'
import LoadingScreen from './loading-screen'
import Breadcrumbs from './breadcrumbs'
import { DEFAULT_ROUTE, IMPORT_ACCOUNT_ROUTE } from '../../../../ui/app/routes'
import EventEmitter from 'events'
import Mascot from '../../../../ui/app/components/mascot'
class CreatePasswordScreen extends Component {
static propTypes = {
@ -20,6 +22,11 @@ class CreatePasswordScreen extends Component {
confirmPassword: '',
}
constructor () {
super()
this.animationEventEmitter = new EventEmitter()
}
componentWillMount () {
const { isInitialized, isUnlocked, history } = this.props
if (isInitialized || isUnlocked) {
@ -56,12 +63,25 @@ class CreatePasswordScreen extends Component {
render () {
const { isLoading } = this.props
return (
<div className="first-time-flow">
{
isLoading
return isLoading
? <LoadingScreen loadingMessage="Creating your new account" />
: (
<div>
<h2 className="alpha-warning">Warning This is Experimental software and is a Developer BETA </h2>
<div className="first-view-main">
<div className="mascara-info">
<Mascot
animationEventEmitter={this.animationEventEmitter}
width="225"
height="225"
/>
<div className="info">
MetaMask is a secure identity vault for Ethereum.
</div>
<div className="info">
It allows you to hold ether & tokens, and interact with decentralized applications.
</div>
</div>
<div className="create-password">
<div className="create-password__title">
Create Password
@ -109,8 +129,7 @@ class CreatePasswordScreen extends Component {
{ */ }
<Breadcrumbs total={3} currentIndex={0} />
</div>
)
}
</div>
</div>
)
}

View File

@ -1,3 +1,4 @@
.first-time-flow {
height: 100vh;
width: 100vw;
@ -5,6 +6,36 @@
overflow: auto;
}
.alpha-warning {
background: #f7861c;
color: #fff;
line-height: 2em;
padding-left: 2em;
}
.first-view-main {
display: flex;
flex-direction: row-reverse;
justify-content: space-between;
}
.mascara-info {
display: flex;
flex-flow: column;
margin-top: 70px;
margin-right: 10vw;
width: 35vw;
max-width: 550px;
}
.mascara-info :first-child {
align-self: flex-end;
}
.info {
font-size: 19px;
}
.create-password,
.unique-image,
.tou,
@ -540,11 +571,10 @@ button.backup-phrase__confirm-seed-option:hover {
text-transform: uppercase;
margin: 35px 0 14px;
transition: 200ms ease-in-out;
background: #f7861c;
background-color: rgba(247, 134, 28, 0.9);
}
button.first-time-flow__button[disabled] {
background-color: rgba(247, 134, 28, 0.9);
opacity: .6;
}

View File

@ -4,5 +4,3 @@ When you log in to MetaMask, your current account is visible to every new site y
For your privacy, for now, please sign out of MetaMask when you're done using a site.
Also, by default, you will be signed in to a test network. To use real Ether, you must connect to the main network manually in the top left network menu.

File diff suppressed because one or more lines are too long

View File

@ -78,9 +78,10 @@ AccountDetailScreen.prototype.render = function () {
address: selected,
}),
]),
h('div.flex-column', {
h('flex-column', {
style: {
lineHeight: '10px',
marginLeft: '15px',
width: '100%',
},
}, [
@ -101,7 +102,7 @@ AccountDetailScreen.prototype.render = function () {
{
style: {
display: 'flex',
justifyContent: 'space-between',
justifyContent: 'flex-start',
alignItems: 'center',
},
},
@ -131,6 +132,8 @@ AccountDetailScreen.prototype.render = function () {
AccountDropdowns,
{
style: {
marginRight: '8px',
marginLeft: 'auto',
cursor: 'pointer',
},
selected,
@ -144,6 +147,7 @@ AccountDetailScreen.prototype.render = function () {
]),
h('.flex-row', {
style: {
width: '15em',
justifyContent: 'space-between',
alignItems: 'baseline',
},
@ -157,10 +161,10 @@ AccountDetailScreen.prototype.render = function () {
textOverflow: 'ellipsis',
paddingTop: '3px',
width: '5em',
height: '15px',
fontSize: '13px',
fontFamily: 'Montserrat Light',
textRendering: 'geometricPrecision',
marginTop: '15px',
marginBottom: '15px',
color: '#AEAEAE',
},
@ -188,7 +192,7 @@ AccountDetailScreen.prototype.render = function () {
},
}),
h('div', {}, [
h('.flex-grow'),
h('button', {
onClick: () => props.dispatch(actions.buyEthView(selected)),
@ -199,12 +203,11 @@ AccountDetailScreen.prototype.render = function () {
onClick: () => props.dispatch(actions.showSendPage()),
style: {
marginBottom: '20px',
marginRight: '8px',
},
}, 'SEND'),
]),
]),
]),
// subview (tx history, pk export confirm, buy eth warning)

View File

@ -34,6 +34,7 @@ const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete')
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
const { BETA_UI_NETWORK_TYPE } = require('../../app/scripts/config').enums
module.exports = connect(mapStateToProps)(App)
@ -396,7 +397,7 @@ App.prototype.renderDropdown = function () {
h(DropdownMenuItem, {
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
onClick: () => { this.props.dispatch(actions.lockMetamask()) },
}, 'Lock'),
}, 'Log Out'),
h(DropdownMenuItem, {
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
@ -405,7 +406,10 @@ App.prototype.renderDropdown = function () {
h(DropdownMenuItem, {
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
onClick: () => { this.props.dispatch(actions.setFeatureFlag('betaUI', true)) },
onClick: () => {
this.props.dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
.then(() => this.props.dispatch(actions.setNetworkEndpoints(BETA_UI_NETWORK_TYPE)))
},
}, 'Try Beta!'),
])
}
@ -466,11 +470,6 @@ App.prototype.renderPrimary = function () {
})
}
if (props.seedWords) {
log.debug('rendering seed words')
return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'})
}
// show initialize screen
if (!props.isInitialized || props.forgottenPassword) {
// show current view
@ -505,6 +504,12 @@ App.prototype.renderPrimary = function () {
}
}
// show seed words screen
if (props.seedWords) {
log.debug('rendering seed words')
return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'})
}
// show current view
switch (props.currentView.name) {

View File

@ -38,6 +38,16 @@ PendingTx.prototype.render = function () {
const txMeta = this.gatherTxMeta()
const txParams = txMeta.txParams || {}
// Allow retry txs
const { lastGasPrice } = txMeta
let forceGasMin
if (lastGasPrice) {
const stripped = ethUtil.stripHexPrefix(lastGasPrice)
const lastGas = new BN(stripped, 16)
const priceBump = lastGas.divn('10')
forceGasMin = lastGas.add(priceBump)
}
// Account Details
const address = txParams.from || props.selectedAddress
const identity = props.identities[address] || { address: address }
@ -199,7 +209,7 @@ PendingTx.prototype.render = function () {
precision: 9,
scale: 9,
suffix: 'GWEI',
min: MIN_GAS_PRICE_BN,
min: forceGasMin || MIN_GAS_PRICE_BN,
style: {
position: 'relative',
top: '5px',

View File

@ -4,6 +4,7 @@ const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('../../ui/app/actions')
const NetworkIndicator = require('./components/network')
const LoadingIndicator = require('./components/loading')
const txHelper = require('../lib/tx-helper')
const isPopupOrNotification = require('../../app/scripts/lib/is-popup-or-notification')
@ -60,6 +61,11 @@ ConfirmTxScreen.prototype.render = function () {
h('.flex-column.flex-grow', [
h(LoadingIndicator, {
isLoading: txData.loadingDefaults,
loadingMessage: 'Estimating transaction cost…',
}),
// subtitle and nav
h('.section-title.flex-row.flex-center', [
!isNotification ? h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {

View File

@ -119,7 +119,7 @@ ConfigScreen.prototype.render = function () {
if (err) {
state.dispatch(actions.displayWarning('Error in retrieving state logs.'))
} else {
exportAsFile('MetaMask State Logs', result)
exportAsFile('MetaMask State Logs.json', result)
}
})
},

View File

@ -21,6 +21,7 @@ html, body {
background: #F7F7F7;
margin: 0;
padding: 0;
height: 100%;
}
html {
@ -107,6 +108,10 @@ button:not([disabled]):active, input[type="submit"]:not([disabled]):active {
transform: scale(0.95);
}
.grow-on-hover:hover {
transform: scale(1.05);
}
a {
text-decoration: none;
color: inherit;
@ -436,12 +441,14 @@ input.large-input {
.account-detail-section {
display: flex;
flex-wrap: wrap;
overflow-x: hidden;
overflow-y: auto;
max-height: 465px;
flex-direction: inherit;
}
.name-label {
.account-detail-section .name-label {
margin-left: 15px;
}
}
.grow-tenx {

View File

@ -247,10 +247,26 @@ SendTransactionScreen.prototype.onSubmit = function () {
const recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '')
const nickname = state.nickname || ' '
const input = document.querySelector('input[name="amount"]').value
const parts = input.split('')
let message
if (isNaN(input) || input === '') {
message = 'Invalid ether value.'
return this.props.dispatch(actions.displayWarning(message))
}
if (parts[1]) {
var decimal = parts[1]
if (decimal.length > 18) {
message = 'Ether amount is too precise.'
return this.props.dispatch(actions.displayWarning(message))
}
}
const value = util.normalizeEthStringToWei(input)
const txData = document.querySelector('input[name="txData"]').value
const balance = this.props.balance
let message
if (value.gt(balance)) {
message = 'Insufficient funds.'

View File

@ -9,11 +9,11 @@
"ui": "npm run test:flat:build:states && beefy ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
"mock": "beefy mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
"watch": "mocha watch --recursive \"test/unit/**/*.js\"",
"mascara": "METAMASK_DEBUG=true node ./mascara/example/server",
"mascara": "gulp build && METAMASK_DEBUG=true node ./mascara/example/server",
"dist": "npm run dist:clear && npm install && gulp dist",
"dist:clear": "rm -rf node_modules/eth-contract-metadata && rm -rf node_modules/eth-phishing-detect",
"test": "npm run lint && npm run test:coverage && npm run test:integration",
"test:unit": "METAMASK_ENV=test mocha --compilers js:babel-core/register --require test/helper.js --recursive \"test/unit/**/*.js\"",
"test:unit": "METAMASK_ENV=test mocha --exit --compilers js:babel-core/register --require test/helper.js --recursive \"test/unit/**/*.js\"",
"test:single": "METAMASK_ENV=test mocha --require test/helper.js",
"test:integration": "gulp build:scss && npm run test:flat && npm run test:mascara",
"test:coverage": "nyc npm run test:unit && npm run test:coveralls-upload",
@ -77,14 +77,14 @@
"eslint-plugin-react": "^7.4.0",
"eth-bin-to-ops": "^1.0.1",
"eth-block-tracker": "^2.2.0",
"eth-json-rpc-filters": "^1.2.5",
"eth-json-rpc-infura": "^2.0.5",
"eth-keyring-controller": "^2.1.4",
"eth-contract-metadata": "^1.1.5",
"eth-hd-keyring": "^1.2.1",
"eth-json-rpc-filters": "^1.2.4",
"eth-keyring-controller": "^2.1.2",
"eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2",
"eth-sig-util": "^1.4.0",
"eth-simple-keyring": "^1.2.0",
"eth-sig-util": "^1.4.2",
"eth-token-tracker": "^1.1.4",
"ethereumjs-abi": "^0.6.4",
"ethereumjs-tx": "^1.3.0",
@ -129,6 +129,7 @@
"obj-multiplex": "^1.0.0",
"obs-store": "^3.0.0",
"once": "^1.3.3",
"percentile": "^1.2.0",
"ping-pong-stream": "^1.0.0",
"pojo-migrator": "^2.1.0",
"polyfill-crypto.getrandomvalues": "^1.0.0",
@ -162,15 +163,15 @@
"request-promise": "^4.2.1",
"sandwich-expando": "^1.1.3",
"semaphore": "^1.0.5",
"shallow-copy": "0.0.1",
"semver": "^5.4.1",
"shallow-copy": "0.0.1",
"sw-stream": "^2.0.0",
"textarea-caret": "^3.0.1",
"through2": "^2.0.3",
"valid-url": "^1.0.9",
"vreme": "^3.0.2",
"web3": "^0.20.1",
"web3-provider-engine": "^13.3.2",
"web3-provider-engine": "^13.5.0",
"web3-stream-provider": "^3.0.1",
"xtend": "^4.0.1"
},
@ -200,16 +201,20 @@
"eth-json-rpc-middleware": "^1.2.7",
"fs-promise": "^2.0.3",
"gulp": "github:gulpjs/gulp#4.0",
"gulp-if": "^2.0.1",
"gulp-babel": "^7.0.0",
"gulp-if": "^2.0.2",
"gulp-json-editor": "^2.2.1",
"gulp-livereload": "^3.8.1",
"gulp-replace": "^0.6.1",
"gulp-sourcemaps": "^2.6.0",
"gulp-stylefmt": "^1.1.0",
"gulp-stylelint": "^4.0.0",
"gulp-uglify": "^3.0.0",
"gulp-uglify-es": "^1.0.0",
"gulp-util": "^3.0.7",
"gulp-watch": "^4.3.5",
"gulp-zip": "^4.0.0",
"gulp-eslint": "^4.0.0",
"isomorphic-fetch": "^2.2.1",
"jsdom": "^11.1.0",
"jsdom-global": "^3.0.2",
@ -239,8 +244,8 @@
"tape": "^4.5.1",
"testem": "^1.10.3",
"uglifyify": "^4.0.2",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"vinyl-buffer": "^1.0.1",
"vinyl-source-stream": "^2.0.0",
"watchify": "^3.9.0"
},
"engines": {

View File

@ -54,6 +54,8 @@ module.exports = function(config) {
// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity
concurrency: 1,
nocache: true,
}
}

View File

@ -5,7 +5,8 @@ module.exports = {
createEngineForTestData,
providerFromEngine,
scaffoldMiddleware,
createStubedProvider
createEthJsQueryStub,
createStubedProvider,
}
@ -18,6 +19,18 @@ function providerFromEngine (engine) {
return provider
}
function createEthJsQueryStub (stubProvider) {
return new Proxy({}, {
get: (obj, method) => {
return (...params) => {
return new Promise((resolve, reject) => {
stubProvider.sendAsync({ method: `eth_${method}`, params }, (err, ress) => resolve(ress.result))
})
}
},
})
}
function createStubedProvider (resultStub) {
const engine = createEngineForTestData()
engine.push(scaffoldMiddleware(resultStub))

View File

@ -51,9 +51,8 @@ describe('tx confirmation screen', function () {
actions.cancelTx({value: firstTxId})((action) => {
result = reducers(initialState, action)
done()
})
done()
})
it('should transition to the account detail view', function () {

View File

@ -3,6 +3,8 @@ const sinon = require('sinon')
const clone = require('clone')
const MetaMaskController = require('../../app/scripts/metamask-controller')
const firstTimeState = require('../../app/scripts/first-time-state')
const BN = require('ethereumjs-util').BN
const GWEI_BN = new BN('1000000000')
describe('MetaMaskController', function () {
const noop = () => {}
@ -39,17 +41,63 @@ describe('MetaMaskController', function () {
beforeEach(function () {
sinon.spy(metamaskController.keyringController, 'createNewVaultAndKeychain')
sinon.spy(metamaskController.keyringController, 'createNewVaultAndRestore')
})
afterEach(function () {
metamaskController.keyringController.createNewVaultAndKeychain.restore()
metamaskController.keyringController.createNewVaultAndRestore.restore()
})
describe('#getGasPrice', function () {
it('gives the 50th percentile lowest accepted gas price from recentBlocksController', async function () {
const realRecentBlocksController = metamaskController.recentBlocksController
metamaskController.recentBlocksController = {
store: {
getState: () => {
return {
recentBlocks: [
{ gasPrices: [ '0x3b9aca00', '0x174876e800'] },
{ gasPrices: [ '0x3b9aca00', '0x174876e800'] },
{ gasPrices: [ '0x174876e800', '0x174876e800' ]},
{ gasPrices: [ '0x174876e800', '0x174876e800' ]},
]
}
}
}
}
const gasPrice = metamaskController.getGasPrice()
assert.equal(gasPrice, '0x3b9aca00', 'accurately estimates 50th percentile accepted gas price')
metamaskController.recentBlocksController = realRecentBlocksController
})
it('gives the 1 gwei price if no blocks have been seen.', async function () {
const realRecentBlocksController = metamaskController.recentBlocksController
metamaskController.recentBlocksController = {
store: {
getState: () => {
return {
recentBlocks: []
}
}
}
}
const gasPrice = metamaskController.getGasPrice()
assert.equal(gasPrice, '0x' + GWEI_BN.toString(16), 'defaults to 1 gwei')
metamaskController.recentBlocksController = realRecentBlocksController
})
})
describe('#createNewVaultAndKeychain', function () {
it('can only create new vault on keyringController once', async function () {
const selectStub = sinon.stub(metamaskController, 'selectFirstIdentity')
const password = 'a-fake-password'
const first = await metamaskController.createNewVaultAndKeychain(password)
@ -60,6 +108,22 @@ describe('MetaMaskController', function () {
selectStub.reset()
})
})
describe('#createNewVaultAndRestore', function () {
it('should be able to call newVaultAndRestore despite a mistake.', async function () {
// const selectStub = sinon.stub(metamaskController, 'selectFirstIdentity')
const password = 'what-what-what'
const wrongSeed = 'debris dizzy just program just float decrease vacant alarm reduce speak stadiu'
const rightSeed = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium'
const first = await metamaskController.createNewVaultAndRestore(password, wrongSeed)
.catch((e) => {
return
})
const second = await metamaskController.createNewVaultAndRestore(password, rightSeed)
assert(metamaskController.keyringController.createNewVaultAndRestore.calledTwice)
})
})
})
})

View File

@ -340,4 +340,63 @@ describe('PendingTransactionTracker', function () {
assert.equal(pendingTxTracker.publishTransaction.callCount, 1, 'Should call publish transaction')
})
})
describe('#_checkIfNonceIsTaken', function () {
beforeEach ( function () {
let confirmedTxList = [{
id: 1,
hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
status: 'confirmed',
txParams: {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
nonce: '0x1',
value: '0xfffff',
},
rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d',
}, {
id: 2,
hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
status: 'confirmed',
txParams: {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
nonce: '0x2',
value: '0xfffff',
},
rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d',
}]
pendingTxTracker.getCompletedTransactions = (address) => {
if (!address) throw new Error('unless behavior has changed #_checkIfNonceIsTaken needs a filtered list of transactions to see if the nonce is taken')
return confirmedTxList
}
})
it('should return false if nonce has not been taken', function (done) {
pendingTxTracker._checkIfNonceIsTaken({
txParams: {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
nonce: '0x3',
value: '0xfffff',
},
})
.then((taken) => {
assert.ok(!taken)
done()
})
.catch(done)
})
it('should return true if nonce has been taken', function (done) {
pendingTxTracker._checkIfNonceIsTaken({
txParams: {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
nonce: '0x2',
value: '0xfffff',
},
}).then((taken) => {
assert.ok(taken)
done()
})
.catch(done)
})
})
})

View File

@ -0,0 +1,48 @@
const assert = require('assert')
const PreferencesController = require('../../app/scripts/controllers/preferences')
describe('preferences controller', function () {
let preferencesController
before(() => {
preferencesController = new PreferencesController()
})
describe('addToken', function () {
it('should add that token to its state', async function () {
const address = '0xabcdef1234567'
const symbol = 'ABBR'
const decimals = 5
await preferencesController.addToken(address, symbol, decimals)
const tokens = preferencesController.getTokens()
assert.equal(tokens.length, 1, 'one token added')
const added = tokens[0]
assert.equal(added.address, address, 'set address correctly')
assert.equal(added.symbol, symbol, 'set symbol correctly')
assert.equal(added.decimals, decimals, 'set decimals correctly')
})
it('should allow updating a token value', async function () {
const address = '0xabcdef1234567'
const symbol = 'ABBR'
const decimals = 5
await preferencesController.addToken(address, symbol, decimals)
const newDecimals = 6
await preferencesController.addToken(address, symbol, newDecimals)
const tokens = preferencesController.getTokens()
assert.equal(tokens.length, 1, 'one token added')
const added = tokens[0]
assert.equal(added.address, address, 'set address correctly')
assert.equal(added.symbol, symbol, 'set symbol correctly')
assert.equal(added.decimals, newDecimals, 'updated decimals correctly')
})
})
})

View File

@ -5,7 +5,7 @@ const ObservableStore = require('obs-store')
const sinon = require('sinon')
const TransactionController = require('../../app/scripts/controllers/transactions')
const TxGasUtils = require('../../app/scripts/lib/tx-gas-utils')
const { createStubedProvider } = require('../stub/provider')
const { createStubedProvider, createEthJsQueryStub } = require('../stub/provider')
const noop = () => true
const currentNetworkId = 42
@ -30,6 +30,8 @@ describe('Transaction Controller', function () {
resolve()
}),
})
txController.query = createEthJsQueryStub(provider)
txController.txGasUtil.query = createEthJsQueryStub(provider)
txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop })
txController.txProviderUtils = new TxGasUtils(txController.provider)
})
@ -110,22 +112,15 @@ describe('Transaction Controller', function () {
history: [],
}
txController.txStateManager._saveTxList([txMeta])
stub = sinon.stub(txController, 'addUnapprovedTransaction').returns(Promise.resolve(txController.txStateManager.addTx(txMeta)))
stub = sinon.stub(txController, 'addUnapprovedTransaction').callsFake(() => {
txController.emit('newUnapprovedTx', txMeta)
return Promise.resolve(txController.txStateManager.addTx(txMeta))
})
afterEach(function () {
txController.txStateManager._saveTxList([])
stub.restore()
})
it('should emit newUnapprovedTx event and pass txMeta as the first argument', function (done) {
txController.once('newUnapprovedTx', (txMetaFromEmit) => {
assert(txMetaFromEmit, 'txMeta is falsey')
assert.equal(txMetaFromEmit.id, 1, 'the right txMeta was passed')
done()
})
txController.newUnapprovedTransaction(txParams)
.catch(done)
})
it('should resolve when finished and status is submitted and resolve with the hash', function (done) {
@ -160,8 +155,17 @@ describe('Transaction Controller', function () {
})
describe('#addUnapprovedTransaction', function () {
let addTxDefaults
beforeEach(() => {
addTxDefaults = txController.addTxDefaults
txController.addTxDefaults = function addTxDefaultsStub () { return Promise.resolve() }
})
afterEach(() => {
txController.addTxDefaults = addTxDefaults
})
it('should add an unapproved transaction and return a valid txMeta', function (done) {
const addTxDefaultsStub = sinon.stub(txController, 'addTxDefaults').callsFake(() => Promise.resolve())
txController.addUnapprovedTransaction({})
.then((txMeta) => {
assert(('id' in txMeta), 'should have a id')
@ -172,10 +176,20 @@ describe('Transaction Controller', function () {
const memTxMeta = txController.txStateManager.getTx(txMeta.id)
assert.deepEqual(txMeta, memTxMeta, `txMeta should be stored in txController after adding it\n expected: ${txMeta} \n got: ${memTxMeta}`)
addTxDefaultsStub.restore()
done()
}).catch(done)
})
it('should emit newUnapprovedTx event and pass txMeta as the first argument', function (done) {
providerResultStub.eth_gasPrice = '4a817c800'
txController.once('newUnapprovedTx', (txMetaFromEmit) => {
assert(txMetaFromEmit, 'txMeta is falsey')
done()
})
txController.addUnapprovedTransaction({})
.catch(done)
})
})
describe('#addTxDefaults', function () {

View File

@ -0,0 +1,32 @@
const assert = require('assert')
const TxGasUtils = require('../../app/scripts/lib/tx-gas-utils')
const { createStubedProvider } = require('../stub/provider')
describe('Tx Gas Util', function () {
let txGasUtil, provider, providerResultStub
beforeEach(function () {
providerResultStub = {}
provider = createStubedProvider(providerResultStub)
txGasUtil = new TxGasUtils({
provider,
})
})
it('removes recipient for txParams with 0x when contract data is provided', function () {
const zeroRecipientandDataTxParams = {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
to: '0x',
data: 'bytecode',
}
const sanitizedTxParams = txGasUtil.validateRecipient(zeroRecipientandDataTxParams)
assert.deepEqual(sanitizedTxParams, { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', data: 'bytecode' }, 'no recipient with 0x')
})
it('should error when recipient is 0x', function () {
const zeroRecipientTxParams = {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
to: '0x',
}
assert.throws(() => { txGasUtil.validateRecipient(zeroRecipientTxParams) }, Error, 'Invalid recipient address')
})
})

View File

@ -201,6 +201,18 @@ describe('util', function () {
var output = util.normalizeEthStringToWei(input)
assert.equal(output.toString(10), ethInWei)
})
it('should account for overflow numbers gracefully by dropping extra precision.', function () {
var input = '1.11111111111111111111'
var output = util.normalizeEthStringToWei(input)
assert.equal(output.toString(10), '1111111111111111111')
})
it('should not truncate very exact wei values that do not have extra precision.', function () {
var input = '1.100000000000000001'
var output = util.normalizeEthStringToWei(input)
assert.equal(output.toString(10), '1100000000000000001')
})
})
describe('#normalizeNumberToWei', function () {

View File

@ -2,7 +2,6 @@ const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('../../actions')
import Select from 'react-select'
// Subviews
@ -34,37 +33,14 @@ AccountImportSubview.prototype.render = function () {
const { type } = state
return (
h('div.flex-center', {
style: {
flexDirection: 'column',
marginTop: '32px',
},
}, [
h('.section-title.flex-row.flex-center', [
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
onClick: (event) => {
props.dispatch(actions.goHome())
},
}),
h('h2.page-subtitle', 'Import Accounts'),
]),
h('div', {
style: {
padding: '10px 0',
width: '260px',
color: 'rgb(174, 174, 174)',
},
}, [
h('div.new-account-import-form', [
h('h3', { style: { padding: '3px' } }, 'SELECT TYPE'),
h('div.new-account-import-form__select-section', [
h('style', `
.has-value.Select--single > .Select-control .Select-value .Select-value-label, .Select-value-label {
color: rgb(174,174,174);
}
`),
h('div.new-account-import-form__select-label', 'SELECT TYPE'),
h(Select, {
className: 'new-account-import-form__select',
name: 'import-type-select',
clearable: false,
value: type || menuItems[0],
@ -75,10 +51,10 @@ AccountImportSubview.prototype.render = function () {
}
}),
onChange: (opt) => {
props.dispatch(actions.showImportPage())
this.setState({ type: opt.value })
},
}),
]),
this.renderImportView(),

View File

@ -0,0 +1,96 @@
const { Component } = require('react')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const { connect } = require('react-redux')
const actions = require('../../actions')
class NewAccountCreateForm extends Component {
constructor (props) {
super(props)
const { numberOfExistingAccounts = 0 } = props
const newAccountNumber = numberOfExistingAccounts + 1
this.state = {
newAccountName: `Account ${newAccountNumber}`,
}
}
render () {
const { newAccountName } = this.state
return h('div.new-account-create-form', [
h('div.new-account-create-form__input-label', {}, [
'Account Name',
]),
h('div.new-account-create-form__input-wrapper', {}, [
h('input.new-account-create-form__input', {
value: this.state.newAccountName,
placeholder: 'E.g. My new account',
onChange: event => this.setState({ newAccountName: event.target.value }),
}, []),
]),
h('div.new-account-create-form__buttons', {}, [
h('button.new-account-create-form__button-cancel', {
onClick: () => this.props.goHome(),
}, [
'CANCEL',
]),
h('button.new-account-create-form__button-create', {
onClick: () => this.props.createAccount(newAccountName),
}, [
'CREATE',
]),
]),
])
}
}
NewAccountCreateForm.propTypes = {
hideModal: PropTypes.func,
showImportPage: PropTypes.func,
createAccount: PropTypes.func,
goHome: PropTypes.func,
numberOfExistingAccounts: PropTypes.number,
}
const mapStateToProps = state => {
const { metamask: { network, selectedAddress, identities = {} } } = state
const numberOfExistingAccounts = Object.keys(identities).length
return {
network,
address: selectedAddress,
numberOfExistingAccounts,
}
}
const mapDispatchToProps = dispatch => {
return {
toCoinbase: (address) => {
dispatch(actions.buyEth({ network: '1', address, amount: 0 }))
},
hideModal: () => {
dispatch(actions.hideModal())
},
createAccount: (newAccountName) => {
dispatch(actions.addNewAccount())
.then((newAccountAddress) => {
if (newAccountName) {
dispatch(actions.saveAccountLabel(newAccountAddress, newAccountName))
}
dispatch(actions.goHome())
})
},
showImportPage: () => dispatch(actions.showImportPage()),
goHome: () => dispatch(actions.goHome()),
}
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(NewAccountCreateForm)

View File

@ -0,0 +1,81 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../actions')
const { getCurrentViewContext } = require('../../selectors')
const classnames = require('classnames')
const NewAccountCreateForm = require('./create-form')
const NewAccountImportForm = require('../import')
function mapStateToProps (state) {
return {
displayedForm: getCurrentViewContext(state),
}
}
function mapDispatchToProps (dispatch) {
return {
displayForm: form => dispatch(actions.setNewAccountForm(form)),
showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)),
showExportPrivateKeyModal: () => {
dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' }))
},
hideModal: () => dispatch(actions.hideModal()),
saveAccountLabel: (address, label) => dispatch(actions.saveAccountLabel(address, label)),
}
}
inherits(AccountDetailsModal, Component)
function AccountDetailsModal (props) {
Component.call(this)
this.state = {
displayedForm: props.displayedForm,
}
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountDetailsModal)
AccountDetailsModal.prototype.render = function () {
const { displayedForm, displayForm } = this.props
return h('div.new-account', {}, [
h('div.new-account__header', [
h('div.new-account__title', 'New Account'),
h('div.new-account__tabs', [
h('div.new-account__tabs__tab', {
className: classnames('new-account__tabs__tab', {
'new-account__tabs__selected': displayedForm === 'CREATE',
'new-account__tabs__unselected cursor-pointer': displayedForm !== 'CREATE',
}),
onClick: () => displayForm('CREATE'),
}, 'Create'),
h('div.new-account__tabs__tab', {
className: classnames('new-account__tabs__tab', {
'new-account__tabs__selected': displayedForm === 'IMPORT',
'new-account__tabs__unselected cursor-pointer': displayedForm !== 'IMPORT',
}),
onClick: () => displayForm('IMPORT'),
}, 'Import'),
]),
]),
h('div.new-account__form', [
displayedForm === 'CREATE'
? h(NewAccountCreateForm)
: h(NewAccountImportForm),
]),
])
}

View File

@ -1,5 +1,6 @@
const abi = require('human-standard-token-abi')
const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url')
const { getTokenAddressFromTokenObject } = require('./util')
const ethUtil = require('ethereumjs-util')
var actions = {
@ -50,12 +51,16 @@ var actions = {
SHOW_NEW_VAULT_SEED: 'SHOW_NEW_VAULT_SEED',
SHOW_INFO_PAGE: 'SHOW_INFO_PAGE',
SHOW_IMPORT_PAGE: 'SHOW_IMPORT_PAGE',
SHOW_NEW_ACCOUNT_PAGE: 'SHOW_NEW_ACCOUNT_PAGE',
SET_NEW_ACCOUNT_FORM: 'SET_NEW_ACCOUNT_FORM',
unlockMetamask: unlockMetamask,
unlockFailed: unlockFailed,
showCreateVault: showCreateVault,
showRestoreVault: showRestoreVault,
showInitializeMenu: showInitializeMenu,
showImportPage,
showNewAccountPage,
setNewAccountForm,
createNewVaultAndKeychain: createNewVaultAndKeychain,
createNewVaultAndRestore: createNewVaultAndRestore,
createNewVaultInProgress: createNewVaultInProgress,
@ -125,6 +130,7 @@ var actions = {
sendTx: sendTx,
signTx: signTx,
signTokenTx: signTokenTx,
updateTransaction,
updateAndApproveTx,
cancelTx: cancelTx,
completedTx: completedTx,
@ -244,6 +250,13 @@ var actions = {
setFeatureFlag,
updateFeatureFlags,
UPDATE_FEATURE_FLAGS: 'UPDATE_FEATURE_FLAGS',
// Network
setNetworkEndpoints,
updateNetworkEndpointType,
UPDATE_NETWORK_ENDPOINT_TYPE: 'UPDATE_NETWORK_ENDPOINT_TYPE',
retryTransaction,
}
module.exports = actions
@ -714,6 +727,23 @@ function signTokenTx (tokenAddress, toAddress, amount, txData) {
}
}
function updateTransaction (txData) {
log.info('actions: updateTx: ' + JSON.stringify(txData))
return (dispatch) => {
log.debug(`actions calling background.updateTx`)
background.updateTransaction(txData, (err) => {
dispatch(actions.hideLoadingIndication())
dispatch(actions.updateTransactionParams(txData.id, txData.txParams))
if (err) {
dispatch(actions.txError(err))
dispatch(actions.goHome())
return log.error(err.message)
}
dispatch(actions.showConfTxPage({ id: txData.id }))
})
}
}
function updateAndApproveTx (txData) {
log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData))
return (dispatch) => {
@ -829,6 +859,7 @@ function cancelTx (txData) {
log.debug(`background.cancelTransaction`)
return new Promise((resolve, reject) => {
background.cancelTransaction(txData.id, () => {
dispatch(actions.clearSend())
dispatch(actions.completedTx(txData.id))
resolve(txData)
})
@ -880,6 +911,20 @@ function showImportPage () {
}
}
function showNewAccountPage (formToSelect) {
return {
type: actions.SHOW_NEW_ACCOUNT_PAGE,
formToSelect,
}
}
function setNewAccountForm (formToSelect) {
return {
type: actions.SET_NEW_ACCOUNT_FORM,
formToSelect,
}
}
function createNewVaultInProgress () {
return {
type: actions.CREATE_NEW_VAULT_IN_PROGRESS,
@ -976,9 +1021,13 @@ function lockMetamask () {
})
.then(newState => {
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.hideLoadingIndication())
dispatch({ type: actions.LOCK_METAMASK })
})
.catch(() => {
dispatch(actions.hideLoadingIndication())
dispatch({ type: actions.LOCK_METAMASK })
})
.catch(() => dispatch({ type: actions.LOCK_METAMASK }))
}
}
@ -1123,10 +1172,12 @@ function removeToken (address) {
function addTokens (tokens) {
return dispatch => {
if (Array.isArray(tokens)) {
dispatch(actions.setSelectedToken(getTokenAddressFromTokenObject(tokens[0])))
return Promise.all(tokens.map(({ address, symbol, decimals }) => (
dispatch(addToken(address, symbol, decimals))
)))
} else {
dispatch(actions.setSelectedToken(getTokenAddressFromTokenObject(tokens)))
return Promise.all(
Object
.entries(tokens)
@ -1196,6 +1247,19 @@ function markAccountsFound () {
return callBackgroundThenUpdate(background.markAccountsFound)
}
function retryTransaction (txId) {
log.debug(`background.retryTransaction`)
return (dispatch) => {
background.retryTransaction(txId, (err, newState) => {
if (err) {
return dispatch(actions.displayWarning(err.message))
}
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.viewPendingTx(txId))
})
}
}
//
// config
//
@ -1472,7 +1536,6 @@ function pairUpdate (coin) {
function shapeShiftSubview (network) {
var pair = 'btc_eth'
return (dispatch) => {
dispatch(actions.showSubLoadingIndication())
shapeShiftRequest('marketinfo', {pair}, (mktResponse) => {
@ -1498,7 +1561,7 @@ function coinShiftRquest (data, marketData) {
dispatch(actions.hideLoadingIndication())
if (response.error) return dispatch(actions.displayWarning(response.error))
var message = `
Deposit your ${response.depositType} to the address bellow:`
Deposit your ${response.depositType} to the address below:`
log.debug(`background.createShapeShiftTx`)
background.createShapeShiftTx(response.deposit, response.depositType)
dispatch(actions.showQrView(response.deposit, [message].concat(marketData)))
@ -1534,7 +1597,7 @@ function reshowQrCode (data, coin) {
if (mktResponse.error) return dispatch(actions.displayWarning(mktResponse.error))
var message = [
`Deposit your ${coin} to the address bellow:`,
`Deposit your ${coin} to the address below:`,
`Deposit Limit: ${mktResponse.limit}`,
`Deposit Minimum:${mktResponse.minimum}`,
]
@ -1600,10 +1663,7 @@ function updateTokenExchangeRate (token = '') {
}
}
function setFeatureFlag (feature, activated) {
const notificationType = activated
? 'BETA_UI_NOTIFICATION_MODAL'
: 'OLD_UI_NOTIFICATION_MODAL'
function setFeatureFlag (feature, activated, notificationType) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
@ -1611,10 +1671,10 @@ function setFeatureFlag (feature, activated) {
dispatch(actions.hideLoadingIndication())
if (err) {
dispatch(actions.displayWarning(err.message))
reject(err)
return reject(err)
}
dispatch(actions.updateFeatureFlags(updatedFeatureFlags))
dispatch(actions.showModal({ name: notificationType }))
notificationType && dispatch(actions.showModal({ name: notificationType }))
resolve(updatedFeatureFlags)
})
})
@ -1698,3 +1758,27 @@ function setUseBlockie (val) {
})
}
}
function setNetworkEndpoints (networkEndpointType) {
return dispatch => {
log.debug('background.setNetworkEndpoints')
return new Promise((resolve, reject) => {
background.setNetworkEndpoints(networkEndpointType, err => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.updateNetworkEndpointType(networkEndpointType))
resolve(networkEndpointType)
})
})
}
}
function updateNetworkEndpointType (networkEndpointType) {
return {
type: actions.UPDATE_NETWORK_ENDPOINT_TYPE,
value: networkEndpointType,
}
}

View File

@ -5,6 +5,8 @@ const { Switch, Redirect, withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const h = require('react-hyperscript')
const actions = require('./actions')
const classnames = require('classnames')
// mascara
const MascaraCreatePassword = require('../../mascara/src/app/first-time/create-password-screen').default
const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ether-screen').default
@ -234,22 +236,22 @@ class App extends Component {
showNetworkDropdown,
hideNetworkDropdown,
currentView,
isMascara,
isOnboarding,
history,
} = this.props
if (window.METAMASK_UI_TYPE === 'notification') {
return null
}
const props = this.props
const {isMascara, isOnboarding} = props
// Do not render header if user is in mascara onboarding
if (isMascara && isOnboarding) {
return null
}
// Do not render header if user is in mascara buy ether
if (isMascara && currentView.name === 'buyEth') {
if (isMascara && props.currentView.name === 'buyEth') {
return null
}
@ -260,7 +262,9 @@ class App extends Component {
}, [
h('.app-header.flex-row.flex-space-between', {
style: {},
className: classnames({
'app-header--initialized': !isOnboarding,
}),
}, [
h('div.app-header-contents', {}, [
h('div.left-menu-wrapper', {
@ -268,19 +272,13 @@ class App extends Component {
}, [
// mini logo
h('img.metafox-icon', {
height: 29,
width: 29,
src: '/images/icon-128.png',
height: 42,
width: 42,
src: '/images/metamask-fox.svg',
}),
// metamask name
h('h1', {
style: {
position: 'relative',
paddingLeft: '9px',
color: '#5B5D67',
},
}, 'MetaMask'),
h('h1', 'MetaMask'),
]),
@ -313,6 +311,7 @@ class App extends Component {
]),
]),
]),
])
)
}

View File

@ -33,15 +33,28 @@ function mapDispatchToProps (dispatch) {
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
showAccountDetail: address => {
dispatch(actions.showAccountDetail(address))
dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu())
},
lockMetamask: () => {
dispatch(actions.lockMetamask())
dispatch(actions.displayWarning(null))
dispatch(actions.hideWarning())
dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu())
},
showNewAccountModal: () => {
dispatch(actions.showModal({ name: 'NEW_ACCOUNT' }))
showConfigPage: () => {
dispatch(actions.showConfigPage())
dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu())
},
showNewAccountPage: (formToSelect) => {
dispatch(actions.showNewAccountPage(formToSelect))
dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu())
},
showInfoPage: () => {
dispatch(actions.showInfoPage())
dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu())
},
}
@ -51,7 +64,7 @@ AccountMenu.prototype.render = function () {
const {
isAccountMenuOpen,
toggleAccountMenu,
showNewAccountModal,
showNewAccountPage,
lockMetamask,
history,
} = this.props
@ -73,15 +86,12 @@ AccountMenu.prototype.render = function () {
h('div.account-menu__accounts', this.renderAccounts()),
h(Divider),
h(Item, {
onClick: showNewAccountModal,
onClick: () => showNewAccountPage('CREATE'),
icon: h('img', { src: 'images/plus-btn-white.svg' }),
text: 'Create Account',
}),
h(Item, {
onClick: () => {
toggleAccountMenu()
history.push(IMPORT_ACCOUNT_ROUTE)
},
onClick: () => showNewAccountPage('IMPORT'),
icon: h('img', { src: 'images/import-account.svg' }),
text: 'Import Account',
}),

View File

@ -40,7 +40,7 @@ BalanceComponent.prototype.render = function () {
// style: {},
// }),
h(Identicon, {
diameter: 45,
diameter: 50,
address: token && token.address,
network,
}),
@ -94,7 +94,8 @@ BalanceComponent.prototype.renderFiatValue = function (formattedBalance) {
}
BalanceComponent.prototype.renderFiatAmount = function (fiatDisplayNumber, fiatSuffix, fiatPrefix) {
if (fiatDisplayNumber === 'N/A') return null
const shouldNotRenderFiat = fiatDisplayNumber === 'N/A' || Number(fiatDisplayNumber) === 0
if (shouldNotRenderFiat) return null
return h('div.fiat-amount', {
style: {},

View File

@ -40,7 +40,7 @@ CoinbaseForm.prototype.render = function () {
}, 'Continue to Coinbase'),
h('button.btn-red', {
onClick: () => props.dispatch(actions.backTobuyView(props.accounts.address)),
onClick: () => props.dispatch(actions.goHome()),
}, 'Cancel'),
]),
])

View File

@ -50,10 +50,18 @@ function sanitizeValue (value) {
CurrencyInput.prototype.handleChange = function (newValue) {
const { onInputChange } = this.props
const { value } = this.state
this.setState({ value: sanitizeValue(newValue) })
let parsedValue = newValue
const newValueLastIndex = newValue.length - 1
onInputChange(sanitizeValue(newValue))
if (value === '0' && newValue[newValueLastIndex] === '0') {
parsedValue = parsedValue.slice(0, newValueLastIndex)
}
const sanitizedValue = sanitizeValue(parsedValue)
this.setState({ value: sanitizedValue })
onInputChange(sanitizedValue)
}
// If state.value === props.value plus a decimal point, or at least one

View File

@ -199,7 +199,7 @@ class AccountDropdowns extends Component {
{},
menuItemStyles,
),
onClick: () => actions.showNewAccountModal(),
onClick: () => actions.showNewAccountPageCreateForm(),
},
[
h(
@ -228,7 +228,7 @@ class AccountDropdowns extends Component {
actions.hideSidebar()
}
},
onClick: () => actions.showImportPage(),
onClick: () => actions.showNewAccountPageImportForm(),
style: Object.assign(
{},
menuItemStyles,
@ -457,9 +457,7 @@ const mapDispatchToProps = (dispatch) => {
identity,
}))
},
showNewAccountModal: () => {
dispatch(actions.showModal({ name: 'NEW_ACCOUNT' }))
},
showNewAccountPageCreateForm: () => dispatch(actions.showNewAccountPage({ form: 'CREATE' })),
showExportPrivateKeyModal: () => {
dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' }))
},
@ -467,7 +465,7 @@ const mapDispatchToProps = (dispatch) => {
dispatch(actions.showAddTokenPage())
},
addNewAccount: () => dispatch(actions.addNewAccount()),
showImportPage: () => dispatch(actions.showImportPage()),
showNewAccountPageImportForm: () => dispatch(actions.showNewAccountPage({ form: 'IMPORT' })),
showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)),
},
}

View File

@ -7,13 +7,13 @@ const debounce = require('debounce')
module.exports = Mascot
inherits(Mascot, Component)
function Mascot () {
function Mascot ({width = '200', height = '200'}) {
Component.call(this)
this.logo = metamaskLogo({
followMouse: true,
pxNotRatio: true,
width: 200,
height: 200,
width,
height,
})
this.refollowMouse = debounce(this.logo.setFollowMouse.bind(this.logo, true), 1000)

View File

@ -62,12 +62,12 @@ AccountDetailsModal.prototype.render = function () {
h('div.account-modal-divider'),
h('button.btn-clear', {
h('button.btn-clear.account-modal__button', {
onClick: () => global.platform.openWindow({ url: genAccountLink(address, network) }),
}, 'View account on Etherscan'),
// Holding on redesign for Export Private Key functionality
h('button.btn-clear', {
h('button.btn-clear.account-modal__button', {
onClick: () => showExportPrivateKeyModal(),
}, 'Export private key'),

View File

@ -69,7 +69,7 @@ BuyOptions.prototype.render = function () {
// h('div.buy-modal-content-option', {}, [
// h('div.buy-modal-content-option-title', {}, 'Shapeshift'),
// h('div.buy-modal-content-option-subtitle', {}, 'Trade any digital asset for any other'),
// ]),
// ]),,
this.renderModalContentOption(
'Direct Deposit',

View File

@ -0,0 +1,184 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../actions')
const networkNames = require('../../../../app/scripts/config.js').networkNames
const ShapeshiftForm = require('../shapeshift-form')
const DIRECT_DEPOSIT_ROW_TITLE = 'Directly Deposit Ether'
const DIRECT_DEPOSIT_ROW_TEXT = `If you already have some Ether, the quickest way to get Ether in
your new wallet by direct deposit.`
const COINBASE_ROW_TITLE = 'Buy on Coinbase'
const COINBASE_ROW_TEXT = `Coinbase is the worlds most popular way to buy and sell bitcoin,
ethereum, and litecoin.`
const SHAPESHIFT_ROW_TITLE = 'Deposit with ShapeShift'
const SHAPESHIFT_ROW_TEXT = `If you own other cryptocurrencies, you can trade and deposit Ether
directly into your MetaMask wallet. No Account Needed.`
const FAUCET_ROW_TITLE = 'Test Faucet'
const facuetRowText = networkName => `Get Ether from a faucet for the ${networkName}`
function mapStateToProps (state) {
return {
network: state.metamask.network,
address: state.metamask.selectedAddress,
}
}
function mapDispatchToProps (dispatch) {
return {
toCoinbase: (address) => {
dispatch(actions.buyEth({ network: '1', address, amount: 0 }))
},
hideModal: () => {
dispatch(actions.hideModal())
},
showAccountDetailModal: () => {
dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' }))
},
toFaucet: network => dispatch(actions.buyEth({ network })),
}
}
inherits(DepositEtherModal, Component)
function DepositEtherModal () {
Component.call(this)
this.state = {
buyingWithShapeshift: false,
}
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(DepositEtherModal)
DepositEtherModal.prototype.renderRow = function ({
logo,
title,
text,
buttonLabel,
onButtonClick,
hide,
className,
hideButton,
hideTitle,
onBackClick,
showBackButton,
}) {
if (hide) {
return null
}
return h('div', {
className: className || 'deposit-ether-modal__buy-row',
}, [
onBackClick && showBackButton && h('div.deposit-ether-modal__buy-row__back', {
onClick: onBackClick,
}, [
h('i.fa.fa-arrow-left.cursor-pointer'),
]),
h('div.deposit-ether-modal__buy-row__logo', [logo]),
h('div.deposit-ether-modal__buy-row__description', [
!hideTitle && h('div.deposit-ether-modal__buy-row__description__title', [title]),
h('div.deposit-ether-modal__buy-row__description__text', [text]),
]),
!hideButton && h('div.deposit-ether-modal__buy-row__button', [
h('button.deposit-ether-modal__deposit-button', {
onClick: onButtonClick,
}, [buttonLabel]),
]),
])
}
DepositEtherModal.prototype.render = function () {
const { network, toCoinbase, address, toFaucet } = this.props
const { buyingWithShapeshift } = this.state
const isTestNetwork = ['3', '4', '42'].find(n => n === network)
const networkName = networkNames[network]
return h('div.deposit-ether-modal', {}, [
h('div.deposit-ether-modal__header', [
h('div.deposit-ether-modal__header__title', ['Deposit Ether']),
h('div.deposit-ether-modal__header__description', [
'To interact with decentralized applications using MetaMask, youll need Ether in your wallet.',
]),
h('div.deposit-ether-modal__header__close', {
onClick: () => {
this.setState({ buyingWithShapeshift: false })
this.props.hideModal()
},
}),
]),
h('div.deposit-ether-modal__buy-rows', [
this.renderRow({
logo: h('img.deposit-ether-modal__buy-row__eth-logo', { src: '../../../images/eth_logo.svg' }),
title: DIRECT_DEPOSIT_ROW_TITLE,
text: DIRECT_DEPOSIT_ROW_TEXT,
buttonLabel: 'View Account',
onButtonClick: () => this.goToAccountDetailsModal(),
hide: buyingWithShapeshift,
}),
this.renderRow({
logo: h('i.fa.fa-tint.fa-2x'),
title: FAUCET_ROW_TITLE,
text: facuetRowText(networkName),
buttonLabel: 'Get Ether',
onButtonClick: () => toFaucet(network),
hide: !isTestNetwork || buyingWithShapeshift,
}),
this.renderRow({
logo: h('img.deposit-ether-modal__buy-row__coinbase-logo', {
src: '../../../images/coinbase logo.png',
}),
title: COINBASE_ROW_TITLE,
text: COINBASE_ROW_TEXT,
buttonLabel: 'Continue to Coinbase',
onButtonClick: () => toCoinbase(address),
hide: isTestNetwork || buyingWithShapeshift,
}),
this.renderRow({
logo: h('img.deposit-ether-modal__buy-row__shapeshift-logo', {
src: '../../../images/shapeshift logo.png',
}),
title: SHAPESHIFT_ROW_TITLE,
text: SHAPESHIFT_ROW_TEXT,
buttonLabel: 'Buy with Shapeshift',
onButtonClick: () => this.setState({ buyingWithShapeshift: true }),
hide: isTestNetwork,
hideButton: buyingWithShapeshift,
hideTitle: buyingWithShapeshift,
onBackClick: () => this.setState({ buyingWithShapeshift: false }),
showBackButton: this.state.buyingWithShapeshift,
className: buyingWithShapeshift && 'deposit-ether-modal__buy-row__shapeshift-buy',
}),
buyingWithShapeshift && h(ShapeshiftForm),
]),
])
}
DepositEtherModal.prototype.goToAccountDetailsModal = function () {
this.props.hideModal()
this.props.showAccountDetailModal()
}

View File

@ -79,11 +79,15 @@ ExportPrivateKeyModal.prototype.renderButton = function (className, onClick, lab
ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, password, address, hideModal) {
return h('div.export-private-key-buttons', {}, [
!privateKey && this.renderButton('btn-clear btn-cancel', () => hideModal(), 'Cancel'),
!privateKey && this.renderButton(
'btn-cancel export-private-key__button export-private-key__button--cancel',
() => hideModal(),
'Cancel'
),
(privateKey
? this.renderButton('btn-clear', () => hideModal(), 'Done')
: this.renderButton('btn-clear', () => this.exportAccountAndGetPrivateKey(this.state.password, address), 'Show')
? this.renderButton('btn-clear export-private-key__button', () => hideModal(), 'Done')
: this.renderButton('btn-clear export-private-key__button', () => this.exportAccountAndGetPrivateKey(this.state.password, address), 'Confirm')
),
])

View File

@ -58,12 +58,12 @@ HideTokenConfirmationModal.prototype.render = function () {
]),
h('div.hide-token-confirmation__buttons', {}, [
h('button.btn-clear', {
h('button.btn-cancel.hide-token-confirmation__button', {
onClick: () => hideModal(),
}, [
'CANCEL',
]),
h('button.btn-clear', {
h('button.btn-clear.hide-token-confirmation__button', {
onClick: () => hideToken(address),
}, [
'HIDE',

View File

@ -9,6 +9,7 @@ const isPopupOrNotification = require('../../../../app/scripts/lib/is-popup-or-n
// Modal Components
const BuyOptions = require('./buy-options-modal')
const DepositEtherModal = require('./deposit-ether-modal')
const AccountDetailsModal = require('./account-details-modal')
const EditAccountNameModal = require('./edit-account-name-modal')
const ExportPrivateKeyModal = require('./export-private-key-modal')
@ -73,6 +74,37 @@ const MODALS = {
},
},
DEPOSIT_ETHER: {
contents: [
h(DepositEtherModal, {}, []),
],
mobileModalStyle: {
width: '100%',
height: '100%',
transform: 'none',
left: '0',
right: '0',
margin: '0 auto',
boxShadow: '0 0 7px 0 rgba(0,0,0,0.08)',
top: '0',
display: 'flex',
},
laptopModalStyle: {
width: '900px',
maxWidth: '900px',
top: 'calc(10% + 10px)',
left: '0',
right: '0',
margin: '0 auto',
boxShadow: '0 0 6px 0 rgba(0,0,0,0.3)',
borderRadius: '8px',
transform: 'none',
},
contentStyle: {
borderRadius: '8px',
},
},
EDIT_ACCOUNT_NAME: {
contents: [
h(EditAccountNameModal, {}, []),

View File

@ -39,7 +39,6 @@ Network.prototype.render = function () {
},
src: 'images/loading.svg',
}),
h('i.fa.fa-caret-down.network-caret'),
])
} else if (providerName === 'mainnet') {
hoverText = 'Main Ethereum Network'
@ -85,12 +84,8 @@ Network.prototype.render = function () {
backgroundColor: '#038789', // $blue-lagoon
nonSelectBackgroundColor: '#15afb2',
}),
h('.network-name', {
style: {
color: '#039396',
}},
'Main Network'),
h('i.fa.fa-caret-down.fa-lg.network-caret'),
h('.network-name', 'Main Network'),
h('i.fa.fa-chevron-down.fa-lg.network-caret'),
])
case 'ropsten-test-network':
return h('.network-indicator', [
@ -98,12 +93,8 @@ Network.prototype.render = function () {
backgroundColor: '#e91550', // $crimson
nonSelectBackgroundColor: '#ec2c50',
}),
h('.network-name', {
style: {
color: '#ff6666',
}},
'Ropsten Test Net'),
h('i.fa.fa-caret-down.fa-lg.network-caret'),
h('.network-name', 'Ropsten Test Net'),
h('i.fa.fa-chevron-down.fa-lg.network-caret'),
])
case 'kovan-test-network':
return h('.network-indicator', [
@ -111,12 +102,8 @@ Network.prototype.render = function () {
backgroundColor: '#690496', // $purple
nonSelectBackgroundColor: '#b039f3',
}),
h('.network-name', {
style: {
color: '#690496',
}},
'Kovan Test Net'),
h('i.fa.fa-caret-down.fa-lg.network-caret'),
h('.network-name', 'Kovan Test Net'),
h('i.fa.fa-chevron-down.fa-lg.network-caret'),
])
case 'rinkeby-test-network':
return h('.network-indicator', [
@ -124,12 +111,8 @@ Network.prototype.render = function () {
backgroundColor: '#ebb33f', // $tulip-tree
nonSelectBackgroundColor: '#ecb23e',
}),
h('.network-name', {
style: {
color: '#e7a218',
}},
'Rinkeby Test Net'),
h('i.fa.fa-caret-down.fa-lg.network-caret'),
h('.network-name', 'Rinkeby Test Net'),
h('i.fa.fa-chevron-down.fa-lg.network-caret'),
])
default:
return h('.network-indicator', [
@ -140,12 +123,8 @@ Network.prototype.render = function () {
},
}),
h('.network-name', {
style: {
color: '#AEAEAE',
}},
'Private Network'),
h('i.fa.fa-caret-down.fa-lg.network-caret'),
h('.network-name', 'Private Network'),
h('i.fa.fa-chevron-down.fa-lg.network-caret'),
])
}
})(),

View File

@ -3,6 +3,7 @@ const Component = require('react').Component
const classnames = require('classnames')
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const R = require('ramda')
const Fuse = require('fuse.js')
const contractMap = require('eth-contract-metadata')
const TokenBalance = require('../../components/token-balance')
@ -17,7 +18,10 @@ const fuse = new Fuse(contractList, {
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: ['address', 'name', 'symbol'],
keys: [
{ name: 'name', weight: 0.5 },
{ name: 'symbol', weight: 0.5 },
],
})
// const actions = require('./actions')
const actions = require('../../actions')
@ -219,9 +223,11 @@ AddTokenScreen.prototype.renderCustomForm = function () {
AddTokenScreen.prototype.renderTokenList = function () {
const { searchQuery = '', selectedTokens } = this.state
const results = searchQuery
? fuse.search(searchQuery) || []
: contractList
const fuseSearchResult = fuse.search(searchQuery)
const addressSearchResult = contractList.filter(token => {
return token.address.toLowerCase() === searchQuery.toLowerCase()
})
const results = [...addressSearchResult, ...fuseSearchResult]
return Array(6).fill(undefined)
.map((_, i) => {
@ -297,12 +303,12 @@ AddTokenScreen.prototype.renderConfirmation = function () {
]),
]),
h('div.add-token__buttons', [
h('button.btn-secondary', {
onClick: () => addTokens(tokens).then(() => history.push(DEFAULT_ROUTE)),
}, 'Add Tokens'),
h('button.btn-tertiary', {
h('button.btn-cancel.add-token__button', {
onClick: () => this.setState({ isShowingConfirmation: false }),
}, 'Back'),
h('button.btn-clear.add-token__button', {
onClick: () => addTokens(tokens).then(() => history.push(DEFAULT_ROUTE)),
}, 'Add Tokens'),
]),
])
)
@ -347,12 +353,12 @@ AddTokenScreen.prototype.render = function () {
]),
]),
h('div.add-token__buttons', [
h('button.btn-secondary', {
onClick: this.onNext,
}, 'Next'),
h('button.btn-tertiary', {
h('button.btn-cancel.add-token__button', {
onClick: () => history.goBack(),
}, 'Cancel'),
h('button.btn-clear.add-token__button', {
onClick: this.onNext,
}, 'Next'),
]),
])
)

View File

@ -24,14 +24,7 @@ JsonImportSubview.prototype.render = function () {
const { error } = this.props
return (
h('div', {
style: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: '5px 15px 0px 15px',
},
}, [
h('div.new-account-import-form__json', [
h('p', 'Used by a variety of different clients'),
h('a.warning', { href: HELP_LINK, target: '_blank' }, 'File import not working? Click here!'),
@ -40,28 +33,35 @@ JsonImportSubview.prototype.render = function () {
readAs: 'text',
onLoad: this.onLoad.bind(this),
style: {
margin: '20px 0px 12px 20px',
margin: '20px 0px 12px 34%',
fontSize: '15px',
display: 'flex',
justifyContent: 'center',
},
}),
h('input.large-input.letter-spacey', {
h('input.new-account-import-form__input-password', {
type: 'password',
placeholder: 'Enter password',
id: 'json-password-box',
onKeyPress: this.createKeyringOnEnter.bind(this),
style: {
width: 260,
marginTop: 12,
},
}),
h('button.primary', {
onClick: this.createNewKeychain.bind(this),
style: {
margin: 12,
},
}, 'Import'),
h('div.new-account-create-form__buttons', {}, [
h('button.new-account-create-form__button-cancel', {
onClick: () => this.props.goHome(),
}, [
'CANCEL',
]),
h('button.new-account-create-form__button-create', {
onClick: () => this.createNewKeychain.bind(this),
}, [
'IMPORT',
]),
]),
error ? h('span.error', error) : null,
])

View File

@ -4,7 +4,7 @@ const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('../../../actions')
module.exports = connect(mapStateToProps)(PrivateKeyImportView)
module.exports = connect(mapStateToProps, mapDispatchToProps)(PrivateKeyImportView)
function mapStateToProps (state) {
return {
@ -12,45 +12,49 @@ function mapStateToProps (state) {
}
}
function mapDispatchToProps (dispatch) {
return {
goHome: () => dispatch(actions.goHome()),
importNewAccount: (strategy, [ privateKey ]) => {
dispatch(actions.importNewAccount(strategy, [ privateKey ]))
},
displayWarning: () => dispatch(actions.displayWarning(null)),
}
}
inherits(PrivateKeyImportView, Component)
function PrivateKeyImportView () {
Component.call(this)
}
PrivateKeyImportView.prototype.componentWillUnmount = function () {
this.props.dispatch(actions.displayWarning(null))
}
PrivateKeyImportView.prototype.render = function () {
const { error } = this.props
const { error, goHome } = this.props
return (
h('div', {
style: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: '5px 15px 0px 15px',
},
}, [
h('span', 'Paste your private key string here'),
h('div.new-account-import-form__private-key', [
h('span.new-account-create-form__instruction', 'Paste your private key string here:'),
h('input.large-input.letter-spacey', {
h('input.new-account-import-form__input-password', {
type: 'password',
id: 'private-key-box',
onKeyPress: this.createKeyringOnEnter.bind(this),
style: {
width: 260,
marginTop: 12,
},
onKeyPress: () => this.createKeyringOnEnter(),
}),
h('button.primary', {
onClick: this.createNewKeychain.bind(this),
style: {
margin: 12,
},
}, 'Import'),
h('div.new-account-create-form__buttons', {}, [
h('button.new-account-create-form__button-cancel', {
onClick: () => goHome(),
}, [
'CANCEL',
]),
h('button.new-account-create-form__button-create', {
onClick: () => this.createNewKeychain(),
}, [
'IMPORT',
]),
]),
error ? h('span.error', error) : null,
])
@ -67,5 +71,6 @@ PrivateKeyImportView.prototype.createKeyringOnEnter = function (event) {
PrivateKeyImportView.prototype.createNewKeychain = function () {
const input = document.getElementById('private-key-box')
const privateKey = input.value
this.props.dispatch(actions.importNewAccount('Private Key', [ privateKey ]))
this.props.importNewAccount('Private Key', [ privateKey ])
}

View File

@ -52,7 +52,10 @@ class RestoreVaultPage extends PersistentForm {
// submit
this.props.createNewVaultAndRestore(password, seed)
.then(() => history.push(DEFAULT_ROUTE))
.catch(({ message }) => this.setState({ error: message }))
.catch(({ message }) => {
this.setState({ error: message })
log.error(message)
})
}
render () {

View File

@ -11,6 +11,7 @@ const { exportAsFile } = require('../../../util')
const SimpleDropdown = require('../../dropdowns/simple-dropdown')
const ToggleButton = require('react-toggle-button')
const { REVEAL_SEED_ROUTE } = require('../../../routes')
const { OLD_UI_NETWORK_TYPE } = require('../../app/scripts/config').enums
const getInfuraCurrencyOptions = () => {
const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => {
@ -230,18 +231,18 @@ class Settings extends Component {
}
render () {
const { warning } = this.props
const { warning, isMascara } = this.props
return (
h('div.settings__content', [
warning && h('div.settings__error', warning),
this.renderBlockieOptIn(),
this.renderCurrentConversion(),
// this.renderCurrentProvider(),
this.renderNewRpcUrl(),
this.renderStateLogs(),
this.renderSeedWords(),
this.renderOldUI(),
!isMascara && this.renderOldUI(),
this.renderBlockieOptIn(),
])
)
}
@ -257,12 +258,14 @@ Settings.propTypes = {
setFeatureFlagToBeta: PropTypes.func,
warning: PropTypes.string,
history: PropTypes.object,
isMascara: PropTypes.bool,
}
const mapStateToProps = state => {
return {
metamask: state.metamask,
warning: state.appState.warning,
isMascara: state.metamask.isMascara,
}
}
@ -273,7 +276,10 @@ const mapDispatchToProps = dispatch => {
displayWarning: warning => dispatch(actions.displayWarning(warning)),
revealSeedConfirmation: () => dispatch(actions.revealSeedConfirmation()),
setUseBlockie: value => dispatch(actions.setUseBlockie(value)),
setFeatureFlagToBeta: () => dispatch(actions.setFeatureFlag('betaUI', false)),
setFeatureFlagToBeta: () => {
return dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
.then(() => dispatch(actions.setNetworkEndpoints(OLD_UI_NETWORK_TYPE)))
},
}
}

View File

@ -231,8 +231,8 @@ ConfirmSendEther.prototype.render = function () {
// Main Send token Card
h('div.confirm-screen-wrapper.flex-column.flex-grow', [
h('h3.flex-center.confirm-screen-header', [
h('button.confirm-screen-back-button', {
onClick: () => this.editTransaction(txMeta),
h('button.btn-clear.confirm-screen-back-button', {
onClick: () => editTransaction(txMeta),
}, 'EDIT'),
h('div.confirm-screen-title', 'Confirm Transaction'),
h('div.confirm-screen-header-tip'),
@ -433,7 +433,9 @@ ConfirmSendEther.prototype.onSubmit = function (event) {
ConfirmSendEther.prototype.cancel = function (event, txMeta) {
event.preventDefault()
this.props.cancelTransaction(txMeta)
const { cancelTransaction } = this.props
cancelTransaction(txMeta)
.then(() => this.props.history.push(DEFAULT_ROUTE))
}
@ -458,26 +460,6 @@ ConfirmSendEther.prototype.gatherTxMeta = function () {
const state = this.state
const txData = clone(state.txData) || clone(props.txData)
if (props.send.editingTransactionId) {
const {
send: {
memo,
amount: value,
gasLimit: gas,
gasPrice,
},
} = props
const { txParams: { from, to } } = txData
txData.txParams = {
from: ethUtil.addHexPrefix(from),
to: ethUtil.addHexPrefix(to),
memo: memo && ethUtil.addHexPrefix(memo),
value: ethUtil.addHexPrefix(value),
gas: ethUtil.addHexPrefix(gas),
gasPrice: ethUtil.addHexPrefix(gasPrice),
}
}
// log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`)
return txData
}

View File

@ -4,7 +4,6 @@ const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const ethAbi = require('ethereumjs-abi')
const tokenAbi = require('human-standard-token-abi')
const abiDecoder = require('abi-decoder')
abiDecoder.addABI(tokenAbi)
@ -305,6 +304,7 @@ ConfirmSendToken.prototype.renderTotalPlusGas = function () {
}
ConfirmSendToken.prototype.render = function () {
const { editTransaction } = this.props
const txMeta = this.gatherTxMeta()
const {
from: {
@ -326,8 +326,8 @@ ConfirmSendToken.prototype.render = function () {
// Main Send token Card
h('div.confirm-screen-wrapper.flex-column.flex-grow', [
h('h3.flex-center.confirm-screen-header', [
h('button.confirm-screen-back-button', {
onClick: () => this.editTransaction(txMeta),
h('button.btn-clear.confirm-screen-back-button', {
onClick: () => editTransaction(txMeta),
}, 'EDIT'),
h('div.confirm-screen-title', 'Confirm Transaction'),
h('div.confirm-screen-header-tip'),
@ -426,7 +426,9 @@ ConfirmSendToken.prototype.onSubmit = function (event) {
ConfirmSendToken.prototype.cancel = function (event, txMeta) {
event.preventDefault()
this.props.cancelTransaction(txMeta)
const { cancelTransaction } = this.props
cancelTransaction(txMeta)
.then(() => this.props.history.push(DEFAULT_ROUTE))
}
@ -451,39 +453,6 @@ ConfirmSendToken.prototype.gatherTxMeta = function () {
const state = this.state
const txData = clone(state.txData) || clone(props.txData)
if (props.send.editingTransactionId) {
const {
send: {
memo,
amount,
gasLimit: gas,
gasPrice,
to,
},
} = props
const { txParams: { from, to: tokenAddress } } = txData
const tokenParams = {
from: ethUtil.addHexPrefix(from),
value: '0',
gas: ethUtil.addHexPrefix(gas),
gasPrice: ethUtil.addHexPrefix(gasPrice),
}
const data = '0xa9059cbb' + Array.prototype.map.call(
ethAbi.rawEncode(['address', 'uint256'], [to, ethUtil.addHexPrefix(amount)]),
x => ('00' + x.toString(16)).slice(-2)
).join('')
txData.txParams = {
...tokenParams,
to: ethUtil.addHexPrefix(tokenAddress),
memo: memo && ethUtil.addHexPrefix(memo),
data,
}
}
// log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`)
return txData
}

View File

@ -32,8 +32,9 @@ GasFeeDisplay.prototype.render = function () {
})
: h('div.currency-display', 'Loading...'),
h('div.send-v2__sliders-icon-container', {
h('button.send-v2__sliders-icon-container', {
onClick,
disabled: !gasTotal,
}, [
h('i.fa.fa-sliders.send-v2__sliders-icon'),
]),

View File

@ -20,6 +20,8 @@ const MIN_GAS_TOTAL = multiplyCurrencies(MIN_GAS_LIMIT_HEX, MIN_GAS_PRICE_HEX, {
multiplierBase: 16,
})
const TOKEN_TRANSFER_FUNCTION_SIGNATURE = '0xa9059cbb'
module.exports = {
MIN_GAS_PRICE_GWEI,
MIN_GAS_PRICE_HEX,
@ -27,4 +29,5 @@ module.exports = {
MIN_GAS_LIMIT_HEX,
MIN_GAS_LIMIT_DEC,
MIN_GAS_TOTAL,
TOKEN_TRANSFER_FUNCTION_SIGNATURE,
}

View File

@ -55,6 +55,8 @@ function mapStateToProps (state) {
data,
amountConversionRate: selectedToken ? tokenToFiatRate : conversionRate,
tokenContract: getSelectedTokenContract(state),
unapprovedTxs: state.metamask.unapprovedTxs,
network: state.metamask.network,
}
}
@ -69,6 +71,7 @@ function mapDispatchToProps (dispatch) {
),
signTx: txParams => dispatch(actions.signTx(txParams)),
updateAndApproveTx: txParams => dispatch(actions.updateAndApproveTx(txParams)),
updateTx: txData => dispatch(actions.updateTransaction(txData)),
setSelectedAddress: address => dispatch(actions.setSelectedAddress(address)),
addToAddressBook: address => dispatch(actions.addToAddressBook(address)),
updateGasTotal: newTotal => dispatch(actions.updateGasTotal(newTotal)),
@ -82,7 +85,6 @@ function mapDispatchToProps (dispatch) {
updateSendErrors: newError => dispatch(actions.updateSendErrors(newError)),
goHome: () => dispatch(actions.goHome()),
clearSend: () => dispatch(actions.clearSend()),
backToConfirmScreen: editingTransactionId => dispatch(actions.showConfTxPage({ id: editingTransactionId })),
setMaxModeTo: bool => dispatch(actions.setMaxModeTo(bool)),
}
}

View File

@ -1,308 +1,242 @@
const PersistentForm = require('../../lib/persistent-form')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const Component = require('react').Component
const connect = require('react-redux').connect
const actions = require('../actions')
const Qr = require('./qr-code')
const isValidAddress = require('../util').isValidAddress
module.exports = connect(mapStateToProps)(ShapeshiftForm)
const classnames = require('classnames')
const { qrcode } = require('qrcode-npm')
const { shapeShiftSubview, pairUpdate, buyWithShapeShift } = require('../actions')
const { isValidAddress } = require('../util')
const SimpleDropdown = require('./dropdowns/simple-dropdown')
function mapStateToProps (state) {
const {
coinOptions,
tokenExchangeRates,
selectedAddress,
} = state.metamask
return {
warning: state.appState.warning,
isSubLoading: state.appState.isSubLoading,
qrRequested: state.appState.qrRequested,
coinOptions,
tokenExchangeRates,
selectedAddress,
}
}
inherits(ShapeshiftForm, PersistentForm)
function mapDispatchToProps (dispatch) {
return {
shapeShiftSubview: () => dispatch(shapeShiftSubview()),
pairUpdate: coin => dispatch(pairUpdate(coin)),
buyWithShapeShift: data => dispatch(buyWithShapeShift(data)),
}
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(ShapeshiftForm)
inherits(ShapeshiftForm, Component)
function ShapeshiftForm () {
PersistentForm.call(this)
this.persistentFormParentId = 'shapeshift-buy-form'
Component.call(this)
this.state = {
depositCoin: 'btc',
refundAddress: '',
showQrCode: false,
depositAddress: '',
errorMessage: '',
isLoading: false,
bought: false,
}
}
ShapeshiftForm.prototype.render = function () {
return this.props.qrRequested ? h(Qr, {key: 'qr'}) : this.renderMain()
ShapeshiftForm.prototype.componentWillMount = function () {
this.props.shapeShiftSubview()
}
ShapeshiftForm.prototype.renderMain = function () {
const marketinfo = this.props.buyView.formView.marketinfo
const coinOptions = this.props.buyView.formView.coinOptions
var coin = marketinfo.pair.split('_')[0].toUpperCase()
return h('.flex-column', {
style: {
position: 'relative',
padding: '25px',
paddingTop: '5px',
width: '90%',
minHeight: '215px',
alignItems: 'center',
overflowY: 'auto',
},
}, [
h('.flex-row', {
style: {
justifyContent: 'center',
alignItems: 'baseline',
height: '42px',
},
}, [
h('img', {
src: coinOptions[coin].image,
width: '25px',
height: '25px',
style: {
marginRight: '5px',
},
}),
h('.input-container', {
position: 'relative',
}, [
h('input#fromCoin.buy-inputs.ex-coins', {
type: 'text',
list: 'coinList',
autoFocus: true,
dataset: {
persistentFormId: 'input-coin',
},
style: {
boxSizing: 'border-box',
},
onChange: this.handleLiveInput.bind(this),
defaultValue: 'BTC',
}),
this.renderCoinList(),
h('i.fa.fa-pencil-square-o.edit-text', {
style: {
fontSize: '12px',
color: '#F7861C',
position: 'absolute',
},
}),
]),
h('.icon-control', {
style: {
position: 'relative',
},
}, [
// Not visible on the screen, can't see it on master.
// h('i.fa.fa-refresh.fa-4.orange', {
// style: {
// bottom: '5px',
// left: '5px',
// color: '#F7861C',
// },
// onClick: this.updateCoin.bind(this),
// }),
h('i.fa.fa-chevron-right.fa-4.orange', {
style: {
position: 'absolute',
bottom: '35%',
left: '0%',
color: '#F7861C',
},
onClick: this.updateCoin.bind(this),
}),
]),
h('#toCoin.ex-coins', marketinfo.pair.split('_')[1].toUpperCase()),
h('img', {
src: coinOptions[marketinfo.pair.split('_')[1].toUpperCase()].image,
width: '25px',
height: '25px',
style: {
marginLeft: '5px',
},
}),
]),
h('.flex-column', {
style: {
marginTop: '1%',
alignItems: 'flex-start',
},
}, [
this.props.warning ?
this.props.warning &&
h('span.error.flex-center', {
style: {
textAlign: 'center',
width: '229px',
height: '82px',
},
}, this.props.warning)
: this.renderInfo(),
this.renderRefundAddressForCoin(coin),
]),
])
ShapeshiftForm.prototype.onCoinChange = function (e) {
const coin = e.target.value
this.setState({
depositCoin: coin,
errorMessage: '',
})
this.props.pairUpdate(coin)
}
ShapeshiftForm.prototype.renderRefundAddressForCoin = function (coin) {
return h(this.activeToggle('.input-container'), {
style: {
marginTop: '1%',
},
}, [
ShapeshiftForm.prototype.onBuyWithShapeShift = function () {
this.setState({
isLoading: true,
showQrCode: true,
})
h('div', `${coin} Address:`),
h('input#fromCoinAddress.buy-inputs', {
type: 'text',
placeholder: `Your ${coin} Refund Address`,
dataset: {
persistentFormId: 'refund-address',
},
style: {
boxSizing: 'border-box',
width: '227px',
height: '30px',
padding: ' 5px ',
},
}),
h('i.fa.fa-pencil-square-o.edit-text', {
style: {
fontSize: '12px',
color: '#F7861C',
position: 'absolute',
},
}),
h('div.flex-row', {
style: {
justifyContent: 'flex-start',
},
}, [
h('button', {
onClick: this.shift.bind(this),
style: {
marginTop: '1%',
},
},
'Submit'),
]),
])
}
ShapeshiftForm.prototype.shift = function () {
var props = this.props
var withdrawal = this.props.buyView.buyAddress
var returnAddress = document.getElementById('fromCoinAddress').value
var pair = this.props.buyView.formView.marketinfo.pair
var data = {
'withdrawal': withdrawal,
'pair': pair,
'returnAddress': returnAddress,
const {
buyWithShapeShift,
selectedAddress: withdrawal,
} = this.props
const {
refundAddress: returnAddress,
depositCoin,
} = this.state
const pair = `${depositCoin}_eth`
const data = {
withdrawal,
pair,
returnAddress,
// Public api key
'apiKey': '803d1f5df2ed1b1476e4b9e6bcd089e34d8874595dda6a23b67d93c56ea9cc2445e98a6748b219b2b6ad654d9f075f1f1db139abfa93158c04e825db122c14b6',
}
var message = [
`Deposit Limit: ${props.buyView.formView.marketinfo.limit}`,
`Deposit Minimum:${props.buyView.formView.marketinfo.minimum}`,
]
if (isValidAddress(withdrawal)) {
this.props.dispatch(actions.coinShiftRquest(data, message))
buyWithShapeShift(data)
.then(d => this.setState({
showQrCode: true,
depositAddress: d.deposit,
isLoading: false,
}))
.catch(() => this.setState({
showQrCode: false,
errorMessage: 'Invalid Request',
isLoading: false,
}))
}
}
ShapeshiftForm.prototype.renderCoinList = function () {
var list = Object.keys(this.props.buyView.formView.coinOptions).map((item) => {
return h('option', {
value: item,
}, item)
})
ShapeshiftForm.prototype.renderMetadata = function (label, value) {
return h('div', {className: 'shapeshift-form__metadata-wrapper'}, [
return h('datalist#coinList', {
onClick: (event) => {
event.preventDefault()
},
}, list)
}
h('div.shapeshift-form__metadata-label', {}, [
h('span', `${label}:`),
]),
ShapeshiftForm.prototype.updateCoin = function (event) {
event.preventDefault()
const props = this.props
var coinOptions = this.props.buyView.formView.coinOptions
var coin = document.getElementById('fromCoin').value
h('div.shapeshift-form__metadata-value', {}, [
h('span', value),
]),
if (!coinOptions[coin.toUpperCase()] || coin.toUpperCase() === 'ETH') {
var message = 'Not a valid coin'
return props.dispatch(actions.displayWarning(message))
} else {
return props.dispatch(actions.pairUpdate(coin))
}
}
ShapeshiftForm.prototype.handleLiveInput = function () {
const props = this.props
var coinOptions = this.props.buyView.formView.coinOptions
var coin = document.getElementById('fromCoin').value
if (!coinOptions[coin.toUpperCase()] || coin.toUpperCase() === 'ETH') {
return null
} else {
return props.dispatch(actions.pairUpdate(coin))
}
}
ShapeshiftForm.prototype.renderInfo = function () {
const marketinfo = this.props.buyView.formView.marketinfo
const coinOptions = this.props.buyView.formView.coinOptions
var coin = marketinfo.pair.split('_')[0].toUpperCase()
return h('span', {
style: {
},
}, [
h('h3.flex-row.text-transform-uppercase', {
style: {
color: '#868686',
paddingTop: '4px',
justifyContent: 'space-around',
textAlign: 'center',
fontSize: '17px',
},
}, `Market Info for ${marketinfo.pair.replace('_', ' to ').toUpperCase()}:`),
h('.marketinfo', ['Status : ', `${coinOptions[coin].status}`]),
h('.marketinfo', ['Exchange Rate: ', `${marketinfo.rate}`]),
h('.marketinfo', ['Limit: ', `${marketinfo.limit}`]),
h('.marketinfo', ['Minimum : ', `${marketinfo.minimum}`]),
])
}
ShapeshiftForm.prototype.activeToggle = function (elementType) {
if (!this.props.buyView.formView.response || this.props.warning) return elementType
return `${elementType}.inactive`
ShapeshiftForm.prototype.renderMarketInfo = function () {
const { depositCoin } = this.state
const coinPair = `${depositCoin}_eth`
const { tokenExchangeRates } = this.props
const {
limit,
rate,
minimum,
} = tokenExchangeRates[coinPair] || {}
return h('div.shapeshift-form__metadata', {}, [
this.renderMetadata('Status', limit ? 'Available' : 'Unavailable'),
this.renderMetadata('Limit', limit),
this.renderMetadata('Exchange Rate', rate),
this.renderMetadata('Minimum', minimum),
])
}
ShapeshiftForm.prototype.renderLoading = function () {
return h('span', {
style: {
position: 'absolute',
left: '70px',
bottom: '194px',
background: 'transparent',
width: '229px',
height: '82px',
display: 'flex',
justifyContent: 'center',
},
}, [
h('img', {
style: {
width: '60px',
},
ShapeshiftForm.prototype.renderQrCode = function () {
const { depositAddress, isLoading } = this.state
const qrImage = qrcode(4, 'M')
qrImage.addData(depositAddress)
qrImage.make()
return h('div.shapeshift-form', {}, [
h('div.shapeshift-form__deposit-instruction', [
'Deposit your BTC to the address below:',
]),
h('div', depositAddress),
h('div.shapeshift-form__qr-code', [
isLoading
? h('img', {
src: 'images/loading.svg',
style: { width: '60px'},
})
: h('div', {
dangerouslySetInnerHTML: { __html: qrImage.createTableTag(4) },
}),
]),
this.renderMarketInfo(),
])
}
ShapeshiftForm.prototype.render = function () {
const { coinOptions, btnClass } = this.props
const { depositCoin, errorMessage, showQrCode, depositAddress } = this.state
const coinPair = `${depositCoin}_eth`
const { tokenExchangeRates } = this.props
const token = tokenExchangeRates[coinPair]
return h('div.shapeshift-form-wrapper', [
showQrCode
? this.renderQrCode()
: h('div.shapeshift-form', [
h('div.shapeshift-form__selectors', [
h('div.shapeshift-form__selector', [
h('div.shapeshift-form__selector-label', 'Deposit'),
h(SimpleDropdown, {
selectedOption: this.state.depositCoin,
onSelect: this.onCoinChange,
options: Object.entries(coinOptions).map(([coin]) => ({
value: coin.toLowerCase(),
displayValue: coin,
})),
}),
]),
h('div.icon.shapeshift-form__caret', {
style: { backgroundImage: 'url(images/caret-right.svg)'},
}),
h('div.shapeshift-form__selector', [
h('div.shapeshift-form__selector-label', [
'Receive',
]),
h('div.shapeshift-form__selector-input', ['ETH']),
]),
]),
h('div', {
className: classnames('shapeshift-form__address-input-wrapper', {
'shapeshift-form__address-input-wrapper--error': errorMessage,
}),
}, [
h('div.shapeshift-form__address-input-label', [
'Your Refund Address',
]),
h('input.shapeshift-form__address-input', {
type: 'text',
onChange: e => this.setState({
refundAddress: e.target.value,
errorMessage: '',
}),
}),
h('divshapeshift-form__address-input-error-message', [errorMessage]),
]),
this.renderMarketInfo(),
]),
!depositAddress && h('button.shapeshift-form__shapeshift-buy-btn', {
className: btnClass,
disabled: !token,
onClick: () => this.onBuyWithShapeShift(),
}, ['Buy']),
])
}

View File

@ -16,6 +16,7 @@ module.exports = connect(mapStateToProps)(ShiftListItem)
function mapStateToProps (state) {
return {
selectedAddress: state.metamask.selectedAddress,
conversionRate: state.metamask.conversionRate,
currentCurrency: state.metamask.currentCurrency,
}
@ -28,8 +29,10 @@ function ShiftListItem () {
}
ShiftListItem.prototype.render = function () {
const { selectedAddress, receivingAddress } = this.props
return (
h('div.tx-list-item.tx-list-clickable', {
selectedAddress === receivingAddress
? h('div.tx-list-item.tx-list-clickable', {
style: {
paddingTop: '20px',
paddingBottom: '20px',
@ -58,6 +61,7 @@ ShiftListItem.prototype.render = function () {
this.renderInfo(),
this.renderUtilComponents(),
])
: null
)
}

View File

@ -86,7 +86,9 @@ TokenCell.prototype.render = function () {
numberOfDecimals: 2,
conversionRate: currentTokenToFiatRate,
})
formattedFiat = `${currentTokenInFiat} ${currentCurrency.toUpperCase()}`
formattedFiat = currentTokenInFiat.toString() === '0'
? ''
: `${currentTokenInFiat} ${currentCurrency.toUpperCase()}`
}
const showFiat = Boolean(currentTokenInFiat) && currentCurrency.toUpperCase() !== symbol
@ -104,7 +106,7 @@ TokenCell.prototype.render = function () {
h(Identicon, {
className: 'token-list-item__identicon',
diameter: 45,
diameter: 50,
address,
network,
}),

View File

@ -1,6 +1,7 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const EthBalance = require('./eth-balance')
const addressSummary = require('../util').addressSummary
@ -9,18 +10,33 @@ const CopyButton = require('./copyButton')
const vreme = new (require('vreme'))()
const Tooltip = require('./tooltip')
const numberToBN = require('number-to-bn')
const actions = require('../actions')
const TransactionIcon = require('./transaction-list-item-icon')
const ShiftListItem = require('./shift-list-item')
module.exports = TransactionListItem
const mapDispatchToProps = dispatch => {
return {
retryTransaction: transactionId => dispatch(actions.retryTransaction(transactionId)),
}
}
module.exports = connect(null, mapDispatchToProps)(TransactionListItem)
inherits(TransactionListItem, Component)
function TransactionListItem () {
Component.call(this)
}
TransactionListItem.prototype.showRetryButton = function () {
const { transaction = {} } = this.props
const { status, time } = transaction
return status === 'submitted' && Date.now() - time > 30000
}
TransactionListItem.prototype.render = function () {
const { transaction, network, conversionRate, currentCurrency } = this.props
const { status } = transaction
if (transaction.key === 'shapeshift') {
if (network === '1') return h(ShiftListItem, transaction)
}
@ -32,7 +48,7 @@ TransactionListItem.prototype.render = function () {
var isMsg = ('msgParams' in transaction)
var isTx = ('txParams' in transaction)
var isPending = transaction.status === 'unapproved'
var isPending = status === 'unapproved'
let txParams
if (isTx) {
txParams = transaction.txParams
@ -44,7 +60,7 @@ TransactionListItem.prototype.render = function () {
const isClickable = ('hash' in transaction && isLinkable) || isPending
return (
h(`.transaction-list-item.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, {
h('.transaction-list-item.flex-column', {
onClick: (event) => {
if (isPending) {
this.props.showTx(transaction.id)
@ -56,9 +72,14 @@ TransactionListItem.prototype.render = function () {
},
style: {
padding: '20px 0',
alignItems: 'center',
},
}, [
h(`.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, {
style: {
width: '100%',
},
}, [
h('.identicon-wrapper.flex-column.flex-center.select-none', [
h(TransactionIcon, { txParams, transaction, isTx, isMsg }),
]),
@ -97,10 +118,46 @@ TransactionListItem.prototype.render = function () {
showFiat: false,
style: {fontSize: '15px'},
}) : h('.flex-column'),
]),
this.showRetryButton() && h('.transition-list-item__retry.grow-on-hover', {
onClick: event => {
event.stopPropagation()
this.resubmit()
},
style: {
height: '22px',
borderRadius: '22px',
color: '#F9881B',
padding: '0 20px',
backgroundColor: '#FFE3C9',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
fontSize: '8px',
cursor: 'pointer',
},
}, [
h('div', {
style: {
paddingRight: '2px',
},
}, 'Taking too long?'),
h('div', {
style: {
textDecoration: 'underline',
},
}, 'Retry with a higher gas price here'),
]),
])
)
}
TransactionListItem.prototype.resubmit = function () {
const { transaction } = this.props
this.props.retryTransaction(transaction.id)
}
function domainField (txParams) {
return h('div', {
style: {

View File

@ -170,6 +170,7 @@ TxListItem.prototype.getSendTokenTotal = async function () {
TxListItem.prototype.render = function () {
const {
transactionStatus,
transactionAmount,
onClick,
transActionId,
dateString,
@ -177,6 +178,7 @@ TxListItem.prototype.render = function () {
className,
} = this.props
const { total, fiatTotal } = this.state
const showFiatTotal = transactionAmount !== '0x0' && fiatTotal
return h(`div${className || ''}`, {
key: transActionId,
@ -232,13 +234,9 @@ TxListItem.prototype.render = function () {
style: {},
}, [
h('span', {
className: classnames('tx-list-value', {
'tx-list-value--confirmed': transactionStatus === 'confirmed',
}),
}, total),
h('span.tx-list-value', total),
fiatTotal && h('span.tx-list-fiat-value', fiatTotal),
showFiatTotal && h('span.tx-list-fiat-value', fiatTotal),
]),
]),

View File

@ -42,23 +42,22 @@ TxList.prototype.componentWillMount = function () {
}
TxList.prototype.render = function () {
return h('div.flex-column.tx-list-container', {}, [
return h('div.flex-column', [
h('div.flex-row.tx-list-header-wrapper', [
h('div.flex-row.tx-list-header', [
h('div', 'transactions'),
]),
]),
h('div.flex-column.tx-list-container', {}, [
this.renderTransaction(),
]),
])
}
TxList.prototype.renderTransaction = function () {
const { txsToRender, conversionRate } = this.props
return txsToRender.length
? txsToRender.map((transaction, i) => this.renderTransactionListItem(transaction, conversionRate))
? txsToRender.map((transaction, i) => this.renderTransactionListItem(transaction, conversionRate, i))
: [h(
'div.tx-list-item.tx-list-item--empty',
{ key: 'tx-list-none' },
@ -67,12 +66,16 @@ TxList.prototype.renderTransaction = function () {
}
// TODO: Consider moving TxListItem into a separate component
TxList.prototype.renderTransactionListItem = function (transaction, conversionRate) {
TxList.prototype.renderTransactionListItem = function (transaction, conversionRate, index) {
// console.log({transaction})
// refer to transaction-list.js:line 58
if (transaction.key === 'shapeshift') {
return h(ShiftListItem, transaction)
return h('div', {
key: `shapeshift${index}`,
}, [
h(ShiftListItem, transaction),
])
}
const props = {

View File

@ -74,18 +74,14 @@ TxView.prototype.renderButtons = function () {
return !selectedToken
? (
h('div.flex-row.flex-center.hero-balance-buttons', [
h('button.btn-clear', {
style: {
textAlign: 'center',
},
h('button.btn-clear.hero-balance-button', {
onClick: () => showModal({
name: 'BUY',
name: 'DEPOSIT_ETHER',
}),
}, 'DEPOSIT'),
h('button.btn-clear', {
h('button.btn-clear.hero-balance-button', {
style: {
textAlign: 'center',
marginLeft: '0.8em',
},
onClick: () => history.push(SEND_ROUTE),
@ -94,11 +90,7 @@ TxView.prototype.renderButtons = function () {
)
: (
h('div.flex-row.flex-center.hero-balance-buttons', [
h('button.btn-clear', {
style: {
textAlign: 'center',
marginLeft: '0.8em',
},
h('button.btn-clear.hero-balance-button', {
onClick: () => history.push(SEND_ROUTE),
}, 'SEND'),
])
@ -114,7 +106,7 @@ TxView.prototype.render = function () {
h('div.flex-row.phone-visible', {
style: {
margin: '1em 0.9em',
margin: '1.5em 1.2em 0',
justifyContent: 'space-between',
alignItems: 'center',
},
@ -150,7 +142,7 @@ TxView.prototype.render = function () {
!isMascara && h('div.open-in-browser', {
onClick: () => global.platform.openExtensionInBrowser(),
}, [h('img', { src: 'images/open.svg' })]),
}, [h('img', { src: 'images/popout.svg' })]),
]),

View File

@ -136,7 +136,7 @@ WalletView.prototype.render = function () {
selectedIdentity.name,
]),
h('button.wallet-view__details-button', 'DETAILS'),
h('button.btn-clear.wallet-view__details-button', 'DETAILS'),
]),
]),
@ -157,7 +157,7 @@ WalletView.prototype.render = function () {
h(TokenList),
h('button.wallet-view__add-token-button', {
h('button.btn-clear.wallet-view__add-token-button', {
onClick: () => history.push(ADD_TOKEN_ROUTE),
}, 'Add Token'),
])

View File

@ -94,6 +94,7 @@
padding: 12px 0;
font-weight: 600;
cursor: pointer;
position: relative;
&:hover {
background-color: rgba(0, 0, 0, .05);
@ -164,9 +165,18 @@
&__buttons {
display: flex;
flex-flow: column nowrap;
flex-flow: row nowrap;
margin: 30px 0 51px;
flex: 0 0 auto;
align-items: center;
justify-content: center;
}
&__button {
flex: 1 0 141px;
margin: 0 12px;
padding: 10px 22px;
height: 54px;
}
&__token-icons-container {
@ -324,18 +334,10 @@
}
&__buttons {
flex-flow: row nowrap;
width: 100%;
align-items: center;
justify-content: center;
padding: 12px 0;
margin: 0;
border-top: 1px solid $gallery;
button {
flex: 1 0 auto;
margin: 0 12px;
}
width: 100%;
}
}
}

View File

@ -6,9 +6,43 @@
background-color: #02c9b1; // TODO: reusable color in colors.css
}
button.btn-clear {
.btn-clear {
background: $white;
border: 1px solid;
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;
&:hover {
border-color: $scorpion;
}
}
// No longer used in flat design, remove when modal buttons done

View File

@ -2,13 +2,15 @@
position: relative;
align-items: center;
font-family: Roboto;
flex: 0 0 auto;
flex: 1 0 auto;
flex-flow: column nowrap;
box-shadow: 0 2px 4px 0 rgba($black, .08);
border-radius: 8px;
display: flex;
@media screen and (max-width: 575px) {
width: 100%;
box-shadow: initial;
}
@media screen and (min-width: 576px) {
@ -102,15 +104,10 @@
.confirm-screen-back-button {
background: transparent;
border: 1px solid $curious-blue;
left: 24px;
position: absolute;
text-align: center;
color: $curious-blue;
padding: 6px 13px 7px 12px;
border-radius: 2px;
height: 30px;
width: 54px;
padding: 6px 12px;
font-size: .7rem;
@media screen and (max-width: $break-small) {
margin-right: 12px;
@ -277,8 +274,8 @@ section .confirm-screen-account-number,
}
.confirm-screen-confirm-button {
height: 62px;
border-radius: 2px;
height: 50px;
border-radius: 4px;
background-color: #02c9b1;
font-size: 16px;
color: $white;
@ -290,11 +287,11 @@ section .confirm-screen-account-number,
box-shadow: none;
flex: 1 0 auto;
font-weight: 300;
margin: 0 8px;
margin: 0 5px;
}
.btn-light.confirm-screen-cancel-button {
height: 62px;
height: 50px;
background: none;
border: none;
opacity: 1;
@ -303,12 +300,11 @@ section .confirm-screen-account-number,
padding-top: 15px;
padding-bottom: 15px;
font-size: 16px;
line-height: 32px;
box-shadow: none;
cursor: pointer;
flex: 1 0 auto;
font-weight: 300;
margin: 0 8px;
margin: 0 5px;
}
#pending-tx-form {
@ -317,7 +313,7 @@ section .confirm-screen-account-number,
display: flex;
flex-flow: row nowrap;
background-color: $white;
padding: 12px 18px;
padding: 12px;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
width: 100%;

View File

@ -17,7 +17,16 @@
@media screen and (min-width: 576px) {
height: 75px;
justify-content: center;
}
.metafox-icon {
cursor: pointer;
}
}
.app-header--initialized {
@media screen and (min-width: 576px) {
&::after {
content: '';
position: absolute;
@ -27,10 +36,6 @@
bottom: -32px;
}
}
.metafox-icon {
cursor: pointer;
}
}
.app-header-contents {
@ -53,7 +58,7 @@
}
@media screen and (min-width: 1281px) {
width: 65vw;
width: 62vw;
}
}
@ -61,8 +66,10 @@
font-family: Roboto;
text-transform: uppercase;
font-weight: 400;
color: #22232c; // $shark
line-height: 29px;
font-size: 1.1rem;
position: relative;
padding-left: 15px;
color: #5b5d67;
@media screen and (max-width: 575px) {
display: none;

View File

@ -16,7 +16,8 @@
flex-direction: row;
justify-content: flex-start;
align-items: center;
margin: 2.8em 2.37em .8em;
margin: 2.3em 2.37em .8em;
flex: 0 0 auto;
}
.balance-container {
@ -37,13 +38,16 @@
}
.balance-display {
.token-amount {
color: $black;
}
@media screen and (max-width: $break-small) {
text-align: center;
.token-amount {
font-size: 175%;
margin-top: 12.5%;
font-size: 1.75rem;
margin-top: 1rem;
}
.fiat-amount {
@ -54,12 +58,12 @@
}
@media screen and (min-width: $break-large) {
margin-left: 3%;
margin-left: .8em;
justify-content: flex-start;
align-items: flex-start;
.token-amount {
font-size: 135%;
font-size: 1.5rem;
}
.fiat-amount {
@ -69,13 +73,6 @@
}
}
.balance-icon {
border-radius: 25px;
width: 45px;
height: 45px;
border: 1px solid $alto;
}
.hero-balance-buttons {
@media screen and (max-width: $break-small) {
@ -89,26 +86,9 @@
flex-grow: 2;
justify-content: flex-end;
}
button.btn-clear {
background: $white;
border: 1px solid;
border-radius: 2px;
font-size: 12px;
@media screen and (max-width: $break-small) {
border-color: $curious-blue;
color: $curious-blue;
height: 36px;
}
@media screen and (min-width: $break-large) {
border-color: $curious-blue;
color: $curious-blue;
padding: 0;
width: 85px;
height: 34px;
}
}
}
}
.hero-balance-button {
width: 6rem;
}

View File

@ -53,3 +53,5 @@
@import './editable-label.scss';
@import './pages/index.scss';
@import './new-account.scss';

View File

@ -258,19 +258,10 @@
width: 286px;
}
.btn-clear {
min-height: 28px;
font-size: 14px;
border-color: $curious-blue;
color: $curious-blue;
border-radius: 2px;
flex-basis: 100%;
width: 75%;
.account-modal__button {
margin-top: 17px;
padding: 10px 22px;
height: 44px;
width: 235px;
font-family: Roboto;
}
}
@ -346,17 +337,17 @@
display: flex;
flex-direction: row;
justify-content: center;
}
.btn-clear {
.export-private-key__button {
margin-top: 17px;
padding: 10px 22px;
width: 141px;
height: 54px;
}
}
.btn-cancel {
.export-private-key__button--cancel {
margin-right: 15px;
border-color: $dusty-gray;
color: $scorpion;
}
}
.private-key-password-display-wrapper {
@ -495,10 +486,9 @@
.hide-token-confirmation {
min-height: 250.72px;
width: 374.49px;
border-radius: 4px;
background-color: #FFFFFF;
box-shadow: 0 1px 7px 0 rgba(0,0,0,0.5);
background-color: $white;
box-shadow: 0 1px 7px 0 rgba(0, 0, 0, .5);
&__container {
padding: 24px 27px 21px;
@ -508,7 +498,7 @@
}
&__identicon {
margin-bottom: 10px
margin-bottom: 10px;
}
&__symbol {
@ -547,20 +537,11 @@
justify-content: center;
margin-top: 15px;
width: 100%;
button {
height: 44px;
width: 113px;
border: 1px solid $scorpion;
border-radius: 2px;
color: $tundora;
font-family: Roboto;
font-size: 14px;
line-height: 20px;
text-align: center;
margin-left: 4px;
margin-right: 4px;
}
&__button {
width: 141px;
margin: 0 5px;
}
}
@ -599,3 +580,256 @@
font-size: 17px;
color: $nile-blue;
}
// Deposit Ether Modal
.deposit-ether-modal {
border-radius: 8px;
font-family: Roboto;
display: flex;
flex-flow: column;
height: 100%;
&__header {
width: 100%;
border-radius: 8px 8px 0 0;
background-color: $mid-gray;
display: flex;
position: relative;
padding: 25px;
flex-flow: column;
align-items: flex-start;
&__title {
color: $white;
font-size: 24px;
line-height: 32px;
}
&__description {
color: $white;
font-size: 16px;
line-height: 22px;
margin-top: 10px;
}
&__close::after {
content: '\00D7';
font-size: 2em;
color: $white;
position: absolute;
top: 20.8px;
right: 28px;
cursor: pointer;
}
}
&__buy-rows {
width: 100%;
padding: 33px;
padding-top: 0px;
display: flex;
flex-flow: column nowrap;
flex: 1;
overflow-y: auto;
@media screen and (max-width: 575px) {
height: 0;
}
}
&__buy-row {
border-bottom: 1px solid $alto;
display: flex;
justify-content: space-between;
align-items: center;
flex: 1;
padding-bottom: 25px;
padding-top: 25px;
@media screen and (max-width: 575px) {
min-height: 360px;
flex-flow: column;
justify-content: center;
padding-top: 45px;
}
&__back {
position: absolute;
top: 10px;
left: 0px;
}
&__shapeshift-buy {
padding-top: 25px;
position: relative;
@media screen and (max-width: 575px) {
display: flex;
justify-content: space-between;
align-items: center;
flex: 1;
padding-bottom: 25px;
flex-flow: column;
justify-content: center;
padding-top: 20px;
min-height: 240px;
border: none;
}
}
&__logo {
display: flex;
justify-content: center;
flex: 0.3 1 auto;
@media screen and (min-width: 575px) {
min-width: 215px;
}
}
&__coinbase-logo {
height: 40px;
width: 180px;
}
&__shapeshift-logo {
height: 60px;
width: 174px;
}
&__eth-logo {
border-radius: 50%;
width: 68px;
height: 68px;
border: 3px solid $tundora;
z-index: 25;
padding: 4px;
background-color: #fff;
}
&__right {
display: flex;
}
&__description {
color: $cape-cod;
flex: 0.5 1 auto;
@media screen and (min-width: 575px) {
min-width: 315px;
}
&__title {
font-size: 20px;
line-height: 30px;
}
&__text {
font-size: 14px;
line-height: 22px;
margin-top: 7px;
}
}
&__button {
display: flex;
justify-content: flex-end;
@media screen and (min-width: 575px) {
min-width: 300px;
}
}
}
&__buy-row:last-of-type {
border-bottom: 0px;
}
&__deposit-button, .shapeshift-form__shapeshift-buy-btn {
height: 54px;
width: 257px;
border: 1px solid $curious-blue;
border-radius: 4px;
display: flex;
justify-content: center;
font-size: 16px;
color: $curious-blue;
background-color: $white;
}
.shapeshift-form-wrapper {
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
margin-top: 28px;
flex: 1 0 auto;
.shapeshift-form {
width: auto;
&__caret {
width: auto;
flex: 1;
}
}
}
.shapeshift-form__shapeshift-buy-btn {
margin-top: 10px;
}
.simple-dropdown {
color: #5B5D67;
font-size: 16px;
font-weight: 300;
line-height: 21px;
border: 1px solid #D8D8D8;
background-color: #FFFFFF;
text-align: center;
width: 100%;
height: 45px;
line-height: 44px;
font-family: Montserrat Light;
}
.simple-dropdown__selected {
text-align: center;
}
}
//Notification Modal
.notification-modal-wrapper {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
position: relative;
border: 1px solid $alto;
box-shadow: 0 0 2px 2px $alto;
font-family: Roboto;
}
.notification-modal-header {
background: $wild-sand;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 30px;
font-size: 22px;
color: $nile-blue;
height: 79px;
}
.notification-modal-message {
padding: 20px;
}
.notification-modal-message {
width: 100%;
display: flex;
justify-content: center;
font-size: 17px;
color: $nile-blue;
}

View File

@ -8,42 +8,26 @@
}
.network-component.pointer {
border: 1px solid $shark;
border: 2px solid $silver;
border-radius: 82px;
padding: 6px;
padding: 3px;
flex: 0 0 auto;
&.ethereum-network {
border-color: rgb(3, 135, 137);
.menu-icon-circle div {
&.ethereum-network .menu-icon-circle div {
background-color: rgba(3, 135, 137, .7) !important;
}
}
&.ropsten-test-network {
border-color: rgb(233, 21, 80);
.menu-icon-circle div {
&.ropsten-test-network .menu-icon-circle div {
background-color: rgba(233, 21, 80, .7) !important;
}
}
&.kovan-test-network {
border-color: rgb(105, 4, 150);
.menu-icon-circle div {
&.kovan-test-network .menu-icon-circle div {
background-color: rgba(105, 4, 150, .7) !important;
}
}
&.rinkeby-test-network {
border-color: rgb(235, 179, 63);
.menu-icon-circle div {
&.rinkeby-test-network .menu-icon-circle div {
background-color: rgba(235, 179, 63, .7) !important;
}
}
}
.dropdown-menu-item {
@ -66,11 +50,12 @@
}
.network-name {
line-height: 15px;
padding: 0 4px;
font-family: Roboto;
font-size: 12px;
flex: 1 0 auto;
color: $tundora;
font-weight: 500;
}
.network-droppo {
@ -167,3 +152,6 @@
line-height: 18px;
}
.network-caret {
margin: 0 8px 2px;
}

View File

@ -0,0 +1,192 @@
.new-account {
width: 376px;
background-color: #FFFFFF;
box-shadow: 0 0 7px 0 rgba(0,0,0,0.08);
z-index: 25;
padding-bottom: 31px;
&__header {
display: flex;
flex-flow: column;
border-bottom: 1px solid $geyser;
}
&__title {
color: $tundora;
font-family: Roboto;
font-size: 32px;
font-weight: 500;
line-height: 43px;
margin-top: 22px;
margin-left: 29px;
}
&__tabs {
margin-left: 22px;
display: flex;
margin-top: 10px;
&__tab {
height: 54px;
width: 75px;
padding: 15px 10px;
color: $dusty-gray;
font-family: Roboto;
font-size: 18px;
line-height: 24px;
text-align: center;
}
&__tab:first-of-type {
margin-right: 20px;
}
&__unselected:hover {
color: $black;
border-bottom: none;
}
&__selected {
color: $curious-blue;
border-bottom: 3px solid $curious-blue;
}
}
}
.new-account-import-form {
&__select-section {
display: flex;
justify-content: space-evenly;
align-items: center;
margin-top: 29px;
}
&__select-label {
color: $scorpion;
font-family: Roboto;
font-size: 16px;
line-height: 21px;
}
&__select {
height: 54px;
width: 210px;
border: 1px solid #D2D8DD;
border-radius: 4px;
background-color: #FFFFFF;
display: flex;
align-items: center;
.Select-control,
.Select-control:hover {
height: 100%;
border: none;
box-shadow: none;
.Select-value {
display: flex;
align-items: center;
}
}
}
&__instruction {
color: $scorpion;
font-family: Roboto;
font-size: 16px;
line-height: 21px;
align-self: flex-start;
margin-left: 30px;
}
&__private-key {
display: flex;
flex-flow: column;
align-items: center;
margin-top: 34px;
}
&__input-password {
height: 54px;
width: 315px;
border: 1px solid $geyser;
border-radius: 4px;
background-color: $white;
margin-top: 16px;
color: $scorpion;
font-family: Roboto;
font-size: 16px;
padding: 0px 20px;
}
&__json {
display: flex;
flex-flow: column;
align-items: center;
margin-top: 29px;
}
}
.new-account-create-form {
display: flex;
flex-flow: column;
align-items: center;
&__input-label {
color: $scorpion;
font-family: Roboto;
font-size: 16px;
line-height: 21px;
margin-top: 29px;
align-self: flex-start;
margin-left: 30px;
}
&__input {
height: 54px;
width: 315.84px;
border: 1px solid $geyser;
border-radius: 4px;
background-color: $white;
color: $scorpion;
font-family: Roboto;
font-size: 16px;
line-height: 21px;
margin-top: 15px;
padding: 0px 20px;
}
&__buttons {
margin-top: 39px;
display: flex;
width: 100%;
justify-content: space-evenly;
}
&__button-cancel,
&__button-create {
height: 55px;
width: 150px;
border-radius: 2px;
background-color: #FFFFFF;
}
&__button-cancel {
border: 1px solid $dusty-gray;
color: $dusty-gray;
font-family: Roboto;
font-size: 16px;
line-height: 21px;
text-align: center;
}
&__button-create {
border: 1px solid $curious-blue;
color: $curious-blue;
font-family: Roboto;
font-size: 16px;
line-height: 21px;
text-align: center;
}
}

View File

@ -4,7 +4,7 @@
// Component Colors
$tx-view-bg: $white;
$wallet-view-bg: $wild-sand;
$wallet-view-bg: $alabaster;
// Main container
.main-container {
@ -40,6 +40,8 @@ $wallet-view-bg: $wild-sand;
.open-in-browser {
cursor: pointer;
display: flex;
justify-content: center;
}
// wallet view and sidebar
@ -47,7 +49,7 @@ $wallet-view-bg: $wild-sand;
.wallet-view {
display: flex;
flex-direction: column;
flex: 33.5 1 33.5%;
flex: 32 1 32%;
width: 0;
background: $wallet-view-bg;
z-index: 200;
@ -69,22 +71,18 @@ $wallet-view-bg: $wild-sand;
}
&__keyring-label {
height: 40px;
height: 50px;
color: $dusty-gray;
font-family: Roboto;
font-size: 10px;
line-height: 40px;
text-align: right;
padding: 0 20px;
padding: 17px 20px 0;
box-sizing: border-box;
}
&__details-button {
color: $curious-blue;
font-size: 10px;
line-height: 13px;
text-align: center;
border: 1px solid $curious-blue;
border-radius: 10.5px;
border-radius: 17px;
background-color: transparent;
margin: 0 auto;
padding: 4px 12px;
@ -121,16 +119,14 @@ $wallet-view-bg: $wild-sand;
&__add-token-button {
flex: 0 0 auto;
color: $dusty-gray;
font-size: 14px;
line-height: 19px;
text-align: center;
margin: 36px auto;
border: 1px solid $dusty-gray;
border-radius: 2px;
font-weight: 300;
background: none;
padding: 9px 30px;
padding: .7rem 2rem;
transition: border-color .3s ease;
&:hover {
border-color: $curious-blue;
}
}
}
@ -159,7 +155,7 @@ $wallet-view-bg: $wild-sand;
background: rgb(250, 250, 250);
z-index: $sidebar-z-index;
position: fixed;
top: 56px;
top: 66px;
left: 0;
right: 0;
bottom: 0;
@ -199,7 +195,7 @@ $wallet-view-bg: $wild-sand;
.main-container {
// margin-top: 6.9vh;
width: 85%;
width: 85vw;
height: 90vh;
box-shadow: 0 0 7px 0 rgba(0, 0, 0, .08);
}
@ -208,7 +204,7 @@ $wallet-view-bg: $wild-sand;
@media screen and (min-width: 769px) {
.main-container {
// margin-top: 6.9vh;
width: 80%;
width: 80vw;
height: 82vh;
box-shadow: 0 0 7px 0 rgba(0, 0, 0, .08);
}
@ -217,7 +213,7 @@ $wallet-view-bg: $wild-sand;
@media screen and (min-width: 1281px) {
.main-container {
// margin-top: 6.9vh;
width: 65%;
width: 62vw;
height: 82vh;
box-shadow: 0 0 7px 0 rgba(0, 0, 0, .08);
}
@ -239,14 +235,6 @@ $wallet-view-bg: $wild-sand;
overflow-y: auto;
background-color: $white;
}
button.btn-clear {
width: 93px;
height: 50px;
font-size: .7em;
background: $white;
border: 1px solid;
}
}
// wallet view
@ -254,9 +242,9 @@ $wallet-view-bg: $wild-sand;
font-size: 24px;
font-weight: 300;
line-height: 20px;
color: $scorpion;
color: $black;
margin-top: 8px;
margin-bottom: 24px;
margin-bottom: .9rem;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;

View File

@ -526,8 +526,9 @@
}
&__form {
margin: 13px 0;
padding: 13px 0;
width: 100%;
overflow-y: auto;
@media screen and (max-width: $break-small) {
padding: 13px 0;
@ -651,11 +652,11 @@
border: 1px solid $curious-blue;
border-radius: 4px;
background-color: $white;
padding: 5px;
position: absolute;
right: 15px;
top: 14px;
cursor: pointer;
font-size: 1em;
}
&__sliders-icon {
@ -677,38 +678,13 @@
border-top: 1px solid $alto;
background: $white;
padding: 0 12px;
flex-shrink: 0;
}
&__next-btn,
&__cancel-btn,
&__next-btn__disabled {
width: 163px;
text-align: center;
height: 55px;
border-radius: 2px;
background-color: $white;
font-family: Roboto;
font-size: 16px;
font-weight: 300;
line-height: 21px;
border: 1px solid;
margin: 0 4px;
}
&__next-btn,
&__next-btn__disabled {
color: $curious-blue;
border-color: $curious-blue;
}
&__next-btn__disabled {
opacity: .5;
cursor: auto;
}
&__cancel-btn {
color: $dusty-gray;
border-color: $dusty-gray;
width: 163px;
margin: 0 4px;
}
&__customize-gas {

View File

@ -12,7 +12,7 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
position: relative;
&__token-balance {
font-size: 130%;
font-size: 1.5rem;
@media #{$wallet-balance-breakpoint-range} {
font-size: 105%;
@ -34,7 +34,8 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
}
&--active {
background-color: rgba($wallet-balance-bg, 1);
background-color: $manatee;
color: $white;
}
&__identicon {
@ -62,11 +63,11 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
height: 55px;
width: 191px;
border-radius: 4px;
background-color: rgba(0,0,0,0.82);
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.5);
position: fixed;
margin-top: 20px;
margin-left: 105px;
background-color: rgba(0, 0, 0, .82);
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .5);
position: absolute;
top: 60px;
right: 25px;
z-index: 2000;
&__close-area {

View File

@ -6,6 +6,10 @@
}
}
.tx-list-header-wrapper {
flex: 0 0 auto;
}
.tx-list-header {
text-transform: capitalize;
}
@ -32,13 +36,9 @@
}
@media screen and (min-width: $break-large) {
.tx-list-header-wrapper {
flex: 0 0 55px;
}
.tx-list-header {
font-size: 16px;
margin: 1.5em 2.37em;
margin: 1.1em 2.37em .8em;
}
.tx-list-container::-webkit-scrollbar {
@ -73,7 +73,7 @@
}
@media screen and (min-width: $break-large) {
padding-bottom: 12px;
padding-bottom: 8px;
}
}
@ -91,21 +91,13 @@
}
.tx-list-date-wrapper {
flex: 1 1 auto;
@media screen and (max-width: $break-small) {
margin-top: 6px;
}
@media screen and (min-width: $break-large) {
margin-top: 12px;
}
flex: 1 1 auto;
}
.tx-list-content-wrapper {
align-items: stretch;
margin-bottom: 4px;
margin-top: 2px;
flex: 1 0 auto;
width: 100%;
display: flex;
@ -115,7 +107,7 @@
font-size: 12px;
.tx-list-status {
font-size: 14px !important;
font-size: 12px !important;
}
.tx-list-account {
@ -129,7 +121,7 @@
.tx-list-fiat-value {
font-size: 12px;
line-height: 16px;
line-height: 22px;
}
}
}
@ -210,7 +202,7 @@
}
@media screen and (min-width: $break-large) {
margin: 0 2.37em;
padding: 0 2.37em;
}
&:last-of-type {
@ -259,6 +251,8 @@
}
.tx-list-fiat-value {
font-size: 12px;
line-height: initial;
text-align: right;
text-overflow: ellipsis;
white-space: nowrap;

View File

@ -8,7 +8,8 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
background: rgba($wallet-balance-bg, 0);
&--active {
background: rgba($wallet-balance-bg, 1);
background: $manatee;
color: $white;
}
}
@ -41,7 +42,7 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
align-items: flex-start;
.token-amount {
font-size: 135%;
font-size: 1.5rem;
}
.fiat-amount {
@ -61,11 +62,13 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
}
}
}
.balance-icon {
border-radius: 25px;
width: 45px;
height: 45px;
border: 1px solid $alto;
}
}
.balance-icon {
border-radius: 25px;
width: 50px;
height: 50px;
border: 1px solid $alto;
padding: 5px;
background: $white;
}

View File

@ -51,14 +51,14 @@
@font-face {
font-family: 'DIN NEXT';
src: url('/fonts/DIN NEXT/DIN NEXT W01 Regular.otf') format('opentype');
src: url('/fonts/DIN Next/DIN Next W01 Regular.otf') format('opentype');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'DIN NEXT Light';
src: url('/fonts/DIN NEXT/DIN NEXT W10 Light.otf') format('opentype');
src: url('/fonts/DIN Next/DIN Next W10 Light.otf') format('opentype');
font-weight: 400;
font-style: normal;
}

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