mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Core of the refactor complete
This commit is contained in:
parent
02a6d2089e
commit
8ff7806f1b
@ -8,6 +8,7 @@ export default class PageContainer extends Component {
|
||||
};
|
||||
|
||||
render () {
|
||||
console.log(`QQQQQQQQQQQQQQQQQ this.props.children`, this.props.children);
|
||||
return (
|
||||
<div className="page-container">
|
||||
{this.props.children}
|
||||
|
@ -0,0 +1,74 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { checksumAddress } from '../../../util'
|
||||
import Identicon from '../../identicon'
|
||||
import CurrencyDisplay from '../../send/currency-display'
|
||||
|
||||
export default class AccountListItem extends Component {
|
||||
|
||||
static propTypes = {
|
||||
account: PropTypes.object,
|
||||
className: PropTypes.string,
|
||||
conversionRate: PropTypes.number,
|
||||
currentCurrency: PropTypes.string,
|
||||
displayAddress: PropTypes.bool,
|
||||
displayBalance: PropTypes.bool,
|
||||
handleClick: PropTypes.func,
|
||||
icon: PropTypes.node,
|
||||
};
|
||||
|
||||
render () {
|
||||
const {
|
||||
className,
|
||||
account,
|
||||
handleClick,
|
||||
icon = null,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
displayBalance = true,
|
||||
displayAddress = false,
|
||||
} = this.props
|
||||
|
||||
const { name, address, balance } = account || {}
|
||||
|
||||
return (<div
|
||||
className={`account-list-item ${className}`}
|
||||
onClick={() => handleClick({ name, address, balance })}
|
||||
>
|
||||
|
||||
<div className='account-list-item__top-row'>
|
||||
<Identicon
|
||||
address={address}
|
||||
diameter={18}
|
||||
className='account-list-item__identicon'
|
||||
/>
|
||||
|
||||
<div className='account-list-item__account-name'>{ name || address }</div>
|
||||
|
||||
{icon && <div className='account-list-item__icon'>{ icon }</div>}
|
||||
|
||||
</div>
|
||||
|
||||
{displayAddress && name && <div className='account-list-item__account-address'>
|
||||
{ checksumAddress(address) }
|
||||
</div>}
|
||||
|
||||
{displayBalance && <CurrencyDisplay
|
||||
primaryCurrency='ETH'
|
||||
convertedCurrency={currentCurrency}
|
||||
value={balance}
|
||||
conversionRate={conversionRate}
|
||||
readOnly={true}
|
||||
className='account-list-item__account-balances'
|
||||
primaryBalanceClassName='account-list-item__account-primary-balance'
|
||||
convertedBalanceClassName='account-list-item__account-secondary-balance'
|
||||
/>}
|
||||
|
||||
</div>)
|
||||
}
|
||||
}
|
||||
|
||||
AccountListItem.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
@ -0,0 +1,15 @@
|
||||
import { connect } from 'react-redux'
|
||||
import {
|
||||
getConversionRate,
|
||||
getConvertedCurrency,
|
||||
} from '../send.selectors.js'
|
||||
import AccountListItem from './account-list-item.component'
|
||||
|
||||
export default connect(mapStateToProps)(AccountListItem)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
conversionRate: getConversionRate(state),
|
||||
currentCurrency: getConvertedCurrency(state),
|
||||
}
|
||||
}
|
@ -31,7 +31,7 @@ export default class AmountMaxButton extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { setMaxModeTo } = this.props
|
||||
const { setMaxModeTo, maxModeOn } = this.props
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -42,7 +42,7 @@ export default class AmountMaxButton extends Component {
|
||||
this.setAmountToMax()
|
||||
}}
|
||||
>
|
||||
{!maxModeOn ? this.context.t('max') : '' ])}
|
||||
{!maxModeOn ? this.context.t('max') : ''}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { connect } from 'react-redux'
|
||||
import {
|
||||
getSelectedToken,
|
||||
getGasTotal,
|
||||
|
@ -1,15 +1,14 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component'
|
||||
import AmountMaxButton from '../amount-max-button/amount-max-button.component'
|
||||
import AmountMaxButton from './amount-max-button/amount-max-button.component'
|
||||
import CurrencyDisplay from '../../../send/currency-display'
|
||||
|
||||
export default class SendAmountRow extends Component {
|
||||
|
||||
static propTypes = {
|
||||
amountConversionRate: PropTypes.string,
|
||||
conversionRate: PropTypes.string,
|
||||
from: PropTypes.object,
|
||||
amountConversionRate: PropTypes.number,
|
||||
conversionRate: PropTypes.number,
|
||||
gasTotal: PropTypes.string,
|
||||
primaryCurrency: PropTypes.string,
|
||||
selectedToken: PropTypes.object,
|
||||
@ -23,7 +22,7 @@ export default class SendAmountRow extends Component {
|
||||
const {
|
||||
amountConversionRate,
|
||||
conversionRate,
|
||||
from: { balance },
|
||||
balance,
|
||||
gasTotal,
|
||||
primaryCurrency,
|
||||
selectedToken,
|
||||
@ -69,16 +68,16 @@ export default class SendAmountRow extends Component {
|
||||
showError={inError}
|
||||
errorType={'amount'}
|
||||
>
|
||||
!inError && gasTotal && <AmountMaxButton />
|
||||
{!inError && gasTotal && <AmountMaxButton />}
|
||||
<CurrencyDisplay
|
||||
inError={inError},
|
||||
primaryCurrency={primaryCurrency},
|
||||
convertedCurrency={convertedCurrency},
|
||||
selectedToken={selectedToken},
|
||||
value={amount || '0x0'},
|
||||
conversionRate={amountConversionRate},
|
||||
handleChange={this.handleAmountChange},
|
||||
>
|
||||
inError={inError}
|
||||
primaryCurrency={primaryCurrency}
|
||||
convertedCurrency={convertedCurrency}
|
||||
selectedToken={selectedToken}
|
||||
value={amount || '0x0'}
|
||||
conversionRate={amountConversionRate}
|
||||
handleChange={newAmount => this.handleAmountChange(newAmount)}
|
||||
/>
|
||||
</SendRowWrapper>
|
||||
);
|
||||
}
|
||||
|
@ -1,41 +1,44 @@
|
||||
import { connect } from 'react-redux'
|
||||
import {
|
||||
getSelectedToken,
|
||||
getPrimaryCurrency,
|
||||
getAmountConversionRate,
|
||||
getConvertedCurrency,
|
||||
getSendAmount,
|
||||
getGasTotal,
|
||||
getSelectedBalance,
|
||||
getTokenBalance,
|
||||
getSendFromBalance,
|
||||
getConversionRate,
|
||||
} from '../../send.selectors.js'
|
||||
import {
|
||||
getMaxModeOn,
|
||||
sendAmountIsInError,
|
||||
getPrimaryCurrency,
|
||||
getAmountConversionRate,
|
||||
} from './send-amount-row.selectors.js'
|
||||
import { getAmountErrorObject } from './send-amount-row.utils.js'
|
||||
import {
|
||||
updateSendAmount,
|
||||
setMaxModeTo,
|
||||
} from '../../../actions'
|
||||
updateSendErrors,
|
||||
} from '../../../../actions'
|
||||
import SendAmountRow from './send-amount-row.component'
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SendToRow)
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SendAmountRow)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
updateSendTo
|
||||
return {
|
||||
selectedToken: getSelectedToken(state),
|
||||
primaryCurrency: getPrimaryCurrency(state),
|
||||
convertedCurrency: getConvertedCurrency(state),
|
||||
amountConversionRate: getAmountConversionRate(state),
|
||||
inError: sendAmountIsInError(state),
|
||||
amount: getSendAmount(state),
|
||||
maxModeOn: getMaxModeOn(state),
|
||||
gasTotal: getGasTotal(state),
|
||||
tokenBalance: getTokenBalance(state),
|
||||
balance: getSendFromBalance(state),
|
||||
}
|
||||
return {
|
||||
selectedToken: getSelectedToken(state),
|
||||
primaryCurrency: getPrimaryCurrency(state),
|
||||
convertedCurrency: getConvertedCurrency(state),
|
||||
amountConversionRate: getAmountConversionRate(state),
|
||||
inError: sendAmountIsInError(state),
|
||||
amount: getSendAmount(state),
|
||||
maxModeOn: getMaxModeOn(state),
|
||||
gasTotal: getGasTotal(state),
|
||||
tokenBalance: getTokenBalance(state),
|
||||
balance: getSendFromBalance(state),
|
||||
conversionRate: getConversionRate(state),
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
|
@ -1,6 +1,14 @@
|
||||
import {
|
||||
getSelectedToken,
|
||||
getSelectedTokenToFiatRate,
|
||||
getConversionRate,
|
||||
} from '../../send.selectors.js'
|
||||
|
||||
const selectors = {
|
||||
getMaxModeOn,
|
||||
sendAmountIsInError,
|
||||
getPrimaryCurrency,
|
||||
getAmountConversionRate,
|
||||
}
|
||||
|
||||
module.exports = selectors
|
||||
@ -10,5 +18,16 @@ function getMaxModeOn (state) {
|
||||
}
|
||||
|
||||
function sendAmountIsInError (state) {
|
||||
return Boolean(state.metamask.send.errors.amount)
|
||||
return Boolean(state.metamask.send.errors.amount)
|
||||
}
|
||||
|
||||
function getPrimaryCurrency (state) {
|
||||
const selectedToken = getSelectedToken(state)
|
||||
return selectedToken && selectedToken.symbol
|
||||
}
|
||||
|
||||
function getAmountConversionRate (state) {
|
||||
return Boolean(getSelectedToken(state))
|
||||
? getSelectedTokenToFiatRate(state)
|
||||
: getConversionRate(state)
|
||||
}
|
||||
|
@ -1,4 +1,11 @@
|
||||
const { isValidAddress } = require('../../../../util')
|
||||
const {
|
||||
conversionGreaterThan,
|
||||
} = require('../../../../conversion-util')
|
||||
const {
|
||||
isBalanceSufficient,
|
||||
isTokenBalanceSufficient,
|
||||
} = require('../../send.utils')
|
||||
|
||||
function getAmountErrorObject ({
|
||||
amount,
|
||||
@ -10,6 +17,14 @@ function getAmountErrorObject ({
|
||||
gasTotal,
|
||||
tokenBalance,
|
||||
}) {
|
||||
console.log(`#& getAmountErrorObject amount`, amount);
|
||||
console.log(`#& getAmountErrorObject balance`, balance);
|
||||
console.log(`#& getAmountErrorObject amountConversionRate`, amountConversionRate);
|
||||
console.log(`#& getAmountErrorObject conversionRate`, conversionRate);
|
||||
console.log(`#& getAmountErrorObject primaryCurrency`, primaryCurrency);
|
||||
console.log(`#& getAmountErrorObject selectedToken`, selectedToken);
|
||||
console.log(`#& getAmountErrorObject gasTotal`, gasTotal);
|
||||
console.log(`#& getAmountErrorObject tokenBalance`, tokenBalance);
|
||||
let insufficientFunds = false
|
||||
if (gasTotal && conversionRate) {
|
||||
insufficientFunds = !isBalanceSufficient({
|
||||
@ -40,11 +55,11 @@ function getAmountErrorObject ({
|
||||
let amountError = null
|
||||
|
||||
if (insufficientFunds) {
|
||||
amountError = this.context.t('insufficientFunds')
|
||||
} else if (insufficientTokens) {
|
||||
amountError = this.context.t('insufficientTokens')
|
||||
amountError = 'insufficientFunds'
|
||||
} else if (inSufficientTokens) {
|
||||
amountError = 'insufficientTokens'
|
||||
} else if (amountLessThanZero) {
|
||||
amountError = this.context.t('negativeETH')
|
||||
amountError = 'negativeETH'
|
||||
}
|
||||
|
||||
return { amount: amountError }
|
||||
|
@ -1,13 +1,14 @@
|
||||
import React, { Component } from 'react'
|
||||
import PageContainerContent from '../../page-container/page-container-header.component'
|
||||
import SendFromRow from './send-from-row/send-from-row.component'
|
||||
import SendToRow from './send-to-row/send-to-row.component'
|
||||
import SendAmountRow from './send-amount-row/send-amount-row.component'
|
||||
import SendGasRow from './send-gas-row/send-gas-row.component'
|
||||
import PageContainerContent from '../../page-container/page-container-content.component'
|
||||
import SendFromRow from './send-from-row/send-from-row.container'
|
||||
import SendToRow from './send-to-row/send-to-row.container'
|
||||
import SendAmountRow from './send-amount-row/send-amount-row.container'
|
||||
import SendGasRow from './send-gas-row/send-gas-row.container'
|
||||
|
||||
export default class SendContent extends Component {
|
||||
|
||||
render () {
|
||||
console.log('111222333444555666777888999')
|
||||
return (
|
||||
<PageContainerContent>
|
||||
<div className='.send-v2__form'>
|
||||
|
@ -0,0 +1,75 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import AccountListItem from '../../../account-list-item/account-list-item.container'
|
||||
|
||||
export default class FromDropdown extends Component {
|
||||
|
||||
static propTypes = {
|
||||
accounts: PropTypes.array,
|
||||
closeDropdown: PropTypes.func,
|
||||
dropdownOpen: PropTypes.bool,
|
||||
onSelect: PropTypes.func,
|
||||
openDropdown: PropTypes.func,
|
||||
selectedAccount: PropTypes.object,
|
||||
};
|
||||
|
||||
renderListItemIcon (icon, color) {
|
||||
return <i className={`fa ${icon} fa-lg`} style={ { color } }/>
|
||||
}
|
||||
|
||||
getListItemIcon (currentAccount, selectedAccount) {
|
||||
return currentAccount.address === selectedAccount.address
|
||||
? this.renderListItemIcon('fa-check', '#02c9b1')
|
||||
: null
|
||||
}
|
||||
|
||||
renderDropdown () {
|
||||
const {
|
||||
accounts,
|
||||
selectedAccount,
|
||||
closeDropdown,
|
||||
onSelect,
|
||||
} = this.props
|
||||
|
||||
return (<div>
|
||||
<div
|
||||
className='send-v2__from-dropdown__close-area'
|
||||
onClick={() => closeDropdown}
|
||||
/>
|
||||
<div className='send-v2__from-dropdown__list'>
|
||||
{...accounts.map(account => <AccountListItem
|
||||
className='account-list-item__dropdown'
|
||||
account={account}
|
||||
handleClick={() => {
|
||||
onSelect(account)
|
||||
closeDropdown()
|
||||
}}
|
||||
icon={this.getListItemIcon(account, selectedAccount.address)}
|
||||
/>)}
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
selectedAccount,
|
||||
openDropdown,
|
||||
dropdownOpen,
|
||||
} = this.props
|
||||
console.log(`&*& openDropdown`, openDropdown);
|
||||
console.log(`&*& dropdownOpen`, dropdownOpen);
|
||||
return <div className='send-v2__from-dropdown'>
|
||||
<AccountListItem
|
||||
account={selectedAccount}
|
||||
handleClick={openDropdown}
|
||||
icon={this.renderListItemIcon('fa-caret-down', '#dedede')}
|
||||
/>
|
||||
{dropdownOpen && this.renderDropdown()},
|
||||
</div>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
FromDropdown.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component'
|
||||
import FromDropdown from '../../../send/from-dropdown'
|
||||
import FromDropdown from './from-dropdown/from-dropdown.component'
|
||||
|
||||
export default class SendFromRow extends Component {
|
||||
|
||||
static propTypes = {
|
||||
closeFromDropdown: PropTypes.func,
|
||||
conversionRate: PropTypes.string,
|
||||
from: PropTypes.string,
|
||||
conversionRate: PropTypes.number,
|
||||
from: PropTypes.object,
|
||||
fromAccounts: PropTypes.array,
|
||||
fromDropdownOpen: PropTypes.bool,
|
||||
openFromDropdown: PropTypes.func,
|
||||
@ -41,7 +41,7 @@ export default class SendFromRow extends Component {
|
||||
openFromDropdown,
|
||||
closeFromDropdown,
|
||||
} = this.props
|
||||
|
||||
console.log(`$% SendFromRow fromAccounts`, fromAccounts);
|
||||
return (
|
||||
<SendRowWrapper label={`${this.context.t('from')}:`}>
|
||||
<FromDropdown
|
||||
|
@ -1,27 +1,30 @@
|
||||
import { connect } from 'react-redux'
|
||||
import {
|
||||
getSendFrom,
|
||||
getConversionRate,
|
||||
getSelectedTokenContract,
|
||||
getCurrentAccountWithSendEtherInfo,
|
||||
accountsWithSendEtherInfoSelector,
|
||||
getSendFromObject,
|
||||
} from '../../send.selectors.js'
|
||||
import { getFromDropdownOpen } from './send-from-row.selectors.js'
|
||||
import {
|
||||
getFromDropdownOpen,
|
||||
} from './send-from-row.selectors.js'
|
||||
import { calcTokenUpdateAmount } from './send-from-row.utils.js'
|
||||
import {
|
||||
updateSendTokenBalance,
|
||||
updateSendFrom,
|
||||
} from '../../../actions'
|
||||
} from '../../../../actions'
|
||||
import {
|
||||
openFromDropdown,
|
||||
closeFromDropdown,
|
||||
} from '../../../ducks/send'
|
||||
} from '../../../../ducks/send'
|
||||
import SendFromRow from './send-from-row.component'
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SendFromRow)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
console.log(`$% mapStateToProps accountsWithSendEtherInfoSelector`, accountsWithSendEtherInfoSelector);
|
||||
return {
|
||||
from: getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state),
|
||||
from: getSendFromObject(state),
|
||||
fromAccounts: accountsWithSendEtherInfoSelector(state),
|
||||
conversionRate: getConversionRate(state),
|
||||
fromDropdownOpen: getFromDropdownOpen(state),
|
||||
@ -38,7 +41,7 @@ function mapDispatchToProps (dispatch) {
|
||||
dispatch(updateSendTokenBalance(tokenBalance))
|
||||
},
|
||||
updateSendFrom: newFrom => dispatch(updateSendFrom(newFrom)),
|
||||
openFromDropdown: () => dispatch(()),
|
||||
closeFromDropdown: () => dispatch(()),
|
||||
openFromDropdown: () => dispatch(openFromDropdown()),
|
||||
closeFromDropdown: () => dispatch(closeFromDropdown()),
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
const {
|
||||
calcTokenAmount,
|
||||
} = require('../../token-util')
|
||||
} = require('../../../../token-util')
|
||||
|
||||
function calcTokenUpdateAmount (usersToken, selectedToken) {
|
||||
const { decimals } = selectedToken || {}
|
||||
|
@ -7,7 +7,7 @@ export default class SendGasRow extends Component {
|
||||
|
||||
static propTypes = {
|
||||
closeFromDropdown: PropTypes.func,
|
||||
conversionRate: PropTypes.string,
|
||||
conversionRate: PropTypes.number,
|
||||
from: PropTypes.string,
|
||||
fromAccounts: PropTypes.array,
|
||||
fromDropdownOpen: PropTypes.bool,
|
||||
@ -15,6 +15,7 @@ export default class SendGasRow extends Component {
|
||||
tokenContract: PropTypes.object,
|
||||
updateSendFrom: PropTypes.func,
|
||||
updateSendTokenBalance: PropTypes.func,
|
||||
gasLoadingError: PropTypes.bool,
|
||||
};
|
||||
|
||||
async handleFromChange (newFrom) {
|
||||
@ -43,11 +44,11 @@ export default class SendGasRow extends Component {
|
||||
return (
|
||||
<SendRowWrapper label={`${this.context.t('gasFee')}:`}>
|
||||
<GasFeeDisplay
|
||||
gasTotal={gasTotal},
|
||||
conversionRate={conversionRate},
|
||||
convertedCurrency={convertedCurrency},
|
||||
onClick={() => showCustomizeGasModal()},
|
||||
gasLoadingError={gasLoadingError},
|
||||
gasTotal={gasTotal}
|
||||
conversionRate={conversionRate}
|
||||
convertedCurrency={convertedCurrency}
|
||||
onClick={() => showCustomizeGasModal()}
|
||||
gasLoadingError={gasLoadingError}
|
||||
/>
|
||||
</SendRowWrapper>
|
||||
);
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { connect } from 'react-redux'
|
||||
import {
|
||||
getConversionRate,
|
||||
getConvertedCurrency,
|
||||
getGasTotal,
|
||||
} from '../../send.selectors.js'
|
||||
import { getGasLoadingError } from './send-gas-row.selectors.js'
|
||||
import { calcTokenUpdateAmount } from './send-gas-row.utils.js'
|
||||
import { showModal } from '../../../actions'
|
||||
import SendGasRow from './send-from-row.component'
|
||||
import { sendGasIsInError } from './send-gas-row.selectors.js'
|
||||
import { showModal } from '../../../../actions'
|
||||
import SendGasRow from './send-gas-row.component'
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SendGasRow)
|
||||
|
||||
@ -15,7 +15,7 @@ function mapStateToProps (state) {
|
||||
conversionRate: getConversionRate(state),
|
||||
convertedCurrency: getConvertedCurrency(state),
|
||||
gasTotal: getGasTotal(state),
|
||||
gasLoadingError: getGasLoadingError(state),
|
||||
gasLoadingError: sendGasIsInError(state),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,5 +5,5 @@ const selectors = {
|
||||
module.exports = selectors
|
||||
|
||||
function sendGasIsInError (state) {
|
||||
return state.send.errors.gasLoading
|
||||
return state.metamask.send.errors.gasLoading
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
export default class SendRowErrorMessage extends Component {
|
||||
|
||||
static propTypes = {
|
||||
@ -11,7 +14,7 @@ export default class SendRowErrorMessage extends Component {
|
||||
|
||||
return (
|
||||
errorMessage
|
||||
? <div className='send-v2__error'>{errorMessage}</div>
|
||||
? <div className='send-v2__error'>{this.context.t(errorMessage)}</div>
|
||||
: null
|
||||
);
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { connect } from 'react-redux'
|
||||
import { getSendErrors } from '../../../send.selectors'
|
||||
import SendRowErrorMessage from './send-row-error-message.component'
|
||||
|
||||
|
@ -19,19 +19,14 @@ export default class SendRowWrapper extends Component {
|
||||
children,
|
||||
} = this.props
|
||||
|
||||
let formField = children[0]
|
||||
let customLabelContent = null
|
||||
|
||||
if (children.length === 2) {
|
||||
formField = children[1]
|
||||
customLabelContent = children[0]
|
||||
}
|
||||
let formField = Array.isArray(children) ? children[1] || children[0] : children
|
||||
let customLabelContent = children.length === 1 ? children[0] : null
|
||||
|
||||
return (
|
||||
<div className="send-v2__form-row">
|
||||
<div className="send-v2__form-label">
|
||||
{label}
|
||||
(showError && <SendRowErrorMessage errorType={errorType}/>)
|
||||
{showError && <SendRowErrorMessage errorType={errorType}/>}
|
||||
{customLabelContent}
|
||||
</div>
|
||||
<div className="send-v2__form-field">
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component'
|
||||
import ToDropdown from '../../../ens-input'
|
||||
import EnsInput from '../../../ens-input'
|
||||
|
||||
export default class SendToRow extends Component {
|
||||
|
||||
@ -14,19 +14,20 @@ export default class SendToRow extends Component {
|
||||
updateSendToError: PropTypes.func,
|
||||
openToDropdown: PropTypes.func,
|
||||
closeToDropdown: PropTypes.func,
|
||||
network: PropTypes.number,
|
||||
network: PropTypes.string,
|
||||
};
|
||||
|
||||
handleToChange (to, nickname = '') {
|
||||
const { updateSendTo, updateSendToError } = this.props
|
||||
updateSendTo(to, nickname)
|
||||
updateSendErrors(to)
|
||||
updateSendToError(to)
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
from,
|
||||
fromAccounts,
|
||||
toAccounts,
|
||||
conversionRate,
|
||||
fromDropdownOpen,
|
||||
tokenContract,
|
||||
@ -34,6 +35,8 @@ export default class SendToRow extends Component {
|
||||
closeToDropdown,
|
||||
network,
|
||||
inError,
|
||||
to,
|
||||
toDropdownOpen,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
@ -44,14 +47,14 @@ export default class SendToRow extends Component {
|
||||
>
|
||||
<EnsInput
|
||||
name={'address'}
|
||||
placeholder={this.context.t('recipient Address')}
|
||||
network={network},
|
||||
to={to},
|
||||
placeholder={this.context.t('recipientAddress')}
|
||||
network={network}
|
||||
to={to}
|
||||
accounts={toAccounts}
|
||||
dropdownOpen={toDropdownOpen}
|
||||
openDropdown={() => openToDropdown()}
|
||||
closeDropdown={() => closeToDropdown()}
|
||||
onChange={this.handleToChange}
|
||||
onChange={(newTo, newNickname) => this.handleToChange(newTo, newNickname)}
|
||||
inError={inError}
|
||||
/>
|
||||
</SendRowWrapper>
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { connect } from 'react-redux'
|
||||
import {
|
||||
getSendTo,
|
||||
getToAccounts,
|
||||
getCurrentNetwork,
|
||||
getSendToAccounts,
|
||||
} from '../../send.selectors.js'
|
||||
import {
|
||||
getToDropdownOpen,
|
||||
@ -11,11 +13,11 @@ import { getToErrorObject } from './send-to-row.utils.js'
|
||||
import {
|
||||
updateSendErrors,
|
||||
updateSendTo,
|
||||
} from '../../../actions'
|
||||
} from '../../../../actions'
|
||||
import {
|
||||
openToDropdown,
|
||||
closeToDropdown,
|
||||
} from '../../../ducks/send'
|
||||
} from '../../../../ducks/send'
|
||||
import SendToRow from './send-to-row.component'
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SendToRow)
|
||||
@ -37,7 +39,7 @@ function mapDispatchToProps (dispatch) {
|
||||
dispatch(updateSendErrors(getToErrorObject(to)))
|
||||
},
|
||||
updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)),
|
||||
openToDropdown: () => dispatch(()),
|
||||
closeToDropdown: () => dispatch(()),
|
||||
openToDropdown: () => dispatch(openToDropdown()),
|
||||
closeToDropdown: () => dispatch(closeToDropdown()),
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import PageContainerFooter from '../../page-container/page-container-footer.component'
|
||||
import { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } from '../../../routes'
|
||||
|
||||
export default class SendFooter extends Component {
|
||||
|
||||
static propTypes = {
|
||||
addToAddressBook: PropTypes.func,
|
||||
amount: PropTypes.string,
|
||||
clearSend: PropTypes.func,
|
||||
editingTransactionId: PropTypes.string,
|
||||
errors: PropTypes.object,
|
||||
from: PropTypes.object,
|
||||
gasLimit: PropTypes.string,
|
||||
gasPrice: PropTypes.string,
|
||||
gasTotal: PropTypes.string,
|
||||
history: PropTypes.object,
|
||||
selectedToken: PropTypes.object,
|
||||
signTokenTx: PropTypes.func,
|
||||
signTx: PropTypes.func,
|
||||
to: PropTypes.string,
|
||||
toAccounts: PropTypes.array,
|
||||
tokenBalance: PropTypes.string,
|
||||
unapprovedTxs: PropTypes.object,
|
||||
updateTx: PropTypes.func,
|
||||
};
|
||||
|
||||
onSubmit (event) {
|
||||
event.preventDefault()
|
||||
const {
|
||||
addToAddressBookIfNew,
|
||||
amount,
|
||||
editingTransactionId,
|
||||
from: {address: from},
|
||||
gasLimit: gas,
|
||||
gasPrice,
|
||||
selectedToken,
|
||||
sign,
|
||||
to,
|
||||
unapprovedTxs,
|
||||
// updateTx,
|
||||
update,
|
||||
toAccounts,
|
||||
} = this.props
|
||||
|
||||
// Should not be needed because submit should be disabled if there are no errors.
|
||||
// const noErrors = !amountError && toError === null
|
||||
|
||||
// if (!noErrors) {
|
||||
// return
|
||||
// }
|
||||
|
||||
// TODO: add nickname functionality
|
||||
addToAddressBookIfNew(to, toAccounts)
|
||||
|
||||
editingTransactionId
|
||||
? update({
|
||||
from,
|
||||
to,
|
||||
amount,
|
||||
gas,
|
||||
gasPrice,
|
||||
selectedToken,
|
||||
editingTransactionId,
|
||||
unapprovedTxs,
|
||||
})
|
||||
: sign({ selectedToken, to, amount, from, gas, gasPrice })
|
||||
|
||||
this.props.history.push(CONFIRM_TRANSACTION_ROUTE)
|
||||
}
|
||||
|
||||
|
||||
render () {
|
||||
const { clearSend, disabled, history } = this.props
|
||||
|
||||
return (
|
||||
<PageContainerFooter
|
||||
onCancel={() => {
|
||||
clearSend()
|
||||
history.push(DEFAULT_ROUTE)
|
||||
}}
|
||||
onSubmit={e => this.onSubmit(e)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SendFooter.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
import { connect } from 'react-redux'
|
||||
import ethUtil from 'ethereumjs-util'
|
||||
import {
|
||||
addToAddressBook,
|
||||
clearSend,
|
||||
goHome,
|
||||
signTokenTx,
|
||||
signTx,
|
||||
updateTransaction,
|
||||
} from '../../../actions'
|
||||
import SendFooter from './send-footer.component'
|
||||
import {
|
||||
getGasLimit,
|
||||
getGasPrice,
|
||||
getGasTotal,
|
||||
getSelectedToken,
|
||||
getSendAmount,
|
||||
getSendEditingTransactionId,
|
||||
getSendFromObject,
|
||||
getSendTo,
|
||||
getSendToAccounts,
|
||||
getTokenBalance,
|
||||
getUnapprovedTxs,
|
||||
} from '../send.selectors'
|
||||
import {
|
||||
isSendFormInError,
|
||||
} from './send-footer.selectors'
|
||||
import {
|
||||
addressIsNew,
|
||||
formShouldBeDisabled,
|
||||
constructTxParams,
|
||||
} from './send-footer.utils'
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SendFooter)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
isToken: Boolean(getSelectedToken(state)),
|
||||
inError: isSendFormInError(state),
|
||||
disabled: formShouldBeDisabled({
|
||||
inError: isSendFormInError(state),
|
||||
selectedToken: getSelectedToken(state),
|
||||
tokenBalance: getTokenBalance(state),
|
||||
gasTotal: getGasTotal(state),
|
||||
}),
|
||||
amount: getSendAmount(state),
|
||||
editingTransactionId: getSendEditingTransactionId(state),
|
||||
from: getSendFromObject(state),
|
||||
gasLimit: getGasLimit(state),
|
||||
gasPrice: getGasPrice(state),
|
||||
selectedToken: getSelectedToken(state),
|
||||
to: getSendTo(state),
|
||||
unapprovedTxs: getUnapprovedTxs(state),
|
||||
toAccounts: getSendToAccounts(state),
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
goHome: () => dispatch(goHome()),
|
||||
clearSend: () => dispatch(clearSend()),
|
||||
sign: ({ selectedToken, to, amount, from, gas, gasPrice }) => {
|
||||
const txParams = constructTxParams({
|
||||
amount,
|
||||
from,
|
||||
gas,
|
||||
gasPrice,
|
||||
selectedToken,
|
||||
to,
|
||||
})
|
||||
|
||||
selectedToken
|
||||
? dispatch(signTokenTx(selectedToken.address, to, amount, txParams))
|
||||
: dispatch(signTx(txParams))
|
||||
},
|
||||
update: ({
|
||||
amount,
|
||||
editingTransactionId,
|
||||
from,
|
||||
gas,
|
||||
gasPrice,
|
||||
selectedToken,
|
||||
to,
|
||||
unapprovedTxs,
|
||||
}) => {
|
||||
const editingTx = constructUpdatedTx({
|
||||
amount,
|
||||
editingTransactionId,
|
||||
from,
|
||||
gas,
|
||||
gasPrice,
|
||||
selectedToken,
|
||||
to,
|
||||
unapprovedTxs,
|
||||
})
|
||||
|
||||
dispatch(updateTransaction(editingTx))
|
||||
},
|
||||
addToAddressBookIfNew: (newAddress, toAccounts, nickname = '') => {
|
||||
const hexPrefixedAddress = ethUtil.addHexPrefix(newAddress)
|
||||
if (addressIsNew(toAccounts)) {
|
||||
// TODO: nickname, i.e. addToAddressBook(recipient, nickname)
|
||||
dispatch(addToAddressBook(hexPrefixedAddress, nickname))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
import { getSendErrors } from '../send.selectors'
|
||||
|
||||
const selectors = {
|
||||
isSendFormInError,
|
||||
}
|
||||
|
||||
module.exports = selectors
|
||||
|
||||
function isSendFormInError (state) {
|
||||
const { amount, to } = getSendErrors(state)
|
||||
return Boolean(amount || to !== null)
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
import ethAbi from 'ethereumjs-abi'
|
||||
import ethUtil from 'ethereumjs-util'
|
||||
import { TOKEN_TRANSFER_FUNCTION_SIGNATURE } from '../send.constants'
|
||||
|
||||
function formShouldBeDisabled ({ inError, selectedToken, tokenBalance, gasTotal }) {
|
||||
const missingTokenBalance = selectedToken && !tokenBalance
|
||||
return inError || !gasTotal || missingTokenBalance
|
||||
}
|
||||
|
||||
function addHexPrefixToObjectValues (obj) {
|
||||
return Object.keys(obj).reduce((newObj, key) => {
|
||||
return { ...newObj, [key]: ethUtil.addHexPrefix(obj[key]) }
|
||||
}, {})
|
||||
}
|
||||
|
||||
function constructTxParams ({ selectedToken, to, amount, from, gas, gasPrice }) {
|
||||
const txParams = {
|
||||
from,
|
||||
value: '0',
|
||||
gas,
|
||||
gasPrice,
|
||||
}
|
||||
|
||||
if (!selectedToken) {
|
||||
txParams.value = amount
|
||||
txParams.to = to
|
||||
}
|
||||
|
||||
const hexPrefixedTxParams = addHexPrefixToObjectValues(txParams)
|
||||
|
||||
return hexPrefixedTxParams
|
||||
}
|
||||
|
||||
function constructUpdatedTx ({
|
||||
amount,
|
||||
editingTransactionId,
|
||||
from,
|
||||
gas,
|
||||
gasPrice,
|
||||
selectedToken,
|
||||
to,
|
||||
unapprovedTxs,
|
||||
}) {
|
||||
const editingTx = {
|
||||
...unapprovedTxs[editingTransactionId],
|
||||
txParams: addHexPrefixToObjectValues({ from, gas, gasPrice }),
|
||||
}
|
||||
|
||||
if (selectedToken) {
|
||||
const data = TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call(
|
||||
ethAbi.rawEncode(['address', 'uint256'], [to, ethUtil.addHexPrefix(amount)]),
|
||||
x => ('00' + x.toString(16)).slice(-2)
|
||||
).join('')
|
||||
|
||||
Object.assign(editingTx.txParams, addHexPrefixToObjectValues({
|
||||
value: '0',
|
||||
to: selectedToken.address,
|
||||
data,
|
||||
}))
|
||||
} else {
|
||||
const { data } = unapprovedTxs[editingTransactionId].txParams
|
||||
|
||||
Object.assign(editingTx.txParams, addHexPrefixToObjectValues({
|
||||
value: amount,
|
||||
to,
|
||||
data,
|
||||
}))
|
||||
|
||||
if (typeof editingTx.txParams.data === 'undefined') {
|
||||
delete editingTx.txParams.data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addressIsNew (toAccounts, newAddress) {
|
||||
return !toAccounts.find(({ address }) => newAddress === address)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
addressIsNew,
|
||||
formShouldBeDisabled,
|
||||
constructTxParams,
|
||||
constructUpdatedTx,
|
||||
}
|
33
ui/app/components/send_/send.constants.js
Normal file
33
ui/app/components/send_/send.constants.js
Normal file
@ -0,0 +1,33 @@
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const { conversionUtil, multiplyCurrencies } = require('../../conversion-util')
|
||||
|
||||
const MIN_GAS_PRICE_HEX = (100000000).toString(16)
|
||||
const MIN_GAS_PRICE_DEC = '100000000'
|
||||
const MIN_GAS_LIMIT_DEC = '21000'
|
||||
const MIN_GAS_LIMIT_HEX = (parseInt(MIN_GAS_LIMIT_DEC)).toString(16)
|
||||
|
||||
const MIN_GAS_PRICE_GWEI = ethUtil.addHexPrefix(conversionUtil(MIN_GAS_PRICE_HEX, {
|
||||
fromDenomination: 'WEI',
|
||||
toDenomination: 'GWEI',
|
||||
fromNumericBase: 'hex',
|
||||
toNumericBase: 'hex',
|
||||
numberOfDecimals: 1,
|
||||
}))
|
||||
|
||||
const MIN_GAS_TOTAL = multiplyCurrencies(MIN_GAS_LIMIT_HEX, MIN_GAS_PRICE_HEX, {
|
||||
toNumericBase: 'hex',
|
||||
multiplicandBase: 16,
|
||||
multiplierBase: 16,
|
||||
})
|
||||
|
||||
const TOKEN_TRANSFER_FUNCTION_SIGNATURE = '0xa9059cbb'
|
||||
|
||||
module.exports = {
|
||||
MIN_GAS_PRICE_GWEI,
|
||||
MIN_GAS_PRICE_HEX,
|
||||
MIN_GAS_PRICE_DEC,
|
||||
MIN_GAS_LIMIT_HEX,
|
||||
MIN_GAS_LIMIT_DEC,
|
||||
MIN_GAS_TOTAL,
|
||||
TOKEN_TRANSFER_FUNCTION_SIGNATURE,
|
||||
}
|
@ -2,14 +2,14 @@ import { valuesFor } from '../../util'
|
||||
import abi from 'human-standard-token-abi'
|
||||
import {
|
||||
multiplyCurrencies,
|
||||
} from './conversion-util'
|
||||
} from '../../conversion-util'
|
||||
|
||||
const selectors = {
|
||||
accountsWithSendEtherInfoSelector,
|
||||
autoAddToBetaUI,
|
||||
getConversionRate,
|
||||
getAddressBook,
|
||||
getConversionRate,
|
||||
getConvertedCurrency,
|
||||
getCurrentAccountWithSendEtherInfo,
|
||||
getCurrentCurrency,
|
||||
getCurrentNetwork,
|
||||
@ -17,6 +17,7 @@ const selectors = {
|
||||
getForceGasMin,
|
||||
getGasLimit,
|
||||
getGasPrice,
|
||||
getGasTotal,
|
||||
getSelectedAccount,
|
||||
getSelectedAddress,
|
||||
getSelectedIdentity,
|
||||
@ -25,12 +26,18 @@ const selectors = {
|
||||
getSelectedTokenExchangeRate,
|
||||
getSelectedTokenToFiatRate,
|
||||
getSendAmount,
|
||||
getSendEditingTransactionId,
|
||||
getSendErrors,
|
||||
getSendFrom,
|
||||
getSendFromObject,
|
||||
getSendFromBalance,
|
||||
getSendMaxModeState,
|
||||
getSendTo,
|
||||
getSendToAccounts,
|
||||
getTokenBalance,
|
||||
getTokenExchangeRate,
|
||||
getUnapprovedTxs,
|
||||
isSendFormInError,
|
||||
transactionsSelector,
|
||||
}
|
||||
|
||||
@ -84,10 +91,18 @@ function getTokenExchangeRate (state, tokenSymbol) {
|
||||
return tokenExchangeRate
|
||||
}
|
||||
|
||||
function getUnapprovedTxs (state) {
|
||||
return state.metamask.unapprovedTxs
|
||||
}
|
||||
|
||||
function getConversionRate (state) {
|
||||
return state.metamask.conversionRate
|
||||
}
|
||||
|
||||
function getConvertedCurrency (state) {
|
||||
return state.metamask.currentCurrency
|
||||
}
|
||||
|
||||
function getAddressBook (state) {
|
||||
return state.metamask.addressBook
|
||||
}
|
||||
@ -97,11 +112,13 @@ function accountsWithSendEtherInfoSelector (state) {
|
||||
accounts,
|
||||
identities,
|
||||
} = state.metamask
|
||||
|
||||
console.log(`accountsWithSendEtherInfoSelector accounts`, accounts);
|
||||
console.log(`accountsWithSendEtherInfoSelector identities`, identities);
|
||||
const accountsWithSendEtherInfo = Object.entries(accounts).map(([key, account]) => {
|
||||
return Object.assign({}, account, identities[key])
|
||||
})
|
||||
|
||||
console.log(`accountsWithSendEtherInfoSelector accountsWithSendEtherInfo`, accountsWithSendEtherInfo);
|
||||
return accountsWithSendEtherInfo
|
||||
}
|
||||
|
||||
@ -132,6 +149,10 @@ function getGasPrice (state) {
|
||||
return state.metamask.send.gasPrice
|
||||
}
|
||||
|
||||
function getGasTotal (state) {
|
||||
return state.metamask.send.gasTotal
|
||||
}
|
||||
|
||||
function getGasLimit (state) {
|
||||
return state.metamask.send.gasLimit
|
||||
}
|
||||
@ -144,8 +165,12 @@ function getSendFrom (state) {
|
||||
return state.metamask.send.from
|
||||
}
|
||||
|
||||
function getSendFromObject (state) {
|
||||
return getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state)
|
||||
}
|
||||
|
||||
function getSendFromBalance (state) {
|
||||
const from = state.metamask.send.from || {}
|
||||
const from = getSendFrom(state) || getSelectedAccount(state)
|
||||
return from.balance
|
||||
}
|
||||
|
||||
@ -203,6 +228,10 @@ function getCurrentViewContext (state) {
|
||||
return currentView.context
|
||||
}
|
||||
|
||||
function getSendEditingTransactionId (state) {
|
||||
return state.metamask.send.editingTransactionId
|
||||
}
|
||||
|
||||
function getSendErrors (state) {
|
||||
return state.metamask.send.errors
|
||||
}
|
||||
@ -211,6 +240,10 @@ function getSendTo (state) {
|
||||
return state.metamask.send.to
|
||||
}
|
||||
|
||||
function getTokenBalance (state) {
|
||||
return state.metamask.send.tokenBalance
|
||||
}
|
||||
|
||||
function getSendToAccounts (state) {
|
||||
const fromAccounts = accountsWithSendEtherInfoSelector(state)
|
||||
const addressBookAccounts = getAddressBook(state)
|
||||
@ -222,3 +255,8 @@ function getSendToAccounts (state) {
|
||||
function getCurrentNetwork (state) {
|
||||
return state.metamask.network
|
||||
}
|
||||
|
||||
function isSendFormInError (state) {
|
||||
const { amount, to } = getSendErrors(state)
|
||||
return Boolean(amount || toError !== null)
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
const {
|
||||
addCurrencies,
|
||||
conversionUtil,
|
||||
conversionGTE,
|
||||
multiplyCurrencies,
|
||||
} = require('../../conversion-util')
|
||||
const {
|
||||
calcTokenAmount,
|
||||
} = require('../../token-util')
|
||||
|
||||
function isBalanceSufficient ({
|
||||
amount = '0x0',
|
||||
gasTotal = '0x0',
|
||||
balance,
|
||||
primaryCurrency,
|
||||
amountConversionRate,
|
||||
conversionRate,
|
||||
}) {
|
||||
const totalAmount = addCurrencies(amount, gasTotal, {
|
||||
aBase: 16,
|
||||
bBase: 16,
|
||||
toNumericBase: 'hex',
|
||||
})
|
||||
|
||||
const balanceIsSufficient = conversionGTE(
|
||||
{
|
||||
value: balance,
|
||||
fromNumericBase: 'hex',
|
||||
fromCurrency: primaryCurrency,
|
||||
conversionRate,
|
||||
},
|
||||
{
|
||||
value: totalAmount,
|
||||
fromNumericBase: 'hex',
|
||||
conversionRate: amountConversionRate || conversionRate,
|
||||
fromCurrency: primaryCurrency,
|
||||
},
|
||||
)
|
||||
|
||||
return balanceIsSufficient
|
||||
}
|
||||
|
||||
function isTokenBalanceSufficient ({
|
||||
amount = '0x0',
|
||||
tokenBalance,
|
||||
decimals,
|
||||
}) {
|
||||
const amountInDec = conversionUtil(amount, {
|
||||
fromNumericBase: 'hex',
|
||||
})
|
||||
|
||||
const tokenBalanceIsSufficient = conversionGTE(
|
||||
{
|
||||
value: tokenBalance,
|
||||
fromNumericBase: 'dec',
|
||||
},
|
||||
{
|
||||
value: calcTokenAmount(amountInDec, decimals),
|
||||
fromNumericBase: 'dec',
|
||||
},
|
||||
)
|
||||
|
||||
return tokenBalanceIsSufficient
|
||||
}
|
||||
|
||||
function getGasTotal (gasLimit, gasPrice) {
|
||||
return multiplyCurrencies(gasLimit, gasPrice, {
|
||||
toNumericBase: 'hex',
|
||||
multiplicandBase: 16,
|
||||
multiplierBase: 16,
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getGasTotal,
|
||||
isBalanceSufficient,
|
||||
isTokenBalanceSufficient,
|
||||
}
|
@ -10,10 +10,11 @@ const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN';
|
||||
const initState = {
|
||||
fromDropdownOpen: false,
|
||||
toDropdownOpen: false,
|
||||
errors: {},
|
||||
}
|
||||
|
||||
// Reducer
|
||||
export default function reducer(state = initState, action = {}) {
|
||||
export default function reducer({ send: sendState = initState }, action = {}) {
|
||||
switch (action.type) {
|
||||
case OPEN_FROM_DROPDOWN:
|
||||
return extend(sendState, {
|
||||
|
@ -8,7 +8,7 @@ const reduceIdentities = require('./reducers/identities')
|
||||
const reduceMetamask = require('./reducers/metamask')
|
||||
const reduceApp = require('./reducers/app')
|
||||
const reduceLocale = require('./reducers/locale')
|
||||
const reduceSend = require('./ducks/send')
|
||||
const reduceSend = require('./ducks/send').default
|
||||
|
||||
window.METAMASK_CACHED_LOG_STATE = null
|
||||
|
||||
|
@ -34,8 +34,8 @@ const { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } = require('./routes')
|
||||
|
||||
import PageContainer from './components/page-container/page-container.component'
|
||||
import SendHeader from './components/send_/send-header/send-header.container'
|
||||
import PageContainerContent from './components/page-container/page-container-content.component'
|
||||
import PageContainerFooter from './components/page-container/page-container-footer.component'
|
||||
import SendContent from './components/send_/send-content/send-content.component'
|
||||
import SendFooter from './components/send_/send-footer/send-footer.container'
|
||||
|
||||
SendTransactionScreen.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
@ -57,8 +57,6 @@ function SendTransactionScreen () {
|
||||
gasLoadingError: false,
|
||||
}
|
||||
|
||||
this.handleToChange = this.handleToChange.bind(this)
|
||||
this.handleAmountChange = this.handleAmountChange.bind(this)
|
||||
this.validateAmount = this.validateAmount.bind(this)
|
||||
}
|
||||
|
||||
@ -176,158 +174,6 @@ SendTransactionScreen.prototype.componentDidUpdate = function (prevProps) {
|
||||
}
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.renderHeader = function () {
|
||||
const { selectedToken, clearSend, history } = this.props
|
||||
|
||||
return h('div.page-container__header', [
|
||||
|
||||
h('div.page-container__title', selectedToken ? this.context.t('sendTokens') : this.context.t('sendETH')),
|
||||
|
||||
h('div.page-container__subtitle', this.context.t('onlySendToEtherAddress')),
|
||||
|
||||
h('div.page-container__header-close', {
|
||||
onClick: () => {
|
||||
clearSend()
|
||||
history.push(DEFAULT_ROUTE)
|
||||
},
|
||||
}),
|
||||
|
||||
])
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.renderErrorMessage = function (errorType) {
|
||||
const { errors } = this.props
|
||||
const errorMessage = errors[errorType]
|
||||
|
||||
return errorMessage
|
||||
? h('div.send-v2__error', [ errorMessage ])
|
||||
: null
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.handleFromChange = async function (newFrom) {
|
||||
const {
|
||||
updateSendFrom,
|
||||
tokenContract,
|
||||
} = this.props
|
||||
|
||||
if (tokenContract) {
|
||||
const usersToken = await tokenContract.balanceOf(newFrom.address)
|
||||
this.updateSendTokenBalance(usersToken)
|
||||
}
|
||||
updateSendFrom(newFrom)
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.renderFromRow = function () {
|
||||
const {
|
||||
from,
|
||||
fromAccounts,
|
||||
conversionRate,
|
||||
} = this.props
|
||||
|
||||
const { fromDropdownOpen } = this.state
|
||||
|
||||
return h('div.send-v2__form-row', [
|
||||
|
||||
h('div.send-v2__form-label', 'From:'),
|
||||
|
||||
h('div.send-v2__form-field', [
|
||||
h(FromDropdown, {
|
||||
dropdownOpen: fromDropdownOpen,
|
||||
accounts: fromAccounts,
|
||||
selectedAccount: from,
|
||||
onSelect: newFrom => this.handleFromChange(newFrom),
|
||||
openDropdown: () => this.setState({ fromDropdownOpen: true }),
|
||||
closeDropdown: () => this.setState({ fromDropdownOpen: false }),
|
||||
conversionRate,
|
||||
}),
|
||||
]),
|
||||
|
||||
])
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.handleToChange = function (to, nickname = '') {
|
||||
const {
|
||||
updateSendTo,
|
||||
updateSendErrors,
|
||||
} = this.props
|
||||
let toError = null
|
||||
|
||||
if (!to) {
|
||||
toError = this.context.t('required')
|
||||
} else if (!isValidAddress(to)) {
|
||||
toError = this.context.t('invalidAddressRecipient')
|
||||
}
|
||||
|
||||
updateSendTo(to, nickname)
|
||||
updateSendErrors({ to: toError })
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.renderToRow = function () {
|
||||
const { toAccounts, errors, to, network } = this.props
|
||||
|
||||
const { toDropdownOpen } = this.state
|
||||
|
||||
return h('div.send-v2__form-row', [
|
||||
|
||||
h('div.send-v2__form-label', [
|
||||
|
||||
this.context.t('to'),
|
||||
|
||||
this.renderErrorMessage(this.context.t('to')),
|
||||
|
||||
]),
|
||||
|
||||
h('div.send-v2__form-field', [
|
||||
h(EnsInput, {
|
||||
name: 'address',
|
||||
placeholder: 'Recipient Address',
|
||||
network,
|
||||
to,
|
||||
accounts: Object.entries(toAccounts).map(([key, account]) => account),
|
||||
dropdownOpen: toDropdownOpen,
|
||||
openDropdown: () => this.setState({ toDropdownOpen: true }),
|
||||
closeDropdown: () => this.setState({ toDropdownOpen: false }),
|
||||
onChange: this.handleToChange,
|
||||
inError: Boolean(errors.to),
|
||||
}),
|
||||
]),
|
||||
|
||||
])
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.handleAmountChange = function (value) {
|
||||
const amount = value
|
||||
const { updateSendAmount, setMaxModeTo } = this.props
|
||||
|
||||
setMaxModeTo(false)
|
||||
this.validateAmount(amount)
|
||||
updateSendAmount(amount)
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.setAmountToMax = function () {
|
||||
const {
|
||||
from: { balance },
|
||||
updateSendAmount,
|
||||
updateSendErrors,
|
||||
tokenBalance,
|
||||
selectedToken,
|
||||
gasTotal,
|
||||
} = this.props
|
||||
const { decimals } = selectedToken || {}
|
||||
const multiplier = Math.pow(10, Number(decimals || 0))
|
||||
|
||||
const maxAmount = selectedToken
|
||||
? multiplyCurrencies(tokenBalance, multiplier, {toNumericBase: 'hex'})
|
||||
: subtractCurrencies(
|
||||
ethUtil.addHexPrefix(balance),
|
||||
ethUtil.addHexPrefix(gasTotal),
|
||||
{ toNumericBase: 'hex' }
|
||||
)
|
||||
|
||||
updateSendErrors({ amount: null })
|
||||
|
||||
updateSendAmount(maxAmount)
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.validateAmount = function (value) {
|
||||
const {
|
||||
@ -384,254 +230,19 @@ SendTransactionScreen.prototype.validateAmount = function (value) {
|
||||
updateSendErrors({ amount: amountError })
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.renderAmountRow = function () {
|
||||
const {
|
||||
selectedToken,
|
||||
primaryCurrency = 'ETH',
|
||||
convertedCurrency,
|
||||
amountConversionRate,
|
||||
errors,
|
||||
amount,
|
||||
setMaxModeTo,
|
||||
maxModeOn,
|
||||
gasTotal,
|
||||
} = this.props
|
||||
|
||||
return h('div.send-v2__form-row', [
|
||||
|
||||
h('div.send-v2__form-label', [
|
||||
'Amount:',
|
||||
this.renderErrorMessage('amount'),
|
||||
!errors.amount && gasTotal && h('div.send-v2__amount-max', {
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
setMaxModeTo(true)
|
||||
this.setAmountToMax()
|
||||
},
|
||||
}, [ !maxModeOn ? this.context.t('max') : '' ]),
|
||||
]),
|
||||
|
||||
h('div.send-v2__form-field', [
|
||||
h(CurrencyDisplay, {
|
||||
inError: Boolean(errors.amount),
|
||||
primaryCurrency,
|
||||
convertedCurrency,
|
||||
selectedToken,
|
||||
value: amount || '0x0',
|
||||
conversionRate: amountConversionRate,
|
||||
handleChange: this.handleAmountChange,
|
||||
}),
|
||||
]),
|
||||
|
||||
])
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.renderGasRow = function () {
|
||||
const {
|
||||
conversionRate,
|
||||
convertedCurrency,
|
||||
showCustomizeGasModal,
|
||||
gasTotal,
|
||||
} = this.props
|
||||
const { gasLoadingError } = this.state
|
||||
|
||||
return h('div.send-v2__form-row', [
|
||||
|
||||
h('div.send-v2__form-label', this.context.t('gasFee')),
|
||||
|
||||
h('div.send-v2__form-field', [
|
||||
|
||||
h(GasFeeDisplay, {
|
||||
gasTotal,
|
||||
conversionRate,
|
||||
convertedCurrency,
|
||||
onClick: showCustomizeGasModal,
|
||||
gasLoadingError,
|
||||
}),
|
||||
|
||||
]),
|
||||
|
||||
])
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.renderMemoRow = function () {
|
||||
const { updateSendMemo, memo } = this.props
|
||||
|
||||
return h('div.send-v2__form-row', [
|
||||
|
||||
h('div.send-v2__form-label', 'Transaction Memo:'),
|
||||
|
||||
h('div.send-v2__form-field', [
|
||||
h(MemoTextArea, {
|
||||
memo,
|
||||
onChange: (event) => updateSendMemo(event.target.value),
|
||||
}),
|
||||
]),
|
||||
|
||||
])
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.renderForm = function () {
|
||||
return h(PageContainerContent, [
|
||||
h('.send-v2__form', [
|
||||
this.renderFromRow(),
|
||||
|
||||
this.renderToRow(),
|
||||
|
||||
this.renderAmountRow(),
|
||||
|
||||
this.renderGasRow(),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.renderFooter = function () {
|
||||
const {
|
||||
clearSend,
|
||||
gasTotal,
|
||||
tokenBalance,
|
||||
selectedToken,
|
||||
errors: { amount: amountError, to: toError },
|
||||
history,
|
||||
} = this.props
|
||||
|
||||
const missingTokenBalance = selectedToken && !tokenBalance
|
||||
const noErrors = !amountError && toError === null
|
||||
|
||||
return h(PageContainerFooter, {
|
||||
onCancel: () => {
|
||||
clearSend()
|
||||
history.push(DEFAULT_ROUTE)
|
||||
},
|
||||
onSubmit: e => this.onSubmit(e),
|
||||
disabled: !noErrors || !gasTotal || missingTokenBalance,
|
||||
})
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.render = function () {
|
||||
const { history } = this.props
|
||||
|
||||
return (
|
||||
|
||||
h(PageContainer, [
|
||||
|
||||
h(SendHeader),
|
||||
|
||||
this.renderForm(),
|
||||
h(SendContent),
|
||||
|
||||
this.renderFooter(),
|
||||
h(SendFooter, { history }),
|
||||
])
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.addToAddressBookIfNew = function (newAddress, nickname = '') {
|
||||
const { toAccounts, addToAddressBook } = this.props
|
||||
if (!toAccounts.find(({ address }) => newAddress === address)) {
|
||||
// TODO: nickname, i.e. addToAddressBook(recipient, nickname)
|
||||
addToAddressBook(newAddress, nickname)
|
||||
}
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.getEditedTx = function () {
|
||||
const {
|
||||
from: {address: from},
|
||||
to,
|
||||
amount,
|
||||
gasLimit: gas,
|
||||
gasPrice,
|
||||
selectedToken,
|
||||
editingTransactionId,
|
||||
unapprovedTxs,
|
||||
} = this.props
|
||||
|
||||
const editingTx = {
|
||||
...unapprovedTxs[editingTransactionId],
|
||||
txParams: {
|
||||
from: ethUtil.addHexPrefix(from),
|
||||
gas: ethUtil.addHexPrefix(gas),
|
||||
gasPrice: ethUtil.addHexPrefix(gasPrice),
|
||||
},
|
||||
}
|
||||
|
||||
if (selectedToken) {
|
||||
const data = TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call(
|
||||
ethAbi.rawEncode(['address', 'uint256'], [to, ethUtil.addHexPrefix(amount)]),
|
||||
x => ('00' + x.toString(16)).slice(-2)
|
||||
).join('')
|
||||
|
||||
Object.assign(editingTx.txParams, {
|
||||
value: ethUtil.addHexPrefix('0'),
|
||||
to: ethUtil.addHexPrefix(selectedToken.address),
|
||||
data,
|
||||
})
|
||||
} else {
|
||||
const { data } = unapprovedTxs[editingTransactionId].txParams
|
||||
|
||||
Object.assign(editingTx.txParams, {
|
||||
value: ethUtil.addHexPrefix(amount),
|
||||
to: ethUtil.addHexPrefix(to),
|
||||
data,
|
||||
})
|
||||
|
||||
if (typeof editingTx.txParams.data === 'undefined') {
|
||||
delete editingTx.txParams.data
|
||||
}
|
||||
}
|
||||
|
||||
return editingTx
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.onSubmit = function (event) {
|
||||
event.preventDefault()
|
||||
const {
|
||||
from: {address: from},
|
||||
to: _to,
|
||||
amount,
|
||||
gasLimit: gas,
|
||||
gasPrice,
|
||||
signTokenTx,
|
||||
signTx,
|
||||
updateTx,
|
||||
selectedToken,
|
||||
editingTransactionId,
|
||||
toNickname,
|
||||
errors: { amount: amountError, to: toError },
|
||||
} = this.props
|
||||
|
||||
const noErrors = !amountError && toError === null
|
||||
|
||||
if (!noErrors) {
|
||||
return
|
||||
}
|
||||
|
||||
const to = ethUtil.addHexPrefix(_to)
|
||||
|
||||
this.addToAddressBookIfNew(to, toNickname)
|
||||
|
||||
if (editingTransactionId) {
|
||||
const editedTx = this.getEditedTx()
|
||||
updateTx(editedTx)
|
||||
} else {
|
||||
|
||||
const txParams = {
|
||||
from,
|
||||
value: '0',
|
||||
gas,
|
||||
gasPrice,
|
||||
}
|
||||
|
||||
if (!selectedToken) {
|
||||
txParams.value = amount
|
||||
txParams.to = to
|
||||
}
|
||||
|
||||
Object.keys(txParams).forEach(key => {
|
||||
txParams[key] = ethUtil.addHexPrefix(txParams[key])
|
||||
})
|
||||
|
||||
selectedToken
|
||||
? signTokenTx(selectedToken.address, to, amount, txParams)
|
||||
: signTx(txParams)
|
||||
}
|
||||
|
||||
this.props.history.push(CONFIRM_TRANSACTION_ROUTE)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user