import { ZeroEx } from '0x.js'; import { BigNumber } from '@0xproject/utils'; 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 = 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, TokenByAddress, TokenState, } from 'ts/types'; 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'; const ICON_DIMENSION = 40; const ETHER_ICON_PATH = '/images/ether.png'; const OUTDATED_WETH_ICON_PATH = '/images/wrapped_eth_gray.png'; interface OutdatedWETHAddressToIsStateLoaded { [address: string]: boolean; } interface OutdatedWETHStateByAddress { [address: string]: TokenState; } interface EthWrappersProps { networkId: number; blockchain: Blockchain; dispatcher: Dispatcher; tokenByAddress: TokenByAddress; userAddress: string; userEtherBalance: BigNumber; lastForceTokenStateRefetch: number; } interface EthWrappersState { ethTokenState: TokenState; isWethStateLoaded: boolean; outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded; outdatedWETHStateByAddress: OutdatedWETHStateByAddress; } export class EthWrappers extends React.Component { private _isUnmounted: boolean; constructor(props: EthWrappersProps) { super(props); this._isUnmounted = false; const outdatedWETHAddresses = this._getOutdatedWETHAddresses(); const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {}; const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {}; _.each(outdatedWETHAddresses, outdatedWETHAddress => { outdatedWETHAddressToIsStateLoaded[outdatedWETHAddress] = false; outdatedWETHStateByAddress[outdatedWETHAddress] = { balance: new BigNumber(0), allowance: new BigNumber(0), }; }); this.state = { outdatedWETHAddressToIsStateLoaded, outdatedWETHStateByAddress, isWethStateLoaded: false, ethTokenState: { balance: new BigNumber(0), allowance: new BigNumber(0), }, }; } public componentWillReceiveProps(nextProps: EthWrappersProps) { 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() { window.scrollTo(0, 0); // tslint:disable-next-line:no-floating-promises this._fetchWETHStateAsync(); } public componentWillUnmount() { this._isUnmounted = true; } public render() { const etherToken = this._getEthToken(); const wethBalance = ZeroEx.toUnitAmount(this.state.ethTokenState.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 (

ETH Wrapper

About Wrapped ETH
Wrap ETH into an ERC20-compliant Ether token. 1 ETH = 1 WETH.
ETH Token Balance {this._renderActionColumnTitle(isBidirectional)}
ETH
{this.props.userEtherBalance.toFixed(PRECISION)} ETH
{this._renderTokenLink(tokenLabel, etherscanUrl)} {this.state.isWethStateLoaded ? ( `${wethBalance.toFixed(PRECISION)} 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, this.state.ethTokenState)}
); } private _renderActionColumnTitle(isBidirectional: boolean) { 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, etherTokenState: TokenState) { 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 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, 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 ( {this._renderTokenLink(tokenLabel, etherscanUrl)} {isStateLoaded ? ( `${balanceInEthIfExists} WETH` ) : ( )} ); }, ); return rows; } private _renderTokenLink(tokenLabel: React.ReactNode, etherscanUrl: string) { return ( {_.isUndefined(etherscanUrl) ? ( tokenLabel ) : ( {tokenLabel} )} ); } private _renderToken(name: string, address: string, imgPath: string) { const tooltipId = `tooltip-${address}`; return (
{name} {address}
); } private async _onOutdatedConversionSuccessfulAsync(outdatedWETHAddress: string) { this.setState({ outdatedWETHAddressToIsStateLoaded: { ...this.state.outdatedWETHAddressToIsStateLoaded, [outdatedWETHAddress]: false, }, }); const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( this.props.userAddress, outdatedWETHAddress, ); this.setState({ outdatedWETHAddressToIsStateLoaded: { ...this.state.outdatedWETHAddressToIsStateLoaded, [outdatedWETHAddress]: true, }, outdatedWETHStateByAddress: { ...this.state.outdatedWETHStateByAddress, [outdatedWETHAddress]: { balance, allowance, }, }, }); } private async _fetchWETHStateAsync() { const tokens = _.values(this.props.tokenByAddress); const wethToken = _.find(tokens, token => token.symbol === 'WETH'); const [wethBalance, wethAllowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( this.props.userAddress, wethToken.address, ); const outdatedWETHAddresses = this._getOutdatedWETHAddresses(); const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {}; const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {}; for (const address of outdatedWETHAddresses) { const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( this.props.userAddress, address, ); outdatedWETHStateByAddress[address] = { balance, allowance, }; outdatedWETHAddressToIsStateLoaded[address] = true; } if (!this._isUnmounted) { this.setState({ outdatedWETHStateByAddress, outdatedWETHAddressToIsStateLoaded, ethTokenState: { balance: wethBalance, allowance: wethAllowance, }, isWethStateLoaded: 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() { const tokens = _.values(this.props.tokenByAddress); const etherToken = _.find(tokens, { symbol: 'WETH' }); return etherToken; } private async _refetchEthTokenStateAsync() { const etherToken = this._getEthToken(); const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( this.props.userAddress, etherToken.address, ); this.setState({ ethTokenState: { balance, allowance, }, }); } } // tslint:disable:max-file-line-count