aboutsummaryrefslogtreecommitdiffstats
path: root/packages/instant
diff options
context:
space:
mode:
Diffstat (limited to 'packages/instant')
-rw-r--r--packages/instant/package.json1
-rw-r--r--packages/instant/public/index.html3
-rw-r--r--packages/instant/src/components/asset_amount_input.tsx15
-rw-r--r--packages/instant/src/components/buy_button.tsx10
-rw-r--r--packages/instant/src/components/zero_ex_instant.tsx80
-rw-r--r--packages/instant/src/constants.ts4
-rw-r--r--packages/instant/src/containers/latest_error.tsx10
-rw-r--r--packages/instant/src/containers/selected_asset_amount_input.ts66
-rw-r--r--packages/instant/src/containers/selected_asset_buy_button.ts4
-rw-r--r--packages/instant/src/containers/selected_asset_theme_provider.ts32
-rw-r--r--packages/instant/src/data/asset_data_network_mapping.ts15
-rw-r--r--packages/instant/src/data/asset_meta_data_map.ts (renamed from packages/instant/src/data/asset_meta_data.ts)5
-rw-r--r--packages/instant/src/index.umd.ts7
-rw-r--r--packages/instant/src/redux/actions.ts2
-rw-r--r--packages/instant/src/redux/async_data.ts4
-rw-r--r--packages/instant/src/redux/reducer.ts31
-rw-r--r--packages/instant/src/redux/store.ts8
-rw-r--r--packages/instant/src/types.ts20
-rw-r--r--packages/instant/src/util/asset.ts53
-rw-r--r--packages/instant/src/util/asset_buyer.ts9
-rw-r--r--packages/instant/src/util/asset_data.ts21
-rw-r--r--packages/instant/src/util/error.ts14
-rw-r--r--packages/instant/test/util/asset.test.ts47
-rw-r--r--packages/instant/test/util/asset_data.test.ts17
-rw-r--r--packages/instant/test/util/error.test.ts24
25 files changed, 373 insertions, 129 deletions
diff --git a/packages/instant/package.json b/packages/instant/package.json
index d7ec85b2f..1a6e9d2e9 100644
--- a/packages/instant/package.json
+++ b/packages/instant/package.json
@@ -45,6 +45,7 @@
"homepage": "https://github.com/0xProject/0x-monorepo/packages/instant/README.md",
"dependencies": {
"@0x/asset-buyer": "^2.1.0",
+ "@0x/order-utils": "^2.0.0",
"@0x/types": "^1.2.0",
"@0x/typescript-typings": "^3.0.3",
"@0x/utils": "^2.0.3",
diff --git a/packages/instant/public/index.html b/packages/instant/public/index.html
index fb041745e..14555fc64 100644
--- a/packages/instant/public/index.html
+++ b/packages/instant/public/index.html
@@ -25,7 +25,8 @@
<div id="zeroExInstantContainer"></div>
<script>
zeroExInstant.render({
-
+ liquiditySource: 'https://api.radarrelay.com/0x/v2/',
+ assetData: '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498',
});
</script>
</body>
diff --git a/packages/instant/src/components/asset_amount_input.tsx b/packages/instant/src/components/asset_amount_input.tsx
index 730e6396f..c03ef1cf3 100644
--- a/packages/instant/src/components/asset_amount_input.tsx
+++ b/packages/instant/src/components/asset_amount_input.tsx
@@ -2,17 +2,18 @@ import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import * as React from 'react';
-import { assetDataUtil } from '../util/asset_data';
-
import { ColorOption } from '../style/theme';
+import { ERC20Asset } from '../types';
+import { assetUtils } from '../util/asset';
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<AssetAmountInputProps> {
@@ -20,19 +21,19 @@ export class AssetAmountInput extends React.Component<AssetAmountInputProps> {
onChange: util.boundNoop,
};
public render(): React.ReactNode {
- const { assetData, onChange, ...rest } = this.props;
+ const { asset, onChange, ...rest } = this.props;
return (
<Container>
<AmountInput {...rest} onChange={this._handleChange} />
<Container display="inline-block" marginLeft="10px">
<Text fontSize={rest.fontSize} fontColor={ColorOption.white} textTransform="uppercase">
- {assetDataUtil.bestNameForAsset(this.props.assetData, '???')}
+ {assetUtils.bestNameForAsset(asset)}
</Text>
</Container>
</Container>
);
}
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 d2a8bd07a..adc32f071 100644
--- a/packages/instant/src/components/buy_button.tsx
+++ b/packages/instant/src/components/buy_button.tsx
@@ -1,9 +1,8 @@
-import { BuyQuote } from '@0x/asset-buyer';
+import { AssetBuyer, BuyQuote } from '@0x/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;
@@ -24,7 +24,7 @@ export class BuyButton extends React.Component<BuyButtonProps> {
onBuyFailure: util.boundNoop,
};
public render(): React.ReactNode {
- const shouldDisableButton = _.isUndefined(this.props.buyQuote);
+ const shouldDisableButton = _.isUndefined(this.props.buyQuote) || _.isUndefined(this.props.assetBuyer);
return (
<Container padding="20px" width="100%">
<Button width="100%" onClick={this._handleClick} isDisabled={shouldDisableButton}>
@@ -37,13 +37,13 @@ export class BuyButton extends React.Component<BuyButtonProps> {
}
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..ffa5a8250 100644
--- a/packages/instant/src/components/zero_ex_instant.tsx
+++ b/packages/instant/src/components/zero_ex_instant.tsx
@@ -1,23 +1,75 @@
+import { AssetBuyer } from '@0x/asset-buyer';
+import { ObjectMap } from '@0x/types';
import * as React from 'react';
import { Provider } from 'react-redux';
+import { SelectedAssetThemeProvider } from '../containers/selected_asset_theme_provider';
import { asyncData } from '../redux/async_data';
-import { store } from '../redux/store';
+import { INITIAL_STATE, State } from '../redux/reducer';
+import { store, Store } from '../redux/store';
import { fonts } from '../style/fonts';
-import { theme, ThemeProvider } from '../style/theme';
+import { AssetMetaData, Network } 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 const ZeroExInstant: React.StatelessComponent<ZeroExInstantProps> = () => (
- <Provider store={store}>
- <ThemeProvider theme={theme}>
- <ZeroExInstantContainer />
- </ThemeProvider>
- </Provider>
-);
+
+export type ZeroExInstantProps = ZeroExInstantRequiredProps & Partial<ZeroExInstantOptionalProps>;
+
+export interface ZeroExInstantRequiredProps {
+ // TODO: Change API when we allow the selection of different assetDatas
+ assetData: string;
+ // TODO: Allow for a function that returns orders
+ liquiditySource: string;
+}
+
+export interface ZeroExInstantOptionalProps {
+ additionalAssetMetaDataMap: ObjectMap<AssetMetaData>;
+ network: Network;
+}
+
+export class ZeroExInstant extends React.Component<ZeroExInstantProps> {
+ private readonly _store: Store;
+ 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.liquiditySource, {
+ networkId: network,
+ });
+ const completeAssetMetaDataMap = {
+ ...props.additionalAssetMetaDataMap,
+ ...state.assetMetaDataMap,
+ };
+ const storeStateFromProps: State = {
+ ...state,
+ assetBuyer,
+ network,
+ selectedAsset: assetUtils.createAssetFromAssetData(props.assetData, completeAssetMetaDataMap, network),
+ assetMetaDataMap: completeAssetMetaDataMap,
+ };
+ return storeStateFromProps;
+ }
+ constructor(props: ZeroExInstantProps) {
+ super(props);
+ this._store = store.create(ZeroExInstant._mergeInitialStateWithProps(this.props, INITIAL_STATE));
+ // tslint:disable-next-line:no-floating-promises
+ asyncData.fetchAndDispatchToStore(this._store);
+ }
+
+ public render(): React.ReactNode {
+ return (
+ <Provider store={this._store}>
+ <SelectedAssetThemeProvider>
+ <ZeroExInstantContainer />
+ </SelectedAssetThemeProvider>
+ </Provider>
+ );
+ }
+}
diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts
index 5d5341f9c..31491c80a 100644
--- a/packages/instant/src/constants.ts
+++ b/packages/instant/src/constants.ts
@@ -1,6 +1,4 @@
import { BigNumber } from '@0x/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;
+export const DEFAULT_ZERO_EX_CONTAINER_SELECTOR = '#zeroExInstantContainer';
diff --git a/packages/instant/src/containers/latest_error.tsx b/packages/instant/src/containers/latest_error.tsx
index 413cf16ad..b75ec00aa 100644
--- a/packages/instant/src/containers/latest_error.tsx
+++ b/packages/instant/src/containers/latest_error.tsx
@@ -4,11 +4,11 @@ import { connect } from 'react-redux';
import { SlidingError } from '../components/sliding_error';
import { State } from '../redux/reducer';
-import { DisplayStatus } from '../types';
+import { Asset, DisplayStatus } from '../types';
import { errorUtil } from '../util/error';
export interface LatestErrorComponentProps {
- assetData?: string;
+ asset?: Asset;
latestError?: any;
slidingDirection: 'down' | 'up';
}
@@ -17,18 +17,18 @@ export const LatestErrorComponent: React.StatelessComponent<LatestErrorComponent
if (!props.latestError) {
return <div />;
}
- const { icon, message } = errorUtil.errorDescription(props.latestError, props.assetData);
+ const { icon, message } = errorUtil.errorDescription(props.latestError, props.asset);
return <SlidingError direction={props.slidingDirection} icon={icon} message={message} />;
};
interface ConnectedState {
- assetData?: string;
+ asset?: Asset;
latestError?: any;
slidingDirection: 'down' | 'up';
}
export interface LatestErrorProps {}
const mapStateToProps = (state: State, _ownProps: LatestErrorProps): ConnectedState => ({
- assetData: state.selectedAssetData,
+ asset: state.selectedAsset,
latestError: state.latestError,
slidingDirection: state.latestErrorDisplay === DisplayStatus.Present ? 'up' : 'down',
});
diff --git a/packages/instant/src/containers/selected_asset_amount_input.ts b/packages/instant/src/containers/selected_asset_amount_input.ts
index e55c8b991..0d847cf02 100644
--- a/packages/instant/src/containers/selected_asset_amount_input.ts
+++ b/packages/instant/src/containers/selected_asset_amount_input.ts
@@ -1,4 +1,5 @@
-import { BuyQuote } from '@0x/asset-buyer';
+import { AssetBuyer, BuyQuote } from '@0x/asset-buyer';
+import { AssetProxyId } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import * as _ from 'lodash';
@@ -6,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 { errorUtil } from '../util/error';
import { AssetAmountInput } from '../components/asset_amount_input';
@@ -22,33 +21,52 @@ 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.metaData.assetProxyId !== AssetProxyId.ERC20) {
+ return {
+ value: state.selectedAssetAmount,
+ };
+ }
+ return {
+ assetBuyer: state.assetBuyer,
+ value: state.selectedAssetAmount,
+ asset: selectedAsset as ERC20Asset,
+ };
+};
const updateBuyQuoteAsync = async (
+ assetBuyer: AssetBuyer,
dispatch: Dispatch<Action>,
- assetData: string,
+ asset: ERC20Asset,
assetAmount: BigNumber,
): Promise<void> => {
// get a new buy quote.
- const baseUnitValue = Web3Wrapper.toBaseUnitAmount(assetAmount, zrxDecimals);
+ const baseUnitValue = Web3Wrapper.toBaseUnitAmount(assetAmount, asset.metaData.decimals);
// mark quote as pending
dispatch(actions.setQuoteRequestStatePending());
let newBuyQuote: BuyQuote | undefined;
try {
- newBuyQuote = await assetBuyer.getBuyQuoteAsync(assetData, baseUnitValue);
+ newBuyQuote = await assetBuyer.getBuyQuoteAsync(asset.assetData, baseUnitValue);
} catch (error) {
dispatch(actions.setQuoteRequestStateFailure());
errorUtil.errorFlasher.flashNewError(dispatch, error);
@@ -66,7 +84,7 @@ const mapDispatchToProps = (
dispatch: Dispatch<Action>,
_ownProps: SelectedAssetAmountInputProps,
): ConnectedDispatch => ({
- onChange: (value, assetData) => {
+ updateBuyQuote: (assetBuyer, value, asset) => {
// Update the input
dispatch(actions.updateSelectedAssetAmount(value));
// invalidate the last buy quote.
@@ -74,16 +92,32 @@ const mapDispatchToProps = (
// reset our buy state
dispatch(actions.updateBuyOrderState(AsyncProcessState.NONE));
- if (!_.isUndefined(value) && !_.isUndefined(assetData)) {
+ if (!_.isUndefined(value) && !_.isUndefined(asset) && !_.isUndefined(assetBuyer)) {
// even if it's debounced, give them the illusion it's loading
dispatch(actions.setQuoteRequestStatePending());
// tslint:disable-next-line:no-floating-promises
- debouncedUpdateBuyQuoteAsync(dispatch, assetData, value);
+ debouncedUpdateBuyQuoteAsync(assetBuyer, dispatch, 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<SelectedAssetAmountInputProps> = 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 4d3315b1a..8189a5377 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 '@0x/asset-buyer';
+import { AssetBuyer, BuyQuote } from '@0x/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.buyOrderState),
buyQuote: state.latestBuyQuote,
});
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<SelectedAssetThemeProviderProps> = connect(
+ mapStateToProps,
+)(ThemeProvider);
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..e8ccbf011
--- /dev/null
+++ b/packages/instant/src/data/asset_data_network_mapping.ts
@@ -0,0 +1,15 @@
+import * as _ from 'lodash';
+
+import { Network } from '../types';
+
+interface AssetDataByNetwork {
+ [Network.Kovan]?: string;
+ [Network.Mainnet]?: string;
+}
+
+export const assetDataNetworkMapping: AssetDataByNetwork[] = [
+ {
+ [Network.Mainnet]: '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498',
+ [Network.Kovan]: '0xf47261b00000000000000000000000002002d3812f58e35f0ea1ffbf80a75a38c32175fa',
+ },
+];
diff --git a/packages/instant/src/data/asset_meta_data.ts b/packages/instant/src/data/asset_meta_data_map.ts
index ae0d32e4b..3a820a0c4 100644
--- a/packages/instant/src/data/asset_meta_data.ts
+++ b/packages/instant/src/data/asset_meta_data_map.ts
@@ -1,12 +1,11 @@
import { AssetProxyId, ObjectMap } from '@0x/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<AssetMetaData> = {
- [zrxAssetData]: {
+export const assetMetaDataMap: ObjectMap<AssetMetaData> = {
+ '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498': {
assetProxyId: AssetProxyId.ERC20,
decimals: 18,
primaryColor: 'rgb(54, 50, 60)',
diff --git a/packages/instant/src/index.umd.ts b/packages/instant/src/index.umd.ts
index d4eca177d..f648b37f2 100644
--- a/packages/instant/src/index.umd.ts
+++ b/packages/instant/src/index.umd.ts
@@ -1,10 +1,9 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
-import { ZeroExInstant } from './index';
+import { DEFAULT_ZERO_EX_CONTAINER_SELECTOR } from './constants';
+import { ZeroExInstant, ZeroExInstantProps } from './index';
-export interface ZeroExInstantOptions {}
-
-export const render = (props: ZeroExInstantOptions, selector: string = '#zeroExInstantContainer') => {
+export const render = (props: ZeroExInstantProps, selector: string = DEFAULT_ZERO_EX_CONTAINER_SELECTOR) => {
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 e52a79e76..2c430ff83 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',
SET_QUOTE_REQUEST_STATE_PENDING = 'SET_QUOTE_REQUEST_STATE_PENDING',
SET_QUOTE_REQUEST_STATE_FAILURE = 'SET_QUOTE_REQUEST_STATE_FAILURE',
SET_ERROR = 'SET_ERROR',
@@ -38,6 +39,7 @@ export const actions = {
updateBuyOrderState: (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),
setQuoteRequestStatePending: () => createAction(ActionTypes.SET_QUOTE_REQUEST_STATE_PENDING),
setQuoteRequestStateFailure: () => createAction(ActionTypes.SET_QUOTE_REQUEST_STATE_FAILURE),
setError: (error?: any) => createAction(ActionTypes.SET_ERROR, error),
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 2d50dd4b9..1538621a5 100644
--- a/packages/instant/src/redux/reducer.ts
+++ b/packages/instant/src/redux/reducer.ts
@@ -1,14 +1,19 @@
-import { BuyQuote } from '@0x/asset-buyer';
+import { AssetBuyer, BuyQuote } from '@0x/asset-buyer';
+import { ObjectMap } from '@0x/types';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
-import { zrxAssetData } from '../constants';
-import { AsyncProcessState, DisplayStatus } from '../types';
+import { assetMetaDataMap } from '../data/asset_meta_data_map';
+import { Asset, AssetMetaData, AsyncProcessState, DisplayStatus, Network } from '../types';
+import { assetUtils } from '../util/asset';
import { Action, ActionTypes } from './actions';
export interface State {
- selectedAssetData?: string;
+ network: Network;
+ assetBuyer?: AssetBuyer;
+ assetMetaDataMap: ObjectMap<AssetMetaData>;
+ selectedAsset?: Asset;
selectedAssetAmount?: BigNumber;
buyOrderState: AsyncProcessState;
ethUsdPrice?: BigNumber;
@@ -19,9 +24,9 @@ export interface State {
}
export const INITIAL_STATE: State = {
- // TODO: Remove hardcoded zrxAssetData
- selectedAssetData: zrxAssetData,
+ network: Network.Mainnet,
selectedAssetAmount: undefined,
+ assetMetaDataMap,
buyOrderState: AsyncProcessState.NONE,
ethUsdPrice: undefined,
latestBuyQuote: undefined,
@@ -82,6 +87,20 @@ export const reducer = (state: State = INITIAL_STATE, action: Action): State =>
latestError: undefined,
latestErrorDisplay: DisplayStatus.Hidden,
};
+ case ActionTypes.UPDATE_SELECTED_ASSET:
+ const newSelectedAssetData = action.data;
+ let newSelectedAsset: Asset | undefined;
+ if (!_.isUndefined(newSelectedAssetData)) {
+ newSelectedAsset = assetUtils.createAssetFromAssetData(
+ newSelectedAssetData,
+ state.assetMetaDataMap,
+ state.network,
+ );
+ }
+ return {
+ ...state,
+ selectedAsset: newSelectedAsset,
+ };
default:
return state;
}
diff --git a/packages/instant/src/redux/store.ts b/packages/instant/src/redux/store.ts
index b9ce9c0c1..01deb8690 100644
--- a/packages/instant/src/redux/store.ts
+++ b/packages/instant/src/redux/store.ts
@@ -4,4 +4,10 @@ import { devToolsEnhancer } from 'redux-devtools-extension/developmentOnly';
import { reducer, State } from './reducer';
-export const store: ReduxStore<State> = createStore(reducer, devToolsEnhancer({}));
+export type Store = ReduxStore<State>;
+
+export const store = {
+ create: (state: State): Store => {
+ return createStore(reducer, state, devToolsEnhancer({}));
+ },
+};
diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts
index 013ada27b..c340623ad 100644
--- a/packages/instant/src/types.ts
+++ b/packages/instant/src/types.ts
@@ -26,12 +26,32 @@ export interface ERC20AssetMetaData {
export interface ERC721AssetMetaData {
assetProxyId: AssetProxyId.ERC721;
name: string;
+ representationUrl?: string;
primaryColor?: string;
}
export type AssetMetaData = ERC20AssetMetaData | ERC721AssetMetaData;
+export interface ERC20Asset {
+ assetData: string;
+ metaData: ERC20AssetMetaData;
+}
+
+export interface ERC721Asset {
+ assetData: string;
+ metaData: ERC721AssetMetaData;
+}
+
+export interface Asset {
+ 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..4e3b2b946
--- /dev/null
+++ b/packages/instant/src/util/asset.ts
@@ -0,0 +1,53 @@
+import { AssetProxyId, ObjectMap } from '@0x/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<AssetMetaData>,
+ network: Network,
+ ): Asset => {
+ return {
+ assetData,
+ metaData: assetUtils.getMetaDataOrThrow(assetData, assetMetaDataMap, network),
+ };
+ },
+ getMetaDataOrThrow: (assetData: string, metaDataMap: ObjectMap<AssetMetaData>, network: Network): AssetMetaData => {
+ let mainnetAssetData: string | undefined = assetData;
+ if (network !== Network.Mainnet) {
+ mainnetAssetData = assetUtils.getAssociatedAssetDataIfExists(assetData, network);
+ }
+ if (_.isUndefined(mainnetAssetData)) {
+ throw new Error(ZeroExInstantError.AssetMetaDataNotAvailable);
+ }
+ const metaData = metaDataMap[mainnetAssetData];
+ if (_.isUndefined(metaData)) {
+ throw new Error(ZeroExInstantError.AssetMetaDataNotAvailable);
+ }
+ return metaData;
+ },
+ bestNameForAsset: (asset?: Asset, defaultName: string = '???'): string => {
+ if (_.isUndefined(asset)) {
+ return defaultName;
+ }
+ const metaData = asset.metaData;
+ switch (metaData.assetProxyId) {
+ case AssetProxyId.ERC20:
+ return metaData.symbol.toUpperCase();
+ case AssetProxyId.ERC721:
+ return metaData.name;
+ default:
+ 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];
+ },
+};
diff --git a/packages/instant/src/util/asset_buyer.ts b/packages/instant/src/util/asset_buyer.ts
deleted file mode 100644
index 6855fbcab..000000000
--- a/packages/instant/src/util/asset_buyer.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { AssetBuyer } from '@0x/asset-buyer';
-
-import { sraApiUrl } from '../constants';
-
-import { getProvider } from './provider';
-
-const provider = getProvider();
-
-export const assetBuyer = AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(provider, sraApiUrl);
diff --git a/packages/instant/src/util/asset_data.ts b/packages/instant/src/util/asset_data.ts
deleted file mode 100644
index fea2e2b19..000000000
--- a/packages/instant/src/util/asset_data.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import * as _ from 'lodash';
-
-import { AssetProxyId } from '@0x/types';
-
-import { assetMetaData } from '../data/asset_meta_data';
-
-export const assetDataUtil = {
- bestNameForAsset: (assetData: string | undefined, defaultString: string) => {
- if (_.isUndefined(assetData)) {
- return defaultString;
- }
- const metaData = assetMetaData[assetData];
- if (_.isUndefined(metaData)) {
- return defaultString;
- }
- if (metaData.assetProxyId === AssetProxyId.ERC20) {
- return metaData.symbol.toUpperCase();
- }
- return defaultString;
- },
-};
diff --git a/packages/instant/src/util/error.ts b/packages/instant/src/util/error.ts
index c9b13ef83..40fd24c7e 100644
--- a/packages/instant/src/util/error.ts
+++ b/packages/instant/src/util/error.ts
@@ -2,7 +2,9 @@ import { AssetBuyerError } from '@0x/asset-buyer';
import { Dispatch } from 'redux';
import { Action, actions } from '../redux/actions';
-import { assetDataUtil } from '../util/asset_data';
+import { Asset } from '../types';
+
+import { assetUtils } from './asset';
class ErrorFlasher {
private _timeoutId?: number;
@@ -27,12 +29,12 @@ class ErrorFlasher {
}
}
-const humanReadableMessageForError = (error: Error, assetData?: string): string | undefined => {
+const humanReadableMessageForError = (error: Error, asset?: Asset): string | undefined => {
const hasInsufficientLiquidity =
error.message === AssetBuyerError.InsufficientAssetLiquidity ||
error.message === AssetBuyerError.InsufficientZrxLiquidity;
if (hasInsufficientLiquidity) {
- const assetName = assetDataUtil.bestNameForAsset(assetData, 'of this asset');
+ const assetName = assetUtils.bestNameForAsset(asset, 'of this asset');
return `Not enough ${assetName} available`;
}
@@ -40,7 +42,7 @@ const humanReadableMessageForError = (error: Error, assetData?: string): string
error.message === AssetBuyerError.StandardRelayerApiError ||
error.message.startsWith(AssetBuyerError.AssetUnavailable)
) {
- const assetName = assetDataUtil.bestNameForAsset(assetData, 'This asset');
+ const assetName = assetUtils.bestNameForAsset(asset, 'This asset');
return `${assetName} is currently unavailable`;
}
@@ -49,10 +51,10 @@ const humanReadableMessageForError = (error: Error, assetData?: string): string
export const errorUtil = {
errorFlasher: new ErrorFlasher(),
- errorDescription: (error?: any, assetData?: string): { icon: string; message: string } => {
+ errorDescription: (error?: any, asset?: Asset): { icon: string; message: string } => {
let bestMessage: string | undefined;
if (error instanceof Error) {
- bestMessage = humanReadableMessageForError(error, assetData);
+ bestMessage = humanReadableMessageForError(error, asset);
}
return {
icon: '😢',
diff --git a/packages/instant/test/util/asset.test.ts b/packages/instant/test/util/asset.test.ts
new file mode 100644
index 000000000..c7db7eba7
--- /dev/null
+++ b/packages/instant/test/util/asset.test.ts
@@ -0,0 +1,47 @@
+import { AssetProxyId, ObjectMap } from '@0x/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: ZRX_META_DATA,
+};
+const META_DATA_MAP: ObjectMap<AssetMetaData> = {
+ [ZRX_ASSET_DATA]: ZRX_META_DATA,
+};
+
+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');
+ });
+ });
+ 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);
+ });
+ });
+});
diff --git a/packages/instant/test/util/asset_data.test.ts b/packages/instant/test/util/asset_data.test.ts
deleted file mode 100644
index cf247142a..000000000
--- a/packages/instant/test/util/asset_data.test.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { assetDataUtil } from '../../src/util/asset_data';
-
-const ZRX_ASSET_DATA = '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498';
-
-describe('assetDataUtil', () => {
- describe('bestNameForAsset', () => {
- it('should return default string if assetData is undefined', () => {
- expect(assetDataUtil.bestNameForAsset(undefined, 'xyz')).toEqual('xyz');
- });
- it('should return default string if assetData isnt found', () => {
- expect(assetDataUtil.bestNameForAsset('fake', 'mah default')).toEqual('mah default');
- });
- it('should return ZRX for ZRX assetData', () => {
- expect(assetDataUtil.bestNameForAsset(ZRX_ASSET_DATA, 'mah default')).toEqual('ZRX');
- });
- });
-});
diff --git a/packages/instant/test/util/error.test.ts b/packages/instant/test/util/error.test.ts
index 78b742f06..90e9c5fb4 100644
--- a/packages/instant/test/util/error.test.ts
+++ b/packages/instant/test/util/error.test.ts
@@ -1,14 +1,24 @@
import { AssetBuyerError } from '@0x/asset-buyer';
+import { AssetProxyId } from '@0x/types';
+import { Asset } from '../../src/types';
import { errorUtil } from '../../src/util/error';
const ZRX_ASSET_DATA = '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498';
+const ZRX_ASSET: Asset = {
+ assetData: ZRX_ASSET_DATA,
+ metaData: {
+ assetProxyId: AssetProxyId.ERC20,
+ symbol: 'zrx',
+ decimals: 18,
+ },
+};
describe('errorUtil', () => {
describe('errorFlasher', () => {
it('should return error and asset name for InsufficientAssetLiquidity', () => {
const insufficientAssetError = new Error(AssetBuyerError.InsufficientAssetLiquidity);
- expect(errorUtil.errorDescription(insufficientAssetError, ZRX_ASSET_DATA).message).toEqual(
+ expect(errorUtil.errorDescription(insufficientAssetError, ZRX_ASSET).message).toEqual(
'Not enough ZRX available',
);
});
@@ -20,27 +30,25 @@ describe('errorUtil', () => {
});
it('should return asset name for InsufficientAssetLiquidity', () => {
const insufficientZrxError = new Error(AssetBuyerError.InsufficientZrxLiquidity);
- expect(errorUtil.errorDescription(insufficientZrxError, ZRX_ASSET_DATA).message).toEqual(
+ expect(errorUtil.errorDescription(insufficientZrxError, ZRX_ASSET).message).toEqual(
'Not enough ZRX available',
);
});
it('should return unavailable error and asset name for StandardRelayerApiError', () => {
const standardRelayerError = new Error(AssetBuyerError.StandardRelayerApiError);
- expect(errorUtil.errorDescription(standardRelayerError, ZRX_ASSET_DATA).message).toEqual(
+ expect(errorUtil.errorDescription(standardRelayerError, ZRX_ASSET).message).toEqual(
'ZRX is currently unavailable',
);
});
it('should return error for AssetUnavailable error', () => {
- const assetUnavailableError = new Error(
- `${AssetBuyerError.AssetUnavailable}: For assetData ${ZRX_ASSET_DATA}`,
- );
- expect(errorUtil.errorDescription(assetUnavailableError, ZRX_ASSET_DATA).message).toEqual(
+ const assetUnavailableError = new Error(`${AssetBuyerError.AssetUnavailable}: For assetData ${ZRX_ASSET}`);
+ expect(errorUtil.errorDescription(assetUnavailableError, ZRX_ASSET).message).toEqual(
'ZRX is currently unavailable',
);
});
it('should return default for AssetUnavailable error', () => {
const assetUnavailableError = new Error(`${AssetBuyerError.AssetUnavailable}: For assetData xyz`);
- expect(errorUtil.errorDescription(assetUnavailableError, 'xyz').message).toEqual(
+ expect(errorUtil.errorDescription(assetUnavailableError, undefined).message).toEqual(
'This asset is currently unavailable',
);
});