diff options
author | Steve Klebanoff <steve.klebanoff@gmail.com> | 2018-11-02 05:33:43 +0800 |
---|---|---|
committer | Steve Klebanoff <steve.klebanoff@gmail.com> | 2018-11-02 05:33:43 +0800 |
commit | f341626e290a5c8241400b8dd0d9cce2dcfeb405 (patch) | |
tree | 08fc761936e7c8d620087ac6b4c83ac6aa1f3864 | |
parent | 7858dafce4c9441c8205fa6ed607ca50851cc4ba (diff) | |
parent | 0955feb0234bc90b7dcf5ad3a308570c9fa5d490 (diff) | |
download | dexon-0x-contracts-f341626e290a5c8241400b8dd0d9cce2dcfeb405.tar.gz dexon-0x-contracts-f341626e290a5c8241400b8dd0d9cce2dcfeb405.tar.zst dexon-0x-contracts-f341626e290a5c8241400b8dd0d9cce2dcfeb405.zip |
Merge branch 'development' into feature/instant/simulated-progress-bar
27 files changed, 485 insertions, 248 deletions
diff --git a/packages/asset-buyer/CHANGELOG.json b/packages/asset-buyer/CHANGELOG.json index 5d6604ea9..ce4effa7e 100644 --- a/packages/asset-buyer/CHANGELOG.json +++ b/packages/asset-buyer/CHANGELOG.json @@ -5,6 +5,10 @@ { "note": "`getAssetBuyerForProvidedOrders` factory function now takes 3 args instead of 4", "pr": 1187 + }, + { + "note": "No longer require that provided orders all have the same maker and taker asset data", + "pr": 1197 } ] }, diff --git a/packages/asset-buyer/src/asset_buyer.ts b/packages/asset-buyer/src/asset_buyer.ts index 34e2d9639..ed52f2d9d 100644 --- a/packages/asset-buyer/src/asset_buyer.ts +++ b/packages/asset-buyer/src/asset_buyer.ts @@ -56,7 +56,6 @@ export class AssetBuyer { ): AssetBuyer { assert.isWeb3Provider('provider', provider); assert.doesConformToSchema('orders', orders, schemas.signedOrdersSchema); - assert.areValidProvidedOrders('orders', orders); assert.assert(orders.length !== 0, `Expected orders to contain at least one order`); const orderProvider = new BasicOrderProvider(orders); const assetBuyer = new AssetBuyer(provider, orderProvider, options); diff --git a/packages/asset-buyer/src/utils/assert.ts b/packages/asset-buyer/src/utils/assert.ts index e8cb7f763..0f39fb4a5 100644 --- a/packages/asset-buyer/src/utils/assert.ts +++ b/packages/asset-buyer/src/utils/assert.ts @@ -1,6 +1,5 @@ import { assert as sharedAssert } from '@0x/assert'; import { schemas } from '@0x/json-schemas'; -import { SignedOrder } from '@0x/types'; import * as _ from 'lodash'; import { BuyQuote, BuyQuoteInfo, OrderProvider, OrderProviderRequest } from '../types'; @@ -31,21 +30,6 @@ export const assert = { sharedAssert.isHexString(`${variableName}.takerAssetData`, orderFetcherRequest.takerAssetData); sharedAssert.isNumber(`${variableName}.networkId`, orderFetcherRequest.networkId); }, - areValidProvidedOrders(variableName: string, orders: SignedOrder[]): void { - if (orders.length === 0) { - return; - } - const makerAssetData = orders[0].makerAssetData; - const takerAssetData = orders[0].takerAssetData; - const filteredOrders = _.filter( - orders, - order => order.makerAssetData === makerAssetData && order.takerAssetData === takerAssetData, - ); - sharedAssert.assert( - orders.length === filteredOrders.length, - `Expected all orders in ${variableName} to have the same makerAssetData and takerAssetData.`, - ); - }, isValidPercentage(variableName: string, percentage: number): void { assert.isNumber(variableName, percentage); assert.assert( diff --git a/packages/instant/public/index.html b/packages/instant/public/index.html index 9f1dfdb64..62532dad9 100644 --- a/packages/instant/public/index.html +++ b/packages/instant/public/index.html @@ -34,25 +34,42 @@ EXPONENTIAL_AT: 1000, DECIMAL_PLACES: 78, }); - const providedOrder = { - senderAddress: '0x0000000000000000000000000000000000000000', - makerAddress: '0x14e2f1f157e7dd4057d02817436d628a37120fd1', - takerAddress: '0x0000000000000000000000000000000000000000', - makerFee: new BigNumber('0'), - takerFee: new BigNumber('0'), - makerAssetAmount: new BigNumber('100000000000000000000'), - takerAssetAmount: new BigNumber('10000000000000000'), - makerAssetData: '0xf47261b00000000000000000000000002002d3812f58e35f0ea1ffbf80a75a38c32175fa', - takerAssetData: '0xf47261b0000000000000000000000000d0a1e359811322d97991e03f863a0c30c2cf029c', - expirationTimeSeconds: new BigNumber('1591858800'), - feeRecipientAddress: '0x0000000000000000000000000000000000000000', - salt: new BigNumber( - '54983920541892966634674340965984367456810207583416050222519063020710969340046', - ), - signature: - '0x1b949656218421c845995457303569a656764afa2b979d41dcefff0009d57ce15001490268bc7caa4269894fd83b741465fc5a7a53eda6ece17eb91fb32655d83703', - exchangeAddress: '0x35dd2932454449b14cee11a94d3674a936d5d7b2', - }; + const providedOrders = [ + // Order selling REP + { + senderAddress: '0x0000000000000000000000000000000000000000', + makerAddress: '0x34a745008a643eebc58920eaa29fb1165b4a288e', + takerAddress: '0x0000000000000000000000000000000000000000', + makerFee: new BigNumber('0'), + takerFee: new BigNumber('0'), + makerAssetAmount: new BigNumber('400000000000000000000'), + takerAssetAmount: new BigNumber('40000000000000000000'), + makerAssetData: '0xf47261b00000000000000000000000008cb3971b8eb709c14616bd556ff6683019e90d9c', + takerAssetData: '0xf47261b0000000000000000000000000d0a1e359811322d97991e03f863a0c30c2cf029c', + expirationTimeSeconds: new BigNumber('1543046400'), + feeRecipientAddress: '0x0000000000000000000000000000000000000000', + salt: new BigNumber('47929252863126413473766089649682650973189811771354566206928245255479607883031'), + signature: '0x1c0bf8ba709ceb5b32e6b0b5a8bb7f07e9d19aba88d8530715f8a298d12188e3862fcc0a30ddfad4062b30459f2859323c064052f12cc687466c457934b9419a1b03', + exchangeAddress: '0x35dd2932454449b14cee11a94d3674a936d5d7b2' + }, + // Order selling ZRX + { + senderAddress: '0x0000000000000000000000000000000000000000', + makerAddress: '0x34a745008a643eebc58920eaa29fb1165b4a288e', + takerAddress: '0x0000000000000000000000000000000000000000', + makerFee: new BigNumber('0'), + takerFee: new BigNumber('0'), + makerAssetAmount: new BigNumber('300000000000000000000'), + takerAssetAmount: new BigNumber('31000000000000000000'), + makerAssetData: '0xf47261b00000000000000000000000002002d3812f58e35f0ea1ffbf80a75a38c32175fa', + takerAssetData: '0xf47261b0000000000000000000000000d0a1e359811322d97991e03f863a0c30c2cf029c', + expirationTimeSeconds: new BigNumber('2524636800'), + feeRecipientAddress: '0x0000000000000000000000000000000000000000', + salt: new BigNumber('64592004666704945574675477805199411288137454783320798602050822322450089238268'), + signature: '0x1c13cacddca8d7d8248e91f412377e68f8f1f9891a59a6c1b2eea9f7b33558c30c4fb86a448e08ab7def40a28fb3a3062dcb33bb3c45302447fce5c4288b7c7f5b03', + exchangeAddress: '0x35dd2932454449b14cee11a94d3674a936d5d7b2' + } + ]; const queryParams = new Uri(window.location.search); const renderOptionsDefaults = { liquiditySource: 'https://api.radarrelay.com/0x/v2/', @@ -60,7 +77,7 @@ } const liquiditySourceOverride = queryParams.getQueryParamValue('liquiditySource'); const renderOptionsOverrides = { - liquiditySource: liquiditySourceOverride === 'provided' ? [providedOrder] : liquiditySourceOverride, + liquiditySource: liquiditySourceOverride === 'provided' ? providedOrders : liquiditySourceOverride, assetData: queryParams.getQueryParamValue('assetData'), networkId: +queryParams.getQueryParamValue('networkId') || undefined, defaultAssetBuyAmount: +queryParams.getQueryParamValue('defaultAssetBuyAmount') || undefined, diff --git a/packages/instant/src/components/animations/position_animation.tsx b/packages/instant/src/components/animations/position_animation.tsx new file mode 100644 index 000000000..4bb21befb --- /dev/null +++ b/packages/instant/src/components/animations/position_animation.tsx @@ -0,0 +1,80 @@ +import { Keyframes } from 'styled-components'; + +import { css, keyframes, styled } from '../../style/theme'; + +export interface TransitionInfo { + from: string; + to: string; +} + +const generateTransitionInfoCss = ( + key: keyof TransitionInfo, + top?: TransitionInfo, + bottom?: TransitionInfo, + left?: TransitionInfo, + right?: TransitionInfo, +): string => { + const topStringIfExists = top ? `top: ${top[key]};` : ''; + const bottomStringIfExists = bottom ? `bottom: ${bottom[key]};` : ''; + const leftStringIfExists = left ? `left: ${left[key]};` : ''; + const rightStringIfExists = right ? `right: ${right[key]};` : ''; + return ` + ${topStringIfExists} + ${bottomStringIfExists} + ${leftStringIfExists} + ${rightStringIfExists} + `; +}; + +const slideKeyframeGenerator = ( + position: string, + top?: TransitionInfo, + bottom?: TransitionInfo, + left?: TransitionInfo, + right?: TransitionInfo, +) => keyframes` + from { + position: ${position}; + ${generateTransitionInfoCss('from', top, bottom, left, right)} + } + + to { + position: ${position}; + ${generateTransitionInfoCss('to', top, bottom, left, right)} + } +`; + +export interface PositionAnimationSettings { + top?: TransitionInfo; + bottom?: TransitionInfo; + left?: TransitionInfo; + right?: TransitionInfo; + timingFunction: string; + duration?: string; +} + +export interface PositionAnimationProps extends PositionAnimationSettings { + position: string; +} + +export const PositionAnimation = + styled.div < + PositionAnimationProps > + ` + animation-name: ${props => + css` + ${slideKeyframeGenerator(props.position, props.top, props.bottom, props.left, props.right)}; + `}; + animation-duration: ${props => props.duration || '0.3s'}; + animation-timing-function: ${props => props.timingFunction}; + animation-delay: 0s; + animation-iteration-count: 1; + animation-fill-mode: forwards; + position: ${props => props.position}; + height: 100%; + width: 100%; +`; + +PositionAnimation.defaultProps = { + position: 'relative', +}; diff --git a/packages/instant/src/components/animations/slide_animation.tsx b/packages/instant/src/components/animations/slide_animation.tsx new file mode 100644 index 000000000..66a314c7f --- /dev/null +++ b/packages/instant/src/components/animations/slide_animation.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; + +import { PositionAnimation, PositionAnimationSettings } from './position_animation'; + +export type SlideAnimationState = 'slidIn' | 'slidOut' | 'none'; +export interface SlideAnimationProps { + position: string; + animationState: SlideAnimationState; + slideInSettings: PositionAnimationSettings; + slideOutSettings: PositionAnimationSettings; +} + +export const SlideAnimation: React.StatelessComponent<SlideAnimationProps> = props => { + if (props.animationState === 'none') { + return <React.Fragment>{props.children}</React.Fragment>; + } + const propsToUse = props.animationState === 'slidIn' ? props.slideInSettings : props.slideOutSettings; + return ( + <PositionAnimation position={props.position} {...propsToUse}> + {props.children} + </PositionAnimation> + ); +}; diff --git a/packages/instant/src/components/animations/slide_animations.tsx b/packages/instant/src/components/animations/slide_animations.tsx deleted file mode 100644 index 84280372b..000000000 --- a/packages/instant/src/components/animations/slide_animations.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import * as React from 'react'; -import { Keyframes } from 'styled-components'; - -import { css, keyframes, styled } from '../../style/theme'; - -const slideKeyframeGenerator = (fromY: string, toY: string) => keyframes` - from { - position: relative; - top: ${fromY}; - } - - to { - position: relative; - top: ${toY}; - } -`; - -export interface SlideAnimationProps { - keyframes: Keyframes; - animationType: string; - animationDirection?: string; -} - -export const SlideAnimation = - styled.div < - SlideAnimationProps > - ` - animation-name: ${props => - css` - ${props.keyframes}; - `}; - animation-duration: 0.3s; - animation-timing-function: ${props => props.animationType}; - animation-delay: 0s; - animation-iteration-count: 1; - animation-fill-mode: ${props => props.animationDirection || 'none'}; - position: relative; -`; - -export interface SlideAnimationComponentProps { - downY: string; -} - -export const SlideUpAnimation: React.StatelessComponent<SlideAnimationComponentProps> = props => ( - <SlideAnimation animationType="ease-in" keyframes={slideKeyframeGenerator(props.downY, '0px')}> - {props.children} - </SlideAnimation> -); - -export const SlideDownAnimation: React.StatelessComponent<SlideAnimationComponentProps> = props => ( - <SlideAnimation - animationDirection="forwards" - animationType="cubic-bezier(0.25, 0.1, 0.25, 1)" - keyframes={slideKeyframeGenerator('0px', props.downY)} - > - {props.children} - </SlideAnimation> -); diff --git a/packages/instant/src/components/buy_order_state_buttons.tsx b/packages/instant/src/components/buy_order_state_buttons.tsx index 3f0764062..1d02f8cd9 100644 --- a/packages/instant/src/components/buy_order_state_buttons.tsx +++ b/packages/instant/src/components/buy_order_state_buttons.tsx @@ -1,16 +1,13 @@ import { AssetBuyer, AssetBuyerError, BuyQuote } from '@0x/asset-buyer'; import * as React from 'react'; -import { BuyButton } from '../components/buy_button'; -import { SecondaryButton } from '../components/secondary_button'; -import { Flex } from '../components/ui/flex'; - -import { PlacingOrderButton } from '../components/placing_order_button'; import { ColorOption } from '../style/theme'; import { OrderProcessState, ZeroExInstantError } from '../types'; -import { Button } from './ui/button'; -import { Text } from './ui/text'; +import { BuyButton } from './buy_button'; +import { PlacingOrderButton } from './placing_order_button'; +import { SecondaryButton } from './secondary_button'; +import { Button, Flex, Text } from './ui'; export interface BuyOrderStateButtonProps { buyQuote?: BuyQuote; diff --git a/packages/instant/src/components/erc20_asset_amount_input.tsx b/packages/instant/src/components/erc20_asset_amount_input.tsx index 5abb34c2f..b1fec6405 100644 --- a/packages/instant/src/components/erc20_asset_amount_input.tsx +++ b/packages/instant/src/components/erc20_asset_amount_input.tsx @@ -8,13 +8,14 @@ import { BigNumberInput } from '../util/big_number_input'; import { util } from '../util/util'; import { ScalingAmountInput } from './scaling_amount_input'; -import { Container, Text } from './ui'; +import { Container, Flex, Icon, Text } from './ui'; // Asset amounts only apply to ERC20 assets export interface ERC20AssetAmountInputProps { asset?: ERC20Asset; value?: BigNumberInput; onChange: (value?: BigNumberInput, asset?: ERC20Asset) => void; + onSelectAssetClick?: (asset?: ERC20Asset) => void; startingFontSizePx: number; fontColor?: ColorOption; isDisabled: boolean; @@ -36,10 +37,18 @@ export class ERC20AssetAmountInput extends React.Component<ERC20AssetAmountInput }; } public render(): React.ReactNode { - const { asset, onChange, ...rest } = this.props; - const amountBorderBottom = this.props.isDisabled ? '' : `1px solid ${transparentWhite}`; + const { asset } = this.props; return ( <Container whiteSpace="nowrap"> + {_.isUndefined(asset) ? this._renderBackupContent() : this._renderContentForAsset(asset)} + </Container> + ); + } + private readonly _renderContentForAsset = (asset: ERC20Asset): React.ReactNode => { + const { onChange, ...rest } = this.props; + const amountBorderBottom = this.props.isDisabled ? '' : `1px solid ${transparentWhite}`; + return ( + <React.Fragment> <Container borderBottom={amountBorderBottom} display="inline-block"> <ScalingAmountInput {...rest} @@ -49,18 +58,50 @@ export class ERC20AssetAmountInput extends React.Component<ERC20AssetAmountInput onFontSizeChange={this._handleFontSizeChange} /> </Container> - <Container display="inline-flex" marginLeft="10px" title={assetUtils.bestNameForAsset(asset)}> - <Text - fontSize={`${this.state.currentFontSizePx}px`} - fontColor={ColorOption.white} - textTransform="uppercase" - > - {assetUtils.formattedSymbolForAsset(asset)} - </Text> + <Container + cursor="pointer" + display="inline-block" + marginLeft="8px" + title={assetUtils.bestNameForAsset(asset, undefined)} + > + <Flex inline={true}> + <Text + fontSize={`${this.state.currentFontSizePx}px`} + fontColor={ColorOption.white} + textTransform="uppercase" + onClick={this._handleSymbolClick} + > + {assetUtils.formattedSymbolForAsset(asset)} + </Text> + {this._renderChevronIcon()} + </Flex> </Container> + </React.Fragment> + ); + }; + private readonly _renderBackupContent = (): React.ReactNode => { + return ( + <Flex> + <Text + fontSize="30px" + fontColor={ColorOption.white} + opacity={0.7} + fontWeight="500" + onClick={this._handleSymbolClick} + > + Select Token + </Text> + {this._renderChevronIcon()} + </Flex> + ); + }; + private readonly _renderChevronIcon = (): React.ReactNode => { + return ( + <Container marginLeft="5px" onClick={this._handleSymbolClick}> + <Icon icon="chevron" width={12} /> </Container> ); - } + }; private readonly _handleChange = (value?: BigNumberInput): void => { this.props.onChange(value, this.props.asset); }; @@ -69,6 +110,11 @@ export class ERC20AssetAmountInput extends React.Component<ERC20AssetAmountInput currentFontSizePx: fontSizePx, }); }; + private readonly _handleSymbolClick = () => { + if (this.props.onSelectAssetClick) { + this.props.onSelectAssetClick(this.props.asset); + } + }; // For assets with symbols of different length, // start scaling the input at different character lengths private readonly _textLengthThresholdForAsset = (asset?: ERC20Asset): number => { diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index 1ef276ff3..19c08db70 100644 --- a/packages/instant/src/components/instant_heading.tsx +++ b/packages/instant/src/components/instant_heading.tsx @@ -4,13 +4,11 @@ import * as React from 'react'; import { SelectedERC20AssetAmountInput } from '../containers/selected_erc20_asset_amount_input'; import { ColorOption } from '../style/theme'; -import { AsyncProcessState, OrderProcessState, OrderState } from '../types'; +import { AsyncProcessState, ERC20Asset, OrderProcessState, OrderState } from '../types'; import { format } from '../util/format'; import { AmountPlaceholder } from './amount_placeholder'; -import { Container, Flex, Text } from './ui'; -import { Icon } from './ui/icon'; -import { Spinner } from './ui/spinner'; +import { Container, Flex, Icon, Spinner, Text } from './ui'; export interface InstantHeadingProps { selectedAssetAmount?: BigNumber; @@ -18,12 +16,13 @@ export interface InstantHeadingProps { ethUsdPrice?: BigNumber; quoteRequestState: AsyncProcessState; buyOrderState: OrderState; + onSelectAssetClick?: (asset?: ERC20Asset) => void; } const PLACEHOLDER_COLOR = ColorOption.white; const ICON_WIDTH = 34; const ICON_HEIGHT = 34; -const ICON_COLOR = ColorOption.white; +const ICON_COLOR = 'white'; export class InstantHeading extends React.Component<InstantHeadingProps, {}> { public render(): React.ReactNode { @@ -49,7 +48,10 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> { </Container> <Flex direction="row" justify="space-between"> <Flex height="60px"> - <SelectedERC20AssetAmountInput startingFontSizePx={38} /> + <SelectedERC20AssetAmountInput + startingFontSizePx={38} + onSelectAssetClick={this.props.onSelectAssetClick} + /> </Flex> <Flex direction="column" justify="space-between"> {iconOrAmounts} @@ -62,8 +64,8 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> { private _renderAmountsSection(): React.ReactNode { return ( <Container> - <Container marginBottom="5px">{this._placeholderOrAmount(this._ethAmount)}</Container> - <Container opacity={0.7}>{this._placeholderOrAmount(this._dollarAmount)}</Container> + <Container marginBottom="5px">{this._renderPlaceholderOrAmount(this._renderEthAmount)}</Container> + <Container opacity={0.7}>{this._renderPlaceholderOrAmount(this._renderDollarAmount)}</Container> </Container> ); } @@ -72,11 +74,11 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> { const processState = this.props.buyOrderState.processState; if (processState === OrderProcessState.FAILURE) { - return <Icon icon={'failed'} width={ICON_WIDTH} height={ICON_HEIGHT} color={ICON_COLOR} />; + return <Icon icon="failed" width={ICON_WIDTH} height={ICON_HEIGHT} color={ICON_COLOR} />; } else if (processState === OrderProcessState.PROCESSING) { return <Spinner widthPx={ICON_HEIGHT} heightPx={ICON_HEIGHT} />; } else if (processState === OrderProcessState.SUCCESS) { - return <Icon icon={'success'} width={ICON_WIDTH} height={ICON_HEIGHT} color={ICON_COLOR} />; + return <Icon icon="success" width={ICON_WIDTH} height={ICON_HEIGHT} color={ICON_COLOR} />; } return undefined; } @@ -94,7 +96,7 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> { return 'I want to buy'; } - private _placeholderOrAmount(amountFunction: () => React.ReactNode): React.ReactNode { + private _renderPlaceholderOrAmount(amountFunction: () => React.ReactNode): React.ReactNode { if (this.props.quoteRequestState === AsyncProcessState.PENDING) { return <AmountPlaceholder isPulsating={true} color={PLACEHOLDER_COLOR} />; } @@ -104,7 +106,7 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> { return amountFunction(); } - private readonly _ethAmount = (): React.ReactNode => { + private readonly _renderEthAmount = (): React.ReactNode => { return ( <Text fontSize="16px" fontColor={ColorOption.white} fontWeight={500}> {format.ethBaseAmount( @@ -116,7 +118,7 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> { ); }; - private readonly _dollarAmount = (): React.ReactNode => { + private readonly _renderDollarAmount = (): React.ReactNode => { return ( <Text fontSize="16px" fontColor={ColorOption.white}> {format.ethBaseAmountInUsd( diff --git a/packages/instant/src/components/placing_order_button.tsx b/packages/instant/src/components/placing_order_button.tsx index 4232e6c22..e5a6371e6 100644 --- a/packages/instant/src/components/placing_order_button.tsx +++ b/packages/instant/src/components/placing_order_button.tsx @@ -2,10 +2,7 @@ import * as React from 'react'; import { ColorOption } from '../style/theme'; -import { Button } from './ui/button'; -import { Container } from './ui/container'; -import { Spinner } from './ui/spinner'; -import { Text } from './ui/text'; +import { Button, Container, Spinner, Text } from './ui'; export const PlacingOrderButton: React.StatelessComponent<{}> = props => ( <Button isDisabled={true} width="100%"> diff --git a/packages/instant/src/components/secondary_button.tsx b/packages/instant/src/components/secondary_button.tsx index 583058b5b..ca698c77a 100644 --- a/packages/instant/src/components/secondary_button.tsx +++ b/packages/instant/src/components/secondary_button.tsx @@ -3,8 +3,7 @@ import * as React from 'react'; import { ColorOption } from '../style/theme'; -import { Button, ButtonProps } from './ui/button'; -import { Text } from './ui/text'; +import { Button, ButtonProps, Text } from './ui'; export interface SecondaryButtonProps extends ButtonProps {} diff --git a/packages/instant/src/components/sliding_error.tsx b/packages/instant/src/components/sliding_error.tsx index cc9abb7dd..17643fd7d 100644 --- a/packages/instant/src/components/sliding_error.tsx +++ b/packages/instant/src/components/sliding_error.tsx @@ -2,7 +2,8 @@ import * as React from 'react'; import { ColorOption } from '../style/theme'; -import { SlideDownAnimation, SlideUpAnimation } from './animations/slide_animations'; +import { PositionAnimationSettings } from './animations/position_animation'; +import { SlideAnimation, SlideAnimationState } from './animations/slide_animation'; import { Container, Flex, Text } from './ui'; @@ -31,16 +32,33 @@ export const Error: React.StatelessComponent<ErrorProps> = props => ( </Container> ); -export type SlidingDirection = 'up' | 'down'; export interface SlidingErrorProps extends ErrorProps { - direction: SlidingDirection; + animationState: SlideAnimationState; } export const SlidingError: React.StatelessComponent<SlidingErrorProps> = props => { - const AnimationComponent = props.direction === 'up' ? SlideUpAnimation : SlideDownAnimation; - + const slideAmount = '120px'; + const slideUpSettings: PositionAnimationSettings = { + timingFunction: 'ease-in', + top: { + from: slideAmount, + to: '0px', + }, + }; + const slideDownSettings: PositionAnimationSettings = { + timingFunction: 'cubic-bezier(0.25, 0.1, 0.25, 1)', + top: { + from: '0px', + to: slideAmount, + }, + }; return ( - <AnimationComponent downY="120px"> + <SlideAnimation + position="relative" + slideInSettings={slideUpSettings} + slideOutSettings={slideDownSettings} + animationState={props.animationState} + > <Error icon={props.icon} message={props.message} /> - </AnimationComponent> + </SlideAnimation> ); }; diff --git a/packages/instant/src/components/sliding_panel.tsx b/packages/instant/src/components/sliding_panel.tsx new file mode 100644 index 000000000..9219ad1f1 --- /dev/null +++ b/packages/instant/src/components/sliding_panel.tsx @@ -0,0 +1,59 @@ +import * as React from 'react'; + +import { ColorOption } from '../style/theme'; +import { zIndex } from '../style/z_index'; + +import { PositionAnimationSettings } from './animations/position_animation'; +import { SlideAnimation, SlideAnimationState } from './animations/slide_animation'; +import { Button, Container, Text } from './ui'; + +export interface PanelProps { + onClose?: () => void; +} + +export const Panel: React.StatelessComponent<PanelProps> = ({ children, onClose }) => ( + <Container backgroundColor={ColorOption.white} width="100%" height="100%" zIndex={zIndex.panel}> + <Button onClick={onClose}> + <Text fontColor={ColorOption.white}>Close </Text> + </Button> + {children} + </Container> +); + +export interface SlidingPanelProps extends PanelProps { + animationState: SlideAnimationState; +} + +export const SlidingPanel: React.StatelessComponent<SlidingPanelProps> = props => { + if (props.animationState === 'none') { + return null; + } + const { animationState, ...rest } = props; + const slideAmount = '100%'; + const slideUpSettings: PositionAnimationSettings = { + duration: '0.3s', + timingFunction: 'ease-in-out', + top: { + from: slideAmount, + to: '0px', + }, + }; + const slideDownSettings: PositionAnimationSettings = { + duration: '0.3s', + timingFunction: 'ease-out', + top: { + from: '0px', + to: slideAmount, + }, + }; + return ( + <SlideAnimation + position="absolute" + slideInSettings={slideUpSettings} + slideOutSettings={slideDownSettings} + animationState={animationState} + > + <Panel {...rest} /> + </SlideAnimation> + ); +}; diff --git a/packages/instant/src/components/ui/container.tsx b/packages/instant/src/components/ui/container.tsx index 76b570de7..7b8642761 100644 --- a/packages/instant/src/components/ui/container.tsx +++ b/packages/instant/src/components/ui/container.tsx @@ -28,6 +28,8 @@ export interface ContainerProps { zIndex?: number; whiteSpace?: string; opacity?: number; + cursor?: string; + overflow?: string; } export const Container = @@ -57,6 +59,8 @@ export const Container = ${props => cssRuleIfExists(props, 'z-index')} ${props => cssRuleIfExists(props, 'white-space')} ${props => cssRuleIfExists(props, 'opacity')} + ${props => cssRuleIfExists(props, 'cursor')} + ${props => cssRuleIfExists(props, 'overflow')} ${props => (props.hasBoxShadow ? `box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1)` : '')}; background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')}; border-color: ${props => (props.borderColor ? props.theme[props.borderColor] : 'none')}; diff --git a/packages/instant/src/components/ui/flex.tsx b/packages/instant/src/components/ui/flex.tsx index 5fa3fc95b..29c6511bb 100644 --- a/packages/instant/src/components/ui/flex.tsx +++ b/packages/instant/src/components/ui/flex.tsx @@ -9,14 +9,14 @@ export interface FlexProps { width?: string; height?: string; backgroundColor?: ColorOption; - className?: string; + inline?: boolean; } export const Flex = styled.div < FlexProps > ` - display: flex; + display: ${props => (props.inline ? 'inline-flex' : 'flex')}; flex-direction: ${props => props.direction}; flex-wrap: ${props => props.flexWrap}; justify-content: ${props => props.justify}; diff --git a/packages/instant/src/components/ui/icon.tsx b/packages/instant/src/components/ui/icon.tsx index 7373c3acd..61b382760 100644 --- a/packages/instant/src/components/ui/icon.tsx +++ b/packages/instant/src/components/ui/icon.tsx @@ -1,17 +1,21 @@ import * as React from 'react'; -import { ColorOption } from '../../style/theme'; - type svgRule = 'evenodd' | 'nonzero' | 'inherit'; interface IconInfo { viewBox: string; + path: string; fillRule?: svgRule; clipRule?: svgRule; - path: string; + stroke?: string; + strokeOpacity?: number; + strokeWidth?: number; + strokeLinecap?: 'butt' | 'round' | 'square' | 'inherit'; + strokeLinejoin?: 'miter' | 'round' | 'bevel' | 'inherit'; } interface IconInfoMapping { failed: IconInfo; success: IconInfo; + chevron: IconInfo; } const ICONS: IconInfoMapping = { failed: { @@ -28,12 +32,21 @@ const ICONS: IconInfoMapping = { path: 'M17 34C26.3887 34 34 26.3888 34 17C34 7.61121 26.3887 0 17 0C7.61133 0 0 7.61121 0 17C0 26.3888 7.61133 34 17 34ZM25.7539 13.0977C26.2969 12.4718 26.2295 11.5244 25.6035 10.9817C24.9775 10.439 24.0303 10.5063 23.4878 11.1323L15.731 20.0771L12.3936 16.7438C11.8071 16.1583 10.8574 16.1589 10.272 16.7451C9.68652 17.3313 9.6875 18.281 10.2734 18.8665L14.75 23.3373L15.8887 24.4746L16.9434 23.2587L25.7539 13.0977Z', }, + chevron: { + viewBox: '0 0 12 7', + path: 'M11 1L6 6L1 1', + stroke: 'white', + strokeOpacity: 0.5, + strokeWidth: 1.5, + strokeLinecap: 'round', + strokeLinejoin: 'round', + }, }; export interface IconProps { width: number; - height: number; - color: ColorOption; + height?: number; + color?: string; icon: keyof IconInfoMapping; } export const Icon: React.SFC<IconProps> = props => { @@ -52,6 +65,11 @@ export const Icon: React.SFC<IconProps> = props => { fill={props.color} fillRule={iconInfo.fillRule || 'nonzero'} clipRule={iconInfo.clipRule || 'nonzero'} + stroke={iconInfo.stroke} + strokeOpacity={iconInfo.strokeOpacity} + strokeWidth={iconInfo.strokeWidth} + strokeLinecap={iconInfo.strokeLinecap} + strokeLinejoin={iconInfo.strokeLinejoin} /> </svg> ); diff --git a/packages/instant/src/components/ui/index.ts b/packages/instant/src/components/ui/index.ts index bf5f6c700..28f76c262 100644 --- a/packages/instant/src/components/ui/index.ts +++ b/packages/instant/src/components/ui/index.ts @@ -1,5 +1,7 @@ export { Text, Title } from './text'; -export { Button } from './button'; -export { Flex } from './flex'; -export { Container } from './container'; -export { Input } from './input'; +export { Button, ButtonProps } from './button'; +export { Flex, FlexProps } from './flex'; +export { Container, ContainerProps } from './container'; +export { Input, InputProps } from './input'; +export { Icon, IconProps } from './icon'; +export { Spinner, SpinnerProps } from './spinner'; diff --git a/packages/instant/src/components/zero_ex_instant_container.tsx b/packages/instant/src/components/zero_ex_instant_container.tsx index c47f3af73..f8e3935fb 100644 --- a/packages/instant/src/components/zero_ex_instant_container.tsx +++ b/packages/instant/src/components/zero_ex_instant_container.tsx @@ -8,30 +8,60 @@ import { SelectedAssetInstantHeading } from '../containers/selected_asset_instan import { SelectedAssetBuyOrderProgress } from '../containers/selected_asset_buy_order_progress'; import { ColorOption } from '../style/theme'; +import { zIndex } from '../style/z_index'; +import { SlideAnimationState } from './animations/slide_animation'; +import { SlidingPanel } from './sliding_panel'; import { Container, Flex } from './ui'; export interface ZeroExInstantContainerProps {} +export interface ZeroExInstantContainerState { + tokenSelectionPanelAnimationState: SlideAnimationState; +} -export const ZeroExInstantContainer: React.StatelessComponent<ZeroExInstantContainerProps> = props => ( - <Container width="350px"> - <Container zIndex={1} position="relative"> - <LatestError /> - </Container> - <Container - zIndex={2} - position="relative" - backgroundColor={ColorOption.white} - borderRadius="3px" - hasBoxShadow={true} - > - <Flex direction="column" justify="flex-start"> - <SelectedAssetInstantHeading /> - <SelectedAssetBuyOrderProgress /> - <LatestBuyQuoteOrderDetails /> - <Container padding="20px" width="100%"> - <SelectedAssetBuyOrderStateButtons /> +export class ZeroExInstantContainer extends React.Component<ZeroExInstantContainerProps, ZeroExInstantContainerState> { + public state = { + tokenSelectionPanelAnimationState: 'none' as SlideAnimationState, + }; + public render(): React.ReactNode { + return ( + <Container width="350px" position="relative"> + <Container zIndex={zIndex.errorPopup} position="relative"> + <LatestError /> </Container> - </Flex> - </Container> - </Container> -); + <Container + zIndex={zIndex.mainContainer} + position="relative" + backgroundColor={ColorOption.white} + borderRadius="3px" + hasBoxShadow={true} + overflow="hidden" + > + <Flex direction="column" justify="flex-start"> + <SelectedAssetInstantHeading onSelectAssetClick={this._handleSymbolClick} /> + <SelectedAssetBuyOrderProgress /> + <LatestBuyQuoteOrderDetails /> + <Container padding="20px" width="100%"> + <SelectedAssetBuyOrderStateButtons /> + </Container> + </Flex> + <SlidingPanel + animationState={this.state.tokenSelectionPanelAnimationState} + onClose={this._handlePanelClose} + > + Select Your Token + </SlidingPanel> + </Container> + </Container> + ); + } + private readonly _handleSymbolClick = (): void => { + this.setState({ + tokenSelectionPanelAnimationState: 'slidIn', + }); + }; + private readonly _handlePanelClose = (): void => { + this.setState({ + tokenSelectionPanelAnimationState: 'slidOut', + }); + }; +} diff --git a/packages/instant/src/containers/latest_error.tsx b/packages/instant/src/containers/latest_error.tsx index 45ca09673..99e55a6c4 100644 --- a/packages/instant/src/containers/latest_error.tsx +++ b/packages/instant/src/containers/latest_error.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { connect } from 'react-redux'; +import { SlideAnimationState } from '../components/animations/slide_animation'; import { SlidingError } from '../components/sliding_error'; import { State } from '../redux/reducer'; import { Asset, DisplayStatus } from '../types'; @@ -9,26 +10,26 @@ import { Asset, DisplayStatus } from '../types'; export interface LatestErrorComponentProps { asset?: Asset; latestErrorMessage?: string; - slidingDirection: 'down' | 'up'; + animationState: SlideAnimationState; } export const LatestErrorComponent: React.StatelessComponent<LatestErrorComponentProps> = props => { if (!props.latestErrorMessage) { return <div />; } - return <SlidingError direction={props.slidingDirection} icon="😢" message={props.latestErrorMessage} />; + return <SlidingError animationState={props.animationState} icon="😢" message={props.latestErrorMessage} />; }; interface ConnectedState { asset?: Asset; latestErrorMessage?: string; - slidingDirection: 'down' | 'up'; + animationState: SlideAnimationState; } export interface LatestErrorProps {} const mapStateToProps = (state: State, _ownProps: LatestErrorProps): ConnectedState => ({ asset: state.selectedAsset, latestErrorMessage: state.latestErrorMessage, - slidingDirection: state.latestErrorDisplayStatus === DisplayStatus.Present ? 'up' : 'down', + animationState: state.latestErrorDisplayStatus === DisplayStatus.Present ? 'slidIn' : 'slidOut', }); export const LatestError = connect(mapStateToProps)(LatestErrorComponent); diff --git a/packages/instant/src/containers/selected_asset_instant_heading.ts b/packages/instant/src/containers/selected_asset_instant_heading.ts index 6b2a29b07..a407279e6 100644 --- a/packages/instant/src/containers/selected_asset_instant_heading.ts +++ b/packages/instant/src/containers/selected_asset_instant_heading.ts @@ -5,11 +5,13 @@ import { connect } from 'react-redux'; import { oc } from 'ts-optchain'; import { State } from '../redux/reducer'; -import { AsyncProcessState, OrderState } from '../types'; +import { AsyncProcessState, ERC20Asset, OrderState } from '../types'; import { InstantHeading } from '../components/instant_heading'; -export interface InstantHeadingProps {} +export interface InstantHeadingProps { + onSelectAssetClick?: (asset?: ERC20Asset) => void; +} interface ConnectedState { selectedAssetAmount?: BigNumber; diff --git a/packages/instant/src/containers/selected_erc20_asset_amount_input.ts b/packages/instant/src/containers/selected_erc20_asset_amount_input.ts index c0245f721..f0e792e2f 100644 --- a/packages/instant/src/containers/selected_erc20_asset_amount_input.ts +++ b/packages/instant/src/containers/selected_erc20_asset_amount_input.ts @@ -19,6 +19,7 @@ import { errorFlasher } from '../util/error_flasher'; export interface SelectedERC20AssetAmountInputProps { fontColor?: ColorOption; startingFontSizePx: number; + onSelectAssetClick?: (asset?: ERC20Asset) => void; } interface ConnectedState { diff --git a/packages/instant/src/data/asset_data_network_mapping.ts b/packages/instant/src/data/asset_data_network_mapping.ts index e8ccbf011..28a04eb8a 100644 --- a/packages/instant/src/data/asset_data_network_mapping.ts +++ b/packages/instant/src/data/asset_data_network_mapping.ts @@ -12,4 +12,8 @@ export const assetDataNetworkMapping: AssetDataByNetwork[] = [ [Network.Mainnet]: '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498', [Network.Kovan]: '0xf47261b00000000000000000000000002002d3812f58e35f0ea1ffbf80a75a38c32175fa', }, + { + [Network.Kovan]: '0xf47261b00000000000000000000000008cb3971b8eb709c14616bd556ff6683019e90d9c', + [Network.Mainnet]: '0xf47261b0000000000000000000000000e94327d07fc17907b4db788e5adf2ed424addff6', + }, ]; diff --git a/packages/instant/src/data/asset_meta_data_map.ts b/packages/instant/src/data/asset_meta_data_map.ts index 3a820a0c4..4e6e15d38 100644 --- a/packages/instant/src/data/asset_meta_data_map.ts +++ b/packages/instant/src/data/asset_meta_data_map.ts @@ -11,4 +11,10 @@ export const assetMetaDataMap: ObjectMap<AssetMetaData> = { primaryColor: 'rgb(54, 50, 60)', symbol: 'zrx', }, + '0xf47261b0000000000000000000000000e94327d07fc17907b4db788e5adf2ed424addff6': { + assetProxyId: AssetProxyId.ERC20, + decimals: 18, + primaryColor: '#512D80', + symbol: 'rep', + }, }; diff --git a/packages/instant/src/style/z_index.ts b/packages/instant/src/style/z_index.ts new file mode 100644 index 000000000..727a5189d --- /dev/null +++ b/packages/instant/src/style/z_index.ts @@ -0,0 +1,5 @@ +export const zIndex = { + errorPopup: 1, + mainContainer: 2, + panel: 3, +}; diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index 62e16dd1d..b43c41739 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -20,7 +20,7 @@ import { Web3ProviderEngine, } from '@0x/subproviders'; import { SignedOrder, Token as ZeroExToken } from '@0x/types'; -import { BigNumber, intervalUtils, logUtils, promisify } from '@0x/utils'; +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'; @@ -38,9 +38,9 @@ import { BlockchainErrs, ContractInstance, Fill, + InjectedProvider, InjectedProviderObservable, InjectedProviderUpdate, - InjectedWeb3, Providers, ProviderType, Side, @@ -83,6 +83,7 @@ export class Blockchain { 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]; @@ -91,48 +92,12 @@ export class Blockchain { } return providerNameIfExists; } - private static _getInjectedWeb3(): InjectedWeb3 { - const injectedWeb3IfExists = (window as any).web3; - // Our core assumptions about the injected web3 object is that it has the following - // properties and methods. - if ( - _.isUndefined(injectedWeb3IfExists) || - _.isUndefined(injectedWeb3IfExists.version) || - _.isUndefined(injectedWeb3IfExists.version.getNetwork) || - _.isUndefined(injectedWeb3IfExists.currentProvider) - ) { - return undefined; - } - return injectedWeb3IfExists; - } - 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.bind(injectedWeb3IfExists.version), - )(), - ); - } catch (err) { - // Ignore error and proceed with networkId undefined - } - } - return networkIdIfExists; - } private static async _getProviderAsync( - injectedWeb3: InjectedWeb3, - networkIdIfExists: number, + injectedProviderIfExists?: InjectedProvider, + networkIdIfExists?: number, shouldUserLedgerProvider: boolean = false, ): Promise<[Provider, LedgerSubprovider | undefined]> { - const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); + const doesInjectedProviderExist = !_.isUndefined(injectedProviderIfExists); const isNetworkIdAvailable = !_.isUndefined(networkIdIfExists); const publicNodeUrlsIfExistsForNetworkId = configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists]; const isPublicNodeAvailableForNetworkId = !_.isUndefined(publicNodeUrlsIfExistsForNetworkId); @@ -156,16 +121,16 @@ export class Blockchain { provider.addProvider(new RedundantSubprovider(rpcSubproviders)); provider.start(); return [provider, ledgerSubprovider]; - } else if (doesInjectedWeb3Exist && isPublicNodeAvailableForNetworkId) { + } 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(injectedWeb3.currentProvider); + const providerName = this._getNameGivenProvider(injectedProviderIfExists); // Wrap Metamask in a compatability wrapper MetamaskSubprovider (to handle inconsistencies) const signerSubprovider = providerName === constants.PROVIDER_NAME_METAMASK - ? new MetamaskSubprovider(injectedWeb3.currentProvider) - : new SignerSubprovider(injectedWeb3.currentProvider); + ? new MetamaskSubprovider(injectedProviderIfExists) + : new SignerSubprovider(injectedProviderIfExists); provider.addProvider(signerSubprovider); provider.addProvider(new FilterSubprovider()); const rpcSubproviders = _.map(publicNodeUrlsIfExistsForNetworkId, publicNodeUrl => { @@ -174,9 +139,9 @@ export class Blockchain { provider.addProvider(new RedundantSubprovider(rpcSubproviders)); provider.start(); return [provider, undefined]; - } else if (doesInjectedWeb3Exist) { + } else if (doesInjectedProviderExist) { // Since no public node for this network, all requests go to injectedWeb3 instance - return [injectedWeb3.currentProvider, undefined]; + 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 @@ -261,7 +226,7 @@ export class Blockchain { 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(); + const networkId = await this._getInjectedProviderNetworkIdIfExistsAsync(); await this._resetOrInitializeAsync(networkId, shouldPollUserAddress, shouldUserLedgerProvider); } public async setProxyAllowanceAsync(token: Token, amountInBaseUnits: BigNumber): Promise<void> { @@ -611,6 +576,45 @@ export class Blockchain { 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.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> { @@ -804,17 +808,17 @@ export class Blockchain { } private async _onPageLoadInitFireAndForgetAsync(): Promise<void> { await utils.onPageLoadPromise; // wait for page to load - const networkIdIfExists = await Blockchain._getInjectedWeb3ProviderNetworkIdIfExistsAsync(); + const networkIdIfExists = await this._getInjectedProviderNetworkIdIfExistsAsync(); this.networkId = !_.isUndefined(networkIdIfExists) ? networkIdIfExists : constants.NETWORK_ID_MAINNET; - const injectedWeb3IfExists = Blockchain._getInjectedWeb3(); - if (!_.isUndefined(injectedWeb3IfExists) && !_.isUndefined(injectedWeb3IfExists.currentProvider)) { - const injectedProviderObservable = injectedWeb3IfExists.currentProvider.publicConfigStore; + 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(injectedWeb3IfExists); + this._updateProviderName(injectedProviderIfExists); const shouldPollUserAddress = true; const shouldUseLedgerProvider = false; this._startWatchingGasPrice(); @@ -851,12 +855,14 @@ export class Blockchain { } this._dispatcher.updateUserWeiBalance(undefined); this.networkId = networkId; - const injectedWeb3IfExists = Blockchain._getInjectedWeb3(); + const injectedProviderIfExists = await this._getInjectedProviderIfExistsAsync(); const [provider, ledgerSubproviderIfExists] = await Blockchain._getProviderAsync( - injectedWeb3IfExists, + injectedProviderIfExists, networkId, shouldUserLedgerProvider, ); + this._web3Wrapper = new Web3Wrapper(provider); + this.networkId = await this._web3Wrapper.getNetworkIdAsync(); if (!_.isUndefined(this._contractWrappers)) { this._contractWrappers.unsubscribeAll(); } @@ -867,7 +873,6 @@ export class Blockchain { 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; @@ -879,7 +884,7 @@ export class Blockchain { const userAddresses = await this._web3Wrapper.getAvailableAddressesAsync(); this._userAddressIfExists = userAddresses[0]; this._dispatcher.updateUserAddress(this._userAddressIfExists); - if (!_.isUndefined(injectedWeb3IfExists)) { + if (!_.isUndefined(injectedProviderIfExists)) { this._dispatcher.updateProviderType(ProviderType.Injected); } await this.fetchTokenInformationAsync(); @@ -888,10 +893,10 @@ export class Blockchain { this._dispatcher.updateNetworkId(networkId); await this._rehydrateStoreWithContractEventsAsync(); } - private _updateProviderName(injectedWeb3IfExists: InjectedWeb3): void { - const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3IfExists); - const providerName = doesInjectedWeb3Exist - ? Blockchain._getNameGivenProvider(injectedWeb3IfExists.currentProvider) + private _updateProviderName(injectedProviderIfExists?: InjectedProvider): void { + const doesInjectedProviderExist = !_.isUndefined(injectedProviderIfExists); + const providerName = doesInjectedProviderExist + ? Blockchain._getNameGivenProvider(injectedProviderIfExists) : constants.PROVIDER_NAME_PUBLIC; this._dispatcher.updateInjectedProviderName(providerName); } diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index 22bfd2cb4..ce4b50a58 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -616,14 +616,6 @@ export interface InjectedProvider extends Provider { publicConfigStore?: InjectedProviderObservable; } -// Minimal expected interface for an injected web3 object -export interface InjectedWeb3 { - currentProvider: InjectedProvider; - version: { - getNetwork(cd: (err: Error, networkId: string) => void): void; - }; -} - export interface TutorialInfo { iconUrl: string; description: string; |