Merge branch 'development' into addExtraDocs

* development:
  Change blockchain prop to not optional
  Other style changes
  Updated padding and colors
  Refactor TokenState type
  Fix a bug causing fillOrdersUpTo validation to fail because of some extra orders being passed
  Implement initial wallet interface

# Conflicts:
#	packages/react-shared/CHANGELOG.md
#	packages/website/ts/types.ts
This commit is contained in:
Fabio Berger
2018-03-22 12:00:41 +00:00
11 changed files with 485 additions and 47 deletions

View File

@@ -3,6 +3,7 @@
## v0.34.0 - _TBD_
* Update Kovan EtherToken artifact address to match TokenRegistry.
* Fix the bug causing `zeroEx.exchange.fillOrdersUpToAsync` validation to fail if there were some extra orders passed (#470)
## v0.33.2 - _March 18, 2018_

View File

@@ -281,6 +281,9 @@ export class ExchangeWrapper extends ContractWrapper {
zrxTokenAddress,
);
filledTakerTokenAmount = filledTakerTokenAmount.plus(singleFilledTakerTokenAmount);
if (filledTakerTokenAmount.eq(fillTakerTokenAmount)) {
break;
}
}
}

View File

@@ -596,6 +596,19 @@ describe('ExchangeWrapper', () => {
const remainingFillAmount = fillableAmount.minus(1);
expect(anotherFilledAmount).to.be.bignumber.equal(remainingFillAmount);
});
it('should successfully fill up to specified amount and leave the rest of the orders untouched', async () => {
const txHash = await zeroEx.exchange.fillOrdersUpToAsync(
signedOrders,
fillableAmount,
shouldThrowOnInsufficientBalanceOrAllowance,
takerAddress,
);
await zeroEx.awaitTransactionMinedAsync(txHash);
const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex);
const zeroAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex);
expect(filledAmount).to.be.bignumber.equal(fillableAmount);
expect(zeroAmount).to.be.bignumber.equal(0);
});
it('should successfully fill up to specified amount even if filling all orders would fail', async () => {
const missingBalance = new BigNumber(1); // User will still have enough balance to fill up to 9,
// but won't have 10 to fully fill all orders in a batch.

View File

@@ -1,5 +1,6 @@
# CHANGELOG
## v0.0.2 - _TBD_
## v0.1.0 - _TBD, 2018_
* Added new colors (#468)
* Fix section and menuItem text display to replace dashes with spaces.

View File

@@ -45,4 +45,7 @@ export const colors = {
orange: '#E69D00',
amber800: '#FF8F00',
darkYellow: '#caca03',
walletBoxShadow: 'rgba(56, 59, 137, 0.2)',
walletBorder: '#f5f5f6',
walletDefaultItemBackground: '#fbfbfc',
};

View File

@@ -10,7 +10,14 @@ 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 } from 'ts/types';
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';
@@ -20,13 +27,6 @@ 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;
@@ -39,9 +39,7 @@ interface EthWrappersProps {
interface EthWrappersState {
ethTokenState: TokenState;
isWethStateLoaded: boolean;
outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded;
outdatedWETHStateByAddress: OutdatedWETHStateByAddress;
outdatedWETHStateByAddress: TokenStateByAddress;
}
export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersState> {
@@ -50,22 +48,20 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
super(props);
this._isUnmounted = false;
const outdatedWETHAddresses = this._getOutdatedWETHAddresses();
const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {};
const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {};
const outdatedWETHStateByAddress: TokenStateByAddress = {};
_.each(outdatedWETHAddresses, outdatedWETHAddress => {
outdatedWETHAddressToIsStateLoaded[outdatedWETHAddress] = false;
outdatedWETHStateByAddress[outdatedWETHAddress] = {
balance: new BigNumber(0),
allowance: new BigNumber(0),
isLoaded: false,
};
});
this.state = {
outdatedWETHAddressToIsStateLoaded,
outdatedWETHStateByAddress,
isWethStateLoaded: false,
ethTokenState: {
balance: new BigNumber(0),
allowance: new BigNumber(0),
isLoaded: false,
},
};
}
@@ -169,7 +165,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
{this._renderTokenLink(tokenLabel, etherscanUrl)}
</TableRowColumn>
<TableRowColumn>
{this.state.isWethStateLoaded ? (
{this.state.ethTokenState.isLoaded ? (
`${wethBalance.toFixed(configs.AMOUNT_DISPLAY_PRECSION)} WETH`
) : (
<i className="zmdi zmdi-spinner zmdi-hc-spin" />
@@ -183,7 +179,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
networkId={this.props.networkId}
isOutdatedWrappedEther={false}
direction={Side.Receive}
isDisabled={!this.state.isWethStateLoaded}
isDisabled={!this.state.ethTokenState.isLoaded}
ethToken={etherToken}
dispatcher={this.props.dispatcher}
blockchain={this.props.blockchain}
@@ -266,8 +262,8 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
...etherToken,
address: outdatedWETHIfExists.address,
};
const isStateLoaded = this.state.outdatedWETHAddressToIsStateLoaded[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,
@@ -345,10 +341,15 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
);
}
private async _onOutdatedConversionSuccessfulAsync(outdatedWETHAddress: string) {
const currentOutdatedWETHState = this.state.outdatedWETHStateByAddress[outdatedWETHAddress];
this.setState({
outdatedWETHAddressToIsStateLoaded: {
...this.state.outdatedWETHAddressToIsStateLoaded,
[outdatedWETHAddress]: false,
outdatedWETHStateByAddress: {
...this.state.outdatedWETHStateByAddress,
[outdatedWETHAddress]: {
balance: currentOutdatedWETHState.balance,
allowance: currentOutdatedWETHState.allowance,
isLoaded: false,
},
},
});
const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress;
@@ -357,15 +358,12 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
outdatedWETHAddress,
);
this.setState({
outdatedWETHAddressToIsStateLoaded: {
...this.state.outdatedWETHAddressToIsStateLoaded,
[outdatedWETHAddress]: true,
},
outdatedWETHStateByAddress: {
...this.state.outdatedWETHStateByAddress,
[outdatedWETHAddress]: {
balance,
allowance,
isLoaded: true,
},
},
});
@@ -380,8 +378,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
);
const outdatedWETHAddresses = this._getOutdatedWETHAddresses();
const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {};
const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {};
const outdatedWETHStateByAddress: TokenStateByAddress = {};
for (const address of outdatedWETHAddresses) {
const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
userAddressIfExists,
@@ -390,18 +387,17 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
outdatedWETHStateByAddress[address] = {
balance,
allowance,
isLoaded: true,
};
outdatedWETHAddressToIsStateLoaded[address] = true;
}
if (!this._isUnmounted) {
this.setState({
outdatedWETHStateByAddress,
outdatedWETHAddressToIsStateLoaded,
ethTokenState: {
balance: wethBalance,
allowance: wethAllowance,
isLoaded: true,
},
isWethStateLoaded: true,
});
}
}
@@ -434,6 +430,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
ethTokenState: {
balance,
allowance,
isLoaded: true,
},
});
}

View File

@@ -19,12 +19,22 @@ import { TokenBalances } from 'ts/components/token_balances';
import { TopBar } from 'ts/components/top_bar/top_bar';
import { TradeHistory } from 'ts/components/trade_history/trade_history';
import { FlashMessage } from 'ts/components/ui/flash_message';
import { Wallet } from 'ts/components/wallet';
import { GenerateOrderForm } from 'ts/containers/generate_order_form';
import { localStorage } from 'ts/local_storage/local_storage';
import { Dispatcher } from 'ts/redux/dispatcher';
import { portalOrderSchema } from 'ts/schemas/portal_order_schema';
import { validator } from 'ts/schemas/validator';
import { BlockchainErrs, HashData, Order, ProviderType, ScreenWidths, TokenByAddress, WebsitePaths } from 'ts/types';
import {
BlockchainErrs,
Environments,
HashData,
Order,
ProviderType,
ScreenWidths,
TokenByAddress,
WebsitePaths,
} from 'ts/types';
import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants';
import { Translate } from 'ts/utils/translate';
@@ -194,6 +204,12 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
<div className="py2" style={{ backgroundColor: colors.grey50 }}>
{this.props.blockchainIsLoaded ? (
<Switch>
{configs.ENVIRONMENT === Environments.DEVELOPMENT && (
<Route
path={`${WebsitePaths.Portal}/wallet`}
render={this._renderWallet.bind(this)}
/>
)}
<Route
path={`${WebsitePaths.Portal}/weth`}
render={this._renderEthWrapper.bind(this)}
@@ -272,6 +288,28 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
isLedgerDialogOpen: !this.state.isLedgerDialogOpen,
});
}
private _renderWallet() {
const allTokens = _.values(this.props.tokenByAddress);
const trackedTokens = _.filter(allTokens, t => t.isTracked);
return (
<div className="flex flex-center">
<div className="mx-auto">
<Wallet
userAddress={this.props.userAddress}
networkId={this.props.networkId}
blockchain={this._blockchain}
blockchainIsLoaded={this.props.blockchainIsLoaded}
blockchainErr={this.props.blockchainErr}
dispatcher={this.props.dispatcher}
tokenByAddress={this.props.tokenByAddress}
trackedTokens={trackedTokens}
userEtherBalanceInWei={this.props.userEtherBalanceInWei}
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
/>
</div>
</div>
);
}
private _renderEthWrapper() {
return (
<EthWrappers

View File

@@ -1,7 +1,8 @@
import * as _ from 'lodash';
import * as React from 'react';
import { MenuItem } from 'ts/components/ui/menu_item';
import { WebsitePaths } from 'ts/types';
import { Environments, WebsitePaths } from 'ts/types';
import { configs } from 'ts/utils/configs';
export interface PortalMenuProps {
menuItemStyle: React.CSSProperties;
@@ -57,6 +58,16 @@ export class PortalMenu extends React.Component<PortalMenuProps, PortalMenuState
>
{this._renderMenuItemWithIcon('Wrap ETH', 'zmdi-circle-o')}
</MenuItem>
{configs.ENVIRONMENT === Environments.DEVELOPMENT && (
<MenuItem
style={this.props.menuItemStyle}
className="py2"
to={`${WebsitePaths.Portal}/wallet`}
onClick={this.props.onClick.bind(this)}
>
{this._renderMenuItemWithIcon('Wallet', 'zmdi-balance-wallet')}
</MenuItem>
)}
</div>
);
}

View File

@@ -37,6 +37,7 @@ import {
ScreenWidths,
Token,
TokenByAddress,
TokenStateByAddress,
TokenVisibility,
} from 'ts/types';
import { configs } from 'ts/utils/configs';
@@ -61,14 +62,6 @@ const styles: Styles = {
},
};
interface TokenStateByAddress {
[address: string]: {
balance: BigNumber;
allowance: BigNumber;
isLoaded: boolean;
};
}
interface TokenBalancesProps {
blockchain: Blockchain;
blockchainErr: BlockchainErrs;

View File

@@ -0,0 +1,373 @@
import { ZeroEx } from '0x.js';
import {
colors,
constants as sharedConstants,
EtherscanLinkSuffixes,
Styles,
utils as sharedUtils,
} from '@0xproject/react-shared';
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import FlatButton from 'material-ui/FlatButton';
import { List, ListItem } from 'material-ui/List';
import NavigationArrowDownward from 'material-ui/svg-icons/navigation/arrow-downward';
import NavigationArrowUpward from 'material-ui/svg-icons/navigation/arrow-upward';
import * as React from 'react';
import ReactTooltip = require('react-tooltip');
import firstBy = require('thenby');
import { Blockchain } from 'ts/blockchain';
import { AllowanceToggle } from 'ts/components/inputs/allowance_toggle';
import { Identicon } from 'ts/components/ui/identicon';
import { TokenIcon } from 'ts/components/ui/token_icon';
import { Dispatcher } from 'ts/redux/dispatcher';
import { BalanceErrs, BlockchainErrs, Token, TokenByAddress, TokenState, TokenStateByAddress } from 'ts/types';
import { constants } from 'ts/utils/constants';
import { utils } from 'ts/utils/utils';
export interface WalletProps {
userAddress?: string;
networkId?: number;
blockchain: Blockchain;
blockchainIsLoaded: boolean;
blockchainErr: BlockchainErrs;
dispatcher: Dispatcher;
tokenByAddress: TokenByAddress;
trackedTokens: Token[];
userEtherBalanceInWei: BigNumber;
lastForceTokenStateRefetch: number;
}
interface WalletState {
trackedTokenStateByAddress: TokenStateByAddress;
}
enum WrappedEtherAction {
Wrap,
Unwrap,
}
interface AllowanceToggleConfig {
token: Token;
tokenState: TokenState;
}
interface AccessoryItemConfig {
wrappedEtherAction?: WrappedEtherAction;
allowanceToggleConfig?: AllowanceToggleConfig;
}
const styles: Styles = {
wallet: {
width: 346,
backgroundColor: colors.white,
borderBottomRightRadius: 10,
borderBottomLeftRadius: 10,
borderTopRightRadius: 10,
borderTopLeftRadius: 10,
boxShadow: `0px 4px 6px ${colors.walletBoxShadow}`,
overflow: 'hidden',
},
list: {
padding: 0,
},
tokenItemInnerDiv: {
paddingLeft: 60,
},
headerItemInnerDiv: {
paddingLeft: 65,
},
footerItemInnerDiv: {
paddingLeft: 24,
},
borderedItem: {
borderBottomColor: colors.walletBorder,
borderBottomStyle: 'solid',
borderWidth: 1,
},
tokenItem: {
backgroundColor: colors.walletDefaultItemBackground,
paddingTop: 8,
paddingBottom: 8,
},
headerItem: {
paddingTop: 8,
paddingBottom: 8,
},
wrappedEtherButtonLabel: {
fontSize: 12,
},
amountLabel: {
fontWeight: 'bold',
color: colors.black,
},
};
const ETHER_ICON_PATH = '/images/ether.png';
const ETHER_TOKEN_SYMBOL = 'WETH';
const ZRX_TOKEN_SYMBOL = 'ZRX';
const ETHER_SYMBOL = 'ETH';
const ICON_DIMENSION = 24;
const TOKEN_AMOUNT_DISPLAY_PRECISION = 3;
export class Wallet extends React.Component<WalletProps, WalletState> {
private _isUnmounted: boolean;
constructor(props: WalletProps) {
super(props);
this._isUnmounted = false;
const initialTrackedTokenStateByAddress = this._getInitialTrackedTokenStateByAddress(props.trackedTokens);
this.state = {
trackedTokenStateByAddress: initialTrackedTokenStateByAddress,
};
}
public componentWillMount() {
const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress);
// tslint:disable-next-line:no-floating-promises
this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses);
}
public componentWillUnmount() {
this._isUnmounted = true;
}
public componentWillReceiveProps(nextProps: WalletProps) {
if (
nextProps.userAddress !== this.props.userAddress ||
nextProps.networkId !== this.props.networkId ||
nextProps.lastForceTokenStateRefetch !== this.props.lastForceTokenStateRefetch
) {
const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress);
// tslint:disable-next-line:no-floating-promises
this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses);
}
if (!_.isEqual(nextProps.trackedTokens, this.props.trackedTokens)) {
const newTokens = _.difference(nextProps.trackedTokens, this.props.trackedTokens);
const newTokenAddresses = _.map(newTokens, token => token.address);
// Add placeholder entry for this token to the state, since fetching the
// balance/allowance is asynchronous
const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress;
_.each(newTokenAddresses, (tokenAddress: string) => {
trackedTokenStateByAddress[tokenAddress] = {
balance: new BigNumber(0),
allowance: new BigNumber(0),
isLoaded: false,
};
});
this.setState({
trackedTokenStateByAddress,
});
// Fetch the actual balance/allowance.
// tslint:disable-next-line:no-floating-promises
this._fetchBalancesAndAllowancesAsync(newTokenAddresses);
}
}
public render() {
const isReadyToRender = this.props.blockchainIsLoaded && this.props.blockchainErr === BlockchainErrs.NoError;
return <div style={styles.wallet}>{isReadyToRender && this._renderRows()}</div>;
}
private _renderRows() {
return (
<List style={styles.list}>
{_.concat(
this._renderHeaderRows(),
this._renderEthRows(),
this._renderTokenRows(),
this._renderFooterRows(),
)}
</List>
);
}
private _renderHeaderRows() {
const userAddress = this.props.userAddress;
const primaryText = utils.getAddressBeginAndEnd(userAddress);
return (
<ListItem
primaryText={primaryText}
leftIcon={<Identicon address={userAddress} diameter={ICON_DIMENSION} />}
style={{ ...styles.headerItem, ...styles.borderedItem }}
innerDivStyle={styles.headerItemInnerDiv}
/>
);
}
private _renderFooterRows() {
const primaryText = '+ other tokens';
return (
<ListItem primaryText={primaryText} style={styles.borderedItem} innerDivStyle={styles.footerItemInnerDiv} />
);
}
private _renderEthRows() {
const primaryText = this._renderAmount(
this.props.userEtherBalanceInWei,
constants.DECIMAL_PLACES_ETH,
ETHER_SYMBOL,
);
const accessoryItemConfig = {
wrappedEtherAction: WrappedEtherAction.Wrap,
};
return (
<ListItem
primaryText={primaryText}
leftIcon={<img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={ETHER_ICON_PATH} />}
rightAvatar={this._renderAccessoryItems(accessoryItemConfig)}
style={{ ...styles.tokenItem, ...styles.borderedItem }}
innerDivStyle={styles.tokenItemInnerDiv}
/>
);
}
private _renderTokenRows() {
const trackedTokens = this.props.trackedTokens;
const trackedTokensStartingWithEtherToken = trackedTokens.sort(
firstBy((t: Token) => t.symbol !== ETHER_TOKEN_SYMBOL)
.thenBy((t: Token) => t.symbol !== ZRX_TOKEN_SYMBOL)
.thenBy('address'),
);
return _.map(trackedTokensStartingWithEtherToken, this._renderTokenRow.bind(this));
}
private _renderTokenRow(token: Token) {
const tokenState = this.state.trackedTokenStateByAddress[token.address];
const tokenLink = sharedUtils.getEtherScanLinkIfExists(
token.address,
this.props.networkId,
EtherscanLinkSuffixes.Address,
);
const amount = this._renderAmount(tokenState.balance, token.decimals, token.symbol);
const wrappedEtherAction = token.symbol === ETHER_TOKEN_SYMBOL ? WrappedEtherAction.Unwrap : undefined;
const accessoryItemConfig: AccessoryItemConfig = {
wrappedEtherAction,
allowanceToggleConfig: {
token,
tokenState,
},
};
return (
<ListItem
primaryText={amount}
leftIcon={this._renderTokenIcon(token, tokenLink)}
rightAvatar={this._renderAccessoryItems(accessoryItemConfig)}
style={{ ...styles.tokenItem, ...styles.borderedItem }}
innerDivStyle={styles.tokenItemInnerDiv}
/>
);
}
private _renderAccessoryItems(config: AccessoryItemConfig) {
const shouldShowWrappedEtherAction = !_.isUndefined(config.wrappedEtherAction);
const shouldShowToggle = !_.isUndefined(config.allowanceToggleConfig);
return (
<div style={{ width: 160 }}>
<div className="flex">
<div className="flex-auto">
{shouldShowWrappedEtherAction && this._renderWrappedEtherButton(config.wrappedEtherAction)}
</div>
<div className="flex-last py1">
{shouldShowToggle && this._renderAllowanceToggle(config.allowanceToggleConfig)}
</div>
</div>
</div>
);
}
private _renderAllowanceToggle(config: AllowanceToggleConfig) {
return (
<AllowanceToggle
networkId={this.props.networkId}
blockchain={this.props.blockchain}
dispatcher={this.props.dispatcher}
token={config.token}
tokenState={config.tokenState}
onErrorOccurred={_.noop} // TODO: Error handling
userAddress={this.props.userAddress}
isDisabled={!config.tokenState.isLoaded}
refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this, config.token.address)}
/>
);
}
private _renderAmount(amount: BigNumber, decimals: number, symbol: string) {
const unitAmount = ZeroEx.toUnitAmount(amount, decimals);
const formattedAmount = unitAmount.toPrecision(TOKEN_AMOUNT_DISPLAY_PRECISION);
const result = `${formattedAmount} ${symbol}`;
return <div style={styles.amountLabel}>{result}</div>;
}
private _renderTokenIcon(token: Token, tokenLink?: string) {
const tooltipId = `tooltip-${token.address}`;
const tokenIcon = <TokenIcon token={token} diameter={ICON_DIMENSION} />;
if (_.isUndefined(tokenLink)) {
return tokenIcon;
} else {
return (
<a href={tokenLink} target="_blank" style={{ textDecoration: 'none' }}>
{tokenIcon}
</a>
);
}
}
private _renderWrappedEtherButton(action: WrappedEtherAction) {
let buttonLabel;
let buttonIcon;
switch (action) {
case WrappedEtherAction.Wrap:
buttonLabel = 'wrap';
buttonIcon = <NavigationArrowDownward />;
break;
case WrappedEtherAction.Unwrap:
buttonLabel = 'unwrap';
buttonIcon = <NavigationArrowUpward />;
break;
default:
throw utils.spawnSwitchErr('wrappedEtherAction', action);
}
return (
<FlatButton
label={buttonLabel}
labelPosition="after"
primary={true}
icon={buttonIcon}
labelStyle={styles.wrappedEtherButtonLabel}
/>
);
}
private _getInitialTrackedTokenStateByAddress(trackedTokens: Token[]) {
const trackedTokenStateByAddress: TokenStateByAddress = {};
_.each(trackedTokens, token => {
trackedTokenStateByAddress[token.address] = {
balance: new BigNumber(0),
allowance: new BigNumber(0),
isLoaded: false,
};
});
return trackedTokenStateByAddress;
}
private async _fetchBalancesAndAllowancesAsync(tokenAddresses: string[]) {
const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress;
const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress;
for (const tokenAddress of tokenAddresses) {
const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
userAddressIfExists,
tokenAddress,
);
trackedTokenStateByAddress[tokenAddress] = {
balance,
allowance,
isLoaded: true,
};
}
if (!this._isUnmounted) {
this.setState({
trackedTokenStateByAddress,
});
}
}
private async _refetchTokenStateAsync(tokenAddress: string) {
const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress;
const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
userAddressIfExists,
tokenAddress,
);
this.setState({
trackedTokenStateByAddress: {
...this.state.trackedTokenStateByAddress,
[tokenAddress]: {
balance,
allowance,
isLoaded: true,
},
},
});
}
}

View File

@@ -21,11 +21,6 @@ export interface TokenByAddress {
[address: string]: Token;
}
export interface TokenState {
allowance: BigNumber;
balance: BigNumber;
}
export interface AssetToken {
address?: string;
amount?: BigNumber;
@@ -484,4 +479,14 @@ export interface OutdatedWrappedEtherByNetworkId {
timestampMsRange: TimestampMsRange;
};
}
export interface TokenStateByAddress {
[address: string]: TokenState;
}
export interface TokenState {
balance: BigNumber;
allowance: BigNumber;
isLoaded: boolean;
}
// tslint:disable:max-file-line-count