diff options
author | Francesco Agosti <francesco.agosti93@gmail.com> | 2018-06-29 02:36:49 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-06-29 02:36:49 +0800 |
commit | 2474d1d2f4cf4ae81d5d67c53005f018a0154ef5 (patch) | |
tree | ac24b29f975be088a9d3d391f8a73acc7ad14aba | |
parent | 0fcbd02d50bd564a9c888f247a4b0a565d928cc6 (diff) | |
parent | e4188f5d4c38e53bf6966a364da41a3aa164b567 (diff) | |
download | dexon-0x-contracts-2474d1d2f4cf4ae81d5d67c53005f018a0154ef5.tar.gz dexon-0x-contracts-2474d1d2f4cf4ae81d5d67c53005f018a0154ef5.tar.zst dexon-0x-contracts-2474d1d2f4cf4ae81d5d67c53005f018a0154ef5.zip |
Merge pull request #780 from 0xProject/feature/website/support-new-metamask
Refactor Blockchain.ts to allow arbitrary provider state changes
-rw-r--r-- | packages/website/ts/blockchain.ts | 259 | ||||
-rw-r--r-- | packages/website/ts/blockchain_watcher.ts | 46 | ||||
-rw-r--r-- | packages/website/ts/components/dialogs/blockchain_err_dialog.tsx | 17 | ||||
-rw-r--r-- | packages/website/ts/components/dialogs/ledger_config_dialog.tsx | 1 | ||||
-rw-r--r-- | packages/website/ts/components/legacy_portal/legacy_portal.tsx | 117 | ||||
-rw-r--r-- | packages/website/ts/components/onboarding/portal_onboarding_flow.tsx | 17 | ||||
-rw-r--r-- | packages/website/ts/components/portal/portal.tsx | 38 | ||||
-rw-r--r-- | packages/website/ts/components/top_bar/top_bar.tsx | 2 | ||||
-rw-r--r-- | packages/website/ts/types.ts | 10 | ||||
-rw-r--r-- | packages/website/ts/utils/configs.ts | 1 | ||||
-rw-r--r-- | yarn.lock | 20 |
11 files changed, 263 insertions, 265 deletions
diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index b65debced..d18c34c32 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -44,6 +44,8 @@ import { BlockchainErrs, ContractInstance, Fill, + InjectedProviderObservable, + InjectedProviderUpdate, Order as PortalOrder, Providers, ProviderType, @@ -80,9 +82,9 @@ export class Blockchain { private _dispatcher: Dispatcher; private _web3Wrapper?: Web3Wrapper; private _blockchainWatcher?: BlockchainWatcher; + private _injectedProviderObservable?: InjectedProviderObservable; + private _injectedProviderUpdateHandler: (update: InjectedProviderUpdate) => Promise<void>; private _userAddressIfExists: string; - private _cachedProvider: Provider; - private _cachedProviderNetworkId: number; private _ledgerSubprovider: LedgerSubprovider; private _defaultGasPrice: BigNumber; private static _getNameGivenProvider(provider: Provider): string { @@ -93,16 +95,62 @@ export class Blockchain { } return providerNameIfExists; } - private static async _getProviderAsync(injectedWeb3: Web3, networkIdIfExists: number): Promise<Provider> { + private static _getInjectedWeb3(): any { + return (window as any).web3; + } + private static async _getInjectedWeb3ProviderNetworkIdIfExistsAsync(): Promise<number | undefined> { + // 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 + // retrieve it from within the web3Wrapper constructor. This is and should remain the only + // call to a web3 instance outside of web3Wrapper in the entire dapp. + // In addition, 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 injectedWeb3IfExists = Blockchain._getInjectedWeb3(); + let networkIdIfExists: number; + if (!_.isUndefined(injectedWeb3IfExists)) { + try { + networkIdIfExists = _.parseInt(await promisify<string>(injectedWeb3IfExists.version.getNetwork)()); + } catch (err) { + // Ignore error and proceed with networkId undefined + } + } + return networkIdIfExists; + } + private static async _getProviderAsync( + injectedWeb3: Web3, + networkIdIfExists: number, + shouldUserLedgerProvider: boolean = false, + ): Promise<[Provider, LedgerSubprovider | undefined]> { const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); + const isNetworkIdAvailable = !_.isUndefined(networkIdIfExists); const publicNodeUrlsIfExistsForNetworkId = configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists]; const isPublicNodeAvailableForNetworkId = !_.isUndefined(publicNodeUrlsIfExistsForNetworkId); - let provider; - if (doesInjectedWeb3Exist && isPublicNodeAvailableForNetworkId) { + if (shouldUserLedgerProvider && isNetworkIdAvailable) { + const isU2FSupported = await utils.isU2FSupportedAsync(); + if (!isU2FSupported) { + throw new Error('Cannot update providerType to LEDGER without U2F support'); + } + const provider = new ProviderEngine(); + 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({ + rpcUrl: publicNodeUrl, + }); + }); + provider.addProvider(new RedundantSubprovider(rpcSubproviders as Subprovider[])); + provider.start(); + return [provider, ledgerSubprovider]; + } else 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(); + const provider = new ProviderEngine(); provider.addProvider(new InjectedWeb3Subprovider(injectedWeb3.currentProvider)); provider.addProvider(new FilterSubprovider()); const rpcSubproviders = _.map(publicNodeUrlsIfExistsForNetworkId, publicNodeUrl => { @@ -112,16 +160,17 @@ export class Blockchain { }); provider.addProvider(new RedundantSubprovider(rpcSubproviders as Subprovider[])); provider.start(); + return [provider, undefined]; } else if (doesInjectedWeb3Exist) { // Since no public node for this network, all requests go to injectedWeb3 instance - provider = injectedWeb3.currentProvider; + return [injectedWeb3.currentProvider, 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. - provider = new ProviderEngine(); + const provider = new ProviderEngine(); provider.addProvider(new FilterSubprovider()); - const networkId = configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_KOVAN; + const networkId = constants.NETWORK_ID_MAINNET; const rpcSubproviders = _.map(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId], publicNodeUrl => { return new RpcSubprovider({ rpcUrl: publicNodeUrl, @@ -129,14 +178,15 @@ export class Blockchain { }); provider.addProvider(new RedundantSubprovider(rpcSubproviders as Subprovider[])); provider.start(); + return [provider, undefined]; } - - return provider; } constructor(dispatcher: Dispatcher) { this._dispatcher = dispatcher; const defaultGasPrice = GWEI_IN_WEI * 30; 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._updateDefaultGasPriceAsync(); // tslint:disable-next-line:no-floating-promises @@ -186,84 +236,17 @@ export class Blockchain { this._ledgerSubprovider.setPath(path); } public async updateProviderToLedgerAsync(networkId: number): Promise<void> { - utils.assert(!_.isUndefined(this._contractWrappers), 'Contract Wrappers must be instantiated.'); - - const isU2FSupported = await utils.isU2FSupportedAsync(); - if (!isU2FSupported) { - throw new Error('Cannot update providerType to LEDGER without U2F support'); - } - - // Cache injected provider so that we can switch the user back to it easily - if (_.isUndefined(this._cachedProvider)) { - this._cachedProvider = this._web3Wrapper.getProvider(); - this._cachedProviderNetworkId = this.networkId; - } - - this._blockchainWatcher.destroy(); - - delete this._userAddressIfExists; - this._dispatcher.updateUserAddress(undefined); // Clear old userAddress - - const provider = new ProviderEngine(); - const ledgerWalletConfigs = { - networkId, - ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync, - }; - this._ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs); - provider.addProvider(this._ledgerSubprovider); - provider.addProvider(new FilterSubprovider()); - const rpcSubproviders = _.map(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId], publicNodeUrl => { - return new RpcSubprovider({ - rpcUrl: publicNodeUrl, - }); - }); - provider.addProvider(new RedundantSubprovider(rpcSubproviders as Subprovider[])); - provider.start(); - this.networkId = networkId; - this._dispatcher.updateNetworkId(this.networkId); const shouldPollUserAddress = false; - this._web3Wrapper = new Web3Wrapper(provider); - this._blockchainWatcher = new BlockchainWatcher( - this._dispatcher, - this._web3Wrapper, - this.networkId, - shouldPollUserAddress, - ); - this._contractWrappers.setProvider(provider, this.networkId); - await this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceStateAsync(); - this._dispatcher.updateProviderType(ProviderType.Ledger); + const shouldUserLedgerProvider = true; + await this._resetOrInitializeAsync(networkId, shouldPollUserAddress, shouldUserLedgerProvider); } public async updateProviderToInjectedAsync(): Promise<void> { - utils.assert(!_.isUndefined(this._contractWrappers), 'Contract Wrappers must be instantiated.'); - - if (_.isUndefined(this._cachedProvider)) { - return; // Going from injected to injected, so we noop - } - - this._blockchainWatcher.destroy(); - - const provider = this._cachedProvider; - this.networkId = this._cachedProviderNetworkId; - const shouldPollUserAddress = true; - this._web3Wrapper = new Web3Wrapper(provider); - this._blockchainWatcher = new BlockchainWatcher( - this._dispatcher, - this._web3Wrapper, - this.networkId, - shouldPollUserAddress, - ); - - const userAddresses = await this._web3Wrapper.getAvailableAddressesAsync(); - this._userAddressIfExists = userAddresses[0]; - - this._contractWrappers.setProvider(provider, this.networkId); - - await this.fetchTokenInformationAsync(); - await this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceStateAsync(); - this._dispatcher.updateProviderType(ProviderType.Injected); - delete this._ledgerSubprovider; - delete this._cachedProvider; + 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 Blockchain._getInjectedWeb3ProviderNetworkIdIfExistsAsync(); + await this._resetOrInitializeAsync(networkId, shouldPollUserAddress, shouldUserLedgerProvider); } public async setProxyAllowanceAsync(token: Token, amountInBaseUnits: BigNumber): Promise<void> { utils.assert(this.isValidAddress(token.address), BlockchainCallErrs.TokenAddressIsInvalid); @@ -539,6 +522,7 @@ export class Blockchain { } public destroy(): void { this._blockchainWatcher.destroy(); + this._injectedProviderObservable.unsubscribe(this._injectedProviderUpdateHandler); this._stopWatchingExchangeLogFillEvents(); } public async fetchTokenInformationAsync(): Promise<void> { @@ -634,6 +618,18 @@ export class Blockchain { 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(); @@ -776,49 +772,64 @@ export class Blockchain { } private async _onPageLoadInitFireAndForgetAsync(): Promise<void> { await utils.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 - // retrieve it from within the web3Wrapper constructor. This is and should remain the only - // call to a web3 instance outside of web3Wrapper in the entire dapp. - // In addition, 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 injectedWeb3 = (window as any).web3; - let networkIdIfExists: number; - if (!_.isUndefined(injectedWeb3)) { - try { - networkIdIfExists = _.parseInt(await promisify<string>(injectedWeb3.version.getNetwork)()); - } catch (err) { - // Ignore error and proceed with networkId undefined + const networkIdIfExists = await Blockchain._getInjectedWeb3ProviderNetworkIdIfExistsAsync(); + this.networkId = !_.isUndefined(networkIdIfExists) ? networkIdIfExists : constants.NETWORK_ID_MAINNET; + const injectedWeb3IfExists = Blockchain._getInjectedWeb3(); + if (!_.isUndefined(injectedWeb3IfExists) && !_.isUndefined(injectedWeb3IfExists.currentProvider)) { + const injectedProviderObservable = injectedWeb3IfExists.currentProvider.publicConfigStore; + if (!_.isUndefined(injectedProviderObservable) && _.isUndefined(this._injectedProviderObservable)) { + this._injectedProviderObservable = injectedProviderObservable; + this._injectedProviderObservable.subscribe(this._injectedProviderUpdateHandler); } } - - const provider = await Blockchain._getProviderAsync(injectedWeb3, networkIdIfExists); - this.networkId = !_.isUndefined(networkIdIfExists) - ? networkIdIfExists - : configs.IS_MAINNET_ENABLED - ? constants.NETWORK_ID_MAINNET - : constants.NETWORK_ID_KOVAN; - this._dispatcher.updateNetworkId(this.networkId); - const zeroExConfigs = { - networkId: this.networkId, - }; - this._contractWrappers = new ContractWrappers(provider, zeroExConfigs); - this._updateProviderName(injectedWeb3); + this._updateProviderName(injectedWeb3IfExists); const shouldPollUserAddress = true; - this._web3Wrapper = new Web3Wrapper(provider); - this._blockchainWatcher = new BlockchainWatcher( - this._dispatcher, - this._web3Wrapper, - this.networkId, - shouldPollUserAddress, + const shouldUseLedgerProvider = false; + await this._resetOrInitializeAsync(this.networkId, shouldPollUserAddress, shouldUseLedgerProvider); + } + 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 injectedWeb3IfExists = Blockchain._getInjectedWeb3(); + const [provider, ledgerSubproviderIfExists] = await Blockchain._getProviderAsync( + injectedWeb3IfExists, + networkId, + shouldUserLedgerProvider, ); - - const userAddresses = await this._web3Wrapper.getAvailableAddressesAsync(); - this._userAddressIfExists = userAddresses[0]; - this._dispatcher.updateUserAddress(this._userAddressIfExists); - await this.fetchTokenInformationAsync(); - await this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceStateAsync(); + if (!_.isUndefined(this._contractWrappers)) { + this._contractWrappers.setProvider(provider, networkId); + } else { + this._contractWrappers = new ContractWrappers(provider, { networkId }); + } + if (!_.isUndefined(this._blockchainWatcher)) { + this._blockchainWatcher.destroy(); + } + this._web3Wrapper = new Web3Wrapper(provider); + 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(injectedWeb3IfExists)) { + this._dispatcher.updateProviderType(ProviderType.Injected); + } + await this.fetchTokenInformationAsync(); + } + await this._blockchainWatcher.startEmittingUserBalanceStateAsync(); + this._dispatcher.updateNetworkId(networkId); await this._rehydrateStoreWithContractEventsAsync(); } private _updateProviderName(injectedWeb3: Web3): void { diff --git a/packages/website/ts/blockchain_watcher.ts b/packages/website/ts/blockchain_watcher.ts index c576db6ac..df5f73fd1 100644 --- a/packages/website/ts/blockchain_watcher.ts +++ b/packages/website/ts/blockchain_watcher.ts @@ -6,24 +6,17 @@ import { Dispatcher } from 'ts/redux/dispatcher'; export class BlockchainWatcher { private _dispatcher: Dispatcher; private _web3Wrapper: Web3Wrapper; - private _prevNetworkId: number; private _shouldPollUserAddress: boolean; - private _watchNetworkAndBalanceIntervalId: NodeJS.Timer; + private _watchBalanceIntervalId: NodeJS.Timer; private _prevUserEtherBalanceInWei?: BigNumber; private _prevUserAddressIfExists: string; - constructor( - dispatcher: Dispatcher, - web3Wrapper: Web3Wrapper, - networkIdIfExists: number, - shouldPollUserAddress: boolean, - ) { + constructor(dispatcher: Dispatcher, web3Wrapper: Web3Wrapper, shouldPollUserAddress: boolean) { this._dispatcher = dispatcher; - this._prevNetworkId = networkIdIfExists; this._shouldPollUserAddress = shouldPollUserAddress; this._web3Wrapper = web3Wrapper; } public destroy(): void { - this._stopEmittingNetworkConnectionAndUserBalanceState(); + this._stopEmittingUserBalanceState(); // HACK: stop() is only available on providerEngine instances const provider = this._web3Wrapper.getProvider(); if (!_.isUndefined((provider as any).stop)) { @@ -34,36 +27,23 @@ export class BlockchainWatcher { public updatePrevUserAddress(userAddress: string): void { this._prevUserAddressIfExists = userAddress; } - public async startEmittingNetworkConnectionAndUserBalanceStateAsync(): Promise<void> { - if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) { + public async startEmittingUserBalanceStateAsync(): Promise<void> { + if (!_.isUndefined(this._watchBalanceIntervalId)) { return; // we are already emitting the state } this._prevUserEtherBalanceInWei = undefined; - this._dispatcher.updateNetworkId(this._prevNetworkId); - await this._updateNetworkAndBalanceAsync(); - this._watchNetworkAndBalanceIntervalId = intervalUtils.setAsyncExcludingInterval( - this._updateNetworkAndBalanceAsync.bind(this), + await this._updateBalanceAsync(); + this._watchBalanceIntervalId = intervalUtils.setAsyncExcludingInterval( + this._updateBalanceAsync.bind(this), 5000, (err: Error) => { logUtils.log(`Watching network and balances failed: ${err.stack}`); - this._stopEmittingNetworkConnectionAndUserBalanceState(); + this._stopEmittingUserBalanceState(); }, ); } - private async _updateNetworkAndBalanceAsync(): Promise<void> { - // Check for network state changes + private async _updateBalanceAsync(): Promise<void> { let prevNodeVersion: string; - let currentNetworkId; - try { - currentNetworkId = await this._web3Wrapper.getNetworkIdAsync(); - } catch (err) { - // Noop - } - if (currentNetworkId !== this._prevNetworkId) { - this._prevNetworkId = currentNetworkId; - this._dispatcher.updateNetworkId(currentNetworkId); - } - // Check for node version changes const currentNodeVersion = await this._web3Wrapper.getNodeVersionAsync(); if (currentNodeVersion !== prevNodeVersion) { @@ -99,9 +79,9 @@ export class BlockchainWatcher { this._dispatcher.updateUserWeiBalance(balanceInWei); } } - private _stopEmittingNetworkConnectionAndUserBalanceState(): void { - if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) { - intervalUtils.clearAsyncExcludingInterval(this._watchNetworkAndBalanceIntervalId); + private _stopEmittingUserBalanceState(): void { + if (!_.isUndefined(this._watchBalanceIntervalId)) { + intervalUtils.clearAsyncExcludingInterval(this._watchBalanceIntervalId); } } } diff --git a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx index b968a3147..c8e10303f 100644 --- a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx +++ b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx @@ -4,7 +4,6 @@ import FlatButton from 'material-ui/FlatButton'; import * as React from 'react'; import { Blockchain } from 'ts/blockchain'; import { BlockchainErrs } from 'ts/types'; -import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; interface BlockchainErrDialogProps { @@ -125,8 +124,7 @@ export class BlockchainErrDialog extends React.Component<BlockchainErrDialogProp Parity Signer Chrome extension </a>{' '} lets you connect to a locally running Parity node. Make sure you have started your local Parity node - with {configs.IS_MAINNET_ENABLED && '`parity ui` or'} `parity --chain kovan ui` in order to connect - to {configs.IS_MAINNET_ENABLED ? 'mainnet or Kovan respectively.' : 'Kovan.'} + with `parity ui` or `parity --chain kovan ui` in order to connect to mainnet or Kovan respectively. </div> <div className="pt2"> <span className="bold">Note:</span> If you have done one of the above steps and are still seeing @@ -142,10 +140,8 @@ export class BlockchainErrDialog extends React.Component<BlockchainErrDialogProp <div> The 0x smart contracts are not deployed on the Ethereum network you are currently connected to (network Id: {this.props.networkId}). In order to use the 0x portal dApp, please connect to the{' '} - {Networks.Kovan} testnet (network Id: {constants.NETWORK_ID_KOVAN}) - {configs.IS_MAINNET_ENABLED - ? ` or ${constants.MAINNET_NAME} (network Id: ${constants.NETWORK_ID_MAINNET}).` - : `.`} + {Networks.Kovan} testnet (network Id: {constants.NETWORK_ID_KOVAN}) or ${constants.MAINNET_NAME}{' '} + (network Id: ${constants.NETWORK_ID_MAINNET}). </div> <h4>Metamask</h4> <div> @@ -159,11 +155,8 @@ export class BlockchainErrDialog extends React.Component<BlockchainErrDialogProp If using the{' '} <a href={constants.URL_PARITY_CHROME_STORE} target="_blank"> Parity Signer Chrome extension - </a>, make sure to start your local Parity node with{' '} - {configs.IS_MAINNET_ENABLED - ? '`parity ui` or `parity --chain Kovan ui` in order to connect to mainnet \ - or Kovan respectively.' - : '`parity --chain kovan ui` in order to connect to Kovan.'} + </a>, make sure to start your local Parity node with `parity ui` or `parity --chain Kovan ui` in + order to connect to mainnet \ or Kovan respectively. </div> </div> ); diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx index c9727b553..38e4732a4 100644 --- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx +++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx @@ -282,6 +282,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, if (didSucceed) { this.setState({ stepIndex: LedgerSteps.SELECT_ADDRESS, + connectionErrMsg: '', }); } return didSucceed; diff --git a/packages/website/ts/components/legacy_portal/legacy_portal.tsx b/packages/website/ts/components/legacy_portal/legacy_portal.tsx index 9a54f3474..c85d97207 100644 --- a/packages/website/ts/components/legacy_portal/legacy_portal.tsx +++ b/packages/website/ts/components/legacy_portal/legacy_portal.tsx @@ -23,7 +23,6 @@ import { GenerateOrderForm } from 'ts/containers/generate_order_form'; import { localStorage } from 'ts/local_storage/local_storage'; import { Dispatcher } from 'ts/redux/dispatcher'; import { BlockchainErrs, HashData, Order, ProviderType, ScreenWidths, TokenByAddress, WebsitePaths } from 'ts/types'; -import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { orderParser } from 'ts/utils/order_parser'; import { Translate } from 'ts/utils/translate'; @@ -170,67 +169,53 @@ export class LegacyPortal extends React.Component<LegacyPortalProps, LegacyPorta /> <div id="portal" className="mx-auto max-width-4" style={{ width: '100%' }}> <Paper className="mb3 mt2"> - {!configs.IS_MAINNET_ENABLED && this.props.networkId === constants.NETWORK_ID_MAINNET ? ( - <div className="p3 center"> - <div className="h2 py2">Mainnet unavailable</div> - <div className="mx-auto pb2 pt2"> - <img src="/images/zrx_token.png" style={{ width: 150 }} /> - </div> - <div> - 0x portal is currently unavailable on the Ethereum mainnet. - <div>To try it out, switch to the Kovan test network (networkId: 42).</div> - <div className="py2">Check back soon!</div> - </div> + <div className="mx-auto flex"> + <div className="col col-2 pr2 pt1 sm-hide xs-hide" style={portalMenuContainerStyle}> + <LegacyPortalMenu menuItemStyle={{ color: colors.white }} /> </div> - ) : ( - <div className="mx-auto flex"> - <div className="col col-2 pr2 pt1 sm-hide xs-hide" style={portalMenuContainerStyle}> - <LegacyPortalMenu menuItemStyle={{ color: colors.white }} /> - </div> - <div className="col col-12 lg-col-10 md-col-10 sm-col sm-col-12"> - <div className="py2" style={{ backgroundColor: colors.grey50 }}> - {this.props.blockchainIsLoaded ? ( - <Switch> - <Route - path={`${WebsitePaths.Portal}/weth`} - render={this._renderEthWrapper.bind(this)} - /> - <Route - path={`${WebsitePaths.Portal}/fill`} - render={this._renderFillOrder.bind(this)} - /> - <Route - path={`${WebsitePaths.Portal}/balances`} - render={this._renderTokenBalances.bind(this)} - /> - <Route - path={`${WebsitePaths.Portal}/trades`} - render={this._renderTradeHistory.bind(this)} - /> - <Route - path={`${WebsitePaths.Home}`} - render={this._renderGenerateOrderForm.bind(this)} - /> - </Switch> - ) : ( - <div className="pt4 sm-px2 sm-pt2 sm-m1" style={{ height: 500 }}> - <div - className="relative sm-px2 sm-pt2 sm-m1" - style={{ height: 122, top: '50%', transform: 'translateY(-50%)' }} - > - <div className="center pb2"> - <CircularProgress size={40} thickness={5} /> - </div> - <div className="center pt2" style={{ paddingBottom: 11 }}> - Loading Portal... - </div> + <div className="col col-12 lg-col-10 md-col-10 sm-col sm-col-12"> + <div className="py2" style={{ backgroundColor: colors.grey50 }}> + {this.props.blockchainIsLoaded ? ( + <Switch> + <Route + path={`${WebsitePaths.Portal}/weth`} + render={this._renderEthWrapper.bind(this)} + /> + <Route + path={`${WebsitePaths.Portal}/fill`} + render={this._renderFillOrder.bind(this)} + /> + <Route + path={`${WebsitePaths.Portal}/balances`} + render={this._renderTokenBalances.bind(this)} + /> + <Route + path={`${WebsitePaths.Portal}/trades`} + render={this._renderTradeHistory.bind(this)} + /> + <Route + path={`${WebsitePaths.Home}`} + render={this._renderGenerateOrderForm.bind(this)} + /> + </Switch> + ) : ( + <div className="pt4 sm-px2 sm-pt2 sm-m1" style={{ height: 500 }}> + <div + className="relative sm-px2 sm-pt2 sm-m1" + style={{ height: 122, top: '50%', transform: 'translateY(-50%)' }} + > + <div className="center pb2"> + <CircularProgress size={40} thickness={5} /> + </div> + <div className="center pt2" style={{ paddingBottom: 11 }}> + Loading Portal... </div> </div> - )} - </div> + </div> + )} </div> </div> - )} + </div> </Paper> <BlockchainErrDialog blockchain={this._blockchain} @@ -249,16 +234,14 @@ export class LegacyPortal extends React.Component<LegacyPortalProps, LegacyPorta onToggleDialog={this._onPortalDisclaimerAccepted.bind(this)} /> <FlashMessage dispatcher={this.props.dispatcher} flashMessage={this.props.flashMessage} /> - {this.props.blockchainIsLoaded && ( - <LedgerConfigDialog - providerType={this.props.providerType} - networkId={this.props.networkId} - blockchain={this._blockchain} - dispatcher={this.props.dispatcher} - toggleDialogFn={this.onToggleLedgerDialog.bind(this)} - isOpen={this.state.isLedgerDialogOpen} - /> - )} + <LedgerConfigDialog + providerType={this.props.providerType} + networkId={this.props.networkId} + blockchain={this._blockchain} + dispatcher={this.props.dispatcher} + toggleDialogFn={this.onToggleLedgerDialog.bind(this)} + isOpen={this.state.isLedgerDialogOpen} + /> </div> <Footer translate={this.props.translate} dispatcher={this.props.dispatcher} /> </div> diff --git a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx index 10d4af30e..296b410fe 100644 --- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx @@ -159,9 +159,11 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp const ethToken = utils.getEthToken(this.props.tokenByAddress); const zrxToken = utils.getZrxToken(this.props.tokenByAddress); if (ethToken && zrxToken) { - const ethTokenAllowance = this.props.trackedTokenStateByAddress[ethToken.address].allowance; - const zrxTokenAllowance = this.props.trackedTokenStateByAddress[zrxToken.address].allowance; - return ethTokenAllowance > new BigNumber(0) && zrxTokenAllowance > new BigNumber(0); + const ethTokenState = this.props.trackedTokenStateByAddress[ethToken.address]; + const zrxTokenState = this.props.trackedTokenStateByAddress[zrxToken.address]; + if (ethTokenState && zrxTokenState) { + return ethTokenState.allowance.gt(0) && zrxTokenState.allowance.gt(0); + } } return false; } @@ -221,12 +223,15 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp if (!token) { return null; } - const tokenState = this.props.trackedTokenStateByAddress[token.address]; + const tokenStateIfExists = this.props.trackedTokenStateByAddress[token.address]; + if (_.isUndefined(tokenStateIfExists)) { + return null; + } return ( <AllowanceToggle token={token} - tokenState={tokenState} - isDisabled={!tokenState.isLoaded} + tokenState={tokenStateIfExists} + isDisabled={!tokenStateIfExists.isLoaded} blockchain={this.props.blockchain} // tslint:disable-next-line:jsx-no-lambda refetchTokenStateAsync={async () => this.props.refetchTokenStateAsync(token.address)} diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx index 9498cb388..438c7b52f 100644 --- a/packages/website/ts/components/portal/portal.tsx +++ b/packages/website/ts/components/portal/portal.tsx @@ -152,9 +152,8 @@ export class Portal extends React.Component<PortalProps, PortalState> { } public componentDidUpdate(prevProps: PortalProps): void { if (!prevProps.blockchainIsLoaded && this.props.blockchainIsLoaded) { - const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress); // tslint:disable-next-line:no-floating-promises - this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses); + this._fetchBalancesAndAllowancesAsync(this._getCurrentTrackedTokensAddresses()); } } public componentWillReceiveProps(nextProps: PortalProps): void { @@ -182,14 +181,14 @@ export class Portal extends React.Component<PortalProps, PortalState> { prevPathname: nextProps.location.pathname, }); } + + // If the address changed, but the network did not, we can just refetch the currently tracked tokens. if ( - nextProps.userAddress !== this.props.userAddress || - nextProps.networkId !== this.props.networkId || + (nextProps.userAddress !== this.props.userAddress && nextProps.networkId === this.props.networkId) || nextProps.lastForceTokenStateRefetch !== this.props.lastForceTokenStateRefetch ) { - const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress); // tslint:disable-next-line:no-floating-promises - this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses); + this._fetchBalancesAndAllowancesAsync(this._getCurrentTrackedTokensAddresses()); } const nextTrackedTokens = utils.getTrackedTokens(nextProps.tokenByAddress); @@ -200,7 +199,7 @@ export class Portal extends React.Component<PortalProps, PortalState> { const newTokenAddresses = _.map(newTokens, token => token.address); // Add placeholder entry for this token to the state, since fetching the // balance/allowance is asynchronous - const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress; + const trackedTokenStateByAddress = { ...this.state.trackedTokenStateByAddress }; for (const tokenAddress of newTokenAddresses) { trackedTokenStateByAddress[tokenAddress] = { balance: new BigNumber(0), @@ -265,16 +264,16 @@ export class Portal extends React.Component<PortalProps, PortalState> { networkId={this.props.networkId} /> <FlashMessage dispatcher={this.props.dispatcher} flashMessage={this.props.flashMessage} /> - {this.props.blockchainIsLoaded && ( - <LedgerConfigDialog - providerType={this.props.providerType} - networkId={this.props.networkId} - blockchain={this._blockchain} - dispatcher={this.props.dispatcher} - toggleDialogFn={this._onToggleLedgerDialog.bind(this)} - isOpen={this.state.isLedgerDialogOpen} - /> - )} + + <LedgerConfigDialog + providerType={this.props.providerType} + networkId={this.props.networkId} + blockchain={this._blockchain} + dispatcher={this.props.dispatcher} + toggleDialogFn={this._onToggleLedgerDialog.bind(this)} + isOpen={this.state.isLedgerDialogOpen} + /> + <AssetPicker userAddress={this.props.userAddress} networkId={this.props.networkId} @@ -608,11 +607,12 @@ export class Portal extends React.Component<PortalProps, PortalState> { const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm; return isSmallScreen; } - private _getCurrentTrackedTokens(): Token[] { return utils.getTrackedTokens(this.props.tokenByAddress); } - + private _getCurrentTrackedTokensAddresses(): string[] { + return _.map(this._getCurrentTrackedTokens(), token => token.address); + } private _getInitialTrackedTokenStateByAddress(trackedTokens: Token[]): TokenStateByAddress { const trackedTokenStateByAddress: TokenStateByAddress = {}; _.each(trackedTokens, token => { diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx index 537edc7bb..fac6c131f 100644 --- a/packages/website/ts/components/top_bar/top_bar.tsx +++ b/packages/website/ts/components/top_bar/top_bar.tsx @@ -13,7 +13,6 @@ import { ProviderDisplay } from 'ts/components/top_bar/provider_display'; import { TopBarMenuItem } from 'ts/components/top_bar/top_bar_menu_item'; import { DropDown } from 'ts/components/ui/drop_down'; import { Dispatcher } from 'ts/redux/dispatcher'; -import { zIndex } from 'ts/style/z_index'; import { Deco, Key, ProviderType, WebsiteLegacyPaths, WebsitePaths } from 'ts/types'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; @@ -58,7 +57,6 @@ const styles: Styles = { width: '100%', position: 'relative', top: 0, - zIndex: zIndex.topBar, paddingBottom: 1, }, bottomBar: { diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index 11ae2e0ba..2db947ba3 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -488,6 +488,16 @@ export enum Providers { Mist = 'MIST', } +export interface InjectedProviderUpdate { + selectedAddress: string; + networkVersion: string; +} + +export interface InjectedProviderObservable { + subscribe(updateHandler: (update: InjectedProviderUpdate) => void): void; + unsubscribe(updateHandler: (update: InjectedProviderUpdate) => void): void; +} + export interface TimestampMsRange { startTimestampMs: number; endTimestampMs: number; diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts index 36c067f84..2f89f8ccb 100644 --- a/packages/website/ts/utils/configs.ts +++ b/packages/website/ts/utils/configs.ts @@ -63,7 +63,6 @@ export const configs = { TKN: '/images/token_icons/tokencard.png', TRST: '/images/token_icons/trust.png', } as { [symbol: string]: string }, - IS_MAINNET_ENABLED: true, GOOGLE_ANALYTICS_ID: 'UA-98720122-1', LAST_LOCAL_STORAGE_FILL_CLEARANCE_DATE: '2017-11-22', LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE: '2018-6-25', @@ -44,6 +44,24 @@ ethereumjs-util "5.1.5" lodash "4.17.10" +"@0xproject/order-watcher@^0.0.6": + version "0.0.6" + resolved "https://registry.npmjs.org/@0xproject/order-watcher/-/order-watcher-0.0.6.tgz#85a8fb21e5755bb555f427b12d64d10b89b332e6" + dependencies: + "@0xproject/assert" "^0.2.12" + "@0xproject/base-contract" "^0.3.4" + "@0xproject/contract-wrappers" "^0.0.5" + "@0xproject/fill-scenarios" "^0.0.4" + "@0xproject/json-schemas" "^0.8.1" + "@0xproject/order-utils" "^0.0.7" + "@0xproject/types" "^0.8.1" + "@0xproject/typescript-typings" "^0.4.1" + "@0xproject/utils" "^0.7.1" + "@0xproject/web3-wrapper" "^0.7.1" + bintrees "1.0.2" + ethers "3.0.22" + lodash "4.17.10" + "@0xproject/types@^0.5.0": version "0.5.0" resolved "https://registry.yarnpkg.com/@0xproject/types/-/types-0.5.0.tgz#ba3cfbc11a8c6344b57c9680aa7df2ea84b9bf05" @@ -1708,7 +1726,7 @@ bindings@^1.2.1, bindings@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7" -bintrees@^1.0.2: +bintrees@1.0.2, bintrees@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.2.tgz#49f896d6e858a4a499df85c38fb399b9aff840f8" |