diff options
Diffstat (limited to 'packages')
9 files changed, 193 insertions, 51 deletions
diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index d3bf6dda4..481917934 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -158,6 +158,14 @@ export class Blockchain { } public async isAddressInTokenRegistryAsync(tokenAddress: string): Promise<boolean> { utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); + // HACK: temporarily whitelist the new WETH token address `as if` they were + // already in the tokenRegistry. + // TODO: Remove this hack once we've updated the TokenRegistries + // Airtable task: https://airtable.com/tblFe0Q9JuKJPYbTn/viwsOG2Y97qdIeCIO/recv3VGmIorFzHBVz + if (configs.SHOULD_DEPRECATE_OLD_WETH_TOKEN && + tokenAddress === configs.NEW_WRAPPED_ETHERS[this.networkId]) { + return true; + } const tokenIfExists = await this.zeroEx.tokenRegistry.getTokenIfExistsAsync(tokenAddress); return !_.isUndefined(tokenIfExists); } @@ -597,13 +605,13 @@ export class Blockchain { // we deploy the new WETH page, everyone will re-fill their trackedTokens with the // new canonical WETH. // TODO: Remove this hack once we've updated the TokenRegistries + // Airtable task: https://airtable.com/tblFe0Q9JuKJPYbTn/viwsOG2Y97qdIeCIO/recv3VGmIorFzHBVz let address = t.address; - if (t.symbol === 'WETH') { - if (this.networkId === 1) { - address = '0xe495bcacaf29a0eb00fb67b86e9cd2a994dd55d8'; - } else if (this.networkId === 42) { - address = '0x739e78d6bebbdf24105a5145fa04436589d1cbd9'; - } + if (configs.SHOULD_DEPRECATE_OLD_WETH_TOKEN && t.symbol === 'WETH') { + const newEtherTokenAddressIfExists = configs.NEW_WRAPPED_ETHERS[this.networkId]; + if (!_.isUndefined(newEtherTokenAddressIfExists)) { + address = newEtherTokenAddressIfExists; + } } const token: Token = { iconUrl, diff --git a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx index 44c23e4b0..25ded40e9 100644 --- a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx +++ b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx @@ -108,7 +108,16 @@ export class EthWethConversionDialog extends className="pt1" style={{fontSize: 12}} > - 1 ETH = 1 WETH + <div className="left">1 ETH = 1 WETH</div> + {this.props.direction === Side.receive && + <div + className="right" + onClick={this.onMaxClick.bind(this)} + style={{color: DARK_BLUE, textDecoration: 'underline', cursor: 'pointer'}} + > + Max + </div> + } </div> </div> </div> @@ -136,6 +145,11 @@ export class EthWethConversionDialog extends </div> ); } + private onMaxClick() { + this.setState({ + value: this.props.tokenState.balance, + }); + } private onValueChange(isValid: boolean, amount?: BigNumber) { this.setState({ value: amount, diff --git a/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx b/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx index 2f824ab44..ffe55794f 100644 --- a/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx +++ b/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx @@ -17,11 +17,11 @@ export function PortalDisclaimerDialog(props: PortalDisclaimerDialogProps) { <FlatButton key="portalAgree" label="I Agree" - onTouchTap={props.onToggleDialog.bind(this)} + onTouchTap={props.onToggleDialog} />, ]} open={props.isOpen} - onRequestClose={props.onToggleDialog.bind(this)} + onRequestClose={props.onToggleDialog} autoScrollBodyContent={true} modal={true} > diff --git a/packages/website/ts/components/dialogs/wrapped_eth_section_notice_dialog.tsx b/packages/website/ts/components/dialogs/wrapped_eth_section_notice_dialog.tsx new file mode 100644 index 000000000..3f485ce4f --- /dev/null +++ b/packages/website/ts/components/dialogs/wrapped_eth_section_notice_dialog.tsx @@ -0,0 +1,38 @@ +import Dialog from 'material-ui/Dialog'; +import FlatButton from 'material-ui/FlatButton'; +import {colors} from 'material-ui/styles'; +import * as React from 'react'; + +interface WrappedEthSectionNoticeDialogProps { + isOpen: boolean; + onToggleDialog: () => void; +} + +export function WrappedEthSectionNoticeDialog(props: WrappedEthSectionNoticeDialogProps) { + return ( + <Dialog + title="Dedicated Wrapped Ether Section" + titleStyle={{fontWeight: 100}} + actions={[ + <FlatButton + key="acknowledgeWrapEthSection" + label="Sounds good" + onTouchTap={props.onToggleDialog} + />, + ]} + open={props.isOpen} + onRequestClose={props.onToggleDialog} + autoScrollBodyContent={true} + modal={true} + > + <div className="pt2" style={{color: colors.grey700}}> + <div> + We have recently updated the Wrapped Ether token (WETH) used by 0x Portal. + Don't worry, unwrapping Ether tied to the old Wrapped Ether token can + be done at any time by clicking on the "Wrap ETH" section in the menu + to the left. + </div> + </div> + </Dialog> + ); +} diff --git a/packages/website/ts/components/eth_wrappers.tsx b/packages/website/ts/components/eth_wrappers.tsx index 59353d18c..da95aa6fd 100644 --- a/packages/website/ts/components/eth_wrappers.tsx +++ b/packages/website/ts/components/eth_wrappers.tsx @@ -12,10 +12,12 @@ import { } from 'material-ui/Table'; import * as moment from 'moment'; import * as React from 'react'; +import ReactTooltip = require('react-tooltip'); import {Blockchain} from 'ts/blockchain'; import {EthWethConversionButton} from 'ts/components/eth_weth_conversion_button'; import {Dispatcher} from 'ts/redux/dispatcher'; import { + EtherscanLinkSuffixes, OutdatedWrappedEtherByNetworkId, Side, Token, @@ -26,6 +28,7 @@ import { import {colors} from 'ts/utils/colors'; import {configs} from 'ts/utils/configs'; import {constants} from 'ts/utils/constants'; +import {utils} from 'ts/utils/utils'; const PRECISION = 5; const DATE_FORMAT = 'D/M/YY'; @@ -84,6 +87,10 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt const etherTokenState = this.props.tokenStateByAddress[etherToken.address]; const wethBalance = ZeroEx.toUnitAmount(etherTokenState.balance, constants.DECIMAL_PLACES_ETH); const isBidirectional = true; + const etherscanUrl = utils.getEtherScanLinkIfExists( + etherToken.address, this.props.networkId, EtherscanLinkSuffixes.address, + ); + const tokenLabel = this.renderToken('Wrapped Ether', etherToken.address, constants.iconUrlBySymbol.WETH); return ( <div className="clearfix lg-px4 md-px4 sm-px2" style={{minHeight: 600}}> <div className="relative"> @@ -91,7 +98,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt <div className="absolute" style={{top: 0, right: 0}}> <a target="_blank" - href="https://weth.io/" + href={constants.WETH_IO_URL} style={{color: colors.grey}} > <div className="flex"> @@ -130,8 +137,11 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt style={{width: ICON_DIMENSION, height: ICON_DIMENSION}} src={ETHER_ICON_PATH} /> - <div className="mt2 ml2 sm-hide xs-hide"> - Ether + <div + className="ml2 sm-hide xs-hide" + style={{marginTop: 12}} + > + ETH </div> </div> </TableRowColumn> @@ -152,15 +162,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt </TableRow> <TableRow key="WETH"> <TableRowColumn className="py1"> - <div className="flex"> - <img - style={{width: ICON_DIMENSION, height: ICON_DIMENSION}} - src={configs.ICON_URL_BY_SYMBOL.WETH} - /> - <div className="mt2 ml2 sm-hide xs-hide"> - Wrapped Ether - </div> - </div> + {this.renderTokenLink(tokenLabel, etherscanUrl)} </TableRowColumn> <TableRowColumn> {wethBalance.toFixed(PRECISION)} WETH @@ -243,8 +245,11 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt private renderOutdatedWeths(etherToken: Token, etherTokenState: TokenState) { const rows = _.map(configs.OUTDATED_WRAPPED_ETHERS, (outdatedWETHByNetworkId: OutdatedWrappedEtherByNetworkId) => { - const outdatedWETH = outdatedWETHByNetworkId[this.props.networkId]; - const timestampMsRange = outdatedWETH.timestampMsRange; + const outdatedWETHIfExists = outdatedWETHByNetworkId[this.props.networkId]; + if (_.isUndefined(outdatedWETHIfExists)) { + return null; // noop + } + const timestampMsRange = outdatedWETHIfExists.timestampMsRange; let dateRange: string; if (!_.isUndefined(timestampMsRange)) { const startMoment = moment(timestampMsRange.startTimestampMs); @@ -255,28 +260,26 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt } const outdatedEtherToken = { ...etherToken, - address: outdatedWETH.address, + address: outdatedWETHIfExists.address, }; - const isStateLoaded = this.state.outdatedWETHAddressToIsStateLoaded[outdatedWETH.address]; - const outdatedEtherTokenState = this.state.outdatedWETHStateByAddress[outdatedWETH.address]; + const isStateLoaded = this.state.outdatedWETHAddressToIsStateLoaded[outdatedWETHIfExists.address]; + const outdatedEtherTokenState = this.state.outdatedWETHStateByAddress[outdatedWETHIfExists.address]; const balanceInEthIfExists = isStateLoaded ? ZeroEx.toUnitAmount( outdatedEtherTokenState.balance, constants.DECIMAL_PLACES_ETH, ).toFixed(PRECISION) : undefined; - const onConversionSuccessful = this.onOutdatedConversionSuccessfulAsync.bind(this, outdatedWETH.address); + const onConversionSuccessful = this.onOutdatedConversionSuccessfulAsync.bind( + this, outdatedWETHIfExists.address, + ); + const etherscanUrl = utils.getEtherScanLinkIfExists( + outdatedWETHIfExists.address, this.props.networkId, EtherscanLinkSuffixes.address, + ); + const tokenLabel = this.renderToken(dateRange, outdatedEtherToken.address, OUTDATED_WETH_ICON_PATH); return ( - <TableRow key={`weth-${outdatedWETH.address}`}> + <TableRow key={`weth-${outdatedWETHIfExists.address}`}> <TableRowColumn className="py1"> - <div className="flex"> - <img - style={{width: ICON_DIMENSION, height: ICON_DIMENSION}} - src={OUTDATED_WETH_ICON_PATH} - /> - <div className="mt2 ml2 sm-hide xs-hide"> - {dateRange} - </div> - </div> + {this.renderTokenLink(tokenLabel, etherscanUrl)} </TableRowColumn> <TableRowColumn> {isStateLoaded ? @@ -302,6 +305,41 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt }); return rows; } + private renderTokenLink(tokenLabel: React.ReactNode, etherscanUrl: string) { + return ( + <span> + {_.isUndefined(etherscanUrl) ? + tokenLabel : + <a href={etherscanUrl} target="_blank" style={{textDecoration: 'none'}}> + {tokenLabel} + </a> + } + </span> + ); + } + private renderToken(name: string, address: string, imgPath: string) { + const tooltipId = `tooltip-${address}`; + return ( + <div className="flex"> + <img + style={{width: ICON_DIMENSION, height: ICON_DIMENSION}} + src={imgPath} + /> + <div + className="ml2 sm-hide xs-hide" + style={{marginTop: 12}} + > + <span + data-tip={true} + data-for={tooltipId} + > + {name} + </span> + <ReactTooltip id={tooltipId}>{address}</ReactTooltip> + </div> + </div> + ); + } private async onOutdatedConversionSuccessfulAsync(outdatedWETHAddress: string) { this.setState({ outdatedWETHAddressToIsStateLoaded: { @@ -346,9 +384,15 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt }); } private getOutdatedWETHAddresses(): string[] { - const outdatedWETHAddresses = _.map(configs.OUTDATED_WRAPPED_ETHERS, outdatedWrappedEther => { - return outdatedWrappedEther[this.props.networkId].address; - }); + const outdatedWETHAddresses = _.compact(_.map(configs.OUTDATED_WRAPPED_ETHERS, + outdatedWrappedEtherByNetwork => { + const outdatedWrappedEtherIfExists = outdatedWrappedEtherByNetwork[this.props.networkId]; + if (_.isUndefined(outdatedWrappedEtherIfExists)) { + return undefined; + } + const address = outdatedWrappedEtherIfExists.address; + return address; + })); return outdatedWETHAddresses; } } // tslint:disable:max-file-line-count diff --git a/packages/website/ts/components/portal.tsx b/packages/website/ts/components/portal.tsx index 2f6f9912a..08028d15e 100644 --- a/packages/website/ts/components/portal.tsx +++ b/packages/website/ts/components/portal.tsx @@ -7,6 +7,7 @@ import {Route, Switch} from 'react-router-dom'; import {Blockchain} from 'ts/blockchain'; import {BlockchainErrDialog} from 'ts/components/dialogs/blockchain_err_dialog'; import {PortalDisclaimerDialog} from 'ts/components/dialogs/portal_disclaimer_dialog'; +import {WrappedEthSectionNoticeDialog} from 'ts/components/dialogs/wrapped_eth_section_notice_dialog'; import {EthWrappers} from 'ts/components/eth_wrappers'; import {FillOrder} from 'ts/components/fill_order'; import {Footer} from 'ts/components/footer'; @@ -63,22 +64,39 @@ interface PortalAllState { prevNetworkId: number; prevNodeVersion: string; prevUserAddress: string; - hasAcceptedDisclaimer: boolean; + prevPathname: string; + isDisclaimerDialogOpen: boolean; + isWethNoticeDialogOpen: boolean; } export class Portal extends React.Component<PortalAllProps, PortalAllState> { private blockchain: Blockchain; private sharedOrderIfExists: Order; private throttledScreenWidthUpdate: () => void; + public static hasAlreadyDismissedWethNotice() { + const didDismissWethNotice = localStorage.getItemIfExists(constants.LOCAL_STORAGE_KEY_DISMISS_WETH_NOTICE); + const hasAlreadyDismissedWethNotice = !_.isUndefined(didDismissWethNotice) && + !_.isEmpty(didDismissWethNotice); + return hasAlreadyDismissedWethNotice; + } constructor(props: PortalAllProps) { super(props); this.sharedOrderIfExists = this.getSharedOrderIfExists(); this.throttledScreenWidthUpdate = _.throttle(this.updateScreenWidth.bind(this), THROTTLE_TIMEOUT); + + const isViewingBalances = _.includes(props.location.pathname, `${WebsitePaths.Portal}/balances`); + const hasAlreadyDismissedWethNotice = Portal.hasAlreadyDismissedWethNotice(); + + const didAcceptPortalDisclaimer = localStorage.getItemIfExists(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER); + const hasAcceptedDisclaimer = !_.isUndefined(didAcceptPortalDisclaimer) && + !_.isEmpty(didAcceptPortalDisclaimer); this.state = { prevNetworkId: this.props.networkId, prevNodeVersion: this.props.nodeVersion, prevUserAddress: this.props.userAddress, - hasAcceptedDisclaimer: false, + prevPathname: this.props.location.pathname, + isDisclaimerDialogOpen: !hasAcceptedDisclaimer, + isWethNoticeDialogOpen: !hasAlreadyDismissedWethNotice && isViewingBalances, }; } public componentDidMount() { @@ -87,12 +105,6 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { } public componentWillMount() { this.blockchain = new Blockchain(this.props.dispatcher); - const didAcceptPortalDisclaimer = localStorage.getItemIfExists(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER); - const hasAcceptedDisclaimer = !_.isUndefined(didAcceptPortalDisclaimer) && - !_.isEmpty(didAcceptPortalDisclaimer); - this.setState({ - hasAcceptedDisclaimer, - }); } public componentWillUnmount() { this.blockchain.destroy(); @@ -128,6 +140,14 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { // tslint:disable-next-line:no-floating-promises this.blockchain.nodeVersionUpdatedFireAndForgetAsync(nextProps.nodeVersion); } + if (nextProps.location.pathname !== this.state.prevPathname) { + const isViewingBalances = _.includes(nextProps.location.pathname, `${WebsitePaths.Portal}/balances`); + const hasAlreadyDismissedWethNotice = Portal.hasAlreadyDismissedWethNotice(); + this.setState({ + prevPathname: nextProps.location.pathname, + isWethNoticeDialogOpen: !hasAlreadyDismissedWethNotice && isViewingBalances, + }); + } } public render() { const updateShouldBlockchainErrDialogBeOpen = this.props.dispatcher @@ -220,8 +240,12 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { toggleDialogFn={updateShouldBlockchainErrDialogBeOpen} networkId={this.props.networkId} /> + <WrappedEthSectionNoticeDialog + isOpen={this.state.isWethNoticeDialogOpen} + onToggleDialog={this.onWethNoticeAccepted.bind(this)} + /> <PortalDisclaimerDialog - isOpen={!this.state.hasAcceptedDisclaimer} + isOpen={this.state.isDisclaimerDialogOpen} onToggleDialog={this.onPortalDisclaimerAccepted.bind(this)} /> <FlashMessage @@ -302,7 +326,13 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { private onPortalDisclaimerAccepted() { localStorage.setItem(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER, 'set'); this.setState({ - hasAcceptedDisclaimer: true, + isDisclaimerDialogOpen: false, + }); + } + private onWethNoticeAccepted() { + localStorage.setItem(constants.DISMISS_WETH_NOTICE_LOCAL_STORAGE_KEY, 'set'); + this.setState({ + isWethNoticeDialogOpen: false, }); } private getSharedOrderIfExists(): Order { diff --git a/packages/website/ts/components/send_button.tsx b/packages/website/ts/components/send_button.tsx index 1fc300964..23e61e77e 100644 --- a/packages/website/ts/components/send_button.tsx +++ b/packages/website/ts/components/send_button.tsx @@ -76,8 +76,8 @@ export class SendButton extends React.Component<SendButtonProps, SendButtonState } else if (!_.includes(errMsg, 'User denied transaction')) { utils.consoleLog(`Unexpected error encountered: ${err}`); utils.consoleLog(err.stack); - await errorReporter.reportAsync(err); this.props.onError(); + await errorReporter.reportAsync(err); } } this.setState({ diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts index 8921c68a4..b66348416 100644 --- a/packages/website/ts/utils/configs.ts +++ b/packages/website/ts/utils/configs.ts @@ -96,6 +96,12 @@ export const configs = { IS_MAINNET_ENABLED: true, LAST_LOCAL_STORAGE_FILL_CLEARANCE_DATE: '2017-11-22', LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE: '2017-12-13', + // NEW_WRAPPED_ETHERS is temporary until we remove the SHOULD_DEPRECATE_OLD_WETH_TOKEN flag + // and add the new WETHs to the tokenRegistry + NEW_WRAPPED_ETHERS: { + 1: '0xe495bcacaf29a0eb00fb67b86e9cd2a994dd55d8', + 42: '0x739e78d6bebbdf24105a5145fa04436589d1cbd9', + } as {[networkId: string]: string}, OUTDATED_WRAPPED_ETHERS: [ { 42: { @@ -123,5 +129,6 @@ export const configs = { `https://kovan.infura.io/${INFURA_API_KEY}`, ], } as PublicNodeUrlsByNetworkId, + SHOULD_DEPRECATE_OLD_WETH_TOKEN: true, SYMBOLS_OF_MINTABLE_TOKENS: ['MKR', 'MLN', 'GNT', 'DGD', 'REP'], }; diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts index 0e4a1056a..e6b51c452 100644 --- a/packages/website/ts/utils/constants.ts +++ b/packages/website/ts/utils/constants.ts @@ -16,6 +16,7 @@ export const constants = { HOME_SCROLL_DURATION_MS: 500, HTTP_NO_CONTENT_STATUS_CODE: 204, LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER: 'didAcceptPortalDisclaimer', + LOCAL_STORAGE_KEY_DISMISS_WETH_NOTICE: 'hasDismissedWethNotice', MAKER_FEE: new BigNumber(0), MAINNET_NAME: 'Main network', MINT_AMOUNT: new BigNumber('100000000000000000000'), |