Merge pull request #351 from 0xProject/feature/portal-ledger-support
Portal Ledger Support, Lazy-loading token balances/allowances
@@ -36,7 +36,6 @@
 | 
			
		||||
        "find-versions": "^2.0.0",
 | 
			
		||||
        "is-mobile": "^0.2.2",
 | 
			
		||||
        "jsonschema": "^1.2.0",
 | 
			
		||||
        "ledgerco": "0xProject/ledger-node-js-api",
 | 
			
		||||
        "less": "^2.7.2",
 | 
			
		||||
        "lodash": "^4.17.4",
 | 
			
		||||
        "material-ui": "^0.17.1",
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 888 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								packages/website/public/images/ledger_icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.8 KiB  | 
| 
		 Before Width: | Height: | Size: 2.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								packages/website/public/images/metamask_or_parity.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 22 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								packages/website/public/images/network_icons/kovan.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 244 B  | 
							
								
								
									
										
											BIN
										
									
								
								packages/website/public/images/network_icons/mainnet.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 205 B  | 
							
								
								
									
										
											BIN
										
									
								
								packages/website/public/images/network_icons/rinkeby.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 126 B  | 
							
								
								
									
										
											BIN
										
									
								
								packages/website/public/images/network_icons/ropsten.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 251 B  | 
@@ -37,10 +37,10 @@ import {
 | 
			
		||||
    EtherscanLinkSuffixes,
 | 
			
		||||
    ProviderType,
 | 
			
		||||
    Side,
 | 
			
		||||
    SideToAssetToken,
 | 
			
		||||
    SignatureData,
 | 
			
		||||
    Token,
 | 
			
		||||
    TokenByAddress,
 | 
			
		||||
    TokenStateByAddress,
 | 
			
		||||
} from 'ts/types';
 | 
			
		||||
import { configs } from 'ts/utils/configs';
 | 
			
		||||
import { constants } from 'ts/utils/constants';
 | 
			
		||||
@@ -54,6 +54,7 @@ import FilterSubprovider = require('web3-provider-engine/subproviders/filters');
 | 
			
		||||
import * as MintableArtifacts from '../contracts/Mintable.json';
 | 
			
		||||
 | 
			
		||||
const BLOCK_NUMBER_BACK_TRACK = 50;
 | 
			
		||||
const GWEI_IN_WEI = 1000000000;
 | 
			
		||||
 | 
			
		||||
export class Blockchain {
 | 
			
		||||
    public networkId: number;
 | 
			
