Implement just-in-time loading of token balances & allowances
This commit is contained in:
@@ -66,7 +66,6 @@ export class Blockchain {
|
||||
private _cachedProvider: Web3.Provider;
|
||||
private _cachedProviderNetworkId: number;
|
||||
private _ledgerSubprovider: LedgerWalletSubprovider;
|
||||
private _zrxPollIntervalId: NodeJS.Timer;
|
||||
private static async _onPageLoadAsync(): Promise<void> {
|
||||
if (document.readyState === 'complete') {
|
||||
return; // Already loaded
|
||||
@@ -250,7 +249,6 @@ export class Blockchain {
|
||||
);
|
||||
await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
|
||||
const allowance = amountInBaseUnits;
|
||||
this._dispatcher.replaceTokenAllowanceByAddress(token.address, allowance);
|
||||
}
|
||||
public async transferAsync(token: Token, toAddress: string, amountInBaseUnits: BigNumber): Promise<void> {
|
||||
const txHash = await this._zeroEx.token.transferAsync(
|
||||
@@ -368,22 +366,25 @@ export class Blockchain {
|
||||
|
||||
const [currBalance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address);
|
||||
|
||||
this._zrxPollIntervalId = intervalUtils.setAsyncExcludingInterval(
|
||||
async () => {
|
||||
const [balance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address);
|
||||
if (!balance.eq(currBalance)) {
|
||||
this._dispatcher.replaceTokenBalanceByAddress(token.address, balance);
|
||||
intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId);
|
||||
delete this._zrxPollIntervalId;
|
||||
}
|
||||
},
|
||||
5000,
|
||||
(err: Error) => {
|
||||
utils.consoleLog(`Polling tokenBalance failed: ${err}`);
|
||||
intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId);
|
||||
delete this._zrxPollIntervalId;
|
||||
},
|
||||
);
|
||||
const newTokenBalancePromise = new Promise((resolve: (balance: BigNumber) => void, reject) => {
|
||||
const tokenPollInterval = intervalUtils.setAsyncExcludingInterval(
|
||||
async () => {
|
||||
const [balance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address);
|
||||
if (!balance.eq(currBalance)) {
|
||||
intervalUtils.clearAsyncExcludingInterval(tokenPollInterval);
|
||||
resolve(balance);
|
||||
}
|
||||
},
|
||||
5000,
|
||||
(err: Error) => {
|
||||
utils.consoleLog(`Polling tokenBalance failed: ${err}`);
|
||||
intervalUtils.clearAsyncExcludingInterval(tokenPollInterval);
|
||||
reject(err);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
return newTokenBalancePromise;
|
||||
}
|
||||
public async signOrderHashAsync(orderHash: string): Promise<SignatureData> {
|
||||
utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
|
||||
@@ -408,7 +409,6 @@ export class Blockchain {
|
||||
from: this._userAddress,
|
||||
});
|
||||
const balanceDelta = constants.MINT_AMOUNT;
|
||||
this._dispatcher.updateTokenBalanceByAddress(token.address, balanceDelta);
|
||||
}
|
||||
public async getBalanceInEthAsync(owner: string): Promise<BigNumber> {
|
||||
const balance = await this._web3Wrapper.getBalanceInEthAsync(owner);
|
||||
@@ -451,23 +451,6 @@ export class Blockchain {
|
||||
}
|
||||
return [balance, allowance];
|
||||
}
|
||||
public async updateTokenBalancesAndAllowancesAsync(tokens: Token[]) {
|
||||
const err = new Error('show stopper');
|
||||
const tokenStateByAddress: TokenStateByAddress = {};
|
||||
for (const token of tokens) {
|
||||
let balance = new BigNumber(0);
|
||||
let allowance = new BigNumber(0);
|
||||
if (this._doesUserAddressExist()) {
|
||||
[balance, allowance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address);
|
||||
}
|
||||
const tokenState = {
|
||||
balance,
|
||||
allowance,
|
||||
};
|
||||
tokenStateByAddress[token.address] = tokenState;
|
||||
}
|
||||
this._dispatcher.updateTokenStateByAddress(tokenStateByAddress);
|
||||
}
|
||||
public async getUserAccountsAsync() {
|
||||
utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
|
||||
const userAccountsIfExists = await this._zeroEx.getAvailableAddressesAsync();
|
||||
@@ -480,7 +463,6 @@ export class Blockchain {
|
||||
this._web3Wrapper.updatePrevUserAddress(newUserAddress);
|
||||
}
|
||||
public destroy() {
|
||||
intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId);
|
||||
this._web3Wrapper.destroy();
|
||||
this._stopWatchingExchangeLogFillEvents();
|
||||
}
|
||||
@@ -527,9 +509,6 @@ export class Blockchain {
|
||||
const allTokens = _.uniq([...tokenRegistryTokens, ...trackedTokensIfExists]);
|
||||
this._dispatcher.updateTokenByAddress(allTokens);
|
||||
|
||||
// Get balance/allowance for tracked tokens
|
||||
await this.updateTokenBalancesAndAllowancesAsync(trackedTokensIfExists);
|
||||
|
||||
const mostPopularTradingPairTokens: Token[] = [
|
||||
_.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[0] }),
|
||||
_.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[1] }),
|
||||
|
||||
@@ -2,25 +2,31 @@ import { BigNumber } from '@0xproject/utils';
|
||||
import Dialog from 'material-ui/Dialog';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
import * as React from 'react';
|
||||
import { Blockchain } from 'ts/blockchain';
|
||||
import { EthAmountInput } from 'ts/components/inputs/eth_amount_input';
|
||||
import { TokenAmountInput } from 'ts/components/inputs/token_amount_input';
|
||||
import { Side, Token, TokenState } from 'ts/types';
|
||||
import { Side, Token } from 'ts/types';
|
||||
import { colors } from 'ts/utils/colors';
|
||||
|
||||
interface EthWethConversionDialogProps {
|
||||
blockchain: Blockchain;
|
||||
userAddress: string;
|
||||
networkId: number;
|
||||
direction: Side;
|
||||
onComplete: (direction: Side, value: BigNumber) => void;
|
||||
onCancelled: () => void;
|
||||
isOpen: boolean;
|
||||
token: Token;
|
||||
tokenState: TokenState;
|
||||
etherBalance: BigNumber;
|
||||
lastForceTokenStateRefetch: number;
|
||||
}
|
||||
|
||||
interface EthWethConversionDialogState {
|
||||
value?: BigNumber;
|
||||
shouldShowIncompleteErrs: boolean;
|
||||
hasErrors: boolean;
|
||||
isEthTokenBalanceLoaded: boolean;
|
||||
ethTokenBalance: BigNumber;
|
||||
}
|
||||
|
||||
export class EthWethConversionDialog extends React.Component<
|
||||
@@ -32,8 +38,14 @@ export class EthWethConversionDialog extends React.Component<
|
||||
this.state = {
|
||||
shouldShowIncompleteErrs: false,
|
||||
hasErrors: false,
|
||||
isEthTokenBalanceLoaded: false,
|
||||
ethTokenBalance: new BigNumber(0),
|
||||
};
|
||||
}
|
||||
public componentWillMount() {
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
this._fetchEthTokenBalanceAsync();
|
||||
}
|
||||
public render() {
|
||||
const convertDialogActions = [
|
||||
<FlatButton key="cancel" label="Cancel" onTouchTap={this._onCancel.bind(this)} />,
|
||||
@@ -72,8 +84,11 @@ export class EthWethConversionDialog extends React.Component<
|
||||
<div className="pt2 mx-auto" style={{ width: 245 }}>
|
||||
{this.props.direction === Side.Receive ? (
|
||||
<TokenAmountInput
|
||||
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
|
||||
blockchain={this.props.blockchain}
|
||||
userAddress={this.props.userAddress}
|
||||
networkId={this.props.networkId}
|
||||
token={this.props.token}
|
||||
tokenState={this.props.tokenState}
|
||||
shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs}
|
||||
shouldCheckBalance={true}
|
||||
shouldCheckAllowance={false}
|
||||
@@ -93,19 +108,20 @@ export class EthWethConversionDialog extends React.Component<
|
||||
)}
|
||||
<div className="pt1" style={{ fontSize: 12 }}>
|
||||
<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>
|
||||
)}
|
||||
{this.props.direction === Side.Receive &&
|
||||
this.state.isEthTokenBalanceLoaded && (
|
||||
<div
|
||||
className="right"
|
||||
onClick={this._onMaxClick.bind(this)}
|
||||
style={{
|
||||
color: colors.darkBlue,
|
||||
textDecoration: 'underline',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
Max
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -132,7 +148,7 @@ export class EthWethConversionDialog extends React.Component<
|
||||
}
|
||||
private _onMaxClick() {
|
||||
this.setState({
|
||||
value: this.props.tokenState.balance,
|
||||
value: this.state.ethTokenBalance,
|
||||
});
|
||||
}
|
||||
private _onValueChange(isValid: boolean, amount?: BigNumber) {
|
||||
@@ -160,4 +176,14 @@ export class EthWethConversionDialog extends React.Component<
|
||||
});
|
||||
this.props.onCancelled();
|
||||
}
|
||||
private async _fetchEthTokenBalanceAsync() {
|
||||
const [balance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
|
||||
this.props.userAddress,
|
||||
this.props.token.address,
|
||||
);
|
||||
this.setState({
|
||||
isEthTokenBalanceLoaded: true,
|
||||
ethTokenBalance: balance,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +197,8 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
|
||||
const selectAddressBalance = this.state.addressBalances[selectedRowIndex];
|
||||
this.props.dispatcher.updateUserAddress(selectedAddress);
|
||||
this.props.blockchain.updateWeb3WrapperPrevUserAddress(selectedAddress);
|
||||
this.props.blockchain.fetchTokenInformationAsync(); // fire and forget
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
this.props.blockchain.fetchTokenInformationAsync();
|
||||
this.props.dispatcher.updateUserEtherBalance(selectAddressBalance);
|
||||
this.setState({
|
||||
stepIndex: LedgerSteps.CONNECT,
|
||||
|
||||
@@ -3,16 +3,20 @@ import * as _ from 'lodash';
|
||||
import Dialog from 'material-ui/Dialog';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
import * as React from 'react';
|
||||
import { Blockchain } from 'ts/blockchain';
|
||||
import { AddressInput } from 'ts/components/inputs/address_input';
|
||||
import { TokenAmountInput } from 'ts/components/inputs/token_amount_input';
|
||||
import { Token, TokenState } from 'ts/types';
|
||||
import { Token } from 'ts/types';
|
||||
|
||||
interface SendDialogProps {
|
||||
blockchain: Blockchain;
|
||||
userAddress: string;
|
||||
networkId: number;
|
||||
onComplete: (recipient: string, value: BigNumber) => void;
|
||||
onCancelled: () => void;
|
||||
isOpen: boolean;
|
||||
token: Token;
|
||||
tokenState: TokenState;
|
||||
lastForceTokenStateRefetch: number;
|
||||
}
|
||||
|
||||
interface SendDialogState {
|
||||
@@ -66,15 +70,18 @@ export class SendDialog extends React.Component<SendDialogProps, SendDialogState
|
||||
/>
|
||||
</div>
|
||||
<TokenAmountInput
|
||||
blockchain={this.props.blockchain}
|
||||
userAddress={this.props.userAddress}
|
||||
networkId={this.props.networkId}
|
||||
label="Amount to send"
|
||||
token={this.props.token}
|
||||
tokenState={this.props.tokenState}
|
||||
shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs}
|
||||
shouldCheckBalance={true}
|
||||
shouldCheckAllowance={false}
|
||||
onChange={this._onValueChange.bind(this)}
|
||||
amount={this.state.value}
|
||||
onVisitBalancesPageClick={this.props.onCancelled}
|
||||
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -82,16 +82,6 @@ export class TrackTokenConfirmationDialog extends React.Component<
|
||||
newTokenEntry.isTracked = true;
|
||||
trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry);
|
||||
this.props.dispatcher.updateTokenByAddress([newTokenEntry]);
|
||||
|
||||
const [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(
|
||||
token.address,
|
||||
);
|
||||
this.props.dispatcher.updateTokenStateByAddress({
|
||||
[token.address]: {
|
||||
balance,
|
||||
allowance,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({
|
||||
|
||||
@@ -12,6 +12,8 @@ import { errorReporter } from 'ts/utils/error_reporter';
|
||||
import { utils } from 'ts/utils/utils';
|
||||
|
||||
interface EthWethConversionButtonProps {
|
||||
userAddress: string;
|
||||
networkId: number;
|
||||
direction: Side;
|
||||
ethToken: Token;
|
||||
ethTokenState: TokenState;
|
||||
@@ -21,6 +23,8 @@ interface EthWethConversionButtonProps {
|
||||
isOutdatedWrappedEther: boolean;
|
||||
onConversionSuccessful?: () => void;
|
||||
isDisabled?: boolean;
|
||||
lastForceTokenStateRefetch: number;
|
||||
refetchEthTokenStateAsync: () => Promise<void>;
|
||||
}
|
||||
|
||||
interface EthWethConversionButtonState {
|
||||
@@ -64,13 +68,16 @@ export class EthWethConversionButton extends React.Component<
|
||||
onClick={this._toggleConversionDialog.bind(this)}
|
||||
/>
|
||||
<EthWethConversionDialog
|
||||
blockchain={this.props.blockchain}
|
||||
userAddress={this.props.userAddress}
|
||||
networkId={this.props.networkId}
|
||||
direction={this.props.direction}
|
||||
isOpen={this.state.isEthConversionDialogVisible}
|
||||
onComplete={this._onConversionAmountSelectedAsync.bind(this)}
|
||||
onCancelled={this._toggleConversionDialog.bind(this)}
|
||||
etherBalance={this.props.userEtherBalance}
|
||||
token={this.props.ethToken}
|
||||
tokenState={this.props.ethTokenState}
|
||||
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -87,21 +94,18 @@ export class EthWethConversionButton extends React.Component<
|
||||
this._toggleConversionDialog();
|
||||
const token = this.props.ethToken;
|
||||
const tokenState = this.props.ethTokenState;
|
||||
let balance = tokenState.balance;
|
||||
try {
|
||||
if (direction === Side.Deposit) {
|
||||
await this.props.blockchain.convertEthToWrappedEthTokensAsync(token.address, value);
|
||||
const ethAmount = ZeroEx.toUnitAmount(value, constants.DECIMAL_PLACES_ETH);
|
||||
this.props.dispatcher.showFlashMessage(`Successfully wrapped ${ethAmount.toString()} ETH to WETH`);
|
||||
balance = balance.plus(value);
|
||||
} else {
|
||||
await this.props.blockchain.convertWrappedEthTokensToEthAsync(token.address, value);
|
||||
const tokenAmount = ZeroEx.toUnitAmount(value, token.decimals);
|
||||
this.props.dispatcher.showFlashMessage(`Successfully unwrapped ${tokenAmount.toString()} WETH to ETH`);
|
||||
balance = balance.minus(value);
|
||||
}
|
||||
if (!this.props.isOutdatedWrappedEther) {
|
||||
this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance);
|
||||
this.props.refetchEthTokenStateAsync();
|
||||
}
|
||||
this.props.onConversionSuccessful();
|
||||
} catch (err) {
|
||||
|
||||
@@ -41,12 +41,14 @@ interface EthWrappersProps {
|
||||
blockchain: Blockchain;
|
||||
dispatcher: Dispatcher;
|
||||
tokenByAddress: TokenByAddress;
|
||||
tokenStateByAddress: TokenStateByAddress;
|
||||
userAddress: string;
|
||||
userEtherBalance: BigNumber;
|
||||
lastForceTokenStateRefetch: number;
|
||||
}
|
||||
|
||||
interface EthWrappersState {
|
||||
ethTokenState: TokenState;
|
||||
isWethStateLoaded: boolean;
|
||||
outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded;
|
||||
outdatedWETHStateByAddress: OutdatedWETHStateByAddress;
|
||||
}
|
||||
@@ -67,18 +69,31 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
|
||||
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._fetchOutdatedWETHStateAsync();
|
||||
this._fetchWETHStateAsync();
|
||||
}
|
||||
public render() {
|
||||
const tokens = _.values(this.props.tokenByAddress);
|
||||
const etherToken = _.find(tokens, { symbol: 'WETH' });
|
||||
const etherTokenState = this.props.tokenStateByAddress[etherToken.address];
|
||||
const wethBalance = ZeroEx.toUnitAmount(etherTokenState.balance, constants.DECIMAL_PLACES_ETH);
|
||||
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,
|
||||
@@ -136,10 +151,14 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
|
||||
</TableRowColumn>
|
||||
<TableRowColumn>
|
||||
<EthWethConversionButton
|
||||
refetchEthTokenStateAsync={this._refetchEthTokenStateAsync.bind(this)}
|
||||
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
|
||||
userAddress={this.props.userAddress}
|
||||
networkId={this.props.networkId}
|
||||
isOutdatedWrappedEther={false}
|
||||
direction={Side.Deposit}
|
||||
ethToken={etherToken}
|
||||
ethTokenState={etherTokenState}
|
||||
ethTokenState={this.state.ethTokenState}
|
||||
dispatcher={this.props.dispatcher}
|
||||
blockchain={this.props.blockchain}
|
||||
userEtherBalance={this.props.userEtherBalance}
|
||||
@@ -150,13 +169,24 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
|
||||
<TableRowColumn className="py1">
|
||||
{this._renderTokenLink(tokenLabel, etherscanUrl)}
|
||||
</TableRowColumn>
|
||||
<TableRowColumn>{wethBalance.toFixed(PRECISION)} WETH</TableRowColumn>
|
||||
<TableRowColumn>
|
||||
{this.state.isWethStateLoaded ? (
|
||||
`${wethBalance.toFixed(PRECISION)} WETH`
|
||||
) : (
|
||||
<i className="zmdi zmdi-spinner zmdi-hc-spin" />
|
||||
)}
|
||||
</TableRowColumn>
|
||||
<TableRowColumn>
|
||||
<EthWethConversionButton
|
||||
refetchEthTokenStateAsync={this._refetchEthTokenStateAsync.bind(this)}
|
||||
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
|
||||
userAddress={this.props.userAddress}
|
||||
networkId={this.props.networkId}
|
||||
isOutdatedWrappedEther={false}
|
||||
direction={Side.Receive}
|
||||
isDisabled={!this.state.isWethStateLoaded}
|
||||
ethToken={etherToken}
|
||||
ethTokenState={etherTokenState}
|
||||
ethTokenState={this.state.ethTokenState}
|
||||
dispatcher={this.props.dispatcher}
|
||||
blockchain={this.props.blockchain}
|
||||
userEtherBalance={this.props.userEtherBalance}
|
||||
@@ -190,7 +220,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody displayRowCheckbox={false}>
|
||||
{this._renderOutdatedWeths(etherToken, etherTokenState)}
|
||||
{this._renderOutdatedWeths(etherToken, this.state.ethTokenState)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
@@ -269,6 +299,10 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
|
||||
</TableRowColumn>
|
||||
<TableRowColumn>
|
||||
<EthWethConversionButton
|
||||
refetchEthTokenStateAsync={this._refetchEthTokenStateAsync.bind(this)}
|
||||
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
|
||||
userAddress={this.props.userAddress}
|
||||
networkId={this.props.networkId}
|
||||
isDisabled={!isStateLoaded}
|
||||
isOutdatedWrappedEther={true}
|
||||
direction={Side.Receive}
|
||||
@@ -338,7 +372,14 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
|
||||
},
|
||||
});
|
||||
}
|
||||
private async _fetchOutdatedWETHStateAsync() {
|
||||
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 = {};
|
||||
@@ -356,6 +397,11 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
|
||||
this.setState({
|
||||
outdatedWETHStateByAddress,
|
||||
outdatedWETHAddressToIsStateLoaded,
|
||||
ethTokenState: {
|
||||
balance: wethBalance,
|
||||
allowance: wethAllowance,
|
||||
},
|
||||
isWethStateLoaded: true,
|
||||
});
|
||||
}
|
||||
private _getOutdatedWETHAddresses(): string[] {
|
||||
@@ -371,4 +417,22 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
|
||||
);
|
||||
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
|
||||
|
||||
@@ -19,7 +19,7 @@ import { VisualOrder } from 'ts/components/visual_order';
|
||||
import { Dispatcher } from 'ts/redux/dispatcher';
|
||||
import { orderSchema } from 'ts/schemas/order_schema';
|
||||
import { SchemaValidator } from 'ts/schemas/validator';
|
||||
import { AlertTypes, BlockchainErrs, Order, Token, TokenByAddress, TokenStateByAddress, WebsitePaths } from 'ts/types';
|
||||
import { AlertTypes, BlockchainErrs, Order, Token, TokenByAddress, WebsitePaths } from 'ts/types';
|
||||
import { colors } from 'ts/utils/colors';
|
||||
import { constants } from 'ts/utils/constants';
|
||||
import { errorReporter } from 'ts/utils/error_reporter';
|
||||
@@ -33,9 +33,9 @@ interface FillOrderProps {
|
||||
networkId: number;
|
||||
userAddress: string;
|
||||
tokenByAddress: TokenByAddress;
|
||||
tokenStateByAddress: TokenStateByAddress;
|
||||
initialOrder: Order;
|
||||
dispatcher: Dispatcher;
|
||||
lastForceTokenStateRefetch: number;
|
||||
}
|
||||
|
||||
interface FillOrderState {
|
||||
@@ -185,7 +185,6 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
|
||||
symbol: takerToken.symbol,
|
||||
};
|
||||
const fillToken = this.props.tokenByAddress[takerToken.address];
|
||||
const fillTokenState = this.props.tokenStateByAddress[takerToken.address];
|
||||
const makerTokenAddress = this.state.parsedOrder.maker.token.address;
|
||||
const makerToken = this.props.tokenByAddress[makerTokenAddress];
|
||||
const makerAssetToken = {
|
||||
@@ -249,14 +248,17 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
|
||||
{!isUserMaker && (
|
||||
<div className="clearfix mx-auto relative" style={{ width: 235, height: 108 }}>
|
||||
<TokenAmountInput
|
||||
blockchain={this.props.blockchain}
|
||||
userAddress={this.props.userAddress}
|
||||
networkId={this.props.networkId}
|
||||
label="Fill amount"
|
||||
onChange={this._onFillAmountChange.bind(this)}
|
||||
shouldShowIncompleteErrs={false}
|
||||
token={fillToken}
|
||||
tokenState={fillTokenState}
|
||||
amount={fillAssetToken.amount}
|
||||
shouldCheckBalance={true}
|
||||
shouldCheckAllowance={true}
|
||||
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
|
||||
/>
|
||||
<div
|
||||
className="absolute sm-hide xs-hide"
|
||||
@@ -556,11 +558,8 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
|
||||
signedOrder,
|
||||
this.props.orderFillAmount,
|
||||
);
|
||||
// After fill completes, let's update the token balances
|
||||
const makerToken = this.props.tokenByAddress[parsedOrder.maker.token.address];
|
||||
const takerToken = this.props.tokenByAddress[parsedOrder.taker.token.address];
|
||||
const tokens = [makerToken, takerToken];
|
||||
await this.props.blockchain.updateTokenBalancesAndAllowancesAsync(tokens);
|
||||
// After fill completes, let's force fetch the token balances
|
||||
this.props.dispatcher.forceTokenStateRefetch();
|
||||
this.setState({
|
||||
isFilling: false,
|
||||
didFillOrderSucceed: true,
|
||||
|
||||
@@ -223,10 +223,7 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt
|
||||
assetView: AssetViews.NEW_TOKEN_FORM,
|
||||
});
|
||||
}
|
||||
private _onNewTokenSubmitted(newToken: Token, newTokenState: TokenState) {
|
||||
this.props.dispatcher.updateTokenStateByAddress({
|
||||
[newToken.address]: newTokenState,
|
||||
});
|
||||
private _onNewTokenSubmitted(newToken: Token) {
|
||||
trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newToken);
|
||||
this.props.dispatcher.addTokenToTokenByAddress(newToken);
|
||||
this.setState({
|
||||
@@ -256,15 +253,6 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt
|
||||
newTokenEntry.isTracked = true;
|
||||
trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry);
|
||||
|
||||
const [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(
|
||||
token.address,
|
||||
);
|
||||
this.props.dispatcher.updateTokenStateByAddress({
|
||||
[token.address]: {
|
||||
balance,
|
||||
allowance,
|
||||
},
|
||||
});
|
||||
this.props.dispatcher.updateTokenByAddress([newTokenEntry]);
|
||||
this.setState({
|
||||
isAddingTokenToTracked: false,
|
||||
|
||||
@@ -53,7 +53,7 @@ interface GenerateOrderFormProps {
|
||||
orderSalt: BigNumber;
|
||||
sideToAssetToken: SideToAssetToken;
|
||||
tokenByAddress: TokenByAddress;
|
||||
tokenStateByAddress: TokenStateByAddress;
|
||||
lastForceTokenStateRefetch: number;
|
||||
}
|
||||
|
||||
interface GenerateOrderFormState {
|
||||
@@ -80,10 +80,8 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G
|
||||
const dispatcher = this.props.dispatcher;
|
||||
const depositTokenAddress = this.props.sideToAssetToken[Side.Deposit].address;
|
||||
const depositToken = this.props.tokenByAddress[depositTokenAddress];
|
||||
const depositTokenState = this.props.tokenStateByAddress[depositTokenAddress];
|
||||
const receiveTokenAddress = this.props.sideToAssetToken[Side.Receive].address;
|
||||
const receiveToken = this.props.tokenByAddress[receiveTokenAddress];
|
||||
const receiveTokenState = this.props.tokenStateByAddress[receiveTokenAddress];
|
||||
const takerExplanation =
|
||||
'If a taker is specified, only they are<br> \
|
||||
allowed to fill this order. If no taker is<br> \
|
||||
@@ -110,9 +108,12 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G
|
||||
tokenByAddress={this.props.tokenByAddress}
|
||||
/>
|
||||
<TokenAmountInput
|
||||
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
|
||||
blockchain={this.props.blockchain}
|
||||
userAddress={this.props.userAddress}
|
||||
networkId={this.props.networkId}
|
||||
label="Sell amount"
|
||||
token={depositToken}
|
||||
tokenState={depositTokenState}
|
||||
amount={this.props.sideToAssetToken[Side.Deposit].amount}
|
||||
onChange={this._onTokenAmountChange.bind(this, depositToken, Side.Deposit)}
|
||||
shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs}
|
||||
@@ -139,9 +140,12 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G
|
||||
tokenByAddress={this.props.tokenByAddress}
|
||||
/>
|
||||
<TokenAmountInput
|
||||
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
|
||||
blockchain={this.props.blockchain}
|
||||
userAddress={this.props.userAddress}
|
||||
networkId={this.props.networkId}
|
||||
label="Receive amount"
|
||||
token={receiveToken}
|
||||
tokenState={receiveTokenState}
|
||||
amount={this.props.sideToAssetToken[Side.Receive].amount}
|
||||
onChange={this._onTokenAmountChange.bind(this, receiveToken, Side.Receive)}
|
||||
shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs}
|
||||
@@ -242,8 +246,10 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G
|
||||
|
||||
// Check if all required inputs were supplied
|
||||
const debitToken = this.props.sideToAssetToken[Side.Deposit];
|
||||
const debitBalance = this.props.tokenStateByAddress[debitToken.address].balance;
|
||||
const debitAllowance = this.props.tokenStateByAddress[debitToken.address].allowance;
|
||||
const [debitBalance, debitAllowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
|
||||
this.props.userAddress,
|
||||
debitToken.address,
|
||||
);
|
||||
const receiveAmount = this.props.sideToAssetToken[Side.Receive].amount;
|
||||
if (
|
||||
!_.isUndefined(debitToken.amount) &&
|
||||
|
||||
@@ -13,7 +13,7 @@ import { colors } from 'ts/utils/colors';
|
||||
interface NewTokenFormProps {
|
||||
blockchain: Blockchain;
|
||||
tokenByAddress: TokenByAddress;
|
||||
onNewTokenSubmitted: (token: Token, tokenState: TokenState) => void;
|
||||
onNewTokenSubmitted: (token: Token) => void;
|
||||
}
|
||||
|
||||
interface NewTokenFormState {
|
||||
@@ -110,13 +110,9 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor
|
||||
}
|
||||
|
||||
let hasBalanceAllowanceErr = false;
|
||||
let balance = new BigNumber(0);
|
||||
let allowance = new BigNumber(0);
|
||||
if (doesContractExist) {
|
||||
try {
|
||||
[balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(
|
||||
this.state.address,
|
||||
);
|
||||
await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(this.state.address);
|
||||
} catch (err) {
|
||||
hasBalanceAllowanceErr = true;
|
||||
}
|
||||
@@ -155,11 +151,7 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor
|
||||
isTracked: true,
|
||||
isRegistered: false,
|
||||
};
|
||||
const newTokenState: TokenState = {
|
||||
balance,
|
||||
allowance,
|
||||
};
|
||||
this.props.onNewTokenSubmitted(newToken, newTokenState);
|
||||
this.props.onNewTokenSubmitted(newToken);
|
||||
}
|
||||
private _onTokenNameChanged(e: any, name: string) {
|
||||
let nameErrText = '';
|
||||
|
||||
@@ -17,6 +17,8 @@ interface AllowanceToggleProps {
|
||||
token: Token;
|
||||
tokenState: TokenState;
|
||||
userAddress: string;
|
||||
isDisabled: boolean;
|
||||
refetchTokenStateAsync: () => Promise<void>;
|
||||
}
|
||||
|
||||
interface AllowanceToggleState {
|
||||
@@ -45,7 +47,7 @@ export class AllowanceToggle extends React.Component<AllowanceToggleProps, Allow
|
||||
<div className="flex">
|
||||
<div>
|
||||
<Toggle
|
||||
disabled={this.state.isSpinnerVisible}
|
||||
disabled={this.state.isSpinnerVisible || this.props.isDisabled}
|
||||
toggled={this._isAllowanceSet()}
|
||||
onToggle={this._onToggleAllowanceAsync.bind(this)}
|
||||
/>
|
||||
@@ -73,6 +75,7 @@ export class AllowanceToggle extends React.Component<AllowanceToggleProps, Allow
|
||||
}
|
||||
try {
|
||||
await this.props.blockchain.setProxyAllowanceAsync(this.props.token, newAllowanceAmountInBaseUnits);
|
||||
await this.props.refetchTokenStateAsync();
|
||||
} catch (err) {
|
||||
this.setState({
|
||||
isSpinnerVisible: false,
|
||||
|
||||
@@ -18,6 +18,7 @@ interface BalanceBoundedInputProps {
|
||||
validate?: (amount: BigNumber) => InputErrMsg;
|
||||
onVisitBalancesPageClick?: () => void;
|
||||
shouldHideVisitBalancesLink?: boolean;
|
||||
isDisabled?: boolean;
|
||||
}
|
||||
|
||||
interface BalanceBoundedInputState {
|
||||
@@ -29,6 +30,7 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp
|
||||
public static defaultProps: Partial<BalanceBoundedInputProps> = {
|
||||
shouldShowIncompleteErrs: false,
|
||||
shouldHideVisitBalancesLink: false,
|
||||
isDisabled: false,
|
||||
};
|
||||
constructor(props: BalanceBoundedInputProps) {
|
||||
super(props);
|
||||
@@ -88,6 +90,7 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp
|
||||
hintText={<span style={{ textTransform: 'capitalize' }}>amount</span>}
|
||||
onChange={this._onValueChange.bind(this)}
|
||||
underlineStyle={{ width: 'calc(100% + 50px)' }}
|
||||
disabled={this.props.isDisabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,13 +3,16 @@ import { BigNumber } from '@0xproject/utils';
|
||||
import * as _ from 'lodash';
|
||||
import * as React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Blockchain } from 'ts/blockchain';
|
||||
import { BalanceBoundedInput } from 'ts/components/inputs/balance_bounded_input';
|
||||
import { InputErrMsg, Token, TokenState, ValidatedBigNumberCallback, WebsitePaths } from 'ts/types';
|
||||
import { colors } from 'ts/utils/colors';
|
||||
|
||||
interface TokenAmountInputProps {
|
||||
userAddress: string;
|
||||
networkId: number;
|
||||
blockchain: Blockchain;
|
||||
token: Token;
|
||||
tokenState: TokenState;
|
||||
label?: string;
|
||||
amount?: BigNumber;
|
||||
shouldShowIncompleteErrs: boolean;
|
||||
@@ -17,11 +20,39 @@ interface TokenAmountInputProps {
|
||||
shouldCheckAllowance: boolean;
|
||||
onChange: ValidatedBigNumberCallback;
|
||||
onVisitBalancesPageClick?: () => void;
|
||||
lastForceTokenStateRefetch: number;
|
||||
}
|
||||
|
||||
interface TokenAmountInputState {}
|
||||
interface TokenAmountInputState {
|
||||
balance: BigNumber;
|
||||
allowance: BigNumber;
|
||||
isBalanceAndAllowanceLoaded: boolean;
|
||||
}
|
||||
|
||||
export class TokenAmountInput extends React.Component<TokenAmountInputProps, TokenAmountInputState> {
|
||||
constructor(props: TokenAmountInputProps) {
|
||||
super(props);
|
||||
const defaultAmount = new BigNumber(0);
|
||||
this.state = {
|
||||
balance: defaultAmount,
|
||||
allowance: defaultAmount,
|
||||
isBalanceAndAllowanceLoaded: false,
|
||||
};
|
||||
}
|
||||
public componentWillMount() {
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
this._fetchBalanceAndAllowanceAsync(this.props.token.address);
|
||||
}
|
||||
public componentWillReceiveProps(nextProps: TokenAmountInputProps) {
|
||||
if (
|
||||
nextProps.userAddress !== this.props.userAddress ||
|
||||
nextProps.networkId !== this.props.networkId ||
|
||||
nextProps.token.address !== this.props.token.address ||
|
||||
nextProps.lastForceTokenStateRefetch !== this.props.lastForceTokenStateRefetch
|
||||
) {
|
||||
this._fetchBalanceAndAllowanceAsync(nextProps.token.address);
|
||||
}
|
||||
}
|
||||
public render() {
|
||||
const amount = this.props.amount
|
||||
? ZeroEx.toUnitAmount(this.props.amount, this.props.token.decimals)
|
||||
@@ -32,12 +63,13 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok
|
||||
<BalanceBoundedInput
|
||||
label={this.props.label}
|
||||
amount={amount}
|
||||
balance={ZeroEx.toUnitAmount(this.props.tokenState.balance, this.props.token.decimals)}
|
||||
balance={ZeroEx.toUnitAmount(this.state.balance, this.props.token.decimals)}
|
||||
onChange={this._onChange.bind(this)}
|
||||
validate={this._validate.bind(this)}
|
||||
shouldCheckBalance={this.props.shouldCheckBalance}
|
||||
shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs}
|
||||
onVisitBalancesPageClick={this.props.onVisitBalancesPageClick}
|
||||
isDisabled={!this.state.isBalanceAndAllowanceLoaded}
|
||||
/>
|
||||
<div style={{ paddingTop: hasLabel ? 39 : 14 }}>{this.props.token.symbol}</div>
|
||||
</div>
|
||||
@@ -51,7 +83,7 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok
|
||||
this.props.onChange(isValid, baseUnitAmount);
|
||||
}
|
||||
private _validate(amount: BigNumber): InputErrMsg {
|
||||
if (this.props.shouldCheckAllowance && amount.gt(this.props.tokenState.allowance)) {
|
||||
if (this.props.shouldCheckAllowance && amount.gt(this.state.allowance)) {
|
||||
return (
|
||||
<span>
|
||||
Insufficient allowance.{' '}
|
||||
@@ -67,4 +99,18 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
private async _fetchBalanceAndAllowanceAsync(tokenAddress: string) {
|
||||
this.setState({
|
||||
isBalanceAndAllowanceLoaded: false,
|
||||
});
|
||||
const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
|
||||
this.props.userAddress,
|
||||
tokenAddress,
|
||||
);
|
||||
this.setState({
|
||||
balance,
|
||||
allowance,
|
||||
isBalanceAndAllowanceLoaded: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,13 +56,13 @@ export interface PortalAllProps {
|
||||
providerType: ProviderType;
|
||||
screenWidth: ScreenWidths;
|
||||
tokenByAddress: TokenByAddress;
|
||||
tokenStateByAddress: TokenStateByAddress;
|
||||
userEtherBalance: BigNumber;
|
||||
userAddress: string;
|
||||
shouldBlockchainErrDialogBeOpen: boolean;
|
||||
userSuppliedOrderCache: Order;
|
||||
location: Location;
|
||||
flashMessage?: string | React.ReactNode;
|
||||
lastForceTokenStateRefetch: number;
|
||||
}
|
||||
|
||||
interface PortalAllState {
|
||||
@@ -132,12 +132,6 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
|
||||
if (nextProps.userAddress !== this.state.prevUserAddress) {
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
this._blockchain.userAddressUpdatedFireAndForgetAsync(nextProps.userAddress);
|
||||
if (!_.isEmpty(nextProps.userAddress) && nextProps.blockchainIsLoaded) {
|
||||
const tokens = _.values(nextProps.tokenByAddress);
|
||||
const trackedTokens = _.filter(tokens, t => t.isTracked);
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
this._updateBalanceAndAllowanceWithLoadingScreenAsync(trackedTokens);
|
||||
}
|
||||
this.setState({
|
||||
prevUserAddress: nextProps.userAddress,
|
||||
});
|
||||
@@ -280,9 +274,9 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
|
||||
blockchain={this._blockchain}
|
||||
dispatcher={this.props.dispatcher}
|
||||
tokenByAddress={this.props.tokenByAddress}
|
||||
tokenStateByAddress={this.props.tokenStateByAddress}
|
||||
userAddress={this.props.userAddress}
|
||||
userEtherBalance={this.props.userEtherBalance}
|
||||
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -296,6 +290,8 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
|
||||
);
|
||||
}
|
||||
private _renderTokenBalances() {
|
||||
const allTokens = _.values(this.props.tokenByAddress);
|
||||
const trackedTokens = _.filter(allTokens, t => t.isTracked);
|
||||
return (
|
||||
<TokenBalances
|
||||
blockchain={this._blockchain}
|
||||
@@ -304,10 +300,11 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
|
||||
dispatcher={this.props.dispatcher}
|
||||
screenWidth={this.props.screenWidth}
|
||||
tokenByAddress={this.props.tokenByAddress}
|
||||
tokenStateByAddress={this.props.tokenStateByAddress}
|
||||
trackedTokens={trackedTokens}
|
||||
userAddress={this.props.userAddress}
|
||||
userEtherBalance={this.props.userEtherBalance}
|
||||
networkId={this.props.networkId}
|
||||
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -325,8 +322,8 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
|
||||
networkId={this.props.networkId}
|
||||
userAddress={this.props.userAddress}
|
||||
tokenByAddress={this.props.tokenByAddress}
|
||||
tokenStateByAddress={this.props.tokenStateByAddress}
|
||||
dispatcher={this.props.dispatcher}
|
||||
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -382,9 +379,4 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
|
||||
const newScreenWidth = utils.getScreenWidth();
|
||||
this.props.dispatcher.updateScreenWidth(newScreenWidth);
|
||||
}
|
||||
private async _updateBalanceAndAllowanceWithLoadingScreenAsync(tokens: Token[]) {
|
||||
this.props.dispatcher.updateBlockchainIsLoaded(false);
|
||||
await this._blockchain.updateTokenBalancesAndAllowancesAsync(tokens);
|
||||
this.props.dispatcher.updateBlockchainIsLoaded(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,15 @@ import { errorReporter } from 'ts/utils/error_reporter';
|
||||
import { utils } from 'ts/utils/utils';
|
||||
|
||||
interface SendButtonProps {
|
||||
userAddress: string;
|
||||
networkId: number;
|
||||
token: Token;
|
||||
tokenState: TokenState;
|
||||
dispatcher: Dispatcher;
|
||||
blockchain: Blockchain;
|
||||
onError: () => void;
|
||||
lastForceTokenStateRefetch: number;
|
||||
refetchTokenStateAsync: (tokenAddress: string) => Promise<void>;
|
||||
}
|
||||
|
||||
interface SendButtonState {
|
||||
@@ -42,11 +46,14 @@ export class SendButton extends React.Component<SendButtonProps, SendButtonState
|
||||
onClick={this._toggleSendDialog.bind(this)}
|
||||
/>
|
||||
<SendDialog
|
||||
blockchain={this.props.blockchain}
|
||||
userAddress={this.props.userAddress}
|
||||
networkId={this.props.networkId}
|
||||
isOpen={this.state.isSendDialogVisible}
|
||||
onComplete={this._onSendAmountSelectedAsync.bind(this)}
|
||||
onCancelled={this._toggleSendDialog.bind(this)}
|
||||
token={this.props.token}
|
||||
tokenState={this.props.tokenState}
|
||||
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -63,11 +70,9 @@ export class SendButton extends React.Component<SendButtonProps, SendButtonState
|
||||
this._toggleSendDialog();
|
||||
const token = this.props.token;
|
||||
const tokenState = this.props.tokenState;
|
||||
let balance = tokenState.balance;
|
||||
try {
|
||||
await this.props.blockchain.transferAsync(token, recipient, value);
|
||||
balance = balance.minus(value);
|
||||
this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance);
|
||||
this.props.refetchTokenStateAsync(token.address);
|
||||
} catch (err) {
|
||||
const errMsg = `${err}`;
|
||||
if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) {
|
||||
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
Styles,
|
||||
Token,
|
||||
TokenByAddress,
|
||||
TokenStateByAddress,
|
||||
TokenVisibility,
|
||||
} from 'ts/types';
|
||||
import { colors } from 'ts/utils/colors';
|
||||
@@ -58,6 +57,14 @@ const styles: Styles = {
|
||||
},
|
||||
};
|
||||
|
||||
interface TokenStateByAddress {
|
||||
[address: string]: {
|
||||
balance: BigNumber;
|
||||
allowance: BigNumber;
|
||||
isLoaded: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface TokenBalancesProps {
|
||||
blockchain: Blockchain;
|
||||
blockchainErr: BlockchainErrs;
|
||||
@@ -65,10 +72,11 @@ interface TokenBalancesProps {
|
||||
dispatcher: Dispatcher;
|
||||
screenWidth: ScreenWidths;
|
||||
tokenByAddress: TokenByAddress;
|
||||
tokenStateByAddress: TokenStateByAddress;
|
||||
trackedTokens: Token[];
|
||||
userAddress: string;
|
||||
userEtherBalance: BigNumber;
|
||||
networkId: number;
|
||||
lastForceTokenStateRefetch: number;
|
||||
}
|
||||
|
||||
interface TokenBalancesState {
|
||||
@@ -76,14 +84,15 @@ interface TokenBalancesState {
|
||||
isBalanceSpinnerVisible: boolean;
|
||||
isDharmaDialogVisible: boolean;
|
||||
isZRXSpinnerVisible: boolean;
|
||||
currentZrxBalance?: BigNumber;
|
||||
isTokenPickerOpen: boolean;
|
||||
isAddingToken: boolean;
|
||||
trackedTokenStateByAddress: TokenStateByAddress;
|
||||
}
|
||||
|
||||
export class TokenBalances extends React.Component<TokenBalancesProps, TokenBalancesState> {
|
||||
public constructor(props: TokenBalancesProps) {
|
||||
super(props);
|
||||
const initialTrackedTokenStateByAddress = this._getInitialTrackedTokenStateByAddress(props.trackedTokens);
|
||||
this.state = {
|
||||
errorType: undefined,
|
||||
isBalanceSpinnerVisible: false,
|
||||
@@ -91,8 +100,14 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
|
||||
isDharmaDialogVisible: DharmaLoanFrame.isAuthTokenPresent(),
|
||||
isTokenPickerOpen: false,
|
||||
isAddingToken: false,
|
||||
trackedTokenStateByAddress: initialTrackedTokenStateByAddress,
|
||||
};
|
||||
}
|
||||
public componentWillMount() {
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress);
|
||||
this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses);
|
||||
}
|
||||
public componentWillReceiveProps(nextProps: TokenBalancesProps) {
|
||||
if (nextProps.userEtherBalance !== this.props.userEtherBalance) {
|
||||
if (this.state.isBalanceSpinnerVisible) {
|
||||
@@ -103,18 +118,21 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
|
||||
isBalanceSpinnerVisible: false,
|
||||
});
|
||||
}
|
||||
const nextZrxToken = _.find(_.values(nextProps.tokenByAddress), t => t.symbol === ZRX_TOKEN_SYMBOL);
|
||||
const nextZrxTokenBalance = nextProps.tokenStateByAddress[nextZrxToken.address].balance;
|
||||
if (!_.isUndefined(this.state.currentZrxBalance) && !nextZrxTokenBalance.eq(this.state.currentZrxBalance)) {
|
||||
if (this.state.isZRXSpinnerVisible) {
|
||||
const receivedAmount = nextZrxTokenBalance.minus(this.state.currentZrxBalance);
|
||||
const receiveAmountInUnits = ZeroEx.toUnitAmount(receivedAmount, constants.DECIMAL_PLACES_ZRX);
|
||||
this.props.dispatcher.showFlashMessage(`Received ${receiveAmountInUnits.toString(10)} Kovan ZRX`);
|
||||
}
|
||||
this.setState({
|
||||
isZRXSpinnerVisible: false,
|
||||
currentZrxBalance: undefined,
|
||||
});
|
||||
|
||||
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);
|
||||
this._fetchBalancesAndAllowancesAsync(newTokenAddresses);
|
||||
}
|
||||
}
|
||||
public componentDidMount() {
|
||||
@@ -303,8 +321,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
|
||||
const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm;
|
||||
const tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG;
|
||||
const actionPaddingX = isSmallScreen ? 2 : 24;
|
||||
const allTokens = _.values(this.props.tokenByAddress);
|
||||
const trackedTokens = _.filter(allTokens, t => t.isTracked);
|
||||
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)
|
||||
@@ -317,7 +334,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
|
||||
return tableRows;
|
||||
}
|
||||
private _renderTokenRow(tokenColSpan: number, actionPaddingX: number, token: Token) {
|
||||
const tokenState = this.props.tokenStateByAddress[token.address];
|
||||
const tokenState = this.state.trackedTokenStateByAddress[token.address];
|
||||
const tokenLink = utils.getEtherScanLinkIfExists(
|
||||
token.address,
|
||||
this.props.networkId,
|
||||
@@ -338,13 +355,19 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
|
||||
)}
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={{ paddingRight: 3, paddingLeft: 3 }}>
|
||||
{this._renderAmount(tokenState.balance, token.decimals)} {token.symbol}
|
||||
{this.state.isZRXSpinnerVisible &&
|
||||
token.symbol === ZRX_TOKEN_SYMBOL && (
|
||||
<span className="pl1">
|
||||
<i className="zmdi zmdi-spinner zmdi-hc-spin" />
|
||||
</span>
|
||||
)}
|
||||
{tokenState.isLoaded ? (
|
||||
<span>
|
||||
{this._renderAmount(tokenState.balance, token.decimals)} {token.symbol}
|
||||
{this.state.isZRXSpinnerVisible &&
|
||||
token.symbol === ZRX_TOKEN_SYMBOL && (
|
||||
<span className="pl1">
|
||||
<i className="zmdi zmdi-spinner zmdi-hc-spin" />
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
) : (
|
||||
<i className="zmdi zmdi-spinner zmdi-hc-spin" />
|
||||
)}
|
||||
</TableRowColumn>
|
||||
<TableRowColumn>
|
||||
<AllowanceToggle
|
||||
@@ -354,6 +377,8 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
|
||||
tokenState={tokenState}
|
||||
onErrorOccurred={this._onErrorOccurred.bind(this)}
|
||||
userAddress={this.props.userAddress}
|
||||
isDisabled={!tokenState.isLoaded}
|
||||
refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this, token.address)}
|
||||
/>
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={{ paddingLeft: actionPaddingX, paddingRight: actionPaddingX }}>
|
||||
@@ -383,11 +408,15 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
|
||||
}}
|
||||
>
|
||||
<SendButton
|
||||
userAddress={this.props.userAddress}
|
||||
networkId={this.props.networkId}
|
||||
blockchain={this.props.blockchain}
|
||||
dispatcher={this.props.dispatcher}
|
||||
token={token}
|
||||
tokenState={tokenState}
|
||||
onError={this._onSendFailed.bind(this)}
|
||||
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
|
||||
refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this, token.address)}
|
||||
/>
|
||||
</TableRowColumn>
|
||||
)}
|
||||
@@ -414,7 +443,6 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
|
||||
} else {
|
||||
this.props.dispatcher.removeTokenToTokenByAddress(token);
|
||||
}
|
||||
this.props.dispatcher.removeFromTokenStateByAddress(tokenAddress);
|
||||
trackedTokenStorage.removeTrackedToken(this.props.userAddress, this.props.networkId, tokenAddress);
|
||||
} else if (isDefaultTrackedToken) {
|
||||
this.props.dispatcher.showFlashMessage(`Cannot remove ${token.name} because it's a default token`);
|
||||
@@ -510,6 +538,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
|
||||
private async _onMintTestTokensAsync(token: Token): Promise<boolean> {
|
||||
try {
|
||||
await this.props.blockchain.mintTestTokensAsync(token);
|
||||
await this._refetchTokenStateAsync(token.address);
|
||||
const amount = ZeroEx.toUnitAmount(constants.MINT_AMOUNT, token.decimals);
|
||||
this.props.dispatcher.showFlashMessage(`Successfully minted ${amount.toString(10)} ${token.symbol}`);
|
||||
return true;
|
||||
@@ -569,15 +598,11 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
|
||||
isBalanceSpinnerVisible: true,
|
||||
});
|
||||
} else {
|
||||
const tokens = _.values(this.props.tokenByAddress);
|
||||
const zrxToken = _.find(tokens, t => t.symbol === ZRX_TOKEN_SYMBOL);
|
||||
const zrxTokenState = this.props.tokenStateByAddress[zrxToken.address];
|
||||
this.setState({
|
||||
isZRXSpinnerVisible: true,
|
||||
currentZrxBalance: zrxTokenState.balance,
|
||||
});
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
this.props.blockchain.pollTokenBalanceAsync(zrxToken);
|
||||
this._startPollingZrxBalanceAsync();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -603,4 +628,63 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
|
||||
isAddingToken: false,
|
||||
});
|
||||
}
|
||||
private async _startPollingZrxBalanceAsync() {
|
||||
const tokens = _.values(this.props.tokenByAddress);
|
||||
const zrxToken = _.find(tokens, t => t.symbol === ZRX_TOKEN_SYMBOL);
|
||||
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
const balance = await this.props.blockchain.pollTokenBalanceAsync(zrxToken);
|
||||
const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress;
|
||||
trackedTokenStateByAddress[zrxToken.address] = {
|
||||
...trackedTokenStateByAddress[zrxToken.address],
|
||||
balance,
|
||||
};
|
||||
this.setState({
|
||||
isZRXSpinnerVisible: false,
|
||||
});
|
||||
}
|
||||
private async _fetchBalancesAndAllowancesAsync(tokenAddresses: string[]) {
|
||||
const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress;
|
||||
for (const tokenAddress of tokenAddresses) {
|
||||
const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
|
||||
this.props.userAddress,
|
||||
tokenAddress,
|
||||
);
|
||||
trackedTokenStateByAddress[tokenAddress] = {
|
||||
balance,
|
||||
allowance,
|
||||
isLoaded: true,
|
||||
};
|
||||
}
|
||||
this.setState({
|
||||
trackedTokenStateByAddress,
|
||||
});
|
||||
}
|
||||
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 _refetchTokenStateAsync(tokenAddress: string) {
|
||||
const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
|
||||
this.props.userAddress,
|
||||
tokenAddress,
|
||||
);
|
||||
this.setState({
|
||||
trackedTokenStateByAddress: {
|
||||
...this.state.trackedTokenStateByAddress,
|
||||
[tokenAddress]: {
|
||||
balance,
|
||||
allowance,
|
||||
isLoaded: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
} // tslint:disable:max-file-line-count
|
||||
|
||||
@@ -81,7 +81,7 @@ export class ProviderPicker extends React.Component<ProviderPickerProps, Provide
|
||||
if (value === ProviderType.Ledger) {
|
||||
this.props.onToggleLedgerDialog();
|
||||
} else {
|
||||
// Fire and forget
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
this.props.blockchain.updateProviderToInjectedAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ interface ConnectedState {
|
||||
networkId: number;
|
||||
sideToAssetToken: SideToAssetToken;
|
||||
tokenByAddress: TokenByAddress;
|
||||
tokenStateByAddress: TokenStateByAddress;
|
||||
lastForceTokenStateRefetch: number;
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: State, ownProps: GenerateOrderFormProps): ConnectedState => ({
|
||||
@@ -45,8 +45,8 @@ const mapStateToProps = (state: State, ownProps: GenerateOrderFormProps): Connec
|
||||
networkId: state.networkId,
|
||||
sideToAssetToken: state.sideToAssetToken,
|
||||
tokenByAddress: state.tokenByAddress,
|
||||
tokenStateByAddress: state.tokenStateByAddress,
|
||||
userAddress: state.userAddress,
|
||||
lastForceTokenStateRefetch: state.lastForceTokenStateRefetch,
|
||||
});
|
||||
|
||||
export const GenerateOrderForm: React.ComponentClass<GenerateOrderFormProps> = connect(mapStateToProps)(
|
||||
|
||||
@@ -28,7 +28,7 @@ interface ConnectedState {
|
||||
orderFillAmount: BigNumber;
|
||||
providerType: ProviderType;
|
||||
tokenByAddress: TokenByAddress;
|
||||
tokenStateByAddress: TokenStateByAddress;
|
||||
lastForceTokenStateRefetch: number;
|
||||
userEtherBalance: BigNumber;
|
||||
screenWidth: ScreenWidths;
|
||||
shouldBlockchainErrDialogBeOpen: boolean;
|
||||
@@ -77,7 +77,7 @@ const mapStateToProps = (state: State, ownProps: PortalComponentAllProps): Conne
|
||||
screenWidth: state.screenWidth,
|
||||
shouldBlockchainErrDialogBeOpen: state.shouldBlockchainErrDialogBeOpen,
|
||||
tokenByAddress: state.tokenByAddress,
|
||||
tokenStateByAddress: state.tokenStateByAddress,
|
||||
lastForceTokenStateRefetch: state.lastForceTokenStateRefetch,
|
||||
userAddress: state.userAddress,
|
||||
userEtherBalance: state.userEtherBalance,
|
||||
userSuppliedOrderCache: state.userSuppliedOrderCache,
|
||||
|
||||
@@ -131,43 +131,9 @@ export class Dispatcher {
|
||||
type: ActionTypes.UpdateTokenByAddress,
|
||||
});
|
||||
}
|
||||
public updateTokenStateByAddress(tokenStateByAddress: TokenStateByAddress) {
|
||||
public forceTokenStateRefetch() {
|
||||
this._dispatch({
|
||||
data: tokenStateByAddress,
|
||||
type: ActionTypes.UpdateTokenStateByAddress,
|
||||
});
|
||||
}
|
||||
public removeFromTokenStateByAddress(tokenAddress: string) {
|
||||
this._dispatch({
|
||||
data: tokenAddress,
|
||||
type: ActionTypes.RemoveFromTokenStateByAddress,
|
||||
});
|
||||
}
|
||||
public replaceTokenAllowanceByAddress(address: string, allowance: BigNumber) {
|
||||
this._dispatch({
|
||||
data: {
|
||||
address,
|
||||
allowance,
|
||||
},
|
||||
type: ActionTypes.ReplaceTokenAllowanceByAddress,
|
||||
});
|
||||
}
|
||||
public replaceTokenBalanceByAddress(address: string, balance: BigNumber) {
|
||||
this._dispatch({
|
||||
data: {
|
||||
address,
|
||||
balance,
|
||||
},
|
||||
type: ActionTypes.ReplaceTokenBalanceByAddress,
|
||||
});
|
||||
}
|
||||
public updateTokenBalanceByAddress(address: string, balanceDelta: BigNumber) {
|
||||
this._dispatch({
|
||||
data: {
|
||||
address,
|
||||
balanceDelta,
|
||||
},
|
||||
type: ActionTypes.UpdateTokenBalanceByAddress,
|
||||
type: ActionTypes.ForceTokenStateRefetch,
|
||||
});
|
||||
}
|
||||
public updateSignatureData(signatureData: SignatureData) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ZeroEx } from '0x.js';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as _ from 'lodash';
|
||||
import * as moment from 'moment';
|
||||
import {
|
||||
Action,
|
||||
ActionTypes,
|
||||
@@ -37,7 +38,7 @@ export interface State {
|
||||
shouldBlockchainErrDialogBeOpen: boolean;
|
||||
sideToAssetToken: SideToAssetToken;
|
||||
tokenByAddress: TokenByAddress;
|
||||
tokenStateByAddress: TokenStateByAddress;
|
||||
lastForceTokenStateRefetch: number;
|
||||
userAddress: string;
|
||||
userEtherBalance: BigNumber;
|
||||
// Note: cache of supplied orderJSON in fill order step. Do not use for anything else.
|
||||
@@ -76,7 +77,7 @@ const INITIAL_STATE: State = {
|
||||
[Side.Receive]: {},
|
||||
},
|
||||
tokenByAddress: {},
|
||||
tokenStateByAddress: {},
|
||||
lastForceTokenStateRefetch: moment().unix(),
|
||||
userAddress: '',
|
||||
userEtherBalance: new BigNumber(0),
|
||||
userSuppliedOrderCache: undefined,
|
||||
@@ -180,74 +181,11 @@ export function reducer(state: State = INITIAL_STATE, action: Action) {
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.UpdateTokenStateByAddress: {
|
||||
const tokenStateByAddress = state.tokenStateByAddress;
|
||||
const updatedTokenStateByAddress = action.data;
|
||||
_.each(updatedTokenStateByAddress, (tokenState: TokenState, address: string) => {
|
||||
const updatedTokenState = {
|
||||
...tokenStateByAddress[address],
|
||||
...tokenState,
|
||||
};
|
||||
tokenStateByAddress[address] = updatedTokenState;
|
||||
});
|
||||
case ActionTypes.ForceTokenStateRefetch:
|
||||
return {
|
||||
...state,
|
||||
tokenStateByAddress,
|
||||
lastForceTokenStateRefetch: moment().unix(),
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.RemoveFromTokenStateByAddress: {
|
||||
const tokenStateByAddress = state.tokenStateByAddress;
|
||||
const tokenAddress = action.data;
|
||||
delete tokenStateByAddress[tokenAddress];
|
||||
return {
|
||||
...state,
|
||||
tokenStateByAddress,
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.ReplaceTokenAllowanceByAddress: {
|
||||
const tokenStateByAddress = state.tokenStateByAddress;
|
||||
const allowance = action.data.allowance;
|
||||
const tokenAddress = action.data.address;
|
||||
tokenStateByAddress[tokenAddress] = {
|
||||
...tokenStateByAddress[tokenAddress],
|
||||
allowance,
|
||||
};
|
||||
return {
|
||||
...state,
|
||||
tokenStateByAddress,
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.ReplaceTokenBalanceByAddress: {
|
||||
const tokenStateByAddress = state.tokenStateByAddress;
|
||||
const balance = action.data.balance;
|
||||
const tokenAddress = action.data.address;
|
||||
tokenStateByAddress[tokenAddress] = {
|
||||
...tokenStateByAddress[tokenAddress],
|
||||
balance,
|
||||
};
|
||||
return {
|
||||
...state,
|
||||
tokenStateByAddress,
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.UpdateTokenBalanceByAddress: {
|
||||
const tokenStateByAddress = state.tokenStateByAddress;
|
||||
const balanceDelta = action.data.balanceDelta;
|
||||
const tokenAddress = action.data.address;
|
||||
const currBalance = tokenStateByAddress[tokenAddress].balance;
|
||||
tokenStateByAddress[tokenAddress] = {
|
||||
...tokenStateByAddress[tokenAddress],
|
||||
balance: currBalance.plus(balanceDelta),
|
||||
};
|
||||
return {
|
||||
...state,
|
||||
tokenStateByAddress,
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.UpdateOrderSignatureData: {
|
||||
return {
|
||||
|
||||
@@ -125,11 +125,7 @@ export enum ActionTypes {
|
||||
UpdateOrderSignatureData = 'UPDATE_ORDER_SIGNATURE_DATA',
|
||||
UpdateTokenByAddress = 'UPDATE_TOKEN_BY_ADDRESS',
|
||||
RemoveTokenFromTokenByAddress = 'REMOVE_TOKEN_FROM_TOKEN_BY_ADDRESS',
|
||||
UpdateTokenStateByAddress = 'UPDATE_TOKEN_STATE_BY_ADDRESS',
|
||||
RemoveFromTokenStateByAddress = 'REMOVE_FROM_TOKEN_STATE_BY_ADDRESS',
|
||||
ReplaceTokenAllowanceByAddress = 'REPLACE_TOKEN_ALLOWANCE_BY_ADDRESS',
|
||||
ReplaceTokenBalanceByAddress = 'REPLACE_TOKEN_BALANCE_BY_ADDRESS',
|
||||
UpdateTokenBalanceByAddress = 'UPDATE_TOKEN_BALANCE_BY_ADDRESS',
|
||||
ForceTokenStateRefetch = 'FORCE_TOKEN_STATE_REFETCH',
|
||||
UpdateOrderExpiry = 'UPDATE_ORDER_EXPIRY',
|
||||
SwapAssetTokens = 'SWAP_ASSET_TOKENS',
|
||||
UpdateUserAddress = 'UPDATE_USER_ADDRESS',
|
||||
|
||||
Reference in New Issue
Block a user