diff --git a/src/routes/safe/component/SendToken/ReviewTx/index.jsx b/src/routes/safe/component/SendToken/ReviewTx/index.jsx new file mode 100644 index 00000000..0463b769 --- /dev/null +++ b/src/routes/safe/component/SendToken/ReviewTx/index.jsx @@ -0,0 +1,34 @@ +// @flow +import * as React from 'react' +import CircularProgress from '@material-ui/core/CircularProgress' +import Block from '~/components/layout/Block' +import Bold from '~/components/layout/Bold' +import Heading from '~/components/layout/Heading' +import Paragraph from '~/components/layout/Paragraph' +import { TKN_DESTINATION_PARAM, TKN_VALUE_PARAM } from '~/routes/safe/component/SendToken/SendTokenForm/index' + +type FormProps = { + values: Object, + submitting: boolean, +} + +const spinnerStyle = { + minHeight: '50px', +} + +const ReviewTx = (symbol: string) => ({ values, submitting }: FormProps) => ( + + Review the move token funds + + Destination: {values[TKN_DESTINATION_PARAM]} + + + {`Amount to transfer: ${values[TKN_VALUE_PARAM]} ${symbol}`} + + + { submitting && } + + +) + +export default ReviewTx diff --git a/src/routes/safe/component/SendToken/SendTokenForm/index.jsx b/src/routes/safe/component/SendToken/SendTokenForm/index.jsx new file mode 100644 index 00000000..61c0f09f --- /dev/null +++ b/src/routes/safe/component/SendToken/SendTokenForm/index.jsx @@ -0,0 +1,50 @@ +// @flow +import * as React from 'react' +import Field from '~/components/forms/Field' +import TextField from '~/components/forms/TextField' +import { composeValidators, inLimit, mustBeFloat, required, greaterThan, mustBeEthereumAddress } from '~/components/forms/validator' +import Block from '~/components/layout/Block' +import Heading from '~/components/layout/Heading' + +export const CONFIRMATIONS_ERROR = 'Number of confirmations can not be higher than the number of owners' + +export const TKN_DESTINATION_PARAM = 'tknDestination' +export const TKN_VALUE_PARAM = 'tknValue' + +type Props = { + funds: string, + symbol: string, +} + +const SendTokenForm = ({ funds, symbol }: Props) => () => ( + + + Send tokens Transaction + + + {`Available tokens: ${funds} ${symbol}`} + + + + + + + + +) + +export default SendTokenForm diff --git a/src/routes/safe/component/SendToken/actions.js b/src/routes/safe/component/SendToken/actions.js new file mode 100644 index 00000000..681ad469 --- /dev/null +++ b/src/routes/safe/component/SendToken/actions.js @@ -0,0 +1,10 @@ +// @flow +import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions' + +export type Actions = { + fetchTransactions: typeof fetchTransactions, +} + +export default { + fetchTransactions, +} diff --git a/src/routes/safe/component/SendToken/index.jsx b/src/routes/safe/component/SendToken/index.jsx new file mode 100644 index 00000000..a63e3e94 --- /dev/null +++ b/src/routes/safe/component/SendToken/index.jsx @@ -0,0 +1,114 @@ +// @flow +import * as React from 'react' +import { connect } from 'react-redux' +import Stepper from '~/components/Stepper' +import { sleep } from '~/utils/timer' +import { type Safe } from '~/routes/safe/store/model/safe' +import { type Balance } from '~/routes/safe/store/model/balance' +import { createTransaction } from '~/routes/safe/component/AddTransaction/createTransactions' +import { getStandardTokenContract } from '~/routes/safe/store/actions/fetchBalances' +import { EMPTY_DATA } from '~/wallets/ethTransactions' +import actions, { type Actions } from './actions' +import selector, { type SelectorProps } from './selector' +import SendTokenForm, { TKN_DESTINATION_PARAM, TKN_VALUE_PARAM } from './SendTokenForm' +import ReviewTx from './ReviewTx' + +const getSteps = () => [ + 'Fill Move Token form', 'Review Move Token form', +] + +type Props = SelectorProps & Actions & { + safe: Safe, + balance: Balance, + onReset: () => void, +} + +type State = { + done: boolean, +} + +export const SEE_TXS_BUTTON_TEXT = 'VISIT TXS' + +const isEther = (symbol: string) => symbol === 'ETH' + +const getTransferData = async (tokenAddress: string, to: string, amount: number) => { + const StandardToken = await getStandardTokenContract() + const myToken = await StandardToken.at(tokenAddress) + + return myToken.contract.transfer.getData(to, amount) +} + +const processTokenTransfer = async (safe: Safe, balance: Balance, to: string, amount: number, userAddress: string) => { + const symbol = balance.get('symbol') + + if (isEther(symbol)) { + return Promise.resolve() + } + + const nonce = Date.now() + const name = `Send ${amount} ${balance.get('symbol')} to ${to}` + const value = isEther(symbol) ? amount : 0 + const tokenAddress = balance.get('address') + const data = isEther(symbol) + ? EMPTY_DATA + : await getTransferData(tokenAddress, to, amount) + + return createTransaction(safe, name, tokenAddress, value, nonce, userAddress, data) +} + +class AddTransaction extends React.Component { + state = { + done: false, + } + + onTransaction = async (values: Object) => { + try { + const { safe, balance, userAddress } = this.props + + const amount = values[TKN_VALUE_PARAM] + const destination = values[TKN_DESTINATION_PARAM] + await processTokenTransfer(safe, balance, destination, amount, userAddress) + await sleep(1500) + this.props.fetchTransactions() + this.setState({ done: true }) + } catch (error) { + this.setState({ done: false }) + // eslint-disable-next-line + console.log('Error while moving ERC20 token funds ' + error) + } + } + + onReset = () => { + this.setState({ done: false }) + this.props.onReset() // This is for show the TX list component + } + + render() { + const { done } = this.state + const { balance } = this.props + const steps = getSteps() + const finishedButton = + const symbol = balance.get('symbol') + + return ( + + + + { SendTokenForm } + + + { ReviewTx } + + + + ) + } +} + +export default connect(selector, actions)(AddTransaction) diff --git a/src/routes/safe/component/SendToken/selector.js b/src/routes/safe/component/SendToken/selector.js new file mode 100644 index 00000000..9e7bfef1 --- /dev/null +++ b/src/routes/safe/component/SendToken/selector.js @@ -0,0 +1,11 @@ +// @flow +import { createStructuredSelector } from 'reselect' +import { userAccountSelector } from '~/wallets/store/selectors/index' + +export type SelectorProps = { + userAddress: userAccountSelector, +} + +export default createStructuredSelector({ + userAddress: userAccountSelector, +})