From 4b348e1e603ec45e1e4bf39533b4b204423cafa0 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 18 Oct 2018 11:09:23 -0700 Subject: quoteState -> quoteRequestState --- packages/instant/src/components/instant_heading.tsx | 10 +++++----- packages/instant/src/containers/selected_asset_amount_input.ts | 6 +++--- .../instant/src/containers/selected_asset_instant_heading.ts | 4 ++-- packages/instant/src/redux/actions.ts | 4 ++-- packages/instant/src/redux/reducer.ts | 10 +++++----- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index a36d35a93..9dd13299c 100644 --- a/packages/instant/src/components/instant_heading.tsx +++ b/packages/instant/src/components/instant_heading.tsx @@ -13,7 +13,7 @@ export interface InstantHeadingProps { selectedAssetAmount?: BigNumber; totalEthBaseAmount?: BigNumber; ethUsdPrice?: BigNumber; - quoteState: AsyncProcessState; + quoteRequestState: AsyncProcessState; } const Placeholder = () => ( @@ -42,8 +42,8 @@ const displayUsdAmount = ({ return format.ethBaseAmountInUsd(totalEthBaseAmount, ethUsdPrice, 2, ); }; -const loadingOrAmount = (quoteState: AsyncProcessState, amount: React.ReactNode): React.ReactNode => { - if (quoteState === AsyncProcessState.PENDING) { +const loadingOrAmount = (quoteRequestState: AsyncProcessState, amount: React.ReactNode): React.ReactNode => { + if (quoteRequestState === AsyncProcessState.PENDING) { return ( …loading @@ -73,11 +73,11 @@ export const InstantHeading: React.StatelessComponent = pro - {loadingOrAmount(props.quoteState, displaytotalEthBaseAmount(props))} + {loadingOrAmount(props.quoteRequestState, displaytotalEthBaseAmount(props))} - {loadingOrAmount(props.quoteState, displayUsdAmount(props))} + {loadingOrAmount(props.quoteRequestState, displayUsdAmount(props))} diff --git a/packages/instant/src/containers/selected_asset_amount_input.ts b/packages/instant/src/containers/selected_asset_amount_input.ts index 0810b093a..aca6a09d4 100644 --- a/packages/instant/src/containers/selected_asset_amount_input.ts +++ b/packages/instant/src/containers/selected_asset_amount_input.ts @@ -44,13 +44,13 @@ const updateBuyQuoteAsync = async ( const baseUnitValue = Web3Wrapper.toBaseUnitAmount(assetAmount, zrxDecimals); // mark quote as pending - dispatch(actions.updateBuyQuoteStatePending()); + dispatch(actions.updateBuyquoteRequestStatePending()); let newBuyQuote: BuyQuote | undefined; try { newBuyQuote = await assetBuyer.getBuyQuoteAsync(assetData, baseUnitValue); } catch (error) { - dispatch(actions.updateBuyQuoteStateFailure()); + dispatch(actions.updateBuyquoteRequestStateFailure()); errorUtil.errorFlasher.flashNewError(dispatch, error); return; } @@ -76,7 +76,7 @@ const mapDispatchToProps = ( if (!_.isUndefined(value) && !_.isUndefined(assetData)) { // even if it's debounced, give them the illusion it's loading - dispatch(actions.updateBuyQuoteStatePending()); + dispatch(actions.updateBuyquoteRequestStatePending()); // tslint:disable-next-line:no-floating-promises debouncedUpdateBuyQuoteAsync(dispatch, assetData, value); } diff --git a/packages/instant/src/containers/selected_asset_instant_heading.ts b/packages/instant/src/containers/selected_asset_instant_heading.ts index 0509db5da..43127582c 100644 --- a/packages/instant/src/containers/selected_asset_instant_heading.ts +++ b/packages/instant/src/containers/selected_asset_instant_heading.ts @@ -15,14 +15,14 @@ interface ConnectedState { selectedAssetAmount?: BigNumber; totalEthBaseAmount?: BigNumber; ethUsdPrice?: BigNumber; - quoteState: AsyncProcessState; + quoteRequestState: AsyncProcessState; } const mapStateToProps = (state: State, _ownProps: InstantHeadingProps): ConnectedState => ({ selectedAssetAmount: state.selectedAssetAmount, totalEthBaseAmount: oc(state).latestBuyQuote.worstCaseQuoteInfo.totalEthAmount(), ethUsdPrice: state.ethUsdPrice, - quoteState: state.quoteState, + quoteRequestState: state.quoteRequestState, }); export const SelectedAssetInstantHeading: React.ComponentClass = connect(mapStateToProps)( diff --git a/packages/instant/src/redux/actions.ts b/packages/instant/src/redux/actions.ts index 9c154c66f..b4aa4f4cb 100644 --- a/packages/instant/src/redux/actions.ts +++ b/packages/instant/src/redux/actions.ts @@ -38,8 +38,8 @@ export const actions = { updatebuyOrderState: (buyState: AsyncProcessState) => createAction(ActionTypes.UPDATE_SELECTED_ASSET_BUY_STATE, buyState), updateLatestBuyQuote: (buyQuote?: BuyQuote) => createAction(ActionTypes.UPDATE_LATEST_BUY_QUOTE, buyQuote), - updateBuyQuoteStatePending: () => createAction(ActionTypes.UPDATE_BUY_QUOTE_STATE_PENDING), - updateBuyQuoteStateFailure: () => createAction(ActionTypes.UPDATE_BUY_QUOTE_STATE_FAILURE), + updateBuyquoteRequestStatePending: () => createAction(ActionTypes.UPDATE_BUY_QUOTE_STATE_PENDING), + updateBuyquoteRequestStateFailure: () => createAction(ActionTypes.UPDATE_BUY_QUOTE_STATE_FAILURE), setError: (error?: any) => createAction(ActionTypes.SET_ERROR, error), hideError: () => createAction(ActionTypes.HIDE_ERROR), clearError: () => createAction(ActionTypes.CLEAR_ERROR), diff --git a/packages/instant/src/redux/reducer.ts b/packages/instant/src/redux/reducer.ts index 05aa37420..d2256ef54 100644 --- a/packages/instant/src/redux/reducer.ts +++ b/packages/instant/src/redux/reducer.ts @@ -17,7 +17,7 @@ export interface State { buyOrderState: AsyncProcessState; ethUsdPrice?: BigNumber; latestBuyQuote?: BuyQuote; - quoteState: AsyncProcessState; + quoteRequestState: AsyncProcessState; latestError?: any; latestErrorDisplay: LatestErrorDisplay; } @@ -31,7 +31,7 @@ export const INITIAL_STATE: State = { latestBuyQuote: undefined, latestError: undefined, latestErrorDisplay: LatestErrorDisplay.Hidden, - quoteState: AsyncProcessState.NONE, + quoteRequestState: AsyncProcessState.NONE, }; export const reducer = (state: State = INITIAL_STATE, action: Action): State => { @@ -50,19 +50,19 @@ export const reducer = (state: State = INITIAL_STATE, action: Action): State => return { ...state, latestBuyQuote: action.data, - quoteState: AsyncProcessState.SUCCESS, + quoteRequestState: AsyncProcessState.SUCCESS, }; case ActionTypes.UPDATE_BUY_QUOTE_STATE_PENDING: return { ...state, latestBuyQuote: undefined, - quoteState: AsyncProcessState.PENDING, + quoteRequestState: AsyncProcessState.PENDING, }; case ActionTypes.UPDATE_BUY_QUOTE_STATE_FAILURE: return { ...state, latestBuyQuote: undefined, - quoteState: AsyncProcessState.FAILURE, + quoteRequestState: AsyncProcessState.FAILURE, }; case ActionTypes.UPDATE_SELECTED_ASSET_BUY_STATE: return { -- cgit From 12b6877aeb0a6bcddab4193b62cd10347b8c328b Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 18 Oct 2018 13:31:11 -0700 Subject: Pulsating holder element showing, even if amount is empty --- .../instant/src/components/amount_placeholder.tsx | 29 ++++++ .../instant/src/components/animations/pulse.tsx | 15 +++ .../instant/src/components/instant_heading.tsx | 112 ++++++++++----------- packages/instant/src/components/ui/container.tsx | 2 + 4 files changed, 101 insertions(+), 57 deletions(-) create mode 100644 packages/instant/src/components/amount_placeholder.tsx create mode 100644 packages/instant/src/components/animations/pulse.tsx diff --git a/packages/instant/src/components/amount_placeholder.tsx b/packages/instant/src/components/amount_placeholder.tsx new file mode 100644 index 000000000..54639effb --- /dev/null +++ b/packages/instant/src/components/amount_placeholder.tsx @@ -0,0 +1,29 @@ +import * as React from 'react'; + +import { ColorOption } from '../style/theme'; + +import { Pulse } from './animations/pulse'; + +import { Text } from './ui'; + +export interface AmountPlaceholderProps { + pulsating: boolean; +} + +const PlainPlaceholder = () => ( + + — + +); + +export const AmountPlaceholder: React.StatelessComponent = props => { + if (props.pulsating) { + return ( + + + + ); + } else { + return ; + } +}; diff --git a/packages/instant/src/components/animations/pulse.tsx b/packages/instant/src/components/animations/pulse.tsx new file mode 100644 index 000000000..01d6ea070 --- /dev/null +++ b/packages/instant/src/components/animations/pulse.tsx @@ -0,0 +1,15 @@ +import { keyframes, styled } from '../../style/theme'; + +const pulsingKeyframes = keyframes` + 0%, 100% { + opacity: 0.2; + } + 50% { + opacity: 100; + } +`; +export const Pulse = styled.div` + animation-name: ${pulsingKeyframes} + animation-duration: 2s; + animation-iteration-count: infinite; +`; diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index 9dd13299c..9ca4ce598 100644 --- a/packages/instant/src/components/instant_heading.tsx +++ b/packages/instant/src/components/instant_heading.tsx @@ -7,6 +7,7 @@ import { ColorOption } from '../style/theme'; import { AsyncProcessState } from '../types'; import { format } from '../util/format'; +import { AmountPlaceholder } from './amount_placeholder'; import { Container, Flex, Text } from './ui'; export interface InstantHeadingProps { @@ -16,70 +17,67 @@ export interface InstantHeadingProps { quoteRequestState: AsyncProcessState; } -const Placeholder = () => ( - - — - -); -const displaytotalEthBaseAmount = ({ - selectedAssetAmount, - totalEthBaseAmount, -}: InstantHeadingProps): React.ReactNode => { - if (_.isUndefined(selectedAssetAmount)) { - return '0 ETH'; +export class InstantHeading extends React.Component { + public render(): React.ReactNode { + const placeholderAmountToAlwaysShow = this._placeholderAmountToAlwaysShow(); + return ( + + + + I want to buy + + + + + + {placeholderAmountToAlwaysShow || this._ethAmount()} + {placeholderAmountToAlwaysShow || this._dollarAmount()} + + + + ); } - return format.ethBaseAmount(totalEthBaseAmount, 4, ); -}; -const displayUsdAmount = ({ - totalEthBaseAmount, - selectedAssetAmount, - ethUsdPrice, -}: InstantHeadingProps): React.ReactNode => { - if (_.isUndefined(selectedAssetAmount)) { - return '$0.00'; + private _placeholderAmountToAlwaysShow(): React.ReactNode | undefined { + if (this.props.quoteRequestState === AsyncProcessState.PENDING) { + return ; + } + if (_.isUndefined(this.props.selectedAssetAmount)) { + return ; + } + return undefined; } - return format.ethBaseAmountInUsd(totalEthBaseAmount, ethUsdPrice, 2, ); -}; -const loadingOrAmount = (quoteRequestState: AsyncProcessState, amount: React.ReactNode): React.ReactNode => { - if (quoteRequestState === AsyncProcessState.PENDING) { + private _ethAmount(): React.ReactNode { return ( - - …loading + + {format.ethBaseAmount(this.props.totalEthBaseAmount, 4, )} ); - } else { - return amount; } -}; -export const InstantHeading: React.StatelessComponent = props => ( - - - - I want to buy + private _dollarAmount(): React.ReactNode { + return ( + + {format.ethBaseAmountInUsd( + this.props.totalEthBaseAmount, + this.props.ethUsdPrice, + 2, + , + )} - - - - - - - {loadingOrAmount(props.quoteRequestState, displaytotalEthBaseAmount(props))} - - - - {loadingOrAmount(props.quoteRequestState, displayUsdAmount(props))} - - - - -); + ); + } +} diff --git a/packages/instant/src/components/ui/container.tsx b/packages/instant/src/components/ui/container.tsx index 02b16e39f..5e2218c68 100644 --- a/packages/instant/src/components/ui/container.tsx +++ b/packages/instant/src/components/ui/container.tsx @@ -27,6 +27,7 @@ export interface ContainerProps { backgroundColor?: ColorOption; hasBoxShadow?: boolean; zIndex?: number; + opacity?: number; } const PlainContainer: React.StatelessComponent = ({ children, className }) => ( @@ -54,6 +55,7 @@ export const Container = styled(PlainContainer)` ${props => cssRuleIfExists(props, 'border-top')} ${props => cssRuleIfExists(props, 'border-bottom')} ${props => cssRuleIfExists(props, 'z-index')} + ${props => cssRuleIfExists(props, 'opacity')} ${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')}; -- cgit From 94ace00e0cfd550bccf33f85441ce1dad33f1348 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 18 Oct 2018 13:38:04 -0700 Subject: fix camel casing of updateBuyOrderState --- packages/instant/src/containers/selected_asset_amount_input.ts | 2 +- packages/instant/src/containers/selected_asset_buy_button.ts | 6 +++--- packages/instant/src/redux/actions.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/instant/src/containers/selected_asset_amount_input.ts b/packages/instant/src/containers/selected_asset_amount_input.ts index aca6a09d4..2a1e5e457 100644 --- a/packages/instant/src/containers/selected_asset_amount_input.ts +++ b/packages/instant/src/containers/selected_asset_amount_input.ts @@ -72,7 +72,7 @@ const mapDispatchToProps = ( // invalidate the last buy quote. dispatch(actions.updateLatestBuyQuote(undefined)); // reset our buy state - dispatch(actions.updatebuyOrderState(AsyncProcessState.NONE)); + dispatch(actions.updateBuyOrderState(AsyncProcessState.NONE)); if (!_.isUndefined(value) && !_.isUndefined(assetData)) { // even if it's debounced, give them the illusion it's loading diff --git a/packages/instant/src/containers/selected_asset_buy_button.ts b/packages/instant/src/containers/selected_asset_buy_button.ts index 4a8e31525..4d3315b1a 100644 --- a/packages/instant/src/containers/selected_asset_buy_button.ts +++ b/packages/instant/src/containers/selected_asset_buy_button.ts @@ -44,9 +44,9 @@ const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyButtonProps): }); const mapDispatchToProps = (dispatch: Dispatch, ownProps: SelectedAssetBuyButtonProps): ConnectedDispatch => ({ - onClick: buyQuote => dispatch(actions.updatebuyOrderState(AsyncProcessState.PENDING)), - onBuySuccess: buyQuote => dispatch(actions.updatebuyOrderState(AsyncProcessState.SUCCESS)), - onBuyFailure: buyQuote => dispatch(actions.updatebuyOrderState(AsyncProcessState.FAILURE)), + onClick: buyQuote => dispatch(actions.updateBuyOrderState(AsyncProcessState.PENDING)), + onBuySuccess: buyQuote => dispatch(actions.updateBuyOrderState(AsyncProcessState.SUCCESS)), + onBuyFailure: buyQuote => dispatch(actions.updateBuyOrderState(AsyncProcessState.FAILURE)), }); export const SelectedAssetBuyButton: React.ComponentClass = connect( diff --git a/packages/instant/src/redux/actions.ts b/packages/instant/src/redux/actions.ts index b4aa4f4cb..016dc29dc 100644 --- a/packages/instant/src/redux/actions.ts +++ b/packages/instant/src/redux/actions.ts @@ -35,7 +35,7 @@ export enum ActionTypes { export const actions = { updateEthUsdPrice: (price?: BigNumber) => createAction(ActionTypes.UPDATE_ETH_USD_PRICE, price), updateSelectedAssetAmount: (amount?: BigNumber) => createAction(ActionTypes.UPDATE_SELECTED_ASSET_AMOUNT, amount), - updatebuyOrderState: (buyState: AsyncProcessState) => + updateBuyOrderState: (buyState: AsyncProcessState) => createAction(ActionTypes.UPDATE_SELECTED_ASSET_BUY_STATE, buyState), updateLatestBuyQuote: (buyQuote?: BuyQuote) => createAction(ActionTypes.UPDATE_LATEST_BUY_QUOTE, buyQuote), updateBuyquoteRequestStatePending: () => createAction(ActionTypes.UPDATE_BUY_QUOTE_STATE_PENDING), -- cgit From 9aa67538231cc4ff71fd0f8ebe4dc66b1bcc4937 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 18 Oct 2018 13:42:59 -0700 Subject: Rename update function to set function since it doesnt take a parameter --- packages/instant/src/containers/selected_asset_amount_input.ts | 6 +++--- packages/instant/src/redux/actions.ts | 8 ++++---- packages/instant/src/redux/reducer.ts | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/instant/src/containers/selected_asset_amount_input.ts b/packages/instant/src/containers/selected_asset_amount_input.ts index 2a1e5e457..e55c8b991 100644 --- a/packages/instant/src/containers/selected_asset_amount_input.ts +++ b/packages/instant/src/containers/selected_asset_amount_input.ts @@ -44,13 +44,13 @@ const updateBuyQuoteAsync = async ( const baseUnitValue = Web3Wrapper.toBaseUnitAmount(assetAmount, zrxDecimals); // mark quote as pending - dispatch(actions.updateBuyquoteRequestStatePending()); + dispatch(actions.setQuoteRequestStatePending()); let newBuyQuote: BuyQuote | undefined; try { newBuyQuote = await assetBuyer.getBuyQuoteAsync(assetData, baseUnitValue); } catch (error) { - dispatch(actions.updateBuyquoteRequestStateFailure()); + dispatch(actions.setQuoteRequestStateFailure()); errorUtil.errorFlasher.flashNewError(dispatch, error); return; } @@ -76,7 +76,7 @@ const mapDispatchToProps = ( if (!_.isUndefined(value) && !_.isUndefined(assetData)) { // even if it's debounced, give them the illusion it's loading - dispatch(actions.updateBuyquoteRequestStatePending()); + dispatch(actions.setQuoteRequestStatePending()); // tslint:disable-next-line:no-floating-promises debouncedUpdateBuyQuoteAsync(dispatch, assetData, value); } diff --git a/packages/instant/src/redux/actions.ts b/packages/instant/src/redux/actions.ts index 016dc29dc..e52a79e76 100644 --- a/packages/instant/src/redux/actions.ts +++ b/packages/instant/src/redux/actions.ts @@ -25,8 +25,8 @@ export enum ActionTypes { UPDATE_SELECTED_ASSET_AMOUNT = 'UPDATE_SELECTED_ASSET_AMOUNT', UPDATE_SELECTED_ASSET_BUY_STATE = 'UPDATE_SELECTED_ASSET_BUY_STATE', UPDATE_LATEST_BUY_QUOTE = 'UPDATE_LATEST_BUY_QUOTE', - UPDATE_BUY_QUOTE_STATE_PENDING = 'UPDATE_BUY_QUOTE_STATE_PENDING', - UPDATE_BUY_QUOTE_STATE_FAILURE = 'UPDATE_BUY_QUOTE_STATE_FAILURE', + SET_QUOTE_REQUEST_STATE_PENDING = 'SET_QUOTE_REQUEST_STATE_PENDING', + SET_QUOTE_REQUEST_STATE_FAILURE = 'SET_QUOTE_REQUEST_STATE_FAILURE', SET_ERROR = 'SET_ERROR', HIDE_ERROR = 'HIDE_ERROR', CLEAR_ERROR = 'CLEAR_ERROR', @@ -38,8 +38,8 @@ export const actions = { updateBuyOrderState: (buyState: AsyncProcessState) => createAction(ActionTypes.UPDATE_SELECTED_ASSET_BUY_STATE, buyState), updateLatestBuyQuote: (buyQuote?: BuyQuote) => createAction(ActionTypes.UPDATE_LATEST_BUY_QUOTE, buyQuote), - updateBuyquoteRequestStatePending: () => createAction(ActionTypes.UPDATE_BUY_QUOTE_STATE_PENDING), - updateBuyquoteRequestStateFailure: () => createAction(ActionTypes.UPDATE_BUY_QUOTE_STATE_FAILURE), + setQuoteRequestStatePending: () => createAction(ActionTypes.SET_QUOTE_REQUEST_STATE_PENDING), + setQuoteRequestStateFailure: () => createAction(ActionTypes.SET_QUOTE_REQUEST_STATE_FAILURE), setError: (error?: any) => createAction(ActionTypes.SET_ERROR, error), hideError: () => createAction(ActionTypes.HIDE_ERROR), clearError: () => createAction(ActionTypes.CLEAR_ERROR), diff --git a/packages/instant/src/redux/reducer.ts b/packages/instant/src/redux/reducer.ts index d2256ef54..6c278099f 100644 --- a/packages/instant/src/redux/reducer.ts +++ b/packages/instant/src/redux/reducer.ts @@ -52,13 +52,13 @@ export const reducer = (state: State = INITIAL_STATE, action: Action): State => latestBuyQuote: action.data, quoteRequestState: AsyncProcessState.SUCCESS, }; - case ActionTypes.UPDATE_BUY_QUOTE_STATE_PENDING: + case ActionTypes.SET_QUOTE_REQUEST_STATE_PENDING: return { ...state, latestBuyQuote: undefined, quoteRequestState: AsyncProcessState.PENDING, }; - case ActionTypes.UPDATE_BUY_QUOTE_STATE_FAILURE: + case ActionTypes.SET_QUOTE_REQUEST_STATE_FAILURE: return { ...state, latestBuyQuote: undefined, -- cgit From b4af27dd4462a001904ac6d69e43aac9fd7bb69a Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 18 Oct 2018 13:48:28 -0700 Subject: Moved LatestErrorDisplay to types file and gave generic name --- packages/instant/src/containers/latest_error.tsx | 5 +++-- packages/instant/src/redux/reducer.ts | 16 ++++++---------- packages/instant/src/types.ts | 4 ++++ 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/instant/src/containers/latest_error.tsx b/packages/instant/src/containers/latest_error.tsx index 08ea418e7..413cf16ad 100644 --- a/packages/instant/src/containers/latest_error.tsx +++ b/packages/instant/src/containers/latest_error.tsx @@ -3,7 +3,8 @@ import * as React from 'react'; import { connect } from 'react-redux'; import { SlidingError } from '../components/sliding_error'; -import { LatestErrorDisplay, State } from '../redux/reducer'; +import { State } from '../redux/reducer'; +import { DisplayStatus } from '../types'; import { errorUtil } from '../util/error'; export interface LatestErrorComponentProps { @@ -29,7 +30,7 @@ export interface LatestErrorProps {} const mapStateToProps = (state: State, _ownProps: LatestErrorProps): ConnectedState => ({ assetData: state.selectedAssetData, latestError: state.latestError, - slidingDirection: state.latestErrorDisplay === LatestErrorDisplay.Present ? 'up' : 'down', + slidingDirection: state.latestErrorDisplay === DisplayStatus.Present ? 'up' : 'down', }); export const LatestError = connect(mapStateToProps)(LatestErrorComponent); diff --git a/packages/instant/src/redux/reducer.ts b/packages/instant/src/redux/reducer.ts index 6c278099f..2d50dd4b9 100644 --- a/packages/instant/src/redux/reducer.ts +++ b/packages/instant/src/redux/reducer.ts @@ -3,14 +3,10 @@ import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import { zrxAssetData } from '../constants'; -import { AsyncProcessState } from '../types'; +import { AsyncProcessState, DisplayStatus } from '../types'; import { Action, ActionTypes } from './actions'; -export enum LatestErrorDisplay { - Present, - Hidden, -} export interface State { selectedAssetData?: string; selectedAssetAmount?: BigNumber; @@ -19,7 +15,7 @@ export interface State { latestBuyQuote?: BuyQuote; quoteRequestState: AsyncProcessState; latestError?: any; - latestErrorDisplay: LatestErrorDisplay; + latestErrorDisplay: DisplayStatus; } export const INITIAL_STATE: State = { @@ -30,7 +26,7 @@ export const INITIAL_STATE: State = { ethUsdPrice: undefined, latestBuyQuote: undefined, latestError: undefined, - latestErrorDisplay: LatestErrorDisplay.Hidden, + latestErrorDisplay: DisplayStatus.Hidden, quoteRequestState: AsyncProcessState.NONE, }; @@ -73,18 +69,18 @@ export const reducer = (state: State = INITIAL_STATE, action: Action): State => return { ...state, latestError: action.data, - latestErrorDisplay: LatestErrorDisplay.Present, + latestErrorDisplay: DisplayStatus.Present, }; case ActionTypes.HIDE_ERROR: return { ...state, - latestErrorDisplay: LatestErrorDisplay.Hidden, + latestErrorDisplay: DisplayStatus.Hidden, }; case ActionTypes.CLEAR_ERROR: return { ...state, latestError: undefined, - latestErrorDisplay: LatestErrorDisplay.Hidden, + latestErrorDisplay: DisplayStatus.Hidden, }; default: return state; diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts index 8e3bb9b37..013ada27b 100644 --- a/packages/instant/src/types.ts +++ b/packages/instant/src/types.ts @@ -7,6 +7,10 @@ export enum AsyncProcessState { SUCCESS = 'Success', FAILURE = 'Failure', } +export enum DisplayStatus { + Present, + Hidden, +} export type FunctionType = (...args: any[]) => any; export type ActionCreatorsMapObject = ObjectMap; -- cgit From 8a6e0776640a7bec7eab1f91b3b611dc4cadd2f7 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 18 Oct 2018 14:51:45 -0700 Subject: feat(instant): Indicate that order details section is loading by having pulsing placeholder --- .../instant/src/components/amount_placeholder.tsx | 17 +++++++----- .../instant/src/components/instant_heading.tsx | 13 +++++++--- packages/instant/src/components/order_details.tsx | 30 ++++++++++++++++++---- .../containers/latest_buy_quote_order_details.ts | 3 +++ 4 files changed, 47 insertions(+), 16 deletions(-) diff --git a/packages/instant/src/components/amount_placeholder.tsx b/packages/instant/src/components/amount_placeholder.tsx index 54639effb..23a00895a 100644 --- a/packages/instant/src/components/amount_placeholder.tsx +++ b/packages/instant/src/components/amount_placeholder.tsx @@ -6,24 +6,27 @@ import { Pulse } from './animations/pulse'; import { Text } from './ui'; -export interface AmountPlaceholderProps { - pulsating: boolean; +export interface PlainPlaceholder { + color: ColorOption; } - -const PlainPlaceholder = () => ( - +export const PlainPlaceholder: React.StatelessComponent = props => ( + ); +export interface AmountPlaceholderProps { + color: ColorOption; + pulsating: boolean; +} export const AmountPlaceholder: React.StatelessComponent = props => { if (props.pulsating) { return ( - + ); } else { - return ; + return ; } }; diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index 9ca4ce598..6a4c850f2 100644 --- a/packages/instant/src/components/instant_heading.tsx +++ b/packages/instant/src/components/instant_heading.tsx @@ -17,6 +17,7 @@ export interface InstantHeadingProps { quoteRequestState: AsyncProcessState; } +const placeholderColor = ColorOption.white; export class InstantHeading extends React.Component { public render(): React.ReactNode { const placeholderAmountToAlwaysShow = this._placeholderAmountToAlwaysShow(); @@ -52,10 +53,10 @@ export class InstantHeading extends React.Component { private _placeholderAmountToAlwaysShow(): React.ReactNode | undefined { if (this.props.quoteRequestState === AsyncProcessState.PENDING) { - return ; + return ; } if (_.isUndefined(this.props.selectedAssetAmount)) { - return ; + return ; } return undefined; } @@ -63,7 +64,11 @@ export class InstantHeading extends React.Component { private _ethAmount(): React.ReactNode { return ( - {format.ethBaseAmount(this.props.totalEthBaseAmount, 4, )} + {format.ethBaseAmount( + this.props.totalEthBaseAmount, + 4, + , + )} ); } @@ -75,7 +80,7 @@ export class InstantHeading extends React.Component { this.props.totalEthBaseAmount, this.props.ethUsdPrice, 2, - , + , )} ); diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index ad4a87714..f48f27edb 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -7,11 +7,13 @@ import { oc } from 'ts-optchain'; import { ColorOption } from '../style/theme'; import { format } from '../util/format'; +import { AmountPlaceholder } from './amount_placeholder'; import { Container, Flex, Text } from './ui'; export interface OrderDetailsProps { buyQuoteInfo?: BuyQuoteInfo; ethUsdPrice?: BigNumber; + isLoading: boolean; } export class OrderDetails extends React.Component { @@ -39,13 +41,20 @@ export class OrderDetails extends React.Component { ethAmount={ethAssetPrice} ethUsdPrice={ethUsdPrice} isEthAmountInBaseUnits={false} + isLoading={this.props.isLoading} + /> + - ); @@ -58,6 +67,7 @@ export interface EthAmountRowProps { isEthAmountInBaseUnits?: boolean; ethUsdPrice?: BigNumber; shouldEmphasize?: boolean; + isLoading: boolean; } export const EthAmountRow: React.StatelessComponent = ({ @@ -66,15 +76,19 @@ export const EthAmountRow: React.StatelessComponent = ({ isEthAmountInBaseUnits, ethUsdPrice, shouldEmphasize, + isLoading, }) => { - const fontWeight = shouldEmphasize ? 700 : 400; + // TODO: put in private function const usdFormatter = isEthAmountInBaseUnits ? format.ethBaseAmountInUsd : format.ethUnitAmountInUsd; - const ethFormatter = isEthAmountInBaseUnits ? format.ethBaseAmount : format.ethUnitAmount; - const usdPriceSection = _.isUndefined(ethUsdPrice) ? null : ( + const shouldHideUsdPriceSection = _.isUndefined(ethUsdPrice) || _.isUndefined(ethAmount); + const usdPriceSection = shouldHideUsdPriceSection ? null : ( ({usdFormatter(ethAmount, ethUsdPrice)}) ); + + const fontWeight = shouldEmphasize ? 700 : 400; + const ethFormatter = isEthAmountInBaseUnits ? format.ethBaseAmount : format.ethUnitAmount; return ( @@ -84,7 +98,13 @@ export const EthAmountRow: React.StatelessComponent = ({ {usdPriceSection} - {ethFormatter(ethAmount)} + {ethFormatter( + ethAmount, + 4, + + + , + )} diff --git a/packages/instant/src/containers/latest_buy_quote_order_details.ts b/packages/instant/src/containers/latest_buy_quote_order_details.ts index 597bf3088..092aaaf20 100644 --- a/packages/instant/src/containers/latest_buy_quote_order_details.ts +++ b/packages/instant/src/containers/latest_buy_quote_order_details.ts @@ -8,18 +8,21 @@ import { oc } from 'ts-optchain'; import { State } from '../redux/reducer'; import { OrderDetails } from '../components/order_details'; +import { AsyncProcessState } from '../types'; export interface LatestBuyQuoteOrderDetailsProps {} interface ConnectedState { buyQuoteInfo?: BuyQuoteInfo; ethUsdPrice?: BigNumber; + isLoading: boolean; } const mapStateToProps = (state: State, _ownProps: LatestBuyQuoteOrderDetailsProps): ConnectedState => ({ // use the worst case quote info buyQuoteInfo: oc(state).latestBuyQuote.worstCaseQuoteInfo(), ethUsdPrice: state.ethUsdPrice, + isLoading: state.quoteRequestState === AsyncProcessState.PENDING, }); export const LatestBuyQuoteOrderDetails: React.ComponentClass = connect( -- cgit From 8ff5e1926993979229783cfb03d38aabd6f2f3d3 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 18 Oct 2018 15:27:34 -0700 Subject: Move USD section into helper function --- packages/instant/src/components/order_details.tsx | 88 +++++++++++------------ 1 file changed, 42 insertions(+), 46 deletions(-) diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index f48f27edb..9a0dbc47a 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -15,7 +15,6 @@ export interface OrderDetailsProps { ethUsdPrice?: BigNumber; isLoading: boolean; } - export class OrderDetails extends React.Component { public render(): React.ReactNode { const { buyQuoteInfo, ethUsdPrice } = this.props; @@ -70,51 +69,48 @@ export interface EthAmountRowProps { isLoading: boolean; } -export const EthAmountRow: React.StatelessComponent = ({ - rowLabel, - ethAmount, - isEthAmountInBaseUnits, - ethUsdPrice, - shouldEmphasize, - isLoading, -}) => { - // TODO: put in private function - const usdFormatter = isEthAmountInBaseUnits ? format.ethBaseAmountInUsd : format.ethUnitAmountInUsd; - const shouldHideUsdPriceSection = _.isUndefined(ethUsdPrice) || _.isUndefined(ethAmount); - const usdPriceSection = shouldHideUsdPriceSection ? null : ( - - ({usdFormatter(ethAmount, ethUsdPrice)}) - - ); +export class EthAmountRow extends React.Component { + public static defaultProps = { + shouldEmphasize: false, + isEthAmountInBaseUnits: true, + }; + public static displayName = 'EthAmountRow'; + public render(): React.ReactNode { + const { rowLabel, ethAmount, isEthAmountInBaseUnits, shouldEmphasize, isLoading } = this.props; - const fontWeight = shouldEmphasize ? 700 : 400; - const ethFormatter = isEthAmountInBaseUnits ? format.ethBaseAmount : format.ethUnitAmount; - return ( - - - - {rowLabel} - - - {usdPriceSection} + const fontWeight = shouldEmphasize ? 700 : 400; + const ethFormatter = isEthAmountInBaseUnits ? format.ethBaseAmount : format.ethUnitAmount; + return ( + + - {ethFormatter( - ethAmount, - 4, - - - , - )} + {rowLabel} - - - - ); -}; - -EthAmountRow.defaultProps = { - shouldEmphasize: false, - isEthAmountInBaseUnits: true, -}; - -EthAmountRow.displayName = 'EthAmountRow'; + + {this._usdSection()} + + {ethFormatter( + ethAmount, + 4, + + + , + )} + + + + + ); + } + private _usdSection(): React.ReactNode { + const usdFormatter = this.props.isEthAmountInBaseUnits ? format.ethBaseAmountInUsd : format.ethUnitAmountInUsd; + const shouldHideUsdPriceSection = _.isUndefined(this.props.ethUsdPrice) || _.isUndefined(this.props.ethAmount); + return shouldHideUsdPriceSection ? null : ( + + + ({usdFormatter(this.props.ethAmount, this.props.ethUsdPrice)}) + + + ); + } +} -- cgit From a42347a776e2fa14fa898a354190f8b75bbe2fb2 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 18 Oct 2018 15:28:24 -0700 Subject: Allow more than 1 class per file Felt silly that refactoring a component defined as a function to a class with a helper function caused a tslint violation --- packages/instant/tslint.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/instant/tslint.json b/packages/instant/tslint.json index abe874a6d..08b76be97 100644 --- a/packages/instant/tslint.json +++ b/packages/instant/tslint.json @@ -2,6 +2,7 @@ "extends": ["@0x/tslint-config"], "rules": { "custom-no-magic-numbers": false, - "semicolon": [true, "always", "ignore-bound-class-methods"] + "semicolon": [true, "always", "ignore-bound-class-methods"], + "max-classes-per-file": false } } -- cgit From aa0e07b0581a645c3d2ea035d3acf28a74de0384 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 18 Oct 2018 15:30:14 -0700 Subject: pulsating -> isPulsating --- packages/instant/src/components/amount_placeholder.tsx | 4 ++-- packages/instant/src/components/instant_heading.tsx | 8 ++++---- packages/instant/src/components/order_details.tsx | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/instant/src/components/amount_placeholder.tsx b/packages/instant/src/components/amount_placeholder.tsx index 23a00895a..e27d0ba81 100644 --- a/packages/instant/src/components/amount_placeholder.tsx +++ b/packages/instant/src/components/amount_placeholder.tsx @@ -17,10 +17,10 @@ export const PlainPlaceholder: React.StatelessComponent = prop export interface AmountPlaceholderProps { color: ColorOption; - pulsating: boolean; + isPulsating: boolean; } export const AmountPlaceholder: React.StatelessComponent = props => { - if (props.pulsating) { + if (props.isPulsating) { return ( diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index 6a4c850f2..e2ef2d668 100644 --- a/packages/instant/src/components/instant_heading.tsx +++ b/packages/instant/src/components/instant_heading.tsx @@ -53,10 +53,10 @@ export class InstantHeading extends React.Component { private _placeholderAmountToAlwaysShow(): React.ReactNode | undefined { if (this.props.quoteRequestState === AsyncProcessState.PENDING) { - return ; + return ; } if (_.isUndefined(this.props.selectedAssetAmount)) { - return ; + return ; } return undefined; } @@ -67,7 +67,7 @@ export class InstantHeading extends React.Component { {format.ethBaseAmount( this.props.totalEthBaseAmount, 4, - , + , )} ); @@ -80,7 +80,7 @@ export class InstantHeading extends React.Component { this.props.totalEthBaseAmount, this.props.ethUsdPrice, 2, - , + , )} ); diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 9a0dbc47a..a7971e1fd 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -93,7 +93,7 @@ export class EthAmountRow extends React.Component { ethAmount, 4, - + , )} -- cgit From ff1f0a967802529e5ee50a7ff52d67bae1252b51 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 18 Oct 2018 15:31:18 -0700 Subject: undefined -> null to follow convention --- packages/instant/src/components/instant_heading.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index e2ef2d668..48cda7f88 100644 --- a/packages/instant/src/components/instant_heading.tsx +++ b/packages/instant/src/components/instant_heading.tsx @@ -51,14 +51,14 @@ export class InstantHeading extends React.Component { ); } - private _placeholderAmountToAlwaysShow(): React.ReactNode | undefined { + private _placeholderAmountToAlwaysShow(): React.ReactNode | null { if (this.props.quoteRequestState === AsyncProcessState.PENDING) { return ; } if (_.isUndefined(this.props.selectedAssetAmount)) { return ; } - return undefined; + return null; } private _ethAmount(): React.ReactNode { -- cgit From 1737411ab7478de4a394242c930fcc7189daae07 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 18 Oct 2018 17:16:28 -0700 Subject: Show order failed messaging when order fails --- .../instant/src/components/instant_heading.tsx | 70 +++++++++++++--------- .../containers/selected_asset_instant_heading.ts | 2 + 2 files changed, 45 insertions(+), 27 deletions(-) diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index a36d35a93..6fc5a0963 100644 --- a/packages/instant/src/components/instant_heading.tsx +++ b/packages/instant/src/components/instant_heading.tsx @@ -14,6 +14,7 @@ export interface InstantHeadingProps { totalEthBaseAmount?: BigNumber; ethUsdPrice?: BigNumber; quoteState: AsyncProcessState; + buyOrderState: AsyncProcessState; } const Placeholder = () => ( @@ -54,32 +55,47 @@ const loadingOrAmount = (quoteState: AsyncProcessState, amount: React.ReactNode) } }; -export const InstantHeading: React.StatelessComponent = props => ( - - - - I want to buy - - - - - - - - {loadingOrAmount(props.quoteState, displaytotalEthBaseAmount(props))} - - - - {loadingOrAmount(props.quoteState, displayUsdAmount(props))} +const topText = (buyOrderState: AsyncProcessState): string => { + if (buyOrderState === AsyncProcessState.FAILURE) { + return 'Order failed'; + } + + return 'I want to buy'; +}; + +export const InstantHeading: React.StatelessComponent = props => { + return ( + + + + {topText(props.buyOrderState)} + + + + + + + {loadingOrAmount(props.quoteState, displaytotalEthBaseAmount(props))} + + + + {loadingOrAmount(props.quoteState, displayUsdAmount(props))} + + - - -); + + ); +}; diff --git a/packages/instant/src/containers/selected_asset_instant_heading.ts b/packages/instant/src/containers/selected_asset_instant_heading.ts index 0509db5da..743362151 100644 --- a/packages/instant/src/containers/selected_asset_instant_heading.ts +++ b/packages/instant/src/containers/selected_asset_instant_heading.ts @@ -16,6 +16,7 @@ interface ConnectedState { totalEthBaseAmount?: BigNumber; ethUsdPrice?: BigNumber; quoteState: AsyncProcessState; + buyOrderState: AsyncProcessState; } const mapStateToProps = (state: State, _ownProps: InstantHeadingProps): ConnectedState => ({ @@ -23,6 +24,7 @@ const mapStateToProps = (state: State, _ownProps: InstantHeadingProps): Connecte totalEthBaseAmount: oc(state).latestBuyQuote.worstCaseQuoteInfo.totalEthAmount(), ethUsdPrice: state.ethUsdPrice, quoteState: state.quoteState, + buyOrderState: state.buyOrderState, }); export const SelectedAssetInstantHeading: React.ComponentClass = connect(mapStateToProps)( -- cgit From 977e20edc3d94e2b95cf6e1e82eecdde2311eb7f Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Fri, 19 Oct 2018 13:27:16 +0100 Subject: style: make sure sideBar bottom padding is on scrollable content, not container --- packages/website/ts/pages/documentation/developers_page.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/website/ts/pages/documentation/developers_page.tsx b/packages/website/ts/pages/documentation/developers_page.tsx index 30b79552f..401f65df3 100644 --- a/packages/website/ts/pages/documentation/developers_page.tsx +++ b/packages/website/ts/pages/documentation/developers_page.tsx @@ -72,7 +72,6 @@ const SidebarContainer = ` ${scrollableContainerStyles} padding-top: 27px; - padding-bottom: 100px; padding-left: ${SIDEBAR_PADDING}px; padding-right: ${SIDEBAR_PADDING}px; overflow: hidden; @@ -163,7 +162,9 @@ export class DevelopersPage extends React.Component - {this.props.screenWidth !== ScreenWidths.Sm && this.props.sidebar} + + {this.props.screenWidth !== ScreenWidths.Sm && this.props.sidebar} + Date: Fri, 19 Oct 2018 14:04:17 +0100 Subject: style: Switch out homepage image with improved one from Ben --- .../website/public/images/landing/0x_homepage.svg | 57 +++------------------- 1 file changed, 6 insertions(+), 51 deletions(-) diff --git a/packages/website/public/images/landing/0x_homepage.svg b/packages/website/public/images/landing/0x_homepage.svg index 061ac8939..d2ccef8f4 100644 --- a/packages/website/public/images/landing/0x_homepage.svg +++ b/packages/website/public/images/landing/0x_homepage.svg @@ -1,24 +1,18 @@ - - - - - - - - - - + + + + + - @@ -56,7 +50,6 @@ - @@ -89,7 +82,6 @@ - @@ -116,7 +108,6 @@ - @@ -143,11 +134,10 @@ - - + @@ -170,7 +160,6 @@ - @@ -197,7 +186,6 @@ - @@ -224,7 +212,6 @@ - @@ -251,7 +238,6 @@ - @@ -277,109 +263,78 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -- cgit From 16ba01cd2e8b2af7bdeb6779e913ea4b6453dd78 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Fri, 19 Oct 2018 14:05:37 +0100 Subject: chore: fix dropdown bug on Firefox and reduced duplicate code --- .../components/dropdowns/developers_drop_down.tsx | 46 +++++++++++----------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/packages/website/ts/components/dropdowns/developers_drop_down.tsx b/packages/website/ts/components/dropdowns/developers_drop_down.tsx index b279566e0..6e85c1499 100644 --- a/packages/website/ts/components/dropdowns/developers_drop_down.tsx +++ b/packages/website/ts/components/dropdowns/developers_drop_down.tsx @@ -89,26 +89,22 @@ export class DevelopersDropDown extends React.Component - - {this._renderTitle('Getting started')} - - - {this._renderLinkSection(gettingStartedKeyToLinkInfo1)} - - {this._renderLinkSection(gettingStartedKeyToLinkInfo2)} + + + {this._renderLinkSection(gettingStartedKeyToLinkInfo1, 'Getting started')} + {this._renderLinkSection(gettingStartedKeyToLinkInfo2)} - - - {this._renderTitle('Popular docs')} - {this._renderLinkSection(popularDocsToLinkInfos)} + + + {this._renderLinkSection(popularDocsToLinkInfos, 'Popular docs')} - {this._renderTitle('Useful links')} - {this._renderLinkSection(usefulLinksToLinkInfo)} + {this._renderLinkSection(usefulLinksToLinkInfo, 'Useful links')} @@ -127,16 +123,7 @@ export class DevelopersDropDown extends React.Component - - {title.toUpperCase()} - - - ); - } - private _renderLinkSection(links: ALink[]): React.ReactNode { + private _renderLinkSection(links: ALink[], title: string = ''): React.ReactNode { const numLinks = links.length; let i = 0; const renderLinks = _.map(links, (link: ALink) => { @@ -159,6 +146,17 @@ export class DevelopersDropDown extends React.Component ); }); - return {renderLinks}; + return ( + + + {!_.isEmpty(title) && ( + + {title.toUpperCase()} + + )} + + {renderLinks} + + ); } } -- cgit From 90ba8e0e8df2ccd2907adfcb143ed954b9d0b4c5 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Fri, 19 Oct 2018 17:09:13 +0100 Subject: Remove unused listeners --- packages/react-docs/src/components/doc_reference.tsx | 10 ---------- packages/website/ts/pages/wiki/wiki.tsx | 8 -------- 2 files changed, 18 deletions(-) diff --git a/packages/react-docs/src/components/doc_reference.tsx b/packages/react-docs/src/components/doc_reference.tsx index 00b1286a4..85547576b 100644 --- a/packages/react-docs/src/components/doc_reference.tsx +++ b/packages/react-docs/src/components/doc_reference.tsx @@ -52,12 +52,6 @@ export interface DocReferenceProps { export interface DocReferenceState {} export class DocReference extends React.Component { - public componentDidMount(): void { - window.addEventListener('hashchange', this._onHashChanged.bind(this), false); - } - public componentWillUnmount(): void { - window.removeEventListener('hashchange', this._onHashChanged.bind(this), false); - } public componentDidUpdate(prevProps: DocReferenceProps, _prevState: DocReferenceState): void { if (!_.isEqual(prevProps.docAgnosticFormat, this.props.docAgnosticFormat)) { const hash = window.location.hash.slice(1); @@ -323,8 +317,4 @@ export class DocReference extends React.Component ); } - private _onHashChanged(_event: any): void { - const hash = window.location.hash.slice(1); - sharedUtils.scrollToHash(hash, sharedConstants.SCROLL_CONTAINER_ID); - } } diff --git a/packages/website/ts/pages/wiki/wiki.tsx b/packages/website/ts/pages/wiki/wiki.tsx index e62300ddf..a35404e33 100644 --- a/packages/website/ts/pages/wiki/wiki.tsx +++ b/packages/website/ts/pages/wiki/wiki.tsx @@ -49,9 +49,6 @@ export class Wiki extends React.Component { isHoveringSidebar: false, }; } - public componentDidMount(): void { - window.addEventListener('hashchange', this._onHashChanged.bind(this), false); - } public componentWillMount(): void { // tslint:disable-next-line:no-floating-promises this._fetchArticlesBySectionAsync(); @@ -59,7 +56,6 @@ export class Wiki extends React.Component { public componentWillUnmount(): void { this._isUnmounted = true; clearTimeout(this._wikiBackoffTimeoutId); - window.removeEventListener('hashchange', this._onHashChanged.bind(this), false); } public render(): React.ReactNode { const sectionNameToLinks = _.isUndefined(this.state.articlesBySection) @@ -198,8 +194,4 @@ export class Wiki extends React.Component { } return sectionNameToLinks; } - private _onHashChanged(_event: any): void { - const hash = window.location.hash.slice(1); - sharedUtils.scrollToHash(hash, sharedConstants.SCROLL_CONTAINER_ID); - } } -- cgit From d129c922edd8bc0319273488b23a5257d736502d Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Fri, 19 Oct 2018 17:22:33 +0100 Subject: Use media module --- packages/website/ts/pages/documentation/developers_page.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/website/ts/pages/documentation/developers_page.tsx b/packages/website/ts/pages/documentation/developers_page.tsx index 401f65df3..361dbc86e 100644 --- a/packages/website/ts/pages/documentation/developers_page.tsx +++ b/packages/website/ts/pages/documentation/developers_page.tsx @@ -6,6 +6,7 @@ import { DocsLogo } from 'ts/components/documentation/docs_logo'; import { DocsTopBar } from 'ts/components/documentation/docs_top_bar'; import { Container } from 'ts/components/ui/container'; import { Dispatcher } from 'ts/redux/dispatcher'; +import { media } from 'ts/style/media'; import { styled } from 'ts/style/theme'; import { BrowserType, OperatingSystemType, ScreenWidths } from 'ts/types'; import { Translate } from 'ts/utils/translate'; @@ -98,14 +99,14 @@ const MainContentContainer = padding-right: ${50 - SCROLLBAR_WIDTH}px; overflow: auto; } - @media (max-width: 40em) { + ${media.small` padding-left: 20px; padding-right: 20px; &:hover { padding-right: ${20 - SCROLLBAR_WIDTH}px; overflow: auto; } - } + `} `; export class DevelopersPage extends React.Component { -- cgit From 0de654bbd52f7d4702cec9f1a9a5a2cbb793181b Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Fri, 19 Oct 2018 17:40:55 +0100 Subject: fix: scroll lag on doc reference and wiki pages by using react-scroll `spy` and only updating the sidebar menu items whose active state had changed --- packages/react-shared/src/components/link.tsx | 16 ++- .../src/components/nested_sidebar_menu.tsx | 119 --------------------- packages/react-shared/src/index.ts | 1 - .../website/ts/components/nested_sidebar_menu.tsx | 92 ++++++++++++++++ .../website/ts/pages/documentation/doc_page.tsx | 2 +- .../website/ts/pages/documentation/docs_home.tsx | 3 +- packages/website/ts/pages/wiki/wiki.tsx | 2 +- 7 files changed, 111 insertions(+), 124 deletions(-) delete mode 100644 packages/react-shared/src/components/nested_sidebar_menu.tsx create mode 100644 packages/website/ts/components/nested_sidebar_menu.tsx diff --git a/packages/react-shared/src/components/link.tsx b/packages/react-shared/src/components/link.tsx index 5a456109b..089e6e2ba 100644 --- a/packages/react-shared/src/components/link.tsx +++ b/packages/react-shared/src/components/link.tsx @@ -7,7 +7,7 @@ import * as validUrl from 'valid-url'; import { LinkType } from '../types'; import { constants } from '../utils/constants'; -interface LinkProps { +interface BaseLinkProps { to: string; shouldOpenInNewTab?: boolean; className?: string; @@ -18,6 +18,12 @@ interface LinkProps { fontColor?: string; } +interface ScrollLinkProps extends BaseLinkProps { + onActivityChanged?: (isActive: boolean) => void; +} + +type LinkProps = BaseLinkProps & ScrollLinkProps; + export interface LinkState {} /** @@ -103,11 +109,14 @@ export class Link extends React.Component { {this.props.children} @@ -119,6 +128,11 @@ export class Link extends React.Component { throw new Error(`Unrecognized LinkType: ${type}`); } } + private _onActivityChanged(isActive: boolean): void { + if (this.props.onActivityChanged) { + this.props.onActivityChanged(isActive); + } + } // HACK(fabio): For some reason, the react-scroll link decided to stop the propagation of click events. // We do however rely on these events being propagated in certain scenarios (e.g when the link // is within a dropdown we want to close upon being clicked). Because of this, we register the diff --git a/packages/react-shared/src/components/nested_sidebar_menu.tsx b/packages/react-shared/src/components/nested_sidebar_menu.tsx deleted file mode 100644 index 196c91af1..000000000 --- a/packages/react-shared/src/components/nested_sidebar_menu.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { ObjectMap } from '@0x/types'; -import * as _ from 'lodash'; -import MenuItem from 'material-ui/MenuItem'; -import * as React from 'react'; - -import { ALink, Styles } from '../types'; -import { colors } from '../utils/colors'; -import { utils } from '../utils/utils'; - -import { Link } from './link'; - -export interface NestedSidebarMenuProps { - sectionNameToLinks: ObjectMap; - sidebarHeader?: React.ReactNode; - shouldReformatMenuItemNames?: boolean; -} - -export interface NestedSidebarMenuState { - scrolledToId?: string; -} - -const styles: Styles = { - menuItem: { - minHeight: 0, - paddingLeft: 8, - borderRadius: 6, - }, - menuItemInnerDiv: { - color: colors.grey800, - fontSize: 14, - lineHeight: 2, - padding: 0, - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - }, -}; - -export class NestedSidebarMenu extends React.Component { - public static defaultProps: Partial = { - shouldReformatMenuItemNames: true, - }; - private _urlIntervalCheckId: number | undefined = undefined; - constructor(props: NestedSidebarMenuProps) { - super(props); - this.state = {}; - } - public componentDidMount(): void { - this._urlIntervalCheckId = window.setInterval(() => { - const scrollId = location.hash.slice(1); - if (scrollId !== this.state.scrolledToId) { - this.setState({ - scrolledToId: scrollId, - }); - } - }, 200); - } - public componentWillUnmount(): void { - window.clearInterval(this._urlIntervalCheckId); - } - public render(): React.ReactNode { - const navigation = _.map(this.props.sectionNameToLinks, (links: ALink[], sectionName: string) => { - const finalSectionName = utils.convertCamelCaseToSpaces(sectionName); - // tslint:disable-next-line:no-unused-variable - return ( -
-
- {finalSectionName.toUpperCase()} -
- {this._renderMenuItems(links)} -
- ); - }); - return ( -
- {this.props.sidebarHeader} -
{navigation}
-
- ); - } - private _renderMenuItems(links: ALink[]): React.ReactNode[] { - const scrolledToId = this.state.scrolledToId; - const menuItems = _.map(links, link => { - const finalMenuItemName = this.props.shouldReformatMenuItemNames - ? utils.convertDashesToSpaces(link.title) - : link.title; - let menuItemStyle = styles.menuItem; - let menuItemInnerDivStyle = styles.menuItemInnerDiv; - const isScrolledTo = link.to === scrolledToId; - if (isScrolledTo) { - menuItemStyle = { - ...menuItemStyle, - backgroundColor: colors.lightLinkBlue, - }; - menuItemInnerDivStyle = { - ...menuItemInnerDivStyle, - color: colors.white, - fontWeight: 'bold', - }; - } - return ( -
- - - - {finalMenuItemName} - - - -
- ); - }); - return menuItems; - } -} diff --git a/packages/react-shared/src/index.ts b/packages/react-shared/src/index.ts index e33b09f19..a693f2a36 100644 --- a/packages/react-shared/src/index.ts +++ b/packages/react-shared/src/index.ts @@ -2,7 +2,6 @@ export { AnchorTitle } from './components/anchor_title'; export { MarkdownLinkBlock } from './components/markdown_link_block'; export { MarkdownCodeBlock } from './components/markdown_code_block'; export { MarkdownSection } from './components/markdown_section'; -export { NestedSidebarMenu } from './components/nested_sidebar_menu'; export { SectionHeader } from './components/section_header'; export { Link } from './components/link'; diff --git a/packages/website/ts/components/nested_sidebar_menu.tsx b/packages/website/ts/components/nested_sidebar_menu.tsx new file mode 100644 index 000000000..7de91bf8c --- /dev/null +++ b/packages/website/ts/components/nested_sidebar_menu.tsx @@ -0,0 +1,92 @@ +import { ALink, colors, Link, utils as sharedUtils } from '@0x/react-shared'; +import { ObjectMap } from '@0x/types'; +import * as _ from 'lodash'; +import * as React from 'react'; +import { Button } from 'ts/components/ui/button'; +import { Text } from 'ts/components/ui/text'; + +export interface NestedSidebarMenuProps { + sectionNameToLinks: ObjectMap; + sidebarHeader?: React.ReactNode; + shouldReformatMenuItemNames?: boolean; +} + +export const NestedSidebarMenu = (props: NestedSidebarMenuProps) => { + const navigation = _.map(props.sectionNameToLinks, (links: ALink[], sectionName: string) => { + const finalSectionName = sharedUtils.convertCamelCaseToSpaces(sectionName); + const menuItems = _.map(links, (link, i) => { + const menuItemTitle = props.shouldReformatMenuItemNames + ? _.capitalize(sharedUtils.convertDashesToSpaces(link.title)) + : link.title; + const finalLink = { + ...link, + title: menuItemTitle, + }; + return ; + }); + // tslint:disable-next-line:no-unused-variable + return ( +
+
+ {finalSectionName.toUpperCase()} +
+ {menuItems} +
+ ); + }); + return ( +
+ {props.sidebarHeader} +
{navigation}
+
+ ); +}; + +export interface MenuItemProps { + link: ALink; +} + +export interface MenuItemState { + isActive: boolean; +} + +export class MenuItem extends React.Component { + constructor(props: MenuItemProps) { + super(props); + const isActive = window.location.hash.slice(1) === props.link.to; + this.state = { + isActive, + }; + } + public render(): React.ReactNode { + const isActive = this.state.isActive; + return ( + + + + ); + } + private _onActivityChanged(isActive: boolean): void { + this.setState({ + isActive, + }); + } +} diff --git a/packages/website/ts/pages/documentation/doc_page.tsx b/packages/website/ts/pages/documentation/doc_page.tsx index 7157abfc9..28bf2dba1 100644 --- a/packages/website/ts/pages/documentation/doc_page.tsx +++ b/packages/website/ts/pages/documentation/doc_page.tsx @@ -6,13 +6,13 @@ import { SupportedDocJson, TypeDocUtils, } from '@0x/react-docs'; -import { NestedSidebarMenu } from '@0x/react-shared'; import findVersions = require('find-versions'); import * as _ from 'lodash'; import CircularProgress from 'material-ui/CircularProgress'; import * as React from 'react'; import semverSort = require('semver-sort'); import { SidebarHeader } from 'ts/components/documentation/sidebar_header'; +import { NestedSidebarMenu } from 'ts/components/nested_sidebar_menu'; import { Container } from 'ts/components/ui/container'; import { DevelopersPage } from 'ts/pages/documentation/developers_page'; import { Dispatcher } from 'ts/redux/dispatcher'; diff --git a/packages/website/ts/pages/documentation/docs_home.tsx b/packages/website/ts/pages/documentation/docs_home.tsx index cf229cb3b..298b7ecf3 100644 --- a/packages/website/ts/pages/documentation/docs_home.tsx +++ b/packages/website/ts/pages/documentation/docs_home.tsx @@ -1,8 +1,9 @@ -import { ALink, colors, Link, NestedSidebarMenu } from '@0x/react-shared'; +import { ALink, colors, Link } from '@0x/react-shared'; import { ObjectMap } from '@0x/types'; import * as _ from 'lodash'; import * as React from 'react'; import { OverviewContent } from 'ts/components/documentation/overview_content'; +import { NestedSidebarMenu } from 'ts/components/nested_sidebar_menu'; import { Button } from 'ts/components/ui/button'; import { DevelopersPage } from 'ts/pages/documentation/developers_page'; import { Dispatcher } from 'ts/redux/dispatcher'; diff --git a/packages/website/ts/pages/wiki/wiki.tsx b/packages/website/ts/pages/wiki/wiki.tsx index a35404e33..76df4f06f 100644 --- a/packages/website/ts/pages/wiki/wiki.tsx +++ b/packages/website/ts/pages/wiki/wiki.tsx @@ -5,7 +5,6 @@ import { HeaderSizes, Link, MarkdownSection, - NestedSidebarMenu, utils as sharedUtils, } from '@0x/react-shared'; import { ObjectMap } from '@0x/types'; @@ -13,6 +12,7 @@ import * as _ from 'lodash'; import CircularProgress from 'material-ui/CircularProgress'; import * as React from 'react'; import { SidebarHeader } from 'ts/components/documentation/sidebar_header'; +import { NestedSidebarMenu } from 'ts/components/nested_sidebar_menu'; import { Button } from 'ts/components/ui/button'; import { Container } from 'ts/components/ui/container'; import { DevelopersPage } from 'ts/pages/documentation/developers_page'; -- cgit From bce90318687b38c35af75cd78bf3ee69271b42ad Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Fri, 19 Oct 2018 17:44:49 +0100 Subject: chore: use `colors` module --- packages/website/ts/components/nested_sidebar_menu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/ts/components/nested_sidebar_menu.tsx b/packages/website/ts/components/nested_sidebar_menu.tsx index 7de91bf8c..f83833d69 100644 --- a/packages/website/ts/components/nested_sidebar_menu.tsx +++ b/packages/website/ts/components/nested_sidebar_menu.tsx @@ -70,7 +70,7 @@ export class MenuItem extends React.Component { borderRadius="4px" padding="0.4em 6px" width="100%" - backgroundColor={isActive ? colors.lightLinkBlue : 'rgb(245, 245, 245)'} + backgroundColor={isActive ? colors.lightLinkBlue : colors.grey100} fontSize="14px" textAlign="left" > -- cgit From 66465816ca049118d58ca34bada241fd146f2656 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 19 Oct 2018 10:19:48 -0700 Subject: Getting rid of displayName, and rename usdSection -> _renderUsdSection --- packages/instant/src/components/order_details.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index a7971e1fd..704009d89 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -74,7 +74,6 @@ export class EthAmountRow extends React.Component { shouldEmphasize: false, isEthAmountInBaseUnits: true, }; - public static displayName = 'EthAmountRow'; public render(): React.ReactNode { const { rowLabel, ethAmount, isEthAmountInBaseUnits, shouldEmphasize, isLoading } = this.props; @@ -87,7 +86,7 @@ export class EthAmountRow extends React.Component { {rowLabel} - {this._usdSection()} + {this._renderUsdSection()} {ethFormatter( ethAmount, @@ -102,7 +101,7 @@ export class EthAmountRow extends React.Component { ); } - private _usdSection(): React.ReactNode { + private _renderUsdSection(): React.ReactNode { const usdFormatter = this.props.isEthAmountInBaseUnits ? format.ethBaseAmountInUsd : format.ethUnitAmountInUsd; const shouldHideUsdPriceSection = _.isUndefined(this.props.ethUsdPrice) || _.isUndefined(this.props.ethAmount); return shouldHideUsdPriceSection ? null : ( -- cgit From 2f4e498a099ebbd8078139f90a054941e427de15 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 19 Oct 2018 10:32:37 -0700 Subject: Refactor to make placeholder logic more straightforward --- packages/instant/src/components/instant_heading.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index 48cda7f88..265c3e0a5 100644 --- a/packages/instant/src/components/instant_heading.tsx +++ b/packages/instant/src/components/instant_heading.tsx @@ -20,7 +20,6 @@ export interface InstantHeadingProps { const placeholderColor = ColorOption.white; export class InstantHeading extends React.Component { public render(): React.ReactNode { - const placeholderAmountToAlwaysShow = this._placeholderAmountToAlwaysShow(); return ( { - {placeholderAmountToAlwaysShow || this._ethAmount()} - {placeholderAmountToAlwaysShow || this._dollarAmount()} + + {this._placeholderOrAmount(this._ethAmount.bind(this))} + + {this._placeholderOrAmount(this._dollarAmount.bind(this))} ); } - private _placeholderAmountToAlwaysShow(): React.ReactNode | null { + private _placeholderOrAmount(amountFunction: () => React.ReactNode): React.ReactNode { if (this.props.quoteRequestState === AsyncProcessState.PENDING) { return ; } if (_.isUndefined(this.props.selectedAssetAmount)) { return ; } - return null; + return amountFunction(); } private _ethAmount(): React.ReactNode { -- cgit From 6588cf919ef70f54754b1428df51e967b817788a Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 19 Oct 2018 10:34:44 -0700 Subject: dont export PlainPlaceholder --- packages/instant/src/components/amount_placeholder.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/instant/src/components/amount_placeholder.tsx b/packages/instant/src/components/amount_placeholder.tsx index e27d0ba81..6ef8f0ac3 100644 --- a/packages/instant/src/components/amount_placeholder.tsx +++ b/packages/instant/src/components/amount_placeholder.tsx @@ -6,10 +6,10 @@ import { Pulse } from './animations/pulse'; import { Text } from './ui'; -export interface PlainPlaceholder { +interface PlainPlaceholder { color: ColorOption; } -export const PlainPlaceholder: React.StatelessComponent = props => ( +const PlainPlaceholder: React.StatelessComponent = props => ( -- cgit From ce4da870d79a589913d1660de8cf012b894357c6 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Fri, 19 Oct 2018 19:38:12 +0100 Subject: chore: use Text --- packages/website/ts/components/nested_sidebar_menu.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/website/ts/components/nested_sidebar_menu.tsx b/packages/website/ts/components/nested_sidebar_menu.tsx index f83833d69..021e3f0e5 100644 --- a/packages/website/ts/components/nested_sidebar_menu.tsx +++ b/packages/website/ts/components/nested_sidebar_menu.tsx @@ -27,9 +27,9 @@ export const NestedSidebarMenu = (props: NestedSidebarMenuProps) => { // tslint:disable-next-line:no-unused-variable return (
-
+ {finalSectionName.toUpperCase()} -
+ {menuItems}
); -- cgit From b49e5c76e485beead2554027a3ff8af101bce1d5 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Fri, 19 Oct 2018 19:38:20 +0100 Subject: Use em for all --- packages/website/ts/components/nested_sidebar_menu.tsx | 2 +- packages/website/ts/pages/documentation/docs_home.tsx | 2 +- packages/website/ts/pages/wiki/wiki.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/website/ts/components/nested_sidebar_menu.tsx b/packages/website/ts/components/nested_sidebar_menu.tsx index 021e3f0e5..db7d55261 100644 --- a/packages/website/ts/components/nested_sidebar_menu.tsx +++ b/packages/website/ts/components/nested_sidebar_menu.tsx @@ -68,7 +68,7 @@ export class MenuItem extends React.Component { > -
+ ); } private readonly _handleClick = async () => { @@ -43,10 +42,13 @@ export class BuyButton extends React.Component { this.props.onClick(this.props.buyQuote); let txnHash; try { - txnHash = await this.props.assetBuyer.executeBuyQuoteAsync(this.props.buyQuote); + txnHash = await this.props.assetBuyer.executeBuyQuoteAsync(this.props.buyQuote, { + gasLimit: 5000000, + }); await web3Wrapper.awaitTransactionSuccessAsync(txnHash); this.props.onBuySuccess(this.props.buyQuote, txnHash); - } catch { + } catch (e) { + console.log('error', e); this.props.onBuyFailure(this.props.buyQuote, txnHash); } }; diff --git a/packages/instant/src/components/retry_button.tsx b/packages/instant/src/components/retry_button.tsx new file mode 100644 index 000000000..5e36506d0 --- /dev/null +++ b/packages/instant/src/components/retry_button.tsx @@ -0,0 +1,24 @@ +import * as React from 'react'; + +import { ColorOption } from '../style/theme'; + +import { Button, Text } from './ui'; + +export interface RetryButtonProps { + onClick: () => void; +} + +export const RetryButton: React.StatelessComponent = props => { + return ( + + ); +}; diff --git a/packages/instant/src/containers/selected_asset_buy_button.ts b/packages/instant/src/containers/selected_asset_buy_button.ts index 99f971321..a5fa0aa8e 100644 --- a/packages/instant/src/containers/selected_asset_buy_button.ts +++ b/packages/instant/src/containers/selected_asset_buy_button.ts @@ -1,3 +1,4 @@ +// TODO: Rename to SelectedAssetButton import { AssetBuyer, BuyQuote } from '@0x/asset-buyer'; import * as _ from 'lodash'; import * as React from 'react'; @@ -8,45 +9,31 @@ import { Action, actions } from '../redux/actions'; import { State } from '../redux/reducer'; import { AsyncProcessState } from '../types'; -import { BuyButton } from '../components/buy_button'; +import { AssetButton } from '../components/asset_button'; +// TODO: rename export interface SelectedAssetBuyButtonProps {} interface ConnectedState { assetBuyer?: AssetBuyer; - text: string; + buyOrderState: AsyncProcessState; buyQuote?: BuyQuote; } interface ConnectedDispatch { - onClick: (buyQuote: BuyQuote) => void; + onBuyClick: (buyQuote: BuyQuote) => void; onBuySuccess: (buyQuote: BuyQuote) => void; onBuyFailure: (buyQuote: BuyQuote) => void; } -const textForState = (state: AsyncProcessState): string => { - switch (state) { - case AsyncProcessState.NONE: - return 'Buy'; - case AsyncProcessState.PENDING: - return '...Loading'; - case AsyncProcessState.SUCCESS: - return 'Success!'; - case AsyncProcessState.FAILURE: - return 'Failed'; - default: - return 'Buy'; - } -}; - const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyButtonProps): ConnectedState => ({ assetBuyer: state.assetBuyer, - text: textForState(state.buyOrderState), + buyOrderState: state.buyOrderState, buyQuote: state.latestBuyQuote, }); const mapDispatchToProps = (dispatch: Dispatch, ownProps: SelectedAssetBuyButtonProps): ConnectedDispatch => ({ - onClick: buyQuote => dispatch(actions.updatebuyOrderState(AsyncProcessState.PENDING)), + onBuyClick: buyQuote => dispatch(actions.updatebuyOrderState(AsyncProcessState.PENDING)), onBuySuccess: buyQuote => dispatch(actions.updatebuyOrderState(AsyncProcessState.SUCCESS)), onBuyFailure: buyQuote => dispatch(actions.updatebuyOrderState(AsyncProcessState.FAILURE)), }); @@ -54,4 +41,4 @@ const mapDispatchToProps = (dispatch: Dispatch, ownProps: SelectedAssetB export const SelectedAssetBuyButton: React.ComponentClass = connect( mapStateToProps, mapDispatchToProps, -)(BuyButton); +)(AssetButton); -- cgit From 6c79a858dff2766917ee3a4b5d4f623b66dadd17 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 19 Oct 2018 13:51:57 -0700 Subject: WIP: clear buy quote working --- packages/instant/src/components/asset_button.tsx | 10 +++------- packages/instant/src/containers/selected_asset_buy_button.ts | 2 ++ packages/instant/src/redux/actions.ts | 2 ++ packages/instant/src/redux/reducer.ts | 8 ++++++++ 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/instant/src/components/asset_button.tsx b/packages/instant/src/components/asset_button.tsx index 84a27303d..4ae7a2704 100644 --- a/packages/instant/src/components/asset_button.tsx +++ b/packages/instant/src/components/asset_button.tsx @@ -9,6 +9,7 @@ import { BuyButton } from './buy_button'; import { RetryButton } from './retry_button'; import { Container } from './ui'; +// TODO: split into sepearte components? getting really big.. interface AssetButtonProps { assetBuyer?: AssetBuyer; buyQuote?: BuyQuote; @@ -16,6 +17,7 @@ interface AssetButtonProps { onBuyClick: (buyQuote: BuyQuote) => void; onBuySuccess: (buyQuote: BuyQuote) => void; onBuyFailure: (buyQuote: BuyQuote) => void; + onRetryClick: () => void; } export class AssetButton extends React.Component { @@ -30,13 +32,7 @@ export class AssetButton extends React.Component { // TODO: figure out why buyOrderState is undefined in beginning, get rid of default switch (this.props.buyOrderState) { case AsyncProcessState.FAILURE: - return ( - { - console.log('try again'); - }} - /> - ); + return ; case AsyncProcessState.SUCCESS: return
; default: diff --git a/packages/instant/src/containers/selected_asset_buy_button.ts b/packages/instant/src/containers/selected_asset_buy_button.ts index a5fa0aa8e..41d196b03 100644 --- a/packages/instant/src/containers/selected_asset_buy_button.ts +++ b/packages/instant/src/containers/selected_asset_buy_button.ts @@ -24,6 +24,7 @@ interface ConnectedDispatch { onBuyClick: (buyQuote: BuyQuote) => void; onBuySuccess: (buyQuote: BuyQuote) => void; onBuyFailure: (buyQuote: BuyQuote) => void; + onRetryClick: () => void; } const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyButtonProps): ConnectedState => ({ @@ -36,6 +37,7 @@ const mapDispatchToProps = (dispatch: Dispatch, ownProps: SelectedAssetB onBuyClick: buyQuote => dispatch(actions.updatebuyOrderState(AsyncProcessState.PENDING)), onBuySuccess: buyQuote => dispatch(actions.updatebuyOrderState(AsyncProcessState.SUCCESS)), onBuyFailure: buyQuote => dispatch(actions.updatebuyOrderState(AsyncProcessState.FAILURE)), + onRetryClick: () => dispatch(actions.clearBuyQuoteAndSelectedAssetAmount()), }); export const SelectedAssetBuyButton: React.ComponentClass = connect( diff --git a/packages/instant/src/redux/actions.ts b/packages/instant/src/redux/actions.ts index bc75ce66c..d6388d77e 100644 --- a/packages/instant/src/redux/actions.ts +++ b/packages/instant/src/redux/actions.ts @@ -31,6 +31,7 @@ export enum ActionTypes { SET_ERROR = 'SET_ERROR', HIDE_ERROR = 'HIDE_ERROR', CLEAR_ERROR = 'CLEAR_ERROR', + CLEAR_BUY_QUOTE_AND_SELECTED_ASSET_AMOUNT = 'CLEAR_BUY_QUOTE_AND_SELECTED_ASSET_AMOUNT', } export const actions = { @@ -45,4 +46,5 @@ export const actions = { setError: (error?: any) => createAction(ActionTypes.SET_ERROR, error), hideError: () => createAction(ActionTypes.HIDE_ERROR), clearError: () => createAction(ActionTypes.CLEAR_ERROR), + clearBuyQuoteAndSelectedAssetAmount: () => createAction(ActionTypes.CLEAR_BUY_QUOTE_AND_SELECTED_ASSET_AMOUNT), }; diff --git a/packages/instant/src/redux/reducer.ts b/packages/instant/src/redux/reducer.ts index 657bd0e40..10e56f152 100644 --- a/packages/instant/src/redux/reducer.ts +++ b/packages/instant/src/redux/reducer.ts @@ -99,6 +99,14 @@ export const reducer = (state: State = INITIAL_STATE, action: Action): State => ...state, selectedAsset: newSelectedAsset, }; + case ActionTypes.CLEAR_BUY_QUOTE_AND_SELECTED_ASSET_AMOUNT: + return { + ...state, + latestBuyQuote: undefined, + quoteState: AsyncProcessState.NONE, + buyOrderState: AsyncProcessState.NONE, + selectedAssetAmount: undefined, + }; default: return state; } -- cgit From d2766d7ced990efd5a91441b459f36e8a21f8513 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 19 Oct 2018 14:49:38 -0700 Subject: Selected Asset button --- packages/instant/src/components/asset_button.tsx | 51 ---------------------- packages/instant/src/components/buy_button.tsx | 6 +-- .../src/components/zero_ex_instant_container.tsx | 6 ++- .../src/containers/selected_asset_button.tsx | 33 ++++++++++++++ .../src/containers/selected_asset_buy_button.ts | 10 +---- .../src/containers/selected_asset_retry_button.tsx | 27 ++++++++++++ 6 files changed, 68 insertions(+), 65 deletions(-) delete mode 100644 packages/instant/src/components/asset_button.tsx create mode 100644 packages/instant/src/containers/selected_asset_button.tsx create mode 100644 packages/instant/src/containers/selected_asset_retry_button.tsx diff --git a/packages/instant/src/components/asset_button.tsx b/packages/instant/src/components/asset_button.tsx deleted file mode 100644 index 4ae7a2704..000000000 --- a/packages/instant/src/components/asset_button.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { AssetBuyer, BuyQuote } from '@0x/asset-buyer'; -import * as React from 'react'; - -import { AsyncProcessState } from '../types'; - -// TODO: better name? - -import { BuyButton } from './buy_button'; -import { RetryButton } from './retry_button'; -import { Container } from './ui'; - -// TODO: split into sepearte components? getting really big.. -interface AssetButtonProps { - assetBuyer?: AssetBuyer; - buyQuote?: BuyQuote; - buyOrderState: AsyncProcessState; - onBuyClick: (buyQuote: BuyQuote) => void; - onBuySuccess: (buyQuote: BuyQuote) => void; - onBuyFailure: (buyQuote: BuyQuote) => void; - onRetryClick: () => void; -} - -export class AssetButton extends React.Component { - public render(): React.ReactNode { - return ( - - {this._renderButton()} - - ); - } - private _renderButton(): React.ReactNode { - // TODO: figure out why buyOrderState is undefined in beginning, get rid of default - switch (this.props.buyOrderState) { - case AsyncProcessState.FAILURE: - return ; - case AsyncProcessState.SUCCESS: - return
; - default: - return ( - - ); - } - } -} diff --git a/packages/instant/src/components/buy_button.tsx b/packages/instant/src/components/buy_button.tsx index 4d2386620..5eef18aa3 100644 --- a/packages/instant/src/components/buy_button.tsx +++ b/packages/instant/src/components/buy_button.tsx @@ -6,7 +6,7 @@ import { ColorOption } from '../style/theme'; import { util } from '../util/util'; import { web3Wrapper } from '../util/web3_wrapper'; -import { Button, Container, Text } from './ui'; +import { Button, Text } from './ui'; export interface BuyButtonProps { buyQuote?: BuyQuote; @@ -14,7 +14,6 @@ export interface BuyButtonProps { onClick: (buyQuote: BuyQuote) => void; onBuySuccess: (buyQuote: BuyQuote, txnHash: string) => void; onBuyFailure: (buyQuote: BuyQuote, tnxHash?: string) => void; - text: string; } export class BuyButton extends React.Component { @@ -24,12 +23,11 @@ export class BuyButton extends React.Component { onBuyFailure: util.boundNoop, }; public render(): React.ReactNode { - // TODO: move container out const shouldDisableButton = _.isUndefined(this.props.buyQuote); return ( ); diff --git a/packages/instant/src/components/zero_ex_instant_container.tsx b/packages/instant/src/components/zero_ex_instant_container.tsx index cf918d890..088d0a93e 100644 --- a/packages/instant/src/components/zero_ex_instant_container.tsx +++ b/packages/instant/src/components/zero_ex_instant_container.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { LatestBuyQuoteOrderDetails } from '../containers/latest_buy_quote_order_details'; import { LatestError } from '../containers/latest_error'; -import { SelectedAssetBuyButton } from '../containers/selected_asset_buy_button'; +import { SelectedAssetButton } from '../containers/selected_asset_button'; import { SelectedAssetInstantHeading } from '../containers/selected_asset_instant_heading'; import { ColorOption } from '../style/theme'; @@ -26,7 +26,9 @@ export const ZeroExInstantContainer: React.StatelessComponent - + + + diff --git a/packages/instant/src/containers/selected_asset_button.tsx b/packages/instant/src/containers/selected_asset_button.tsx new file mode 100644 index 000000000..d6e7303dd --- /dev/null +++ b/packages/instant/src/containers/selected_asset_button.tsx @@ -0,0 +1,33 @@ +import { AssetBuyer, BuyQuote } from '@0x/asset-buyer'; +import * as _ from 'lodash'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; + +import { State } from '../redux/reducer'; +import { AsyncProcessState } from '../types'; + +import { SelectedAssetBuyButton } from './selected_asset_buy_button'; +import { SelectedAssetRetryButton } from './selected_asset_retry_button'; + +interface ConnectedState { + buyOrderState: AsyncProcessState; +} +export interface SelectedAssetButtonProps {} +const mapStateToProps = (state: State, _ownProps: SelectedAssetButtonProps): ConnectedState => ({ + buyOrderState: state.buyOrderState, +}); + +const SelectedAssetButtonPresentationComponent: React.StatelessComponent<{ + buyOrderState: AsyncProcessState; +}> = props => { + if (props.buyOrderState === AsyncProcessState.FAILURE) { + return ; + } + + return ; +}; + +export const SelectedAssetButton: React.ComponentClass = connect(mapStateToProps)( + SelectedAssetButtonPresentationComponent, +); diff --git a/packages/instant/src/containers/selected_asset_buy_button.ts b/packages/instant/src/containers/selected_asset_buy_button.ts index 41d196b03..7306ec2ec 100644 --- a/packages/instant/src/containers/selected_asset_buy_button.ts +++ b/packages/instant/src/containers/selected_asset_buy_button.ts @@ -1,4 +1,3 @@ -// TODO: Rename to SelectedAssetButton import { AssetBuyer, BuyQuote } from '@0x/asset-buyer'; import * as _ from 'lodash'; import * as React from 'react'; @@ -9,14 +8,12 @@ import { Action, actions } from '../redux/actions'; import { State } from '../redux/reducer'; import { AsyncProcessState } from '../types'; -import { AssetButton } from '../components/asset_button'; +import { BuyButton } from '../components/buy_button'; -// TODO: rename export interface SelectedAssetBuyButtonProps {} interface ConnectedState { assetBuyer?: AssetBuyer; - buyOrderState: AsyncProcessState; buyQuote?: BuyQuote; } @@ -24,12 +21,10 @@ interface ConnectedDispatch { onBuyClick: (buyQuote: BuyQuote) => void; onBuySuccess: (buyQuote: BuyQuote) => void; onBuyFailure: (buyQuote: BuyQuote) => void; - onRetryClick: () => void; } const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyButtonProps): ConnectedState => ({ assetBuyer: state.assetBuyer, - buyOrderState: state.buyOrderState, buyQuote: state.latestBuyQuote, }); @@ -37,10 +32,9 @@ const mapDispatchToProps = (dispatch: Dispatch, ownProps: SelectedAssetB onBuyClick: buyQuote => dispatch(actions.updatebuyOrderState(AsyncProcessState.PENDING)), onBuySuccess: buyQuote => dispatch(actions.updatebuyOrderState(AsyncProcessState.SUCCESS)), onBuyFailure: buyQuote => dispatch(actions.updatebuyOrderState(AsyncProcessState.FAILURE)), - onRetryClick: () => dispatch(actions.clearBuyQuoteAndSelectedAssetAmount()), }); export const SelectedAssetBuyButton: React.ComponentClass = connect( mapStateToProps, mapDispatchToProps, -)(AssetButton); +)(BuyButton); diff --git a/packages/instant/src/containers/selected_asset_retry_button.tsx b/packages/instant/src/containers/selected_asset_retry_button.tsx new file mode 100644 index 000000000..f8963ca8e --- /dev/null +++ b/packages/instant/src/containers/selected_asset_retry_button.tsx @@ -0,0 +1,27 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; + +import { Action, actions } from '../redux/actions'; + +import { RetryButton } from '../components/retry_button'; +import { State } from '../redux/reducer'; + +export interface SelectedAssetRetryButtonProps {} + +interface ConnectedDispatch { + onClick: () => void; +} + +const mapDispatchToProps = ( + dispatch: Dispatch, + _ownProps: SelectedAssetRetryButtonProps, +): ConnectedDispatch => ({ + onClick: () => dispatch(actions.clearBuyQuoteAndSelectedAssetAmount()), +}); + +export const SelectedAssetRetryButton: React.ComponentClass = connect( + null, + mapDispatchToProps, +)(RetryButton); -- cgit From 092d010c2dcdeffa7c44c147d380255c33649679 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 19 Oct 2018 15:57:11 -0700 Subject: Render failure icon --- .../instant/src/components/instant_heading.tsx | 25 ++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index 5d39e4048..7ae509e3a 100644 --- a/packages/instant/src/components/instant_heading.tsx +++ b/packages/instant/src/components/instant_heading.tsx @@ -9,6 +9,7 @@ import { format } from '../util/format'; import { AmountPlaceholder } from './amount_placeholder'; import { Container, Flex, Text } from './ui'; +import { Icon } from './ui/icon'; export interface InstantHeadingProps { selectedAssetAmount?: BigNumber; @@ -43,14 +44,34 @@ export class InstantHeading extends React.Component { - {this._placeholderOrAmount(this._ethAmount)} - {this._placeholderOrAmount(this._dollarAmount)} + {this._renderIconOrAmounts()} ); } + private _renderIconOrAmounts(): React.ReactNode { + const icon = this._renderIcon(); + if (icon) { + return icon; + } + + return ( + + {this._placeholderOrAmount(this._ethAmount)} + {this._placeholderOrAmount(this._dollarAmount)} + + ); + } + + private _renderIcon(): React.ReactNode { + if (this.props.buyOrderState === AsyncProcessState.FAILURE) { + return ; + } + return undefined; + } + private _renderTopText(): React.ReactNode { if (this.props.buyOrderState === AsyncProcessState.FAILURE) { return 'Order failed'; -- cgit From ac2d93ab2261e19f87870f4143806d62c1d1c619 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 19 Oct 2018 16:06:42 -0700 Subject: Small refacotor of icons or amounts part --- packages/instant/src/components/instant_heading.tsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index 7ae509e3a..856e4d43e 100644 --- a/packages/instant/src/components/instant_heading.tsx +++ b/packages/instant/src/components/instant_heading.tsx @@ -22,6 +22,7 @@ export interface InstantHeadingProps { const placeholderColor = ColorOption.white; export class InstantHeading extends React.Component { public render(): React.ReactNode { + const iconOrAmounts = this._renderIcon() || this._renderAmountsSection(); return ( { - {this._renderIconOrAmounts()} + {iconOrAmounts} ); } - private _renderIconOrAmounts(): React.ReactNode { - const icon = this._renderIcon(); - if (icon) { - return icon; - } - + private _renderAmountsSection(): React.ReactNode { return ( {this._placeholderOrAmount(this._ethAmount)} -- cgit From 51779fec38a32c0bbbc0209559f84aeda8a45d1a Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 19 Oct 2018 16:11:01 -0700 Subject: linting --- packages/instant/src/components/buy_button.tsx | 1 - packages/instant/src/containers/selected_asset_button.tsx | 2 -- packages/instant/src/containers/selected_asset_retry_button.tsx | 1 - 3 files changed, 4 deletions(-) diff --git a/packages/instant/src/components/buy_button.tsx b/packages/instant/src/components/buy_button.tsx index 5eef18aa3..9a3ded699 100644 --- a/packages/instant/src/components/buy_button.tsx +++ b/packages/instant/src/components/buy_button.tsx @@ -46,7 +46,6 @@ export class BuyButton extends React.Component { await web3Wrapper.awaitTransactionSuccessAsync(txnHash); this.props.onBuySuccess(this.props.buyQuote, txnHash); } catch (e) { - console.log('error', e); this.props.onBuyFailure(this.props.buyQuote, txnHash); } }; diff --git a/packages/instant/src/containers/selected_asset_button.tsx b/packages/instant/src/containers/selected_asset_button.tsx index d6e7303dd..d84ae1a52 100644 --- a/packages/instant/src/containers/selected_asset_button.tsx +++ b/packages/instant/src/containers/selected_asset_button.tsx @@ -1,8 +1,6 @@ -import { AssetBuyer, BuyQuote } from '@0x/asset-buyer'; import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { State } from '../redux/reducer'; import { AsyncProcessState } from '../types'; diff --git a/packages/instant/src/containers/selected_asset_retry_button.tsx b/packages/instant/src/containers/selected_asset_retry_button.tsx index f8963ca8e..66177551d 100644 --- a/packages/instant/src/containers/selected_asset_retry_button.tsx +++ b/packages/instant/src/containers/selected_asset_retry_button.tsx @@ -6,7 +6,6 @@ import { Dispatch } from 'redux'; import { Action, actions } from '../redux/actions'; import { RetryButton } from '../components/retry_button'; -import { State } from '../redux/reducer'; export interface SelectedAssetRetryButtonProps {} -- cgit From bf0a4bd91b6e6d15a468b0e75a3a5846c98b85ab Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 19 Oct 2018 16:37:54 -0700 Subject: feat(instant): Add failure state and icon --- packages/instant/src/components/retry_button.tsx | 17 ++----------- .../instant/src/components/secondary_button.tsx | 28 ++++++++++++++++++++++ .../src/containers/selected_asset_button.tsx | 5 ++++ 3 files changed, 35 insertions(+), 15 deletions(-) create mode 100644 packages/instant/src/components/secondary_button.tsx diff --git a/packages/instant/src/components/retry_button.tsx b/packages/instant/src/components/retry_button.tsx index 5e36506d0..28547ce54 100644 --- a/packages/instant/src/components/retry_button.tsx +++ b/packages/instant/src/components/retry_button.tsx @@ -1,24 +1,11 @@ import * as React from 'react'; -import { ColorOption } from '../style/theme'; - -import { Button, Text } from './ui'; +import { SecondaryButton } from './secondary_button'; export interface RetryButtonProps { onClick: () => void; } export const RetryButton: React.StatelessComponent = props => { - return ( - - ); + return ; }; diff --git a/packages/instant/src/components/secondary_button.tsx b/packages/instant/src/components/secondary_button.tsx new file mode 100644 index 000000000..e073f6061 --- /dev/null +++ b/packages/instant/src/components/secondary_button.tsx @@ -0,0 +1,28 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +import { ColorOption } from '../style/theme'; + +import { Button, ButtonProps } from './ui/button'; +import { Text } from './ui/text'; + +export interface SecondaryButtonProps extends ButtonProps { + text: string; +} + +export const SecondaryButton: React.StatelessComponent = props => { + const buttonProps = _.omit(props, 'text'); + return ( + + ); +}; diff --git a/packages/instant/src/containers/selected_asset_button.tsx b/packages/instant/src/containers/selected_asset_button.tsx index d84ae1a52..8c617804e 100644 --- a/packages/instant/src/containers/selected_asset_button.tsx +++ b/packages/instant/src/containers/selected_asset_button.tsx @@ -2,6 +2,7 @@ import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; +import { SecondaryButton } from '../components/secondary_button'; import { State } from '../redux/reducer'; import { AsyncProcessState } from '../types'; @@ -21,6 +22,10 @@ const SelectedAssetButtonPresentationComponent: React.StatelessComponent<{ }> = props => { if (props.buyOrderState === AsyncProcessState.FAILURE) { return ; + } else if (props.buyOrderState === AsyncProcessState.SUCCESS) { + return ; + } else if (props.buyOrderState === AsyncProcessState.PENDING) { + return ; } return ; -- cgit From a766d78706cb45336263388360584390fe0e62f2 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 19 Oct 2018 16:39:42 -0700 Subject: Remove hack fix --- packages/instant/src/components/buy_button.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/instant/src/components/buy_button.tsx b/packages/instant/src/components/buy_button.tsx index 9a3ded699..2def34fd7 100644 --- a/packages/instant/src/components/buy_button.tsx +++ b/packages/instant/src/components/buy_button.tsx @@ -40,12 +40,10 @@ export class BuyButton extends React.Component { this.props.onClick(this.props.buyQuote); let txnHash; try { - txnHash = await this.props.assetBuyer.executeBuyQuoteAsync(this.props.buyQuote, { - gasLimit: 5000000, - }); + txnHash = await this.props.assetBuyer.executeBuyQuoteAsync(this.props.buyQuote); await web3Wrapper.awaitTransactionSuccessAsync(txnHash); this.props.onBuySuccess(this.props.buyQuote, txnHash); - } catch (e) { + } catch { this.props.onBuyFailure(this.props.buyQuote, txnHash); } }; -- cgit From 7e24c04c0bd45cae753aac818b1ec0a64bd85fa4 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sat, 20 Oct 2018 15:01:29 +0100 Subject: fix: Update address to ZRX token to newly deployed 'mintable' ZRX token on Kovan/Ropsten --- packages/website/ts/utils/fake_token_registry.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/website/ts/utils/fake_token_registry.ts b/packages/website/ts/utils/fake_token_registry.ts index 607dd2553..0d5b3486a 100644 --- a/packages/website/ts/utils/fake_token_registry.ts +++ b/packages/website/ts/utils/fake_token_registry.ts @@ -694,7 +694,7 @@ export const fakeTokenRegistry: { [networkId: string]: FakeTokenRegistryEntry[] ], '42': [ { - address: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570', + address: '0x2002d3812f58e35f0ea1ffbf80a75a38c32175fa', name: '0x Protocol Token', symbol: 'ZRX', decimals: 18, @@ -858,7 +858,7 @@ export const fakeTokenRegistry: { [networkId: string]: FakeTokenRegistryEntry[] decimals: 19, }, { - address: '0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d', + address: '0xff67881f8d12f372d91baae9752eb3631ff0ed00', name: '0x Protocol Token', symbol: 'ZRX', decimals: 18, -- cgit From b9dccf9da340e276ddf6f6dd35118cc6fb77026f Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 21 Oct 2018 19:20:36 +0200 Subject: chore: add whole number schema --- packages/json-schemas/schemas/whole_number_schema.json | 5 +++++ packages/json-schemas/src/schemas.ts | 2 ++ packages/json-schemas/tsconfig.json | 3 ++- 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 packages/json-schemas/schemas/whole_number_schema.json diff --git a/packages/json-schemas/schemas/whole_number_schema.json b/packages/json-schemas/schemas/whole_number_schema.json new file mode 100644 index 000000000..944ce820e --- /dev/null +++ b/packages/json-schemas/schemas/whole_number_schema.json @@ -0,0 +1,5 @@ +{ + "id": "/wholeNumberSchema", + "type": "string", + "pattern": "^\\d+$" +} diff --git a/packages/json-schemas/src/schemas.ts b/packages/json-schemas/src/schemas.ts index 8318df617..8ece5de75 100644 --- a/packages/json-schemas/src/schemas.ts +++ b/packages/json-schemas/src/schemas.ts @@ -34,6 +34,7 @@ import * as signedOrderSchema from '../schemas/signed_order_schema.json'; import * as signedOrdersSchema from '../schemas/signed_orders_schema.json'; import * as tokenSchema from '../schemas/token_schema.json'; import * as txDataSchema from '../schemas/tx_data_schema.json'; +import * as wholeNumberSchema from '../schemas/whole_number_schema.json'; import * as zeroExTransactionSchema from '../schemas/zero_ex_transaction_schema.json'; export const schemas = { @@ -74,4 +75,5 @@ export const schemas = { relayerApiOrdersResponseSchema, relayerApiAssetDataPairsSchema, zeroExTransactionSchema, + wholeNumberSchema, }; diff --git a/packages/json-schemas/tsconfig.json b/packages/json-schemas/tsconfig.json index 425dfcfe1..76d2cf240 100644 --- a/packages/json-schemas/tsconfig.json +++ b/packages/json-schemas/tsconfig.json @@ -43,6 +43,7 @@ "./schemas/js_number.json", "./schemas/zero_ex_transaction_schema.json", "./schemas/tx_data_schema.json", - "./schemas/index_filter_values_schema.json" + "./schemas/index_filter_values_schema.json", + "./schemas/wholeNumberSchema.json" ] } -- cgit From 632d7b6fc121444055c91e00ab616757ffebdf68 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 21 Oct 2018 19:24:19 +0200 Subject: fix: improve schemas by enforcing that amounts that must be whole numbers (e.g Order asset amounts) no longer allow decimal numbers --- packages/json-schemas/schemas/order_cancel_schema.json | 2 +- .../schemas/order_fill_or_kill_requests_schema.json | 2 +- .../json-schemas/schemas/order_fill_requests_schema.json | 2 +- packages/json-schemas/schemas/order_schema.json | 12 ++++++------ .../schemas/relayer_api_asset_data_trade_info_schema.json | 4 ++-- .../schemas/relayer_api_order_config_payload_schema.json | 6 +++--- .../schemas/relayer_api_order_config_response_schema.json | 4 ++-- .../json-schemas/schemas/zero_ex_transaction_schema.json | 2 +- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/json-schemas/schemas/order_cancel_schema.json b/packages/json-schemas/schemas/order_cancel_schema.json index 09d4068c6..8d0999941 100644 --- a/packages/json-schemas/schemas/order_cancel_schema.json +++ b/packages/json-schemas/schemas/order_cancel_schema.json @@ -4,7 +4,7 @@ "items": { "properties": { "order": { "$ref": "/orderSchema" }, - "takerTokenCancelAmount": { "$ref": "/numberSchema" } + "takerTokenCancelAmount": { "$ref": "/wholeNumberSchema" } }, "required": ["order", "takerTokenCancelAmount"], "type": "object" diff --git a/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.json b/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.json index c9549c72e..73bbf20bb 100644 --- a/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.json +++ b/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.json @@ -4,7 +4,7 @@ "items": { "properties": { "signedOrder": { "$ref": "/signedOrderSchema" }, - "fillTakerAmount": { "$ref": "/numberSchema" } + "fillTakerAmount": { "$ref": "/wholeNumberSchema" } }, "required": ["signedOrder", "fillTakerAmount"], "type": "object" diff --git a/packages/json-schemas/schemas/order_fill_requests_schema.json b/packages/json-schemas/schemas/order_fill_requests_schema.json index 98325653b..d06fb19a2 100644 --- a/packages/json-schemas/schemas/order_fill_requests_schema.json +++ b/packages/json-schemas/schemas/order_fill_requests_schema.json @@ -4,7 +4,7 @@ "items": { "properties": { "signedOrder": { "$ref": "/signedOrderSchema" }, - "takerTokenFillAmount": { "$ref": "/numberSchema" } + "takerTokenFillAmount": { "$ref": "/wholeNumberSchema" } }, "required": ["signedOrder", "takerTokenFillAmount"], "type": "object" diff --git a/packages/json-schemas/schemas/order_schema.json b/packages/json-schemas/schemas/order_schema.json index de2c8c7c2..c70b9e2dd 100644 --- a/packages/json-schemas/schemas/order_schema.json +++ b/packages/json-schemas/schemas/order_schema.json @@ -3,17 +3,17 @@ "properties": { "makerAddress": { "$ref": "/addressSchema" }, "takerAddress": { "$ref": "/addressSchema" }, - "makerFee": { "$ref": "/numberSchema" }, - "takerFee": { "$ref": "/numberSchema" }, + "makerFee": { "$ref": "/wholeNumberSchema" }, + "takerFee": { "$ref": "/wholeNumberSchema" }, "senderAddress": { "$ref": "/addressSchema" }, - "makerAssetAmount": { "$ref": "/numberSchema" }, - "takerAssetAmount": { "$ref": "/numberSchema" }, + "makerAssetAmount": { "$ref": "/wholeNumberSchema" }, + "takerAssetAmount": { "$ref": "/wholeNumberSchema" }, "makerAssetData": { "$ref": "/hexSchema" }, "takerAssetData": { "$ref": "/hexSchema" }, - "salt": { "$ref": "/numberSchema" }, + "salt": { "$ref": "/wholeNumberSchema" }, "exchangeAddress": { "$ref": "/addressSchema" }, "feeRecipientAddress": { "$ref": "/addressSchema" }, - "expirationTimeSeconds": { "$ref": "/numberSchema" } + "expirationTimeSeconds": { "$ref": "/wholeNumberSchema" } }, "required": [ "makerAddress", diff --git a/packages/json-schemas/schemas/relayer_api_asset_data_trade_info_schema.json b/packages/json-schemas/schemas/relayer_api_asset_data_trade_info_schema.json index 0ab9b444f..e0f274c5d 100644 --- a/packages/json-schemas/schemas/relayer_api_asset_data_trade_info_schema.json +++ b/packages/json-schemas/schemas/relayer_api_asset_data_trade_info_schema.json @@ -3,8 +3,8 @@ "type": "object", "properties": { "assetData": { "$ref": "/hexSchema" }, - "minAmount": { "$ref": "/numberSchema" }, - "maxAmount": { "$ref": "/numberSchema" }, + "minAmount": { "$ref": "/wholeNumberSchema" }, + "maxAmount": { "$ref": "/wholeNumberSchema" }, "precision": { "type": "number" } }, "required": ["assetData"] diff --git a/packages/json-schemas/schemas/relayer_api_order_config_payload_schema.json b/packages/json-schemas/schemas/relayer_api_order_config_payload_schema.json index bc95b61ef..f4583fc62 100644 --- a/packages/json-schemas/schemas/relayer_api_order_config_payload_schema.json +++ b/packages/json-schemas/schemas/relayer_api_order_config_payload_schema.json @@ -4,12 +4,12 @@ "properties": { "makerAddress": { "$ref": "/addressSchema" }, "takerAddress": { "$ref": "/addressSchema" }, - "makerAssetAmount": { "$ref": "/numberSchema" }, - "takerAssetAmount": { "$ref": "/numberSchema" }, + "makerAssetAmount": { "$ref": "/wholeNumberSchema" }, + "takerAssetAmount": { "$ref": "/wholeNumberSchema" }, "makerAssetData": { "$ref": "/hexSchema" }, "takerAssetData": { "$ref": "/hexSchema" }, "exchangeAddress": { "$ref": "/addressSchema" }, - "expirationTimeSeconds": { "$ref": "/numberSchema" } + "expirationTimeSeconds": { "$ref": "/wholeNumberSchema" } }, "required": [ "makerAddress", diff --git a/packages/json-schemas/schemas/relayer_api_order_config_response_schema.json b/packages/json-schemas/schemas/relayer_api_order_config_response_schema.json index 0742febdf..8193861e1 100644 --- a/packages/json-schemas/schemas/relayer_api_order_config_response_schema.json +++ b/packages/json-schemas/schemas/relayer_api_order_config_response_schema.json @@ -2,8 +2,8 @@ "id": "/relayerApiOrderConfigResponseSchema", "type": "object", "properties": { - "makerFee": { "$ref": "/numberSchema" }, - "takerFee": { "$ref": "/numberSchema" }, + "makerFee": { "$ref": "/wholeNumberSchema" }, + "takerFee": { "$ref": "/wholeNumberSchema" }, "feeRecipientAddress": { "$ref": "/addressSchema" }, "senderAddress": { "$ref": "/addressSchema" } }, diff --git a/packages/json-schemas/schemas/zero_ex_transaction_schema.json b/packages/json-schemas/schemas/zero_ex_transaction_schema.json index ad3b11583..0c714f14d 100644 --- a/packages/json-schemas/schemas/zero_ex_transaction_schema.json +++ b/packages/json-schemas/schemas/zero_ex_transaction_schema.json @@ -3,7 +3,7 @@ "properties": { "data": { "$ref": "/hexSchema" }, "signerAddress": { "$ref": "/addressSchema" }, - "salt": { "$ref": "/numberSchema" } + "salt": { "$ref": "/wholeNumberSchema" } }, "required": ["data", "salt", "signerAddress"], "type": "object" -- cgit From e0149618f306b9f3b9afc5001a9ef4840a1b5237 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 21 Oct 2018 19:26:06 +0200 Subject: Add changelog --- packages/json-schemas/CHANGELOG.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/json-schemas/CHANGELOG.json b/packages/json-schemas/CHANGELOG.json index 8a88df6e2..3bd6552e3 100644 --- a/packages/json-schemas/CHANGELOG.json +++ b/packages/json-schemas/CHANGELOG.json @@ -1,4 +1,14 @@ [ + { + "version": "2.0.1", + "changes": [ + { + "note": + "Improve schemas by enforcing that amounts that must be whole numbers (e.g Order asset amounts) no longer allow decimal amounts", + "pr": 1173 + } + ] + }, { "version": "2.0.0", "changes": [ -- cgit From 528ae4376e5e605dac9666f2a5917803e942a1f9 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 21 Oct 2018 19:27:42 +0200 Subject: revert unrelated change --- packages/website/ts/utils/fake_token_registry.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/website/ts/utils/fake_token_registry.ts b/packages/website/ts/utils/fake_token_registry.ts index 0d5b3486a..607dd2553 100644 --- a/packages/website/ts/utils/fake_token_registry.ts +++ b/packages/website/ts/utils/fake_token_registry.ts @@ -694,7 +694,7 @@ export const fakeTokenRegistry: { [networkId: string]: FakeTokenRegistryEntry[] ], '42': [ { - address: '0x2002d3812f58e35f0ea1ffbf80a75a38c32175fa', + address: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570', name: '0x Protocol Token', symbol: 'ZRX', decimals: 18, @@ -858,7 +858,7 @@ export const fakeTokenRegistry: { [networkId: string]: FakeTokenRegistryEntry[] decimals: 19, }, { - address: '0xff67881f8d12f372d91baae9752eb3631ff0ed00', + address: '0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d', name: '0x Protocol Token', symbol: 'ZRX', decimals: 18, -- cgit From 4dfbc6747e6f866f9fd76eecf75c6f0ad91dbe9f Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 21 Oct 2018 20:08:22 +0200 Subject: Fix links --- packages/website/md/docs/json_schemas/2/schemas.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/website/md/docs/json_schemas/2/schemas.md b/packages/website/md/docs/json_schemas/2/schemas.md index ec7cf6f15..c3d6bdbc3 100644 --- a/packages/website/md/docs/json_schemas/2/schemas.md +++ b/packages/website/md/docs/json_schemas/2/schemas.md @@ -1,8 +1,8 @@ Basic Schemas -* [Address type](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/address.json) -* [Number type](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/number.json) -* [Hex type](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/hex.json) +* [Address type](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/address_schema.json) +* [Number type](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/number_schema.json) +* [Hex type](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/hex_schema.json) * [JS number](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/js_number.json) 0x Protocol Schemas @@ -18,7 +18,7 @@ Basic Schemas * [BlockRange](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/block_range_schema.json) * [IndexFilter Values](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/index_filter_values_schema.json) * [OrderFillRequests](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/order_fill_requests_schema.json) -* [OrderCancellationRequests](https://github.com/0xProjet/0x-monorepo/blob/development/packages/json-schemas/schemas/order_cancel_schema.json) +* [OrderCancellationRequests](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/order_cancel_schema.json) * [OrderFillOrKillRequests](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.json) * [SignedOrders](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/signed_orders_schema.json) * [Token](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/token_schema.json) @@ -34,7 +34,7 @@ Standard Relayer API Schemas * [Orders channel subscribe](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_orders_channel_subscribe_schema.json) * [Orders channel update](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_orders_channel_update_response_schema.json) * [Orderbook response](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_orderbook_response_schema.json) -* [Asset pairs](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_asset_pairs_schema.json) -* [Trade info](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_asset_trade_info_schema.json) -* [Asset pairs response](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_asset_pairs_response_schema.json) +* [Asset pairs](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_asset_data_pairs_schema.json) +* [Trade info](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_asset_data_trade_info_schema.json) +* [Asset pairs response](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_asset_data_pairs_response_schema.json) * [Fee recipients response](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_fee_recipients_response_schema.json) -- cgit From 24b5b35a749bc985c3d0b73844a70a2fdb37d942 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 21 Oct 2018 20:22:51 +0200 Subject: chore: link to schemas at a particular commit so it doesn't change when they are updated --- packages/website/md/docs/json_schemas/2/schemas.md | 58 +++++++++++----------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/website/md/docs/json_schemas/2/schemas.md b/packages/website/md/docs/json_schemas/2/schemas.md index c3d6bdbc3..8ea6c8e85 100644 --- a/packages/website/md/docs/json_schemas/2/schemas.md +++ b/packages/website/md/docs/json_schemas/2/schemas.md @@ -1,40 +1,40 @@ Basic Schemas -* [Address type](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/address_schema.json) -* [Number type](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/number_schema.json) -* [Hex type](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/hex_schema.json) -* [JS number](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/js_number.json) +* [Address type](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/address_schema.json) +* [Number type](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/number_schema.json) +* [Hex type](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/hex_schema.json) +* [JS number](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/js_number.json) 0x Protocol Schemas -* [Order](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/order_schema.json) -* [SignedOrder](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/signed_order_schema.json) -* [OrderHash](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/order_hash_schema.json) -* [ECSignature](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/ec_signature_schema.json) +* [Order](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/order_schema.json) +* [SignedOrder](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/signed_order_schema.json) +* [OrderHash](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/order_hash_schema.json) +* [ECSignature](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/ec_signature_schema.json) 0x.js Schemas -* [BlockParam](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/block_param_schema.json) -* [BlockRange](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/block_range_schema.json) -* [IndexFilter Values](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/index_filter_values_schema.json) -* [OrderFillRequests](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/order_fill_requests_schema.json) -* [OrderCancellationRequests](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/order_cancel_schema.json) -* [OrderFillOrKillRequests](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.json) -* [SignedOrders](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/signed_orders_schema.json) -* [Token](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/token_schema.json) -* [TxData](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/tx_data_schema.json) +* [BlockParam](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/block_param_schema.json) +* [BlockRange](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/block_range_schema.json) +* [IndexFilter Values](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/index_filter_values_schema.json) +* [OrderFillRequests](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/order_fill_requests_schema.json) +* [OrderCancellationRequests](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/order_cancel_schema.json) +* [OrderFillOrKillRequests](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.json) +* [SignedOrders](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/signed_orders_schema.json) +* [Token](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/token_schema.json) +* [TxData](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/tx_data_schema.json) Standard Relayer API Schemas -* [Paginated collection](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/paginated_collection_schema.json) -* [Error response](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_error_response_schema.json) -* [Order config payload](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_order_config_payload_schema.json) -* [Order config response](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_order_config_response_schema.json) -* [Orders channel subscribe payload](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_orders_channel_subscribe_payload_schema.json) -* [Orders channel subscribe](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_orders_channel_subscribe_schema.json) -* [Orders channel update](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_orders_channel_update_response_schema.json) -* [Orderbook response](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_orderbook_response_schema.json) -* [Asset pairs](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_asset_data_pairs_schema.json) -* [Trade info](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_asset_data_trade_info_schema.json) -* [Asset pairs response](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_asset_data_pairs_response_schema.json) -* [Fee recipients response](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_fee_recipients_response_schema.json) +* [Paginated collection](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/paginated_collection_schema.json) +* [Error response](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_error_response_schema.json) +* [Order config payload](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_order_config_payload_schema.json) +* [Order config response](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_order_config_response_schema.json) +* [Orders channel subscribe payload](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_orders_channel_subscribe_payload_schema.json) +* [Orders channel subscribe](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_orders_channel_subscribe_schema.json) +* [Orders channel update](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_orders_channel_update_response_schema.json) +* [Orderbook response](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_orderbook_response_schema.json) +* [Asset pairs](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_asset_data_pairs_schema.json) +* [Trade info](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_asset_data_trade_info_schema.json) +* [Asset pairs response](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_asset_data_pairs_response_schema.json) +* [Fee recipients response](https://github.com/0xProject/0x-monorepo/blob/5ec4b27200297708298deca97603849a37b2f66a/packages/json-schemas/schemas/relayer_api_fee_recipients_response_schema.json) -- cgit From 01f82ddf789f4f747816d6f766ffae9fb61e180e Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 21 Oct 2018 20:24:38 +0200 Subject: chore: Add whole number to next json-schema doc md section --- packages/website/md/docs/json_schemas/3/schemas.md | 41 ++++++++++++++++++++++ .../ts/containers/json_schemas_documentation.ts | 17 ++++++--- 2 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 packages/website/md/docs/json_schemas/3/schemas.md diff --git a/packages/website/md/docs/json_schemas/3/schemas.md b/packages/website/md/docs/json_schemas/3/schemas.md new file mode 100644 index 000000000..99aff7b5a --- /dev/null +++ b/packages/website/md/docs/json_schemas/3/schemas.md @@ -0,0 +1,41 @@ +Basic Schemas + +* [Address type](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/address_schema.json) +* [Number type](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/number_schema.json) +* [Whole number type](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/whole_number_schema.json) +* [Hex type](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/hex_schema.json) +* [JS number](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/js_number.json) + +0x Protocol Schemas + +* [Order](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/order_schema.json) +* [SignedOrder](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/signed_order_schema.json) +* [OrderHash](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/order_hash_schema.json) +* [ECSignature](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/ec_signature_schema.json) + +0x.js Schemas + +* [BlockParam](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/block_param_schema.json) +* [BlockRange](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/block_range_schema.json) +* [IndexFilter Values](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/index_filter_values_schema.json) +* [OrderFillRequests](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/order_fill_requests_schema.json) +* [OrderCancellationRequests](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/order_cancel_schema.json) +* [OrderFillOrKillRequests](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.json) +* [SignedOrders](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/signed_orders_schema.json) +* [Token](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/token_schema.json) +* [TxData](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/tx_data_schema.json) + +Standard Relayer API Schemas + +* [Paginated collection](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/paginated_collection_schema.json) +* [Error response](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_error_response_schema.json) +* [Order config payload](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_order_config_payload_schema.json) +* [Order config response](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_order_config_response_schema.json) +* [Orders channel subscribe payload](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_orders_channel_subscribe_payload_schema.json) +* [Orders channel subscribe](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_orders_channel_subscribe_schema.json) +* [Orders channel update](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_orders_channel_update_response_schema.json) +* [Orderbook response](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_orderbook_response_schema.json) +* [Asset pairs](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_asset_data_pairs_schema.json) +* [Trade info](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_asset_data_trade_info_schema.json) +* [Asset pairs response](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_asset_data_pairs_response_schema.json) +* [Fee recipients response](https://github.com/0xProject/0x-monorepo/blob/528ae4376e5e605dac9666f2a5917803e942a1f9/packages/json-schemas/schemas/relayer_api_fee_recipients_response_schema.json) diff --git a/packages/website/ts/containers/json_schemas_documentation.ts b/packages/website/ts/containers/json_schemas_documentation.ts index cb5918784..9c4bb8e26 100644 --- a/packages/website/ts/containers/json_schemas_documentation.ts +++ b/packages/website/ts/containers/json_schemas_documentation.ts @@ -15,8 +15,9 @@ const InstallationMarkdown1 = require('md/docs/json_schemas/1/installation'); const InstallationMarkdown3 = require('md/docs/json_schemas/3/installation'); const usageMarkdown1 = require('md/docs/json_schemas/1/usage'); const usageMarkdown3 = require('md/docs/json_schemas/3/usage'); -const SchemasMarkdownV1 = require('md/docs/json_schemas/1/schemas'); -const SchemasMarkdownV2 = require('md/docs/json_schemas/2/schemas'); +const SchemasMarkdown1 = require('md/docs/json_schemas/1/schemas'); +const SchemasMarkdown2 = require('md/docs/json_schemas/2/schemas'); +const SchemasMarkdown3 = require('md/docs/json_schemas/3/schemas'); /* tslint:enable:no-var-requires */ const markdownSections = { @@ -40,19 +41,25 @@ const docsInfoConfig: DocsInfoConfig = { '0.0.1': { [markdownSections.introduction]: IntroMarkdown1, [markdownSections.installation]: InstallationMarkdown1, - [markdownSections.schemas]: SchemasMarkdownV1, + [markdownSections.schemas]: SchemasMarkdown1, [markdownSections.usage]: usageMarkdown1, }, '1.0.0': { [markdownSections.introduction]: IntroMarkdown1, [markdownSections.installation]: InstallationMarkdown1, - [markdownSections.schemas]: SchemasMarkdownV2, + [markdownSections.schemas]: SchemasMarkdown2, [markdownSections.usage]: usageMarkdown1, }, '2.0.0': { [markdownSections.introduction]: IntroMarkdown3, [markdownSections.installation]: InstallationMarkdown3, - [markdownSections.schemas]: SchemasMarkdownV2, + [markdownSections.schemas]: SchemasMarkdown2, + [markdownSections.usage]: usageMarkdown3, + }, + '2.0.1': { + [markdownSections.introduction]: IntroMarkdown3, + [markdownSections.installation]: InstallationMarkdown3, + [markdownSections.schemas]: SchemasMarkdown3, [markdownSections.usage]: usageMarkdown3, }, }, -- cgit From 39c7f3dc88d5e43399e055a2535ce2cddb560f4c Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 21 Oct 2018 20:30:53 +0200 Subject: chore: fix file name --- packages/json-schemas/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/json-schemas/tsconfig.json b/packages/json-schemas/tsconfig.json index 76d2cf240..7b14166c0 100644 --- a/packages/json-schemas/tsconfig.json +++ b/packages/json-schemas/tsconfig.json @@ -44,6 +44,6 @@ "./schemas/zero_ex_transaction_schema.json", "./schemas/tx_data_schema.json", "./schemas/index_filter_values_schema.json", - "./schemas/wholeNumberSchema.json" + "./schemas/whole_number_schema.json" ] } -- cgit From dbf75a43c37af31858dfca4026ab12f0491c520f Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 21 Oct 2018 20:31:35 +0200 Subject: Add wholeNumberSchema test --- packages/json-schemas/test/schema_test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/json-schemas/test/schema_test.ts b/packages/json-schemas/test/schema_test.ts index 795261ef2..bfa2c5668 100644 --- a/packages/json-schemas/test/schema_test.ts +++ b/packages/json-schemas/test/schema_test.ts @@ -36,6 +36,7 @@ const { relayerApiOrdersChannelUpdateSchema, relayerApiOrdersResponseSchema, relayerApiOrderSchema, + wholeNumberSchema, } = schemas; describe('Schema', () => { @@ -73,6 +74,17 @@ describe('Schema', () => { validateAgainstSchema(testCases, numberSchema, shouldFail); }); }); + describe('#wholeNumberSchema', () => { + it('should validate valid numbers', () => { + const testCases = ['5', '42', '0']; + validateAgainstSchema(testCases, wholeNumberSchema); + }); + it('should fail for invalid numbers', () => { + const testCases = ['1.3', '0.2', '00.00', '.3', '1.', 'abacaba', 'и', '1..0']; + const shouldFail = true; + validateAgainstSchema(testCases, wholeNumberSchema, shouldFail); + }); + }); describe('#addressSchema', () => { it('should validate valid addresses', () => { const testCases = ['0x8b0292b11a196601ed2ce54b665cafeca0347d42', NULL_ADDRESS]; -- cgit From 38eaacdd4425ec73761a95bd5b035b15e6fe675f Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Mon, 22 Oct 2018 10:55:34 -0700 Subject: Update yarn.lock --- yarn.lock | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6b3219ed6..a49282db9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1869,6 +1869,10 @@ aes-js@^0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-0.2.4.tgz#94b881ab717286d015fa219e08fb66709dda5a3d" +aes-js@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.1.tgz#89fd1f94ae51b4c72d62466adc1a7323ff52f072" + agent-base@4, agent-base@^4.1.0, agent-base@~4.2.0: version "4.2.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" @@ -3299,7 +3303,7 @@ bs-logger@0.x: dependencies: fast-json-stable-stringify "^2.0.0" -bs58@=4.0.1: +bs58@=4.0.1, bs58@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" dependencies: @@ -3322,6 +3326,14 @@ bs58check@^1.0.8: bs58 "^3.1.0" create-hash "^1.1.0" +bs58check@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" + dependencies: + bs58 "^4.0.0" + create-hash "^1.1.0" + safe-buffer "^5.1.2" + bser@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" @@ -5901,6 +5913,19 @@ ethereumjs-wallet@0.6.0: utf8 "^2.1.1" uuid "^2.0.1" +ethereumjs-wallet@~0.6.0: + version "0.6.2" + resolved "https://registry.yarnpkg.com/ethereumjs-wallet/-/ethereumjs-wallet-0.6.2.tgz#67244b6af3e8113b53d709124b25477b64aeccda" + dependencies: + aes-js "^3.1.1" + bs58check "^2.1.2" + ethereumjs-util "^5.2.0" + hdkey "^1.0.0" + safe-buffer "^5.1.2" + scrypt.js "^0.2.0" + utf8 "^3.0.0" + uuid "^3.3.2" + ethers@~4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.4.tgz#d3f85e8b27f4b59537e06526439b0fb15b44dc65" @@ -6705,7 +6730,7 @@ ganache-core@0xProject/ganache-core#monorepo-dep: ethereumjs-tx "0xProject/ethereumjs-tx#fake-tx-include-signature-by-default" ethereumjs-util "^5.2.0" ethereumjs-vm "2.3.5" - ethereumjs-wallet "~0.6.0" + ethereumjs-wallet "0.6.0" fake-merkle-patricia-tree "~1.0.1" heap "~0.2.6" js-scrypt "^0.2.0" @@ -7430,6 +7455,14 @@ hdkey@^0.7.0, hdkey@^0.7.1: coinstring "^2.0.0" secp256k1 "^3.0.1" +hdkey@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/hdkey/-/hdkey-1.1.0.tgz#e74e7b01d2c47f797fa65d1d839adb7a44639f29" + dependencies: + coinstring "^2.0.0" + safe-buffer "^5.1.1" + secp256k1 "^3.0.1" + he@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" @@ -15513,6 +15546,10 @@ utf8@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/utf8/-/utf8-2.1.2.tgz#1fa0d9270e9be850d9b05027f63519bf46457d96" +utf8@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" + util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" -- cgit From 2bb53d5b1df7cebaf6fd2e17640729f7401d7162 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Mon, 22 Oct 2018 10:56:11 -0700 Subject: fix(website): do not fetch balances for empty tokens --- packages/website/ts/components/portal/portal.tsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx index 2299881c4..94ab63ac6 100644 --- a/packages/website/ts/components/portal/portal.tsx +++ b/packages/website/ts/components/portal/portal.tsx @@ -210,12 +210,16 @@ export class Portal extends React.Component { isLoaded: false, }; } - this.setState({ - trackedTokenStateByAddress, - }); - // Fetch the actual balance/allowance. - // tslint:disable-next-line:no-floating-promises - this._fetchBalancesAndAllowancesAsync(newTokenAddresses); + this.setState( + { + trackedTokenStateByAddress, + }, + () => { + // Fetch the actual balance/allowance. + // tslint:disable-next-line:no-floating-promises + this._fetchBalancesAndAllowancesAsync(newTokenAddresses); + }, + ); } } public render(): React.ReactNode { @@ -644,6 +648,9 @@ export class Portal extends React.Component { } private async _fetchBalancesAndAllowancesAsync(tokenAddresses: string[]): Promise { + if (_.isEmpty(tokenAddresses)) { + return; + } const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress; const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress; const balancesAndAllowances = await Promise.all( -- cgit From 1a3b1607b12cd75a1fc12ecd504dad61bef8d291 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Mon, 22 Oct 2018 10:56:39 -0700 Subject: fix(website): create correct subprovider for metamask --- packages/website/ts/blockchain.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index efb5aef23..62e16dd1d 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -163,7 +163,7 @@ export class Blockchain { const providerName = this._getNameGivenProvider(injectedWeb3.currentProvider); // Wrap Metamask in a compatability wrapper MetamaskSubprovider (to handle inconsistencies) const signerSubprovider = - providerName === Providers.Metamask + providerName === constants.PROVIDER_NAME_METAMASK ? new MetamaskSubprovider(injectedWeb3.currentProvider) : new SignerSubprovider(injectedWeb3.currentProvider); provider.addProvider(signerSubprovider); -- cgit From 15ed3b35df1103ac9b0b4104551ecff9559ddb9b Mon Sep 17 00:00:00 2001 From: fragosti Date: Mon, 22 Oct 2018 13:50:08 -0700 Subject: chore: change order of code to be clearer --- packages/instant/src/types.ts | 1 + packages/instant/src/util/asset.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts index 7323123c3..c340623ad 100644 --- a/packages/instant/src/types.ts +++ b/packages/instant/src/types.ts @@ -41,6 +41,7 @@ export interface ERC721Asset { assetData: string; metaData: ERC721AssetMetaData; } + export interface Asset { assetData: string; metaData: AssetMetaData; diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts index 4e3b2b946..bf6cd663d 100644 --- a/packages/instant/src/util/asset.ts +++ b/packages/instant/src/util/asset.ts @@ -16,10 +16,11 @@ export const assetUtils = { }; }, getMetaDataOrThrow: (assetData: string, metaDataMap: ObjectMap, network: Network): AssetMetaData => { - let mainnetAssetData: string | undefined = assetData; + let mainnetAssetData: string | undefined; if (network !== Network.Mainnet) { mainnetAssetData = assetUtils.getAssociatedAssetDataIfExists(assetData, network); } + mainnetAssetData = assetData; if (_.isUndefined(mainnetAssetData)) { throw new Error(ZeroExInstantError.AssetMetaDataNotAvailable); } -- cgit From 37c5165319d5962aedc683c105df446ce62cd72f Mon Sep 17 00:00:00 2001 From: fragosti Date: Mon, 22 Oct 2018 14:01:08 -0700 Subject: feat: add asset tests --- packages/instant/test/util/asset.test.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 packages/instant/test/util/asset.test.ts diff --git a/packages/instant/test/util/asset.test.ts b/packages/instant/test/util/asset.test.ts new file mode 100644 index 000000000..9ca4a3d52 --- /dev/null +++ b/packages/instant/test/util/asset.test.ts @@ -0,0 +1,25 @@ +import { AssetProxyId } from '@0x/types'; + +import { Asset } from '../../src/types'; +import { assetUtils } from '../../src/util/asset'; + +const ZRX_ASSET_DATA = '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498'; +const ZRX_ASSET: Asset = { + assetData: ZRX_ASSET_DATA, + metaData: { + assetProxyId: AssetProxyId.ERC20, + symbol: 'zrx', + decimals: 18, + }, +}; + +describe('assetDataUtil', () => { + describe('bestNameForAsset', () => { + it('should return default string if assetData is undefined', () => { + expect(assetUtils.bestNameForAsset(undefined, 'xyz')).toEqual('xyz'); + }); + it('should return ZRX for ZRX assetData', () => { + expect(assetUtils.bestNameForAsset(ZRX_ASSET, 'mah default')).toEqual('ZRX'); + }); + }); +}); -- cgit From 48e4452a0442acb992c204ca1fbec36df1a75d6d Mon Sep 17 00:00:00 2001 From: fragosti Date: Mon, 22 Oct 2018 14:20:20 -0700 Subject: feat: add tests for assetUtils --- packages/instant/src/util/asset.ts | 3 +-- packages/instant/test/util/asset.test.ts | 36 +++++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts index bf6cd663d..4e3b2b946 100644 --- a/packages/instant/src/util/asset.ts +++ b/packages/instant/src/util/asset.ts @@ -16,11 +16,10 @@ export const assetUtils = { }; }, getMetaDataOrThrow: (assetData: string, metaDataMap: ObjectMap, network: Network): AssetMetaData => { - let mainnetAssetData: string | undefined; + let mainnetAssetData: string | undefined = assetData; if (network !== Network.Mainnet) { mainnetAssetData = assetUtils.getAssociatedAssetDataIfExists(assetData, network); } - mainnetAssetData = assetData; if (_.isUndefined(mainnetAssetData)) { throw new Error(ZeroExInstantError.AssetMetaDataNotAvailable); } diff --git a/packages/instant/test/util/asset.test.ts b/packages/instant/test/util/asset.test.ts index 9ca4a3d52..2c357d25c 100644 --- a/packages/instant/test/util/asset.test.ts +++ b/packages/instant/test/util/asset.test.ts @@ -1,16 +1,21 @@ -import { AssetProxyId } from '@0x/types'; +import { AssetData, AssetProxyId, ObjectMap } from '@0x/types'; -import { Asset } from '../../src/types'; +import { Asset, AssetMetaData, ERC20AssetMetaData, Network, ZeroExInstantError } from '../../src/types'; import { assetUtils } from '../../src/util/asset'; const ZRX_ASSET_DATA = '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498'; +const ZRX_ASSET_DATA_KOVAN = '0xf47261b00000000000000000000000002002d3812f58e35f0ea1ffbf80a75a38c32175fa'; +const ZRX_META_DATA: ERC20AssetMetaData = { + assetProxyId: AssetProxyId.ERC20, + symbol: 'zrx', + decimals: 18, +}; const ZRX_ASSET: Asset = { assetData: ZRX_ASSET_DATA, - metaData: { - assetProxyId: AssetProxyId.ERC20, - symbol: 'zrx', - decimals: 18, - }, + metaData: ZRX_META_DATA, +}; +const META_DATA_MAP: ObjectMap = { + [ZRX_ASSET_DATA]: ZRX_META_DATA, }; describe('assetDataUtil', () => { @@ -22,4 +27,21 @@ describe('assetDataUtil', () => { expect(assetUtils.bestNameForAsset(ZRX_ASSET, 'mah default')).toEqual('ZRX'); }); }); + describe('getMetaDataOrThrow', () => { + it('should return the metaData for the supplied mainnet asset data', () => { + expect(assetUtils.getMetaDataOrThrow(ZRX_ASSET_DATA, META_DATA_MAP, Network.Mainnet)).toEqual( + ZRX_META_DATA, + ); + }); + it('should return the metaData for the supplied non-mainnet asset data', () => { + expect(assetUtils.getMetaDataOrThrow(ZRX_ASSET_DATA_KOVAN, META_DATA_MAP, Network.Kovan)).toEqual( + ZRX_META_DATA, + ); + }); + it('should throw if the metaData for the asset is not available', () => { + expect(() => + assetUtils.getMetaDataOrThrow('asset data we dont have', META_DATA_MAP, Network.Mainnet), + ).toThrowError(ZeroExInstantError.AssetMetaDataNotAvailable); + }); + }); }); -- cgit From c5014af7fe7b06c01edfc677da48628d4972e298 Mon Sep 17 00:00:00 2001 From: fragosti Date: Mon, 22 Oct 2018 15:27:35 -0700 Subject: chore: run linter --- packages/instant/test/util/asset.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/instant/test/util/asset.test.ts b/packages/instant/test/util/asset.test.ts index 2c357d25c..c7db7eba7 100644 --- a/packages/instant/test/util/asset.test.ts +++ b/packages/instant/test/util/asset.test.ts @@ -1,4 +1,4 @@ -import { AssetData, AssetProxyId, ObjectMap } from '@0x/types'; +import { AssetProxyId, ObjectMap } from '@0x/types'; import { Asset, AssetMetaData, ERC20AssetMetaData, Network, ZeroExInstantError } from '../../src/types'; import { assetUtils } from '../../src/util/asset'; -- cgit From 1ba207f1fef4338682b4cc7e45af8c073e63d263 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Mon, 22 Oct 2018 15:33:55 -0700 Subject: fix(website): asset-buyer usage documentation formatting --- packages/website/md/docs/asset_buyer/installation.md | 4 ++-- packages/website/md/docs/asset_buyer/usage.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/website/md/docs/asset_buyer/installation.md b/packages/website/md/docs/asset_buyer/installation.md index 76affaa09..3c0c95068 100644 --- a/packages/website/md/docs/asset_buyer/installation.md +++ b/packages/website/md/docs/asset_buyer/installation.md @@ -1,10 +1,10 @@ -**Install** +#### Install ```bash yarn add @0x/asset-buyer ``` -**Import** +#### Import ```javascript import { AssetBuyer } from '@0x/asset-buyer'; diff --git a/packages/website/md/docs/asset_buyer/usage.md b/packages/website/md/docs/asset_buyer/usage.md index 6462d938e..209c15062 100644 --- a/packages/website/md/docs/asset_buyer/usage.md +++ b/packages/website/md/docs/asset_buyer/usage.md @@ -1,4 +1,4 @@ -**Construction** +#### Construction Connecting to a standard relayer API compliant url: @@ -16,7 +16,7 @@ const orders = []; // get these from your own API, a relayer, a friend, from any const assetBuyer = AssetBuyer.getAssetBuyerForProvidedOrders(provider, orders); ``` -**Get a quote** +#### Get a quote A [BuyQuote](#types-BuyQuote) object contains enough information to display buy information to an end user @@ -30,7 +30,7 @@ console.log(quoteInfo.feeAmount); // a portion of the total ethAmount above that console.log(quoteInfo.ethPerAssetPrice); // the rate that this quote provides (e.g. 0.0035ETH / REP) ``` -**Perform a buy** +#### Perform a buy Pass the [BuyQuote](#types-BuyQuote) object from above back to the assetBuyer in order to initiate the buy transaction -- cgit From b75fe10c790a60c875914a2a79ae8c4761b78bb9 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Wed, 17 Oct 2018 00:49:48 -0700 Subject: feat(contract-wrappers): export ForwarderWrapperError and ContractWrapperError.SignatureRequestDenied --- packages/contract-wrappers/CHANGELOG.json | 8 ++++++++ packages/contract-wrappers/src/index.ts | 1 + packages/contract-wrappers/src/types.ts | 5 +++++ packages/contract-wrappers/src/utils/constants.ts | 1 + packages/contract-wrappers/src/utils/decorators.ts | 14 +++++++++++++- 5 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index f66be3f0a..c3d986b4a 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -37,6 +37,14 @@ "note": "Removed ContractNotFound errors. Checking for this error was somewhat ineffecient. Relevant methods/functions now return the default error from web3-wrapper, which we feel provides enough information.", "pr": 1105 + }, + { + "note": "Add `ForwarderWrapperError` to public interface", + "pr": 1147 + }, + { + "note": "Add `ContractWrapperError.SignatureRequestDenied` to public interface", + "pr": 1147 } ], "timestamp": 1539871071 diff --git a/packages/contract-wrappers/src/index.ts b/packages/contract-wrappers/src/index.ts index a38bd122b..d66ff5c9c 100644 --- a/packages/contract-wrappers/src/index.ts +++ b/packages/contract-wrappers/src/index.ts @@ -39,6 +39,7 @@ export { TransactionEncoder } from './utils/transaction_encoder'; export { ContractWrappersError, + ForwarderWrapperError, IndexedFilterValues, BlockRange, ContractWrappersConfig, diff --git a/packages/contract-wrappers/src/types.ts b/packages/contract-wrappers/src/types.ts index 60750179f..5a5bdd530 100644 --- a/packages/contract-wrappers/src/types.ts +++ b/packages/contract-wrappers/src/types.ts @@ -18,6 +18,10 @@ export enum ExchangeWrapperError { AssetDataMismatch = 'ASSET_DATA_MISMATCH', } +export enum ForwarderWrapperError { + CompleteFillFailed = 'COMPLETE_FILL_FAILED', +} + export enum ContractWrappersError { ContractNotDeployedOnNetwork = 'CONTRACT_NOT_DEPLOYED_ON_NETWORK', InsufficientAllowanceForTransfer = 'INSUFFICIENT_ALLOWANCE_FOR_TRANSFER', @@ -30,6 +34,7 @@ export enum ContractWrappersError { SubscriptionAlreadyPresent = 'SUBSCRIPTION_ALREADY_PRESENT', ERC721OwnerNotFound = 'ERC_721_OWNER_NOT_FOUND', ERC721NoApproval = 'ERC_721_NO_APPROVAL', + SignatureRequestDenied = 'SIGNATURE_REQUEST_DENIED', } export enum InternalContractWrappersError { diff --git a/packages/contract-wrappers/src/utils/constants.ts b/packages/contract-wrappers/src/utils/constants.ts index 9ff61f62a..b89438592 100644 --- a/packages/contract-wrappers/src/utils/constants.ts +++ b/packages/contract-wrappers/src/utils/constants.ts @@ -14,4 +14,5 @@ export const constants = { ZERO_AMOUNT: new BigNumber(0), ONE_AMOUNT: new BigNumber(1), ETHER_TOKEN_DECIMALS: 18, + METAMASK_DENIED_SIGNATURE_PATTERN: 'MetaMask Tx Signature: User denied transaction signature', }; diff --git a/packages/contract-wrappers/src/utils/decorators.ts b/packages/contract-wrappers/src/utils/decorators.ts index e17246015..932b11785 100644 --- a/packages/contract-wrappers/src/utils/decorators.ts +++ b/packages/contract-wrappers/src/utils/decorators.ts @@ -29,6 +29,14 @@ const schemaErrorTransformer = (error: Error) => { return error; }; +const signatureRequestErrorTransformer = (error: Error) => { + if (_.includes(error.message, constants.METAMASK_DENIED_SIGNATURE_PATTERN)) { + const errMsg = ContractWrappersError.SignatureRequestDenied; + return new Error(errMsg); + } + return error; +}; + /** * Source: https://stackoverflow.com/a/29837695/3546986 */ @@ -87,7 +95,11 @@ const syncErrorHandlerFactory = (errorTransformer: ErrorTransformer) => { }; // _.flow(f, g) = f ∘ g -const zeroExErrorTransformer = _.flow(schemaErrorTransformer, contractCallErrorTransformer); +const zeroExErrorTransformer = _.flow( + schemaErrorTransformer, + contractCallErrorTransformer, + signatureRequestErrorTransformer, +); export const decorators = { asyncZeroExErrorHandler: asyncErrorHandlerFactory(zeroExErrorTransformer), -- cgit From 1f0c7f8fbeba90ac1f65c57ff58782051c751b3d Mon Sep 17 00:00:00 2001 From: "F. Eugene Aumson" Date: Tue, 23 Oct 2018 12:08:16 -0400 Subject: feat(order_utils.py): ERC20 asset data encoding and decoding In addition to the ERC20 codec, also: Stopped ignoring type errors on 3rd party imports, by including interface stubs for them; Removed the unimplemented signature-utils module, which was just a stand-in when the python project support was first put in place. https://github.com/0xProject/0x-monorepo/pull/1144 --- CODEOWNERS | 1 + packages/order-utils/test/asset_data_utils_test.ts | 31 +++++++ python-packages/order_utils/setup.py | 39 ++++++-- python-packages/order_utils/src/conf.py | 5 +- python-packages/order_utils/src/index.rst | 5 +- .../order_utils/src/zero_ex/dev_utils/__init__.py | 1 + .../order_utils/src/zero_ex/dev_utils/abi_utils.py | 102 +++++++++++++++++++++ .../src/zero_ex/dev_utils/type_assertions.py | 33 +++++++ .../src/zero_ex/order_utils/asset_data_utils.py | 72 +++++++++++++++ .../src/zero_ex/order_utils/signature_utils.py | 13 --- .../order_utils/stubs/distutils/__init__.pyi | 0 .../stubs/distutils/command/__init__.pyi | 0 .../order_utils/stubs/distutils/command/clean.pyi | 7 ++ .../order_utils/stubs/pytest/__init__.pyi | 0 .../order_utils/stubs/pytest/raises.pyi | 1 + .../order_utils/stubs/setuptools/__init__.pyi | 6 ++ .../stubs/setuptools/command/__init__.pyi | 0 .../order_utils/stubs/setuptools/command/test.pyi | 3 + .../order_utils/stubs/web3/__init__.pyi | 10 ++ python-packages/order_utils/test/test_abi_utils.py | 53 +++++++++++ .../order_utils/test/test_asset_data_utils.py | 35 +++++++ python-packages/order_utils/test/test_doctest.py | 22 ++++- .../order_utils/test/test_signature_utils.py | 8 -- 23 files changed, 410 insertions(+), 37 deletions(-) create mode 100644 packages/order-utils/test/asset_data_utils_test.ts mode change 100644 => 100755 python-packages/order_utils/setup.py create mode 100644 python-packages/order_utils/src/zero_ex/dev_utils/__init__.py create mode 100644 python-packages/order_utils/src/zero_ex/dev_utils/abi_utils.py create mode 100644 python-packages/order_utils/src/zero_ex/dev_utils/type_assertions.py create mode 100644 python-packages/order_utils/src/zero_ex/order_utils/asset_data_utils.py delete mode 100644 python-packages/order_utils/src/zero_ex/order_utils/signature_utils.py create mode 100644 python-packages/order_utils/stubs/distutils/__init__.pyi create mode 100644 python-packages/order_utils/stubs/distutils/command/__init__.pyi create mode 100644 python-packages/order_utils/stubs/distutils/command/clean.pyi create mode 100644 python-packages/order_utils/stubs/pytest/__init__.pyi create mode 100644 python-packages/order_utils/stubs/pytest/raises.pyi create mode 100644 python-packages/order_utils/stubs/setuptools/__init__.pyi create mode 100644 python-packages/order_utils/stubs/setuptools/command/__init__.pyi create mode 100644 python-packages/order_utils/stubs/setuptools/command/test.pyi create mode 100644 python-packages/order_utils/stubs/web3/__init__.pyi create mode 100644 python-packages/order_utils/test/test_abi_utils.py create mode 100644 python-packages/order_utils/test/test_asset_data_utils.py delete mode 100644 python-packages/order_utils/test/test_signature_utils.py diff --git a/CODEOWNERS b/CODEOWNERS index 278234aff..ba40af31a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -26,3 +26,4 @@ packages/subproviders/ @fabioberger @dekz packages/connect/ @fragosti packages/monorepo-scripts/ @fabioberger packages/order-utils/ @fabioberger @LogvinovLeon +python-packages/ @feuGeneA \ No newline at end of file diff --git a/packages/order-utils/test/asset_data_utils_test.ts b/packages/order-utils/test/asset_data_utils_test.ts new file mode 100644 index 000000000..f8b850604 --- /dev/null +++ b/packages/order-utils/test/asset_data_utils_test.ts @@ -0,0 +1,31 @@ +import * as chai from 'chai'; + +import { ERC20AssetData } from '@0x/types'; + +import { assetDataUtils } from '../src/asset_data_utils'; + +import { chaiSetup } from './utils/chai_setup'; + +chaiSetup.configure(); +const expect = chai.expect; + +const KNOWN_ENCODINGS = [ + { + address: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48', + assetData: '0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48', + }, +]; + +const ERC20_ASSET_PROXY_ID = '0xf47261b0'; + +describe('assetDataUtils', () => { + it('should encode', () => { + const assetData = assetDataUtils.encodeERC20AssetData(KNOWN_ENCODINGS[0].address); + expect(assetData).to.equal(KNOWN_ENCODINGS[0].assetData); + }); + it('should decode', () => { + const assetData: ERC20AssetData = assetDataUtils.decodeERC20AssetData(KNOWN_ENCODINGS[0].assetData); + expect(assetData.tokenAddress).to.equal(KNOWN_ENCODINGS[0].address); + expect(assetData.assetProxyId).to.equal(ERC20_ASSET_PROXY_ID); + }); +}); diff --git a/python-packages/order_utils/setup.py b/python-packages/order_utils/setup.py old mode 100644 new mode 100755 index a76d724aa..1a094cfe1 --- a/python-packages/order_utils/setup.py +++ b/python-packages/order_utils/setup.py @@ -1,13 +1,16 @@ +#!/usr/bin/env python + """setuptools module for order_utils package.""" import subprocess # nosec from shutil import rmtree -from os import path, remove, walk +from os import environ, path, remove, walk +from sys import argv -from distutils.command.clean import clean # type: ignore -from setuptools import setup # type: ignore -import setuptools.command.build_py # type: ignore -from setuptools.command.test import test as TestCommand # type: ignore +from distutils.command.clean import clean +import distutils.command.build_py +from setuptools import setup +from setuptools.command.test import test as TestCommand class TestCommandExtension(TestCommand): @@ -15,13 +18,13 @@ class TestCommandExtension(TestCommand): def run_tests(self): """Invoke pytest.""" - import pytest # type: ignore + import pytest pytest.main() # pylint: disable=too-many-ancestors -class LintCommand(setuptools.command.build_py.build_py): +class LintCommand(distutils.command.build_py.build_py): """Custom setuptools command class for running linters.""" def run(self): @@ -34,7 +37,7 @@ class LintCommand(setuptools.command.build_py.build_py): # docstring style checker: "pydocstyle src test setup.py".split(), # static type checker: - "mypy src setup.py".split(), + "mypy src test setup.py".split(), # security issue checker: "bandit -r src ./setup.py".split(), # general linter: @@ -42,6 +45,21 @@ class LintCommand(setuptools.command.build_py.build_py): # pylint takes relatively long to run, so it runs last, to enable # fast failures. ] + + # tell mypy where to find interface stubs for 3rd party libs + environ["MYPYPATH"] = path.join( + path.dirname(path.realpath(argv[0])), "stubs" + ) + + # HACK(gene): until eth_abi releases + # https://github.com/ethereum/eth-abi/pull/107 , we need to simply + # create an empty file `py.typed` in the eth_abi package directory. + import eth_abi + + eth_abi_dir = path.dirname(path.realpath(eth_abi.__file__)) + with open(path.join(eth_abi_dir, "py.typed"), "a"): + pass + for lint_command in lint_commands: print( "Running lint command `", " ".join(lint_command).strip(), "`" @@ -79,7 +97,7 @@ setup( "test": TestCommandExtension, }, include_package_data=True, - install_requires=["web3"], + install_requires=["eth-abi", "web3"], extras_require={ "dev": [ "bandit", @@ -87,6 +105,7 @@ setup( "coverage", "coveralls", "mypy", + "mypy_extensions", "pycodestyle", "pydocstyle", "pylint", @@ -118,7 +137,7 @@ setup( "Topic :: Software Development :: Libraries", "Topic :: Utilities", ], - zip_safe=False, + zip_safe=False, # required per mypy command_options={ "build_sphinx": { "source_dir": ("setup.py", "src"), diff --git a/python-packages/order_utils/src/conf.py b/python-packages/order_utils/src/conf.py index f3f15967c..e74a29d00 100644 --- a/python-packages/order_utils/src/conf.py +++ b/python-packages/order_utils/src/conf.py @@ -2,6 +2,9 @@ # Reference: http://www.sphinx-doc.org/en/master/config +from typing import List + + # pylint: disable=invalid-name # because these variables are not named in upper case, as globals should be. @@ -29,7 +32,7 @@ master_doc = "index" # The master toctree document. language = None -exclude_patterns = [] # type: ignore +exclude_patterns: List[str] = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = None diff --git a/python-packages/order_utils/src/index.rst b/python-packages/order_utils/src/index.rst index cbc4c8409..e09abcca2 100644 --- a/python-packages/order_utils/src/index.rst +++ b/python-packages/order_utils/src/index.rst @@ -10,9 +10,12 @@ order_utils.py .. automodule:: zero_ex.order_utils :members: -.. automodule:: zero_ex.order_utils.signature_utils +.. automodule:: zero_ex.order_utils.asset_data_utils :members: +.. autoclass:: zero_ex.order_utils.asset_data_utils.ERC20AssetData + +See source for properties. Sphinx does not easily generate class property docs; pull requests welcome. Indices and tables ================== diff --git a/python-packages/order_utils/src/zero_ex/dev_utils/__init__.py b/python-packages/order_utils/src/zero_ex/dev_utils/__init__.py new file mode 100644 index 000000000..b6a224d2c --- /dev/null +++ b/python-packages/order_utils/src/zero_ex/dev_utils/__init__.py @@ -0,0 +1 @@ +"""Dev utils to be shared across 0x projects and packages.""" diff --git a/python-packages/order_utils/src/zero_ex/dev_utils/abi_utils.py b/python-packages/order_utils/src/zero_ex/dev_utils/abi_utils.py new file mode 100644 index 000000000..71b6128ca --- /dev/null +++ b/python-packages/order_utils/src/zero_ex/dev_utils/abi_utils.py @@ -0,0 +1,102 @@ +"""Ethereum ABI utilities. + +Builds on the eth-abi package, adding some convenience methods like those found +in npmjs.com/package/ethereumjs-abi. Ideally, all of this code should be +pushed upstream into eth-abi. +""" + +import re +from typing import Any, List + +from mypy_extensions import TypedDict + +from eth_abi import encode_abi +from web3 import Web3 + +from .type_assertions import assert_is_string, assert_is_list + + +class MethodSignature(TypedDict, total=False): + """Object interface to an ABI method signature.""" + + method: str + args: List[str] + + +def parse_signature(signature: str) -> MethodSignature: + """Parse a method signature into its constituent parts. + + >>> parse_signature("ERC20Token(address)") + {'method': 'ERC20Token', 'args': ['address']} + """ + assert_is_string(signature, "signature") + + matches = re.match(r"^(\w+)\((.+)\)$", signature) + if matches is None: + raise ValueError(f"Invalid method signature {signature}") + return {"method": matches[1], "args": matches[2].split(",")} + + +def elementary_name(name: str) -> str: + """Convert from short to canonical names; barely implemented. + + Modeled after ethereumjs-abi's ABI.elementaryName(), but only implemented + to support our particular use case and a few other simple ones. + + >>> elementary_name("address") + 'address' + >>> elementary_name("uint") + 'uint256' + """ + assert_is_string(name, "name") + + return { + "int": "int256", + "uint": "uint256", + "fixed": "fixed128x128", + "ufixed": "ufixed128x128", + }.get(name, name) + + +def event_id(name: str, types: List[str]) -> str: + """Return the Keccak-256 hash of the given method. + + >>> event_id("ERC20Token", ["address"]) + '0xf47261b06eedbfce68afd46d0f3c27c60b03faad319eaf33103611cf8f6456ad' + """ + assert_is_string(name, "name") + assert_is_list(types, "types") + + signature = f"{name}({','.join(list(map(elementary_name, types)))})" + return Web3.sha3(text=signature).hex() + + +def method_id(name: str, types: List[str]) -> str: + """Return the 4-byte method identifier. + + >>> method_id("ERC20Token", ["address"]) + '0xf47261b0' + """ + assert_is_string(name, "name") + assert_is_list(types, "types") + + return event_id(name, types)[0:10] + + +def simple_encode(method: str, *args: Any) -> bytes: + # docstring considered all one line by pylint: disable=line-too-long + r"""Encode a method ABI. + + >>> simple_encode("ERC20Token(address)", "0x1dc4c1cefef38a777b15aa20260a54e584b16c48") + b'\xf4ra\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1d\xc4\xc1\xce\xfe\xf3\x8aw{\x15\xaa &\nT\xe5\x84\xb1lH' + """ # noqa: E501 (line too long) + assert_is_string(method, "method") + + signature: MethodSignature = parse_signature(method) + + return bytes.fromhex( + ( + method_id(signature["method"], signature["args"]) + + encode_abi(signature["args"], args).hex() + )[2:] + ) diff --git a/python-packages/order_utils/src/zero_ex/dev_utils/type_assertions.py b/python-packages/order_utils/src/zero_ex/dev_utils/type_assertions.py new file mode 100644 index 000000000..745d014e6 --- /dev/null +++ b/python-packages/order_utils/src/zero_ex/dev_utils/type_assertions.py @@ -0,0 +1,33 @@ +"""Assertions for runtime type checking of function arguments.""" + +from typing import Any + + +def assert_is_string(value: Any, name: str) -> None: + """If :param value: isn't of type str, raise a TypeError. + + >>> try: assert_is_string(123, 'var') + ... except TypeError as type_error: print(str(type_error)) + ... + expected variable 'var', with value 123, to have type 'str', not 'int' + """ + if not isinstance(value, str): + raise TypeError( + f"expected variable '{name}', with value {str(value)}, to have" + + f" type 'str', not '{type(value).__name__}'" + ) + + +def assert_is_list(value: Any, name: str) -> None: + """If :param value: isn't of type list, raise a TypeError. + + >>> try: assert_is_list(123, 'var') + ... except TypeError as type_error: print(str(type_error)) + ... + expected variable 'var', with value 123, to have type 'list', not 'int' + """ + if not isinstance(value, list): + raise TypeError( + f"expected variable '{name}', with value {str(value)}, to have" + + f" type 'list', not '{type(value).__name__}'" + ) diff --git a/python-packages/order_utils/src/zero_ex/order_utils/asset_data_utils.py b/python-packages/order_utils/src/zero_ex/order_utils/asset_data_utils.py new file mode 100644 index 000000000..451de39af --- /dev/null +++ b/python-packages/order_utils/src/zero_ex/order_utils/asset_data_utils.py @@ -0,0 +1,72 @@ +"""Asset data encoding and decoding utilities.""" + +from mypy_extensions import TypedDict + +import eth_abi + +from zero_ex.dev_utils import abi_utils +from zero_ex.dev_utils.type_assertions import assert_is_string + + +ERC20_ASSET_DATA_BYTE_LENGTH = 36 +SELECTOR_LENGTH = 10 + + +class ERC20AssetData(TypedDict): + """Object interface to ERC20 asset data.""" + + asset_proxy_id: str + token_address: str + + +def encode_erc20_asset_data(token_address: str) -> str: + """Encode an ERC20 token address into an asset data string. + + :param token_address: the ERC20 token's contract address. + :rtype: hex encoded asset data string, usable in the makerAssetData or + takerAssetData fields in a 0x order. + + >>> encode_erc20_asset_data('0x1dc4c1cefef38a777b15aa20260a54e584b16c48') + '0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48' + """ + assert_is_string(token_address, "token_address") + + return ( + "0x" + + abi_utils.simple_encode("ERC20Token(address)", token_address).hex() + ) + + +def decode_erc20_asset_data(asset_data: str) -> ERC20AssetData: + # docstring considered all one line by pylint: disable=line-too-long + """Decode an ERC20 assetData hex string. + + :param asset_data: String produced by prior call to encode_erc20_asset_data() + + >>> decode_erc20_asset_data("0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48") + {'asset_proxy_id': '0xf47261b0', 'token_address': '0x1dc4c1cefef38a777b15aa20260a54e584b16c48'} + """ # noqa: E501 (line too long) + assert_is_string(asset_data, "asset_data") + + if len(asset_data) < ERC20_ASSET_DATA_BYTE_LENGTH: + raise ValueError( + "Could not decode ERC20 Proxy Data. Expected length of encoded" + + f" data to be at least {str(ERC20_ASSET_DATA_BYTE_LENGTH)}." + + f" Got {str(len(asset_data))}." + ) + + asset_proxy_id: str = asset_data[0:10] + if asset_proxy_id != abi_utils.method_id("ERC20Token", ["address"]): + raise ValueError( + "Could not decode ERC20 Proxy Data. Expected Asset Proxy Id to be" + + f" ERC20 ({abi_utils.method_id('ERC20Token', ['address'])})" + + f" but got {asset_proxy_id}." + ) + + # workaround for https://github.com/PyCQA/pylint/issues/1498 + # pylint: disable=unsubscriptable-object + token_address = eth_abi.decode_abi( + ["address"], bytes.fromhex(asset_data[SELECTOR_LENGTH:]) + )[0] + + return {"asset_proxy_id": asset_proxy_id, "token_address": token_address} diff --git a/python-packages/order_utils/src/zero_ex/order_utils/signature_utils.py b/python-packages/order_utils/src/zero_ex/order_utils/signature_utils.py deleted file mode 100644 index 7f4697106..000000000 --- a/python-packages/order_utils/src/zero_ex/order_utils/signature_utils.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Signature utilities.""" - - -def ec_sign_order_hash(): - """Signs an orderHash. - - Returns its elliptic curve signature and signature type. This method - currently supports TestRPC, Geth, and Parity above and below v1.6.6. - - >>> ec_sign_order_hash() - 'stub return value' - """ - return "stub return value" diff --git a/python-packages/order_utils/stubs/distutils/__init__.pyi b/python-packages/order_utils/stubs/distutils/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/python-packages/order_utils/stubs/distutils/command/__init__.pyi b/python-packages/order_utils/stubs/distutils/command/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/python-packages/order_utils/stubs/distutils/command/clean.pyi b/python-packages/order_utils/stubs/distutils/command/clean.pyi new file mode 100644 index 000000000..46a42ddb1 --- /dev/null +++ b/python-packages/order_utils/stubs/distutils/command/clean.pyi @@ -0,0 +1,7 @@ +from distutils.core import Command + +class clean(Command): + def initialize_options(self: clean) -> None: ... + def finalize_options(self: clean) -> None: ... + def run(self: clean) -> None: ... + ... diff --git a/python-packages/order_utils/stubs/pytest/__init__.pyi b/python-packages/order_utils/stubs/pytest/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/python-packages/order_utils/stubs/pytest/raises.pyi b/python-packages/order_utils/stubs/pytest/raises.pyi new file mode 100644 index 000000000..2e3b29f3d --- /dev/null +++ b/python-packages/order_utils/stubs/pytest/raises.pyi @@ -0,0 +1 @@ +def raises(exception: Exception) -> ExceptionInfo: ... diff --git a/python-packages/order_utils/stubs/setuptools/__init__.pyi b/python-packages/order_utils/stubs/setuptools/__init__.pyi new file mode 100644 index 000000000..baa349d70 --- /dev/null +++ b/python-packages/order_utils/stubs/setuptools/__init__.pyi @@ -0,0 +1,6 @@ +from distutils.dist import Distribution +from typing import Any + +def setup(**attrs: Any) -> Distribution: ... + +class Command: ... diff --git a/python-packages/order_utils/stubs/setuptools/command/__init__.pyi b/python-packages/order_utils/stubs/setuptools/command/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/python-packages/order_utils/stubs/setuptools/command/test.pyi b/python-packages/order_utils/stubs/setuptools/command/test.pyi new file mode 100644 index 000000000..c5ec770ad --- /dev/null +++ b/python-packages/order_utils/stubs/setuptools/command/test.pyi @@ -0,0 +1,3 @@ +from setuptools import Command + +class test(Command): ... diff --git a/python-packages/order_utils/stubs/web3/__init__.pyi b/python-packages/order_utils/stubs/web3/__init__.pyi new file mode 100644 index 000000000..c6f357009 --- /dev/null +++ b/python-packages/order_utils/stubs/web3/__init__.pyi @@ -0,0 +1,10 @@ +from typing import Optional, Union + +class Web3: + @staticmethod + def sha3( + primitive: Optional[Union[bytes, int, None]] = None, + text: Optional[str] = None, + hexstr: Optional[str] = None + ) -> bytes: ... + ... diff --git a/python-packages/order_utils/test/test_abi_utils.py b/python-packages/order_utils/test/test_abi_utils.py new file mode 100644 index 000000000..49a2a4f20 --- /dev/null +++ b/python-packages/order_utils/test/test_abi_utils.py @@ -0,0 +1,53 @@ +"""Tests of 0x.abi_utils.""" + +import pytest + +from zero_ex.dev_utils.abi_utils import ( + elementary_name, + event_id, + method_id, + parse_signature, + simple_encode, +) + + +def test_parse_signature_type_error(): + """Test that passing in wrong types raises TypeError.""" + with pytest.raises(TypeError): + parse_signature(123) + + +def test_parse_signature_bad_input(): + """Test that passing a non-signature string raises a ValueError.""" + with pytest.raises(ValueError): + parse_signature("a string that's not even close to a signature") + + +def test_elementary_name_type_error(): + """Test that passing in wrong types raises TypeError.""" + with pytest.raises(TypeError): + elementary_name(123) + + +def test_event_id_type_error(): + """Test that passing in wrong types raises TypeError.""" + with pytest.raises(TypeError): + event_id(123, []) + + with pytest.raises(TypeError): + event_id("valid string", 123) + + +def test_method_id_type_error(): + """Test that passing in wrong types raises TypeError.""" + with pytest.raises(TypeError): + method_id(123, []) + + with pytest.raises(TypeError): + method_id("ERC20Token", 123) + + +def test_simple_encode_type_error(): + """Test that passing in wrong types raises TypeError.""" + with pytest.raises(TypeError): + simple_encode(123) diff --git a/python-packages/order_utils/test/test_asset_data_utils.py b/python-packages/order_utils/test/test_asset_data_utils.py new file mode 100644 index 000000000..eeada5873 --- /dev/null +++ b/python-packages/order_utils/test/test_asset_data_utils.py @@ -0,0 +1,35 @@ +"""Tests of 0x.order_utils.asset_data_utils.""" + +import pytest + +from zero_ex.order_utils.asset_data_utils import ( + encode_erc20_asset_data, + decode_erc20_asset_data, + ERC20_ASSET_DATA_BYTE_LENGTH, +) + + +def test_encode_erc20_asset_data_type_error(): + """Test that passing in a non-string raises a TypeError.""" + with pytest.raises(TypeError): + encode_erc20_asset_data(123) + + +def test_decode_erc20_asset_data_type_error(): + """Test that passing in a non-string raises a TypeError.""" + with pytest.raises(TypeError): + decode_erc20_asset_data(123) + + +def test_decode_erc20_asset_data_too_short(): + """Test that passing an insufficiently long string raises a ValueError.""" + with pytest.raises(ValueError): + decode_erc20_asset_data(" " * (ERC20_ASSET_DATA_BYTE_LENGTH - 1)) + + +def test_decode_erc20_asset_data_invalid_proxy_id(): + """Test that passing data with an invalid proxy ID raises a ValueError.""" + with pytest.raises(ValueError): + decode_erc20_asset_data( + "0xffffffff" + (" " * ERC20_ASSET_DATA_BYTE_LENGTH) + ) diff --git a/python-packages/order_utils/test/test_doctest.py b/python-packages/order_utils/test/test_doctest.py index a0e61f84a..ba5da5418 100644 --- a/python-packages/order_utils/test/test_doctest.py +++ b/python-packages/order_utils/test/test_doctest.py @@ -1,10 +1,24 @@ """Exercise doctests for order_utils module.""" from doctest import testmod -from zero_ex.order_utils import signature_utils +from zero_ex.dev_utils import abi_utils, type_assertions +from zero_ex.order_utils import asset_data_utils -def test_doctest(): - """Invoke doctest on the module.""" - (failure_count, _) = testmod(signature_utils) + +def test_doctest_asset_data_utils(): + """Invoke doctest on the asset_data_utils module.""" + (failure_count, _) = testmod(asset_data_utils) + assert failure_count == 0 + + +def test_doctest_abi_utils(): + """Invoke doctest on the abi_utils module.""" + (failure_count, _) = testmod(abi_utils) + assert failure_count == 0 + + +def test_doctest_type_assertions(): + """Invoke doctest on the type_assertions module.""" + (failure_count, _) = testmod(type_assertions) assert failure_count == 0 diff --git a/python-packages/order_utils/test/test_signature_utils.py b/python-packages/order_utils/test/test_signature_utils.py deleted file mode 100644 index 7e830f9f8..000000000 --- a/python-packages/order_utils/test/test_signature_utils.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Tests of 0x.order_utils.signature_utils.*.""" - -from zero_ex.order_utils.signature_utils import ec_sign_order_hash - - -def test_ec_sign_order_hash(): - """Test the signing of order hashes.""" - assert ec_sign_order_hash() == "stub return value" -- cgit From e086c7b8e637fd57160273c69e27222ae8586a29 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 15 Oct 2018 17:52:51 -0700 Subject: Round up for Market Buys in Forwarding Contract. Includes new test cases + regression testing. --- .../extensions/Forwarder/MixinExchangeWrapper.sol | 12 +- packages/contracts/test/extensions/forwarder.ts | 267 ++++++++++++++++++++- packages/contracts/test/utils/forwarder_wrapper.ts | 9 +- 3 files changed, 280 insertions(+), 8 deletions(-) diff --git a/packages/contracts/contracts/extensions/Forwarder/MixinExchangeWrapper.sol b/packages/contracts/contracts/extensions/Forwarder/MixinExchangeWrapper.sol index fea9a53c2..4991c0ea5 100644 --- a/packages/contracts/contracts/extensions/Forwarder/MixinExchangeWrapper.sol +++ b/packages/contracts/contracts/extensions/Forwarder/MixinExchangeWrapper.sol @@ -155,8 +155,10 @@ contract MixinExchangeWrapper is uint256 remainingMakerAssetFillAmount = safeSub(makerAssetFillAmount, totalFillResults.makerAssetFilledAmount); // Convert the remaining amount of makerAsset to buy into remaining amount - // of takerAsset to sell, assuming entire amount can be sold in the current order - uint256 remainingTakerAssetFillAmount = getPartialAmountFloor( + // of takerAsset to sell, assuming entire amount can be sold in the current order. + // We round up because the exchange rate computed by fillOrder rounds in favor + // of the Maker. In this case we want to overestimate the amount of takerAsset. + uint256 remainingTakerAssetFillAmount = getPartialAmountCeil( orders[i].takerAssetAmount, orders[i].makerAssetAmount, remainingMakerAssetFillAmount @@ -224,7 +226,9 @@ contract MixinExchangeWrapper is // Convert the remaining amount of ZRX to buy into remaining amount // of WETH to sell, assuming entire amount can be sold in the current order. - uint256 remainingWethSellAmount = getPartialAmountFloor( + // We round up because the exchange rate computed by fillOrder rounds in favor + // of the Maker. In this case we want to overestimate the amount of takerAsset. + uint256 remainingWethSellAmount = getPartialAmountCeil( orders[i].takerAssetAmount, safeSub(orders[i].makerAssetAmount, orders[i].takerFee), // our exchange rate after fees remainingZrxBuyAmount @@ -233,7 +237,7 @@ contract MixinExchangeWrapper is // Attempt to sell the remaining amount of WETH. FillResults memory singleFillResult = fillOrderNoThrow( orders[i], - safeAdd(remainingWethSellAmount, 1), // we add 1 wei to the fill amount to make up for rounding errors + remainingWethSellAmount, signatures[i] ); diff --git a/packages/contracts/test/extensions/forwarder.ts b/packages/contracts/test/extensions/forwarder.ts index b76624fa9..0c4a06373 100644 --- a/packages/contracts/test/extensions/forwarder.ts +++ b/packages/contracts/test/extensions/forwarder.ts @@ -45,6 +45,7 @@ describe(ContractName.Forwarder, () => { let weth: DummyERC20TokenContract; let zrxToken: DummyERC20TokenContract; + let erc20TokenA: DummyERC20TokenContract; let erc721Token: DummyERC721TokenContract; let forwarderContract: ForwarderContract; let wethContract: WETH9Contract; @@ -77,7 +78,6 @@ describe(ContractName.Forwarder, () => { erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); const numDummyErc20ToDeploy = 3; - let erc20TokenA; [erc20TokenA, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS, @@ -902,6 +902,271 @@ describe(ContractName.Forwarder, () => { ); expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); }); + it('Should buy slightly greater MakerAsset when exchange rate is rounded', async () => { + // The 0x Protocol contracts round the exchange rate in favor of the Maker. + // In this case, the taker must round up how much they're going to spend, which + // in turn increases the amount of MakerAsset being purchased. + // Example: + // The taker wants to buy 5 units of the MakerAsset at a rate of 3M/2T. + // For every 2 units of TakerAsset, the taker will receive 3 units of MakerAsset. + // To purchase 5 units, the taker must spend 10/3 = 3.33 units of TakerAssset. + // However, the Taker can only spend whole units. + // Spending floor(10/3) = 3 units will yield a profit of Floor(3*3/2) = Floor(4.5) = 4 units of MakerAsset. + // Spending ceil(10/3) = 4 units will yield a profit of Floor(4*3/2) = 6 units of MakerAsset. + // + // The forwarding contract will opt for the second option, which overbuys, to ensure the taker + // receives at least the amount of MakerAsset they requested. + // + // Construct test case using values from example above + orderWithoutFee = await orderFactory.newSignedOrderAsync({ + makerAssetAmount: new BigNumber('30'), + takerAssetAmount: new BigNumber('20'), + makerAssetData: assetDataUtils.encodeERC20AssetData(erc20TokenA.address), + takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address), + makerFee: new BigNumber(0), + takerFee: new BigNumber(0), + }); + const ordersWithoutFee = [orderWithoutFee]; + const feeOrders: SignedOrder[] = []; + const desiredMakerAssetFillAmount = new BigNumber('5'); + const makerAssetFillAmount = new BigNumber('6'); + const ethValue = new BigNumber('4'); + // Execute test case + tx = await forwarderWrapper.marketBuyOrdersWithEthAsync( + ordersWithoutFee, + feeOrders, + desiredMakerAssetFillAmount, + { + value: ethValue, + from: takerAddress, + }, + ); + // Fetch end balances and construct expected outputs + const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); + const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); + const newBalances = await erc20Wrapper.getBalancesAsync(); + const primaryTakerAssetFillAmount = ethValue; + const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed)); + // Validate test case + expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount); + expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); + expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount), + ); + expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount), + ); + expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); + expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal( + constants.ZERO_AMOUNT, + ); + expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); + }); + it('Should buy slightly greater MakerAsset when exchange rate is rounded, and MakerAsset is ZRX', async () => { + // See the test case above for a detailed description of this case. + // The difference here is that the MakerAsset is ZRX. We expect the same result as above, + // but this tests a different code path. + // + // Construct test case using values from example above + orderWithoutFee = await orderFactory.newSignedOrderAsync({ + makerAssetAmount: new BigNumber('30'), + takerAssetAmount: new BigNumber('20'), + makerAssetData: zrxAssetData, + takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address), + makerFee: new BigNumber(0), + takerFee: new BigNumber(0), + }); + const ordersWithoutFee = [orderWithoutFee]; + const feeOrders: SignedOrder[] = []; + const desiredMakerAssetFillAmount = new BigNumber('5'); + const makerAssetFillAmount = new BigNumber('6'); + const ethValue = new BigNumber('4'); + // Execute test case + tx = await forwarderWrapper.marketBuyOrdersWithEthAsync( + ordersWithoutFee, + feeOrders, + desiredMakerAssetFillAmount, + { + value: ethValue, + from: takerAddress, + }, + ); + // Fetch end balances and construct expected outputs + const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); + const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); + const newBalances = await erc20Wrapper.getBalancesAsync(); + const primaryTakerAssetFillAmount = ethValue; + const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed)); + // Validate test case + expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount); + expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); + expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount), + ); + expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount), + ); + expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount), + ); + expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); + expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); + }); + it('Should buy slightly greater MakerAsset when exchange rate is rounded (Regression Test)', async () => { + // Order taken from a transaction on mainnet that failed due to a rounding error. +- // tx=0x3d22b18fe2615c1903408b1b969744bcd117becc30205df18d5c5c54d27d1237 + orderWithoutFee = await orderFactory.newSignedOrderAsync({ + makerAssetAmount: new BigNumber('268166666666666666666'), + takerAssetAmount: new BigNumber('219090625878836371'), + makerAssetData: assetDataUtils.encodeERC20AssetData(erc20TokenA.address), + takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address), + makerFee: new BigNumber(0), + takerFee: new BigNumber(0), + }); + const ordersWithoutFee = [orderWithoutFee]; + const feeOrders: SignedOrder[] = []; + // The taker will receive more than the desired amount of makerAsset due to rounding + const desiredMakerAssetFillAmount = new BigNumber('5000000000000000000'); + const ethValue = new BigNumber('4084971271824171'); + const makerAssetFillAmount = ethValue + .times(orderWithoutFee.makerAssetAmount) + .dividedToIntegerBy(orderWithoutFee.takerAssetAmount); + // Execute test case + tx = await forwarderWrapper.marketBuyOrdersWithEthAsync( + ordersWithoutFee, + feeOrders, + desiredMakerAssetFillAmount, + { + value: ethValue, + from: takerAddress, + }, + ); + // Fetch end balances and construct expected outputs + const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); + const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); + const newBalances = await erc20Wrapper.getBalancesAsync(); + const primaryTakerAssetFillAmount = ethValue; + const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed)); + // Validate test case + expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount); + expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); + expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount), + ); + expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount), + ); + expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); + expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal( + constants.ZERO_AMOUNT, + ); + expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); + }); + it('Should buy slightly greater MakerAsset when exchange rate is rounded, and MakerAsset is ZRX (Regression Test)', async () => { + // Order taken from a transaction on mainnet that failed due to a rounding error. +- // tx=0x3d22b18fe2615c1903408b1b969744bcd117becc30205df18d5c5c54d27d1237 + orderWithoutFee = await orderFactory.newSignedOrderAsync({ + makerAssetAmount: new BigNumber('268166666666666666666'), + takerAssetAmount: new BigNumber('219090625878836371'), + makerAssetData: zrxAssetData, + takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address), + makerFee: new BigNumber(0), + takerFee: new BigNumber(0), + }); + const ordersWithoutFee = [orderWithoutFee]; + const feeOrders: SignedOrder[] = []; + // The taker will receive more than the desired amount of makerAsset due to rounding + const desiredMakerAssetFillAmount = new BigNumber('5000000000000000000'); + const ethValue = new BigNumber('4084971271824171'); + const makerAssetFillAmount = ethValue + .times(orderWithoutFee.makerAssetAmount) + .dividedToIntegerBy(orderWithoutFee.takerAssetAmount); + // Execute test case + tx = await forwarderWrapper.marketBuyOrdersWithEthAsync( + ordersWithoutFee, + feeOrders, + desiredMakerAssetFillAmount, + { + value: ethValue, + from: takerAddress, + }, + ); + // Fetch end balances and construct expected outputs + const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); + const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); + const newBalances = await erc20Wrapper.getBalancesAsync(); + const primaryTakerAssetFillAmount = ethValue; + const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed)); + // Validate test case + expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount); + expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); + expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount), + ); + expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount), + ); + expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount), + ); + expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); + expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); + }); + it('Should buy correct MakerAsset when exchange rate is NOT rounded, and MakerAsset is ZRX (Regression Test)', async () => { + // An extra unit of TakerAsset was sent to the exchange contract to account for rounding errors, in Forwarder v1. + // Specifically, the takerFillAmount was calculated using Floor(desiredMakerAmount * exchangeRate) + 1 + // We have since changed this to be Ceil(desiredMakerAmount * exchangeRate) + // These calculations produce different results when `desiredMakerAmount * exchangeRate` is an integer. + // + // This test verifies that `ceil` is sufficient: + // Let TakerAssetAmount = MakerAssetAmount * 2 + // -> exchangeRate = TakerAssetAmount / MakerAssetAmount = (2*MakerAssetAmount)/MakerAssetAmount = 2 + // .: desiredMakerAmount * exchangeRate is an integer. + // + // Construct test case using values from example above + orderWithoutFee = await orderFactory.newSignedOrderAsync({ + makerAssetAmount: new BigNumber('30'), + takerAssetAmount: new BigNumber('60'), + makerAssetData: zrxAssetData, + takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address), + makerFee: new BigNumber(0), + takerFee: new BigNumber(0), + }); + const ordersWithoutFee = [orderWithoutFee]; + const feeOrders: SignedOrder[] = []; + const makerAssetFillAmount = new BigNumber('5'); + const ethValue = new BigNumber('10'); + // Execute test case + tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, { + value: ethValue, + from: takerAddress, + }); + // Fetch end balances and construct expected outputs + const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); + const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); + const newBalances = await erc20Wrapper.getBalancesAsync(); + const primaryTakerAssetFillAmount = ethValue; + const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed)); + // Validate test case + expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); + expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount), + ); + expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount), + ); + expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount), + ); + expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); + expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); + }); }); describe('marketBuyOrdersWithEth with extra fees', () => { it('should buy an asset and send fee to feeRecipient', async () => { diff --git a/packages/contracts/test/utils/forwarder_wrapper.ts b/packages/contracts/test/utils/forwarder_wrapper.ts index f1a64d47d..a0bfcfe1d 100644 --- a/packages/contracts/test/utils/forwarder_wrapper.ts +++ b/packages/contracts/test/utils/forwarder_wrapper.ts @@ -26,9 +26,12 @@ export class ForwarderWrapper { _.forEach(feeOrders, feeOrder => { const feeAvailable = feeOrder.makerAssetAmount.minus(feeOrder.takerFee); if (!remainingFeeAmount.isZero() && feeAvailable.gt(remainingFeeAmount)) { - wethAmount = wethAmount - .plus(feeOrder.takerAssetAmount.times(remainingFeeAmount).dividedToIntegerBy(feeAvailable)) - .plus(1); + wethAmount = wethAmount.plus( + feeOrder.takerAssetAmount + .times(remainingFeeAmount) + .dividedBy(feeAvailable) + .ceil(), + ); remainingFeeAmount = new BigNumber(0); } else if (!remainingFeeAmount.isZero()) { wethAmount = wethAmount.plus(feeOrder.takerAssetAmount); -- cgit From dcd428a4a299de0daf6bbfb8e8a7c15685b673cf Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Wed, 17 Oct 2018 09:54:41 -0700 Subject: feat(monorepo-scripts): add ForwarderWrapperError to IGNORED_EXCESSIVE_TYPES in docGenConfigs --- packages/monorepo-scripts/CHANGELOG.json | 4 ++++ packages/monorepo-scripts/src/doc_gen_configs.ts | 1 + 2 files changed, 5 insertions(+) diff --git a/packages/monorepo-scripts/CHANGELOG.json b/packages/monorepo-scripts/CHANGELOG.json index 4797fd929..170a97a33 100644 --- a/packages/monorepo-scripts/CHANGELOG.json +++ b/packages/monorepo-scripts/CHANGELOG.json @@ -9,6 +9,10 @@ { "note": "Add AssetBuyerError to the IGNORED_EXCESSIVE_TYPES array", "pr": 1139 + }, + { + "note": "Add ForwarderError to the IGNORED_EXCESSIVE_TYPES array", + "pr": 1147 } ] }, diff --git a/packages/monorepo-scripts/src/doc_gen_configs.ts b/packages/monorepo-scripts/src/doc_gen_configs.ts index 0aaf5a6a5..7a14f8664 100644 --- a/packages/monorepo-scripts/src/doc_gen_configs.ts +++ b/packages/monorepo-scripts/src/doc_gen_configs.ts @@ -56,6 +56,7 @@ export const docGenConfigs: DocGenConfigs = { 'ContractWrappersError', 'OrderError', 'AssetBuyerError', + 'ForwarderWrapperError', ], // Some libraries only export types. In those cases, we cannot check if the exported types are part of the // "exported public interface". Thus we add them here and skip those checks. -- cgit From 117ee19370e56f3559b9b11b04becc5ad9c7d634 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Wed, 17 Oct 2018 10:03:46 -0700 Subject: feat(asset-buyer): throw SignatureRequestDenied and TransactionValueTooLow errors when executing buy --- packages/asset-buyer/CHANGELOG.json | 4 +++ packages/asset-buyer/src/asset_buyer.ts | 43 +++++++++++++++++++++------------ packages/asset-buyer/src/types.ts | 2 ++ 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/packages/asset-buyer/CHANGELOG.json b/packages/asset-buyer/CHANGELOG.json index 5ebe2aa24..7ebcd8c2f 100644 --- a/packages/asset-buyer/CHANGELOG.json +++ b/packages/asset-buyer/CHANGELOG.json @@ -25,6 +25,10 @@ { "note": "Add missing types to public interface", "pr": 1139 + }, + { + "note": "Throw `SignatureRequestDenied` and `TransactionValueTooLow` errors when executing buy", + "pr": 1147 } ], "timestamp": 1539871071 diff --git a/packages/asset-buyer/src/asset_buyer.ts b/packages/asset-buyer/src/asset_buyer.ts index 2fe8f6deb..2f9a3108e 100644 --- a/packages/asset-buyer/src/asset_buyer.ts +++ b/packages/asset-buyer/src/asset_buyer.ts @@ -1,4 +1,4 @@ -import { ContractWrappers } from '@0x/contract-wrappers'; +import { ContractWrappers, ContractWrappersError, ForwarderWrapperError } from '@0x/contract-wrappers'; import { schemas } from '@0x/json-schemas'; import { SignedOrder } from '@0x/order-utils'; import { ObjectMap } from '@0x/types'; @@ -210,21 +210,32 @@ export class AssetBuyer { throw new Error(AssetBuyerError.NoAddressAvailable); } } - // if no ethAmount is provided, default to the worst ethAmount from buyQuote - const txHash = await this._contractWrappers.forwarder.marketBuyOrdersWithEthAsync( - orders, - assetBuyAmount, - finalTakerAddress, - ethAmount || worstCaseQuoteInfo.totalEthAmount, - feeOrders, - feePercentage, - feeRecipient, - { - gasLimit, - gasPrice, - }, - ); - return txHash; + try { + // if no ethAmount is provided, default to the worst ethAmount from buyQuote + const txHash = await this._contractWrappers.forwarder.marketBuyOrdersWithEthAsync( + orders, + assetBuyAmount, + finalTakerAddress, + ethAmount || worstCaseQuoteInfo.totalEthAmount, + feeOrders, + feePercentage, + feeRecipient, + { + gasLimit, + gasPrice, + shouldValidate: true, + }, + ); + return txHash; + } catch (err) { + if (_.includes(err.message, ContractWrappersError.SignatureRequestDenied)) { + throw new Error(AssetBuyerError.SignatureRequestDenied); + } else if (_.includes(err.message, ForwarderWrapperError.CompleteFillFailed)) { + throw new Error(AssetBuyerError.TransactionValueTooLow); + } else { + throw err; + } + } } /** * Grab orders from the map, if there is a miss or it is time to refresh, fetch and process the orders diff --git a/packages/asset-buyer/src/types.ts b/packages/asset-buyer/src/types.ts index 0d8e732d7..f15e7e934 100644 --- a/packages/asset-buyer/src/types.ts +++ b/packages/asset-buyer/src/types.ts @@ -112,6 +112,8 @@ export enum AssetBuyerError { NoAddressAvailable = 'NO_ADDRESS_AVAILABLE', InvalidOrderProviderResponse = 'INVALID_ORDER_PROVIDER_RESPONSE', AssetUnavailable = 'ASSET_UNAVAILABLE', + SignatureRequestDenied = 'SIGNATURE_REQUEST_DENIED', + TransactionValueTooLow = 'TRANSACTION_VALUE_TOO_LOW', } export interface OrdersAndFillableAmounts { -- cgit From af2bf053bc9cfe80618109dc10a90515d780cd25 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Mon, 22 Oct 2018 22:23:32 -0700 Subject: feat(contract-wrappers): relax requirement for throwing `ContractWrappersError.SignatureRequestDenied` --- packages/contract-wrappers/src/utils/constants.ts | 2 +- packages/contract-wrappers/src/utils/decorators.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/contract-wrappers/src/utils/constants.ts b/packages/contract-wrappers/src/utils/constants.ts index b89438592..c587ba526 100644 --- a/packages/contract-wrappers/src/utils/constants.ts +++ b/packages/contract-wrappers/src/utils/constants.ts @@ -14,5 +14,5 @@ export const constants = { ZERO_AMOUNT: new BigNumber(0), ONE_AMOUNT: new BigNumber(1), ETHER_TOKEN_DECIMALS: 18, - METAMASK_DENIED_SIGNATURE_PATTERN: 'MetaMask Tx Signature: User denied transaction signature', + USER_DENIED_SIGNATURE_PATTERN: 'User denied transaction signature', }; diff --git a/packages/contract-wrappers/src/utils/decorators.ts b/packages/contract-wrappers/src/utils/decorators.ts index 932b11785..a4207ae4c 100644 --- a/packages/contract-wrappers/src/utils/decorators.ts +++ b/packages/contract-wrappers/src/utils/decorators.ts @@ -30,7 +30,7 @@ const schemaErrorTransformer = (error: Error) => { }; const signatureRequestErrorTransformer = (error: Error) => { - if (_.includes(error.message, constants.METAMASK_DENIED_SIGNATURE_PATTERN)) { + if (_.includes(error.message, constants.USER_DENIED_SIGNATURE_PATTERN)) { const errMsg = ContractWrappersError.SignatureRequestDenied; return new Error(errMsg); } -- cgit From 56953320b30604b051c39374d0abff8456051fd7 Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Tue, 23 Oct 2018 10:07:48 -0700 Subject: Run prettier and linter --- packages/contracts/test/extensions/forwarder.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/contracts/test/extensions/forwarder.ts b/packages/contracts/test/extensions/forwarder.ts index 0c4a06373..c006be0fe 100644 --- a/packages/contracts/test/extensions/forwarder.ts +++ b/packages/contracts/test/extensions/forwarder.ts @@ -1017,7 +1017,6 @@ describe(ContractName.Forwarder, () => { }); it('Should buy slightly greater MakerAsset when exchange rate is rounded (Regression Test)', async () => { // Order taken from a transaction on mainnet that failed due to a rounding error. -- // tx=0x3d22b18fe2615c1903408b1b969744bcd117becc30205df18d5c5c54d27d1237 orderWithoutFee = await orderFactory.newSignedOrderAsync({ makerAssetAmount: new BigNumber('268166666666666666666'), takerAssetAmount: new BigNumber('219090625878836371'), @@ -1070,7 +1069,6 @@ describe(ContractName.Forwarder, () => { }); it('Should buy slightly greater MakerAsset when exchange rate is rounded, and MakerAsset is ZRX (Regression Test)', async () => { // Order taken from a transaction on mainnet that failed due to a rounding error. -- // tx=0x3d22b18fe2615c1903408b1b969744bcd117becc30205df18d5c5c54d27d1237 orderWithoutFee = await orderFactory.newSignedOrderAsync({ makerAssetAmount: new BigNumber('268166666666666666666'), takerAssetAmount: new BigNumber('219090625878836371'), -- cgit From 864f89c535cf68a9325f27eae7f49a088c481120 Mon Sep 17 00:00:00 2001 From: fragosti Date: Tue, 23 Oct 2018 14:09:44 -0700 Subject: chore: incorportate pr feedback --- packages/instant/src/components/buy_button.tsx | 2 +- .../instant/src/components/zero_ex_instant.tsx | 8 ++--- packages/instant/src/constants.ts | 1 + packages/instant/src/index.umd.ts | 3 +- yarn.lock | 39 +++++++++++++++++++++- 5 files changed, 46 insertions(+), 7 deletions(-) diff --git a/packages/instant/src/components/buy_button.tsx b/packages/instant/src/components/buy_button.tsx index 3ef7c1f5c..adc32f071 100644 --- a/packages/instant/src/components/buy_button.tsx +++ b/packages/instant/src/components/buy_button.tsx @@ -24,7 +24,7 @@ export class BuyButton extends React.Component { onBuyFailure: util.boundNoop, }; public render(): React.ReactNode { - const shouldDisableButton = _.isUndefined(this.props.buyQuote); + const shouldDisableButton = _.isUndefined(this.props.buyQuote) || _.isUndefined(this.props.assetBuyer); return (