commit
7f46b1cd45
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -2,6 +2,19 @@
|
||||||
|
|
||||||
## Current Master
|
## Current Master
|
||||||
|
|
||||||
|
## 5.1.1 Mon Jun 15 2020
|
||||||
|
|
||||||
|
- [#393](https://github.com/poanetwork/nifty-wallet/pull/393) - (Feature) "Send all" option for tokens transfer
|
||||||
|
- [#391](https://github.com/poanetwork/nifty-wallet/pull/391) - (Feature) Gas price oracles npm package integration
|
||||||
|
- [#389](https://github.com/poanetwork/nifty-wallet/pull/389) - (Feature) Support 24 words mnemonic phrase
|
||||||
|
- [#388](https://github.com/poanetwork/nifty-wallet/pull/388) - (Feature) "Send all" option for simple coin transfers
|
||||||
|
- [#385](https://github.com/poanetwork/nifty-wallet/pull/385) - (Feature) Display value of current pending tx's nonce on send tx screen
|
||||||
|
- [#384](https://github.com/poanetwork/nifty-wallet/pull/384) - (Fix) placement of HW Connect button title
|
||||||
|
- [#383](https://github.com/poanetwork/nifty-wallet/pull/383) - (Chore) Replace POA-ETH Binance link to POA-BTC
|
||||||
|
- [#382](https://github.com/poanetwork/nifty-wallet/pull/382) - (Fix) replace vulnerable npm dependencies with newer versions of packages, update chromedriver to match the latest Google Chrome release
|
||||||
|
- [#381](https://github.com/poanetwork/nifty-wallet/pull/381) - (Feature) Add RNS integration
|
||||||
|
- [#381](https://github.com/poanetwork/nifty-wallet/pull/381) - (Fix) ENS/RNS integration when sending tokens
|
||||||
|
|
||||||
## 5.1.0 Tue May 12 2020
|
## 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
|
- [#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
|
||||||
|
|
|
@ -355,6 +355,9 @@
|
||||||
"ensNameNotFound": {
|
"ensNameNotFound": {
|
||||||
"message": "ENS name not found"
|
"message": "ENS name not found"
|
||||||
},
|
},
|
||||||
|
"rnsNameNotFound": {
|
||||||
|
"message": "RNS name not found"
|
||||||
|
},
|
||||||
"enterPassword": {
|
"enterPassword": {
|
||||||
"message": "Enter password"
|
"message": "Enter password"
|
||||||
},
|
},
|
||||||
|
|
|
@ -349,6 +349,9 @@
|
||||||
"ensNameNotFound": {
|
"ensNameNotFound": {
|
||||||
"message": "Nom ENS inconnu"
|
"message": "Nom ENS inconnu"
|
||||||
},
|
},
|
||||||
|
"rnsNameNotFound": {
|
||||||
|
"message": "Nom RNS inconnu"
|
||||||
|
},
|
||||||
"enterPassword": {
|
"enterPassword": {
|
||||||
"message": "Entrez votre mot de passe"
|
"message": "Entrez votre mot de passe"
|
||||||
},
|
},
|
||||||
|
|
|
@ -313,6 +313,9 @@
|
||||||
"ensNameNotFound": {
|
"ensNameNotFound": {
|
||||||
"message": "Nou pa jwenn non ENS ou a"
|
"message": "Nou pa jwenn non ENS ou a"
|
||||||
},
|
},
|
||||||
|
"rnsNameNotFound": {
|
||||||
|
"message": "Nou pa jwenn non RNS ou a"
|
||||||
|
},
|
||||||
"enterPassword": {
|
"enterPassword": {
|
||||||
"message": "Mete modpas"
|
"message": "Mete modpas"
|
||||||
},
|
},
|
||||||
|
|
|
@ -352,6 +352,9 @@
|
||||||
"ensNameNotFound": {
|
"ensNameNotFound": {
|
||||||
"message": "Nome ENS non trovato"
|
"message": "Nome ENS non trovato"
|
||||||
},
|
},
|
||||||
|
"rnsNameNotFound": {
|
||||||
|
"message": "Nome RNS non trovato"
|
||||||
|
},
|
||||||
"enterPassword": {
|
"enterPassword": {
|
||||||
"message": "Inserisci password"
|
"message": "Inserisci password"
|
||||||
},
|
},
|
||||||
|
|
|
@ -349,6 +349,9 @@
|
||||||
"ensNameNotFound": {
|
"ensNameNotFound": {
|
||||||
"message": "ENS 이름을 찾을 수 없습니다"
|
"message": "ENS 이름을 찾을 수 없습니다"
|
||||||
},
|
},
|
||||||
|
"rnsNameNotFound": {
|
||||||
|
"message": "RNS 이름을 찾을 수 없습니다"
|
||||||
|
},
|
||||||
"enterPassword": {
|
"enterPassword": {
|
||||||
"message": "비밀번호를 입력해주세요"
|
"message": "비밀번호를 입력해주세요"
|
||||||
},
|
},
|
||||||
|
|
|
@ -313,6 +313,9 @@
|
||||||
"ensNameNotFound": {
|
"ensNameNotFound": {
|
||||||
"message": "Nie znaleziono nazwy ENS"
|
"message": "Nie znaleziono nazwy ENS"
|
||||||
},
|
},
|
||||||
|
"rnsNameNotFound": {
|
||||||
|
"message": "Nie znaleziono nazwy RNS"
|
||||||
|
},
|
||||||
"enterPassword": {
|
"enterPassword": {
|
||||||
"message": "Wpisz hasło"
|
"message": "Wpisz hasło"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "__MSG_appName__",
|
"name": "__MSG_appName__",
|
||||||
"short_name": "__MSG_appName__",
|
"short_name": "__MSG_appName__",
|
||||||
"version": "5.1.0",
|
"version": "5.1.1",
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"author": "POA Network",
|
"author": "POA Network",
|
||||||
"description": "__MSG_appDescription__",
|
"description": "__MSG_appDescription__",
|
||||||
|
|
|
@ -10,7 +10,7 @@ import abiDecoder from 'abi-decoder'
|
||||||
abiDecoder.addABI(abi)
|
abiDecoder.addABI(abi)
|
||||||
|
|
||||||
import TransactionStateManager from './tx-state-manager'
|
import TransactionStateManager from './tx-state-manager'
|
||||||
const TxGasUtil = require('./tx-gas-utils')
|
import TxGasUtil from './tx-gas-utils'
|
||||||
const PendingTransactionTracker = require('./pending-tx-tracker')
|
const PendingTransactionTracker = require('./pending-tx-tracker')
|
||||||
import NonceTracker from 'nonce-tracker'
|
import NonceTracker from 'nonce-tracker'
|
||||||
import * as txUtils from './lib/util'
|
import * as txUtils from './lib/util'
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
const EthQuery = require('ethjs-query')
|
import EthQuery from 'ethjs-query'
|
||||||
const {
|
import { hexToBn, BnMultiplyByFraction, bnToHex } from '../../lib/util'
|
||||||
hexToBn,
|
import { addHexPrefix } from 'ethereumjs-util'
|
||||||
BnMultiplyByFraction,
|
import { MIN_GAS_LIMIT_HEX } from '../../../../ui/app/components/send/send.constants'
|
||||||
bnToHex,
|
import log from 'loglevel'
|
||||||
} = require('../../lib/util')
|
|
||||||
const { addHexPrefix } = require('ethereumjs-util')
|
|
||||||
const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send.
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
tx-gas-utils are gas utility methods for Transaction manager
|
tx-gas-utils are gas utility methods for Transaction manager
|
||||||
|
@ -14,24 +11,31 @@ and used to do things like calculate gas of a tx.
|
||||||
@param {Object} provider - A network provider.
|
@param {Object} provider - A network provider.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class TxGasUtil {
|
export default class TxGasUtil {
|
||||||
|
|
||||||
constructor (provider) {
|
constructor (provider) {
|
||||||
this.query = new EthQuery(provider)
|
this.query = new EthQuery(provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param txMeta {Object} - the txMeta object
|
@param {Object} txMeta - the txMeta object
|
||||||
@returns {object} the txMeta object with the gas written to the txParams
|
@returns {GasAnalysisResult} The result of the gas analysis
|
||||||
*/
|
*/
|
||||||
async analyzeGasUsage (txMeta) {
|
async analyzeGasUsage (txMeta) {
|
||||||
const block = await this.query.getBlockByNumber('latest', false)
|
const block = await this.query.getBlockByNumber('latest', false)
|
||||||
let estimatedGasHex
|
|
||||||
|
// fallback to block gasLimit
|
||||||
|
const blockGasLimitBN = hexToBn(block.gasLimit)
|
||||||
|
const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20)
|
||||||
|
let estimatedGasHex = bnToHex(saferGasLimitBN)
|
||||||
try {
|
try {
|
||||||
estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit)
|
estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit)
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
|
log.warn(error)
|
||||||
txMeta.simulationFails = {
|
txMeta.simulationFails = {
|
||||||
reason: err.message,
|
reason: error.message,
|
||||||
|
errorKey: error.errorKey,
|
||||||
|
debug: { blockNumber: block.number, blockGasLimit: block.gasLimit },
|
||||||
}
|
}
|
||||||
return txMeta
|
return txMeta
|
||||||
}
|
}
|
||||||
|
@ -63,9 +67,9 @@ class TxGasUtil {
|
||||||
if (recipient) code = await this.query.getCode(recipient)
|
if (recipient) code = await this.query.getCode(recipient)
|
||||||
|
|
||||||
if (hasRecipient && (!code || code === '0x' || code === '0x0')) {
|
if (hasRecipient && (!code || code === '0x' || code === '0x0')) {
|
||||||
txParams.gas = SIMPLE_GAS_COST
|
txParams.gas = MIN_GAS_LIMIT_HEX
|
||||||
txMeta.simpleSend = true // Prevents buffer addition
|
txMeta.simpleSend = true // Prevents buffer addition
|
||||||
return SIMPLE_GAS_COST
|
return MIN_GAS_LIMIT_HEX
|
||||||
}
|
}
|
||||||
|
|
||||||
// if not, fall back to block gasLimit
|
// if not, fall back to block gasLimit
|
||||||
|
@ -103,9 +107,9 @@ class TxGasUtil {
|
||||||
/**
|
/**
|
||||||
Adds a gas buffer with out exceeding the block gas limit
|
Adds a gas buffer with out exceeding the block gas limit
|
||||||
|
|
||||||
@param initialGasLimitHex {string} - the initial gas limit to add the buffer too
|
@param {string} initialGasLimitHex - the initial gas limit to add the buffer too
|
||||||
@param blockGasLimitHex {string} - the block gas limit
|
@param {string} blockGasLimitHex - the block gas limit
|
||||||
@returns {string} the buffered gas limit as a hex string
|
@returns {string} - the buffered gas limit as a hex string
|
||||||
*/
|
*/
|
||||||
addGasBuffer (initialGasLimitHex, blockGasLimitHex) {
|
addGasBuffer (initialGasLimitHex, blockGasLimitHex) {
|
||||||
const initialGasLimitBn = hexToBn(initialGasLimitHex)
|
const initialGasLimitBn = hexToBn(initialGasLimitHex)
|
||||||
|
@ -114,12 +118,14 @@ class TxGasUtil {
|
||||||
const bufferedGasLimitBn = initialGasLimitBn.muln(1.5)
|
const bufferedGasLimitBn = initialGasLimitBn.muln(1.5)
|
||||||
|
|
||||||
// if initialGasLimit is above blockGasLimit, dont modify it
|
// if initialGasLimit is above blockGasLimit, dont modify it
|
||||||
if (initialGasLimitBn.gt(upperGasLimitBn)) return bnToHex(initialGasLimitBn)
|
if (initialGasLimitBn.gt(upperGasLimitBn)) {
|
||||||
|
return bnToHex(initialGasLimitBn)
|
||||||
|
}
|
||||||
// if bufferedGasLimit is below blockGasLimit, use bufferedGasLimit
|
// if bufferedGasLimit is below blockGasLimit, use bufferedGasLimit
|
||||||
if (bufferedGasLimitBn.lt(upperGasLimitBn)) return bnToHex(bufferedGasLimitBn)
|
if (bufferedGasLimitBn.lt(upperGasLimitBn)) {
|
||||||
|
return bnToHex(bufferedGasLimitBn)
|
||||||
|
}
|
||||||
// otherwise use blockGasLimit
|
// otherwise use blockGasLimit
|
||||||
return bnToHex(upperGasLimitBn)
|
return bnToHex(upperGasLimitBn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = TxGasUtil
|
|
||||||
|
|
|
@ -91,7 +91,7 @@ function getExchanges ({network, amount, address}) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: 'Binance',
|
name: 'Binance',
|
||||||
link: 'https://www.binance.com/en/trade/POA_ETH',
|
link: 'https://www.binance.com/en/trade/POA_BTC',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'BiBox',
|
name: 'BiBox',
|
||||||
|
|
|
@ -61,6 +61,7 @@ import nanoid from 'nanoid'
|
||||||
const { importTypes } = require('../../old-ui/app/accounts/import/enums')
|
const { importTypes } = require('../../old-ui/app/accounts/import/enums')
|
||||||
const { LEDGER, TREZOR } = require('../../old-ui/app/components/connect-hardware/enum')
|
const { LEDGER, TREZOR } = require('../../old-ui/app/components/connect-hardware/enum')
|
||||||
const { ifPOA, ifRSK, getNetworkID, getDPath, setDPath } = require('../../old-ui/app/util')
|
const { ifPOA, ifRSK, getNetworkID, getDPath, setDPath } = require('../../old-ui/app/util')
|
||||||
|
const { GasPriceOracle } = require('gas-price-oracle')
|
||||||
|
|
||||||
import {
|
import {
|
||||||
PhishingController,
|
PhishingController,
|
||||||
|
@ -443,7 +444,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
setDProvider: this.setDProvider.bind(this),
|
setDProvider: this.setDProvider.bind(this),
|
||||||
markPasswordForgotten: this.markPasswordForgotten.bind(this),
|
markPasswordForgotten: this.markPasswordForgotten.bind(this),
|
||||||
unMarkPasswordForgotten: this.unMarkPasswordForgotten.bind(this),
|
unMarkPasswordForgotten: this.unMarkPasswordForgotten.bind(this),
|
||||||
getGasPrice: (cb) => cb(null, this.getGasPrice()),
|
getGasPrice: nodeify(this.getGasPrice, this),
|
||||||
|
getPendingNonce: nodeify(this.getPendingNonce, this),
|
||||||
|
|
||||||
// shapeshift
|
// shapeshift
|
||||||
createShapeShiftTx: this.createShapeShiftTx.bind(this),
|
createShapeShiftTx: this.createShapeShiftTx.bind(this),
|
||||||
|
@ -1869,10 +1871,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
* @returns {string} A hex representation of the suggested wei gas price.
|
* @returns {string} A hex representation of the suggested wei gas price.
|
||||||
*/
|
*/
|
||||||
async getGasPrice () {
|
async getGasPrice () {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve) => {
|
||||||
const { networkController } = this
|
const { networkController } = this
|
||||||
|
|
||||||
|
|
||||||
const networkIdStr = networkController.store.getState().network
|
const networkIdStr = networkController.store.getState().network
|
||||||
const networkId = parseInt(networkIdStr)
|
const networkId = parseInt(networkIdStr)
|
||||||
const isETHC = networkId === CLASSIC_CODE || networkId === MAINNET_CODE
|
const isETHC = networkId === CLASSIC_CODE || networkId === MAINNET_CODE
|
||||||
|
@ -1967,24 +1968,31 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
getGasPriceFromOracles (networkId) {
|
getGasPriceFromOracles (networkId) {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
|
if (networkId === MAINNET_CODE) {
|
||||||
|
const oracle = new GasPriceOracle()
|
||||||
|
// optional fallbackGasPrices
|
||||||
|
const fallbackGasPrices = {
|
||||||
|
instant: 70, fast: 31, standard: 20, low: 7,
|
||||||
|
}
|
||||||
|
oracle.gasPrices(fallbackGasPrices).then((gasPrices) => {
|
||||||
|
gasPrices && (gasPrices.standard || gasPrices.fast) ? resolve(gasPrices.standard || gasPrices.fast) : reject()
|
||||||
|
})
|
||||||
|
} else if (networkId === CLASSIC_CODE) {
|
||||||
const gasPriceOracleETC = 'https://gasprice-etc.poa.network'
|
const gasPriceOracleETC = 'https://gasprice-etc.poa.network'
|
||||||
const gasPriceOracleETH = 'https://gasprice.poa.network'
|
|
||||||
const gasPriceOracle = networkId === CLASSIC_CODE ?
|
|
||||||
gasPriceOracleETC : networkId === MAINNET_CODE ? gasPriceOracleETH : null
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (gasPriceOracle) {
|
const response = await fetch(gasPriceOracleETC)
|
||||||
const response = await fetch(gasPriceOracle)
|
|
||||||
const parsedResponse = await response.json()
|
const parsedResponse = await response.json()
|
||||||
if (parsedResponse && (parsedResponse.standard || parsedResponse.fast)) {
|
if (parsedResponse && (parsedResponse.standard || parsedResponse.fast)) {
|
||||||
resolve(parsedResponse.standard || parsedResponse.fast)
|
resolve(parsedResponse.standard || parsedResponse.fast)
|
||||||
} else {
|
} else {
|
||||||
reject()
|
reject('Empty response from gas price oracle')
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
reject(error)
|
reject(error)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
reject(`No gas price oracles for ${networkId}`)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,8 @@ class ConfirmSeedScreen extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount () {
|
// eslint-disable-next-line camelcase
|
||||||
|
UNSAFE_componentWillMount () {
|
||||||
const { seedWords, history } = this.props
|
const { seedWords, history } = this.props
|
||||||
|
|
||||||
if (!seedWords) {
|
if (!seedWords) {
|
||||||
|
|
|
@ -40,7 +40,8 @@ class CreatePasswordScreen extends Component {
|
||||||
this.animationEventEmitter = new EventEmitter()
|
this.animationEventEmitter = new EventEmitter()
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount () {
|
// eslint-disable-next-line camelcase
|
||||||
|
UNSAFE_componentWillMount () {
|
||||||
const { isInitialized, history } = this.props
|
const { isInitialized, history } = this.props
|
||||||
|
|
||||||
if (isInitialized) {
|
if (isInitialized) {
|
||||||
|
|
|
@ -41,7 +41,8 @@ class ImportSeedPhraseScreen extends Component {
|
||||||
let seedPhraseError = null
|
let seedPhraseError = null
|
||||||
|
|
||||||
if (seedPhrase) {
|
if (seedPhrase) {
|
||||||
if (this.parseSeedPhrase(seedPhrase).split(' ').length !== 12) {
|
const wordsCount = this.parseSeedPhrase(seedPhrase).split(' ').length
|
||||||
|
if (wordsCount !== 12 && wordsCount !== 24) {
|
||||||
seedPhraseError = this.context.t('seedPhraseReq')
|
seedPhraseError = this.context.t('seedPhraseReq')
|
||||||
} else if (!validateMnemonic(seedPhrase)) {
|
} else if (!validateMnemonic(seedPhrase)) {
|
||||||
seedPhraseError = this.context.t('invalidSeedPhrase')
|
seedPhraseError = this.context.t('invalidSeedPhrase')
|
||||||
|
|
|
@ -58,7 +58,8 @@ class BackupPhraseScreen extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount () {
|
// eslint-disable-next-line camelcase
|
||||||
|
UNSAFE_componentWillMount () {
|
||||||
const { seedWords, history } = this.props
|
const { seedWords, history } = this.props
|
||||||
|
|
||||||
if (!seedWords) {
|
if (!seedWords) {
|
||||||
|
|
|
@ -26,7 +26,8 @@ export class ShapeShiftForm extends Component {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
// eslint-disable-next-line camelcase
|
||||||
|
UNSAFE_componentWillMount () {
|
||||||
this.props.shapeShiftSubview()
|
this.props.shapeShiftSubview()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -159,7 +159,7 @@ AddSuggestedTokenScreen.prototype.render = function () {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
AddSuggestedTokenScreen.prototype.componentWillMount = function () {
|
AddSuggestedTokenScreen.prototype.UNSAFE_componentWillMount = function () {
|
||||||
if (typeof global.ethereumProvider === 'undefined') return
|
if (typeof global.ethereumProvider === 'undefined') return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -316,7 +316,8 @@ export default class AddTokenScreen extends Component {
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount () {
|
// eslint-disable-next-line camelcase
|
||||||
|
UNSAFE_componentWillMount () {
|
||||||
if (typeof global.ethereumProvider === 'undefined') return
|
if (typeof global.ethereumProvider === 'undefined') return
|
||||||
|
|
||||||
this.eth = new Eth(global.ethereumProvider)
|
this.eth = new Eth(global.ethereumProvider)
|
||||||
|
|
|
@ -1,32 +1,43 @@
|
||||||
const Component = require('react').Component
|
import { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import debounce from 'debounce'
|
||||||
|
import copyToClipboard from 'copy-to-clipboard'
|
||||||
|
import ENS from 'ethjs-ens'
|
||||||
|
import log from 'loglevel'
|
||||||
|
|
||||||
const h = require('react-hyperscript')
|
const h = require('react-hyperscript')
|
||||||
const inherits = require('util').inherits
|
|
||||||
const debounce = require('debounce')
|
|
||||||
const copyToClipboard = require('copy-to-clipboard')
|
|
||||||
const ENS = require('ethjs-ens')
|
|
||||||
const networkMap = require('ethjs-ens/lib/network-map.json')
|
const networkMap = require('ethjs-ens/lib/network-map.json')
|
||||||
|
const RNSRegistryData = require('@rsksmart/rns-registry/RNSRegistryData.json')
|
||||||
const ensRE = /.+\..+$/
|
const ensRE = /.+\..+$/
|
||||||
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
||||||
const log = require('loglevel')
|
const { isValidENSAddress, isValidRNSAddress } = require('../util')
|
||||||
const { isValidENSAddress } = require('../util')
|
const {
|
||||||
|
RSK_CODE,
|
||||||
|
RSK_TESTNET_CODE,
|
||||||
|
} = require('../../../app/scripts/controllers/network/enums')
|
||||||
|
|
||||||
|
class EnsInput extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
network: PropTypes.string,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
|
name: PropTypes.string,
|
||||||
|
identities: PropTypes.object,
|
||||||
|
addressBook: PropTypes.array,
|
||||||
|
updateSendTo: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = EnsInput
|
render () {
|
||||||
|
|
||||||
inherits(EnsInput, Component)
|
|
||||||
function EnsInput () {
|
|
||||||
Component.call(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
EnsInput.prototype.render = function () {
|
|
||||||
const props = this.props
|
const props = this.props
|
||||||
|
|
||||||
function onInputChange () {
|
function onInputChange () {
|
||||||
|
const recipient = document.querySelector('input[name="address"]').value
|
||||||
|
this.props.updateSendTo(recipient, ' ')
|
||||||
const network = this.props.network
|
const network = this.props.network
|
||||||
const networkHasEnsSupport = getNetworkEnsSupport(network)
|
const networkHasEnsSupport = getNetworkEnsSupport(network)
|
||||||
if (!networkHasEnsSupport) return
|
const networkHasRnsSupport = getNetworkRnsSupport(network)
|
||||||
|
if (!networkHasEnsSupport && !networkHasRnsSupport) return
|
||||||
|
|
||||||
const recipient = document.querySelector('input[name="address"]').value
|
|
||||||
if (recipient.match(ensRE) === null) {
|
if (recipient.match(ensRE) === null) {
|
||||||
return this.setState({
|
return this.setState({
|
||||||
loadingEns: false,
|
loadingEns: false,
|
||||||
|
@ -76,25 +87,32 @@ EnsInput.prototype.render = function () {
|
||||||
this.ensIcon(),
|
this.ensIcon(),
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsInput.prototype.componentDidMount = function () {
|
componentDidMount () {
|
||||||
const network = this.props.network
|
const network = this.props.network
|
||||||
const networkHasEnsSupport = getNetworkEnsSupport(network)
|
const networkHasEnsSupport = getNetworkEnsSupport(network)
|
||||||
|
const networkHasRnsSupport = getNetworkRnsSupport(network)
|
||||||
|
|
||||||
this.setState({ ensResolution: ZERO_ADDRESS })
|
this.setState({ ensResolution: ZERO_ADDRESS })
|
||||||
|
|
||||||
if (networkHasEnsSupport) {
|
if (networkHasEnsSupport) {
|
||||||
const provider = global.ethereumProvider
|
const provider = global.ethereumProvider
|
||||||
this.ens = new ENS({ provider, network })
|
this.ens = new ENS({ provider, network })
|
||||||
this.checkName = debounce(this.lookupEnsName.bind(this), 200)
|
this.checkName = debounce(this.lookupEnsName.bind(this, 'ENS'), 200)
|
||||||
|
} else if (networkHasRnsSupport) {
|
||||||
|
const registryAddress = getRnsRegistryAddress(network)
|
||||||
|
const provider = global.ethereumProvider
|
||||||
|
this.ens = new ENS({ provider, network, registryAddress })
|
||||||
|
this.checkName = debounce(this.lookupEnsName.bind(this, 'RNS'), 200)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
EnsInput.prototype.lookupEnsName = function () {
|
lookupEnsName (nameService) {
|
||||||
const recipient = document.querySelector('input[name="address"]').value
|
const recipient = document.querySelector('input[name="address"]').value
|
||||||
const { ensResolution } = this.state
|
const { ensResolution } = this.state
|
||||||
|
|
||||||
log.info(`ENS attempting to resolve name: ${recipient}`)
|
log.info(`${nameService} attempting to resolve name: ${recipient}`)
|
||||||
this.ens.lookup(recipient.trim())
|
this.ens.lookup(recipient.trim())
|
||||||
.then((address) => {
|
.then((address) => {
|
||||||
if (address === ZERO_ADDRESS) throw new Error('No address has been set for this name.')
|
if (address === ZERO_ADDRESS) throw new Error('No address has been set for this name.')
|
||||||
|
@ -116,9 +134,12 @@ EnsInput.prototype.lookupEnsName = function () {
|
||||||
ensFailure: true,
|
ensFailure: true,
|
||||||
toError: null,
|
toError: null,
|
||||||
}
|
}
|
||||||
if (isValidENSAddress(recipient) && reason.message === 'ENS name not defined.') {
|
if (
|
||||||
setStateObj.hoverText = 'ENS name not found'
|
(isValidENSAddress(recipient) || isValidRNSAddress(recipient)) &&
|
||||||
setStateObj.toError = 'ensNameNotFound'
|
reason.message === 'ENS name not defined.'
|
||||||
|
) {
|
||||||
|
setStateObj.hoverText = '${nameService} name not found'
|
||||||
|
setStateObj.toError = `${nameService.toLowerCase()}NameNotFound`
|
||||||
setStateObj.ensFailure = false
|
setStateObj.ensFailure = false
|
||||||
} else {
|
} else {
|
||||||
log.error(reason)
|
log.error(reason)
|
||||||
|
@ -127,9 +148,9 @@ EnsInput.prototype.lookupEnsName = function () {
|
||||||
|
|
||||||
return this.setState(setStateObj)
|
return this.setState(setStateObj)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsInput.prototype.componentDidUpdate = function (prevProps, prevState) {
|
componentDidUpdate (prevProps, prevState) {
|
||||||
const state = this.state || {}
|
const state = this.state || {}
|
||||||
const ensResolution = state.ensResolution
|
const ensResolution = state.ensResolution
|
||||||
// If an address is sent without a nickname, meaning not from ENS or from
|
// If an address is sent without a nickname, meaning not from ENS or from
|
||||||
|
@ -139,9 +160,9 @@ EnsInput.prototype.componentDidUpdate = function (prevProps, prevState) {
|
||||||
ensResolution !== prevState.ensResolution) {
|
ensResolution !== prevState.ensResolution) {
|
||||||
this.props.onChange({ toAddress: ensResolution, nickname, toError: state.toError, toWarning: state.toWarning })
|
this.props.onChange({ toAddress: ensResolution, nickname, toError: state.toError, toWarning: state.toWarning })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsInput.prototype.ensIcon = function (recipient) {
|
ensIcon () {
|
||||||
const { hoverText } = this.state || {}
|
const { hoverText } = this.state || {}
|
||||||
return h('span', {
|
return h('span', {
|
||||||
title: hoverText,
|
title: hoverText,
|
||||||
|
@ -151,10 +172,10 @@ EnsInput.prototype.ensIcon = function (recipient) {
|
||||||
right: '0px',
|
right: '0px',
|
||||||
transform: 'translatex(-40px)',
|
transform: 'translatex(-40px)',
|
||||||
},
|
},
|
||||||
}, this.ensIconContents(recipient))
|
}, this.ensIconContents())
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsInput.prototype.ensIconContents = function (recipient) {
|
ensIconContents () {
|
||||||
const { loadingEns, ensFailure, ensResolution, toError } = this.state || { ensResolution: ZERO_ADDRESS}
|
const { loadingEns, ensFailure, ensResolution, toError } = this.state || { ensResolution: ZERO_ADDRESS}
|
||||||
|
|
||||||
if (toError) return
|
if (toError) return
|
||||||
|
@ -193,8 +214,27 @@ EnsInput.prototype.ensIconContents = function (recipient) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNetworkEnsSupport (network) {
|
function getNetworkEnsSupport (network) {
|
||||||
return Boolean(networkMap[network])
|
return Boolean(networkMap[network])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getNetworkRnsSupport (network) {
|
||||||
|
return (network === RSK_CODE || network === RSK_TESTNET_CODE)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRnsRegistryAddress (network) {
|
||||||
|
if (network === RSK_CODE) {
|
||||||
|
return RNSRegistryData.address.rskMainnet
|
||||||
|
}
|
||||||
|
|
||||||
|
if (network === RSK_TESTNET_CODE) {
|
||||||
|
return RNSRegistryData.address.rskTestnet
|
||||||
|
};
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = EnsInput
|
||||||
|
|
|
@ -25,10 +25,11 @@ const { tokenInfoGetter, calcTokenAmount } = require('../../../ui/app/token-util
|
||||||
import BigNumber from 'bignumber.js'
|
import BigNumber from 'bignumber.js'
|
||||||
import ethNetProps from 'eth-net-props'
|
import ethNetProps from 'eth-net-props'
|
||||||
import { getMetaMaskAccounts } from '../../../ui/app/selectors'
|
import { getMetaMaskAccounts } from '../../../ui/app/selectors'
|
||||||
|
import { MIN_GAS_LIMIT_DEC } from '../../../ui/app/components/send/send.constants'
|
||||||
import * as Toast from './toast'
|
import * as Toast from './toast'
|
||||||
|
|
||||||
const MIN_GAS_PRICE_BN = new BN('0')
|
const MIN_GAS_PRICE_BN = new BN('0')
|
||||||
const MIN_GAS_LIMIT_BN = new BN('21000')
|
const MIN_GAS_LIMIT_BN = new BN(MIN_GAS_LIMIT_DEC)
|
||||||
const emptyAddress = '0x0000000000000000000000000000000000000000'
|
const emptyAddress = '0x0000000000000000000000000000000000000000'
|
||||||
|
|
||||||
class PendingTx extends Component {
|
class PendingTx extends Component {
|
||||||
|
@ -472,12 +473,7 @@ class PendingTx extends Component {
|
||||||
},
|
},
|
||||||
}, [
|
}, [
|
||||||
h('.cell.label'),
|
h('.cell.label'),
|
||||||
h('.cell.value', {
|
h('.cell.value', `Data included: ${dataLength} bytes`),
|
||||||
style: {
|
|
||||||
fontFamily: 'Nunito Regular',
|
|
||||||
fontSize: '14px',
|
|
||||||
},
|
|
||||||
}, `Data included: ${dataLength} bytes`),
|
|
||||||
]),
|
]),
|
||||||
]), // End of Table
|
]), // End of Table
|
||||||
|
|
||||||
|
@ -591,7 +587,8 @@ class PendingTx extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount () {
|
// eslint-disable-next-line camelcase
|
||||||
|
UNSAFE_componentWillMount () {
|
||||||
const txMeta = this.gatherTxMeta()
|
const txMeta = this.gatherTxMeta()
|
||||||
const txParams = txMeta.txParams || {}
|
const txParams = txMeta.txParams || {}
|
||||||
if (this.props.isToken || this.state.isToken) {
|
if (this.props.isToken || this.state.isToken) {
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
export default class AmountMaxButton extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
balance: PropTypes.string,
|
||||||
|
buttonDataLoading: PropTypes.bool,
|
||||||
|
clearMaxAmount: PropTypes.func,
|
||||||
|
inError: PropTypes.bool,
|
||||||
|
gasTotal: PropTypes.string,
|
||||||
|
maxModeOn: PropTypes.bool,
|
||||||
|
sendToken: PropTypes.object,
|
||||||
|
setAmountToMax: PropTypes.func,
|
||||||
|
setMaxModeTo: PropTypes.func,
|
||||||
|
updateGasData: PropTypes.func,
|
||||||
|
tokenBalance: PropTypes.string,
|
||||||
|
address: PropTypes.string,
|
||||||
|
amount: PropTypes.string,
|
||||||
|
to: PropTypes.string,
|
||||||
|
blockGasLimit: PropTypes.string,
|
||||||
|
data: PropTypes.string,
|
||||||
|
}
|
||||||
|
|
||||||
|
async setMaxAmount () {
|
||||||
|
const {
|
||||||
|
updateGasData,
|
||||||
|
address,
|
||||||
|
sendToken,
|
||||||
|
amount: value,
|
||||||
|
to,
|
||||||
|
data,
|
||||||
|
blockGasLimit,
|
||||||
|
setAmountToMax,
|
||||||
|
} = this.props
|
||||||
|
const params = { address, sendToken, blockGasLimit, to, value, data }
|
||||||
|
await updateGasData(params)
|
||||||
|
|
||||||
|
const {
|
||||||
|
balance,
|
||||||
|
gasTotal,
|
||||||
|
tokenBalance,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
setAmountToMax({
|
||||||
|
balance,
|
||||||
|
gasTotal,
|
||||||
|
sendToken,
|
||||||
|
tokenBalance,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMaxClick = () => {
|
||||||
|
const { setMaxModeTo, clearMaxAmount, maxModeOn } = this.props
|
||||||
|
|
||||||
|
if (!maxModeOn) {
|
||||||
|
setMaxModeTo(true)
|
||||||
|
this.setMaxAmount()
|
||||||
|
} else {
|
||||||
|
setMaxModeTo(false)
|
||||||
|
clearMaxAmount()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { maxModeOn, buttonDataLoading, inError } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="send__amount-max secondary-description" onClick={buttonDataLoading || inError ? null : this.onMaxClick}>
|
||||||
|
<input type="checkbox" checked={maxModeOn} readOnly />
|
||||||
|
{'send max amount'}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import {
|
||||||
|
getGasTotal,
|
||||||
|
getSendToken,
|
||||||
|
getSendFromBalance,
|
||||||
|
getTokenBalance,
|
||||||
|
getSendMaxModeState,
|
||||||
|
getSendTo,
|
||||||
|
getSendHexData,
|
||||||
|
} from '../../../../../ui/app/selectors'
|
||||||
|
import { calcMaxAmount } from './amount-max-button.utils.js'
|
||||||
|
import {
|
||||||
|
updateSendAmount,
|
||||||
|
setMaxModeTo,
|
||||||
|
updateGasData,
|
||||||
|
} from '../../../../../ui/app/actions'
|
||||||
|
import AmountMaxButton from './amount-max-button.component'
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(AmountMaxButton)
|
||||||
|
|
||||||
|
function mapStateToProps (state) {
|
||||||
|
|
||||||
|
return {
|
||||||
|
balance: getSendFromBalance(state),
|
||||||
|
gasTotal: getGasTotal(state),
|
||||||
|
maxModeOn: getSendMaxModeState(state),
|
||||||
|
sendToken: getSendToken(state),
|
||||||
|
tokenBalance: getTokenBalance(state),
|
||||||
|
send: state.metamask.send,
|
||||||
|
amount: state.metamask.send.amount,
|
||||||
|
blockGasLimit: state.metamask.currentBlockGasLimit,
|
||||||
|
address: state.metamask.selectedAddress,
|
||||||
|
to: getSendTo(state),
|
||||||
|
data: getSendHexData(state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps (dispatch) {
|
||||||
|
return {
|
||||||
|
setAmountToMax: (maxAmountDataObject) => {
|
||||||
|
dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject)))
|
||||||
|
},
|
||||||
|
clearMaxAmount: () => {
|
||||||
|
dispatch(updateSendAmount('0'))
|
||||||
|
},
|
||||||
|
setMaxModeTo: (bool) => dispatch(setMaxModeTo(bool)),
|
||||||
|
updateGasData: (params) => dispatch(updateGasData(params)),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { subtractCurrencies, BIG_NUMBER_WEI_MULTIPLIER } from '../../../../../ui/app/conversion-util'
|
||||||
|
import ethUtil from 'ethereumjs-util'
|
||||||
|
import BigNumber from 'bignumber.js'
|
||||||
|
|
||||||
|
export function calcMaxAmount ({ balance, gasTotal, sendToken, tokenBalance }) {
|
||||||
|
const { decimals } = sendToken || {}
|
||||||
|
const multiplier = Math.pow(10, Number(decimals || 0))
|
||||||
|
|
||||||
|
let maxBalance
|
||||||
|
if (sendToken) {
|
||||||
|
const tokenBalanceBN = new BigNumber(tokenBalance.toString())
|
||||||
|
maxBalance = tokenBalanceBN.div(multiplier).toString()
|
||||||
|
} else {
|
||||||
|
const maxBalanceInWei =
|
||||||
|
subtractCurrencies(
|
||||||
|
ethUtil.addHexPrefix(balance),
|
||||||
|
ethUtil.addHexPrefix(gasTotal),
|
||||||
|
{ toNumericBase: 'dec' },
|
||||||
|
)
|
||||||
|
const maxBalanceInWeiBN = new BigNumber(maxBalanceInWei.toString())
|
||||||
|
maxBalance = maxBalanceInWeiBN.div(BIG_NUMBER_WEI_MULTIPLIER).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxBalance
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export { default } from './amount-max-button.container'
|
|
@ -0,0 +1,97 @@
|
||||||
|
import React from 'react'
|
||||||
|
import assert from 'assert'
|
||||||
|
import { shallow } from 'enzyme'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
import AmountMaxButton from '../amount-max-button.component.js'
|
||||||
|
|
||||||
|
describe('AmountMaxButton Component', function () {
|
||||||
|
let wrapper
|
||||||
|
let instance
|
||||||
|
|
||||||
|
const propsMethodSpies = {
|
||||||
|
setAmountToMax: sinon.spy(),
|
||||||
|
setMaxModeTo: sinon.spy(),
|
||||||
|
}
|
||||||
|
|
||||||
|
const MOCK_EVENT = { preventDefault: () => {} }
|
||||||
|
|
||||||
|
before(function () {
|
||||||
|
sinon.spy(AmountMaxButton.prototype, 'setMaxAmount')
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
wrapper = shallow((
|
||||||
|
<AmountMaxButton
|
||||||
|
balance="mockBalance"
|
||||||
|
gasTotal="mockGasTotal"
|
||||||
|
maxModeOn={false}
|
||||||
|
sendToken={ { address: 'mockTokenAddress' } }
|
||||||
|
setAmountToMax={propsMethodSpies.setAmountToMax}
|
||||||
|
setMaxModeTo={propsMethodSpies.setMaxModeTo}
|
||||||
|
tokenBalance="mockTokenBalance"
|
||||||
|
/>
|
||||||
|
), {
|
||||||
|
context: {
|
||||||
|
t: (str) => str + '_t',
|
||||||
|
metricsEvent: () => {},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
instance = wrapper.instance()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
propsMethodSpies.setAmountToMax.resetHistory()
|
||||||
|
propsMethodSpies.setMaxModeTo.resetHistory()
|
||||||
|
AmountMaxButton.prototype.setMaxAmount.resetHistory()
|
||||||
|
})
|
||||||
|
|
||||||
|
after(function () {
|
||||||
|
sinon.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('setMaxAmount', function () {
|
||||||
|
|
||||||
|
it('should call setAmountToMax with the correct params', function () {
|
||||||
|
assert.equal(propsMethodSpies.setAmountToMax.callCount, 0)
|
||||||
|
instance.setMaxAmount()
|
||||||
|
assert.equal(propsMethodSpies.setAmountToMax.callCount, 1)
|
||||||
|
assert.deepEqual(
|
||||||
|
propsMethodSpies.setAmountToMax.getCall(0).args,
|
||||||
|
[{
|
||||||
|
balance: 'mockBalance',
|
||||||
|
gasTotal: 'mockGasTotal',
|
||||||
|
sendToken: { address: 'mockTokenAddress' },
|
||||||
|
tokenBalance: 'mockTokenBalance',
|
||||||
|
}],
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('render', function () {
|
||||||
|
it('should render an element with a send-v2__amount-max class', function () {
|
||||||
|
assert(wrapper.exists('.send-v2__amount-max'))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call setMaxModeTo and setMaxAmount when the checkbox is checked', function () {
|
||||||
|
const {
|
||||||
|
onClick,
|
||||||
|
} = wrapper.find('.send-v2__amount-max').props()
|
||||||
|
|
||||||
|
assert.equal(AmountMaxButton.prototype.setMaxAmount.callCount, 0)
|
||||||
|
assert.equal(propsMethodSpies.setMaxModeTo.callCount, 0)
|
||||||
|
onClick(MOCK_EVENT)
|
||||||
|
assert.equal(AmountMaxButton.prototype.setMaxAmount.callCount, 1)
|
||||||
|
assert.equal(propsMethodSpies.setMaxModeTo.callCount, 1)
|
||||||
|
assert.deepEqual(
|
||||||
|
propsMethodSpies.setMaxModeTo.getCall(0).args,
|
||||||
|
[true],
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should render the expected text when maxModeOn is false', function () {
|
||||||
|
wrapper.setProps({ maxModeOn: false })
|
||||||
|
assert.equal(wrapper.find('.send-v2__amount-max').text(), 'max_t')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,93 @@
|
||||||
|
import assert from 'assert'
|
||||||
|
import proxyquire from 'proxyquire'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
let mapStateToProps
|
||||||
|
let mapDispatchToProps
|
||||||
|
|
||||||
|
const actionSpies = {
|
||||||
|
setMaxModeTo: sinon.spy(),
|
||||||
|
updateSendAmount: sinon.spy(),
|
||||||
|
}
|
||||||
|
const duckActionSpies = {
|
||||||
|
updateSendErrors: sinon.spy(),
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyquire('../amount-max-button.container.js', {
|
||||||
|
'react-redux': {
|
||||||
|
connect: (ms, md) => {
|
||||||
|
mapStateToProps = ms
|
||||||
|
mapDispatchToProps = md
|
||||||
|
return () => ({})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'../../../../../selectors': {
|
||||||
|
getGasTotal: (s) => `mockGasTotal:${s}`,
|
||||||
|
getSendToken: (s) => `mockSendToken:${s}`,
|
||||||
|
getSendFromBalance: (s) => `mockBalance:${s}`,
|
||||||
|
getTokenBalance: (s) => `mockTokenBalance:${s}`,
|
||||||
|
getSendMaxModeState: (s) => `mockMaxModeOn:${s}`,
|
||||||
|
getBasicGasEstimateLoadingStatus: (s) => `mockButtonDataLoading:${s}`,
|
||||||
|
},
|
||||||
|
'./amount-max-button.utils.js': { calcMaxAmount: (mockObj) => mockObj.val + 1 },
|
||||||
|
'../../../../../store/actions': actionSpies,
|
||||||
|
'../../../../../ducks/send/send.duck': duckActionSpies,
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('amount-max-button container', function () {
|
||||||
|
|
||||||
|
describe('mapStateToProps()', function () {
|
||||||
|
|
||||||
|
it('should map the correct properties to props', function () {
|
||||||
|
assert.deepEqual(mapStateToProps('mockState'), {
|
||||||
|
balance: 'mockBalance:mockState',
|
||||||
|
buttonDataLoading: 'mockButtonDataLoading:mockState',
|
||||||
|
gasTotal: 'mockGasTotal:mockState',
|
||||||
|
maxModeOn: 'mockMaxModeOn:mockState',
|
||||||
|
sendToken: 'mockSendToken:mockState',
|
||||||
|
tokenBalance: 'mockTokenBalance:mockState',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('mapDispatchToProps()', function () {
|
||||||
|
let dispatchSpy
|
||||||
|
let mapDispatchToPropsObject
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
dispatchSpy = sinon.spy()
|
||||||
|
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('setAmountToMax()', function () {
|
||||||
|
it('should dispatch an action', function () {
|
||||||
|
mapDispatchToPropsObject.setAmountToMax({ val: 11, foo: 'bar' })
|
||||||
|
assert(dispatchSpy.calledTwice)
|
||||||
|
assert(duckActionSpies.updateSendErrors.calledOnce)
|
||||||
|
assert.deepEqual(
|
||||||
|
duckActionSpies.updateSendErrors.getCall(0).args[0],
|
||||||
|
{ amount: null },
|
||||||
|
)
|
||||||
|
assert(actionSpies.updateSendAmount.calledOnce)
|
||||||
|
assert.equal(
|
||||||
|
actionSpies.updateSendAmount.getCall(0).args[0],
|
||||||
|
12,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('setMaxModeTo()', function () {
|
||||||
|
it('should dispatch an action', function () {
|
||||||
|
mapDispatchToPropsObject.setMaxModeTo('mockVal')
|
||||||
|
assert(dispatchSpy.calledOnce)
|
||||||
|
assert.equal(
|
||||||
|
actionSpies.setMaxModeTo.getCall(0).args[0],
|
||||||
|
'mockVal',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
|
@ -0,0 +1,27 @@
|
||||||
|
import assert from 'assert'
|
||||||
|
import {
|
||||||
|
calcMaxAmount,
|
||||||
|
} from '../amount-max-button.utils.js'
|
||||||
|
|
||||||
|
describe('amount-max-button utils', function () {
|
||||||
|
|
||||||
|
describe('calcMaxAmount()', function () {
|
||||||
|
it('should calculate the correct amount when no sendToken defined', function () {
|
||||||
|
assert.deepEqual(calcMaxAmount({
|
||||||
|
balance: 'ffffff',
|
||||||
|
gasTotal: 'ff',
|
||||||
|
sendToken: false,
|
||||||
|
}), 'ffff00')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should calculate the correct amount when a sendToken is defined', function () {
|
||||||
|
assert.deepEqual(calcMaxAmount({
|
||||||
|
sendToken: {
|
||||||
|
decimals: 10,
|
||||||
|
},
|
||||||
|
tokenBalance: '64',
|
||||||
|
}), 'e8d4a51000')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
|
@ -139,7 +139,8 @@ class SendTransactionScreen extends PersistentForm {
|
||||||
PersistentForm.call(this)
|
PersistentForm.call(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount () {
|
// eslint-disable-next-line camelcase
|
||||||
|
UNSAFE_componentWillMount () {
|
||||||
this.getContractMethods()
|
this.getContractMethods()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,8 @@ import log from 'loglevel'
|
||||||
import SendProfile from './send-profile'
|
import SendProfile from './send-profile'
|
||||||
import SendHeader from './send-header'
|
import SendHeader from './send-header'
|
||||||
import ErrorComponent from '../error'
|
import ErrorComponent from '../error'
|
||||||
import { getMetaMaskAccounts } from '../../../../ui/app/selectors'
|
import { getMetaMaskAccounts, getSendToken, getSendTo, getTokenBalance, getSendTokenContract } from '../../../../ui/app/selectors'
|
||||||
|
import AmountMaxButton from './amount-max-button'
|
||||||
|
|
||||||
class SendTransactionScreen extends PersistentForm {
|
class SendTransactionScreen extends PersistentForm {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
|
@ -30,13 +31,12 @@ class SendTransactionScreen extends PersistentForm {
|
||||||
balance: 0,
|
balance: 0,
|
||||||
decimals: 0,
|
decimals: 0,
|
||||||
},
|
},
|
||||||
amount: '',
|
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
}
|
}
|
||||||
PersistentForm.call(this)
|
PersistentForm.call(this)
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
const { isLoading, token, amount } = this.state
|
const { isLoading, token } = this.state
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<Loading isLoading={isLoading} loadingMessage="Loading..." />
|
<Loading isLoading={isLoading} loadingMessage="Loading..." />
|
||||||
|
@ -50,6 +50,7 @@ class SendTransactionScreen extends PersistentForm {
|
||||||
identities,
|
identities,
|
||||||
addressBook,
|
addressBook,
|
||||||
error,
|
error,
|
||||||
|
updateSendTo,
|
||||||
} = props
|
} = props
|
||||||
const nextDisabled = token.balance <= 0
|
const nextDisabled = token.balance <= 0
|
||||||
|
|
||||||
|
@ -63,22 +64,24 @@ class SendTransactionScreen extends PersistentForm {
|
||||||
<EnsInput
|
<EnsInput
|
||||||
name="address"
|
name="address"
|
||||||
placeholder="Recipient Address"
|
placeholder="Recipient Address"
|
||||||
onChange={() => this.recipientDidChange.bind(this)}
|
onChange={this.recipientDidChange.bind(this)}
|
||||||
network={network}
|
network={network}
|
||||||
identities={identities}
|
identities={identities}
|
||||||
addressBook={addressBook}
|
addressBook={addressBook}
|
||||||
|
updateSendTo={updateSendTo}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
<section className="flex-row flex-center">
|
<section className="flex-row flex-center">
|
||||||
<input className="large-input"
|
<input className="large-input"
|
||||||
name="amount"
|
name="amount"
|
||||||
value={amount}
|
value={this.props.amount || ''}
|
||||||
onChange={(e) => this.amountDidChange(e.target.value)}
|
onChange={(e) => this.amountDidChange(e.target.value)}
|
||||||
placeholder="Amount"
|
placeholder="Amount"
|
||||||
type="number"
|
type="number"
|
||||||
style={{
|
style={{
|
||||||
marginRight: '6px',
|
marginRight: '6px',
|
||||||
}}
|
}}
|
||||||
|
disabled={!!this.props.maxModeOn}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={() => this.onSubmit()}
|
onClick={() => this.onSubmit()}
|
||||||
|
@ -86,13 +89,22 @@ class SendTransactionScreen extends PersistentForm {
|
||||||
>Next
|
>Next
|
||||||
</button>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
|
<section className="flex-row flex-left amount-max-container"><AmountMaxButton /></section>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.getTokensMetadata()
|
this.getTokensMetadata()
|
||||||
.then(() => {
|
.then((token) => {
|
||||||
|
this.props.updateSendToken(token)
|
||||||
|
|
||||||
|
const {
|
||||||
|
sendToken,
|
||||||
|
tokenContract,
|
||||||
|
address,
|
||||||
|
} = this.props
|
||||||
|
this.props.updateSendTokenBalance({sendToken, tokenContract, address})
|
||||||
this.createFreshTokenTracker()
|
this.createFreshTokenTracker()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -102,16 +114,17 @@ class SendTransactionScreen extends PersistentForm {
|
||||||
this.tokenInfoGetter = tokenInfoGetter()
|
this.tokenInfoGetter = tokenInfoGetter()
|
||||||
const { tokenAddress, network } = this.props
|
const { tokenAddress, network } = this.props
|
||||||
const { symbol = '', decimals = 0 } = await this.tokenInfoGetter(tokenAddress)
|
const { symbol = '', decimals = 0 } = await this.tokenInfoGetter(tokenAddress)
|
||||||
this.setState({
|
const token = {
|
||||||
token: {
|
|
||||||
address: tokenAddress,
|
address: tokenAddress,
|
||||||
network,
|
network,
|
||||||
symbol,
|
symbol,
|
||||||
decimals,
|
decimals,
|
||||||
},
|
}
|
||||||
|
this.setState({
|
||||||
|
token,
|
||||||
})
|
})
|
||||||
|
|
||||||
return Promise.resolve()
|
return Promise.resolve(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
|
@ -120,6 +133,9 @@ class SendTransactionScreen extends PersistentForm {
|
||||||
this.tracker.stop()
|
this.tracker.stop()
|
||||||
this.tracker.removeListener('update', this.balanceUpdater)
|
this.tracker.removeListener('update', this.balanceUpdater)
|
||||||
this.tracker.removeListener('error', this.showError)
|
this.tracker.removeListener('error', this.showError)
|
||||||
|
this.props.updateSendAmount(null)
|
||||||
|
this.props.setMaxModeTo(false)
|
||||||
|
this.props.updateSendTo('')
|
||||||
}
|
}
|
||||||
|
|
||||||
createFreshTokenTracker () {
|
createFreshTokenTracker () {
|
||||||
|
@ -176,14 +192,13 @@ class SendTransactionScreen extends PersistentForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
amountDidChange (amount) {
|
amountDidChange (amount) {
|
||||||
this.setState({
|
this.props.updateSendAmount(amount)
|
||||||
amount,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async onSubmit () {
|
async onSubmit () {
|
||||||
const state = this.state || {}
|
const state = this.state || {}
|
||||||
const { token, amount } = state
|
const { token } = state
|
||||||
|
const { amount } = this.props
|
||||||
let recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '')
|
let recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '')
|
||||||
let nickname = state.nickname || ' '
|
let nickname = state.nickname || ' '
|
||||||
if (typeof recipient === 'object') {
|
if (typeof recipient === 'object') {
|
||||||
|
@ -284,6 +299,12 @@ const mapStateToProps = (state) => {
|
||||||
network: state.metamask.network,
|
network: state.metamask.network,
|
||||||
addressBook: state.metamask.addressBook,
|
addressBook: state.metamask.addressBook,
|
||||||
tokenAddress: state.appState.currentView.tokenAddress,
|
tokenAddress: state.appState.currentView.tokenAddress,
|
||||||
|
to: getSendTo(state),
|
||||||
|
sendToken: getSendToken(state),
|
||||||
|
amount: state.metamask.send.amount,
|
||||||
|
maxModeOn: state.metamask.send.maxModeOn,
|
||||||
|
tokenBalance: getTokenBalance(state),
|
||||||
|
tokenContract: getSendTokenContract(state),
|
||||||
}
|
}
|
||||||
|
|
||||||
result.error = result.warning && result.warning.split('.')[0]
|
result.error = result.warning && result.warning.split('.')[0]
|
||||||
|
@ -307,6 +328,11 @@ const mapDispatchToProps = dispatch => {
|
||||||
txParams,
|
txParams,
|
||||||
confTxScreenParams,
|
confTxScreenParams,
|
||||||
) => dispatch(actions.signTokenTx(tokenAddress, toAddress, tokensValueWithDec, txParams, confTxScreenParams)),
|
) => dispatch(actions.signTokenTx(tokenAddress, toAddress, tokensValueWithDec, txParams, confTxScreenParams)),
|
||||||
|
updateSendTokenBalance: props => dispatch(actions.updateSendTokenBalance(props)),
|
||||||
|
setMaxModeTo: maxMode => dispatch(actions.setMaxModeTo(maxMode)),
|
||||||
|
updateSendAmount: amount => dispatch(actions.updateSendAmount(amount)),
|
||||||
|
updateSendTo: (to, nickname) => dispatch(actions.updateSendTo(to, nickname)),
|
||||||
|
updateSendToken: token => dispatch(actions.updateSendToken(token)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,14 +14,14 @@ import ethUtil from 'ethereumjs-util'
|
||||||
import SendProfile from './send-profile'
|
import SendProfile from './send-profile'
|
||||||
import SendHeader from './send-header'
|
import SendHeader from './send-header'
|
||||||
import ErrorComponent from '../error'
|
import ErrorComponent from '../error'
|
||||||
import { getMetaMaskAccounts } from '../../../../ui/app/selectors'
|
import { getMetaMaskAccounts, getGasTotal, getTokenBalance, getCurrentEthBalance, getSendToken, getSendTo } from '../../../../ui/app/selectors'
|
||||||
import * as Toast from '../toast'
|
import * as Toast from '../toast'
|
||||||
|
import AmountMaxButton from './amount-max-button'
|
||||||
|
|
||||||
const optionalDataLabelStyle = {
|
const optionalDataLabelStyle = {
|
||||||
background: '#ffffff',
|
background: '#ffffff',
|
||||||
color: '#333333',
|
color: '#333333',
|
||||||
marginTop: '16px',
|
marginTop: '16px',
|
||||||
marginBottom: '16px',
|
|
||||||
}
|
}
|
||||||
const optionalDataValueStyle = {
|
const optionalDataValueStyle = {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
@ -29,6 +29,19 @@ const optionalDataValueStyle = {
|
||||||
}
|
}
|
||||||
|
|
||||||
class SendTransactionScreen extends PersistentForm {
|
class SendTransactionScreen extends PersistentForm {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
pendingNonce: null,
|
||||||
|
recipient: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchPendingNonce () {
|
||||||
|
const pendingNonce = await this.props.getPendingNonce(this.props.address)
|
||||||
|
this.setState({pendingNonce: pendingNonce})
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
this.persistentFormParentId = 'send-tx-form'
|
this.persistentFormParentId = 'send-tx-form'
|
||||||
|
|
||||||
|
@ -38,6 +51,7 @@ class SendTransactionScreen extends PersistentForm {
|
||||||
identities,
|
identities,
|
||||||
addressBook,
|
addressBook,
|
||||||
error,
|
error,
|
||||||
|
updateSendTo,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -61,6 +75,8 @@ class SendTransactionScreen extends PersistentForm {
|
||||||
network={network}
|
network={network}
|
||||||
identities={identities}
|
identities={identities}
|
||||||
addressBook={addressBook}
|
addressBook={addressBook}
|
||||||
|
value={this.state.recipient || ''}
|
||||||
|
updateSendTo={updateSendTo}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
@ -76,6 +92,12 @@ class SendTransactionScreen extends PersistentForm {
|
||||||
dataset={{
|
dataset={{
|
||||||
persistentFormid: 'tx-amount',
|
persistentFormid: 'tx-amount',
|
||||||
}}
|
}}
|
||||||
|
disabled={!!this.props.maxModeOn}
|
||||||
|
value={this.props.amount || ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newAmount = e.target.value
|
||||||
|
this.props.updateSendAmount(newAmount)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
@ -84,6 +106,7 @@ class SendTransactionScreen extends PersistentForm {
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
<section className="flex-row flex-left amount-max-container"><AmountMaxButton /></section>
|
||||||
|
|
||||||
<h3 className="flex-center"
|
<h3 className="flex-center"
|
||||||
style={optionalDataLabelStyle}
|
style={optionalDataLabelStyle}
|
||||||
|
@ -99,6 +122,10 @@ class SendTransactionScreen extends PersistentForm {
|
||||||
dataset={{
|
dataset={{
|
||||||
persistentFormid: 'tx-data',
|
persistentFormid: 'tx-data',
|
||||||
}}
|
}}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newTxData = e.target.value
|
||||||
|
this.props.updateSendHexData(newTxData)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
@ -117,19 +144,31 @@ class SendTransactionScreen extends PersistentForm {
|
||||||
dataset={{
|
dataset={{
|
||||||
persistentFormid: 'tx-custom-nonce',
|
persistentFormid: 'tx-custom-nonce',
|
||||||
}}
|
}}
|
||||||
|
defaultValue={this.state.pendingNonce}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this._isMounted = true
|
||||||
|
if (this._isMounted) {
|
||||||
|
this.fetchPendingNonce()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
this.props.dispatch(actions.displayWarning(''))
|
this.props.displayWarning('')
|
||||||
|
this.props.updateSendAmount(null)
|
||||||
|
this.props.setMaxModeTo(false)
|
||||||
|
this.props.updateSendTo('')
|
||||||
|
this._isMounted = false
|
||||||
}
|
}
|
||||||
|
|
||||||
navigateToAccounts (event) {
|
navigateToAccounts (event) {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
this.props.dispatch(actions.showAccountsPage())
|
this.props.showAccountsPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
recipientDidChange (recipient, nickname) {
|
recipientDidChange (recipient, nickname) {
|
||||||
|
@ -137,6 +176,7 @@ class SendTransactionScreen extends PersistentForm {
|
||||||
recipient: recipient,
|
recipient: recipient,
|
||||||
nickname: nickname,
|
nickname: nickname,
|
||||||
})
|
})
|
||||||
|
this.props.updateSendTo(recipient, nickname)
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit () {
|
onSubmit () {
|
||||||
|
@ -158,14 +198,14 @@ class SendTransactionScreen extends PersistentForm {
|
||||||
|
|
||||||
if (isNaN(input) || input === '') {
|
if (isNaN(input) || input === '') {
|
||||||
message = 'Invalid ether value.'
|
message = 'Invalid ether value.'
|
||||||
return this.props.dispatch(actions.displayWarning(message))
|
return this.props.displayWarning(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parts[1]) {
|
if (parts[1]) {
|
||||||
const decimal = parts[1]
|
const decimal = parts[1]
|
||||||
if (decimal.length > 18) {
|
if (decimal.length > 18) {
|
||||||
message = 'Ether amount is too precise.'
|
message = 'Ether amount is too precise.'
|
||||||
return this.props.dispatch(actions.displayWarning(message))
|
return this.props.displayWarning(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,32 +216,32 @@ class SendTransactionScreen extends PersistentForm {
|
||||||
|
|
||||||
if (value.gt(balance)) {
|
if (value.gt(balance)) {
|
||||||
message = 'Insufficient funds.'
|
message = 'Insufficient funds.'
|
||||||
return this.props.dispatch(actions.displayWarning(message))
|
return this.props.displayWarning(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input < 0) {
|
if (input < 0) {
|
||||||
message = 'Can not send negative amounts of ETH.'
|
message = 'Can not send negative amounts of ETH.'
|
||||||
return this.props.dispatch(actions.displayWarning(message))
|
return this.props.displayWarning(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((isInvalidChecksumAddress(recipient, this.props.network))) {
|
if ((isInvalidChecksumAddress(recipient, this.props.network))) {
|
||||||
message = 'Recipient address checksum is invalid.'
|
message = 'Recipient address checksum is invalid.'
|
||||||
return this.props.dispatch(actions.displayWarning(message))
|
return this.props.displayWarning(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((!isValidAddress(recipient, this.props.network) && !txData) || (!recipient && !txData)) {
|
if ((!isValidAddress(recipient, this.props.network) && !txData) || (!recipient && !txData)) {
|
||||||
message = 'Recipient address is invalid.'
|
message = 'Recipient address is invalid.'
|
||||||
return this.props.dispatch(actions.displayWarning(message))
|
return this.props.displayWarning(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isHex(ethUtil.stripHexPrefix(txData)) && txData) {
|
if (!isHex(ethUtil.stripHexPrefix(txData)) && txData) {
|
||||||
message = 'Transaction data must be hex string.'
|
message = 'Transaction data must be hex string.'
|
||||||
return this.props.dispatch(actions.displayWarning(message))
|
return this.props.displayWarning(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.dispatch(actions.hideWarning())
|
this.props.hideWarning()
|
||||||
|
|
||||||
this.props.dispatch(actions.addToAddressBook(recipient, nickname))
|
this.props.addToAddressBook(recipient, nickname)
|
||||||
|
|
||||||
const txParams = {
|
const txParams = {
|
||||||
from: this.props.address,
|
from: this.props.address,
|
||||||
|
@ -212,19 +252,30 @@ class SendTransactionScreen extends PersistentForm {
|
||||||
if (txData) txParams.data = txData
|
if (txData) txParams.data = txData
|
||||||
if (txCustomNonce) txParams.nonce = '0x' + parseInt(txCustomNonce, 10).toString(16)
|
if (txCustomNonce) txParams.nonce = '0x' + parseInt(txCustomNonce, 10).toString(16)
|
||||||
|
|
||||||
this.props.dispatch(actions.signTx(txParams))
|
this.props.signTx(txParams)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const accounts = getMetaMaskAccounts(state)
|
const accounts = getMetaMaskAccounts(state)
|
||||||
|
const balance = getCurrentEthBalance(state)
|
||||||
|
const gasTotal = getGasTotal(state)
|
||||||
const result = {
|
const result = {
|
||||||
|
send: state.metamask.send,
|
||||||
address: state.metamask.selectedAddress,
|
address: state.metamask.selectedAddress,
|
||||||
accounts,
|
accounts,
|
||||||
identities: state.metamask.identities,
|
identities: state.metamask.identities,
|
||||||
warning: state.appState.warning,
|
warning: state.appState.warning,
|
||||||
network: state.metamask.network,
|
network: state.metamask.network,
|
||||||
addressBook: state.metamask.addressBook,
|
addressBook: state.metamask.addressBook,
|
||||||
|
balance,
|
||||||
|
gasTotal,
|
||||||
|
to: getSendTo(state),
|
||||||
|
sendToken: getSendToken(state),
|
||||||
|
tokenBalance: getTokenBalance(state),
|
||||||
|
amount: state.metamask.send.amount,
|
||||||
|
maxModeOn: state.metamask.send.maxModeOn,
|
||||||
|
blockGasLimit: state.metamask.currentBlockGasLimit,
|
||||||
}
|
}
|
||||||
|
|
||||||
result.error = result.warning && result.warning.split('.')[0]
|
result.error = result.warning && result.warning.split('.')[0]
|
||||||
|
@ -234,4 +285,19 @@ function mapStateToProps (state) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = connect(mapStateToProps)(SendTransactionScreen)
|
function mapDispatchToProps (dispatch) {
|
||||||
|
return {
|
||||||
|
addToAddressBook: (recipient, nickname) => dispatch(actions.addToAddressBook(recipient, nickname)),
|
||||||
|
showAccountsPage: () => dispatch(actions.showAccountsPage()),
|
||||||
|
displayWarning: msg => dispatch(actions.displayWarning(msg)),
|
||||||
|
hideWarning: () => dispatch(actions.hideWarning()),
|
||||||
|
getPendingNonce: address => dispatch(actions.getPendingNonce(address)),
|
||||||
|
signTx: txParams => dispatch(actions.signTx(txParams)),
|
||||||
|
updateSendAmount: amount => dispatch(actions.updateSendAmount(amount)),
|
||||||
|
setMaxModeTo: maxMode => dispatch(actions.setMaxModeTo(maxMode)),
|
||||||
|
updateSendTo: (to, nickname) => dispatch(actions.updateSendTo(to, nickname)),
|
||||||
|
updateSendHexData: txData => dispatch(actions.updateSendHexData(txData)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = connect(mapStateToProps, mapDispatchToProps)(SendTransactionScreen)
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
line-height: 54px;
|
||||||
}
|
}
|
||||||
.hw-connect__connect-btn.disabled {
|
.hw-connect__connect-btn.disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
|
|
@ -630,10 +630,6 @@ input.large-input {
|
||||||
|
|
||||||
/* accounts screen */
|
/* accounts screen */
|
||||||
|
|
||||||
.identity-section {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.identity-section .identity-panel {
|
.identity-section .identity-panel {
|
||||||
background: #E9E9E9;
|
background: #E9E9E9;
|
||||||
border-bottom: 1px solid #B1B1B1;
|
border-bottom: 1px solid #B1B1B1;
|
||||||
|
@ -670,10 +666,6 @@ input.large-input {
|
||||||
flex-grow: 10;
|
flex-grow: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.name-label{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.unapproved-tx-icon {
|
.unapproved-tx-icon {
|
||||||
height: 16px;
|
height: 16px;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
|
@ -735,14 +727,14 @@ input.large-input {
|
||||||
|
|
||||||
/* Send Screen */
|
/* Send Screen */
|
||||||
|
|
||||||
.send-screen {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.send-screen section {
|
.send-screen section {
|
||||||
margin: 10px 30px;
|
margin: 10px 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.send-screen section.amount-max-container {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.send-screen input {
|
.send-screen input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
@ -750,17 +742,34 @@ input.large-input {
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.send__amount-max {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send__amount-max input {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary-description {
|
||||||
|
font-family: 'Nunito Regular';
|
||||||
|
font-size: 14px
|
||||||
|
}
|
||||||
|
|
||||||
/* Ether Balance Widget */
|
/* Ether Balance Widget */
|
||||||
|
|
||||||
.ether-balance-label {
|
.ether-balance-label {
|
||||||
color: #ABA9AA;
|
color: #ABA9AA;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-size{
|
.icon-size {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info{
|
.info {
|
||||||
font-family: 'Nunito Regular';
|
font-family: 'Nunito Regular';
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -829,10 +838,6 @@ input.large-input {
|
||||||
font-family: Nunito Semibold;
|
font-family: Nunito Semibold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buy-radio {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.eth-warning{
|
.eth-warning{
|
||||||
transition: opacity 400ms ease-in, transform 400ms ease-in;
|
transition: opacity 400ms ease-in, transform 400ms ease-in;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,28 @@
|
||||||
const inherits = require('util').inherits
|
import { Component } from 'react'
|
||||||
const Component = require('react').Component
|
import { connect } from 'react-redux'
|
||||||
const connect = require('react-redux').connect
|
import PropTypes from 'prop-types'
|
||||||
const h = require('react-hyperscript')
|
const h = require('react-hyperscript')
|
||||||
const actions = require('../../../../ui/app/actions')
|
const { confirmSeedWords, showAccountDetail } = require('../../../../ui/app/actions')
|
||||||
const exportAsFile = require('../../util').exportAsFile
|
const { exportAsFile } = require('../../util')
|
||||||
|
|
||||||
module.exports = connect(mapStateToProps)(CreateVaultCompleteScreen)
|
class CreateVaultCompleteScreen extends Component {
|
||||||
|
|
||||||
inherits(CreateVaultCompleteScreen, Component)
|
static propTypes = {
|
||||||
function CreateVaultCompleteScreen () {
|
seed: PropTypes.string,
|
||||||
Component.call(this)
|
cachedSeed: PropTypes.string,
|
||||||
}
|
confirmSeedWords: PropTypes.func,
|
||||||
|
showAccountDetail: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
render () {
|
||||||
return {
|
|
||||||
seed: state.appState.currentView.seedWords,
|
|
||||||
cachedSeed: state.metamask.seedWords,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateVaultCompleteScreen.prototype.render = function () {
|
|
||||||
const state = this.props
|
const state = this.props
|
||||||
const seed = state.seed || state.cachedSeed || ''
|
const seed = state.seed || state.cachedSeed || ''
|
||||||
|
const wordsCount = seed.split(' ').length
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
h('.initialize-screen.flex-column.flex-center.flex-grow', [
|
h('.initialize-screen.flex-column.flex-center.flex-grow', [
|
||||||
|
|
||||||
// // subtitle and nav
|
|
||||||
// h('.section-title.flex-row.flex-center', [
|
|
||||||
// h('h2.page-subtitle', 'Vault Created'),
|
|
||||||
// ]),
|
|
||||||
|
|
||||||
h('h3.flex-center.section-title', {
|
h('h3.flex-center.section-title', {
|
||||||
style: {
|
style: {
|
||||||
background: '#ffffff',
|
background: '#ffffff',
|
||||||
|
@ -51,7 +42,7 @@ CreateVaultCompleteScreen.prototype.render = function () {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
},
|
},
|
||||||
}, [
|
}, [
|
||||||
h('div.error', 'These 12 words are the only way to restore your Nifty Wallet accounts.\nSave them somewhere safe and secret.'),
|
h('div.error', `These ${wordsCount} words are the only way to restore your Nifty Wallet accounts.\nSave them somewhere safe and secret.`),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
h('textarea.twelve-word-phrase', {
|
h('textarea.twelve-word-phrase', {
|
||||||
|
@ -78,12 +69,29 @@ CreateVaultCompleteScreen.prototype.render = function () {
|
||||||
}, 'Save Seed Words As File'),
|
}, 'Save Seed Words As File'),
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmSeedWords () {
|
||||||
|
return this.props.confirmSeedWords()
|
||||||
|
}
|
||||||
|
|
||||||
|
showAccountDetail (account) {
|
||||||
|
return this.props.showAccountDetail(account)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateVaultCompleteScreen.prototype.confirmSeedWords = function () {
|
function mapStateToProps (state) {
|
||||||
return this.props.dispatch(actions.confirmSeedWords())
|
return {
|
||||||
|
seed: state.appState.currentView.seedWords,
|
||||||
|
cachedSeed: state.metamask.seedWords,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateVaultCompleteScreen.prototype.showAccountDetail = function (account) {
|
function mapDispatchToProps (dispatch) {
|
||||||
return this.props.dispatch(actions.showAccountDetail(account))
|
return {
|
||||||
|
confirmSeedWords: () => dispatch(confirmSeedWords()),
|
||||||
|
showAccountDetail: (account) => dispatch(showAccountDetail(account)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports = connect(mapStateToProps, mapDispatchToProps)(CreateVaultCompleteScreen)
|
||||||
|
|
|
@ -179,8 +179,9 @@ RestoreVaultScreen.prototype.createNewVaultAndRestore = function () {
|
||||||
this.props.dispatch(actions.displayWarning(this.warning))
|
this.props.dispatch(actions.displayWarning(this.warning))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (seed.split(' ').length !== 12) {
|
const wordsCount = seed.split(' ').length
|
||||||
this.warning = 'seed phrases are 12 words long'
|
if (wordsCount !== 12 && wordsCount !== 24) {
|
||||||
|
this.warning = 'seed phrases are 12 or 24 words long'
|
||||||
this.props.dispatch(actions.displayWarning(this.warning))
|
this.props.dispatch(actions.displayWarning(this.warning))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ module.exports = {
|
||||||
isAllOneCase,
|
isAllOneCase,
|
||||||
isValidAddress,
|
isValidAddress,
|
||||||
isValidENSAddress,
|
isValidENSAddress,
|
||||||
|
isValidRNSAddress,
|
||||||
numericBalance,
|
numericBalance,
|
||||||
parseBalance,
|
parseBalance,
|
||||||
formatBalance,
|
formatBalance,
|
||||||
|
@ -139,6 +140,10 @@ function isValidENSAddress (address) {
|
||||||
return address.match(/^.{7,}\.(eth|test)$/)
|
return address.match(/^.{7,}\.(eth|test)$/)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isValidRNSAddress (address) {
|
||||||
|
return address.match(/^[a-z0-9]+\.rsk$/)
|
||||||
|
}
|
||||||
|
|
||||||
function isInvalidChecksumAddress (address, network) {
|
function isInvalidChecksumAddress (address, network) {
|
||||||
const prefixed = ethUtil.addHexPrefix(address)
|
const prefixed = ethUtil.addHexPrefix(address)
|
||||||
if (address === '0x0000000000000000000000000000000000000000') return false
|
if (address === '0x0000000000000000000000000000000000000000') return false
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
|
@ -85,6 +85,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.5.5",
|
"@babel/runtime": "^7.5.5",
|
||||||
"@material-ui/core": "^4.1.1",
|
"@material-ui/core": "^4.1.1",
|
||||||
|
"@rsksmart/rns-registry": "^1.0.4",
|
||||||
"@rsksmart/rsk-contract-metadata": "github:rsksmart/rsk-contract-metadata#master",
|
"@rsksmart/rsk-contract-metadata": "github:rsksmart/rsk-contract-metadata#master",
|
||||||
"@rsksmart/rsk-testnet-contract-metadata": "github:rsksmart/rsk-testnet-contract-metadata#master",
|
"@rsksmart/rsk-testnet-contract-metadata": "github:rsksmart/rsk-testnet-contract-metadata#master",
|
||||||
"@zxing/library": "^0.8.0",
|
"@zxing/library": "^0.8.0",
|
||||||
|
@ -142,6 +143,7 @@
|
||||||
"fast-levenshtein": "^2.0.6",
|
"fast-levenshtein": "^2.0.6",
|
||||||
"fuse.js": "^3.2.0",
|
"fuse.js": "^3.2.0",
|
||||||
"gaba": "^1.9.3",
|
"gaba": "^1.9.3",
|
||||||
|
"gas-price-oracle": "^0.1.4",
|
||||||
"human-standard-token-abi": "^2.0.0",
|
"human-standard-token-abi": "^2.0.0",
|
||||||
"idb-global": "^2.1.0",
|
"idb-global": "^2.1.0",
|
||||||
"iframe-stream": "^3.0.0",
|
"iframe-stream": "^3.0.0",
|
||||||
|
@ -155,7 +157,7 @@
|
||||||
"lodash.uniqby": "^4.7.0",
|
"lodash.uniqby": "^4.7.0",
|
||||||
"loglevel": "^1.4.1",
|
"loglevel": "^1.4.1",
|
||||||
"metamascara": "^2.0.0",
|
"metamascara": "^2.0.0",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.5",
|
||||||
"multihashes": "^0.4.12",
|
"multihashes": "^0.4.12",
|
||||||
"nanoid": "^2.1.6",
|
"nanoid": "^2.1.6",
|
||||||
"nifty-wallet-inpage-provider": "github:poanetwork/nifty-wallet-inpage-provider#1.5.1",
|
"nifty-wallet-inpage-provider": "github:poanetwork/nifty-wallet-inpage-provider#1.5.1",
|
||||||
|
@ -228,7 +230,7 @@
|
||||||
"@storybook/addon-info": "^5.3.14",
|
"@storybook/addon-info": "^5.3.14",
|
||||||
"@storybook/addon-knobs": "^5.3.14",
|
"@storybook/addon-knobs": "^5.3.14",
|
||||||
"@storybook/react": "^5.3.14",
|
"@storybook/react": "^5.3.14",
|
||||||
"addons-linter": "^1.22.0",
|
"addons-linter": "^1.25.0",
|
||||||
"babel-eslint": "^10.0.2",
|
"babel-eslint": "^10.0.2",
|
||||||
"babel-loader": "^8.0.6",
|
"babel-loader": "^8.0.6",
|
||||||
"babelify": "^10.0.0",
|
"babelify": "^10.0.0",
|
||||||
|
@ -236,7 +238,7 @@
|
||||||
"browserify": "^16.2.3",
|
"browserify": "^16.2.3",
|
||||||
"browserify-derequire": "^1.0.1",
|
"browserify-derequire": "^1.0.1",
|
||||||
"chai": "^4.1.0",
|
"chai": "^4.1.0",
|
||||||
"chromedriver": "^80.0.2",
|
"chromedriver": "^83.0.0",
|
||||||
"clipboardy": "^1.2.3",
|
"clipboardy": "^1.2.3",
|
||||||
"compression": "^1.7.1",
|
"compression": "^1.7.1",
|
||||||
"coveralls": "^3.0.0",
|
"coveralls": "^3.0.0",
|
||||||
|
@ -275,14 +277,14 @@
|
||||||
"gulp-util": "^3.0.7",
|
"gulp-util": "^3.0.7",
|
||||||
"gulp-watch": "^5.0.1",
|
"gulp-watch": "^5.0.1",
|
||||||
"gulp-zip": "^4.0.0",
|
"gulp-zip": "^4.0.0",
|
||||||
"http-server": "^0.12.1",
|
"http-server": "^0.12.3",
|
||||||
"image-size": "^0.6.2",
|
"image-size": "^0.6.2",
|
||||||
"isomorphic-fetch": "^2.2.1",
|
"isomorphic-fetch": "^2.2.1",
|
||||||
"jsdoc": "^3.6.3",
|
"jsdoc": "^3.6.3",
|
||||||
"jsdom": "^11.2.0",
|
"jsdom": "^11.2.0",
|
||||||
"jsdom-global": "^3.0.2",
|
"jsdom-global": "^3.0.2",
|
||||||
"jshint-stylish": "~2.2.1",
|
"jshint-stylish": "~2.2.1",
|
||||||
"karma": "^4.4.1",
|
"karma": "^5.0.9",
|
||||||
"karma-chrome-launcher": "^2.2.0",
|
"karma-chrome-launcher": "^2.2.0",
|
||||||
"karma-cli": "^1.0.1",
|
"karma-cli": "^1.0.1",
|
||||||
"karma-firefox-launcher": "^1.0.1",
|
"karma-firefox-launcher": "^1.0.1",
|
||||||
|
@ -293,7 +295,7 @@
|
||||||
"mocha-jsdom": "^1.1.0",
|
"mocha-jsdom": "^1.1.0",
|
||||||
"mocha-sinon": "^2.0.0",
|
"mocha-sinon": "^2.0.0",
|
||||||
"nock": "^9.0.14",
|
"nock": "^9.0.14",
|
||||||
"node-sass": "^4.12.0",
|
"node-sass": "^4.14.1",
|
||||||
"nyc": "^15.0.0",
|
"nyc": "^15.0.0",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"png-file-stream": "^1.1.0",
|
"png-file-stream": "^1.1.0",
|
||||||
|
@ -317,7 +319,7 @@
|
||||||
"source-map": "^0.7.2",
|
"source-map": "^0.7.2",
|
||||||
"static-server": "^2.2.1",
|
"static-server": "^2.2.1",
|
||||||
"style-loader": "^0.21.0",
|
"style-loader": "^0.21.0",
|
||||||
"stylelint": "^9.10.1",
|
"stylelint": "^13.6.0",
|
||||||
"stylelint-config-standard": "^18.2.0",
|
"stylelint-config-standard": "^18.2.0",
|
||||||
"tape": "^4.5.1",
|
"tape": "^4.5.1",
|
||||||
"testem": "^2.16.0",
|
"testem": "^2.16.0",
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const { screens, menus, NETWORKS } = require('../elements')
|
const { screens, menus, NETWORKS } = require('../elements')
|
||||||
const testSeedPhrase = 'horn among position unable audit puzzle cannon apology gun autumn plug parrot'
|
const testSeedPhrase = 'horn among position unable audit puzzle cannon apology gun autumn plug parrot'
|
||||||
|
const test24SeedPhrase = 'gravity trophy shrimp suspect sheriff avocado label trust dove tragic pitch title network myself spell task protect smooth sword diary brain blossom under bulb'
|
||||||
|
|
||||||
const importGanacheSeedPhrase = async (f, account2, password) => {
|
const importGanacheSeedPhrase = async (f, account2, password) => {
|
||||||
it('logs out', async () => {
|
it('logs out', async () => {
|
||||||
|
@ -12,6 +13,38 @@ const importGanacheSeedPhrase = async (f, account2, password) => {
|
||||||
await logOut.click()
|
await logOut.click()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('restores from 24 seed phrase', async () => {
|
||||||
|
const restoreSeedLink = await f.waitUntilShowUp(screens.lock.linkRestore)
|
||||||
|
assert.equal(await restoreSeedLink.getText(), screens.lock.linkRestoreText)
|
||||||
|
await restoreSeedLink.click()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds 24 words seed phrase', async () => {
|
||||||
|
const seedTextArea = await f.waitUntilShowUp(screens.restoreVault.textArea)
|
||||||
|
await seedTextArea.sendKeys(test24SeedPhrase)
|
||||||
|
|
||||||
|
let field = await f.driver.findElement(screens.restoreVault.fieldPassword)
|
||||||
|
await field.sendKeys(password)
|
||||||
|
field = await f.driver.findElement(screens.restoreVault.fieldPasswordConfirm)
|
||||||
|
await field.sendKeys(password)
|
||||||
|
field = await f.waitUntilShowUp(screens.restoreVault.buttos.ok)
|
||||||
|
await f.click(field)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('balance renders', async () => {
|
||||||
|
const balance = await f.waitUntilShowUp(screens.main.balance)
|
||||||
|
assert.equal(await balance.getText(), '0', "balance isn't correct")
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs out', async () => {
|
||||||
|
await f.setProvider(NETWORKS.LOCALHOST)
|
||||||
|
const menu = await f.waitUntilShowUp(menus.sandwich.menu)
|
||||||
|
await menu.click()
|
||||||
|
const logOut = await f.waitUntilShowUp(menus.sandwich.logOut)
|
||||||
|
assert.equal(await logOut.getText(), menus.sandwich.textLogOut)
|
||||||
|
await logOut.click()
|
||||||
|
})
|
||||||
|
|
||||||
it('restores from seed phrase', async () => {
|
it('restores from seed phrase', async () => {
|
||||||
const restoreSeedLink = await f.waitUntilShowUp(screens.lock.linkRestore)
|
const restoreSeedLink = await f.waitUntilShowUp(screens.lock.linkRestore)
|
||||||
assert.equal(await restoreSeedLink.getText(), screens.lock.linkRestoreText)
|
assert.equal(await restoreSeedLink.getText(), screens.lock.linkRestoreText)
|
||||||
|
|
|
@ -71,7 +71,7 @@ describe('', function () {
|
||||||
assert.deepEqual(exchanges, [
|
assert.deepEqual(exchanges, [
|
||||||
{
|
{
|
||||||
name: 'Binance',
|
name: 'Binance',
|
||||||
link: 'https://www.binance.com/en/trade/POA_ETH',
|
link: 'https://www.binance.com/en/trade/POA_BTC',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'BiBox',
|
name: 'BiBox',
|
||||||
|
|
|
@ -137,7 +137,7 @@ describe('MetaMaskController', function () {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const gasPrice = await metamaskController.getGasPrice()
|
const gasPrice = await metamaskController.getGasPriceFromBlocks(1)
|
||||||
assert.equal(gasPrice, '0x174876e800', 'accurately estimates 65th percentile accepted gas price')
|
assert.equal(gasPrice, '0x174876e800', 'accurately estimates 65th percentile accepted gas price')
|
||||||
|
|
||||||
metamaskController.recentBlocksController = realRecentBlocksController
|
metamaskController.recentBlocksController = realRecentBlocksController
|
||||||
|
|
|
@ -3,7 +3,7 @@ const Transaction = require('ethereumjs-tx')
|
||||||
|
|
||||||
|
|
||||||
const { hexToBn, bnToHex } = require('../../../../../app/scripts/lib/util')
|
const { hexToBn, bnToHex } = require('../../../../../app/scripts/lib/util')
|
||||||
const TxUtils = require('../../../../../app/scripts/controllers/transactions/tx-gas-utils')
|
import TxUtils from '../../../../../app/scripts/controllers/transactions/tx-gas-utils'
|
||||||
|
|
||||||
|
|
||||||
describe('txUtils', function () {
|
describe('txUtils', function () {
|
||||||
|
|
|
@ -188,6 +188,7 @@ const actions = {
|
||||||
signTokenTx: signTokenTx,
|
signTokenTx: signTokenTx,
|
||||||
updateTransaction,
|
updateTransaction,
|
||||||
updateAndApproveTx,
|
updateAndApproveTx,
|
||||||
|
getPendingNonce,
|
||||||
cancelTx,
|
cancelTx,
|
||||||
cancelTxs,
|
cancelTxs,
|
||||||
completedTx: completedTx,
|
completedTx: completedTx,
|
||||||
|
@ -211,6 +212,7 @@ const actions = {
|
||||||
UPDATE_SEND_ERRORS: 'UPDATE_SEND_ERRORS',
|
UPDATE_SEND_ERRORS: 'UPDATE_SEND_ERRORS',
|
||||||
UPDATE_MAX_MODE: 'UPDATE_MAX_MODE',
|
UPDATE_MAX_MODE: 'UPDATE_MAX_MODE',
|
||||||
UPDATE_SEND: 'UPDATE_SEND',
|
UPDATE_SEND: 'UPDATE_SEND',
|
||||||
|
UPDATE_SEND_TOKEN: 'UPDATE_SEND_TOKEN',
|
||||||
CLEAR_SEND: 'CLEAR_SEND',
|
CLEAR_SEND: 'CLEAR_SEND',
|
||||||
OPEN_FROM_DROPDOWN: 'OPEN_FROM_DROPDOWN',
|
OPEN_FROM_DROPDOWN: 'OPEN_FROM_DROPDOWN',
|
||||||
CLOSE_FROM_DROPDOWN: 'CLOSE_FROM_DROPDOWN',
|
CLOSE_FROM_DROPDOWN: 'CLOSE_FROM_DROPDOWN',
|
||||||
|
@ -229,6 +231,7 @@ const actions = {
|
||||||
updateSendMemo,
|
updateSendMemo,
|
||||||
setMaxModeTo,
|
setMaxModeTo,
|
||||||
updateSend,
|
updateSend,
|
||||||
|
updateSendToken,
|
||||||
updateSendErrors,
|
updateSendErrors,
|
||||||
clearSend,
|
clearSend,
|
||||||
setSelectedAddress,
|
setSelectedAddress,
|
||||||
|
@ -1060,7 +1063,6 @@ function setGasTotal (gasTotal) {
|
||||||
|
|
||||||
function updateGasData ({
|
function updateGasData ({
|
||||||
blockGasLimit,
|
blockGasLimit,
|
||||||
recentBlocks,
|
|
||||||
selectedAddress,
|
selectedAddress,
|
||||||
selectedToken,
|
selectedToken,
|
||||||
to,
|
to,
|
||||||
|
@ -1099,11 +1101,13 @@ function updateGasData ({
|
||||||
dispatch(actions.setGasTotal(gasEstimate))
|
dispatch(actions.setGasTotal(gasEstimate))
|
||||||
dispatch(updateSendErrors({ gasLoadingError: null }))
|
dispatch(updateSendErrors({ gasLoadingError: null }))
|
||||||
dispatch(actions.gasLoadingFinished())
|
dispatch(actions.gasLoadingFinished())
|
||||||
|
return Promise.resolve()
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
log.error(err)
|
log.error(err)
|
||||||
dispatch(updateSendErrors({ gasLoadingError: 'gasLoadingError' }))
|
dispatch(updateSendErrors({ gasLoadingError: 'gasLoadingError' }))
|
||||||
dispatch(actions.gasLoadingFinished())
|
dispatch(actions.gasLoadingFinished())
|
||||||
|
return Promise.reject()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1131,7 +1135,7 @@ function showChooseContractExecutorPage ({methodSelected, methodABI, inputValues
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSendTokenBalance ({
|
function updateSendTokenBalance ({
|
||||||
selectedToken,
|
sendToken,
|
||||||
tokenContract,
|
tokenContract,
|
||||||
address,
|
address,
|
||||||
}) {
|
}) {
|
||||||
|
@ -1142,7 +1146,7 @@ function updateSendTokenBalance ({
|
||||||
return tokenBalancePromise
|
return tokenBalancePromise
|
||||||
.then(usersToken => {
|
.then(usersToken => {
|
||||||
if (usersToken) {
|
if (usersToken) {
|
||||||
const newTokenBalance = calcTokenBalance({ selectedToken, usersToken })
|
const newTokenBalance = calcTokenBalance({ sendToken, usersToken })
|
||||||
dispatch(setSendTokenBalance(newTokenBalance.toString(10)))
|
dispatch(setSendTokenBalance(newTokenBalance.toString(10)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1216,6 +1220,13 @@ function updateSend (newSend) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateSendToken (token) {
|
||||||
|
return {
|
||||||
|
type: actions.UPDATE_SEND_TOKEN,
|
||||||
|
value: token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function clearSend () {
|
function clearSend () {
|
||||||
return {
|
return {
|
||||||
type: actions.CLEAR_SEND,
|
type: actions.CLEAR_SEND,
|
||||||
|
@ -1304,6 +1315,24 @@ function updateAndApproveTx (txData) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPendingNonce (address) {
|
||||||
|
log.info('actions: getPendingNonce')
|
||||||
|
return (dispatch) => {
|
||||||
|
log.debug(`actions calling background.getPendingNonce`)
|
||||||
|
dispatch(actions.showLoadingIndication())
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
background.getPendingNonce(address, (err, nonce) => {
|
||||||
|
if (err) {
|
||||||
|
dispatch(actions.displayWarning(err.message))
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
dispatch(actions.hideLoadingIndication())
|
||||||
|
resolve(nonce)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function completedTx (id) {
|
function completedTx (id) {
|
||||||
return {
|
return {
|
||||||
type: actions.COMPLETED_TX,
|
type: actions.COMPLETED_TX,
|
||||||
|
|
|
@ -248,4 +248,5 @@ module.exports = {
|
||||||
conversionMax,
|
conversionMax,
|
||||||
toNegative,
|
toNegative,
|
||||||
subtractCurrencies,
|
subtractCurrencies,
|
||||||
|
BIG_NUMBER_WEI_MULTIPLIER,
|
||||||
}
|
}
|
||||||
|
|
|
@ -283,6 +283,35 @@ function reduceMetamask (state, action) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
case actions.UPDATE_SEND_TOKEN:
|
||||||
|
const newSend = {
|
||||||
|
...metamaskState.send,
|
||||||
|
token: action.value,
|
||||||
|
}
|
||||||
|
// erase token-related state when switching back to native currency
|
||||||
|
if (newSend.editingTransactionId && !newSend.token) {
|
||||||
|
const unapprovedTx = newSend?.unapprovedTxs?.[newSend.editingTransactionId] || {}
|
||||||
|
const txParams = unapprovedTx.txParams || {}
|
||||||
|
Object.assign(newSend, {
|
||||||
|
tokenBalance: null,
|
||||||
|
balance: '0',
|
||||||
|
from: unapprovedTx.from || '',
|
||||||
|
unapprovedTxs: {
|
||||||
|
...newSend.unapprovedTxs,
|
||||||
|
[newSend.editingTransactionId]: {
|
||||||
|
...unapprovedTx,
|
||||||
|
txParams: {
|
||||||
|
...txParams,
|
||||||
|
data: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return Object.assign(metamaskState, {
|
||||||
|
send: newSend,
|
||||||
|
})
|
||||||
|
|
||||||
case actions.CLEAR_SEND:
|
case actions.CLEAR_SEND:
|
||||||
return extend(metamaskState, {
|
return extend(metamaskState, {
|
||||||
send: {
|
send: {
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
const abi = require('human-standard-token-abi')
|
import abi from 'human-standard-token-abi'
|
||||||
|
import { pipe } from 'ramda'
|
||||||
import {
|
import {
|
||||||
transactionsSelector,
|
transactionsSelector,
|
||||||
} from './selectors/transactions'
|
} from './selectors/transactions'
|
||||||
const {
|
import { addHexPrefix } from 'ethereumjs-util'
|
||||||
|
import {
|
||||||
|
conversionUtil,
|
||||||
multiplyCurrencies,
|
multiplyCurrencies,
|
||||||
} = require('./conversion-util')
|
} from './conversion-util'
|
||||||
|
import {
|
||||||
|
calcGasTotal,
|
||||||
|
} from './components/send/send.utils'
|
||||||
|
|
||||||
const selectors = {
|
const selectors = {
|
||||||
getSelectedAddress,
|
getSelectedAddress,
|
||||||
|
@ -33,6 +39,21 @@ const selectors = {
|
||||||
preferencesSelector,
|
preferencesSelector,
|
||||||
getMetaMaskAccounts,
|
getMetaMaskAccounts,
|
||||||
getUsePhishDetect,
|
getUsePhishDetect,
|
||||||
|
getGasLimit,
|
||||||
|
getGasPrice,
|
||||||
|
getGasTotal,
|
||||||
|
getGasPriceInHexWei,
|
||||||
|
priceEstimateToWei,
|
||||||
|
getCurrentEthBalance,
|
||||||
|
getSendToken,
|
||||||
|
getSendTokenAddress,
|
||||||
|
getSendTokenContract,
|
||||||
|
getTokenBalance,
|
||||||
|
getSendFromBalance,
|
||||||
|
getSendFromObject,
|
||||||
|
getSendTo,
|
||||||
|
getSendHexData,
|
||||||
|
getTargetAccount,
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = selectors
|
module.exports = selectors
|
||||||
|
@ -151,12 +172,20 @@ function getSendFrom (state) {
|
||||||
return state.metamask.send.from
|
return state.metamask.send.from
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSendTo (state) {
|
||||||
|
return state.metamask.send.to
|
||||||
|
}
|
||||||
|
|
||||||
function getSendAmount (state) {
|
function getSendAmount (state) {
|
||||||
return state.metamask.send.amount
|
return state.metamask.send.amount
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSendMaxModeState (state) {
|
function getSendMaxModeState (state) {
|
||||||
return state.metamask.send.maxModeOn
|
return state.metamask.send.maxModeOn || false
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSendHexData (state) {
|
||||||
|
return state.metamask.send.data
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentCurrency (state) {
|
function getCurrentCurrency (state) {
|
||||||
|
@ -203,3 +232,73 @@ function getTotalUnapprovedCount ({ metamask }) {
|
||||||
function preferencesSelector ({ metamask }) {
|
function preferencesSelector ({ metamask }) {
|
||||||
return metamask.preferences
|
return metamask.preferences
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getGasLimit (state) {
|
||||||
|
return state.metamask.send.gasLimit || '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGasPrice (state) {
|
||||||
|
return state.metamask.send.gasPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGasTotal (state) {
|
||||||
|
return calcGasTotal(getGasLimit(state), getGasPrice(state))
|
||||||
|
}
|
||||||
|
|
||||||
|
function priceEstimateToWei (priceEstimate) {
|
||||||
|
return conversionUtil(priceEstimate, {
|
||||||
|
fromNumericBase: 'hex',
|
||||||
|
toNumericBase: 'hex',
|
||||||
|
fromDenomination: 'GWEI',
|
||||||
|
toDenomination: 'WEI',
|
||||||
|
numberOfDecimals: 9,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGasPriceInHexWei (price) {
|
||||||
|
return pipe(
|
||||||
|
(x) => conversionUtil(x, { fromNumericBase: 'dec', toNumericBase: 'hex' }),
|
||||||
|
priceEstimateToWei,
|
||||||
|
addHexPrefix,
|
||||||
|
)(price)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentEthBalance (state) {
|
||||||
|
return getCurrentAccountWithSendEtherInfo(state).balance
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSendToken (state) {
|
||||||
|
return state.metamask.send.token
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSendTokenAddress (state) {
|
||||||
|
return getSendToken(state)?.address
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSendTokenContract (state) {
|
||||||
|
const sendTokenAddress = getSendTokenAddress(state)
|
||||||
|
return sendTokenAddress
|
||||||
|
? global.eth.contract(abi).at(sendTokenAddress)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTokenBalance (state) {
|
||||||
|
return state.metamask.send.tokenBalance
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSendFromBalance (state) {
|
||||||
|
const fromAccount = getSendFromObject(state)
|
||||||
|
return fromAccount.balance
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSendFromObject (state) {
|
||||||
|
const fromAddress = getSendFrom(state)
|
||||||
|
return fromAddress
|
||||||
|
? getTargetAccount(state, fromAddress)
|
||||||
|
: getSelectedAccount(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTargetAccount (state, targetAddress) {
|
||||||
|
const accounts = getMetaMaskAccounts(state)
|
||||||
|
return accounts[targetAddress]
|
||||||
|
}
|
||||||
|
|
|
@ -25,7 +25,8 @@ class WelcomeScreen extends Component {
|
||||||
this.animationEventEmitter = new EventEmitter()
|
this.animationEventEmitter = new EventEmitter()
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount () {
|
// eslint-disable-next-line camelcase
|
||||||
|
UNSAFE_componentWillMount () {
|
||||||
const { history, welcomeScreenSeen } = this.props
|
const { history, welcomeScreenSeen } = this.props
|
||||||
|
|
||||||
if (welcomeScreenSeen) {
|
if (welcomeScreenSeen) {
|
||||||
|
|
Loading…
Reference in New Issue