|
|
|
const { inherits } = require('util')
|
|
|
|
const PersistentForm = require('../lib/persistent-form')
|
|
|
|
const h = require('react-hyperscript')
|
|
|
|
const connect = require('react-redux').connect
|
|
|
|
const Identicon = require('./components/identicon')
|
|
|
|
const EnsInput = require('./components/ens-input')
|
|
|
|
const GasTooltip = require('./components/send/gas-tooltip')
|
|
|
|
const CurrencyToggle = require('./components/send/currency-toggle')
|
|
|
|
const GasFeeDisplay = require('./components/send/gas-fee-display')
|
|
|
|
const { getSelectedIdentity } = require('./selectors')
|
|
|
|
|
|
|
|
const {
|
|
|
|
showAccountsPage,
|
|
|
|
backToAccountDetail,
|
|
|
|
displayWarning,
|
|
|
|
hideWarning,
|
|
|
|
addToAddressBook,
|
|
|
|
signTx,
|
|
|
|
} = require('./actions')
|
|
|
|
const { stripHexPrefix, addHexPrefix } = require('ethereumjs-util')
|
|
|
|
const { isHex, numericBalance } = require('./util')
|
|
|
|
const { conversionUtil } = require('./conversion-util')
|
|
|
|
const BigNumber = require('bignumber.js')
|
|
|
|
|
|
|
|
const ARAGON = '960b236A07cf122663c4303350609A66A7B288C0'
|
|
|
|
|
|
|
|
module.exports = connect(mapStateToProps)(SendTransactionScreen)
|
|
|
|
|
|
|
|
function mapStateToProps (state) {
|
|
|
|
const {
|
|
|
|
selectedAddress: address,
|
|
|
|
accounts,
|
|
|
|
identities,
|
|
|
|
network,
|
|
|
|
addressBook,
|
|
|
|
conversionRate,
|
|
|
|
currentBlockGasLimit: blockGasLimit,
|
|
|
|
} = state.metamask
|
|
|
|
const { warning } = state.appState
|
|
|
|
const selectedIdentity = getSelectedIdentity(state)
|
|
|
|
const account = accounts[address]
|
|
|
|
|
|
|
|
return {
|
|
|
|
address,
|
|
|
|
accounts,
|
|
|
|
identities,
|
|
|
|
network,
|
|
|
|
addressBook,
|
|
|
|
conversionRate,
|
|
|
|
blockGasLimit,
|
|
|
|
warning,
|
|
|
|
selectedIdentity,
|
|
|
|
error: warning && warning.split('.')[0],
|
|
|
|
account,
|
|
|
|
identity: identities[address],
|
|
|
|
balance: account ? numericBalance(account.balance) : null,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
inherits(SendTransactionScreen, PersistentForm)
|
|
|
|
function SendTransactionScreen () {
|
|
|
|
PersistentForm.call(this)
|
|
|
|
|
|
|
|
// [WIP] These are the bare minimum of tx props needed to sign a transaction
|
|
|
|
// We will need a few more for contract-related interactions
|
|
|
|
this.state = {
|
|
|
|
newTx: {
|
|
|
|
from: '',
|
|
|
|
to: '',
|
|
|
|
// these values are hardcoded, so "Next" can be clicked
|
|
|
|
amount: '0x0', // see L544
|
|
|
|
gasPrice: '0x5d21dba00',
|
|
|
|
gas: '0x7b0d',
|
|
|
|
txData: null,
|
|
|
|
memo: '',
|
|
|
|
},
|
|
|
|
activeCurrency: 'USD',
|
|
|
|
tooltipIsOpen: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
this.back = this.back.bind(this)
|
|
|
|
this.closeTooltip = this.closeTooltip.bind(this)
|
|
|
|
this.onSubmit = this.onSubmit.bind(this)
|
|
|
|
this.recipientDidChange = this.recipientDidChange.bind(this)
|
|
|
|
this.setActiveCurrency = this.setActiveCurrency.bind(this)
|
|
|
|
this.toggleTooltip = this.toggleTooltip.bind(this)
|
|
|
|
}
|
|
|
|
|
|
|
|
SendTransactionScreen.prototype.render = function () {
|
|
|
|
this.persistentFormParentId = 'send-tx-form'
|
|
|
|
|
|
|
|
const props = this.props
|
|
|
|
const {
|
|
|
|
// selectedIdentity,
|
|
|
|
// network,
|
|
|
|
// identities,
|
|
|
|
// addressBook,
|
|
|
|
conversionRate,
|
|
|
|
} = props
|
|
|
|
|
|
|
|
const { blockGasLimit, newTx, activeCurrency } = this.state
|
|
|
|
const { gas, gasPrice } = newTx
|
|
|
|
// console.log(`activeCurrency`, activeCurrency)
|
|
|
|
// console.log({ selectedIdentity, identities })
|
|
|
|
// console.log('SendTransactionScreen state:', this.state)
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
|
|
h('div.send-screen-wrapper', [
|
|
|
|
// Main Send token Card
|
|
|
|
h('div.send-screen-card', [
|
|
|
|
|
|
|
|
h('img.send-eth-icon', { src: '../images/eth_logo.svg' }),
|
|
|
|
|
|
|
|
h('div.send-screen__title', 'Send'),
|
|
|
|
|
|
|
|
h('div.send-screen__subtitle', 'Send Ethereum to anyone with an Ethereum account'),
|
|
|
|
|
|
|
|
h('div.send-screen-input-wrapper', [
|
|
|
|
|
|
|
|
h('div', 'From:'),
|
|
|
|
|
|
|
|
h('input.large-input.send-screen-input', {
|
|
|
|
list: 'accounts',
|
|
|
|
placeholder: 'Account',
|
|
|
|
value: this.state.newTx.from,
|
|
|
|
onChange: (event) => {
|
|
|
|
console.log('event', event.target.value)
|
|
|
|
this.setState({
|
|
|
|
newTx: {
|
|
|
|
...this.state.newTx,
|
|
|
|
from: event.target.value,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
|
|
|
|
h('datalist#accounts', [
|
|
|
|
Object.keys(props.identities).map((key) => {
|
|
|
|
const identity = props.identities[key]
|
|
|
|
return h('option', {
|
|
|
|
value: identity.address,
|
|
|
|
label: identity.name,
|
|
|
|
key: identity.address,
|
|
|
|
})
|
|
|
|
}),
|
|
|
|
]),
|
|
|
|
|
|
|
|
]),
|
|
|
|
|
|
|
|
h('div.send-screen-input-wrapper', [
|
|
|
|
|
|
|
|
h('div', 'To:'),
|
|
|
|
|
|
|
|
h('input.large-input.send-screen-input', {
|
|
|
|
name: 'address',
|
|
|
|
list: 'addresses',
|
|
|
|
placeholder: 'Address',
|
|
|
|
value: this.state.newTx.to,
|
|
|
|
onChange: (event) => {
|
|
|
|
console.log('event', event.target.value)
|
|
|
|
this.setState({
|
|
|
|
newTx: {
|
|
|
|
...this.state.newTx,
|
|
|
|
to: event.target.value,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
|
|
|
|
h('datalist#addresses', [
|
|
|
|
// Corresponds to the addresses owned.
|
|
|
|
Object.entries(props.identities).map(([key, { address, name }]) => {
|
|
|
|
return h('option', {
|
|
|
|
value: address,
|
|
|
|
label: name,
|
|
|
|
key: address,
|
|
|
|
})
|
|
|
|
}),
|
|
|
|
// Corresponds to previously sent-to addresses.
|
|
|
|
props.addressBook.map(({ address, name }) => {
|
|
|
|
return h('option', {
|
|
|
|
value: address,
|
|
|
|
label: name,
|
|
|
|
key: address,
|
|
|
|
})
|
|
|
|
}),
|
|
|
|
]),
|
|
|
|
|
|
|
|
// h(EnsInput, {
|
|
|
|
// name: 'address',
|
|
|
|
// placeholder: 'Recipient Address',
|
|
|
|
// value: this.state.newTx.to,
|
|
|
|
// onChange: (event) => {
|
|
|
|
// this.setState({
|
|
|
|
// newTx: Object.assign(
|
|
|
|
// this.state.newTx,
|
|
|
|
// {
|
|
|
|
// to: event.target.value,
|
|
|
|
// }
|
|
|
|
// ),
|
|
|
|
// })
|
|
|
|
// },
|
|
|
|
// network,
|
|
|
|
// identities,
|
|
|
|
// addressBook,
|
|
|
|
// }),
|
|
|
|
|
|
|
|
]),
|
|
|
|
|
|
|
|
h('div.send-screen-input-wrapper', [
|
|
|
|
|
|
|
|
h('div.send-screen-amount-labels', [
|
|
|
|
h('span', 'Amount'),
|
|
|
|
h(CurrencyToggle, {
|
|
|
|
activeCurrency,
|
|
|
|
onClick: (newCurrency) => this.setActiveCurrency(newCurrency),
|
|
|
|
}), // holding on icon from design
|
|
|
|
]),
|
|
|
|
|
|
|
|
h('input.large-input.send-screen-input', {
|
|
|
|
placeholder: `0 ${activeCurrency}`,
|
|
|
|
type: 'number',
|
|
|
|
onChange: (event) => {
|
|
|
|
this.setState({
|
|
|
|
newTx: Object.assign(
|
|
|
|
this.state.newTx,
|
|
|
|
{
|
|
|
|
amount: event.target.value,
|
|
|
|
}
|
|
|
|
),
|
|
|
|
})
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
|
|
|
|
]),
|
|
|
|
|
|
|
|
h('div.send-screen-input-wrapper', [
|
|
|
|
this.state.tooltipIsOpen && h(GasTooltip, {
|
|
|
|
className: 'send-tooltip',
|
|
|
|
gasPrice,
|
|
|
|
gasLimit: gas,
|
|
|
|
onClose: this.closeTooltip,
|
|
|
|
onFeeChange: ({gasLimit, gasPrice}) => {
|
|
|
|
this.setState({
|
|
|
|
newTx: {
|
|
|
|
...this.state.newTx,
|
|
|
|
gas: gasLimit,
|
|
|
|
gasPrice,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
|
|
|
|
h('div.send-screen-gas-labels', [
|
|
|
|
h('span', [
|
|
|
|
h('i.fa.fa-bolt'),
|
|
|
|
'Gas fee:',
|
|
|
|
]),
|
|
|
|
h('span', 'What\'s this?'),
|
|
|
|
]),
|
|
|
|
|
|
|
|
// TODO: handle loading time when switching to USD
|
|
|
|
h('div.large-input.send-screen-gas-input', {}, [
|
|
|
|
h(GasFeeDisplay, {
|
|
|
|
activeCurrency,
|
|
|
|
conversionRate,
|
|
|
|
gas,
|
|
|
|
gasPrice,
|
|
|
|
blockGasLimit,
|
|
|
|
}),
|
|
|
|
h('div.send-screen-gas-input-customize', {
|
|
|
|
onClick: this.toggleTooltip,
|
|
|
|
}, [
|
|
|
|
'Customize',
|
|
|
|
]),
|
|
|
|
]),
|
|
|
|
|
|
|
|
]),
|
|
|
|
|
|
|
|
h('div.send-screen-input-wrapper', [
|
|
|
|
h('div', 'Transaction memo (optional)'),
|
|
|
|
h('input.large-input.send-screen-input', {
|
|
|
|
onChange: () => {
|
|
|
|
this.setState({
|
|
|
|
newTx: Object.assign(
|
|
|
|
this.state.newTx,
|
|
|
|
{
|
|
|
|
memo: event.target.value,
|
|
|
|
}
|
|
|
|
),
|
|
|
|
})
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
]),
|
|
|
|
|
|
|
|
h('div.send-screen-input-wrapper', {}, [
|
|
|
|
h('div', {}, ['Data (optional)']),
|
|
|
|
h('input.large-input.send-screen-input', {
|
|
|
|
onChange: () => {
|
|
|
|
this.setState({
|
|
|
|
newTx: Object.assign(
|
|
|
|
this.state.newTx,
|
|
|
|
{
|
|
|
|
txData: event.target.value,
|
|
|
|
}
|
|
|
|
),
|
|
|
|
})
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
]),
|
|
|
|
]),
|
|
|
|
|
|
|
|
// Buttons underneath card
|
|
|
|
h('section.flex-column.flex-center', [
|
|
|
|
h('button.btn-secondary.send-screen__send-button', {
|
|
|
|
onClick: (event) => this.onSubmit(event),
|
|
|
|
}, 'Next'),
|
|
|
|
h('button.btn-tertiary.send-screen__cancel-button', {
|
|
|
|
onClick: this.back,
|
|
|
|
}, 'Cancel'),
|
|
|
|
]),
|
|
|
|
])
|
|
|
|
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
SendTransactionScreen.prototype.toggleTooltip = function () {
|
|
|
|
this.setState({ tooltipIsOpen: !this.state.tooltipIsOpen })
|
|
|
|
}
|
|
|
|
|
|
|
|
SendTransactionScreen.prototype.closeTooltip = function () {
|
|
|
|
this.setState({ tooltipIsOpen: false })
|
|
|
|
}
|
|
|
|
|
|
|
|
SendTransactionScreen.prototype.setActiveCurrency = function (newCurrency) {
|
|
|
|
this.setState({ activeCurrency: newCurrency })
|
|
|
|
}
|
|
|
|
|
|
|
|
SendTransactionScreen.prototype.navigateToAccounts = function (event) {
|
|
|
|
event.stopPropagation()
|
|
|
|
this.props.dispatch(showAccountsPage())
|
|
|
|
}
|
|
|
|
|
|
|
|
SendTransactionScreen.prototype.back = function () {
|
|
|
|
var address = this.props.address
|
|
|
|
this.props.dispatch(backToAccountDetail(address))
|
|
|
|
}
|
|
|
|
|
|
|
|
SendTransactionScreen.prototype.recipientDidChange = function (recipient, nickname) {
|
|
|
|
this.setState({
|
|
|
|
recipient: recipient,
|
|
|
|
nickname: nickname,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
SendTransactionScreen.prototype.onSubmit = function (event) {
|
|
|
|
event.preventDefault()
|
|
|
|
const state = this.state || {}
|
|
|
|
|
|
|
|
// const recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '')
|
|
|
|
const recipient = state.newTx.to
|
|
|
|
|
|
|
|
const nickname = state.nickname || ' '
|
|
|
|
|
|
|
|
// const input = document.querySelector('input[name="amount"]').value
|
|
|
|
// const input = state.newTx.value
|
|
|
|
// const value = util.normalizeEthStringToWei(input)
|
|
|
|
|
|
|
|
// https://consensys.slack.com/archives/G1L7H42BT/p1503439134000169?thread_ts=1503438076.000411&cid=G1L7H42BT
|
|
|
|
// From @kumavis: "not needed for MVP but we will end up adding it again so consider just adding it now"
|
|
|
|
const txData = false
|
|
|
|
// Must replace with memo data.
|
|
|
|
// const txData = document.querySelector('input[name="txData"]').value
|
|
|
|
|
|
|
|
let message
|
|
|
|
|
|
|
|
// if (value.gt(balance)) {
|
|
|
|
// message = 'Insufficient funds.'
|
|
|
|
// 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.isValidAddress(recipient) && !txData) || (!recipient && !txData)) {
|
|
|
|
// message = 'Recipient address is invalid.'
|
|
|
|
// return this.props.dispatch(actions.displayWarning(message))
|
|
|
|
// }
|
|
|
|
|
|
|
|
if (txData && !isHex(stripHexPrefix(txData))) {
|
|
|
|
message = 'Transaction data must be hex string.'
|
|
|
|
return this.props.dispatch(displayWarning(message))
|
|
|
|
}
|
|
|
|
|
|
|
|
this.props.dispatch(hideWarning())
|
|
|
|
|
|
|
|
this.props.dispatch(addToAddressBook(recipient, nickname))
|
|
|
|
|
|
|
|
// TODO: need a clean way to integrate this into conversionUtil
|
|
|
|
const sendConversionRate = this.props.currentCurrency === 'ETH'
|
|
|
|
? this.props.conversionRate
|
|
|
|
: new BigNumber(1.0).div(this.props.conversionRate)
|
|
|
|
|
|
|
|
const sendAmount = conversionUtil(this.state.newTx.amount, {
|
|
|
|
fromNumericBase: 'dec',
|
|
|
|
toNumericBase: 'hex',
|
|
|
|
fromCurrency: this.props.currentCurrency,
|
|
|
|
toCurrency: 'ETH',
|
|
|
|
conversionRate: sendConversionRate,
|
|
|
|
})
|
|
|
|
|
|
|
|
var txParams = {
|
|
|
|
from: this.state.newTx.from,
|
|
|
|
to: this.state.newTx.to,
|
|
|
|
|
|
|
|
value: sendAmount,
|
|
|
|
|
|
|
|
// New: gas will now be specified on this step
|
|
|
|
gas: this.state.newTx.gas,
|
|
|
|
gasPrice: this.state.newTx.gasPrice,
|
|
|
|
}
|
|
|
|
|
|
|
|
if (recipient) txParams.to = addHexPrefix(recipient)
|
|
|
|
if (txData) txParams.data = txData
|
|
|
|
|
|
|
|
this.props.dispatch(signTx(txParams))
|
|
|
|
}
|