Merge remote-tracking branch 'origin/develop' into add-tokens-validation
This commit is contained in:
commit
53b3802396
|
@ -15,6 +15,7 @@ const UnlockScreen = require('./unlock')
|
||||||
// accounts
|
// accounts
|
||||||
const AccountDetailScreen = require('./account-detail')
|
const AccountDetailScreen = require('./account-detail')
|
||||||
const SendTransactionScreen = require('./send')
|
const SendTransactionScreen = require('./send')
|
||||||
|
const SendTokenScreen = require('./send-token')
|
||||||
const ConfirmTxScreen = require('./conf-tx')
|
const ConfirmTxScreen = require('./conf-tx')
|
||||||
// notice
|
// notice
|
||||||
const NoticeScreen = require('./components/notice')
|
const NoticeScreen = require('./components/notice')
|
||||||
|
@ -577,6 +578,10 @@ App.prototype.renderPrimary = function () {
|
||||||
log.debug('rendering send tx screen')
|
log.debug('rendering send tx screen')
|
||||||
return h(SendTransactionScreen, {key: 'send-transaction'})
|
return h(SendTransactionScreen, {key: 'send-transaction'})
|
||||||
|
|
||||||
|
case 'sendToken':
|
||||||
|
log.debug('rendering send tx screen')
|
||||||
|
return h(SendTokenScreen, {key: 'send-token'})
|
||||||
|
|
||||||
case 'newKeychain':
|
case 'newKeychain':
|
||||||
log.debug('rendering new keychain screen')
|
log.debug('rendering new keychain screen')
|
||||||
return h(NewKeyChainScreen, {key: 'new-keychain'})
|
return h(NewKeyChainScreen, {key: 'new-keychain'})
|
||||||
|
|
|
@ -16,9 +16,9 @@ function EthBalanceComponent () {
|
||||||
EthBalanceComponent.prototype.render = function () {
|
EthBalanceComponent.prototype.render = function () {
|
||||||
var props = this.props
|
var props = this.props
|
||||||
let { value } = props
|
let { value } = props
|
||||||
const { style, width, network } = props
|
const { style, width, network, isToken, tokenSymbol } = props
|
||||||
var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true
|
var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true
|
||||||
value = value ? formatBalance(value, 6, needsParse, network) : '...'
|
value = value ? formatBalance(value, 6, needsParse, network, isToken, tokenSymbol) : '...'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ const util = require('../util')
|
||||||
const MiniAccountPanel = require('./mini-account-panel')
|
const MiniAccountPanel = require('./mini-account-panel')
|
||||||
const Copyable = require('./copyable')
|
const Copyable = require('./copyable')
|
||||||
const EthBalance = require('./eth-balance')
|
const EthBalance = require('./eth-balance')
|
||||||
|
const TokenBalance = require('./token-balance')
|
||||||
const addressSummary = util.addressSummary
|
const addressSummary = util.addressSummary
|
||||||
const accountSummary = util.accountSummary
|
const accountSummary = util.accountSummary
|
||||||
const nameForAddress = require('../../lib/contract-namer')
|
const nameForAddress = require('../../lib/contract-namer')
|
||||||
|
@ -20,6 +21,9 @@ const { getEnvironmentType } = require('../../../app/scripts/lib/util')
|
||||||
const NetworkIndicator = require('../components/network')
|
const NetworkIndicator = require('../components/network')
|
||||||
const { ENVIRONMENT_TYPE_NOTIFICATION } = require('../../../app/scripts/lib/enums')
|
const { ENVIRONMENT_TYPE_NOTIFICATION } = require('../../../app/scripts/lib/enums')
|
||||||
const connect = require('react-redux').connect
|
const connect = require('react-redux').connect
|
||||||
|
const abiDecoder = require('abi-decoder')
|
||||||
|
const { tokenInfoGetter, calcTokenAmount } = require('../../../ui/app/token-util')
|
||||||
|
const BigNumber = require('bignumber.js')
|
||||||
|
|
||||||
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('21000')
|
||||||
|
@ -32,7 +36,11 @@ function PendingTx () {
|
||||||
valid: true,
|
valid: true,
|
||||||
txData: null,
|
txData: null,
|
||||||
submitting: false,
|
submitting: false,
|
||||||
|
tokenSymbol: '',
|
||||||
|
tokenDecimals: 0,
|
||||||
|
tokenDataRetrieved: false,
|
||||||
}
|
}
|
||||||
|
this.tokenInfoGetter = tokenInfoGetter()
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
|
@ -57,12 +65,32 @@ function mapStateToProps (state) {
|
||||||
}
|
}
|
||||||
|
|
||||||
PendingTx.prototype.render = function () {
|
PendingTx.prototype.render = function () {
|
||||||
|
const state = this.state
|
||||||
|
if (!state.tokenDataRetrieved) return null
|
||||||
const props = this.props
|
const props = this.props
|
||||||
const { currentCurrency, blockGasLimit, network, provider, isUnlocked } = props
|
const { currentCurrency, blockGasLimit, network, provider, isUnlocked } = props
|
||||||
|
|
||||||
const conversionRate = props.conversionRate
|
const conversionRate = props.conversionRate
|
||||||
const txMeta = this.gatherTxMeta()
|
const txMeta = this.gatherTxMeta()
|
||||||
const txParams = txMeta.txParams || {}
|
const txParams = txMeta.txParams || {}
|
||||||
|
let { isToken, tokensToSend, tokensTransferTo } = props
|
||||||
|
let token = {
|
||||||
|
address: txParams.to,
|
||||||
|
}
|
||||||
|
|
||||||
|
const decodedData = txParams.data && abiDecoder.decodeMethod(txParams.data)
|
||||||
|
if (decodedData && decodedData.name === 'transfer') {
|
||||||
|
isToken = true
|
||||||
|
const tokenValBN = new BigNumber(calcTokenAmount(decodedData.params[1].value, state.tokenDecimals))
|
||||||
|
const multiplier = Math.pow(10, 18)
|
||||||
|
tokensToSend = tokenValBN.mul(multiplier).toString(16)
|
||||||
|
tokensTransferTo = decodedData.params[0].value
|
||||||
|
token = {
|
||||||
|
address: txParams.to,
|
||||||
|
decimals: state.tokenDecimals,
|
||||||
|
symbol: state.tokenSymbol,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Allow retry txs
|
// Allow retry txs
|
||||||
const { lastGasPrice } = txMeta
|
const { lastGasPrice } = txMeta
|
||||||
|
@ -217,7 +245,10 @@ PendingTx.prototype.render = function () {
|
||||||
fontFamily: 'Nunito Regular',
|
fontFamily: 'Nunito Regular',
|
||||||
},
|
},
|
||||||
}, [
|
}, [
|
||||||
h(EthBalance, {
|
isToken ? h(TokenBalance, {
|
||||||
|
token,
|
||||||
|
fontSize: '12px',
|
||||||
|
}) : h(EthBalance, {
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
value: balance,
|
value: balance,
|
||||||
conversionRate,
|
conversionRate,
|
||||||
|
@ -231,7 +262,7 @@ PendingTx.prototype.render = function () {
|
||||||
|
|
||||||
forwardCarrat(),
|
forwardCarrat(),
|
||||||
|
|
||||||
this.miniAccountPanelForRecipient(),
|
this.miniAccountPanelForRecipient(isToken, tokensTransferTo),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
h('style', `
|
h('style', `
|
||||||
|
@ -326,7 +357,17 @@ PendingTx.prototype.render = function () {
|
||||||
// in the way that gas and gasLimit currently are.
|
// in the way that gas and gasLimit currently are.
|
||||||
h('.row', [
|
h('.row', [
|
||||||
h('.cell.label', 'Amount'),
|
h('.cell.label', 'Amount'),
|
||||||
h(EthBalance, { valueStyle, dimStyle, value: txParams.value, currentCurrency, conversionRate, network }),
|
h(EthBalance, {
|
||||||
|
valueStyle,
|
||||||
|
dimStyle,
|
||||||
|
value: isToken ? tokensToSend/* (new BN(tokensToSend)).mul(1e18)*/ : txParams.value,
|
||||||
|
currentCurrency,
|
||||||
|
conversionRate,
|
||||||
|
network,
|
||||||
|
isToken,
|
||||||
|
tokenSymbol: this.state.tokenSymbol,
|
||||||
|
showFiat: !isToken,
|
||||||
|
}),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
// Gas Limit (customizable)
|
// Gas Limit (customizable)
|
||||||
|
@ -381,7 +422,14 @@ PendingTx.prototype.render = function () {
|
||||||
// Max Transaction Fee (calculated)
|
// Max Transaction Fee (calculated)
|
||||||
h('.cell.row', [
|
h('.cell.row', [
|
||||||
h('.cell.label', 'Max Transaction Fee'),
|
h('.cell.label', 'Max Transaction Fee'),
|
||||||
h(EthBalance, { valueStyle, dimStyle, value: txFeeBn.toString(16), currentCurrency, conversionRate, network }),
|
h(EthBalance, {
|
||||||
|
valueStyle,
|
||||||
|
dimStyle,
|
||||||
|
value: txFeeBn.toString(16),
|
||||||
|
currentCurrency,
|
||||||
|
conversionRate,
|
||||||
|
network,
|
||||||
|
}),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
h('.cell.row', {
|
h('.cell.row', {
|
||||||
|
@ -482,11 +530,12 @@ PendingTx.prototype.render = function () {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
PendingTx.prototype.miniAccountPanelForRecipient = function () {
|
PendingTx.prototype.miniAccountPanelForRecipient = function (isToken, tokensTransferTo) {
|
||||||
const props = this.props
|
const props = this.props
|
||||||
const txData = props.txData
|
const txData = props.txData
|
||||||
const txParams = txData.txParams || {}
|
const txParams = txData.txParams || {}
|
||||||
const isContractDeploy = !('to' in txParams)
|
const isContractDeploy = !('to' in txParams)
|
||||||
|
const to = isToken ? tokensTransferTo : txParams.to
|
||||||
|
|
||||||
// If it's not a contract deploy, send to the account
|
// If it's not a contract deploy, send to the account
|
||||||
if (!isContractDeploy) {
|
if (!isContractDeploy) {
|
||||||
|
@ -506,17 +555,17 @@ PendingTx.prototype.miniAccountPanelForRecipient = function () {
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
},
|
},
|
||||||
}, accountSummary(nameForAddress(txParams.to, props.identities)), 6, 4),
|
}, accountSummary(nameForAddress(to, props.identities)), 6, 4),
|
||||||
|
|
||||||
h(Copyable, {
|
h(Copyable, {
|
||||||
value: ethUtil.toChecksumAddress(txParams.to),
|
value: ethUtil.toChecksumAddress(to),
|
||||||
}, [
|
}, [
|
||||||
h('span.font-small', {
|
h('span.font-small', {
|
||||||
style: {
|
style: {
|
||||||
fontFamily: 'Nunito Regular',
|
fontFamily: 'Nunito Regular',
|
||||||
color: 'rgba(255, 255, 255, 0.7)',
|
color: 'rgba(255, 255, 255, 0.7)',
|
||||||
},
|
},
|
||||||
}, addressSummary(txParams.to, 6, 4, false)),
|
}, addressSummary(to, 6, 4, false)),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
])
|
])
|
||||||
|
@ -536,6 +585,29 @@ PendingTx.prototype.miniAccountPanelForRecipient = function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PendingTx.prototype.componentWillMount = function () {
|
||||||
|
const txMeta = this.gatherTxMeta()
|
||||||
|
const txParams = txMeta.txParams || {}
|
||||||
|
this.updateTokenInfo(txParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingTx.prototype.componentWillUnmount = function () {
|
||||||
|
this.setState({
|
||||||
|
tokenSymbol: '',
|
||||||
|
tokenDecimals: 0,
|
||||||
|
tokenDataRetrieved: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingTx.prototype.updateTokenInfo = async function (txParams) {
|
||||||
|
const tokenParams = await this.tokenInfoGetter(txParams.to)
|
||||||
|
this.setState({
|
||||||
|
tokenSymbol: tokenParams.symbol,
|
||||||
|
tokenDecimals: tokenParams.decimals,
|
||||||
|
tokenDataRetrieved: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
PendingTx.prototype.gasPriceChanged = function (newBN, valid) {
|
PendingTx.prototype.gasPriceChanged = function (newBN, valid) {
|
||||||
log.info(`Gas price changed to: ${newBN.toString(10)}`)
|
log.info(`Gas price changed to: ${newBN.toString(10)}`)
|
||||||
const txMeta = this.gatherTxMeta()
|
const txMeta = this.gatherTxMeta()
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
const Component = require('react').Component
|
||||||
|
const h = require('react-hyperscript')
|
||||||
|
const inherits = require('util').inherits
|
||||||
|
const TokenTracker = require('eth-token-watcher')
|
||||||
|
const connect = require('react-redux').connect
|
||||||
|
const selectors = require('../../../ui/app/selectors')
|
||||||
|
const log = require('loglevel')
|
||||||
|
|
||||||
|
function mapStateToProps (state) {
|
||||||
|
return {
|
||||||
|
userAddress: selectors.getSelectedAddress(state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = connect(mapStateToProps)(TokenBalance)
|
||||||
|
|
||||||
|
|
||||||
|
inherits(TokenBalance, Component)
|
||||||
|
function TokenBalance () {
|
||||||
|
this.state = {
|
||||||
|
string: '',
|
||||||
|
symbol: '',
|
||||||
|
isLoading: true,
|
||||||
|
error: null,
|
||||||
|
}
|
||||||
|
Component.call(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenBalance.prototype.render = function () {
|
||||||
|
const state = this.state
|
||||||
|
const props = this.props
|
||||||
|
const { symbol, string, isLoading } = state
|
||||||
|
const { balanceOnly } = this.props
|
||||||
|
|
||||||
|
const valueStyle = props.valueStyle ? props.valueStyle : {
|
||||||
|
color: '#ffffff',
|
||||||
|
width: '100%',
|
||||||
|
fontSize: props.fontSize || '14px',
|
||||||
|
textAlign: 'right',
|
||||||
|
}
|
||||||
|
const dimStyle = props.dimStyle ? props.dimStyle : {
|
||||||
|
color: ' #60db97',
|
||||||
|
fontSize: props.fontSize || '14px',
|
||||||
|
marginLeft: '5px',
|
||||||
|
}
|
||||||
|
|
||||||
|
return isLoading
|
||||||
|
? h('div', '')
|
||||||
|
: h('.flex-row', {
|
||||||
|
style: {
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
lineHeight: '20px',
|
||||||
|
textRendering: 'geometricPrecision',
|
||||||
|
},
|
||||||
|
}, [
|
||||||
|
h('div.hide-text-overflow.token-balance__amount', {
|
||||||
|
style: valueStyle,
|
||||||
|
}, string),
|
||||||
|
!balanceOnly && h('span.token-balance__symbol', {
|
||||||
|
style: dimStyle,
|
||||||
|
}, symbol),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenBalance.prototype.componentDidMount = function () {
|
||||||
|
this.createFreshTokenTracker()
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenBalance.prototype.createFreshTokenTracker = function () {
|
||||||
|
if (this.tracker) {
|
||||||
|
// Clean up old trackers when refreshing:
|
||||||
|
this.tracker.stop()
|
||||||
|
this.tracker.removeListener('update', this.balanceUpdater)
|
||||||
|
this.tracker.removeListener('error', this.showError)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!global.ethereumProvider) return
|
||||||
|
const { userAddress, token } = this.props
|
||||||
|
|
||||||
|
this.tracker = new TokenTracker({
|
||||||
|
userAddress,
|
||||||
|
provider: global.ethereumProvider,
|
||||||
|
tokens: [token],
|
||||||
|
pollingInterval: 8000,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// Set up listener instances for cleaning up
|
||||||
|
this.balanceUpdater = this.updateBalance.bind(this)
|
||||||
|
this.showError = error => {
|
||||||
|
this.setState({ error, isLoading: false })
|
||||||
|
}
|
||||||
|
this.tracker.on('update', this.balanceUpdater)
|
||||||
|
this.tracker.on('error', this.showError)
|
||||||
|
|
||||||
|
this.tracker.updateBalances()
|
||||||
|
.then(() => {
|
||||||
|
this.updateBalance(this.tracker.serialize())
|
||||||
|
})
|
||||||
|
.catch((reason) => {
|
||||||
|
log.error(`Problem updating balances`, reason)
|
||||||
|
this.setState({ isLoading: false })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenBalance.prototype.componentDidUpdate = function (nextProps) {
|
||||||
|
const {
|
||||||
|
userAddress: oldAddress,
|
||||||
|
token: { address: oldTokenAddress },
|
||||||
|
} = this.props
|
||||||
|
const {
|
||||||
|
userAddress: newAddress,
|
||||||
|
token: { address: newTokenAddress },
|
||||||
|
} = nextProps
|
||||||
|
|
||||||
|
if ((!oldAddress || !newAddress) && (!oldTokenAddress || !newTokenAddress)) return
|
||||||
|
if ((oldAddress === newAddress) && (oldTokenAddress === newTokenAddress)) return
|
||||||
|
|
||||||
|
this.setState({ isLoading: true })
|
||||||
|
this.createFreshTokenTracker()
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenBalance.prototype.updateBalance = function (tokens = []) {
|
||||||
|
if (!this.tracker.running) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const [{ string, symbol }] = tokens
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
string,
|
||||||
|
symbol,
|
||||||
|
isLoading: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenBalance.prototype.componentWillUnmount = function () {
|
||||||
|
if (!this.tracker) return
|
||||||
|
this.tracker.stop()
|
||||||
|
this.tracker.removeListener('update', this.balanceUpdater)
|
||||||
|
this.tracker.removeListener('error', this.showError)
|
||||||
|
}
|
||||||
|
|
|
@ -7,11 +7,11 @@ const Dropdown = require('./dropdown').Dropdown
|
||||||
const DropdownMenuItem = require('./dropdown').DropdownMenuItem
|
const DropdownMenuItem = require('./dropdown').DropdownMenuItem
|
||||||
const ethUtil = require('ethereumjs-util')
|
const ethUtil = require('ethereumjs-util')
|
||||||
const copyToClipboard = require('copy-to-clipboard')
|
const copyToClipboard = require('copy-to-clipboard')
|
||||||
|
const actions = require('../../../ui/app/actions')
|
||||||
|
const connect = require('react-redux').connect
|
||||||
|
|
||||||
const tokenCellDropDownPrefix = 'token-cell_dropdown_'
|
const tokenCellDropDownPrefix = 'token-cell_dropdown_'
|
||||||
|
|
||||||
module.exports = TokenCell
|
|
||||||
|
|
||||||
inherits(TokenCell, Component)
|
inherits(TokenCell, Component)
|
||||||
function TokenCell () {
|
function TokenCell () {
|
||||||
Component.call(this)
|
Component.call(this)
|
||||||
|
@ -76,7 +76,7 @@ TokenCell.prototype.render = function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
TokenCell.prototype.renderTokenOptions = function (menuToTop, ind) {
|
TokenCell.prototype.renderTokenOptions = function (menuToTop, ind) {
|
||||||
const { address, symbol, string, network, userAddress } = this.props
|
const { address, symbol, string, network, userAddress, showSendTokenPage } = this.props
|
||||||
const { optionsMenuActive } = this.state
|
const { optionsMenuActive } = this.state
|
||||||
|
|
||||||
return h(
|
return h(
|
||||||
|
@ -84,9 +84,9 @@ TokenCell.prototype.renderTokenOptions = function (menuToTop, ind) {
|
||||||
{
|
{
|
||||||
style: {
|
style: {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
marginLeft: '-263px',
|
marginLeft: menuToTop ? '-273px' : '-263px',
|
||||||
minWidth: '180px',
|
minWidth: '180px',
|
||||||
marginTop: menuToTop ? '-200px' : '30px',
|
marginTop: menuToTop ? '-214px' : '30px',
|
||||||
width: '280px',
|
width: '280px',
|
||||||
},
|
},
|
||||||
isOpen: optionsMenuActive,
|
isOpen: optionsMenuActive,
|
||||||
|
@ -100,6 +100,16 @@ TokenCell.prototype.renderTokenOptions = function (menuToTop, ind) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
h(
|
||||||
|
DropdownMenuItem,
|
||||||
|
{
|
||||||
|
closeMenu: () => {},
|
||||||
|
onClick: () => {
|
||||||
|
showSendTokenPage(address)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
`Send`,
|
||||||
|
),
|
||||||
h(
|
h(
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
{
|
{
|
||||||
|
@ -159,3 +169,11 @@ function tokenFactoryFor (tokenAddress) {
|
||||||
return `https://tokenfactory.surge.sh/#/token/${tokenAddress}`
|
return `https://tokenfactory.surge.sh/#/token/${tokenAddress}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => {
|
||||||
|
return {
|
||||||
|
showSendTokenPage: (tokenAddress) => dispatch(actions.showSendTokenPage(tokenAddress)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = connect(null, mapDispatchToProps)(TokenCell)
|
||||||
|
|
||||||
|
|
|
@ -16,22 +16,28 @@ const Loading = require('./components/loading')
|
||||||
module.exports = connect(mapStateToProps)(ConfirmTxScreen)
|
module.exports = connect(mapStateToProps)(ConfirmTxScreen)
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
|
const { metamask, appState } = state
|
||||||
|
const { screenParams, pendingTxIndex } = appState.currentView
|
||||||
return {
|
return {
|
||||||
identities: state.metamask.identities,
|
identities: metamask.identities,
|
||||||
accounts: state.metamask.accounts,
|
accounts: metamask.accounts,
|
||||||
selectedAddress: state.metamask.selectedAddress,
|
selectedAddress: metamask.selectedAddress,
|
||||||
unapprovedTxs: state.metamask.unapprovedTxs,
|
unapprovedTxs: metamask.unapprovedTxs,
|
||||||
unapprovedMsgs: state.metamask.unapprovedMsgs,
|
unapprovedMsgs: metamask.unapprovedMsgs,
|
||||||
unapprovedPersonalMsgs: state.metamask.unapprovedPersonalMsgs,
|
unapprovedPersonalMsgs: metamask.unapprovedPersonalMsgs,
|
||||||
unapprovedTypedMessages: state.metamask.unapprovedTypedMessages,
|
unapprovedTypedMessages: metamask.unapprovedTypedMessages,
|
||||||
index: state.appState.currentView.pendingTxIndex || 0,
|
index: pendingTxIndex || 0,
|
||||||
warning: state.appState.warning,
|
warning: appState.warning,
|
||||||
network: state.metamask.network,
|
network: metamask.network,
|
||||||
provider: state.metamask.provider,
|
provider: metamask.provider,
|
||||||
conversionRate: state.metamask.conversionRate,
|
conversionRate: metamask.conversionRate,
|
||||||
currentCurrency: state.metamask.currentCurrency,
|
currentCurrency: metamask.currentCurrency,
|
||||||
blockGasLimit: state.metamask.currentBlockGasLimit,
|
blockGasLimit: metamask.currentBlockGasLimit,
|
||||||
computedBalances: state.metamask.computedBalances,
|
computedBalances: metamask.computedBalances,
|
||||||
|
isToken: (screenParams && screenParams.isToken),
|
||||||
|
tokenSymbol: (screenParams && screenParams.tokenSymbol),
|
||||||
|
tokensToSend: (screenParams && screenParams.tokensToSend),
|
||||||
|
tokensTransferTo: (screenParams && screenParams.tokensTransferTo),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +101,10 @@ ConfirmTxScreen.prototype.render = function () {
|
||||||
unconfTxListLength,
|
unconfTxListLength,
|
||||||
computedBalances,
|
computedBalances,
|
||||||
network,
|
network,
|
||||||
|
isToken: props.isToken,
|
||||||
|
tokenSymbol: props.tokenSymbol,
|
||||||
|
tokensToSend: props.tokensToSend,
|
||||||
|
tokensTransferTo: props.tokensTransferTo,
|
||||||
// Actions
|
// Actions
|
||||||
buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress),
|
buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress),
|
||||||
sendTransaction: this.sendTransaction.bind(this),
|
sendTransaction: this.sendTransaction.bind(this),
|
||||||
|
|
|
@ -0,0 +1,399 @@
|
||||||
|
const inherits = require('util').inherits
|
||||||
|
const PersistentForm = require('../lib/persistent-form')
|
||||||
|
const h = require('react-hyperscript')
|
||||||
|
const connect = require('react-redux').connect
|
||||||
|
const Identicon = require('./components/identicon')
|
||||||
|
const actions = require('../../ui/app/actions')
|
||||||
|
const util = require('./util')
|
||||||
|
const numericBalance = require('./util').numericBalance
|
||||||
|
const addressSummary = require('./util').addressSummary
|
||||||
|
const TokenBalance = require('./components/token-balance')
|
||||||
|
const EnsInput = require('./components/ens-input')
|
||||||
|
const ethUtil = require('ethereumjs-util')
|
||||||
|
const { tokenInfoGetter, calcTokenAmountWithDec } = require('../../ui/app/token-util')
|
||||||
|
const TokenTracker = require('eth-token-watcher')
|
||||||
|
const Loading = require('./components/loading')
|
||||||
|
const BigNumber = require('bignumber.js')
|
||||||
|
BigNumber.config({ ERRORS: false })
|
||||||
|
const log = require('loglevel')
|
||||||
|
|
||||||
|
module.exports = connect(mapStateToProps)(SendTransactionScreen)
|
||||||
|
|
||||||
|
function mapStateToProps (state) {
|
||||||
|
var result = {
|
||||||
|
address: state.metamask.selectedAddress,
|
||||||
|
accounts: state.metamask.accounts,
|
||||||
|
identities: state.metamask.identities,
|
||||||
|
warning: state.appState.warning,
|
||||||
|
network: state.metamask.network,
|
||||||
|
addressBook: state.metamask.addressBook,
|
||||||
|
tokenAddress: state.appState.currentView.tokenAddress,
|
||||||
|
}
|
||||||
|
|
||||||
|
result.error = result.warning && result.warning.split('.')[0]
|
||||||
|
|
||||||
|
result.account = result.accounts[result.address]
|
||||||
|
result.identity = result.identities[result.address]
|
||||||
|
result.balance = result.account ? numericBalance(result.account.balance) : null
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
inherits(SendTransactionScreen, PersistentForm)
|
||||||
|
function SendTransactionScreen () {
|
||||||
|
this.state = {
|
||||||
|
token: {
|
||||||
|
address: '',
|
||||||
|
symbol: '',
|
||||||
|
balance: 0,
|
||||||
|
decimals: 0,
|
||||||
|
},
|
||||||
|
isLoading: true,
|
||||||
|
}
|
||||||
|
PersistentForm.call(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.render = function () {
|
||||||
|
const { isLoading, token } = this.state
|
||||||
|
if (isLoading) {
|
||||||
|
return h(Loading, {
|
||||||
|
isLoading: isLoading,
|
||||||
|
loadingMessage: 'Loading...',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.persistentFormParentId = 'send-tx-form'
|
||||||
|
|
||||||
|
const props = this.props
|
||||||
|
const {
|
||||||
|
address,
|
||||||
|
identity,
|
||||||
|
network,
|
||||||
|
identities,
|
||||||
|
addressBook,
|
||||||
|
} = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
h('.send-screen.flex-column.flex-grow', [
|
||||||
|
|
||||||
|
//
|
||||||
|
// Sender Profile
|
||||||
|
//
|
||||||
|
|
||||||
|
h('.account-data-subsection.flex-row.flex-grow', {
|
||||||
|
style: {
|
||||||
|
background: 'linear-gradient(rgb(84, 36, 147), rgb(104, 45, 182))',
|
||||||
|
padding: '30px',
|
||||||
|
},
|
||||||
|
}, [
|
||||||
|
|
||||||
|
// header - identicon + nav
|
||||||
|
h('.flex-row.flex-space-between', [
|
||||||
|
|
||||||
|
// large identicon
|
||||||
|
h('.identicon-wrapper.flex-column.flex-center.select-none', {
|
||||||
|
style: {
|
||||||
|
display: 'inline-block',
|
||||||
|
},
|
||||||
|
}, [
|
||||||
|
h(Identicon, {
|
||||||
|
diameter: 62,
|
||||||
|
address: address,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
|
||||||
|
// invisible place holder
|
||||||
|
h('i.fa.fa-users.fa-lg.invisible', {
|
||||||
|
style: {
|
||||||
|
marginTop: '28px',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
]),
|
||||||
|
|
||||||
|
// account label
|
||||||
|
|
||||||
|
h('.flex-column', {
|
||||||
|
style: {
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
},
|
||||||
|
}, [
|
||||||
|
h('h2.font-medium.flex-center', {
|
||||||
|
style: {
|
||||||
|
color: '#ffffff',
|
||||||
|
paddingTop: '8px',
|
||||||
|
marginBottom: '8px',
|
||||||
|
},
|
||||||
|
}, identity && identity.name),
|
||||||
|
|
||||||
|
// address and getter actions
|
||||||
|
h('.flex-row.flex-center', {
|
||||||
|
style: {
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)',
|
||||||
|
marginBottom: '30px',
|
||||||
|
},
|
||||||
|
}, [
|
||||||
|
|
||||||
|
h('div', {
|
||||||
|
style: {
|
||||||
|
lineHeight: '16px',
|
||||||
|
fontSize: '14px',
|
||||||
|
},
|
||||||
|
}, addressSummary(address)),
|
||||||
|
|
||||||
|
]),
|
||||||
|
|
||||||
|
// balance
|
||||||
|
h('.flex-row.flex-center', [
|
||||||
|
|
||||||
|
h(TokenBalance, {
|
||||||
|
token,
|
||||||
|
}),
|
||||||
|
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
|
||||||
|
//
|
||||||
|
// Required Fields
|
||||||
|
//
|
||||||
|
|
||||||
|
h('h3.flex-center', {
|
||||||
|
style: {
|
||||||
|
color: '#333333',
|
||||||
|
marginTop: '18px',
|
||||||
|
marginBottom: '14px',
|
||||||
|
},
|
||||||
|
}, [
|
||||||
|
// back button
|
||||||
|
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
|
||||||
|
style: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: '30px',
|
||||||
|
},
|
||||||
|
onClick: this.back.bind(this),
|
||||||
|
}),
|
||||||
|
`Send ${this.state.token.symbol} Tokens`,
|
||||||
|
]),
|
||||||
|
|
||||||
|
// error message
|
||||||
|
props.error && h('div', {style: {
|
||||||
|
marginLeft: '30px',
|
||||||
|
marginRight: '30px',
|
||||||
|
}}, [
|
||||||
|
h('div.error.flex-center', props.error),
|
||||||
|
]),
|
||||||
|
|
||||||
|
// 'to' field
|
||||||
|
h('section.flex-row.flex-center', [
|
||||||
|
h(EnsInput, {
|
||||||
|
name: 'address',
|
||||||
|
placeholder: 'Recipient Address',
|
||||||
|
onChange: this.recipientDidChange.bind(this),
|
||||||
|
network,
|
||||||
|
identities,
|
||||||
|
addressBook,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
|
||||||
|
// 'amount' and send button
|
||||||
|
h('section.flex-row.flex-center', [
|
||||||
|
|
||||||
|
h('input.large-input', {
|
||||||
|
name: 'amount',
|
||||||
|
placeholder: 'Amount',
|
||||||
|
type: 'number',
|
||||||
|
style: {
|
||||||
|
marginRight: '6px',
|
||||||
|
},
|
||||||
|
dataset: {
|
||||||
|
persistentFormId: 'tx-amount',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
h('button', {
|
||||||
|
onClick: this.onSubmit.bind(this),
|
||||||
|
}, 'Next'),
|
||||||
|
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.componentDidMount = function () {
|
||||||
|
this.getTokensMetadata()
|
||||||
|
.then(() => {
|
||||||
|
this.createFreshTokenTracker()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.getTokensMetadata = async function () {
|
||||||
|
this.setState({isLoading: true})
|
||||||
|
this.tokenInfoGetter = tokenInfoGetter()
|
||||||
|
const { tokenAddress, network } = this.props
|
||||||
|
const { symbol = '', decimals = 0 } = await this.tokenInfoGetter(tokenAddress)
|
||||||
|
this.setState({
|
||||||
|
token: {
|
||||||
|
address: tokenAddress,
|
||||||
|
network,
|
||||||
|
symbol,
|
||||||
|
decimals,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.componentDidUnmount = function () {
|
||||||
|
this.props.dispatch(actions.displayWarning(''))
|
||||||
|
if (!this.tracker) return
|
||||||
|
this.tracker.stop()
|
||||||
|
this.tracker.removeListener('update', this.balanceUpdater)
|
||||||
|
this.tracker.removeListener('error', this.showError)
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.createFreshTokenTracker = function () {
|
||||||
|
this.setState({isLoading: true})
|
||||||
|
const { address, tokenAddress } = this.props
|
||||||
|
if (!util.isValidAddress(tokenAddress)) return
|
||||||
|
if (this.tracker) {
|
||||||
|
// Clean up old trackers when refreshing:
|
||||||
|
this.tracker.stop()
|
||||||
|
this.tracker.removeListener('update', this.balanceUpdater)
|
||||||
|
this.tracker.removeListener('error', this.showError)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!global.ethereumProvider) return
|
||||||
|
|
||||||
|
this.tracker = new TokenTracker({
|
||||||
|
userAddress: address,
|
||||||
|
provider: global.ethereumProvider,
|
||||||
|
tokens: [this.state.token],
|
||||||
|
pollingInterval: 8000,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// Set up listener instances for cleaning up
|
||||||
|
this.balanceUpdater = this.updateBalances.bind(this)
|
||||||
|
this.showError = (error) => {
|
||||||
|
this.setState({ error, isLoading: false })
|
||||||
|
}
|
||||||
|
this.tracker.on('update', this.balanceUpdater)
|
||||||
|
this.tracker.on('error', this.showError)
|
||||||
|
|
||||||
|
this.tracker.updateBalances()
|
||||||
|
.then(() => {
|
||||||
|
this.updateBalances(this.tracker.serialize())
|
||||||
|
})
|
||||||
|
.catch((reason) => {
|
||||||
|
log.error(`Problem updating balances`, reason)
|
||||||
|
this.setState({ isLoading: false })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.updateBalances = function (tokens) {
|
||||||
|
if (!this.tracker.running) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.setState({ token: (tokens && tokens[0]), isLoading: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.navigateToAccounts = function (event) {
|
||||||
|
event.stopPropagation()
|
||||||
|
this.props.dispatch(actions.showAccountsPage())
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.back = function () {
|
||||||
|
var address = this.props.address
|
||||||
|
this.props.dispatch(actions.backToAccountDetail(address))
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.recipientDidChange = function (recipient, nickname) {
|
||||||
|
this.setState({
|
||||||
|
recipient: recipient,
|
||||||
|
nickname: nickname,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.onSubmit = async function () {
|
||||||
|
const state = this.state || {}
|
||||||
|
const { token } = state
|
||||||
|
const recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '')
|
||||||
|
const nickname = state.nickname || ' '
|
||||||
|
const input = document.querySelector('input[name="amount"]').value
|
||||||
|
const parts = input.split('.')
|
||||||
|
|
||||||
|
let message
|
||||||
|
|
||||||
|
if (isNaN(input) || input === '') {
|
||||||
|
message = 'Invalid token\'s amount.'
|
||||||
|
return this.props.dispatch(actions.displayWarning(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parts[1]) {
|
||||||
|
var decimal = parts[1]
|
||||||
|
if (decimal.length > 18) {
|
||||||
|
message = 'Token\'s amount is too precise.'
|
||||||
|
return this.props.dispatch(actions.displayWarning(message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenAddress = ethUtil.addHexPrefix(token.address)
|
||||||
|
const tokensValueWithoutDec = new BigNumber(input)
|
||||||
|
const tokensValueWithDec = new BigNumber(calcTokenAmountWithDec(input, token.decimals))
|
||||||
|
|
||||||
|
if (tokensValueWithDec.gt(token.balance)) {
|
||||||
|
message = 'Insufficient token\'s balance.'
|
||||||
|
return this.props.dispatch(actions.displayWarning(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input < 0) {
|
||||||
|
message = 'Can not send negative amounts of ETH.'
|
||||||
|
return this.props.dispatch(actions.displayWarning(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((util.isInvalidChecksumAddress(recipient))) {
|
||||||
|
message = 'Recipient address checksum is invalid.'
|
||||||
|
return this.props.dispatch(actions.displayWarning(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!util.isValidAddress(recipient) || (!recipient)) {
|
||||||
|
message = 'Recipient address is invalid.'
|
||||||
|
return this.props.dispatch(actions.displayWarning(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.dispatch(actions.hideWarning())
|
||||||
|
|
||||||
|
this.props.dispatch(actions.addToAddressBook(recipient, nickname))
|
||||||
|
|
||||||
|
var txParams = {
|
||||||
|
from: this.props.address,
|
||||||
|
value: '0x',
|
||||||
|
}
|
||||||
|
|
||||||
|
const toAddress = ethUtil.addHexPrefix(recipient)
|
||||||
|
|
||||||
|
txParams.to = tokenAddress
|
||||||
|
|
||||||
|
const tokensAmount = `0x${input.toString(16)}`
|
||||||
|
const encoded = this.generateTokenTransferData({toAddress, amount: tokensAmount})
|
||||||
|
txParams.data = encoded
|
||||||
|
|
||||||
|
const confTxScreenParams = {
|
||||||
|
isToken: true,
|
||||||
|
tokenSymbol: token.symbol,
|
||||||
|
tokensToSend: tokensValueWithoutDec,
|
||||||
|
tokensTransferTo: toAddress,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.dispatch(actions.signTokenTx(tokenAddress, toAddress, tokensValueWithDec, txParams, confTxScreenParams))
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.generateTokenTransferData = function ({ toAddress = '0x0', amount = '0x0' }) {
|
||||||
|
const TOKEN_TRANSFER_FUNCTION_SIGNATURE = '0xa9059cbb'
|
||||||
|
const abi = require('ethereumjs-abi')
|
||||||
|
return TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call(
|
||||||
|
abi.rawEncode(['address', 'uint256'], [toAddress, ethUtil.addHexPrefix(amount)]),
|
||||||
|
x => ('00' + x.toString(16)).slice(-2)
|
||||||
|
).join('')
|
||||||
|
}
|
|
@ -260,7 +260,7 @@ SendTransactionScreen.prototype.onSubmit = function () {
|
||||||
const recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '')
|
const recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '')
|
||||||
const nickname = state.nickname || ' '
|
const nickname = state.nickname || ' '
|
||||||
const input = document.querySelector('input[name="amount"]').value
|
const input = document.querySelector('input[name="amount"]').value
|
||||||
const parts = input.split('')
|
const parts = input.split('.')
|
||||||
|
|
||||||
let message
|
let message
|
||||||
|
|
||||||
|
|
|
@ -111,10 +111,11 @@ function parseBalance (balance) {
|
||||||
|
|
||||||
// Takes wei hex, returns an object with three properties.
|
// Takes wei hex, returns an object with three properties.
|
||||||
// Its "formatted" property is what we generally use to render values.
|
// Its "formatted" property is what we generally use to render values.
|
||||||
function formatBalance (balance, decimalsToKeep, needsParse = true, network) {
|
function formatBalance (balance, decimalsToKeep, needsParse = true, network, isToken, tokenSymbol) {
|
||||||
const isSokol = parseInt(network) === 77
|
const isSokol = parseInt(network) === 77
|
||||||
const isPOA = parseInt(network) === 99
|
const isPOA = parseInt(network) === 99
|
||||||
const coinName = isPOA ? 'POA' : isSokol ? 'SPOA' : 'ETH'
|
const coinName = isPOA ? 'POA' : isSokol ? 'SPOA' : 'ETH'
|
||||||
|
const assetName = isToken ? tokenSymbol : coinName
|
||||||
var parsed = needsParse ? parseBalance(balance) : balance.split('.')
|
var parsed = needsParse ? parseBalance(balance) : balance.split('.')
|
||||||
var beforeDecimal = parsed[0]
|
var beforeDecimal = parsed[0]
|
||||||
var afterDecimal = parsed[1]
|
var afterDecimal = parsed[1]
|
||||||
|
@ -124,14 +125,14 @@ function formatBalance (balance, decimalsToKeep, needsParse = true, network) {
|
||||||
if (afterDecimal !== '0') {
|
if (afterDecimal !== '0') {
|
||||||
var sigFigs = afterDecimal.match(/^0*(.{2})/) // default: grabs 2 most significant digits
|
var sigFigs = afterDecimal.match(/^0*(.{2})/) // default: grabs 2 most significant digits
|
||||||
if (sigFigs) { afterDecimal = sigFigs[0] }
|
if (sigFigs) { afterDecimal = sigFigs[0] }
|
||||||
formatted = '0.' + afterDecimal + ` ${coinName}`
|
formatted = '0.' + afterDecimal + ` ${assetName}`
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
formatted = beforeDecimal + '.' + afterDecimal.slice(0, 3) + ` ${coinName}`
|
formatted = beforeDecimal + '.' + afterDecimal.slice(0, 3) + ` ${assetName}`
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
afterDecimal += Array(decimalsToKeep).join('0')
|
afterDecimal += Array(decimalsToKeep).join('0')
|
||||||
formatted = beforeDecimal + '.' + afterDecimal.slice(0, decimalsToKeep) + ` ${coinName}`
|
formatted = beforeDecimal + '.' + afterDecimal.slice(0, decimalsToKeep) + ` ${assetName}`
|
||||||
}
|
}
|
||||||
return formatted
|
return formatted
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,11 @@ module.exports = {
|
||||||
token: {
|
token: {
|
||||||
menu: By.id('token-cell_dropdown_0'),
|
menu: By.id('token-cell_dropdown_0'),
|
||||||
items: By.className('dropdown-menu-item'),
|
items: By.className('dropdown-menu-item'),
|
||||||
view: By.css('#token-cell_dropdown_0 > div > div > li:nth-child(2)'),
|
send: By.css('#token-cell_dropdown_0 > div > div > li:nth-child(2)'),
|
||||||
copy: By.css('#token-cell_dropdown_0 > div > div > li:nth-child(3)'),
|
view: By.css('#token-cell_dropdown_0 > div > div > li:nth-child(3)'),
|
||||||
remove: By.css('#token-cell_dropdown_0 > div > div > li:nth-child(4)'),
|
copy: By.css('#token-cell_dropdown_0 > div > div > li:nth-child(4)'),
|
||||||
|
remove: By.css('#token-cell_dropdown_0 > div > div > li:nth-child(5)'),
|
||||||
|
sendText: 'Send',
|
||||||
viewText: 'View token on block explorer',
|
viewText: 'View token on block explorer',
|
||||||
copyText: 'Copy address to clipboard',
|
copyText: 'Copy address to clipboard',
|
||||||
removeText: 'Remove',
|
removeText: 'Remove',
|
||||||
|
@ -25,6 +27,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
account: {
|
account: {
|
||||||
account1: By.css('#app-content > div > div.full-width > div > div:nth-child(2) > span > div > div > span > div > li:nth-child(2) > span'),
|
account1: By.css('#app-content > div > div.full-width > div > div:nth-child(2) > span > div > div > span > div > li:nth-child(2) > span'),
|
||||||
|
account2: By.css('#app-content > div > div.full-width > div > div:nth-child(2) > span > div > div > span > div > li:nth-child(3) > span'),
|
||||||
menu: By.css('#app-content > div > div.full-width > div > div:nth-child(2) > span > div'),
|
menu: By.css('#app-content > div > div.full-width > div > div:nth-child(2) > span > div'),
|
||||||
delete: By.css('#app-content > div > div.full-width > div > div:nth-child(2) > span > div > div > span > div > li:nth-child(4) > div.remove'),
|
delete: By.css('#app-content > div > div.full-width > div > div:nth-child(2) > span > div > div > span > div > li:nth-child(4) > div.remove'),
|
||||||
createAccount: By.css('#app-content > div > div.full-width > div > div:nth-child(2) > span > div > div > span > div > li:nth-child(3) > span'),
|
createAccount: By.css('#app-content > div > div.full-width > div > div:nth-child(2) > span > div > div > span > div > li:nth-child(3) > span'),
|
||||||
|
@ -43,6 +46,29 @@ module.exports = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
screens: {
|
screens: {
|
||||||
|
sendTokens: {
|
||||||
|
error: By.className('error flex-center'),
|
||||||
|
errorText: {
|
||||||
|
invalidAmount: 'Invalid token\'s amount',
|
||||||
|
address: 'Recipient address is invalid',
|
||||||
|
largeAmount: 'Insufficient token\'s balance',
|
||||||
|
tooPrecise: 'Token\'s amount is too precise',
|
||||||
|
negativeAmount: 'Can not send negative amounts of ETH',
|
||||||
|
},
|
||||||
|
title: By.className('flex-center'),
|
||||||
|
balance: By.className('hide-text-overflow token-balance__amount'),
|
||||||
|
symbol: By.className('token-balance__symbol'),
|
||||||
|
field: {
|
||||||
|
address: By.name('address'),
|
||||||
|
addressPlaceholder: 'Recipient Address',
|
||||||
|
amount: By.name('amount'),
|
||||||
|
amountPlaceholder: 'Amount',
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
next: By.xpath('//*[@id="app-content"]/div/div[4]/div/section[2]/button'),
|
||||||
|
arrow: By.className('fa fa-arrow-left fa-lg cursor-pointer'),
|
||||||
|
},
|
||||||
|
},
|
||||||
yourPR: {
|
yourPR: {
|
||||||
key: By.css('#app-content > div > div.app-primary.from-right > div > div.privateKey > div.flex-row > p'),
|
key: By.css('#app-content > div > div.app-primary.from-right > div > div.privateKey > div.flex-row > p'),
|
||||||
copy: By.className('clipboard cursor-pointer'),
|
copy: By.className('clipboard cursor-pointer'),
|
||||||
|
@ -120,14 +146,17 @@ module.exports = {
|
||||||
titleText: 'Delete Custom RPC',
|
titleText: 'Delete Custom RPC',
|
||||||
},
|
},
|
||||||
confirmTransaction: {
|
confirmTransaction: {
|
||||||
buttons: {
|
title: By.className('flex-row flex-center'),
|
||||||
|
amount: By.css('#pending-tx-form > div:nth-child(1) > div.table-box > div:nth-child(2) > div.ether-balance.ether-balance-amount > div > div > div > div:nth-child(1)'),
|
||||||
|
symbol: By.css('#pending-tx-form > div:nth-child(1) > div.table-box > div:nth-child(2) > div.ether-balance.ether-balance-amount > div > div > div > div:nth-child(2)'),
|
||||||
|
button: {
|
||||||
submit: By.css('#pending-tx-form > div.flex-row.flex-space-around.conf-buttons > input'),
|
submit: By.css('#pending-tx-form > div.flex-row.flex-space-around.conf-buttons > input'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
sendTransaction: {
|
sendTransaction: {
|
||||||
title: By.css('#app-content > div > div.app-primary.from-right > div > h3:nth-child(2)'),
|
title: By.css('#app-content > div > div.app-primary.from-right > div > h3:nth-child(2)'),
|
||||||
titleText: 'Send Transaction',
|
titleText: 'Send Transaction',
|
||||||
fields: {
|
field: {
|
||||||
address: By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(3) > div > input'),
|
address: By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(3) > div > input'),
|
||||||
amount: By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(4) > input'),
|
amount: By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(4) > input'),
|
||||||
},
|
},
|
||||||
|
@ -179,7 +208,8 @@ module.exports = {
|
||||||
},
|
},
|
||||||
main: {
|
main: {
|
||||||
identicon: By.className('identicon-wrapper select-none'),
|
identicon: By.className('identicon-wrapper select-none'),
|
||||||
accountName: By.className('sizing-input'),
|
fieldAccountName: By.className('sizing-input'),
|
||||||
|
accountName: By.css('#app-content > div > div.app-primary.from-left > div > div > div:nth-child(1) > flex-column > div.name-label > div > div > h2'),
|
||||||
edit: By.className('edit-text'),
|
edit: By.className('edit-text'),
|
||||||
iconCopy: By.className('clipboard cursor-pointer white'),
|
iconCopy: By.className('clipboard cursor-pointer white'),
|
||||||
transactionList: By.css('#app-content > div > div.app-primary.from-left > div > section > section > div > div > div > div.ether-balance.ether-balance-amount > div > div > div > div:nth-child(1)'),
|
transactionList: By.css('#app-content > div > div.app-primary.from-left > div > section > section > div > div > div > div.ether-balance.ether-balance-amount > div > div > div > div:nth-child(1)'),
|
||||||
|
@ -199,7 +229,7 @@ module.exports = {
|
||||||
tokens: {
|
tokens: {
|
||||||
menu: By.className('inactiveForm pointer'),
|
menu: By.className('inactiveForm pointer'),
|
||||||
token: By.className('token-cell'),
|
token: By.className('token-cell'),
|
||||||
balance: By.css('#app-content > div > div.app-primary.from-left > div > section > div.full-flex-height > ol > li:nth-child(2) > h3'),
|
balance: By.css('#token-cell_0 > h3'),
|
||||||
amount: By.css('#app-content > div > div.app-primary.from-left > div > section > div.full-flex-height > div > span'),
|
amount: By.css('#app-content > div > div.app-primary.from-left > div > section > div.full-flex-height > div > span'),
|
||||||
textNoTokens: 'No tokens found',
|
textNoTokens: 'No tokens found',
|
||||||
textYouOwn1token: 'You own 1 token',
|
textYouOwn1token: 'You own 1 token',
|
||||||
|
|
|
@ -134,13 +134,13 @@ describe('Metamask popup page', async function () {
|
||||||
await menu.click()
|
await menu.click()
|
||||||
const field = await waitUntilShowUp(screens.main.edit)
|
const field = await waitUntilShowUp(screens.main.edit)
|
||||||
await field.click()
|
await field.click()
|
||||||
const accountName = await waitUntilShowUp(screens.main.accountName)
|
const accountName = await waitUntilShowUp(screens.main.fieldAccountName)
|
||||||
assert.notEqual(accountName, false, '\'Account name\' change dialog isn\'t opened')
|
assert.notEqual(accountName, false, '\'Account name\' change dialog isn\'t opened')
|
||||||
assert.equal(await accountName.getAttribute('value'), 'Account 1', 'incorrect account name')
|
assert.equal(await accountName.getAttribute('value'), 'Account 1', 'incorrect account name')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('fill out new account\'s name', async () => {
|
it('fill out new account\'s name', async () => {
|
||||||
const field = await waitUntilShowUp(screens.main.accountName)
|
const field = await waitUntilShowUp(screens.main.fieldAccountName)
|
||||||
await field.clear()
|
await field.clear()
|
||||||
await field.sendKeys(newAccountName)
|
await field.sendKeys(newAccountName)
|
||||||
})
|
})
|
||||||
|
@ -150,7 +150,7 @@ describe('Metamask popup page', async function () {
|
||||||
assert.equal(await button.getText(), 'Save', 'button has incorrect name')
|
assert.equal(await button.getText(), 'Save', 'button has incorrect name')
|
||||||
assert.notEqual(button, true, 'button \'Save\' does not present')
|
assert.notEqual(button, true, 'button \'Save\' does not present')
|
||||||
await click(button)
|
await click(button)
|
||||||
const accountName = await waitUntilShowUp(screens.main.accountName, 10)
|
const accountName = await waitUntilShowUp(screens.main.fieldAccountName, 10)
|
||||||
assert.equal(accountName, false, '\'Account name\' change dialog isn\'t opened')
|
assert.equal(accountName, false, '\'Account name\' change dialog isn\'t opened')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -587,8 +587,8 @@ describe('Metamask popup page', async function () {
|
||||||
it('adds recipient address and amount', async function () {
|
it('adds recipient address and amount', async function () {
|
||||||
const sendTranscationScreen = await waitUntilShowUp(screens.sendTransaction.title)
|
const sendTranscationScreen = await waitUntilShowUp(screens.sendTransaction.title)
|
||||||
assert.equal(await sendTranscationScreen.getText(), screens.sendTransaction.titleText, 'Transaction screen has incorrect titlr')
|
assert.equal(await sendTranscationScreen.getText(), screens.sendTransaction.titleText, 'Transaction screen has incorrect titlr')
|
||||||
const inputAddress = await waitUntilShowUp(screens.sendTransaction.fields.address)
|
const inputAddress = await waitUntilShowUp(screens.sendTransaction.field.address)
|
||||||
const inputAmmount = await waitUntilShowUp(screens.sendTransaction.fields.amount)
|
const inputAmmount = await waitUntilShowUp(screens.sendTransaction.field.amount)
|
||||||
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
|
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
|
||||||
await inputAmmount.sendKeys('10')
|
await inputAmmount.sendKeys('10')
|
||||||
const button = await waitUntilShowUp(screens.sendTransaction.buttonNext)
|
const button = await waitUntilShowUp(screens.sendTransaction.buttonNext)
|
||||||
|
@ -597,7 +597,7 @@ describe('Metamask popup page', async function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('confirms transaction', async function () {
|
it('confirms transaction', async function () {
|
||||||
const button = await waitUntilShowUp(screens.confirmTransaction.buttons.submit)
|
const button = await waitUntilShowUp(screens.confirmTransaction.button.submit)
|
||||||
assert.equal(await button.getAttribute('value'), 'Submit', 'button has incorrect name')
|
assert.equal(await button.getAttribute('value'), 'Submit', 'button has incorrect name')
|
||||||
await click(button)
|
await click(button)
|
||||||
})
|
})
|
||||||
|
@ -924,7 +924,7 @@ describe('Metamask popup page', async function () {
|
||||||
it('confirms transaction in MetaMask popup', async function () {
|
it('confirms transaction in MetaMask popup', async function () {
|
||||||
const windowHandles = await driver.getAllWindowHandles()
|
const windowHandles = await driver.getAllWindowHandles()
|
||||||
await driver.switchTo().window(windowHandles[windowHandles.length - 1])
|
await driver.switchTo().window(windowHandles[windowHandles.length - 1])
|
||||||
const button = await waitUntilShowUp(screens.confirmTransaction.buttons.submit)
|
const button = await waitUntilShowUp(screens.confirmTransaction.button.submit)
|
||||||
await click(button)
|
await click(button)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -949,6 +949,9 @@ describe('Metamask popup page', async function () {
|
||||||
|
|
||||||
it('navigates to the add token screen', async function () {
|
it('navigates to the add token screen', async function () {
|
||||||
await waitUntilShowUp(screens.main.identicon)
|
await waitUntilShowUp(screens.main.identicon)
|
||||||
|
const tab = await waitUntilShowUp(screens.main.tokens.menu)
|
||||||
|
await tab.click()
|
||||||
|
|
||||||
const addTokenButton = await waitUntilShowUp(screens.main.tokens.buttonAdd)
|
const addTokenButton = await waitUntilShowUp(screens.main.tokens.buttonAdd)
|
||||||
assert.equal(await addTokenButton.getText(), screens.main.tokens.buttonAddText)
|
assert.equal(await addTokenButton.getText(), screens.main.tokens.buttonAddText)
|
||||||
await click(addTokenButton)
|
await click(addTokenButton)
|
||||||
|
@ -1009,6 +1012,50 @@ describe('Metamask popup page', async function () {
|
||||||
await switchToFirstPage()
|
await switchToFirstPage()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
describe('Token menu', function () {
|
||||||
|
|
||||||
|
it('token menu is displayed and clickable ', async function () {
|
||||||
|
const menu = await waitUntilShowUp(menus.token.menu)
|
||||||
|
await menu.click()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('link \'View on blockexplorer...\' leads to correct page ', async function () {
|
||||||
|
const menu = await waitUntilShowUp(menus.token.view)
|
||||||
|
assert.notEqual(menu, false, 'item isn\'t displayed')
|
||||||
|
assert.equal(await menu.getText(), menus.token.viewText, 'incorrect name')
|
||||||
|
await menu.click()
|
||||||
|
await switchToLastPage()
|
||||||
|
const title = await driver.getCurrentUrl()
|
||||||
|
assert.equal(title.includes('https://etherscan.io/token/'), true, 'link leads to wrong page')
|
||||||
|
await switchToFirstPage()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('item \'Copy\' is displayed and clickable ', async function () {
|
||||||
|
let menu = await waitUntilShowUp(menus.token.menu)
|
||||||
|
await menu.click()
|
||||||
|
const item = await waitUntilShowUp(menus.token.copy)
|
||||||
|
assert.notEqual(item, false, 'item isn\'t displayed')
|
||||||
|
assert.equal(await item.getText(), menus.token.copyText, 'incorrect name')
|
||||||
|
await item.click()
|
||||||
|
menu = await waitUntilShowUp(menus.token.menu, 10)
|
||||||
|
assert.notEqual(menu, false, 'menu wasn\'t closed')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('item \'Remove\' is displayed', async function () {
|
||||||
|
const menu = await waitUntilShowUp(menus.token.menu)
|
||||||
|
await menu.click()
|
||||||
|
const item = await waitUntilShowUp(menus.token.remove)
|
||||||
|
assert.notEqual(item, false, 'item isn\'t displayed')
|
||||||
|
assert.equal(await item.getText(), menus.token.removeText, 'incorrect name')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('item \'Send \' is displayed', async function () {
|
||||||
|
const item = await waitUntilShowUp(menus.token.send)
|
||||||
|
assert.notEqual(item, false, 'item isn\'t displayed')
|
||||||
|
assert.equal(await item.getText(), menus.token.sendText, 'incorrect name')
|
||||||
|
await waitUntilShowUp(menus.token.menu)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('Check support of token per network basis ', async function () {
|
describe('Check support of token per network basis ', async function () {
|
||||||
|
|
||||||
|
@ -1092,44 +1139,178 @@ describe('Metamask popup page', async function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Token menu', function () {
|
describe('Transfer tokens', function () {
|
||||||
|
|
||||||
it('token menu is displayed and clickable ', async function () {
|
const account2 = '0x2f318C334780961FB129D2a6c30D0763d9a5C970'
|
||||||
const menu = await waitUntilShowUp(menus.token.menu)
|
const invalidAddress = '0xkqjefwblknnecwe'
|
||||||
await menu.click()
|
const invalidAmount = 'eeeee'
|
||||||
})
|
const largeAmount = '123'
|
||||||
|
const preciseAmount = '0.123456789123456789123'
|
||||||
it('link \'View on blockexplorer...\' leads to correct page ', async function () {
|
const negativeAmount = '-1'
|
||||||
const menu = await waitUntilShowUp(menus.token.view)
|
it('switch to account 1 ', async function () {
|
||||||
assert.notEqual(menu, false, 'item isn\'t displayed')
|
const accountMenu = await waitUntilShowUp(menus.account.menu)
|
||||||
assert.equal(await menu.getText(), menus.token.viewText, 'incorrect name')
|
await accountMenu.click()
|
||||||
await menu.click()
|
const item = await waitUntilShowUp(menus.account.account1)
|
||||||
await switchToLastPage()
|
|
||||||
const title = await driver.getCurrentUrl()
|
|
||||||
assert.equal(title.includes('https://etherscan.io/token/'), true, 'link leads to wrong page')
|
|
||||||
await switchToFirstPage()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('item \'Copy\' is displayed and clickable ', async function () {
|
|
||||||
let menu = await waitUntilShowUp(menus.token.menu)
|
|
||||||
await menu.click()
|
|
||||||
const item = await waitUntilShowUp(menus.token.copy)
|
|
||||||
assert.notEqual(item, false, 'item isn\'t displayed')
|
|
||||||
assert.equal(await item.getText(), menus.token.copyText, 'incorrect name')
|
|
||||||
await item.click()
|
await item.click()
|
||||||
menu = await waitUntilShowUp(menus.token.menu, 10)
|
await delay(2000)
|
||||||
assert.notEqual(menu, false, 'menu wasn\'t closed')
|
const accountName = await waitUntilShowUp(screens.main.accountName)
|
||||||
|
assert.equal(await accountName.getText(), 'Account 1', 'account name incorrect')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('item \'Remove\' is displayed', async function () {
|
it('open screen \'Transfer tokens\' ', async function () {
|
||||||
const menu = await waitUntilShowUp(menus.token.menu)
|
const menu = await waitUntilShowUp(menus.token.menu)
|
||||||
await menu.click()
|
await menu.click()
|
||||||
const item = await waitUntilShowUp(menus.token.remove)
|
const item = await waitUntilShowUp(menus.token.send)
|
||||||
assert.notEqual(item, false, 'item isn\'t displayed')
|
await item.click()
|
||||||
assert.equal(await item.getText(), menus.token.removeText, 'incorrect name')
|
})
|
||||||
|
|
||||||
|
it('field \'Amount\' is displayed and has correct placeholder ', async function () {
|
||||||
|
const item = await waitUntilShowUp(screens.sendTokens.field.amount)
|
||||||
|
assert.equal(await item.getAttribute('placeholder'), screens.sendTokens.field.amountPlaceholder, 'placeholder is incorrect')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('field \'Address\' is displayed and has correct placeholder ', async function () {
|
||||||
|
const item = await waitUntilShowUp(screens.sendTokens.field.address)
|
||||||
|
assert.equal(await item.getAttribute('placeholder'), screens.sendTokens.field.addressPlaceholder, 'placeholder is incorrect')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('token\'s balance is correct ', async function () {
|
||||||
|
const item = await waitUntilShowUp(screens.sendTokens.balance)
|
||||||
|
assert.equal(await item.getText(), '100', 'token\'s balance is incorrect')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('token\'s symbol is correct ', async function () {
|
||||||
|
const item = await waitUntilShowUp(screens.sendTokens.symbol)
|
||||||
|
assert.equal(await item.getText(), 'TST', 'token\'s symbol is incorrect')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('error message if invalid token\'s amount', async function () {
|
||||||
|
const button = await waitUntilShowUp(screens.sendTokens.button.next)
|
||||||
|
assert.equal(await button.getText(), 'Next', 'button \'Next\' has incorrect name')
|
||||||
|
await click(button)
|
||||||
|
const error = await waitUntilShowUp(screens.sendTokens.error)
|
||||||
|
assert.equal(await error.getText(), screens.sendTokens.errorText.invalidAmount, ' error message is incorrect')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('error message if invalid address', async function () {
|
||||||
|
const amount = await waitUntilShowUp(screens.sendTokens.field.amount)
|
||||||
|
await amount.sendKeys('1')
|
||||||
|
const address = await waitUntilShowUp(screens.sendTokens.field.address)
|
||||||
|
await address.sendKeys(invalidAddress)
|
||||||
|
const button = await waitUntilShowUp(screens.sendTokens.button.next)
|
||||||
|
await click(button)
|
||||||
|
await click(button)
|
||||||
|
await delay(2000)
|
||||||
|
const error = await waitUntilShowUp(screens.sendTokens.error)
|
||||||
|
assert.equal(await error.getText(), screens.sendTokens.errorText.address, ' error message is incorrect')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('error message if amount is large', async function () {
|
||||||
|
const amount = await waitUntilShowUp(screens.sendTokens.field.amount)
|
||||||
|
await amount.sendKeys(largeAmount)
|
||||||
|
const address = await waitUntilShowUp(screens.sendTokens.field.address)
|
||||||
|
await clearField(address)
|
||||||
|
await address.sendKeys(account2)
|
||||||
|
const button = await waitUntilShowUp(screens.sendTokens.button.next)
|
||||||
|
await click(button)
|
||||||
|
await click(button)
|
||||||
|
await delay(2000)
|
||||||
|
const error = await waitUntilShowUp(screens.sendTokens.error)
|
||||||
|
assert.equal(await error.getText(), screens.sendTokens.errorText.largeAmount, ' error message is incorrect')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('error message if amount is invalid', async function () {
|
||||||
|
const amount = await waitUntilShowUp(screens.sendTokens.field.amount)
|
||||||
|
await clearField(amount)
|
||||||
|
await amount.sendKeys(invalidAmount)
|
||||||
|
const button = await waitUntilShowUp(screens.sendTokens.button.next)
|
||||||
|
await click(button)
|
||||||
|
await click(button)
|
||||||
|
await delay(2000)
|
||||||
|
const error = await waitUntilShowUp(screens.sendTokens.error)
|
||||||
|
assert.equal(await error.getText(), screens.sendTokens.errorText.invalidAmount, ' error message is incorrect')
|
||||||
|
})
|
||||||
|
it.skip('error message if amount is too precise', async function () {
|
||||||
|
const amount = await waitUntilShowUp(screens.sendTokens.field.amount)
|
||||||
|
await clearField(amount)
|
||||||
|
await amount.sendKeys(preciseAmount)
|
||||||
|
const button = await waitUntilShowUp(screens.sendTokens.button.next)
|
||||||
|
await click(button)
|
||||||
|
await click(button)
|
||||||
|
await delay(2000)
|
||||||
|
const error = await waitUntilShowUp(screens.sendTokens.error)
|
||||||
|
assert.equal(await error.getText(), screens.sendTokens.errorText.tooPrecise, ' error message is incorrect')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('error message if amount is negative', async function () {
|
||||||
|
const amount = await waitUntilShowUp(screens.sendTokens.field.amount)
|
||||||
|
await clearField(amount)
|
||||||
|
await amount.sendKeys(negativeAmount)
|
||||||
|
const button = await waitUntilShowUp(screens.sendTokens.button.next)
|
||||||
|
await click(button)
|
||||||
|
await click(button)
|
||||||
|
await delay(2000)
|
||||||
|
const error = await waitUntilShowUp(screens.sendTokens.error)
|
||||||
|
assert.equal(await error.getText(), screens.sendTokens.errorText.negativeAmount, ' error message is incorrect')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('\'Confirm transaction\' screen is opened if address and amount are correct', async function () {
|
||||||
|
const amount = await waitUntilShowUp(screens.sendTokens.field.amount)
|
||||||
|
await clearField(amount)
|
||||||
|
await amount.sendKeys('5')
|
||||||
|
const button = await waitUntilShowUp(screens.sendTokens.button.next)
|
||||||
|
await click(button)
|
||||||
|
|
||||||
|
const buttonSubmit = await waitUntilShowUp(screens.confirmTransaction.button.submit)
|
||||||
|
assert.notEqual(buttonSubmit, false, 'incorrect screen was opened')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('\'Confirm transaction\' screen: token\'s amount is correct', async function () {
|
||||||
|
const amount = await waitUntilShowUp(screens.confirmTransaction.amount)
|
||||||
|
assert.equal(await amount.getText(), '5.000', ' amount is incorrect')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('\'Confirm transaction\' screen: token\'s symbol is correct', async function () {
|
||||||
|
const symbol = await waitUntilShowUp(screens.confirmTransaction.symbol)
|
||||||
|
assert.equal(await symbol.getText(), 'TST', ' symbol is incorrect')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('submit transaction', async function () {
|
||||||
|
await driver.navigate().refresh()
|
||||||
|
const button = await waitUntilShowUp(screens.confirmTransaction.button.submit)
|
||||||
|
await click(button)
|
||||||
|
const list = await waitUntilShowUp(screens.main.transactionList)
|
||||||
|
assert.notEqual(list, false, ' main screen isn\'t opened')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('correct amount substracted from sender\'s tokens balance', async function () {
|
||||||
|
const tab = await waitUntilShowUp(screens.main.tokens.menu)
|
||||||
|
await tab.click()
|
||||||
|
await driver.navigate().refresh()
|
||||||
|
await delay(5000)
|
||||||
|
await driver.navigate().refresh()
|
||||||
|
await delay(5000)
|
||||||
|
await driver.navigate().refresh()
|
||||||
|
await delay(5000)
|
||||||
|
const balance = await waitUntilShowUp(screens.main.tokens.balance)
|
||||||
|
|
||||||
|
assert.equal(await balance.getText(), '95 TST', 'balance is incorrect')
|
||||||
|
})
|
||||||
|
it('switch to account 2 ', async function () {
|
||||||
|
const accountMenu = await waitUntilShowUp(menus.account.menu)
|
||||||
|
await accountMenu.click()
|
||||||
|
const item = await waitUntilShowUp(menus.account.account2)
|
||||||
|
await item.click()
|
||||||
|
await delay(2000)
|
||||||
|
const accountName = await waitUntilShowUp(screens.main.accountName)
|
||||||
|
assert.equal(await accountName.getText(), 'Account 2', 'account name incorrect')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('receiver got correct amount of tokens', async function () {
|
||||||
|
const balance = await waitUntilShowUp(screens.main.tokens.balance)
|
||||||
|
assert.equal(await balance.getText(), '5 TST', 'balance is incorrect')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Remove token , provider is localhost', function () {
|
describe('Remove token , provider is localhost', function () {
|
||||||
|
|
||||||
it('remove option opens \'Remove token\' screen ', async function () {
|
it('remove option opens \'Remove token\' screen ', async function () {
|
||||||
|
|
|
@ -1074,7 +1074,7 @@ function sendTx (txData) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function signTokenTx (tokenAddress, toAddress, amount, txData) {
|
function signTokenTx (tokenAddress, toAddress, amount, txData, confTxScreenParams) {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
dispatch(actions.showLoadingIndication())
|
dispatch(actions.showLoadingIndication())
|
||||||
const token = global.eth.contract(abi).at(tokenAddress)
|
const token = global.eth.contract(abi).at(tokenAddress)
|
||||||
|
@ -1083,7 +1083,7 @@ function signTokenTx (tokenAddress, toAddress, amount, txData) {
|
||||||
dispatch(actions.hideLoadingIndication())
|
dispatch(actions.hideLoadingIndication())
|
||||||
dispatch(actions.displayWarning(err.message))
|
dispatch(actions.displayWarning(err.message))
|
||||||
})
|
})
|
||||||
dispatch(actions.showConfTxPage({}))
|
dispatch(actions.showConfTxPage(confTxScreenParams || {}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1109,7 +1109,7 @@ function updateTransaction (txData) {
|
||||||
.then(() => updateMetamaskStateFromBackground())
|
.then(() => updateMetamaskStateFromBackground())
|
||||||
.then(newState => dispatch(actions.updateMetamaskState(newState)))
|
.then(newState => dispatch(actions.updateMetamaskState(newState)))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
dispatch(actions.showConfTxPage({ id: txData.id }))
|
dispatch(actions.showConfTxPage({ id: txData.id}))
|
||||||
dispatch(actions.hideLoadingIndication())
|
dispatch(actions.hideLoadingIndication())
|
||||||
return txData
|
return txData
|
||||||
})
|
})
|
||||||
|
@ -1143,6 +1143,7 @@ function updateAndApproveTx (txData) {
|
||||||
dispatch(actions.clearSend())
|
dispatch(actions.clearSend())
|
||||||
dispatch(actions.completedTx(txData.id))
|
dispatch(actions.completedTx(txData.id))
|
||||||
dispatch(actions.hideLoadingIndication())
|
dispatch(actions.hideLoadingIndication())
|
||||||
|
dispatch(actions.setCurrentAccountTab('history'))
|
||||||
|
|
||||||
if (!hasUnconfirmedTransactions(getState())) {
|
if (!hasUnconfirmedTransactions(getState())) {
|
||||||
return global.platform.closeNotificationWindow()
|
return global.platform.closeNotificationWindow()
|
||||||
|
@ -1542,11 +1543,12 @@ function showAccountsPage () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showConfTxPage ({transForward = true, id}) {
|
function showConfTxPage (screenParams) {
|
||||||
return {
|
return {
|
||||||
type: actions.SHOW_CONF_TX_PAGE,
|
type: actions.SHOW_CONF_TX_PAGE,
|
||||||
transForward,
|
transForward: (screenParams.transForward || true),
|
||||||
id,
|
id: screenParams.id,
|
||||||
|
value: screenParams,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1994,9 +1996,11 @@ function showSendPage () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSendTokenPage () {
|
|
||||||
|
function showSendTokenPage (address) {
|
||||||
return {
|
return {
|
||||||
type: actions.SHOW_SEND_TOKEN_PAGE,
|
type: actions.SHOW_SEND_TOKEN_PAGE,
|
||||||
|
value: address,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -122,7 +122,7 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa
|
||||||
|
|
||||||
if (isUnapproved) {
|
if (isUnapproved) {
|
||||||
opts.onClick = () => {
|
opts.onClick = () => {
|
||||||
this.props.showConfTxPage({ id: transactionId })
|
this.props.showConfTxPage({ id: transactionId})
|
||||||
history.push(CONFIRM_TRANSACTION_ROUTE)
|
history.push(CONFIRM_TRANSACTION_ROUTE)
|
||||||
}
|
}
|
||||||
opts.transactionStatus = this.context.t('notStarted')
|
opts.transactionStatus = this.context.t('notStarted')
|
||||||
|
|
|
@ -297,6 +297,7 @@ function reduceApp (state, action) {
|
||||||
currentView: {
|
currentView: {
|
||||||
name: 'sendToken',
|
name: 'sendToken',
|
||||||
context: appState.currentView.context,
|
context: appState.currentView.context,
|
||||||
|
tokenAddress: action.value,
|
||||||
},
|
},
|
||||||
transForward: true,
|
transForward: true,
|
||||||
warning: null,
|
warning: null,
|
||||||
|
@ -438,6 +439,7 @@ function reduceApp (state, action) {
|
||||||
currentView: {
|
currentView: {
|
||||||
name: 'confTx',
|
name: 'confTx',
|
||||||
pendingTxIndex: action.id ? indexForPending(state, action.id) : 0,
|
pendingTxIndex: action.id ? indexForPending(state, action.id) : 0,
|
||||||
|
screenParams: action.value,
|
||||||
},
|
},
|
||||||
transForward: action.transForward,
|
transForward: action.transForward,
|
||||||
warning: null,
|
warning: null,
|
||||||
|
|
|
@ -47,9 +47,15 @@ function calcTokenAmount (value, decimals) {
|
||||||
return new BigNumber(value).div(multiplier).toNumber()
|
return new BigNumber(value).div(multiplier).toNumber()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function calcTokenAmountWithDec (valueWithoutDec, decimals) {
|
||||||
|
const multiplier = Math.pow(10, Number(decimals || 0))
|
||||||
|
return new BigNumber(valueWithoutDec).mul(multiplier).toNumber()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
tokenInfoGetter,
|
tokenInfoGetter,
|
||||||
calcTokenAmount,
|
calcTokenAmount,
|
||||||
|
calcTokenAmountWithDec,
|
||||||
getSymbolAndDecimals,
|
getSymbolAndDecimals,
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,7 @@ async function startApp (metamaskState, accountManager, opts) {
|
||||||
const unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.unapprovedPersonalMsgs, metamaskState.unapprovedTypedMessages, metamaskState.network)
|
const unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.unapprovedPersonalMsgs, metamaskState.unapprovedTypedMessages, metamaskState.network)
|
||||||
const numberOfUnapprivedTx = unapprovedTxsAll.length
|
const numberOfUnapprivedTx = unapprovedTxsAll.length
|
||||||
if (numberOfUnapprivedTx > 0) {
|
if (numberOfUnapprivedTx > 0) {
|
||||||
|
|
||||||
store.dispatch(actions.showConfTxPage({
|
store.dispatch(actions.showConfTxPage({
|
||||||
id: unapprovedTxsAll[numberOfUnapprivedTx - 1].id,
|
id: unapprovedTxsAll[numberOfUnapprivedTx - 1].id,
|
||||||
}))
|
}))
|
||||||
|
|
Loading…
Reference in New Issue