import { ZeroEx } from '0x.js'; import { colors, EtherscanLinkSuffixes, utils as sharedUtils } from '@0xproject/react-shared'; 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 { 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; } interface EthWrappersState { ethTokenState: TokenState; outdatedWETHStateByAddress: TokenStateByAddress; } export class EthWrappers extends React.Component { 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 = ZeroEx.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, configs.ICON_URL_BY_SYMBOL.WETH); const userEtherBalanceInEth = ZeroEx.toUnitAmount( this.props.userEtherBalanceInWei, constants.DECIMAL_PLACES_ETH, ); 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
{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, this.state.ethTokenState)}
); } 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, etherTokenState: TokenState): 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 ? ZeroEx.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