diff options
Diffstat (limited to 'packages/website/ts/blockchain.ts')
-rw-r--r-- | packages/website/ts/blockchain.ts | 177 |
1 files changed, 111 insertions, 66 deletions
diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index 56a8c99f4..c92023541 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'; @@ -48,7 +50,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'); @@ -70,8 +71,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; @@ -116,7 +117,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 @@ -138,8 +138,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(); } @@ -190,14 +190,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 = { @@ -212,10 +212,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() { @@ -225,21 +230,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; @@ -252,7 +263,7 @@ export class Blockchain { this._showFlashMessageIfLedger(); const txHash = await this._zeroEx.token.setProxyAllowanceAsync( token.address, - this._userAddress, + this._userAddressIfExists, amountInBaseUnits, { gasPrice: this._defaultGasPrice, @@ -261,10 +272,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, { @@ -306,6 +320,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; @@ -315,7 +330,7 @@ export class Blockchain { signedOrder, fillTakerTokenAmount, shouldThrowOnInsufficientBalanceOrAllowance, - this._userAddress, + this._userAddressIfExists, { gasPrice: this._defaultGasPrice, }, @@ -348,7 +363,7 @@ export class Blockchain { return unavailableTakerAmount; } public getExchangeContractAddressIfExists() { - return this._exchangeAddress; + return this._zeroEx.exchange.getContractAddress(); } public async validateFillOrderThrowIfInvalidAsync( signedOrder: SignedOrder, @@ -374,12 +389,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); @@ -398,7 +416,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)) { @@ -428,22 +446,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> { @@ -451,9 +474,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) { @@ -461,21 +489,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]; } @@ -488,10 +524,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() { @@ -504,7 +540,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 => { @@ -512,9 +550,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) => { @@ -540,7 +580,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); } @@ -561,7 +601,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 @@ -606,16 +646,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, @@ -631,7 +673,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>) { @@ -655,10 +697,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 @@ -669,7 +713,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 { @@ -740,20 +784,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 @@ -763,7 +808,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]; |