Merge pull request #393 from poanetwork/vb-send-all-tokens

Send all option for tokens send
This commit is contained in:
Victor Baranov 2020-06-15 16:21:44 +03:00 committed by GitHub
commit 414ad22b39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 104 additions and 34 deletions

View File

@ -1,4 +1,4 @@
import { multiplyCurrencies, subtractCurrencies, BIG_NUMBER_WEI_MULTIPLIER } from '../../../../../ui/app/conversion-util' import { subtractCurrencies, BIG_NUMBER_WEI_MULTIPLIER } from '../../../../../ui/app/conversion-util'
import ethUtil from 'ethereumjs-util' import ethUtil from 'ethereumjs-util'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
@ -6,17 +6,10 @@ export function calcMaxAmount ({ balance, gasTotal, sendToken, tokenBalance }) {
const { decimals } = sendToken || {} const { decimals } = sendToken || {}
const multiplier = Math.pow(10, Number(decimals || 0)) const multiplier = Math.pow(10, Number(decimals || 0))
let maxBalance let maxBalance
if (sendToken) { if (sendToken) {
maxBalance = multiplyCurrencies( const tokenBalanceBN = new BigNumber(tokenBalance.toString())
tokenBalance, maxBalance = tokenBalanceBN.div(multiplier).toString()
multiplier,
{
toNumericBase: 'hex',
multiplicandBase: 16,
},
)
} else { } else {
const maxBalanceInWei = const maxBalanceInWei =
subtractCurrencies( subtractCurrencies(

View File

@ -18,7 +18,8 @@ import log from 'loglevel'
import SendProfile from './send-profile' import SendProfile from './send-profile'
import SendHeader from './send-header' import SendHeader from './send-header'
import ErrorComponent from '../error' import ErrorComponent from '../error'
import { getMetaMaskAccounts } from '../../../../ui/app/selectors' import { getMetaMaskAccounts, getSendToken, getSendTo, getTokenBalance, getSendTokenContract } from '../../../../ui/app/selectors'
import AmountMaxButton from './amount-max-button'
class SendTransactionScreen extends PersistentForm { class SendTransactionScreen extends PersistentForm {
constructor (props) { constructor (props) {
@ -30,13 +31,12 @@ class SendTransactionScreen extends PersistentForm {
balance: 0, balance: 0,
decimals: 0, decimals: 0,
}, },
amount: '',
isLoading: true, isLoading: true,
} }
PersistentForm.call(this) PersistentForm.call(this)
} }
render () { render () {
const { isLoading, token, amount } = this.state const { isLoading, token } = this.state
if (isLoading) { if (isLoading) {
return ( return (
<Loading isLoading={isLoading} loadingMessage="Loading..." /> <Loading isLoading={isLoading} loadingMessage="Loading..." />
@ -50,6 +50,7 @@ class SendTransactionScreen extends PersistentForm {
identities, identities,
addressBook, addressBook,
error, error,
updateSendTo,
} = props } = props
const nextDisabled = token.balance <= 0 const nextDisabled = token.balance <= 0
@ -67,18 +68,20 @@ class SendTransactionScreen extends PersistentForm {
network={network} network={network}
identities={identities} identities={identities}
addressBook={addressBook} addressBook={addressBook}
updateSendTo={updateSendTo}
/> />
</section> </section>
<section className="flex-row flex-center"> <section className="flex-row flex-center">
<input className="large-input" <input className="large-input"
name="amount" name="amount"
value={amount} value={this.props.amount || ''}
onChange={(e) => this.amountDidChange(e.target.value)} onChange={(e) => this.amountDidChange(e.target.value)}
placeholder="Amount" placeholder="Amount"
type="number" type="number"
style={{ style={{
marginRight: '6px', marginRight: '6px',
}} }}
disabled={!!this.props.maxModeOn}
/> />
<button <button
onClick={() => this.onSubmit()} onClick={() => this.onSubmit()}
@ -86,13 +89,22 @@ class SendTransactionScreen extends PersistentForm {
>Next >Next
</button> </button>
</section> </section>
<section className="flex-row flex-left amount-max-container"><AmountMaxButton /></section>
</div> </div>
) )
} }
componentDidMount () { componentDidMount () {
this.getTokensMetadata() this.getTokensMetadata()
.then(() => { .then((token) => {
this.props.updateSendToken(token)
const {
sendToken,
tokenContract,
address,
} = this.props
this.props.updateSendTokenBalance({sendToken, tokenContract, address})
this.createFreshTokenTracker() this.createFreshTokenTracker()
}) })
} }
@ -102,16 +114,17 @@ class SendTransactionScreen extends PersistentForm {
this.tokenInfoGetter = tokenInfoGetter() this.tokenInfoGetter = tokenInfoGetter()
const { tokenAddress, network } = this.props const { tokenAddress, network } = this.props
const { symbol = '', decimals = 0 } = await this.tokenInfoGetter(tokenAddress) const { symbol = '', decimals = 0 } = await this.tokenInfoGetter(tokenAddress)
const token = {
address: tokenAddress,
network,
symbol,
decimals,
}
this.setState({ this.setState({
token: { token,
address: tokenAddress,
network,
symbol,
decimals,
},
}) })
return Promise.resolve() return Promise.resolve(token)
} }
componentWillUnmount () { componentWillUnmount () {
@ -120,6 +133,9 @@ class SendTransactionScreen extends PersistentForm {
this.tracker.stop() this.tracker.stop()
this.tracker.removeListener('update', this.balanceUpdater) this.tracker.removeListener('update', this.balanceUpdater)
this.tracker.removeListener('error', this.showError) this.tracker.removeListener('error', this.showError)
this.props.updateSendAmount(null)
this.props.setMaxModeTo(false)
this.props.updateSendTo('')
} }
createFreshTokenTracker () { createFreshTokenTracker () {
@ -176,14 +192,13 @@ class SendTransactionScreen extends PersistentForm {
} }
amountDidChange (amount) { amountDidChange (amount) {
this.setState({ this.props.updateSendAmount(amount)
amount,
})
} }
async onSubmit () { async onSubmit () {
const state = this.state || {} const state = this.state || {}
const { token, amount } = state const { token } = state
const { amount } = this.props
let recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '') let recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '')
let nickname = state.nickname || ' ' let nickname = state.nickname || ' '
if (typeof recipient === 'object') { if (typeof recipient === 'object') {
@ -284,6 +299,12 @@ const mapStateToProps = (state) => {
network: state.metamask.network, network: state.metamask.network,
addressBook: state.metamask.addressBook, addressBook: state.metamask.addressBook,
tokenAddress: state.appState.currentView.tokenAddress, tokenAddress: state.appState.currentView.tokenAddress,
to: getSendTo(state),
sendToken: getSendToken(state),
amount: state.metamask.send.amount,
maxModeOn: state.metamask.send.maxModeOn,
tokenBalance: getTokenBalance(state),
tokenContract: getSendTokenContract(state),
} }
result.error = result.warning && result.warning.split('.')[0] result.error = result.warning && result.warning.split('.')[0]
@ -307,6 +328,11 @@ const mapDispatchToProps = dispatch => {
txParams, txParams,
confTxScreenParams, confTxScreenParams,
) => dispatch(actions.signTokenTx(tokenAddress, toAddress, tokensValueWithDec, txParams, confTxScreenParams)), ) => dispatch(actions.signTokenTx(tokenAddress, toAddress, tokensValueWithDec, txParams, confTxScreenParams)),
updateSendTokenBalance: props => dispatch(actions.updateSendTokenBalance(props)),
setMaxModeTo: maxMode => dispatch(actions.setMaxModeTo(maxMode)),
updateSendAmount: amount => dispatch(actions.updateSendAmount(amount)),
updateSendTo: (to, nickname) => dispatch(actions.updateSendTo(to, nickname)),
updateSendToken: token => dispatch(actions.updateSendToken(token)),
} }
} }

View File

@ -289,14 +289,14 @@ function mapDispatchToProps (dispatch) {
return { return {
addToAddressBook: (recipient, nickname) => dispatch(actions.addToAddressBook(recipient, nickname)), addToAddressBook: (recipient, nickname) => dispatch(actions.addToAddressBook(recipient, nickname)),
showAccountsPage: () => dispatch(actions.showAccountsPage()), showAccountsPage: () => dispatch(actions.showAccountsPage()),
displayWarning: (msg) => dispatch(actions.displayWarning(msg)), displayWarning: msg => dispatch(actions.displayWarning(msg)),
hideWarning: () => dispatch(actions.hideWarning()), hideWarning: () => dispatch(actions.hideWarning()),
getPendingNonce: (address) => dispatch(actions.getPendingNonce(address)), getPendingNonce: address => dispatch(actions.getPendingNonce(address)),
signTx: (txParams) => dispatch(actions.signTx(txParams)), signTx: txParams => dispatch(actions.signTx(txParams)),
updateSendAmount: (amount) => dispatch(actions.updateSendAmount(amount)), updateSendAmount: amount => dispatch(actions.updateSendAmount(amount)),
setMaxModeTo: (maxMode) => dispatch(actions.setMaxModeTo(maxMode)), setMaxModeTo: maxMode => dispatch(actions.setMaxModeTo(maxMode)),
updateSendTo: (to, nickname) => dispatch(actions.updateSendTo(to, nickname)), updateSendTo: (to, nickname) => dispatch(actions.updateSendTo(to, nickname)),
updateSendHexData: (txData) => dispatch(actions.updateSendHexData(txData)), updateSendHexData: txData => dispatch(actions.updateSendHexData(txData)),
} }
} }

View File

@ -212,6 +212,7 @@ const actions = {
UPDATE_SEND_ERRORS: 'UPDATE_SEND_ERRORS', UPDATE_SEND_ERRORS: 'UPDATE_SEND_ERRORS',
UPDATE_MAX_MODE: 'UPDATE_MAX_MODE', UPDATE_MAX_MODE: 'UPDATE_MAX_MODE',
UPDATE_SEND: 'UPDATE_SEND', UPDATE_SEND: 'UPDATE_SEND',
UPDATE_SEND_TOKEN: 'UPDATE_SEND_TOKEN',
CLEAR_SEND: 'CLEAR_SEND', CLEAR_SEND: 'CLEAR_SEND',
OPEN_FROM_DROPDOWN: 'OPEN_FROM_DROPDOWN', OPEN_FROM_DROPDOWN: 'OPEN_FROM_DROPDOWN',
CLOSE_FROM_DROPDOWN: 'CLOSE_FROM_DROPDOWN', CLOSE_FROM_DROPDOWN: 'CLOSE_FROM_DROPDOWN',
@ -230,6 +231,7 @@ const actions = {
updateSendMemo, updateSendMemo,
setMaxModeTo, setMaxModeTo,
updateSend, updateSend,
updateSendToken,
updateSendErrors, updateSendErrors,
clearSend, clearSend,
setSelectedAddress, setSelectedAddress,
@ -1133,7 +1135,7 @@ function showChooseContractExecutorPage ({methodSelected, methodABI, inputValues
} }
function updateSendTokenBalance ({ function updateSendTokenBalance ({
selectedToken, sendToken,
tokenContract, tokenContract,
address, address,
}) { }) {
@ -1144,7 +1146,7 @@ function updateSendTokenBalance ({
return tokenBalancePromise return tokenBalancePromise
.then(usersToken => { .then(usersToken => {
if (usersToken) { if (usersToken) {
const newTokenBalance = calcTokenBalance({ selectedToken, usersToken }) const newTokenBalance = calcTokenBalance({ sendToken, usersToken })
dispatch(setSendTokenBalance(newTokenBalance.toString(10))) dispatch(setSendTokenBalance(newTokenBalance.toString(10)))
} }
}) })
@ -1218,6 +1220,13 @@ function updateSend (newSend) {
} }
} }
function updateSendToken (token) {
return {
type: actions.UPDATE_SEND_TOKEN,
value: token,
}
}
function clearSend () { function clearSend () {
return { return {
type: actions.CLEAR_SEND, type: actions.CLEAR_SEND,

View File

@ -283,6 +283,35 @@ function reduceMetamask (state, action) {
}, },
}) })
case actions.UPDATE_SEND_TOKEN:
const newSend = {
...metamaskState.send,
token: action.value,
}
// erase token-related state when switching back to native currency
if (newSend.editingTransactionId && !newSend.token) {
const unapprovedTx = newSend?.unapprovedTxs?.[newSend.editingTransactionId] || {}
const txParams = unapprovedTx.txParams || {}
Object.assign(newSend, {
tokenBalance: null,
balance: '0',
from: unapprovedTx.from || '',
unapprovedTxs: {
...newSend.unapprovedTxs,
[newSend.editingTransactionId]: {
...unapprovedTx,
txParams: {
...txParams,
data: '',
},
},
},
})
}
return Object.assign(metamaskState, {
send: newSend,
})
case actions.CLEAR_SEND: case actions.CLEAR_SEND:
return extend(metamaskState, { return extend(metamaskState, {
send: { send: {

View File

@ -46,6 +46,8 @@ const selectors = {
priceEstimateToWei, priceEstimateToWei,
getCurrentEthBalance, getCurrentEthBalance,
getSendToken, getSendToken,
getSendTokenAddress,
getSendTokenContract,
getTokenBalance, getTokenBalance,
getSendFromBalance, getSendFromBalance,
getSendFromObject, getSendFromObject,
@ -269,6 +271,17 @@ function getSendToken (state) {
return state.metamask.send.token return state.metamask.send.token
} }
function getSendTokenAddress (state) {
return getSendToken(state)?.address
}
function getSendTokenContract (state) {
const sendTokenAddress = getSendTokenAddress(state)
return sendTokenAddress
? global.eth.contract(abi).at(sendTokenAddress)
: null
}
function getTokenBalance (state) { function getTokenBalance (state) {
return state.metamask.send.tokenBalance return state.metamask.send.tokenBalance
} }