Merge branch 'development' into feature/website/0x-org
This commit is contained in:
@@ -113,20 +113,23 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> {
|
||||
}
|
||||
|
||||
private readonly _renderEthAmount = (): React.ReactNode => {
|
||||
const ethAmount = format.ethBaseUnitAmount(
|
||||
this.props.totalEthBaseUnitAmount,
|
||||
4,
|
||||
<AmountPlaceholder isPulsating={false} color={PLACEHOLDER_COLOR} />,
|
||||
);
|
||||
|
||||
const fontSize = _.isString(ethAmount) && ethAmount.length >= 13 ? '14px' : '16px';
|
||||
return (
|
||||
<Text
|
||||
fontSize="16px"
|
||||
fontSize={fontSize}
|
||||
textAlign="right"
|
||||
width="100%"
|
||||
fontColor={ColorOption.white}
|
||||
fontWeight={500}
|
||||
noWrap={true}
|
||||
>
|
||||
{format.ethBaseUnitAmount(
|
||||
this.props.totalEthBaseUnitAmount,
|
||||
4,
|
||||
<AmountPlaceholder isPulsating={false} color={PLACEHOLDER_COLOR} />,
|
||||
)}
|
||||
{ethAmount}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,124 +4,227 @@ import * as _ from 'lodash';
|
||||
import * as React from 'react';
|
||||
import { oc } from 'ts-optchain';
|
||||
|
||||
import { BIG_NUMBER_ZERO } from '../constants';
|
||||
import { BIG_NUMBER_ZERO, DEFAULT_UNKOWN_ASSET_NAME } from '../constants';
|
||||
import { ColorOption } from '../style/theme';
|
||||
import { BaseCurrency } from '../types';
|
||||
import { format } from '../util/format';
|
||||
|
||||
import { AmountPlaceholder } from './amount_placeholder';
|
||||
import { SectionHeader } from './section_header';
|
||||
|
||||
import { Container } from './ui/container';
|
||||
import { Flex } from './ui/flex';
|
||||
import { Text } from './ui/text';
|
||||
import { Text, TextProps } from './ui/text';
|
||||
|
||||
export interface OrderDetailsProps {
|
||||
buyQuoteInfo?: BuyQuoteInfo;
|
||||
selectedAssetUnitAmount?: BigNumber;
|
||||
ethUsdPrice?: BigNumber;
|
||||
isLoading: boolean;
|
||||
assetName?: string;
|
||||
baseCurrency: BaseCurrency;
|
||||
onBaseCurrencySwitchEth: () => void;
|
||||
onBaseCurrencySwitchUsd: () => void;
|
||||
}
|
||||
export class OrderDetails extends React.Component<OrderDetailsProps> {
|
||||
public render(): React.ReactNode {
|
||||
const { buyQuoteInfo, ethUsdPrice, selectedAssetUnitAmount } = this.props;
|
||||
const buyQuoteAccessor = oc(buyQuoteInfo);
|
||||
const assetEthBaseUnitAmount = buyQuoteAccessor.assetEthAmount();
|
||||
const feeEthBaseUnitAmount = buyQuoteAccessor.feeEthAmount();
|
||||
const totalEthBaseUnitAmount = buyQuoteAccessor.totalEthAmount();
|
||||
const pricePerTokenEth =
|
||||
!_.isUndefined(assetEthBaseUnitAmount) &&
|
||||
!_.isUndefined(selectedAssetUnitAmount) &&
|
||||
!selectedAssetUnitAmount.eq(BIG_NUMBER_ZERO)
|
||||
? assetEthBaseUnitAmount.div(selectedAssetUnitAmount).ceil()
|
||||
: undefined;
|
||||
const shouldShowUsdError = this.props.baseCurrency === BaseCurrency.USD && this._hadErrorFetchingUsdPrice();
|
||||
return (
|
||||
<Container width="100%" flexGrow={1} padding="20px 20px 0px 20px">
|
||||
<Container marginBottom="10px">
|
||||
<Text
|
||||
letterSpacing="1px"
|
||||
fontColor={ColorOption.primaryColor}
|
||||
fontWeight={600}
|
||||
textTransform="uppercase"
|
||||
fontSize="14px"
|
||||
>
|
||||
Order Details
|
||||
</Text>
|
||||
</Container>
|
||||
<EthAmountRow
|
||||
rowLabel="Token Price"
|
||||
ethAmount={pricePerTokenEth}
|
||||
ethUsdPrice={ethUsdPrice}
|
||||
isLoading={this.props.isLoading}
|
||||
/>
|
||||
<EthAmountRow
|
||||
rowLabel="Fee"
|
||||
ethAmount={feeEthBaseUnitAmount}
|
||||
ethUsdPrice={ethUsdPrice}
|
||||
isLoading={this.props.isLoading}
|
||||
/>
|
||||
<EthAmountRow
|
||||
rowLabel="Total Cost"
|
||||
ethAmount={totalEthBaseUnitAmount}
|
||||
ethUsdPrice={ethUsdPrice}
|
||||
shouldEmphasize={true}
|
||||
isLoading={this.props.isLoading}
|
||||
/>
|
||||
<Container marginBottom="10px">{this._renderHeader()}</Container>
|
||||
{shouldShowUsdError ? this._renderErrorFetchingUsdPrice() : this._renderRows()}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
private _renderRows(): React.ReactNode {
|
||||
const { buyQuoteInfo } = this.props;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<OrderDetailsRow
|
||||
labelText={this._assetAmountLabel()}
|
||||
primaryValue={this._displayAmountOrPlaceholder(buyQuoteInfo && buyQuoteInfo.assetEthAmount)}
|
||||
/>
|
||||
<OrderDetailsRow
|
||||
labelText="Fee"
|
||||
primaryValue={this._displayAmountOrPlaceholder(buyQuoteInfo && buyQuoteInfo.feeEthAmount)}
|
||||
/>
|
||||
<OrderDetailsRow
|
||||
labelText="Total Cost"
|
||||
isLabelBold={true}
|
||||
primaryValue={this._displayAmountOrPlaceholder(buyQuoteInfo && buyQuoteInfo.totalEthAmount)}
|
||||
isPrimaryValueBold={true}
|
||||
secondaryValue={this._totalCostSecondaryValue()}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
private _renderErrorFetchingUsdPrice(): React.ReactNode {
|
||||
return (
|
||||
<Text>
|
||||
There was an error fetching the USD price.
|
||||
<Text
|
||||
onClick={this.props.onBaseCurrencySwitchEth}
|
||||
fontWeight={700}
|
||||
fontColor={ColorOption.primaryColor}
|
||||
>
|
||||
Click here
|
||||
</Text>
|
||||
{' to view ETH prices'}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
private _hadErrorFetchingUsdPrice(): boolean {
|
||||
return this.props.ethUsdPrice ? this.props.ethUsdPrice.equals(BIG_NUMBER_ZERO) : false;
|
||||
}
|
||||
|
||||
private _totalCostSecondaryValue(): React.ReactNode {
|
||||
const secondaryCurrency = this.props.baseCurrency === BaseCurrency.USD ? BaseCurrency.ETH : BaseCurrency.USD;
|
||||
|
||||
const canDisplayCurrency =
|
||||
secondaryCurrency === BaseCurrency.ETH ||
|
||||
(secondaryCurrency === BaseCurrency.USD && this.props.ethUsdPrice && !this._hadErrorFetchingUsdPrice());
|
||||
|
||||
if (this.props.buyQuoteInfo && canDisplayCurrency) {
|
||||
return this._displayAmount(secondaryCurrency, this.props.buyQuoteInfo.totalEthAmount);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _displayAmountOrPlaceholder(weiAmount?: BigNumber): React.ReactNode {
|
||||
const { baseCurrency, isLoading } = this.props;
|
||||
|
||||
if (_.isUndefined(weiAmount)) {
|
||||
return (
|
||||
<Container opacity={0.5}>
|
||||
<AmountPlaceholder color={ColorOption.lightGrey} isPulsating={isLoading} />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
return this._displayAmount(baseCurrency, weiAmount);
|
||||
}
|
||||
|
||||
private _displayAmount(currency: BaseCurrency, weiAmount: BigNumber): React.ReactNode {
|
||||
switch (currency) {
|
||||
case BaseCurrency.USD:
|
||||
return format.ethBaseUnitAmountInUsd(weiAmount, this.props.ethUsdPrice, 2, '');
|
||||
case BaseCurrency.ETH:
|
||||
return format.ethBaseUnitAmount(weiAmount, 4, '');
|
||||
}
|
||||
}
|
||||
|
||||
private _assetAmountLabel(): React.ReactNode {
|
||||
const { assetName, baseCurrency } = this.props;
|
||||
const numTokens = this.props.selectedAssetUnitAmount;
|
||||
|
||||
// Display as 0 if we have a selected asset
|
||||
const displayNumTokens =
|
||||
assetName && assetName !== DEFAULT_UNKOWN_ASSET_NAME && _.isUndefined(numTokens)
|
||||
? new BigNumber(0)
|
||||
: numTokens;
|
||||
if (!_.isUndefined(displayNumTokens)) {
|
||||
let numTokensWithSymbol: React.ReactNode = displayNumTokens.toString();
|
||||
if (assetName) {
|
||||
numTokensWithSymbol += ` ${assetName}`;
|
||||
}
|
||||
const pricePerTokenWei = this._pricePerTokenWei();
|
||||
if (pricePerTokenWei) {
|
||||
const atPriceDisplay = (
|
||||
<Text fontColor={ColorOption.lightGrey}>
|
||||
@ {this._displayAmount(baseCurrency, pricePerTokenWei)}
|
||||
</Text>
|
||||
);
|
||||
numTokensWithSymbol = (
|
||||
<React.Fragment>
|
||||
{numTokensWithSymbol} {atPriceDisplay}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
return numTokensWithSymbol;
|
||||
}
|
||||
return 'Token Amount';
|
||||
}
|
||||
|
||||
private _pricePerTokenWei(): BigNumber | undefined {
|
||||
const buyQuoteAccessor = oc(this.props.buyQuoteInfo);
|
||||
const assetTotalInWei = buyQuoteAccessor.assetEthAmount();
|
||||
const selectedAssetUnitAmount = this.props.selectedAssetUnitAmount;
|
||||
return !_.isUndefined(assetTotalInWei) &&
|
||||
!_.isUndefined(selectedAssetUnitAmount) &&
|
||||
!selectedAssetUnitAmount.eq(BIG_NUMBER_ZERO)
|
||||
? assetTotalInWei.div(selectedAssetUnitAmount).ceil()
|
||||
: undefined;
|
||||
}
|
||||
|
||||
private _baseCurrencyChoice(choice: BaseCurrency): React.ReactNode {
|
||||
const onClick =
|
||||
choice === BaseCurrency.ETH ? this.props.onBaseCurrencySwitchEth : this.props.onBaseCurrencySwitchUsd;
|
||||
const isSelected = this.props.baseCurrency === choice;
|
||||
|
||||
const textStyle: TextProps = { onClick, fontSize: '12px' };
|
||||
if (isSelected) {
|
||||
textStyle.fontColor = ColorOption.primaryColor;
|
||||
textStyle.fontWeight = 700;
|
||||
} else {
|
||||
textStyle.fontColor = ColorOption.lightGrey;
|
||||
}
|
||||
return <Text {...textStyle}>{choice}</Text>;
|
||||
}
|
||||
|
||||
private _renderHeader(): React.ReactNode {
|
||||
return (
|
||||
<Flex justify="space-between">
|
||||
<SectionHeader>Order Details</SectionHeader>
|
||||
<Container>
|
||||
{this._baseCurrencyChoice(BaseCurrency.ETH)}
|
||||
<Container marginLeft="5px" marginRight="5px" display="inline">
|
||||
<Text fontSize="12px" fontColor={ColorOption.feintGrey}>
|
||||
/
|
||||
</Text>
|
||||
</Container>
|
||||
{this._baseCurrencyChoice(BaseCurrency.USD)}
|
||||
</Container>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export interface EthAmountRowProps {
|
||||
rowLabel: string;
|
||||
ethAmount?: BigNumber;
|
||||
isEthAmountInBaseUnits?: boolean;
|
||||
ethUsdPrice?: BigNumber;
|
||||
shouldEmphasize?: boolean;
|
||||
isLoading: boolean;
|
||||
export interface OrderDetailsRowProps {
|
||||
labelText: React.ReactNode;
|
||||
isLabelBold?: boolean;
|
||||
isPrimaryValueBold?: boolean;
|
||||
primaryValue: React.ReactNode;
|
||||
secondaryValue?: React.ReactNode;
|
||||
}
|
||||
|
||||
export class EthAmountRow extends React.Component<EthAmountRowProps> {
|
||||
public static defaultProps = {
|
||||
shouldEmphasize: false,
|
||||
isEthAmountInBaseUnits: true,
|
||||
};
|
||||
export class OrderDetailsRow extends React.Component<OrderDetailsRowProps, {}> {
|
||||
public render(): React.ReactNode {
|
||||
const { rowLabel, ethAmount, isEthAmountInBaseUnits, shouldEmphasize, isLoading } = this.props;
|
||||
|
||||
const fontWeight = shouldEmphasize ? 700 : 400;
|
||||
const ethFormatter = isEthAmountInBaseUnits ? format.ethBaseUnitAmount : format.ethUnitAmount;
|
||||
return (
|
||||
<Container padding="10px 0px" borderTop="1px dashed" borderColor={ColorOption.feintGrey}>
|
||||
<Flex justify="space-between">
|
||||
<Text fontWeight={fontWeight} fontColor={ColorOption.grey}>
|
||||
{rowLabel}
|
||||
<Text fontWeight={this.props.isLabelBold ? 700 : 400} fontColor={ColorOption.grey}>
|
||||
{this.props.labelText}
|
||||
</Text>
|
||||
<Container>
|
||||
{this._renderUsdSection()}
|
||||
<Text fontWeight={fontWeight} fontColor={ColorOption.grey}>
|
||||
{ethFormatter(
|
||||
ethAmount,
|
||||
4,
|
||||
<Container opacity={0.5}>
|
||||
<AmountPlaceholder color={ColorOption.lightGrey} isPulsating={isLoading} />
|
||||
</Container>,
|
||||
)}
|
||||
</Text>
|
||||
</Container>
|
||||
<Container>{this._renderValues()}</Container>
|
||||
</Flex>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
private _renderUsdSection(): React.ReactNode {
|
||||
const usdFormatter = this.props.isEthAmountInBaseUnits
|
||||
? format.ethBaseUnitAmountInUsd
|
||||
: format.ethUnitAmountInUsd;
|
||||
const shouldHideUsdPriceSection = _.isUndefined(this.props.ethUsdPrice) || _.isUndefined(this.props.ethAmount);
|
||||
return shouldHideUsdPriceSection ? null : (
|
||||
|
||||
private _renderValues(): React.ReactNode {
|
||||
const secondaryValueNode: React.ReactNode = this.props.secondaryValue && (
|
||||
<Container marginRight="3px" display="inline-block">
|
||||
<Text fontColor={ColorOption.lightGrey}>
|
||||
({usdFormatter(this.props.ethAmount, this.props.ethUsdPrice)})
|
||||
</Text>
|
||||
<Text fontColor={ColorOption.lightGrey}>({this.props.secondaryValue})</Text>
|
||||
</Container>
|
||||
);
|
||||
return (
|
||||
<React.Fragment>
|
||||
{secondaryValueNode}
|
||||
<Text fontWeight={this.props.isPrimaryValueBold ? 700 : 400}>{this.props.primaryValue}</Text>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { envUtil } from '../util/env';
|
||||
import { CoinbaseWalletLogo } from './coinbase_wallet_logo';
|
||||
import { MetaMaskLogo } from './meta_mask_logo';
|
||||
import { PaymentMethodDropdown } from './payment_method_dropdown';
|
||||
import { SectionHeader } from './section_header';
|
||||
import { Circle } from './ui/circle';
|
||||
import { Container } from './ui/container';
|
||||
import { Flex } from './ui/flex';
|
||||
@@ -29,15 +30,7 @@ export class PaymentMethod extends React.Component<PaymentMethodProps> {
|
||||
<Container width="100%" height="120px" padding="20px 20px 0px 20px">
|
||||
<Container marginBottom="12px">
|
||||
<Flex justify="space-between">
|
||||
<Text
|
||||
letterSpacing="1px"
|
||||
fontColor={ColorOption.primaryColor}
|
||||
fontWeight={600}
|
||||
textTransform="uppercase"
|
||||
fontSize="14px"
|
||||
>
|
||||
{this._renderTitleText()}
|
||||
</Text>
|
||||
<SectionHeader>{this._renderTitleText()}</SectionHeader>
|
||||
{this._renderTitleLabel()}
|
||||
</Flex>
|
||||
</Container>
|
||||
|
||||
20
packages/instant/src/components/section_header.tsx
Normal file
20
packages/instant/src/components/section_header.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { ColorOption } from '../style/theme';
|
||||
|
||||
import { Text } from './ui/text';
|
||||
|
||||
export interface SectionHeaderProps {}
|
||||
export const SectionHeader: React.StatelessComponent<SectionHeaderProps> = props => {
|
||||
return (
|
||||
<Text
|
||||
letterSpacing="1px"
|
||||
fontColor={ColorOption.primaryColor}
|
||||
fontWeight={600}
|
||||
textTransform="uppercase"
|
||||
fontSize="12px"
|
||||
>
|
||||
{props.children}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
@@ -122,6 +122,7 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
|
||||
window,
|
||||
state.selectedAsset,
|
||||
this.props.affiliateInfo,
|
||||
state.baseCurrency,
|
||||
),
|
||||
);
|
||||
analytics.trackInstantOpened();
|
||||
|
||||
@@ -17,6 +17,7 @@ export const ONE_MINUTE_MS = ONE_SECOND_MS * 60;
|
||||
export const GIT_SHA = process.env.GIT_SHA;
|
||||
export const NODE_ENV = process.env.NODE_ENV;
|
||||
export const NPM_PACKAGE_VERSION = process.env.NPM_PACKAGE_VERSION;
|
||||
export const DEFAULT_UNKOWN_ASSET_NAME = '???';
|
||||
export const ACCOUNT_UPDATE_INTERVAL_TIME_MS = ONE_SECOND_MS * 5;
|
||||
export const BUY_QUOTE_UPDATE_INTERVAL_TIME_MS = ONE_SECOND_MS * 15;
|
||||
export const DEFAULT_GAS_PRICE = GWEI_IN_WEI.mul(6);
|
||||
|
||||
@@ -1,32 +1,41 @@
|
||||
import { BuyQuoteInfo } from '@0x/asset-buyer';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
import * as React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import { oc } from 'ts-optchain';
|
||||
|
||||
import { Action, actions } from '../redux/actions';
|
||||
import { State } from '../redux/reducer';
|
||||
|
||||
import { OrderDetails } from '../components/order_details';
|
||||
import { AsyncProcessState } from '../types';
|
||||
import { OrderDetails, OrderDetailsProps } from '../components/order_details';
|
||||
import { AsyncProcessState, BaseCurrency, Omit } from '../types';
|
||||
import { assetUtils } from '../util/asset';
|
||||
|
||||
export interface LatestBuyQuoteOrderDetailsProps {}
|
||||
|
||||
interface ConnectedState {
|
||||
buyQuoteInfo?: BuyQuoteInfo;
|
||||
selectedAssetUnitAmount?: BigNumber;
|
||||
ethUsdPrice?: BigNumber;
|
||||
isLoading: boolean;
|
||||
}
|
||||
type DispatchProperties = 'onBaseCurrencySwitchEth' | 'onBaseCurrencySwitchUsd';
|
||||
|
||||
interface ConnectedState extends Omit<OrderDetailsProps, DispatchProperties> {}
|
||||
const mapStateToProps = (state: State, _ownProps: LatestBuyQuoteOrderDetailsProps): ConnectedState => ({
|
||||
// use the worst case quote info
|
||||
buyQuoteInfo: oc(state).latestBuyQuote.worstCaseQuoteInfo(),
|
||||
selectedAssetUnitAmount: state.selectedAssetUnitAmount,
|
||||
ethUsdPrice: state.ethUsdPrice,
|
||||
isLoading: state.quoteRequestState === AsyncProcessState.Pending,
|
||||
assetName: assetUtils.bestNameForAsset(state.selectedAsset),
|
||||
baseCurrency: state.baseCurrency,
|
||||
});
|
||||
|
||||
interface ConnectedDispatch extends Pick<OrderDetailsProps, DispatchProperties> {}
|
||||
const mapDispatchToProps = (dispatch: Dispatch<Action>): ConnectedDispatch => ({
|
||||
onBaseCurrencySwitchEth: () => {
|
||||
dispatch(actions.updateBaseCurrency(BaseCurrency.ETH));
|
||||
},
|
||||
onBaseCurrencySwitchUsd: () => {
|
||||
dispatch(actions.updateBaseCurrency(BaseCurrency.USD));
|
||||
},
|
||||
});
|
||||
|
||||
export interface LatestBuyQuoteOrderDetailsProps {}
|
||||
export const LatestBuyQuoteOrderDetails: React.ComponentClass<LatestBuyQuoteOrderDetailsProps> = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(OrderDetails);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { BuyQuote } from '@0x/asset-buyer';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { ActionsUnion, AddressAndEthBalanceInWei, Asset, StandardSlidingPanelContent } from '../types';
|
||||
import { ActionsUnion, AddressAndEthBalanceInWei, Asset, BaseCurrency, StandardSlidingPanelContent } from '../types';
|
||||
|
||||
export interface PlainAction<T extends string> {
|
||||
type: T;
|
||||
@@ -43,6 +43,7 @@ export enum ActionTypes {
|
||||
RESET_AMOUNT = 'RESET_AMOUNT',
|
||||
OPEN_STANDARD_SLIDING_PANEL = 'OPEN_STANDARD_SLIDING_PANEL',
|
||||
CLOSE_STANDARD_SLIDING_PANEL = 'CLOSE_STANDARD_SLIDING_PANEL',
|
||||
UPDATE_BASE_CURRENCY = 'UPDATE_BASE_CURRENCY',
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
@@ -72,4 +73,5 @@ export const actions = {
|
||||
openStandardSlidingPanel: (content: StandardSlidingPanelContent) =>
|
||||
createAction(ActionTypes.OPEN_STANDARD_SLIDING_PANEL, content),
|
||||
closeStandardSlidingPanel: () => createAction(ActionTypes.CLOSE_STANDARD_SLIDING_PANEL),
|
||||
updateBaseCurrency: (baseCurrency: BaseCurrency) => createAction(ActionTypes.UPDATE_BASE_CURRENCY, baseCurrency),
|
||||
};
|
||||
|
||||
@@ -99,6 +99,9 @@ export const analyticsMiddleware: Middleware = store => next => middlewareAction
|
||||
analytics.trackInstallWalletModalClosed();
|
||||
}
|
||||
break;
|
||||
case ActionTypes.UPDATE_BASE_CURRENCY:
|
||||
analytics.trackBaseCurrencyChanged(curState.baseCurrency);
|
||||
analytics.addEventProperties({ baseCurrency: curState.baseCurrency });
|
||||
}
|
||||
|
||||
return nextAction;
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as _ from 'lodash';
|
||||
import { Dispatch } from 'redux';
|
||||
|
||||
import { BIG_NUMBER_ZERO } from '../constants';
|
||||
import { AccountState, ERC20Asset, OrderProcessState, ProviderState, QuoteFetchOrigin } from '../types';
|
||||
import { AccountState, BaseCurrency, ERC20Asset, OrderProcessState, ProviderState, QuoteFetchOrigin } from '../types';
|
||||
import { analytics } from '../util/analytics';
|
||||
import { assetUtils } from '../util/asset';
|
||||
import { buyQuoteUpdater } from '../util/buy_quote_updater';
|
||||
@@ -24,7 +24,9 @@ export const asyncData = {
|
||||
const errorMessage = 'Error fetching ETH/USD price';
|
||||
errorFlasher.flashNewErrorMessage(dispatch, errorMessage);
|
||||
dispatch(actions.updateEthUsdPrice(BIG_NUMBER_ZERO));
|
||||
dispatch(actions.updateBaseCurrency(BaseCurrency.ETH));
|
||||
errorReporter.report(e);
|
||||
analytics.trackUsdPriceFailed();
|
||||
}
|
||||
},
|
||||
fetchAvailableAssetDatasAndDispatchToStore: async (state: State, dispatch: Dispatch) => {
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
Asset,
|
||||
AssetMetaData,
|
||||
AsyncProcessState,
|
||||
BaseCurrency,
|
||||
DisplayStatus,
|
||||
Network,
|
||||
OrderProcessState,
|
||||
@@ -33,6 +34,7 @@ export interface DefaultState {
|
||||
latestErrorDisplayStatus: DisplayStatus;
|
||||
quoteRequestState: AsyncProcessState;
|
||||
standardSlidingPanelSettings: StandardSlidingPanelSettings;
|
||||
baseCurrency: BaseCurrency;
|
||||
}
|
||||
|
||||
// State that is required but needs to be derived from the props
|
||||
@@ -64,6 +66,7 @@ export const DEFAULT_STATE: DefaultState = {
|
||||
animationState: 'none',
|
||||
content: StandardSlidingPanelContent.None,
|
||||
},
|
||||
baseCurrency: BaseCurrency.USD,
|
||||
};
|
||||
|
||||
export const createReducer = (initialState: State) => {
|
||||
@@ -243,6 +246,11 @@ export const createReducer = (initialState: State) => {
|
||||
animationState: 'slidOut',
|
||||
},
|
||||
};
|
||||
case ActionTypes.UPDATE_BASE_CURRENCY:
|
||||
return {
|
||||
...state,
|
||||
baseCurrency: action.data,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ export enum QuoteFetchOrigin {
|
||||
Heartbeat = 'Heartbeat',
|
||||
}
|
||||
|
||||
export enum BaseCurrency {
|
||||
USD = 'USD',
|
||||
ETH = 'ETH',
|
||||
}
|
||||
|
||||
export interface SimulatedProgress {
|
||||
startTimeUnix: number;
|
||||
expectedEndTimeUnix: number;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { GIT_SHA, HEAP_ENABLED, INSTANT_DISCHARGE_TARGET, NODE_ENV, NPM_PACKAGE_
|
||||
import {
|
||||
AffiliateInfo,
|
||||
Asset,
|
||||
BaseCurrency,
|
||||
Network,
|
||||
OrderProcessState,
|
||||
OrderSource,
|
||||
@@ -37,6 +38,7 @@ enum EventNames {
|
||||
ACCOUNT_UNLOCK_REQUESTED = 'Account - Unlock Requested',
|
||||
ACCOUNT_UNLOCK_DENIED = 'Account - Unlock Denied',
|
||||
ACCOUNT_ADDRESS_CHANGED = 'Account - Address Changed',
|
||||
BASE_CURRENCY_CHANGED = 'Base Currency - Changed',
|
||||
PAYMENT_METHOD_DROPDOWN_OPENED = 'Payment Method - Dropdown Opened',
|
||||
PAYMENT_METHOD_OPENED_ETHERSCAN = 'Payment Method - Opened Etherscan',
|
||||
PAYMENT_METHOD_COPIED_ADDRESS = 'Payment Method - Copied Address',
|
||||
@@ -47,6 +49,7 @@ enum EventNames {
|
||||
BUY_TX_SUBMITTED = 'Buy - Tx Submitted',
|
||||
BUY_TX_SUCCEEDED = 'Buy - Tx Succeeded',
|
||||
BUY_TX_FAILED = 'Buy - Tx Failed',
|
||||
USD_PRICE_FETCH_FAILED = 'USD Price - Fetch Failed',
|
||||
INSTALL_WALLET_CLICKED = 'Install Wallet - Clicked',
|
||||
INSTALL_WALLET_MODAL_OPENED = 'Install Wallet - Modal - Opened',
|
||||
INSTALL_WALLET_MODAL_CLICKED_EXPLANATION = 'Install Wallet - Modal - Clicked Explanation',
|
||||
@@ -118,6 +121,7 @@ export interface AnalyticsEventOptions {
|
||||
selectedAssetSymbol?: string;
|
||||
selectedAssetData?: string;
|
||||
selectedAssetDecimals?: number;
|
||||
baseCurrency?: string;
|
||||
}
|
||||
export enum TokenSelectorClosedVia {
|
||||
ClickedX = 'Clicked X',
|
||||
@@ -141,6 +145,7 @@ export const analytics = {
|
||||
window: Window,
|
||||
selectedAsset?: Asset,
|
||||
affiliateInfo?: AffiliateInfo,
|
||||
baseCurrency?: BaseCurrency,
|
||||
): AnalyticsEventOptions => {
|
||||
const affiliateAddress = affiliateInfo ? affiliateInfo.feeRecipient : 'none';
|
||||
const affiliateFeePercent = affiliateInfo ? parseFloat(affiliateInfo.feePercentage.toFixed(4)) : 0;
|
||||
@@ -159,6 +164,7 @@ export const analytics = {
|
||||
selectedAssetName: selectedAsset ? selectedAsset.metaData.name : 'none',
|
||||
selectedAssetData: selectedAsset ? selectedAsset.assetData : 'none',
|
||||
instantEnvironment: INSTANT_DISCHARGE_TARGET || `Local ${NODE_ENV}`,
|
||||
baseCurrency,
|
||||
};
|
||||
return eventOptions;
|
||||
},
|
||||
@@ -170,6 +176,8 @@ export const analytics = {
|
||||
trackAccountUnlockDenied: trackingEventFnWithoutPayload(EventNames.ACCOUNT_UNLOCK_DENIED),
|
||||
trackAccountAddressChanged: (address: string) =>
|
||||
trackingEventFnWithPayload(EventNames.ACCOUNT_ADDRESS_CHANGED)({ address }),
|
||||
trackBaseCurrencyChanged: (currencyChangedTo: BaseCurrency) =>
|
||||
trackingEventFnWithPayload(EventNames.BASE_CURRENCY_CHANGED)({ currencyChangedTo }),
|
||||
trackPaymentMethodDropdownOpened: trackingEventFnWithoutPayload(EventNames.PAYMENT_METHOD_DROPDOWN_OPENED),
|
||||
trackPaymentMethodOpenedEtherscan: trackingEventFnWithoutPayload(EventNames.PAYMENT_METHOD_OPENED_ETHERSCAN),
|
||||
trackPaymentMethodCopiedAddress: trackingEventFnWithoutPayload(EventNames.PAYMENT_METHOD_COPIED_ADDRESS),
|
||||
@@ -230,4 +238,5 @@ export const analytics = {
|
||||
fetchOrigin,
|
||||
});
|
||||
},
|
||||
trackUsdPriceFailed: trackingEventFnWithoutPayload(EventNames.USD_PRICE_FETCH_FAILED),
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { AssetBuyerError } from '@0x/asset-buyer';
|
||||
import { AssetProxyId, ObjectMap } from '@0x/types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { DEFAULT_UNKOWN_ASSET_NAME } from '../constants';
|
||||
import { assetDataNetworkMapping } from '../data/asset_data_network_mapping';
|
||||
import { Asset, AssetMetaData, ERC20Asset, Network, ZeroExInstantError } from '../types';
|
||||
|
||||
@@ -71,7 +72,7 @@ export const assetUtils = {
|
||||
}
|
||||
return metaData;
|
||||
},
|
||||
bestNameForAsset: (asset?: Asset, defaultName: string = '???'): string => {
|
||||
bestNameForAsset: (asset?: Asset, defaultName: string = DEFAULT_UNKOWN_ASSET_NAME): string => {
|
||||
if (_.isUndefined(asset)) {
|
||||
return defaultName;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { BigNumber } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { ETH_DECIMALS } from '../constants';
|
||||
import { BIG_NUMBER_ZERO, ETH_DECIMALS } from '../constants';
|
||||
|
||||
export const format = {
|
||||
ethBaseUnitAmount: (
|
||||
@@ -20,24 +20,38 @@ export const format = {
|
||||
ethUnitAmount?: BigNumber,
|
||||
decimalPlaces: number = 4,
|
||||
defaultText: React.ReactNode = '0 ETH',
|
||||
minUnitAmountToDisplay: BigNumber = new BigNumber('0.00001'),
|
||||
): React.ReactNode => {
|
||||
if (_.isUndefined(ethUnitAmount)) {
|
||||
return defaultText;
|
||||
}
|
||||
const roundedAmount = ethUnitAmount.round(decimalPlaces).toDigits(decimalPlaces);
|
||||
return `${roundedAmount} ETH`;
|
||||
let roundedAmount = ethUnitAmount.round(decimalPlaces).toDigits(decimalPlaces);
|
||||
|
||||
if (roundedAmount.eq(BIG_NUMBER_ZERO) && ethUnitAmount.greaterThan(BIG_NUMBER_ZERO)) {
|
||||
// Sometimes for small ETH amounts (i.e. 0.000045) the amount rounded to 4 decimalPlaces is 0
|
||||
// If that is the case, show to 1 significant digit
|
||||
roundedAmount = new BigNumber(ethUnitAmount.toPrecision(1));
|
||||
}
|
||||
|
||||
const displayAmount =
|
||||
roundedAmount.greaterThan(BIG_NUMBER_ZERO) && roundedAmount.lessThan(minUnitAmountToDisplay)
|
||||
? `< ${minUnitAmountToDisplay.toString()}`
|
||||
: roundedAmount.toString();
|
||||
|
||||
return `${displayAmount} ETH`;
|
||||
},
|
||||
ethBaseUnitAmountInUsd: (
|
||||
ethBaseUnitAmount?: BigNumber,
|
||||
ethUsdPrice?: BigNumber,
|
||||
decimalPlaces: number = 2,
|
||||
defaultText: React.ReactNode = '$0.00',
|
||||
minUnitAmountToDisplay: BigNumber = new BigNumber('0.00001'),
|
||||
): React.ReactNode => {
|
||||
if (_.isUndefined(ethBaseUnitAmount) || _.isUndefined(ethUsdPrice)) {
|
||||
return defaultText;
|
||||
}
|
||||
const ethUnitAmount = Web3Wrapper.toUnitAmount(ethBaseUnitAmount, ETH_DECIMALS);
|
||||
return format.ethUnitAmountInUsd(ethUnitAmount, ethUsdPrice, decimalPlaces);
|
||||
return format.ethUnitAmountInUsd(ethUnitAmount, ethUsdPrice, decimalPlaces, minUnitAmountToDisplay);
|
||||
},
|
||||
ethUnitAmountInUsd: (
|
||||
ethUnitAmount?: BigNumber,
|
||||
@@ -48,7 +62,13 @@ export const format = {
|
||||
if (_.isUndefined(ethUnitAmount) || _.isUndefined(ethUsdPrice)) {
|
||||
return defaultText;
|
||||
}
|
||||
return `$${ethUnitAmount.mul(ethUsdPrice).toFixed(decimalPlaces)}`;
|
||||
const rawUsdPrice = ethUnitAmount.mul(ethUsdPrice);
|
||||
const roundedUsdPrice = rawUsdPrice.toFixed(decimalPlaces);
|
||||
if (roundedUsdPrice === '0.00' && rawUsdPrice.gt(BIG_NUMBER_ZERO)) {
|
||||
return '<$0.01';
|
||||
} else {
|
||||
return `$${roundedUsdPrice}`;
|
||||
}
|
||||
},
|
||||
ethAddress: (address: string): string => {
|
||||
return `0x${address.slice(2, 7)}…${address.slice(-5)}`;
|
||||
|
||||
@@ -41,6 +41,18 @@ describe('format', () => {
|
||||
it('converts BigNumber(5.3014059295032) to the string `5.301 ETH`', () => {
|
||||
expect(format.ethUnitAmount(BIG_NUMBER_IRRATIONAL)).toBe('5.301 ETH');
|
||||
});
|
||||
it('shows 1 significant digit when rounded amount would be 0', () => {
|
||||
expect(format.ethUnitAmount(new BigNumber(0.00003))).toBe('0.00003 ETH');
|
||||
expect(format.ethUnitAmount(new BigNumber(0.000034))).toBe('0.00003 ETH');
|
||||
expect(format.ethUnitAmount(new BigNumber(0.000035))).toBe('0.00004 ETH');
|
||||
});
|
||||
it('shows < 0.00001 when hits threshold', () => {
|
||||
expect(format.ethUnitAmount(new BigNumber(0.000011))).toBe('0.00001 ETH');
|
||||
expect(format.ethUnitAmount(new BigNumber(0.00001))).toBe('0.00001 ETH');
|
||||
expect(format.ethUnitAmount(new BigNumber(0.000009))).toBe('< 0.00001 ETH');
|
||||
expect(format.ethUnitAmount(new BigNumber(0.0000000009))).toBe('< 0.00001 ETH');
|
||||
expect(format.ethUnitAmount(new BigNumber(0))).toBe('0 ETH');
|
||||
});
|
||||
it('returns defaultText param when ethUnitAmount is not defined', () => {
|
||||
const defaultText = 'defaultText';
|
||||
expect(format.ethUnitAmount(undefined, 4, defaultText)).toBe(defaultText);
|
||||
@@ -86,6 +98,12 @@ describe('format', () => {
|
||||
it('correctly formats 5.3014059295032 ETH to usd according to some price', () => {
|
||||
expect(format.ethUnitAmountInUsd(BIG_NUMBER_IRRATIONAL, BIG_NUMBER_FAKE_ETH_USD_PRICE)).toBe('$13.43');
|
||||
});
|
||||
it('correctly formats amount that is less than 1 cent', () => {
|
||||
expect(format.ethUnitAmountInUsd(new BigNumber(0.000001), BIG_NUMBER_FAKE_ETH_USD_PRICE)).toBe('<$0.01');
|
||||
});
|
||||
it('correctly formats exactly 1 cent', () => {
|
||||
expect(format.ethUnitAmountInUsd(new BigNumber(0.0039), BIG_NUMBER_FAKE_ETH_USD_PRICE)).toBe('$0.01');
|
||||
});
|
||||
it('returns defaultText param when ethUnitAmountInUsd or ethUsdPrice is not defined', () => {
|
||||
const defaultText = 'defaultText';
|
||||
expect(format.ethUnitAmountInUsd(undefined, undefined, 2, defaultText)).toBe(defaultText);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// tslint:disable:no-console
|
||||
import { getContractAddressesForNetworkOrThrow } from '@0x/contract-addresses';
|
||||
import { web3Factory } from '@0x/dev-utils';
|
||||
import { Web3ProviderEngine } from '@0x/subproviders';
|
||||
import { logUtils } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import 'reflect-metadata';
|
||||
import { Connection, ConnectionOptions, createConnection, Repository } from 'typeorm';
|
||||
import { Connection, ConnectionOptions, createConnection } from 'typeorm';
|
||||
|
||||
import { ERC20EventsSource } from '../data_sources/contract-wrappers/erc20_events';
|
||||
import { ERC20ApprovalEvent } from '../entities';
|
||||
@@ -16,33 +16,63 @@ const NETWORK_ID = 1;
|
||||
const START_BLOCK_OFFSET = 100; // Number of blocks before the last known block to consider when updating fill events.
|
||||
const BATCH_SAVE_SIZE = 1000; // Number of events to save at once.
|
||||
const BLOCK_FINALITY_THRESHOLD = 10; // When to consider blocks as final. Used to compute default endBlock.
|
||||
const WETH_START_BLOCK = 4719568; // Block number when the WETH contract was deployed.
|
||||
|
||||
let connection: Connection;
|
||||
|
||||
interface Token {
|
||||
// name is used for logging only.
|
||||
name: string;
|
||||
address: string;
|
||||
defaultStartBlock: number;
|
||||
}
|
||||
|
||||
const tokensToGetApprovalEvents: Token[] = [
|
||||
{
|
||||
name: 'WETH',
|
||||
address: getContractAddressesForNetworkOrThrow(NETWORK_ID).etherToken,
|
||||
defaultStartBlock: 4719568, // Block when the WETH contract was deployed.
|
||||
},
|
||||
{
|
||||
name: 'ZRX',
|
||||
address: getContractAddressesForNetworkOrThrow(NETWORK_ID).zrxToken,
|
||||
defaultStartBlock: 4145415, // Block when the ZRX contract was deployed.
|
||||
},
|
||||
{
|
||||
name: 'DAI',
|
||||
address: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',
|
||||
defaultStartBlock: 4752008, // Block when the DAI contract was deployed.
|
||||
},
|
||||
];
|
||||
|
||||
(async () => {
|
||||
connection = await createConnection(ormConfig as ConnectionOptions);
|
||||
const provider = web3Factory.getRpcProvider({
|
||||
rpcUrl: INFURA_ROOT_URL,
|
||||
});
|
||||
const endBlock = await calculateEndBlockAsync(provider);
|
||||
await getAndSaveWETHApprovalEventsAsync(provider, endBlock);
|
||||
for (const token of tokensToGetApprovalEvents) {
|
||||
await getAndSaveApprovalEventsAsync(provider, token, endBlock);
|
||||
}
|
||||
process.exit(0);
|
||||
})().catch(handleError);
|
||||
|
||||
async function getAndSaveWETHApprovalEventsAsync(provider: Web3ProviderEngine, endBlock: number): Promise<void> {
|
||||
console.log('Checking existing approval events...');
|
||||
async function getAndSaveApprovalEventsAsync(
|
||||
provider: Web3ProviderEngine,
|
||||
token: Token,
|
||||
endBlock: number,
|
||||
): Promise<void> {
|
||||
logUtils.log(`Getting approval events for ${token.name}...`);
|
||||
logUtils.log('Checking existing approval events...');
|
||||
const repository = connection.getRepository(ERC20ApprovalEvent);
|
||||
const startBlock = (await getStartBlockAsync(repository)) || WETH_START_BLOCK;
|
||||
const startBlock = (await getStartBlockAsync(token)) || token.defaultStartBlock;
|
||||
|
||||
console.log(`Getting WETH approval events starting at ${startBlock}...`);
|
||||
const wethTokenAddress = getContractAddressesForNetworkOrThrow(NETWORK_ID).etherToken;
|
||||
const eventsSource = new ERC20EventsSource(provider, NETWORK_ID, wethTokenAddress);
|
||||
logUtils.log(`Getting approval events starting at ${startBlock}...`);
|
||||
const eventsSource = new ERC20EventsSource(provider, NETWORK_ID, token.address);
|
||||
const eventLogs = await eventsSource.getApprovalEventsAsync(startBlock, endBlock);
|
||||
|
||||
console.log(`Parsing ${eventLogs.length} WETH approval events...`);
|
||||
logUtils.log(`Parsing ${eventLogs.length} approval events...`);
|
||||
const events = parseERC20ApprovalEvents(eventLogs);
|
||||
console.log(`Retrieved and parsed ${events.length} total WETH approval events.`);
|
||||
logUtils.log(`Retrieved and parsed ${events.length} total approval events.`);
|
||||
await repository.save(events, { chunk: Math.ceil(events.length / BATCH_SAVE_SIZE) });
|
||||
}
|
||||
|
||||
@@ -52,15 +82,15 @@ async function calculateEndBlockAsync(provider: Web3ProviderEngine): Promise<num
|
||||
return currentBlock - BLOCK_FINALITY_THRESHOLD;
|
||||
}
|
||||
|
||||
async function getStartBlockAsync(repository: Repository<ERC20ApprovalEvent>): Promise<number | null> {
|
||||
const fillEventCount = await repository.count();
|
||||
if (fillEventCount === 0) {
|
||||
console.log(`No existing approval events found.`);
|
||||
async function getStartBlockAsync(token: Token): Promise<number | null> {
|
||||
const queryResult = await connection.query(
|
||||
`SELECT block_number FROM raw.erc20_approval_events WHERE token_address = $1 ORDER BY block_number DESC LIMIT 1`,
|
||||
[token.address],
|
||||
);
|
||||
if (queryResult.length === 0) {
|
||||
logUtils.log(`No existing approval events found for ${token.name}.`);
|
||||
return null;
|
||||
}
|
||||
const queryResult = await connection.query(
|
||||
`SELECT block_number FROM raw.erc20_approval_events ORDER BY block_number DESC LIMIT 1`,
|
||||
);
|
||||
const lastKnownBlock = queryResult[0].block_number;
|
||||
return lastKnownBlock - START_BLOCK_OFFSET;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Web3Source } from '../data_sources/web3';
|
||||
import { Block } from '../entities';
|
||||
import * as ormConfig from '../ormconfig';
|
||||
import { parseBlock } from '../parsers/web3';
|
||||
import { EXCHANGE_START_BLOCK, handleError, INFURA_ROOT_URL } from '../utils';
|
||||
import { handleError, INFURA_ROOT_URL } from '../utils';
|
||||
|
||||
// Number of blocks to save at once.
|
||||
const BATCH_SAVE_SIZE = 1000;
|
||||
@@ -37,22 +37,19 @@ interface MissingBlocksResponse {
|
||||
|
||||
async function getAllMissingBlocksAsync(web3Source: Web3Source): Promise<void> {
|
||||
const blocksRepository = connection.getRepository(Block);
|
||||
let fromBlock = EXCHANGE_START_BLOCK;
|
||||
while (true) {
|
||||
const blockNumbers = await getMissingBlockNumbersAsync(fromBlock);
|
||||
const blockNumbers = await getMissingBlockNumbersAsync();
|
||||
if (blockNumbers.length === 0) {
|
||||
// There are no more missing blocks. We're done.
|
||||
break;
|
||||
}
|
||||
await getAndSaveBlocksAsync(web3Source, blocksRepository, blockNumbers);
|
||||
fromBlock = Math.max(...blockNumbers) + 1;
|
||||
}
|
||||
const totalBlocks = await blocksRepository.count();
|
||||
console.log(`Done saving blocks. There are now ${totalBlocks} total blocks.`);
|
||||
}
|
||||
|
||||
async function getMissingBlockNumbersAsync(fromBlock: number): Promise<number[]> {
|
||||
console.log(`Checking for missing blocks starting at ${fromBlock}...`);
|
||||
async function getMissingBlockNumbersAsync(): Promise<number[]> {
|
||||
// Note(albrow): The easiest way to get all the blocks we need is to
|
||||
// consider all the events tables together in a single query. If this query
|
||||
// gets too slow, we should consider re-architecting so that we can work on
|
||||
@@ -66,13 +63,12 @@ async function getMissingBlockNumbersAsync(fromBlock: number): Promise<number[]>
|
||||
)
|
||||
SELECT DISTINCT(block_number) FROM all_events
|
||||
WHERE block_number NOT IN (SELECT number FROM raw.blocks)
|
||||
AND block_number >= $1
|
||||
ORDER BY block_number ASC LIMIT $2`,
|
||||
[fromBlock, MAX_BLOCKS_PER_QUERY],
|
||||
ORDER BY block_number ASC LIMIT $1`,
|
||||
[MAX_BLOCKS_PER_QUERY],
|
||||
)) as MissingBlocksResponse[];
|
||||
const blockNumberStrings = R.pluck('block_number', response);
|
||||
const blockNumbers = R.map(parseInt, blockNumberStrings);
|
||||
console.log(`Found ${blockNumbers.length} missing blocks in the given range.`);
|
||||
console.log(`Found ${blockNumbers.length} missing blocks.`);
|
||||
return blockNumbers;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export const SidebarHeader: React.StatelessComponent<SidebarHeaderProps> = ({
|
||||
return (
|
||||
<Container>
|
||||
<Container className="flex justify-bottom">
|
||||
<Container className="left pl1" width="150px">
|
||||
<Container className="col col-7 pl1">
|
||||
<Text
|
||||
fontColor={colors.lightLinkBlue}
|
||||
fontSize={screenWidth === ScreenWidths.Sm ? '20px' : '22px'}
|
||||
@@ -37,12 +37,14 @@ export const SidebarHeader: React.StatelessComponent<SidebarHeaderProps> = ({
|
||||
{!_.isUndefined(docsVersion) &&
|
||||
!_.isUndefined(availableDocVersions) &&
|
||||
!_.isUndefined(onVersionSelected) && (
|
||||
<div className="right" style={{ alignSelf: 'flex-end', paddingBottom: 4 }}>
|
||||
<VersionDropDown
|
||||
selectedVersion={docsVersion}
|
||||
versions={availableDocVersions}
|
||||
onVersionSelected={onVersionSelected}
|
||||
/>
|
||||
<div className="col col-5 pl1" style={{ alignSelf: 'flex-end', paddingBottom: 4 }}>
|
||||
<Container className="right">
|
||||
<VersionDropDown
|
||||
selectedVersion={docsVersion}
|
||||
versions={availableDocVersions}
|
||||
onVersionSelected={onVersionSelected}
|
||||
/>
|
||||
</Container>
|
||||
</div>
|
||||
)}
|
||||
</Container>
|
||||
|
||||
@@ -24,7 +24,7 @@ const docsInfoConfig: DocsInfoConfig = {
|
||||
id: DocPackages.OrderWatcher,
|
||||
packageName: '@0x/order-watcher',
|
||||
type: SupportedDocJson.TypeDoc,
|
||||
displayName: 'OrderWatcher',
|
||||
displayName: 'Order Watcher',
|
||||
packageUrl: 'https://github.com/0xProject/0x-monorepo',
|
||||
markdownMenu: {
|
||||
'getting-started': [markdownSections.introduction, markdownSections.installation],
|
||||
|
||||
Reference in New Issue
Block a user