diff options
21 files changed, 308 insertions, 278 deletions
diff --git a/packages/web3-wrapper/CHANGELOG.md b/packages/web3-wrapper/CHANGELOG.md index ac0f5ff0b..6dabc7eb4 100644 --- a/packages/web3-wrapper/CHANGELOG.md +++ b/packages/web3-wrapper/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## v0.2.1 _TBD_ + + * Add a `getProvider` method (#444) + ## v0.2.0 _March 4, 2018_ * Ensure all returned user addresses are lowercase (#373) diff --git a/packages/web3-wrapper/src/index.ts b/packages/web3-wrapper/src/index.ts index 64a19f531..e14eb2914 100644 --- a/packages/web3-wrapper/src/index.ts +++ b/packages/web3-wrapper/src/index.ts @@ -11,7 +11,7 @@ export class Web3Wrapper { if (_.isUndefined((provider as any).sendAsync)) { // 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` - // 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; } this._web3 = new Web3(); @@ -22,6 +22,9 @@ export class Web3Wrapper { public getContractDefaults(): Partial<TxData> { return this._defaults; } + public getProvider(): Web3.Provider { + return this._web3.currentProvider; + } public setProvider(provider: Web3.Provider) { this._web3.setProvider(provider); } diff --git a/packages/website/package.json b/packages/website/package.json index db3035642..a7fc1fe26 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -22,6 +22,7 @@ "@0xproject/react-docs": "^0.0.1", "@0xproject/react-shared": "^0.0.1", "@0xproject/subproviders": "^0.7.0", + "@0xproject/web3-wrapper": "^0.2.1", "@0xproject/utils": "^0.4.1", "accounting": "^0.4.1", "basscss": "^8.0.3", diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index fca9504d7..14582eae9 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -24,9 +24,11 @@ import { RedundantRPCSubprovider, } from '@0xproject/subproviders'; import { BigNumber, intervalUtils, promisify } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as _ from 'lodash'; import * as React from 'react'; import contract = require('truffle-contract'); +import { BlockchainWatcher } from 'ts/blockchain_watcher'; import { TokenSendCompleted } from 'ts/components/flash_messages/token_send_completed'; import { TransactionSubmitted } from 'ts/components/flash_messages/transaction_submitted'; 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 { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; -import { Web3Wrapper } from 'ts/web3_wrapper'; import Web3 = require('web3'); import ProviderEngine = require('web3-provider-engine'); import FilterSubprovider = require('web3-provider-engine/subproviders/filters'); @@ -63,8 +64,8 @@ export class Blockchain { private _zeroEx: ZeroEx; private _dispatcher: Dispatcher; private _web3Wrapper?: Web3Wrapper; - private _exchangeAddress: string; - private _userAddress: string; + private _blockchainWatcher?: BlockchainWatcher; + private _userAddressIfExists: string; private _cachedProvider: Web3.Provider; private _cachedProviderNetworkId: number; private _ledgerSubprovider: LedgerWalletSubprovider; @@ -115,7 +116,6 @@ 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 @@ -137,8 +137,8 @@ export class Blockchain { } } public async userAddressUpdatedFireAndForgetAsync(newUserAddress: string) { - if (this._userAddress !== newUserAddress) { - this._userAddress = newUserAddress; + if (this._userAddressIfExists !== newUserAddress) { + this._userAddressIfExists = newUserAddress; await this.fetchTokenInformationAsync(); await this._rehydrateStoreWithContractEvents(); } @@ -189,14 +189,14 @@ export class Blockchain { // Cache injected provider so that we can switch the user back to it easily if (_.isUndefined(this._cachedProvider)) { - this._cachedProvider = this._web3Wrapper.getProviderObj(); + this._cachedProvider = this._web3Wrapper.getProvider(); this._cachedProviderNetworkId = this.networkId; } - this._web3Wrapper.destroy(); + this._blockchainWatcher.destroy(); - this._userAddress = ''; - this._dispatcher.updateUserAddress(''); // Clear old userAddress + delete this._userAddressIfExists; + this._dispatcher.updateUserAddress(undefined); // Clear old userAddress const provider = new ProviderEngine(); const ledgerWalletConfigs = { @@ -211,10 +211,15 @@ export class Blockchain { this.networkId = networkId; this._dispatcher.updateNetworkId(this.networkId); 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); - await this._postInstantiationOrUpdatingProviderZeroExAsync(); - this._web3Wrapper.startEmittingNetworkConnectionAndUserBalanceState(); + this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceState(); this._dispatcher.updateProviderType(ProviderType.Ledger); } public async updateProviderToInjectedAsync() { @@ -224,21 +229,27 @@ export class Blockchain { return; // Going from injected to injected, so we noop } - this._web3Wrapper.destroy(); + this._blockchainWatcher.destroy(); const provider = this._cachedProvider; this.networkId = this._cachedProviderNetworkId; 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); - await this._postInstantiationOrUpdatingProviderZeroExAsync(); await this.fetchTokenInformationAsync(); - this._web3Wrapper.startEmittingNetworkConnectionAndUserBalanceState(); + this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceState(); this._dispatcher.updateProviderType(ProviderType.Injected); delete this._ledgerSubprovider; delete this._cachedProvider; @@ -251,7 +262,7 @@ export class Blockchain { this._showFlashMessageIfLedger(); const txHash = await this._zeroEx.token.setProxyAllowanceAsync( token.address, - this._userAddress, + this._userAddressIfExists, amountInBaseUnits, { gasPrice: this._defaultGasPrice, @@ -260,10 +271,13 @@ export class Blockchain { await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); } 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(); const txHash = await this._zeroEx.token.transferAsync( token.address, - this._userAddress, + this._userAddressIfExists, toAddress, amountInBaseUnits, { @@ -305,6 +319,7 @@ export class Blockchain { return zeroExSignedOrder; } public async fillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber): Promise<BigNumber> { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); const shouldThrowOnInsufficientBalanceOrAllowance = true; @@ -314,7 +329,7 @@ export class Blockchain { signedOrder, fillTakerTokenAmount, shouldThrowOnInsufficientBalanceOrAllowance, - this._userAddress, + this._userAddressIfExists, { gasPrice: this._defaultGasPrice, }, @@ -347,7 +362,7 @@ export class Blockchain { return unavailableTakerAmount; } public getExchangeContractAddressIfExists() { - return this._exchangeAddress; + return this._zeroEx.exchange.getContractAddress(); } public async validateFillOrderThrowIfInvalidAsync( signedOrder: SignedOrder, @@ -373,12 +388,15 @@ export class Blockchain { public async pollTokenBalanceAsync(token: Token) { 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 tokenPollInterval = intervalUtils.setAsyncExcludingInterval( async () => { - const [balance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address); + const [balance] = await this.getTokenBalanceAndAllowanceAsync( + this._userAddressIfExists, + token.address, + ); if (!balance.eq(currBalance)) { intervalUtils.clearAsyncExcludingInterval(tokenPollInterval); resolve(balance); @@ -397,7 +415,7 @@ export class Blockchain { } public async signOrderHashAsync(orderHash: string): Promise<ECSignature> { 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 // but no account addresses associated with it. if (_.isUndefined(makerAddress)) { @@ -427,22 +445,27 @@ export class Blockchain { const mintableContract = await this._instantiateContractIfExistsAsync(MintableArtifacts, token.address); this._showFlashMessageIfLedger(); await mintableContract.mint(constants.MINT_AMOUNT, { - from: this._userAddress, + from: this._userAddressIfExists, gasPrice: this._defaultGasPrice, }); } - public async getBalanceInEthAsync(owner: string): Promise<BigNumber> { - const balance = await this._web3Wrapper.getBalanceInEthAsync(owner); - return balance; + public async getBalanceInWeiAsync(owner: string): Promise<BigNumber> { + const balanceInWei = await this._web3Wrapper.getBalanceInWeiAsync(owner); + return balanceInWei; } public async convertEthToWrappedEthTokensAsync(etherTokenAddress: string, amount: BigNumber): Promise<void> { utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); this._showFlashMessageIfLedger(); - const txHash = await this._zeroEx.etherToken.depositAsync(etherTokenAddress, amount, this._userAddress, { - gasPrice: this._defaultGasPrice, - }); + const txHash = await this._zeroEx.etherToken.depositAsync( + etherTokenAddress, + amount, + this._userAddressIfExists, + { + gasPrice: this._defaultGasPrice, + }, + ); await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); } public async convertWrappedEthTokensToEthAsync(etherTokenAddress: string, amount: BigNumber): Promise<void> { @@ -450,9 +473,14 @@ export class Blockchain { utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); this._showFlashMessageIfLedger(); - const txHash = await this._zeroEx.etherToken.withdrawAsync(etherTokenAddress, amount, this._userAddress, { - gasPrice: this._defaultGasPrice, - }); + const txHash = await this._zeroEx.etherToken.withdrawAsync( + etherTokenAddress, + amount, + this._userAddressIfExists, + { + gasPrice: this._defaultGasPrice, + }, + ); await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); } public async doesContractExistAtAddressAsync(address: string) { @@ -460,21 +488,29 @@ export class Blockchain { return doesContractExist; } 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; } - 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.'); - if (_.isEmpty(ownerAddress)) { + if (_.isUndefined(ownerAddressIfExists)) { const zero = new BigNumber(0); return [zero, zero]; } let balance = new BigNumber(0); let allowance = new BigNumber(0); if (this._doesUserAddressExist()) { - balance = await this._zeroEx.token.getBalanceAsync(tokenAddress, ownerAddress); - allowance = await this._zeroEx.token.getProxyAllowanceAsync(tokenAddress, ownerAddress); + balance = await this._zeroEx.token.getBalanceAsync(tokenAddress, ownerAddressIfExists); + allowance = await this._zeroEx.token.getProxyAllowanceAsync(tokenAddress, ownerAddressIfExists); } return [balance, allowance]; } @@ -487,10 +523,10 @@ export class Blockchain { // by-passes the web3Wrapper logic for updating the prevUserAddress. We therefore need to // manually update it. This should only be called by the LedgerConfigDialog. public updateWeb3WrapperPrevUserAddress(newUserAddress: string) { - this._web3Wrapper.updatePrevUserAddress(newUserAddress); + this._blockchainWatcher.updatePrevUserAddress(newUserAddress); } public destroy() { - this._web3Wrapper.destroy(); + this._blockchainWatcher.destroy(); this._stopWatchingExchangeLogFillEvents(); } public async fetchTokenInformationAsync() { @@ -503,7 +539,9 @@ export class Blockchain { 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); if (_.isEmpty(trackedTokensByAddress)) { _.each(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, symbol => { @@ -511,9 +549,11 @@ export class Blockchain { token.isTracked = true; trackedTokensByAddress[token.address] = token; }); - _.each(trackedTokensByAddress, (token: Token, address: string) => { - trackedTokenStorage.addTrackedTokenToUser(this._userAddress, this.networkId, token); - }); + if (!_.isUndefined(this._userAddressIfExists)) { + _.each(trackedTokensByAddress, (token: Token, address: string) => { + trackedTokenStorage.addTrackedTokenToUser(this._userAddressIfExists, 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) => { @@ -539,7 +579,7 @@ export class Blockchain { 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); } @@ -560,7 +600,7 @@ export class Blockchain { return receipt; } private _doesUserAddressExist(): boolean { - return this._userAddress !== ''; + return !_.isUndefined(this._userAddressIfExists); } private async _rehydrateStoreWithContractEvents() { // Ensure we are only ever listening to one set of events @@ -605,16 +645,18 @@ export class Blockchain { this._updateLatestFillsBlockIfNeeded(decodedLog.blockNumber); const fill = await this._convertDecodedLogToFillAsync(decodedLog); if (decodedLogEvent.isRemoved) { - tradeHistoryStorage.removeFillFromUser(this._userAddress, this.networkId, fill); + tradeHistoryStorage.removeFillFromUser(this._userAddressIfExists, this.networkId, fill); } else { - tradeHistoryStorage.addFillToUser(this._userAddress, this.networkId, fill); + tradeHistoryStorage.addFillToUser(this._userAddressIfExists, this.networkId, fill); } } }, ); } 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 = { fromBlock, toBlock: 'latest' as BlockParam, @@ -630,7 +672,7 @@ export class Blockchain { } this._updateLatestFillsBlockIfNeeded(decodedLog.blockNumber); 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>) { @@ -654,10 +696,12 @@ export class Blockchain { } private _doesLogEventInvolveUser(decodedLog: LogWithDecodedArgs<LogFillContractEventArgs>) { 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; } private _updateLatestFillsBlockIfNeeded(blockNumber: number) { + utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); + const isBlockPending = _.isNull(blockNumber); if (!isBlockPending) { // 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 const blockNumberToSet = 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 { @@ -739,20 +783,21 @@ export class Blockchain { this._zeroEx = new ZeroEx(provider, zeroExConfigs); this._updateProviderName(injectedWeb3); const shouldPollUserAddress = true; - this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress); - await this._postInstantiationOrUpdatingProviderZeroExAsync(); - this._userAddress = await this._web3Wrapper.getFirstAccountIfExistsAsync(); - this._dispatcher.updateUserAddress(this._userAddress); + this._web3Wrapper = new Web3Wrapper(provider); + this._blockchainWatcher = new BlockchainWatcher( + this._dispatcher, + this._web3Wrapper, + this.networkId, + shouldPollUserAddress, + ); + + const userAddresses = await this._web3Wrapper.getAvailableAddressesAsync(); + this._userAddressIfExists = userAddresses[0]; + this._dispatcher.updateUserAddress(this._userAddressIfExists); await this.fetchTokenInformationAsync(); - this._web3Wrapper.startEmittingNetworkConnectionAndUserBalanceState(); + this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceState(); 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) { const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); const providerName = doesInjectedWeb3Exist @@ -762,7 +807,7 @@ export class Blockchain { } private async _instantiateContractIfExistsAsync(artifact: any, address?: string): Promise<ContractInstance> { const c = await contract(artifact); - const providerObj = this._web3Wrapper.getProviderObj(); + const providerObj = this._web3Wrapper.getProvider(); c.setProvider(providerObj); const artifactNetworkConfigs = artifact.networks[this.networkId]; diff --git a/packages/website/ts/blockchain_watcher.ts b/packages/website/ts/blockchain_watcher.ts new file mode 100644 index 000000000..d3801cef4 --- /dev/null +++ b/packages/website/ts/blockchain_watcher.ts @@ -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); + } + } +} diff --git a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx index 5c61f0d57..d1bdb447f 100644 --- a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx +++ b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx @@ -1,5 +1,7 @@ +import { ZeroEx } from '0x.js'; import { colors } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; +import * as _ from 'lodash'; import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; import * as React from 'react'; @@ -7,6 +9,7 @@ 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 } from 'ts/types'; +import { constants } from 'ts/utils/constants'; interface EthWethConversionDialogProps { blockchain: Blockchain; @@ -17,7 +20,7 @@ interface EthWethConversionDialogProps { onCancelled: () => void; isOpen: boolean; token: Token; - etherBalance: BigNumber; + etherBalanceInWei: BigNumber; lastForceTokenStateRefetch: number; } @@ -75,6 +78,7 @@ export class EthWethConversionDialog extends React.Component< ? 'Convert your Ether into a tokenized, tradable form.' : "Convert your Wrapped Ether back into it's native form."; const isWrappedVersion = this.props.direction === Side.Receive; + const etherBalanceInEth = ZeroEx.toUnitAmount(this.props.etherBalanceInWei, constants.DECIMAL_PLACES_ETH); return ( <div> <div className="pb2">{explanation}</div> @@ -103,7 +107,7 @@ export class EthWethConversionDialog extends React.Component< /> ) : ( <EthAmountInput - balance={this.props.etherBalance} + balance={etherBalanceInEth} amount={this.state.value} onChange={this._onValueChange.bind(this)} shouldCheckBalance={true} @@ -182,8 +186,9 @@ export class EthWethConversionDialog extends React.Component< this.props.onCancelled(); } private async _fetchEthTokenBalanceAsync() { + const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress; const [balance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( - this.props.userAddress, + userAddressIfExists, this.props.token.address, ); if (!this._isUnmounted) { diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx index 8a242cd33..87b75ed95 100644 --- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx +++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx @@ -1,3 +1,4 @@ +import { ZeroEx } from '0x.js'; import { colors, constants as sharedConstants } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; @@ -160,14 +161,15 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, } private _renderAddressTableRows() { 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 balanceTooltipId = `balance-${userAddress}`; const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId]; // We specifically prefix kovan ETH. // TODO: We should probably add prefixes for all networks 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 ( <TableRow key={userAddress} style={{ height: 40 }}> <TableRowColumn colSpan={2}> @@ -204,7 +206,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, this.props.blockchain.updateWeb3WrapperPrevUserAddress(selectedAddress); // tslint:disable-next-line:no-floating-promises this.props.blockchain.fetchTokenInformationAsync(); - this.props.dispatcher.updateUserEtherBalance(selectAddressBalance); + this.props.dispatcher.updateUserWeiBalance(selectAddressBalance); this.setState({ stepIndex: LedgerSteps.CONNECT, }); @@ -233,8 +235,8 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, try { userAddresses = await this._getUserAddressesAsync(); for (const address of userAddresses) { - const balance = await this.props.blockchain.getBalanceInEthAsync(address); - addressBalances.push(balance); + const balanceInWei = await this.props.blockchain.getBalanceInWeiAsync(address); + addressBalances.push(balanceInWei); } } catch (err) { utils.consoleLog(`Ledger error: ${JSON.stringify(err)}`); diff --git a/packages/website/ts/components/eth_weth_conversion_button.tsx b/packages/website/ts/components/eth_weth_conversion_button.tsx index 62bafdba7..8e13d0aae 100644 --- a/packages/website/ts/components/eth_weth_conversion_button.tsx +++ b/packages/website/ts/components/eth_weth_conversion_button.tsx @@ -18,7 +18,7 @@ interface EthWethConversionButtonProps { ethToken: Token; dispatcher: Dispatcher; blockchain: Blockchain; - userEtherBalance: BigNumber; + userEtherBalanceInWei: BigNumber; isOutdatedWrappedEther: boolean; onConversionSuccessful?: () => void; isDisabled?: boolean; @@ -74,7 +74,7 @@ export class EthWethConversionButton extends React.Component< isOpen={this.state.isEthConversionDialogVisible} onComplete={this._onConversionAmountSelectedAsync.bind(this)} onCancelled={this._toggleConversionDialog.bind(this)} - etherBalance={this.props.userEtherBalance} + etherBalanceInWei={this.props.userEtherBalanceInWei} token={this.props.ethToken} lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} /> diff --git a/packages/website/ts/components/eth_wrappers.tsx b/packages/website/ts/components/eth_wrappers.tsx index 7ac5d5c9c..b12c637e5 100644 --- a/packages/website/ts/components/eth_wrappers.tsx +++ b/packages/website/ts/components/eth_wrappers.tsx @@ -15,7 +15,6 @@ import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; -const PRECISION = 5; const DATE_FORMAT = 'D/M/YY'; const ICON_DIMENSION = 40; const ETHER_ICON_PATH = '/images/ether.png'; @@ -34,7 +33,7 @@ interface EthWrappersProps { dispatcher: Dispatcher; tokenByAddress: TokenByAddress; userAddress: string; - userEtherBalance: BigNumber; + userEtherBalanceInWei: BigNumber; lastForceTokenStateRefetch: number; } @@ -98,6 +97,10 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt EtherscanLinkSuffixes.Address, ); const tokenLabel = this._renderToken('Wrapped Ether', etherToken.address, configs.ICON_URL_BY_SYMBOL.WETH); + const userEtherBalanceInEth = ZeroEx.toUnitAmount( + this.props.userEtherBalanceInWei, + constants.DECIMAL_PLACES_ETH, + ); return ( <div className="clearfix lg-px4 md-px4 sm-px2" style={{ minHeight: 600 }}> <div className="relative"> @@ -144,7 +147,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt </div> </TableRowColumn> <TableRowColumn> - {this.props.userEtherBalance.toFixed(PRECISION)} ETH + {userEtherBalanceInEth.toFixed(configs.AMOUNT_DISPLAY_PRECSION)} ETH </TableRowColumn> <TableRowColumn> <EthWethConversionButton @@ -157,7 +160,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt ethToken={etherToken} dispatcher={this.props.dispatcher} blockchain={this.props.blockchain} - userEtherBalance={this.props.userEtherBalance} + userEtherBalanceInWei={this.props.userEtherBalanceInWei} /> </TableRowColumn> </TableRow> @@ -167,7 +170,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt </TableRowColumn> <TableRowColumn> {this.state.isWethStateLoaded ? ( - `${wethBalance.toFixed(PRECISION)} WETH` + `${wethBalance.toFixed(configs.AMOUNT_DISPLAY_PRECSION)} WETH` ) : ( <i className="zmdi zmdi-spinner zmdi-hc-spin" /> )} @@ -184,7 +187,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt ethToken={etherToken} dispatcher={this.props.dispatcher} blockchain={this.props.blockchain} - userEtherBalance={this.props.userEtherBalance} + userEtherBalanceInWei={this.props.userEtherBalanceInWei} /> </TableRowColumn> </TableRow> @@ -267,7 +270,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt const outdatedEtherTokenState = this.state.outdatedWETHStateByAddress[outdatedWETHIfExists.address]; const balanceInEthIfExists = isStateLoaded ? ZeroEx.toUnitAmount(outdatedEtherTokenState.balance, constants.DECIMAL_PLACES_ETH).toFixed( - PRECISION, + configs.AMOUNT_DISPLAY_PRECSION, ) : undefined; const onConversionSuccessful = this._onOutdatedConversionSuccessfulAsync.bind( @@ -304,7 +307,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt ethToken={outdatedEtherToken} dispatcher={this.props.dispatcher} blockchain={this.props.blockchain} - userEtherBalance={this.props.userEtherBalance} + userEtherBalanceInWei={this.props.userEtherBalanceInWei} onConversionSuccessful={onConversionSuccessful} /> </TableRowColumn> @@ -348,8 +351,9 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt [outdatedWETHAddress]: false, }, }); + const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress; const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( - this.props.userAddress, + userAddressIfExists, outdatedWETHAddress, ); this.setState({ @@ -369,8 +373,9 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt private async _fetchWETHStateAsync() { const tokens = _.values(this.props.tokenByAddress); const wethToken = _.find(tokens, token => token.symbol === 'WETH'); + const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress; const [wethBalance, wethAllowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( - this.props.userAddress, + userAddressIfExists, wethToken.address, ); @@ -379,7 +384,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {}; for (const address of outdatedWETHAddresses) { const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( - this.props.userAddress, + userAddressIfExists, address, ); outdatedWETHStateByAddress[address] = { @@ -420,8 +425,9 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt } private async _refetchEthTokenStateAsync() { const etherToken = this._getEthToken(); + const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress; const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( - this.props.userAddress, + userAddressIfExists, etherToken.address, ); this.setState({ diff --git a/packages/website/ts/components/generate_order/generate_order_form.tsx b/packages/website/ts/components/generate_order/generate_order_form.tsx index 26fa904fe..ea1d57a7b 100644 --- a/packages/website/ts/components/generate_order/generate_order_form.tsx +++ b/packages/website/ts/components/generate_order/generate_order_form.tsx @@ -237,8 +237,9 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G // Check if all required inputs were supplied 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( - this.props.userAddress, + userAddressIfExists, debitToken.address, ); const receiveAmount = this.props.sideToAssetToken[Side.Receive].amount; diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx index 7fe303cf4..3d353d87c 100644 --- a/packages/website/ts/components/inputs/allowance_toggle.tsx +++ b/packages/website/ts/components/inputs/allowance_toggle.tsx @@ -67,6 +67,7 @@ export class AllowanceToggle extends React.Component<AllowanceToggleProps, Allow private async _onToggleAllowanceAsync(): Promise<void> { if (this.props.userAddress === '') { this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return; } this.setState({ diff --git a/packages/website/ts/components/inputs/token_amount_input.tsx b/packages/website/ts/components/inputs/token_amount_input.tsx index 53248c065..b55840fc4 100644 --- a/packages/website/ts/components/inputs/token_amount_input.tsx +++ b/packages/website/ts/components/inputs/token_amount_input.tsx @@ -109,8 +109,9 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok this.setState({ isBalanceAndAllowanceLoaded: false, }); + const userAddressIfExists = _.isEmpty(userAddress) ? undefined : userAddress; const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( - userAddress, + userAddressIfExists, tokenAddress, ); if (!this._isUnmounted) { diff --git a/packages/website/ts/components/portal.tsx b/packages/website/ts/components/portal.tsx index d71e821c6..7df340f45 100644 --- a/packages/website/ts/components/portal.tsx +++ b/packages/website/ts/components/portal.tsx @@ -46,7 +46,7 @@ export interface PortalAllProps { providerType: ProviderType; screenWidth: ScreenWidths; tokenByAddress: TokenByAddress; - userEtherBalance: BigNumber; + userEtherBalanceInWei: BigNumber; userAddress: string; shouldBlockchainErrDialogBeOpen: boolean; userSuppliedOrderCache: Order; @@ -121,8 +121,9 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { }); } if (nextProps.userAddress !== this.state.prevUserAddress) { + const newUserAddress = _.isEmpty(nextProps.userAddress) ? undefined : nextProps.userAddress; // tslint:disable-next-line:no-floating-promises - this._blockchain.userAddressUpdatedFireAndForgetAsync(nextProps.userAddress); + this._blockchain.userAddressUpdatedFireAndForgetAsync(newUserAddress); this.setState({ prevUserAddress: nextProps.userAddress, }); @@ -279,7 +280,7 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { dispatcher={this.props.dispatcher} tokenByAddress={this.props.tokenByAddress} userAddress={this.props.userAddress} - userEtherBalance={this.props.userEtherBalance} + userEtherBalanceInWei={this.props.userEtherBalanceInWei} lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} /> ); @@ -306,7 +307,7 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { tokenByAddress={this.props.tokenByAddress} trackedTokens={trackedTokens} userAddress={this.props.userAddress} - userEtherBalance={this.props.userEtherBalance} + userEtherBalanceInWei={this.props.userEtherBalanceInWei} networkId={this.props.networkId} lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} /> diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx index 7e7596fd7..894e0721f 100644 --- a/packages/website/ts/components/token_balances.tsx +++ b/packages/website/ts/components/token_balances.tsx @@ -48,7 +48,6 @@ const ETHER_ICON_PATH = '/images/ether.png'; const ETHER_TOKEN_SYMBOL = 'WETH'; const ZRX_TOKEN_SYMBOL = 'ZRX'; -const PRECISION = 5; const ICON_DIMENSION = 40; const ARTIFICIAL_FAUCET_REQUEST_DELAY = 1000; const TOKEN_TABLE_ROW_HEIGHT = 60; @@ -79,7 +78,7 @@ interface TokenBalancesProps { tokenByAddress: TokenByAddress; trackedTokens: Token[]; userAddress: string; - userEtherBalance: BigNumber; + userEtherBalanceInWei: BigNumber; networkId: number; lastForceTokenStateRefetch: number; } @@ -119,11 +118,14 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala this._isUnmounted = true; } public componentWillReceiveProps(nextProps: TokenBalancesProps) { - if (nextProps.userEtherBalance !== this.props.userEtherBalance) { + if (nextProps.userEtherBalanceInWei !== this.props.userEtherBalanceInWei) { 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]; - this.props.dispatcher.showFlashMessage(`Received ${receivedAmount.toString(10)} ${networkName} Ether`); + this.props.dispatcher.showFlashMessage( + `Received ${receivedAmountInEth.toString(10)} ${networkName} Ether`, + ); } this.setState({ isBalanceSpinnerVisible: false, @@ -205,6 +207,10 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala token balances in order to execute trades.<br> \ Toggling sets an allowance for the<br> \ smart contract so you can start trading that token.'; + const userEtherBalanceInEth = ZeroEx.toUnitAmount( + this.props.userEtherBalanceInWei, + constants.DECIMAL_PLACES_ETH, + ); return ( <div className="lg-px4 md-px4 sm-px1 pb2"> <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} /> </TableRowColumn> <TableRowColumn> - {this.props.userEtherBalance.toFixed(PRECISION)} ETH + {userEtherBalanceInEth.toFixed(configs.AMOUNT_DISPLAY_PRECSION)} ETH {this.state.isBalanceSpinnerVisible && ( <span className="pl1"> <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) { const unitAmount = ZeroEx.toUnitAmount(amount, decimals); - return unitAmount.toNumber().toFixed(PRECISION); + return unitAmount.toNumber().toFixed(configs.AMOUNT_DISPLAY_PRECSION); } private _renderTokenName(token: Token) { const tooltipId = `tooltip-${token.address}`; @@ -681,9 +687,10 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala } private async _fetchBalancesAndAllowancesAsync(tokenAddresses: string[]) { const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress; + const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress; for (const tokenAddress of tokenAddresses) { const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( - this.props.userAddress, + userAddressIfExists, tokenAddress, ); trackedTokenStateByAddress[tokenAddress] = { @@ -710,8 +717,9 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala return trackedTokenStateByAddress; } private async _refetchTokenStateAsync(tokenAddress: string) { + const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress; const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( - this.props.userAddress, + userAddressIfExists, tokenAddress, ); this.setState({ diff --git a/packages/website/ts/components/trade_history/trade_history_item.tsx b/packages/website/ts/components/trade_history/trade_history_item.tsx index 6b8d7c7b5..dbe72259b 100644 --- a/packages/website/ts/components/trade_history/trade_history_item.tsx +++ b/packages/website/ts/components/trade_history/trade_history_item.tsx @@ -9,8 +9,8 @@ import * as ReactTooltip from 'react-tooltip'; import { EtherScanIcon } from 'ts/components/ui/etherscan_icon'; import { Party } from 'ts/components/ui/party'; import { Fill, Token, TokenByAddress } from 'ts/types'; +import { configs } from 'ts/utils/configs'; -const PRECISION = 5; const IDENTICON_DIAMETER = 40; interface TradeHistoryItemProps { @@ -131,7 +131,7 @@ export class TradeHistoryItem extends React.Component<TradeHistoryItemProps, Tra {this._renderAmount(givenAmount, givenToken.symbol, givenToken.decimals)} </div> <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> ); @@ -163,7 +163,7 @@ export class TradeHistoryItem extends React.Component<TradeHistoryItemProps, Tra const unitAmount = ZeroEx.toUnitAmount(amount, decimals); return ( <span> - {unitAmount.toFixed(PRECISION)} {symbol} + {unitAmount.toFixed(configs.AMOUNT_DISPLAY_PRECSION)} {symbol} </span> ); } diff --git a/packages/website/ts/components/visual_order.tsx b/packages/website/ts/components/visual_order.tsx index ec2d47f39..3bf464e92 100644 --- a/packages/website/ts/components/visual_order.tsx +++ b/packages/website/ts/components/visual_order.tsx @@ -3,10 +3,9 @@ import * as _ from 'lodash'; import * as React from 'react'; import { Party } from 'ts/components/ui/party'; import { AssetToken, Token, TokenByAddress } from 'ts/types'; +import { configs } from 'ts/utils/configs'; import { utils } from 'ts/utils/utils'; -const PRECISION = 5; - interface VisualOrderProps { makerAssetToken: AssetToken; takerAssetToken: AssetToken; @@ -67,7 +66,7 @@ export class VisualOrder extends React.Component<VisualOrderProps, VisualOrderSt const unitAmount = ZeroEx.toUnitAmount(assetToken.amount, token.decimals); return ( <div style={{ fontSize: 13 }}> - {unitAmount.toNumber().toFixed(PRECISION)} {token.symbol} + {unitAmount.toNumber().toFixed(configs.AMOUNT_DISPLAY_PRECSION)} {token.symbol} </div> ); } diff --git a/packages/website/ts/containers/portal.ts b/packages/website/ts/containers/portal.ts index befa16bdb..725564ead 100644 --- a/packages/website/ts/containers/portal.ts +++ b/packages/website/ts/containers/portal.ts @@ -21,7 +21,7 @@ interface ConnectedState { providerType: ProviderType; tokenByAddress: TokenByAddress; lastForceTokenStateRefetch: number; - userEtherBalance: BigNumber; + userEtherBalanceInWei: BigNumber; screenWidth: ScreenWidths; shouldBlockchainErrDialogBeOpen: boolean; userAddress: string; @@ -72,7 +72,7 @@ const mapStateToProps = (state: State, ownProps: PortalComponentAllProps): Conne tokenByAddress: state.tokenByAddress, lastForceTokenStateRefetch: state.lastForceTokenStateRefetch, userAddress: state.userAddress, - userEtherBalance: state.userEtherBalance, + userEtherBalanceInWei: state.userEtherBalanceInWei, userSuppliedOrderCache: state.userSuppliedOrderCache, flashMessage: state.flashMessage, translate: state.translate, diff --git a/packages/website/ts/redux/dispatcher.ts b/packages/website/ts/redux/dispatcher.ts index 5c40ded2c..13e9a10cc 100644 --- a/packages/website/ts/redux/dispatcher.ts +++ b/packages/website/ts/redux/dispatcher.ts @@ -86,7 +86,7 @@ export class Dispatcher { type: ActionTypes.UpdateOrderTakerAddress, }); } - public updateUserAddress(address: string) { + public updateUserAddress(address?: string) { this._dispatch({ data: address, type: ActionTypes.UpdateUserAddress, @@ -125,14 +125,14 @@ export class Dispatcher { public batchDispatch( tokenByAddress: TokenByAddress, networkId: number, - userAddress: string, + userAddressIfExists: string | undefined, sideToAssetToken: SideToAssetToken, ) { this._dispatch({ data: { tokenByAddress, networkId, - userAddress, + userAddressIfExists, sideToAssetToken, }, type: ActionTypes.BatchDispatch, @@ -155,7 +155,7 @@ export class Dispatcher { type: ActionTypes.UpdateOrderECSignature, }); } - public updateUserEtherBalance(balance: BigNumber) { + public updateUserWeiBalance(balance: BigNumber) { this._dispatch({ data: balance, type: ActionTypes.UpdateUserEtherBalance, diff --git a/packages/website/ts/redux/reducer.ts b/packages/website/ts/redux/reducer.ts index 1f489db85..a628f65c2 100644 --- a/packages/website/ts/redux/reducer.ts +++ b/packages/website/ts/redux/reducer.ts @@ -38,7 +38,7 @@ export interface State { tokenByAddress: TokenByAddress; lastForceTokenStateRefetch: number; userAddress: string; - userEtherBalance: BigNumber; + userEtherBalanceInWei: BigNumber; // Note: cache of supplied orderJSON in fill order step. Do not use for anything else. userSuppliedOrderCache: Order; @@ -77,7 +77,7 @@ const INITIAL_STATE: State = { tokenByAddress: {}, lastForceTokenStateRefetch: moment().unix(), userAddress: '', - userEtherBalance: new BigNumber(0), + userEtherBalanceInWei: new BigNumber(0), userSuppliedOrderCache: undefined, // Docs @@ -138,7 +138,7 @@ export function reducer(state: State = INITIAL_STATE, action: Action) { case ActionTypes.UpdateUserEtherBalance: { return { ...state, - userEtherBalance: action.data, + userEtherBalanceInWei: action.data, }; } @@ -184,10 +184,11 @@ export function reducer(state: State = INITIAL_STATE, action: Action) { } case ActionTypes.BatchDispatch: { + const userAddress = _.isUndefined(action.data.userAddressIfExists) ? '' : action.data.userAddressIfExists; return { ...state, networkId: action.data.networkId, - userAddress: action.data.userAddress, + userAddress, sideToAssetToken: action.data.sideToAssetToken, tokenByAddress: action.data.tokenByAddress, }; @@ -284,9 +285,10 @@ export function reducer(state: State = INITIAL_STATE, action: Action) { } case ActionTypes.UpdateUserAddress: { + const userAddress = _.isUndefined(action.data) ? '' : action.data; return { ...state, - userAddress: action.data, + userAddress, }; } diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts index f33b06c0a..f966cedbe 100644 --- a/packages/website/ts/utils/configs.ts +++ b/packages/website/ts/utils/configs.ts @@ -9,6 +9,7 @@ const isDevelopment = _.includes( const INFURA_API_KEY = 'T5WSC8cautR4KXyYgsRs'; export const configs = { + AMOUNT_DISPLAY_PRECSION: 5, BACKEND_BASE_URL: 'https://website-api.0xproject.com', BASE_URL, BITLY_ACCESS_TOKEN: 'ffc4c1a31e5143848fb7c523b39f91b9b213d208', diff --git a/packages/website/ts/web3_wrapper.ts b/packages/website/ts/web3_wrapper.ts deleted file mode 100644 index 9d8d771af..000000000 --- a/packages/website/ts/web3_wrapper.ts +++ /dev/null @@ -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); - } - } -} |