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
|
||||
const AccountDetailScreen = require('./account-detail')
|
||||
const SendTransactionScreen = require('./send')
|
||||
const SendTokenScreen = require('./send-token')
|
||||
const ConfirmTxScreen = require('./conf-tx')
|
||||
// notice
|
||||
const NoticeScreen = require('./components/notice')
|
||||
|
@ -577,6 +578,10 @@ App.prototype.renderPrimary = function () {
|
|||
log.debug('rendering send tx screen')
|
||||
return h(SendTransactionScreen, {key: 'send-transaction'})
|
||||
|
||||
case 'sendToken':
|
||||
log.debug('rendering send tx screen')
|
||||
return h(SendTokenScreen, {key: 'send-token'})
|
||||
|
||||
case 'newKeychain':
|
||||
log.debug('rendering new keychain screen')
|
||||
return h(NewKeyChainScreen, {key: 'new-keychain'})
|
||||
|
|
|
@ -16,9 +16,9 @@ function EthBalanceComponent () {
|
|||
EthBalanceComponent.prototype.render = function () {
|
||||
var props = this.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
|
||||
value = value ? formatBalance(value, 6, needsParse, network) : '...'
|
||||
value = value ? formatBalance(value, 6, needsParse, network, isToken, tokenSymbol) : '...'
|
||||
|
||||
return (
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ const util = require('../util')
|
|||
const MiniAccountPanel = require('./mini-account-panel')
|
||||
const Copyable = require('./copyable')
|
||||
const EthBalance = require('./eth-balance')
|
||||
const TokenBalance = require('./token-balance')
|
||||
const addressSummary = util.addressSummary
|
||||
const accountSummary = util.accountSummary
|
||||
const nameForAddress = require('../../lib/contract-namer')
|
||||
|
@ -20,6 +21,9 @@ const { getEnvironmentType } = require('../../../app/scripts/lib/util')
|
|||
const NetworkIndicator = require('../components/network')
|
||||
const { ENVIRONMENT_TYPE_NOTIFICATION } = require('../../../app/scripts/lib/enums')
|
||||
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_LIMIT_BN = new BN('21000')
|
||||
|
@ -32,7 +36,11 @@ function PendingTx () {
|
|||
valid: true,
|
||||
txData: null,
|
||||
submitting: false,
|
||||
tokenSymbol: '',
|
||||
tokenDecimals: 0,
|
||||
tokenDataRetrieved: false,
|
||||
}
|
||||
this.tokenInfoGetter = tokenInfoGetter()
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
|
@ -57,12 +65,32 @@ function mapStateToProps (state) {
|
|||
}
|
||||
|
||||
PendingTx.prototype.render = function () {
|
||||
const state = this.state
|
||||
if (!state.tokenDataRetrieved) return null
|
||||
const props = this.props
|
||||
const { currentCurrency, blockGasLimit, network, provider, isUnlocked } = props
|
||||
|
||||
const conversionRate = props.conversionRate
|
||||
const txMeta = this.gatherTxMeta()
|
||||
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
|
||||
const { lastGasPrice } = txMeta
|
||||
|
@ -217,7 +245,10 @@ PendingTx.prototype.render = function () {
|
|||
fontFamily: 'Nunito Regular',
|
||||
},
|
||||
}, [
|
||||
h(EthBalance, {
|
||||
isToken ? h(TokenBalance, {
|
||||
token,
|
||||
fontSize: '12px',
|
||||
}) : h(EthBalance, {
|
||||
fontSize: '12px',
|
||||
value: balance,
|
||||
conversionRate,
|
||||
|
@ -231,7 +262,7 @@ PendingTx.prototype.render = function () {
|
|||
|
||||
forwardCarrat(),
|
||||
|
||||
this.miniAccountPanelForRecipient(),
|
||||
this.miniAccountPanelForRecipient(isToken, tokensTransferTo),
|
||||
]),
|
||||
|
||||
h('style', `
|
||||
|
@ -326,7 +357,17 @@ PendingTx.prototype.render = function () {
|
|||
// in the way that gas and gasLimit currently are.
|
||||
h('.row', [
|
||||
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)
|
||||
|
@ -381,7 +422,14 @@ PendingTx.prototype.render = function () {
|
|||
// Max Transaction Fee (calculated)
|
||||
h('.cell.row', [
|
||||
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', {
|
||||
|
@ -482,11 +530,12 @@ PendingTx.prototype.render = function () {
|
|||
)
|
||||
}
|
||||
|
||||
PendingTx.prototype.miniAccountPanelForRecipient = function () {
|
||||
PendingTx.prototype.miniAccountPanelForRecipient = function (isToken, tokensTransferTo) {
|
||||
const props = this.props
|
||||
const txData = props.txData
|
||||
const txParams = txData.txParams || {}
|
||||
const isContractDeploy = !('to' in txParams)
|
||||
const to = isToken ? tokensTransferTo : txParams.to
|
||||
|
||||
// If it's not a contract deploy, send to the account
|
||||
if (!isContractDeploy) {
|
||||
|
@ -506,17 +555,17 @@ PendingTx.prototype.miniAccountPanelForRecipient = function () {
|
|||
display: 'inline-block',
|
||||
whiteSpace: 'nowrap',
|
||||
},
|
||||
}, accountSummary(nameForAddress(txParams.to, props.identities)), 6, 4),
|
||||
}, accountSummary(nameForAddress(to, props.identities)), 6, 4),
|
||||
|
||||
h(Copyable, {
|
||||
value: ethUtil.toChecksumAddress(txParams.to),
|
||||
value: ethUtil.toChecksumAddress(to),
|
||||
}, [
|
||||
h('span.font-small', {
|
||||
style: {
|
||||
fontFamily: 'Nunito Regular',
|
||||
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) {
|
||||
log.info(`Gas price changed to: ${newBN.toString(10)}`)
|
||||
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 ethUtil = require('ethereumjs-util')
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
const actions = require('../../../ui/app/actions')
|
||||
const connect = require('react-redux').connect
|
||||
|
||||
const tokenCellDropDownPrefix = 'token-cell_dropdown_'
|
||||
|
||||
module.exports = TokenCell
|
||||
|
||||
inherits(TokenCell, Component)
|
||||
function TokenCell () {
|
||||
Component.call(this)
|
||||
|
@ -76,7 +76,7 @@ TokenCell.prototype.render = function () {
|
|||
}
|
||||
|
||||
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
|
||||
|
||||
return h(
|
||||
|
@ -84,9 +84,9 @@ TokenCell.prototype.renderTokenOptions = function (menuToTop, ind) {
|
|||
{
|
||||
style: {
|
||||
position: 'relative',
|
||||
marginLeft: '-263px',
|
||||
marginLeft: menuToTop ? '-273px' : '-263px',
|
||||
minWidth: '180px',
|
||||
marginTop: menuToTop ? '-200px' : '30px',
|
||||
marginTop: menuToTop ? '-214px' : '30px',
|
||||
width: '280px',
|
||||
},
|
||||
isOpen: optionsMenuActive,
|
||||
|
@ -100,6 +100,16 @@ TokenCell.prototype.renderTokenOptions = function (menuToTop, ind) {
|
|||
},
|
||||
},
|
||||
[
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => {
|
||||
showSendTokenPage(address)
|
||||
},
|
||||
},
|
||||
`Send`,
|
||||
),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
|
@ -159,3 +169,11 @@ function tokenFactoryFor (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)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { metamask, appState } = state
|
||||
const { screenParams, pendingTxIndex } = appState.currentView
|
||||
return {
|
||||
identities: state.metamask.identities,
|
||||
accounts: state.metamask.accounts,
|
||||
selectedAddress: state.metamask.selectedAddress,
|
||||
unapprovedTxs: state.metamask.unapprovedTxs,
|
||||
unapprovedMsgs: state.metamask.unapprovedMsgs,
|
||||
unapprovedPersonalMsgs: state.metamask.unapprovedPersonalMsgs,
|
||||
unapprovedTypedMessages: state.metamask.unapprovedTypedMessages,
|
||||
index: state.appState.currentView.pendingTxIndex || 0,
|
||||
warning: state.appState.warning,
|
||||
network: state.metamask.network,
|
||||
provider: state.metamask.provider,
|
||||
conversionRate: state.metamask.conversionRate,
|
||||
currentCurrency: state.metamask.currentCurrency,
|
||||
blockGasLimit: state.metamask.currentBlockGasLimit,
|
||||
computedBalances: state.metamask.computedBalances,
|
||||
identities: metamask.identities,
|
||||
accounts: metamask.accounts,
|
||||
selectedAddress: metamask.selectedAddress,
|
||||
unapprovedTxs: metamask.unapprovedTxs,
|
||||
unapprovedMsgs: metamask.unapprovedMsgs,
|
||||
unapprovedPersonalMsgs: metamask.unapprovedPersonalMsgs,
|
||||
unapprovedTypedMessages: metamask.unapprovedTypedMessages,
|
||||
index: pendingTxIndex || 0,
|
||||
warning: appState.warning,
|
||||
network: metamask.network,
|
||||
provider: metamask.provider,
|
||||
conversionRate: metamask.conversionRate,
|
||||
currentCurrency: metamask.currentCurrency,
|
||||
blockGasLimit: metamask.currentBlockGasLimit,
|
||||
computedBalances: metamask.computedBalances,
|
||||
isToken: (screenParams && screenParams.isToken),
|
||||
tokenSymbol: (screenParams && screenParams.tokenSymbol),
|
||||
tokensToSend: (screenParams && screenParams.tokensToSend),
|
||||
tokensTransferTo: (screenParams && screenParams.tokensTransferTo),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,6 +101,10 @@ ConfirmTxScreen.prototype.render = function () {
|
|||
unconfTxListLength,
|
||||
computedBalances,
|
||||
network,
|
||||
isToken: props.isToken,
|
||||
tokenSymbol: props.tokenSymbol,
|
||||
tokensToSend: props.tokensToSend,
|
||||
tokensTransferTo: props.tokensTransferTo,
|
||||
// Actions
|
||||
buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress),
|
||||
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 nickname = state.nickname || ' '
|
||||
const input = document.querySelector('input[name="amount"]').value
|
||||
const parts = input.split('')
|
||||
const parts = input.split('.')
|
||||
|
||||
let message
|
||||
|
||||
|
|
|
@ -111,10 +111,11 @@ function parseBalance (balance) {
|
|||
|
||||
// Takes wei hex, returns an object with three properties.
|
||||
// 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 isPOA = parseInt(network) === 99
|
||||
const coinName = isPOA ? 'POA' : isSokol ? 'SPOA' : 'ETH'
|
||||
const assetName = isToken ? tokenSymbol : coinName
|
||||
var parsed = needsParse ? parseBalance(balance) : balance.split('.')
|
||||
var beforeDecimal = parsed[0]
|
||||
var afterDecimal = parsed[1]
|
||||
|
@ -124,14 +125,14 @@ function formatBalance (balance, decimalsToKeep, needsParse = true, network) {
|
|||
if (afterDecimal !== '0') {
|
||||
var sigFigs = afterDecimal.match(/^0*(.{2})/) // default: grabs 2 most significant digits
|
||||
if (sigFigs) { afterDecimal = sigFigs[0] }
|
||||
formatted = '0.' + afterDecimal + ` ${coinName}`
|
||||
formatted = '0.' + afterDecimal + ` ${assetName}`
|
||||
}
|
||||
} else {
|
||||
formatted = beforeDecimal + '.' + afterDecimal.slice(0, 3) + ` ${coinName}`
|
||||
formatted = beforeDecimal + '.' + afterDecimal.slice(0, 3) + ` ${assetName}`
|
||||
}
|
||||
} else {
|
||||
afterDecimal += Array(decimalsToKeep).join('0')
|
||||
formatted = beforeDecimal + '.' + afterDecimal.slice(0, decimalsToKeep) + ` ${coinName}`
|
||||
formatted = beforeDecimal + '.' + afterDecimal.slice(0, decimalsToKeep) + ` ${assetName}`
|
||||
}
|
||||
return formatted
|
||||
}
|
||||
|
|
|
@ -8,9 +8,11 @@ module.exports = {
|
|||
token: {
|
||||
menu: By.id('token-cell_dropdown_0'),
|
||||
items: By.className('dropdown-menu-item'),
|
||||
view: 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)'),
|
||||
remove: By.css('#token-cell_dropdown_0 > div > div > li:nth-child(4)'),
|
||||
send: By.css('#token-cell_dropdown_0 > div > div > li:nth-child(2)'),
|
||||
view: By.css('#token-cell_dropdown_0 > div > div > li:nth-child(3)'),
|
||||
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',
|
||||
copyText: 'Copy address to clipboard',
|
||||
removeText: 'Remove',
|
||||
|
@ -25,6 +27,7 @@ module.exports = {
|
|||
},
|
||||
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'),
|
||||
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'),
|
||||
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'),
|
||||
|
@ -43,6 +46,29 @@ module.exports = {
|
|||
},
|
||||
},
|
||||
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: {
|
||||
key: By.css('#app-content > div > div.app-primary.from-right > div > div.privateKey > div.flex-row > p'),
|
||||
copy: By.className('clipboard cursor-pointer'),
|
||||
|
@ -120,14 +146,17 @@ module.exports = {
|
|||
titleText: 'Delete Custom RPC',
|
||||
},
|
||||
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'),
|
||||
},
|
||||
},
|
||||
sendTransaction: {
|
||||
title: By.css('#app-content > div > div.app-primary.from-right > div > h3:nth-child(2)'),
|
||||
titleText: 'Send Transaction',
|
||||
fields: {
|
||||
field: {
|
||||
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'),
|
||||
},
|
||||
|
@ -179,7 +208,8 @@ module.exports = {
|
|||
},
|
||||
main: {
|
||||
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'),
|
||||
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)'),
|
||||
|
@ -199,7 +229,7 @@ module.exports = {
|
|||
tokens: {
|
||||
menu: By.className('inactiveForm pointer'),
|
||||
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'),
|
||||
textNoTokens: 'No tokens found',
|
||||
textYouOwn1token: 'You own 1 token',
|
||||
|
|
|
@ -134,13 +134,13 @@ describe('Metamask popup page', async function () {
|
|||
await menu.click()
|
||||
const field = await waitUntilShowUp(screens.main.edit)
|
||||
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.equal(await accountName.getAttribute('value'), 'Account 1', 'incorrect account name')
|
||||
})
|
||||
|
||||
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.sendKeys(newAccountName)
|
||||
})
|
||||
|
@ -150,7 +150,7 @@ describe('Metamask popup page', async function () {
|
|||
assert.equal(await button.getText(), 'Save', 'button has incorrect name')
|
||||
assert.notEqual(button, true, 'button \'Save\' does not present')
|
||||
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')
|
||||
})
|
||||
|
||||
|
@ -587,8 +587,8 @@ describe('Metamask popup page', async function () {
|
|||
it('adds recipient address and amount', async function () {
|
||||
const sendTranscationScreen = await waitUntilShowUp(screens.sendTransaction.title)
|
||||
assert.equal(await sendTranscationScreen.getText(), screens.sendTransaction.titleText, 'Transaction screen has incorrect titlr')
|
||||
const inputAddress = await waitUntilShowUp(screens.sendTransaction.fields.address)
|
||||
const inputAmmount = await waitUntilShowUp(screens.sendTransaction.fields.amount)
|
||||
const inputAddress = await waitUntilShowUp(screens.sendTransaction.field.address)
|
||||
const inputAmmount = await waitUntilShowUp(screens.sendTransaction.field.amount)
|
||||
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
|
||||
await inputAmmount.sendKeys('10')
|
||||
const button = await waitUntilShowUp(screens.sendTransaction.buttonNext)
|
||||
|
@ -597,7 +597,7 @@ describe('Metamask popup page', 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')
|
||||
await click(button)
|
||||
})
|
||||
|
@ -924,7 +924,7 @@ describe('Metamask popup page', async function () {
|
|||
it('confirms transaction in MetaMask popup', async function () {
|
||||
const windowHandles = await driver.getAllWindowHandles()
|
||||
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)
|
||||
})
|
||||
|
||||
|
@ -949,6 +949,9 @@ describe('Metamask popup page', async function () {
|
|||
|
||||
it('navigates to the add token screen', async function () {
|
||||
await waitUntilShowUp(screens.main.identicon)
|
||||
const tab = await waitUntilShowUp(screens.main.tokens.menu)
|
||||
await tab.click()
|
||||
|
||||
const addTokenButton = await waitUntilShowUp(screens.main.tokens.buttonAdd)
|
||||
assert.equal(await addTokenButton.getText(), screens.main.tokens.buttonAddText)
|
||||
await click(addTokenButton)
|
||||
|
@ -1009,6 +1012,50 @@ describe('Metamask popup page', async function () {
|
|||
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 () {
|
||||
|
||||
|
@ -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 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')
|
||||
const account2 = '0x2f318C334780961FB129D2a6c30D0763d9a5C970'
|
||||
const invalidAddress = '0xkqjefwblknnecwe'
|
||||
const invalidAmount = 'eeeee'
|
||||
const largeAmount = '123'
|
||||
const preciseAmount = '0.123456789123456789123'
|
||||
const negativeAmount = '-1'
|
||||
it('switch to account 1 ', async function () {
|
||||
const accountMenu = await waitUntilShowUp(menus.account.menu)
|
||||
await accountMenu.click()
|
||||
const item = await waitUntilShowUp(menus.account.account1)
|
||||
await item.click()
|
||||
menu = await waitUntilShowUp(menus.token.menu, 10)
|
||||
assert.notEqual(menu, false, 'menu wasn\'t closed')
|
||||
await delay(2000)
|
||||
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)
|
||||
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')
|
||||
const item = await waitUntilShowUp(menus.token.send)
|
||||
await item.click()
|
||||
})
|
||||
|
||||
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 () {
|
||||
|
||||
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 => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
const token = global.eth.contract(abi).at(tokenAddress)
|
||||
|
@ -1083,7 +1083,7 @@ function signTokenTx (tokenAddress, toAddress, amount, txData) {
|
|||
dispatch(actions.hideLoadingIndication())
|
||||
dispatch(actions.displayWarning(err.message))
|
||||
})
|
||||
dispatch(actions.showConfTxPage({}))
|
||||
dispatch(actions.showConfTxPage(confTxScreenParams || {}))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1109,7 +1109,7 @@ function updateTransaction (txData) {
|
|||
.then(() => updateMetamaskStateFromBackground())
|
||||
.then(newState => dispatch(actions.updateMetamaskState(newState)))
|
||||
.then(() => {
|
||||
dispatch(actions.showConfTxPage({ id: txData.id }))
|
||||
dispatch(actions.showConfTxPage({ id: txData.id}))
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
return txData
|
||||
})
|
||||
|
@ -1143,6 +1143,7 @@ function updateAndApproveTx (txData) {
|
|||
dispatch(actions.clearSend())
|
||||
dispatch(actions.completedTx(txData.id))
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
dispatch(actions.setCurrentAccountTab('history'))
|
||||
|
||||
if (!hasUnconfirmedTransactions(getState())) {
|
||||
return global.platform.closeNotificationWindow()
|
||||
|
@ -1542,11 +1543,12 @@ function showAccountsPage () {
|
|||
}
|
||||
}
|
||||
|
||||
function showConfTxPage ({transForward = true, id}) {
|
||||
function showConfTxPage (screenParams) {
|
||||
return {
|
||||
type: actions.SHOW_CONF_TX_PAGE,
|
||||
transForward,
|
||||
id,
|
||||
transForward: (screenParams.transForward || true),
|
||||
id: screenParams.id,
|
||||
value: screenParams,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1994,9 +1996,11 @@ function showSendPage () {
|
|||
}
|
||||
}
|
||||
|
||||
function showSendTokenPage () {
|
||||
|
||||
function showSendTokenPage (address) {
|
||||
return {
|
||||
type: actions.SHOW_SEND_TOKEN_PAGE,
|
||||
value: address,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -122,7 +122,7 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa
|
|||
|
||||
if (isUnapproved) {
|
||||
opts.onClick = () => {
|
||||
this.props.showConfTxPage({ id: transactionId })
|
||||
this.props.showConfTxPage({ id: transactionId})
|
||||
history.push(CONFIRM_TRANSACTION_ROUTE)
|
||||
}
|
||||
opts.transactionStatus = this.context.t('notStarted')
|
||||
|
|
|
@ -297,6 +297,7 @@ function reduceApp (state, action) {
|
|||
currentView: {
|
||||
name: 'sendToken',
|
||||
context: appState.currentView.context,
|
||||
tokenAddress: action.value,
|
||||
},
|
||||
transForward: true,
|
||||
warning: null,
|
||||
|
@ -438,6 +439,7 @@ function reduceApp (state, action) {
|
|||
currentView: {
|
||||
name: 'confTx',
|
||||
pendingTxIndex: action.id ? indexForPending(state, action.id) : 0,
|
||||
screenParams: action.value,
|
||||
},
|
||||
transForward: action.transForward,
|
||||
warning: null,
|
||||
|
|
|
@ -47,9 +47,15 @@ function calcTokenAmount (value, decimals) {
|
|||
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 = {
|
||||
tokenInfoGetter,
|
||||
calcTokenAmount,
|
||||
calcTokenAmountWithDec,
|
||||
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 numberOfUnapprivedTx = unapprovedTxsAll.length
|
||||
if (numberOfUnapprivedTx > 0) {
|
||||
|
||||
store.dispatch(actions.showConfTxPage({
|
||||
id: unapprovedTxsAll[numberOfUnapprivedTx - 1].id,
|
||||
}))
|
||||
|
|
Loading…
Reference in New Issue