aboutsummaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
authorBrandon Millman <brandon@0xproject.com>2018-10-30 01:58:11 +0800
committerGitHub <noreply@github.com>2018-10-30 01:58:11 +0800
commitfdf9e860dedf5dbe7840951f304a33ac2d7b1b51 (patch)
tree2acfc843dce83d5f8643980f9c74af25d12a18df /packages
parent4e4291eccdd6c837bbec70603aa6eb64d3aa8d85 (diff)
parent3f35239b27653da898218e53909982203fad6d17 (diff)
downloaddexon-sol-tools-fdf9e860dedf5dbe7840951f304a33ac2d7b1b51.tar
dexon-sol-tools-fdf9e860dedf5dbe7840951f304a33ac2d7b1b51.tar.gz
dexon-sol-tools-fdf9e860dedf5dbe7840951f304a33ac2d7b1b51.tar.bz2
dexon-sol-tools-fdf9e860dedf5dbe7840951f304a33ac2d7b1b51.tar.lz
dexon-sol-tools-fdf9e860dedf5dbe7840951f304a33ac2d7b1b51.tar.xz
dexon-sol-tools-fdf9e860dedf5dbe7840951f304a33ac2d7b1b51.tar.zst
dexon-sol-tools-fdf9e860dedf5dbe7840951f304a33ac2d7b1b51.zip
Merge pull request #1187 from 0xProject/feature/instant/fixed-orders-in-render-method
[instant] Add ability to toggle render settings through URL, flash error on incorrect network, provided liquidity
Diffstat (limited to 'packages')
-rw-r--r--packages/asset-buyer/CHANGELOG.json9
-rw-r--r--packages/asset-buyer/src/asset_buyer.ts5
-rw-r--r--packages/instant/package.json2
-rw-r--r--packages/instant/public/index.html43
-rw-r--r--packages/instant/src/components/buy_button.tsx8
-rw-r--r--packages/instant/src/components/buy_order_state_buttons.tsx2
-rw-r--r--packages/instant/src/components/sliding_error.tsx16
-rw-r--r--packages/instant/src/components/zero_ex_instant.tsx61
-rw-r--r--packages/instant/src/containers/latest_error.tsx14
-rw-r--r--packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts19
-rw-r--r--packages/instant/src/containers/selected_erc20_asset_amount_input.ts22
-rw-r--r--packages/instant/src/index.umd.ts13
-rw-r--r--packages/instant/src/redux/actions.ts4
-rw-r--r--packages/instant/src/redux/reducer.ts20
-rw-r--r--packages/instant/src/types.ts2
-rw-r--r--packages/instant/src/util/assert.ts44
-rw-r--r--packages/instant/src/util/asset.ts5
-rw-r--r--packages/instant/src/util/balance.ts4
-rw-r--r--packages/instant/src/util/big_number_input.ts19
-rw-r--r--packages/instant/src/util/error.ts71
-rw-r--r--packages/instant/src/util/error_flasher.ts26
-rw-r--r--packages/instant/test/util/error.test.ts56
22 files changed, 262 insertions, 203 deletions
diff --git a/packages/asset-buyer/CHANGELOG.json b/packages/asset-buyer/CHANGELOG.json
index 7ebcd8c2f..5d6604ea9 100644
--- a/packages/asset-buyer/CHANGELOG.json
+++ b/packages/asset-buyer/CHANGELOG.json
@@ -1,5 +1,14 @@
[
{
+ "version": "2.2.0",
+ "changes": [
+ {
+ "note": "`getAssetBuyerForProvidedOrders` factory function now takes 3 args instead of 4",
+ "pr": 1187
+ }
+ ]
+ },
+ {
"version": "2.1.0",
"changes": [
{
diff --git a/packages/asset-buyer/src/asset_buyer.ts b/packages/asset-buyer/src/asset_buyer.ts
index 74f3cb471..34e2d9639 100644
--- a/packages/asset-buyer/src/asset_buyer.ts
+++ b/packages/asset-buyer/src/asset_buyer.ts
@@ -52,16 +52,13 @@ export class AssetBuyer {
public static getAssetBuyerForProvidedOrders(
provider: Provider,
orders: SignedOrder[],
- feeOrders: SignedOrder[] = [],
options: Partial<AssetBuyerOpts> = {},
): AssetBuyer {
assert.isWeb3Provider('provider', provider);
assert.doesConformToSchema('orders', orders, schemas.signedOrdersSchema);
- assert.doesConformToSchema('feeOrders', feeOrders, schemas.signedOrdersSchema);
assert.areValidProvidedOrders('orders', orders);
- assert.areValidProvidedOrders('feeOrders', feeOrders);
assert.assert(orders.length !== 0, `Expected orders to contain at least one order`);
- const orderProvider = new BasicOrderProvider(_.concat(orders, feeOrders));
+ const orderProvider = new BasicOrderProvider(orders);
const assetBuyer = new AssetBuyer(provider, orderProvider, options);
return assetBuyer;
}
diff --git a/packages/instant/package.json b/packages/instant/package.json
index 0329c3078..81d2e4c7b 100644
--- a/packages/instant/package.json
+++ b/packages/instant/package.json
@@ -44,7 +44,9 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/packages/instant/README.md",
"dependencies": {
+ "@0x/assert": "^1.0.14",
"@0x/asset-buyer": "^2.1.0",
+ "@0x/json-schemas": "^2.0.0",
"@0x/order-utils": "^2.0.0",
"@0x/types": "^1.2.0",
"@0x/typescript-typings": "^3.0.3",
diff --git a/packages/instant/public/index.html b/packages/instant/public/index.html
index 14555fc64..9f1dfdb64 100644
--- a/packages/instant/public/index.html
+++ b/packages/instant/public/index.html
@@ -6,6 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>0x Instant Dev Environment</title>
<script type="text/javascript" src="/main.bundle.js" charset="utf-8"></script>
+ <script type="text/javascript" src="https://unpkg.com/jsuri@1.3.1/Uri.js" charset="utf-8"></script>
+ <script type="text/javascript" src="https://unpkg.com/bignumber.js@4.1.0/bignumber.js" charset="utf-8"></script>
<style>
#zeroExInstantContainer {
display: flex;
@@ -24,10 +26,47 @@
<body>
<div id="zeroExInstantContainer"></div>
<script>
- zeroExInstant.render({
+ const removeUndefined = (obj) => {
+ for (let k in obj) if (obj[k] === undefined) delete obj[k];
+ return obj;
+ }
+ BigNumber.config({
+ EXPONENTIAL_AT: 1000,
+ DECIMAL_PLACES: 78,
+ });
+ const providedOrder = {
+ senderAddress: '0x0000000000000000000000000000000000000000',
+ makerAddress: '0x14e2f1f157e7dd4057d02817436d628a37120fd1',
+ takerAddress: '0x0000000000000000000000000000000000000000',
+ makerFee: new BigNumber('0'),
+ takerFee: new BigNumber('0'),
+ makerAssetAmount: new BigNumber('100000000000000000000'),
+ takerAssetAmount: new BigNumber('10000000000000000'),
+ makerAssetData: '0xf47261b00000000000000000000000002002d3812f58e35f0ea1ffbf80a75a38c32175fa',
+ takerAssetData: '0xf47261b0000000000000000000000000d0a1e359811322d97991e03f863a0c30c2cf029c',
+ expirationTimeSeconds: new BigNumber('1591858800'),
+ feeRecipientAddress: '0x0000000000000000000000000000000000000000',
+ salt: new BigNumber(
+ '54983920541892966634674340965984367456810207583416050222519063020710969340046',
+ ),
+ signature:
+ '0x1b949656218421c845995457303569a656764afa2b979d41dcefff0009d57ce15001490268bc7caa4269894fd83b741465fc5a7a53eda6ece17eb91fb32655d83703',
+ exchangeAddress: '0x35dd2932454449b14cee11a94d3674a936d5d7b2',
+ };
+ const queryParams = new Uri(window.location.search);
+ const renderOptionsDefaults = {
liquiditySource: 'https://api.radarrelay.com/0x/v2/',
assetData: '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498',
- });
+ }
+ const liquiditySourceOverride = queryParams.getQueryParamValue('liquiditySource');
+ const renderOptionsOverrides = {
+ liquiditySource: liquiditySourceOverride === 'provided' ? [providedOrder] : liquiditySourceOverride,
+ assetData: queryParams.getQueryParamValue('assetData'),
+ networkId: +queryParams.getQueryParamValue('networkId') || undefined,
+ defaultAssetBuyAmount: +queryParams.getQueryParamValue('defaultAssetBuyAmount') || undefined,
+ }
+ const renderOptions = Object.assign({}, renderOptionsDefaults, removeUndefined(renderOptionsOverrides));
+ zeroExInstant.render(renderOptions);
</script>
</body>
diff --git a/packages/instant/src/components/buy_button.tsx b/packages/instant/src/components/buy_button.tsx
index bcd435250..129aedaf3 100644
--- a/packages/instant/src/components/buy_button.tsx
+++ b/packages/instant/src/components/buy_button.tsx
@@ -17,7 +17,7 @@ export interface BuyButtonProps {
assetBuyer?: AssetBuyer;
onValidationPending: (buyQuote: BuyQuote) => void;
onValidationFail: (buyQuote: BuyQuote, errorMessage: AssetBuyerError | ZeroExInstantError) => void;
- onSignatureDenied: (buyQuote: BuyQuote, preventedError: Error) => void;
+ onSignatureDenied: (buyQuote: BuyQuote) => void;
onBuyProcessing: (buyQuote: BuyQuote, txHash: string) => void;
onBuySuccess: (buyQuote: BuyQuote, txHash: string) => void;
onBuyFailure: (buyQuote: BuyQuote, txHash: string) => void;
@@ -49,8 +49,8 @@ export class BuyButton extends React.Component<BuyButtonProps> {
this.props.onValidationPending(buyQuote);
const takerAddress = await getBestAddress();
- const hasSufficentEth = await balanceUtil.hasSufficentEth(takerAddress, buyQuote, web3Wrapper);
- if (!hasSufficentEth) {
+ const hasSufficientEth = await balanceUtil.hasSufficientEth(takerAddress, buyQuote, web3Wrapper);
+ if (!hasSufficientEth) {
this.props.onValidationFail(buyQuote, ZeroExInstantError.InsufficientETH);
return;
}
@@ -61,7 +61,7 @@ export class BuyButton extends React.Component<BuyButtonProps> {
} catch (e) {
if (e instanceof Error) {
if (e.message === AssetBuyerError.SignatureRequestDenied) {
- this.props.onSignatureDenied(buyQuote, e);
+ this.props.onSignatureDenied(buyQuote);
return;
} else if (e.message === AssetBuyerError.TransactionValueTooLow) {
this.props.onValidationFail(buyQuote, AssetBuyerError.TransactionValueTooLow);
diff --git a/packages/instant/src/components/buy_order_state_buttons.tsx b/packages/instant/src/components/buy_order_state_buttons.tsx
index 7c06ff31b..d01e9ff57 100644
--- a/packages/instant/src/components/buy_order_state_buttons.tsx
+++ b/packages/instant/src/components/buy_order_state_buttons.tsx
@@ -19,7 +19,7 @@ export interface BuyOrderStateButtonProps {
onViewTransaction: () => void;
onValidationPending: (buyQuote: BuyQuote) => void;
onValidationFail: (buyQuote: BuyQuote, errorMessage: AssetBuyerError | ZeroExInstantError) => void;
- onSignatureDenied: (buyQuote: BuyQuote, error: Error) => void;
+ onSignatureDenied: (buyQuote: BuyQuote) => void;
onBuyProcessing: (buyQuote: BuyQuote, txHash: string) => void;
onBuySuccess: (buyQuote: BuyQuote, txHash: string) => void;
onBuyFailure: (buyQuote: BuyQuote, txHash: string) => void;
diff --git a/packages/instant/src/components/sliding_error.tsx b/packages/instant/src/components/sliding_error.tsx
index 3865a8797..cc9abb7dd 100644
--- a/packages/instant/src/components/sliding_error.tsx
+++ b/packages/instant/src/components/sliding_error.tsx
@@ -4,7 +4,7 @@ import { ColorOption } from '../style/theme';
import { SlideDownAnimation, SlideUpAnimation } from './animations/slide_animations';
-import { Container, Text } from './ui';
+import { Container, Flex, Text } from './ui';
export interface ErrorProps {
icon: string;
@@ -20,12 +20,14 @@ export const Error: React.StatelessComponent<ErrorProps> = props => (
borderRadius="6px"
marginBottom="10px"
>
- <Container marginRight="5px" display="inline" top="3px" position="relative">
- <Text fontSize="20px">{props.icon}</Text>
- </Container>
- <Text fontWeight="500" fontColor={ColorOption.darkOrange}>
- {props.message}
- </Text>
+ <Flex justify="flex-start">
+ <Container marginRight="5px" display="inline" top="3px" position="relative">
+ <Text fontSize="20px">{props.icon}</Text>
+ </Container>
+ <Text fontWeight="500" fontColor={ColorOption.darkOrange}>
+ {props.message}
+ </Text>
+ </Flex>
</Container>
);
diff --git a/packages/instant/src/components/zero_ex_instant.tsx b/packages/instant/src/components/zero_ex_instant.tsx
index ffa5a8250..19a2d6b9b 100644
--- a/packages/instant/src/components/zero_ex_instant.tsx
+++ b/packages/instant/src/components/zero_ex_instant.tsx
@@ -1,5 +1,6 @@
import { AssetBuyer } from '@0x/asset-buyer';
-import { ObjectMap } from '@0x/types';
+import { ObjectMap, SignedOrder } from '@0x/types';
+import * as _ from 'lodash';
import * as React from 'react';
import { Provider } from 'react-redux';
@@ -10,7 +11,10 @@ import { store, Store } from '../redux/store';
import { fonts } from '../style/fonts';
import { AssetMetaData, Network } from '../types';
import { assetUtils } from '../util/asset';
+import { BigNumberInput } from '../util/big_number_input';
+import { errorFlasher } from '../util/error_flasher';
import { getProvider } from '../util/provider';
+import { web3Wrapper } from '../util/web3_wrapper';
import { ZeroExInstantContainer } from './zero_ex_instant_container';
@@ -21,28 +25,34 @@ export type ZeroExInstantProps = ZeroExInstantRequiredProps & Partial<ZeroExInst
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;
+ liquiditySource: string | SignedOrder[];
}
export interface ZeroExInstantOptionalProps {
+ defaultAssetBuyAmount?: number;
additionalAssetMetaDataMap: ObjectMap<AssetMetaData>;
- network: Network;
+ networkId: 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;
+ const networkId = props.networkId || state.network;
// TODO: Provider needs to not be hard-coded to injected web3.
- const assetBuyer = AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(getProvider(), props.liquiditySource, {
- networkId: network,
- });
+ const provider = getProvider();
+ const assetBuyerOptions = {
+ networkId,
+ };
+ let assetBuyer;
+ if (_.isString(props.liquiditySource)) {
+ assetBuyer = AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(
+ provider,
+ props.liquiditySource,
+ assetBuyerOptions,
+ );
+ } else {
+ assetBuyer = AssetBuyer.getAssetBuyerForProvidedOrders(provider, props.liquiditySource, assetBuyerOptions);
+ }
const completeAssetMetaDataMap = {
...props.additionalAssetMetaDataMap,
...state.assetMetaDataMap,
@@ -50,17 +60,26 @@ export class ZeroExInstant extends React.Component<ZeroExInstantProps> {
const storeStateFromProps: State = {
...state,
assetBuyer,
- network,
- selectedAsset: assetUtils.createAssetFromAssetData(props.assetData, completeAssetMetaDataMap, network),
+ network: networkId,
+ selectedAsset: assetUtils.createAssetFromAssetData(props.assetData, completeAssetMetaDataMap, networkId),
+ selectedAssetAmount: _.isUndefined(props.defaultAssetBuyAmount)
+ ? state.selectedAssetAmount
+ : new BigNumberInput(props.defaultAssetBuyAmount),
assetMetaDataMap: completeAssetMetaDataMap,
};
return storeStateFromProps;
}
constructor(props: ZeroExInstantProps) {
super(props);
- this._store = store.create(ZeroExInstant._mergeInitialStateWithProps(this.props, INITIAL_STATE));
+ const initialAppState = ZeroExInstant._mergeInitialStateWithProps(this.props, INITIAL_STATE);
+ this._store = store.create(initialAppState);
+ }
+
+ public componentDidMount(): void {
// tslint:disable-next-line:no-floating-promises
asyncData.fetchAndDispatchToStore(this._store);
+ // tslint:disable-next-line:no-floating-promises
+ this._flashErrorIfWrongNetwork();
}
public render(): React.ReactNode {
@@ -72,4 +91,14 @@ export class ZeroExInstant extends React.Component<ZeroExInstantProps> {
</Provider>
);
}
+
+ private readonly _flashErrorIfWrongNetwork = async (): Promise<void> => {
+ const msToShowError = 30000; // 30 seconds
+ const network = this._store.getState().network;
+ const networkOfProvider = await web3Wrapper.getNetworkIdAsync();
+ if (network !== networkOfProvider) {
+ const errorMessage = `Wrong network detected. Try switching to ${Network[network]}.`;
+ errorFlasher.flashNewErrorMessage(this._store.dispatch, errorMessage, msToShowError);
+ }
+ };
}
diff --git a/packages/instant/src/containers/latest_error.tsx b/packages/instant/src/containers/latest_error.tsx
index b75ec00aa..45ca09673 100644
--- a/packages/instant/src/containers/latest_error.tsx
+++ b/packages/instant/src/containers/latest_error.tsx
@@ -5,32 +5,30 @@ import { connect } from 'react-redux';
import { SlidingError } from '../components/sliding_error';
import { State } from '../redux/reducer';
import { Asset, DisplayStatus } from '../types';
-import { errorUtil } from '../util/error';
export interface LatestErrorComponentProps {
asset?: Asset;
- latestError?: any;
+ latestErrorMessage?: string;
slidingDirection: 'down' | 'up';
}
export const LatestErrorComponent: React.StatelessComponent<LatestErrorComponentProps> = props => {
- if (!props.latestError) {
+ if (!props.latestErrorMessage) {
return <div />;
}
- const { icon, message } = errorUtil.errorDescription(props.latestError, props.asset);
- return <SlidingError direction={props.slidingDirection} icon={icon} message={message} />;
+ return <SlidingError direction={props.slidingDirection} icon="😢" message={props.latestErrorMessage} />;
};
interface ConnectedState {
asset?: Asset;
- latestError?: any;
+ latestErrorMessage?: string;
slidingDirection: 'down' | 'up';
}
export interface LatestErrorProps {}
const mapStateToProps = (state: State, _ownProps: LatestErrorProps): ConnectedState => ({
asset: state.selectedAsset,
- latestError: state.latestError,
- slidingDirection: state.latestErrorDisplay === DisplayStatus.Present ? 'up' : 'down',
+ latestErrorMessage: state.latestErrorMessage,
+ slidingDirection: state.latestErrorDisplayStatus === DisplayStatus.Present ? 'up' : 'down',
});
export const LatestError = connect(mapStateToProps)(LatestErrorComponent);
diff --git a/packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts b/packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts
index 241c0192c..500d6b88a 100644
--- a/packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts
+++ b/packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts
@@ -4,14 +4,13 @@ import * as React from 'react';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
+import { BuyOrderStateButtons } from '../components/buy_order_state_buttons';
import { Action, actions } from '../redux/actions';
import { State } from '../redux/reducer';
import { OrderProcessState, OrderState, ZeroExInstantError } from '../types';
+import { errorFlasher } from '../util/error_flasher';
import { etherscanUtil } from '../util/etherscan';
-import { BuyOrderStateButtons } from '../components/buy_order_state_buttons';
-import { errorUtil } from '../util/error';
-
interface ConnectedState {
buyQuote?: BuyQuote;
buyOrderProcessingState: OrderProcessState;
@@ -21,7 +20,7 @@ interface ConnectedState {
interface ConnectedDispatch {
onValidationPending: (buyQuote: BuyQuote) => void;
- onSignatureDenied: (buyQuote: BuyQuote, error: Error) => void;
+ onSignatureDenied: (buyQuote: BuyQuote) => void;
onBuyProcessing: (buyQuote: BuyQuote, txHash: string) => void;
onBuySuccess: (buyQuote: BuyQuote, txHash: string) => void;
onBuyFailure: (buyQuote: BuyQuote, txHash: string) => void;
@@ -68,13 +67,19 @@ const mapDispatchToProps = (
dispatch(actions.updateBuyOrderState({ processState: OrderProcessState.SUCCESS, txHash })),
onBuyFailure: (buyQuote: BuyQuote, txHash: string) =>
dispatch(actions.updateBuyOrderState({ processState: OrderProcessState.FAILURE, txHash })),
- onSignatureDenied: (buyQuote, error) => {
+ onSignatureDenied: () => {
dispatch(actions.resetAmount());
- errorUtil.errorFlasher.flashNewError(dispatch, error);
+ const errorMessage = 'You denied this transaction';
+ errorFlasher.flashNewErrorMessage(dispatch, errorMessage);
},
onValidationFail: (buyQuote, error) => {
dispatch(actions.updateBuyOrderState({ processState: OrderProcessState.NONE }));
- errorUtil.errorFlasher.flashNewError(dispatch, new Error(error));
+ if (error === ZeroExInstantError.InsufficientETH) {
+ const errorMessage = "You don't have enough ETH";
+ errorFlasher.flashNewErrorMessage(dispatch, errorMessage);
+ } else {
+ errorFlasher.flashNewErrorMessage(dispatch);
+ }
},
onRetry: () => {
dispatch(actions.resetAmount());
diff --git a/packages/instant/src/containers/selected_erc20_asset_amount_input.ts b/packages/instant/src/containers/selected_erc20_asset_amount_input.ts
index 41ed974a2..4767b15d4 100644
--- a/packages/instant/src/containers/selected_erc20_asset_amount_input.ts
+++ b/packages/instant/src/containers/selected_erc20_asset_amount_input.ts
@@ -1,4 +1,4 @@
-import { AssetBuyer, BuyQuote } from '@0x/asset-buyer';
+import { AssetBuyer, AssetBuyerError, BuyQuote } from '@0x/asset-buyer';
import { AssetProxyId } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
@@ -12,8 +12,9 @@ import { Action, actions } from '../redux/actions';
import { State } from '../redux/reducer';
import { ColorOption } from '../style/theme';
import { ERC20Asset, OrderProcessState } from '../types';
+import { assetUtils } from '../util/asset';
import { BigNumberInput } from '../util/big_number_input';
-import { errorUtil } from '../util/error';
+import { errorFlasher } from '../util/error_flasher';
export interface SelectedERC20AssetAmountInputProps {
fontColor?: ColorOption;
@@ -78,11 +79,24 @@ const updateBuyQuoteAsync = async (
newBuyQuote = await assetBuyer.getBuyQuoteAsync(asset.assetData, baseUnitValue);
} catch (error) {
dispatch(actions.setQuoteRequestStateFailure());
- errorUtil.errorFlasher.flashNewError(dispatch, error);
+ let errorMessage;
+ if (error.message === AssetBuyerError.InsufficientAssetLiquidity) {
+ const assetName = assetUtils.bestNameForAsset(asset, 'of this asset');
+ errorMessage = `Not enough ${assetName} available`;
+ } else if (error.message === AssetBuyerError.InsufficientZrxLiquidity) {
+ errorMessage = 'Not enough ZRX available';
+ } else if (
+ error.message === AssetBuyerError.StandardRelayerApiError ||
+ error.message.startsWith(AssetBuyerError.AssetUnavailable)
+ ) {
+ const assetName = assetUtils.bestNameForAsset(asset, 'This asset');
+ errorMessage = `${assetName} is currently unavailable`;
+ }
+ errorFlasher.flashNewErrorMessage(dispatch, errorMessage);
return;
}
// We have a successful new buy quote
- errorUtil.errorFlasher.clearError(dispatch);
+ errorFlasher.clearError(dispatch);
// invalidate the last buy quote.
dispatch(actions.updateLatestBuyQuote(newBuyQuote));
};
diff --git a/packages/instant/src/index.umd.ts b/packages/instant/src/index.umd.ts
index f648b37f2..dabd45cae 100644
--- a/packages/instant/src/index.umd.ts
+++ b/packages/instant/src/index.umd.ts
@@ -1,9 +1,22 @@
+import * as _ from 'lodash';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { DEFAULT_ZERO_EX_CONTAINER_SELECTOR } from './constants';
import { ZeroExInstant, ZeroExInstantProps } from './index';
+import { assert } from './util/assert';
export const render = (props: ZeroExInstantProps, selector: string = DEFAULT_ZERO_EX_CONTAINER_SELECTOR) => {
+ assert.isHexString('assetData', props.assetData);
+ assert.isValidLiquiditySource('liquiditySource', props.liquiditySource);
+ if (!_.isUndefined(props.additionalAssetMetaDataMap)) {
+ assert.isValidAssetMetaDataMap('additionalAssetMetaDataMap', props.additionalAssetMetaDataMap);
+ }
+ if (!_.isUndefined(props.defaultAssetBuyAmount)) {
+ assert.isNumber('defaultAssetBuyAmount', props.defaultAssetBuyAmount);
+ }
+ if (!_.isUndefined(props.networkId)) {
+ assert.isNumber('networkId', props.networkId);
+ }
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 46045024b..bfae68e2b 100644
--- a/packages/instant/src/redux/actions.ts
+++ b/packages/instant/src/redux/actions.ts
@@ -30,7 +30,7 @@ export enum ActionTypes {
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',
+ SET_ERROR_MESSAGE = 'SET_ERROR_MESSAGE',
HIDE_ERROR = 'HIDE_ERROR',
CLEAR_ERROR = 'CLEAR_ERROR',
RESET_AMOUNT = 'RESET_AMOUNT',
@@ -45,7 +45,7 @@ export const actions = {
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),
+ setErrorMessage: (errorMessage: string) => createAction(ActionTypes.SET_ERROR_MESSAGE, errorMessage),
hideError: () => createAction(ActionTypes.HIDE_ERROR),
clearError: () => createAction(ActionTypes.CLEAR_ERROR),
resetAmount: () => createAction(ActionTypes.RESET_AMOUNT),
diff --git a/packages/instant/src/redux/reducer.ts b/packages/instant/src/redux/reducer.ts
index 614ed21ac..dd9403052 100644
--- a/packages/instant/src/redux/reducer.ts
+++ b/packages/instant/src/redux/reducer.ts
@@ -29,8 +29,8 @@ export interface State {
ethUsdPrice?: BigNumber;
latestBuyQuote?: BuyQuote;
quoteRequestState: AsyncProcessState;
- latestError?: any;
- latestErrorDisplay: DisplayStatus;
+ latestErrorMessage?: string;
+ latestErrorDisplayStatus: DisplayStatus;
}
export const INITIAL_STATE: State = {
@@ -40,8 +40,8 @@ export const INITIAL_STATE: State = {
buyOrderState: { processState: OrderProcessState.NONE },
ethUsdPrice: undefined,
latestBuyQuote: undefined,
- latestError: undefined,
- latestErrorDisplay: DisplayStatus.Hidden,
+ latestErrorMessage: undefined,
+ latestErrorDisplayStatus: DisplayStatus.Hidden,
quoteRequestState: AsyncProcessState.NONE,
};
@@ -88,22 +88,22 @@ export const reducer = (state: State = INITIAL_STATE, action: Action): State =>
...state,
buyOrderState: action.data,
};
- case ActionTypes.SET_ERROR:
+ case ActionTypes.SET_ERROR_MESSAGE:
return {
...state,
- latestError: action.data,
- latestErrorDisplay: DisplayStatus.Present,
+ latestErrorMessage: action.data,
+ latestErrorDisplayStatus: DisplayStatus.Present,
};
case ActionTypes.HIDE_ERROR:
return {
...state,
- latestErrorDisplay: DisplayStatus.Hidden,
+ latestErrorDisplayStatus: DisplayStatus.Hidden,
};
case ActionTypes.CLEAR_ERROR:
return {
...state,
- latestError: undefined,
- latestErrorDisplay: DisplayStatus.Hidden,
+ latestErrorMessage: undefined,
+ latestErrorDisplayStatus: DisplayStatus.Hidden,
};
case ActionTypes.UPDATE_SELECTED_ASSET:
const newSelectedAssetData = action.data;
diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts
index c02b66990..336465e43 100644
--- a/packages/instant/src/types.ts
+++ b/packages/instant/src/types.ts
@@ -44,7 +44,7 @@ export interface ERC20AssetMetaData {
export interface ERC721AssetMetaData {
assetProxyId: AssetProxyId.ERC721;
name: string;
- representationUrl?: string;
+ imageUrl?: string;
primaryColor?: string;
}
diff --git a/packages/instant/src/util/assert.ts b/packages/instant/src/util/assert.ts
new file mode 100644
index 000000000..584d3d4b1
--- /dev/null
+++ b/packages/instant/src/util/assert.ts
@@ -0,0 +1,44 @@
+import { assert as sharedAssert } from '@0x/assert';
+import { schemas } from '@0x/json-schemas';
+import { assetDataUtils } from '@0x/order-utils';
+import { AssetProxyId, ObjectMap, SignedOrder } from '@0x/types';
+import * as _ from 'lodash';
+
+import { AssetMetaData } from '../types';
+
+export const assert = {
+ ...sharedAssert,
+ isValidLiquiditySource(variableName: string, liquiditySource: string | SignedOrder[]): void {
+ if (_.isString(liquiditySource)) {
+ sharedAssert.isUri(variableName, liquiditySource);
+ return;
+ }
+ sharedAssert.doesConformToSchema(variableName, liquiditySource, schemas.signedOrdersSchema);
+ },
+ isValidAssetMetaDataMap(variableName: string, metaDataMap: ObjectMap<AssetMetaData>): void {
+ _.forEach(metaDataMap, (metaData, assetData) => {
+ assert.isHexString(`key ${assetData} of ${variableName}`, assetData);
+ assert.isValidAssetMetaData(`${variableName}.${assetData}`, metaData);
+ const assetDataProxyId = assetDataUtils.decodeAssetProxyId(assetData);
+ assert.assert(
+ metaData.assetProxyId === assetDataProxyId,
+ `Expected meta data for assetData ${assetData} to have asset proxy id of ${assetDataProxyId}, but instead got ${
+ metaData.assetProxyId
+ }`,
+ );
+ });
+ },
+ isValidAssetMetaData(variableName: string, metaData: AssetMetaData): void {
+ assert.isHexString(`${variableName}.assetProxyId`, metaData.assetProxyId);
+ if (!_.isUndefined(metaData.primaryColor)) {
+ assert.isString(`${variableName}.primaryColor`, metaData.primaryColor);
+ }
+ if (metaData.assetProxyId === AssetProxyId.ERC20) {
+ assert.isNumber(`${variableName}.decimals`, metaData.decimals);
+ assert.isString(`${variableName}.symbol`, metaData.symbol);
+ } else if (metaData.assetProxyId === AssetProxyId.ERC721) {
+ assert.isString(`${variableName}.name`, metaData.name);
+ assert.isUri(`${variableName}.imageUrl`, metaData.imageUrl);
+ }
+ },
+};
diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts
index 2c5b6325d..630103c7b 100644
--- a/packages/instant/src/util/asset.ts
+++ b/packages/instant/src/util/asset.ts
@@ -18,7 +18,10 @@ export const assetUtils = {
getMetaDataOrThrow: (assetData: string, metaDataMap: ObjectMap<AssetMetaData>, network: Network): AssetMetaData => {
let mainnetAssetData: string | undefined = assetData;
if (network !== Network.Mainnet) {
- mainnetAssetData = assetUtils.getAssociatedAssetDataIfExists(assetData, network);
+ const mainnetAssetDataIfExists = assetUtils.getAssociatedAssetDataIfExists(assetData, network);
+ // Just so we don't fail in the case where we are on a non-mainnet network,
+ // but pass in a valid mainnet assetData.
+ mainnetAssetData = mainnetAssetDataIfExists || assetData;
}
if (_.isUndefined(mainnetAssetData)) {
throw new Error(ZeroExInstantError.AssetMetaDataNotAvailable);
diff --git a/packages/instant/src/util/balance.ts b/packages/instant/src/util/balance.ts
index 533656858..f2271495b 100644
--- a/packages/instant/src/util/balance.ts
+++ b/packages/instant/src/util/balance.ts
@@ -3,11 +3,11 @@ import { Web3Wrapper } from '@0x/web3-wrapper';
import * as _ from 'lodash';
export const balanceUtil = {
- hasSufficentEth: async (takerAddress: string | undefined, buyQuote: BuyQuote, web3Wrapper: Web3Wrapper) => {
+ hasSufficientEth: async (takerAddress: string | undefined, buyQuote: BuyQuote, web3Wrapper: Web3Wrapper) => {
if (_.isUndefined(takerAddress)) {
return false;
}
const balanceWei = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
- return balanceWei >= buyQuote.worstCaseQuoteInfo.totalEthAmount;
+ return balanceWei.gte(buyQuote.worstCaseQuoteInfo.totalEthAmount);
},
};
diff --git a/packages/instant/src/util/big_number_input.ts b/packages/instant/src/util/big_number_input.ts
index d2a9a8dc5..370d91a0a 100644
--- a/packages/instant/src/util/big_number_input.ts
+++ b/packages/instant/src/util/big_number_input.ts
@@ -10,14 +10,19 @@ import * as _ from 'lodash';
*/
export class BigNumberInput extends BigNumber {
private readonly _isEndingWithDecimal: boolean;
- constructor(bigNumberString: string) {
- const hasDecimalPeriod = _.endsWith(bigNumberString, '.');
- let internalString = bigNumberString;
- if (hasDecimalPeriod) {
- internalString = bigNumberString.slice(0, -1);
+ constructor(numberOrString: string | number) {
+ if (_.isString(numberOrString)) {
+ const hasDecimalPeriod = _.endsWith(numberOrString, '.');
+ let internalString = numberOrString;
+ if (hasDecimalPeriod) {
+ internalString = numberOrString.slice(0, -1);
+ }
+ super(internalString);
+ this._isEndingWithDecimal = hasDecimalPeriod;
+ } else {
+ super(numberOrString);
+ this._isEndingWithDecimal = false;
}
- super(internalString);
- this._isEndingWithDecimal = hasDecimalPeriod;
}
public toDisplayString(): string {
const internalString = super.toString();
diff --git a/packages/instant/src/util/error.ts b/packages/instant/src/util/error.ts
deleted file mode 100644
index 39c563c75..000000000
--- a/packages/instant/src/util/error.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import { AssetBuyerError } from '@0x/asset-buyer';
-import { Dispatch } from 'redux';
-
-import { Action, actions } from '../redux/actions';
-import { Asset, ZeroExInstantError } from '../types';
-
-import { assetUtils } from './asset';
-
-class ErrorFlasher {
- private _timeoutId?: number;
- public flashNewError(dispatch: Dispatch<Action>, error: any, delayMs: number = 7000): void {
- this._clearTimeout();
-
- // dispatch new message
- dispatch(actions.setError(error));
-
- this._timeoutId = window.setTimeout(() => {
- dispatch(actions.hideError());
- }, delayMs);
- }
- public clearError(dispatch: Dispatch<Action>): void {
- this._clearTimeout();
- dispatch(actions.hideError());
- }
- private _clearTimeout(): void {
- if (this._timeoutId) {
- window.clearTimeout(this._timeoutId);
- }
- }
-}
-
-const humanReadableMessageForError = (error: Error, asset?: Asset): string | undefined => {
- const hasInsufficientLiquidity =
- error.message === AssetBuyerError.InsufficientAssetLiquidity ||
- error.message === AssetBuyerError.InsufficientZrxLiquidity;
- if (hasInsufficientLiquidity) {
- const assetName = assetUtils.bestNameForAsset(asset, 'of this asset');
- return `Not enough ${assetName} available`;
- }
-
- if (
- error.message === AssetBuyerError.StandardRelayerApiError ||
- error.message.startsWith(AssetBuyerError.AssetUnavailable)
- ) {
- const assetName = assetUtils.bestNameForAsset(asset, 'This asset');
- return `${assetName} is currently unavailable`;
- }
-
- if (error.message === AssetBuyerError.SignatureRequestDenied) {
- return 'You denied this transaction';
- }
- if (error.message === ZeroExInstantError.InsufficientETH) {
- return "You don't have enough ETH";
- }
-
- return undefined;
-};
-
-export const errorUtil = {
- errorFlasher: new ErrorFlasher(),
- errorDescription: (error?: any, asset?: Asset): { icon: string; message: string } => {
- let bestMessage: string | undefined;
- if (error instanceof Error) {
- bestMessage = humanReadableMessageForError(error, asset);
- }
- return {
- icon: '😢',
- message: bestMessage || 'Something went wrong...',
- };
- },
-};
diff --git a/packages/instant/src/util/error_flasher.ts b/packages/instant/src/util/error_flasher.ts
new file mode 100644
index 000000000..068c12fe2
--- /dev/null
+++ b/packages/instant/src/util/error_flasher.ts
@@ -0,0 +1,26 @@
+import { Dispatch } from 'redux';
+
+import { Action, actions } from '../redux/actions';
+
+class ErrorFlasher {
+ private _timeoutId?: number;
+ public flashNewErrorMessage(dispatch: Dispatch<Action>, errorMessage?: string, delayMs: number = 7000): void {
+ this._clearTimeout();
+ // dispatch new message
+ dispatch(actions.setErrorMessage(errorMessage || 'Something went wrong...'));
+ this._timeoutId = window.setTimeout(() => {
+ dispatch(actions.hideError());
+ }, delayMs);
+ }
+ public clearError(dispatch: Dispatch<Action>): void {
+ this._clearTimeout();
+ dispatch(actions.hideError());
+ }
+ private _clearTimeout(): void {
+ if (this._timeoutId) {
+ window.clearTimeout(this._timeoutId);
+ }
+ }
+}
+
+export const errorFlasher = new ErrorFlasher();
diff --git a/packages/instant/test/util/error.test.ts b/packages/instant/test/util/error.test.ts
deleted file mode 100644
index 90e9c5fb4..000000000
--- a/packages/instant/test/util/error.test.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-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).message).toEqual(
- 'Not enough ZRX available',
- );
- });
- it('should return error default name for InsufficientAssetLiquidity', () => {
- const insufficientZrxError = new Error(AssetBuyerError.InsufficientZrxLiquidity);
- expect(errorUtil.errorDescription(insufficientZrxError).message).toEqual(
- 'Not enough of this asset available',
- );
- });
- it('should return asset name for InsufficientAssetLiquidity', () => {
- const insufficientZrxError = new Error(AssetBuyerError.InsufficientZrxLiquidity);
- 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).message).toEqual(
- 'ZRX is currently unavailable',
- );
- });
- it('should return error for AssetUnavailable error', () => {
- 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, undefined).message).toEqual(
- 'This asset is currently unavailable',
- );
- });
- });
-});