mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge branch 'develop' into i4409-i4410-ens-input-enhancements
This commit is contained in:
commit
06307ef8ae
10
CHANGELOG.md
10
CHANGELOG.md
@ -2,7 +2,15 @@
|
|||||||
|
|
||||||
## Current Master
|
## Current Master
|
||||||
|
|
||||||
- Fix bug where account reset did not work with custom RPC providers.
|
## 4.8.0 Thur Jun 14 2018
|
||||||
|
|
||||||
|
- [#4513](https://github.com/MetaMask/metamask-extension/pull/4513): Attempting to import an empty private key will now show a clear error.
|
||||||
|
- [#4570](https://github.com/MetaMask/metamask-extension/pull/4570): Fix bug where metamask data would stop being written to disk after prolonged use.
|
||||||
|
- [#4523](https://github.com/MetaMask/metamask-extension/pull/4523): Fix bug where account reset did not work with custom RPC providers.
|
||||||
|
- [#4524](https://github.com/MetaMask/metamask-extension/pull/4524): Fix for Brave i18n getAcceptLanguages.
|
||||||
|
- [#4557](https://github.com/MetaMask/metamask-extension/pull/4557): Fix bug where nonce mutex was never released.
|
||||||
|
- [#4566](https://github.com/MetaMask/metamask-extension/pull/4566): Add phishing notice.
|
||||||
|
- [#4591](https://github.com/MetaMask/metamask-extension/pull/4591): Allow Copying Token Addresses and link to Token on Etherscan.
|
||||||
|
|
||||||
## 4.7.4 Tue Jun 05 2018
|
## 4.7.4 Tue Jun 05 2018
|
||||||
|
|
||||||
|
@ -146,6 +146,9 @@
|
|||||||
"copy": {
|
"copy": {
|
||||||
"message": "Copy"
|
"message": "Copy"
|
||||||
},
|
},
|
||||||
|
"copyContractAddress": {
|
||||||
|
"message": "Copy Contract Address"
|
||||||
|
},
|
||||||
"copyToClipboard": {
|
"copyToClipboard": {
|
||||||
"message": "Copy to clipboard"
|
"message": "Copy to clipboard"
|
||||||
},
|
},
|
||||||
@ -958,6 +961,9 @@
|
|||||||
"viewAccount": {
|
"viewAccount": {
|
||||||
"message": "View Account"
|
"message": "View Account"
|
||||||
},
|
},
|
||||||
|
"viewOnEtherscan": {
|
||||||
|
"message": "View on Etherscan"
|
||||||
|
},
|
||||||
"visitWebSite": {
|
"visitWebSite": {
|
||||||
"message": "Visit our web site"
|
"message": "Visit our web site"
|
||||||
},
|
},
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "__MSG_appName__",
|
"name": "__MSG_appName__",
|
||||||
"short_name": "__MSG_appName__",
|
"short_name": "__MSG_appName__",
|
||||||
"version": "4.7.4",
|
"version": "4.8.0",
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"author": "https://metamask.io",
|
"author": "https://metamask.io",
|
||||||
"description": "__MSG_appDescription__",
|
"description": "__MSG_appDescription__",
|
||||||
|
@ -16,7 +16,18 @@ const accountImporter = {
|
|||||||
|
|
||||||
strategies: {
|
strategies: {
|
||||||
'Private Key': (privateKey) => {
|
'Private Key': (privateKey) => {
|
||||||
const stripped = ethUtil.stripHexPrefix(privateKey)
|
if (!privateKey) {
|
||||||
|
throw new Error('Cannot import an empty key.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefixed = ethUtil.addHexPrefix(privateKey)
|
||||||
|
const buffer = ethUtil.toBuffer(prefixed)
|
||||||
|
|
||||||
|
if (!ethUtil.isValidPrivate(buffer)) {
|
||||||
|
throw new Error('Cannot import invalid private key.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const stripped = ethUtil.stripHexPrefix(prefixed)
|
||||||
return stripped
|
return stripped
|
||||||
},
|
},
|
||||||
'JSON File': (input, password) => {
|
'JSON File': (input, password) => {
|
||||||
|
@ -16,6 +16,7 @@ const ExtensionPlatform = require('./platforms/extension')
|
|||||||
const Migrator = require('./lib/migrator/')
|
const Migrator = require('./lib/migrator/')
|
||||||
const migrations = require('./migrations/')
|
const migrations = require('./migrations/')
|
||||||
const PortStream = require('./lib/port-stream.js')
|
const PortStream = require('./lib/port-stream.js')
|
||||||
|
const createStreamSink = require('./lib/createStreamSink')
|
||||||
const NotificationManager = require('./lib/notification-manager.js')
|
const NotificationManager = require('./lib/notification-manager.js')
|
||||||
const MetamaskController = require('./metamask-controller')
|
const MetamaskController = require('./metamask-controller')
|
||||||
const firstTimeState = require('./first-time-state')
|
const firstTimeState = require('./first-time-state')
|
||||||
@ -273,7 +274,7 @@ function setupController (initState, initLangCode) {
|
|||||||
asStream(controller.store),
|
asStream(controller.store),
|
||||||
debounce(1000),
|
debounce(1000),
|
||||||
storeTransform(versionifyData),
|
storeTransform(versionifyData),
|
||||||
storeTransform(persistData),
|
createStreamSink(persistData),
|
||||||
(error) => {
|
(error) => {
|
||||||
log.error('MetaMask - Persistence pipeline failed', error)
|
log.error('MetaMask - Persistence pipeline failed', error)
|
||||||
}
|
}
|
||||||
@ -289,7 +290,7 @@ function setupController (initState, initLangCode) {
|
|||||||
return versionedData
|
return versionedData
|
||||||
}
|
}
|
||||||
|
|
||||||
function persistData (state) {
|
async function persistData (state) {
|
||||||
if (!state) {
|
if (!state) {
|
||||||
throw new Error('MetaMask - updated state is missing', state)
|
throw new Error('MetaMask - updated state is missing', state)
|
||||||
}
|
}
|
||||||
@ -297,12 +298,13 @@ function setupController (initState, initLangCode) {
|
|||||||
throw new Error('MetaMask - updated state does not have data', state)
|
throw new Error('MetaMask - updated state does not have data', state)
|
||||||
}
|
}
|
||||||
if (localStore.isSupported) {
|
if (localStore.isSupported) {
|
||||||
localStore.set(state)
|
try {
|
||||||
.catch((err) => {
|
await localStore.set(state)
|
||||||
|
} catch (err) {
|
||||||
|
// log error so we dont break the pipeline
|
||||||
log.error('error setting state in local store:', err)
|
log.error('error setting state in local store:', err)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
return state
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -165,7 +165,7 @@ class TransactionController extends EventEmitter {
|
|||||||
// add default tx params
|
// add default tx params
|
||||||
txMeta = await this.addTxGasDefaults(txMeta)
|
txMeta = await this.addTxGasDefaults(txMeta)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
log.warn(error)
|
||||||
this.txStateManager.setTxStatusFailed(txMeta.id, error)
|
this.txStateManager.setTxStatusFailed(txMeta.id, error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
@ -264,7 +264,12 @@ class TransactionController extends EventEmitter {
|
|||||||
// must set transaction to submitted/failed before releasing lock
|
// must set transaction to submitted/failed before releasing lock
|
||||||
nonceLock.releaseLock()
|
nonceLock.releaseLock()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.txStateManager.setTxStatusFailed(txId, err)
|
// this is try-catch wrapped so that we can guarantee that the nonceLock is released
|
||||||
|
try {
|
||||||
|
this.txStateManager.setTxStatusFailed(txId, err)
|
||||||
|
} catch (err) {
|
||||||
|
log.error(err)
|
||||||
|
}
|
||||||
// must set transaction to submitted/failed before releasing lock
|
// must set transaction to submitted/failed before releasing lock
|
||||||
if (nonceLock) nonceLock.releaseLock()
|
if (nonceLock) nonceLock.releaseLock()
|
||||||
// continue with error chain
|
// continue with error chain
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const Config = require('./recipient-blacklist-config.json')
|
const Config = require('./recipient-blacklist.js')
|
||||||
|
|
||||||
/** @module*/
|
/** @module*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"blacklist": [
|
|
||||||
"0x627306090abab3a6e1400e9345bc60c78a8bef57",
|
|
||||||
"0xf17f52151ebef6c7334fad080c5704d77216b732",
|
|
||||||
"0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef",
|
|
||||||
"0x821aea9a577a9b44299b9c15c88cf3087f3b5544",
|
|
||||||
"0x0d1d4e623d10f9fba5db95830f7d3839406c6af2",
|
|
||||||
"0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e",
|
|
||||||
"0x2191ef87e392377ec08e7c08eb105ef5448eced5",
|
|
||||||
"0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5",
|
|
||||||
"0x6330a553fc93768f612722bb8c2ec78ac90b3bbc",
|
|
||||||
"0x5aeda56215b167893e80b4fe645ba6d5bab767de"
|
|
||||||
]
|
|
||||||
}
|
|
@ -0,0 +1,17 @@
|
|||||||
|
module.exports = {
|
||||||
|
'blacklist': [
|
||||||
|
// IDEX phisher
|
||||||
|
'0x9bcb0A9d99d815Bb87ee3191b1399b1Bcc46dc77',
|
||||||
|
// Ganache default seed phrases
|
||||||
|
'0x627306090abab3a6e1400e9345bc60c78a8bef57',
|
||||||
|
'0xf17f52151ebef6c7334fad080c5704d77216b732',
|
||||||
|
'0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef',
|
||||||
|
'0x821aea9a577a9b44299b9c15c88cf3087f3b5544',
|
||||||
|
'0x0d1d4e623d10f9fba5db95830f7d3839406c6af2',
|
||||||
|
'0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e',
|
||||||
|
'0x2191ef87e392377ec08e7c08eb105ef5448eced5',
|
||||||
|
'0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5',
|
||||||
|
'0x6330a553fc93768f612722bb8c2ec78ac90b3bbc',
|
||||||
|
'0x5aeda56215b167893e80b4fe645ba6d5bab767de',
|
||||||
|
],
|
||||||
|
}
|
@ -49,29 +49,35 @@ class NonceTracker {
|
|||||||
await this._globalMutexFree()
|
await this._globalMutexFree()
|
||||||
// await lock free, then take lock
|
// await lock free, then take lock
|
||||||
const releaseLock = await this._takeMutex(address)
|
const releaseLock = await this._takeMutex(address)
|
||||||
// evaluate multiple nextNonce strategies
|
try {
|
||||||
const nonceDetails = {}
|
// evaluate multiple nextNonce strategies
|
||||||
const networkNonceResult = await this._getNetworkNextNonce(address)
|
const nonceDetails = {}
|
||||||
const highestLocallyConfirmed = this._getHighestLocallyConfirmed(address)
|
const networkNonceResult = await this._getNetworkNextNonce(address)
|
||||||
const nextNetworkNonce = networkNonceResult.nonce
|
const highestLocallyConfirmed = this._getHighestLocallyConfirmed(address)
|
||||||
const highestSuggested = Math.max(nextNetworkNonce, highestLocallyConfirmed)
|
const nextNetworkNonce = networkNonceResult.nonce
|
||||||
|
const highestSuggested = Math.max(nextNetworkNonce, highestLocallyConfirmed)
|
||||||
|
|
||||||
const pendingTxs = this.getPendingTransactions(address)
|
const pendingTxs = this.getPendingTransactions(address)
|
||||||
const localNonceResult = this._getHighestContinuousFrom(pendingTxs, highestSuggested) || 0
|
const localNonceResult = this._getHighestContinuousFrom(pendingTxs, highestSuggested) || 0
|
||||||
|
|
||||||
nonceDetails.params = {
|
nonceDetails.params = {
|
||||||
highestLocallyConfirmed,
|
highestLocallyConfirmed,
|
||||||
highestSuggested,
|
highestSuggested,
|
||||||
nextNetworkNonce,
|
nextNetworkNonce,
|
||||||
|
}
|
||||||
|
nonceDetails.local = localNonceResult
|
||||||
|
nonceDetails.network = networkNonceResult
|
||||||
|
|
||||||
|
const nextNonce = Math.max(networkNonceResult.nonce, localNonceResult.nonce)
|
||||||
|
assert(Number.isInteger(nextNonce), `nonce-tracker - nextNonce is not an integer - got: (${typeof nextNonce}) "${nextNonce}"`)
|
||||||
|
|
||||||
|
// return nonce and release cb
|
||||||
|
return { nextNonce, nonceDetails, releaseLock }
|
||||||
|
} catch (err) {
|
||||||
|
// release lock if we encounter an error
|
||||||
|
releaseLock()
|
||||||
|
throw err
|
||||||
}
|
}
|
||||||
nonceDetails.local = localNonceResult
|
|
||||||
nonceDetails.network = networkNonceResult
|
|
||||||
|
|
||||||
const nextNonce = Math.max(networkNonceResult.nonce, localNonceResult.nonce)
|
|
||||||
assert(Number.isInteger(nextNonce), `nonce-tracker - nextNonce is not an integer - got: (${typeof nextNonce}) "${nextNonce}"`)
|
|
||||||
|
|
||||||
// return nonce and release cb
|
|
||||||
return { nextNonce, nonceDetails, releaseLock }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getCurrentBlock () {
|
async _getCurrentBlock () {
|
||||||
@ -85,8 +91,8 @@ class NonceTracker {
|
|||||||
|
|
||||||
async _globalMutexFree () {
|
async _globalMutexFree () {
|
||||||
const globalMutex = this._lookupMutex('global')
|
const globalMutex = this._lookupMutex('global')
|
||||||
const release = await globalMutex.acquire()
|
const releaseLock = await globalMutex.acquire()
|
||||||
release()
|
releaseLock()
|
||||||
}
|
}
|
||||||
|
|
||||||
async _takeMutex (lockId) {
|
async _takeMutex (lockId) {
|
||||||
|
@ -196,14 +196,14 @@ class PendingTransactionTracker extends EventEmitter {
|
|||||||
async _checkPendingTxs () {
|
async _checkPendingTxs () {
|
||||||
const signedTxList = this.getPendingTransactions()
|
const signedTxList = this.getPendingTransactions()
|
||||||
// in order to keep the nonceTracker accurate we block it while updating pending transactions
|
// in order to keep the nonceTracker accurate we block it while updating pending transactions
|
||||||
const nonceGlobalLock = await this.nonceTracker.getGlobalLock()
|
const { releaseLock } = await this.nonceTracker.getGlobalLock()
|
||||||
try {
|
try {
|
||||||
await Promise.all(signedTxList.map((txMeta) => this._checkPendingTx(txMeta)))
|
await Promise.all(signedTxList.map((txMeta) => this._checkPendingTx(txMeta)))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error('PendingTransactionWatcher - Error updating pending transactions')
|
log.error('PendingTransactionWatcher - Error updating pending transactions')
|
||||||
log.error(err)
|
log.error(err)
|
||||||
}
|
}
|
||||||
nonceGlobalLock.releaseLock()
|
releaseLock()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,9 +38,30 @@ web3.setProvider = function () {
|
|||||||
log.debug('MetaMask - overrode web3.setProvider')
|
log.debug('MetaMask - overrode web3.setProvider')
|
||||||
}
|
}
|
||||||
log.debug('MetaMask - injected web3')
|
log.debug('MetaMask - injected web3')
|
||||||
// export global web3, with usage-detection
|
|
||||||
setupDappAutoReload(web3, inpageProvider.publicConfigStore)
|
setupDappAutoReload(web3, inpageProvider.publicConfigStore)
|
||||||
|
|
||||||
|
// export global web3, with usage-detection and deprecation warning
|
||||||
|
|
||||||
|
/* TODO: Uncomment this area once auto-reload.js has been deprecated:
|
||||||
|
let hasBeenWarned = false
|
||||||
|
global.web3 = new Proxy(web3, {
|
||||||
|
get: (_web3, key) => {
|
||||||
|
// show warning once on web3 access
|
||||||
|
if (!hasBeenWarned && key !== 'currentProvider') {
|
||||||
|
console.warn('MetaMask: web3 will be deprecated in the near future in favor of the ethereumProvider \nhttps://github.com/MetaMask/faq/blob/master/detecting_metamask.md#web3-deprecation')
|
||||||
|
hasBeenWarned = true
|
||||||
|
}
|
||||||
|
// return value normally
|
||||||
|
return _web3[key]
|
||||||
|
},
|
||||||
|
set: (_web3, key, value) => {
|
||||||
|
// set value normally
|
||||||
|
_web3[key] = value
|
||||||
|
},
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
|
||||||
// set web3 defaultAccount
|
// set web3 defaultAccount
|
||||||
inpageProvider.publicConfigStore.subscribe(function (state) {
|
inpageProvider.publicConfigStore.subscribe(function (state) {
|
||||||
web3.eth.defaultAccount = state.selectedAddress
|
web3.eth.defaultAccount = state.selectedAddress
|
||||||
|
24
app/scripts/lib/createStreamSink.js
Normal file
24
app/scripts/lib/createStreamSink.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
const WritableStream = require('readable-stream').Writable
|
||||||
|
const promiseToCallback = require('promise-to-callback')
|
||||||
|
|
||||||
|
module.exports = createStreamSink
|
||||||
|
|
||||||
|
|
||||||
|
function createStreamSink(asyncWriteFn, _opts) {
|
||||||
|
return new AsyncWritableStream(asyncWriteFn, _opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
class AsyncWritableStream extends WritableStream {
|
||||||
|
|
||||||
|
constructor (asyncWriteFn, _opts) {
|
||||||
|
const opts = Object.assign({ objectMode: true }, _opts)
|
||||||
|
super(opts)
|
||||||
|
this._asyncWriteFn = asyncWriteFn
|
||||||
|
}
|
||||||
|
|
||||||
|
// write from incomming stream to state
|
||||||
|
_write (chunk, encoding, callback) {
|
||||||
|
promiseToCallback(this._asyncWriteFn(chunk, encoding))(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,8 +2,7 @@ const extension = require('extensionizer')
|
|||||||
const promisify = require('pify')
|
const promisify = require('pify')
|
||||||
const allLocales = require('../../_locales/index.json')
|
const allLocales = require('../../_locales/index.json')
|
||||||
|
|
||||||
const isSupported = extension.i18n && extension.i18n.getAcceptLanguages
|
const getPreferredLocales = extension.i18n ? promisify(
|
||||||
const getPreferredLocales = isSupported ? promisify(
|
|
||||||
extension.i18n.getAcceptLanguages,
|
extension.i18n.getAcceptLanguages,
|
||||||
{ errorFirst: false }
|
{ errorFirst: false }
|
||||||
) : async () => []
|
) : async () => []
|
||||||
@ -18,7 +17,21 @@ const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().r
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async function getFirstPreferredLangCode () {
|
async function getFirstPreferredLangCode () {
|
||||||
const userPreferredLocaleCodes = await getPreferredLocales()
|
let userPreferredLocaleCodes
|
||||||
|
|
||||||
|
try {
|
||||||
|
userPreferredLocaleCodes = await getPreferredLocales()
|
||||||
|
} catch (e) {
|
||||||
|
// Brave currently throws when calling getAcceptLanguages, so this handles that.
|
||||||
|
userPreferredLocaleCodes = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// safeguard for Brave Browser until they implement chrome.i18n.getAcceptLanguages
|
||||||
|
// https://github.com/MetaMask/metamask-extension/issues/4270
|
||||||
|
if (!userPreferredLocaleCodes){
|
||||||
|
userPreferredLocaleCodes = []
|
||||||
|
}
|
||||||
|
|
||||||
const firstPreferredLangCode = userPreferredLocaleCodes
|
const firstPreferredLangCode = userPreferredLocaleCodes
|
||||||
.map(code => code.toLowerCase())
|
.map(code => code.toLowerCase())
|
||||||
.find(code => existingLocaleCodes.includes(code))
|
.find(code => existingLocaleCodes.includes(code))
|
||||||
@ -26,3 +39,4 @@ async function getFirstPreferredLangCode () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = getFirstPreferredLangCode
|
module.exports = getFirstPreferredLangCode
|
||||||
|
|
||||||
|
@ -46,7 +46,6 @@ const GWEI_BN = new BN('1000000000')
|
|||||||
const percentile = require('percentile')
|
const percentile = require('percentile')
|
||||||
const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
|
const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
|
||||||
const cleanErrorStack = require('./lib/cleanErrorStack')
|
const cleanErrorStack = require('./lib/cleanErrorStack')
|
||||||
const DiagnosticsReporter = require('./lib/diagnostics-reporter')
|
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
|
|
||||||
module.exports = class MetamaskController extends EventEmitter {
|
module.exports = class MetamaskController extends EventEmitter {
|
||||||
@ -65,12 +64,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
const initState = opts.initState || {}
|
const initState = opts.initState || {}
|
||||||
this.recordFirstTimeInfo(initState)
|
this.recordFirstTimeInfo(initState)
|
||||||
|
|
||||||
// metamask diagnostics reporter
|
|
||||||
this.diagnostics = opts.diagnostics || new DiagnosticsReporter({
|
|
||||||
firstTimeInfo: initState.firstTimeInfo,
|
|
||||||
version,
|
|
||||||
})
|
|
||||||
|
|
||||||
// platform-specific api
|
// platform-specific api
|
||||||
this.platform = opts.platform
|
this.platform = opts.platform
|
||||||
|
|
||||||
@ -92,7 +85,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
this.preferencesController = new PreferencesController({
|
this.preferencesController = new PreferencesController({
|
||||||
initState: initState.PreferencesController,
|
initState: initState.PreferencesController,
|
||||||
initLangCode: opts.initLangCode,
|
initLangCode: opts.initLangCode,
|
||||||
diagnostics: this.diagnostics,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// currency controller
|
// currency controller
|
||||||
@ -189,9 +181,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
version,
|
version,
|
||||||
firstVersion: initState.firstTimeInfo.version,
|
firstVersion: initState.firstTimeInfo.version,
|
||||||
})
|
})
|
||||||
this.noticeController.updateNoticesList()
|
|
||||||
// to be uncommented when retrieving notices from a remote server.
|
|
||||||
// this.noticeController.startPolling()
|
|
||||||
|
|
||||||
this.shapeshiftController = new ShapeShiftController({
|
this.shapeshiftController = new ShapeShiftController({
|
||||||
initState: initState.ShapeShiftController,
|
initState: initState.ShapeShiftController,
|
||||||
@ -436,28 +425,24 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
* @returns {Object} vault
|
* @returns {Object} vault
|
||||||
*/
|
*/
|
||||||
async createNewVaultAndKeychain (password) {
|
async createNewVaultAndKeychain (password) {
|
||||||
const release = await this.createVaultMutex.acquire()
|
const releaseLock = await this.createVaultMutex.acquire()
|
||||||
let vault
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let vault
|
||||||
const accounts = await this.keyringController.getAccounts()
|
const accounts = await this.keyringController.getAccounts()
|
||||||
|
|
||||||
if (accounts.length > 0) {
|
if (accounts.length > 0) {
|
||||||
vault = await this.keyringController.fullUpdate()
|
vault = await this.keyringController.fullUpdate()
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
vault = await this.keyringController.createNewVaultAndKeychain(password)
|
vault = await this.keyringController.createNewVaultAndKeychain(password)
|
||||||
const accounts = await this.keyringController.getAccounts()
|
const accounts = await this.keyringController.getAccounts()
|
||||||
this.preferencesController.setAddresses(accounts)
|
this.preferencesController.setAddresses(accounts)
|
||||||
this.selectFirstIdentity()
|
this.selectFirstIdentity()
|
||||||
}
|
}
|
||||||
release()
|
releaseLock()
|
||||||
|
return vault
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
release()
|
releaseLock()
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
|
|
||||||
return vault
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -466,7 +451,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
* @param {} seed
|
* @param {} seed
|
||||||
*/
|
*/
|
||||||
async createNewVaultAndRestore (password, seed) {
|
async createNewVaultAndRestore (password, seed) {
|
||||||
const release = await this.createVaultMutex.acquire()
|
const releaseLock = await this.createVaultMutex.acquire()
|
||||||
try {
|
try {
|
||||||
// clear known identities
|
// clear known identities
|
||||||
this.preferencesController.setAddresses([])
|
this.preferencesController.setAddresses([])
|
||||||
@ -476,10 +461,10 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
const accounts = await this.keyringController.getAccounts()
|
const accounts = await this.keyringController.getAccounts()
|
||||||
this.preferencesController.setAddresses(accounts)
|
this.preferencesController.setAddresses(accounts)
|
||||||
this.selectFirstIdentity()
|
this.selectFirstIdentity()
|
||||||
release()
|
releaseLock()
|
||||||
return vault
|
return vault
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
release()
|
releaseLock()
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ const EventEmitter = require('events').EventEmitter
|
|||||||
const semver = require('semver')
|
const semver = require('semver')
|
||||||
const extend = require('xtend')
|
const extend = require('xtend')
|
||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
const hardCodedNotices = require('../../notices/notices.json')
|
const hardCodedNotices = require('../../notices/notices.js')
|
||||||
const uniqBy = require('lodash.uniqby')
|
const uniqBy = require('lodash.uniqby')
|
||||||
|
|
||||||
module.exports = class NoticeController extends EventEmitter {
|
module.exports = class NoticeController extends EventEmitter {
|
||||||
@ -16,8 +16,12 @@ module.exports = class NoticeController extends EventEmitter {
|
|||||||
noticesList: [],
|
noticesList: [],
|
||||||
}, opts.initState)
|
}, opts.initState)
|
||||||
this.store = new ObservableStore(initState)
|
this.store = new ObservableStore(initState)
|
||||||
|
// setup memStore
|
||||||
this.memStore = new ObservableStore({})
|
this.memStore = new ObservableStore({})
|
||||||
this.store.subscribe(() => this._updateMemstore())
|
this.store.subscribe(() => this._updateMemstore())
|
||||||
|
this._updateMemstore()
|
||||||
|
// pull in latest notices
|
||||||
|
this.updateNoticesList()
|
||||||
}
|
}
|
||||||
|
|
||||||
getNoticesList () {
|
getNoticesList () {
|
||||||
@ -29,9 +33,9 @@ module.exports = class NoticeController extends EventEmitter {
|
|||||||
return notices.filter((notice) => notice.read === false)
|
return notices.filter((notice) => notice.read === false)
|
||||||
}
|
}
|
||||||
|
|
||||||
getLatestUnreadNotice () {
|
getNextUnreadNotice () {
|
||||||
const unreadNotices = this.getUnreadNotices()
|
const unreadNotices = this.getUnreadNotices()
|
||||||
return unreadNotices[unreadNotices.length - 1]
|
return unreadNotices[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
async setNoticesList (noticesList) {
|
async setNoticesList (noticesList) {
|
||||||
@ -47,7 +51,7 @@ module.exports = class NoticeController extends EventEmitter {
|
|||||||
notices[index].read = true
|
notices[index].read = true
|
||||||
notices[index].body = ''
|
notices[index].body = ''
|
||||||
this.setNoticesList(notices)
|
this.setNoticesList(notices)
|
||||||
const latestNotice = this.getLatestUnreadNotice()
|
const latestNotice = this.getNextUnreadNotice()
|
||||||
cb(null, latestNotice)
|
cb(null, latestNotice)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
cb(err)
|
cb(err)
|
||||||
@ -64,15 +68,6 @@ module.exports = class NoticeController extends EventEmitter {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
startPolling () {
|
|
||||||
if (this.noticePoller) {
|
|
||||||
clearInterval(this.noticePoller)
|
|
||||||
}
|
|
||||||
this.noticePoller = setInterval(() => {
|
|
||||||
this.noticeController.updateNoticesList()
|
|
||||||
}, 300000)
|
|
||||||
}
|
|
||||||
|
|
||||||
_mergeNotices (oldNotices, newNotices) {
|
_mergeNotices (oldNotices, newNotices) {
|
||||||
return uniqBy(oldNotices.concat(newNotices), 'id')
|
return uniqBy(oldNotices.concat(newNotices), 'id')
|
||||||
}
|
}
|
||||||
@ -91,19 +86,15 @@ module.exports = class NoticeController extends EventEmitter {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
_mapNoticeIds (notices) {
|
|
||||||
return notices.map((notice) => notice.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
async _retrieveNoticeData () {
|
async _retrieveNoticeData () {
|
||||||
// Placeholder for the API.
|
// Placeholder for remote notice API.
|
||||||
return hardCodedNotices
|
return hardCodedNotices
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateMemstore () {
|
_updateMemstore () {
|
||||||
const lastUnreadNotice = this.getLatestUnreadNotice()
|
const nextUnreadNotice = this.getNextUnreadNotice()
|
||||||
const noActiveNotices = !lastUnreadNotice
|
const noActiveNotices = !nextUnreadNotice
|
||||||
this.memStore.updateState({ lastUnreadNotice, noActiveNotices })
|
this.memStore.updateState({ nextUnreadNotice, noActiveNotices })
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@
|
|||||||
"conversionRate": 12.7200827,
|
"conversionRate": 12.7200827,
|
||||||
"conversionDate": 1487363041,
|
"conversionDate": 1487363041,
|
||||||
"noActiveNotices": true,
|
"noActiveNotices": true,
|
||||||
"lastUnreadNotice": {
|
"nextUnreadNotice": {
|
||||||
"read": true,
|
"read": true,
|
||||||
"date": "Thu Feb 09 2017",
|
"date": "Thu Feb 09 2017",
|
||||||
"title": "Terms of Use",
|
"title": "Terms of Use",
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"conversionRate": 12.7527416,
|
"conversionRate": 12.7527416,
|
||||||
"conversionDate": 1487624341,
|
"conversionDate": 1487624341,
|
||||||
"noActiveNotices": false,
|
"noActiveNotices": false,
|
||||||
"lastUnreadNotice": {
|
"nextUnreadNotice": {
|
||||||
"read": false,
|
"read": false,
|
||||||
"date": "Thu Feb 09 2017",
|
"date": "Thu Feb 09 2017",
|
||||||
"title": "Terms of Use",
|
"title": "Terms of Use",
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"conversionRate": 8.3533002,
|
"conversionRate": 8.3533002,
|
||||||
"conversionDate": 1481671082,
|
"conversionDate": 1481671082,
|
||||||
"noActiveNotices": false,
|
"noActiveNotices": false,
|
||||||
"lastUnreadNotice": {
|
"nextUnreadNotice": {
|
||||||
"read": false,
|
"read": false,
|
||||||
"date": "Tue Dec 13 2016",
|
"date": "Tue Dec 13 2016",
|
||||||
"title": "MultiVault Support",
|
"title": "MultiVault Support",
|
||||||
|
@ -14,7 +14,7 @@ import LoadingScreen from './loading-screen'
|
|||||||
class NoticeScreen extends Component {
|
class NoticeScreen extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
address: PropTypes.string.isRequired,
|
address: PropTypes.string.isRequired,
|
||||||
lastUnreadNotice: PropTypes.shape({
|
nextUnreadNotice: PropTypes.shape({
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
date: PropTypes.string,
|
date: PropTypes.string,
|
||||||
body: PropTypes.string,
|
body: PropTypes.string,
|
||||||
@ -31,7 +31,7 @@ class NoticeScreen extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
lastUnreadNotice: {},
|
nextUnreadNotice: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -47,8 +47,8 @@ class NoticeScreen extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
acceptTerms = () => {
|
acceptTerms = () => {
|
||||||
const { markNoticeRead, lastUnreadNotice, history } = this.props
|
const { markNoticeRead, nextUnreadNotice, history } = this.props
|
||||||
markNoticeRead(lastUnreadNotice)
|
markNoticeRead(nextUnreadNotice)
|
||||||
.then(hasActiveNotices => {
|
.then(hasActiveNotices => {
|
||||||
if (!hasActiveNotices) {
|
if (!hasActiveNotices) {
|
||||||
history.push(INITIALIZE_BACKUP_PHRASE_ROUTE)
|
history.push(INITIALIZE_BACKUP_PHRASE_ROUTE)
|
||||||
@ -72,7 +72,7 @@ class NoticeScreen extends Component {
|
|||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
address,
|
address,
|
||||||
lastUnreadNotice: { title, body },
|
nextUnreadNotice: { title, body },
|
||||||
isLoading,
|
isLoading,
|
||||||
} = this.props
|
} = this.props
|
||||||
const { atBottom } = this.state
|
const { atBottom } = this.state
|
||||||
@ -113,12 +113,12 @@ class NoticeScreen extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = ({ metamask, appState }) => {
|
const mapStateToProps = ({ metamask, appState }) => {
|
||||||
const { selectedAddress, lastUnreadNotice, noActiveNotices } = metamask
|
const { selectedAddress, nextUnreadNotice, noActiveNotices } = metamask
|
||||||
const { isLoading } = appState
|
const { isLoading } = appState
|
||||||
|
|
||||||
return {
|
return {
|
||||||
address: selectedAddress,
|
address: selectedAddress,
|
||||||
lastUnreadNotice,
|
nextUnreadNotice,
|
||||||
noActiveNotices,
|
noActiveNotices,
|
||||||
isLoading,
|
isLoading,
|
||||||
}
|
}
|
||||||
|
6
notices/archive/notice_4.md
Normal file
6
notices/archive/notice_4.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
Dear MetaMask Users,
|
||||||
|
|
||||||
|
There have been several instances of high-profile legitimate websites such as BTC Manager and Games Workshop that have had their websites temporarily compromised. This involves showing a fake MetaMask window on the page asking for user's seed phrases. MetaMask will never open itself in this way and users are encouraged to report these instances immediately to either [our phishing blacklist](https://github.com/MetaMask/eth-phishing-detect/issues) or our support email at [support@metamask.io](mailto:support@metamask.io).
|
||||||
|
|
||||||
|
Please read our full article on this ongoing issue at [https://medium.com/metamask/new-phishing-strategy-becoming-common-1b1123837168](https://medium.com/metamask/new-phishing-strategy-becoming-common-1b1123837168).
|
||||||
|
|
@ -1,27 +0,0 @@
|
|||||||
var fs = require('fs')
|
|
||||||
var path = require('path')
|
|
||||||
var prompt = require('prompt')
|
|
||||||
var open = require('open')
|
|
||||||
var extend = require('extend')
|
|
||||||
var notices = require('./notices.json')
|
|
||||||
|
|
||||||
|
|
||||||
console.log('List of Notices')
|
|
||||||
console.log(`ID \t DATE \t\t\t TITLE`)
|
|
||||||
notices.forEach((notice) => {
|
|
||||||
console.log(`${(' ' + notice.id).slice(-2)} \t ${notice.date} \t ${notice.title}`)
|
|
||||||
})
|
|
||||||
prompt.get(['id'], (error, res) => {
|
|
||||||
prompt.start()
|
|
||||||
if (error) {
|
|
||||||
console.log("Exiting...")
|
|
||||||
process.exit()
|
|
||||||
}
|
|
||||||
var index = notices.findIndex((notice) => { return notice.id == res.id})
|
|
||||||
if (index === -1) {
|
|
||||||
console.log('Notice not found. Exiting...')
|
|
||||||
}
|
|
||||||
notices.splice(index, 1)
|
|
||||||
fs.unlink(`notices/archive/notice_${res.id}.md`)
|
|
||||||
fs.writeFile(`notices/notices.json`, JSON.stringify(notices))
|
|
||||||
})
|
|
@ -1,33 +0,0 @@
|
|||||||
var fsp = require('fs-promise')
|
|
||||||
var path = require('path')
|
|
||||||
var prompt = require('prompt')
|
|
||||||
var open = require('open')
|
|
||||||
var extend = require('extend')
|
|
||||||
var notices = require('./notices.json')
|
|
||||||
var id = Number(require('./notice-nonce.json'))
|
|
||||||
|
|
||||||
var date = new Date().toDateString()
|
|
||||||
|
|
||||||
var notice = {
|
|
||||||
read: false,
|
|
||||||
date: date,
|
|
||||||
}
|
|
||||||
|
|
||||||
fsp.writeFile(`notices/archive/notice_${id}.md`,'Message goes here. Please write out your notice and save before proceeding at the command line.')
|
|
||||||
.then(() => {
|
|
||||||
open(`notices/archive/notice_${id}.md`)
|
|
||||||
prompt.start()
|
|
||||||
prompt.get(['title'], (err, result) => {
|
|
||||||
notice.title = result.title
|
|
||||||
fsp.readFile(`notices/archive/notice_${id}.md`)
|
|
||||||
.then((body) => {
|
|
||||||
notice.body = body.toString()
|
|
||||||
notice.id = id
|
|
||||||
notices.push(notice)
|
|
||||||
return fsp.writeFile(`notices/notices.json`, JSON.stringify(notices))
|
|
||||||
}).then((completion) => {
|
|
||||||
id += 1
|
|
||||||
return fsp.writeFile(`notices/notice-nonce.json`, id)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
@ -1 +0,0 @@
|
|||||||
4
|
|
34
notices/notices.js
Normal file
34
notices/notices.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// fs.readFileSync is inlined by browserify transform "brfs"
|
||||||
|
const fs = require('fs')
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
read: false,
|
||||||
|
date: 'Thu Feb 09 2017',
|
||||||
|
title: 'Terms of Use',
|
||||||
|
body: fs.readFileSync(__dirname + '/archive/notice_0.md', 'utf8'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
read: false,
|
||||||
|
date: 'Mon May 08 2017',
|
||||||
|
title: 'Privacy Notice',
|
||||||
|
body: fs.readFileSync(__dirname + '/archive/notice_2.md', 'utf8'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
read: false,
|
||||||
|
date: 'Tue Nov 28 2017',
|
||||||
|
title: 'Seed Phrase Alert',
|
||||||
|
firstVersion: '<=3.12.0',
|
||||||
|
body: fs.readFileSync(__dirname + '/archive/notice_3.md', 'utf8'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
read: false,
|
||||||
|
date: 'Wed Jun 13 2018',
|
||||||
|
title: 'Phishing Warning',
|
||||||
|
body: fs.readFileSync(__dirname + '/archive/notice_4.md', 'utf8'),
|
||||||
|
}
|
||||||
|
]
|
File diff suppressed because one or more lines are too long
@ -73,7 +73,7 @@ function mapStateToProps (state) {
|
|||||||
network: state.metamask.network,
|
network: state.metamask.network,
|
||||||
provider: state.metamask.provider,
|
provider: state.metamask.provider,
|
||||||
forgottenPassword: state.appState.forgottenPassword,
|
forgottenPassword: state.appState.forgottenPassword,
|
||||||
lastUnreadNotice: state.metamask.lastUnreadNotice,
|
nextUnreadNotice: state.metamask.nextUnreadNotice,
|
||||||
lostAccounts: state.metamask.lostAccounts,
|
lostAccounts: state.metamask.lostAccounts,
|
||||||
frequentRpcList: state.metamask.frequentRpcList || [],
|
frequentRpcList: state.metamask.frequentRpcList || [],
|
||||||
featureFlags,
|
featureFlags,
|
||||||
@ -460,9 +460,9 @@ App.prototype.renderPrimary = function () {
|
|||||||
}, [
|
}, [
|
||||||
|
|
||||||
h(NoticeScreen, {
|
h(NoticeScreen, {
|
||||||
notice: props.lastUnreadNotice,
|
notice: props.nextUnreadNotice,
|
||||||
key: 'NoticeScreen',
|
key: 'NoticeScreen',
|
||||||
onConfirm: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
|
onConfirm: () => props.dispatch(actions.markNoticeRead(props.nextUnreadNotice)),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
!props.isInitialized && h('.flex-row.flex-center.flex-grow', [
|
!props.isInitialized && h('.flex-row.flex-center.flex-grow', [
|
||||||
|
12990
package-lock.json
generated
12990
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,7 @@
|
|||||||
"dist": "gulp dist",
|
"dist": "gulp dist",
|
||||||
"doc": "jsdoc -c development/tools/.jsdoc.json",
|
"doc": "jsdoc -c development/tools/.jsdoc.json",
|
||||||
"test": "npm run test:unit && npm run test:integration && npm run lint",
|
"test": "npm run test:unit && npm run test:integration && npm run lint",
|
||||||
|
"watch:test:unit": "nodemon --exec \"npm run test:unit\" ./test ./app ./ui",
|
||||||
"test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\" \"ui/app/**/*.test.js\" && dot-only-hunter",
|
"test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\" \"ui/app/**/*.test.js\" && dot-only-hunter",
|
||||||
"test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js",
|
"test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js",
|
||||||
"test:integration": "npm run test:integration:build && npm run test:flat && npm run test:mascara",
|
"test:integration": "npm run test:integration:build && npm run test:flat && npm run test:mascara",
|
||||||
@ -45,8 +46,6 @@
|
|||||||
"disc": "gulp disc --debug",
|
"disc": "gulp disc --debug",
|
||||||
"announce": "node development/announcer.js",
|
"announce": "node development/announcer.js",
|
||||||
"version:bump": "node development/run-version-bump.js",
|
"version:bump": "node development/run-version-bump.js",
|
||||||
"generateNotice": "node notices/notice-generator.js",
|
|
||||||
"deleteNotice": "node notices/notice-delete.js",
|
|
||||||
"storybook": "start-storybook -p 6006 -c .storybook"
|
"storybook": "start-storybook -p 6006 -c .storybook"
|
||||||
},
|
},
|
||||||
"browserify": {
|
"browserify": {
|
||||||
|
@ -134,21 +134,34 @@ describe('Using MetaMask with an existing account', function () {
|
|||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('clicks through the privacy notice', async () => {
|
it('clicks through the ToS', async () => {
|
||||||
const [nextScreen] = await findElements(driver, By.css('.tou button'))
|
// terms of use
|
||||||
await nextScreen.click()
|
|
||||||
await delay(regularDelayMs)
|
|
||||||
|
|
||||||
const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled()
|
const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled()
|
||||||
assert.equal(canClickThrough, false, 'disabled continue button')
|
assert.equal(canClickThrough, false, 'disabled continue button')
|
||||||
const element = await findElement(driver, By.linkText('Attributions'))
|
const bottomOfTos = await findElement(driver, By.linkText('Attributions'))
|
||||||
await driver.executeScript('arguments[0].scrollIntoView(true)', element)
|
await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
const acceptTos = await findElement(driver, By.css('.tou button'))
|
||||||
const acceptTos = await findElement(driver, By.xpath(`//button[contains(text(), 'Accept')]`))
|
|
||||||
await acceptTos.click()
|
await acceptTos.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('clicks through the privacy notice', async () => {
|
||||||
|
// privacy notice
|
||||||
|
const nextScreen = await findElement(driver, By.css('.tou button'))
|
||||||
|
await nextScreen.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clicks through the phishing notice', async () => {
|
||||||
|
// phishing notice
|
||||||
|
const noticeElement = await driver.findElement(By.css('.markdown'))
|
||||||
|
await driver.executeScript('arguments[0].scrollTop = arguments[0].scrollHeight', noticeElement)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
const nextScreen = await findElement(driver, By.css('.tou button'))
|
||||||
|
await nextScreen.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Show account information', () => {
|
describe('Show account information', () => {
|
||||||
|
@ -128,22 +128,35 @@ describe('MetaMask', function () {
|
|||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('clicks through the privacy notice', async () => {
|
it('clicks through the ToS', async () => {
|
||||||
const nextScreen = await findElement(driver, By.css('.tou button'))
|
// terms of use
|
||||||
await nextScreen.click()
|
|
||||||
await delay(regularDelayMs)
|
|
||||||
|
|
||||||
const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled()
|
const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled()
|
||||||
assert.equal(canClickThrough, false, 'disabled continue button')
|
assert.equal(canClickThrough, false, 'disabled continue button')
|
||||||
const bottomOfTos = await findElement(driver, By.linkText('Attributions'))
|
const bottomOfTos = await findElement(driver, By.linkText('Attributions'))
|
||||||
await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos)
|
await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const acceptTos = await findElement(driver, By.css('.tou button'))
|
const acceptTos = await findElement(driver, By.css('.tou button'))
|
||||||
await acceptTos.click()
|
await acceptTos.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('clicks through the privacy notice', async () => {
|
||||||
|
// privacy notice
|
||||||
|
const nextScreen = await findElement(driver, By.css('.tou button'))
|
||||||
|
await nextScreen.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clicks through the phishing notice', async () => {
|
||||||
|
// phishing notice
|
||||||
|
const noticeElement = await driver.findElement(By.css('.markdown'))
|
||||||
|
await driver.executeScript('arguments[0].scrollTop = arguments[0].scrollHeight', noticeElement)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
const nextScreen = await findElement(driver, By.css('.tou button'))
|
||||||
|
await nextScreen.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
let seedPhrase
|
let seedPhrase
|
||||||
|
|
||||||
it('reveals the seed phrase', async () => {
|
it('reveals the seed phrase', async () => {
|
||||||
|
@ -21,7 +21,7 @@ function delay (time) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildChromeWebDriver (extPath) {
|
function buildChromeWebDriver (extPath) {
|
||||||
const tmpProfile = path.join(os.tmpdir(), fs.mkdtempSync('mm-chrome-profile'));
|
const tmpProfile = fs.mkdtempSync(path.join(os.tmpdir(), 'mm-chrome-profile'))
|
||||||
return new webdriver.Builder()
|
return new webdriver.Builder()
|
||||||
.withCapabilities({
|
.withCapabilities({
|
||||||
chromeOptions: {
|
chromeOptions: {
|
||||||
|
@ -71,13 +71,6 @@ describe('Metamask popup page', function () {
|
|||||||
it('matches MetaMask title', async () => {
|
it('matches MetaMask title', async () => {
|
||||||
const title = await driver.getTitle()
|
const title = await driver.getTitle()
|
||||||
assert.equal(title, 'MetaMask', 'title matches MetaMask')
|
assert.equal(title, 'MetaMask', 'title matches MetaMask')
|
||||||
})
|
|
||||||
|
|
||||||
it('shows privacy notice', async () => {
|
|
||||||
await delay(300)
|
|
||||||
const privacy = await driver.findElement(By.css('.terms-header')).getText()
|
|
||||||
assert.equal(privacy, 'PRIVACY NOTICE', 'shows privacy notice')
|
|
||||||
await driver.findElement(By.css('button')).click()
|
|
||||||
await delay(300)
|
await delay(300)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -100,6 +93,24 @@ describe('Metamask popup page', function () {
|
|||||||
await button.click()
|
await button.click()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('shows privacy notice', async () => {
|
||||||
|
const privacy = await driver.findElement(By.css('.terms-header')).getText()
|
||||||
|
assert.equal(privacy, 'PRIVACY NOTICE', 'shows privacy notice')
|
||||||
|
await driver.findElement(By.css('button')).click()
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows phishing notice', async () => {
|
||||||
|
await delay(300)
|
||||||
|
const noticeHeader = await driver.findElement(By.css('.terms-header')).getText()
|
||||||
|
assert.equal(noticeHeader, 'PHISHING WARNING', 'shows phishing warning')
|
||||||
|
const element = await driver.findElement(By.css('.markdown'))
|
||||||
|
await driver.executeScript('arguments[0].scrollTop = arguments[0].scrollHeight', element)
|
||||||
|
await delay(300)
|
||||||
|
await driver.findElement(By.css('button')).click()
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
|
||||||
it('accepts password with length of eight', async () => {
|
it('accepts password with length of eight', async () => {
|
||||||
const passwordBox = await driver.findElement(By.id('password-box'))
|
const passwordBox = await driver.findElement(By.id('password-box'))
|
||||||
const passwordBoxConfirm = await driver.findElement(By.id('password-box-confirm'))
|
const passwordBoxConfirm = await driver.findElement(By.id('password-box-confirm'))
|
||||||
|
@ -117,12 +117,12 @@ async function runSendFlowTest(assert, done) {
|
|||||||
const sendGasField = await queryAsync($, '.send-v2__gas-fee-display')
|
const sendGasField = await queryAsync($, '.send-v2__gas-fee-display')
|
||||||
assert.equal(
|
assert.equal(
|
||||||
sendGasField.find('.currency-display__input-wrapper > input').val(),
|
sendGasField.find('.currency-display__input-wrapper > input').val(),
|
||||||
'0.000198264',
|
'0.000021',
|
||||||
'send gas field should show estimated gas total'
|
'send gas field should show estimated gas total'
|
||||||
)
|
)
|
||||||
assert.equal(
|
assert.equal(
|
||||||
sendGasField.find('.currency-display__converted-value')[0].textContent,
|
sendGasField.find('.currency-display__converted-value')[0].textContent,
|
||||||
'$0.24 USD',
|
'$0.03 USD',
|
||||||
'send gas field should show estimated gas total converted to USD'
|
'send gas field should show estimated gas total converted to USD'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,31 +1,59 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const accountImporter = require('../../../app/scripts/account-import-strategies/index')
|
|
||||||
const ethUtil = require('ethereumjs-util')
|
const ethUtil = require('ethereumjs-util')
|
||||||
|
const accountImporter = require('../../../app/scripts/account-import-strategies/index')
|
||||||
|
const { assertRejects } = require('../test-utils')
|
||||||
|
|
||||||
describe('Account Import Strategies', function () {
|
describe('Account Import Strategies', function () {
|
||||||
const privkey = '0x4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553'
|
const privkey = '0x4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553'
|
||||||
const json = '{"version":3,"id":"dbb54385-0a99-437f-83c0-647de9f244c3","address":"a7f92ce3fba24196cf6f4bd2e1eb3db282ba998c","Crypto":{"ciphertext":"bde13d9ade5c82df80281ca363320ce254a8a3a06535bbf6ffdeaf0726b1312c","cipherparams":{"iv":"fbf93718a57f26051b292f072f2e5b41"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"7ffe00488319dec48e4c49a120ca49c6afbde9272854c64d9541c83fc6acdffe","n":8192,"r":8,"p":1},"mac":"2adfd9c4bc1cdac4c85bddfb31d9e21a684e0e050247a70c5698facf6b7d4681"}}'
|
const json = '{"version":3,"id":"dbb54385-0a99-437f-83c0-647de9f244c3","address":"a7f92ce3fba24196cf6f4bd2e1eb3db282ba998c","Crypto":{"ciphertext":"bde13d9ade5c82df80281ca363320ce254a8a3a06535bbf6ffdeaf0726b1312c","cipherparams":{"iv":"fbf93718a57f26051b292f072f2e5b41"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"7ffe00488319dec48e4c49a120ca49c6afbde9272854c64d9541c83fc6acdffe","n":8192,"r":8,"p":1},"mac":"2adfd9c4bc1cdac4c85bddfb31d9e21a684e0e050247a70c5698facf6b7d4681"}}'
|
||||||
|
|
||||||
it('imports a private key and strips 0x prefix', async function () {
|
describe('private key import', function () {
|
||||||
const importPrivKey = await accountImporter.importAccount('Private Key', [ privkey ])
|
it('imports a private key and strips 0x prefix', async function () {
|
||||||
assert.equal(importPrivKey, ethUtil.stripHexPrefix(privkey))
|
const importPrivKey = await accountImporter.importAccount('Private Key', [ privkey ])
|
||||||
|
assert.equal(importPrivKey, ethUtil.stripHexPrefix(privkey))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error for empty string private key', async () => {
|
||||||
|
assertRejects(async function() {
|
||||||
|
await accountImporter.importAccount('Private Key', [ '' ])
|
||||||
|
}, Error, 'no empty strings')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error for undefined string private key', async () => {
|
||||||
|
assertRejects(async function () {
|
||||||
|
await accountImporter.importAccount('Private Key', [ undefined ])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error for undefined string private key', async () => {
|
||||||
|
assertRejects(async function () {
|
||||||
|
await accountImporter.importAccount('Private Key', [])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error for invalid private key', async () => {
|
||||||
|
assertRejects(async function () {
|
||||||
|
await accountImporter.importAccount('Private Key', [ 'popcorn' ])
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('fails when password is incorrect for keystore', async function () {
|
describe('JSON keystore import', function () {
|
||||||
const wrongPassword = 'password2'
|
it('fails when password is incorrect for keystore', async function () {
|
||||||
|
const wrongPassword = 'password2'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await accountImporter.importAccount('JSON File', [ json, wrongPassword])
|
await accountImporter.importAccount('JSON File', [ json, wrongPassword])
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
assert.equal(error.message, 'Key derivation failed - possibly wrong passphrase')
|
assert.equal(error.message, 'Key derivation failed - possibly wrong passphrase')
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('imports json string and password to return a private key', async function () {
|
||||||
|
const fileContentsPassword = 'password1'
|
||||||
|
const importJson = await accountImporter.importAccount('JSON File', [ json, fileContentsPassword])
|
||||||
|
assert.equal(importJson, '0x5733876abe94146069ce8bcbabbde2677f2e35fa33e875e92041ed2ac87e5bc7')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('imports json string and password to return a private key', async function () {
|
|
||||||
const fileContentsPassword = 'password1'
|
|
||||||
const importJson = await accountImporter.importAccount('JSON File', [ json, fileContentsPassword])
|
|
||||||
assert.equal(importJson, '0x5733876abe94146069ce8bcbabbde2677f2e35fa33e875e92041ed2ac87e5bc7')
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -14,18 +14,6 @@ describe('notice-controller', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('notices', function () {
|
describe('notices', function () {
|
||||||
describe('#getNoticesList', function () {
|
|
||||||
it('should return an empty array when new', function (done) {
|
|
||||||
// const testList = [{
|
|
||||||
// id: 0,
|
|
||||||
// read: false,
|
|
||||||
// title: 'Futuristic Notice',
|
|
||||||
// }]
|
|
||||||
var result = noticeController.getNoticesList()
|
|
||||||
assert.equal(result.length, 0)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#setNoticesList', function () {
|
describe('#setNoticesList', function () {
|
||||||
it('should set data appropriately', function (done) {
|
it('should set data appropriately', function (done) {
|
||||||
@ -41,36 +29,6 @@ describe('notice-controller', function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#updateNoticeslist', function () {
|
|
||||||
it('should integrate the latest changes from the source', function (done) {
|
|
||||||
var testList = [{
|
|
||||||
id: 55,
|
|
||||||
read: false,
|
|
||||||
title: 'Futuristic Notice',
|
|
||||||
}]
|
|
||||||
noticeController.setNoticesList(testList)
|
|
||||||
noticeController.updateNoticesList().then(() => {
|
|
||||||
var newList = noticeController.getNoticesList()
|
|
||||||
assert.ok(newList[0].id === 55)
|
|
||||||
assert.ok(newList[1])
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
it('should not overwrite any existing fields', function (done) {
|
|
||||||
var testList = [{
|
|
||||||
id: 0,
|
|
||||||
read: false,
|
|
||||||
title: 'Futuristic Notice',
|
|
||||||
}]
|
|
||||||
noticeController.setNoticesList(testList)
|
|
||||||
var newList = noticeController.getNoticesList()
|
|
||||||
assert.equal(newList[0].id, 0)
|
|
||||||
assert.equal(newList[0].title, 'Futuristic Notice')
|
|
||||||
assert.equal(newList.length, 1)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#markNoticeRead', function () {
|
describe('#markNoticeRead', function () {
|
||||||
it('should mark a notice as read', function (done) {
|
it('should mark a notice as read', function (done) {
|
||||||
var testList = [{
|
var testList = [{
|
||||||
@ -86,7 +44,7 @@ describe('notice-controller', function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#getLatestUnreadNotice', function () {
|
describe('#getNextUnreadNotice', function () {
|
||||||
it('should retrieve the latest unread notice', function (done) {
|
it('should retrieve the latest unread notice', function (done) {
|
||||||
var testList = [
|
var testList = [
|
||||||
{id: 0, read: true, title: 'Past Notice'},
|
{id: 0, read: true, title: 'Past Notice'},
|
||||||
@ -94,8 +52,8 @@ describe('notice-controller', function () {
|
|||||||
{id: 2, read: false, title: 'Future Notice'},
|
{id: 2, read: false, title: 'Future Notice'},
|
||||||
]
|
]
|
||||||
noticeController.setNoticesList(testList)
|
noticeController.setNoticesList(testList)
|
||||||
var latestUnread = noticeController.getLatestUnreadNotice()
|
var latestUnread = noticeController.getNextUnreadNotice()
|
||||||
assert.equal(latestUnread.id, 2)
|
assert.equal(latestUnread.id, 1)
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
it('should return undefined if no unread notices exist.', function (done) {
|
it('should return undefined if no unread notices exist.', function (done) {
|
||||||
@ -105,7 +63,7 @@ describe('notice-controller', function () {
|
|||||||
{id: 2, read: true, title: 'Future Notice'},
|
{id: 2, read: true, title: 'Future Notice'},
|
||||||
]
|
]
|
||||||
noticeController.setNoticesList(testList)
|
noticeController.setNoticesList(testList)
|
||||||
var latestUnread = noticeController.getLatestUnreadNotice()
|
var latestUnread = noticeController.getNextUnreadNotice()
|
||||||
assert.ok(!latestUnread)
|
assert.ok(!latestUnread)
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
|
17
test/unit/test-utils.js
Normal file
17
test/unit/test-utils.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
const assert = require('assert')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
assertRejects,
|
||||||
|
}
|
||||||
|
|
||||||
|
// assert.rejects added in node v10
|
||||||
|
async function assertRejects (asyncFn, regExp) {
|
||||||
|
let f = () => {}
|
||||||
|
try {
|
||||||
|
await asyncFn()
|
||||||
|
} catch (error) {
|
||||||
|
f = () => { throw error }
|
||||||
|
} finally {
|
||||||
|
assert.throws(f, regExp)
|
||||||
|
}
|
||||||
|
}
|
@ -314,7 +314,7 @@ function mapStateToProps (state) {
|
|||||||
noActiveNotices,
|
noActiveNotices,
|
||||||
seedWords,
|
seedWords,
|
||||||
unapprovedTxs,
|
unapprovedTxs,
|
||||||
lastUnreadNotice,
|
nextUnreadNotice,
|
||||||
lostAccounts,
|
lostAccounts,
|
||||||
unapprovedMsgCount,
|
unapprovedMsgCount,
|
||||||
unapprovedPersonalMsgCount,
|
unapprovedPersonalMsgCount,
|
||||||
@ -348,7 +348,7 @@ function mapStateToProps (state) {
|
|||||||
network: state.metamask.network,
|
network: state.metamask.network,
|
||||||
provider: state.metamask.provider,
|
provider: state.metamask.provider,
|
||||||
forgottenPassword: state.appState.forgottenPassword,
|
forgottenPassword: state.appState.forgottenPassword,
|
||||||
lastUnreadNotice,
|
nextUnreadNotice,
|
||||||
lostAccounts,
|
lostAccounts,
|
||||||
frequentRpcList: state.metamask.frequentRpcList || [],
|
frequentRpcList: state.metamask.frequentRpcList || [],
|
||||||
currentCurrency: state.metamask.currentCurrency,
|
currentCurrency: state.metamask.currentCurrency,
|
||||||
|
@ -4,14 +4,21 @@ const h = require('react-hyperscript')
|
|||||||
const inherits = require('util').inherits
|
const inherits = require('util').inherits
|
||||||
const connect = require('react-redux').connect
|
const connect = require('react-redux').connect
|
||||||
const actions = require('../../actions')
|
const actions = require('../../actions')
|
||||||
|
const genAccountLink = require('etherscan-link').createAccountLink
|
||||||
|
const copyToClipboard = require('copy-to-clipboard')
|
||||||
|
const { Menu, Item, CloseArea } = require('./components/menu')
|
||||||
|
|
||||||
TokenMenuDropdown.contextTypes = {
|
TokenMenuDropdown.contextTypes = {
|
||||||
t: PropTypes.func,
|
t: PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = connect(null, mapDispatchToProps)(TokenMenuDropdown)
|
module.exports = connect(mapStateToProps, mapDispatchToProps)(TokenMenuDropdown)
|
||||||
|
|
||||||
|
function mapStateToProps (state) {
|
||||||
|
return {
|
||||||
|
network: state.metamask.network,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function mapDispatchToProps (dispatch) {
|
function mapDispatchToProps (dispatch) {
|
||||||
return {
|
return {
|
||||||
@ -37,22 +44,34 @@ TokenMenuDropdown.prototype.onClose = function (e) {
|
|||||||
TokenMenuDropdown.prototype.render = function () {
|
TokenMenuDropdown.prototype.render = function () {
|
||||||
const { showHideTokenConfirmationModal } = this.props
|
const { showHideTokenConfirmationModal } = this.props
|
||||||
|
|
||||||
return h('div.token-menu-dropdown', {}, [
|
return h(Menu, { className: 'token-menu-dropdown', isShowing: true }, [
|
||||||
h('div.token-menu-dropdown__close-area', {
|
h(CloseArea, {
|
||||||
onClick: this.onClose,
|
onClick: this.onClose,
|
||||||
}),
|
}),
|
||||||
h('div.token-menu-dropdown__container', {}, [
|
h(Item, {
|
||||||
h('div.token-menu-dropdown__options', {}, [
|
onClick: (e) => {
|
||||||
|
e.stopPropagation()
|
||||||
h('div.token-menu-dropdown__option', {
|
showHideTokenConfirmationModal(this.props.token)
|
||||||
onClick: (e) => {
|
this.props.onClose()
|
||||||
e.stopPropagation()
|
},
|
||||||
showHideTokenConfirmationModal(this.props.token)
|
text: this.context.t('hideToken'),
|
||||||
this.props.onClose()
|
}),
|
||||||
},
|
h(Item, {
|
||||||
}, this.context.t('hideToken')),
|
onClick: (e) => {
|
||||||
|
e.stopPropagation()
|
||||||
]),
|
copyToClipboard(this.props.token.address)
|
||||||
]),
|
this.props.onClose()
|
||||||
|
},
|
||||||
|
text: this.context.t('copyContractAddress'),
|
||||||
|
}),
|
||||||
|
h(Item, {
|
||||||
|
onClick: (e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
const url = genAccountLink(this.props.token.address, this.props.network)
|
||||||
|
global.platform.openWindow({ url })
|
||||||
|
this.props.onClose()
|
||||||
|
},
|
||||||
|
text: this.context.t('viewOnEtherscan'),
|
||||||
|
}),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
@ -86,9 +86,9 @@ class Home extends Component {
|
|||||||
// if (!props.noActiveNotices) {
|
// if (!props.noActiveNotices) {
|
||||||
// log.debug('rendering notice screen for unread notices.')
|
// log.debug('rendering notice screen for unread notices.')
|
||||||
// return h(NoticeScreen, {
|
// return h(NoticeScreen, {
|
||||||
// notice: props.lastUnreadNotice,
|
// notice: props.nextUnreadNotice,
|
||||||
// key: 'NoticeScreen',
|
// key: 'NoticeScreen',
|
||||||
// onConfirm: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
|
// onConfirm: () => props.dispatch(actions.markNoticeRead(props.nextUnreadNotice)),
|
||||||
// })
|
// })
|
||||||
// } else if (props.lostAccounts && props.lostAccounts.length > 0) {
|
// } else if (props.lostAccounts && props.lostAccounts.length > 0) {
|
||||||
// log.debug('rendering notice screen for lost accounts view.')
|
// log.debug('rendering notice screen for lost accounts view.')
|
||||||
@ -279,7 +279,7 @@ function mapStateToProps (state) {
|
|||||||
noActiveNotices,
|
noActiveNotices,
|
||||||
seedWords,
|
seedWords,
|
||||||
unapprovedTxs,
|
unapprovedTxs,
|
||||||
lastUnreadNotice,
|
nextUnreadNotice,
|
||||||
lostAccounts,
|
lostAccounts,
|
||||||
unapprovedMsgCount,
|
unapprovedMsgCount,
|
||||||
unapprovedPersonalMsgCount,
|
unapprovedPersonalMsgCount,
|
||||||
@ -313,7 +313,7 @@ function mapStateToProps (state) {
|
|||||||
network: state.metamask.network,
|
network: state.metamask.network,
|
||||||
provider: state.metamask.provider,
|
provider: state.metamask.provider,
|
||||||
forgottenPassword: state.appState.forgottenPassword,
|
forgottenPassword: state.appState.forgottenPassword,
|
||||||
lastUnreadNotice,
|
nextUnreadNotice,
|
||||||
lostAccounts,
|
lostAccounts,
|
||||||
frequentRpcList: state.metamask.frequentRpcList || [],
|
frequentRpcList: state.metamask.frequentRpcList || [],
|
||||||
currentCurrency: state.metamask.currentCurrency,
|
currentCurrency: state.metamask.currentCurrency,
|
||||||
|
@ -154,11 +154,11 @@ class Notice extends Component {
|
|||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
const { metamask } = state
|
const { metamask } = state
|
||||||
const { noActiveNotices, lastUnreadNotice, lostAccounts } = metamask
|
const { noActiveNotices, nextUnreadNotice, lostAccounts } = metamask
|
||||||
|
|
||||||
return {
|
return {
|
||||||
noActiveNotices,
|
noActiveNotices,
|
||||||
lastUnreadNotice,
|
nextUnreadNotice,
|
||||||
lostAccounts,
|
lostAccounts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -171,21 +171,21 @@ Notice.propTypes = {
|
|||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
const mapDispatchToProps = dispatch => {
|
||||||
return {
|
return {
|
||||||
markNoticeRead: lastUnreadNotice => dispatch(actions.markNoticeRead(lastUnreadNotice)),
|
markNoticeRead: nextUnreadNotice => dispatch(actions.markNoticeRead(nextUnreadNotice)),
|
||||||
markAccountsFound: () => dispatch(actions.markAccountsFound()),
|
markAccountsFound: () => dispatch(actions.markAccountsFound()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||||
const { noActiveNotices, lastUnreadNotice, lostAccounts } = stateProps
|
const { noActiveNotices, nextUnreadNotice, lostAccounts } = stateProps
|
||||||
const { markNoticeRead, markAccountsFound } = dispatchProps
|
const { markNoticeRead, markAccountsFound } = dispatchProps
|
||||||
|
|
||||||
let notice
|
let notice
|
||||||
let onConfirm
|
let onConfirm
|
||||||
|
|
||||||
if (!noActiveNotices) {
|
if (!noActiveNotices) {
|
||||||
notice = lastUnreadNotice
|
notice = nextUnreadNotice
|
||||||
onConfirm = () => markNoticeRead(lastUnreadNotice)
|
onConfirm = () => markNoticeRead(nextUnreadNotice)
|
||||||
} else if (lostAccounts && lostAccounts.length > 0) {
|
} else if (lostAccounts && lostAccounts.length > 0) {
|
||||||
notice = generateLostAccountsNotice(lostAccounts)
|
notice = generateLostAccountsNotice(lostAccounts)
|
||||||
onConfirm = () => markAccountsFound()
|
onConfirm = () => markAccountsFound()
|
||||||
|
@ -57,6 +57,7 @@ CurrencyDisplay.prototype.getValueToRender = function ({ selectedToken, conversi
|
|||||||
return selectedToken
|
return selectedToken
|
||||||
? conversionUtil(ethUtil.addHexPrefix(value), {
|
? conversionUtil(ethUtil.addHexPrefix(value), {
|
||||||
fromNumericBase: 'hex',
|
fromNumericBase: 'hex',
|
||||||
|
toNumericBase: 'dec',
|
||||||
toCurrency: symbol,
|
toCurrency: symbol,
|
||||||
conversionRate: multiplier,
|
conversionRate: multiplier,
|
||||||
invertConversionRate: true,
|
invertConversionRate: true,
|
||||||
@ -91,8 +92,12 @@ CurrencyDisplay.prototype.getConvertedValueToRender = function (nonFormattedValu
|
|||||||
: convertedValue
|
: convertedValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeLeadingZeroes (str) {
|
||||||
|
return str.replace(/^0*(?=\d)/, '')
|
||||||
|
}
|
||||||
|
|
||||||
CurrencyDisplay.prototype.handleChange = function (newVal) {
|
CurrencyDisplay.prototype.handleChange = function (newVal) {
|
||||||
this.setState({ valueToRender: newVal })
|
this.setState({ valueToRender: removeLeadingZeroes(newVal) })
|
||||||
this.props.onChange(this.getAmount(newVal))
|
this.props.onChange(this.getAmount(newVal))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ export default class SendAmountRow extends Component {
|
|||||||
tokenBalance: PropTypes.string,
|
tokenBalance: PropTypes.string,
|
||||||
updateSendAmount: PropTypes.func,
|
updateSendAmount: PropTypes.func,
|
||||||
updateSendAmountError: PropTypes.func,
|
updateSendAmountError: PropTypes.func,
|
||||||
|
updateGas: PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
validateAmount (amount) {
|
validateAmount (amount) {
|
||||||
@ -56,6 +57,14 @@ export default class SendAmountRow extends Component {
|
|||||||
updateSendAmount(amount)
|
updateSendAmount(amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateGas (amount) {
|
||||||
|
const { selectedToken, updateGas } = this.props
|
||||||
|
|
||||||
|
if (selectedToken) {
|
||||||
|
updateGas({ amount })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
amount,
|
amount,
|
||||||
@ -77,12 +86,15 @@ export default class SendAmountRow extends Component {
|
|||||||
<CurrencyDisplay
|
<CurrencyDisplay
|
||||||
conversionRate={amountConversionRate}
|
conversionRate={amountConversionRate}
|
||||||
convertedCurrency={convertedCurrency}
|
convertedCurrency={convertedCurrency}
|
||||||
onBlur={newAmount => this.updateAmount(newAmount)}
|
onBlur={newAmount => {
|
||||||
|
this.updateGas(newAmount)
|
||||||
|
this.updateAmount(newAmount)
|
||||||
|
}}
|
||||||
onChange={newAmount => this.validateAmount(newAmount)}
|
onChange={newAmount => this.validateAmount(newAmount)}
|
||||||
inError={inError}
|
inError={inError}
|
||||||
primaryCurrency={primaryCurrency || 'ETH'}
|
primaryCurrency={primaryCurrency || 'ETH'}
|
||||||
selectedToken={selectedToken}
|
selectedToken={selectedToken}
|
||||||
value={amount || '0x0'}
|
value={amount}
|
||||||
/>
|
/>
|
||||||
</SendRowWrapper>
|
</SendRowWrapper>
|
||||||
)
|
)
|
||||||
|
@ -12,10 +12,12 @@ const propsMethodSpies = {
|
|||||||
setMaxModeTo: sinon.spy(),
|
setMaxModeTo: sinon.spy(),
|
||||||
updateSendAmount: sinon.spy(),
|
updateSendAmount: sinon.spy(),
|
||||||
updateSendAmountError: sinon.spy(),
|
updateSendAmountError: sinon.spy(),
|
||||||
|
updateGas: sinon.spy(),
|
||||||
}
|
}
|
||||||
|
|
||||||
sinon.spy(SendAmountRow.prototype, 'updateAmount')
|
sinon.spy(SendAmountRow.prototype, 'updateAmount')
|
||||||
sinon.spy(SendAmountRow.prototype, 'validateAmount')
|
sinon.spy(SendAmountRow.prototype, 'validateAmount')
|
||||||
|
sinon.spy(SendAmountRow.prototype, 'updateGas')
|
||||||
|
|
||||||
describe('SendAmountRow Component', function () {
|
describe('SendAmountRow Component', function () {
|
||||||
let wrapper
|
let wrapper
|
||||||
@ -36,6 +38,7 @@ describe('SendAmountRow Component', function () {
|
|||||||
tokenBalance={'mockTokenBalance'}
|
tokenBalance={'mockTokenBalance'}
|
||||||
updateSendAmount={propsMethodSpies.updateSendAmount}
|
updateSendAmount={propsMethodSpies.updateSendAmount}
|
||||||
updateSendAmountError={propsMethodSpies.updateSendAmountError}
|
updateSendAmountError={propsMethodSpies.updateSendAmountError}
|
||||||
|
updateGas={propsMethodSpies.updateGas}
|
||||||
/>, { context: { t: str => str + '_t' } })
|
/>, { context: { t: str => str + '_t' } })
|
||||||
instance = wrapper.instance()
|
instance = wrapper.instance()
|
||||||
})
|
})
|
||||||
@ -139,8 +142,14 @@ describe('SendAmountRow Component', function () {
|
|||||||
assert.equal(primaryCurrency, 'mockPrimaryCurrency')
|
assert.equal(primaryCurrency, 'mockPrimaryCurrency')
|
||||||
assert.deepEqual(selectedToken, { address: 'mockTokenAddress' })
|
assert.deepEqual(selectedToken, { address: 'mockTokenAddress' })
|
||||||
assert.equal(value, 'mockAmount')
|
assert.equal(value, 'mockAmount')
|
||||||
|
assert.equal(SendAmountRow.prototype.updateGas.callCount, 0)
|
||||||
assert.equal(SendAmountRow.prototype.updateAmount.callCount, 0)
|
assert.equal(SendAmountRow.prototype.updateAmount.callCount, 0)
|
||||||
onBlur('mockNewAmount')
|
onBlur('mockNewAmount')
|
||||||
|
assert.equal(SendAmountRow.prototype.updateGas.callCount, 1)
|
||||||
|
assert.deepEqual(
|
||||||
|
SendAmountRow.prototype.updateGas.getCall(0).args,
|
||||||
|
['mockNewAmount']
|
||||||
|
)
|
||||||
assert.equal(SendAmountRow.prototype.updateAmount.callCount, 1)
|
assert.equal(SendAmountRow.prototype.updateAmount.callCount, 1)
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
SendAmountRow.prototype.updateAmount.getCall(0).args,
|
SendAmountRow.prototype.updateAmount.getCall(0).args,
|
||||||
|
@ -18,7 +18,7 @@ export default class SendContent extends Component {
|
|||||||
<div className="send-v2__form">
|
<div className="send-v2__form">
|
||||||
<SendFromRow />
|
<SendFromRow />
|
||||||
<SendToRow updateGas={(updateData) => this.props.updateGas(updateData)} />
|
<SendToRow updateGas={(updateData) => this.props.updateGas(updateData)} />
|
||||||
<SendAmountRow />
|
<SendAmountRow updateGas={(updateData) => this.props.updateGas(updateData)} />
|
||||||
<SendGasRow />
|
<SendGasRow />
|
||||||
</div>
|
</div>
|
||||||
</PageContainerContent>
|
</PageContainerContent>
|
||||||
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
|
|||||||
import PersistentForm from '../../../lib/persistent-form'
|
import PersistentForm from '../../../lib/persistent-form'
|
||||||
import {
|
import {
|
||||||
getAmountErrorObject,
|
getAmountErrorObject,
|
||||||
|
getToAddressForGasUpdate,
|
||||||
doesAmountErrorRequireUpdate,
|
doesAmountErrorRequireUpdate,
|
||||||
} from './send.utils'
|
} from './send.utils'
|
||||||
|
|
||||||
@ -38,7 +39,7 @@ export default class SendTransactionScreen extends PersistentForm {
|
|||||||
updateSendTokenBalance: PropTypes.func,
|
updateSendTokenBalance: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
updateGas ({ to } = {}) {
|
updateGas ({ to: updatedToAddress, amount: value } = {}) {
|
||||||
const {
|
const {
|
||||||
amount,
|
amount,
|
||||||
blockGasLimit,
|
blockGasLimit,
|
||||||
@ -48,6 +49,7 @@ export default class SendTransactionScreen extends PersistentForm {
|
|||||||
recentBlocks,
|
recentBlocks,
|
||||||
selectedAddress,
|
selectedAddress,
|
||||||
selectedToken = {},
|
selectedToken = {},
|
||||||
|
to: currentToAddress,
|
||||||
updateAndSetGasTotal,
|
updateAndSetGasTotal,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
@ -59,8 +61,8 @@ export default class SendTransactionScreen extends PersistentForm {
|
|||||||
recentBlocks,
|
recentBlocks,
|
||||||
selectedAddress,
|
selectedAddress,
|
||||||
selectedToken,
|
selectedToken,
|
||||||
to: to && to.toLowerCase(),
|
to: getToAddressForGasUpdate(updatedToAddress, currentToAddress),
|
||||||
value: amount,
|
value: value || amount,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ import {
|
|||||||
getSendAmount,
|
getSendAmount,
|
||||||
getSendEditingTransactionId,
|
getSendEditingTransactionId,
|
||||||
getSendFromObject,
|
getSendFromObject,
|
||||||
|
getSendTo,
|
||||||
getTokenBalance,
|
getTokenBalance,
|
||||||
} from './send.selectors'
|
} from './send.selectors'
|
||||||
import {
|
import {
|
||||||
@ -54,6 +55,7 @@ function mapStateToProps (state) {
|
|||||||
recentBlocks: getRecentBlocks(state),
|
recentBlocks: getRecentBlocks(state),
|
||||||
selectedAddress: getSelectedAddress(state),
|
selectedAddress: getSelectedAddress(state),
|
||||||
selectedToken: getSelectedToken(state),
|
selectedToken: getSelectedToken(state),
|
||||||
|
to: getSendTo(state),
|
||||||
tokenBalance: getTokenBalance(state),
|
tokenBalance: getTokenBalance(state),
|
||||||
tokenContract: getSelectedTokenContract(state),
|
tokenContract: getSelectedTokenContract(state),
|
||||||
tokenToFiatRate: getSelectedTokenToFiatRate(state),
|
tokenToFiatRate: getSelectedTokenToFiatRate(state),
|
||||||
|
@ -4,6 +4,7 @@ const {
|
|||||||
conversionGTE,
|
conversionGTE,
|
||||||
multiplyCurrencies,
|
multiplyCurrencies,
|
||||||
conversionGreaterThan,
|
conversionGreaterThan,
|
||||||
|
conversionLessThan,
|
||||||
} = require('../../conversion-util')
|
} = require('../../conversion-util')
|
||||||
const {
|
const {
|
||||||
calcTokenAmount,
|
calcTokenAmount,
|
||||||
@ -20,6 +21,7 @@ const abi = require('ethereumjs-abi')
|
|||||||
const ethUtil = require('ethereumjs-util')
|
const ethUtil = require('ethereumjs-util')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
addGasBuffer,
|
||||||
calcGasTotal,
|
calcGasTotal,
|
||||||
calcTokenBalance,
|
calcTokenBalance,
|
||||||
doesAmountErrorRequireUpdate,
|
doesAmountErrorRequireUpdate,
|
||||||
@ -27,6 +29,7 @@ module.exports = {
|
|||||||
estimateGasPriceFromRecentBlocks,
|
estimateGasPriceFromRecentBlocks,
|
||||||
generateTokenTransferData,
|
generateTokenTransferData,
|
||||||
getAmountErrorObject,
|
getAmountErrorObject,
|
||||||
|
getToAddressForGasUpdate,
|
||||||
isBalanceSufficient,
|
isBalanceSufficient,
|
||||||
isTokenBalanceSufficient,
|
isTokenBalanceSufficient,
|
||||||
}
|
}
|
||||||
@ -175,9 +178,8 @@ async function estimateGas ({ selectedAddress, selectedToken, blockGasLimit, to,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if recipient has no code, gas is 21k max:
|
// if recipient has no code, gas is 21k max:
|
||||||
const hasRecipient = Boolean(to)
|
if (!selectedToken) {
|
||||||
if (hasRecipient && !selectedToken) {
|
const code = Boolean(to) && await global.eth.getCode(to)
|
||||||
const code = await global.eth.getCode(to)
|
|
||||||
if (!code || code === '0x') {
|
if (!code || code === '0x') {
|
||||||
return SIMPLE_GAS_COST
|
return SIMPLE_GAS_COST
|
||||||
}
|
}
|
||||||
@ -201,16 +203,46 @@ async function estimateGas ({ selectedAddress, selectedToken, blockGasLimit, to,
|
|||||||
err.message.includes('gas required exceeds allowance or always failing transaction')
|
err.message.includes('gas required exceeds allowance or always failing transaction')
|
||||||
)
|
)
|
||||||
if (simulationFailed) {
|
if (simulationFailed) {
|
||||||
return resolve(paramsForGasEstimate.gas)
|
const estimateWithBuffer = addGasBuffer(paramsForGasEstimate.gas, blockGasLimit, 1.5)
|
||||||
|
return resolve(ethUtil.addHexPrefix(estimateWithBuffer))
|
||||||
} else {
|
} else {
|
||||||
return reject(err)
|
return reject(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return resolve(estimatedGas.toString(16))
|
const estimateWithBuffer = addGasBuffer(estimatedGas.toString(16), blockGasLimit, 1.5)
|
||||||
|
return resolve(ethUtil.addHexPrefix(estimateWithBuffer))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addGasBuffer (initialGasLimitHex, blockGasLimitHex, bufferMultiplier = 1.5) {
|
||||||
|
const upperGasLimit = multiplyCurrencies(blockGasLimitHex, 0.9, {
|
||||||
|
toNumericBase: 'hex',
|
||||||
|
multiplicandBase: 16,
|
||||||
|
multiplierBase: 10,
|
||||||
|
numberOfDecimals: '0',
|
||||||
|
})
|
||||||
|
const bufferedGasLimit = multiplyCurrencies(initialGasLimitHex, bufferMultiplier, {
|
||||||
|
toNumericBase: 'hex',
|
||||||
|
multiplicandBase: 16,
|
||||||
|
multiplierBase: 10,
|
||||||
|
numberOfDecimals: '0',
|
||||||
|
})
|
||||||
|
|
||||||
|
// if initialGasLimit is above blockGasLimit, dont modify it
|
||||||
|
if (conversionGreaterThan(
|
||||||
|
{ value: initialGasLimitHex, fromNumericBase: 'hex' },
|
||||||
|
{ value: upperGasLimit, fromNumericBase: 'hex' },
|
||||||
|
)) return initialGasLimitHex
|
||||||
|
// if bufferedGasLimit is below blockGasLimit, use bufferedGasLimit
|
||||||
|
if (conversionLessThan(
|
||||||
|
{ value: bufferedGasLimit, fromNumericBase: 'hex' },
|
||||||
|
{ value: upperGasLimit, fromNumericBase: 'hex' },
|
||||||
|
)) return bufferedGasLimit
|
||||||
|
// otherwise use blockGasLimit
|
||||||
|
return upperGasLimit
|
||||||
|
}
|
||||||
|
|
||||||
function generateTokenTransferData ({ toAddress = '0x0', amount = '0x0', selectedToken }) {
|
function generateTokenTransferData ({ toAddress = '0x0', amount = '0x0', selectedToken }) {
|
||||||
if (!selectedToken) return
|
if (!selectedToken) return
|
||||||
return TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call(
|
return TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call(
|
||||||
@ -237,3 +269,7 @@ function estimateGasPriceFromRecentBlocks (recentBlocks) {
|
|||||||
|
|
||||||
return lowestPrices[Math.floor(lowestPrices.length / 2)]
|
return lowestPrices[Math.floor(lowestPrices.length / 2)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getToAddressForGasUpdate (...addresses) {
|
||||||
|
return [...addresses, ''].find(str => str !== undefined && str !== null).toLowerCase()
|
||||||
|
}
|
||||||
|
@ -201,7 +201,7 @@ describe('Send Component', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('updateGas', () => {
|
describe('updateGas', () => {
|
||||||
it('should call updateAndSetGasTotal with the correct params', () => {
|
it('should call updateAndSetGasTotal with the correct params if no to prop is passed', () => {
|
||||||
propsMethodSpies.updateAndSetGasTotal.resetHistory()
|
propsMethodSpies.updateAndSetGasTotal.resetHistory()
|
||||||
wrapper.instance().updateGas()
|
wrapper.instance().updateGas()
|
||||||
assert.equal(propsMethodSpies.updateAndSetGasTotal.callCount, 1)
|
assert.equal(propsMethodSpies.updateAndSetGasTotal.callCount, 1)
|
||||||
@ -215,12 +215,22 @@ describe('Send Component', function () {
|
|||||||
recentBlocks: ['mockBlock'],
|
recentBlocks: ['mockBlock'],
|
||||||
selectedAddress: 'mockSelectedAddress',
|
selectedAddress: 'mockSelectedAddress',
|
||||||
selectedToken: 'mockSelectedToken',
|
selectedToken: 'mockSelectedToken',
|
||||||
to: undefined,
|
to: '',
|
||||||
value: 'mockAmount',
|
value: 'mockAmount',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should call updateAndSetGasTotal with the correct params if a to prop is passed', () => {
|
||||||
|
propsMethodSpies.updateAndSetGasTotal.resetHistory()
|
||||||
|
wrapper.setProps({ to: 'someAddress' })
|
||||||
|
wrapper.instance().updateGas()
|
||||||
|
assert.equal(
|
||||||
|
propsMethodSpies.updateAndSetGasTotal.getCall(0).args[0].to,
|
||||||
|
'someaddress',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
it('should call updateAndSetGasTotal with to set to lowercase if passed', () => {
|
it('should call updateAndSetGasTotal with to set to lowercase if passed', () => {
|
||||||
propsMethodSpies.updateAndSetGasTotal.resetHistory()
|
propsMethodSpies.updateAndSetGasTotal.resetHistory()
|
||||||
wrapper.instance().updateGas({ to: '0xABC' })
|
wrapper.instance().updateGas({ to: '0xABC' })
|
||||||
|
@ -39,6 +39,7 @@ proxyquire('../send.container.js', {
|
|||||||
getSelectedTokenContract: (s) => `mockTokenContract:${s}`,
|
getSelectedTokenContract: (s) => `mockTokenContract:${s}`,
|
||||||
getSelectedTokenToFiatRate: (s) => `mockTokenToFiatRate:${s}`,
|
getSelectedTokenToFiatRate: (s) => `mockTokenToFiatRate:${s}`,
|
||||||
getSendAmount: (s) => `mockAmount:${s}`,
|
getSendAmount: (s) => `mockAmount:${s}`,
|
||||||
|
getSendTo: (s) => `mockTo:${s}`,
|
||||||
getSendEditingTransactionId: (s) => `mockEditingTransactionId:${s}`,
|
getSendEditingTransactionId: (s) => `mockEditingTransactionId:${s}`,
|
||||||
getSendFromObject: (s) => `mockFrom:${s}`,
|
getSendFromObject: (s) => `mockFrom:${s}`,
|
||||||
getTokenBalance: (s) => `mockTokenBalance:${s}`,
|
getTokenBalance: (s) => `mockTokenBalance:${s}`,
|
||||||
@ -70,6 +71,7 @@ describe('send container', () => {
|
|||||||
recentBlocks: 'mockRecentBlocks:mockState',
|
recentBlocks: 'mockRecentBlocks:mockState',
|
||||||
selectedAddress: 'mockSelectedAddress:mockState',
|
selectedAddress: 'mockSelectedAddress:mockState',
|
||||||
selectedToken: 'mockSelectedToken:mockState',
|
selectedToken: 'mockSelectedToken:mockState',
|
||||||
|
to: 'mockTo:mockState',
|
||||||
tokenBalance: 'mockTokenBalance:mockState',
|
tokenBalance: 'mockTokenBalance:mockState',
|
||||||
tokenContract: 'mockTokenContract:mockState',
|
tokenContract: 'mockTokenContract:mockState',
|
||||||
tokenToFiatRate: 'mockTokenToFiatRate:mockState',
|
tokenToFiatRate: 'mockTokenToFiatRate:mockState',
|
||||||
|
@ -18,10 +18,12 @@ const {
|
|||||||
const stubs = {
|
const stubs = {
|
||||||
addCurrencies: sinon.stub().callsFake((a, b, obj) => a + b),
|
addCurrencies: sinon.stub().callsFake((a, b, obj) => a + b),
|
||||||
conversionUtil: sinon.stub().callsFake((val, obj) => parseInt(val, 16)),
|
conversionUtil: sinon.stub().callsFake((val, obj) => parseInt(val, 16)),
|
||||||
conversionGTE: sinon.stub().callsFake((obj1, obj2) => obj1.value > obj2.value),
|
conversionGTE: sinon.stub().callsFake((obj1, obj2) => obj1.value >= obj2.value),
|
||||||
multiplyCurrencies: sinon.stub().callsFake((a, b) => `${a}x${b}`),
|
multiplyCurrencies: sinon.stub().callsFake((a, b) => `${a}x${b}`),
|
||||||
calcTokenAmount: sinon.stub().callsFake((a, d) => 'calc:' + a + d),
|
calcTokenAmount: sinon.stub().callsFake((a, d) => 'calc:' + a + d),
|
||||||
rawEncode: sinon.stub().returns([16, 1100]),
|
rawEncode: sinon.stub().returns([16, 1100]),
|
||||||
|
conversionGreaterThan: sinon.stub().callsFake((obj1, obj2) => obj1.value > obj2.value),
|
||||||
|
conversionLessThan: sinon.stub().callsFake((obj1, obj2) => obj1.value < obj2.value),
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendUtils = proxyquire('../send.utils.js', {
|
const sendUtils = proxyquire('../send.utils.js', {
|
||||||
@ -30,6 +32,8 @@ const sendUtils = proxyquire('../send.utils.js', {
|
|||||||
conversionUtil: stubs.conversionUtil,
|
conversionUtil: stubs.conversionUtil,
|
||||||
conversionGTE: stubs.conversionGTE,
|
conversionGTE: stubs.conversionGTE,
|
||||||
multiplyCurrencies: stubs.multiplyCurrencies,
|
multiplyCurrencies: stubs.multiplyCurrencies,
|
||||||
|
conversionGreaterThan: stubs.conversionGreaterThan,
|
||||||
|
conversionLessThan: stubs.conversionLessThan,
|
||||||
},
|
},
|
||||||
'../../token-util': { calcTokenAmount: stubs.calcTokenAmount },
|
'../../token-util': { calcTokenAmount: stubs.calcTokenAmount },
|
||||||
'ethereumjs-abi': {
|
'ethereumjs-abi': {
|
||||||
@ -44,6 +48,7 @@ const {
|
|||||||
estimateGasPriceFromRecentBlocks,
|
estimateGasPriceFromRecentBlocks,
|
||||||
generateTokenTransferData,
|
generateTokenTransferData,
|
||||||
getAmountErrorObject,
|
getAmountErrorObject,
|
||||||
|
getToAddressForGasUpdate,
|
||||||
calcTokenBalance,
|
calcTokenBalance,
|
||||||
isBalanceSufficient,
|
isBalanceSufficient,
|
||||||
isTokenBalanceSufficient,
|
isTokenBalanceSufficient,
|
||||||
@ -255,7 +260,7 @@ describe('send utils', () => {
|
|||||||
estimateGasMethod: sinon.stub().callsFake(
|
estimateGasMethod: sinon.stub().callsFake(
|
||||||
(data, cb) => cb(
|
(data, cb) => cb(
|
||||||
data.to.match(/willFailBecauseOf:/) ? { message: data.to.match(/:(.+)$/)[1] } : null,
|
data.to.match(/willFailBecauseOf:/) ? { message: data.to.match(/:(.+)$/)[1] } : null,
|
||||||
{ toString: (n) => `mockToString:${n}` }
|
{ toString: (n) => `0xabc${n}` }
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@ -279,13 +284,23 @@ describe('send utils', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should call ethQuery.estimateGas with the expected params', async () => {
|
it('should call ethQuery.estimateGas with the expected params', async () => {
|
||||||
const result = await estimateGas(baseMockParams)
|
const result = await sendUtils.estimateGas(baseMockParams)
|
||||||
assert.equal(baseMockParams.estimateGasMethod.callCount, 1)
|
assert.equal(baseMockParams.estimateGasMethod.callCount, 1)
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
baseMockParams.estimateGasMethod.getCall(0).args[0],
|
baseMockParams.estimateGasMethod.getCall(0).args[0],
|
||||||
Object.assign({ gasPrice: undefined, value: undefined }, baseExpectedCall)
|
Object.assign({ gasPrice: undefined, value: undefined }, baseExpectedCall)
|
||||||
)
|
)
|
||||||
assert.equal(result, 'mockToString:16')
|
assert.equal(result, '0xabc16')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call ethQuery.estimateGas with the expected params when initialGasLimitHex is lower than the upperGasLimit', async () => {
|
||||||
|
const result = await estimateGas(Object.assign({}, baseMockParams, { blockGasLimit: '0xbcd' }))
|
||||||
|
assert.equal(baseMockParams.estimateGasMethod.callCount, 1)
|
||||||
|
assert.deepEqual(
|
||||||
|
baseMockParams.estimateGasMethod.getCall(0).args[0],
|
||||||
|
Object.assign({ gasPrice: undefined, value: undefined }, baseExpectedCall, { gas: '0xbcdx0.95' })
|
||||||
|
)
|
||||||
|
assert.equal(result, '0xabc16x1.5')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should call ethQuery.estimateGas with a value of 0x0 and the expected data and to if passed a selectedToken', async () => {
|
it('should call ethQuery.estimateGas with a value of 0x0 and the expected data and to if passed a selectedToken', async () => {
|
||||||
@ -300,7 +315,7 @@ describe('send utils', () => {
|
|||||||
to: 'mockAddress',
|
to: 'mockAddress',
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
assert.equal(result, 'mockToString:16')
|
assert.equal(result, '0xabc16')
|
||||||
})
|
})
|
||||||
|
|
||||||
it(`should return ${SIMPLE_GAS_COST} if ethQuery.getCode does not return '0x'`, async () => {
|
it(`should return ${SIMPLE_GAS_COST} if ethQuery.getCode does not return '0x'`, async () => {
|
||||||
@ -309,6 +324,12 @@ describe('send utils', () => {
|
|||||||
assert.equal(result, SIMPLE_GAS_COST)
|
assert.equal(result, SIMPLE_GAS_COST)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it(`should return ${SIMPLE_GAS_COST} if not passed a selectedToken or truthy to address`, async () => {
|
||||||
|
assert.equal(baseMockParams.estimateGasMethod.callCount, 0)
|
||||||
|
const result = await estimateGas(Object.assign({}, baseMockParams, { to: null }))
|
||||||
|
assert.equal(result, SIMPLE_GAS_COST)
|
||||||
|
})
|
||||||
|
|
||||||
it(`should not return ${SIMPLE_GAS_COST} if passed a selectedToken`, async () => {
|
it(`should not return ${SIMPLE_GAS_COST} if passed a selectedToken`, async () => {
|
||||||
assert.equal(baseMockParams.estimateGasMethod.callCount, 0)
|
assert.equal(baseMockParams.estimateGasMethod.callCount, 0)
|
||||||
const result = await estimateGas(Object.assign({}, baseMockParams, { to: '0x123', selectedToken: { address: '' } }))
|
const result = await estimateGas(Object.assign({}, baseMockParams, { to: '0x123', selectedToken: { address: '' } }))
|
||||||
@ -401,4 +422,15 @@ describe('send utils', () => {
|
|||||||
assert.equal(estimateGasPriceFromRecentBlocks(mockRecentBlocks), '0x5')
|
assert.equal(estimateGasPriceFromRecentBlocks(mockRecentBlocks), '0x5')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('getToAddressForGasUpdate()', () => {
|
||||||
|
it('should return empty string if all params are undefined or null', () => {
|
||||||
|
assert.equal(getToAddressForGasUpdate(undefined, null), '')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return the first string that is not defined or null in lower case', () => {
|
||||||
|
assert.equal(getToAddressForGasUpdate('A', null), 'a')
|
||||||
|
assert.equal(getToAddressForGasUpdate(undefined, 'B'), 'b')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -178,7 +178,14 @@ SignatureRequest.prototype.renderBody = function () {
|
|||||||
rows = data
|
rows = data
|
||||||
} else if (type === 'eth_sign') {
|
} else if (type === 'eth_sign') {
|
||||||
rows = [{ name: this.context.t('message'), value: data }]
|
rows = [{ name: this.context.t('message'), value: data }]
|
||||||
notice = this.context.t('signNotice')
|
notice = [this.context.t('signNotice'),
|
||||||
|
h('span.request-signature__help-link', {
|
||||||
|
onClick: () => {
|
||||||
|
global.platform.openWindow({
|
||||||
|
url: 'https://consensys.zendesk.com/hc/en-us/articles/360004427792',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}, this.context.t('learnMore'))]
|
||||||
}
|
}
|
||||||
|
|
||||||
return h('div.request-signature__body', {}, [
|
return h('div.request-signature__body', {}, [
|
||||||
|
@ -190,6 +190,16 @@ const conversionGreaterThan = (
|
|||||||
return firstValue.gt(secondValue)
|
return firstValue.gt(secondValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const conversionLessThan = (
|
||||||
|
{ ...firstProps },
|
||||||
|
{ ...secondProps },
|
||||||
|
) => {
|
||||||
|
const firstValue = converter({ ...firstProps })
|
||||||
|
const secondValue = converter({ ...secondProps })
|
||||||
|
|
||||||
|
return firstValue.lt(secondValue)
|
||||||
|
}
|
||||||
|
|
||||||
const conversionMax = (
|
const conversionMax = (
|
||||||
{ ...firstProps },
|
{ ...firstProps },
|
||||||
{ ...secondProps },
|
{ ...secondProps },
|
||||||
@ -229,6 +239,7 @@ module.exports = {
|
|||||||
addCurrencies,
|
addCurrencies,
|
||||||
multiplyCurrencies,
|
multiplyCurrencies,
|
||||||
conversionGreaterThan,
|
conversionGreaterThan,
|
||||||
|
conversionLessThan,
|
||||||
conversionGTE,
|
conversionGTE,
|
||||||
conversionLTE,
|
conversionLTE,
|
||||||
conversionMax,
|
conversionMax,
|
||||||
|
@ -183,6 +183,12 @@
|
|||||||
padding: 6px 18px 15px;
|
padding: 6px 18px 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__help-link {
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline;
|
||||||
|
color: $curious-blue;
|
||||||
|
}
|
||||||
|
|
||||||
&__footer {
|
&__footer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -81,13 +81,9 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
|
|||||||
}
|
}
|
||||||
|
|
||||||
.token-menu-dropdown {
|
.token-menu-dropdown {
|
||||||
height: 55px;
|
|
||||||
width: 80%;
|
width: 80%;
|
||||||
border-radius: 4px;
|
|
||||||
background-color: rgba(0, 0, 0, .82);
|
|
||||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .5);
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 60px;
|
top: 52px;
|
||||||
right: 25px;
|
right: 25px;
|
||||||
z-index: 2000;
|
z-index: 2000;
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ function reduceMetamask (state, action) {
|
|||||||
identities: {},
|
identities: {},
|
||||||
unapprovedTxs: {},
|
unapprovedTxs: {},
|
||||||
noActiveNotices: true,
|
noActiveNotices: true,
|
||||||
lastUnreadNotice: undefined,
|
nextUnreadNotice: undefined,
|
||||||
frequentRpcList: [],
|
frequentRpcList: [],
|
||||||
addressBook: [],
|
addressBook: [],
|
||||||
selectedTokenAddress: null,
|
selectedTokenAddress: null,
|
||||||
@ -65,7 +65,7 @@ function reduceMetamask (state, action) {
|
|||||||
case actions.SHOW_NOTICE:
|
case actions.SHOW_NOTICE:
|
||||||
return extend(metamaskState, {
|
return extend(metamaskState, {
|
||||||
noActiveNotices: false,
|
noActiveNotices: false,
|
||||||
lastUnreadNotice: action.value,
|
nextUnreadNotice: action.value,
|
||||||
})
|
})
|
||||||
|
|
||||||
case actions.CLEAR_NOTICES:
|
case actions.CLEAR_NOTICES:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user