1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Merge branch 'NewUI-flat' into MM-333-auto-add-users-to-new-UI

This commit is contained in:
Chi Kei Chan 2017-12-07 09:46:28 -08:00 committed by GitHub
commit 845aec82b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 369 additions and 109 deletions

View File

@ -2,6 +2,12 @@
## Current Master
## 3.12.1 2017-11-29
- Fix bug where a user could be shown two different seed phrases.
- Detect when multiple web3 extensions are active, and provide useful error.
- Adds notice about seed phrase backup.
## 3.12.0 2017-10-25
- Add support for alternative ENS TLDs (Ethereum Name Service Top-Level Domains).

View File

@ -1,7 +1,7 @@
<!--
FAQ:
BEFORE SUBMITTING, please make sure your question hasn't been answered in our FAQ: https://github.com/MetaMask/faq
Common questions such as "Where is my ether?" or "Where did my tokens go?" are answered in the FAQ.
BEFORE SUBMITTING, please make sure your question hasn't been answered in our support center: https://support.metamask.io
Common questions such as "Where is my ether?" or "Where did my tokens go?" are answered there.
Bug Reports:

View File

@ -1,10 +1,11 @@
const urlUtil = require('url')
const endOfStream = require('end-of-stream')
const pipe = require('pump')
const pump = require('pump')
const log = require('loglevel')
const extension = require('extensionizer')
const LocalStorageStore = require('obs-store/lib/localStorage')
const storeTransform = require('obs-store/lib/transform')
const asStream = require('obs-store/lib/asStream')
const ExtensionPlatform = require('./platforms/extension')
const Migrator = require('./lib/migrator/')
const migrations = require('./migrations/')
@ -72,10 +73,10 @@ function setupController (initState) {
global.metamaskController = controller
// setup state persistence
pipe(
controller.store,
pump(
asStream(controller.store),
storeTransform(versionifyData),
diskStore
asStream(diskStore)
)
function versionifyData (state) {

View File

@ -53,7 +53,7 @@ module.exports = class NetworkController extends EventEmitter {
lookupNetwork () {
// Prevent firing when provider is not defined.
if (!this.ethQuery || !this.ethQuery.sendAsync) {
return
return log.warn('NetworkController - lookupNetwork aborted due to missing ethQuery')
}
this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => {
if (err) return this.setNetworkState('loading')

View File

@ -72,6 +72,12 @@ module.exports = class TransactionController extends EventEmitter {
})
this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager))
this.pendingTxTracker.on('tx:confirmed', this.txStateManager.setTxStatusConfirmed.bind(this.txStateManager))
this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => {
if (!txMeta.firstRetryBlockNumber) {
txMeta.firstRetryBlockNumber = latestBlockNumber
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:block-update')
}
})
this.pendingTxTracker.on('tx:retry', (txMeta) => {
if (!('retryCount' in txMeta)) txMeta.retryCount = 0
txMeta.retryCount++

View File

@ -31,6 +31,13 @@ var inpageProvider = new MetamaskInpageProvider(metamaskStream)
// setup web3
//
if (typeof window.web3 !== 'undefined') {
throw new Error(`MetaMask detected another web3.
MetaMask will not work reliably with another web3 extension.
This usually happens if you have two MetaMasks installed,
or MetaMask and another web3 extension. Please remove one
and try again.`)
}
var web3 = new Web3(inpageProvider)
web3.setProvider = function () {
log.debug('MetaMask - overrode web3.setProvider')

View File

@ -3,6 +3,7 @@ const RpcEngine = require('json-rpc-engine')
const createIdRemapMiddleware = require('json-rpc-engine/src/idRemapMiddleware')
const createStreamMiddleware = require('json-rpc-middleware-stream')
const LocalStorageStore = require('obs-store')
const asStream = require('obs-store/lib/asStream')
const ObjectMultiplex = require('obj-multiplex')
module.exports = MetamaskInpageProvider
@ -21,9 +22,10 @@ function MetamaskInpageProvider (connectionStream) {
// subscribe to metamask public config (one-way)
self.publicConfigStore = new LocalStorageStore({ storageKey: 'MetaMask-Config' })
pump(
mux.createStream('publicConfig'),
self.publicConfigStore,
asStream(self.publicConfigStore),
(err) => logStreamDisconnectWarning('MetaMask PublicConfigStore', err)
)

View File

@ -65,11 +65,11 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
}
resubmitPendingTxs () {
resubmitPendingTxs (block) {
const pending = this.getPendingTransactions()
// only try resubmitting if their are transactions to resubmit
if (!pending.length) return
pending.forEach((txMeta) => this._resubmitTx(txMeta).catch((err) => {
pending.forEach((txMeta) => this._resubmitTx(txMeta, block.number).catch((err) => {
/*
Dont marked as failed if the error is a "known" transaction warning
"there is already a transaction with the same sender-nonce
@ -101,13 +101,25 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
}))
}
async _resubmitTx (txMeta) {
async _resubmitTx (txMeta, latestBlockNumber) {
if (!txMeta.firstRetryBlockNumber) {
this.emit('tx:block-update', txMeta, latestBlockNumber)
}
if (Date.now() > txMeta.time + this.retryTimePeriod) {
const hours = (this.retryTimePeriod / 3.6e+6).toFixed(1)
const err = new Error(`Gave up submitting after ${hours} hours.`)
return this.emit('tx:failed', txMeta.id, err)
}
const firstRetryBlockNumber = txMeta.firstRetryBlockNumber || latestBlockNumber
const txBlockDistance = Number.parseInt(latestBlockNumber, 16) - Number.parseInt(firstRetryBlockNumber, 16)
const retryCount = txMeta.retryCount || 0
// Exponential backoff to limit retries at publishing
if (txBlockDistance <= Math.pow(2, retryCount) - 1) return
// Only auto-submit already-signed txs:
if (!('rawTx' in txMeta)) return

View File

@ -3,6 +3,7 @@ const extend = require('xtend')
const pump = require('pump')
const Dnode = require('dnode')
const ObservableStore = require('obs-store')
const asStream = require('obs-store/lib/asStream')
const AccountTracker = require('./lib/account-tracker')
const EthQuery = require('eth-query')
const RpcEngine = require('json-rpc-engine')
@ -31,6 +32,7 @@ const ConfigManager = require('./lib/config-manager')
const nodeify = require('./lib/nodeify')
const accountImporter = require('./account-import-strategies')
const getBuyEthUrl = require('./lib/buy-eth-url')
const Mutex = require('await-semaphore').Mutex
const version = require('../manifest.json').version
module.exports = class MetamaskController extends EventEmitter {
@ -38,10 +40,12 @@ module.exports = class MetamaskController extends EventEmitter {
constructor (opts) {
super()
this.sendUpdate = debounce(this.privateSendUpdate.bind(this), 200)
this.opts = opts
const initState = opts.initState || {}
this.recordFirstTimeInfo(initState)
// platform-specific api
this.platform = opts.platform
@ -49,6 +53,9 @@ module.exports = class MetamaskController extends EventEmitter {
// observable state store
this.store = new ObservableStore(initState)
// lock to ensure only one vault created at once
this.createVaultMutex = new Mutex()
// network store
this.networkController = new NetworkController(initState.NetworkController)
@ -144,6 +151,8 @@ module.exports = class MetamaskController extends EventEmitter {
// notices
this.noticeController = new NoticeController({
initState: initState.NoticeController,
version,
firstVersion: initState.firstTimeInfo.version,
})
this.noticeController.updateNoticesList()
// to be uncommented when retrieving notices from a remote server.
@ -454,7 +463,7 @@ module.exports = class MetamaskController extends EventEmitter {
setupPublicConfig (outStream) {
pump(
this.publicConfigStore,
asStream(this.publicConfigStore),
outStream,
(err) => {
if (err) log.error(err)
@ -470,15 +479,34 @@ module.exports = class MetamaskController extends EventEmitter {
// Vault Management
//
async createNewVaultAndKeychain (password, cb) {
const vault = await this.keyringController.createNewVaultAndKeychain(password)
async createNewVaultAndKeychain (password) {
const release = await this.createVaultMutex.acquire()
let vault
try {
const accounts = await this.keyringController.getAccounts()
if (accounts.length > 0) {
vault = await this.keyringController.fullUpdate()
} else {
vault = await this.keyringController.createNewVaultAndKeychain(password)
this.selectFirstIdentity(vault)
}
release()
} catch (err) {
release()
throw err
}
return vault
}
async createNewVaultAndRestore (password, seed, cb) {
async createNewVaultAndRestore (password, seed) {
const release = await this.createVaultMutex.acquire()
const vault = await this.keyringController.createNewVaultAndRestore(password, seed)
this.selectFirstIdentity(vault)
release()
return vault
}
@ -785,4 +813,13 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
recordFirstTimeInfo (initState) {
if (!('firstTimeInfo' in initState)) {
initState.firstTimeInfo = {
version,
date: Date.now(),
}
}
}
}

View File

@ -0,0 +1,41 @@
const version = 20
/*
This migration ensures previous installations
get a `firstTimeInfo` key on the metamask state,
so that we can version notices in the future.
*/
const clone = require('clone')
module.exports = {
version,
migrate: function (originalVersionedData) {
const versionedData = clone(originalVersionedData)
versionedData.meta.version = version
try {
const state = versionedData.data
const newState = transformState(state)
versionedData.data = newState
} catch (err) {
console.warn(`MetaMask Migration #${version}` + err.stack)
}
return Promise.resolve(versionedData)
},
}
function transformState (state) {
const newState = state
if ('metamask' in newState &&
!('firstTimeInfo' in newState.metamask)) {
newState.metamask.firstTimeInfo = {
version: '3.12.0',
date: Date.now(),
}
}
return newState
}

View File

@ -30,4 +30,5 @@ module.exports = [
require('./017'),
require('./018'),
require('./019'),
require('./020'),
]

View File

@ -1,13 +1,17 @@
const EventEmitter = require('events').EventEmitter
const semver = require('semver')
const extend = require('xtend')
const ObservableStore = require('obs-store')
const hardCodedNotices = require('../../notices/notices.json')
const uniqBy = require('lodash.uniqby')
module.exports = class NoticeController extends EventEmitter {
constructor (opts) {
super()
this.noticePoller = null
this.firstVersion = opts.firstVersion
this.version = opts.version
const initState = extend({
noticesList: [],
}, opts.initState)
@ -30,9 +34,9 @@ module.exports = class NoticeController extends EventEmitter {
return unreadNotices[unreadNotices.length - 1]
}
setNoticesList (noticesList) {
async setNoticesList (noticesList) {
this.store.updateState({ noticesList })
return Promise.resolve(true)
return true
}
markNoticeRead (noticeToMark, cb) {
@ -50,12 +54,14 @@ module.exports = class NoticeController extends EventEmitter {
}
}
updateNoticesList () {
return this._retrieveNoticeData().then((newNotices) => {
var oldNotices = this.getNoticesList()
var combinedNotices = this._mergeNotices(oldNotices, newNotices)
return Promise.resolve(this.setNoticesList(combinedNotices))
})
async updateNoticesList () {
const newNotices = await this._retrieveNoticeData()
const oldNotices = this.getNoticesList()
const combinedNotices = this._mergeNotices(oldNotices, newNotices)
const filteredNotices = this._filterNotices(combinedNotices)
const result = this.setNoticesList(filteredNotices)
this._updateMemstore()
return result
}
startPolling () {
@ -68,22 +74,30 @@ module.exports = class NoticeController extends EventEmitter {
}
_mergeNotices (oldNotices, newNotices) {
var noticeMap = this._mapNoticeIds(oldNotices)
newNotices.forEach((notice) => {
if (noticeMap.indexOf(notice.id) === -1) {
oldNotices.push(notice)
return uniqBy(oldNotices.concat(newNotices), 'id')
}
_filterNotices(notices) {
return notices.filter((newNotice) => {
if ('version' in newNotice) {
const satisfied = semver.satisfies(this.version, newNotice.version)
return satisfied
}
if ('firstVersion' in newNotice) {
const satisfied = semver.satisfies(this.firstVersion, newNotice.firstVersion)
return satisfied
}
return true
})
return oldNotices
}
_mapNoticeIds (notices) {
return notices.map((notice) => notice.id)
}
_retrieveNoticeData () {
async _retrieveNoticeData () {
// Placeholder for the API.
return Promise.resolve(hardCodedNotices)
return hardCodedNotices
}
_updateMemstore () {

View File

@ -126,11 +126,17 @@ gulp.task('manifest:production', function() {
'./dist/firefox/manifest.json',
'./dist/chrome/manifest.json',
'./dist/edge/manifest.json',
'./dist/opera/manifest.json',
],{base: './dist/'})
// Exclude chromereload script in production:
.pipe(gulpif(!debug,jsoneditor(function(json) {
json.background.scripts = ["scripts/background.js"]
json.background.scripts = json.background.scripts.filter((script) => {
return !script.includes('chromereload')
})
return json
})))
.pipe(gulp.dest('./dist/', { overwrite: true }))
})

View File

@ -64,7 +64,7 @@ class NoticeScreen extends Component {
<Identicon address={address} diameter={70} />
<div className="tou__title">{title}</div>
<Markdown
className="tou__body"
className="tou__body markdown"
source={body}
skipHtml
/>

View File

@ -0,0 +1,11 @@
Please take a moment to [back up your seed phrase again](https://support.metamask.io/kb/article/28-abbu-always-be-backed-up-how-to-make-sure-your-12-word-metamask-seed-phrase-is-backed-up).
MetaMask has become aware of a previous issue where a very small number of users were shown the wrong seed phrase to back up. The only way to protect yourself from this issue, is to back up your seed phrase again now.
You can follow the guide at this link:
[https://support.metamask.io/kb/article/28-abbu-always-be-backed-up-how-to-make-sure-your-12-word-metamask-seed-phrase-is-backed-up](https://support.metamask.io/kb/article/28-abbu-always-be-backed-up-how-to-make-sure-your-12-word-metamask-seed-phrase-is-backed-up)
We have fixed the known issue, but will be issuing ongoing bug bounties to help prevent this kind of problem in the future.
For more information on this issue, [see this blog post](https://medium.com/metamask/seed-phrase-issue-bounty-awarded-e1986e811021)

View File

@ -1 +1 @@
3
4

File diff suppressed because one or more lines are too long

View File

@ -118,6 +118,7 @@
"lodash.debounce": "^4.0.8",
"lodash.memoize": "^4.1.2",
"lodash.shuffle": "^4.2.0",
"lodash.uniqby": "^4.7.0",
"loglevel": "^1.4.1",
"metamascara": "^1.3.1",
"metamask-logo": "^2.1.2",
@ -126,7 +127,7 @@
"multiplex": "^6.7.0",
"number-to-bn": "^1.7.0",
"obj-multiplex": "^1.0.0",
"obs-store": "^2.3.1",
"obs-store": "^3.0.0",
"once": "^1.3.3",
"ping-pong-stream": "^1.0.0",
"pojo-migrator": "^2.1.0",
@ -142,7 +143,7 @@
"react-addons-css-transition-group": "^15.6.0",
"react-dom": "^15.6.2",
"react-hyperscript": "^3.0.0",
"react-markdown": "^2.3.0",
"react-markdown": "^3.0.0",
"react-redux": "^5.0.5",
"react-select": "^1.0.0",
"react-simple-file-input": "^2.0.0",
@ -160,6 +161,7 @@
"sandwich-expando": "^1.1.3",
"semaphore": "^1.0.5",
"shallow-copy": "0.0.1",
"semver": "^5.4.1",
"sw-stream": "^2.0.0",
"textarea-caret": "^3.0.1",
"through2": "^2.0.3",

View File

@ -6,23 +6,7 @@ async function runFirstTimeUsageTest (assert, done) {
const app = $('#app-content')
// recurse notices
while (true) {
const button = app.find('button')
if (button.html() === 'Accept') {
// still notices to accept
const termsPage = app.find('.markdown')[0]
termsPage.scrollTop = termsPage.scrollHeight
await timeout()
console.log('Clearing notice')
button.click()
await timeout()
} else {
// exit loop
console.log('No more notices...')
break
}
}
await skipNotices(app)
await timeout()
@ -51,28 +35,13 @@ async function runFirstTimeUsageTest (assert, done) {
assert.equal(created.textContent, 'Your unique account image', 'unique image screen')
// Agree button
const button = app.find('button')[0]
let button = app.find('button')[0]
assert.ok(button, 'button present')
button.click()
await timeout(1000)
// Privacy Screen
const detail = app.find('.tou__title')[0]
assert.equal(detail.textContent, 'Privacy Notice', 'privacy notice screen')
app.find('button').click()
await timeout(1000)
// terms of service screen
const tou = app.find('.tou__title')[0]
assert.equal(tou.textContent, 'Terms of Use', 'terms of use screen')
app.find('.tou__body').scrollTop(100000)
await timeout(1000)
app.find('.first-time-flow__button').click()
await timeout(1000)
await skipNotices(app)
// secret backup phrase
const seedTitle = app.find('.backup-phrase__title')[0]
@ -157,3 +126,23 @@ function timeout (time) {
setTimeout(resolve, time || 1500)
})
}
async function skipNotices (app) {
while (true) {
const button = app.find('button')
if (button && button.html() === 'Accept') {
// still notices to accept
const termsPage = app.find('.markdown')[0]
if (!termsPage) {
break
}
termsPage.scrollTop = termsPage.scrollHeight
await timeout()
button.click()
await timeout()
} else {
console.log('No more notices...')
break
}
}
}

View File

@ -11,6 +11,15 @@ describe('MetaMaskController', function () {
unlockAccountMessage: noop,
showUnapprovedTx: noop,
platform: {},
encryptor: {
encrypt: function(password, object) {
this.object = object
return Promise.resolve()
},
decrypt: function () {
return Promise.resolve(this.object)
}
},
// initial state
initState: clone(firstTimeState),
})
@ -27,6 +36,30 @@ describe('MetaMaskController', function () {
describe('Metamask Controller', function () {
assert(metamaskController)
beforeEach(function () {
sinon.spy(metamaskController.keyringController, 'createNewVaultAndKeychain')
})
afterEach(function () {
metamaskController.keyringController.createNewVaultAndKeychain.restore()
})
describe('#createNewVaultAndKeychain', function () {
it('can only create new vault on keyringController once', async function () {
const selectStub = sinon.stub(metamaskController, 'selectFirstIdentity')
const password = 'a-fake-password'
const first = await metamaskController.createNewVaultAndKeychain(password)
const second = await metamaskController.createNewVaultAndKeychain(password)
assert(metamaskController.keyringController.createNewVaultAndKeychain.calledOnce)
selectStub.reset()
})
})
})
})

View File

@ -207,6 +207,7 @@ describe('PendingTransactionTracker', function () {
})
describe('#resubmitPendingTxs', function () {
const blockStub = { number: '0x0' };
beforeEach(function () {
const txMeta2 = txMeta3 = txMeta
txList = [txMeta, txMeta2, txMeta3].map((tx) => {
@ -224,7 +225,7 @@ describe('PendingTransactionTracker', function () {
Promise.all(txList.map((tx) => tx.processed))
.then((txCompletedList) => done())
.catch(done)
pendingTxTracker.resubmitPendingTxs()
pendingTxTracker.resubmitPendingTxs(blockStub)
})
it('should not emit \'tx:failed\' if the txMeta throws a known txError', function (done) {
knownErrors =[
@ -251,7 +252,7 @@ describe('PendingTransactionTracker', function () {
.then((txCompletedList) => done())
.catch(done)
pendingTxTracker.resubmitPendingTxs()
pendingTxTracker.resubmitPendingTxs(blockStub)
})
it('should emit \'tx:warning\' if it encountered a real error', function (done) {
pendingTxTracker.once('tx:warning', (txMeta, err) => {
@ -269,12 +270,14 @@ describe('PendingTransactionTracker', function () {
.then((txCompletedList) => done())
.catch(done)
pendingTxTracker.resubmitPendingTxs()
pendingTxTracker.resubmitPendingTxs(blockStub)
})
})
describe('#_resubmitTx', function () {
it('should publishing the transaction', function (done) {
const enoughBalance = '0x100000'
const mockFirstRetryBlockNumber = '0x1'
let txMetaToTestExponentialBackoff
beforeEach(() => {
pendingTxTracker.getBalance = (address) => {
assert.equal(address, txMeta.txParams.from, 'Should pass the address')
return enoughBalance
@ -282,6 +285,20 @@ describe('PendingTransactionTracker', function () {
pendingTxTracker.publishTransaction = async (rawTx) => {
assert.equal(rawTx, txMeta.rawTx, 'Should pass the rawTx')
}
sinon.spy(pendingTxTracker, 'publishTransaction')
txMetaToTestExponentialBackoff = Object.assign({}, txMeta, {
retryCount: 4,
firstRetryBlockNumber: mockFirstRetryBlockNumber,
})
})
afterEach(() => {
pendingTxTracker.publishTransaction.reset()
})
it('should publish the transaction', function (done) {
const enoughBalance = '0x100000'
// Stubbing out current account state:
// Adding the fake tx:
@ -291,6 +308,36 @@ describe('PendingTransactionTracker', function () {
assert.ifError(err, 'should not throw an error')
done(err)
})
assert.equal(pendingTxTracker.publishTransaction.callCount, 1, 'Should call publish transaction')
})
it('should not publish the transaction if the limit of retries has been exceeded', function (done) {
const enoughBalance = '0x100000'
const mockLatestBlockNumber = '0x5'
pendingTxTracker._resubmitTx(txMetaToTestExponentialBackoff, mockLatestBlockNumber)
.then(() => done())
.catch((err) => {
assert.ifError(err, 'should not throw an error')
done(err)
})
assert.equal(pendingTxTracker.publishTransaction.callCount, 0, 'Should NOT call publish transaction')
})
it('should publish the transaction if the number of blocks since last retry exceeds the last set limit', function (done) {
const enoughBalance = '0x100000'
const mockLatestBlockNumber = '0x11'
pendingTxTracker._resubmitTx(txMetaToTestExponentialBackoff, mockLatestBlockNumber)
.then(() => done())
.catch((err) => {
assert.ifError(err, 'should not throw an error')
done(err)
})
assert.equal(pendingTxTracker.publishTransaction.callCount, 1, 'Should call publish transaction')
})
})
})

View File

@ -149,6 +149,7 @@ var actions = {
UPDATE_SEND_AMOUNT: 'UPDATE_SEND_AMOUNT',
UPDATE_SEND_MEMO: 'UPDATE_SEND_MEMO',
UPDATE_SEND_ERRORS: 'UPDATE_SEND_ERRORS',
UPDATE_MAX_MODE: 'UPDATE_MAX_MODE',
UPDATE_SEND: 'UPDATE_SEND',
CLEAR_SEND: 'CLEAR_SEND',
updateGasLimit,
@ -160,6 +161,7 @@ var actions = {
updateSendAmount,
updateSendMemo,
updateSendErrors,
setMaxModeTo,
updateSend,
clearSend,
setSelectedAddress,
@ -642,6 +644,13 @@ function updateSendErrors (error) {
}
}
function setMaxModeTo (bool) {
return {
type: actions.UPDATE_MAX_MODE,
value: bool,
}
}
function updateSend (newSend) {
return {
type: actions.UPDATE_SEND,

View File

@ -5,6 +5,8 @@ const connect = require('react-redux').connect
const actions = require('../../actions')
const GasModalCard = require('./gas-modal-card')
const ethUtil = require('ethereumjs-util')
const {
MIN_GAS_PRICE_DEC,
MIN_GAS_LIMIT_DEC,
@ -19,6 +21,7 @@ const {
conversionUtil,
multiplyCurrencies,
conversionGreaterThan,
subtractCurrencies,
} = require('../../conversion-util')
const {
@ -30,6 +33,7 @@ const {
getSendFrom,
getCurrentAccountWithSendEtherInfo,
getSelectedTokenToFiatRate,
getSendMaxModeState,
} = require('../../selectors')
function mapStateToProps (state) {
@ -42,6 +46,7 @@ function mapStateToProps (state) {
gasLimit: getGasLimit(state),
conversionRate,
amount: getSendAmount(state),
maxModeOn: getSendMaxModeState(state),
balance: currentAccount.balance,
primaryCurrency: selectedToken && selectedToken.symbol,
selectedToken,
@ -55,6 +60,7 @@ function mapDispatchToProps (dispatch) {
updateGasPrice: newGasPrice => dispatch(actions.updateGasPrice(newGasPrice)),
updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)),
updateGasTotal: newGasTotal => dispatch(actions.updateGasTotal(newGasTotal)),
updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)),
}
}
@ -93,8 +99,21 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
updateGasLimit,
hideModal,
updateGasTotal,
maxModeOn,
selectedToken,
balance,
updateSendAmount,
} = this.props
if (maxModeOn && !selectedToken) {
const maxAmount = subtractCurrencies(
ethUtil.addHexPrefix(balance),
ethUtil.addHexPrefix(gasTotal),
{ toNumericBase: 'hex' }
)
updateSendAmount(maxAmount)
}
updateGasPrice(gasPrice)
updateGasLimit(gasLimit)
updateGasTotal(gasTotal)
@ -112,12 +131,13 @@ CustomizeGasModal.prototype.validate = function ({ gasTotal, gasLimit }) {
selectedToken,
amountConversionRate,
conversionRate,
maxModeOn,
} = this.props
let error = null
const balanceIsSufficient = isBalanceSufficient({
amount: selectedToken ? '0' : amount,
amount: selectedToken || maxModeOn ? '0' : amount,
gasTotal,
balance,
selectedToken,

View File

@ -78,5 +78,6 @@ function mapDispatchToProps (dispatch) {
goHome: () => dispatch(actions.goHome()),
clearSend: () => dispatch(actions.clearSend()),
backToConfirmScreen: editingTransactionId => dispatch(actions.showConfTxPage({ id: editingTransactionId })),
setMaxModeTo: bool => dispatch(actions.setMaxModeTo(bool)),
}
}

View File

@ -8,6 +8,8 @@ const actions = require('../actions')
const Tooltip = require('../components/tooltip')
const getCaretCoordinates = require('textarea-caret')
let isSubmitting = false
module.exports = connect(mapStateToProps)(InitializeMenuScreen)
inherits(InitializeMenuScreen, Component)
@ -164,7 +166,10 @@ InitializeMenuScreen.prototype.createNewVaultAndKeychain = function () {
return
}
if (!isSubmitting) {
isSubmitting = true
this.props.dispatch(actions.createNewVaultAndKeychain(password))
}
}
InitializeMenuScreen.prototype.inputChanged = function (event) {

View File

@ -33,6 +33,7 @@ function reduceMetamask (state, action) {
amount: '0x0',
memo: '',
errors: {},
maxModeOn: false,
editingTransactionId: null,
},
coinOptions: {},
@ -259,6 +260,14 @@ function reduceMetamask (state, action) {
},
})
case actions.UPDATE_MAX_MODE:
return extend(metamaskState, {
send: {
...metamaskState.send,
maxModeOn: action.value,
},
})
case actions.UPDATE_SEND:
return extend(metamaskState, {
send: {

View File

@ -25,6 +25,7 @@ const selectors = {
getSelectedTokenToFiatRate,
getSelectedTokenContract,
autoAddToBetaUI,
getSendMaxModeState,
}
module.exports = selectors
@ -136,6 +137,10 @@ function getSendAmount (state) {
return state.metamask.send.amount
}
function getSendMaxModeState (state) {
return state.metamask.send.maxModeOn
}
function getCurrentCurrency (state) {
return state.metamask.currentCurrency
}

View File

@ -13,8 +13,6 @@ const GasFeeDisplay = require('./components/send/gas-fee-display-v2')
const {
MIN_GAS_TOTAL,
MIN_GAS_PRICE_HEX,
MIN_GAS_LIMIT_HEX,
} = require('./components/send/send-constants')
const {
@ -313,8 +311,9 @@ SendTransactionScreen.prototype.renderToRow = function () {
SendTransactionScreen.prototype.handleAmountChange = function (value) {
const amount = value
const { updateSendAmount } = this.props
const { updateSendAmount, setMaxModeTo } = this.props
setMaxModeTo(false)
this.validateAmount(amount)
updateSendAmount(amount)
}
@ -324,11 +323,9 @@ SendTransactionScreen.prototype.setAmountToMax = function () {
from: { balance },
updateSendAmount,
updateSendErrors,
updateGasPrice,
updateGasLimit,
updateGasTotal,
tokenBalance,
selectedToken,
gasTotal,
} = this.props
const { decimals } = selectedToken || {}
const multiplier = Math.pow(10, Number(decimals || 0))
@ -337,16 +334,12 @@ SendTransactionScreen.prototype.setAmountToMax = function () {
? multiplyCurrencies(tokenBalance, multiplier, {toNumericBase: 'hex'})
: subtractCurrencies(
ethUtil.addHexPrefix(balance),
ethUtil.addHexPrefix(MIN_GAS_TOTAL),
ethUtil.addHexPrefix(gasTotal),
{ toNumericBase: 'hex' }
)
updateSendErrors({ amount: null })
if (!selectedToken) {
updateGasPrice(MIN_GAS_PRICE_HEX)
updateGasLimit(MIN_GAS_LIMIT_HEX)
updateGasTotal(MIN_GAS_TOTAL)
}
updateSendAmount(maxAmount)
}
@ -407,6 +400,8 @@ SendTransactionScreen.prototype.renderAmountRow = function () {
amountConversionRate,
errors,
amount,
setMaxModeTo,
maxModeOn,
} = this.props
return h('div.send-v2__form-row', [
@ -417,9 +412,10 @@ SendTransactionScreen.prototype.renderAmountRow = function () {
!errors.amount && h('div.send-v2__amount-max', {
onClick: (event) => {
event.preventDefault()
setMaxModeTo(true)
this.setAmountToMax()
},
}, [ 'Max' ]),
}, [ !maxModeOn ? 'Max' : '' ]),
]),
h('div.send-v2__form-field', [