parent
13f22ff6b0
commit
e1077836ce
@ -0,0 +1,394 @@ |
||||
const Component = require('react').Component |
||||
const { connect } = require('react-redux') |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const abi = require('human-standard-token-abi') |
||||
const abiDecoder = require('abi-decoder') |
||||
abiDecoder.addABI(abi) |
||||
const actions = require('../../actions') |
||||
const clone = require('clone') |
||||
const Identicon = require('../identicon') |
||||
const ethUtil = require('ethereumjs-util') |
||||
const BN = ethUtil.BN |
||||
const hexToBn = require('../../../../app/scripts/lib/hex-to-bn') |
||||
const { conversionUtil } = require('../../conversion-util') |
||||
|
||||
const MIN_GAS_PRICE_GWEI_BN = new BN(1) |
||||
const GWEI_FACTOR = new BN(1e9) |
||||
const MIN_GAS_PRICE_BN = MIN_GAS_PRICE_GWEI_BN.mul(GWEI_FACTOR) |
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(ConfirmSendToken) |
||||
|
||||
function mapStateToProps (state, ownProps) { |
||||
const { token: { symbol }, txData } = ownProps |
||||
const { txParams } = txData || {} |
||||
const tokenData = txParams.data && abiDecoder.decodeMethod(txParams.data) |
||||
const { |
||||
conversionRate, |
||||
identities, |
||||
} = state.metamask |
||||
const accounts = state.metamask.accounts |
||||
const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0] |
||||
const tokenExchangeRates = state.metamask.tokenExchangeRates |
||||
const pair = `${symbol.toLowerCase()}_eth` |
||||
const { rate: tokenExchangeRate = 0 } = tokenExchangeRates[pair] || {} |
||||
|
||||
return { |
||||
conversionRate, |
||||
identities, |
||||
selectedAddress, |
||||
tokenExchangeRate, |
||||
tokenData: tokenData || {}, |
||||
} |
||||
} |
||||
|
||||
function mapDispatchToProps (dispatch, ownProps) { |
||||
const { token: { symbol } } = ownProps |
||||
|
||||
return { |
||||
backToAccountDetail: address => dispatch(actions.backToAccountDetail(address)), |
||||
cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })), |
||||
updateTokenExchangeRate: () => dispatch(actions.updateTokenExchangeRate(symbol)), |
||||
} |
||||
} |
||||
|
||||
inherits(ConfirmSendToken, Component) |
||||
function ConfirmSendToken () { |
||||
Component.call(this) |
||||
this.state = {} |
||||
this.onSubmit = this.onSubmit.bind(this) |
||||
} |
||||
|
||||
ConfirmSendToken.prototype.componentWillMount = function () { |
||||
this.props.updateTokenExchangeRate() |
||||
} |
||||
|
||||
ConfirmSendToken.prototype.getAmount = function () { |
||||
const { conversionRate, tokenExchangeRate, token, tokenData } = this.props |
||||
const { params = [] } = tokenData |
||||
const { value } = params[1] || {} |
||||
const { decimals } = token |
||||
const multiplier = Math.pow(10, Number(decimals || 0)) |
||||
const sendTokenAmount = Number(value / multiplier) |
||||
|
||||
return { |
||||
fiat: tokenExchangeRate |
||||
? +(sendTokenAmount * tokenExchangeRate * conversionRate).toFixed(2) |
||||
: null, |
||||
token: +sendTokenAmount.toFixed(decimals), |
||||
} |
||||
|
||||
} |
||||
|
||||
ConfirmSendToken.prototype.getGasFee = function () { |
||||
const { conversionRate, tokenExchangeRate, token } = this.props |
||||
const txMeta = this.gatherTxMeta() |
||||
const txParams = txMeta.txParams || {} |
||||
const { decimals } = token |
||||
|
||||
// Gas
|
||||
const gas = txParams.gas |
||||
const gasBn = hexToBn(gas) |
||||
|
||||
// Gas Price
|
||||
const gasPrice = txParams.gasPrice || MIN_GAS_PRICE_BN.toString(16) |
||||
const gasPriceBn = hexToBn(gasPrice) |
||||
const txFeeBn = gasBn.mul(gasPriceBn) |
||||
|
||||
|
||||
const USD = conversionUtil(txFeeBn, { |
||||
fromNumericBase: 'BN', |
||||
toNumericBase: 'dec', |
||||
fromDenomination: 'WEI', |
||||
fromCurrency: 'ETH', |
||||
toCurrency: 'USD', |
||||
numberOfDecimals: 2, |
||||
conversionRate, |
||||
}) |
||||
const ETH = conversionUtil(txFeeBn, { |
||||
fromNumericBase: 'BN', |
||||
toNumericBase: 'dec', |
||||
fromDenomination: 'WEI', |
||||
fromCurrency: 'ETH', |
||||
toCurrency: 'ETH', |
||||
numberOfDecimals: 6, |
||||
conversionRate, |
||||
}) |
||||
|
||||
return { |
||||
fiat: +Number(USD).toFixed(2), |
||||
eth: ETH, |
||||
token: tokenExchangeRate |
||||
? +(ETH * tokenExchangeRate).toFixed(decimals) |
||||
: null, |
||||
} |
||||
} |
||||
|
||||
ConfirmSendToken.prototype.getData = function () { |
||||
const { identities } = this.props |
||||
const txMeta = this.gatherTxMeta() |
||||
const txParams = txMeta.txParams || {} |
||||
|
||||
return { |
||||
from: { |
||||
address: txParams.from, |
||||
name: identities[txParams.from].name, |
||||
}, |
||||
to: { |
||||
address: txParams.to, |
||||
name: identities[txParams.to] ? identities[txParams.to].name : 'New Recipient', |
||||
}, |
||||
memo: txParams.memo || '', |
||||
} |
||||
} |
||||
|
||||
ConfirmSendToken.prototype.renderHeroAmount = function () { |
||||
const { token: { symbol } } = this.props |
||||
const { fiat: fiatAmount, token: tokenAmount } = this.getAmount() |
||||
const txMeta = this.gatherTxMeta() |
||||
const txParams = txMeta.txParams || {} |
||||
const { memo = '' } = txParams |
||||
|
||||
return fiatAmount |
||||
? ( |
||||
h('div.confirm-send-token__hero-amount-wrapper', [ |
||||
h('h3.flex-center.confirm-screen-send-amount', `$${fiatAmount}`), |
||||
h('h3.flex-center.confirm-screen-send-amount-currency', 'USD'), |
||||
h('div.flex-center.confirm-memo-wrapper', [ |
||||
h('h3.confirm-screen-send-memo', memo), |
||||
]), |
||||
]) |
||||
) |
||||
: ( |
||||
h('div.confirm-send-token__hero-amount-wrapper', [ |
||||
h('h3.flex-center.confirm-screen-send-amount', tokenAmount), |
||||
h('h3.flex-center.confirm-screen-send-amount-currency', symbol), |
||||
h('div.flex-center.confirm-memo-wrapper', [ |
||||
h('h3.confirm-screen-send-memo', memo), |
||||
]), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
ConfirmSendToken.prototype.renderGasFee = function () { |
||||
const { token: { symbol } } = this.props |
||||
const { fiat: fiatGas, token: tokenGas, eth: ethGas } = this.getGasFee() |
||||
|
||||
return ( |
||||
h('section.flex-row.flex-center.confirm-screen-row', [ |
||||
h('span.confirm-screen-label.confirm-screen-section-column', [ 'Gas Fee' ]), |
||||
h('div.confirm-screen-section-column', [ |
||||
h('div.confirm-screen-row-info', `$${fiatGas} USD`), |
||||
|
||||
h( |
||||
'div.confirm-screen-row-detail', |
||||
tokenGas ? `${tokenGas} ${symbol}` : `${ethGas} ETH` |
||||
), |
||||
]), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
ConfirmSendToken.prototype.renderTotalPlusGas = function () { |
||||
const { token: { symbol } } = this.props |
||||
const { fiat: fiatAmount, token: tokenAmount } = this.getAmount() |
||||
const { fiat: fiatGas, token: tokenGas } = this.getGasFee() |
||||
|
||||
return fiatAmount && fiatGas |
||||
? ( |
||||
h('section.flex-row.flex-center.confirm-screen-total-box ', [ |
||||
h('div.confirm-screen-section-column', [ |
||||
h('span.confirm-screen-label', [ 'Total ' ]), |
||||
h('div.confirm-screen-total-box__subtitle', [ 'Amount + Gas' ]), |
||||
]), |
||||
|
||||
h('div.confirm-screen-section-column', [ |
||||
h('div.confirm-screen-row-info', `$${fiatAmount + fiatGas} USD`), |
||||
h('div.confirm-screen-row-detail', `${tokenAmount + tokenGas} ${symbol}`), |
||||
]), |
||||
]) |
||||
) |
||||
: ( |
||||
h('section.flex-row.flex-center.confirm-screen-total-box ', [ |
||||
h('div.confirm-screen-section-column', [ |
||||
h('span.confirm-screen-label', [ 'Total ' ]), |
||||
h('div.confirm-screen-total-box__subtitle', [ 'Amount + Gas' ]), |
||||
]), |
||||
|
||||
h('div.confirm-screen-section-column', [ |
||||
h('div.confirm-screen-row-info', `${tokenAmount} ${symbol}`), |
||||
h('div.confirm-screen-row-detail', `+ ${fiatGas} USD Gas`), |
||||
]), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
ConfirmSendToken.prototype.render = function () { |
||||
const { backToAccountDetail, selectedAddress } = this.props |
||||
const txMeta = this.gatherTxMeta() |
||||
const txParams = txMeta.txParams || {} |
||||
|
||||
const { |
||||
from: { |
||||
address: fromAddress, |
||||
name: fromName, |
||||
}, |
||||
to: { |
||||
address: toAddress, |
||||
name: toName, |
||||
}, |
||||
} = this.getData() |
||||
|
||||
this.inputs = [] |
||||
|
||||
return ( |
||||
h('div.flex-column.flex-grow.confirm-screen-container', { |
||||
style: { minWidth: '355px' }, |
||||
}, [ |
||||
// Main Send token Card
|
||||
h('div.confirm-screen-wrapper.flex-column.flex-grow', [ |
||||
h('h3.flex-center.confirm-screen-header', [ |
||||
h('button.confirm-screen-back-button', { |
||||
onClick: () => backToAccountDetail(selectedAddress), |
||||
}, 'BACK'), |
||||
h('div.confirm-screen-title', 'Confirm Transaction'), |
||||
]), |
||||
h('div.flex-row.flex-center.confirm-screen-identicons', [ |
||||
h('div.confirm-screen-account-wrapper', [ |
||||
h( |
||||
Identicon, |
||||
{ |
||||
address: fromAddress, |
||||
diameter: 100, |
||||
}, |
||||
), |
||||
h('span.confirm-screen-account-name', fromName), |
||||
h('span.confirm-screen-account-number', fromAddress.slice(fromAddress.length - 4)), |
||||
]), |
||||
h('i.fa.fa-arrow-right.fa-lg'), |
||||
h('div.confirm-screen-account-wrapper', [ |
||||
h( |
||||
Identicon, |
||||
{ |
||||
address: txParams.to, |
||||
diameter: 100, |
||||
}, |
||||
), |
||||
h('span.confirm-screen-account-name', toName), |
||||
h('span.confirm-screen-account-number', toAddress.slice(toAddress.length - 4)), |
||||
]), |
||||
]), |
||||
|
||||
h('h3.flex-center.confirm-screen-sending-to-message', { |
||||
style: { |
||||
textAlign: 'center', |
||||
fontSize: '16px', |
||||
}, |
||||
}, [ |
||||
`You're sending to Recipient ...${toAddress.slice(toAddress.length - 4)}`, |
||||
]), |
||||
|
||||
this.renderHeroAmount(), |
||||
|
||||
h('div.confirm-screen-rows', [ |
||||
h('section.flex-row.flex-center.confirm-screen-row', [ |
||||
h('span.confirm-screen-label.confirm-screen-section-column', [ 'From' ]), |
||||
h('div.confirm-screen-section-column', [ |
||||
h('div.confirm-screen-row-info', fromName), |
||||
h('div.confirm-screen-row-detail', `...${fromAddress.slice(fromAddress.length - 4)}`), |
||||
]), |
||||
]), |
||||
|
||||
h('section.flex-row.flex-center.confirm-screen-row', [ |
||||
h('span.confirm-screen-label.confirm-screen-section-column', [ 'To' ]), |
||||
h('div.confirm-screen-section-column', [ |
||||
h('div.confirm-screen-row-info', toName), |
||||
h('div.confirm-screen-row-detail', `...${toAddress.slice(toAddress.length - 4)}`), |
||||
]), |
||||
]), |
||||
|
||||
this.renderGasFee(), |
||||
|
||||
this.renderTotalPlusGas(), |
||||
|
||||
]), |
||||
]), |
||||
|
||||
h('form#pending-tx-form.flex-column.flex-center', { |
||||
onSubmit: this.onSubmit, |
||||
}, [ |
||||
|
||||
// Accept Button
|
||||
h('button.confirm-screen-confirm-button', ['CONFIRM']), |
||||
|
||||
// Cancel Button
|
||||
h('div.cancel.btn-light.confirm-screen-cancel-button', { |
||||
onClick: (event) => this.cancel(event, txMeta), |
||||
}, 'CANCEL'), |
||||
]), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
ConfirmSendToken.prototype.onSubmit = function (event) { |
||||
event.preventDefault() |
||||
const txMeta = this.gatherTxMeta() |
||||
const valid = this.checkValidity() |
||||
this.setState({ valid, submitting: true }) |
||||
|
||||
if (valid && this.verifyGasParams()) { |
||||
this.props.sendTransaction(txMeta, event) |
||||
} else { |
||||
this.props.dispatch(actions.displayWarning('Invalid Gas Parameters')) |
||||
this.setState({ submitting: false }) |
||||
} |
||||
} |
||||
|
||||
ConfirmSendToken.prototype.cancel = function (event, txMeta) { |
||||
event.preventDefault() |
||||
this.props.cancelTransaction(txMeta) |
||||
} |
||||
|
||||
ConfirmSendToken.prototype.checkValidity = function () { |
||||
const form = this.getFormEl() |
||||
const valid = form.checkValidity() |
||||
return valid |
||||
} |
||||
|
||||
ConfirmSendToken.prototype.getFormEl = function () { |
||||
const form = document.querySelector('form#pending-tx-form') |
||||
// Stub out form for unit tests:
|
||||
if (!form) { |
||||
return { checkValidity () { return true } } |
||||
} |
||||
return form |
||||
} |
||||
|
||||
// After a customizable state value has been updated,
|
||||
ConfirmSendToken.prototype.gatherTxMeta = function () { |
||||
const props = this.props |
||||
const state = this.state |
||||
const txData = clone(state.txData) || clone(props.txData) |
||||
|
||||
// log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`)
|
||||
return txData |
||||
} |
||||
|
||||
ConfirmSendToken.prototype.verifyGasParams = function () { |
||||
// We call this in case the gas has not been modified at all
|
||||
if (!this.state) { return true } |
||||
return ( |
||||
this._notZeroOrEmptyString(this.state.gas) && |
||||
this._notZeroOrEmptyString(this.state.gasPrice) |
||||
) |
||||
} |
||||
|
||||
ConfirmSendToken.prototype._notZeroOrEmptyString = function (obj) { |
||||
return obj !== '' && obj !== '0x0' |
||||
} |
||||
|
||||
ConfirmSendToken.prototype.bnMultiplyByFraction = function (targetBN, numerator, denominator) { |
||||
const numBN = new BN(numerator) |
||||
const denomBN = new BN(denominator) |
||||
return targetBN.mul(numBN).div(denomBN) |
||||
} |
Loading…
Reference in new issue