aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website/ts/blockchain.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/website/ts/blockchain.ts')
-rw-r--r--packages/website/ts/blockchain.ts953
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