send tokens
This commit is contained in:
parent
62f3369dd3
commit
29f87f1d90
|
@ -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,31 @@ function mapStateToProps (state) {
|
|||
}
|
||||
|
||||
PendingTx.prototype.render = function () {
|
||||
if (!this.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, this.state.tokenDecimals))
|
||||
const multiplier = Math.pow(10, 18)
|
||||
tokensToSend = tokenValBN.mul(multiplier).toString(16)
|
||||
tokensTransferTo = decodedData.params[0].value
|
||||
token = {
|
||||
address: txParams.to,
|
||||
decimals: this.state.tokenDecimals,
|
||||
symbol: this.state.tokenSymbol,
|
||||
}
|
||||
}
|
||||
|
||||
// Allow retry txs
|
||||
const { lastGasPrice } = txMeta
|
||||
|
@ -217,7 +244,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 +261,7 @@ PendingTx.prototype.render = function () {
|
|||
|
||||
forwardCarrat(),
|
||||
|
||||
this.miniAccountPanelForRecipient(),
|
||||
this.miniAccountPanelForRecipient(isToken, tokensTransferTo),
|
||||
]),
|
||||
|
||||
h('style', `
|
||||
|
@ -326,7 +356,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 +421,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 +529,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 +554,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 +584,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(
|
||||
|
@ -100,6 +100,16 @@ TokenCell.prototype.renderTokenOptions = function (menuToTop, ind) {
|
|||
},
|
||||
},
|
||||
[
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => {
|
||||
showSendTokenPage(address)
|
||||
},
|
||||
},
|
||||
`Send`,
|
||||
),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
|
@ -161,3 +171,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,315 @@
|
|||
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 EthBalance = require('./components/eth-balance')
|
||||
const EnsInput = require('./components/ens-input')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const { tokenInfoGetter } = require('../../ui/app/token-util')
|
||||
const TOKEN_TRANSFER_FUNCTION_SIGNATURE = '0xa9059cbb'
|
||||
|
||||
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,
|
||||
conversionRate: state.metamask.conversionRate,
|
||||
currentCurrency: state.metamask.currentCurrency,
|
||||
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 () {
|
||||
PersistentForm.call(this)
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.render = function () {
|
||||
this.persistentFormParentId = 'send-tx-form'
|
||||
|
||||
const props = this.props
|
||||
const {
|
||||
address,
|
||||
account,
|
||||
identity,
|
||||
network,
|
||||
identities,
|
||||
addressBook,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
} = 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(EthBalance, {
|
||||
value: account && account.balance,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
network,
|
||||
}),
|
||||
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
|
||||
//
|
||||
// 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 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.componentWillUnmount = function () {
|
||||
this.props.dispatch(actions.displayWarning(''))
|
||||
}
|
||||
|
||||
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 { tokenAddress } = this.props
|
||||
const state = this.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('')
|
||||
|
||||
this.tokenInfoGetter = tokenInfoGetter()
|
||||
|
||||
let message
|
||||
|
||||
if (isNaN(input) || input === '') {
|
||||
message = 'Invalid ether value.'
|
||||
return this.props.dispatch(actions.displayWarning(message))
|
||||
}
|
||||
|
||||
if (parts[1]) {
|
||||
var decimal = parts[1]
|
||||
if (decimal.length > 18) {
|
||||
message = 'Ether amount is too precise.'
|
||||
return this.props.dispatch(actions.displayWarning(message))
|
||||
}
|
||||
}
|
||||
|
||||
const value = util.normalizeEthStringToWei(input)
|
||||
// const balance = this.props.balance
|
||||
|
||||
// if (value.gt(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)
|
||||
|
||||
if (recipient) txParams.to = ethUtil.addHexPrefix(tokenAddress)
|
||||
|
||||
const { symbol = '', decimals = 0 } = await this.tokenInfoGetter(txParams.to)
|
||||
const tokeValRaw = value / 1e18 * `1e${decimals}` // todo: use calcTokenAmount function
|
||||
const tokenVal = `0x${tokeValRaw.toString(16)}`
|
||||
const encoded = this.generateTokenTransferData({toAddress, amount: tokenVal})
|
||||
txParams.data = encoded
|
||||
|
||||
const confTxScreenParams = {
|
||||
isToken: true,
|
||||
tokenSymbol: symbol,
|
||||
tokensToSend: value,
|
||||
tokensTransferTo: toAddress,
|
||||
}
|
||||
|
||||
this.props.dispatch(actions.signTokenTx(tokenAddress, toAddress, value, txParams, confTxScreenParams))
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.generateTokenTransferData = function ({ toAddress = '0x0', amount = '0x0' }) {
|
||||
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('')
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
@ -1542,11 +1542,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 +1995,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,
|
||||
|
|
|
@ -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