Merge pull request #444 from 0xProject/dedupWeb3Wrapper

Remove custom web3Wrapper from website
This commit is contained in:
Fabio Berger 2018-03-11 12:49:15 +01:00 committed by GitHub
commit 870ba445b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 308 additions and 278 deletions

View File

@ -1,5 +1,9 @@
# CHANGELOG # CHANGELOG
## v0.2.1 _TBD_
* Add a `getProvider` method (#444)
## v0.2.0 _March 4, 2018_ ## v0.2.0 _March 4, 2018_
* Ensure all returned user addresses are lowercase (#373) * Ensure all returned user addresses are lowercase (#373)

View File

@ -11,7 +11,7 @@ export class Web3Wrapper {
if (_.isUndefined((provider as any).sendAsync)) { if (_.isUndefined((provider as any).sendAsync)) {
// Web3@1.0 provider doesn't support synchronous http requests, // Web3@1.0 provider doesn't support synchronous http requests,
// so it only has an async `send` method, instead of a `send` and `sendAsync` in web3@0.x.x` // so it only has an async `send` method, instead of a `send` and `sendAsync` in web3@0.x.x`
// We re-assign the send method so that Web3@1.0 providers work with 0x.js // We re-assign the send method so that Web3@1.0 providers work with @0xproject/web3-wrapper
(provider as any).sendAsync = (provider as any).send; (provider as any).sendAsync = (provider as any).send;
} }
this._web3 = new Web3(); this._web3 = new Web3();
@ -22,6 +22,9 @@ export class Web3Wrapper {
public getContractDefaults(): Partial<TxData> { public getContractDefaults(): Partial<TxData> {
return this._defaults; return this._defaults;
} }
public getProvider(): Web3.Provider {
return this._web3.currentProvider;
}
public setProvider(provider: Web3.Provider) { public setProvider(provider: Web3.Provider) {
this._web3.setProvider(provider); this._web3.setProvider(provider);
} }

View File

@ -22,6 +22,7 @@
"@0xproject/react-docs": "^0.0.1", "@0xproject/react-docs": "^0.0.1",
"@0xproject/react-shared": "^0.0.1", "@0xproject/react-shared": "^0.0.1",
"@0xproject/subproviders": "^0.7.0", "@0xproject/subproviders": "^0.7.0",
"@0xproject/web3-wrapper": "^0.2.1",
"@0xproject/utils": "^0.4.1", "@0xproject/utils": "^0.4.1",
"accounting": "^0.4.1", "accounting": "^0.4.1",
"basscss": "^8.0.3", "basscss": "^8.0.3",

View File

@ -24,9 +24,11 @@ import {
RedundantRPCSubprovider, RedundantRPCSubprovider,
} from '@0xproject/subproviders'; } from '@0xproject/subproviders';
import { BigNumber, intervalUtils, promisify } from '@0xproject/utils'; import { BigNumber, intervalUtils, promisify } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as React from 'react'; import * as React from 'react';
import contract = require('truffle-contract'); import contract = require('truffle-contract');
import { BlockchainWatcher } from 'ts/blockchain_watcher';
import { TokenSendCompleted } from 'ts/components/flash_messages/token_send_completed'; import { TokenSendCompleted } from 'ts/components/flash_messages/token_send_completed';
import { TransactionSubmitted } from 'ts/components/flash_messages/transaction_submitted'; import { TransactionSubmitted } from 'ts/components/flash_messages/transaction_submitted';
import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage'; import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage';
@ -47,7 +49,6 @@ import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants'; import { constants } from 'ts/utils/constants';
import { errorReporter } from 'ts/utils/error_reporter'; import { errorReporter } from 'ts/utils/error_reporter';
import { utils } from 'ts/utils/utils'; import { utils } from 'ts/utils/utils';
import { Web3Wrapper } from 'ts/web3_wrapper';
import Web3 = require('web3'); import Web3 = require('web3');
import ProviderEngine = require('web3-provider-engine'); import ProviderEngine = require('web3-provider-engine');
import FilterSubprovider = require('web3-provider-engine/subproviders/filters'); import FilterSubprovider = require('web3-provider-engine/subproviders/filters');
@ -63,8 +64,8 @@ export class Blockchain {
private _zeroEx: ZeroEx; private _zeroEx: ZeroEx;
private _dispatcher: Dispatcher; private _dispatcher: Dispatcher;
private _web3Wrapper?: Web3Wrapper; private _web3Wrapper?: Web3Wrapper;
private _exchangeAddress: string; private _blockchainWatcher?: BlockchainWatcher;
private _userAddress: string; private _userAddressIfExists: string;
private _cachedProvider: Web3.Provider; private _cachedProvider: Web3.Provider;
private _cachedProviderNetworkId: number; private _cachedProviderNetworkId: number;
private _ledgerSubprovider: LedgerWalletSubprovider; private _ledgerSubprovider: LedgerWalletSubprovider;
@ -115,7 +116,6 @@ export class Blockchain {
} }
constructor(dispatcher: Dispatcher, isSalePage: boolean = false) { constructor(dispatcher: Dispatcher, isSalePage: boolean = false) {
this._dispatcher = dispatcher; this._dispatcher = dispatcher;
this._userAddress = '';
const defaultGasPrice = GWEI_IN_WEI * 30; const defaultGasPrice = GWEI_IN_WEI * 30;
this._defaultGasPrice = new BigNumber(defaultGasPrice); this._defaultGasPrice = new BigNumber(defaultGasPrice);
// tslint:disable-next-line:no-floating-promises // tslint:disable-next-line:no-floating-promises
@ -137,8 +137,8 @@ export class Blockchain {
} }
} }
public async userAddressUpdatedFireAndForgetAsync(newUserAddress: string) { public async userAddressUpdatedFireAndForgetAsync(newUserAddress: string) {
if (this._userAddress !== newUserAddress) { if (this._userAddressIfExists !== newUserAddress) {
this._userAddress = newUserAddress; this._userAddressIfExists = newUserAddress;
await this.fetchTokenInformationAsync(); await this.fetchTokenInformationAsync();
await this._rehydrateStoreWithContractEvents(); await this._rehydrateStoreWithContractEvents();
} }
@ -189,14 +189,14 @@ export class Blockchain {
// Cache injected provider so that we can switch the user back to it easily // Cache injected provider so that we can switch the user back to it easily
if (_.isUndefined(this._cachedProvider)) { if (_.isUndefined(this._cachedProvider)) {
this._cachedProvider = this._web3Wrapper.getProviderObj(); this._cachedProvider = this._web3Wrapper.getProvider();
this._cachedProviderNetworkId = this.networkId; this._cachedProviderNetworkId = this.networkId;
} }
this._web3Wrapper.destroy(); this._blockchainWatcher.destroy();
this._userAddress = ''; delete this._userAddressIfExists;
this._dispatcher.updateUserAddress(''); // Clear old userAddress this._dispatcher.updateUserAddress(undefined); // Clear old userAddress
const provider = new ProviderEngine(); const provider = new ProviderEngine();
const ledgerWalletConfigs = { const ledgerWalletConfigs = {
@ -211,10 +211,15 @@ export class Blockchain {
this.networkId = networkId; this.networkId = networkId;
this._dispatcher.updateNetworkId(this.networkId); this._dispatcher.updateNetworkId(this.networkId);
const shouldPollUserAddress = false; const shouldPollUserAddress = false;
this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress); this._web3Wrapper = new Web3Wrapper(provider);
this._blockchainWatcher = new BlockchainWatcher(
this._dispatcher,
this._web3Wrapper,
this.networkId,
shouldPollUserAddress,
);
this._zeroEx.setProvider(provider, this.networkId); this._zeroEx.setProvider(provider, this.networkId);
await this._postInstantiationOrUpdatingProviderZeroExAsync(); this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceState();
this._web3Wrapper.startEmittingNetworkConnectionAndUserBalanceState();
this._dispatcher.updateProviderType(ProviderType.Ledger); this._dispatcher.updateProviderType(ProviderType.Ledger);
} }
public async updateProviderToInjectedAsync() { public async updateProviderToInjectedAsync() {
@ -224,21 +229,27 @@ export class Blockchain {
return; // Going from injected to injected, so we noop return; // Going from injected to injected, so we noop
} }
this._web3Wrapper.destroy(); this._blockchainWatcher.destroy();
const provider = this._cachedProvider; const provider = this._cachedProvider;
this.networkId = this._cachedProviderNetworkId; this.networkId = this._cachedProviderNetworkId;
const shouldPollUserAddress = true; const shouldPollUserAddress = true;
this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress); this._web3Wrapper = new Web3Wrapper(provider);
this._blockchainWatcher = new BlockchainWatcher(
this._dispatcher,
this._web3Wrapper,
this.networkId,
shouldPollUserAddress,
);
this._userAddress = await this._web3Wrapper.getFirstAccountIfExistsAsync(); const userAddresses = await this._web3Wrapper.getAvailableAddressesAsync();
this._userAddressIfExists = userAddresses[0];
this._zeroEx.setProvider(provider, this.networkId); this._zeroEx.setProvider(provider, this.networkId);
await this._postInstantiationOrUpdatingProviderZeroExAsync();
await this.fetchTokenInformationAsync(); await this.fetchTokenInformationAsync();
this._web3Wrapper.startEmittingNetworkConnectionAndUserBalanceState(); this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceState();
this._dispatcher.updateProviderType(ProviderType.Injected); this._dispatcher.updateProviderType(ProviderType.Injected);
delete this._ledgerSubprovider; delete this._ledgerSubprovider;
delete this._cachedProvider; delete this._cachedProvider;
@ -251,7 +262,7 @@ export class Blockchain {
this._showFlashMessageIfLedger(); this._showFlashMessageIfLedger();
const txHash = await this._zeroEx.token.setProxyAllowanceAsync( const txHash = await this._zeroEx.token.setProxyAllowanceAsync(
token.address, token.address,
this._userAddress, this._userAddressIfExists,
amountInBaseUnits, amountInBaseUnits,
{ {
gasPrice: this._defaultGasPrice, gasPrice: this._defaultGasPrice,
@ -260,10 +271,13 @@ export class Blockchain {
await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
} }
public async transferAsync(token: Token, toAddress: string, amountInBaseUnits: BigNumber): Promise<void> { public async transferAsync(token: Token, toAddress: string, amountInBaseUnits: BigNumber): Promise<void> {
utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
this._showFlashMessageIfLedger(); this._showFlashMessageIfLedger();
const txHash = await this._zeroEx.token.transferAsync( const txHash = await this._zeroEx.token.transferAsync(
token.address, token.address,
this._userAddress, this._userAddressIfExists,
toAddress, toAddress,
amountInBaseUnits, amountInBaseUnits,
{ {
@ -305,6 +319,7 @@ export class Blockchain {
return zeroExSignedOrder; return zeroExSignedOrder;
} }
public async fillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber): Promise<BigNumber> { public async fillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber): Promise<BigNumber> {
utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
const shouldThrowOnInsufficientBalanceOrAllowance = true; const shouldThrowOnInsufficientBalanceOrAllowance = true;
@ -314,7 +329,7 @@ export class Blockchain {
signedOrder, signedOrder,
fillTakerTokenAmount, fillTakerTokenAmount,
shouldThrowOnInsufficientBalanceOrAllowance, shouldThrowOnInsufficientBalanceOrAllowance,
this._userAddress, this._userAddressIfExists,
{ {
gasPrice: this._defaultGasPrice, gasPrice: this._defaultGasPrice,
}, },
@ -347,7 +362,7 @@ export class Blockchain {
return unavailableTakerAmount; return unavailableTakerAmount;
} }
public getExchangeContractAddressIfExists() { public getExchangeContractAddressIfExists() {
return this._exchangeAddress; return this._zeroEx.exchange.getContractAddress();
} }
public async validateFillOrderThrowIfInvalidAsync( public async validateFillOrderThrowIfInvalidAsync(
signedOrder: SignedOrder, signedOrder: SignedOrder,
@ -373,12 +388,15 @@ export class Blockchain {
public async pollTokenBalanceAsync(token: Token) { public async pollTokenBalanceAsync(token: Token) {
utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
const [currBalance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address); const [currBalance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddressIfExists, token.address);
const newTokenBalancePromise = new Promise((resolve: (balance: BigNumber) => void, reject) => { const newTokenBalancePromise = new Promise((resolve: (balance: BigNumber) => void, reject) => {
const tokenPollInterval = intervalUtils.setAsyncExcludingInterval( const tokenPollInterval = intervalUtils.setAsyncExcludingInterval(
async () => { async () => {
const [balance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address); const [balance] = await this.getTokenBalanceAndAllowanceAsync(
this._userAddressIfExists,
token.address,
);
if (!balance.eq(currBalance)) { if (!balance.eq(currBalance)) {
intervalUtils.clearAsyncExcludingInterval(tokenPollInterval); intervalUtils.clearAsyncExcludingInterval(tokenPollInterval);
resolve(balance); resolve(balance);
@ -397,7 +415,7 @@ export class Blockchain {
} }
public async signOrderHashAsync(orderHash: string): Promise<ECSignature> { public async signOrderHashAsync(orderHash: string): Promise<ECSignature> {
utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
const makerAddress = this._userAddress; const makerAddress = this._userAddressIfExists;
// If makerAddress is undefined, this means they have a web3 instance injected into their browser // If makerAddress is undefined, this means they have a web3 instance injected into their browser
// but no account addresses associated with it. // but no account addresses associated with it.
if (_.isUndefined(makerAddress)) { if (_.isUndefined(makerAddress)) {
@ -427,22 +445,27 @@ export class Blockchain {
const mintableContract = await this._instantiateContractIfExistsAsync(MintableArtifacts, token.address); const mintableContract = await this._instantiateContractIfExistsAsync(MintableArtifacts, token.address);
this._showFlashMessageIfLedger(); this._showFlashMessageIfLedger();
await mintableContract.mint(constants.MINT_AMOUNT, { await mintableContract.mint(constants.MINT_AMOUNT, {
from: this._userAddress, from: this._userAddressIfExists,
gasPrice: this._defaultGasPrice, gasPrice: this._defaultGasPrice,
}); });
} }
public async getBalanceInEthAsync(owner: string): Promise<BigNumber> { public async getBalanceInWeiAsync(owner: string): Promise<BigNumber> {
const balance = await this._web3Wrapper.getBalanceInEthAsync(owner); const balanceInWei = await this._web3Wrapper.getBalanceInWeiAsync(owner);
return balance; return balanceInWei;
} }
public async convertEthToWrappedEthTokensAsync(etherTokenAddress: string, amount: BigNumber): Promise<void> { public async convertEthToWrappedEthTokensAsync(etherTokenAddress: string, amount: BigNumber): Promise<void> {
utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
this._showFlashMessageIfLedger(); this._showFlashMessageIfLedger();
const txHash = await this._zeroEx.etherToken.depositAsync(etherTokenAddress, amount, this._userAddress, { const txHash = await this._zeroEx.etherToken.depositAsync(
gasPrice: this._defaultGasPrice, etherTokenAddress,
}); amount,
this._userAddressIfExists,
{
gasPrice: this._defaultGasPrice,
},
);
await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
} }
public async convertWrappedEthTokensToEthAsync(etherTokenAddress: string, amount: BigNumber): Promise<void> { public async convertWrappedEthTokensToEthAsync(etherTokenAddress: string, amount: BigNumber): Promise<void> {
@ -450,9 +473,14 @@ export class Blockchain {
utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
this._showFlashMessageIfLedger(); this._showFlashMessageIfLedger();
const txHash = await this._zeroEx.etherToken.withdrawAsync(etherTokenAddress, amount, this._userAddress, { const txHash = await this._zeroEx.etherToken.withdrawAsync(
gasPrice: this._defaultGasPrice, etherTokenAddress,
}); amount,
this._userAddressIfExists,
{
gasPrice: this._defaultGasPrice,
},
);
await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
} }
public async doesContractExistAtAddressAsync(address: string) { public async doesContractExistAtAddressAsync(address: string) {
@ -460,21 +488,29 @@ export class Blockchain {
return doesContractExist; return doesContractExist;
} }
public async getCurrentUserTokenBalanceAndAllowanceAsync(tokenAddress: string): Promise<BigNumber[]> { public async getCurrentUserTokenBalanceAndAllowanceAsync(tokenAddress: string): Promise<BigNumber[]> {
const tokenBalanceAndAllowance = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, tokenAddress); utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
const tokenBalanceAndAllowance = await this.getTokenBalanceAndAllowanceAsync(
this._userAddressIfExists,
tokenAddress,
);
return tokenBalanceAndAllowance; return tokenBalanceAndAllowance;
} }
public async getTokenBalanceAndAllowanceAsync(ownerAddress: string, tokenAddress: string): Promise<BigNumber[]> { public async getTokenBalanceAndAllowanceAsync(
ownerAddressIfExists: string,
tokenAddress: string,
): Promise<BigNumber[]> {
utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
if (_.isEmpty(ownerAddress)) { if (_.isUndefined(ownerAddressIfExists)) {
const zero = new BigNumber(0); const zero = new BigNumber(0);
return [zero, zero]; return [zero, zero];
} }
let balance = new BigNumber(0); let balance = new BigNumber(0);
let allowance = new BigNumber(0); let allowance = new BigNumber(0);
if (this._doesUserAddressExist()) { if (this._doesUserAddressExist()) {
balance = await this._zeroEx.token.getBalanceAsync(tokenAddress, ownerAddress); balance = await this._zeroEx.token.getBalanceAsync(tokenAddress, ownerAddressIfExists);
allowance = await this._zeroEx.token.getProxyAllowanceAsync(tokenAddress, ownerAddress); allowance = await this._zeroEx.token.getProxyAllowanceAsync(tokenAddress, ownerAddressIfExists);
} }
return [balance, allowance]; return [balance, allowance];
} }
@ -487,10 +523,10 @@ export class Blockchain {
// by-passes the web3Wrapper logic for updating the prevUserAddress. We therefore need to // by-passes the web3Wrapper logic for updating the prevUserAddress. We therefore need to
// manually update it. This should only be called by the LedgerConfigDialog. // manually update it. This should only be called by the LedgerConfigDialog.
public updateWeb3WrapperPrevUserAddress(newUserAddress: string) { public updateWeb3WrapperPrevUserAddress(newUserAddress: string) {
this._web3Wrapper.updatePrevUserAddress(newUserAddress); this._blockchainWatcher.updatePrevUserAddress(newUserAddress);
} }
public destroy() { public destroy() {
this._web3Wrapper.destroy(); this._blockchainWatcher.destroy();
this._stopWatchingExchangeLogFillEvents(); this._stopWatchingExchangeLogFillEvents();
} }
public async fetchTokenInformationAsync() { public async fetchTokenInformationAsync() {
@ -503,7 +539,9 @@ export class Blockchain {
const tokenRegistryTokensByAddress = await this._getTokenRegistryTokensByAddressAsync(); const tokenRegistryTokensByAddress = await this._getTokenRegistryTokensByAddressAsync();
const trackedTokensByAddress = trackedTokenStorage.getTrackedTokensByAddress(this._userAddress, this.networkId); const trackedTokensByAddress = _.isUndefined(this._userAddressIfExists)
? {}
: trackedTokenStorage.getTrackedTokensByAddress(this._userAddressIfExists, this.networkId);
const tokenRegistryTokens = _.values(tokenRegistryTokensByAddress); const tokenRegistryTokens = _.values(tokenRegistryTokensByAddress);
if (_.isEmpty(trackedTokensByAddress)) { if (_.isEmpty(trackedTokensByAddress)) {
_.each(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, symbol => { _.each(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, symbol => {
@ -511,9 +549,11 @@ export class Blockchain {
token.isTracked = true; token.isTracked = true;
trackedTokensByAddress[token.address] = token; trackedTokensByAddress[token.address] = token;
}); });
_.each(trackedTokensByAddress, (token: Token, address: string) => { if (!_.isUndefined(this._userAddressIfExists)) {
trackedTokenStorage.addTrackedTokenToUser(this._userAddress, this.networkId, token); _.each(trackedTokensByAddress, (token: Token, address: string) => {
}); trackedTokenStorage.addTrackedTokenToUser(this._userAddressIfExists, this.networkId, token);
});
}
} else { } else {
// Properly set all tokenRegistry tokens `isTracked` to true if they are in the existing trackedTokens array // Properly set all tokenRegistry tokens `isTracked` to true if they are in the existing trackedTokens array
_.each(trackedTokensByAddress, (trackedToken: Token, address: string) => { _.each(trackedTokensByAddress, (trackedToken: Token, address: string) => {
@ -539,7 +579,7 @@ export class Blockchain {
address: mostPopularTradingPairTokens[1].address, address: mostPopularTradingPairTokens[1].address,
}, },
}; };
this._dispatcher.batchDispatch(allTokensByAddress, this.networkId, this._userAddress, sideToAssetToken); this._dispatcher.batchDispatch(allTokensByAddress, this.networkId, this._userAddressIfExists, sideToAssetToken);
this._dispatcher.updateBlockchainIsLoaded(true); this._dispatcher.updateBlockchainIsLoaded(true);
} }
@ -560,7 +600,7 @@ export class Blockchain {
return receipt; return receipt;
} }
private _doesUserAddressExist(): boolean { private _doesUserAddressExist(): boolean {
return this._userAddress !== ''; return !_.isUndefined(this._userAddressIfExists);
} }
private async _rehydrateStoreWithContractEvents() { private async _rehydrateStoreWithContractEvents() {
// Ensure we are only ever listening to one set of events // Ensure we are only ever listening to one set of events
@ -605,16 +645,18 @@ export class Blockchain {
this._updateLatestFillsBlockIfNeeded(decodedLog.blockNumber); this._updateLatestFillsBlockIfNeeded(decodedLog.blockNumber);
const fill = await this._convertDecodedLogToFillAsync(decodedLog); const fill = await this._convertDecodedLogToFillAsync(decodedLog);
if (decodedLogEvent.isRemoved) { if (decodedLogEvent.isRemoved) {
tradeHistoryStorage.removeFillFromUser(this._userAddress, this.networkId, fill); tradeHistoryStorage.removeFillFromUser(this._userAddressIfExists, this.networkId, fill);
} else { } else {
tradeHistoryStorage.addFillToUser(this._userAddress, this.networkId, fill); tradeHistoryStorage.addFillToUser(this._userAddressIfExists, this.networkId, fill);
} }
} }
}, },
); );
} }
private async _fetchHistoricalExchangeLogFillEventsAsync(indexFilterValues: IndexedFilterValues) { private async _fetchHistoricalExchangeLogFillEventsAsync(indexFilterValues: IndexedFilterValues) {
const fromBlock = tradeHistoryStorage.getFillsLatestBlock(this._userAddress, this.networkId); utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
const fromBlock = tradeHistoryStorage.getFillsLatestBlock(this._userAddressIfExists, this.networkId);
const blockRange: BlockRange = { const blockRange: BlockRange = {
fromBlock, fromBlock,
toBlock: 'latest' as BlockParam, toBlock: 'latest' as BlockParam,
@ -630,7 +672,7 @@ export class Blockchain {
} }
this._updateLatestFillsBlockIfNeeded(decodedLog.blockNumber); this._updateLatestFillsBlockIfNeeded(decodedLog.blockNumber);
const fill = await this._convertDecodedLogToFillAsync(decodedLog); const fill = await this._convertDecodedLogToFillAsync(decodedLog);
tradeHistoryStorage.addFillToUser(this._userAddress, this.networkId, fill); tradeHistoryStorage.addFillToUser(this._userAddressIfExists, this.networkId, fill);
} }
} }
private async _convertDecodedLogToFillAsync(decodedLog: LogWithDecodedArgs<LogFillContractEventArgs>) { private async _convertDecodedLogToFillAsync(decodedLog: LogWithDecodedArgs<LogFillContractEventArgs>) {
@ -654,10 +696,12 @@ export class Blockchain {
} }
private _doesLogEventInvolveUser(decodedLog: LogWithDecodedArgs<LogFillContractEventArgs>) { private _doesLogEventInvolveUser(decodedLog: LogWithDecodedArgs<LogFillContractEventArgs>) {
const args = decodedLog.args; const args = decodedLog.args;
const isUserMakerOrTaker = args.maker === this._userAddress || args.taker === this._userAddress; const isUserMakerOrTaker = args.maker === this._userAddressIfExists || args.taker === this._userAddressIfExists;
return isUserMakerOrTaker; return isUserMakerOrTaker;
} }
private _updateLatestFillsBlockIfNeeded(blockNumber: number) { private _updateLatestFillsBlockIfNeeded(blockNumber: number) {
utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
const isBlockPending = _.isNull(blockNumber); const isBlockPending = _.isNull(blockNumber);
if (!isBlockPending) { if (!isBlockPending) {
// Hack: I've observed the behavior where a client won't register certain fill events // Hack: I've observed the behavior where a client won't register certain fill events
@ -668,7 +712,7 @@ export class Blockchain {
// TODO: Debug if this is a race condition, and apply a more precise fix // TODO: Debug if this is a race condition, and apply a more precise fix
const blockNumberToSet = const blockNumberToSet =
blockNumber - BLOCK_NUMBER_BACK_TRACK < 0 ? 0 : blockNumber - BLOCK_NUMBER_BACK_TRACK; blockNumber - BLOCK_NUMBER_BACK_TRACK < 0 ? 0 : blockNumber - BLOCK_NUMBER_BACK_TRACK;
tradeHistoryStorage.setFillsLatestBlock(this._userAddress, this.networkId, blockNumberToSet); tradeHistoryStorage.setFillsLatestBlock(this._userAddressIfExists, this.networkId, blockNumberToSet);
} }
} }
private _stopWatchingExchangeLogFillEvents(): void { private _stopWatchingExchangeLogFillEvents(): void {
@ -739,20 +783,21 @@ export class Blockchain {
this._zeroEx = new ZeroEx(provider, zeroExConfigs); this._zeroEx = new ZeroEx(provider, zeroExConfigs);
this._updateProviderName(injectedWeb3); this._updateProviderName(injectedWeb3);
const shouldPollUserAddress = true; const shouldPollUserAddress = true;
this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress); this._web3Wrapper = new Web3Wrapper(provider);
await this._postInstantiationOrUpdatingProviderZeroExAsync(); this._blockchainWatcher = new BlockchainWatcher(
this._userAddress = await this._web3Wrapper.getFirstAccountIfExistsAsync(); this._dispatcher,
this._dispatcher.updateUserAddress(this._userAddress); this._web3Wrapper,
this.networkId,
shouldPollUserAddress,
);
const userAddresses = await this._web3Wrapper.getAvailableAddressesAsync();
this._userAddressIfExists = userAddresses[0];
this._dispatcher.updateUserAddress(this._userAddressIfExists);
await this.fetchTokenInformationAsync(); await this.fetchTokenInformationAsync();
this._web3Wrapper.startEmittingNetworkConnectionAndUserBalanceState(); this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceState();
await this._rehydrateStoreWithContractEvents(); await this._rehydrateStoreWithContractEvents();
} }
// This method should always be run after instantiating or updating the provider
// of the ZeroEx instance.
private async _postInstantiationOrUpdatingProviderZeroExAsync() {
utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
this._exchangeAddress = this._zeroEx.exchange.getContractAddress();
}
private _updateProviderName(injectedWeb3: Web3) { private _updateProviderName(injectedWeb3: Web3) {
const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3);
const providerName = doesInjectedWeb3Exist const providerName = doesInjectedWeb3Exist
@ -762,7 +807,7 @@ export class Blockchain {
} }
private async _instantiateContractIfExistsAsync(artifact: any, address?: string): Promise<ContractInstance> { private async _instantiateContractIfExistsAsync(artifact: any, address?: string): Promise<ContractInstance> {
const c = await contract(artifact); const c = await contract(artifact);
const providerObj = this._web3Wrapper.getProviderObj(); const providerObj = this._web3Wrapper.getProvider();
c.setProvider(providerObj); c.setProvider(providerObj);
const artifactNetworkConfigs = artifact.networks[this.networkId]; const artifactNetworkConfigs = artifact.networks[this.networkId];

View File

@ -0,0 +1,107 @@
import { BigNumber, intervalUtils, promisify } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as _ from 'lodash';
import { Dispatcher } from 'ts/redux/dispatcher';
import { utils } from 'ts/utils/utils';
export class BlockchainWatcher {
private _dispatcher: Dispatcher;
private _web3Wrapper: Web3Wrapper;
private _prevNetworkId: number;
private _shouldPollUserAddress: boolean;
private _watchNetworkAndBalanceIntervalId: NodeJS.Timer;
private _prevUserEtherBalanceInWei: BigNumber;
private _prevUserAddressIfExists: string;
constructor(
dispatcher: Dispatcher,
web3Wrapper: Web3Wrapper,
networkIdIfExists: number,
shouldPollUserAddress: boolean,
) {
this._dispatcher = dispatcher;
this._prevNetworkId = networkIdIfExists;
this._shouldPollUserAddress = shouldPollUserAddress;
this._web3Wrapper = web3Wrapper;
}
public destroy() {
this._stopEmittingNetworkConnectionAndUserBalanceStateAsync();
// HACK: stop() is only available on providerEngine instances
const provider = this._web3Wrapper.getProvider();
if (!_.isUndefined((provider as any).stop)) {
(provider as any).stop();
}
}
// This should only be called from the LedgerConfigDialog
public updatePrevUserAddress(userAddress: string) {
this._prevUserAddressIfExists = userAddress;
}
public startEmittingNetworkConnectionAndUserBalanceState() {
if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) {
return; // we are already emitting the state
}
let prevNodeVersion: string;
this._prevUserEtherBalanceInWei = new BigNumber(0);
this._dispatcher.updateNetworkId(this._prevNetworkId);
this._watchNetworkAndBalanceIntervalId = intervalUtils.setAsyncExcludingInterval(
async () => {
// Check for network state changes
let currentNetworkId;
try {
currentNetworkId = await this._web3Wrapper.getNetworkIdAsync();
} catch (err) {
// Noop
}
if (currentNetworkId !== this._prevNetworkId) {
this._prevNetworkId = currentNetworkId;
this._dispatcher.updateNetworkId(currentNetworkId);
}
// Check for node version changes
const currentNodeVersion = await this._web3Wrapper.getNodeVersionAsync();
if (currentNodeVersion !== prevNodeVersion) {
prevNodeVersion = currentNodeVersion;
this._dispatcher.updateNodeVersion(currentNodeVersion);
}
if (this._shouldPollUserAddress) {
const addresses = await this._web3Wrapper.getAvailableAddressesAsync();
const userAddressIfExists = addresses[0];
// Update makerAddress on network change
if (this._prevUserAddressIfExists !== userAddressIfExists) {
this._prevUserAddressIfExists = userAddressIfExists;
this._dispatcher.updateUserAddress(userAddressIfExists);
}
// Check for user ether balance changes
if (!_.isUndefined(userAddressIfExists)) {
await this._updateUserWeiBalanceAsync(userAddressIfExists);
}
} else {
// This logic is primarily for the Ledger, since we don't regularly poll for the address
// we simply update the balance for the last fetched address.
if (!_.isUndefined(this._prevUserAddressIfExists)) {
await this._updateUserWeiBalanceAsync(this._prevUserAddressIfExists);
}
}
},
5000,
(err: Error) => {
utils.consoleLog(`Watching network and balances failed: ${err.stack}`);
this._stopEmittingNetworkConnectionAndUserBalanceStateAsync();
},
);
}
private async _updateUserWeiBalanceAsync(userAddress: string) {
const balanceInWei = await this._web3Wrapper.getBalanceInWeiAsync(userAddress);
if (!balanceInWei.eq(this._prevUserEtherBalanceInWei)) {
this._prevUserEtherBalanceInWei = balanceInWei;
this._dispatcher.updateUserWeiBalance(balanceInWei);
}
}
private _stopEmittingNetworkConnectionAndUserBalanceStateAsync() {
if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) {
intervalUtils.clearAsyncExcludingInterval(this._watchNetworkAndBalanceIntervalId);
}
}
}

View File

@ -1,5 +1,7 @@
import { ZeroEx } from '0x.js';
import { colors } from '@0xproject/react-shared'; import { colors } from '@0xproject/react-shared';
import { BigNumber } from '@0xproject/utils'; import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import Dialog from 'material-ui/Dialog'; import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton'; import FlatButton from 'material-ui/FlatButton';
import * as React from 'react'; import * as React from 'react';
@ -7,6 +9,7 @@ import { Blockchain } from 'ts/blockchain';
import { EthAmountInput } from 'ts/components/inputs/eth_amount_input'; import { EthAmountInput } from 'ts/components/inputs/eth_amount_input';
import { TokenAmountInput } from 'ts/components/inputs/token_amount_input'; import { TokenAmountInput } from 'ts/components/inputs/token_amount_input';
import { Side, Token } from 'ts/types'; import { Side, Token } from 'ts/types';
import { constants } from 'ts/utils/constants';
interface EthWethConversionDialogProps { interface EthWethConversionDialogProps {
blockchain: Blockchain; blockchain: Blockchain;
@ -17,7 +20,7 @@ interface EthWethConversionDialogProps {
onCancelled: () => void; onCancelled: () => void;
isOpen: boolean; isOpen: boolean;
token: Token; token: Token;
etherBalance: BigNumber; etherBalanceInWei: BigNumber;
lastForceTokenStateRefetch: number; lastForceTokenStateRefetch: number;
} }
@ -75,6 +78,7 @@ export class EthWethConversionDialog extends React.Component<
? 'Convert your Ether into a tokenized, tradable form.' ? 'Convert your Ether into a tokenized, tradable form.'
: "Convert your Wrapped Ether back into it's native form."; : "Convert your Wrapped Ether back into it's native form.";
const isWrappedVersion = this.props.direction === Side.Receive; const isWrappedVersion = this.props.direction === Side.Receive;
const etherBalanceInEth = ZeroEx.toUnitAmount(this.props.etherBalanceInWei, constants.DECIMAL_PLACES_ETH);
return ( return (
<div> <div>
<div className="pb2">{explanation}</div> <div className="pb2">{explanation}</div>
@ -103,7 +107,7 @@ export class EthWethConversionDialog extends React.Component<
/> />
) : ( ) : (
<EthAmountInput <EthAmountInput
balance={this.props.etherBalance} balance={etherBalanceInEth}
amount={this.state.value} amount={this.state.value}
onChange={this._onValueChange.bind(this)} onChange={this._onValueChange.bind(this)}
shouldCheckBalance={true} shouldCheckBalance={true}
@ -182,8 +186,9 @@ export class EthWethConversionDialog extends React.Component<
this.props.onCancelled(); this.props.onCancelled();
} }
private async _fetchEthTokenBalanceAsync() { private async _fetchEthTokenBalanceAsync() {
const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress;
const [balance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( const [balance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
this.props.userAddress, userAddressIfExists,
this.props.token.address, this.props.token.address,
); );
if (!this._isUnmounted) { if (!this._isUnmounted) {

View File

@ -1,3 +1,4 @@
import { ZeroEx } from '0x.js';
import { colors, constants as sharedConstants } from '@0xproject/react-shared'; import { colors, constants as sharedConstants } from '@0xproject/react-shared';
import { BigNumber } from '@0xproject/utils'; import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
@ -160,14 +161,15 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
} }
private _renderAddressTableRows() { private _renderAddressTableRows() {
const rows = _.map(this.state.userAddresses, (userAddress: string, i: number) => { const rows = _.map(this.state.userAddresses, (userAddress: string, i: number) => {
const balance = this.state.addressBalances[i]; const balanceInWei = this.state.addressBalances[i];
const addressTooltipId = `address-${userAddress}`; const addressTooltipId = `address-${userAddress}`;
const balanceTooltipId = `balance-${userAddress}`; const balanceTooltipId = `balance-${userAddress}`;
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId]; const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
// We specifically prefix kovan ETH. // We specifically prefix kovan ETH.
// TODO: We should probably add prefixes for all networks // TODO: We should probably add prefixes for all networks
const isKovanNetwork = networkName === 'Kovan'; const isKovanNetwork = networkName === 'Kovan';
const balanceString = `${balance.toString()} ${isKovanNetwork ? 'Kovan ' : ''}ETH`; const balanceInEth = ZeroEx.toUnitAmount(balanceInWei, constants.DECIMAL_PLACES_ETH);
const balanceString = `${balanceInEth.toString()} ${isKovanNetwork ? 'Kovan ' : ''}ETH`;
return ( return (
<TableRow key={userAddress} style={{ height: 40 }}> <TableRow key={userAddress} style={{ height: 40 }}>
<TableRowColumn colSpan={2}> <TableRowColumn colSpan={2}>
@ -204,7 +206,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
this.props.blockchain.updateWeb3WrapperPrevUserAddress(selectedAddress); this.props.blockchain.updateWeb3WrapperPrevUserAddress(selectedAddress);
// tslint:disable-next-line:no-floating-promises // tslint:disable-next-line:no-floating-promises
this.props.blockchain.fetchTokenInformationAsync(); this.props.blockchain.fetchTokenInformationAsync();
this.props.dispatcher.updateUserEtherBalance(selectAddressBalance); this.props.dispatcher.updateUserWeiBalance(selectAddressBalance);
this.setState({ this.setState({
stepIndex: LedgerSteps.CONNECT, stepIndex: LedgerSteps.CONNECT,
}); });
@ -233,8 +235,8 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
try { try {
userAddresses = await this._getUserAddressesAsync(); userAddresses = await this._getUserAddressesAsync();
for (const address of userAddresses) { for (const address of userAddresses) {
const balance = await this.props.blockchain.getBalanceInEthAsync(address); const balanceInWei = await this.props.blockchain.getBalanceInWeiAsync(address);
addressBalances.push(balance); addressBalances.push(balanceInWei);
} }
} catch (err) { } catch (err) {
utils.consoleLog(`Ledger error: ${JSON.stringify(err)}`); utils.consoleLog(`Ledger error: ${JSON.stringify(err)}`);

View File

@ -18,7 +18,7 @@ interface EthWethConversionButtonProps {
ethToken: Token; ethToken: Token;
dispatcher: Dispatcher; dispatcher: Dispatcher;
blockchain: Blockchain; blockchain: Blockchain;
userEtherBalance: BigNumber; userEtherBalanceInWei: BigNumber;
isOutdatedWrappedEther: boolean; isOutdatedWrappedEther: boolean;
onConversionSuccessful?: () => void; onConversionSuccessful?: () => void;
isDisabled?: boolean; isDisabled?: boolean;
@ -74,7 +74,7 @@ export class EthWethConversionButton extends React.Component<
isOpen={this.state.isEthConversionDialogVisible} isOpen={this.state.isEthConversionDialogVisible}
onComplete={this._onConversionAmountSelectedAsync.bind(this)} onComplete={this._onConversionAmountSelectedAsync.bind(this)}
onCancelled={this._toggleConversionDialog.bind(this)} onCancelled={this._toggleConversionDialog.bind(this)}
etherBalance={this.props.userEtherBalance} etherBalanceInWei={this.props.userEtherBalanceInWei}
token={this.props.ethToken} token={this.props.ethToken}
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
/> />

View File

@ -15,7 +15,6 @@ import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants'; import { constants } from 'ts/utils/constants';
import { utils } from 'ts/utils/utils'; import { utils } from 'ts/utils/utils';
const PRECISION = 5;
const DATE_FORMAT = 'D/M/YY'; const DATE_FORMAT = 'D/M/YY';
const ICON_DIMENSION = 40; const ICON_DIMENSION = 40;
const ETHER_ICON_PATH = '/images/ether.png'; const ETHER_ICON_PATH = '/images/ether.png';
@ -34,7 +33,7 @@ interface EthWrappersProps {
dispatcher: Dispatcher; dispatcher: Dispatcher;
tokenByAddress: TokenByAddress; tokenByAddress: TokenByAddress;
userAddress: string; userAddress: string;
userEtherBalance: BigNumber; userEtherBalanceInWei: BigNumber;
lastForceTokenStateRefetch: number; lastForceTokenStateRefetch: number;
} }
@ -98,6 +97,10 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
EtherscanLinkSuffixes.Address, EtherscanLinkSuffixes.Address,
); );
const tokenLabel = this._renderToken('Wrapped Ether', etherToken.address, configs.ICON_URL_BY_SYMBOL.WETH); const tokenLabel = this._renderToken('Wrapped Ether', etherToken.address, configs.ICON_URL_BY_SYMBOL.WETH);
const userEtherBalanceInEth = ZeroEx.toUnitAmount(
this.props.userEtherBalanceInWei,
constants.DECIMAL_PLACES_ETH,
);
return ( return (
<div className="clearfix lg-px4 md-px4 sm-px2" style={{ minHeight: 600 }}> <div className="clearfix lg-px4 md-px4 sm-px2" style={{ minHeight: 600 }}>
<div className="relative"> <div className="relative">
@ -144,7 +147,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
</div> </div>
</TableRowColumn> </TableRowColumn>
<TableRowColumn> <TableRowColumn>
{this.props.userEtherBalance.toFixed(PRECISION)} ETH {userEtherBalanceInEth.toFixed(configs.AMOUNT_DISPLAY_PRECSION)} ETH
</TableRowColumn> </TableRowColumn>
<TableRowColumn> <TableRowColumn>
<EthWethConversionButton <EthWethConversionButton
@ -157,7 +160,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
ethToken={etherToken} ethToken={etherToken}
dispatcher={this.props.dispatcher} dispatcher={this.props.dispatcher}
blockchain={this.props.blockchain} blockchain={this.props.blockchain}
userEtherBalance={this.props.userEtherBalance} userEtherBalanceInWei={this.props.userEtherBalanceInWei}
/> />
</TableRowColumn> </TableRowColumn>
</TableRow> </TableRow>
@ -167,7 +170,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
</TableRowColumn> </TableRowColumn>
<TableRowColumn> <TableRowColumn>
{this.state.isWethStateLoaded ? ( {this.state.isWethStateLoaded ? (
`${wethBalance.toFixed(PRECISION)} WETH` `${wethBalance.toFixed(configs.AMOUNT_DISPLAY_PRECSION)} WETH`
) : ( ) : (
<i className="zmdi zmdi-spinner zmdi-hc-spin" /> <i className="zmdi zmdi-spinner zmdi-hc-spin" />
)} )}
@ -184,7 +187,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
ethToken={etherToken} ethToken={etherToken}
dispatcher={this.props.dispatcher} dispatcher={this.props.dispatcher}
blockchain={this.props.blockchain} blockchain={this.props.blockchain}
userEtherBalance={this.props.userEtherBalance} userEtherBalanceInWei={this.props.userEtherBalanceInWei}
/> />
</TableRowColumn> </TableRowColumn>
</TableRow> </TableRow>
@ -267,7 +270,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
const outdatedEtherTokenState = this.state.outdatedWETHStateByAddress[outdatedWETHIfExists.address]; const outdatedEtherTokenState = this.state.outdatedWETHStateByAddress[outdatedWETHIfExists.address];
const balanceInEthIfExists = isStateLoaded const balanceInEthIfExists = isStateLoaded
? ZeroEx.toUnitAmount(outdatedEtherTokenState.balance, constants.DECIMAL_PLACES_ETH).toFixed( ? ZeroEx.toUnitAmount(outdatedEtherTokenState.balance, constants.DECIMAL_PLACES_ETH).toFixed(
PRECISION, configs.AMOUNT_DISPLAY_PRECSION,
) )
: undefined; : undefined;
const onConversionSuccessful = this._onOutdatedConversionSuccessfulAsync.bind( const onConversionSuccessful = this._onOutdatedConversionSuccessfulAsync.bind(
@ -304,7 +307,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
ethToken={outdatedEtherToken} ethToken={outdatedEtherToken}
dispatcher={this.props.dispatcher} dispatcher={this.props.dispatcher}
blockchain={this.props.blockchain} blockchain={this.props.blockchain}
userEtherBalance={this.props.userEtherBalance} userEtherBalanceInWei={this.props.userEtherBalanceInWei}
onConversionSuccessful={onConversionSuccessful} onConversionSuccessful={onConversionSuccessful}
/> />
</TableRowColumn> </TableRowColumn>
@ -348,8 +351,9 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
[outdatedWETHAddress]: false, [outdatedWETHAddress]: false,
}, },
}); });
const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress;
const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
this.props.userAddress, userAddressIfExists,
outdatedWETHAddress, outdatedWETHAddress,
); );
this.setState({ this.setState({
@ -369,8 +373,9 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
private async _fetchWETHStateAsync() { private async _fetchWETHStateAsync() {
const tokens = _.values(this.props.tokenByAddress); const tokens = _.values(this.props.tokenByAddress);
const wethToken = _.find(tokens, token => token.symbol === 'WETH'); const wethToken = _.find(tokens, token => token.symbol === 'WETH');
const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress;
const [wethBalance, wethAllowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( const [wethBalance, wethAllowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
this.props.userAddress, userAddressIfExists,
wethToken.address, wethToken.address,
); );
@ -379,7 +384,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {}; const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {};
for (const address of outdatedWETHAddresses) { for (const address of outdatedWETHAddresses) {
const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
this.props.userAddress, userAddressIfExists,
address, address,
); );
outdatedWETHStateByAddress[address] = { outdatedWETHStateByAddress[address] = {
@ -420,8 +425,9 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
} }
private async _refetchEthTokenStateAsync() { private async _refetchEthTokenStateAsync() {
const etherToken = this._getEthToken(); const etherToken = this._getEthToken();
const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress;
const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
this.props.userAddress, userAddressIfExists,
etherToken.address, etherToken.address,
); );
this.setState({ this.setState({

View File

@ -237,8 +237,9 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G
// Check if all required inputs were supplied // Check if all required inputs were supplied
const debitToken = this.props.sideToAssetToken[Side.Deposit]; const debitToken = this.props.sideToAssetToken[Side.Deposit];
const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress;
const [debitBalance, debitAllowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( const [debitBalance, debitAllowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
this.props.userAddress, userAddressIfExists,
debitToken.address, debitToken.address,
); );
const receiveAmount = this.props.sideToAssetToken[Side.Receive].amount; const receiveAmount = this.props.sideToAssetToken[Side.Receive].amount;

View File

@ -67,6 +67,7 @@ export class AllowanceToggle extends React.Component<AllowanceToggleProps, Allow
private async _onToggleAllowanceAsync(): Promise<void> { private async _onToggleAllowanceAsync(): Promise<void> {
if (this.props.userAddress === '') { if (this.props.userAddress === '') {
this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
return;
} }
this.setState({ this.setState({

View File

@ -109,8 +109,9 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok
this.setState({ this.setState({
isBalanceAndAllowanceLoaded: false, isBalanceAndAllowanceLoaded: false,
}); });
const userAddressIfExists = _.isEmpty(userAddress) ? undefined : userAddress;
const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
userAddress, userAddressIfExists,
tokenAddress, tokenAddress,
); );
if (!this._isUnmounted) { if (!this._isUnmounted) {

View File

@ -46,7 +46,7 @@ export interface PortalAllProps {
providerType: ProviderType; providerType: ProviderType;
screenWidth: ScreenWidths; screenWidth: ScreenWidths;
tokenByAddress: TokenByAddress; tokenByAddress: TokenByAddress;
userEtherBalance: BigNumber; userEtherBalanceInWei: BigNumber;
userAddress: string; userAddress: string;
shouldBlockchainErrDialogBeOpen: boolean; shouldBlockchainErrDialogBeOpen: boolean;
userSuppliedOrderCache: Order; userSuppliedOrderCache: Order;
@ -121,8 +121,9 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
}); });
} }
if (nextProps.userAddress !== this.state.prevUserAddress) { if (nextProps.userAddress !== this.state.prevUserAddress) {
const newUserAddress = _.isEmpty(nextProps.userAddress) ? undefined : nextProps.userAddress;
// tslint:disable-next-line:no-floating-promises // tslint:disable-next-line:no-floating-promises
this._blockchain.userAddressUpdatedFireAndForgetAsync(nextProps.userAddress); this._blockchain.userAddressUpdatedFireAndForgetAsync(newUserAddress);
this.setState({ this.setState({
prevUserAddress: nextProps.userAddress, prevUserAddress: nextProps.userAddress,
}); });
@ -279,7 +280,7 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
dispatcher={this.props.dispatcher} dispatcher={this.props.dispatcher}
tokenByAddress={this.props.tokenByAddress} tokenByAddress={this.props.tokenByAddress}
userAddress={this.props.userAddress} userAddress={this.props.userAddress}
userEtherBalance={this.props.userEtherBalance} userEtherBalanceInWei={this.props.userEtherBalanceInWei}
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
/> />
); );
@ -306,7 +307,7 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
tokenByAddress={this.props.tokenByAddress} tokenByAddress={this.props.tokenByAddress}
trackedTokens={trackedTokens} trackedTokens={trackedTokens}
userAddress={this.props.userAddress} userAddress={this.props.userAddress}
userEtherBalance={this.props.userEtherBalance} userEtherBalanceInWei={this.props.userEtherBalanceInWei}
networkId={this.props.networkId} networkId={this.props.networkId}
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
/> />

View File

@ -48,7 +48,6 @@ const ETHER_ICON_PATH = '/images/ether.png';
const ETHER_TOKEN_SYMBOL = 'WETH'; const ETHER_TOKEN_SYMBOL = 'WETH';
const ZRX_TOKEN_SYMBOL = 'ZRX'; const ZRX_TOKEN_SYMBOL = 'ZRX';
const PRECISION = 5;
const ICON_DIMENSION = 40; const ICON_DIMENSION = 40;
const ARTIFICIAL_FAUCET_REQUEST_DELAY = 1000; const ARTIFICIAL_FAUCET_REQUEST_DELAY = 1000;
const TOKEN_TABLE_ROW_HEIGHT = 60; const TOKEN_TABLE_ROW_HEIGHT = 60;
@ -79,7 +78,7 @@ interface TokenBalancesProps {
tokenByAddress: TokenByAddress; tokenByAddress: TokenByAddress;
trackedTokens: Token[]; trackedTokens: Token[];
userAddress: string; userAddress: string;
userEtherBalance: BigNumber; userEtherBalanceInWei: BigNumber;
networkId: number; networkId: number;
lastForceTokenStateRefetch: number; lastForceTokenStateRefetch: number;
} }
@ -119,11 +118,14 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
this._isUnmounted = true; this._isUnmounted = true;
} }
public componentWillReceiveProps(nextProps: TokenBalancesProps) { public componentWillReceiveProps(nextProps: TokenBalancesProps) {
if (nextProps.userEtherBalance !== this.props.userEtherBalance) { if (nextProps.userEtherBalanceInWei !== this.props.userEtherBalanceInWei) {
if (this.state.isBalanceSpinnerVisible) { if (this.state.isBalanceSpinnerVisible) {
const receivedAmount = nextProps.userEtherBalance.minus(this.props.userEtherBalance); const receivedAmountInWei = nextProps.userEtherBalanceInWei.minus(this.props.userEtherBalanceInWei);
const receivedAmountInEth = ZeroEx.toUnitAmount(receivedAmountInWei, constants.DECIMAL_PLACES_ETH);
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId]; const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
this.props.dispatcher.showFlashMessage(`Received ${receivedAmount.toString(10)} ${networkName} Ether`); this.props.dispatcher.showFlashMessage(
`Received ${receivedAmountInEth.toString(10)} ${networkName} Ether`,
);
} }
this.setState({ this.setState({
isBalanceSpinnerVisible: false, isBalanceSpinnerVisible: false,
@ -205,6 +207,10 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
token balances in order to execute trades.<br> \ token balances in order to execute trades.<br> \
Toggling sets an allowance for the<br> \ Toggling sets an allowance for the<br> \
smart contract so you can start trading that token.'; smart contract so you can start trading that token.';
const userEtherBalanceInEth = ZeroEx.toUnitAmount(
this.props.userEtherBalanceInWei,
constants.DECIMAL_PLACES_ETH,
);
return ( return (
<div className="lg-px4 md-px4 sm-px1 pb2"> <div className="lg-px4 md-px4 sm-px1 pb2">
<h3>{isTestNetwork ? 'Test ether' : 'Ether'}</h3> <h3>{isTestNetwork ? 'Test ether' : 'Ether'}</h3>
@ -241,7 +247,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
<img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={ETHER_ICON_PATH} /> <img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={ETHER_ICON_PATH} />
</TableRowColumn> </TableRowColumn>
<TableRowColumn> <TableRowColumn>
{this.props.userEtherBalance.toFixed(PRECISION)} ETH {userEtherBalanceInEth.toFixed(configs.AMOUNT_DISPLAY_PRECSION)} ETH
{this.state.isBalanceSpinnerVisible && ( {this.state.isBalanceSpinnerVisible && (
<span className="pl1"> <span className="pl1">
<i className="zmdi zmdi-spinner zmdi-hc-spin" /> <i className="zmdi zmdi-spinner zmdi-hc-spin" />
@ -493,7 +499,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
} }
private _renderAmount(amount: BigNumber, decimals: number) { private _renderAmount(amount: BigNumber, decimals: number) {
const unitAmount = ZeroEx.toUnitAmount(amount, decimals); const unitAmount = ZeroEx.toUnitAmount(amount, decimals);
return unitAmount.toNumber().toFixed(PRECISION); return unitAmount.toNumber().toFixed(configs.AMOUNT_DISPLAY_PRECSION);
} }
private _renderTokenName(token: Token) { private _renderTokenName(token: Token) {
const tooltipId = `tooltip-${token.address}`; const tooltipId = `tooltip-${token.address}`;
@ -681,9 +687,10 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
} }
private async _fetchBalancesAndAllowancesAsync(tokenAddresses: string[]) { private async _fetchBalancesAndAllowancesAsync(tokenAddresses: string[]) {
const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress; const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress;
const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress;
for (const tokenAddress of tokenAddresses) { for (const tokenAddress of tokenAddresses) {
const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
this.props.userAddress, userAddressIfExists,
tokenAddress, tokenAddress,
); );
trackedTokenStateByAddress[tokenAddress] = { trackedTokenStateByAddress[tokenAddress] = {
@ -710,8 +717,9 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
return trackedTokenStateByAddress; return trackedTokenStateByAddress;
} }
private async _refetchTokenStateAsync(tokenAddress: string) { private async _refetchTokenStateAsync(tokenAddress: string) {
const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress;
const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
this.props.userAddress, userAddressIfExists,
tokenAddress, tokenAddress,
); );
this.setState({ this.setState({

View File

@ -9,8 +9,8 @@ import * as ReactTooltip from 'react-tooltip';
import { EtherScanIcon } from 'ts/components/ui/etherscan_icon'; import { EtherScanIcon } from 'ts/components/ui/etherscan_icon';
import { Party } from 'ts/components/ui/party'; import { Party } from 'ts/components/ui/party';
import { Fill, Token, TokenByAddress } from 'ts/types'; import { Fill, Token, TokenByAddress } from 'ts/types';
import { configs } from 'ts/utils/configs';
const PRECISION = 5;
const IDENTICON_DIAMETER = 40; const IDENTICON_DIAMETER = 40;
interface TradeHistoryItemProps { interface TradeHistoryItemProps {
@ -131,7 +131,7 @@ export class TradeHistoryItem extends React.Component<TradeHistoryItemProps, Tra
{this._renderAmount(givenAmount, givenToken.symbol, givenToken.decimals)} {this._renderAmount(givenAmount, givenToken.symbol, givenToken.decimals)}
</div> </div>
<div style={{ color: colors.grey400, fontSize: 14 }}> <div style={{ color: colors.grey400, fontSize: 14 }}>
{exchangeRate.toFixed(PRECISION)} {givenToken.symbol}/{receiveToken.symbol} {exchangeRate.toFixed(configs.AMOUNT_DISPLAY_PRECSION)} {givenToken.symbol}/{receiveToken.symbol}
</div> </div>
</div> </div>
); );
@ -163,7 +163,7 @@ export class TradeHistoryItem extends React.Component<TradeHistoryItemProps, Tra
const unitAmount = ZeroEx.toUnitAmount(amount, decimals); const unitAmount = ZeroEx.toUnitAmount(amount, decimals);
return ( return (
<span> <span>
{unitAmount.toFixed(PRECISION)} {symbol} {unitAmount.toFixed(configs.AMOUNT_DISPLAY_PRECSION)} {symbol}
</span> </span>
); );
} }

View File

@ -3,10 +3,9 @@ import * as _ from 'lodash';
import * as React from 'react'; import * as React from 'react';
import { Party } from 'ts/components/ui/party'; import { Party } from 'ts/components/ui/party';
import { AssetToken, Token, TokenByAddress } from 'ts/types'; import { AssetToken, Token, TokenByAddress } from 'ts/types';
import { configs } from 'ts/utils/configs';
import { utils } from 'ts/utils/utils'; import { utils } from 'ts/utils/utils';
const PRECISION = 5;
interface VisualOrderProps { interface VisualOrderProps {
makerAssetToken: AssetToken; makerAssetToken: AssetToken;
takerAssetToken: AssetToken; takerAssetToken: AssetToken;
@ -67,7 +66,7 @@ export class VisualOrder extends React.Component<VisualOrderProps, VisualOrderSt
const unitAmount = ZeroEx.toUnitAmount(assetToken.amount, token.decimals); const unitAmount = ZeroEx.toUnitAmount(assetToken.amount, token.decimals);
return ( return (
<div style={{ fontSize: 13 }}> <div style={{ fontSize: 13 }}>
{unitAmount.toNumber().toFixed(PRECISION)} {token.symbol} {unitAmount.toNumber().toFixed(configs.AMOUNT_DISPLAY_PRECSION)} {token.symbol}
</div> </div>
); );
} }

View File

@ -21,7 +21,7 @@ interface ConnectedState {
providerType: ProviderType; providerType: ProviderType;
tokenByAddress: TokenByAddress; tokenByAddress: TokenByAddress;
lastForceTokenStateRefetch: number; lastForceTokenStateRefetch: number;
userEtherBalance: BigNumber; userEtherBalanceInWei: BigNumber;
screenWidth: ScreenWidths; screenWidth: ScreenWidths;
shouldBlockchainErrDialogBeOpen: boolean; shouldBlockchainErrDialogBeOpen: boolean;
userAddress: string; userAddress: string;
@ -72,7 +72,7 @@ const mapStateToProps = (state: State, ownProps: PortalComponentAllProps): Conne
tokenByAddress: state.tokenByAddress, tokenByAddress: state.tokenByAddress,
lastForceTokenStateRefetch: state.lastForceTokenStateRefetch, lastForceTokenStateRefetch: state.lastForceTokenStateRefetch,
userAddress: state.userAddress, userAddress: state.userAddress,
userEtherBalance: state.userEtherBalance, userEtherBalanceInWei: state.userEtherBalanceInWei,
userSuppliedOrderCache: state.userSuppliedOrderCache, userSuppliedOrderCache: state.userSuppliedOrderCache,
flashMessage: state.flashMessage, flashMessage: state.flashMessage,
translate: state.translate, translate: state.translate,

View File

@ -86,7 +86,7 @@ export class Dispatcher {
type: ActionTypes.UpdateOrderTakerAddress, type: ActionTypes.UpdateOrderTakerAddress,
}); });
} }
public updateUserAddress(address: string) { public updateUserAddress(address?: string) {
this._dispatch({ this._dispatch({
data: address, data: address,
type: ActionTypes.UpdateUserAddress, type: ActionTypes.UpdateUserAddress,
@ -125,14 +125,14 @@ export class Dispatcher {
public batchDispatch( public batchDispatch(
tokenByAddress: TokenByAddress, tokenByAddress: TokenByAddress,
networkId: number, networkId: number,
userAddress: string, userAddressIfExists: string | undefined,
sideToAssetToken: SideToAssetToken, sideToAssetToken: SideToAssetToken,
) { ) {
this._dispatch({ this._dispatch({
data: { data: {
tokenByAddress, tokenByAddress,
networkId, networkId,
userAddress, userAddressIfExists,
sideToAssetToken, sideToAssetToken,
}, },
type: ActionTypes.BatchDispatch, type: ActionTypes.BatchDispatch,
@ -155,7 +155,7 @@ export class Dispatcher {
type: ActionTypes.UpdateOrderECSignature, type: ActionTypes.UpdateOrderECSignature,
}); });
} }
public updateUserEtherBalance(balance: BigNumber) { public updateUserWeiBalance(balance: BigNumber) {
this._dispatch({ this._dispatch({
data: balance, data: balance,
type: ActionTypes.UpdateUserEtherBalance, type: ActionTypes.UpdateUserEtherBalance,

View File

@ -38,7 +38,7 @@ export interface State {
tokenByAddress: TokenByAddress; tokenByAddress: TokenByAddress;
lastForceTokenStateRefetch: number; lastForceTokenStateRefetch: number;
userAddress: string; userAddress: string;
userEtherBalance: BigNumber; userEtherBalanceInWei: BigNumber;
// Note: cache of supplied orderJSON in fill order step. Do not use for anything else. // Note: cache of supplied orderJSON in fill order step. Do not use for anything else.
userSuppliedOrderCache: Order; userSuppliedOrderCache: Order;
@ -77,7 +77,7 @@ const INITIAL_STATE: State = {
tokenByAddress: {}, tokenByAddress: {},
lastForceTokenStateRefetch: moment().unix(), lastForceTokenStateRefetch: moment().unix(),
userAddress: '', userAddress: '',
userEtherBalance: new BigNumber(0), userEtherBalanceInWei: new BigNumber(0),
userSuppliedOrderCache: undefined, userSuppliedOrderCache: undefined,
// Docs // Docs
@ -138,7 +138,7 @@ export function reducer(state: State = INITIAL_STATE, action: Action) {
case ActionTypes.UpdateUserEtherBalance: { case ActionTypes.UpdateUserEtherBalance: {
return { return {
...state, ...state,
userEtherBalance: action.data, userEtherBalanceInWei: action.data,
}; };
} }
@ -184,10 +184,11 @@ export function reducer(state: State = INITIAL_STATE, action: Action) {
} }
case ActionTypes.BatchDispatch: { case ActionTypes.BatchDispatch: {
const userAddress = _.isUndefined(action.data.userAddressIfExists) ? '' : action.data.userAddressIfExists;
return { return {
...state, ...state,
networkId: action.data.networkId, networkId: action.data.networkId,
userAddress: action.data.userAddress, userAddress,
sideToAssetToken: action.data.sideToAssetToken, sideToAssetToken: action.data.sideToAssetToken,
tokenByAddress: action.data.tokenByAddress, tokenByAddress: action.data.tokenByAddress,
}; };
@ -284,9 +285,10 @@ export function reducer(state: State = INITIAL_STATE, action: Action) {
} }
case ActionTypes.UpdateUserAddress: { case ActionTypes.UpdateUserAddress: {
const userAddress = _.isUndefined(action.data) ? '' : action.data;
return { return {
...state, ...state,
userAddress: action.data, userAddress,
}; };
} }

View File

@ -9,6 +9,7 @@ const isDevelopment = _.includes(
const INFURA_API_KEY = 'T5WSC8cautR4KXyYgsRs'; const INFURA_API_KEY = 'T5WSC8cautR4KXyYgsRs';
export const configs = { export const configs = {
AMOUNT_DISPLAY_PRECSION: 5,
BACKEND_BASE_URL: 'https://website-api.0xproject.com', BACKEND_BASE_URL: 'https://website-api.0xproject.com',
BASE_URL, BASE_URL,
BITLY_ACCESS_TOKEN: 'ffc4c1a31e5143848fb7c523b39f91b9b213d208', BITLY_ACCESS_TOKEN: 'ffc4c1a31e5143848fb7c523b39f91b9b213d208',

View File

@ -1,157 +0,0 @@
import { BigNumber, intervalUtils, promisify } from '@0xproject/utils';
import * as _ from 'lodash';
import { Dispatcher } from 'ts/redux/dispatcher';
import { utils } from 'ts/utils/utils';
import * as Web3 from 'web3';
export class Web3Wrapper {
private _dispatcher: Dispatcher;
private _web3: Web3;
private _prevNetworkId: number;
private _shouldPollUserAddress: boolean;
private _watchNetworkAndBalanceIntervalId: NodeJS.Timer;
private _prevUserEtherBalanceInEth: BigNumber;
private _prevUserAddress: string;
constructor(
dispatcher: Dispatcher,
provider: Web3.Provider,
networkIdIfExists: number,
shouldPollUserAddress: boolean,
) {
this._dispatcher = dispatcher;
this._prevNetworkId = networkIdIfExists;
this._shouldPollUserAddress = shouldPollUserAddress;
this._web3 = new Web3();
this._web3.setProvider(provider);
}
public isAddress(address: string) {
return this._web3.isAddress(address);
}
public async getAccountsAsync(): Promise<string[]> {
const addresses = await promisify<string[]>(this._web3.eth.getAccounts)();
return addresses;
}
public async getFirstAccountIfExistsAsync() {
const addresses = await this.getAccountsAsync();
if (_.isEmpty(addresses)) {
return '';
}
return addresses[0];
}
public async getNodeVersionAsync(): Promise<string> {
const nodeVersion = await promisify<string>(this._web3.version.getNode)();
return nodeVersion;
}
public getProviderObj() {
return this._web3.currentProvider;
}
public async getNetworkIdIfExists() {
try {
const networkId = await this._getNetworkAsync();
return Number(networkId);
} catch (err) {
return undefined;
}
}
public async getBalanceInEthAsync(owner: string): Promise<BigNumber> {
const balanceInWei: BigNumber = await promisify<BigNumber>(this._web3.eth.getBalance)(owner);
const balanceEthOldBigNumber = this._web3.fromWei(balanceInWei, 'ether');
const balanceEth = new BigNumber(balanceEthOldBigNumber);
return balanceEth;
}
public async doesContractExistAtAddressAsync(address: string): Promise<boolean> {
const code = await promisify<string>(this._web3.eth.getCode)(address);
// Regex matches 0x0, 0x00, 0x in order to accomodate poorly implemented clients
const zeroHexAddressRegex = /^0[xX][0]*$/;
const didFindCode = _.isNull(code.match(zeroHexAddressRegex));
return didFindCode;
}
public async signTransactionAsync(address: string, message: string): Promise<string> {
const signData = await promisify<string>(this._web3.eth.sign)(address, message);
return signData;
}
public async getBlockTimestampAsync(blockHash: string): Promise<number> {
const { timestamp } = await promisify<Web3.BlockWithoutTransactionData>(this._web3.eth.getBlock)(blockHash);
return timestamp;
}
public destroy() {
this._stopEmittingNetworkConnectionAndUserBalanceStateAsync();
// HACK: stop() is only available on providerEngine instances
const provider = this._web3.currentProvider;
if (!_.isUndefined((provider as any).stop)) {
(provider as any).stop();
}
}
// This should only be called from the LedgerConfigDialog
public updatePrevUserAddress(userAddress: string) {
this._prevUserAddress = userAddress;
}
public startEmittingNetworkConnectionAndUserBalanceState() {
if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) {
return; // we are already emitting the state
}
let prevNodeVersion: string;
this._prevUserEtherBalanceInEth = new BigNumber(0);
this._dispatcher.updateNetworkId(this._prevNetworkId);
this._watchNetworkAndBalanceIntervalId = intervalUtils.setAsyncExcludingInterval(
async () => {
// Check for network state changes
const currentNetworkId = await this.getNetworkIdIfExists();
if (currentNetworkId !== this._prevNetworkId) {
this._prevNetworkId = currentNetworkId;
this._dispatcher.updateNetworkId(currentNetworkId);
}
// Check for node version changes
const currentNodeVersion = await this.getNodeVersionAsync();
if (currentNodeVersion !== prevNodeVersion) {
prevNodeVersion = currentNodeVersion;
this._dispatcher.updateNodeVersion(currentNodeVersion);
}
if (this._shouldPollUserAddress) {
const userAddressIfExists = await this.getFirstAccountIfExistsAsync();
// Update makerAddress on network change
if (this._prevUserAddress !== userAddressIfExists) {
this._prevUserAddress = userAddressIfExists;
this._dispatcher.updateUserAddress(userAddressIfExists);
}
// Check for user ether balance changes
if (!_.isEmpty(userAddressIfExists)) {
await this._updateUserEtherBalanceAsync(userAddressIfExists);
}
} else {
// This logic is primarily for the Ledger, since we don't regularly poll for the address
// we simply update the balance for the last fetched address.
if (!_.isEmpty(this._prevUserAddress)) {
await this._updateUserEtherBalanceAsync(this._prevUserAddress);
}
}
},
5000,
(err: Error) => {
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)) {
this._prevUserEtherBalanceInEth = balance;
this._dispatcher.updateUserEtherBalance(balance);
}
}
private _stopEmittingNetworkConnectionAndUserBalanceStateAsync() {
if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) {
intervalUtils.clearAsyncExcludingInterval(this._watchNetworkAndBalanceIntervalId);
}
}
}