diff options
Diffstat (limited to 'packages/website/ts/blockchain.ts')
-rw-r--r-- | packages/website/ts/blockchain.ts | 953 |
1 files changed, 0 insertions, 953 deletions
diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts deleted file mode 100644 index ea5a59340..000000000 --- a/packages/website/ts/blockchain.ts +++ /dev/null @@ -1,953 +0,0 @@ -import { - BlockRange, - ContractWrappers, - DecodedLogEvent, - ExchangeCancelEventArgs, - ExchangeEventArgs, - ExchangeEvents, - ExchangeFillEventArgs, - IndexedFilterValues, -} from '@0x/contract-wrappers'; -import { assetDataUtils, orderHashUtils, signatureUtils } from '@0x/order-utils'; -import { EtherscanLinkSuffixes, utils as sharedUtils } from '@0x/react-shared'; -import { - ledgerEthereumBrowserClientFactoryAsync, - LedgerSubprovider, - MetamaskSubprovider, - RedundantSubprovider, - RPCSubprovider, - SignerSubprovider, - Web3ProviderEngine, -} from '@0x/subproviders'; -import { SignedOrder, Token as ZeroExToken } from '@0x/types'; -import { BigNumber, intervalUtils, logUtils } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import { BlockParam, LogWithDecodedArgs, Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; -import * as _ from 'lodash'; -import * as moment from 'moment'; -import * as React from 'react'; -import contract from 'truffle-contract'; -import { BlockchainWatcher } from 'ts/blockchain_watcher'; -import { AssetSendCompleted } from 'ts/components/flash_messages/asset_send_completed'; -import { TransactionSubmitted } from 'ts/components/flash_messages/transaction_submitted'; -import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage'; -import { tradeHistoryStorage } from 'ts/local_storage/trade_history_storage'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { - BlockchainCallErrs, - BlockchainErrs, - ContractInstance, - Fill, - InjectedProvider, - InjectedProviderObservable, - InjectedProviderUpdate, - Providers, - ProviderType, - Side, - SideToAssetToken, - Token, - TokenByAddress, -} from 'ts/types'; -import { backendClient } from 'ts/utils/backend_client'; -import { configs } from 'ts/utils/configs'; -import { constants } from 'ts/utils/constants'; -import { errorReporter } from 'ts/utils/error_reporter'; -import { fakeTokenRegistry } from 'ts/utils/fake_token_registry'; -import { tokenAddressOverrides } from 'ts/utils/token_address_overrides'; -import { utils } from 'ts/utils/utils'; -import FilterSubprovider from 'web3-provider-engine/subproviders/filters'; - -import MintableArtifacts from '../contracts/Mintable.json'; - -const BLOCK_NUMBER_BACK_TRACK = 50; -const GWEI_IN_WEI = 1000000000; - -const providerToName: { [provider: string]: string } = { - [Providers.Metamask]: constants.PROVIDER_NAME_METAMASK, - [Providers.Parity]: constants.PROVIDER_NAME_PARITY_SIGNER, - [Providers.Mist]: constants.PROVIDER_NAME_MIST, - [Providers.CoinbaseWallet]: constants.PROVIDER_NAME_COINBASE_WALLET, - [Providers.Cipher]: constants.PROVIDER_NAME_CIPHER, -}; - -export class Blockchain { - public networkId: number; - public nodeVersion: string; - private _contractWrappers: ContractWrappers; - private readonly _dispatcher: Dispatcher; - private _web3Wrapper?: Web3Wrapper; - private _blockchainWatcher?: BlockchainWatcher; - private _injectedProviderObservable?: InjectedProviderObservable; - private readonly _injectedProviderUpdateHandler: (update: InjectedProviderUpdate) => Promise<void>; - private _userAddressIfExists: string; - private _ledgerSubprovider: LedgerSubprovider; - private _defaultGasPrice: BigNumber; - private _watchGasPriceIntervalId: NodeJS.Timer; - private _injectedProviderIfExists?: InjectedProvider; - private static _getNameGivenProvider(provider: Provider): string { - const providerType = utils.getProviderType(provider); - const providerNameIfExists = providerToName[providerType]; - if (_.isUndefined(providerNameIfExists)) { - return constants.PROVIDER_NAME_GENERIC; - } - return providerNameIfExists; - } - private static async _getProviderAsync( - injectedProviderIfExists?: InjectedProvider, - networkIdIfExists?: number, - shouldUserLedgerProvider: boolean = false, - ): Promise<[Provider, LedgerSubprovider | undefined]> { - const doesInjectedProviderExist = !_.isUndefined(injectedProviderIfExists); - const isNetworkIdAvailable = !_.isUndefined(networkIdIfExists); - const publicNodeUrlsIfExistsForNetworkId = configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists]; - const isPublicNodeAvailableForNetworkId = !_.isUndefined(publicNodeUrlsIfExistsForNetworkId); - - if (shouldUserLedgerProvider && isNetworkIdAvailable) { - const isU2FSupported = await utils.isU2FSupportedAsync(); - if (!isU2FSupported) { - throw new Error('Cannot update providerType to LEDGER without U2F support'); - } - const provider = new Web3ProviderEngine(); - const ledgerWalletConfigs = { - networkId: networkIdIfExists, - ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync, - }; - const ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs); - provider.addProvider(ledgerSubprovider); - provider.addProvider(new FilterSubprovider()); - const rpcSubproviders = _.map(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists], publicNodeUrl => { - return new RPCSubprovider(publicNodeUrl); - }); - provider.addProvider(new RedundantSubprovider(rpcSubproviders)); - provider.start(); - return [provider, ledgerSubprovider]; - } else if (doesInjectedProviderExist && isPublicNodeAvailableForNetworkId) { - // We catch all requests involving a users account and send it to the injectedWeb3 - // instance. All other requests go to the public hosted node. - const provider = new Web3ProviderEngine(); - const providerName = this._getNameGivenProvider(injectedProviderIfExists); - // Wrap Metamask in a compatability wrapper MetamaskSubprovider (to handle inconsistencies) - const signerSubprovider = - providerName === constants.PROVIDER_NAME_METAMASK - ? new MetamaskSubprovider(injectedProviderIfExists) - : new SignerSubprovider(injectedProviderIfExists); - provider.addProvider(signerSubprovider); - provider.addProvider(new FilterSubprovider()); - const rpcSubproviders = _.map(publicNodeUrlsIfExistsForNetworkId, publicNodeUrl => { - return new RPCSubprovider(publicNodeUrl); - }); - provider.addProvider(new RedundantSubprovider(rpcSubproviders)); - provider.start(); - return [provider, undefined]; - } else if (doesInjectedProviderExist) { - // Since no public node for this network, all requests go to injectedWeb3 instance - return [injectedProviderIfExists, undefined]; - } else { - // If no injectedWeb3 instance, all requests fallback to our public hosted mainnet/testnet node - // We do this so that users can still browse the 0x Portal DApp even if they do not have web3 - // injected into their browser. - const provider = new Web3ProviderEngine(); - provider.addProvider(new FilterSubprovider()); - const networkId = constants.NETWORK_ID_MAINNET; - const rpcSubproviders = _.map(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId], publicNodeUrl => { - return new RPCSubprovider(publicNodeUrl); - }); - provider.addProvider(new RedundantSubprovider(rpcSubproviders)); - provider.start(); - return [provider, undefined]; - } - } - constructor(dispatcher: Dispatcher) { - this._dispatcher = dispatcher; - const defaultGasPrice = GWEI_IN_WEI * 40; - this._defaultGasPrice = new BigNumber(defaultGasPrice); - // We need a unique reference to this function so we can use it to unsubcribe. - this._injectedProviderUpdateHandler = this._handleInjectedProviderUpdateAsync.bind(this); - // tslint:disable-next-line:no-floating-promises - this._onPageLoadInitFireAndForgetAsync(); - } - public async networkIdUpdatedFireAndForgetAsync(newNetworkId: number): Promise<void> { - const isConnected = !_.isUndefined(newNetworkId); - if (!isConnected) { - this.networkId = newNetworkId; - this._dispatcher.encounteredBlockchainError(BlockchainErrs.DisconnectedFromEthereumNode); - this._dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - } else if (this.networkId !== newNetworkId) { - this.networkId = newNetworkId; - this._dispatcher.encounteredBlockchainError(BlockchainErrs.NoError); - await this.fetchTokenInformationAsync(); - await this._rehydrateStoreWithContractEventsAsync(); - } - } - public async userAddressUpdatedFireAndForgetAsync(newUserAddress: string): Promise<void> { - if (this._userAddressIfExists !== newUserAddress) { - this._userAddressIfExists = newUserAddress; - await this.fetchTokenInformationAsync(); - await this._rehydrateStoreWithContractEventsAsync(); - } - } - public async nodeVersionUpdatedFireAndForgetAsync(nodeVersion: string): Promise<void> { - if (this.nodeVersion !== nodeVersion) { - this.nodeVersion = nodeVersion; - } - } - public async isAddressInTokenRegistryAsync(tokenAddress: string): Promise<boolean> { - const tokens = fakeTokenRegistry[this.networkId]; - const tokenIfExists = _.find(tokens, { address: tokenAddress }); - - // HACK: Override token addresses on testnets - const tokenSymbolToAddressOverrides = tokenAddressOverrides[this.networkId]; - let isTokenAddressInOverrides = false; - if (!_.isUndefined(tokenSymbolToAddressOverrides)) { - isTokenAddressInOverrides = _.values(tokenSymbolToAddressOverrides).includes(tokenAddress); - } - return !_.isUndefined(tokenIfExists) || isTokenAddressInOverrides; - } - public getLedgerDerivationPathIfExists(): string { - if (_.isUndefined(this._ledgerSubprovider)) { - return undefined; - } - const path = this._ledgerSubprovider.getPath(); - return path; - } - public updateLedgerDerivationPathIfExists(path: string): void { - if (_.isUndefined(this._ledgerSubprovider)) { - return; // noop - } - this._ledgerSubprovider.setPath(path); - } - public async updateProviderToLedgerAsync(networkId: number): Promise<void> { - const shouldPollUserAddress = false; - const shouldUserLedgerProvider = true; - await this._resetOrInitializeAsync(networkId, shouldPollUserAddress, shouldUserLedgerProvider); - } - public async updateProviderToInjectedAsync(): Promise<void> { - const shouldPollUserAddress = true; - const shouldUserLedgerProvider = false; - this._dispatcher.updateBlockchainIsLoaded(false); - // We don't want to be out of sync with the network the injected provider declares. - const networkId = await this._getInjectedProviderNetworkIdIfExistsAsync(); - await this._resetOrInitializeAsync(networkId, shouldPollUserAddress, shouldUserLedgerProvider); - } - public async setProxyAllowanceAsync(token: Token, amountInBaseUnits: BigNumber): Promise<void> { - utils.assert(this.isValidAddress(token.address), BlockchainCallErrs.TokenAddressIsInvalid); - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - utils.assert(!_.isUndefined(this._contractWrappers), 'Contract Wrappers must be instantiated.'); - - this._showFlashMessageIfLedger(); - const txHash = await this._contractWrappers.erc20Token.setProxyAllowanceAsync( - token.address, - this._userAddressIfExists, - amountInBaseUnits, - { - gasPrice: this._defaultGasPrice, - }, - ); - await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); - } - public async sendAsync(toAddress: string, amountInBaseUnits: BigNumber): Promise<void> { - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - const transaction = { - from: this._userAddressIfExists, - to: toAddress, - value: amountInBaseUnits, - gasPrice: this._defaultGasPrice, - }; - this._showFlashMessageIfLedger(); - const txHash = await this._web3Wrapper.sendTransactionAsync(transaction); - await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); - const etherScanLinkIfExists = sharedUtils.getEtherScanLinkIfExists( - txHash, - this.networkId, - EtherscanLinkSuffixes.Tx, - ); - this._dispatcher.showFlashMessage( - React.createElement(AssetSendCompleted, { - etherScanLinkIfExists, - toAddress, - amountInBaseUnits, - decimals: constants.DECIMAL_PLACES_ETH, - symbol: constants.ETHER_SYMBOL, - }), - ); - } - public async transferAsync(token: Token, toAddress: string, amountInBaseUnits: BigNumber): Promise<void> { - utils.assert(!_.isUndefined(this._contractWrappers), 'ContractWrappers must be instantiated.'); - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - - this._showFlashMessageIfLedger(); - const txHash = await this._contractWrappers.erc20Token.transferAsync( - token.address, - this._userAddressIfExists, - toAddress, - amountInBaseUnits, - { - gasPrice: this._defaultGasPrice, - }, - ); - await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); - const etherScanLinkIfExists = sharedUtils.getEtherScanLinkIfExists( - txHash, - this.networkId, - EtherscanLinkSuffixes.Tx, - ); - this._dispatcher.showFlashMessage( - React.createElement(AssetSendCompleted, { - etherScanLinkIfExists, - toAddress, - amountInBaseUnits, - decimals: token.decimals, - symbol: token.symbol, - }), - ); - } - public async fillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber): Promise<BigNumber> { - utils.assert(!_.isUndefined(this._contractWrappers), 'ContractWrappers must be instantiated.'); - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - this._showFlashMessageIfLedger(); - const txHash = await this._contractWrappers.exchange.fillOrderAsync( - signedOrder, - fillTakerTokenAmount, - this._userAddressIfExists, - { - gasPrice: this._defaultGasPrice, - }, - ); - const receipt = await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); - const logs: Array<LogWithDecodedArgs<ExchangeEventArgs>> = receipt.logs as any; - const logFill = _.find(logs, { event: ExchangeEvents.Fill }); - const args = (logFill.args as any) as ExchangeFillEventArgs; - const takerAssetFilledAmount = args.takerAssetFilledAmount; - return takerAssetFilledAmount; - } - public async cancelOrderAsync(signedOrder: SignedOrder): Promise<string> { - this._showFlashMessageIfLedger(); - const txHash = await this._contractWrappers.exchange.cancelOrderAsync(signedOrder, { - gasPrice: this._defaultGasPrice, - }); - const receipt = await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); - const logs: Array<LogWithDecodedArgs<ExchangeEventArgs>> = receipt.logs as any; - const logCancel = _.find(logs, { event: ExchangeEvents.Cancel }); - const args = (logCancel.args as any) as ExchangeCancelEventArgs; - const cancelledOrderHash = args.orderHash; - return cancelledOrderHash; - } - public async getUnavailableTakerAmountAsync(orderHash: string): Promise<BigNumber> { - utils.assert(orderHashUtils.isValidOrderHash(orderHash), 'Must be valid orderHash'); - utils.assert(!_.isUndefined(this._contractWrappers), 'ContractWrappers must be instantiated.'); - const unavailableTakerAmount = await this._contractWrappers.exchange.getFilledTakerAssetAmountAsync(orderHash); - return unavailableTakerAmount; - } - public getExchangeContractAddressIfExists(): string | undefined { - return this._contractWrappers.exchange.address; - } - public async validateFillOrderThrowIfInvalidAsync( - signedOrder: SignedOrder, - fillTakerTokenAmount: BigNumber, - takerAddress: string, - ): Promise<void> { - await this._contractWrappers.exchange.validateFillOrderThrowIfInvalidAsync( - signedOrder, - fillTakerTokenAmount, - takerAddress, - ); - } - public isValidAddress(address: string): boolean { - const lowercaseAddress = address.toLowerCase(); - return Web3Wrapper.isAddress(lowercaseAddress); - } - public async isValidSignatureAsync(data: string, signature: string, signerAddress: string): Promise<boolean> { - const result = await signatureUtils.isValidSignatureAsync( - this._contractWrappers.getProvider(), - data, - signature, - signerAddress, - ); - return result; - } - public async pollTokenBalanceAsync(token: Token): Promise<BigNumber> { - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - - 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._userAddressIfExists, - token.address, - ); - if (!balance.eq(currBalance)) { - intervalUtils.clearAsyncExcludingInterval(tokenPollInterval); - resolve(balance); - } - }, - 5000, - (err: Error) => { - logUtils.log(`Polling tokenBalance failed: ${err}`); - intervalUtils.clearAsyncExcludingInterval(tokenPollInterval); - reject(err); - }, - ); - }); - - return newTokenBalancePromise; - } - public async signOrderHashAsync(orderHash: string): Promise<string> { - utils.assert(!_.isUndefined(this._contractWrappers), 'ContractWrappers must be instantiated.'); - 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)) { - throw new Error('Tried to send a sign request but user has no associated addresses'); - } - this._showFlashMessageIfLedger(); - const provider = this._contractWrappers.getProvider(); - const ecSignatureString = await signatureUtils.ecSignHashAsync(provider, orderHash, makerAddress); - this._dispatcher.updateSignature(ecSignatureString); - return ecSignatureString; - } - public async mintTestTokensAsync(token: Token): Promise<void> { - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - - const mintableContract = await this._instantiateContractIfExistsAsync(MintableArtifacts, token.address); - this._showFlashMessageIfLedger(); - await mintableContract.mint(constants.MINT_AMOUNT, { - from: this._userAddressIfExists, - gasPrice: this._defaultGasPrice, - }); - } - 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._contractWrappers), 'ContractWrappers must be instantiated.'); - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - - this._showFlashMessageIfLedger(); - const txHash = await this._contractWrappers.etherToken.depositAsync( - etherTokenAddress, - amount, - this._userAddressIfExists, - { - gasPrice: this._defaultGasPrice, - }, - ); - await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); - } - public async convertWrappedEthTokensToEthAsync(etherTokenAddress: string, amount: BigNumber): Promise<void> { - utils.assert(!_.isUndefined(this._contractWrappers), 'ContractWrappers must be instantiated.'); - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - - this._showFlashMessageIfLedger(); - const txHash = await this._contractWrappers.etherToken.withdrawAsync( - etherTokenAddress, - amount, - this._userAddressIfExists, - { - gasPrice: this._defaultGasPrice, - }, - ); - await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); - } - public async doesContractExistAtAddressAsync(address: string): Promise<boolean> { - const doesContractExist = await this._web3Wrapper.doesContractExistAtAddressAsync(address); - return doesContractExist; - } - public async getCurrentUserTokenBalanceAndAllowanceAsync(tokenAddress: string): Promise<BigNumber[]> { - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - - const tokenBalanceAndAllowance = await this.getTokenBalanceAndAllowanceAsync( - this._userAddressIfExists, - tokenAddress, - ); - return tokenBalanceAndAllowance; - } - public async getTokenBalanceAndAllowanceAsync( - ownerAddressIfExists: string, - tokenAddress: string, - ): Promise<[BigNumber, BigNumber]> { - utils.assert(!_.isUndefined(this._contractWrappers), 'ContractWrappers must be instantiated.'); - - 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, allowance] = await Promise.all([ - this._contractWrappers.erc20Token.getBalanceAsync(tokenAddress, ownerAddressIfExists), - this._contractWrappers.erc20Token.getProxyAllowanceAsync(tokenAddress, ownerAddressIfExists), - ]); - } - return [balance, allowance]; - } - public async getUserAccountsAsync(): Promise<string[]> { - utils.assert(!_.isUndefined(this._contractWrappers), 'ContractWrappers must be instantiated.'); - const provider = this._contractWrappers.getProvider(); - const web3Wrapper = new Web3Wrapper(provider); - const userAccountsIfExists = await web3Wrapper.getAvailableAddressesAsync(); - return userAccountsIfExists; - } - // HACK: When a user is using a Ledger, we simply dispatch the selected userAddress, which - // 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): void { - this._blockchainWatcher.updatePrevUserAddress(newUserAddress); - } - public destroy(): void { - this._blockchainWatcher.destroy(); - if (this._injectedProviderObservable) { - this._injectedProviderObservable.unsubscribe(this._injectedProviderUpdateHandler); - } - this._stopWatchingExchangeLogFillEvents(); - this._stopWatchingGasPrice(); - } - public async fetchTokenInformationAsync(): Promise<void> { - utils.assert( - !_.isUndefined(this.networkId), - 'Cannot call fetchTokenInformationAsync if disconnected from Ethereum node', - ); - - this._dispatcher.updateBlockchainIsLoaded(false); - - const tokenRegistryTokensByAddress = await this._getTokenRegistryTokensByAddressAsync(); - - const trackedTokensByAddress = _.isUndefined(this._userAddressIfExists) - ? {} - : trackedTokenStorage.getTrackedTokensByAddress(this._userAddressIfExists, this.networkId); - const tokenRegistryTokens = _.values(tokenRegistryTokensByAddress); - const tokenRegistryTokenSymbols = _.map(tokenRegistryTokens, t => t.symbol); - const defaultTrackedTokensInRegistry = _.intersection( - tokenRegistryTokenSymbols, - configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, - ); - const currentTimestamp = moment().unix(); - if (defaultTrackedTokensInRegistry.length !== configs.DEFAULT_TRACKED_TOKEN_SYMBOLS.length) { - this._dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - this._dispatcher.encounteredBlockchainError(BlockchainErrs.DefaultTokensNotInTokenRegistry); - const err = new Error( - `Default tracked tokens (${JSON.stringify( - configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, - )}) not found in tokenRegistry: ${JSON.stringify(tokenRegistryTokens)}`, - ); - errorReporter.report(err); - return; - } - if (_.isEmpty(trackedTokensByAddress)) { - _.each(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, symbol => { - const token = _.find(tokenRegistryTokens, t => t.symbol === symbol); - token.trackedTimestamp = currentTimestamp; - trackedTokensByAddress[token.address] = token; - }); - if (!_.isUndefined(this._userAddressIfExists)) { - _.each(trackedTokensByAddress, (token: Token) => { - trackedTokenStorage.addTrackedTokenToUser(this._userAddressIfExists, this.networkId, token); - }); - } - } else { - // Properly set all tokenRegistry tokens `trackedTimestamp` if they are in the existing trackedTokens array - _.each(trackedTokensByAddress, (trackedToken: Token, address: string) => { - if (!_.isUndefined(tokenRegistryTokensByAddress[address])) { - tokenRegistryTokensByAddress[address].trackedTimestamp = trackedToken.trackedTimestamp; - } - }); - } - const allTokensByAddress = { - ...tokenRegistryTokensByAddress, - ...trackedTokensByAddress, - }; - const allTokens = _.values(allTokensByAddress); - const mostPopularTradingPairTokens: Token[] = [ - _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[0] }), - _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[1] }), - ]; - const sideToAssetToken: SideToAssetToken = { - [Side.Deposit]: { - address: mostPopularTradingPairTokens[0].address, - }, - [Side.Receive]: { - address: mostPopularTradingPairTokens[1].address, - }, - }; - this._dispatcher.batchDispatch(allTokensByAddress, this.networkId, this._userAddressIfExists, sideToAssetToken); - - this._dispatcher.updateBlockchainIsLoaded(true); - } - private async _getInjectedProviderIfExistsAsync(): Promise<InjectedProvider | undefined> { - if (!_.isUndefined(this._injectedProviderIfExists)) { - return this._injectedProviderIfExists; - } - let injectedProviderIfExists = (window as any).ethereum; - if (!_.isUndefined(injectedProviderIfExists)) { - if (!_.isUndefined(injectedProviderIfExists.enable)) { - try { - await injectedProviderIfExists.enable(); - } catch (err) { - errorReporter.report(err); - } - } - } else { - const injectedWeb3IfExists = (window as any).web3; - if (!_.isUndefined(injectedWeb3IfExists) && !_.isUndefined(injectedWeb3IfExists.currentProvider)) { - injectedProviderIfExists = injectedWeb3IfExists.currentProvider; - } else { - return undefined; - } - } - this._injectedProviderIfExists = injectedProviderIfExists; - return injectedProviderIfExists; - } - private async _getInjectedProviderNetworkIdIfExistsAsync(): Promise<number | undefined> { - // If the user has an injectedWeb3 instance that is disconnected from a backing - // Ethereum node, this call will throw. We need to handle this case gracefully - const injectedProviderIfExists = await this._getInjectedProviderIfExistsAsync(); - let networkIdIfExists: number; - if (!_.isUndefined(injectedProviderIfExists)) { - try { - const injectedWeb3Wrapper = new Web3Wrapper(injectedProviderIfExists); - networkIdIfExists = await injectedWeb3Wrapper.getNetworkIdAsync(); - } catch (err) { - // Ignore error and proceed with networkId undefined - } - } - return networkIdIfExists; - } - private async _showEtherScanLinkAndAwaitTransactionMinedAsync( - txHash: string, - ): Promise<TransactionReceiptWithDecodedLogs> { - const etherScanLinkIfExists = sharedUtils.getEtherScanLinkIfExists( - txHash, - this.networkId, - EtherscanLinkSuffixes.Tx, - ); - this._dispatcher.showFlashMessage( - React.createElement(TransactionSubmitted, { - etherScanLinkIfExists, - }), - ); - const provider = this._contractWrappers.getProvider(); - const web3Wrapper = new Web3Wrapper(provider); - const exchangeAbi = this._contractWrappers.exchange.abi; - web3Wrapper.abiDecoder.addABI(exchangeAbi); - const receipt = await web3Wrapper.awaitTransactionSuccessAsync(txHash); - return receipt; - } - private _doesUserAddressExist(): boolean { - return !_.isUndefined(this._userAddressIfExists); - } - private async _handleInjectedProviderUpdateAsync(update: InjectedProviderUpdate): Promise<void> { - if (update.networkVersion === 'loading' || !_.isUndefined(this._ledgerSubprovider)) { - return; - } - const updatedNetworkId = _.parseInt(update.networkVersion); - if (this.networkId === updatedNetworkId) { - return; - } - const shouldPollUserAddress = true; - const shouldUserLedgerProvider = false; - await this._resetOrInitializeAsync(updatedNetworkId, shouldPollUserAddress, shouldUserLedgerProvider); - } - private async _rehydrateStoreWithContractEventsAsync(): Promise<void> { - // Ensure we are only ever listening to one set of events - this._stopWatchingExchangeLogFillEvents(); - - if (!this._doesUserAddressExist()) { - return; // short-circuit - } - - if (!_.isUndefined(this._contractWrappers)) { - // Since we do not have an index on the `taker` address and want to show - // transactions where an account is either the `maker` or `taker`, we loop - // through all fill events, and filter/cache them client-side. - const filterIndexObj = {}; - await this._startListeningForExchangeLogFillEventsAsync(filterIndexObj); - } - } - private async _startListeningForExchangeLogFillEventsAsync(indexFilterValues: IndexedFilterValues): Promise<void> { - utils.assert(!_.isUndefined(this._contractWrappers), 'ContractWrappers must be instantiated.'); - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - - // Fetch historical logs - await this._fetchHistoricalExchangeLogFillEventsAsync(indexFilterValues); - - // Start a subscription for new logs - this._contractWrappers.exchange.subscribe( - ExchangeEvents.Fill, - indexFilterValues, - async (err: Error, decodedLogEvent: DecodedLogEvent<ExchangeFillEventArgs>) => { - if (err) { - // Note: it's not entirely clear from the documentation which - // errors will be thrown by `watch`. For now, let's log the error - // to rollbar and stop watching when one occurs - errorReporter.report(err); // fire and forget - return; - } else { - const decodedLog = decodedLogEvent.log; - if (!this._doesLogEventInvolveUser(decodedLog)) { - return; // We aren't interested in the fill event - } - this._updateLatestFillsBlockIfNeeded(decodedLog.blockNumber); - const fill = await this._convertDecodedLogToFillAsync(decodedLog); - if (decodedLogEvent.isRemoved) { - tradeHistoryStorage.removeFillFromUser(this._userAddressIfExists, this.networkId, fill); - } else { - tradeHistoryStorage.addFillToUser(this._userAddressIfExists, this.networkId, fill); - } - } - }, - ); - } - private async _fetchHistoricalExchangeLogFillEventsAsync(indexFilterValues: IndexedFilterValues): Promise<void> { - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - - const fromBlock = tradeHistoryStorage.getFillsLatestBlock(this._userAddressIfExists, this.networkId); - const blockRange: BlockRange = { - fromBlock, - toBlock: 'latest' as BlockParam, - }; - const decodedLogs = await this._contractWrappers.exchange.getLogsAsync<ExchangeFillEventArgs>( - ExchangeEvents.Fill, - blockRange, - indexFilterValues, - ); - for (const decodedLog of decodedLogs) { - if (!this._doesLogEventInvolveUser(decodedLog)) { - continue; // We aren't interested in the fill event - } - this._updateLatestFillsBlockIfNeeded(decodedLog.blockNumber); - const fill = await this._convertDecodedLogToFillAsync(decodedLog); - tradeHistoryStorage.addFillToUser(this._userAddressIfExists, this.networkId, fill); - } - } - private async _convertDecodedLogToFillAsync(decodedLog: LogWithDecodedArgs<ExchangeFillEventArgs>): Promise<Fill> { - const args = decodedLog.args; - const blockTimestamp = await this._web3Wrapper.getBlockTimestampAsync(decodedLog.blockHash); - const makerToken = assetDataUtils.decodeERC20AssetData(args.makerAssetData).tokenAddress; - const takerToken = assetDataUtils.decodeERC20AssetData(args.takerAssetData).tokenAddress; - const fill = { - filledTakerTokenAmount: args.takerAssetFilledAmount, - filledMakerTokenAmount: args.makerAssetFilledAmount, - logIndex: decodedLog.logIndex, - maker: args.makerAddress, - orderHash: args.orderHash, - taker: args.takerAddress, - makerToken, - takerToken, - paidMakerFee: args.makerFeePaid, - paidTakerFee: args.takerFeePaid, - transactionHash: decodedLog.transactionHash, - blockTimestamp, - }; - return fill; - } - private _doesLogEventInvolveUser(decodedLog: LogWithDecodedArgs<ExchangeFillEventArgs>): boolean { - const args = decodedLog.args; - const isUserMakerOrTaker = args.maker === this._userAddressIfExists || args.taker === this._userAddressIfExists; - return isUserMakerOrTaker; - } - private _updateLatestFillsBlockIfNeeded(blockNumber: number): void { - 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 - // and lowering the cache blockNumber fixes the issue. As a quick fix for now, simply - // set the cached blockNumber 50 below the one returned. This way, upon refreshing, a user - // would still attempt to re-fetch events from the previous 50 blocks, but won't need to - // re-fetch all events in all blocks. - // 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._userAddressIfExists, this.networkId, blockNumberToSet); - } - } - private _stopWatchingExchangeLogFillEvents(): void { - this._contractWrappers.exchange.unsubscribeAll(); - } - private async _getTokenRegistryTokensByAddressAsync(): Promise<TokenByAddress> { - let tokenRegistryTokens; - if (this.networkId === constants.NETWORK_ID_MAINNET) { - tokenRegistryTokens = await backendClient.getTokenInfosAsync(); - } else { - tokenRegistryTokens = fakeTokenRegistry[this.networkId]; - const tokenSymbolToAddressOverrides = tokenAddressOverrides[this.networkId]; - if (!_.isUndefined(tokenAddressOverrides)) { - // HACK: Override token addresses on testnets - tokenRegistryTokens = _.map(tokenRegistryTokens, (token: ZeroExToken) => { - const overrideIfExists = tokenSymbolToAddressOverrides[token.symbol]; - if (!_.isUndefined(overrideIfExists)) { - return { - ...token, - address: overrideIfExists, - }; - } - return token; - }); - } - } - const tokenByAddress: TokenByAddress = {}; - _.each(tokenRegistryTokens, (t: ZeroExToken) => { - // HACK: For now we have a hard-coded list of iconUrls for the dummyTokens - // TODO: Refactor this out and pull the iconUrl directly from the TokenRegistry - const iconUrl = utils.getTokenIconUrl(t.symbol); - const token: Token = { - iconUrl, - address: t.address, - name: t.name, - symbol: t.symbol, - decimals: t.decimals, - trackedTimestamp: undefined, - isRegistered: true, - }; - tokenByAddress[token.address] = token; - }); - return tokenByAddress; - } - private async _onPageLoadInitFireAndForgetAsync(): Promise<void> { - await utils.onPageLoadPromise; // wait for page to load - const networkIdIfExists = await this._getInjectedProviderNetworkIdIfExistsAsync(); - this.networkId = !_.isUndefined(networkIdIfExists) ? networkIdIfExists : constants.NETWORK_ID_MAINNET; - const injectedProviderIfExists = await this._getInjectedProviderIfExistsAsync(); - if (!_.isUndefined(injectedProviderIfExists)) { - const injectedProviderObservable = injectedProviderIfExists.publicConfigStore; - if (!_.isUndefined(injectedProviderObservable) && _.isUndefined(this._injectedProviderObservable)) { - this._injectedProviderObservable = injectedProviderObservable; - this._injectedProviderObservable.subscribe(this._injectedProviderUpdateHandler); - } - } - this._updateProviderName(injectedProviderIfExists); - const shouldPollUserAddress = true; - const shouldUseLedgerProvider = false; - this._startWatchingGasPrice(); - await this._resetOrInitializeAsync(this.networkId, shouldPollUserAddress, shouldUseLedgerProvider); - } - private _startWatchingGasPrice(): void { - if (!_.isUndefined(this._watchGasPriceIntervalId)) { - return; // we are already watching - } - const oneMinuteInMs = 60000; - // tslint:disable-next-line:no-floating-promises - this._updateDefaultGasPriceAsync(); - this._watchGasPriceIntervalId = intervalUtils.setAsyncExcludingInterval( - this._updateDefaultGasPriceAsync.bind(this), - oneMinuteInMs, - (err: Error) => { - logUtils.log(`Watching gas price failed: ${err.stack}`); - this._stopWatchingGasPrice(); - }, - ); - } - private _stopWatchingGasPrice(): void { - if (!_.isUndefined(this._watchGasPriceIntervalId)) { - intervalUtils.clearAsyncExcludingInterval(this._watchGasPriceIntervalId); - } - } - private async _resetOrInitializeAsync( - networkId: number, - shouldPollUserAddress: boolean = false, - shouldUserLedgerProvider: boolean = false, - ): Promise<void> { - if (!shouldUserLedgerProvider) { - this._dispatcher.updateBlockchainIsLoaded(false); - } - this._dispatcher.updateUserWeiBalance(undefined); - this.networkId = networkId; - const injectedProviderIfExists = await this._getInjectedProviderIfExistsAsync(); - const [provider, ledgerSubproviderIfExists] = await Blockchain._getProviderAsync( - injectedProviderIfExists, - networkId, - shouldUserLedgerProvider, - ); - this._web3Wrapper = new Web3Wrapper(provider); - this.networkId = await this._web3Wrapper.getNetworkIdAsync(); - if (!_.isUndefined(this._contractWrappers)) { - this._contractWrappers.unsubscribeAll(); - } - const contractWrappersConfig = { - networkId, - }; - this._contractWrappers = new ContractWrappers(provider, contractWrappersConfig); - if (!_.isUndefined(this._blockchainWatcher)) { - this._blockchainWatcher.destroy(); - } - this._blockchainWatcher = new BlockchainWatcher(this._dispatcher, this._web3Wrapper, shouldPollUserAddress); - if (shouldUserLedgerProvider && !_.isUndefined(ledgerSubproviderIfExists)) { - delete this._userAddressIfExists; - this._ledgerSubprovider = ledgerSubproviderIfExists; - this._dispatcher.updateUserAddress(undefined); - this._dispatcher.updateProviderType(ProviderType.Ledger); - } else { - delete this._ledgerSubprovider; - const userAddresses = await this._web3Wrapper.getAvailableAddressesAsync(); - this._userAddressIfExists = userAddresses[0]; - this._dispatcher.updateUserAddress(this._userAddressIfExists); - if (!_.isUndefined(injectedProviderIfExists)) { - this._dispatcher.updateProviderType(ProviderType.Injected); - } - await this.fetchTokenInformationAsync(); - } - await this._blockchainWatcher.startEmittingUserBalanceStateAsync(); - this._dispatcher.updateNetworkId(networkId); - await this._rehydrateStoreWithContractEventsAsync(); - } - private _updateProviderName(injectedProviderIfExists?: InjectedProvider): void { - const doesInjectedProviderExist = !_.isUndefined(injectedProviderIfExists); - const providerName = doesInjectedProviderExist - ? Blockchain._getNameGivenProvider(injectedProviderIfExists) - : constants.PROVIDER_NAME_PUBLIC; - this._dispatcher.updateInjectedProviderName(providerName); - } - private async _instantiateContractIfExistsAsync(artifact: any, address?: string): Promise<ContractInstance> { - const c = await contract(artifact); - const providerObj = this._web3Wrapper.getProvider(); - c.setProvider(providerObj); - - const artifactNetworkConfigs = artifact.networks[this.networkId]; - let contractAddress; - if (!_.isUndefined(address)) { - contractAddress = address; - } else if (!_.isUndefined(artifactNetworkConfigs)) { - contractAddress = artifactNetworkConfigs.address; - } - - if (!_.isUndefined(contractAddress)) { - const doesContractExist = await this.doesContractExistAtAddressAsync(contractAddress); - if (!doesContractExist) { - logUtils.log(`Contract does not exist: ${artifact.contract_name} at ${contractAddress}`); - throw new Error(BlockchainCallErrs.ContractDoesNotExist); - } - } - - try { - const contractInstance = _.isUndefined(address) ? await c.deployed() : await c.at(address); - return contractInstance; - } catch (err) { - const errMsg = `${err}`; - logUtils.log(`Notice: Error encountered: ${err} ${err.stack}`); - if (_.includes(errMsg, 'not been deployed to detected network')) { - throw new Error(BlockchainCallErrs.ContractDoesNotExist); - } else { - errorReporter.report(err); - throw new Error(BlockchainCallErrs.UnhandledError); - } - } - } - private _showFlashMessageIfLedger(): void { - if (!_.isUndefined(this._ledgerSubprovider)) { - this._dispatcher.showFlashMessage('Confirm the transaction on your Ledger Nano S'); - } - } - private async _updateDefaultGasPriceAsync(): Promise<void> { - try { - const gasInfo = await backendClient.getGasInfoAsync(); - const gasPriceInGwei = new BigNumber(gasInfo.fast / 10); - const gasPriceInWei = gasPriceInGwei.multipliedBy(1000000000); - this._defaultGasPrice = gasPriceInWei; - } catch (err) { - return; - } - } -} // tslint:disable:max-file-line-count |