Merge pull request #380 from poanetwork/develop

NW release 5.1.0
This commit is contained in:
Victor Baranov 2020-05-12 10:38:21 +03:00 committed by GitHub
commit cb1169a6a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 1022 additions and 1044 deletions

View File

@ -2,6 +2,13 @@
## Current Master
## 5.1.0 Tue May 12 2020
- [#356](https://github.com/poanetwork/nifty-wallet/pull/356) - (Backwards-compatibility feature) Custom derivation paths and access to funds in accounts derived from ETH dPath
- [#379](https://github.com/poanetwork/nifty-wallet/pull/379) - (Feature) Ability to set custom nonce of tx
- [#377](https://github.com/poanetwork/nifty-wallet/pull/377) - (Fix) Sign message screen: do not decode message if it is not hex encoded
- [#364](https://github.com/poanetwork/nifty-wallet/pull/364) - (Fix) notifications order in batch requests
## 5.0.3 Fri May 01 2020
- [#373](https://github.com/poanetwork/nifty-wallet/pull/373) - (Feature) Add STAKE token

View File

@ -1,7 +1,7 @@
{
"name": "__MSG_appName__",
"short_name": "__MSG_appName__",
"version": "5.0.3",
"version": "5.1.0",
"manifest_version": 2,
"author": "POA Network",
"description": "__MSG_appDescription__",

View File

@ -5,11 +5,11 @@
// this needs to run before anything else
require('./lib/setupFetchDebugging')()
const endOfStream = require('end-of-stream')
const pump = require('pump')
const debounce = require('debounce-stream')
const log = require('loglevel')
const extension = require('extensionizer')
import endOfStream from 'end-of-stream'
import pump from 'pump'
import debounce from 'debounce-stream'
import log from 'loglevel'
import extension from 'extensionizer'
const LocalStorageStore = require('obs-store/lib/localStorage')
const LocalStore = require('./lib/local-store')
const storeTransform = require('obs-store/lib/transform')
@ -19,13 +19,11 @@ const Migrator = require('./lib/migrator/')
const migrations = require('./migrations/')
const PortStream = require('extension-port-stream')
const createStreamSink = require('./lib/createStreamSink')
const NotificationManager = require('./lib/notification-manager.js')
import NotificationManager from './lib/notification-manager.js'
const MetamaskController = require('./metamask-controller')
const rawFirstTimeState = require('./first-time-state')
const setupRaven = require('./lib/setupRaven')
const reportFailedTxToSentry = require('./lib/reportFailedTxToSentry')
const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics')
const EdgeEncryptor = require('./edge-encryptor')
const getFirstPreferredLangCode = require('./lib/get-first-preferred-lang-code')
const getObjStructure = require('./lib/getObjStructure')
@ -51,12 +49,6 @@ global.METAMASK_NOTIFIER = notificationManager
const release = platform.getVersion()
const raven = setupRaven({ release })
// browser check if it is Edge - https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
// Internet Explorer 6-11
const isIE = !!document.documentMode
// Edge 20+
const isEdge = !isIE && !!window.StyleMedia
let popupIsOpen = false
let notificationIsOpen = false
const openMetamaskTabsIDs = {}
@ -70,9 +62,6 @@ let versionedData
// initialization flow
initialize().catch(log.error)
// setup metamask mesh testing container
setupMetamaskMeshMetrics()
/**
* An object representing a transaction, in whatever state it is in.
@ -177,6 +166,7 @@ async function initialize () {
async function loadStateFromPersistence () {
// migrations
const migrator = new Migrator({ migrations })
migrator.on('error', console.warn)
// read from disk
// first from preferred, async API:
@ -256,7 +246,7 @@ function setupController (initState, initLangCode) {
showUnconfirmedMessage: triggerUi,
unlockAccountMessage: triggerUi,
showUnapprovedTx: triggerUi,
showWatchAssetUi: showWatchAssetUi,
openPopup: openPopup,
// initial state
initState,
// initial locale code
@ -269,7 +259,6 @@ function setupController (initState, initLangCode) {
getOpenMetamaskTabsIds: () => {
return openMetamaskTabsIDs
},
encryptor: isEdge ? new EdgeEncryptor() : undefined,
})
global.metamaskController = controller
@ -460,28 +449,27 @@ function setupController (initState, initLangCode) {
/**
* Opens the browser popup for user confirmation
*/
function triggerUi () {
extension.tabs.query({ active: true }, tabs => {
const currentlyActiveMetamaskTab = Boolean(tabs.find(tab => openMetamaskTabsIDs[tab.id]))
/**
* https://github.com/poanetwork/metamask-extension/issues/19
* !notificationIsOpen was removed from the check, because notification can be opened, but it can be behind the DApp
* for some reasons. For example, if notification popup was opened, but user moved focus to DApp.
* New transaction, in this case, will not appear in front of DApp.
*/
if (!popupIsOpen && !currentlyActiveMetamaskTab) {
notificationManager.showPopup()
}
})
async function triggerUi () {
const tabs = await platform.getActiveTabs()
const currentlyActiveMetamaskTab = Boolean(tabs.find((tab) => openMetamaskTabsIDs[tab.id]))
/**
* https://github.com/poanetwork/metamask-extension/issues/19
* !notificationIsOpen was removed from the check, because notification can be opened, but it can be behind the DApp
* for some reasons. For example, if notification popup was opened, but user moved focus to DApp.
* New transaction, in this case, will not appear in front of DApp.
*/
if (!popupIsOpen && !currentlyActiveMetamaskTab) {
await notificationManager.showPopup()
}
}
/**
* Opens the browser popup for user confirmation of watchAsset
* then it waits until user interact with the UI
*/
function showWatchAssetUi () {
triggerUi()
return new Promise(
async function openPopup () {
await triggerUi()
await new Promise(
(resolve) => {
const interval = setInterval(() => {
if (!notificationIsOpen) {

View File

@ -12,10 +12,10 @@ import createJsonRpcClient from './createJsonRpcClient'
import createLocalhostClient from './createLocalhostClient'
const createPocketClient = require('./createPocketClient')
const { createSwappableProxy, createEventEmitterProxy } = require('swappable-obj-proxy')
const ethNetProps = require('eth-net-props')
import ethNetProps from 'eth-net-props'
import parse from 'url-parse'
const networks = { networkList: {} }
const { isKnownProvider, getDPath } = require('../../../../old-ui/app/util')
const { isKnownProvider } = require('../../../../old-ui/app/util')
const {
ROPSTEN,
@ -205,8 +205,6 @@ module.exports = class NetworkController extends EventEmitter {
const previousNetworkID = this.getNetworkState()
this.setNetworkState('loading')
this._configureProvider(opts)
const dPath = getDPath(opts.type)
this.store.updateState({ dPath })
this.emit('networkDidChange', opts.type, previousNetworkID)
}

View File

@ -47,7 +47,7 @@ class PreferencesController {
this.diagnostics = opts.diagnostics
this.network = opts.network
this.store = new ObservableStore(initState)
this.showWatchAssetUi = opts.showWatchAssetUi
this.openPopup = opts.openPopup
this._subscribeProviderType()
}
// PUBLIC METHODS
@ -591,7 +591,7 @@ class PreferencesController {
}
const tokenOpts = { rawAddress, decimals, symbol, image }
this.addSuggestedERC20Asset(tokenOpts)
return this.showWatchAssetUi().then(() => {
return this.openPopup().then(() => {
const tokenAddresses = this.getTokens().filter(token => token.address === normalizeAddress(rawAddress))
return tokenAddresses.length > 0
})

View File

@ -290,7 +290,8 @@ class TransactionController extends EventEmitter {
*/
async updateAndApproveTransaction (txMeta) {
this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction')
await this.approveTransaction(txMeta.id)
const customNonce = txMeta.txParams.nonce
await this.approveTransaction(txMeta.id, customNonce)
}
/**
@ -301,7 +302,7 @@ class TransactionController extends EventEmitter {
if any of these steps fails the tx status will be set to failed
@param txId {number} - the tx's Id
*/
async approveTransaction (txId) {
async approveTransaction (txId, customNonce) {
let nonceLock
try {
// approve
@ -315,7 +316,7 @@ class TransactionController extends EventEmitter {
// if txMeta has lastGasPrice then it is a retry at same nonce with higher
// gas price transaction and their for the nonce should not be calculated
const nonce = txMeta.lastGasPrice ? txMeta.txParams.nonce : nonceLock.nextNonce
txMeta.txParams.nonce = ethUtil.addHexPrefix(nonce.toString(16))
txMeta.txParams.nonce = customNonce || ethUtil.addHexPrefix(nonce.toString(16))
// add nonce debugging information to txMeta
txMeta.nonceDetails = nonceLock.nonceDetails
this.txStateManager.updateTx(txMeta, 'transactions#approveTransaction')

View File

@ -1,97 +0,0 @@
const asmcrypto = require('asmcrypto.js')
const Unibabel = require('browserify-unibabel')
/**
* A Microsoft Edge-specific encryption class that exposes
* the interface expected by eth-keykeyring-controller
*/
class EdgeEncryptor {
/**
* Encrypts an arbitrary object to ciphertext
*
* @param {string} password Used to generate a key to encrypt the data
* @param {Object} dataObject Data to encrypt
* @returns {Promise<string>} Promise resolving to an object with ciphertext
*/
encrypt (password, dataObject) {
const salt = this._generateSalt()
return this._keyFromPassword(password, salt)
.then(function (key) {
const data = JSON.stringify(dataObject)
const dataBuffer = Unibabel.utf8ToBuffer(data)
const vector = global.crypto.getRandomValues(new Uint8Array(16))
const resultbuffer = asmcrypto.AES_GCM.encrypt(dataBuffer, key, vector)
const buffer = new Uint8Array(resultbuffer)
const vectorStr = Unibabel.bufferToBase64(vector)
const vaultStr = Unibabel.bufferToBase64(buffer)
return JSON.stringify({
data: vaultStr,
iv: vectorStr,
salt: salt,
})
})
}
/**
* Decrypts an arbitrary object from ciphertext
*
* @param {string} password Used to generate a key to decrypt the data
* @param {string} text Ciphertext of an encrypted object
* @returns {Promise<Object>} Promise resolving to copy of decrypted object
*/
decrypt (password, text) {
const payload = JSON.parse(text)
const salt = payload.salt
return this._keyFromPassword(password, salt)
.then(function (key) {
const encryptedData = Unibabel.base64ToBuffer(payload.data)
const vector = Unibabel.base64ToBuffer(payload.iv)
return new Promise((resolve, reject) => {
let result
try {
result = asmcrypto.AES_GCM.decrypt(encryptedData, key, vector)
} catch (err) {
return reject(new Error('Incorrect password'))
}
const decryptedData = new Uint8Array(result)
const decryptedStr = Unibabel.bufferToUtf8(decryptedData)
const decryptedObj = JSON.parse(decryptedStr)
resolve(decryptedObj)
})
})
}
/**
* Retrieves a cryptographic key using a password
*
* @private
* @param {string} password Password used to unlock a cryptographic key
* @param {string} salt Random base64 data
* @returns {Promise<Object>} Promise resolving to a derived key
*/
_keyFromPassword (password, salt) {
const passBuffer = Unibabel.utf8ToBuffer(password)
const saltBuffer = Unibabel.base64ToBuffer(salt)
return new Promise((resolve) => {
const key = asmcrypto.PBKDF2_HMAC_SHA256.bytes(passBuffer, saltBuffer, 10000)
resolve(key)
})
}
/**
* Generates random base64 encoded data
*
* @private
* @returns {string} Randomized base64 encoded data
*/
_generateSalt (byteCount = 32) {
const view = new Uint8Array(byteCount)
global.crypto.getRandomValues(view)
const b64encoded = btoa(String.fromCharCode.apply(null, view))
return b64encoded
}
}
module.exports = EdgeEncryptor

View File

@ -1,7 +1,7 @@
const extension = require('extensionizer')
const height = 620
const width = 360
import ExtensionPlatform from '../platforms/extension'
const NOTIFICATION_HEIGHT = 620
const NOTIFICATION_WIDTH = 360
class NotificationManager {
@ -12,47 +12,45 @@ class NotificationManager {
*
*/
constructor () {
this.platform = new ExtensionPlatform()
}
/**
* Either brings an existing MetaMask notification window into focus, or creates a new notification window. New
* notification windows are given a 'popup' type.
*
*/
showPopup () {
this._getPopup((err, popup) => {
if (err) throw err
async showPopup () {
const popup = await this._getPopup()
// Bring focus to chrome popup
if (popup) {
// bring focus to existing chrome popup
extension.windows.update(popup.id, { focused: true })
} else {
const cb = (currentPopup) => {
this._popupId = currentPopup.id
extension.windows.update(currentPopup.id, { focused: true })
}
// create new notification popup
const creation = extension.windows.create({
url: 'notification.html',
type: 'popup',
width,
height,
}, cb)
creation && creation.then && creation.then(cb)
}
})
// Bring focus to chrome popup
if (popup) {
// bring focus to existing chrome popup
await this.platform.focusWindow(popup.id)
} else {
// create new notification popup
const popupWindow = await this.platform.openWindow({
url: 'notification.html',
type: 'popup',
width: NOTIFICATION_WIDTH,
height: NOTIFICATION_HEIGHT,
})
this._popupId = popupWindow.id
}
}
/**
* Closes a MetaMask notification if it window exists.
*
*/
closePopup () {
// closes notification popup
this._getPopup((err, popup) => {
if (err) throw err
if (!popup) return
extension.windows.remove(popup.id, console.error)
})
async closePopup () {
const popup = this._getPopup()
if (!popup) {
return
}
await this.platform.removeWindow(popup.id)
}
/**
@ -60,39 +58,19 @@ class NotificationManager {
* type 'popup')
*
* @private
* @param {Function} cb A node style callback that to whcih the found notification window will be passed.
* @param {Function} cb - A node style callback that to whcih the found notification window will be passed.
*
*/
_getPopup (cb) {
this._getWindows((err, windows) => {
if (err) throw err
cb(null, this._getPopupIn(windows))
})
}
/**
* Returns all open MetaMask windows.
*
* @private
* @param {Function} cb A node style callback that to which the windows will be passed.
*
*/
_getWindows (cb) {
// Ignore in test environment
if (!extension.windows) {
return cb()
}
extension.windows.getAll({}, (windows) => {
cb(null, windows)
})
async _getPopup () {
const windows = await this.platform.getAllWindows()
return this._getPopupIn(windows)
}
/**
* Given an array of windows, returns the 'popup' that has been opened by MetaMask, or null if no such window exists.
*
* @private
* @param {array} windows An array of objects containing data about the open MetaMask extension windows.
* @param {array} windows - An array of objects containing data about the open MetaMask extension windows.
*
*/
_getPopupIn (windows) {
@ -104,4 +82,4 @@ class NotificationManager {
}
module.exports = NotificationManager
export default NotificationManager

View File

@ -1,5 +1,5 @@
const KeyringController = require('eth-keychain-controller')
const log = require('loglevel')
import KeyringController from 'eth-keychain-controller'
import log from 'loglevel'
const { getDPath } = require('../../../old-ui/app/util')
const seedPhraseVerifier = {
@ -17,14 +17,14 @@ const seedPhraseVerifier = {
* @returns {Promise<void>} Promises undefined
*
*/
verifyAccounts (createdAccounts, seedWords, network) {
verifyAccounts (createdAccounts, seedWords, network, isCreatedWithCorrectDPath) {
return new Promise((resolve, reject) => {
if (!createdAccounts || createdAccounts.length < 1) {
return reject(new Error('No created accounts defined.'))
}
const dPath = getDPath(network)
const dPath = getDPath(network, isCreatedWithCorrectDPath)
const keyringController = new KeyringController({})
const Keyring = keyringController.getKeyringClassForType('HD Key Tree')
const opts = {
@ -56,4 +56,4 @@ const seedPhraseVerifier = {
},
}
module.exports = seedPhraseVerifier
export default seedPhraseVerifier

View File

@ -1,12 +0,0 @@
module.exports = setupMetamaskMeshMetrics
/**
* Injects an iframe into the current document for testing
*/
function setupMetamaskMeshMetrics () {
const testingContainer = document.createElement('iframe')
testingContainer.src = 'https://metamask.github.io/mesh-testing/'
console.log('Injecting Nifty Wallet Mesh testing client')
document.head.appendChild(testingContainer)
}

View File

@ -6,6 +6,7 @@ const {
ENVIRONMENT_TYPE_POPUP,
ENVIRONMENT_TYPE_NOTIFICATION,
ENVIRONMENT_TYPE_FULLSCREEN,
ENVIRONMENT_TYPE_BACKGROUND,
PLATFORM_FIREFOX,
PLATFORM_OPERA,
PLATFORM_CHROME,
@ -13,17 +14,6 @@ const {
PLATFORM_BRAVE,
} = require('./enums')
/**
* Generates an example stack trace
*
* @returns {string} A stack trace
*
*/
function getStack () {
const stack = new Error('Stack trace generator - not an error').stack
return stack
}
/**
* Used to determine the window type through which the app is being viewed.
* - 'popup' refers to the extension opened through the browser app icon (in top right corner in chrome and firefox)
@ -34,12 +24,15 @@ function getStack () {
*
*/
const getEnvironmentType = (url = window.location.href) => {
if (url.match(/popup.html(?:#.*)*$/)) {
const parsedUrl = new URL(url)
if (parsedUrl.pathname === '/popup.html') {
return ENVIRONMENT_TYPE_POPUP
} else if (url.match(/home.html(?:\?.+)*$/) || url.match(/home.html(?:#.*)*$/)) {
} else if (['/home.html', '/phishing.html'].includes(parsedUrl.pathname)) {
return ENVIRONMENT_TYPE_FULLSCREEN
} else {
} else if (parsedUrl.pathname === '/notification.html') {
return ENVIRONMENT_TYPE_NOTIFICATION
} else {
return ENVIRONMENT_TYPE_BACKGROUND
}
}
@ -175,7 +168,6 @@ module.exports = {
removeListeners,
applyListeners,
getPlatform,
getStack,
getEnvironmentType,
sufficientBalance,
hexToBn,

View File

@ -52,7 +52,7 @@ const version = require('../manifest.json').version
import ethUtil, { BN } from 'ethereumjs-util'
const GWEI_BN = new BN('1000000000')
import percentile from 'percentile'
const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
import seedPhraseVerifier from './lib/seed-phrase-verifier'
import log from 'loglevel'
const TrezorKeyring = require('eth-trezor-keyring')
const LedgerBridgeKeyring = require('eth-ledger-bridge-keyring')
@ -111,7 +111,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.preferencesController = new PreferencesController({
initState: initState.PreferencesController,
initLangCode: opts.initLangCode,
showWatchAssetUi: opts.showWatchAssetUi,
openPopup: opts.openPopup,
network: this.networkController,
})
@ -169,26 +169,32 @@ module.exports = class MetamaskController extends EventEmitter {
})
// ensure accountTracker updates balances after network change
this.networkController.on('networkDidChange', (newType, previousNetworkIDStr) => {
const dPath = getDPath(newType)
this.deriveKeyringFromNewDPath(dPath)
.then(accounts => {
this.accountTracker._updateAccounts()
this.detectTokensController.restartTokenDetection()
this.networkController.on('networkDidChange', (newNetworkType, previousNetworkIDStr) => {
this.keyringController.isCreatedWithCorrectDPath()
.then(isCreatedWithCorrectDPath => {
const dPath = getDPath(newNetworkType, isCreatedWithCorrectDPath)
this.deriveKeyringFromNewDPath(dPath)
.then(_accounts => {
this.accountTracker._updateAccounts()
this.detectTokensController.restartTokenDetection()
const previousNetworkID = parseInt(previousNetworkIDStr, 10)
const nextNetwork = getNetworkID({network: newType})
const nextNetworkID = parseInt(nextNetwork && nextNetwork.netId, 10)
if (nextNetworkID !== previousNetworkID) {
const isPreviousETC = previousNetworkID === CLASSIC_CODE
const isPreviousRSK = ifRSK(previousNetworkID)
const isNextETC = nextNetworkID === CLASSIC_CODE
const isNextRSK = ifRSK(nextNetworkID)
if (isPreviousETC || isPreviousRSK || isNextETC || isNextRSK) {
this.forgetDevice(LEDGER, false)
this.forgetDevice(TREZOR, false)
const previousNetworkID = parseInt(previousNetworkIDStr, 10)
const nextNetwork = getNetworkID({network: newNetworkType})
const nextNetworkID = parseInt(nextNetwork && nextNetwork.netId, 10)
if (nextNetworkID !== previousNetworkID) {
const isPreviousETC = previousNetworkID === CLASSIC_CODE
const isPreviousRSK = ifRSK(previousNetworkID)
const isNextETC = nextNetworkID === CLASSIC_CODE
const isNextRSK = ifRSK(nextNetworkID)
if (isPreviousETC || isPreviousRSK || isNextETC || isNextRSK) {
this.forgetDevice(LEDGER, false)
this.forgetDevice(TREZOR, false)
}
}
}
})
.catch(e => {
console.log(e)
})
})
.catch(e => {
console.log(e)
@ -493,6 +499,7 @@ module.exports = class MetamaskController extends EventEmitter {
addNewKeyring: nodeify(keyringController.addNewKeyring, keyringController),
addNewMultisig: nodeify(keyringController.addNewMultisig, keyringController),
exportAccount: nodeify(keyringController.exportAccount, keyringController),
isCreatedWithCorrectDPath: nodeify(keyringController.isCreatedWithCorrectDPath, keyringController),
// txController
cancelTransaction: nodeify(txController.cancelTransaction, txController),
@ -582,7 +589,7 @@ module.exports = class MetamaskController extends EventEmitter {
* @param {} password
* @param {} seed
*/
async createNewVaultAndRestore (password, seed) {
async createNewVaultAndRestore (password, seed, dPath) {
const releaseLock = await this.createVaultMutex.acquire()
try {
let accounts, lastBalance
@ -592,9 +599,8 @@ module.exports = class MetamaskController extends EventEmitter {
// clear known identities
this.preferencesController.setAddresses([])
// create new vault
const network = this.networkController.getProviderConfig().type
const dPath = getDPath(network)
this.store.updateState({dPath})
const networkType = this.networkController.getProviderConfig().type
const isCreatedWithCorrectDPath = true
const vault = await keyringController.createNewVaultAndRestore(password, seed, dPath)
const ethQuery = new EthQuery(this.provider)
@ -606,7 +612,7 @@ module.exports = class MetamaskController extends EventEmitter {
throw new Error('MetamaskController - No HD Key Tree found')
}
setDPath(primaryKeyring, network)
setDPath(primaryKeyring, networkType, isCreatedWithCorrectDPath)
// seek out the first zero balance
while (lastBalance !== '0x0') {
@ -911,13 +917,14 @@ module.exports = class MetamaskController extends EventEmitter {
* @returns {} keyState
*/
async addNewAccount () {
const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0]
const keyringController = this.keyringController
const primaryKeyring = keyringController.getKeyringsByType('HD Key Tree')[0]
if (!primaryKeyring) {
throw new Error('MetamaskController - No HD Key Tree found')
}
const network = this.networkController.getProviderConfig().type
setDPath(primaryKeyring, network)
const keyringController = this.keyringController
const networkType = this.networkController.getProviderConfig().type
const isCreatedWithCorrectDPath = await keyringController.isCreatedWithCorrectDPath()
setDPath(primaryKeyring, networkType, isCreatedWithCorrectDPath)
const oldAccounts = await keyringController.getAccounts()
const keyState = await keyringController.addNewAccount(primaryKeyring)
const newAccounts = await keyringController.getAccounts()
@ -965,12 +972,14 @@ module.exports = class MetamaskController extends EventEmitter {
* @returns {Promise<string>} Seed phrase to be confirmed by the user.
*/
async verifySeedPhrase () {
const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0]
const keyringController = this.keyringController
const isCreatedWithCorrectDPath = await keyringController.isCreatedWithCorrectDPath()
const primaryKeyring = keyringController.getKeyringsByType('HD Key Tree')[0]
if (!primaryKeyring) {
throw new Error('MetamaskController - No HD Key Tree found')
}
const network = this.networkController.getProviderConfig().type
setDPath(primaryKeyring, network)
const networkType = this.networkController.getProviderConfig().type
setDPath(primaryKeyring, networkType, isCreatedWithCorrectDPath)
const serialized = await primaryKeyring.serialize()
const seedWords = serialized.mnemonic
@ -981,7 +990,7 @@ module.exports = class MetamaskController extends EventEmitter {
}
try {
await seedPhraseVerifier.verifyAccounts(accounts, seedWords, network)
await seedPhraseVerifier.verifyAccounts(accounts, seedWords, networkType, isCreatedWithCorrectDPath)
return seedWords
} catch (err) {
log.error(err.message)
@ -1086,8 +1095,6 @@ module.exports = class MetamaskController extends EventEmitter {
const privateKey = await accountImporter.importAccount(strategy, args)
keyring = await this.keyringController.addNewKeyring('Simple Key Pair', [ privateKey ])
}
const network = this.networkController.getProviderConfig().type
setDPath(keyring, network)
const accounts = await keyring.getAccounts()
// update accounts in preferences controller
const allAccounts = await this.keyringController.getAccounts()
@ -1945,7 +1952,7 @@ module.exports = class MetamaskController extends EventEmitter {
.sort((block1, block2) => block1.number - block2.number)[recentBlocks.length - 1]
const gasPrice = recentBlock && recentBlock.minimumGasPrice && recentBlock.minimumGasPrice.toString()
if (gasPrice !== '0x' && gasPrice !== '0x0' && gasPrice !== '') {
return gasPrice
} else {

View File

@ -12,8 +12,64 @@ class ExtensionPlatform {
extension.runtime.reload()
}
openWindow ({ url }) {
extension.tabs.create({ url })
openTab (options) {
return new Promise((resolve, reject) => {
extension.tabs.create(options, (newTab) => {
const error = checkForError()
if (error) {
return reject(error)
}
return resolve(newTab)
})
})
}
openWindow (options) {
return new Promise((resolve, reject) => {
extension.windows.create(options, (newWindow) => {
const error = checkForError()
if (error) {
return reject(error)
}
return resolve(newWindow)
})
})
}
closeWindow (windowId) {
return new Promise((resolve, reject) => {
extension.windows.remove(windowId, () => {
const error = checkForError()
if (error) {
return reject(error)
}
return resolve()
})
})
}
focusWindow (windowId) {
return new Promise((resolve, reject) => {
extension.windows.update(windowId, { focused: true }, () => {
const error = checkForError()
if (error) {
return reject(error)
}
return resolve()
})
})
}
getLastFocusedWindow () {
return new Promise((resolve, reject) => {
extension.windows.getLastFocused((windowObject) => {
const error = checkForError()
if (error) {
return reject(error)
}
return resolve(windowObject)
})
})
}
closeCurrentWindow () {
@ -22,27 +78,6 @@ class ExtensionPlatform {
})
}
/**
* Closes all notifications windows, when action is confirmed in popup
* or closes notification window itself, when action is confirmed from it
*/
closeNotificationWindow () {
return extension.windows.getCurrent((curWindowsDetails) => {
if (curWindowsDetails.type === 'popup') {
return extension.windows.remove(curWindowsDetails.id)
} else {
extension.windows.getAll((windowsDetails) => {
const windowsDetailsFiltered = windowsDetails.filter((windowDetails) => windowDetails.id !== curWindowsDetails.id)
return windowsDetailsFiltered.forEach((windowDetails) => {
if (windowDetails.type === 'popup') {
extension.windows.remove(windowDetails.id)
}
})
})
}
})
}
getVersion () {
return extension.runtime.getManifest().version
}
@ -57,7 +92,7 @@ class ExtensionPlatform {
if (route) {
extensionURL += `#${route}`
}
this.openWindow({ url: extensionURL })
this.openTab({ url: extensionURL })
if (getEnvironmentType() !== ENVIRONMENT_TYPE_BACKGROUND) {
window.close()
}
@ -86,6 +121,30 @@ class ExtensionPlatform {
}
}
getAllWindows () {
return new Promise((resolve, reject) => {
extension.windows.getAll((windows) => {
const error = checkForError()
if (error) {
return reject(error)
}
return resolve(windows)
})
})
}
getActiveTabs () {
return new Promise((resolve, reject) => {
extension.tabs.query({ active: true }, (tabs) => {
const error = checkForError()
if (error) {
return reject(error)
}
return resolve(tabs)
})
})
}
currentTab () {
return new Promise((resolve, reject) => {
extension.tabs.getCurrent((tab) => {

View File

@ -3,11 +3,8 @@ const OldMetaMaskUiCss = require('../../old-ui/css')
const startPopup = require('./popup-core')
const PortStream = require('extension-port-stream')
const { getEnvironmentType } = require('./lib/util')
const { ENVIRONMENT_TYPE_NOTIFICATION } = require('./lib/enums')
import extension from 'extensionizer'
const ExtensionPlatform = require('./platforms/extension')
const NotificationManager = require('./lib/notification-manager')
const notificationManager = new NotificationManager()
const setupRaven = require('./lib/setupRaven')
const log = require('loglevel')
@ -29,7 +26,6 @@ async function start () {
// identify window type (popup, notification)
const windowType = getEnvironmentType(window.location.href)
global.METAMASK_UI_TYPE = windowType
closePopupIfOpen(windowType)
// setup stream to background
const extensionPort = extension.runtime.connect({ name: windowType })
@ -51,13 +47,6 @@ async function start () {
})
function closePopupIfOpen (windowType) {
if (windowType !== ENVIRONMENT_TYPE_NOTIFICATION) {
// should close only chrome popup
notificationManager.closePopup()
}
}
function displayCriticalError (err) {
container.innerHTML = '<div class="critical-error">The Nifty Wallet app failed to load: please open and close Nifty Wallet again to restart.</div>'
container.style.height = '80px'

View File

@ -4,7 +4,7 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('../../ui/app/actions')
const { getCurrentKeyring, ifContractAcc, valuesFor, toChecksumAddress } = require('./util')
const { getCurrentKeyring, ifContractAcc, valuesFor, toChecksumAddress, ifLooseAcc, ifRSK, ifETC } = require('./util')
const Identicon = require('./components/identicon')
const EthBalance = require('./components/eth-balance')
const TransactionList = require('./components/transaction-list')
@ -14,10 +14,10 @@ const TabBar = require('./components/tab-bar')
const TokenList = require('./components/token-list')
const AccountDropdowns = require('./components/account-dropdowns/account-dropdowns.component').AccountDropdowns
const CopyButton = require('./components/copy/copy-button')
const ToastComponent = require('./components/toast')
import * as Toast from './components/toast'
import { getMetaMaskAccounts } from '../../ui/app/selectors'
module.exports = connect(mapStateToProps)(AccountDetailScreen)
module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountDetailScreen)
function mapStateToProps (state) {
const accounts = getMetaMaskAccounts(state)
@ -42,11 +42,66 @@ function mapStateToProps (state) {
}
}
function mapDispatchToProps (dispatch) {
return {
actions: {
showSendPage: () => dispatch(actions.showSendPage()),
showSendContractPage: ({ methodSelected, methodABI, inputValues }) => dispatch(actions.showSendContractPage({methodSelected, methodABI, inputValues})),
buyEthView: (selected) => dispatch(actions.buyEthView(selected)),
viewPendingTx: (txId) => dispatch(actions.viewPendingTx(txId)),
setAccountLabel: (account, label) => dispatch(actions.setAccountLabel(account, label)),
showRemoveTokenPage: (token) => dispatch(actions.showRemoveTokenPage(token)),
showAddSuggestedTokenPage: () => dispatch(actions.showAddSuggestedTokenPage()),
showAddTokenPage: () => dispatch(actions.showAddTokenPage()),
setCurrentAccountTab: (key) => dispatch(actions.setCurrentAccountTab(key)),
displayToast: (msg) => dispatch(actions.displayToast(msg)),
isCreatedWithCorrectDPath: () => dispatch(actions.isCreatedWithCorrectDPath()),
},
}
}
inherits(AccountDetailScreen, Component)
function AccountDetailScreen () {
Component.call(this)
}
AccountDetailScreen.prototype.componentDidMount = function () {
const props = this.props
const { address, network, keyrings, identities } = props
props.actions.isCreatedWithCorrectDPath()
.then(isCreatedWithCorrectDPath => {
if (!isCreatedWithCorrectDPath) {
const currentKeyring = getCurrentKeyring(address, network, keyrings, identities)
if (!ifLooseAcc(currentKeyring) && !ifContractAcc(currentKeyring) && (ifRSK(network) || ifETC(network))) {
props.actions.displayToast(Toast.ERROR_ON_INCORRECT_DPATH)
}
}
})
}
AccountDetailScreen.prototype.componentWillUpdate = function (nextProps) {
const {
network: oldNet,
} = this.props
const {
network: newNet,
} = nextProps
if (oldNet !== newNet) {
const props = this.props
const { address, keyrings, identities } = props
props.actions.isCreatedWithCorrectDPath()
.then(isCreatedWithCorrectDPath => {
if (!isCreatedWithCorrectDPath) {
const currentKeyring = getCurrentKeyring(address, newNet, keyrings, identities)
if (!ifLooseAcc(currentKeyring) && !ifContractAcc(currentKeyring) && (ifRSK(newNet) || ifETC(newNet))) {
props.actions.displayToast(Toast.ERROR_ON_INCORRECT_DPATH)
}
}
})
}
}
AccountDetailScreen.prototype.render = function () {
const props = this.props
const { network, conversionRate, currentCurrency } = props
@ -56,7 +111,7 @@ AccountDetailScreen.prototype.render = function () {
const account = props.accounts[selected]
if (Object.keys(props.suggestedTokens).length > 0) {
this.props.dispatch(actions.showAddSuggestedTokenPage())
this.props.actions.showAddSuggestedTokenPage()
}
const currentKeyring = getCurrentKeyring(props.address, network, props.keyrings, props.identities)
@ -65,8 +120,9 @@ AccountDetailScreen.prototype.render = function () {
h('.account-detail-section.full-flex-height', [
h(ToastComponent, {
isSuccess: false,
h(Toast.ToastComponent, {
type: Toast.TOAST_TYPE_ERROR,
hideManually: true,
}),
// identicon, label, balance, etc
@ -108,7 +164,7 @@ AccountDetailScreen.prototype.render = function () {
isEditingLabel: false,
},
saveText: (text) => {
props.dispatch(actions.setAccountLabel(selected, text))
props.actions.setAccountLabel(selected, text)
},
}, [
@ -223,16 +279,16 @@ AccountDetailScreen.prototype.render = function () {
h('.flex-grow'),
!ifContractAcc(currentKeyring) ? h('button', {
onClick: () => props.dispatch(actions.buyEthView(selected)),
onClick: () => props.actions.buyEthView(selected),
style: { marginRight: '10px' },
}, 'Buy') : null,
h('button', {
onClick: () => {
if (ifContractAcc(currentKeyring)) {
return props.dispatch(actions.showSendContractPage({}))
return props.actions.showSendContractPage({})
} else {
return props.dispatch(actions.showSendPage())
return props.actions.showSendPage()
}
},
}, ifContractAcc(currentKeyring) ? 'Execute methods' : 'Send'),
@ -278,7 +334,7 @@ AccountDetailScreen.prototype.tabSections = function () {
],
defaultTab: currentAccountTab || 'history',
tabSelected: (key) => {
this.props.dispatch(actions.setCurrentAccountTab(key))
this.props.actions.setCurrentAccountTab(key)
},
}),
@ -297,8 +353,8 @@ AccountDetailScreen.prototype.tabSwitchView = function () {
userAddress: address,
network,
tokens,
addToken: () => this.props.dispatch(actions.showAddTokenPage()),
removeToken: (token) => this.props.dispatch(actions.showRemoveTokenPage(token)),
addToken: () => this.props.actions.showAddTokenPage(),
removeToken: (token) => this.props.actions.showRemoveTokenPage(token),
})
default:
return this.transactionList()
@ -317,7 +373,7 @@ AccountDetailScreen.prototype.transactionList = function () {
address,
shapeShiftTxList,
viewPendingTx: (txId) => {
this.props.dispatch(actions.viewPendingTx(txId))
this.props.actions.viewPendingTx(txId)
},
})
}

View File

@ -4,10 +4,11 @@ import actions from '../../../../ui/app/actions'
import { connect } from 'react-redux'
import { DropdownMenuItem } from '../dropdown'
import Identicon from '../identicon'
import { ifLooseAcc, ifContractAcc, ifHardwareAcc } from '../../util'
import { ifLooseAcc, ifContractAcc, ifHardwareAcc, ifRSK, ifETC } from '../../util'
import { getHdPaths, isLedger } from '../connect-hardware/util'
import { LEDGER } from '../connect-hardware/enum'
import { importTypes, labels } from '../../accounts/import/enums'
import { ERROR_ON_INCORRECT_DPATH } from '../toast'
class AccountsDropdownItemView extends Component {
static propTypes = {
@ -100,7 +101,7 @@ class AccountsDropdownItemView extends Component {
}
}
ifProxyAcc (address, setProxy) {
ifProxyAcc (address, _setProxy) {
return new Promise((resolve, reject) => {
this.props.actions.getContract(address)
.then(contractProps => {
@ -138,6 +139,15 @@ class AccountsDropdownItemView extends Component {
} else {
this.preventToast()
}
} else if (!ifLooseAcc(keyring) && !ifContractAcc(keyring) && (ifRSK(this.props.network) || ifETC(this.props.network))) {
this.props.actions.isCreatedWithCorrectDPath()
.then(isCreatedWithCorrectDPath => {
if (isCreatedWithCorrectDPath) {
this.preventToast()
} else {
this.props.actions.displayToast(ERROR_ON_INCORRECT_DPATH)
}
})
} else {
this.preventToast()
}
@ -152,6 +162,13 @@ class AccountsDropdownItemView extends Component {
}
}
function mapStateToProps (state) {
const result = {
network: state.metamask.network,
}
return result
}
const mapDispatchToProps = (dispatch) => {
return {
@ -163,10 +180,11 @@ const mapDispatchToProps = (dispatch) => {
return dispatch(actions.connectHardwareAndUnlockAddress(deviceName, hdPath, address))
},
displayToast: (msg) => dispatch(actions.displayToast(msg)),
isCreatedWithCorrectDPath: () => dispatch(actions.isCreatedWithCorrectDPath()),
},
}
}
module.exports = {
AccountsDropdownItemView: connect(null, mapDispatchToProps)(AccountsDropdownItemView),
AccountsDropdownItemView: connect(mapStateToProps, mapDispatchToProps)(AccountsDropdownItemView),
}

View File

@ -1,8 +1,8 @@
const Component = require('react').Component
import { Component } from 'react'
const h = require('react-hyperscript')
const inherits = require('util').inherits
const ethUtil = require('ethereumjs-util')
const extend = require('xtend')
import ethUtil from 'ethereumjs-util'
import extend from 'xtend'
module.exports = BinaryRenderer
@ -14,7 +14,7 @@ function BinaryRenderer () {
BinaryRenderer.prototype.render = function () {
const props = this.props
const { value, style } = props
const text = this.hexToText(value)
const message = this.msgHexToText(value)
const defaultStyle = extend({
width: '100%',
@ -30,16 +30,16 @@ BinaryRenderer.prototype.render = function () {
h('textarea.font-small', {
readOnly: true,
style: defaultStyle,
defaultValue: text,
defaultValue: message,
})
)
}
BinaryRenderer.prototype.hexToText = function (hex) {
BinaryRenderer.prototype.msgHexToText = (hex) => {
try {
const stripped = ethUtil.stripHexPrefix(hex)
const buff = Buffer.from(stripped, 'hex')
return buff.toString('utf8')
return buff.length === 32 ? hex : buff.toString('utf8')
} catch (e) {
return hex
}

View File

@ -74,7 +74,7 @@ class ConnectScreen extends Component {
<Button
type="primary"
large={true}
onClick={() => global.platform.openWindow({
onClick={() => global.platform.openTab({
url: 'https://google.com/chrome',
})}
>Download Google Chrome</Button>

View File

@ -5,7 +5,7 @@ import PropTypes from 'prop-types'
import clone from 'clone'
import log from 'loglevel'
const ethUtil = require('ethereumjs-util')
import ethUtil from 'ethereumjs-util'
const BN = ethUtil.BN
const hexToBn = require('../../../app/scripts/lib/hex-to-bn')
const util = require('../util')
@ -25,7 +25,7 @@ const { tokenInfoGetter, calcTokenAmount } = require('../../../ui/app/token-util
import BigNumber from 'bignumber.js'
import ethNetProps from 'eth-net-props'
import { getMetaMaskAccounts } from '../../../ui/app/selectors'
import ToastComponent from './toast'
import * as Toast from './toast'
const MIN_GAS_PRICE_BN = new BN('0')
const MIN_GAS_LIMIT_BN = new BN('21000')
@ -47,12 +47,12 @@ class PendingTx extends Component {
isUnlocked: PropTypes.bool,
currentCurrency: PropTypes.string,
conversionRate: PropTypes.number,
unconfTxListLength: PropTypes.number,
provider: PropTypes.object,
index: PropTypes.number,
blockGasLimit: PropTypes.string,
tokensToSend: PropTypes.objectOf(BigNumber),
tokensTransferTo: PropTypes.string,
unapprovedTxs: PropTypes.object,
}
constructor (opts = {}) {
@ -132,12 +132,13 @@ class PendingTx extends Component {
const dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0
const { totalTx, positionOfCurrentTx, nextTxId, prevTxId, showNavigation } = this.getNavigateTxData()
const balanceBn = hexToBn(balance)
const insufficientBalance = balanceBn.lt(maxCost)
const dangerousGasLimit = gasBn.gte(saferGasLimitBN)
const gasLimitSpecified = txMeta.gasLimitSpecified
const buyDisabled = insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting
const showRejectAll = props.unconfTxListLength > 1
const isNotification = getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION
@ -157,15 +158,15 @@ class PendingTx extends Component {
fontSize: '14px',
}
const isError = txMeta.simulationFails || !isValidAddress || insufficientBalance || (dangerousGasLimit && !gasLimitSpecified)
const isError = txMeta.simulationFails || !isValidAddress || insufficientBalance || (dangerousGasLimit && !gasLimitSpecified)
return (
h('div', {
key: txMeta.id,
}, [
h(ToastComponent, {
isSuccess: false,
h(Toast.ToastComponent, {
type: Toast.TOAST_TYPE_ERROR,
}),
h('form#pending-tx-form', {
@ -179,7 +180,7 @@ class PendingTx extends Component {
h('.flex-row.flex-center', {
style: {
maxWidth: '100%',
padding: showRejectAll ? '20px 20px 50px 20px' : '20px 20px 20px 20px',
padding: showNavigation ? '20px 20px 50px 20px' : '20px 20px 20px 20px',
background: 'linear-gradient(rgb(84, 36, 147), rgb(104, 45, 182))',
position: 'relative',
},
@ -197,22 +198,22 @@ class PendingTx extends Component {
h('h3', {
style: {
alignSelf: 'center',
display: props.unconfTxListLength > 1 ? 'block' : 'none',
display: showNavigation ? 'block' : 'none',
fontSize: '14px',
},
}, [
h('i.fa.white-arrow-left.fa-lg.cursor-pointer', {
style: {
display: props.index === 0 ? 'none' : 'inline-block',
display: positionOfCurrentTx === 1 ? 'none' : 'inline-block',
},
onClick: () => props.actions.previousTx(),
onClick: () => props.actions.nextTx(prevTxId),
}),
` ${props.index + 1} of ${props.unconfTxListLength} `,
` ${positionOfCurrentTx} of ${totalTx} `,
h('i.fa.white-arrow-right.fa-lg.cursor-pointer', {
style: {
display: props.index + 1 === props.unconfTxListLength ? 'none' : 'inline-block',
display: positionOfCurrentTx === totalTx ? 'none' : 'inline-block',
},
onClick: () => props.actions.nextTx(),
onClick: () => props.actions.nextTx(nextTxId),
}),
])],
),
@ -519,7 +520,7 @@ class PendingTx extends Component {
onClick: props.cancelTransaction,
}, 'Reject'),
]),
showRejectAll ? h('.flex-row.flex-space-around.conf-buttons', {
showNavigation ? h('.flex-row.flex-space-around.conf-buttons', {
style: {
display: 'flex',
justifyContent: 'flex-end',
@ -731,6 +732,23 @@ class PendingTx extends Component {
}
}
getNavigateTxData () {
const { unapprovedTxs, network, txData: { id } = {} } = this.props
const currentNetworkUnapprovedTxs = Object.keys(unapprovedTxs)
.filter((key) => unapprovedTxs[key].metamaskNetworkId === network)
.reduce((acc, key) => ({ ...acc, [key]: unapprovedTxs[key] }), {})
const enumUnapprovedTxs = Object.keys(currentNetworkUnapprovedTxs)
const currentPosition = enumUnapprovedTxs.indexOf(id ? id.toString() : '')
return {
totalTx: enumUnapprovedTxs.length,
positionOfCurrentTx: currentPosition + 1,
nextTxId: enumUnapprovedTxs[currentPosition + 1],
prevTxId: enumUnapprovedTxs[currentPosition - 1],
showNavigation: enumUnapprovedTxs.length > 1,
}
}
}
function forwardCarrat () {
@ -755,7 +773,7 @@ function mapStateToProps (state) {
unapprovedMsgs: state.metamask.unapprovedMsgs,
unapprovedPersonalMsgs: state.metamask.unapprovedPersonalMsgs,
unapprovedTypedMessages: state.metamask.unapprovedTypedMessages,
index: state.appState.currentView.pendingTxIndex || 0,
index: state.appState.currentView.key || 0,
warning: state.appState.warning,
network: state.metamask.network,
provider: state.metamask.provider,
@ -764,14 +782,14 @@ function mapStateToProps (state) {
currentCurrency: state.metamask.currentCurrency,
blockGasLimit: state.metamask.currentBlockGasLimit,
computedBalances: state.metamask.computedBalances,
pendingTxIndex: state.appState.currentView.pendingTxIndex || 0,
}
}
const mapDispatchToProps = (dispatch) => {
return {
actions: {
previousTx: () => dispatch(actions.previousTx()),
nextTx: () => dispatch(actions.nextTx()),
nextTx: (txId) => dispatch(actions.nextTx(txId)),
displayWarning: (msg) => dispatch(actions.displayWarning(msg)),
goHome: () => dispatch(actions.goHome()),
},

View File

@ -5,7 +5,7 @@ import PersistentForm from '../../../lib/persistent-form'
import SendProfile from './send-profile'
import SendHeader from './send-header'
import ErrorComponent from '../error'
import ToastComponent from '../toast'
import * as Toast from '../toast'
import Select from 'react-select'
import actions from '../../../../ui/app/actions'
import abi from 'web3-eth-abi'
@ -154,7 +154,7 @@ class SendTransactionScreen extends PersistentForm {
<SendProfile />
<SendHeader title="Execute Method" />
<ErrorComponent error={error} />
<ToastComponent isSuccess={true} />
<Toast.ToastComponent type={Toast.TOAST_TYPE_SUCCESS} />
<div style={{ padding: '0 30px' }}>
<Select
clearable={false}

View File

@ -1,23 +1,220 @@
const inherits = require('util').inherits
const PersistentForm = require('../../../lib/persistent-form')
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('../../../../ui/app/actions')
const {
import React from 'react'
import PersistentForm from '../../../lib/persistent-form'
import { connect } from 'react-redux'
import actions from '../../../../ui/app/actions'
import {
numericBalance,
isHex,
normalizeEthStringToWei,
isInvalidChecksumAddress,
isValidAddress,
} = require('../../util')
const EnsInput = require('../ens-input')
const ethUtil = require('ethereumjs-util')
} from '../../util'
import EnsInput from '../ens-input'
import ethUtil from 'ethereumjs-util'
import SendProfile from './send-profile'
import SendHeader from './send-header'
import ErrorComponent from '../error'
import { getMetaMaskAccounts } from '../../../../ui/app/selectors'
import ToastComponent from '../toast'
module.exports = connect(mapStateToProps)(SendTransactionScreen)
import * as Toast from '../toast'
const optionalDataLabelStyle = {
background: '#ffffff',
color: '#333333',
marginTop: '16px',
marginBottom: '16px',
}
const optionalDataValueStyle = {
width: '100%',
resize: 'none',
}
class SendTransactionScreen extends PersistentForm {
render () {
this.persistentFormParentId = 'send-tx-form'
const props = this.props
const {
network,
identities,
addressBook,
error,
} = props
return (
<div className="send-screen flex-column flex-grow">
<Toast.ToastComponent type={Toast.TOAST_TYPE_ERROR} />
<SendProfile/>
<SendHeader
title= "Send Transaction"
/>
<ErrorComponent
error={error}
/>
<section className="flex-row flex-center">
<EnsInput
name="address"
placeholder="Recipient Address"
onChange={this.recipientDidChange.bind(this)}
network={network}
identities={identities}
addressBook={addressBook}
/>
</section>
<section className="flex-row flex-center">
<input className="large-input"
name= "amount"
placeholder= "Amount"
type= "number"
style= {{
marginRight: '6px',
}}
dataset={{
persistentFormid: 'tx-amount',
}}
/>
<button
onClick={this.onSubmit.bind(this)}>
Next
</button>
</section>
<h3 className="flex-center"
style={optionalDataLabelStyle}
>
Transaction Data (optional)
</h3>
<section className="flex-column flex-center">
<input className="large-input"
name= "txData"
placeholder= "e.g. 0x01234"
style={optionalDataValueStyle}
dataset={{
persistentFormid: 'tx-data',
}}
/>
</section>
<h3 className="flex-center"
style={optionalDataLabelStyle}
>
Custom nonce (optional)
</h3>
<section className="flex-column flex-center">
<input className="large-input"
name= "txCustomNonce"
type= "number"
placeholder= "e.g. 42"
style={optionalDataValueStyle}
dataset={{
persistentFormid: 'tx-custom-nonce',
}}
/>
</section>
</div>
)
}
componentWillUnmount () {
this.props.dispatch(actions.displayWarning(''))
}
navigateToAccounts (event) {
event.stopPropagation()
this.props.dispatch(actions.showAccountsPage())
}
recipientDidChange (recipient, nickname) {
this.setState({
recipient: recipient,
nickname: nickname,
})
}
onSubmit () {
const state = this.state || {}
let recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '')
let nickname = state.nickname || ' '
if (typeof recipient === 'object') {
if (recipient.toAddress) {
recipient = recipient.toAddress
}
if (recipient.nickname) {
nickname = recipient.nickname
}
}
const input = document.querySelector('input[name="amount"]').value
const parts = input.split('.')
let message
if (isNaN(input) || input === '') {
message = 'Invalid ether value.'
return this.props.dispatch(actions.displayWarning(message))
}
if (parts[1]) {
const decimal = parts[1]
if (decimal.length > 18) {
message = 'Ether amount is too precise.'
return this.props.dispatch(actions.displayWarning(message))
}
}
const value = normalizeEthStringToWei(input)
const txData = document.querySelector('input[name="txData"]').value
const txCustomNonce = document.querySelector('input[name="txCustomNonce"]').value
const balance = this.props.balance
if (value.gt(balance)) {
message = 'Insufficient funds.'
return this.props.dispatch(actions.displayWarning(message))
}
if (input < 0) {
message = 'Can not send negative amounts of ETH.'
return this.props.dispatch(actions.displayWarning(message))
}
if ((isInvalidChecksumAddress(recipient, this.props.network))) {
message = 'Recipient address checksum is invalid.'
return this.props.dispatch(actions.displayWarning(message))
}
if ((!isValidAddress(recipient, this.props.network) && !txData) || (!recipient && !txData)) {
message = 'Recipient address is invalid.'
return this.props.dispatch(actions.displayWarning(message))
}
if (!isHex(ethUtil.stripHexPrefix(txData)) && txData) {
message = 'Transaction data must be hex string.'
return this.props.dispatch(actions.displayWarning(message))
}
this.props.dispatch(actions.hideWarning())
this.props.dispatch(actions.addToAddressBook(recipient, nickname))
const txParams = {
from: this.props.address,
value: '0x' + value.toString(16),
}
if (recipient) txParams.to = ethUtil.addHexPrefix(recipient)
if (txData) txParams.data = txData
if (txCustomNonce) txParams.nonce = '0x' + parseInt(txCustomNonce, 10).toString(16)
this.props.dispatch(actions.signTx(txParams))
}
}
function mapStateToProps (state) {
const accounts = getMetaMaskAccounts(state)
@ -37,200 +234,4 @@ function mapStateToProps (state) {
return result
}
inherits(SendTransactionScreen, PersistentForm)
function SendTransactionScreen () {
PersistentForm.call(this)
}
SendTransactionScreen.prototype.render = function () {
this.persistentFormParentId = 'send-tx-form'
const props = this.props
const {
network,
identities,
addressBook,
error,
} = props
return (
h('.send-screen.flex-column.flex-grow', [
h(ToastComponent, {
isSuccess: false,
}),
//
// Sender Profile
//
h(SendProfile),
//
// Send Header
//
h(SendHeader, {
title: 'Send Transaction',
}),
// error message
h(ErrorComponent, {
error,
}),
// 'to' field
h('section.flex-row.flex-center', [
h(EnsInput, {
name: 'address',
placeholder: 'Recipient Address',
onChange: this.recipientDidChange.bind(this),
network,
identities,
addressBook,
}),
]),
// 'amount' and send button
h('section.flex-row.flex-center', [
h('input.large-input', {
name: 'amount',
placeholder: 'Amount',
type: 'number',
style: {
marginRight: '6px',
},
dataset: {
persistentFormid: 'tx-amount',
},
}),
h('button', {
onClick: this.onSubmit.bind(this),
}, 'Next'),
]),
//
// Optional Fields
//
h('h3.flex-center', {
style: {
background: '#ffffff',
color: '#333333',
marginTop: '16px',
marginBottom: '16px',
},
}, [
'Transaction Data (optional)',
]),
// 'data' field
h('section.flex-column.flex-center', [
h('input.large-input', {
name: 'txData',
placeholder: '0x01234',
style: {
width: '100%',
resize: 'none',
},
dataset: {
persistentFormid: 'tx-data',
},
}),
]),
])
)
}
SendTransactionScreen.prototype.componentWillUnmount = function () {
this.props.dispatch(actions.displayWarning(''))
}
SendTransactionScreen.prototype.navigateToAccounts = function (event) {
event.stopPropagation()
this.props.dispatch(actions.showAccountsPage())
}
SendTransactionScreen.prototype.recipientDidChange = function (recipient, nickname) {
this.setState({
recipient: recipient,
nickname: nickname,
})
}
SendTransactionScreen.prototype.onSubmit = function () {
const state = this.state || {}
let recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '')
let nickname = state.nickname || ' '
if (typeof recipient === 'object') {
if (recipient.toAddress) {
recipient = recipient.toAddress
}
if (recipient.nickname) {
nickname = recipient.nickname
}
}
const input = document.querySelector('input[name="amount"]').value
const parts = input.split('.')
let message
if (isNaN(input) || input === '') {
message = 'Invalid ether value.'
return this.props.dispatch(actions.displayWarning(message))
}
if (parts[1]) {
const decimal = parts[1]
if (decimal.length > 18) {
message = 'Ether amount is too precise.'
return this.props.dispatch(actions.displayWarning(message))
}
}
const value = normalizeEthStringToWei(input)
const txData = document.querySelector('input[name="txData"]').value
const balance = this.props.balance
if (value.gt(balance)) {
message = 'Insufficient funds.'
return this.props.dispatch(actions.displayWarning(message))
}
if (input < 0) {
message = 'Can not send negative amounts of ETH.'
return this.props.dispatch(actions.displayWarning(message))
}
if ((isInvalidChecksumAddress(recipient, this.props.network))) {
message = 'Recipient address checksum is invalid.'
return this.props.dispatch(actions.displayWarning(message))
}
if ((!isValidAddress(recipient, this.props.network) && !txData) || (!recipient && !txData)) {
message = 'Recipient address is invalid.'
return this.props.dispatch(actions.displayWarning(message))
}
if (!isHex(ethUtil.stripHexPrefix(txData)) && txData) {
message = 'Transaction data must be hex string.'
return this.props.dispatch(actions.displayWarning(message))
}
this.props.dispatch(actions.hideWarning())
this.props.dispatch(actions.addToAddressBook(recipient, nickname))
const txParams = {
from: this.props.address,
value: '0x' + value.toString(16),
}
if (recipient) txParams.to = ethUtil.addHexPrefix(recipient)
if (txData) txParams.data = txData
this.props.dispatch(actions.signTx(txParams))
}
module.exports = connect(mapStateToProps)(SendTransactionScreen)

View File

@ -212,7 +212,7 @@ ShiftListItem.prototype.renderInfo = function () {
paddingLeft: '29px',
textAlign: 'left',
},
onClick: () => global.platform.openWindow({ url }),
onClick: () => global.platform.openTab({ url }),
}, [
h('div', {
style: {

View File

@ -4,12 +4,18 @@ import { connect } from 'react-redux'
import classnames from 'classnames'
import actions from '../../../ui/app/actions'
const TOAST_TYPE_SUCCESS = 'success'
const TOAST_TYPE_ERROR = 'error'
const ERROR_ON_INCORRECT_DPATH = 'The account is derived from ETH derivation path despite you connected to another chain. If you are ready to switch to correct derivation path, just restore from the same seed phrase.'
class ToastComponent extends Component {
static propTypes = {
msg: PropTypes.string,
toastMsg: PropTypes.string,
isSuccess: PropTypes.bool,
type: PropTypes.string,
hideToast: PropTypes.func,
hideManually: PropTypes.bool,
}
constructor (props) {
@ -19,10 +25,12 @@ class ToastComponent extends Component {
componentDidUpdate (prevProps) {
if ((!prevProps.msg && this.props.msg) || (!prevProps.toastMsg && this.props.toastMsg)) {
this.timerID = setTimeout(() => {
this.props.hideToast()
clearTimeout(this.timerID)
}, 4000)
if (!this.props.hideManually) {
this.timerID = setTimeout(() => {
this.props.hideToast()
clearTimeout(this.timerID)
}, 4000)
}
}
}
@ -31,15 +39,23 @@ class ToastComponent extends Component {
clearTimeout(this.timerID)
}
_getClass (type) {
switch (type) {
case TOAST_TYPE_SUCCESS:
return 'green'
case TOAST_TYPE_ERROR:
return 'red'
default:
return 'green'
}
}
render () {
let toastMsg = this.props.msg || this.props.toastMsg
toastMsg = (toastMsg && toastMsg.message) || toastMsg
return toastMsg ? (
<div
className={classnames('toast', {
'green': this.props.isSuccess,
'red': !this.props.isSuccess,
})}
className={classnames('toast', this._getClass(this.props.type))}
onClick={(e) => this.props.hideToast()}
>{toastMsg}</div>
) : null
@ -58,4 +74,9 @@ function mapDispatchToProps (dispatch) {
}
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(ToastComponent)
module.exports = {
ToastComponent: connect(mapStateToProps, mapDispatchToProps)(ToastComponent),
TOAST_TYPE_SUCCESS,
TOAST_TYPE_ERROR,
ERROR_ON_INCORRECT_DPATH,
}

View File

@ -1,11 +1,11 @@
const inherits = require('util').inherits
const Component = require('react').Component
import PropTypes from 'prop-types'
import { Component } from 'react'
import { connect } from 'react-redux'
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('../../ui/app/actions')
const LoadingIndicator = require('./components/loading')
const txHelper = require('../lib/tx-helper')
const log = require('loglevel')
import log from 'loglevel'
const { getCurrentKeyring, ifContractAcc } = require('./util')
const PendingTx = require('./components/pending-tx')
@ -15,118 +15,224 @@ import PendingTypedMsg from './components/pending-typed-msg'
const Loading = require('./components/loading')
const { DAI_CODE, POA_SOKOL_CODE, RSK_TESTNET_CODE, GOERLI_TESTNET_CODE } = require('../../app/scripts/controllers/network/enums')
const { getMetaMaskAccounts } = require('../../ui/app/selectors')
import BigNumber from 'bignumber.js'
module.exports = connect(mapStateToProps)(ConfirmTxScreen)
function mapStateToProps (state) {
const { metamask, appState } = state
const { screenParams, pendingTxIndex } = appState.currentView
return {
identities: metamask.identities,
accounts: getMetaMaskAccounts(state),
keyrings: metamask.keyrings,
selectedAddress: metamask.selectedAddress,
unapprovedTxs: metamask.unapprovedTxs,
unapprovedMsgs: metamask.unapprovedMsgs,
unapprovedPersonalMsgs: metamask.unapprovedPersonalMsgs,
unapprovedTypedMessages: metamask.unapprovedTypedMessages,
index: pendingTxIndex || 0,
warning: appState.warning,
network: metamask.network,
provider: metamask.provider,
conversionRate: metamask.conversionRate,
currentCurrency: metamask.currentCurrency,
blockGasLimit: metamask.currentBlockGasLimit,
computedBalances: metamask.computedBalances,
isToken: (screenParams && screenParams.isToken),
tokenSymbol: (screenParams && screenParams.tokenSymbol),
tokensToSend: (screenParams && screenParams.tokensToSend),
tokensTransferTo: (screenParams && screenParams.tokensTransferTo),
isContractExecutionByUser: (screenParams && screenParams.isContractExecutionByUser),
}
}
inherits(ConfirmTxScreen, Component)
function ConfirmTxScreen () {
Component.call(this)
}
ConfirmTxScreen.prototype.render = function () {
const props = this.props
const { network, unapprovedTxs, currentCurrency, computedBalances,
unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, blockGasLimit } = props
let { conversionRate } = props
const isTestnet = parseInt(network) === POA_SOKOL_CODE || parseInt(network) === RSK_TESTNET_CODE || parseInt(network) === GOERLI_TESTNET_CODE
const isDai = parseInt(network) === DAI_CODE
if (isTestnet) {
conversionRate = 0
} else if (isDai) {
conversionRate = 1
class ConfirmTxScreen extends Component {
static propTypes = {
network: PropTypes.string,
identities: PropTypes.objectOf(PropTypes.object),
keyrings: PropTypes.array,
actions: PropTypes.objectOf(PropTypes.func),
isToken: PropTypes.bool,
isContractExecutionByUser: PropTypes.bool,
selectedAddress: PropTypes.string,
warning: PropTypes.string,
unapprovedTxs: PropTypes.object,
unapprovedMsgs: PropTypes.object,
unapprovedPersonalMsgs: PropTypes.object,
unapprovedTypedMessages: PropTypes.object,
pendingTxIndex: PropTypes.number,
blockGasLimit: PropTypes.string,
accounts: PropTypes.object,
currentCurrency: PropTypes.string,
computedBalances: PropTypes.object,
conversionRate: PropTypes.number,
tokenSymbol: PropTypes.string,
tokensToSend: PropTypes.objectOf(BigNumber),
tokensTransferTo: PropTypes.string,
}
const unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, network)
const ind = props.index || 0
const txData = unconfTxList[ind] || {}
const txParams = txData.params || {}
render () {
const props = this.props
const { network, unapprovedTxs, currentCurrency, computedBalances,
unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, blockGasLimit } = props
let { conversionRate } = props
log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`)
if (unconfTxList.length === 0) return h(Loading, { isLoading: true })
const isTestnet = parseInt(network) === POA_SOKOL_CODE || parseInt(network) === RSK_TESTNET_CODE || parseInt(network) === GOERLI_TESTNET_CODE
const isDai = parseInt(network) === DAI_CODE
if (isTestnet) {
conversionRate = 0
} else if (isDai) {
conversionRate = 1
}
const unconfTxListLength = unconfTxList.length
const unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, network)
const ind = props.pendingTxIndex || 0
const txData = unconfTxList[ind] || {}
const txParams = txData.params || {}
return (
log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`)
if (unconfTxList.length === 0) return h(Loading, { isLoading: true })
h('.flex-column.flex-grow', {
style: {
width: '100%',
},
}, [
const unconfTxListLength = unconfTxList.length
h(LoadingIndicator, {
isLoading: this.state ? !this.state.bypassLoadingScreen : txData.loadingDefaults,
loadingMessage: 'Estimating transaction cost…',
canBypass: true,
bypass: () => {
this.setState({bypassLoadingScreen: true})
return (
h('.flex-column.flex-grow', {
style: {
width: '100%',
},
}),
}, [
// subtitle and nav
h(LoadingIndicator, {
isLoading: this.state ? !this.state.bypassLoadingScreen : txData.loadingDefaults,
loadingMessage: 'Estimating transaction cost…',
canBypass: true,
bypass: () => {
this.setState({bypassLoadingScreen: true})
},
}),
warningIfExists(props.warning),
// subtitle and nav
currentTxView({
// Properties
txData: txData,
key: txData.id,
selectedAddress: props.selectedAddress,
accounts: props.accounts,
identities: props.identities,
conversionRate,
currentCurrency,
blockGasLimit,
unconfTxListLength,
computedBalances,
network,
isToken: props.isToken,
tokenSymbol: props.tokenSymbol,
tokensToSend: props.tokensToSend,
tokensTransferTo: props.tokensTransferTo,
// Actions
buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress, props.isContractExecutionByUser),
sendTransaction: this.sendTransaction.bind(this),
cancelTransaction: this.cancelTransaction.bind(this, txData),
cancelAllTransactions: this.cancelAllTransactions.bind(this, unconfTxList),
signMessage: this.signMessage.bind(this, txData),
signPersonalMessage: this.signPersonalMessage.bind(this, txData),
signTypedMessage: this.signTypedMessage.bind(this, txData),
cancelMessage: this.cancelMessage.bind(this, txData),
cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData),
cancelTypedMessage: this.cancelTypedMessage.bind(this, txData),
}),
])
)
warningIfExists(props.warning),
currentTxView({
// Properties
txData: txData,
key: txData.id,
selectedAddress: props.selectedAddress,
accounts: props.accounts,
identities: props.identities,
conversionRate,
currentCurrency,
blockGasLimit,
unconfTxListLength,
computedBalances,
network,
isToken: props.isToken,
tokenSymbol: props.tokenSymbol,
tokensToSend: props.tokensToSend,
tokensTransferTo: props.tokensTransferTo,
// Actions
buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress, props.isContractExecutionByUser),
sendTransaction: this.sendTransaction.bind(this),
cancelTransaction: this.cancelTransaction.bind(this, txData),
cancelAllTransactions: this.cancelAllTransactions.bind(this, unconfTxList),
signMessage: this.signMessage.bind(this, txData),
signPersonalMessage: this.signPersonalMessage.bind(this, txData),
signTypedMessage: this.signTypedMessage.bind(this, txData),
cancelMessage: this.cancelMessage.bind(this, txData),
cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData),
cancelTypedMessage: this.cancelTypedMessage.bind(this, txData),
}),
])
)
}
buyEth (address, isContractExecutionByUser, event) {
event.preventDefault()
this.props.actions.buyEthView(address, isContractExecutionByUser)
}
sendTransaction (txData, event) {
this.stopPropagation(event)
this.props.actions.updateAndApproveTx(txData)
this._checkIfContractExecutionAndUnlockContract(txData)
}
cancelTransaction (txData, event) {
this.stopPropagation(event)
event.preventDefault()
this.props.actions.cancelTx(txData)
this._checkIfContractExecutionAndUnlockContract(txData)
}
cancelAllTransactions (unconfTxList, event) {
this.stopPropagation(event)
event.preventDefault()
this.props.actions.cancelTxs(unconfTxList)
this._checkIfMultipleContractExecutionAndUnlockContract(unconfTxList)
}
signMessage (msgData, event) {
log.info('conf-tx.js: signing message')
const params = msgData.msgParams
params.metamaskId = msgData.id
this.stopPropagation(event)
this.props.actions.signMsg(params)
}
stopPropagation (event) {
if (event.stopPropagation) {
event.stopPropagation()
}
}
signPersonalMessage (msgData, event) {
log.info('conf-tx.js: signing personal message')
const params = msgData.msgParams
params.metamaskId = msgData.id
this.stopPropagation(event)
this.props.actions.signPersonalMsg(params)
}
signTypedMessage (msgData, event) {
log.info('conf-tx.js: signing typed message')
const params = msgData.msgParams
params.metamaskId = msgData.id
this.stopPropagation(event)
this.props.actions.signTypedMsg(params)
}
cancelMessage (msgData, event) {
log.info('canceling message')
this.stopPropagation(event)
this.props.actions.cancelMsg(msgData)
}
cancelPersonalMessage (msgData, event) {
log.info('canceling personal message')
this.stopPropagation(event)
this.props.actions.cancelPersonalMsg(msgData)
}
cancelTypedMessage (msgData, event) {
log.info('canceling typed message')
this.stopPropagation(event)
this.props.actions.cancelTypedMsg(msgData)
}
_checkIfMultipleContractExecutionAndUnlockContract (unconfTxList) {
const areTxsToOneContractFromTheList = unconfTxList.slice(0).reduce((res, txData, ind, unconfTxList) => {
if (txData.txParams.data && this.props.isContractExecutionByUser) {
const to = txData && txData.txParams && txData.txParams.to
const targetContractIsInTheList = Object.keys(this.props.accounts).some((acc) => acc === to)
if (targetContractIsInTheList && Object.keys(res).length === 0) {
res = { status: true, to }
} else if (res.status && res.to !== to) {
res = { status: false }
unconfTxList.splice(1)
}
} else {
res = { status: false }
unconfTxList.splice(1)
}
return res
}, {})
if (areTxsToOneContractFromTheList.status) {
this._unlockContract(areTxsToOneContractFromTheList.to)
}
}
_checkIfContractExecutionAndUnlockContract (txData) {
if (txData.txParams.data && this.props.isContractExecutionByUser) {
const to = txData && txData.txParams && txData.txParams.to
const targetContractIsInTheList = Object.keys(this.props.accounts).some((acc) => acc === to)
if (targetContractIsInTheList) {
this._unlockContract(to)
}
}
}
_unlockContract (to) {
const currentKeyring = getCurrentKeyring(to, this.props.network, this.props.keyrings, this.props.identities)
if (ifContractAcc(currentKeyring)) {
this.props.actions.showAccountDetail(to)
}
}
}
function currentTxView (opts) {
@ -153,119 +259,6 @@ function currentTxView (opts) {
}
}
ConfirmTxScreen.prototype.buyEth = function (address, isContractExecutionByUser, event) {
event.preventDefault()
this.props.dispatch(actions.buyEthView(address, isContractExecutionByUser))
}
ConfirmTxScreen.prototype.sendTransaction = function (txData, event) {
this.stopPropagation(event)
this.props.dispatch(actions.updateAndApproveTx(txData))
this._checkIfContractExecutionAndUnlockContract(txData)
}
ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) {
this.stopPropagation(event)
event.preventDefault()
this.props.dispatch(actions.cancelTx(txData))
this._checkIfContractExecutionAndUnlockContract(txData)
}
ConfirmTxScreen.prototype.cancelAllTransactions = function (unconfTxList, event) {
this.stopPropagation(event)
event.preventDefault()
this.props.dispatch(actions.cancelAllTx(unconfTxList))
this._checkIfMultipleContractExecutionAndUnlockContract(unconfTxList)
}
ConfirmTxScreen.prototype.signMessage = function (msgData, event) {
log.info('conf-tx.js: signing message')
const params = msgData.msgParams
params.metamaskId = msgData.id
this.stopPropagation(event)
this.props.dispatch(actions.signMsg(params))
}
ConfirmTxScreen.prototype.stopPropagation = function (event) {
if (event.stopPropagation) {
event.stopPropagation()
}
}
ConfirmTxScreen.prototype.signPersonalMessage = function (msgData, event) {
log.info('conf-tx.js: signing personal message')
const params = msgData.msgParams
params.metamaskId = msgData.id
this.stopPropagation(event)
this.props.dispatch(actions.signPersonalMsg(params))
}
ConfirmTxScreen.prototype.signTypedMessage = function (msgData, event) {
log.info('conf-tx.js: signing typed message')
const params = msgData.msgParams
params.metamaskId = msgData.id
this.stopPropagation(event)
this.props.dispatch(actions.signTypedMsg(params))
}
ConfirmTxScreen.prototype.cancelMessage = function (msgData, event) {
log.info('canceling message')
this.stopPropagation(event)
this.props.dispatch(actions.cancelMsg(msgData))
}
ConfirmTxScreen.prototype.cancelPersonalMessage = function (msgData, event) {
log.info('canceling personal message')
this.stopPropagation(event)
this.props.dispatch(actions.cancelPersonalMsg(msgData))
}
ConfirmTxScreen.prototype.cancelTypedMessage = function (msgData, event) {
log.info('canceling typed message')
this.stopPropagation(event)
this.props.dispatch(actions.cancelTypedMsg(msgData))
}
ConfirmTxScreen.prototype._checkIfMultipleContractExecutionAndUnlockContract = function (unconfTxList) {
const areTxsToOneContractFromTheList = unconfTxList.slice(0).reduce((res, txData, ind, unconfTxList) => {
if (txData.txParams.data && this.props.isContractExecutionByUser) {
const to = txData && txData.txParams && txData.txParams.to
const targetContractIsInTheList = Object.keys(this.props.accounts).some((acc) => acc === to)
if (targetContractIsInTheList && Object.keys(res).length === 0) {
res = { status: true, to }
} else if (res.status && res.to !== to) {
res = { status: false }
unconfTxList.splice(1)
}
} else {
res = { status: false }
unconfTxList.splice(1)
}
return res
}, {})
if (areTxsToOneContractFromTheList.status) {
this._unlockContract(areTxsToOneContractFromTheList.to)
}
}
ConfirmTxScreen.prototype._checkIfContractExecutionAndUnlockContract = function (txData) {
if (txData.txParams.data && this.props.isContractExecutionByUser) {
const to = txData && txData.txParams && txData.txParams.to
const targetContractIsInTheList = Object.keys(this.props.accounts).some((acc) => acc === to)
if (targetContractIsInTheList) {
this._unlockContract(to)
}
}
}
ConfirmTxScreen.prototype._unlockContract = function (to) {
const currentKeyring = getCurrentKeyring(to, this.props.network, this.props.keyrings, this.props.identities)
if (ifContractAcc(currentKeyring)) {
this.props.dispatch(actions.showAccountDetail(to))
}
}
function warningIfExists (warning) {
if (warning &&
// Do not display user rejections on this screen:
@ -277,3 +270,51 @@ function warningIfExists (warning) {
}, warning)
}
}
function mapStateToProps (state) {
const { metamask, appState } = state
const { screenParams, pendingTxIndex } = appState.currentView
return {
identities: metamask.identities,
accounts: getMetaMaskAccounts(state),
keyrings: metamask.keyrings,
selectedAddress: metamask.selectedAddress,
unapprovedTxs: metamask.unapprovedTxs,
unapprovedMsgs: metamask.unapprovedMsgs,
unapprovedPersonalMsgs: metamask.unapprovedPersonalMsgs,
unapprovedTypedMessages: metamask.unapprovedTypedMessages,
pendingTxIndex: pendingTxIndex || 0,
warning: appState.warning,
network: metamask.network,
provider: metamask.provider,
conversionRate: metamask.conversionRate,
currentCurrency: metamask.currentCurrency,
blockGasLimit: metamask.currentBlockGasLimit,
computedBalances: metamask.computedBalances,
isToken: (screenParams && screenParams.isToken),
tokenSymbol: (screenParams && screenParams.tokenSymbol),
tokensToSend: (screenParams && screenParams.tokensToSend),
tokensTransferTo: (screenParams && screenParams.tokensTransferTo),
isContractExecutionByUser: (screenParams && screenParams.isContractExecutionByUser),
}
}
function mapDispatchToProps (dispatch) {
return {
actions: {
buyEthView: (address, isContractExecutionByUser) => dispatch(actions.buyEthView(address, isContractExecutionByUser)),
updateAndApproveTx: (txData) => dispatch(actions.updateAndApproveTx(txData)),
cancelTx: (txData) => dispatch(actions.cancelTx(txData)),
cancelTxs: (unconfTxList) => dispatch(actions.cancelTxs(unconfTxList)),
signMsg: (params) => dispatch(actions.signMsg(params)),
signPersonalMsg: (params) => dispatch(actions.signPersonalMsg(params)),
signTypedMsg: (params) => dispatch(actions.signTypedMsg(params)),
cancelMsg: (msgData) => dispatch(actions.cancelMsg(msgData)),
cancelPersonalMsg: (msgData) => dispatch(actions.cancelPersonalMsg(msgData)),
cancelTypedMsg: (msgData) => dispatch(actions.cancelTypedMsg(msgData)),
showAccountDetail: (to) => dispatch(actions.showAccountDetail(to)),
},
}
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(ConfirmTxScreen)

View File

@ -293,7 +293,7 @@ app sections
color: #ffffff !important;
font-size: 12px;
text-align: center;
padding: 10px;
padding: 20px;
width: 357px;
line-height: 14px;
position: fixed;

View File

@ -4,6 +4,7 @@ const Component = require('react').Component
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const actions = require('../../../../../ui/app/actions')
const { getDPath } = require('../../../util')
module.exports = connect(mapStateToProps)(RevealSeedConfirmation)
@ -16,6 +17,7 @@ function mapStateToProps (state) {
return {
warning: state.appState.warning,
dPath: state.metamask.dPath,
provider: state.metamask.provider,
}
}
@ -115,7 +117,9 @@ RevealSeedConfirmation.prototype.checkConfirmation = function (event) {
}
}
RevealSeedConfirmation.prototype.revealSeedWords = function () {
RevealSeedConfirmation.prototype.revealSeedWords = async function () {
const password = document.getElementById('password-box').value
this.props.dispatch(actions.requestRevealSeed(password, this.props.dPath))
const isCreatedWithCorrectDPath = this.props.dispatch(actions.isCreatedWithCorrectDPath())
const dPath = getDPath(this.props.provider.type, isCreatedWithCorrectDPath)
this.props.dispatch(actions.requestRevealSeed(password, dPath))
}

View File

@ -3,6 +3,7 @@ const PersistentForm = require('../../../lib/persistent-form')
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const actions = require('../../../../ui/app/actions')
const { getDPath } = require('../../util')
module.exports = connect(mapStateToProps)(RestoreVaultScreen)
@ -15,6 +16,7 @@ function mapStateToProps (state) {
return {
warning: state.appState.warning,
forgottenPassword: state.appState.forgottenPassword,
provider: state.metamask.provider,
}
}
@ -185,5 +187,7 @@ RestoreVaultScreen.prototype.createNewVaultAndRestore = function () {
// submit
this.warning = null
this.props.dispatch(actions.displayWarning(this.warning))
this.props.dispatch(actions.createNewVaultAndRestore(password, seed))
const isCreatedWithCorrectDPath = true
const dPath = getDPath(this.props.provider.type, isCreatedWithCorrectDPath)
this.props.dispatch(actions.createNewVaultAndRestore(password, seed, dPath))
}

View File

@ -111,11 +111,13 @@ UnlockScreen.prototype.onKeyPress = function (event) {
UnlockScreen.prototype.submitPassword = async function (event) {
const element = event.target
const password = element.value
const props = this.props
// reset input
element.value = ''
try {
const dPath = getDPath(this.props.provider.type) || this.props.dPath
await this.props.dispatch(actions.tryUnlockMetamask(password, dPath))
const isCreatedWithCorrectDPath = await props.dispatch(actions.isCreatedWithCorrectDPath())
const dPath = getDPath(props.provider.type, isCreatedWithCorrectDPath) || props.dPath
await props.dispatch(actions.tryUnlockMetamask(password, dPath))
} catch (e) {
log.error(e)
}

View File

@ -39,7 +39,7 @@ const {
RSK,
RSK_TESTNET,
RSK_TICK,
// customDPaths,
customDPaths,
} = require('../../app/scripts/controllers/network/enums')
const valueTable = {
@ -88,6 +88,7 @@ module.exports = {
ifHardwareAcc,
getAllKeyRingsAccounts,
ifRSK,
ifETC,
ifRSKByProviderType,
ifPOA,
toChecksumAddress,
@ -432,6 +433,12 @@ function ifRSK (network) {
return numericNet === RSK_CODE || numericNet === RSK_TESTNET_CODE
}
function ifETC (network) {
if (!network) return false
const numericNet = isNaN(network) ? network : parseInt(network)
return numericNet === CLASSIC_CODE
}
function ifRSKByProviderType (type) {
if (!type) return false
return type === RSK || type === RSK_TESTNET
@ -557,14 +564,16 @@ function getNetworkID ({ network }) {
}
}
function getDPath (network) {
// todo: return when the robust solution will be ready
return `m/44'/60'/0'/0`
// return customDPaths[network] || `m/44'/60'/0'/0`
function getDPath (networkType, isCreatedWithCorrectDPath) {
if (isCreatedWithCorrectDPath) {
return customDPaths[networkType] || `m/44'/60'/0'/0`
} else {
return `m/44'/60'/0'/0`
}
}
function setDPath (keyring, network) {
const dPath = getDPath(network)
function setDPath (keyring, networkType, isCreatedWithCorrectDPath) {
const dPath = getDPath(networkType, isCreatedWithCorrectDPath)
if (dPath && keyring.setHdPath) {
keyring.setHdPath(dPath)
}

View File

@ -328,6 +328,6 @@
},
"engines": {
"node": "10.19.0",
"npm": "^6.13.4"
"npm": "^6.14.5"
}
}

View File

@ -298,7 +298,7 @@ module.exports = {
accountName: By.className('font-medium color-forest'),
edit: By.className('edit-text'),
iconCopy: By.className('clipboard cursor-pointer white'),
transactionList: By.css('#app-content > div > div.app-primary.from-left > div > section > section > div > div > div > div.ether-balance.ether-balance-amount > div > div > div > div:nth-child(1)'),
transactionList: By.css('#app-content > div > div.app-primary.from-left > div > section > section > div > div > div > div.ether-balance.ether-balance-amount > div > div.flex-column > div > div:nth-child(1)'),
buttons: {
send: By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > button:nth-child(4)'),
buy: By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > button:nth-child(3)'),
@ -309,7 +309,7 @@ module.exports = {
},
network: By.className('network-name'),
sent: {
menu: By.className('wallet-view__tab-history'),
menu: By.css('#wallet-view__tab-history'),
tokens: By.className('activeForm right'),
},
// balance: By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > div.ether-balance.ether-balance-amount > div > div > div:nth-child(1) > div:nth-child(1)'),

View File

@ -1,17 +1,15 @@
const path = require('path')
const Func = require('./func').Functions
const account1 = '0x2E428ABd9313D256d64D1f69fe3929C3BE18fD1f'
// todo:
// const account1RSK = '0x7a9bc05F7441d862d1B83CB724861a9872FF43fe'
const account1RSK = '0x2E428aBd9313D256d64D1f69fe3929c3Be18Fd1F'
const account1RSK = '0x7a9bc05F7441d862d1B83CB724861a9872FF43fe'
const account2 = '0xd7b7AFeCa35e32594e29504771aC847E2a803742'
const testsFolder = './test-cases'
const setup = require(`${testsFolder}/setup.spec`)
const login = require(`${testsFolder}/login.spec`)
const { accountCreation } = require(`${testsFolder}/account-creation.spec`)
const { accountCreation, getCreatedAccounts } = require(`${testsFolder}/account-creation.spec`)
const connectHDWallet = require(`${testsFolder}/connect-hd-wallet.spec`)
const importAccount = require(`${testsFolder}/import-account.spec`)
// const importContractAccount = require(`${testsFolder}/import-contract-account.spec`)
const importContractAccount = require(`${testsFolder}/import-contract-account.spec`)
const deleteImportedAccount = require(`${testsFolder}/delete-imported-account.spec`)
const signData = require(`${testsFolder}/sign-data.spec`)
const exportPrivateKey = require(`${testsFolder}/export-private-key.spec`)
@ -100,9 +98,9 @@ describe('Metamask popup page', async function () {
await importAccount(f)
})
// describe('Import Contract account', async () => {
// await importContractAccount(f, account1, getCreatedAccounts)
// })
describe('Import Contract account', async () => {
await importContractAccount(f, account1, getCreatedAccounts)
})
describe('Delete Imported Account', async () => {
await deleteImportedAccount(f)

View File

@ -15,7 +15,7 @@ const deleteImportedAccount = async (f) => {
})
it("Can't remove imported account with 'No' button", async function () {
const button = await f.waitUntilShowUp(deleteImportedAccountScr.buttons.no)
const button = await f.waitUntilShowUp(deleteImportedAccountScr.buttons.no2)
assert.equal(await button.getText(), 'No', 'button has incorrect name')
await f.click(button)
await f.driver.findElements(main.container)

View File

@ -60,6 +60,8 @@ const importGanacheSeedPhrase = async (f, account2, password) => {
})
it('finds the transaction in the transactions list', async () => {
const sentTab = await f.waitUntilShowUp(screens.main.sent.menu)
await sentTab.click()
const transactionAmount = await f.waitUntilShowUp(screens.main.transactionList)
assert.equal(await transactionAmount.getText(), '10.0')
})

View File

@ -443,7 +443,7 @@ describe('preferences controller', function () {
req.params.options = { address, symbol, decimals, image }
sandbox.stub(preferencesController, '_validateERC20AssetParams').returns(true)
preferencesController.showWatchAssetUi = async () => {}
preferencesController.openPopup = async () => {}
await preferencesController._handleWatchAssetERC20(req.params.options)
const suggested = preferencesController.getSuggestedTokens()
@ -463,7 +463,7 @@ describe('preferences controller', function () {
req.params.options = { address, symbol, decimals, image }
sandbox.stub(preferencesController, '_validateERC20AssetParams').returns(true)
preferencesController.showWatchAssetUi = async () => {
preferencesController.openPopup = async () => {
await preferencesController.addToken(address, symbol, decimals, image)
}

View File

@ -345,7 +345,6 @@ describe('Transaction Controller', function () {
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
gasPrice: '0x77359400',
gas: '0x7b0d',
nonce: '0x4b',
},
metamaskNetworkId: currentNetworkId,
}

View File

@ -1,101 +0,0 @@
const assert = require('assert')
const EdgeEncryptor = require('../../../app/scripts/edge-encryptor')
var password = 'passw0rd1'
var data = 'some random data'
global.crypto = global.crypto || {
getRandomValues: function (array) {
for (let i = 0; i < array.length; i++) {
array[i] = Math.random() * 100
}
return array
},
}
describe('EdgeEncryptor', function () {
const edgeEncryptor = new EdgeEncryptor()
describe('encrypt', function () {
it('should encrypt the data.', function (done) {
edgeEncryptor.encrypt(password, data)
.then(function (encryptedData) {
assert.notEqual(data, encryptedData)
assert.notEqual(encryptedData.length, 0)
done()
}).catch(function (err) {
done(err)
})
})
it('should return proper format.', function (done) {
edgeEncryptor.encrypt(password, data)
.then(function (encryptedData) {
const encryptedObject = JSON.parse(encryptedData)
assert.ok(encryptedObject.data, 'there is no data')
assert.ok(encryptedObject.iv && encryptedObject.iv.length !== 0, 'there is no iv')
assert.ok(encryptedObject.salt && encryptedObject.salt.length !== 0, 'there is no salt')
done()
}).catch(function (err) {
done(err)
})
})
it('should not return the same twice.', function (done) {
const encryptPromises = []
encryptPromises.push(edgeEncryptor.encrypt(password, data))
encryptPromises.push(edgeEncryptor.encrypt(password, data))
Promise.all(encryptPromises).then((encryptedData) => {
assert.equal(encryptedData.length, 2)
assert.notEqual(encryptedData[0], encryptedData[1])
assert.notEqual(encryptedData[0].length, 0)
assert.notEqual(encryptedData[1].length, 0)
done()
})
})
})
describe('decrypt', function () {
it('should be able to decrypt the encrypted data.', function (done) {
edgeEncryptor.encrypt(password, data)
.then(function (encryptedData) {
edgeEncryptor.decrypt(password, encryptedData)
.then(function (decryptedData) {
assert.equal(decryptedData, data)
done()
})
.catch(function (err) {
done(err)
})
})
.catch(function (err) {
done(err)
})
})
it('cannot decrypt the encrypted data with wrong password.', function (done) {
edgeEncryptor.encrypt(password, data)
.then(function (encryptedData) {
edgeEncryptor.decrypt('wrong password', encryptedData)
.then(function (decryptedData) {
assert.fail('could decrypt with wrong password')
done()
})
.catch(function (err) {
assert.ok(err instanceof Error)
assert.equal(err.message, 'Incorrect password')
done()
})
})
.catch(function (err) {
done(err)
})
})
})
})

View File

@ -1,8 +1,8 @@
const assert = require('assert')
const clone = require('clone')
const KeyringController = require('eth-keychain-controller')
import assert from 'assert'
import clone from 'clone'
import KeyringController from 'eth-keychain-controller'
import seedPhraseVerifier from '../../../app/scripts/lib/seed-phrase-verifier'
const firstTimeState = require('../../../app/scripts/first-time-state')
const seedPhraseVerifier = require('../../../app/scripts/lib/seed-phrase-verifier')
const mockEncryptor = require('../../lib/mock-encryptor')
describe('SeedPhraseVerifier', function () {

View File

@ -12,12 +12,12 @@ describe('BinaryRenderer', function () {
})
it('recovers message', function () {
const result = binaryRenderer.hexToText(hex)
const result = binaryRenderer.msgHexToText(hex)
assert.equal(result, message)
})
it('recovers message with hex prefix', function () {
const result = binaryRenderer.hexToText('0x' + hex)
const result = binaryRenderer.msgHexToText('0x' + hex)
assert.equal(result, message)
})
})

View File

@ -235,7 +235,7 @@ describe('Actions', () => {
createNewVaultAndRestoreSpy = sinon.stub(background, 'createNewVaultAndRestore')
createNewVaultAndRestoreSpy.callsFake((password, seed, callback) => {
createNewVaultAndRestoreSpy.callsFake((password, seed, dPath, callback) => {
callback(new Error('error'))
})

View File

@ -19,6 +19,7 @@ const { POA,
CLASSIC } = require('../../app/scripts/controllers/network/enums')
const { hasUnconfirmedTransactions } = require('./helpers/confirm-transaction/util')
const WebcamUtils = require('../lib/webcam-utils')
import { getEnvironmentType } from '../../app/scripts/lib/util'
const actions = {
_setBackgroundConnection: _setBackgroundConnection,
@ -30,6 +31,9 @@ const actions = {
MODAL_CLOSE: 'UI_MODAL_CLOSE',
showModal: showModal,
hideModal: hideModal,
CLOSE_NOTIFICATION_WINDOW: 'CLOSE_NOTIFICATION_WINDOW',
// sidebar state
SIDEBAR_OPEN: 'UI_SIDEBAR_OPEN',
SIDEBAR_CLOSE: 'UI_SIDEBAR_CLOSE',
@ -173,7 +177,6 @@ const actions = {
COMPLETED_TX: 'COMPLETED_TX',
TRANSACTION_ERROR: 'TRANSACTION_ERROR',
NEXT_TX: 'NEXT_TX',
PREVIOUS_TX: 'PREV_TX',
EDIT_TX: 'EDIT_TX',
signMsg: signMsg,
cancelMsg: cancelMsg,
@ -181,19 +184,16 @@ const actions = {
cancelPersonalMsg,
signTypedMsg,
cancelTypedMsg,
sendTx: sendTx,
signTx: signTx,
signTokenTx: signTokenTx,
updateTransaction,
updateAndApproveTx,
cancelTx: cancelTx,
cancelTx,
cancelTxs,
completedTx: completedTx,
txError: txError,
nextTx: nextTx,
editTx,
previousTx: previousTx,
cancelAllTx: cancelAllTx,
viewPendingTx: viewPendingTx,
VIEW_PENDING_TX: 'VIEW_PENDING_TX',
updateTransactionParams,
@ -370,6 +370,9 @@ const actions = {
getRequestAccountTabIds,
setOpenMetamaskTabsIDs,
getOpenMetamaskTabsIds,
isCreatedWithCorrectDPath,
closeCurrentNotificationWindow,
closeNotificationWindow,
}
module.exports = actions
@ -404,7 +407,12 @@ function tryUnlockMetamask (password, dPath) {
})
.then(() => {
dispatch(actions.unlockSucceeded())
return forceUpdateMetamaskState(dispatch)
return updateMetamaskStateFromBackground()
.then(newState => {
newState = Object.assign(newState, {dPath: dPath})
dispatch(actions.updateMetamaskState(newState))
forceUpdateMetamaskState(dispatch)
})
})
.catch((err) => {
log.error(err)
@ -434,6 +442,21 @@ function tryUnlockMetamask (password, dPath) {
}
}
function isCreatedWithCorrectDPath () {
return dispatch => {
return new Promise((resolve, reject) => {
background.isCreatedWithCorrectDPath((err, isCreatedWithCorrectDPath) => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
resolve(isCreatedWithCorrectDPath)
})
})
}
}
function transitionForward () {
return {
type: this.TRANSITION_FORWARD,
@ -466,7 +489,7 @@ function confirmSeedWords () {
}
}
function createNewVaultAndRestore (password, seed) {
function createNewVaultAndRestore (password, seed, dPath) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
log.debug(`background.createNewVaultAndRestore`)
@ -477,7 +500,7 @@ function createNewVaultAndRestore (password, seed) {
return reject(err)
}
background.createNewVaultAndRestore(password, seed, (err) => {
background.createNewVaultAndRestore(password, seed, dPath, (err) => {
if (err) {
return reject(err)
}
@ -489,6 +512,11 @@ function createNewVaultAndRestore (password, seed) {
.then(() => dispatch(actions.unMarkPasswordForgotten()))
.then(() => {
dispatch(actions.showAccountsPage())
updateMetamaskStateFromBackground()
.then(newState => {
newState = Object.assign(newState, {dPath: dPath})
dispatch(actions.updateMetamaskState(newState))
})
dispatch(actions.hideLoadingIndication())
})
.catch(err => {
@ -918,9 +946,8 @@ function setCurrentCurrency (currencyCode) {
function signMsg (msgData) {
log.debug('action - signMsg')
return (dispatch, getState) => {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
log.debug(`actions calling background.signMessage`)
background.signMessage(msgData, (err, newState) => {
@ -935,10 +962,7 @@ function signMsg (msgData) {
}
dispatch(actions.completedTx(msgData.metamaskId))
if (!hasUnconfirmedTransactions(getState())) {
return global.platform.closeNotificationWindow()
}
dispatch(closeCurrentNotificationWindow())
return resolve(msgData)
})
@ -948,7 +972,7 @@ function signMsg (msgData) {
function signPersonalMsg (msgData) {
log.debug('action - signPersonalMsg')
return (dispatch, getState) => {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
@ -966,9 +990,7 @@ function signPersonalMsg (msgData) {
dispatch(actions.completedTx(msgData.metamaskId))
if (!hasUnconfirmedTransactions(getState())) {
return global.platform.closeNotificationWindow()
}
dispatch(actions.closeCurrentNotificationWindow())
return resolve(msgData)
})
@ -978,7 +1000,7 @@ function signPersonalMsg (msgData) {
function signTypedMsg (msgData) {
log.debug('action - signTypedMsg')
return (dispatch, getState) => {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
@ -996,9 +1018,7 @@ function signTypedMsg (msgData) {
dispatch(actions.completedTx(msgData.metamaskId))
if (!hasUnconfirmedTransactions(getState())) {
return global.platform.closeNotificationWindow()
}
dispatch(actions.closeCurrentNotificationWindow())
return resolve(msgData)
})
@ -1203,25 +1223,6 @@ function clearSend () {
}
function sendTx (txData) {
log.info(`actions - sendTx: ${JSON.stringify(txData.txParams)}`)
return (dispatch, getState) => {
log.debug(`actions calling background.approveTransaction`)
background.approveTransaction(txData.id, (err) => {
if (err) {
err = err.message || err.error || err
dispatch(actions.txError(err))
return log.error(err)
}
dispatch(actions.completedTx(txData.id))
if (!hasUnconfirmedTransactions(getState())) {
return global.platform.closeNotificationWindow()
}
})
}
}
function signTokenTx (tokenAddress, toAddress, amount, txData, confTxScreenParams) {
return dispatch => {
dispatch(actions.showLoadingIndication())
@ -1267,10 +1268,9 @@ function updateTransaction (txData) {
function updateAndApproveTx (txData) {
log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData))
return (dispatch, getState) => {
return (dispatch) => {
log.debug(`actions calling background.updateAndApproveTx`)
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.updateAndApproveTransaction(txData, err => {
dispatch(actions.updateTransactionParams(txData.id, txData.txParams))
@ -1293,11 +1293,7 @@ function updateAndApproveTx (txData) {
dispatch(actions.clearSend())
dispatch(actions.completedTx(txData.id))
dispatch(actions.hideLoadingIndication())
dispatch(actions.setCurrentAccountTab('history'))
if (!hasUnconfirmedTransactions(getState())) {
return global.platform.closeNotificationWindow()
}
dispatch(actions.closeCurrentNotificationWindow())
return txData
})
@ -1331,7 +1327,7 @@ function txError (err) {
}
function cancelMsg (msgData) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
@ -1346,9 +1342,7 @@ function cancelMsg (msgData) {
dispatch(actions.completedTx(msgData.id))
if (!hasUnconfirmedTransactions(getState())) {
return global.platform.closeNotificationWindow()
}
dispatch(actions.closeCurrentNotificationWindow())
return resolve(msgData)
})
@ -1357,7 +1351,7 @@ function cancelMsg (msgData) {
}
function cancelPersonalMsg (msgData) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
@ -1372,9 +1366,7 @@ function cancelPersonalMsg (msgData) {
dispatch(actions.completedTx(id))
if (!hasUnconfirmedTransactions(getState())) {
return global.platform.closeNotificationWindow()
}
dispatch(actions.closeCurrentNotificationWindow())
return resolve(msgData)
})
@ -1383,7 +1375,7 @@ function cancelPersonalMsg (msgData) {
}
function cancelTypedMsg (msgData) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
@ -1398,9 +1390,7 @@ function cancelTypedMsg (msgData) {
dispatch(actions.completedTx(id))
if (!hasUnconfirmedTransactions(getState())) {
return global.platform.closeNotificationWindow()
}
dispatch(actions.closeCurrentNotificationWindow())
return resolve(msgData)
})
@ -1409,12 +1399,11 @@ function cancelTypedMsg (msgData) {
}
function cancelTx (txData) {
return (dispatch, getState) => {
return (dispatch) => {
log.debug(`background.cancelTransaction`)
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.cancelTransaction(txData.id, err => {
background.cancelTransaction(txData.id, (err) => {
if (err) {
return reject(err)
}
@ -1423,15 +1412,12 @@ function cancelTx (txData) {
})
})
.then(() => updateMetamaskStateFromBackground())
.then(newState => dispatch(actions.updateMetamaskState(newState)))
.then((newState) => dispatch(actions.updateMetamaskState(newState)))
.then(() => {
dispatch(actions.clearSend())
dispatch(actions.completedTx(txData.id))
dispatch(actions.hideLoadingIndication())
if (!hasUnconfirmedTransactions(getState())) {
return global.platform.closeNotificationWindow()
}
dispatch(actions.closeCurrentNotificationWindow())
return txData
})
@ -1444,9 +1430,9 @@ function cancelTx (txData) {
* @return {function(*): Promise<void>}
*/
function cancelTxs (txDataList) {
return async (dispatch, getState) => {
return async (dispatch) => {
dispatch(actions.showLoadingIndication())
const txIds = txDataList.map(({id}) => id)
const txIds = txDataList.map(({ id }) => id)
const cancellations = txIds.map((id) => new Promise((resolve, reject) => {
background.cancelTransaction(id, (err) => {
if (err) {
@ -1468,30 +1454,12 @@ function cancelTxs (txDataList) {
dispatch(actions.hideLoadingIndication())
if (global.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION) {
if (getEnvironmentType() === ENVIRONMENT_TYPE_NOTIFICATION) {
return global.platform.closeCurrentWindow()
}
}
}
/**
* @deprecated
* @param {Array<object>} txsData
* @return {Function}
*/
function cancelAllTx (txsData) {
return (dispatch) => {
txsData.forEach((txData, i) => {
background.cancelTransaction(txData.id, () => {
dispatch(actions.completedTx(txData.id))
if (i === txsData.length - 1) {
dispatch(actions.goHome())
global.platform.closeNotificationWindow()
}
})
})
}
}
//
// initialize screen
//
@ -1762,9 +1730,10 @@ function showConfTxPage (screenParams) {
}
}
function nextTx () {
function nextTx (txId) {
return {
type: actions.NEXT_TX,
value: txId,
}
}
@ -1775,12 +1744,6 @@ function viewPendingTx (txId) {
}
}
function previousTx () {
return {
type: actions.PREVIOUS_TX,
}
}
function editTx (txId) {
return {
type: actions.EDIT_TX,
@ -2131,6 +2094,23 @@ function hideModal (payload) {
}
}
function closeCurrentNotificationWindow () {
return (dispatch, getState) => {
if (getEnvironmentType() === ENVIRONMENT_TYPE_NOTIFICATION &&
!hasUnconfirmedTransactions(getState())) {
global.platform.closeCurrentWindow()
dispatch(actions.closeNotificationWindow())
}
}
}
function closeNotificationWindow () {
return {
type: actions.CLOSE_NOTIFICATION_WINDOW,
}
}
function showSidebar ({ transitionName, type }) {
return {
type: actions.SIDEBAR_OPEN,
@ -2335,7 +2315,7 @@ function showSendContractPage ({methodSelected, methodABI, inputValues}) {
function buyEth (opts) {
return (dispatch) => {
const url = getBuyEthUrl(opts)
global.platform.openWindow({ url })
global.platform.openTab({ url })
dispatch({
type: actions.BUY_ETH,
})

View File

@ -68,7 +68,7 @@ TokenMenuDropdown.prototype.render = function () {
onClick: (e) => {
e.stopPropagation()
const url = ethNetProps.explorerLinks.getExplorerAccountLinkFor(this.props.token.address, this.props.network)
global.platform.openWindow({ url })
global.platform.openTab({ url })
this.props.onClose()
},
text: this.context.t('viewOnEtherscan'),

View File

@ -1,8 +1,8 @@
const extend = require('xtend')
import extend from 'xtend'
import log from 'loglevel'
const actions = require('../actions')
const txHelper = require('../../lib/tx-helper')
const { customHdPaths } = require('../../../old-ui/app/components/connect-hardware/util.js')
const log = require('loglevel')
module.exports = reduceApp
@ -79,7 +79,8 @@ function reduceApp (state, action) {
customHdPaths: customHdPaths,
}, state.appState)
let curPendingTxIndex = appState.currentView.pendingTxIndex || 0
const curPendingTxIndex = appState.currentView.pendingTxIndex || 0
const curPendingTxId = appState.currentView.pendingTxId || 0
switch (action.type) {
// dropdown methods
@ -507,6 +508,7 @@ function reduceApp (state, action) {
currentView: {
name: 'confTx',
pendingTxIndex: action.id ? indexForPending(state, action.id) : 0,
pendingTxId: action.id,
screenParams: action.value,
},
transForward: action.transForward,
@ -559,11 +561,14 @@ function reduceApp (state, action) {
}
case actions.NEXT_TX:
const increment = (action.value - curPendingTxId)
return extend(appState, {
transForward: true,
currentView: {
name: 'confTx',
pendingTxIndex: ++curPendingTxIndex,
pendingTxIndex: curPendingTxIndex + increment,
pendingTxId: action.value,
index: curPendingTxIndex + increment,
warning: null,
},
})
@ -575,16 +580,7 @@ function reduceApp (state, action) {
currentView: {
name: 'confTx',
pendingTxIndex,
warning: null,
},
})
case actions.PREVIOUS_TX:
return extend(appState, {
transForward: false,
currentView: {
name: 'confTx',
pendingTxIndex: --curPendingTxIndex,
pendingTxId: action.value,
warning: null,
},
})

View File

@ -6,6 +6,8 @@ import { roundExponential } from '../helpers/confirm-transaction/util'
const unapprovedTxsSelector = state => state.metamask.unapprovedTxs
const unapprovedMsgsSelector = state => state.metamask.unapprovedMsgs
const unapprovedPersonalMsgsSelector = state => state.metamask.unapprovedPersonalMsgs
const unapprovedDecryptMsgsSelector = (state) => state.metamask.unapprovedDecryptMsgs
const unapprovedEncryptionPublicKeyMsgsSelector = (state) => state.metamask.unapprovedEncryptionPublicKeyMsgs
const unapprovedTypedMessagesSelector = state => state.metamask.unapprovedTypedMessages
const networkSelector = state => state.metamask.network
@ -13,18 +15,24 @@ export const unconfirmedTransactionsListSelector = createSelector(
unapprovedTxsSelector,
unapprovedMsgsSelector,
unapprovedPersonalMsgsSelector,
unapprovedDecryptMsgsSelector,
unapprovedEncryptionPublicKeyMsgsSelector,
unapprovedTypedMessagesSelector,
networkSelector,
(
unapprovedTxs = {},
unapprovedMsgs = {},
unapprovedPersonalMsgs = {},
unapprovedDecryptMsgs = {},
unapprovedEncryptionPublicKeyMsgs = {},
unapprovedTypedMessages = {},
network,
) => txHelper(
unapprovedTxs,
unapprovedMsgs,
unapprovedPersonalMsgs,
unapprovedDecryptMsgs,
unapprovedEncryptionPublicKeyMsgs,
unapprovedTypedMessages,
network,
) || [],
@ -34,12 +42,16 @@ export const unconfirmedTransactionsHashSelector = createSelector(
unapprovedTxsSelector,
unapprovedMsgsSelector,
unapprovedPersonalMsgsSelector,
unapprovedDecryptMsgsSelector,
unapprovedEncryptionPublicKeyMsgsSelector,
unapprovedTypedMessagesSelector,
networkSelector,
(
unapprovedTxs = {},
unapprovedMsgs = {},
unapprovedPersonalMsgs = {},
unapprovedDecryptMsgs = {},
unapprovedEncryptionPublicKeyMsgs = {},
unapprovedTypedMessages = {},
network,
) => {
@ -58,6 +70,8 @@ export const unconfirmedTransactionsHashSelector = createSelector(
...filteredUnapprovedTxs,
...unapprovedMsgs,
...unapprovedPersonalMsgs,
...unapprovedDecryptMsgs,
...unapprovedEncryptionPublicKeyMsgs,
...unapprovedTypedMessages,
}
},
@ -65,18 +79,24 @@ export const unconfirmedTransactionsHashSelector = createSelector(
const unapprovedMsgCountSelector = state => state.metamask.unapprovedMsgCount
const unapprovedPersonalMsgCountSelector = state => state.metamask.unapprovedPersonalMsgCount
const unapprovedDecryptMsgCountSelector = (state) => state.metamask.unapprovedDecryptMsgCount
const unapprovedEncryptionPublicKeyMsgCountSelector = (state) => state.metamask.unapprovedEncryptionPublicKeyMsgCount
const unapprovedTypedMessagesCountSelector = state => state.metamask.unapprovedTypedMessagesCount
export const unconfirmedTransactionsCountSelector = createSelector(
unapprovedTxsSelector,
unapprovedMsgCountSelector,
unapprovedPersonalMsgCountSelector,
unapprovedDecryptMsgCountSelector,
unapprovedEncryptionPublicKeyMsgCountSelector,
unapprovedTypedMessagesCountSelector,
networkSelector,
(
unapprovedTxs = {},
unapprovedMsgCount = 0,
unapprovedPersonalMsgCount = 0,
unapprovedDecryptMsgCount = 0,
unapprovedEncryptionPublicKeyMsgCount = 0,
unapprovedTypedMessagesCount = 0,
network,
) => {
@ -86,7 +106,7 @@ export const unconfirmedTransactionsCountSelector = createSelector(
})
return filteredUnapprovedTxIds.length + unapprovedTypedMessagesCount + unapprovedMsgCount +
unapprovedPersonalMsgCount
unapprovedPersonalMsgCount + unapprovedDecryptMsgCount + unapprovedEncryptionPublicKeyMsgCount
},
)
@ -132,15 +152,16 @@ export const tokenAmountAndToAddressSelector = createSelector(
let tokenAmount = 0
if (params && params.length) {
const toParam = params.find(param => param.name === TOKEN_PARAM_TO)
const valueParam = params.find(param => param.name === TOKEN_PARAM_VALUE)
const toParam = params.find((param) => param.name === TOKEN_PARAM_TO)
const valueParam = params.find((param) => param.name === TOKEN_PARAM_VALUE)
toAddress = toParam ? toParam.value : params[0].value
const value = valueParam ? Number(valueParam.value) : Number(params[1].value)
tokenAmount = roundExponential(value)
if (tokenDecimals) {
tokenAmount = calcTokenAmount(value, tokenDecimals)
tokenAmount = calcTokenAmount(value, tokenDecimals).toNumber()
}
tokenAmount = roundExponential(tokenAmount)
}
return {
@ -158,11 +179,11 @@ export const approveTokenAmountAndToAddressSelector = createSelector(
let tokenAmount = 0
if (params && params.length) {
toAddress = params.find(param => param.name === TOKEN_PARAM_SPENDER).value
const value = Number(params.find(param => param.name === TOKEN_PARAM_VALUE).value)
toAddress = params.find((param) => param.name === TOKEN_PARAM_SPENDER).value
const value = Number(params.find((param) => param.name === TOKEN_PARAM_VALUE).value)
if (tokenDecimals) {
tokenAmount = calcTokenAmount(value, tokenDecimals)
tokenAmount = calcTokenAmount(value, tokenDecimals).toNumber()
}
tokenAmount = roundExponential(tokenAmount)
@ -183,11 +204,11 @@ export const sendTokenTokenAmountAndToAddressSelector = createSelector(
let tokenAmount = 0
if (params && params.length) {
toAddress = params.find(param => param.name === TOKEN_PARAM_TO).value
let value = Number(params.find(param => param.name === TOKEN_PARAM_VALUE).value)
toAddress = params.find((param) => param.name === TOKEN_PARAM_TO).value
let value = Number(params.find((param) => param.name === TOKEN_PARAM_VALUE).value)
if (tokenDecimals) {
value = calcTokenAmount(value, tokenDecimals)
value = calcTokenAmount(value, tokenDecimals).toNumber()
}
tokenAmount = roundExponential(value)

View File

@ -54,9 +54,8 @@ async function startApp (metamaskState, accountManager, opts) {
const unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.unapprovedPersonalMsgs, metamaskState.unapprovedTypedMessages, metamaskState.network)
const numberOfUnapprivedTx = unapprovedTxsAll.length
if (numberOfUnapprivedTx > 0) {
store.dispatch(actions.showConfTxPage({
id: unapprovedTxsAll[numberOfUnapprivedTx - 1].id,
id: unapprovedTxsAll[0].id,
}))
}