		||||
@@ -64,8 +65,9 @@ export class Blockchain {
 | 
			
		||||
    private _exchangeAddress: string;
 | 
			
		||||
    private _userAddress: string;
 | 
			
		||||
    private _cachedProvider: Web3.Provider;
 | 
			
		||||
    private _cachedProviderNetworkId: number;
 | 
			
		||||
    private _ledgerSubprovider: LedgerWalletSubprovider;
 | 
			
		||||
    private _zrxPollIntervalId: NodeJS.Timer;
 | 
			
		||||
    private _defaultGasPrice: BigNumber;
 | 
			
		||||
    private static async _onPageLoadAsync(): Promise<void> {
 | 
			
		||||
        if (document.readyState === 'complete') {
 | 
			
		||||
            return; // Already loaded
 | 
			
		||||
@@ -111,7 +113,7 @@ export class Blockchain {
 | 
			
		||||
            // injected into their browser.
 | 
			
		||||
            provider = new ProviderEngine();
 | 
			
		||||
            provider.addProvider(new FilterSubprovider());
 | 
			
		||||
            const networkId = configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_TESTNET;
 | 
			
		||||
            const networkId = configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_KOVAN;
 | 
			
		||||
            provider.addProvider(new RedundantRPCSubprovider(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId]));
 | 
			
		||||
            provider.start();
 | 
			
		||||
        }
 | 
			
		||||
@@ -121,6 +123,10 @@ export class Blockchain {
 | 
			
		||||
    constructor(dispatcher: Dispatcher, isSalePage: boolean = false) {
 | 
			
		||||
        this._dispatcher = dispatcher;
 | 
			
		||||
        this._userAddress = '';
 | 
			
		||||
        const defaultGasPrice = GWEI_IN_WEI * 30;
 | 
			
		||||
        this._defaultGasPrice = new BigNumber(defaultGasPrice);
 | 
			
		||||
        // tslint:disable-next-line:no-floating-promises
 | 
			
		||||
        this._updateDefaultGasPriceAsync();
 | 
			
		||||
        // tslint:disable-next-line:no-floating-promises
 | 
			
		||||
        this._onPageLoadInitFireAndForgetAsync();
 | 
			
		||||
    }
 | 
			
		||||
@@ -133,14 +139,14 @@ export class Blockchain {
 | 
			
		||||
        } else if (this.networkId !== newNetworkId) {
 | 
			
		||||
            this.networkId = newNetworkId;
 | 
			
		||||
            this._dispatcher.encounteredBlockchainError(BlockchainErrs.NoError);
 | 
			
		||||
            await this._fetchTokenInformationAsync();
 | 
			
		||||
            await this.fetchTokenInformationAsync();
 | 
			
		||||
            await this._rehydrateStoreWithContractEvents();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    public async userAddressUpdatedFireAndForgetAsync(newUserAddress: string) {
 | 
			
		||||
        if (this._userAddress !== newUserAddress) {
 | 
			
		||||
            this._userAddress = newUserAddress;
 | 
			
		||||
            await this._fetchTokenInformationAsync();
 | 
			
		||||
            await this.fetchTokenInformationAsync();
 | 
			
		||||
            await this._rehydrateStoreWithContractEvents();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -180,84 +186,96 @@ export class Blockchain {
 | 
			
		||||
        }
 | 
			
		||||
        this._ledgerSubprovider.setPathIndex(pathIndex);
 | 
			
		||||
    }
 | 
			
		||||
    public async providerTypeUpdatedFireAndForgetAsync(providerType: ProviderType) {
 | 
			
		||||
    public async updateProviderToLedgerAsync(networkId: number) {
 | 
			
		||||
        utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
 | 
			
		||||
        // Should actually be Web3.Provider|ProviderEngine union type but it causes issues
 | 
			
		||||
        // later on in the logic.
 | 
			
		||||
        let provider;
 | 
			
		||||
        switch (providerType) {
 | 
			
		||||
            case ProviderType.Ledger: {
 | 
			
		||||
                const isU2FSupported = await utils.isU2FSupportedAsync();
 | 
			
		||||
                if (!isU2FSupported) {
 | 
			
		||||
                    throw new Error('Cannot update providerType to LEDGER without U2F support');
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Cache injected provider so that we can switch the user back to it easily
 | 
			
		||||
                this._cachedProvider = this._web3Wrapper.getProviderObj();
 | 
			
		||||
 | 
			
		||||
                this._dispatcher.updateUserAddress(''); // Clear old userAddress
 | 
			
		||||
 | 
			
		||||
                provider = new ProviderEngine();
 | 
			
		||||
                const ledgerWalletConfigs = {
 | 
			
		||||
                    networkId: this.networkId,
 | 
			
		||||
                    ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync,
 | 
			
		||||
                };
 | 
			
		||||
                this._ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs);
 | 
			
		||||
                provider.addProvider(this._ledgerSubprovider);
 | 
			
		||||
                provider.addProvider(new FilterSubprovider());
 | 
			
		||||
                const networkId = configs.IS_MAINNET_ENABLED
 | 
			
		||||
                    ? constants.NETWORK_ID_MAINNET
 | 
			
		||||
                    : constants.NETWORK_ID_TESTNET;
 | 
			
		||||
                provider.addProvider(new RedundantRPCSubprovider(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId]));
 | 
			
		||||
                provider.start();
 | 
			
		||||
                this._web3Wrapper.destroy();
 | 
			
		||||
                const shouldPollUserAddress = false;
 | 
			
		||||
                this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress);
 | 
			
		||||
                this._zeroEx.setProvider(provider, networkId);
 | 
			
		||||
                await this._postInstantiationOrUpdatingProviderZeroExAsync();
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            case ProviderType.Injected: {
 | 
			
		||||
                if (_.isUndefined(this._cachedProvider)) {
 | 
			
		||||
                    return; // Going from injected to injected, so we noop
 | 
			
		||||
                }
 | 
			
		||||
                provider = this._cachedProvider;
 | 
			
		||||
                const shouldPollUserAddress = true;
 | 
			
		||||
                this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress);
 | 
			
		||||
                this._zeroEx.setProvider(provider, this.networkId);
 | 
			
		||||
                await this._postInstantiationOrUpdatingProviderZeroExAsync();
 | 
			
		||||
                delete this._ledgerSubprovider;
 | 
			
		||||
                delete this._cachedProvider;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            default:
 | 
			
		||||
                throw utils.spawnSwitchErr('providerType', providerType);
 | 
			
		||||
        const isU2FSupported = await utils.isU2FSupportedAsync();
 | 
			
		||||
        if (!isU2FSupported) {
 | 
			
		||||
            throw new Error('Cannot update providerType to LEDGER without U2F support');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await this._fetchTokenInformationAsync();
 | 
			
		||||
        // Cache injected provider so that we can switch the user back to it easily
 | 
			
		||||
        if (_.isUndefined(this._cachedProvider)) {
 | 
			
		||||
            this._cachedProvider = this._web3Wrapper.getProviderObj();
 | 
			
		||||
            this._cachedProviderNetworkId = this.networkId;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this._web3Wrapper.destroy();
 | 
			
		||||
 | 
			
		||||
        this._userAddress = '';
 | 
			
		||||
        this._dispatcher.updateUserAddress(''); // Clear old userAddress
 | 
			
		||||
 | 
			
		||||
        const provider = new ProviderEngine();
 | 
			
		||||
        const ledgerWalletConfigs = {
 | 
			
		||||
            networkId,
 | 
			
		||||
            ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync,
 | 
			
		||||
        };
 | 
			
		||||
        this._ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs);
 | 
			
		||||
        provider.addProvider(this._ledgerSubprovider);
 | 
			
		||||
        provider.addProvider(new FilterSubprovider());
 | 
			
		||||
        provider.addProvider(new RedundantRPCSubprovider(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId]));
 | 
			
		||||
        provider.start();
 | 
			
		||||
        this.networkId = networkId;
 | 
			
		||||
        this._dispatcher.updateNetworkId(this.networkId);
 | 
			
		||||
        const shouldPollUserAddress = false;
 | 
			
		||||
        this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress);
 | 
			
		||||
        this._zeroEx.setProvider(provider, this.networkId);
 | 
			
		||||
        await this._postInstantiationOrUpdatingProviderZeroExAsync();
 | 
			
		||||
        this._web3Wrapper.startEmittingNetworkConnectionAndUserBalanceState();
 | 
			
		||||
        this._dispatcher.updateProviderType(ProviderType.Ledger);
 | 
			
		||||
    }
 | 
			
		||||
    public async updateProviderToInjectedAsync() {
 | 
			
		||||
        utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
 | 
			
		||||
 | 
			
		||||
        if (_.isUndefined(this._cachedProvider)) {
 | 
			
		||||
            return; // Going from injected to injected, so we noop
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this._web3Wrapper.destroy();
 | 
			
		||||
 | 
			
		||||
        const provider = this._cachedProvider;
 | 
			
		||||
        this.networkId = this._cachedProviderNetworkId;
 | 
			
		||||
 | 
			
		||||
        const shouldPollUserAddress = true;
 | 
			
		||||
        this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress);
 | 
			
		||||
 | 
			
		||||
        this._userAddress = await this._web3Wrapper.getFirstAccountIfExistsAsync();
 | 
			
		||||
 | 
			
		||||
        this._zeroEx.setProvider(provider, this.networkId);
 | 
			
		||||
        await this._postInstantiationOrUpdatingProviderZeroExAsync();
 | 
			
		||||
 | 
			
		||||
        await this.fetchTokenInformationAsync();
 | 
			
		||||
        this._web3Wrapper.startEmittingNetworkConnectionAndUserBalanceState();
 | 
			
		||||
        this._dispatcher.updateProviderType(ProviderType.Injected);
 | 
			
		||||
        delete this._ledgerSubprovider;
 | 
			
		||||
        delete this._cachedProvider;
 | 
			
		||||
    }
 | 
			
		||||
    public async setProxyAllowanceAsync(token: Token, amountInBaseUnits: BigNumber): Promise<void> {
 | 
			
		||||
        utils.assert(this.isValidAddress(token.address), BlockchainCallErrs.TokenAddressIsInvalid);
 | 
			
		||||
        utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
 | 
			
		||||
        utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
 | 
			
		||||
 | 
			
		||||
        this._showFlashMessageIfLedger();
 | 
			
		||||
        const txHash = await this._zeroEx.token.setProxyAllowanceAsync(
 | 
			
		||||
            token.address,
 | 
			
		||||
            this._userAddress,
 | 
			
		||||
            amountInBaseUnits,
 | 
			
		||||
            {
 | 
			
		||||
                gasPrice: this._defaultGasPrice,
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
        await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
 | 
			
		||||
        const allowance = amountInBaseUnits;
 | 
			
		||||
        this._dispatcher.replaceTokenAllowanceByAddress(token.address, allowance);
 | 
			
		||||
    }
 | 
			
		||||
    public async transferAsync(token: Token, toAddress: string, amountInBaseUnits: BigNumber): Promise<void> {
 | 
			
		||||
        this._showFlashMessageIfLedger();
 | 
			
		||||
        const txHash = await this._zeroEx.token.transferAsync(
 | 
			
		||||
            token.address,
 | 
			
		||||
            this._userAddress,
 | 
			
		||||
            toAddress,
 | 
			
		||||
            amountInBaseUnits,
 | 
			
		||||
            {
 | 
			
		||||
                gasPrice: this._defaultGasPrice,
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
        await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
 | 
			
		||||
        const etherScanLinkIfExists = utils.getEtherScanLinkIfExists(txHash, this.networkId, EtherscanLinkSuffixes.Tx);
 | 
			
		||||
@@ -309,11 +327,15 @@ export class Blockchain {
 | 
			
		||||
 | 
			
		||||
        const shouldThrowOnInsufficientBalanceOrAllowance = true;
 | 
			
		||||
 | 
			
		||||
        this._showFlashMessageIfLedger();
 | 
			
		||||
        const txHash = await this._zeroEx.exchange.fillOrderAsync(
 | 
			
		||||
            signedOrder,
 | 
			
		||||
            fillTakerTokenAmount,
 | 
			
		||||
            shouldThrowOnInsufficientBalanceOrAllowance,
 | 
			
		||||
            this._userAddress,
 | 
			
		||||
            {
 | 
			
		||||
                gasPrice: this._defaultGasPrice,
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
        const receipt = await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
 | 
			
		||||
        const logs: Array<LogWithDecodedArgs<ExchangeContractEventArgs>> = receipt.logs as any;
 | 
			
		||||
@@ -324,7 +346,10 @@ export class Blockchain {
 | 
			
		||||
        return filledTakerTokenAmount;
 | 
			
		||||
    }
 | 
			
		||||
    public async cancelOrderAsync(signedOrder: SignedOrder, cancelTakerTokenAmount: BigNumber): Promise<BigNumber> {
 | 
			
		||||
        const txHash = await this._zeroEx.exchange.cancelOrderAsync(signedOrder, cancelTakerTokenAmount);
 | 
			
		||||
        this._showFlashMessageIfLedger();
 | 
			
		||||
        const txHash = await this._zeroEx.exchange.cancelOrderAsync(signedOrder, cancelTakerTokenAmount, {
 | 
			
		||||
            gasPrice: this._defaultGasPrice,
 | 
			
		||||
        });
 | 
			
		||||
        const receipt = await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
 | 
			
		||||
        const logs: Array<LogWithDecodedArgs<ExchangeContractEventArgs>> = receipt.logs as any;
 | 
			
		||||
        this._zeroEx.exchange.throwLogErrorsAsErrors(logs);
 | 
			
		||||
@@ -368,22 +393,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.');
 | 
			
		||||
@@ -393,7 +421,21 @@ export class Blockchain {
 | 
			
		||||
        if (_.isUndefined(makerAddress)) {
 | 
			
		||||
            throw new Error('Tried to send a sign request but user has no associated addresses');
 | 
			
		||||
        }
 | 
			
		||||
        const ecSignature = await this._zeroEx.signOrderHashAsync(orderHash, makerAddress);
 | 
			
		||||
 | 
			
		||||
        this._showFlashMessageIfLedger();
 | 
			
		||||
        const nodeVersion = await this._web3Wrapper.getNodeVersionAsync();
 | 
			
		||||
        const isParityNode = utils.isParityNode(nodeVersion);
 | 
			
		||||
        const isTestRpc = utils.isTestRpc(nodeVersion);
 | 
			
		||||
        const isLedgerSigner = !_.isUndefined(this._ledgerSubprovider);
 | 
			
		||||
        let shouldAddPersonalMessagePrefix = true;
 | 
			
		||||
        if ((isParityNode && !isLedgerSigner) || isTestRpc || isLedgerSigner) {
 | 
			
		||||
            shouldAddPersonalMessagePrefix = false;
 | 
			
		||||
        }
 | 
			
		||||
        const ecSignature = await this._zeroEx.signOrderHashAsync(
 | 
			
		||||
            orderHash,
 | 
			
		||||
            makerAddress,
 | 
			
		||||
            shouldAddPersonalMessagePrefix,
 | 
			
		||||
        );
 | 
			
		||||
        const signatureData = _.extend({}, ecSignature, {
 | 
			
		||||
            hash: orderHash,
 | 
			
		||||
        });
 | 
			
		||||
@@ -404,11 +446,11 @@ export class Blockchain {
 | 
			
		||||
        utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
 | 
			
		||||
 | 
			
		||||
        const mintableContract = await this._instantiateContractIfExistsAsync(MintableArtifacts, token.address);
 | 
			
		||||
        this._showFlashMessageIfLedger();
 | 
			
		||||
        await mintableContract.mint(constants.MINT_AMOUNT, {
 | 
			
		||||
            from: this._userAddress,
 | 
			
		||||
            gasPrice: this._defaultGasPrice,
 | 
			
		||||
        });
 | 
			
		||||
        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);
 | 
			
		||||
@@ -418,14 +460,20 @@ export class Blockchain {
 | 
			
		||||
        utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
 | 
			
		||||
        utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
 | 
			
		||||
 | 
			
		||||
        const txHash = await this._zeroEx.etherToken.depositAsync(etherTokenAddress, amount, this._userAddress);
 | 
			
		||||
        this._showFlashMessageIfLedger();
 | 
			
		||||
        const txHash = await this._zeroEx.etherToken.depositAsync(etherTokenAddress, amount, this._userAddress, {
 | 
			
		||||
            gasPrice: this._defaultGasPrice,
 | 
			
		||||
        });
 | 
			
		||||
        await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
 | 
			
		||||
    }
 | 
			
		||||
    public async convertWrappedEthTokensToEthAsync(etherTokenAddress: string, amount: BigNumber): Promise<void> {
 | 
			
		||||
        utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
 | 
			
		||||
        utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
 | 
			
		||||
 | 
			
		||||
        const txHash = await this._zeroEx.etherToken.withdrawAsync(etherTokenAddress, amount, this._userAddress);
 | 
			
		||||
        this._showFlashMessageIfLedger();
 | 
			
		||||
        const txHash = await this._zeroEx.etherToken.withdrawAsync(etherTokenAddress, amount, this._userAddress, {
 | 
			
		||||
            gasPrice: this._defaultGasPrice,
 | 
			
		||||
        });
 | 
			
		||||
        await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
 | 
			
		||||
    }
 | 
			
		||||
    public async doesContractExistAtAddressAsync(address: string) {
 | 
			
		||||
@@ -451,22 +499,6 @@ export class Blockchain {
 | 
			
		||||
        }
 | 
			
		||||
        return [balance, allowance];
 | 
			
		||||
    }
 | 
			
		||||
    public async updateTokenBalancesAndAllowancesAsync(tokens: Token[]) {
 | 
			
		||||
        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();
 | 
			
		||||
@@ -479,10 +511,59 @@ export class Blockchain {
 | 
			
		||||
        this._web3Wrapper.updatePrevUserAddress(newUserAddress);
 | 
			
		||||
    }
 | 
			
		||||
    public destroy() {
 | 
			
		||||
        intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId);
 | 
			
		||||
        this._web3Wrapper.destroy();
 | 
			
		||||
        this._stopWatchingExchangeLogFillEvents();
 | 
			
		||||
    }
 | 
			
		||||
    public async fetchTokenInformationAsync() {
 | 
			
		||||
        utils.assert(
 | 
			
		||||
            !_.isUndefined(this.networkId),
 | 
			
		||||
            'Cannot call fetchTokenInformationAsync if disconnected from Ethereum node',
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        this._dispatcher.updateBlockchainIsLoaded(false);
 | 
			
		||||
 | 
			
		||||
        const tokenRegistryTokensByAddress = await this._getTokenRegistryTokensByAddressAsync();
 | 
			
		||||
 | 
			
		||||
        const trackedTokensByAddress = trackedTokenStorage.getTrackedTokensByAddress(this._userAddress, this.networkId);
 | 
			
		||||
        const tokenRegistryTokens = _.values(tokenRegistryTokensByAddress);
 | 
			
		||||
        if (_.isEmpty(trackedTokensByAddress)) {
 | 
			
		||||
            _.each(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, symbol => {
 | 
			
		||||
                const token = _.find(tokenRegistryTokens, t => t.symbol === symbol);
 | 
			
		||||
                token.isTracked = true;
 | 
			
		||||
                trackedTokensByAddress[token.address] = token;
 | 
			
		||||
            });
 | 
			
		||||
            _.each(trackedTokensByAddress, (token: Token, address: string) => {
 | 
			
		||||
                trackedTokenStorage.addTrackedTokenToUser(this._userAddress, this.networkId, token);
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            // Properly set all tokenRegistry tokens `isTracked` to true if they are in the existing trackedTokens array
 | 
			
		||||
            _.each(trackedTokensByAddress, (trackedToken: Token, address: string) => {
 | 
			
		||||
                if (!_.isUndefined(tokenRegistryTokensByAddress[address])) {
 | 
			
		||||
                    tokenRegistryTokensByAddress[address].isTracked = true;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        const allTokensByAddress = {
 | 
			
		||||
            ...tokenRegistryTokensByAddress,
 | 
			
		||||
            ...trackedTokensByAddress,
 | 
			
		||||
        };
 | 
			
		||||
        const allTokens = _.values(allTokensByAddress);
 | 
			
		||||
        const mostPopularTradingPairTokens: Token[] = [
 | 
			
		||||
            _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[0] }),
 | 
			
		||||
            _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[1] }),
 | 
			
		||||
        ];
 | 
			
		||||
        const sideToAssetToken: SideToAssetToken = {
 | 
			
		||||
            [Side.Deposit]: {
 | 
			
		||||
                address: mostPopularTradingPairTokens[0].address,
 | 
			
		||||
            },
 | 
			
		||||
            [Side.Receive]: {
 | 
			
		||||
                address: mostPopularTradingPairTokens[1].address,
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
        this._dispatcher.batchDispatch(allTokensByAddress, this.networkId, this._userAddress, sideToAssetToken);
 | 
			
		||||
 | 
			
		||||
        this._dispatcher.updateBlockchainIsLoaded(true);
 | 
			
		||||
    }
 | 
			
		||||
    private async _showEtherScanLinkAndAwaitTransactionMinedAsync(
 | 
			
		||||
        txHash: string,
 | 
			
		||||
    ): Promise<TransactionReceiptWithDecodedLogs> {
 | 
			
		||||
@@ -665,17 +746,23 @@ export class Blockchain {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const provider = await Blockchain._getProviderAsync(injectedWeb3, networkIdIfExists);
 | 
			
		||||
        const networkId = !_.isUndefined(networkIdIfExists)
 | 
			
		||||
        this.networkId = !_.isUndefined(networkIdIfExists)
 | 
			
		||||
            ? networkIdIfExists
 | 
			
		||||
            : configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_TESTNET;
 | 
			
		||||
            : configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_KOVAN;
 | 
			
		||||
        this._dispatcher.updateNetworkId(this.networkId);
 | 
			
		||||
        const zeroExConfigs = {
 | 
			
		||||
            networkId,
 | 
			
		||||
            networkId: this.networkId,
 | 
			
		||||
        };
 | 
			
		||||
        this._zeroEx = new ZeroEx(provider, zeroExConfigs);
 | 
			
		||||
        this._updateProviderName(injectedWeb3);
 | 
			
		||||
        const shouldPollUserAddress = true;
 | 
			
		||||
        this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, networkId, shouldPollUserAddress);
 | 
			
		||||
        this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress);
 | 
			
		||||
        await this._postInstantiationOrUpdatingProviderZeroExAsync();
 | 
			
		||||
        this._userAddress = await this._web3Wrapper.getFirstAccountIfExistsAsync();
 | 
			
		||||
        this._dispatcher.updateUserAddress(this._userAddress);
 | 
			
		||||
        await this.fetchTokenInformationAsync();
 | 
			
		||||
        this._web3Wrapper.startEmittingNetworkConnectionAndUserBalanceState();
 | 
			
		||||
        await this._rehydrateStoreWithContractEvents();
 | 
			
		||||
    }
 | 
			
		||||
    // This method should always be run after instantiating or updating the provider
 | 
			
		||||
    // of the ZeroEx instance.
 | 
			
		||||
@@ -690,60 +777,6 @@ export class Blockchain {
 | 
			
		||||
            : constants.PROVIDER_NAME_PUBLIC;
 | 
			
		||||
        this._dispatcher.updateInjectedProviderName(providerName);
 | 
			
		||||
    }
 | 
			
		||||
    private async _fetchTokenInformationAsync() {
 | 
			
		||||
        utils.assert(
 | 
			
		||||
            !_.isUndefined(this.networkId),
 | 
			
		||||
            'Cannot call fetchTokenInformationAsync if disconnected from Ethereum node',
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        this._dispatcher.updateBlockchainIsLoaded(false);
 | 
			
		||||
        this._dispatcher.clearTokenByAddress();
 | 
			
		||||
 | 
			
		||||
        const tokenRegistryTokensByAddress = await this._getTokenRegistryTokensByAddressAsync();
 | 
			
		||||
 | 
			
		||||
        // HACK: We need to fetch the userAddress here because otherwise we cannot save the
 | 
			
		||||
        // tracked tokens in localStorage under the users address nor fetch the token
 | 
			
		||||
        // balances and allowances and we need to do this in order not to trigger the blockchain
 | 
			
		||||
        // loading dialog to show up twice. First to load the contracts, and second to load the
 | 
			
		||||
        // balances and allowances.
 | 
			
		||||
        this._userAddress = await this._web3Wrapper.getFirstAccountIfExistsAsync();
 | 
			
		||||
        if (!_.isEmpty(this._userAddress)) {
 | 
			
		||||
            this._dispatcher.updateUserAddress(this._userAddress);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let trackedTokensIfExists = trackedTokenStorage.getTrackedTokensIfExists(this._userAddress, this.networkId);
 | 
			
		||||
        const tokenRegistryTokens = _.values(tokenRegistryTokensByAddress);
 | 
			
		||||
        if (_.isUndefined(trackedTokensIfExists)) {
 | 
			
		||||
            trackedTokensIfExists = _.map(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, symbol => {
 | 
			
		||||
                const token = _.find(tokenRegistryTokens, t => t.symbol === symbol);
 | 
			
		||||
                token.isTracked = true;
 | 
			
		||||
                return token;
 | 
			
		||||
            });
 | 
			
		||||
            _.each(trackedTokensIfExists, token => {
 | 
			
		||||
                trackedTokenStorage.addTrackedTokenToUser(this._userAddress, this.networkId, token);
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            // Properly set all tokenRegistry tokens `isTracked` to true if they are in the existing trackedTokens array
 | 
			
		||||
            _.each(trackedTokensIfExists, trackedToken => {
 | 
			
		||||
                if (!_.isUndefined(tokenRegistryTokensByAddress[trackedToken.address])) {
 | 
			
		||||
                    tokenRegistryTokensByAddress[trackedToken.address].isTracked = true;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        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] }),
 | 
			
		||||
        ];
 | 
			
		||||
        this._dispatcher.updateChosenAssetTokenAddress(Side.Deposit, mostPopularTradingPairTokens[0].address);
 | 
			
		||||
        this._dispatcher.updateChosenAssetTokenAddress(Side.Receive, mostPopularTradingPairTokens[1].address);
 | 
			
		||||
        this._dispatcher.updateBlockchainIsLoaded(true);
 | 
			
		||||
    }
 | 
			
		||||
    private async _instantiateContractIfExistsAsync(artifact: any, address?: string): Promise<ContractInstance> {
 | 
			
		||||
        const c = await contract(artifact);
 | 
			
		||||
        const providerObj = this._web3Wrapper.getProviderObj();
 | 
			
		||||
@@ -779,4 +812,20 @@ export class Blockchain {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    private _showFlashMessageIfLedger() {
 | 
			
		||||
        if (!_.isUndefined(this._ledgerSubprovider)) {
 | 
			
		||||
            this._dispatcher.showFlashMessage('Confirm the transaction on your Ledger Nano S');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    private async _updateDefaultGasPriceAsync() {
 | 
			
		||||
        const endpoint = `${configs.BACKEND_BASE_URL}/eth_gas_station`;
 | 
			
		||||
        const response = await fetch(endpoint);
 | 
			
		||||
        if (response.status !== 200) {
 | 
			
		||||
            return; // noop and we keep hard-coded default
 | 
			
		||||
        }
 | 
			
		||||
        const gasInfo = await response.json();
 | 
			
		||||
        const gasPriceInGwei = new BigNumber(gasInfo.average / 10);
 | 
			
		||||
        const gasPriceInWei = gasPriceInGwei.mul(1000000000);
 | 
			
		||||
        this._defaultGasPrice = gasPriceInWei;
 | 
			
		||||
    }
 | 
			
		||||
} // tslint:disable:max-file-line-count
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ import Dialog from 'material-ui/Dialog';
 | 
			
		||||
import FlatButton from 'material-ui/FlatButton';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { Blockchain } from 'ts/blockchain';
 | 
			
		||||
import { BlockchainErrs } from 'ts/types';
 | 
			
		||||
import { BlockchainErrs, Networks } from 'ts/types';
 | 
			
		||||
import { colors } from 'ts/utils/colors';
 | 
			
		||||
import { configs } from 'ts/utils/configs';
 | 
			
		||||
import { constants } from 'ts/utils/constants';
 | 
			
		||||
@@ -129,7 +129,7 @@ export class BlockchainErrDialog extends React.Component<BlockchainErrDialogProp
 | 
			
		||||
                <div>
 | 
			
		||||
                    The 0x smart contracts are not deployed on the Ethereum network you are currently connected to
 | 
			
		||||
                    (network Id: {this.props.networkId}). In order to use the 0x portal dApp, please connect to the{' '}
 | 
			
		||||
                    {constants.TESTNET_NAME} testnet (network Id: {constants.NETWORK_ID_TESTNET})
 | 
			
		||||
                    {Networks.Kovan} testnet (network Id: {constants.NETWORK_ID_KOVAN})
 | 
			
		||||
                    {configs.IS_MAINNET_ENABLED
 | 
			
		||||
                        ? ` or ${constants.MAINNET_NAME} (network Id: ${constants.NETWORK_ID_MAINNET}).`
 | 
			
		||||
                        : `.`}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,38 +2,55 @@ 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<
 | 
			
		||||
    EthWethConversionDialogProps,
 | 
			
		||||
    EthWethConversionDialogState
 | 
			
		||||
> {
 | 
			
		||||
    private _isUnmounted: boolean;
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super();
 | 
			
		||||
        this._isUnmounted = false;
 | 
			
		||||
        this.state = {
 | 
			
		||||
            shouldShowIncompleteErrs: false,
 | 
			
		||||
            hasErrors: false,
 | 
			
		||||
            isEthTokenBalanceLoaded: false,
 | 
			
		||||
            ethTokenBalance: new BigNumber(0),
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    public componentWillMount() {
 | 
			
		||||
        // tslint:disable-next-line:no-floating-promises
 | 
			
		||||
        this._fetchEthTokenBalanceAsync();
 | 
			
		||||
    }
 | 
			
		||||
    public componentWillUnmount() {
 | 
			
		||||
        this._isUnmounted = true;
 | 
			
		||||
    }
 | 
			
		||||
    public render() {
 | 
			
		||||
        const convertDialogActions = [
 | 
			
		||||
            <FlatButton key="cancel" label="Cancel" onTouchTap={this._onCancel.bind(this)} />,
 | 
			
		||||
@@ -72,8 +89,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 +113,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 +153,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 +181,16 @@ 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,
 | 
			
		||||
        );
 | 
			
		||||
        if (!this._isUnmounted) {
 | 
			
		||||
            this.setState({
 | 
			
		||||
                isEthTokenBalanceLoaded: true,
 | 
			
		||||
                ethTokenBalance: balance,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,10 @@ import TextField from 'material-ui/TextField';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import ReactTooltip = require('react-tooltip');
 | 
			
		||||
import { Blockchain } from 'ts/blockchain';
 | 
			
		||||
import { NetworkDropDown } from 'ts/components/dropdowns/network_drop_down';
 | 
			
		||||
import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button';
 | 
			
		||||
import { Dispatcher } from 'ts/redux/dispatcher';
 | 
			
		||||
import { ProviderType } from 'ts/types';
 | 
			
		||||
import { colors } from 'ts/utils/colors';
 | 
			
		||||
import { configs } from 'ts/utils/configs';
 | 
			
		||||
import { constants } from 'ts/utils/constants';
 | 
			
		||||
@@ -27,27 +29,33 @@ interface LedgerConfigDialogProps {
 | 
			
		||||
    dispatcher: Dispatcher;
 | 
			
		||||
    blockchain: Blockchain;
 | 
			
		||||
    networkId: number;
 | 
			
		||||
    providerType: ProviderType;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface LedgerConfigDialogState {
 | 
			
		||||
    didConnectFail: boolean;
 | 
			
		||||
    connectionErrMsg: string;
 | 
			
		||||
    stepIndex: LedgerSteps;
 | 
			
		||||
    userAddresses: string[];
 | 
			
		||||
    addressBalances: BigNumber[];
 | 
			
		||||
    derivationPath: string;
 | 
			
		||||
    derivationErrMsg: string;
 | 
			
		||||
    preferredNetworkId: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, LedgerConfigDialogState> {
 | 
			
		||||
    constructor(props: LedgerConfigDialogProps) {
 | 
			
		||||
        super(props);
 | 
			
		||||
        const derivationPathIfExists = props.blockchain.getLedgerDerivationPathIfExists();
 | 
			
		||||
        this.state = {
 | 
			
		||||
            didConnectFail: false,
 | 
			
		||||
            connectionErrMsg: '',
 | 
			
		||||
            stepIndex: LedgerSteps.CONNECT,
 | 
			
		||||
            userAddresses: [],
 | 
			
		||||
            addressBalances: [],
 | 
			
		||||
            derivationPath: configs.DEFAULT_DERIVATION_PATH,
 | 
			
		||||
            derivationPath: _.isUndefined(derivationPathIfExists)
 | 
			
		||||
                ? configs.DEFAULT_DERIVATION_PATH
 | 
			
		||||
                : derivationPathIfExists,
 | 
			
		||||
            derivationErrMsg: '',
 | 
			
		||||
            preferredNetworkId: props.networkId,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    public render() {
 | 
			
		||||
@@ -74,19 +82,28 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    private _renderConnectStep() {
 | 
			
		||||
        const networkIds = _.values(constants.NETWORK_ID_BY_NAME);
 | 
			
		||||
        return (
 | 
			
		||||
            <div>
 | 
			
		||||
                <div className="h4 pt3">Follow these instructions before proceeding:</div>
 | 
			
		||||
                <ol>
 | 
			
		||||
                <ol className="mb0">
 | 
			
		||||
                    <li className="pb1">Connect your Ledger Nano S & Open the Ethereum application</li>
 | 
			
		||||
                    <li className="pb1">Verify that Browser Support is enabled in Settings</li>
 | 
			
		||||
                    <li className="pb1">Verify that "Browser Support" AND "Contract Data" are enabled in Settings</li>
 | 
			
		||||
                    <li className="pb1">
 | 
			
		||||
                        If no Browser Support is found in settings, verify that you have{' '}
 | 
			
		||||
                        <a href="https://www.ledgerwallet.com/apps/manager" target="_blank">
 | 
			
		||||
                            Firmware >1.2
 | 
			
		||||
                        </a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li>Choose your desired network:</li>
 | 
			
		||||
                </ol>
 | 
			
		||||
                <div className="pb2">
 | 
			
		||||
                    <NetworkDropDown
 | 
			
		||||
                        updateSelectedNetwork={this._onSelectedNetworkUpdated.bind(this)}
 | 
			
		||||
                        selectedNetworkId={this.state.preferredNetworkId}
 | 
			
		||||
                        avialableNetworkIds={networkIds}
 | 
			
		||||
                    />
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className="center pb3">
 | 
			
		||||
                    <LifeCycleRaisedButton
 | 
			
		||||
                        isPrimary={true}
 | 
			
		||||
@@ -95,9 +112,9 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
 | 
			
		||||
                        labelComplete="Connected!"
 | 
			
		||||
                        onClickAsyncFn={this._onConnectLedgerClickAsync.bind(this, true)}
 | 
			
		||||
                    />
 | 
			
		||||
                    {this.state.didConnectFail && (
 | 
			
		||||
                    {!_.isEmpty(this.state.connectionErrMsg) && (
 | 
			
		||||
                        <div className="pt2 left-align" style={{ color: colors.red200 }}>
 | 
			
		||||
                            Failed to connect. Follow the instructions and try again.
 | 
			
		||||
                            {this.state.connectionErrMsg}
 | 
			
		||||
                        </div>
 | 
			
		||||
                    )}
 | 
			
		||||
                </div>
 | 
			
		||||
@@ -172,7 +189,8 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
 | 
			
		||||
    }
 | 
			
		||||
    private _onClose() {
 | 
			
		||||
        this.setState({
 | 
			
		||||
            didConnectFail: false,
 | 
			
		||||
            connectionErrMsg: '',
 | 
			
		||||
            stepIndex: LedgerSteps.CONNECT,
 | 
			
		||||
        });
 | 
			
		||||
        const isOpen = false;
 | 
			
		||||
        this.props.toggleDialogFn(isOpen);
 | 
			
		||||
@@ -184,6 +202,8 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
 | 
			
		||||
        const selectAddressBalance = this.state.addressBalances[selectedRowIndex];
 | 
			
		||||
        this.props.dispatcher.updateUserAddress(selectedAddress);
 | 
			
		||||
        this.props.blockchain.updateWeb3WrapperPrevUserAddress(selectedAddress);
 | 
			
		||||
        // tslint:disable-next-line:no-floating-promises
 | 
			
		||||
        this.props.blockchain.fetchTokenInformationAsync();
 | 
			
		||||
        this.props.dispatcher.updateUserEtherBalance(selectAddressBalance);
 | 
			
		||||
        this.setState({
 | 
			
		||||
            stepIndex: LedgerSteps.CONNECT,
 | 
			
		||||
@@ -219,7 +239,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
 | 
			
		||||
        } catch (err) {
 | 
			
		||||
            utils.consoleLog(`Ledger error: ${JSON.stringify(err)}`);
 | 
			
		||||
            this.setState({
 | 
			
		||||
                didConnectFail: true,
 | 
			
		||||
                connectionErrMsg: 'Failed to connect. Follow the instructions and try again.',
 | 
			
		||||
            });
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
@@ -241,6 +261,22 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    private async _onConnectLedgerClickAsync() {
 | 
			
		||||
        const isU2FSupported = await utils.isU2FSupportedAsync();
 | 
			
		||||
        if (!isU2FSupported) {
 | 
			
		||||
            utils.consoleLog(`U2F not supported in this browser`);
 | 
			
		||||
            this.setState({
 | 
			
		||||
                connectionErrMsg: 'U2F not supported by this browser. Try using Chrome.',
 | 
			
		||||
            });
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (
 | 
			
		||||
            this.props.providerType !== ProviderType.Ledger ||
 | 
			
		||||
            (this.props.providerType === ProviderType.Ledger && this.props.networkId !== this.state.preferredNetworkId)
 | 
			
		||||
        ) {
 | 
			
		||||
            await this.props.blockchain.updateProviderToLedgerAsync(this.state.preferredNetworkId);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const didSucceed = await this._fetchAddressesAndBalancesAsync();
 | 
			
		||||
        if (didSucceed) {
 | 
			
		||||
            this.setState({
 | 
			
		||||
@@ -258,4 +294,9 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
 | 
			
		||||
        }
 | 
			
		||||
        return userAddresses;
 | 
			
		||||
    }
 | 
			
		||||
    private _onSelectedNetworkUpdated(e: any, index: number, networkId: number) {
 | 
			
		||||
        this.setState({
 | 
			
		||||
            preferredNetworkId: networkId,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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({
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,40 @@
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
import DropDownMenu from 'material-ui/DropDownMenu';
 | 
			
		||||
import MenuItem from 'material-ui/MenuItem';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { constants } from 'ts/utils/constants';
 | 
			
		||||
 | 
			
		||||
interface NetworkDropDownProps {
 | 
			
		||||
    updateSelectedNetwork: (e: any, index: number, value: number) => void;
 | 
			
		||||
    selectedNetworkId: number;
 | 
			
		||||
    avialableNetworkIds: number[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface NetworkDropDownState {}
 | 
			
		||||
 | 
			
		||||
export class NetworkDropDown extends React.Component<NetworkDropDownProps, NetworkDropDownState> {
 | 
			
		||||
    public render() {
 | 
			
		||||
        return (
 | 
			
		||||
            <div className="mx-auto" style={{ width: 120 }}>
 | 
			
		||||
                <DropDownMenu value={this.props.selectedNetworkId} onChange={this.props.updateSelectedNetwork}>
 | 
			
		||||
                    {this._renderDropDownItems()}
 | 
			
		||||
                </DropDownMenu>
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    private _renderDropDownItems() {
 | 
			
		||||
        const items = _.map(this.props.avialableNetworkIds, networkId => {
 | 
			
		||||
            const networkName = constants.NETWORK_NAME_BY_ID[networkId];
 | 
			
		||||
            const primaryText = (
 | 
			
		||||
                <div className="flex">
 | 
			
		||||
                    <div className="pr1" style={{ width: 14, paddingTop: 2 }}>
 | 
			
		||||
                        <img src={`/images/network_icons/${networkName.toLowerCase()}.png`} style={{ width: 14 }} />
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div>{networkName}</div>
 | 
			
		||||
                </div>
 | 
			
		||||
            );
 | 
			
		||||
            return <MenuItem key={networkId} value={networkId} primaryText={primaryText} />;
 | 
			
		||||
        });
 | 
			
		||||
        return items;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -6,21 +6,24 @@ import * as React from 'react';
 | 
			
		||||
import { Blockchain } from 'ts/blockchain';
 | 
			
		||||
import { EthWethConversionDialog } from 'ts/components/dialogs/eth_weth_conversion_dialog';
 | 
			
		||||
import { Dispatcher } from 'ts/redux/dispatcher';
 | 
			
		||||
import { BlockchainCallErrs, Side, Token, TokenState } from 'ts/types';
 | 
			
		||||
import { BlockchainCallErrs, Side, Token } from 'ts/types';
 | 
			
		||||
import { constants } from 'ts/utils/constants';
 | 
			
		||||
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;
 | 
			
		||||
    dispatcher: Dispatcher;
 | 
			
		||||
    blockchain: Blockchain;
 | 
			
		||||
    userEtherBalance: BigNumber;
 | 
			
		||||
    isOutdatedWrappedEther: boolean;
 | 
			
		||||
    onConversionSuccessful?: () => void;
 | 
			
		||||
    isDisabled?: boolean;
 | 
			
		||||
    lastForceTokenStateRefetch: number;
 | 
			
		||||
    refetchEthTokenStateAsync: () => Promise<void>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface EthWethConversionButtonState {
 | 
			
		||||
@@ -64,13 +67,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>
 | 
			
		||||
        );
 | 
			
		||||
@@ -86,29 +92,25 @@ 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);
 | 
			
		||||
                await this.props.refetchEthTokenStateAsync();
 | 
			
		||||
            }
 | 
			
		||||
            this.props.onConversionSuccessful();
 | 
			
		||||
        } catch (err) {
 | 
			
		||||
            const errMsg = `${err}`;
 | 
			
		||||
            if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) {
 | 
			
		||||
                this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
 | 
			
		||||
            } else if (!_.includes(errMsg, 'User denied transaction')) {
 | 
			
		||||
            } else if (!utils.didUserDenyWeb3Request(errMsg)) {
 | 
			
		||||
                utils.consoleLog(`Unexpected error encountered: ${err}`);
 | 
			
		||||
                utils.consoleLog(err.stack);
 | 
			
		||||
                const errorMsg =
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,6 @@ import {
 | 
			
		||||
    Token,
 | 
			
		||||
    TokenByAddress,
 | 
			
		||||
    TokenState,
 | 
			
		||||
    TokenStateByAddress,
 | 
			
		||||
} from 'ts/types';
 | 
			
		||||
import { colors } from 'ts/utils/colors';
 | 
			
		||||
import { configs } from 'ts/utils/configs';
 | 
			
		||||
@@ -41,19 +40,23 @@ 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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersState> {
 | 
			
		||||
    private _isUnmounted: boolean;
 | 
			
		||||
    constructor(props: EthWrappersProps) {
 | 
			
		||||
        super(props);
 | 
			
		||||
        this._isUnmounted = false;
 | 
			
		||||
        const outdatedWETHAddresses = this._getOutdatedWETHAddresses();
 | 
			
		||||
        const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {};
 | 
			
		||||
        const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {};
 | 
			
		||||
@@ -67,18 +70,34 @@ 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 componentWillUnmount() {
 | 
			
		||||
        this._isUnmounted = true;
 | 
			
		||||
    }
 | 
			
		||||
    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 +155,13 @@ 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}
 | 
			
		||||
                                            dispatcher={this.props.dispatcher}
 | 
			
		||||
                                            blockchain={this.props.blockchain}
 | 
			
		||||
                                            userEtherBalance={this.props.userEtherBalance}
 | 
			
		||||
@@ -150,13 +172,23 @@ 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}
 | 
			
		||||
                                            dispatcher={this.props.dispatcher}
 | 
			
		||||
                                            blockchain={this.props.blockchain}
 | 
			
		||||
                                            userEtherBalance={this.props.userEtherBalance}
 | 
			
		||||
@@ -190,7 +222,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 +301,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 +374,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 = {};
 | 
			
		||||
@@ -353,10 +396,17 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
 | 
			
		||||
            };
 | 
			
		||||
            outdatedWETHAddressToIsStateLoaded[address] = true;
 | 
			
		||||
        }
 | 
			
		||||
        this.setState({
 | 
			
		||||
            outdatedWETHStateByAddress,
 | 
			
		||||
            outdatedWETHAddressToIsStateLoaded,
 | 
			
		||||
        });
 | 
			
		||||
        if (!this._isUnmounted) {
 | 
			
		||||
            this.setState({
 | 
			
		||||
                outdatedWETHStateByAddress,
 | 
			
		||||
                outdatedWETHAddressToIsStateLoaded,
 | 
			
		||||
                ethTokenState: {
 | 
			
		||||
                    balance: wethBalance,
 | 
			
		||||
                    allowance: wethAllowance,
 | 
			
		||||
                },
 | 
			
		||||
                isWethStateLoaded: true,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    private _getOutdatedWETHAddresses(): string[] {
 | 
			
		||||
        const outdatedWETHAddresses = _.compact(
 | 
			
		||||
@@ -371,4 +421,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 {
 | 
			
		||||
@@ -59,8 +59,10 @@ interface FillOrderState {
 | 
			
		||||
 | 
			
		||||
export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
 | 
			
		||||
    private _validator: SchemaValidator;
 | 
			
		||||
    private _isUnmounted: boolean;
 | 
			
		||||
    constructor(props: FillOrderProps) {
 | 
			
		||||
        super(props);
 | 
			
		||||
        this._isUnmounted = false;
 | 
			
		||||
        this.state = {
 | 
			
		||||
            globalErrMsg: '',
 | 
			
		||||
            didOrderValidationRun: false,
 | 
			
		||||
@@ -90,6 +92,9 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
 | 
			
		||||
    public componentDidMount() {
 | 
			
		||||
        window.scrollTo(0, 0);
 | 
			
		||||
    }
 | 
			
		||||
    public componentWillUnmount() {
 | 
			
		||||
        this._isUnmounted = true;
 | 
			
		||||
    }
 | 
			
		||||
    public render() {
 | 
			
		||||
        return (
 | 
			
		||||
            <div className="clearfix lg-px4 md-px4 sm-px2" style={{ minHeight: 600 }}>
 | 
			
		||||
@@ -185,7 +190,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 +253,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"
 | 
			
		||||
@@ -454,12 +461,14 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
 | 
			
		||||
            if (!_.isEmpty(orderJSON)) {
 | 
			
		||||
                orderJSONErrMsg = 'Submitted order JSON is not valid JSON';
 | 
			
		||||
            }
 | 
			
		||||
            this.setState({
 | 
			
		||||
                didOrderValidationRun: true,
 | 
			
		||||
                orderJSON,
 | 
			
		||||
                orderJSONErrMsg,
 | 
			
		||||
                parsedOrder,
 | 
			
		||||
            });
 | 
			
		||||
            if (!this._isUnmounted) {
 | 
			
		||||
                this.setState({
 | 
			
		||||
                    didOrderValidationRun: true,
 | 
			
		||||
                    orderJSON,
 | 
			
		||||
                    orderJSONErrMsg,
 | 
			
		||||
                    parsedOrder,
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -556,11 +565,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,
 | 
			
		||||
@@ -573,7 +579,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
 | 
			
		||||
                isFilling: false,
 | 
			
		||||
            });
 | 
			
		||||
            const errMsg = `${err}`;
 | 
			
		||||
            if (_.includes(errMsg, 'User denied transaction signature')) {
 | 
			
		||||
            if (utils.didUserDenyWeb3Request(errMsg)) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            globalErrMsg = 'Failed to fill order, please refresh and try again';
 | 
			
		||||
@@ -653,7 +659,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
 | 
			
		||||
                isCancelling: false,
 | 
			
		||||
            });
 | 
			
		||||
            const errMsg = `${err}`;
 | 
			
		||||
            if (_.includes(errMsg, 'User denied transaction signature')) {
 | 
			
		||||
            if (utils.didUserDenyWeb3Request(errMsg)) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            globalErrMsg = 'Failed to cancel order, please refresh and try again';
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ import { TrackTokenConfirmation } from 'ts/components/track_token_confirmation';
 | 
			
		||||
import { TokenIcon } from 'ts/components/ui/token_icon';
 | 
			
		||||
import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage';
 | 
			
		||||
import { Dispatcher } from 'ts/redux/dispatcher';
 | 
			
		||||
import { DialogConfigs, Token, TokenByAddress, TokenState, TokenVisibility } from 'ts/types';
 | 
			
		||||
import { DialogConfigs, Token, TokenByAddress, TokenVisibility } from 'ts/types';
 | 
			
		||||
 | 
			
		||||
const TOKEN_ICON_DIMENSION = 100;
 | 
			
		||||
const TILE_DIMENSION = 146;
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,6 @@ import {
 | 
			
		||||
    SignatureData,
 | 
			
		||||
    Token,
 | 
			
		||||
    TokenByAddress,
 | 
			
		||||
    TokenStateByAddress,
 | 
			
		||||
} from 'ts/types';
 | 
			
		||||
import { colors } from 'ts/utils/colors';
 | 
			
		||||
import { errorReporter } from 'ts/utils/error_reporter';
 | 
			
		||||
@@ -53,7 +52,7 @@ interface GenerateOrderFormProps {
 | 
			
		||||
    orderSalt: BigNumber;
 | 
			
		||||
    sideToAssetToken: SideToAssetToken;
 | 
			
		||||
    tokenByAddress: TokenByAddress;
 | 
			
		||||
    tokenStateByAddress: TokenStateByAddress;
 | 
			
		||||
    lastForceTokenStateRefetch: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface GenerateOrderFormState {
 | 
			
		||||
@@ -80,10 +79,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 +107,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 +139,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 +245,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) &&
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,3 @@
 | 
			
		||||
import { BigNumber } from '@0xproject/utils';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
import TextField from 'material-ui/TextField';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
@@ -7,13 +6,13 @@ import { AddressInput } from 'ts/components/inputs/address_input';
 | 
			
		||||
import { Alert } from 'ts/components/ui/alert';
 | 
			
		||||
import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button';
 | 
			
		||||
import { RequiredLabel } from 'ts/components/ui/required_label';
 | 
			
		||||
import { AlertTypes, Token, TokenByAddress, TokenState } from 'ts/types';
 | 
			
		||||
import { AlertTypes, Token, TokenByAddress } from 'ts/types';
 | 
			
		||||
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 +109,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 +150,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,12 +75,13 @@ 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,
 | 
			
		||||
            });
 | 
			
		||||
            const errMsg = `${err}`;
 | 
			
		||||
            if (_.includes(errMsg, 'User denied transaction')) {
 | 
			
		||||
            if (utils.didUserDenyWeb3Request(errMsg)) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            utils.consoleLog(`Unexpected error encountered: ${err}`);
 | 
			
		||||
 
 | 
			
		||||
@@ -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}
 | 
			
		||||
            />
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
@@ -100,7 +103,7 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp
 | 
			
		||||
            },
 | 
			
		||||
            () => {
 | 
			
		||||
                const isValid = _.isUndefined(errMsg);
 | 
			
		||||
                if (utils.isNumeric(amountString)) {
 | 
			
		||||
                if (utils.isNumeric(amountString) && !_.includes(amountString, '-')) {
 | 
			
		||||
                    this.props.onChange(isValid, new BigNumber(amountString));
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.props.onChange(isValid);
 | 
			
		||||
 
 | 
			
		||||
@@ -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 { InputErrMsg, Token, 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,45 @@ 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> {
 | 
			
		||||
    private _isUnmounted: boolean;
 | 
			
		||||
    constructor(props: TokenAmountInputProps) {
 | 
			
		||||
        super(props);
 | 
			
		||||
        this._isUnmounted = false;
 | 
			
		||||
        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, this.props.userAddress);
 | 
			
		||||
    }
 | 
			
		||||
    public componentWillUnmount() {
 | 
			
		||||
        this._isUnmounted = true;
 | 
			
		||||
    }
 | 
			
		||||
    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
 | 
			
		||||
        ) {
 | 
			
		||||
            // tslint:disable-next-line:no-floating-promises
 | 
			
		||||
            this._fetchBalanceAndAllowanceAsync(nextProps.token.address, nextProps.userAddress);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    public render() {
 | 
			
		||||
        const amount = this.props.amount
 | 
			
		||||
            ? ZeroEx.toUnitAmount(this.props.amount, this.props.token.decimals)
 | 
			
		||||
@@ -32,12 +69,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 +89,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 +105,20 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok
 | 
			
		||||
            return undefined;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    private async _fetchBalanceAndAllowanceAsync(tokenAddress: string, userAddress: string) {
 | 
			
		||||
        this.setState({
 | 
			
		||||
            isBalanceAndAllowanceLoaded: false,
 | 
			
		||||
        });
 | 
			
		||||
        const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
 | 
			
		||||
            userAddress,
 | 
			
		||||
            tokenAddress,
 | 
			
		||||
        );
 | 
			
		||||
        if (!this._isUnmounted) {
 | 
			
		||||
            this.setState({
 | 
			
		||||
                balance,
 | 
			
		||||
                allowance,
 | 
			
		||||
                isBalanceAndAllowanceLoaded: true,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,13 @@
 | 
			
		||||
import { BigNumber } from '@0xproject/utils';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
import CircularProgress from 'material-ui/CircularProgress';
 | 
			
		||||
import Paper from 'material-ui/Paper';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import * as DocumentTitle from 'react-document-title';
 | 
			
		||||
import { Route, Switch } from 'react-router-dom';
 | 
			
		||||
import { Blockchain } from 'ts/blockchain';
 | 
			
		||||
import { BlockchainErrDialog } from 'ts/components/dialogs/blockchain_err_dialog';
 | 
			
		||||
import { LedgerConfigDialog } from 'ts/components/dialogs/ledger_config_dialog';
 | 
			
		||||
import { PortalDisclaimerDialog } from 'ts/components/dialogs/portal_disclaimer_dialog';
 | 
			
		||||
import { WrappedEthSectionNoticeDialog } from 'ts/components/dialogs/wrapped_eth_section_notice_dialog';
 | 
			
		||||
import { EthWrappers } from 'ts/components/eth_wrappers';
 | 
			
		||||
@@ -13,25 +15,15 @@ import { FillOrder } from 'ts/components/fill_order';
 | 
			
		||||
import { Footer } from 'ts/components/footer';
 | 
			
		||||
import { PortalMenu } from 'ts/components/portal_menu';
 | 
			
		||||
import { TokenBalances } from 'ts/components/token_balances';
 | 
			
		||||
import { TopBar } from 'ts/components/top_bar';
 | 
			
		||||
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 { Loading } from 'ts/components/ui/loading';
 | 
			
		||||
import { GenerateOrderForm } from 'ts/containers/generate_order_form';
 | 
			
		||||
import { localStorage } from 'ts/local_storage/local_storage';
 | 
			
		||||
import { Dispatcher } from 'ts/redux/dispatcher';
 | 
			
		||||
import { orderSchema } from 'ts/schemas/order_schema';
 | 
			
		||||
import { SchemaValidator } from 'ts/schemas/validator';
 | 
			
		||||
import {
 | 
			
		||||
    BlockchainErrs,
 | 
			
		||||
    HashData,
 | 
			
		||||
    Order,
 | 
			
		||||
    ScreenWidths,
 | 
			
		||||
    Token,
 | 
			
		||||
    TokenByAddress,
 | 
			
		||||
    TokenStateByAddress,
 | 
			
		||||
    WebsitePaths,
 | 
			
		||||
} from 'ts/types';
 | 
			
		||||
import { BlockchainErrs, HashData, Order, ProviderType, ScreenWidths, TokenByAddress, WebsitePaths } from 'ts/types';
 | 
			
		||||
import { colors } from 'ts/utils/colors';
 | 
			
		||||
import { configs } from 'ts/utils/configs';
 | 
			
		||||
import { constants } from 'ts/utils/constants';
 | 
			
		||||
@@ -46,18 +38,20 @@ export interface PortalAllProps {
 | 
			
		||||
    blockchainIsLoaded: boolean;
 | 
			
		||||
    dispatcher: Dispatcher;
 | 
			
		||||
    hashData: HashData;
 | 
			
		||||
    injectedProviderName: string;
 | 
			
		||||
    networkId: number;
 | 
			
		||||
    nodeVersion: string;
 | 
			
		||||
    orderFillAmount: BigNumber;
 | 
			
		||||
    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 {
 | 
			
		||||
@@ -67,6 +61,7 @@ interface PortalAllState {
 | 
			
		||||
    prevPathname: string;
 | 
			
		||||
    isDisclaimerDialogOpen: boolean;
 | 
			
		||||
    isWethNoticeDialogOpen: boolean;
 | 
			
		||||
    isLedgerDialogOpen: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Portal extends React.Component<PortalAllProps, PortalAllState> {
 | 
			
		||||
@@ -96,6 +91,7 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
 | 
			
		||||
            prevPathname: this.props.location.pathname,
 | 
			
		||||
            isDisclaimerDialogOpen: !hasAcceptedDisclaimer,
 | 
			
		||||
            isWethNoticeDialogOpen: !hasAlreadyDismissedWethNotice && isViewingBalances,
 | 
			
		||||
            isLedgerDialogOpen: false,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    public componentDidMount() {
 | 
			
		||||
@@ -125,11 +121,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);
 | 
			
		||||
                // tslint:disable-next-line:no-floating-promises
 | 
			
		||||
                this._updateBalanceAndAllowanceWithLoadingScreenAsync(tokens);
 | 
			
		||||
            }
 | 
			
		||||
            this.setState({
 | 
			
		||||
                prevUserAddress: nextProps.userAddress,
 | 
			
		||||
            });
 | 
			
		||||
@@ -167,8 +158,14 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
 | 
			
		||||
                <DocumentTitle title="0x Portal DApp" />
 | 
			
		||||
                <TopBar
 | 
			
		||||
                    userAddress={this.props.userAddress}
 | 
			
		||||
                    networkId={this.props.networkId}
 | 
			
		||||
                    injectedProviderName={this.props.injectedProviderName}
 | 
			
		||||
                    onToggleLedgerDialog={this.onToggleLedgerDialog.bind(this)}
 | 
			
		||||
                    dispatcher={this.props.dispatcher}
 | 
			
		||||
                    providerType={this.props.providerType}
 | 
			
		||||
                    blockchainIsLoaded={this.props.blockchainIsLoaded}
 | 
			
		||||
                    location={this.props.location}
 | 
			
		||||
                    blockchain={this._blockchain}
 | 
			
		||||
                />
 | 
			
		||||
                <div id="portal" className="mx-auto max-width-4" style={{ width: '100%' }}>
 | 
			
		||||
                    <Paper className="mb3 mt2">
 | 
			
		||||
@@ -215,7 +212,19 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
 | 
			
		||||
                                                />
 | 
			
		||||
                                            </Switch>
 | 
			
		||||
                                        ) : (
 | 
			
		||||
                                            <Loading />
 | 
			
		||||
                                            <div className="pt4 sm-px2 sm-pt2 sm-m1" style={{ height: 500 }}>
 | 
			
		||||
                                                <div
 | 
			
		||||
                                                    className="relative sm-px2 sm-pt2 sm-m1"
 | 
			
		||||
                                                    style={{ height: 122, top: '50%', transform: 'translateY(-50%)' }}
 | 
			
		||||
                                                >
 | 
			
		||||
                                                    <div className="center pb2">
 | 
			
		||||
                                                        <CircularProgress size={40} thickness={5} />
 | 
			
		||||
                                                    </div>
 | 
			
		||||
                                                    <div className="center pt2" style={{ paddingBottom: 11 }}>
 | 
			
		||||
                                                        Loading Portal...
 | 
			
		||||
                                                    </div>
 | 
			
		||||
                                                </div>
 | 
			
		||||
                                            </div>
 | 
			
		||||
                                        )}
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
@@ -239,11 +248,26 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
 | 
			
		||||
                        onToggleDialog={this._onPortalDisclaimerAccepted.bind(this)}
 | 
			
		||||
                    />
 | 
			
		||||
                    <FlashMessage dispatcher={this.props.dispatcher} flashMessage={this.props.flashMessage} />
 | 
			
		||||
                    {this.props.blockchainIsLoaded && (
 | 
			
		||||
                        <LedgerConfigDialog
 | 
			
		||||
                            providerType={this.props.providerType}
 | 
			
		||||
                            networkId={this.props.networkId}
 | 
			
		||||
                            blockchain={this._blockchain}
 | 
			
		||||
                            dispatcher={this.props.dispatcher}
 | 
			
		||||
                            toggleDialogFn={this.onToggleLedgerDialog.bind(this)}
 | 
			
		||||
                            isOpen={this.state.isLedgerDialogOpen}
 | 
			
		||||
                        />
 | 
			
		||||
                    )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <Footer />
 | 
			
		||||
                <Footer />;
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    public onToggleLedgerDialog() {
 | 
			
		||||
        this.setState({
 | 
			
		||||
            isLedgerDialogOpen: !this.state.isLedgerDialogOpen,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    private _renderEthWrapper() {
 | 
			
		||||
        return (
 | 
			
		||||
            <EthWrappers
 | 
			
		||||
@@ -251,9 +275,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}
 | 
			
		||||
            />
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
@@ -267,6 +291,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}
 | 
			
		||||
@@ -275,10 +301,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}
 | 
			
		||||
            />
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
@@ -296,8 +323,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}
 | 
			
		||||
            />
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
@@ -353,9 +380,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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,16 +5,19 @@ import * as React from 'react';
 | 
			
		||||
import { Blockchain } from 'ts/blockchain';
 | 
			
		||||
import { SendDialog } from 'ts/components/dialogs/send_dialog';
 | 
			
		||||
import { Dispatcher } from 'ts/redux/dispatcher';
 | 
			
		||||
import { BlockchainCallErrs, Token, TokenState } from 'ts/types';
 | 
			
		||||
import { BlockchainCallErrs, Token } from 'ts/types';
 | 
			
		||||
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 +45,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>
 | 
			
		||||
        );
 | 
			
		||||
@@ -62,18 +68,15 @@ 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);
 | 
			
		||||
            await this.props.refetchTokenStateAsync(token.address);
 | 
			
		||||
        } catch (err) {
 | 
			
		||||
            const errMsg = `${err}`;
 | 
			
		||||
            if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) {
 | 
			
		||||
                this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
 | 
			
		||||
                return;
 | 
			
		||||
            } else if (!_.includes(errMsg, 'User denied transaction')) {
 | 
			
		||||
            } else if (!utils.didUserDenyWeb3Request(errMsg)) {
 | 
			
		||||
                utils.consoleLog(`Unexpected error encountered: ${err}`);
 | 
			
		||||
                utils.consoleLog(err.stack);
 | 
			
		||||
                this.props.onError();
 | 
			
		||||
 
 | 
			
		||||
@@ -27,11 +27,11 @@ import {
 | 
			
		||||
    BlockchainCallErrs,
 | 
			
		||||
    BlockchainErrs,
 | 
			
		||||
    EtherscanLinkSuffixes,
 | 
			
		||||
    Networks,
 | 
			
		||||
    ScreenWidths,
 | 
			
		||||
    Styles,
 | 
			
		||||
    Token,
 | 
			
		||||
    TokenByAddress,
 | 
			
		||||
    TokenStateByAddress,
 | 
			
		||||
    TokenVisibility,
 | 
			
		||||
} from 'ts/types';
 | 
			
		||||
import { colors } from 'ts/utils/colors';
 | 
			
		||||
@@ -58,6 +58,14 @@ const styles: Styles = {
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface TokenStateByAddress {
 | 
			
		||||
    [address: string]: {
 | 
			
		||||
        balance: BigNumber;
 | 
			
		||||
        allowance: BigNumber;
 | 
			
		||||
        isLoaded: boolean;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface TokenBalancesProps {
 | 
			
		||||
    blockchain: Blockchain;
 | 
			
		||||
    blockchainErr: BlockchainErrs;
 | 
			
		||||
@@ -65,10 +73,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 +85,17 @@ interface TokenBalancesState {
 | 
			
		||||
    isBalanceSpinnerVisible: boolean;
 | 
			
		||||
    isDharmaDialogVisible: boolean;
 | 
			
		||||
    isZRXSpinnerVisible: boolean;
 | 
			
		||||
    currentZrxBalance?: BigNumber;
 | 
			
		||||
    isTokenPickerOpen: boolean;
 | 
			
		||||
    isAddingToken: boolean;
 | 
			
		||||
    trackedTokenStateByAddress: TokenStateByAddress;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class TokenBalances extends React.Component<TokenBalancesProps, TokenBalancesState> {
 | 
			
		||||
    private _isUnmounted: boolean;
 | 
			
		||||
    public constructor(props: TokenBalancesProps) {
 | 
			
		||||
        super(props);
 | 
			
		||||
        this._isUnmounted = false;
 | 
			
		||||
        const initialTrackedTokenStateByAddress = this._getInitialTrackedTokenStateByAddress(props.trackedTokens);
 | 
			
		||||
        this.state = {
 | 
			
		||||
            errorType: undefined,
 | 
			
		||||
            isBalanceSpinnerVisible: false,
 | 
			
		||||
@@ -91,8 +103,17 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
 | 
			
		||||
            isDharmaDialogVisible: DharmaLoanFrame.isAuthTokenPresent(),
 | 
			
		||||
            isTokenPickerOpen: false,
 | 
			
		||||
            isAddingToken: false,
 | 
			
		||||
            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: TokenBalancesProps) {
 | 
			
		||||
        if (nextProps.userEtherBalance !== this.props.userEtherBalance) {
 | 
			
		||||
            if (this.state.isBalanceSpinnerVisible) {
 | 
			
		||||
@@ -103,18 +124,36 @@ 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`);
 | 
			
		||||
 | 
			
		||||
        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;
 | 
			
		||||
            for (const tokenAddress of newTokenAddresses) {
 | 
			
		||||
                trackedTokenStateByAddress[tokenAddress] = {
 | 
			
		||||
                    balance: new BigNumber(0),
 | 
			
		||||
                    allowance: new BigNumber(0),
 | 
			
		||||
                    isLoaded: false,
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
            this.setState({
 | 
			
		||||
                isZRXSpinnerVisible: false,
 | 
			
		||||
                currentZrxBalance: undefined,
 | 
			
		||||
                trackedTokenStateByAddress,
 | 
			
		||||
            });
 | 
			
		||||
            // Fetch the actual balance/allowance.
 | 
			
		||||
            // tslint:disable-next-line:no-floating-promises
 | 
			
		||||
            this._fetchBalancesAndAllowancesAsync(newTokenAddresses);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    public componentDidMount() {
 | 
			
		||||
@@ -137,13 +176,13 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
 | 
			
		||||
                onTouchTap={this._onDharmaDialogToggle.bind(this, false)}
 | 
			
		||||
            />,
 | 
			
		||||
        ];
 | 
			
		||||
        const isTestNetwork = this.props.networkId === constants.NETWORK_ID_TESTNET;
 | 
			
		||||
        const isKovanTestNetwork = this.props.networkId === constants.NETWORK_ID_KOVAN;
 | 
			
		||||
        const dharmaButtonColumnStyle = {
 | 
			
		||||
            paddingLeft: 3,
 | 
			
		||||
            display: isTestNetwork ? 'table-cell' : 'none',
 | 
			
		||||
            display: isKovanTestNetwork ? 'table-cell' : 'none',
 | 
			
		||||
        };
 | 
			
		||||
        const stubColumnStyle = {
 | 
			
		||||
            display: isTestNetwork ? 'none' : 'table-cell',
 | 
			
		||||
            display: isKovanTestNetwork ? 'none' : 'table-cell',
 | 
			
		||||
        };
 | 
			
		||||
        const allTokenRowHeight = _.size(this.props.tokenByAddress) * TOKEN_TABLE_ROW_HEIGHT;
 | 
			
		||||
        const tokenTableHeight =
 | 
			
		||||
@@ -162,10 +201,10 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
 | 
			
		||||
                                  smart contract so you can start trading that token.';
 | 
			
		||||
        return (
 | 
			
		||||
            <div className="lg-px4 md-px4 sm-px1 pb2">
 | 
			
		||||
                <h3>{isTestNetwork ? 'Test ether' : 'Ether'}</h3>
 | 
			
		||||
                <h3>{isKovanTestNetwork ? 'Test ether' : 'Ether'}</h3>
 | 
			
		||||
                <Divider />
 | 
			
		||||
                <div className="pt2 pb2">
 | 
			
		||||
                    {isTestNetwork
 | 
			
		||||
                    {isKovanTestNetwork
 | 
			
		||||
                        ? 'In order to try out the 0x Portal Dapp, request some test ether to pay for \
 | 
			
		||||
                        gas costs. It might take a bit of time for the test ether to show up.'
 | 
			
		||||
                        : 'Ether must be converted to Ether Tokens in order to be tradable via 0x. \
 | 
			
		||||
@@ -177,12 +216,12 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
 | 
			
		||||
                            <TableHeaderColumn>Currency</TableHeaderColumn>
 | 
			
		||||
                            <TableHeaderColumn>Balance</TableHeaderColumn>
 | 
			
		||||
                            <TableRowColumn className="sm-hide xs-hide" style={stubColumnStyle} />
 | 
			
		||||
                            {isTestNetwork && (
 | 
			
		||||
                            {isKovanTestNetwork && (
 | 
			
		||||
                                <TableHeaderColumn style={{ paddingLeft: 3 }}>
 | 
			
		||||
                                    {isSmallScreen ? 'Faucet' : 'Request from faucet'}
 | 
			
		||||
                                </TableHeaderColumn>
 | 
			
		||||
                            )}
 | 
			
		||||
                            {isTestNetwork && (
 | 
			
		||||
                            {isKovanTestNetwork && (
 | 
			
		||||
                                <TableHeaderColumn style={dharmaButtonColumnStyle}>
 | 
			
		||||
                                    {isSmallScreen ? 'Loan' : 'Request Dharma loan'}
 | 
			
		||||
                                    <HelpTooltip style={{ paddingLeft: 4 }} explanation={dharmaLoanExplanation} />
 | 
			
		||||
@@ -204,7 +243,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
 | 
			
		||||
                                )}
 | 
			
		||||
                            </TableRowColumn>
 | 
			
		||||
                            <TableRowColumn className="sm-hide xs-hide" style={stubColumnStyle} />
 | 
			
		||||
                            {isTestNetwork && (
 | 
			
		||||
                            {isKovanTestNetwork && (
 | 
			
		||||
                                <TableRowColumn style={{ paddingLeft: 3 }}>
 | 
			
		||||
                                    <LifeCycleRaisedButton
 | 
			
		||||
                                        labelReady="Request"
 | 
			
		||||
@@ -214,7 +253,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
 | 
			
		||||
                                    />
 | 
			
		||||
                                </TableRowColumn>
 | 
			
		||||
                            )}
 | 
			
		||||
                            {isTestNetwork && (
 | 
			
		||||
                            {isKovanTestNetwork && (
 | 
			
		||||
                                <TableRowColumn style={dharmaButtonColumnStyle}>
 | 
			
		||||
                                    <RaisedButton
 | 
			
		||||
                                        label="Request"
 | 
			
		||||
@@ -228,7 +267,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
 | 
			
		||||
                </Table>
 | 
			
		||||
                <div className="clearfix" style={{ paddingBottom: 1 }}>
 | 
			
		||||
                    <div className="col col-10">
 | 
			
		||||
                        <h3 className="pt2">{isTestNetwork ? 'Test tokens' : 'Tokens'}</h3>
 | 
			
		||||
                        <h3 className="pt2">{isKovanTestNetwork ? 'Test tokens' : 'Tokens'}</h3>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div className="col col-1 pt3 align-right">
 | 
			
		||||
                        <FloatingActionButton mini={true} zDepth={0} onClick={this._onAddTokenClicked.bind(this)}>
 | 
			
		||||
@@ -243,7 +282,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
 | 
			
		||||
                </div>
 | 
			
		||||
                <Divider />
 | 
			
		||||
                <div className="pt2 pb2">
 | 
			
		||||
                    {isTestNetwork
 | 
			
		||||
                    {isKovanTestNetwork
 | 
			
		||||
                        ? "Mint some test tokens you'd like to use to generate or fill an order using 0x."
 | 
			
		||||
                        : "Set trading permissions for a token you'd like to start trading."}
 | 
			
		||||
                </div>
 | 
			
		||||
@@ -303,8 +342,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 +355,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 +376,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 +398,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 }}>
 | 
			
		||||
@@ -366,7 +412,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
 | 
			
		||||
                        />
 | 
			
		||||
                    )}
 | 
			
		||||
                    {token.symbol === ZRX_TOKEN_SYMBOL &&
 | 
			
		||||
                        this.props.networkId === constants.NETWORK_ID_TESTNET && (
 | 
			
		||||
                        this.props.networkId === constants.NETWORK_ID_KOVAN && (
 | 
			
		||||
                            <LifeCycleRaisedButton
 | 
			
		||||
                                labelReady="Request"
 | 
			
		||||
                                labelLoading="Sending..."
 | 
			
		||||
@@ -383,11 +429,14 @@ 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 +463,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`);
 | 
			
		||||
@@ -449,9 +497,9 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
 | 
			
		||||
            case BalanceErrs.incorrectNetworkForFaucet:
 | 
			
		||||
                return (
 | 
			
		||||
                    <div>
 | 
			
		||||
                        Our faucet can only send test Ether to addresses on the {constants.TESTNET_NAME} testnet
 | 
			
		||||
                        (networkId {constants.NETWORK_ID_TESTNET}). Please make sure you are connected to the{' '}
 | 
			
		||||
                        {constants.TESTNET_NAME} testnet and try requesting ether again.
 | 
			
		||||
                        Our faucet can only send test Ether to addresses on the {Networks.Kovan} testnet (networkId{' '}
 | 
			
		||||
                        {constants.NETWORK_ID_KOVAN}). Please make sure you are connected to the {Networks.Kovan}{' '}
 | 
			
		||||
                        testnet and try requesting ether again.
 | 
			
		||||
                    </div>
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
@@ -510,6 +558,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;
 | 
			
		||||
@@ -519,7 +568,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
 | 
			
		||||
                this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            if (_.includes(errMsg, 'User denied transaction')) {
 | 
			
		||||
            if (utils.didUserDenyWeb3Request(errMsg)) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            utils.consoleLog(`Unexpected error encountered: ${err}`);
 | 
			
		||||
@@ -539,7 +588,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
 | 
			
		||||
 | 
			
		||||
        // If on another network other then the testnet our faucet serves test ether
 | 
			
		||||
        // from, we must show user an error message
 | 
			
		||||
        if (this.props.blockchain.networkId !== constants.NETWORK_ID_TESTNET) {
 | 
			
		||||
        if (this.props.blockchain.networkId !== constants.NETWORK_ID_KOVAN) {
 | 
			
		||||
            this.setState({
 | 
			
		||||
                errorType: BalanceErrs.incorrectNetworkForFaucet,
 | 
			
		||||
            });
 | 
			
		||||
@@ -569,15 +618,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 +648,65 @@ 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,
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        if (!this._isUnmounted) {
 | 
			
		||||
            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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										148
									
								
								packages/website/ts/components/top_bar/provider_display.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,148 @@
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
import RaisedButton from 'material-ui/RaisedButton';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { Blockchain } from 'ts/blockchain';
 | 
			
		||||
import { ProviderPicker } from 'ts/components/top_bar/provider_picker';
 | 
			
		||||
import { DropDown } from 'ts/components/ui/drop_down';
 | 
			
		||||
import { Identicon } from 'ts/components/ui/identicon';
 | 
			
		||||
import { Dispatcher } from 'ts/redux/dispatcher';
 | 
			
		||||
import { ProviderType } from 'ts/types';
 | 
			
		||||
import { colors } from 'ts/utils/colors';
 | 
			
		||||
import { constants } from 'ts/utils/constants';
 | 
			
		||||
import { utils } from 'ts/utils/utils';
 | 
			
		||||
 | 
			
		||||
const IDENTICON_DIAMETER = 32;
 | 
			
		||||
 | 
			
		||||
interface ProviderDisplayProps {
 | 
			
		||||
    dispatcher: Dispatcher;
 | 
			
		||||
    userAddress: string;
 | 
			
		||||
    networkId: number;
 | 
			
		||||
    injectedProviderName: string;
 | 
			
		||||
    providerType: ProviderType;
 | 
			
		||||
    onToggleLedgerDialog: () => void;
 | 
			
		||||
    blockchain: Blockchain;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface ProviderDisplayState {}
 | 
			
		||||
 | 
			
		||||
export class ProviderDisplay extends React.Component<ProviderDisplayProps, ProviderDisplayState> {
 | 
			
		||||
    public render() {
 | 
			
		||||
        const isAddressAvailable = !_.isEmpty(this.props.userAddress);
 | 
			
		||||
        const isExternallyInjectedProvider = ProviderType.Injected && this.props.injectedProviderName !== '0x Public';
 | 
			
		||||
        const displayAddress = isAddressAvailable
 | 
			
		||||
            ? utils.getAddressBeginAndEnd(this.props.userAddress)
 | 
			
		||||
            : isExternallyInjectedProvider ? 'Account locked' : '0x0000...0000';
 | 
			
		||||
        // If the "injected" provider is our fallback public node, then we want to
 | 
			
		||||
        // show the "connect a wallet" message instead of the providerName
 | 
			
		||||
        const injectedProviderName = isExternallyInjectedProvider
 | 
			
		||||
            ? this.props.injectedProviderName
 | 
			
		||||
            : 'Connect a wallet';
 | 
			
		||||
        const providerTitle =
 | 
			
		||||
            this.props.providerType === ProviderType.Injected ? injectedProviderName : 'Ledger Nano S';
 | 
			
		||||
        const hoverActiveNode = (
 | 
			
		||||
            <div className="flex right lg-pr0 md-pr2 sm-pr2" style={{ paddingTop: 16 }}>
 | 
			
		||||
                <div>
 | 
			
		||||
                    <Identicon address={this.props.userAddress} diameter={IDENTICON_DIAMETER} />
 | 
			
		||||
                </div>
 | 
			
		||||
                <div style={{ marginLeft: 12, paddingTop: 1 }}>
 | 
			
		||||
                    <div style={{ fontSize: 12, color: colors.amber800 }}>{providerTitle}</div>
 | 
			
		||||
                    <div style={{ fontSize: 14 }}>{displayAddress}</div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div
 | 
			
		||||
                    style={{ borderLeft: `1px solid ${colors.grey300}`, marginLeft: 17, paddingTop: 1 }}
 | 
			
		||||
                    className="px2"
 | 
			
		||||
                >
 | 
			
		||||
                    <i style={{ fontSize: 30, color: colors.grey300 }} className="zmdi zmdi zmdi-chevron-down" />
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
        const hasInjectedProvider =
 | 
			
		||||
            this.props.injectedProviderName !== '0x Public' && this.props.providerType === ProviderType.Injected;
 | 
			
		||||
        const hasLedgerProvider = this.props.providerType === ProviderType.Ledger;
 | 
			
		||||
        const horizontalPosition = hasInjectedProvider || hasLedgerProvider ? 'left' : 'middle';
 | 
			
		||||
        return (
 | 
			
		||||
            <div style={{ width: 'fit-content', height: 48, float: 'right' }}>
 | 
			
		||||
                <DropDown
 | 
			
		||||
                    hoverActiveNode={hoverActiveNode}
 | 
			
		||||
                    popoverContent={this.renderPopoverContent(hasInjectedProvider, hasLedgerProvider)}
 | 
			
		||||
                    anchorOrigin={{ horizontal: horizontalPosition, vertical: 'bottom' }}
 | 
			
		||||
                    targetOrigin={{ horizontal: horizontalPosition, vertical: 'top' }}
 | 
			
		||||
                    zDepth={1}
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    public renderPopoverContent(hasInjectedProvider: boolean, hasLedgerProvider: boolean) {
 | 
			
		||||
        if (hasInjectedProvider || hasLedgerProvider) {
 | 
			
		||||
            return (
 | 
			
		||||
                <ProviderPicker
 | 
			
		||||
                    dispatcher={this.props.dispatcher}
 | 
			
		||||
                    networkId={this.props.networkId}
 | 
			
		||||
                    injectedProviderName={this.props.injectedProviderName}
 | 
			
		||||
                    providerType={this.props.providerType}
 | 
			
		||||
                    onToggleLedgerDialog={this.props.onToggleLedgerDialog}
 | 
			
		||||
                    blockchain={this.props.blockchain}
 | 
			
		||||
                />
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            // Nothing to connect to, show install/info popover
 | 
			
		||||
            return (
 | 
			
		||||
                <div className="px2" style={{ maxWidth: 420 }}>
 | 
			
		||||
                    <div className="center h4 py2" style={{ color: colors.grey700 }}>
 | 
			
		||||
                        Choose a wallet:
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div className="flex pb3">
 | 
			
		||||
                        <div className="center px2">
 | 
			
		||||
                            <div style={{ color: colors.darkGrey }}>Install a browser wallet</div>
 | 
			
		||||
                            <div className="py2">
 | 
			
		||||
                                <img src="/images/metamask_or_parity.png" width="135" />
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div>
 | 
			
		||||
                                Use{' '}
 | 
			
		||||
                                <a
 | 
			
		||||
                                    href={constants.URL_METAMASK_CHROME_STORE}
 | 
			
		||||
                                    target="_blank"
 | 
			
		||||
                                    style={{ color: colors.lightBlueA700 }}
 | 
			
		||||
                                >
 | 
			
		||||
                                    Metamask
 | 
			
		||||
                                </a>{' '}
 | 
			
		||||
                                or{' '}
 | 
			
		||||
                                <a
 | 
			
		||||
                                    href={constants.URL_PARITY_CHROME_STORE}
 | 
			
		||||
                                    target="_blank"
 | 
			
		||||
                                    style={{ color: colors.lightBlueA700 }}
 | 
			
		||||
                                >
 | 
			
		||||
                                    Parity Signer
 | 
			
		||||
                                </a>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div>
 | 
			
		||||
                            <div
 | 
			
		||||
                                className="pl1 ml1"
 | 
			
		||||
                                style={{ borderLeft: `1px solid ${colors.grey300}`, height: 65 }}
 | 
			
		||||
                            />
 | 
			
		||||
                            <div className="py1">or</div>
 | 
			
		||||
                            <div
 | 
			
		||||
                                className="pl1 ml1"
 | 
			
		||||
                                style={{ borderLeft: `1px solid ${colors.grey300}`, height: 68 }}
 | 
			
		||||
                            />
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div className="px2 center">
 | 
			
		||||
                            <div style={{ color: colors.darkGrey }}>Connect to a ledger hardware wallet</div>
 | 
			
		||||
                            <div style={{ paddingTop: 21, paddingBottom: 29 }}>
 | 
			
		||||
                                <img src="/images/ledger_icon.png" style={{ width: 80 }} />
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div>
 | 
			
		||||
                                <RaisedButton
 | 
			
		||||
                                    style={{ width: '100%' }}
 | 
			
		||||
                                    label="Use Ledger"
 | 
			
		||||
                                    onClick={this.props.onToggleLedgerDialog}
 | 
			
		||||
                                />
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										81
									
								
								packages/website/ts/components/top_bar/provider_picker.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,81 @@
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { Blockchain } from 'ts/blockchain';
 | 
			
		||||
import { Dispatcher } from 'ts/redux/dispatcher';
 | 
			
		||||
import { ProviderType } from 'ts/types';
 | 
			
		||||
import { colors } from 'ts/utils/colors';
 | 
			
		||||
import { constants } from 'ts/utils/constants';
 | 
			
		||||
 | 
			
		||||
interface ProviderPickerProps {
 | 
			
		||||
    networkId: number;
 | 
			
		||||
    injectedProviderName: string;
 | 
			
		||||
    providerType: ProviderType;
 | 
			
		||||
    onToggleLedgerDialog: () => void;
 | 
			
		||||
    dispatcher: Dispatcher;
 | 
			
		||||
    blockchain: Blockchain;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface ProviderPickerState {}
 | 
			
		||||
 | 
			
		||||
export class ProviderPicker extends React.Component<ProviderPickerProps, ProviderPickerState> {
 | 
			
		||||
    public render() {
 | 
			
		||||
        const isLedgerSelected = this.props.providerType === ProviderType.Ledger;
 | 
			
		||||
        const menuStyle = {
 | 
			
		||||
            padding: 10,
 | 
			
		||||
            paddingTop: 15,
 | 
			
		||||
            paddingBottom: 15,
 | 
			
		||||
        };
 | 
			
		||||
        // Show dropdown with two options
 | 
			
		||||
        return (
 | 
			
		||||
            <div style={{ width: 225, overflow: 'hidden' }}>
 | 
			
		||||
                <RadioButtonGroup name="provider" defaultSelected={this.props.providerType}>
 | 
			
		||||
                    <RadioButton
 | 
			
		||||
                        onClick={this._onProviderRadioChanged.bind(this, ProviderType.Injected)}
 | 
			
		||||
                        style={{ ...menuStyle, backgroundColor: !isLedgerSelected && colors.grey50 }}
 | 
			
		||||
                        value={ProviderType.Injected}
 | 
			
		||||
                        label={this._renderLabel(this.props.injectedProviderName, !isLedgerSelected)}
 | 
			
		||||
                    />
 | 
			
		||||
                    <RadioButton
 | 
			
		||||
                        onClick={this._onProviderRadioChanged.bind(this, ProviderType.Ledger)}
 | 
			
		||||
                        style={{ ...menuStyle, backgroundColor: isLedgerSelected && colors.grey50 }}
 | 
			
		||||
                        value={ProviderType.Ledger}
 | 
			
		||||
                        label={this._renderLabel('Ledger Nano S', isLedgerSelected)}
 | 
			
		||||
                    />
 | 
			
		||||
                </RadioButtonGroup>
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    private _renderLabel(title: string, shouldShowNetwork: boolean) {
 | 
			
		||||
        const label = (
 | 
			
		||||
            <div className="flex">
 | 
			
		||||
                <div style={{ fontSize: 14 }}>{title}</div>
 | 
			
		||||
                {shouldShowNetwork && this._renderNetwork()}
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
        return label;
 | 
			
		||||
    }
 | 
			
		||||
    private _renderNetwork() {
 | 
			
		||||
        const networkName = constants.NETWORK_NAME_BY_ID[this.props.networkId];
 | 
			
		||||
        return (
 | 
			
		||||
            <div className="flex" style={{ marginTop: 1 }}>
 | 
			
		||||
                <div className="relative" style={{ width: 14, paddingLeft: 14 }}>
 | 
			
		||||
                    <img
 | 
			
		||||
                        src={`/images/network_icons/${networkName.toLowerCase()}.png`}
 | 
			
		||||
                        className="absolute"
 | 
			
		||||
                        style={{ top: 6, width: 10 }}
 | 
			
		||||
                    />
 | 
			
		||||
                </div>
 | 
			
		||||
                <div style={{ color: colors.lightGrey, fontSize: 11 }}>{networkName}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    private _onProviderRadioChanged(value: string) {
 | 
			
		||||
        if (value === ProviderType.Ledger) {
 | 
			
		||||
            this.props.onToggleLedgerDialog();
 | 
			
		||||
        } else {
 | 
			
		||||
            // tslint:disable-next-line:no-floating-promises
 | 
			
		||||
            this.props.blockchain.updateProviderToInjectedAsync();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,21 +1,31 @@
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
import Drawer from 'material-ui/Drawer';
 | 
			
		||||
import Menu from 'material-ui/Menu';
 | 
			
		||||
import MenuItem from 'material-ui/MenuItem';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { Link } from 'react-router-dom';
 | 
			
		||||
import ReactTooltip = require('react-tooltip');
 | 
			
		||||
import { Blockchain } from 'ts/blockchain';
 | 
			
		||||
import { PortalMenu } from 'ts/components/portal_menu';
 | 
			
		||||
import { TopBarMenuItem } from 'ts/components/top_bar_menu_item';
 | 
			
		||||
import { DropDownMenuItem } from 'ts/components/ui/drop_down_menu_item';
 | 
			
		||||
import { ProviderDisplay } from 'ts/components/top_bar/provider_display';
 | 
			
		||||
import { TopBarMenuItem } from 'ts/components/top_bar/top_bar_menu_item';
 | 
			
		||||
import { DropDown } from 'ts/components/ui/drop_down';
 | 
			
		||||
import { Identicon } from 'ts/components/ui/identicon';
 | 
			
		||||
import { DocsInfo } from 'ts/pages/documentation/docs_info';
 | 
			
		||||
import { NestedSidebarMenu } from 'ts/pages/shared/nested_sidebar_menu';
 | 
			
		||||
import { DocsMenu, MenuSubsectionsBySection, Styles, WebsitePaths } from 'ts/types';
 | 
			
		||||
import { Dispatcher } from 'ts/redux/dispatcher';
 | 
			
		||||
import { DocsMenu, MenuSubsectionsBySection, ProviderType, Styles, WebsitePaths } from 'ts/types';
 | 
			
		||||
import { colors } from 'ts/utils/colors';
 | 
			
		||||
import { constants } from 'ts/utils/constants';
 | 
			
		||||
 | 
			
		||||
interface TopBarProps {
 | 
			
		||||
    userAddress?: string;
 | 
			
		||||
    networkId?: number;
 | 
			
		||||
    injectedProviderName?: string;
 | 
			
		||||
    providerType?: ProviderType;
 | 
			
		||||
    onToggleLedgerDialog?: () => void;
 | 
			
		||||
    blockchain?: Blockchain;
 | 
			
		||||
    dispatcher?: Dispatcher;
 | 
			
		||||
    blockchainIsLoaded: boolean;
 | 
			
		||||
    location: Location;
 | 
			
		||||
    docsVersion?: string;
 | 
			
		||||
@@ -125,6 +135,15 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
 | 
			
		||||
            cursor: 'pointer',
 | 
			
		||||
            paddingTop: 16,
 | 
			
		||||
        };
 | 
			
		||||
        const hoverActiveNode = (
 | 
			
		||||
            <div className="flex relative" style={{ color: menuIconStyle.color }}>
 | 
			
		||||
                <div style={{ paddingRight: 10 }}>Developers</div>
 | 
			
		||||
                <div className="absolute" style={{ paddingLeft: 3, right: 3, top: -2 }}>
 | 
			
		||||
                    <i className="zmdi zmdi-caret-right" style={{ fontSize: 22 }} />
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
        const popoverContent = <Menu style={{ color: colors.darkGrey }}>{developerSectionMenuItems}</Menu>;
 | 
			
		||||
        return (
 | 
			
		||||
            <div style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style }} className="pb1">
 | 
			
		||||
                <div className={parentClassNames}>
 | 
			
		||||
@@ -138,11 +157,12 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
 | 
			
		||||
                    {!this._isViewingPortal() && (
 | 
			
		||||
                        <div className={menuClasses}>
 | 
			
		||||
                            <div className="flex justify-between">
 | 
			
		||||
                                <DropDownMenuItem
 | 
			
		||||
                                    title="Developers"
 | 
			
		||||
                                    subMenuItems={developerSectionMenuItems}
 | 
			
		||||
                                <DropDown
 | 
			
		||||
                                    hoverActiveNode={hoverActiveNode}
 | 
			
		||||
                                    popoverContent={popoverContent}
 | 
			
		||||
                                    anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }}
 | 
			
		||||
                                    targetOrigin={{ horizontal: 'middle', vertical: 'top' }}
 | 
			
		||||
                                    style={styles.menuItem}
 | 
			
		||||
                                    isNightVersion={isNightVersion}
 | 
			
		||||
                                />
 | 
			
		||||
                                <TopBarMenuItem
 | 
			
		||||
                                    title="Wiki"
 | 
			
		||||
@@ -167,10 +187,19 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    )}
 | 
			
		||||
                    {this.props.blockchainIsLoaded &&
 | 
			
		||||
                        !_.isEmpty(this.props.userAddress) && (
 | 
			
		||||
                            <div className="col col-5 sm-hide xs-hide">{this._renderUser()}</div>
 | 
			
		||||
                        )}
 | 
			
		||||
                    {this.props.blockchainIsLoaded && (
 | 
			
		||||
                        <div className="sm-hide xs-hide col col-5">
 | 
			
		||||
                            <ProviderDisplay
 | 
			
		||||
                                dispatcher={this.props.dispatcher}
 | 
			
		||||
                                userAddress={this.props.userAddress}
 | 
			
		||||
                                networkId={this.props.networkId}
 | 
			
		||||
                                injectedProviderName={this.props.injectedProviderName}
 | 
			
		||||
                                providerType={this.props.providerType}
 | 
			
		||||
                                onToggleLedgerDialog={this.props.onToggleLedgerDialog}
 | 
			
		||||
                                blockchain={this.props.blockchain}
 | 
			
		||||
                            />
 | 
			
		||||
                        </div>
 | 
			
		||||
                    )}
 | 
			
		||||
                    <div className={`col ${isFullWidthPage ? 'col-2 pl2' : 'col-1'} md-hide lg-hide`}>
 | 
			
		||||
                        <div style={menuIconStyle}>
 | 
			
		||||
                            <i className="zmdi zmdi-menu" onClick={this._onMenuButtonClick.bind(this)} />
 | 
			
		||||
@@ -1,36 +1,35 @@
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
import Menu from 'material-ui/Menu';
 | 
			
		||||
import Popover from 'material-ui/Popover';
 | 
			
		||||
import Popover, { PopoverAnimationVertical } from 'material-ui/Popover';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { colors } from 'ts/utils/colors';
 | 
			
		||||
import { MaterialUIPosition } from 'ts/types';
 | 
			
		||||
 | 
			
		||||
const CHECK_CLOSE_POPOVER_INTERVAL_MS = 300;
 | 
			
		||||
const DEFAULT_STYLE = {
 | 
			
		||||
    fontSize: 14,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface DropDownMenuItemProps {
 | 
			
		||||
    title: string;
 | 
			
		||||
    subMenuItems: React.ReactNode[];
 | 
			
		||||
interface DropDownProps {
 | 
			
		||||
    hoverActiveNode: React.ReactNode;
 | 
			
		||||
    popoverContent: React.ReactNode;
 | 
			
		||||
    anchorOrigin: MaterialUIPosition;
 | 
			
		||||
    targetOrigin: MaterialUIPosition;
 | 
			
		||||
    style?: React.CSSProperties;
 | 
			
		||||
    menuItemStyle?: React.CSSProperties;
 | 
			
		||||
    isNightVersion?: boolean;
 | 
			
		||||
    zDepth?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface DropDownMenuItemState {
 | 
			
		||||
interface DropDownState {
 | 
			
		||||
    isDropDownOpen: boolean;
 | 
			
		||||
    anchorEl?: HTMLInputElement;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class DropDownMenuItem extends React.Component<DropDownMenuItemProps, DropDownMenuItemState> {
 | 
			
		||||
    public static defaultProps: Partial<DropDownMenuItemProps> = {
 | 
			
		||||
export class DropDown extends React.Component<DropDownProps, DropDownState> {
 | 
			
		||||
    public static defaultProps: Partial<DropDownProps> = {
 | 
			
		||||
        style: DEFAULT_STYLE,
 | 
			
		||||
        menuItemStyle: DEFAULT_STYLE,
 | 
			
		||||
        isNightVersion: false,
 | 
			
		||||
        zDepth: 1,
 | 
			
		||||
    };
 | 
			
		||||
    private _isHovering: boolean;
 | 
			
		||||
    private _popoverCloseCheckIntervalId: number;
 | 
			
		||||
    constructor(props: DropDownMenuItemProps) {
 | 
			
		||||
    constructor(props: DropDownProps) {
 | 
			
		||||
        super(props);
 | 
			
		||||
        this.state = {
 | 
			
		||||
            isDropDownOpen: false,
 | 
			
		||||
@@ -44,30 +43,35 @@ export class DropDownMenuItem extends React.Component<DropDownMenuItemProps, Dro
 | 
			
		||||
    public componentWillUnmount() {
 | 
			
		||||
        window.clearInterval(this._popoverCloseCheckIntervalId);
 | 
			
		||||
    }
 | 
			
		||||
    public componentWillReceiveProps(nextProps: DropDownProps) {
 | 
			
		||||
        // HACK: If the popoverContent is updated to a different dimension and the users
 | 
			
		||||
        // mouse is no longer above it, the dropdown can enter an inconsistent state where
 | 
			
		||||
        // it believes the user is still hovering over it. In order to remedy this, we
 | 
			
		||||
        // call hoverOff whenever the dropdown receives updated props. This is a hack
 | 
			
		||||
        // because it will effectively close the dropdown on any prop update, barring
 | 
			
		||||
        // dropdowns from having dynamic content.
 | 
			
		||||
        this._onHoverOff();
 | 
			
		||||
    }
 | 
			
		||||
    public render() {
 | 
			
		||||
        const colorStyle = this.props.isNightVersion ? 'white' : this.props.style.color;
 | 
			
		||||
        return (
 | 
			
		||||
            <div
 | 
			
		||||
                style={{ ...this.props.style, color: colorStyle }}
 | 
			
		||||
                style={{ ...this.props.style, width: 'fit-content', height: '100%' }}
 | 
			
		||||
                onMouseEnter={this._onHover.bind(this)}
 | 
			
		||||
                onMouseLeave={this._onHoverOff.bind(this)}
 | 
			
		||||
            >
 | 
			
		||||
                <div className="flex relative">
 | 
			
		||||
                    <div style={{ paddingRight: 10 }}>{this.props.title}</div>
 | 
			
		||||
                    <div className="absolute" style={{ paddingLeft: 3, right: 3, top: -2 }}>
 | 
			
		||||
                        <i className="zmdi zmdi-caret-right" style={{ fontSize: 22 }} />
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                {this.props.hoverActiveNode}
 | 
			
		||||
                <Popover
 | 
			
		||||
                    open={this.state.isDropDownOpen}
 | 
			
		||||
                    anchorEl={this.state.anchorEl}
 | 
			
		||||
                    anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }}
 | 
			
		||||
                    targetOrigin={{ horizontal: 'middle', vertical: 'top' }}
 | 
			
		||||
                    anchorOrigin={this.props.anchorOrigin}
 | 
			
		||||
                    targetOrigin={this.props.targetOrigin}
 | 
			
		||||
                    onRequestClose={this._closePopover.bind(this)}
 | 
			
		||||
                    useLayerForClickAway={false}
 | 
			
		||||
                    animation={PopoverAnimationVertical}
 | 
			
		||||
                    zDepth={this.props.zDepth}
 | 
			
		||||
                >
 | 
			
		||||
                    <div onMouseEnter={this._onHover.bind(this)} onMouseLeave={this._onHoverOff.bind(this)}>
 | 
			
		||||
                        <Menu style={{ color: colors.grey }}>{this.props.subMenuItems}</Menu>
 | 
			
		||||
                        {this.props.popoverContent}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </Popover>
 | 
			
		||||
            </div>
 | 
			
		||||
@@ -87,7 +91,7 @@ export class DropDownMenuItem extends React.Component<DropDownMenuItemProps, Dro
 | 
			
		||||
            anchorEl: event.currentTarget,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    private _onHoverOff(event: React.FormEvent<HTMLInputElement>) {
 | 
			
		||||
    private _onHoverOff() {
 | 
			
		||||
        this._isHovering = false;
 | 
			
		||||
    }
 | 
			
		||||
    private _checkIfShouldClosePopover() {
 | 
			
		||||
@@ -1,39 +0,0 @@
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
import Paper from 'material-ui/Paper';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { DefaultPlayer as Video } from 'react-html5video';
 | 
			
		||||
import 'react-html5video/dist/styles.css';
 | 
			
		||||
import { utils } from 'ts/utils/utils';
 | 
			
		||||
 | 
			
		||||
interface LoadingProps {}
 | 
			
		||||
 | 
			
		||||
interface LoadingState {}
 | 
			
		||||
 | 
			
		||||
export class Loading extends React.Component<LoadingProps, LoadingState> {
 | 
			
		||||
    public render() {
 | 
			
		||||
        return (
 | 
			
		||||
            <div className="pt4 sm-px2 sm-pt2 sm-m1" style={{ height: 500 }}>
 | 
			
		||||
                <Paper className="mx-auto" style={{ maxWidth: 400 }}>
 | 
			
		||||
                    {utils.isUserOnMobile() ? (
 | 
			
		||||
                        <img className="p1" src="/gifs/0xAnimation.gif" width="96%" />
 | 
			
		||||
                    ) : (
 | 
			
		||||
                        <div style={{ pointerEvents: 'none' }}>
 | 
			
		||||
                            <Video
 | 
			
		||||
                                autoPlay={true}
 | 
			
		||||
                                loop={true}
 | 
			
		||||
                                muted={true}
 | 
			
		||||
                                controls={[]}
 | 
			
		||||
                                poster="/images/loading_poster.png"
 | 
			
		||||
                            >
 | 
			
		||||
                                <source src="/videos/0xAnimation.mp4" type="video/mp4" />
 | 
			
		||||
                            </Video>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    )}
 | 
			
		||||
                    <div className="center pt2" style={{ paddingBottom: 11 }}>
 | 
			
		||||
                        Connecting to the blockchain...
 | 
			
		||||
                    </div>
 | 
			
		||||
                </Paper>
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -6,14 +6,7 @@ import { Blockchain } from 'ts/blockchain';
 | 
			
		||||
import { GenerateOrderForm as GenerateOrderFormComponent } from 'ts/components/generate_order/generate_order_form';
 | 
			
		||||
import { Dispatcher } from 'ts/redux/dispatcher';
 | 
			
		||||
import { State } from 'ts/redux/reducer';
 | 
			
		||||
import {
 | 
			
		||||
    BlockchainErrs,
 | 
			
		||||
    HashData,
 | 
			
		||||
    SideToAssetToken,
 | 
			
		||||
    SignatureData,
 | 
			
		||||
    TokenByAddress,
 | 
			
		||||
    TokenStateByAddress,
 | 
			
		||||
} from 'ts/types';
 | 
			
		||||
import { BlockchainErrs, HashData, SideToAssetToken, SignatureData, TokenByAddress } from 'ts/types';
 | 
			
		||||
 | 
			
		||||
interface GenerateOrderFormProps {
 | 
			
		||||
    blockchain: Blockchain;
 | 
			
		||||
@@ -32,7 +25,7 @@ interface ConnectedState {
 | 
			
		||||
    networkId: number;
 | 
			
		||||
    sideToAssetToken: SideToAssetToken;
 | 
			
		||||
    tokenByAddress: TokenByAddress;
 | 
			
		||||
    tokenStateByAddress: TokenStateByAddress;
 | 
			
		||||
    lastForceTokenStateRefetch: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const mapStateToProps = (state: State, ownProps: GenerateOrderFormProps): ConnectedState => ({
 | 
			
		||||
@@ -45,8 +38,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)(
 | 
			
		||||
 
 | 
			
		||||
@@ -6,18 +6,20 @@ import { Dispatch } from 'redux';
 | 
			
		||||
import { Portal as PortalComponent, PortalAllProps as PortalComponentAllProps } from 'ts/components/portal';
 | 
			
		||||
import { Dispatcher } from 'ts/redux/dispatcher';
 | 
			
		||||
import { State } from 'ts/redux/reducer';
 | 
			
		||||
import { BlockchainErrs, HashData, Order, ScreenWidths, Side, TokenByAddress, TokenStateByAddress } from 'ts/types';
 | 
			
		||||
import { BlockchainErrs, HashData, Order, ProviderType, ScreenWidths, Side, TokenByAddress } from 'ts/types';
 | 
			
		||||
import { constants } from 'ts/utils/constants';
 | 
			
		||||
 | 
			
		||||
interface ConnectedState {
 | 
			
		||||
    blockchainErr: BlockchainErrs;
 | 
			
		||||
    blockchainIsLoaded: boolean;
 | 
			
		||||
    hashData: HashData;
 | 
			
		||||
    injectedProviderName: string;
 | 
			
		||||
    networkId: number;
 | 
			
		||||
    nodeVersion: string;
 | 
			
		||||
    orderFillAmount: BigNumber;
 | 
			
		||||
    providerType: ProviderType;
 | 
			
		||||
    tokenByAddress: TokenByAddress;
 | 
			
		||||
    tokenStateByAddress: TokenStateByAddress;
 | 
			
		||||
    lastForceTokenStateRefetch: number;
 | 
			
		||||
    userEtherBalance: BigNumber;
 | 
			
		||||
    screenWidth: ScreenWidths;
 | 
			
		||||
    shouldBlockchainErrDialogBeOpen: boolean;
 | 
			
		||||
@@ -57,14 +59,16 @@ const mapStateToProps = (state: State, ownProps: PortalComponentAllProps): Conne
 | 
			
		||||
    return {
 | 
			
		||||
        blockchainErr: state.blockchainErr,
 | 
			
		||||
        blockchainIsLoaded: state.blockchainIsLoaded,
 | 
			
		||||
        hashData,
 | 
			
		||||
        injectedProviderName: state.injectedProviderName,
 | 
			
		||||
        networkId: state.networkId,
 | 
			
		||||
        nodeVersion: state.nodeVersion,
 | 
			
		||||
        orderFillAmount: state.orderFillAmount,
 | 
			
		||||
        hashData,
 | 
			
		||||
        providerType: state.providerType,
 | 
			
		||||
        screenWidth: state.screenWidth,
 | 
			
		||||
        shouldBlockchainErrDialogBeOpen: state.shouldBlockchainErrDialogBeOpen,
 | 
			
		||||
        tokenByAddress: state.tokenByAddress,
 | 
			
		||||
        tokenStateByAddress: state.tokenStateByAddress,
 | 
			
		||||
        lastForceTokenStateRefetch: state.lastForceTokenStateRefetch,
 | 
			
		||||
        userAddress: state.userAddress,
 | 
			
		||||
        userEtherBalance: state.userEtherBalance,
 | 
			
		||||
        userSuppliedOrderCache: state.userSuppliedOrderCache,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								packages/website/ts/globals.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -10,7 +10,6 @@ declare module 'thenby';
 | 
			
		||||
declare module 'react-highlight';
 | 
			
		||||
declare module 'react-recaptcha';
 | 
			
		||||
declare module 'react-document-title';
 | 
			
		||||
declare module 'ledgerco';
 | 
			
		||||
declare module 'ethereumjs-tx';
 | 
			
		||||
 | 
			
		||||
declare module '*.json' {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
import { localStorage } from 'ts/local_storage/local_storage';
 | 
			
		||||
import { Token, TrackedTokensByUserAddress } from 'ts/types';
 | 
			
		||||
import { Token, TokenByAddress, TrackedTokensByUserAddress } from 'ts/types';
 | 
			
		||||
import { configs } from 'ts/utils/configs';
 | 
			
		||||
 | 
			
		||||
const TRACKED_TOKENS_KEY = 'trackedTokens';
 | 
			
		||||
@@ -39,18 +39,22 @@ export const trackedTokenStorage = {
 | 
			
		||||
        const trackedTokensByUserAddress = JSON.parse(trackedTokensJSONString);
 | 
			
		||||
        return trackedTokensByUserAddress;
 | 
			
		||||
    },
 | 
			
		||||
    getTrackedTokensIfExists(userAddress: string, networkId: number): Token[] {
 | 
			
		||||
    getTrackedTokensByAddress(userAddress: string, networkId: number): TokenByAddress {
 | 
			
		||||
        const trackedTokensByAddress: TokenByAddress = {};
 | 
			
		||||
        const trackedTokensJSONString = localStorage.getItemIfExists(TRACKED_TOKENS_KEY);
 | 
			
		||||
        if (_.isEmpty(trackedTokensJSONString)) {
 | 
			
		||||
            return undefined;
 | 
			
		||||
            return trackedTokensByAddress;
 | 
			
		||||
        }
 | 
			
		||||
        const trackedTokensByUserAddress = JSON.parse(trackedTokensJSONString);
 | 
			
		||||
        const trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress];
 | 
			
		||||
        if (_.isUndefined(trackedTokensByNetworkId)) {
 | 
			
		||||
            return undefined;
 | 
			
		||||
            return trackedTokensByAddress;
 | 
			
		||||
        }
 | 
			
		||||
        const trackedTokens = trackedTokensByNetworkId[networkId];
 | 
			
		||||
        return trackedTokens;
 | 
			
		||||
        _.each(trackedTokens, (trackedToken: Token) => {
 | 
			
		||||
            trackedTokensByAddress[trackedToken.address] = trackedToken;
 | 
			
		||||
        });
 | 
			
		||||
        return trackedTokensByAddress;
 | 
			
		||||
    },
 | 
			
		||||
    removeTrackedToken(userAddress: string, networkId: number, tokenAddress: string): void {
 | 
			
		||||
        const trackedTokensByUserAddress = this.getTrackedTokensByUserAddress();
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ import * as _ from 'lodash';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import * as DocumentTitle from 'react-document-title';
 | 
			
		||||
import { Footer } from 'ts/components/footer';
 | 
			
		||||
import { TopBar } from 'ts/components/top_bar';
 | 
			
		||||
import { TopBar } from 'ts/components/top_bar/top_bar';
 | 
			
		||||
import { Profile } from 'ts/pages/about/profile';
 | 
			
		||||
import { ProfileInfo, Styles } from 'ts/types';
 | 
			
		||||
import { colors } from 'ts/utils/colors';
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ import * as React from 'react';
 | 
			
		||||
import DocumentTitle = require('react-document-title');
 | 
			
		||||
import { scroller } from 'react-scroll';
 | 
			
		||||
import semverSort = require('semver-sort');
 | 
			
		||||
import { TopBar } from 'ts/components/top_bar';
 | 
			
		||||
import { TopBar } from 'ts/components/top_bar/top_bar';
 | 
			
		||||
import { Badge } from 'ts/components/ui/badge';
 | 
			
		||||
import { Comment } from 'ts/pages/documentation/comment';
 | 
			
		||||
import { DocsInfo } from 'ts/pages/documentation/docs_info';
 | 
			
		||||
@@ -40,9 +40,9 @@ import { utils } from 'ts/utils/utils';
 | 
			
		||||
const SCROLL_TOP_ID = 'docsScrollTop';
 | 
			
		||||
 | 
			
		||||
const networkNameToColor: { [network: string]: string } = {
 | 
			
		||||
    [Networks.kovan]: colors.purple,
 | 
			
		||||
    [Networks.ropsten]: colors.red,
 | 
			
		||||
    [Networks.mainnet]: colors.turquois,
 | 
			
		||||
    [Networks.Kovan]: colors.purple,
 | 
			
		||||
    [Networks.Ropsten]: colors.red,
 | 
			
		||||
    [Networks.Mainnet]: colors.turquois,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface DocumentationAllProps {
 | 
			
		||||
@@ -78,8 +78,10 @@ const styles: Styles = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export class Documentation extends React.Component<DocumentationAllProps, DocumentationState> {
 | 
			
		||||
    private _isUnmounted: boolean;
 | 
			
		||||
    constructor(props: DocumentationAllProps) {
 | 
			
		||||
        super(props);
 | 
			
		||||
        this._isUnmounted = false;
 | 
			
		||||
        this.state = {
 | 
			
		||||
            docAgnosticFormat: undefined,
 | 
			
		||||
        };
 | 
			
		||||
@@ -92,6 +94,9 @@ export class Documentation extends React.Component<DocumentationAllProps, Docume
 | 
			
		||||
        // tslint:disable-next-line:no-floating-promises
 | 
			
		||||
        this._fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists);
 | 
			
		||||
    }
 | 
			
		||||
    public componentWillUnmount() {
 | 
			
		||||
        this._isUnmounted = true;
 | 
			
		||||
    }
 | 
			
		||||
    public render() {
 | 
			
		||||
        const menuSubsectionsBySection = _.isUndefined(this.state.docAgnosticFormat)
 | 
			
		||||
            ? {}
 | 
			
		||||
@@ -367,13 +372,15 @@ export class Documentation extends React.Component<DocumentationAllProps, Docume
 | 
			
		||||
        );
 | 
			
		||||
        const docAgnosticFormat = this.props.docsInfo.convertToDocAgnosticFormat(versionDocObj as DoxityDocObj);
 | 
			
		||||
 | 
			
		||||
        this.setState(
 | 
			
		||||
            {
 | 
			
		||||
                docAgnosticFormat,
 | 
			
		||||
            },
 | 
			
		||||
            () => {
 | 
			
		||||
                this._scrollToHash();
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
        if (!this._isUnmounted) {
 | 
			
		||||
            this.setState(
 | 
			
		||||
                {
 | 
			
		||||
                    docAgnosticFormat,
 | 
			
		||||
                },
 | 
			
		||||
                () => {
 | 
			
		||||
                    this._scrollToHash();
 | 
			
		||||
                },
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ import * as _ from 'lodash';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import * as DocumentTitle from 'react-document-title';
 | 
			
		||||
import { Footer } from 'ts/components/footer';
 | 
			
		||||
import { TopBar } from 'ts/components/top_bar';
 | 
			
		||||
import { TopBar } from 'ts/components/top_bar/top_bar';
 | 
			
		||||
import { Question } from 'ts/pages/faq/question';
 | 
			
		||||
import { FAQQuestion, FAQSection, Styles, WebsitePaths } from 'ts/types';
 | 
			
		||||
import { colors } from 'ts/utils/colors';
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ import * as React from 'react';
 | 
			
		||||
import DocumentTitle = require('react-document-title');
 | 
			
		||||
import { Link } from 'react-router-dom';
 | 
			
		||||
import { Footer } from 'ts/components/footer';
 | 
			
		||||
import { TopBar } from 'ts/components/top_bar';
 | 
			
		||||
import { TopBar } from 'ts/components/top_bar/top_bar';
 | 
			
		||||
import { ScreenWidths, WebsitePaths } from 'ts/types';
 | 
			
		||||
import { colors } from 'ts/utils/colors';
 | 
			
		||||
import { constants } from 'ts/utils/constants';
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { Footer } from 'ts/components/footer';
 | 
			
		||||
import { TopBar } from 'ts/components/top_bar';
 | 
			
		||||
import { TopBar } from 'ts/components/top_bar/top_bar';
 | 
			
		||||
import { Styles } from 'ts/types';
 | 
			
		||||
 | 
			
		||||
export interface NotFoundProps {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ import CircularProgress from 'material-ui/CircularProgress';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import DocumentTitle = require('react-document-title');
 | 
			
		||||
import { scroller } from 'react-scroll';
 | 
			
		||||
import { TopBar } from 'ts/components/top_bar';
 | 
			
		||||
import { TopBar } from 'ts/components/top_bar/top_bar';
 | 
			
		||||
import { MarkdownSection } from 'ts/pages/shared/markdown_section';
 | 
			
		||||
import { NestedSidebarMenu } from 'ts/pages/shared/nested_sidebar_menu';
 | 
			
		||||
import { SectionHeader } from 'ts/pages/shared/section_header';
 | 
			
		||||
@@ -45,8 +45,10 @@ const styles: Styles = {
 | 
			
		||||
 | 
			
		||||
export class Wiki extends React.Component<WikiProps, WikiState> {
 | 
			
		||||
    private _wikiBackoffTimeoutId: number;
 | 
			
		||||
    private _isUnmounted: boolean;
 | 
			
		||||
    constructor(props: WikiProps) {
 | 
			
		||||
        super(props);
 | 
			
		||||
        this._isUnmounted = false;
 | 
			
		||||
        this.state = {
 | 
			
		||||
            articlesBySection: undefined,
 | 
			
		||||
        };
 | 
			
		||||
@@ -56,6 +58,7 @@ export class Wiki extends React.Component<WikiProps, WikiState> {
 | 
			
		||||
        this._fetchArticlesBySectionAsync();
 | 
			
		||||
    }
 | 
			
		||||
    public componentWillUnmount() {
 | 
			
		||||
        this._isUnmounted = true;
 | 
			
		||||
        clearTimeout(this._wikiBackoffTimeoutId);
 | 
			
		||||
    }
 | 
			
		||||
    public render() {
 | 
			
		||||
@@ -179,14 +182,16 @@ export class Wiki extends React.Component<WikiProps, WikiState> {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const articlesBySection = await response.json();
 | 
			
		||||
        this.setState(
 | 
			
		||||
            {
 | 
			
		||||
                articlesBySection,
 | 
			
		||||
            },
 | 
			
		||||
            () => {
 | 
			
		||||
                this._scrollToHash();
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
        if (!this._isUnmounted) {
 | 
			
		||||
            this.setState(
 | 
			
		||||
                {
 | 
			
		||||
                    articlesBySection,
 | 
			
		||||
                },
 | 
			
		||||
                () => {
 | 
			
		||||
                    this._scrollToHash();
 | 
			
		||||
                },
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    private _getMenuSubsectionsBySection(articlesBySection: ArticlesBySection) {
 | 
			
		||||
        const sectionNames = _.keys(articlesBySection);
 | 
			
		||||
 
 | 
			
		||||
@@ -9,9 +9,10 @@ import {
 | 
			
		||||
    ProviderType,
 | 
			
		||||
    ScreenWidths,
 | 
			
		||||
    Side,
 | 
			
		||||
    SideToAssetToken,
 | 
			
		||||
    SignatureData,
 | 
			
		||||
    Token,
 | 
			
		||||
    TokenStateByAddress,
 | 
			
		||||
    TokenByAddress,
 | 
			
		||||
} from 'ts/types';
 | 
			
		||||
 | 
			
		||||
export class Dispatcher {
 | 
			
		||||
@@ -120,9 +121,20 @@ export class Dispatcher {
 | 
			
		||||
            type: ActionTypes.RemoveTokenFromTokenByAddress,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    public clearTokenByAddress() {
 | 
			
		||||
    public batchDispatch(
 | 
			
		||||
        tokenByAddress: TokenByAddress,
 | 
			
		||||
        networkId: number,
 | 
			
		||||
        userAddress: string,
 | 
			
		||||
        sideToAssetToken: SideToAssetToken,
 | 
			
		||||
    ) {
 | 
			
		||||
        this._dispatch({
 | 
			
		||||
            type: ActionTypes.ClearTokenByAddress,
 | 
			
		||||
            data: {
 | 
			
		||||
                tokenByAddress,
 | 
			
		||||
                networkId,
 | 
			
		||||
                userAddress,
 | 
			
		||||
                sideToAssetToken,
 | 
			
		||||
            },
 | 
			
		||||
            type: ActionTypes.BatchDispatch,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    public updateTokenByAddress(tokens: Token[]) {
 | 
			
		||||
@@ -131,43 +143,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,
 | 
			
		||||
@@ -12,8 +13,6 @@ import {
 | 
			
		||||
    SideToAssetToken,
 | 
			
		||||
    SignatureData,
 | 
			
		||||
    TokenByAddress,
 | 
			
		||||
    TokenState,
 | 
			
		||||
    TokenStateByAddress,
 | 
			
		||||
} from 'ts/types';
 | 
			
		||||
import { utils } from 'ts/utils/utils';
 | 
			
		||||
 | 
			
		||||
@@ -37,7 +36,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 +75,7 @@ const INITIAL_STATE: State = {
 | 
			
		||||
        [Side.Receive]: {},
 | 
			
		||||
    },
 | 
			
		||||
    tokenByAddress: {},
 | 
			
		||||
    tokenStateByAddress: {},
 | 
			
		||||
    lastForceTokenStateRefetch: moment().unix(),
 | 
			
		||||
    userAddress: '',
 | 
			
		||||
    userEtherBalance: new BigNumber(0),
 | 
			
		||||
    userSuppliedOrderCache: undefined,
 | 
			
		||||
@@ -139,13 +138,6 @@ export function reducer(state: State = INITIAL_STATE, action: Action) {
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case ActionTypes.ClearTokenByAddress: {
 | 
			
		||||
            return {
 | 
			
		||||
                ...state,
 | 
			
		||||
                tokenByAddress: {},
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case ActionTypes.AddTokenToTokenByAddress: {
 | 
			
		||||
            const newTokenByAddress = state.tokenByAddress;
 | 
			
		||||
            newTokenByAddress[action.data.address] = action.data;
 | 
			
		||||
@@ -180,74 +172,21 @@ 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.BatchDispatch: {
 | 
			
		||||
            return {
 | 
			
		||||
                ...state,
 | 
			
		||||
                tokenStateByAddress,
 | 
			
		||||
                networkId: action.data.networkId,
 | 
			
		||||
                userAddress: action.data.userAddress,
 | 
			
		||||
                sideToAssetToken: action.data.sideToAssetToken,
 | 
			
		||||
                tokenByAddress: action.data.tokenByAddress,
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case ActionTypes.RemoveFromTokenStateByAddress: {
 | 
			
		||||
            const tokenStateByAddress = state.tokenStateByAddress;
 | 
			
		||||
            const tokenAddress = action.data;
 | 
			
		||||
            delete tokenStateByAddress[tokenAddress];
 | 
			
		||||
        case ActionTypes.ForceTokenStateRefetch:
 | 
			
		||||
            return {
 | 
			
		||||
                ...state,
 | 
			
		||||
                tokenStateByAddress,
 | 
			
		||||
                lastForceTokenStateRefetch: moment().unix(),
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -25,10 +25,6 @@ export interface TokenState {
 | 
			
		||||
    balance: BigNumber;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface TokenStateByAddress {
 | 
			
		||||
    [address: string]: TokenState;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface AssetToken {
 | 
			
		||||
    address?: string;
 | 
			
		||||
    amount?: BigNumber;
 | 
			
		||||
@@ -110,12 +106,12 @@ export enum BalanceErrs {
 | 
			
		||||
 | 
			
		||||
export enum ActionTypes {
 | 
			
		||||
    // Portal
 | 
			
		||||
    BatchDispatch = 'BATCH_DISPATCH',
 | 
			
		||||
    UpdateScreenWidth = 'UPDATE_SCREEN_WIDTH',
 | 
			
		||||
    UpdateNodeVersion = 'UPDATE_NODE_VERSION',
 | 
			
		||||
    ResetState = 'RESET_STATE',
 | 
			
		||||
    AddTokenToTokenByAddress = 'ADD_TOKEN_TO_TOKEN_BY_ADDRESS',
 | 
			
		||||
    BlockchainErrEncountered = 'BLOCKCHAIN_ERR_ENCOUNTERED',
 | 
			
		||||
    ClearTokenByAddress = 'CLEAR_TOKEN_BY_ADDRESS',
 | 
			
		||||
    UpdateBlockchainIsLoaded = 'UPDATE_BLOCKCHAIN_IS_LOADED',
 | 
			
		||||
    UpdateNetworkId = 'UPDATE_NETWORK_ID',
 | 
			
		||||
    UpdateChosenAssetToken = 'UPDATE_CHOSEN_ASSET_TOKEN',
 | 
			
		||||
@@ -125,11 +121,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',
 | 
			
		||||
@@ -496,16 +488,6 @@ export interface SignPersonalMessageParams {
 | 
			
		||||
    data: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface TxParams {
 | 
			
		||||
    nonce: string;
 | 
			
		||||
    gasPrice?: number;
 | 
			
		||||
    gasLimit: string;
 | 
			
		||||
    to: string;
 | 
			
		||||
    value?: string;
 | 
			
		||||
    data?: string;
 | 
			
		||||
    chainId: number; // EIP 155 chainId - mainnet: 1, ropsten: 3
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface PublicNodeUrlsByNetworkId {
 | 
			
		||||
    [networkId: number]: string[];
 | 
			
		||||
}
 | 
			
		||||
@@ -610,10 +592,10 @@ export interface AddressByContractName {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum Networks {
 | 
			
		||||
    mainnet = 'Mainnet',
 | 
			
		||||
    kovan = 'Kovan',
 | 
			
		||||
    ropsten = 'Ropsten',
 | 
			
		||||
    rinkeby = 'Rinkeby',
 | 
			
		||||
    Mainnet = 'Mainnet',
 | 
			
		||||
    Kovan = 'Kovan',
 | 
			
		||||
    Ropsten = 'Ropsten',
 | 
			
		||||
    Rinkeby = 'Rinkeby',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum AbiTypes {
 | 
			
		||||
@@ -678,4 +660,9 @@ export enum SmartContractDocSections {
 | 
			
		||||
    ZRXToken = 'ZRXToken',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface MaterialUIPosition {
 | 
			
		||||
    vertical: 'bottom' | 'top' | 'center';
 | 
			
		||||
    horizontal: 'left' | 'middle' | 'right';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// tslint:disable:max-file-line-count
 | 
			
		||||
 
 | 
			
		||||
@@ -16,24 +16,24 @@ const isDevelopment = _.includes(
 | 
			
		||||
const INFURA_API_KEY = 'T5WSC8cautR4KXyYgsRs';
 | 
			
		||||
 | 
			
		||||
export const configs = {
 | 
			
		||||
    BACKEND_BASE_URL: isDevelopment ? 'https://localhost:3001' : 'https://website-api.0xproject.com',
 | 
			
		||||
    BACKEND_BASE_URL: 'https://website-api.0xproject.com',
 | 
			
		||||
    BASE_URL,
 | 
			
		||||
    BITLY_ACCESS_TOKEN: 'ffc4c1a31e5143848fb7c523b39f91b9b213d208',
 | 
			
		||||
    CONTRACT_ADDRESS: {
 | 
			
		||||
        '1.0.0': {
 | 
			
		||||
            [Networks.mainnet]: {
 | 
			
		||||
            [Networks.Mainnet]: {
 | 
			
		||||
                [SmartContractDocSections.Exchange]: '0x12459c951127e0c374ff9105dda097662a027093',
 | 
			
		||||
                [SmartContractDocSections.TokenTransferProxy]: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4',
 | 
			
		||||
                [SmartContractDocSections.ZRXToken]: '0xe41d2489571d322189246dafa5ebde1f4699f498',
 | 
			
		||||
                [SmartContractDocSections.TokenRegistry]: '0x926a74c5c36adf004c87399e65f75628b0f98d2c',
 | 
			
		||||
            },
 | 
			
		||||
            [Networks.ropsten]: {
 | 
			
		||||
            [Networks.Ropsten]: {
 | 
			
		||||
                [SmartContractDocSections.Exchange]: '0x479cc461fecd078f766ecc58533d6f69580cf3ac',
 | 
			
		||||
                [SmartContractDocSections.TokenTransferProxy]: '0x4e9aad8184de8833365fea970cd9149372fdf1e6',
 | 
			
		||||
                [SmartContractDocSections.ZRXToken]: '0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d',
 | 
			
		||||
                [SmartContractDocSections.TokenRegistry]: '0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed',
 | 
			
		||||
            },
 | 
			
		||||
            [Networks.kovan]: {
 | 
			
		||||
            [Networks.Kovan]: {
 | 
			
		||||
                [SmartContractDocSections.Exchange]: '0x90fe2af704b34e0224bf2299c838e04d4dcf1364',
 | 
			
		||||
                [SmartContractDocSections.TokenTransferProxy]: '0x087Eed4Bc1ee3DE49BeFbd66C662B434B15d49d4',
 | 
			
		||||
                [SmartContractDocSections.ZRXToken]: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570',
 | 
			
		||||
@@ -120,6 +120,8 @@ export const configs = {
 | 
			
		||||
    PUBLIC_NODE_URLS_BY_NETWORK_ID: {
 | 
			
		||||
        [1]: [`https://mainnet.infura.io/${INFURA_API_KEY}`, 'https://mainnet.0xproject.com'],
 | 
			
		||||
        [42]: [`https://kovan.infura.io/${INFURA_API_KEY}`, 'https://kovan.0xproject.com'],
 | 
			
		||||
        [3]: [`https://ropsten.infura.io/${INFURA_API_KEY}`],
 | 
			
		||||
        [4]: [`https://rinkeby.infura.io/${INFURA_API_KEY}`],
 | 
			
		||||
    } as PublicNodeUrlsByNetworkId,
 | 
			
		||||
    SHOULD_DEPRECATE_OLD_WETH_TOKEN: true,
 | 
			
		||||
    SYMBOLS_OF_MINTABLE_TOKENS: ['MKR', 'MLN', 'GNT', 'DGD', 'REP'],
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,8 @@ export const constants = {
 | 
			
		||||
        1: 4145578,
 | 
			
		||||
        42: 3117574,
 | 
			
		||||
        50: 0,
 | 
			
		||||
        3: 1719261,
 | 
			
		||||
        4: 1570919,
 | 
			
		||||
    } as { [networkId: number]: number },
 | 
			
		||||
    HOME_SCROLL_DURATION_MS: 500,
 | 
			
		||||
    HTTP_NO_CONTENT_STATUS_CODE: 204,
 | 
			
		||||
@@ -19,19 +21,19 @@ export const constants = {
 | 
			
		||||
    MAINNET_NAME: 'Main network',
 | 
			
		||||
    MINT_AMOUNT: new BigNumber('100000000000000000000'),
 | 
			
		||||
    NETWORK_ID_MAINNET: 1,
 | 
			
		||||
    NETWORK_ID_TESTNET: 42,
 | 
			
		||||
    NETWORK_ID_KOVAN: 42,
 | 
			
		||||
    NETWORK_ID_TESTRPC: 50,
 | 
			
		||||
    NETWORK_NAME_BY_ID: {
 | 
			
		||||
        1: Networks.mainnet,
 | 
			
		||||
        3: Networks.ropsten,
 | 
			
		||||
        4: Networks.rinkeby,
 | 
			
		||||
        42: Networks.kovan,
 | 
			
		||||
        1: Networks.Mainnet,
 | 
			
		||||
        3: Networks.Ropsten,
 | 
			
		||||
        4: Networks.Rinkeby,
 | 
			
		||||
        42: Networks.Kovan,
 | 
			
		||||
    } as { [symbol: number]: string },
 | 
			
		||||
    NETWORK_ID_BY_NAME: {
 | 
			
		||||
        [Networks.mainnet]: 1,
 | 
			
		||||
        [Networks.ropsten]: 3,
 | 
			
		||||
        [Networks.rinkeby]: 4,
 | 
			
		||||
        [Networks.kovan]: 42,
 | 
			
		||||
        [Networks.Mainnet]: 1,
 | 
			
		||||
        [Networks.Ropsten]: 3,
 | 
			
		||||
        [Networks.Rinkeby]: 4,
 | 
			
		||||
        [Networks.Kovan]: 42,
 | 
			
		||||
    } as { [networkName: string]: number },
 | 
			
		||||
    NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
 | 
			
		||||
    PROVIDER_NAME_LEDGER: 'Ledger',
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ export const muiTheme = getMuiTheme({
 | 
			
		||||
        textColor: colors.black,
 | 
			
		||||
    },
 | 
			
		||||
    palette: {
 | 
			
		||||
        accent1Color: colors.lightBlueA700,
 | 
			
		||||
        pickerHeaderColor: colors.lightBlue,
 | 
			
		||||
        primary1Color: colors.lightBlue,
 | 
			
		||||
        primary2Color: colors.lightBlue,
 | 
			
		||||
 
 | 
			
		||||
@@ -151,7 +151,7 @@ export const utils = {
 | 
			
		||||
        if (_.isUndefined(networkName)) {
 | 
			
		||||
            return undefined;
 | 
			
		||||
        }
 | 
			
		||||
        const etherScanPrefix = networkName === Networks.mainnet ? '' : `${networkName.toLowerCase()}.`;
 | 
			
		||||
        const etherScanPrefix = networkName === Networks.Mainnet ? '' : `${networkName.toLowerCase()}.`;
 | 
			
		||||
        return `https://${etherScanPrefix}etherscan.io/${suffix}/${addressOrTxHash}`;
 | 
			
		||||
    },
 | 
			
		||||
    setUrlHash(anchorId: string) {
 | 
			
		||||
@@ -183,7 +183,7 @@ export const utils = {
 | 
			
		||||
    // after a user was prompted to sign a message or send a transaction and decided to
 | 
			
		||||
    // reject the request.
 | 
			
		||||
    didUserDenyWeb3Request(errMsg: string) {
 | 
			
		||||
        const metamaskDenialErrMsg = 'User denied message';
 | 
			
		||||
        const metamaskDenialErrMsg = 'User denied';
 | 
			
		||||
        const paritySignerDenialErrMsg = 'Request has been rejected';
 | 
			
		||||
        const ledgerDenialErrMsg = 'Invalid status 6985';
 | 
			
		||||
        const isUserDeniedErrMsg =
 | 
			
		||||
@@ -276,4 +276,10 @@ export const utils = {
 | 
			
		||||
            exchangeContractErrorToHumanReadableError[error] || ZeroExErrorToHumanReadableError[error];
 | 
			
		||||
        return humanReadableErrorMsg;
 | 
			
		||||
    },
 | 
			
		||||
    isParityNode(nodeVersion: string): boolean {
 | 
			
		||||
        return _.includes(nodeVersion, 'Parity');
 | 
			
		||||
    },
 | 
			
		||||
    isTestRpc(nodeVersion: string): boolean {
 | 
			
		||||
        return _.includes(nodeVersion, 'TestRPC');
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -24,9 +24,6 @@ export class Web3Wrapper {
 | 
			
		||||
 | 
			
		||||
        this._web3 = new Web3();
 | 
			
		||||
        this._web3.setProvider(provider);
 | 
			
		||||
 | 
			
		||||
        // tslint:disable-next-line:no-floating-promises
 | 
			
		||||
        this._startEmittingNetworkConnectionAndUserBalanceStateAsync();
 | 
			
		||||
    }
 | 
			
		||||
    public isAddress(address: string) {
 | 
			
		||||
        return this._web3.isAddress(address);
 | 
			
		||||
@@ -90,11 +87,7 @@ export class Web3Wrapper {
 | 
			
		||||
    public updatePrevUserAddress(userAddress: string) {
 | 
			
		||||
        this._prevUserAddress = userAddress;
 | 
			
		||||
    }
 | 
			
		||||
    private async _getNetworkAsync() {
 | 
			
		||||
        const networkId = await promisify(this._web3.version.getNetwork)();
 | 
			
		||||
        return networkId;
 | 
			
		||||
    }
 | 
			
		||||
    private async _startEmittingNetworkConnectionAndUserBalanceStateAsync() {
 | 
			
		||||
    public startEmittingNetworkConnectionAndUserBalanceState() {
 | 
			
		||||
        if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) {
 | 
			
		||||
            return; // we are already emitting the state
 | 
			
		||||
        }
 | 
			
		||||
@@ -127,7 +120,7 @@ export class Web3Wrapper {
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Check for user ether balance changes
 | 
			
		||||
                    if (userAddressIfExists !== '') {
 | 
			
		||||
                    if (!_.isEmpty(userAddressIfExists)) {
 | 
			
		||||
                        await this._updateUserEtherBalanceAsync(userAddressIfExists);
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
@@ -140,11 +133,15 @@ export class Web3Wrapper {
 | 
			
		||||
            },
 | 
			
		||||
            5000,
 | 
			
		||||
            (err: Error) => {
 | 
			
		||||
                utils.consoleLog(`Watching network and balances failed: ${err}`);
 | 
			
		||||
                utils.consoleLog(`Watching network and balances failed: ${err.stack}`);
 | 
			
		||||
                this._stopEmittingNetworkConnectionAndUserBalanceStateAsync();
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    private async _getNetworkAsync() {
 | 
			
		||||
        const networkId = await promisify(this._web3.version.getNetwork)();
 | 
			
		||||
        return networkId;
 | 
			
		||||
    }
 | 
			
		||||
    private async _updateUserEtherBalanceAsync(userAddress: string) {
 | 
			
		||||
        const balance = await this.getBalanceInEthAsync(userAddress);
 | 
			
		||||
        if (!balance.eq(this._prevUserEtherBalanceInEth)) {
 | 
			
		||||
@@ -153,6 +150,8 @@ export class Web3Wrapper {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    private _stopEmittingNetworkConnectionAndUserBalanceStateAsync() {
 | 
			
		||||
        intervalUtils.clearAsyncExcludingInterval(this._watchNetworkAndBalanceIntervalId);
 | 
			
		||||
        if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) {
 | 
			
		||||
            intervalUtils.clearAsyncExcludingInterval(this._watchNetworkAndBalanceIntervalId);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||