From a764dfa789ba44e519371b4a1e4569db7f551fb7 Mon Sep 17 00:00:00 2001 From: fragosti Date: Thu, 18 Oct 2018 10:43:41 -0700 Subject: feat: MVP of passing in sraApiUrl, assetData and other settings from render method --- .../instant/src/components/asset_amount_input.tsx | 29 +++------ packages/instant/src/components/buy_button.tsx | 8 +-- .../instant/src/components/zero_ex_instant.tsx | 58 ++++++++++++++---- packages/instant/src/constants.ts | 3 - .../src/containers/selected_asset_amount_input.ts | 70 +++++++++++++++++----- .../src/containers/selected_asset_buy_button.ts | 4 +- .../instant/src/data/asset_data_network_mapping.ts | 25 ++++++++ packages/instant/src/data/asset_meta_data.ts | 15 ----- packages/instant/src/data/asset_meta_data_map.ts | 14 +++++ packages/instant/src/index.umd.ts | 6 +- packages/instant/src/redux/actions.ts | 2 + packages/instant/src/redux/async_data.ts | 4 +- packages/instant/src/redux/reducer.ts | 25 ++++++-- packages/instant/src/redux/store.ts | 14 ++++- packages/instant/src/types.ts | 22 +++++++ packages/instant/src/util/asset.ts | 34 +++++++++++ packages/instant/src/util/asset_buyer.ts | 9 --- 17 files changed, 249 insertions(+), 93 deletions(-) create mode 100644 packages/instant/src/data/asset_data_network_mapping.ts delete mode 100644 packages/instant/src/data/asset_meta_data.ts create mode 100644 packages/instant/src/data/asset_meta_data_map.ts create mode 100644 packages/instant/src/util/asset.ts delete mode 100644 packages/instant/src/util/asset_buyer.ts (limited to 'packages/instant/src') diff --git a/packages/instant/src/components/asset_amount_input.tsx b/packages/instant/src/components/asset_amount_input.tsx index 7c6b03ee9..9f4b5861a 100644 --- a/packages/instant/src/components/asset_amount_input.tsx +++ b/packages/instant/src/components/asset_amount_input.tsx @@ -1,18 +1,19 @@ -import { AssetProxyId } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import * as React from 'react'; +import { oc } from 'ts-optchain'; -import { assetMetaData } from '../data/asset_meta_data'; import { ColorOption } from '../style/theme'; +import { ERC20Asset } from '../types'; import { util } from '../util/util'; import { AmountInput, AmountInputProps } from './amount_input'; import { Container, Text } from './ui'; +// Asset amounts only apply to ERC20 assets export interface AssetAmountInputProps extends AmountInputProps { - assetData?: string; - onChange: (value?: BigNumber, assetData?: string) => void; + asset?: ERC20Asset; + onChange: (value?: BigNumber, asset?: ERC20Asset) => void; } export class AssetAmountInput extends React.Component { @@ -20,33 +21,19 @@ export class AssetAmountInput extends React.Component { onChange: util.boundNoop, }; public render(): React.ReactNode { - const { assetData, onChange, ...rest } = this.props; + const { asset, onChange, ...rest } = this.props; return ( - {this._getAssetSymbolLabel()} + {oc(asset).metaData.symbol()} ); } - private readonly _getAssetSymbolLabel = (): string => { - const unknownLabel = '???'; - if (_.isUndefined(this.props.assetData)) { - return unknownLabel; - } - const metaData = assetMetaData[this.props.assetData]; - if (_.isUndefined(metaData)) { - return unknownLabel; - } - if (metaData.assetProxyId === AssetProxyId.ERC20) { - return metaData.symbol; - } - return unknownLabel; - }; private readonly _handleChange = (value?: BigNumber): void => { - this.props.onChange(value, this.props.assetData); + this.props.onChange(value, this.props.asset); }; } diff --git a/packages/instant/src/components/buy_button.tsx b/packages/instant/src/components/buy_button.tsx index 0706817c9..191426be1 100644 --- a/packages/instant/src/components/buy_button.tsx +++ b/packages/instant/src/components/buy_button.tsx @@ -1,9 +1,8 @@ -import { BuyQuote } from '@0xproject/asset-buyer'; +import { AssetBuyer, BuyQuote } from '@0xproject/asset-buyer'; import * as _ from 'lodash'; import * as React from 'react'; import { ColorOption } from '../style/theme'; -import { assetBuyer } from '../util/asset_buyer'; import { util } from '../util/util'; import { web3Wrapper } from '../util/web3_wrapper'; @@ -11,6 +10,7 @@ import { Button, Container, Text } from './ui'; export interface BuyButtonProps { buyQuote?: BuyQuote; + assetBuyer?: AssetBuyer; onClick: (buyQuote: BuyQuote) => void; onBuySuccess: (buyQuote: BuyQuote, txnHash: string) => void; onBuyFailure: (buyQuote: BuyQuote, tnxHash?: string) => void; @@ -37,13 +37,13 @@ export class BuyButton extends React.Component { } private readonly _handleClick = async () => { // The button is disabled when there is no buy quote anyway. - if (_.isUndefined(this.props.buyQuote)) { + if (_.isUndefined(this.props.buyQuote) || _.isUndefined(this.props.assetBuyer)) { return; } this.props.onClick(this.props.buyQuote); let txnHash; try { - txnHash = await assetBuyer.executeBuyQuoteAsync(this.props.buyQuote); + txnHash = await this.props.assetBuyer.executeBuyQuoteAsync(this.props.buyQuote); await web3Wrapper.awaitTransactionSuccessAsync(txnHash); this.props.onBuySuccess(this.props.buyQuote, txnHash); } catch { diff --git a/packages/instant/src/components/zero_ex_instant.tsx b/packages/instant/src/components/zero_ex_instant.tsx index f6472e811..cc7b0ecb8 100644 --- a/packages/instant/src/components/zero_ex_instant.tsx +++ b/packages/instant/src/components/zero_ex_instant.tsx @@ -1,23 +1,59 @@ +import { AssetBuyer } from '@0xproject/asset-buyer'; +import { ObjectMap } from '@0xproject/types'; import * as React from 'react'; import { Provider } from 'react-redux'; +import { assetMetaDataMap } from '../data/asset_meta_data_map'; import { asyncData } from '../redux/async_data'; -import { store } from '../redux/store'; +import { State } from '../redux/reducer'; +import { store, Store } from '../redux/store'; import { fonts } from '../style/fonts'; import { theme, ThemeProvider } from '../style/theme'; +import { AssetMetaData } from '../types'; +import { assetUtils } from '../util/asset'; +import { getProvider } from '../util/provider'; import { ZeroExInstantContainer } from './zero_ex_instant_container'; fonts.include(); -// tslint:disable-next-line:no-floating-promises -asyncData.fetchAndDispatchToStore(); -export interface ZeroExInstantProps {} +export interface ZeroExInstantProps { + // TODO: Change API when we allow the selection of different assetDatas + assetData: string; + sraApiUrl: string; + additionalAssetMetaDataMap: ObjectMap; +} -export const ZeroExInstant: React.StatelessComponent = () => ( - - - - - -); +export class ZeroExInstant extends React.Component { + public static defaultProps = { + additionalAssetMetaDataMap: {}, + }; + public store: Store; + constructor(props: ZeroExInstantProps) { + super(props); + // TODO: Provider needs to not be hard-coded to injected web3. + const assetBuyer = AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(getProvider(), props.sraApiUrl); + const completeAssetMetaDataMap = { + ...props.additionalAssetMetaDataMap, + ...assetMetaDataMap, + }; + const storeStateFromProps: Partial = { + assetBuyer, + selectedAsset: assetUtils.createAssetFromAssetData(props.assetData, completeAssetMetaDataMap), + assetMetaDataMap: completeAssetMetaDataMap, + }; + this.store = store.create(storeStateFromProps); + // tslint:disable-next-line:no-floating-promises + asyncData.fetchAndDispatchToStore(this.store); + } + + public render(): React.ReactNode { + return ( + + + + + + ); + } +} diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts index 1fd321c5a..b27378d4c 100644 --- a/packages/instant/src/constants.ts +++ b/packages/instant/src/constants.ts @@ -1,6 +1,3 @@ import { BigNumber } from '@0xproject/utils'; export const BIG_NUMBER_ZERO = new BigNumber(0); -export const sraApiUrl = 'https://api.radarrelay.com/0x/v2/'; -export const zrxAssetData = '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498'; -export const zrxDecimals = 18; export const ethDecimals = 18; diff --git a/packages/instant/src/containers/selected_asset_amount_input.ts b/packages/instant/src/containers/selected_asset_amount_input.ts index f2ca96ae4..b75a22a0e 100644 --- a/packages/instant/src/containers/selected_asset_amount_input.ts +++ b/packages/instant/src/containers/selected_asset_amount_input.ts @@ -1,3 +1,5 @@ +import { AssetBuyer } from '@0xproject/asset-buyer'; +import { AssetProxyId } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as _ from 'lodash'; @@ -5,12 +7,10 @@ import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; -import { zrxDecimals } from '../constants'; import { Action, actions } from '../redux/actions'; import { State } from '../redux/reducer'; import { ColorOption } from '../style/theme'; -import { AsyncProcessState } from '../types'; -import { assetBuyer } from '../util/asset_buyer'; +import { AsyncProcessState, ERC20Asset } from '../types'; import { AssetAmountInput } from '../components/asset_amount_input'; @@ -20,30 +20,54 @@ export interface SelectedAssetAmountInputProps { } interface ConnectedState { + assetBuyer?: AssetBuyer; value?: BigNumber; - assetData?: string; + asset?: ERC20Asset; } interface ConnectedDispatch { - onChange: (value?: BigNumber, assetData?: string) => void; + updateBuyQuote: (assetBuyer?: AssetBuyer, value?: BigNumber, asset?: ERC20Asset) => void; } -const mapStateToProps = (state: State, _ownProps: SelectedAssetAmountInputProps): ConnectedState => ({ - value: state.selectedAssetAmount, - assetData: state.selectedAssetData, -}); +interface ConnectedProps { + value?: BigNumber; + asset?: ERC20Asset; + onChange: (value?: BigNumber, asset?: ERC20Asset) => void; +} + +type FinalProps = ConnectedProps & SelectedAssetAmountInputProps; + +const mapStateToProps = (state: State, _ownProps: SelectedAssetAmountInputProps): ConnectedState => { + const selectedAsset = state.selectedAsset; + if (_.isUndefined(selectedAsset) || selectedAsset.assetProxyId !== AssetProxyId.ERC20) { + return { + value: state.selectedAssetAmount, + }; + } + return { + assetBuyer: state.assetBuyer, + value: state.selectedAssetAmount, + asset: selectedAsset as ERC20Asset, + }; +}; const updateBuyQuoteAsync = async ( dispatch: Dispatch, - assetData?: string, + assetBuyer?: AssetBuyer, + asset?: ERC20Asset, assetAmount?: BigNumber, ): Promise => { - if (_.isUndefined(assetAmount) || _.isUndefined(assetData)) { + if ( + _.isUndefined(assetBuyer) || + _.isUndefined(assetAmount) || + _.isUndefined(asset) || + _.isUndefined(asset.metaData) + ) { return; } // get a new buy quote. - const baseUnitValue = Web3Wrapper.toBaseUnitAmount(assetAmount, zrxDecimals); - const newBuyQuote = await assetBuyer.getBuyQuoteAsync(assetData, baseUnitValue); + const baseUnitValue = Web3Wrapper.toBaseUnitAmount(assetAmount, asset.metaData.decimals); + const newBuyQuote = await assetBuyer.getBuyQuoteAsync(asset.assetData, baseUnitValue); // invalidate the last buy quote. dispatch(actions.updateLatestBuyQuote(newBuyQuote)); }; @@ -54,7 +78,7 @@ const mapDispatchToProps = ( dispatch: Dispatch, _ownProps: SelectedAssetAmountInputProps, ): ConnectedDispatch => ({ - onChange: (value, assetData) => { + updateBuyQuote: (assetBuyer, value, asset) => { // Update the input dispatch(actions.updateSelectedAssetAmount(value)); // invalidate the last buy quote. @@ -62,11 +86,27 @@ const mapDispatchToProps = ( // reset our buy state dispatch(actions.updateSelectedAssetBuyState(AsyncProcessState.NONE)); // tslint:disable-next-line:no-floating-promises - debouncedUpdateBuyQuoteAsync(dispatch, assetData, value); + debouncedUpdateBuyQuoteAsync(dispatch, assetBuyer, asset, value); }, }); +const mergeProps = ( + connectedState: ConnectedState, + connectedDispatch: ConnectedDispatch, + ownProps: SelectedAssetAmountInputProps, +): FinalProps => { + return { + ...ownProps, + asset: connectedState.asset, + value: connectedState.value, + onChange: (value, asset) => { + connectedDispatch.updateBuyQuote(connectedState.assetBuyer, value, asset); + }, + }; +}; + export const SelectedAssetAmountInput: React.ComponentClass = connect( mapStateToProps, mapDispatchToProps, + mergeProps, )(AssetAmountInput); diff --git a/packages/instant/src/containers/selected_asset_buy_button.ts b/packages/instant/src/containers/selected_asset_buy_button.ts index 4cbaf5537..4118932b2 100644 --- a/packages/instant/src/containers/selected_asset_buy_button.ts +++ b/packages/instant/src/containers/selected_asset_buy_button.ts @@ -1,4 +1,4 @@ -import { BuyQuote } from '@0xproject/asset-buyer'; +import { AssetBuyer, BuyQuote } from '@0xproject/asset-buyer'; import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; @@ -13,6 +13,7 @@ import { BuyButton } from '../components/buy_button'; export interface SelectedAssetBuyButtonProps {} interface ConnectedState { + assetBuyer?: AssetBuyer; text: string; buyQuote?: BuyQuote; } @@ -39,6 +40,7 @@ const textForState = (state: AsyncProcessState): string => { }; const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyButtonProps): ConnectedState => ({ + assetBuyer: state.assetBuyer, text: textForState(state.selectedAssetBuyState), buyQuote: state.latestBuyQuote, }); diff --git a/packages/instant/src/data/asset_data_network_mapping.ts b/packages/instant/src/data/asset_data_network_mapping.ts new file mode 100644 index 000000000..9e14462f8 --- /dev/null +++ b/packages/instant/src/data/asset_data_network_mapping.ts @@ -0,0 +1,25 @@ +import * as _ from 'lodash'; + +import { Network } from '../types'; + +interface AssetDataByNetwork { + [Network.Kovan]?: string; + [Network.Mainnet]?: string; +} + +const sameAssetDatas: AssetDataByNetwork[] = [ + { + [Network.Mainnet]: '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498', + [Network.Kovan]: '0xf47261b00000000000000000000000002002d3812f58e35f0ea1ffbf80a75a38c32175fa', + }, +]; + +export const assetDataNetworkMapping = { + getAssociatedAssetDataIfExists: (assetData: string, network: Network): string | undefined => { + const assetDataGroupIfExists = _.find(sameAssetDatas, value => value[network] === assetData); + if (_.isUndefined(assetDataGroupIfExists)) { + return; + } + return assetDataGroupIfExists[Network.Mainnet]; + }, +}; diff --git a/packages/instant/src/data/asset_meta_data.ts b/packages/instant/src/data/asset_meta_data.ts deleted file mode 100644 index e4d3e8f73..000000000 --- a/packages/instant/src/data/asset_meta_data.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { AssetProxyId, ObjectMap } from '@0xproject/types'; - -import { zrxAssetData } from '../constants'; -import { AssetMetaData } from '../types'; - -// Map from assetData string to AssetMetaData object -// TODO: import this from somewhere else. -export const assetMetaData: ObjectMap = { - [zrxAssetData]: { - assetProxyId: AssetProxyId.ERC20, - decimals: 18, - primaryColor: 'rgb(54, 50, 60)', - symbol: 'zrx', - }, -}; diff --git a/packages/instant/src/data/asset_meta_data_map.ts b/packages/instant/src/data/asset_meta_data_map.ts new file mode 100644 index 000000000..7d83865f1 --- /dev/null +++ b/packages/instant/src/data/asset_meta_data_map.ts @@ -0,0 +1,14 @@ +import { AssetProxyId, ObjectMap } from '@0xproject/types'; + +import { AssetMetaData } from '../types'; + +// Map from assetData string to AssetMetaData object +// TODO: import this from somewhere else. +export const assetMetaDataMap: ObjectMap = { + '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498': { + assetProxyId: AssetProxyId.ERC20, + decimals: 18, + primaryColor: 'rgb(54, 50, 60)', + symbol: 'zrx', + }, +}; diff --git a/packages/instant/src/index.umd.ts b/packages/instant/src/index.umd.ts index d4eca177d..fe78ce401 100644 --- a/packages/instant/src/index.umd.ts +++ b/packages/instant/src/index.umd.ts @@ -1,10 +1,8 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; -import { ZeroExInstant } from './index'; +import { ZeroExInstant, ZeroExInstantProps } from './index'; -export interface ZeroExInstantOptions {} - -export const render = (props: ZeroExInstantOptions, selector: string = '#zeroExInstantContainer') => { +export const render = (props: ZeroExInstantProps, selector: string = '#zeroExInstantContainer') => { ReactDOM.render(React.createElement(ZeroExInstant, props), document.querySelector(selector)); }; diff --git a/packages/instant/src/redux/actions.ts b/packages/instant/src/redux/actions.ts index 7d07b4950..fe055b75f 100644 --- a/packages/instant/src/redux/actions.ts +++ b/packages/instant/src/redux/actions.ts @@ -25,6 +25,7 @@ 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_SELECTED_ASSET = 'UPDATE_SELECTED_ASSET', } export const actions = { @@ -33,4 +34,5 @@ export const actions = { updateSelectedAssetBuyState: (buyState: AsyncProcessState) => createAction(ActionTypes.UPDATE_SELECTED_ASSET_BUY_STATE, buyState), updateLatestBuyQuote: (buyQuote?: BuyQuote) => createAction(ActionTypes.UPDATE_LATEST_BUY_QUOTE, buyQuote), + updateSelectedAsset: (assetData?: string) => createAction(ActionTypes.UPDATE_SELECTED_ASSET, assetData), }; diff --git a/packages/instant/src/redux/async_data.ts b/packages/instant/src/redux/async_data.ts index 348838307..4ed89bdc3 100644 --- a/packages/instant/src/redux/async_data.ts +++ b/packages/instant/src/redux/async_data.ts @@ -3,10 +3,10 @@ import { coinbaseApi } from '../util/coinbase_api'; import { ActionTypes } from './actions'; -import { store } from './store'; +import { Store } from './store'; export const asyncData = { - fetchAndDispatchToStore: async () => { + fetchAndDispatchToStore: async (store: Store) => { let ethUsdPrice = BIG_NUMBER_ZERO; try { ethUsdPrice = await coinbaseApi.getEthUsdPrice(); diff --git a/packages/instant/src/redux/reducer.ts b/packages/instant/src/redux/reducer.ts index adecf2ab7..9922131b4 100644 --- a/packages/instant/src/redux/reducer.ts +++ b/packages/instant/src/redux/reducer.ts @@ -1,14 +1,17 @@ -import { BuyQuote } from '@0xproject/asset-buyer'; +import { AssetBuyer, BuyQuote } from '@0xproject/asset-buyer'; +import { ObjectMap } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; -import { zrxAssetData } from '../constants'; -import { AsyncProcessState } from '../types'; +import { Asset, AssetMetaData, AsyncProcessState } from '../types'; +import { assetUtils } from '../util/asset'; import { Action, ActionTypes } from './actions'; export interface State { - selectedAssetData?: string; + assetBuyer?: AssetBuyer; + assetMetaDataMap: ObjectMap; + selectedAsset?: Asset; selectedAssetAmount?: BigNumber; selectedAssetBuyState: AsyncProcessState; ethUsdPrice?: BigNumber; @@ -16,14 +19,14 @@ export interface State { } export const INITIAL_STATE: State = { - // TODO: Remove hardcoded zrxAssetData - selectedAssetData: zrxAssetData, selectedAssetAmount: undefined, + assetMetaDataMap: {}, selectedAssetBuyState: AsyncProcessState.NONE, ethUsdPrice: undefined, latestBuyQuote: undefined, }; +// TODO: Figure out why there is an INITIAL_STATE key in the store... export const reducer = (state: State = INITIAL_STATE, action: Action): State => { switch (action.type) { case ActionTypes.UPDATE_ETH_USD_PRICE: @@ -46,6 +49,16 @@ export const reducer = (state: State = INITIAL_STATE, action: Action): State => ...state, selectedAssetBuyState: action.data, }; + case ActionTypes.UPDATE_SELECTED_ASSET: + const newSelectedAssetData = action.data; + let newSelectedAsset: Asset | undefined; + if (!_.isUndefined(newSelectedAssetData)) { + newSelectedAsset = assetUtils.createAssetFromAssetData(newSelectedAssetData, state.assetMetaDataMap); + } + return { + ...state, + selectedAsset: newSelectedAsset, + }; default: return state; } diff --git a/packages/instant/src/redux/store.ts b/packages/instant/src/redux/store.ts index fcd19f9a8..fc943f1be 100644 --- a/packages/instant/src/redux/store.ts +++ b/packages/instant/src/redux/store.ts @@ -1,6 +1,16 @@ import * as _ from 'lodash'; import { createStore, Store as ReduxStore } from 'redux'; -import { reducer, State } from './reducer'; +import { INITIAL_STATE, reducer, State } from './reducer'; -export const store: ReduxStore = createStore(reducer); +export type Store = ReduxStore; + +export const store = { + create: (withState: Partial): Store => { + const allInitialState = { + INITIAL_STATE, + ...withState, + }; + return createStore(reducer, allInitialState); + }, +}; diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts index bf3ee392f..867605573 100644 --- a/packages/instant/src/types.ts +++ b/packages/instant/src/types.ts @@ -22,12 +22,34 @@ export interface ERC20AssetMetaData { export interface ERC721AssetMetaData { assetProxyId: AssetProxyId.ERC721; name: string; + representationUrl?: string; primaryColor?: string; } export type AssetMetaData = ERC20AssetMetaData | ERC721AssetMetaData; +export interface ERC20Asset { + assetProxyId: AssetProxyId.ERC20; + assetData: string; + metaData: ERC20AssetMetaData; +} + +export interface ERC721Asset { + assetProxyId: AssetProxyId.ERC721; + assetData: string; + metaData: ERC721AssetMetaData; +} +export interface Asset { + assetProxyId: AssetProxyId; + assetData: string; + metaData: AssetMetaData; +} + export enum Network { Kovan = 42, Mainnet = 1, } + +export enum ZeroExInstantError { + AssetMetaDataNotAvailable = 'ASSET_META_DATA_NOT_AVAILABLE', +} diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts new file mode 100644 index 000000000..ec22276ae --- /dev/null +++ b/packages/instant/src/util/asset.ts @@ -0,0 +1,34 @@ +import { assetDataUtils } from '@0xproject/order-utils'; +import { AssetProxyId, ObjectMap } from '@0xproject/types'; +import * as _ from 'lodash'; + +import { assetDataNetworkMapping } from '../data/asset_data_network_mapping'; +import { Asset, AssetMetaData, Network, ZeroExInstantError } from '../types'; + +export const assetUtils = { + createAssetFromAssetData: (assetData: string, assetMetaDataMap: ObjectMap): Asset => { + return { + assetProxyId: assetDataUtils.decodeAssetProxyId(assetData), + assetData, + metaData: assetUtils.getMetaDataOrThrow(assetData, assetMetaDataMap), + }; + }, + getMetaDataOrThrow: ( + assetData: string, + metaDataMap: ObjectMap, + network: Network = Network.Mainnet, + ): AssetMetaData => { + let mainnetAssetData: string | undefined = assetData; + if (network !== Network.Mainnet) { + mainnetAssetData = assetDataNetworkMapping.getAssociatedAssetDataIfExists(assetData, network); + } + if (_.isUndefined(mainnetAssetData)) { + throw new Error(ZeroExInstantError.AssetMetaDataNotAvailable); + } + const metaData = metaDataMap[mainnetAssetData]; + if (_.isUndefined(metaData)) { + throw new Error(); + } + return metaData; + }, +}; diff --git a/packages/instant/src/util/asset_buyer.ts b/packages/instant/src/util/asset_buyer.ts deleted file mode 100644 index 27d66d600..000000000 --- a/packages/instant/src/util/asset_buyer.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { AssetBuyer } from '@0xproject/asset-buyer'; - -import { sraApiUrl } from '../constants'; - -import { getProvider } from './provider'; - -const provider = getProvider(); - -export const assetBuyer = AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(provider, sraApiUrl); -- cgit From b4fb6b5ff3106c9223508469a2bee583bcea8da0 Mon Sep 17 00:00:00 2001 From: fragosti Date: Thu, 18 Oct 2018 15:48:35 -0700 Subject: fix: add error message and move more logic to assetUtils --- packages/instant/src/data/asset_data_network_mapping.ts | 12 +----------- packages/instant/src/util/asset.ts | 11 +++++++++-- 2 files changed, 10 insertions(+), 13 deletions(-) (limited to 'packages/instant/src') diff --git a/packages/instant/src/data/asset_data_network_mapping.ts b/packages/instant/src/data/asset_data_network_mapping.ts index 9e14462f8..e8ccbf011 100644 --- a/packages/instant/src/data/asset_data_network_mapping.ts +++ b/packages/instant/src/data/asset_data_network_mapping.ts @@ -7,19 +7,9 @@ interface AssetDataByNetwork { [Network.Mainnet]?: string; } -const sameAssetDatas: AssetDataByNetwork[] = [ +export const assetDataNetworkMapping: AssetDataByNetwork[] = [ { [Network.Mainnet]: '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498', [Network.Kovan]: '0xf47261b00000000000000000000000002002d3812f58e35f0ea1ffbf80a75a38c32175fa', }, ]; - -export const assetDataNetworkMapping = { - getAssociatedAssetDataIfExists: (assetData: string, network: Network): string | undefined => { - const assetDataGroupIfExists = _.find(sameAssetDatas, value => value[network] === assetData); - if (_.isUndefined(assetDataGroupIfExists)) { - return; - } - return assetDataGroupIfExists[Network.Mainnet]; - }, -}; diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts index edeac0da3..e0d5ffcb7 100644 --- a/packages/instant/src/util/asset.ts +++ b/packages/instant/src/util/asset.ts @@ -19,14 +19,14 @@ export const assetUtils = { ): AssetMetaData => { let mainnetAssetData: string | undefined = assetData; if (network !== Network.Mainnet) { - mainnetAssetData = assetDataNetworkMapping.getAssociatedAssetDataIfExists(assetData, network); + mainnetAssetData = assetUtils.getAssociatedAssetDataIfExists(assetData, network); } if (_.isUndefined(mainnetAssetData)) { throw new Error(ZeroExInstantError.AssetMetaDataNotAvailable); } const metaData = metaDataMap[mainnetAssetData]; if (_.isUndefined(metaData)) { - throw new Error(); + throw new Error(ZeroExInstantError.AssetMetaDataNotAvailable); } return metaData; }, @@ -44,4 +44,11 @@ export const assetUtils = { return defaultName; } }, + getAssociatedAssetDataIfExists: (assetData: string, network: Network): string | undefined => { + const assetDataGroupIfExists = _.find(assetDataNetworkMapping, value => value[network] === assetData); + if (_.isUndefined(assetDataGroupIfExists)) { + return; + } + return assetDataGroupIfExists[Network.Mainnet]; + }, }; -- cgit From 44635f34f0df4da935ed4793566995873aac5055 Mon Sep 17 00:00:00 2001 From: fragosti Date: Thu, 18 Oct 2018 15:54:53 -0700 Subject: fix: error test undefined case --- packages/instant/src/util/asset.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages/instant/src') diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts index e0d5ffcb7..a105a7c2b 100644 --- a/packages/instant/src/util/asset.ts +++ b/packages/instant/src/util/asset.ts @@ -37,9 +37,9 @@ export const assetUtils = { const metaData = asset.metaData; switch (metaData.assetProxyId) { case AssetProxyId.ERC20: - return metaData.symbol; + return metaData.symbol.toUpperCase(); case AssetProxyId.ERC721: - return metaData.name; + return metaData.name.toUpperCase(); default: return defaultName; } -- cgit From ad6dc8e891059f00f2ddda1884bbc78c1e47ea24 Mon Sep 17 00:00:00 2001 From: fragosti Date: Thu, 18 Oct 2018 16:04:30 -0700 Subject: fix: refactor ZeroExInstant state creation logic and fix bug --- packages/instant/src/components/zero_ex_instant.tsx | 9 ++++++--- packages/instant/src/redux/reducer.ts | 1 - packages/instant/src/redux/store.ts | 3 +-- 3 files changed, 7 insertions(+), 6 deletions(-) (limited to 'packages/instant/src') diff --git a/packages/instant/src/components/zero_ex_instant.tsx b/packages/instant/src/components/zero_ex_instant.tsx index 5b75a7556..a67c0f412 100644 --- a/packages/instant/src/components/zero_ex_instant.tsx +++ b/packages/instant/src/components/zero_ex_instant.tsx @@ -29,8 +29,7 @@ export class ZeroExInstant extends React.Component { additionalAssetMetaDataMap: {}, }; public store: Store; - constructor(props: ZeroExInstantProps) { - super(props); + private static _createPartialStoreStateFromProps(props: ZeroExInstantProps): Partial { // TODO: Provider needs to not be hard-coded to injected web3. const assetBuyer = AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(getProvider(), props.sraApiUrl); const completeAssetMetaDataMap = { @@ -42,7 +41,11 @@ export class ZeroExInstant extends React.Component { selectedAsset: assetUtils.createAssetFromAssetData(props.assetData, completeAssetMetaDataMap), assetMetaDataMap: completeAssetMetaDataMap, }; - this.store = store.create(storeStateFromProps); + return storeStateFromProps; + } + constructor(props: ZeroExInstantProps) { + super(props); + this.store = store.create(ZeroExInstant._createPartialStoreStateFromProps(this.props)); // tslint:disable-next-line:no-floating-promises asyncData.fetchAndDispatchToStore(this.store); } diff --git a/packages/instant/src/redux/reducer.ts b/packages/instant/src/redux/reducer.ts index 657bd0e40..83422be45 100644 --- a/packages/instant/src/redux/reducer.ts +++ b/packages/instant/src/redux/reducer.ts @@ -36,7 +36,6 @@ export const INITIAL_STATE: State = { quoteState: AsyncProcessState.NONE, }; -// TODO: Figure out why there is an INITIAL_STATE key in the store... export const reducer = (state: State = INITIAL_STATE, action: Action): State => { switch (action.type) { case ActionTypes.UPDATE_ETH_USD_PRICE: diff --git a/packages/instant/src/redux/store.ts b/packages/instant/src/redux/store.ts index 505234299..88d964bbc 100644 --- a/packages/instant/src/redux/store.ts +++ b/packages/instant/src/redux/store.ts @@ -9,10 +9,9 @@ export type Store = ReduxStore; export const store = { create: (withState: Partial): Store => { const allInitialState = { - INITIAL_STATE, + ...INITIAL_STATE, ...withState, }; return createStore(reducer, allInitialState, devToolsEnhancer({})); }, }; - -- cgit From 7a8adf9db5a83c3f44d6fe9e84e00ff1ec1e88e0 Mon Sep 17 00:00:00 2001 From: fragosti Date: Thu, 18 Oct 2018 16:35:59 -0700 Subject: feat: use color from metaData instead of default primaryColor --- .../instant/src/components/zero_ex_instant.tsx | 6 ++-- .../containers/selected_asset_theme_provider.ts | 32 ++++++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 packages/instant/src/containers/selected_asset_theme_provider.ts (limited to 'packages/instant/src') diff --git a/packages/instant/src/components/zero_ex_instant.tsx b/packages/instant/src/components/zero_ex_instant.tsx index a67c0f412..c59c838a2 100644 --- a/packages/instant/src/components/zero_ex_instant.tsx +++ b/packages/instant/src/components/zero_ex_instant.tsx @@ -3,12 +3,12 @@ import { ObjectMap } from '@0x/types'; import * as React from 'react'; import { Provider } from 'react-redux'; +import { SelectedAssetThemeProvider } from '../containers/selected_asset_theme_provider'; import { assetMetaDataMap } from '../data/asset_meta_data_map'; import { asyncData } from '../redux/async_data'; import { State } from '../redux/reducer'; import { store, Store } from '../redux/store'; import { fonts } from '../style/fonts'; -import { theme, ThemeProvider } from '../style/theme'; import { AssetMetaData } from '../types'; import { assetUtils } from '../util/asset'; import { getProvider } from '../util/provider'; @@ -53,9 +53,9 @@ export class ZeroExInstant extends React.Component { public render(): React.ReactNode { return ( - + - + ); } diff --git a/packages/instant/src/containers/selected_asset_theme_provider.ts b/packages/instant/src/containers/selected_asset_theme_provider.ts new file mode 100644 index 000000000..6e6b83d73 --- /dev/null +++ b/packages/instant/src/containers/selected_asset_theme_provider.ts @@ -0,0 +1,32 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import { connect } from 'react-redux'; + +import { State } from '../redux/reducer'; +import { Theme, theme as defaultTheme, ThemeProvider } from '../style/theme'; +import { Asset } from '../types'; + +export interface SelectedAssetThemeProviderProps {} + +interface ConnectedState { + theme: Theme; +} + +const getTheme = (asset?: Asset): Theme => { + if (!_.isUndefined(asset) && !_.isUndefined(asset.metaData.primaryColor)) { + return { + ...defaultTheme, + primaryColor: asset.metaData.primaryColor, + }; + } + return defaultTheme; +}; + +const mapStateToProps = (state: State, _ownProps: SelectedAssetThemeProviderProps): ConnectedState => { + const theme = getTheme(state.selectedAsset); + return { theme }; +}; + +export const SelectedAssetThemeProvider: React.ComponentClass = connect( + mapStateToProps, +)(ThemeProvider); -- cgit From 7bc2df56023641c27de62bc7e9a8953767cbc076 Mon Sep 17 00:00:00 2001 From: fragosti Date: Thu, 18 Oct 2018 17:42:32 -0700 Subject: feat: add network to state and use it where relevant --- .../instant/src/components/zero_ex_instant.tsx | 41 ++++++++++++++-------- packages/instant/src/redux/reducer.ts | 13 +++++-- packages/instant/src/redux/store.ts | 10 ++---- packages/instant/src/util/asset.ts | 14 ++++---- 4 files changed, 47 insertions(+), 31 deletions(-) (limited to 'packages/instant/src') diff --git a/packages/instant/src/components/zero_ex_instant.tsx b/packages/instant/src/components/zero_ex_instant.tsx index c59c838a2..142e74dae 100644 --- a/packages/instant/src/components/zero_ex_instant.tsx +++ b/packages/instant/src/components/zero_ex_instant.tsx @@ -4,12 +4,11 @@ import * as React from 'react'; import { Provider } from 'react-redux'; import { SelectedAssetThemeProvider } from '../containers/selected_asset_theme_provider'; -import { assetMetaDataMap } from '../data/asset_meta_data_map'; import { asyncData } from '../redux/async_data'; -import { State } from '../redux/reducer'; +import { INITIAL_STATE, State } from '../redux/reducer'; import { store, Store } from '../redux/store'; import { fonts } from '../style/fonts'; -import { AssetMetaData } from '../types'; +import { AssetMetaData, Network } from '../types'; import { assetUtils } from '../util/asset'; import { getProvider } from '../util/provider'; @@ -17,35 +16,49 @@ import { ZeroExInstantContainer } from './zero_ex_instant_container'; fonts.include(); -export interface ZeroExInstantProps { +export type ZeroExInstantProps = ZeroExInstantRequiredProps & Partial; + +export interface ZeroExInstantRequiredProps { // TODO: Change API when we allow the selection of different assetDatas assetData: string; - sraApiUrl: string; + // TODO: Allow for a function that returns orders + liquiditySource: string; +} + +export interface ZeroExInstantOptionalProps { additionalAssetMetaDataMap: ObjectMap; + network: Network; } export class ZeroExInstant extends React.Component { - public static defaultProps = { - additionalAssetMetaDataMap: {}, - }; public store: Store; - private static _createPartialStoreStateFromProps(props: ZeroExInstantProps): Partial { + private static _mergeInitialStateWithProps(props: ZeroExInstantProps, state: State = INITIAL_STATE): State { + // Create merged object such that properties in props override default settings + const optionalPropsWithDefaults: ZeroExInstantOptionalProps = { + additionalAssetMetaDataMap: props.additionalAssetMetaDataMap || {}, + network: props.network || state.network, + }; + const { network } = optionalPropsWithDefaults; // TODO: Provider needs to not be hard-coded to injected web3. - const assetBuyer = AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(getProvider(), props.sraApiUrl); + const assetBuyer = AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(getProvider(), props.liquiditySource, { + networkId: network, + }); const completeAssetMetaDataMap = { ...props.additionalAssetMetaDataMap, - ...assetMetaDataMap, + ...state.assetMetaDataMap, }; - const storeStateFromProps: Partial = { + const storeStateFromProps: State = { + ...state, assetBuyer, - selectedAsset: assetUtils.createAssetFromAssetData(props.assetData, completeAssetMetaDataMap), + network, + selectedAsset: assetUtils.createAssetFromAssetData(props.assetData, completeAssetMetaDataMap, network), assetMetaDataMap: completeAssetMetaDataMap, }; return storeStateFromProps; } constructor(props: ZeroExInstantProps) { super(props); - this.store = store.create(ZeroExInstant._createPartialStoreStateFromProps(this.props)); + this.store = store.create(ZeroExInstant._mergeInitialStateWithProps(this.props, INITIAL_STATE)); // tslint:disable-next-line:no-floating-promises asyncData.fetchAndDispatchToStore(this.store); } diff --git a/packages/instant/src/redux/reducer.ts b/packages/instant/src/redux/reducer.ts index 83422be45..e9e0a85e0 100644 --- a/packages/instant/src/redux/reducer.ts +++ b/packages/instant/src/redux/reducer.ts @@ -3,7 +3,8 @@ import { ObjectMap } from '@0x/types'; import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; -import { Asset, AssetMetaData, AsyncProcessState } from '../types'; +import { assetMetaDataMap } from '../data/asset_meta_data_map'; +import { Asset, AssetMetaData, AsyncProcessState, Network } from '../types'; import { assetUtils } from '../util/asset'; import { Action, ActionTypes } from './actions'; @@ -13,6 +14,7 @@ export enum LatestErrorDisplay { Hidden, } export interface State { + network: Network; assetBuyer?: AssetBuyer; assetMetaDataMap: ObjectMap; selectedAsset?: Asset; @@ -26,8 +28,9 @@ export interface State { } export const INITIAL_STATE: State = { + network: Network.Mainnet, selectedAssetAmount: undefined, - assetMetaDataMap: {}, + assetMetaDataMap, buyOrderState: AsyncProcessState.NONE, ethUsdPrice: undefined, latestBuyQuote: undefined, @@ -92,7 +95,11 @@ export const reducer = (state: State = INITIAL_STATE, action: Action): State => const newSelectedAssetData = action.data; let newSelectedAsset: Asset | undefined; if (!_.isUndefined(newSelectedAssetData)) { - newSelectedAsset = assetUtils.createAssetFromAssetData(newSelectedAssetData, state.assetMetaDataMap); + newSelectedAsset = assetUtils.createAssetFromAssetData( + newSelectedAssetData, + state.assetMetaDataMap, + state.network, + ); } return { ...state, diff --git a/packages/instant/src/redux/store.ts b/packages/instant/src/redux/store.ts index 88d964bbc..01deb8690 100644 --- a/packages/instant/src/redux/store.ts +++ b/packages/instant/src/redux/store.ts @@ -2,16 +2,12 @@ import * as _ from 'lodash'; import { createStore, Store as ReduxStore } from 'redux'; import { devToolsEnhancer } from 'redux-devtools-extension/developmentOnly'; -import { INITIAL_STATE, reducer, State } from './reducer'; +import { reducer, State } from './reducer'; export type Store = ReduxStore; export const store = { - create: (withState: Partial): Store => { - const allInitialState = { - ...INITIAL_STATE, - ...withState, - }; - return createStore(reducer, allInitialState, devToolsEnhancer({})); + create: (state: State): Store => { + return createStore(reducer, state, devToolsEnhancer({})); }, }; diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts index a105a7c2b..d50efb5d1 100644 --- a/packages/instant/src/util/asset.ts +++ b/packages/instant/src/util/asset.ts @@ -6,17 +6,17 @@ import { assetDataNetworkMapping } from '../data/asset_data_network_mapping'; import { Asset, AssetMetaData, Network, ZeroExInstantError } from '../types'; export const assetUtils = { - createAssetFromAssetData: (assetData: string, assetMetaDataMap: ObjectMap): Asset => { + createAssetFromAssetData: ( + assetData: string, + assetMetaDataMap: ObjectMap, + network: Network, + ): Asset => { return { assetData, - metaData: assetUtils.getMetaDataOrThrow(assetData, assetMetaDataMap), + metaData: assetUtils.getMetaDataOrThrow(assetData, assetMetaDataMap, network), }; }, - getMetaDataOrThrow: ( - assetData: string, - metaDataMap: ObjectMap, - network: Network = Network.Mainnet, - ): AssetMetaData => { + getMetaDataOrThrow: (assetData: string, metaDataMap: ObjectMap, network: Network): AssetMetaData => { let mainnetAssetData: string | undefined = assetData; if (network !== Network.Mainnet) { mainnetAssetData = assetUtils.getAssociatedAssetDataIfExists(assetData, network); -- cgit From f3a6800306a6fd40f19d15318b9fdac4dcdf76c8 Mon Sep 17 00:00:00 2001 From: fragosti Date: Thu, 18 Oct 2018 18:08:49 -0700 Subject: fix: dont force NFTs to uppercase their name --- packages/instant/src/util/asset.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/instant/src') diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts index d50efb5d1..698107538 100644 --- a/packages/instant/src/util/asset.ts +++ b/packages/instant/src/util/asset.ts @@ -39,7 +39,7 @@ export const assetUtils = { case AssetProxyId.ERC20: return metaData.symbol.toUpperCase(); case AssetProxyId.ERC721: - return metaData.name.toUpperCase(); + return metaData.name; default: return defaultName; } -- cgit From 48f1e6057c878a948d21ed7d2214c7792c648f75 Mon Sep 17 00:00:00 2001 From: fragosti Date: Fri, 19 Oct 2018 09:23:58 -0700 Subject: chore: run linter --- packages/instant/src/util/asset.ts | 1 - 1 file changed, 1 deletion(-) (limited to 'packages/instant/src') diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts index 698107538..4e3b2b946 100644 --- a/packages/instant/src/util/asset.ts +++ b/packages/instant/src/util/asset.ts @@ -1,4 +1,3 @@ -import { assetDataUtils } from '@0x/order-utils'; import { AssetProxyId, ObjectMap } from '@0x/types'; import * as _ from 'lodash'; -- 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(-) (limited to 'packages/instant/src') 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 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 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'packages/instant/src') 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); } -- 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 +- packages/instant/src/components/zero_ex_instant.tsx | 8 ++++---- packages/instant/src/constants.ts | 1 + packages/instant/src/index.umd.ts | 3 ++- 4 files changed, 8 insertions(+), 6 deletions(-) (limited to 'packages/instant/src') 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 (