import { colors, EtherscanLinkSuffixes, utils as sharedUtils } from '@0x/react-shared'; import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as _ from 'lodash'; import Divider from 'material-ui/Divider'; import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn } from 'material-ui/Table'; import * as moment from 'moment'; import * as React from 'react'; import ReactTooltip from 'react-tooltip'; import { Blockchain } from 'ts/blockchain'; import { EthWethConversionButton } from 'ts/components/eth_weth_conversion_button'; import { Dispatcher } from 'ts/redux/dispatcher'; import { OutdatedWrappedEtherByNetworkId, Side, Token, TokenByAddress, TokenState, TokenStateByAddress, } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; const DATE_FORMAT = 'D/M/YY'; const ICON_DIMENSION = 40; const ETHER_ICON_PATH = '/images/ether.png'; const OUTDATED_WETH_ICON_PATH = '/images/wrapped_eth_gray.png'; interface EthWrappersProps { networkId: number; blockchain: Blockchain; dispatcher: Dispatcher; tokenByAddress: TokenByAddress; userAddress: string; userEtherBalanceInWei?: BigNumber; lastForceTokenStateRefetch: number; isFullWidth?: boolean; } interface EthWrappersState { ethTokenState: TokenState; outdatedWETHStateByAddress: TokenStateByAddress; } export class EthWrappers extends React.Component { public static defaultProps: Partial = { isFullWidth: false, }; private _isUnmounted: boolean; constructor(props: EthWrappersProps) { super(props); this._isUnmounted = false; const outdatedWETHAddresses = this._getOutdatedWETHAddresses(); const outdatedWETHStateByAddress: TokenStateByAddress = {}; _.each(outdatedWETHAddresses, outdatedWETHAddress => { outdatedWETHStateByAddress[outdatedWETHAddress] = { balance: new BigNumber(0), allowance: new BigNumber(0), isLoaded: false, }; }); this.state = { outdatedWETHStateByAddress, ethTokenState: { balance: new BigNumber(0), allowance: new BigNumber(0), isLoaded: false, }, }; } public componentWillReceiveProps(nextProps: EthWrappersProps): void { if ( nextProps.userAddress !== this.props.userAddress || nextProps.networkId !== this.props.networkId || nextProps.lastForceTokenStateRefetch !== this.props.lastForceTokenStateRefetch ) { // tslint:disable-next-line:no-floating-promises this._fetchWETHStateAsync(); } } public componentDidMount(): void { window.scrollTo(0, 0); // tslint:disable-next-line:no-floating-promises this._fetchWETHStateAsync(); } public componentWillUnmount(): void { this._isUnmounted = true; } public render(): React.ReactNode { const etherToken = this._getEthToken(); const wethBalance = Web3Wrapper.toUnitAmount(this.state.ethTokenState.balance, constants.DECIMAL_PLACES_ETH); const isBidirectional = true; const etherscanUrl = sharedUtils.getEtherScanLinkIfExists( etherToken.address, this.props.networkId, EtherscanLinkSuffixes.Address, ); const tokenLabel = this._renderToken( 'Wrapped Ether', etherToken.address, utils.getTokenIconUrl(etherToken.symbol), ); const userEtherBalanceInEth = !_.isUndefined(this.props.userEtherBalanceInWei) ? Web3Wrapper.toUnitAmount(this.props.userEtherBalanceInWei, constants.DECIMAL_PLACES_ETH) : undefined; const rootClassName = this.props.isFullWidth ? 'clearfix' : 'clearfix lg-px4 md-px4 sm-px2'; return (

ETH Wrapper

About Wrapped ETH
Wrap ETH into an ERC20-compliant Ether token. 1 ETH = 1 WETH.
ETH Token Balance {this._renderActionColumnTitle(isBidirectional)}
ETH
{!_.isUndefined(userEtherBalanceInEth) ? ( `${userEtherBalanceInEth.toFixed(configs.AMOUNT_DISPLAY_PRECSION)} ETH` ) : ( )}
{this._renderTokenLink(tokenLabel, etherscanUrl)} {this.state.ethTokenState.isLoaded ? ( `${wethBalance.toFixed(configs.AMOUNT_DISPLAY_PRECSION)} WETH` ) : ( )}

Outdated WETH

The{' '} canonical WETH {' '} contract is updated when necessary. Unwrap outdated WETH in order to
 retrieve your ETH and move it to the updated WETH token.
WETH Version Balance {this._renderActionColumnTitle(!isBidirectional)} {this._renderOutdatedWeths(etherToken)}
); } private _renderActionColumnTitle(isBidirectional: boolean): React.ReactNode { let iconClass = 'zmdi-long-arrow-right'; let leftSymbol = 'WETH'; let rightSymbol = 'ETH'; if (isBidirectional) { iconClass = 'zmdi-swap'; leftSymbol = 'ETH'; rightSymbol = 'WETH'; } return (
{leftSymbol}
{rightSymbol}
); } private _renderOutdatedWeths(etherToken: Token): React.ReactNode { const rows = _.map( configs.OUTDATED_WRAPPED_ETHERS, (outdatedWETHByNetworkId: OutdatedWrappedEtherByNetworkId) => { 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); const endMoment = moment(timestampMsRange.endTimestampMs); dateRange = `${startMoment.format(DATE_FORMAT)}-${endMoment.format(DATE_FORMAT)}`; } else { dateRange = '-'; } const outdatedEtherToken = { ...etherToken, address: outdatedWETHIfExists.address, }; const outdatedEtherTokenState = this.state.outdatedWETHStateByAddress[outdatedWETHIfExists.address]; const isStateLoaded = outdatedEtherTokenState.isLoaded; const balanceInEthIfExists = isStateLoaded ? Web3Wrapper.toUnitAmount(outdatedEtherTokenState.balance, constants.DECIMAL_PLACES_ETH).toFixed( configs.AMOUNT_DISPLAY_PRECSION, ) : undefined; const onConversionSuccessful = this._onOutdatedConversionSuccessfulAsync.bind( this, outdatedWETHIfExists.address, ); const etherscanUrl = sharedUtils.getEtherScanLinkIfExists( outdatedWETHIfExists.address, this.props.networkId, EtherscanLinkSuffixes.Address, ); const tokenLabel = this._renderToken(dateRange, outdatedEtherToken.address, OUTDATED_WETH_ICON_PATH); return ( {this._renderTokenLink(tokenLabel, etherscanUrl)} {isStateLoaded ? ( `${balanceInEthIfExists} WETH` ) : ( )} ); }, ); return rows; } private _renderTokenLink(tokenLabel: React.ReactNode, etherscanUrl: string): React.ReactNode { return ( {_.isUndefined(etherscanUrl) ? ( tokenLabel ) : ( {tokenLabel} )} ); } private _renderToken(name: string, address: string, imgPath: string): React.ReactNode { const tooltipId = `tooltip-${address}`; return (
{name} {address}
); } private async _onOutdatedConversionSuccessfulAsync(outdatedWETHAddress: string): Promise { const currentOutdatedWETHState = this.state.outdatedWETHStateByAddress[outdatedWETHAddress]; this.setState({ outdatedWETHStateByAddress: { ...this.state.outdatedWETHStateByAddress, [outdatedWETHAddress]: { balance: currentOutdatedWETHState.balance, allowance: currentOutdatedWETHState.allowance, isLoaded: false, }, }, }); const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress; const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( userAddressIfExists, outdatedWETHAddress, ); this.setState({ outdatedWETHStateByAddress: { ...this.state.outdatedWETHStateByAddress, [outdatedWETHAddress]: { balance, allowance, isLoaded: true, }, }, }); } private async _fetchWETHStateAsync(): Promise { const tokens = _.values(this.props.tokenByAddress); const wethToken = _.find(tokens, token => token.symbol === 'WETH'); const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress; const [wethBalance, wethAllowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( userAddressIfExists, wethToken.address, ); const outdatedWETHAddresses = this._getOutdatedWETHAddresses(); const outdatedWETHStateByAddress: TokenStateByAddress = {}; for (const address of outdatedWETHAddresses) { const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( userAddressIfExists, address, ); outdatedWETHStateByAddress[address] = { balance, allowance, isLoaded: true, }; } if (!this._isUnmounted) { this.setState({ outdatedWETHStateByAddress, ethTokenState: { balance: wethBalance, allowance: wethAllowance, isLoaded: true, }, }); } } private _getOutdatedWETHAddresses(): string[] { 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; } private _getEthToken(): Token { const tokens = _.values(this.props.tokenByAddress); const etherToken = _.find(tokens, { symbol: 'WETH' }); return etherToken; } private async _refetchEthTokenStateAsync(): Promise { const etherToken = this._getEthToken(); const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress; const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( userAddressIfExists, etherToken.address, ); this.setState({ ethTokenState: { balance, allowance, isLoaded: true, }, }); } } // tslint:disable:max-file-line-count