diff options
Diffstat (limited to 'packages/website/ts/blockchain.ts')
-rw-r--r-- | packages/website/ts/blockchain.ts | 321 |
1 files changed, 166 insertions, 155 deletions
diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index b13c48a65..7851ea8ff 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -1,59 +1,60 @@ -import * as _ from 'lodash'; -import * as React from 'react'; import { - ZeroEx, - ZeroExError, + BlockParam, + DecodedLogEvent, ExchangeContractErrs, ExchangeContractEventArgs, ExchangeEvents, - SubscriptionOpts, IndexedFilterValues, - DecodedLogEvent, - BlockParam, - LogFillContractEventArgs, LogCancelContractEventArgs, - Token as ZeroExToken, + LogFillContractEventArgs, LogWithDecodedArgs, - TransactionReceiptWithDecodedLogs, - SignedOrder, Order, + SignedOrder, + SubscriptionOpts, + Token as ZeroExToken, + TransactionReceiptWithDecodedLogs, + ZeroEx, + ZeroExError, } from '0x.js'; import BigNumber from 'bignumber.js'; -import Web3 = require('web3'); +import compareVersions = require('compare-versions'); import promisify = require('es6-promisify'); +import ethUtil = require('ethereumjs-util'); import findVersions = require('find-versions'); -import compareVersions = require('compare-versions'); +import * as _ from 'lodash'; +import * as React from 'react'; import contract = require('truffle-contract'); -import ethUtil = require('ethereumjs-util'); -import ProviderEngine = require('web3-provider-engine'); -import FilterSubprovider = require('web3-provider-engine/subproviders/filters'); -import { TransactionSubmitted } from 'ts/components/flash_messages/transaction_submitted'; import {TokenSendCompleted} from 'ts/components/flash_messages/token_send_completed'; -import {RedundantRPCSubprovider} from 'ts/subproviders/redundant_rpc_subprovider'; +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 {InjectedWeb3SubProvider} from 'ts/subproviders/injected_web3_subprovider'; import {ledgerWalletSubproviderFactory} from 'ts/subproviders/ledger_wallet_subprovider_factory'; -import {Dispatcher} from 'ts/redux/dispatcher'; -import {utils} from 'ts/utils/utils'; -import {constants} from 'ts/utils/constants'; -import {configs} from 'ts/utils/configs'; +import {RedundantRPCSubprovider} from 'ts/subproviders/redundant_rpc_subprovider'; import { - BlockchainErrs, - Token, - SignatureData, - Side, - ContractResponse, BlockchainCallErrs, + BlockchainErrs, ContractInstance, - ProviderType, - LedgerWalletSubprovider, + ContractResponse, EtherscanLinkSuffixes, + LedgerWalletSubprovider, + ProviderType, + Side, + SignatureData, + Token, TokenByAddress, TokenStateByAddress, } from 'ts/types'; -import {Web3Wrapper} from 'ts/web3_wrapper'; +import {configs} from 'ts/utils/configs'; +import {constants} from 'ts/utils/constants'; import {errorReporter} from 'ts/utils/error_reporter'; -import {tradeHistoryStorage} from 'ts/local_storage/trade_history_storage'; -import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage'; +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'); + import * as MintableArtifacts from '../contracts/Mintable.json'; const ALLOWANCE_TO_ZERO_GAS_AMOUNT = 45730; @@ -72,9 +73,116 @@ export class Blockchain { private cachedProvider: Web3.Provider; private ledgerSubProvider: LedgerWalletSubprovider; private zrxPollIntervalId: number; + public static toHumanReadableErrorMsg(error: ZeroExError|ExchangeContractErrs, takerAddress: string): string { + const ZeroExErrorToHumanReadableError: {[error: string]: string} = { + [ZeroExError.TokenTransferProxyContractDoesNotExist]: 'Token transfer proxy contract does not exist', + [ZeroExError.TokenContractDoesNotExist]: 'Token contract does not exist', + [ZeroExError.ZRXContractDoesNotExist]: 'ZRX contract does not exist', + [ZeroExError.EtherTokenContractDoesNotExist]: 'Ether token contract does not exist', + [ZeroExError.ExchangeContractDoesNotExist]: 'Exchange contract does not exist', + [ZeroExError.UnhandledError]: ' Unhandled error occured', + [ZeroExError.UserHasNoAssociatedAddress]: 'User has no addresses available', + [ZeroExError.InvalidSignature]: 'Order signature is not valid', + [ZeroExError.ContractNotDeployedOnNetwork]: 'Contract is not deployed on the detected network', + [ZeroExError.InvalidJump]: 'Invalid jump occured while executing the transaction', + [ZeroExError.OutOfGas]: 'Transaction ran out of gas', + [ZeroExError.NoNetworkId]: 'No network id detected', + }; + const exchangeContractErrorToHumanReadableError: {[error: string]: string} = { + [ExchangeContractErrs.OrderFillExpired]: 'This order has expired', + [ExchangeContractErrs.OrderCancelExpired]: 'This order has expired', + [ExchangeContractErrs.OrderCancelAmountZero]: 'Order cancel amount can\'t be 0', + [ExchangeContractErrs.OrderAlreadyCancelledOrFilled]: + 'This order has already been completely filled or cancelled', + [ExchangeContractErrs.OrderFillAmountZero]: 'Order fill amount can\'t be 0', + [ExchangeContractErrs.OrderRemainingFillAmountZero]: + 'This order has already been completely filled or cancelled', + [ExchangeContractErrs.OrderFillRoundingError]: 'Rounding error will occur when filling this order', + [ExchangeContractErrs.InsufficientTakerBalance]: + 'Taker no longer has a sufficient balance to complete this order', + [ExchangeContractErrs.InsufficientTakerAllowance]: + 'Taker no longer has a sufficient allowance to complete this order', + [ExchangeContractErrs.InsufficientMakerBalance]: + 'Maker no longer has a sufficient balance to complete this order', + [ExchangeContractErrs.InsufficientMakerAllowance]: + 'Maker no longer has a sufficient allowance to complete this order', + [ExchangeContractErrs.InsufficientTakerFeeBalance]: 'Taker no longer has a sufficient balance to pay fees', + [ExchangeContractErrs.InsufficientTakerFeeAllowance]: + 'Taker no longer has a sufficient allowance to pay fees', + [ExchangeContractErrs.InsufficientMakerFeeBalance]: 'Maker no longer has a sufficient balance to pay fees', + [ExchangeContractErrs.InsufficientMakerFeeAllowance]: + 'Maker no longer has a sufficient allowance to pay fees', + [ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker]: + `This order can only be filled by ${takerAddress}`, + [ExchangeContractErrs.InsufficientRemainingFillAmount]: + 'Insufficient remaining fill amount', + }; + const humanReadableErrorMsg = exchangeContractErrorToHumanReadableError[error] || + ZeroExErrorToHumanReadableError[error]; + return humanReadableErrorMsg; + } + private static async getProviderAsync(injectedWeb3: Web3, networkIdIfExists: number): Promise<Web3.Provider> { + const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); + const publicNodeUrlsIfExistsForNetworkId = constants.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists]; + const isPublicNodeAvailableForNetworkId = !_.isUndefined(publicNodeUrlsIfExistsForNetworkId); + + let provider; + if (doesInjectedWeb3Exist && 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. + provider = new ProviderEngine(); + provider.addProvider(new InjectedWeb3SubProvider(injectedWeb3)); + provider.addProvider(new FilterSubprovider()); + provider.addProvider(new RedundantRPCSubprovider( + publicNodeUrlsIfExistsForNetworkId, + )); + provider.start(); + } else if (doesInjectedWeb3Exist) { + // Since no public node for this network, all requests go to injectedWeb3 instance + provider = injectedWeb3.currentProvider; + } 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. + provider = new ProviderEngine(); + provider.addProvider(new FilterSubprovider()); + const networkId = configs.isMainnetEnabled ? + constants.MAINNET_NETWORK_ID : + constants.TESTNET_NETWORK_ID; + provider.addProvider(new RedundantRPCSubprovider( + constants.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId], + )); + provider.start(); + } + + return provider; + } + private static getNameGivenProvider(provider: Web3.Provider): string { + if (!_.isUndefined((provider as any).isMetaMask)) { + return constants.METAMASK_PROVIDER_NAME; + } + + // HACK: We use the fact that Parity Signer's provider is an instance of their + // internal `Web3FrameProvider` class. + const isParitySigner = _.startsWith(provider.constructor.toString(), 'function Web3FrameProvider'); + if (isParitySigner) { + return constants.PARITY_SIGNER_PROVIDER_NAME; + } + + return constants.GENERIC_PROVIDER_NAME; + } + private static async onPageLoadAsync() { + if (document.readyState === 'complete') { + return; // Already loaded + } + return new Promise((resolve, reject) => { + window.onload = resolve; + }); + } constructor(dispatcher: Dispatcher, isSalePage: boolean = false) { this.dispatcher = dispatcher; this.userAddress = ''; + // tslint:disable-next-line:no-floating-promises this.onPageLoadInitFireAndForgetAsync(); } public async networkIdUpdatedFireAndForgetAsync(newNetworkId: number) { @@ -157,8 +265,8 @@ export class Blockchain { this.web3Wrapper.destroy(); const shouldPollUserAddress = false; this.web3Wrapper = new Web3Wrapper(this.dispatcher, provider, this.networkId, shouldPollUserAddress); - await this.zeroEx.setProviderAsync(provider); - await this.postInstantiationOrUpdatingProviderZeroExAsync(); + this.zeroEx.setProvider(provider, this.networkId); + this.postInstantiationOrUpdatingProviderZeroEx(); break; } @@ -169,8 +277,8 @@ export class Blockchain { provider = this.cachedProvider; const shouldPollUserAddress = true; this.web3Wrapper = new Web3Wrapper(this.dispatcher, provider, this.networkId, shouldPollUserAddress); - await this.zeroEx.setProviderAsync(provider); - await this.postInstantiationOrUpdatingProviderZeroExAsync(); + this.zeroEx.setProvider(provider, this.networkId); + this.postInstantiationOrUpdatingProviderZeroEx(); delete this.ledgerSubProvider; delete this.cachedProvider; break; @@ -208,7 +316,7 @@ export class Blockchain { amountInBaseUnits, })); } - public portalOrderToSignedOrder(maker: string, taker: string, makerTokenAddress: string, + public portalOrderToSignedOrder(maker: string, takerIfExists: string, makerTokenAddress: string, takerTokenAddress: string, makerTokenAmount: BigNumber, takerTokenAmount: BigNumber, makerFee: BigNumber, takerFee: BigNumber, expirationUnixTimestampSec: BigNumber, @@ -216,7 +324,7 @@ export class Blockchain { signatureData: SignatureData, salt: BigNumber): SignedOrder { const ecSignature = signatureData; const exchangeContractAddress = this.getExchangeContractAddressIfExists(); - taker = _.isEmpty(taker) ? constants.NULL_ADDRESS : taker; + const taker = _.isEmpty(takerIfExists) ? constants.NULL_ADDRESS : takerIfExists; const signedOrder = { ecSignature, exchangeContractAddress, @@ -273,51 +381,6 @@ export class Blockchain { public getExchangeContractAddressIfExists() { return this.exchangeAddress; } - public toHumanReadableErrorMsg(error: ZeroExError|ExchangeContractErrs, takerAddress: string): string { - const ZeroExErrorToHumanReadableError: {[error: string]: string} = { - [ZeroExError.ContractDoesNotExist]: 'Contract does not exist', - [ZeroExError.ExchangeContractDoesNotExist]: 'Exchange contract does not exist', - [ZeroExError.UnhandledError]: ' Unhandled error occured', - [ZeroExError.UserHasNoAssociatedAddress]: 'User has no addresses available', - [ZeroExError.InvalidSignature]: 'Order signature is not valid', - [ZeroExError.ContractNotDeployedOnNetwork]: 'Contract is not deployed on the detected network', - [ZeroExError.InvalidJump]: 'Invalid jump occured while executing the transaction', - [ZeroExError.OutOfGas]: 'Transaction ran out of gas', - [ZeroExError.NoNetworkId]: 'No network id detected', - }; - const exchangeContractErrorToHumanReadableError: {[error: string]: string} = { - [ExchangeContractErrs.OrderFillExpired]: 'This order has expired', - [ExchangeContractErrs.OrderCancelExpired]: 'This order has expired', - [ExchangeContractErrs.OrderCancelAmountZero]: 'Order cancel amount can\'t be 0', - [ExchangeContractErrs.OrderAlreadyCancelledOrFilled]: - 'This order has already been completely filled or cancelled', - [ExchangeContractErrs.OrderFillAmountZero]: 'Order fill amount can\'t be 0', - [ExchangeContractErrs.OrderRemainingFillAmountZero]: - 'This order has already been completely filled or cancelled', - [ExchangeContractErrs.OrderFillRoundingError]: 'Rounding error will occur when filling this order', - [ExchangeContractErrs.InsufficientTakerBalance]: - 'Taker no longer has a sufficient balance to complete this order', - [ExchangeContractErrs.InsufficientTakerAllowance]: - 'Taker no longer has a sufficient allowance to complete this order', - [ExchangeContractErrs.InsufficientMakerBalance]: - 'Maker no longer has a sufficient balance to complete this order', - [ExchangeContractErrs.InsufficientMakerAllowance]: - 'Maker no longer has a sufficient allowance to complete this order', - [ExchangeContractErrs.InsufficientTakerFeeBalance]: 'Taker no longer has a sufficient balance to pay fees', - [ExchangeContractErrs.InsufficientTakerFeeAllowance]: - 'Taker no longer has a sufficient allowance to pay fees', - [ExchangeContractErrs.InsufficientMakerFeeBalance]: 'Maker no longer has a sufficient balance to pay fees', - [ExchangeContractErrs.InsufficientMakerFeeAllowance]: - 'Maker no longer has a sufficient allowance to pay fees', - [ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker]: - `This order can only be filled by ${takerAddress}`, - [ExchangeContractErrs.InsufficientRemainingFillAmount]: - 'Insufficient remaining fill amount', - }; - const humanReadableErrorMsg = exchangeContractErrorToHumanReadableError[error] || - ZeroExErrorToHumanReadableError[error]; - return humanReadableErrorMsg; - } public async validateFillOrderThrowIfInvalidAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber, takerAddress: string): Promise<void> { @@ -446,6 +509,7 @@ export class Blockchain { public destroy() { clearInterval(this.zrxPollIntervalId); this.web3Wrapper.destroy(); + // tslint:disable-next-line:no-floating-promises this.stopWatchingExchangeLogFillEventsAsync(); // fire and forget } private async showEtherScanLinkAndAwaitTransactionMinedAsync( @@ -485,7 +549,7 @@ export class Blockchain { // Start a subscription for new logs const exchangeAddress = this.getExchangeContractAddressIfExists(); - const subscriptionId = await this.zeroEx.exchange.subscribeAsync( + const subscriptionId = this.zeroEx.exchange.subscribe( ExchangeEvents.LogFill, indexFilterValues, async (err: Error, decodedLogEvent: DecodedLogEvent<LogFillContractEventArgs>) => { const decodedLog = decodedLogEvent.log; @@ -493,7 +557,9 @@ export class Blockchain { // 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 + // tslint:disable-next-line:no-floating-promises errorReporter.reportAsync(err); // fire and forget + // tslint:disable-next-line:no-floating-promises this.stopWatchingExchangeLogFillEventsAsync(); // fire and forget return; } else { @@ -529,7 +595,7 @@ export class Blockchain { } } private async convertDecodedLogToFillAsync(decodedLog: LogWithDecodedArgs<LogFillContractEventArgs>) { - const args = decodedLog.args as LogFillContractEventArgs; + const args = decodedLog.args; const blockTimestamp = await this.web3Wrapper.getBlockTimestampAsync(decodedLog.blockHash); const fill = { filledTakerTokenAmount: args.filledTakerTokenAmount, @@ -548,7 +614,7 @@ export class Blockchain { return fill; } private doesLogEventInvolveUser(decodedLog: LogWithDecodedArgs<LogFillContractEventArgs>) { - const args = decodedLog.args as LogFillContractEventArgs; + const args = decodedLog.args; const isUserMakerOrTaker = args.maker === this.userAddress || args.taker === this.userAddress; return isUserMakerOrTaker; @@ -594,7 +660,7 @@ export class Blockchain { return tokenByAddress; } private async onPageLoadInitFireAndForgetAsync() { - await this.onPageLoadAsync(); // wait for page to load + await Blockchain.onPageLoadAsync(); // wait for page to load // Hack: We need to know the networkId the injectedWeb3 is connected to (if it is defined) in // order to properly instantiate the web3Wrapper. Since we must use the async call, we cannot @@ -612,81 +678,34 @@ export class Blockchain { } } - const provider = await this.getProviderAsync(injectedWeb3, networkId); - this.zeroEx = new ZeroEx(provider); - await this.updateProviderName(injectedWeb3); + const provider = await Blockchain.getProviderAsync(injectedWeb3, networkId); + const zeroExConfig = { + networkId: this.networkId, + }; + this.zeroEx = new ZeroEx(provider, zeroExConfig); + this.updateProviderName(injectedWeb3); const shouldPollUserAddress = true; this.web3Wrapper = new Web3Wrapper(this.dispatcher, provider, networkId, shouldPollUserAddress); - await this.postInstantiationOrUpdatingProviderZeroExAsync(); + this.postInstantiationOrUpdatingProviderZeroEx(); } // This method should always be run after instantiating or updating the provider // of the ZeroEx instance. - private async postInstantiationOrUpdatingProviderZeroExAsync() { + private postInstantiationOrUpdatingProviderZeroEx(): void { utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); - this.exchangeAddress = await this.zeroEx.exchange.getContractAddressAsync(); + this.exchangeAddress = this.zeroEx.exchange.getContractAddress(); } - private updateProviderName(injectedWeb3: Web3) { + private updateProviderName(injectedWeb3: Web3): void { const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); const providerName = doesInjectedWeb3Exist ? - this.getNameGivenProvider(injectedWeb3.currentProvider) : + Blockchain.getNameGivenProvider(injectedWeb3.currentProvider) : constants.PUBLIC_PROVIDER_NAME; this.dispatcher.updateInjectedProviderName(providerName); } // This is only ever called by the LedgerWallet subprovider in order to retrieve // the current networkId without this value going stale. - private getBlockchainNetworkId() { + private getBlockchainNetworkId(): number { return this.networkId; } - private async getProviderAsync(injectedWeb3: Web3, networkIdIfExists: number) { - const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); - const publicNodeUrlsIfExistsForNetworkId = constants.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists]; - const isPublicNodeAvailableForNetworkId = !_.isUndefined(publicNodeUrlsIfExistsForNetworkId); - - let provider; - if (doesInjectedWeb3Exist && 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. - provider = new ProviderEngine(); - provider.addProvider(new InjectedWeb3SubProvider(injectedWeb3)); - provider.addProvider(new FilterSubprovider()); - provider.addProvider(new RedundantRPCSubprovider( - publicNodeUrlsIfExistsForNetworkId, - )); - provider.start(); - } else if (doesInjectedWeb3Exist) { - // Since no public node for this network, all requests go to injectedWeb3 instance - provider = injectedWeb3.currentProvider; - } 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. - provider = new ProviderEngine(); - provider.addProvider(new FilterSubprovider()); - const networkId = configs.isMainnetEnabled ? - constants.MAINNET_NETWORK_ID : - constants.TESTNET_NETWORK_ID; - provider.addProvider(new RedundantRPCSubprovider( - constants.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId], - )); - provider.start(); - } - - return provider; - } - private getNameGivenProvider(provider: Web3.Provider): string { - if (!_.isUndefined((provider as any).isMetaMask)) { - return constants.METAMASK_PROVIDER_NAME; - } - - // HACK: We use the fact that Parity Signer's provider is an instance of their - // internal `Web3FrameProvider` class. - const isParitySigner = _.startsWith(provider.constructor.toString(), 'function Web3FrameProvider'); - if (isParitySigner) { - return constants.PARITY_SIGNER_PROVIDER_NAME; - } - - return constants.GENERIC_PROVIDER_NAME; - } private async fetchTokenInformationAsync() { utils.assert(!_.isUndefined(this.networkId), 'Cannot call fetchTokenInformationAsync if disconnected from Ethereum node'); @@ -776,12 +795,4 @@ export class Blockchain { } } } - private async onPageLoadAsync() { - if (document.readyState === 'complete') { - return; // Already loaded - } - return new Promise((resolve, reject) => { - window.onload = resolve; - }); - } -} +} // tslint:disable:max-file-line-count |