aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website/ts/components/token_balances.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/website/ts/components/token_balances.tsx')
-rw-r--r--packages/website/ts/components/token_balances.tsx699
1 files changed, 699 insertions, 0 deletions
diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx
new file mode 100644
index 000000000..2d533a9f2
--- /dev/null
+++ b/packages/website/ts/components/token_balances.tsx
@@ -0,0 +1,699 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {ZeroEx} from '0x.js';
+import DharmaLoanFrame from 'dharma-loan-frame';
+import {colors} from 'material-ui/styles';
+import Dialog from 'material-ui/Dialog';
+import Divider from 'material-ui/Divider';
+import FlatButton from 'material-ui/FlatButton';
+import RaisedButton from 'material-ui/RaisedButton';
+import FloatingActionButton from 'material-ui/FloatingActionButton';
+import ContentAdd from 'material-ui/svg-icons/content/add';
+import ContentRemove from 'material-ui/svg-icons/content/remove';
+import {
+ Table,
+ TableBody,
+ TableHeader,
+ TableRow,
+ TableHeaderColumn,
+ TableRowColumn,
+} from 'material-ui/Table';
+import ReactTooltip = require('react-tooltip');
+import BigNumber from 'bignumber.js';
+import firstBy = require('thenby');
+import QueryString = require('query-string');
+import {Dispatcher} from 'ts/redux/dispatcher';
+import {
+ TokenByAddress,
+ TokenStateByAddress,
+ Token,
+ BlockchainErrs,
+ BalanceErrs,
+ Styles,
+ ScreenWidths,
+ EtherscanLinkSuffixes,
+ BlockchainCallErrs,
+ TokenVisibility,
+} from 'ts/types';
+import {Blockchain} from 'ts/blockchain';
+import {utils} from 'ts/utils/utils';
+import {constants} from 'ts/utils/constants';
+import {configs} from 'ts/utils/configs';
+import {LifeCycleRaisedButton} from 'ts/components/ui/lifecycle_raised_button';
+import {HelpTooltip} from 'ts/components/ui/help_tooltip';
+import {errorReporter} from 'ts/utils/error_reporter';
+import {AllowanceToggle} from 'ts/components/inputs/allowance_toggle';
+import {EthWethConversionButton} from 'ts/components/eth_weth_conversion_button';
+import {SendButton} from 'ts/components/send_button';
+import {AssetPicker} from 'ts/components/generate_order/asset_picker';
+import {TokenIcon} from 'ts/components/ui/token_icon';
+import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage';
+
+const ETHER_ICON_PATH = '/images/ether.png';
+const ETHER_TOKEN_SYMBOL = 'WETH';
+const ZRX_TOKEN_SYMBOL = 'ZRX';
+
+const PRECISION = 5;
+const ICON_DIMENSION = 40;
+const ARTIFICIAL_FAUCET_REQUEST_DELAY = 1000;
+const TOKEN_TABLE_ROW_HEIGHT = 60;
+const MAX_TOKEN_TABLE_HEIGHT = 420;
+const TOKEN_COL_SPAN_LG = 2;
+const TOKEN_COL_SPAN_SM = 1;
+
+const styles: Styles = {
+ bgColor: {
+ backgroundColor: colors.grey50,
+ },
+};
+
+interface TokenBalancesProps {
+ blockchain: Blockchain;
+ blockchainErr: BlockchainErrs;
+ blockchainIsLoaded: boolean;
+ dispatcher: Dispatcher;
+ screenWidth: ScreenWidths;
+ tokenByAddress: TokenByAddress;
+ tokenStateByAddress: TokenStateByAddress;
+ userAddress: string;
+ userEtherBalance: BigNumber;
+ networkId: number;
+}
+
+interface TokenBalancesState {
+ errorType: BalanceErrs;
+ isBalanceSpinnerVisible: boolean;
+ isDharmaDialogVisible: boolean;
+ isZRXSpinnerVisible: boolean;
+ currentZrxBalance?: BigNumber;
+ isTokenPickerOpen: boolean;
+ isAddingToken: boolean;
+}
+
+export class TokenBalances extends React.Component<TokenBalancesProps, TokenBalancesState> {
+ public constructor(props: TokenBalancesProps) {
+ super(props);
+ this.state = {
+ errorType: undefined,
+ isBalanceSpinnerVisible: false,
+ isZRXSpinnerVisible: false,
+ isDharmaDialogVisible: DharmaLoanFrame.isAuthTokenPresent(),
+ isTokenPickerOpen: false,
+ isAddingToken: false,
+ };
+ }
+ public componentWillReceiveProps(nextProps: TokenBalancesProps) {
+ if (nextProps.userEtherBalance !== this.props.userEtherBalance) {
+ if (this.state.isBalanceSpinnerVisible) {
+ const receivedAmount = nextProps.userEtherBalance.minus(this.props.userEtherBalance);
+ this.props.dispatcher.showFlashMessage(`Received ${receivedAmount.toString(10)} Kovan Ether`);
+ }
+ this.setState({
+ isBalanceSpinnerVisible: false,
+ });
+ }
+ const nextZrxToken = _.find(_.values(nextProps.tokenByAddress), t => t.symbol === ZRX_TOKEN_SYMBOL);
+ const nextZrxTokenBalance = nextProps.tokenStateByAddress[nextZrxToken.address].balance;
+ if (!_.isUndefined(this.state.currentZrxBalance) && !nextZrxTokenBalance.eq(this.state.currentZrxBalance)) {
+ if (this.state.isZRXSpinnerVisible) {
+ const receivedAmount = nextZrxTokenBalance.minus(this.state.currentZrxBalance);
+ const receiveAmountInUnits = ZeroEx.toUnitAmount(receivedAmount, 18);
+ this.props.dispatcher.showFlashMessage(`Received ${receiveAmountInUnits.toString(10)} Kovan ZRX`);
+ }
+ this.setState({
+ isZRXSpinnerVisible: false,
+ currentZrxBalance: undefined,
+ });
+ }
+ }
+ public componentDidMount() {
+ window.scrollTo(0, 0);
+ }
+ public render() {
+ const errorDialogActions = [
+ <FlatButton
+ key="errorOkBtn"
+ label="Ok"
+ primary={true}
+ onTouchTap={this.onErrorDialogToggle.bind(this, false)}
+ />,
+ ];
+ const dharmaDialogActions = [
+ <FlatButton
+ key="dharmaCloseBtn"
+ label="Close"
+ primary={true}
+ onTouchTap={this.onDharmaDialogToggle.bind(this, false)}
+ />,
+ ];
+ const isTestNetwork = this.props.networkId === constants.TESTNET_NETWORK_ID;
+ const dharmaButtonColumnStyle = {
+ paddingLeft: 3,
+ display: isTestNetwork ? 'table-cell' : 'none',
+ };
+ const stubColumnStyle = {
+ display: isTestNetwork ? 'none' : 'table-cell',
+ };
+ const allTokenRowHeight = _.size(this.props.tokenByAddress) * TOKEN_TABLE_ROW_HEIGHT;
+ const tokenTableHeight = allTokenRowHeight < MAX_TOKEN_TABLE_HEIGHT ?
+ allTokenRowHeight :
+ MAX_TOKEN_TABLE_HEIGHT;
+ const isSmallScreen = this.props.screenWidth === ScreenWidths.SM;
+ const tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG;
+ const dharmaLoanExplanation = 'If you need access to larger amounts of ether,<br> \
+ you can request a loan from the Dharma Loan<br> \
+ network. Your loan should be funded in 5<br> \
+ minutes or less.';
+ const allowanceExplanation = '0x smart contracts require access to your<br> \
+ token balances in order to execute trades.<br> \
+ Toggling permissions sets an allowance for the<br> \
+ smart contract so you can start trading that token.';
+ return (
+ <div className="lg-px4 md-px4 sm-px1 pb2">
+ <h3>{isTestNetwork ? 'Test ether' : 'Ether'}</h3>
+ <Divider />
+ <div className="pt2 pb2">
+ {isTestNetwork ?
+ 'In order to try out the 0x Portal Dapp, request some test ether to pay for \
+ gas costs. It might take a bit of time for the test ether to show up.' :
+ 'Ether must be converted to Ether Tokens in order to be tradable via 0x. \
+ You can convert between Ether and Ether Tokens by clicking the "convert" button below.'
+ }
+ </div>
+ <Table
+ selectable={false}
+ style={styles.bgColor}
+ >
+ <TableHeader displaySelectAll={false} adjustForCheckbox={false}>
+ <TableRow>
+ <TableHeaderColumn>Currency</TableHeaderColumn>
+ <TableHeaderColumn>Balance</TableHeaderColumn>
+ <TableRowColumn
+ className="sm-hide xs-hide"
+ style={stubColumnStyle}
+ />
+ {
+ isTestNetwork &&
+ <TableHeaderColumn
+ style={{paddingLeft: 3}}
+ >
+ {isSmallScreen ? 'Faucet' : 'Request from faucet'}
+ </TableHeaderColumn>
+ }
+ {
+ isTestNetwork &&
+ <TableHeaderColumn
+ style={dharmaButtonColumnStyle}
+ >
+ {isSmallScreen ? 'Loan' : 'Request Dharma loan'}
+ <HelpTooltip
+ style={{paddingLeft: 4}}
+ explanation={dharmaLoanExplanation}
+ />
+ </TableHeaderColumn>
+ }
+ </TableRow>
+ </TableHeader>
+ <TableBody displayRowCheckbox={false}>
+ <TableRow key="ETH">
+ <TableRowColumn className="py1">
+ <img
+ style={{width: ICON_DIMENSION, height: ICON_DIMENSION}}
+ src={ETHER_ICON_PATH}
+ />
+ </TableRowColumn>
+ <TableRowColumn>
+ {this.props.userEtherBalance.toFixed(PRECISION)} ETH
+ {this.state.isBalanceSpinnerVisible &&
+ <span className="pl1">
+ <i className="zmdi zmdi-spinner zmdi-hc-spin" />
+ </span>
+ }
+ </TableRowColumn>
+ <TableRowColumn
+ className="sm-hide xs-hide"
+ style={stubColumnStyle}
+ />
+ {
+ isTestNetwork &&
+ <TableRowColumn style={{paddingLeft: 3}}>
+ <LifeCycleRaisedButton
+ labelReady="Request"
+ labelLoading="Sending..."
+ labelComplete="Sent!"
+ onClickAsyncFn={this.faucetRequestAsync.bind(this, true)}
+ />
+ </TableRowColumn>
+ }
+ {
+ isTestNetwork &&
+ <TableRowColumn style={dharmaButtonColumnStyle}>
+ <RaisedButton
+ label="Request"
+ style={{width: '100%'}}
+ onTouchTap={this.onDharmaDialogToggle.bind(this)}
+ />
+ </TableRowColumn>
+ }
+ </TableRow>
+ </TableBody>
+ </Table>
+ <div className="clearfix" style={{paddingBottom: 1}}>
+ <div className="col col-10">
+ <h3 className="pt2">
+ {isTestNetwork ? 'Test tokens' : 'Tokens'}
+ </h3>
+ </div>
+ <div className="col col-1 pt3 align-right">
+ <FloatingActionButton
+ mini={true}
+ zDepth={0}
+ onClick={this.onAddTokenClicked.bind(this)}
+ >
+ <ContentAdd />
+ </FloatingActionButton>
+ </div>
+ <div className="col col-1 pt3 align-right">
+ <FloatingActionButton
+ mini={true}
+ zDepth={0}
+ onClick={this.onRemoveTokenClicked.bind(this)}
+ >
+ <ContentRemove />
+ </FloatingActionButton>
+ </div>
+ </div>
+ <Divider />
+ <div className="pt2 pb2">
+ {isTestNetwork ?
+ 'Mint some test tokens you\'d like to use to generate or fill an order using 0x.' :
+ 'Set trading permissions for a token you\'d like to start trading.'
+ }
+ </div>
+ <Table
+ selectable={false}
+ bodyStyle={{height: tokenTableHeight}}
+ style={styles.bgColor}
+ >
+ <TableHeader displaySelectAll={false} adjustForCheckbox={false}>
+ <TableRow>
+ <TableHeaderColumn
+ colSpan={tokenColSpan}
+ >
+ Token
+ </TableHeaderColumn>
+ <TableHeaderColumn style={{paddingLeft: 3}}>Balance</TableHeaderColumn>
+ <TableHeaderColumn>
+ <div className="inline-block">{!isSmallScreen && 'Trade '}Permissions</div>
+ <HelpTooltip
+ style={{paddingLeft: 4}}
+ explanation={allowanceExplanation}
+ />
+ </TableHeaderColumn>
+ <TableHeaderColumn>
+ Action
+ </TableHeaderColumn>
+ {this.props.screenWidth !== ScreenWidths.SM &&
+ <TableHeaderColumn>
+ Send
+ </TableHeaderColumn>
+ }
+ </TableRow>
+ </TableHeader>
+ <TableBody displayRowCheckbox={false}>
+ {this.renderTokenTableRows()}
+ </TableBody>
+ </Table>
+ <Dialog
+ title="Oh oh"
+ titleStyle={{fontWeight: 100}}
+ actions={errorDialogActions}
+ open={!_.isUndefined(this.state.errorType)}
+ onRequestClose={this.onErrorDialogToggle.bind(this, false)}
+ >
+ {this.renderErrorDialogBody()}
+ </Dialog>
+ <Dialog
+ title="Request Dharma Loan"
+ titleStyle={{fontWeight: 100, backgroundColor: 'rgb(250, 250, 250)'}}
+ bodyStyle={{backgroundColor: 'rgb(37, 37, 37)'}}
+ actionsContainerStyle={{backgroundColor: 'rgb(250, 250, 250)'}}
+ autoScrollBodyContent={true}
+ actions={dharmaDialogActions}
+ open={this.state.isDharmaDialogVisible}
+ >
+ {this.renderDharmaLoanFrame()}
+ </Dialog>
+ <AssetPicker
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ blockchain={this.props.blockchain}
+ dispatcher={this.props.dispatcher}
+ isOpen={this.state.isTokenPickerOpen}
+ currentTokenAddress={''}
+ onTokenChosen={this.onAssetTokenPicked.bind(this)}
+ tokenByAddress={this.props.tokenByAddress}
+ tokenVisibility={this.state.isAddingToken ? TokenVisibility.UNTRACKED : TokenVisibility.TRACKED}
+ />
+ </div>
+ );
+ }
+ private renderTokenTableRows() {
+ if (!this.props.blockchainIsLoaded || this.props.blockchainErr !== '') {
+ return '';
+ }
+ const isSmallScreen = this.props.screenWidth === ScreenWidths.SM;
+ const tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG;
+ const actionPaddingX = isSmallScreen ? 2 : 24;
+ const allTokens = _.values(this.props.tokenByAddress);
+ const trackedTokens = _.filter(allTokens, t => t.isTracked);
+ const trackedTokensStartingWithEtherToken = trackedTokens.sort(
+ firstBy((t: Token) => (t.symbol !== ETHER_TOKEN_SYMBOL))
+ .thenBy((t: Token) => (t.symbol !== ZRX_TOKEN_SYMBOL))
+ .thenBy('address'),
+ );
+ const tableRows = _.map(
+ trackedTokensStartingWithEtherToken,
+ this.renderTokenRow.bind(this, tokenColSpan, actionPaddingX),
+ );
+ return tableRows;
+ }
+ private renderTokenRow(tokenColSpan: number, actionPaddingX: number, token: Token) {
+ const tokenState = this.props.tokenStateByAddress[token.address];
+ const tokenLink = utils.getEtherScanLinkIfExists(token.address, this.props.networkId,
+ EtherscanLinkSuffixes.address);
+ const isMintable = _.includes(configs.symbolsOfMintableTokens, token.symbol) &&
+ this.props.networkId !== constants.MAINNET_NETWORK_ID;
+ return (
+ <TableRow key={token.address} style={{height: TOKEN_TABLE_ROW_HEIGHT}}>
+ <TableRowColumn
+ colSpan={tokenColSpan}
+ >
+ {_.isUndefined(tokenLink) ?
+ this.renderTokenName(token) :
+ <a href={tokenLink} target="_blank" style={{textDecoration: 'none'}}>
+ {this.renderTokenName(token)}
+ </a>
+ }
+ </TableRowColumn>
+ <TableRowColumn style={{paddingRight: 3, paddingLeft: 3}}>
+ {this.renderAmount(tokenState.balance, token.decimals)} {token.symbol}
+ {this.state.isZRXSpinnerVisible && token.symbol === ZRX_TOKEN_SYMBOL &&
+ <span className="pl1">
+ <i className="zmdi zmdi-spinner zmdi-hc-spin" />
+ </span>
+ }
+ </TableRowColumn>
+ <TableRowColumn>
+ <AllowanceToggle
+ blockchain={this.props.blockchain}
+ dispatcher={this.props.dispatcher}
+ token={token}
+ tokenState={tokenState}
+ onErrorOccurred={this.onErrorOccurred.bind(this)}
+ userAddress={this.props.userAddress}
+ />
+ </TableRowColumn>
+ <TableRowColumn
+ style={{paddingLeft: actionPaddingX, paddingRight: actionPaddingX}}
+ >
+ {isMintable &&
+ <LifeCycleRaisedButton
+ labelReady="Mint"
+ labelLoading={<span style={{fontSize: 12}}>Minting...</span>}
+ labelComplete="Minted!"
+ onClickAsyncFn={this.onMintTestTokensAsync.bind(this, token)}
+ />
+ }
+ {token.symbol === ETHER_TOKEN_SYMBOL &&
+ <EthWethConversionButton
+ blockchain={this.props.blockchain}
+ dispatcher={this.props.dispatcher}
+ ethToken={this.getWrappedEthToken()}
+ ethTokenState={tokenState}
+ userEtherBalance={this.props.userEtherBalance}
+ onError={this.onEthWethConversionFailed.bind(this)}
+ />
+ }
+ {token.symbol === ZRX_TOKEN_SYMBOL && this.props.networkId === constants.TESTNET_NETWORK_ID &&
+ <LifeCycleRaisedButton
+ labelReady="Request"
+ labelLoading="Sending..."
+ labelComplete="Sent!"
+ onClickAsyncFn={this.faucetRequestAsync.bind(this, false)}
+ />
+ }
+ </TableRowColumn>
+ {this.props.screenWidth !== ScreenWidths.SM &&
+ <TableRowColumn
+ style={{paddingLeft: actionPaddingX, paddingRight: actionPaddingX}}
+ >
+ <SendButton
+ blockchain={this.props.blockchain}
+ dispatcher={this.props.dispatcher}
+ token={token}
+ tokenState={tokenState}
+ onError={this.onSendFailed.bind(this)}
+ />
+ </TableRowColumn>
+ }
+ </TableRow>
+ );
+ }
+ private onAssetTokenPicked(tokenAddress: string) {
+ if (_.isEmpty(tokenAddress)) {
+ this.setState({
+ isTokenPickerOpen: false,
+ });
+ return;
+ }
+ const token = this.props.tokenByAddress[tokenAddress];
+ const isDefaultTrackedToken = _.includes(configs.defaultTrackedTokenSymbols, token.symbol);
+ if (!this.state.isAddingToken && !isDefaultTrackedToken) {
+ if (token.isRegistered) {
+ // Remove the token from tracked tokens
+ const newToken = _.assign({}, token, {
+ isTracked: false,
+ });
+ this.props.dispatcher.updateTokenByAddress([newToken]);
+ } else {
+ this.props.dispatcher.removeTokenToTokenByAddress(token);
+ }
+ this.props.dispatcher.removeFromTokenStateByAddress(tokenAddress);
+ trackedTokenStorage.removeTrackedToken(this.props.userAddress, this.props.networkId, tokenAddress);
+ } else if (isDefaultTrackedToken) {
+ this.props.dispatcher.showFlashMessage(`Cannot remove ${token.name} because it's a default token`);
+ }
+ this.setState({
+ isTokenPickerOpen: false,
+ });
+ }
+ private onEthWethConversionFailed() {
+ this.setState({
+ errorType: BalanceErrs.wethConversionFailed,
+ });
+ }
+ private onSendFailed() {
+ this.setState({
+ errorType: BalanceErrs.sendFailed,
+ });
+ }
+ private renderAmount(amount: BigNumber, decimals: number) {
+ const unitAmount = ZeroEx.toUnitAmount(amount, decimals);
+ return unitAmount.toNumber().toFixed(PRECISION);
+ }
+ private renderTokenName(token: Token) {
+ const tooltipId = `tooltip-${token.address}`;
+ return (
+ <div className="flex">
+ <TokenIcon token={token} diameter={ICON_DIMENSION} />
+ <div
+ data-tip={true}
+ data-for={tooltipId}
+ className="mt2 ml2 sm-hide xs-hide"
+ >
+ {token.name}
+ </div>
+ <ReactTooltip id={tooltipId}>{token.address}</ReactTooltip>
+ </div>
+ );
+ }
+ private renderErrorDialogBody() {
+ switch (this.state.errorType) {
+ case BalanceErrs.incorrectNetworkForFaucet:
+ return (
+ <div>
+ Our faucet can only send test Ether to addresses on the {constants.TESTNET_NAME}
+ {' '}testnet (networkId {constants.TESTNET_NETWORK_ID}). Please make sure you are
+ {' '}connected to the {constants.TESTNET_NAME} testnet and try requesting ether again.
+ </div>
+ );
+
+ case BalanceErrs.faucetRequestFailed:
+ return (
+ <div>
+ An unexpected error occurred while trying to request test Ether from our faucet.
+ {' '}Please refresh the page and try again.
+ </div>
+ );
+
+ case BalanceErrs.faucetQueueIsFull:
+ return (
+ <div>
+ Our test Ether faucet queue is full. Please try requesting test Ether again later.
+ </div>
+ );
+
+ case BalanceErrs.mintingFailed:
+ return (
+ <div>
+ Minting your test tokens failed unexpectedly. Please refresh the page and try again.
+ </div>
+ );
+
+ case BalanceErrs.wethConversionFailed:
+ return (
+ <div>
+ Converting between Ether and Ether Tokens failed unexpectedly.
+ Please refresh the page and try again.
+ </div>
+ );
+
+ case BalanceErrs.allowanceSettingFailed:
+ return (
+ <div>
+ An unexpected error occurred while trying to set your test token allowance.
+ {' '}Please refresh the page and try again.
+ </div>
+ );
+
+ case undefined:
+ return null; // No error to show
+
+ default:
+ throw utils.spawnSwitchErr('errorType', this.state.errorType);
+ }
+ }
+ private renderDharmaLoanFrame() {
+ if (utils.isUserOnMobile()) {
+ return (
+ <h4 style={{ textAlign: 'center' }}>
+ We apologize -- Dharma loan requests are not available on
+ mobile yet. Please try again through your desktop browser.
+ </h4>
+ );
+ } else {
+ return (
+ <DharmaLoanFrame
+ partner="0x"
+ env={utils.getCurrentEnvironment()}
+ screenWidth={this.props.screenWidth}
+ />
+ );
+ }
+ }
+ private onErrorOccurred(errorType: BalanceErrs) {
+ this.setState({
+ errorType,
+ });
+ }
+ private async onMintTestTokensAsync(token: Token): Promise<boolean> {
+ try {
+ await this.props.blockchain.mintTestTokensAsync(token);
+ const amount = ZeroEx.toUnitAmount(constants.MINT_AMOUNT, token.decimals);
+ this.props.dispatcher.showFlashMessage(`Successfully minted ${amount.toString(10)} ${token.symbol}`);
+ return true;
+ } catch (err) {
+ const errMsg = '' + err;
+ if (_.includes(errMsg, BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES)) {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ return false;
+ }
+ if (_.includes(errMsg, 'User denied transaction')) {
+ return false;
+ }
+ utils.consoleLog(`Unexpected error encountered: ${err}`);
+ utils.consoleLog(err.stack);
+ await errorReporter.reportAsync(err);
+ this.setState({
+ errorType: BalanceErrs.mintingFailed,
+ });
+ return false;
+ }
+ }
+ private async faucetRequestAsync(isEtherRequest: boolean): Promise<boolean> {
+ if (this.props.userAddress === '') {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ return false;
+ }
+
+ // If on another network other then the testnet our faucet serves test ether
+ // from, we must show user an error message
+ if (this.props.blockchain.networkId !== constants.TESTNET_NETWORK_ID) {
+ this.setState({
+ errorType: BalanceErrs.incorrectNetworkForFaucet,
+ });
+ return false;
+ }
+
+ await utils.sleepAsync(ARTIFICIAL_FAUCET_REQUEST_DELAY);
+
+ const segment = isEtherRequest ? 'ether' : 'zrx';
+ const response = await fetch(`${constants.ETHER_FAUCET_ENDPOINT}/${segment}/${this.props.userAddress}`);
+ const responseBody = await response.text();
+ if (response.status !== constants.SUCCESS_STATUS) {
+ utils.consoleLog(`Unexpected status code: ${response.status} -> ${responseBody}`);
+ await errorReporter.reportAsync(new Error(`Faucet returned non-200: ${JSON.stringify(response)}`));
+ const errorType = response.status === constants.UNAVAILABLE_STATUS ?
+ BalanceErrs.faucetQueueIsFull :
+ BalanceErrs.faucetRequestFailed;
+ this.setState({
+ errorType,
+ });
+ return false;
+ }
+
+ if (isEtherRequest) {
+ this.setState({
+ isBalanceSpinnerVisible: true,
+ });
+ } else {
+ const tokens = _.values(this.props.tokenByAddress);
+ const zrxToken = _.find(tokens, t => t.symbol === ZRX_TOKEN_SYMBOL);
+ const zrxTokenState = this.props.tokenStateByAddress[zrxToken.address];
+ this.setState({
+ isZRXSpinnerVisible: true,
+ currentZrxBalance: zrxTokenState.balance,
+ });
+ this.props.blockchain.pollTokenBalanceAsync(zrxToken);
+ }
+ return true;
+ }
+ private onErrorDialogToggle(isOpen: boolean) {
+ this.setState({
+ errorType: undefined,
+ });
+ }
+ private onDharmaDialogToggle() {
+ this.setState({
+ isDharmaDialogVisible: !this.state.isDharmaDialogVisible,
+ });
+ }
+ private getWrappedEthToken() {
+ const tokens = _.values(this.props.tokenByAddress);
+ const wrappedEthToken = _.find(tokens, {symbol: ETHER_TOKEN_SYMBOL});
+ return wrappedEthToken;
+ }
+ private onAddTokenClicked() {
+ this.setState({
+ isTokenPickerOpen: true,
+ isAddingToken: true,
+ });
+ }
+ private onRemoveTokenClicked() {
+ this.setState({
+ isTokenPickerOpen: true,
+ isAddingToken: false,
+ });
+ }
+}