Merge pull request #283 from 0xProject/createWethPage

Merge WETH page improvements into development
This commit is contained in:
Fabio Berger
2017-12-19 23:36:37 +01:00
committed by GitHub
11 changed files with 195 additions and 59 deletions

View File

@@ -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,

View File

@@ -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: colors.darkBlue, 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,

View File

@@ -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}
>

View File

@@ -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>
);
}

View File

@@ -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, configs.ICON_URL_BY_SYMBOL.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.URL_WETH_IO}
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

View File

@@ -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.LOCAL_STORAGE_KEY_DISMISS_WETH_NOTICE, 'set');
this.setState({
isWethNoticeDialogOpen: false,
});
}
private getSharedOrderIfExists(): Order {

View File

@@ -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({

View File

@@ -29,7 +29,6 @@ const docsInfoConfig: DocsInfoConfig = {
Sections.Exchange,
Sections.TokenRegistry,
Sections.ZRXToken,
Sections.EtherToken,
Sections.TokenTransferProxy,
],
},
@@ -42,13 +41,11 @@ const docsInfoConfig: DocsInfoConfig = {
TokenTransferProxy: Sections.TokenTransferProxy,
TokenRegistry: Sections.TokenRegistry,
ZRXToken: Sections.ZRXToken,
EtherToken: Sections.EtherToken,
},
visibleConstructors: [
Sections.Exchange,
Sections.TokenRegistry,
Sections.ZRXToken,
Sections.EtherToken,
Sections.TokenTransferProxy,
],
convertToDocAgnosticFormatFn: doxityUtils.convertToDocAgnosticFormat.bind(doxityUtils),

View File

@@ -669,7 +669,6 @@ export enum SmartContractDocSections {
TokenTransferProxy = 'TokenTransferProxy',
TokenRegistry = 'TokenRegistry',
ZRXToken = 'ZRXToken',
EtherToken = 'EtherToken',
}
// tslint:disable:max-file-line-count

View File

@@ -26,21 +26,18 @@ export const configs = {
[SmartContractDocSections.Exchange]: '0x12459c951127e0c374ff9105dda097662a027093',
[SmartContractDocSections.TokenTransferProxy]: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4',
[SmartContractDocSections.ZRXToken]: '0xe41d2489571d322189246dafa5ebde1f4699f498',
[SmartContractDocSections.EtherToken]: '0x2956356cd2a2bf3202f771f50d3d14a367b48070',
[SmartContractDocSections.TokenRegistry]: '0x926a74c5c36adf004c87399e65f75628b0f98d2c',
},
[Networks.ropsten]: {
[SmartContractDocSections.Exchange]: '0x479cc461fecd078f766ecc58533d6f69580cf3ac',
[SmartContractDocSections.TokenTransferProxy]: '0x4e9aad8184de8833365fea970cd9149372fdf1e6',
[SmartContractDocSections.ZRXToken]: '0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d',
[SmartContractDocSections.EtherToken]: '0xc00fd9820cd2898cc4c054b7bf142de637ad129a',
[SmartContractDocSections.TokenRegistry]: '0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed',
},
[Networks.kovan]: {
[SmartContractDocSections.Exchange]: '0x90fe2af704b34e0224bf2299c838e04d4dcf1364',
[SmartContractDocSections.TokenTransferProxy]: '0x087Eed4Bc1ee3DE49BeFbd66C662B434B15d49d4',
[SmartContractDocSections.ZRXToken]: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570',
[SmartContractDocSections.EtherToken]: '0x05d090b51c40b020eab3bfcb6a2dff130df22e9c',
[SmartContractDocSections.TokenRegistry]: '0xf18e504561f4347bea557f3d4558f559dddbae7f',
},
},
@@ -95,7 +92,13 @@ export const configs = {
} as {[symbol: string]: string},
IS_MAINNET_ENABLED: true,
LAST_LOCAL_STORAGE_FILL_CLEARANCE_DATE: '2017-11-22',
LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE: '2017-12-13',
LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE: '2017-12-19',
// 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: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
42: '0xd0a1e359811322d97991e03f863a0c30c2cf029c',
} as {[networkId: string]: string},
OUTDATED_WRAPPED_ETHERS: [
{
42: {
@@ -123,5 +126,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'],
};

View File

@@ -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'),
@@ -81,5 +82,6 @@ export const constants = {
'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L123',
URL_WEB3_LOG_ENTRY_EVENT: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L127',
URL_WEB3_PROVIDER_DOCS: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L150',
URL_WETH_IO: 'https://weth.io/',
URL_ZEROEX_CHAT: 'https://chat.0xproject.com',
};