aboutsummaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/instant/public/index.html11
-rw-r--r--packages/instant/src/components/erc20_asset_amount_input.tsx46
-rw-r--r--packages/instant/src/components/erc20_token_selector.tsx107
-rw-r--r--packages/instant/src/components/instant_heading.tsx2
-rw-r--r--packages/instant/src/components/search_input.tsx26
-rw-r--r--packages/instant/src/components/sliding_panel.tsx24
-rw-r--r--packages/instant/src/components/ui/circle.tsx22
-rw-r--r--packages/instant/src/components/ui/container.tsx11
-rw-r--r--packages/instant/src/components/ui/icon.tsx23
-rw-r--r--packages/instant/src/components/ui/index.ts1
-rw-r--r--packages/instant/src/components/ui/overlay.tsx4
-rw-r--r--packages/instant/src/components/ui/text.tsx5
-rw-r--r--packages/instant/src/components/zero_ex_instant_container.tsx4
-rw-r--r--packages/instant/src/components/zero_ex_instant_provider.tsx32
-rw-r--r--packages/instant/src/containers/available_erc20_token_selector.ts45
-rw-r--r--packages/instant/src/containers/selected_erc20_asset_amount_input.ts20
-rw-r--r--packages/instant/src/data/asset_data_network_mapping.ts4
-rw-r--r--packages/instant/src/data/asset_meta_data_map.ts17
-rw-r--r--packages/instant/src/index.umd.ts9
-rw-r--r--packages/instant/src/redux/actions.ts6
-rw-r--r--packages/instant/src/redux/async_data.ts37
-rw-r--r--packages/instant/src/redux/reducer.ts19
-rw-r--r--packages/instant/src/style/theme.ts8
-rw-r--r--packages/instant/src/types.ts3
-rw-r--r--packages/instant/src/util/assert.ts11
-rw-r--r--packages/instant/src/util/asset.ts55
-rw-r--r--packages/instant/test/util/asset.test.ts1
27 files changed, 454 insertions, 99 deletions
diff --git a/packages/instant/public/index.html b/packages/instant/public/index.html
index 96a703224..92c8a6c21 100644
--- a/packages/instant/public/index.html
+++ b/packages/instant/public/index.html
@@ -72,11 +72,11 @@
];
const queryParams = new Uri(window.location.search);
const renderOptionsDefaults = {
- liquiditySource: 'https://api.radarrelay.com/0x/v2/',
- assetData: '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498',
+ orderSource: 'https://api.radarrelay.com/0x/v2/',
onClose: () => { console.log('0x Instant Closed') }
}
- const liquiditySourceOverride = queryParams.getQueryParamValue('liquiditySource');
+ const orderSourceOverride = queryParams.getQueryParamValue('orderSource');
+ const availableAssetDatasString = queryParams.getQueryParamValue('availableAssetDatas');
const feeRecipientOverride = queryParams.getQueryParamValue('feeRecipient');
const feePercentageOverride = +queryParams.getQueryParamValue('feePercentage');
let affiliateInfoOverride;
@@ -87,10 +87,11 @@
};
}
const renderOptionsOverrides = {
- liquiditySource: liquiditySourceOverride === 'provided' ? providedOrders : liquiditySourceOverride,
- assetData: queryParams.getQueryParamValue('assetData'),
+ orderSource: orderSourceOverride === 'provided' ? [providedOrder] : orderSourceOverride,
networkId: +queryParams.getQueryParamValue('networkId') || undefined,
defaultAssetBuyAmount: +queryParams.getQueryParamValue('defaultAssetBuyAmount') || undefined,
+ availableAssetDatas: availableAssetDatasString ? JSON.parse(availableAssetDatasString) : undefined,
+ defaultSelectedAssetData: queryParams.getQueryParamValue('defaultSelectedAssetData'),
affiliateInfo: affiliateInfoOverride,
}
const renderOptions = Object.assign({}, renderOptionsDefaults, removeUndefined(renderOptionsOverrides));
diff --git a/packages/instant/src/components/erc20_asset_amount_input.tsx b/packages/instant/src/components/erc20_asset_amount_input.tsx
index b1fec6405..5e07dcf2f 100644
--- a/packages/instant/src/components/erc20_asset_amount_input.tsx
+++ b/packages/instant/src/components/erc20_asset_amount_input.tsx
@@ -2,7 +2,7 @@ import * as _ from 'lodash';
import * as React from 'react';
import { ColorOption, transparentWhite } from '../style/theme';
-import { ERC20Asset } from '../types';
+import { ERC20Asset, SimpleHandler } from '../types';
import { assetUtils } from '../util/asset';
import { BigNumberInput } from '../util/big_number_input';
import { util } from '../util/util';
@@ -19,6 +19,7 @@ export interface ERC20AssetAmountInputProps {
startingFontSizePx: number;
fontColor?: ColorOption;
isDisabled: boolean;
+ numberOfAssetsAvailable?: number;
}
export interface ERC20AssetAmountInputState {
@@ -40,13 +41,14 @@ export class ERC20AssetAmountInput extends React.Component<ERC20AssetAmountInput
const { asset } = this.props;
return (
<Container whiteSpace="nowrap">
- {_.isUndefined(asset) ? this._renderBackupContent() : this._renderContentForAsset(asset)}
+ {_.isUndefined(asset) ? this._renderTokenSelectionContent() : this._renderContentForAsset(asset)}
</Container>
);
}
private readonly _renderContentForAsset = (asset: ERC20Asset): React.ReactNode => {
const { onChange, ...rest } = this.props;
const amountBorderBottom = this.props.isDisabled ? '' : `1px solid ${transparentWhite}`;
+ const onSymbolClick = this._generateSelectAssetClickHandler();
return (
<React.Fragment>
<Container borderBottom={amountBorderBottom} display="inline-block">
@@ -59,7 +61,6 @@ export class ERC20AssetAmountInput extends React.Component<ERC20AssetAmountInput
/>
</Container>
<Container
- cursor="pointer"
display="inline-block"
marginLeft="8px"
title={assetUtils.bestNameForAsset(asset, undefined)}
@@ -69,7 +70,7 @@ export class ERC20AssetAmountInput extends React.Component<ERC20AssetAmountInput
fontSize={`${this.state.currentFontSizePx}px`}
fontColor={ColorOption.white}
textTransform="uppercase"
- onClick={this._handleSymbolClick}
+ onClick={onSymbolClick}
>
{assetUtils.formattedSymbolForAsset(asset)}
</Text>
@@ -79,7 +80,14 @@ export class ERC20AssetAmountInput extends React.Component<ERC20AssetAmountInput
</React.Fragment>
);
};
- private readonly _renderBackupContent = (): React.ReactNode => {
+ private readonly _renderTokenSelectionContent = (): React.ReactNode => {
+ const { numberOfAssetsAvailable } = this.props;
+ let text = 'Select Token';
+ if (_.isUndefined(numberOfAssetsAvailable)) {
+ text = 'Loading...';
+ } else if (numberOfAssetsAvailable === 0) {
+ text = 'Assets Unavailable';
+ }
return (
<Flex>
<Text
@@ -87,18 +95,21 @@ export class ERC20AssetAmountInput extends React.Component<ERC20AssetAmountInput
fontColor={ColorOption.white}
opacity={0.7}
fontWeight="500"
- onClick={this._handleSymbolClick}
+ onClick={this._generateSelectAssetClickHandler()}
>
- Select Token
+ {text}
</Text>
{this._renderChevronIcon()}
</Flex>
);
};
private readonly _renderChevronIcon = (): React.ReactNode => {
+ if (!this._areMultipleAssetsAvailable()) {
+ return null;
+ }
return (
- <Container marginLeft="5px" onClick={this._handleSymbolClick}>
- <Icon icon="chevron" width={12} />
+ <Container marginLeft="5px">
+ <Icon icon="chevron" width={12} onClick={this._handleSelectAssetClick} />
</Container>
);
};
@@ -110,9 +121,22 @@ export class ERC20AssetAmountInput extends React.Component<ERC20AssetAmountInput
currentFontSizePx: fontSizePx,
});
};
- private readonly _handleSymbolClick = () => {
+ private readonly _generateSelectAssetClickHandler = (): SimpleHandler | undefined => {
+ // We don't want to allow opening the token selection panel if there are no assets.
+ // Since styles are inferred from the presence of a click handler, we want to return undefined
+ // instead of providing a noop.
+ if (!this._areMultipleAssetsAvailable() || _.isUndefined(this.props.onSelectAssetClick)) {
+ return undefined;
+ }
+ return this._handleSelectAssetClick;
+ };
+ private readonly _areMultipleAssetsAvailable = (): boolean => {
+ const { numberOfAssetsAvailable } = this.props;
+ return !_.isUndefined(numberOfAssetsAvailable) && numberOfAssetsAvailable > 1;
+ };
+ private readonly _handleSelectAssetClick = (): void => {
if (this.props.onSelectAssetClick) {
- this.props.onSelectAssetClick(this.props.asset);
+ this.props.onSelectAssetClick();
}
};
// For assets with symbols of different length,
diff --git a/packages/instant/src/components/erc20_token_selector.tsx b/packages/instant/src/components/erc20_token_selector.tsx
new file mode 100644
index 000000000..481778495
--- /dev/null
+++ b/packages/instant/src/components/erc20_token_selector.tsx
@@ -0,0 +1,107 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+
+import { ColorOption } from '../style/theme';
+import { ERC20Asset } from '../types';
+import { assetUtils } from '../util/asset';
+
+import { SearchInput } from './search_input';
+import { Circle, Container, Flex, Text } from './ui';
+
+export interface ERC20TokenSelectorProps {
+ tokens: ERC20Asset[];
+ onTokenSelect: (token: ERC20Asset) => void;
+}
+
+export interface ERC20TokenSelectorState {
+ searchQuery?: string;
+}
+
+export class ERC20TokenSelector extends React.Component<ERC20TokenSelectorProps> {
+ public state: ERC20TokenSelectorState = {
+ searchQuery: undefined,
+ };
+ public render(): React.ReactNode {
+ const { tokens, onTokenSelect } = this.props;
+ return (
+ <Container>
+ <SearchInput
+ placeholder="Search tokens..."
+ width="100%"
+ value={this.state.searchQuery}
+ onChange={this._handleSearchInputChange}
+ />
+ <Container overflow="scroll" height="275px" marginTop="10px">
+ {_.map(tokens, token => {
+ if (!this._isTokenQueryMatch(token)) {
+ return null;
+ }
+ return <TokenSelectorRow key={token.assetData} token={token} onClick={onTokenSelect} />;
+ })}
+ </Container>
+ </Container>
+ );
+ }
+ private readonly _handleSearchInputChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
+ const searchQuery = event.target.value;
+ this.setState({
+ searchQuery,
+ });
+ };
+ private readonly _isTokenQueryMatch = (token: ERC20Asset): boolean => {
+ const { searchQuery } = this.state;
+ if (_.isUndefined(searchQuery)) {
+ return true;
+ }
+ const stringToSearch = `${token.metaData.name} ${token.metaData.symbol}`;
+ return _.includes(stringToSearch.toLowerCase(), searchQuery.toLowerCase());
+ };
+}
+
+interface TokenSelectorRowProps {
+ token: ERC20Asset;
+ onClick: (token: ERC20Asset) => void;
+}
+
+class TokenSelectorRow extends React.Component<TokenSelectorRowProps> {
+ public render(): React.ReactNode {
+ const { token } = this.props;
+ const displaySymbol = assetUtils.bestNameForAsset(token);
+ return (
+ <Container
+ padding="12px 0px"
+ borderBottom="1px solid"
+ borderColor={ColorOption.feintGrey}
+ backgroundColor={ColorOption.white}
+ width="100%"
+ onClick={this._handleClick}
+ darkenOnHover={true}
+ cursor="pointer"
+ >
+ <Container marginLeft="5px">
+ <Flex justify="flex-start">
+ <Container marginRight="10px">
+ <Circle diameter={30} fillColor={token.metaData.primaryColor}>
+ <Flex height="100%">
+ <Text fontColor={ColorOption.white} fontSize="8px">
+ {displaySymbol}
+ </Text>
+ </Flex>
+ </Circle>
+ </Container>
+ <Text fontSize="14px" fontWeight={700} fontColor={ColorOption.black}>
+ {displaySymbol}
+ </Text>
+ <Container margin="0px 5px">
+ <Text fontSize="14px"> - </Text>
+ </Container>
+ <Text fontSize="14px">{token.metaData.name}</Text>
+ </Flex>
+ </Container>
+ </Container>
+ );
+ }
+ private readonly _handleClick = (): void => {
+ this.props.onClick(this.props.token);
+ };
+}
diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx
index 19c08db70..80d7a3ee2 100644
--- a/packages/instant/src/components/instant_heading.tsx
+++ b/packages/instant/src/components/instant_heading.tsx
@@ -22,7 +22,7 @@ export interface InstantHeadingProps {
const PLACEHOLDER_COLOR = ColorOption.white;
const ICON_WIDTH = 34;
const ICON_HEIGHT = 34;
-const ICON_COLOR = 'white';
+const ICON_COLOR = ColorOption.white;
export class InstantHeading extends React.Component<InstantHeadingProps, {}> {
public render(): React.ReactNode {
diff --git a/packages/instant/src/components/search_input.tsx b/packages/instant/src/components/search_input.tsx
new file mode 100644
index 000000000..f082eaa16
--- /dev/null
+++ b/packages/instant/src/components/search_input.tsx
@@ -0,0 +1,26 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+
+import { ColorOption } from '../style/theme';
+
+import { Container, Flex, Icon, Input, InputProps } from './ui';
+
+export interface SearchInputProps extends InputProps {
+ backgroundColor?: ColorOption;
+}
+
+export const SearchInput: React.StatelessComponent<SearchInputProps> = props => (
+ <Container backgroundColor={props.backgroundColor} borderRadius="3px" padding=".5em .3em">
+ <Flex justify="flex-start" align="flex-end">
+ <Icon width={14} height={14} icon="search" color={ColorOption.lightGrey} padding="0px 12px" />
+ <Input {...props} fontSize="14px" fontColor={props.fontColor} />
+ </Flex>
+ </Container>
+);
+
+SearchInput.displayName = 'SearchInput';
+
+SearchInput.defaultProps = {
+ backgroundColor: ColorOption.lightestGrey,
+ fontColor: ColorOption.grey,
+};
diff --git a/packages/instant/src/components/sliding_panel.tsx b/packages/instant/src/components/sliding_panel.tsx
index 9219ad1f1..ea1d6b9a1 100644
--- a/packages/instant/src/components/sliding_panel.tsx
+++ b/packages/instant/src/components/sliding_panel.tsx
@@ -5,18 +5,28 @@ import { zIndex } from '../style/z_index';
import { PositionAnimationSettings } from './animations/position_animation';
import { SlideAnimation, SlideAnimationState } from './animations/slide_animation';
-import { Button, Container, Text } from './ui';
+import { Container, Flex, Icon, Text } from './ui';
export interface PanelProps {
+ title?: string;
onClose?: () => void;
}
-export const Panel: React.StatelessComponent<PanelProps> = ({ children, onClose }) => (
- <Container backgroundColor={ColorOption.white} width="100%" height="100%" zIndex={zIndex.panel}>
- <Button onClick={onClose}>
- <Text fontColor={ColorOption.white}>Close </Text>
- </Button>
- {children}
+export const Panel: React.StatelessComponent<PanelProps> = ({ title, children, onClose }) => (
+ <Container backgroundColor={ColorOption.white} width="100%" height="100%" zIndex={zIndex.panel} padding="20px">
+ <Flex justify="space-between">
+ {title && (
+ <Container marginTop="3px">
+ <Text fontColor={ColorOption.darkGrey} fontSize="18px" fontWeight="600" lineHeight="22px">
+ {title}
+ </Text>
+ </Container>
+ )}
+ <Container position="relative" bottom="7px">
+ <Icon width={12} color={ColorOption.lightGrey} icon="closeX" onClick={onClose} />
+ </Container>
+ </Flex>
+ <Container marginTop="10px">{children}</Container>
</Container>
);
diff --git a/packages/instant/src/components/ui/circle.tsx b/packages/instant/src/components/ui/circle.tsx
new file mode 100644
index 000000000..eec2777d2
--- /dev/null
+++ b/packages/instant/src/components/ui/circle.tsx
@@ -0,0 +1,22 @@
+import { styled } from '../../style/theme';
+
+export interface CircleProps {
+ diameter: number;
+ fillColor?: string;
+}
+
+export const Circle =
+ styled.div <
+ CircleProps >
+ `
+ width: ${props => props.diameter}px;
+ height: ${props => props.diameter}px;
+ background-color: ${props => props.fillColor};
+ border-radius: 50%;
+`;
+
+Circle.displayName = 'Circle';
+
+Circle.defaultProps = {
+ fillColor: 'white',
+};
diff --git a/packages/instant/src/components/ui/container.tsx b/packages/instant/src/components/ui/container.tsx
index 7b8642761..a0a187e5f 100644
--- a/packages/instant/src/components/ui/container.tsx
+++ b/packages/instant/src/components/ui/container.tsx
@@ -1,3 +1,5 @@
+import { darken } from 'polished';
+
import { ColorOption, styled } from '../../style/theme';
import { cssRuleIfExists } from '../../style/util';
@@ -30,6 +32,7 @@ export interface ContainerProps {
opacity?: number;
cursor?: string;
overflow?: string;
+ darkenOnHover?: boolean;
}
export const Container =
@@ -64,6 +67,14 @@ export const Container =
${props => (props.hasBoxShadow ? `box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1)` : '')};
background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')};
border-color: ${props => (props.borderColor ? props.theme[props.borderColor] : 'none')};
+ &:hover {
+ ${props =>
+ props.darkenOnHover
+ ? `background-color: ${
+ props.backgroundColor ? darken(0.05, props.theme[props.backgroundColor]) : 'none'
+ }`
+ : ''};
+ }
`;
Container.defaultProps = {
diff --git a/packages/instant/src/components/ui/icon.tsx b/packages/instant/src/components/ui/icon.tsx
index f12059cff..94ea26900 100644
--- a/packages/instant/src/components/ui/icon.tsx
+++ b/packages/instant/src/components/ui/icon.tsx
@@ -1,7 +1,7 @@
import * as _ from 'lodash';
import * as React from 'react';
-import { styled } from '../../style/theme';
+import { ColorOption, styled, Theme, withTheme } from '../../style/theme';
type svgRule = 'evenodd' | 'nonzero' | 'inherit';
interface IconInfo {
@@ -20,6 +20,7 @@ interface IconInfoMapping {
failed: IconInfo;
success: IconInfo;
chevron: IconInfo;
+ search: IconInfo;
}
const ICONS: IconInfoMapping = {
closeX: {
@@ -52,19 +53,28 @@ const ICONS: IconInfoMapping = {
strokeLinecap: 'round',
strokeLinejoin: 'round',
},
+ search: {
+ viewBox: '0 0 14 14',
+ fillRule: 'evenodd',
+ clipRule: 'evenodd',
+ path:
+ 'M8.39404 5.19727C8.39404 6.96289 6.96265 8.39453 5.19702 8.39453C3.4314 8.39453 2 6.96289 2 5.19727C2 3.43164 3.4314 2 5.19702 2C6.96265 2 8.39404 3.43164 8.39404 5.19727ZM8.09668 9.51074C7.26855 10.0684 6.27075 10.3945 5.19702 10.3945C2.3269 10.3945 0 8.06738 0 5.19727C0 2.32715 2.3269 0 5.19702 0C8.06738 0 10.394 2.32715 10.394 5.19727C10.394 6.27051 10.0686 7.26855 9.51074 8.09668L13.6997 12.2861L12.2854 13.7002L8.09668 9.51074Z',
+ },
};
export interface IconProps {
className?: string;
width: number;
height?: number;
- color?: string;
+ color?: ColorOption;
icon: keyof IconInfoMapping;
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
padding?: string;
+ theme: Theme;
}
-const PlainIcon: React.SFC<IconProps> = props => {
+const PlainIcon: React.StatelessComponent<IconProps> = props => {
const iconInfo = ICONS[props.icon];
+ const colorValue = _.isUndefined(props.color) ? undefined : props.theme[props.color];
return (
<div onClick={props.onClick} className={props.className}>
<svg
@@ -76,7 +86,7 @@ const PlainIcon: React.SFC<IconProps> = props => {
>
<path
d={iconInfo.path}
- fill={props.color}
+ fill={colorValue}
fillRule={iconInfo.fillRule || 'nonzero'}
clipRule={iconInfo.clipRule || 'nonzero'}
stroke={iconInfo.stroke}
@@ -90,7 +100,7 @@ const PlainIcon: React.SFC<IconProps> = props => {
);
};
-export const Icon = styled(PlainIcon)`
+export const Icon = withTheme(styled(PlainIcon)`
cursor: ${props => (!_.isUndefined(props.onClick) ? 'pointer' : 'default')};
transition: opacity 0.5s ease;
padding: ${props => props.padding};
@@ -101,10 +111,9 @@ export const Icon = styled(PlainIcon)`
&:active {
opacity: 1;
}
-`;
+`);
Icon.defaultProps = {
- color: 'white',
padding: '0em 0em',
};
diff --git a/packages/instant/src/components/ui/index.ts b/packages/instant/src/components/ui/index.ts
index 0efabdb85..87f5c11a1 100644
--- a/packages/instant/src/components/ui/index.ts
+++ b/packages/instant/src/components/ui/index.ts
@@ -1,4 +1,5 @@
export { Text, TextProps, Title } from './text';
+export { Circle, CircleProps } from './circle';
export { Button, ButtonProps } from './button';
export { Flex, FlexProps } from './flex';
export { Container, ContainerProps } from './container';
diff --git a/packages/instant/src/components/ui/overlay.tsx b/packages/instant/src/components/ui/overlay.tsx
index c5258b031..f1706c874 100644
--- a/packages/instant/src/components/ui/overlay.tsx
+++ b/packages/instant/src/components/ui/overlay.tsx
@@ -1,7 +1,7 @@
import * as _ from 'lodash';
import * as React from 'react';
-import { overlayBlack, styled } from '../../style/theme';
+import { ColorOption, overlayBlack, styled } from '../../style/theme';
import { Container } from './container';
import { Flex } from './flex';
@@ -16,7 +16,7 @@ export interface OverlayProps {
const PlainOverlay: React.StatelessComponent<OverlayProps> = ({ children, className, onClose }) => (
<Flex height="100vh" className={className}>
<Container position="absolute" top="0px" right="0px">
- <Icon height={18} width={18} color="white" icon="closeX" onClick={onClose} padding="2em 2em" />
+ <Icon height={18} width={18} color={ColorOption.white} icon="closeX" onClick={onClose} padding="2em 2em" />
</Container>
<div>{children}</div>
</Flex>
diff --git a/packages/instant/src/components/ui/text.tsx b/packages/instant/src/components/ui/text.tsx
index fd72f6cc8..cba6e7b20 100644
--- a/packages/instant/src/components/ui/text.tsx
+++ b/packages/instant/src/components/ui/text.tsx
@@ -18,7 +18,6 @@ export interface TextProps {
fontWeight?: number | string;
textDecorationLine?: string;
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
- hoverColor?: string;
noWrap?: boolean;
display?: string;
}
@@ -46,9 +45,7 @@ export const Text =
${props => (props.textTransform ? `text-transform: ${props.textTransform}` : '')};
&:hover {
${props =>
- props.onClick
- ? `color: ${props.hoverColor || darken(darkenOnHoverAmount, props.theme[props.fontColor || 'white'])}`
- : ''};
+ props.onClick ? `color: ${darken(darkenOnHoverAmount, props.theme[props.fontColor || 'white'])}` : ''};
}
`;
diff --git a/packages/instant/src/components/zero_ex_instant_container.tsx b/packages/instant/src/components/zero_ex_instant_container.tsx
index f8e3935fb..c1bd17502 100644
--- a/packages/instant/src/components/zero_ex_instant_container.tsx
+++ b/packages/instant/src/components/zero_ex_instant_container.tsx
@@ -1,5 +1,6 @@
import * as React from 'react';
+import { AvailableERC20TokenSelector } from '../containers/available_erc20_token_selector';
import { LatestBuyQuoteOrderDetails } from '../containers/latest_buy_quote_order_details';
import { LatestError } from '../containers/latest_error';
import { SelectedAssetBuyOrderStateButtons } from '../containers/selected_asset_buy_order_state_buttons';
@@ -45,10 +46,11 @@ export class ZeroExInstantContainer extends React.Component<ZeroExInstantContain
</Container>
</Flex>
<SlidingPanel
+ title="Select Token"
animationState={this.state.tokenSelectionPanelAnimationState}
onClose={this._handlePanelClose}
>
- Select Your Token
+ <AvailableERC20TokenSelector onTokenSelect={this._handlePanelClose} />
</SlidingPanel>
</Container>
</Container>
diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx
index fce03a280..59a850bc5 100644
--- a/packages/instant/src/components/zero_ex_instant_provider.tsx
+++ b/packages/instant/src/components/zero_ex_instant_provider.tsx
@@ -23,13 +23,13 @@ export type ZeroExInstantProviderProps = ZeroExInstantProviderRequiredProps &
Partial<ZeroExInstantProviderOptionalProps>;
export interface ZeroExInstantProviderRequiredProps {
- // TODO: Change API when we allow the selection of different assetDatas
- assetData: string;
- liquiditySource: string | SignedOrder[];
+ orderSource: string | SignedOrder[];
}
export interface ZeroExInstantProviderOptionalProps {
+ availableAssetDatas: string[];
defaultAssetBuyAmount: number;
+ defaultSelectedAssetData: string;
additionalAssetMetaDataMap: ObjectMap<AssetMetaData>;
networkId: Network;
affiliateInfo: AffiliateInfo;
@@ -37,6 +37,7 @@ export interface ZeroExInstantProviderOptionalProps {
export class ZeroExInstantProvider extends React.Component<ZeroExInstantProviderProps> {
private readonly _store: Store;
+ // TODO(fragosti): Write tests for this beast once we inject a provider.
private static _mergeInitialStateWithProps(props: ZeroExInstantProviderProps, state: State = INITIAL_STATE): State {
const networkId = props.networkId || state.network;
// TODO: Provider needs to not be hard-coded to injected web3.
@@ -45,14 +46,14 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
networkId,
};
let assetBuyer;
- if (_.isString(props.liquiditySource)) {
+ if (_.isString(props.orderSource)) {
assetBuyer = AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(
provider,
- props.liquiditySource,
+ props.orderSource,
assetBuyerOptions,
);
} else {
- assetBuyer = AssetBuyer.getAssetBuyerForProvidedOrders(provider, props.liquiditySource, assetBuyerOptions);
+ assetBuyer = AssetBuyer.getAssetBuyerForProvidedOrders(provider, props.orderSource, assetBuyerOptions);
}
const completeAssetMetaDataMap = {
...props.additionalAssetMetaDataMap,
@@ -62,10 +63,19 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
...state,
assetBuyer,
network: networkId,
- selectedAsset: assetUtils.createAssetFromAssetData(props.assetData, completeAssetMetaDataMap, networkId),
+ selectedAsset: _.isUndefined(props.defaultSelectedAssetData)
+ ? undefined
+ : assetUtils.createAssetFromAssetDataOrThrow(
+ props.defaultSelectedAssetData,
+ completeAssetMetaDataMap,
+ networkId,
+ ),
selectedAssetAmount: _.isUndefined(props.defaultAssetBuyAmount)
? state.selectedAssetAmount
: new BigNumberInput(props.defaultAssetBuyAmount),
+ availableAssets: _.isUndefined(props.availableAssetDatas)
+ ? undefined
+ : assetUtils.createAssetsFromAssetDatas(props.availableAssetDatas, completeAssetMetaDataMap, networkId),
assetMetaDataMap: completeAssetMetaDataMap,
affiliateInfo: props.affiliateInfo,
};
@@ -78,8 +88,14 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
}
public componentDidMount(): void {
+ const state = this._store.getState();
// tslint:disable-next-line:no-floating-promises
- asyncData.fetchAndDispatchToStore(this._store);
+ asyncData.fetchEthPriceAndDispatchToStore(this._store);
+ // fetch available assets if none are specified
+ if (_.isUndefined(state.availableAssets)) {
+ // tslint:disable-next-line:no-floating-promises
+ asyncData.fetchAvailableAssetDatasAndDispatchToStore(this._store);
+ }
// warm up the gas price estimator cache just in case we can't
// grab the gas price estimate when submitting the transaction
diff --git a/packages/instant/src/containers/available_erc20_token_selector.ts b/packages/instant/src/containers/available_erc20_token_selector.ts
new file mode 100644
index 000000000..4d4218d22
--- /dev/null
+++ b/packages/instant/src/containers/available_erc20_token_selector.ts
@@ -0,0 +1,45 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
+
+import { State } from '../redux/reducer';
+import { ERC20Asset } from '../types';
+import { assetUtils } from '../util/asset';
+
+import { ERC20TokenSelector } from '../components/erc20_token_selector';
+import { Action, actions } from '../redux/actions';
+
+export interface AvailableERC20TokenSelectorProps {
+ onTokenSelect?: (token: ERC20Asset) => void;
+}
+
+interface ConnectedState {
+ tokens: ERC20Asset[];
+}
+
+interface ConnectedDispatch {
+ onTokenSelect: (token: ERC20Asset) => void;
+}
+
+const mapStateToProps = (state: State, _ownProps: AvailableERC20TokenSelectorProps): ConnectedState => ({
+ tokens: assetUtils.getERC20AssetsFromAssets(state.availableAssets || []),
+});
+
+const mapDispatchToProps = (
+ dispatch: Dispatch<Action>,
+ ownProps: AvailableERC20TokenSelectorProps,
+): ConnectedDispatch => ({
+ onTokenSelect: (token: ERC20Asset) => {
+ dispatch(actions.updateSelectedAsset(token));
+ dispatch(actions.resetAmount());
+ if (ownProps.onTokenSelect) {
+ ownProps.onTokenSelect(token);
+ }
+ },
+});
+
+export const AvailableERC20TokenSelector: React.ComponentClass<AvailableERC20TokenSelectorProps> = connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(ERC20TokenSelector);
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 fcbd0efe3..99999ee14 100644
--- a/packages/instant/src/containers/selected_erc20_asset_amount_input.ts
+++ b/packages/instant/src/containers/selected_erc20_asset_amount_input.ts
@@ -28,6 +28,7 @@ interface ConnectedState {
value?: BigNumberInput;
asset?: ERC20Asset;
isDisabled: boolean;
+ numberOfAssetsAvailable?: number;
affiliateInfo?: AffiliateInfo;
}
@@ -45,6 +46,7 @@ interface ConnectedProps {
asset?: ERC20Asset;
onChange: (value?: BigNumberInput, asset?: ERC20Asset) => void;
isDisabled: boolean;
+ numberOfAssetsAvailable?: number;
}
type FinalProps = ConnectedProps & SelectedERC20AssetAmountInputProps;
@@ -53,20 +55,17 @@ const mapStateToProps = (state: State, _ownProps: SelectedERC20AssetAmountInputP
const processState = state.buyOrderState.processState;
const isEnabled = processState === OrderProcessState.NONE || processState === OrderProcessState.FAILURE;
const isDisabled = !isEnabled;
-
- const selectedAsset = state.selectedAsset;
- if (_.isUndefined(selectedAsset) || selectedAsset.metaData.assetProxyId !== AssetProxyId.ERC20) {
- return {
- value: state.selectedAssetAmount,
- isDisabled,
- };
- }
-
+ const selectedAsset =
+ !_.isUndefined(state.selectedAsset) && state.selectedAsset.metaData.assetProxyId === AssetProxyId.ERC20
+ ? (state.selectedAsset as ERC20Asset)
+ : undefined;
+ const numberOfAssetsAvailable = _.isUndefined(state.availableAssets) ? undefined : state.availableAssets.length;
return {
assetBuyer: state.assetBuyer,
value: state.selectedAssetAmount,
- asset: selectedAsset as ERC20Asset,
+ asset: selectedAsset,
isDisabled,
+ numberOfAssetsAvailable,
affiliateInfo: state.affiliateInfo,
};
};
@@ -152,6 +151,7 @@ const mergeProps = (
connectedDispatch.updateBuyQuote(connectedState.assetBuyer, value, asset, connectedState.affiliateInfo);
},
isDisabled: connectedState.isDisabled,
+ numberOfAssetsAvailable: connectedState.numberOfAssetsAvailable,
};
};
diff --git a/packages/instant/src/data/asset_data_network_mapping.ts b/packages/instant/src/data/asset_data_network_mapping.ts
index a7bc6a967..43bd34697 100644
--- a/packages/instant/src/data/asset_data_network_mapping.ts
+++ b/packages/instant/src/data/asset_data_network_mapping.ts
@@ -20,7 +20,7 @@ export const assetDataNetworkMapping: AssetDataByNetwork[] = [
},
// OMG
{
- [Network.Mainnet]: '0xf47261b000000000000000000000000042d6622dece394b54999fbd73d108123806f6a18',
+ [Network.Mainnet]: '0xf47261b0000000000000000000000000d26114cd6ee289accf82350c8d8487fedb8a0c07',
[Network.Kovan]: '0xf47261b000000000000000000000000046096d8ec059dbaae2950b30e01634ff0dc652ec',
},
// MKR
@@ -59,6 +59,6 @@ export const assetDataNetworkMapping: AssetDataByNetwork[] = [
// REP
{
[Network.Kovan]: '0xf47261b00000000000000000000000008cb3971b8eb709c14616bd556ff6683019e90d9c',
- [Network.Mainnet]: '0xf47261b0000000000000000000000000e94327d07fc17907b4db788e5adf2ed424addff6',
+ [Network.Mainnet]: '0xf47261b00000000000000000000000001985365e9f78359a9b6ad760e32412f4a445e862',
},
];
diff --git a/packages/instant/src/data/asset_meta_data_map.ts b/packages/instant/src/data/asset_meta_data_map.ts
index d7cf2c0d8..8a0f29e21 100644
--- a/packages/instant/src/data/asset_meta_data_map.ts
+++ b/packages/instant/src/data/asset_meta_data_map.ts
@@ -10,65 +10,76 @@ export const assetMetaDataMap: ObjectMap<AssetMetaData> = {
decimals: 18,
primaryColor: 'rgb(54, 50, 60)',
symbol: 'zrx',
+ name: '0x',
},
'0xf47261b000000000000000000000000042d6622dece394b54999fbd73d108123806f6a18': {
assetProxyId: AssetProxyId.ERC20,
decimals: 18,
primaryColor: '#ec3e6c',
symbol: 'spank',
+ name: 'Spank',
},
- '0xf47261b0000000000000000000000000d26114cd6EE289AccF82350c8d8487fedB8A0C07': {
+ '0xf47261b0000000000000000000000000d26114cd6ee289accf82350c8d8487fedb8a0c07': {
assetProxyId: AssetProxyId.ERC20,
decimals: 18,
primaryColor: '#2e61ea',
symbol: 'omg',
+ name: 'OmiseGo',
},
'0xf47261b00000000000000000000000009f8f72aa9304c8b593d555f12ef6589cc3a579a2': {
assetProxyId: AssetProxyId.ERC20,
decimals: 18,
- primaryColor: 'white',
+ primaryColor: '#87e4ca',
symbol: 'mkr',
+ name: 'Maker',
},
'0xf47261b00000000000000000000000000d8775f648430679a709e98d2b0cb6250d2887ef': {
assetProxyId: AssetProxyId.ERC20,
decimals: 18,
primaryColor: '#9c326c',
symbol: 'bat',
+ name: 'Basic Attention Token',
},
'0xf47261b0000000000000000000000000744d70fdbe2ba4cf95131626614a1763df805b9e': {
assetProxyId: AssetProxyId.ERC20,
decimals: 18,
primaryColor: '#5663b0',
symbol: 'snt',
+ name: 'Status',
},
'0xf47261b00000000000000000000000000f5d2fb29fb7d3cfee444a200298f468908cc942': {
assetProxyId: AssetProxyId.ERC20,
decimals: 18,
primaryColor: '#f08839',
symbol: 'mana',
+ name: 'Decentraland',
},
'0xf47261b0000000000000000000000000a74476443119A942dE498590Fe1f2454d7D4aC0d': {
assetProxyId: AssetProxyId.ERC20,
decimals: 18,
primaryColor: '#263469',
symbol: 'gnt',
+ name: 'Golem',
},
'0xf47261b000000000000000000000000012480e24eb5bec1a9d4369cab6a80cad3c0a377a': {
assetProxyId: AssetProxyId.ERC20,
decimals: 18,
primaryColor: '#de5445',
symbol: 'sub',
+ name: 'Substratum',
},
'0xf47261b000000000000000000000000008d32b0da63e2C3bcF8019c9c5d849d7a9d791e6': {
assetProxyId: AssetProxyId.ERC20,
decimals: 18,
primaryColor: '#000',
symbol: 'dentacoin',
+ name: 'Dentacoin',
},
- '0xf47261b0000000000000000000000000e94327d07fc17907b4db788e5adf2ed424addff6': {
+ '0xf47261b00000000000000000000000001985365e9f78359a9b6ad760e32412f4a445e862': {
assetProxyId: AssetProxyId.ERC20,
decimals: 18,
primaryColor: '#512D80',
symbol: 'rep',
+ name: 'Augur',
},
};
diff --git a/packages/instant/src/index.umd.ts b/packages/instant/src/index.umd.ts
index 806187a16..a183932c6 100644
--- a/packages/instant/src/index.umd.ts
+++ b/packages/instant/src/index.umd.ts
@@ -7,8 +7,10 @@ import { ZeroExInstantOverlay, ZeroExInstantOverlayProps } from './index';
import { assert } from './util/assert';
export const render = (props: ZeroExInstantOverlayProps, selector: string = DEFAULT_ZERO_EX_CONTAINER_SELECTOR) => {
- assert.isHexString('props.assetData', props.assetData);
- assert.isValidLiquiditySource('props.liquiditySource', props.liquiditySource);
+ assert.isValidOrderSource('orderSource', props.orderSource);
+ if (!_.isUndefined(props.defaultSelectedAssetData)) {
+ assert.isHexString('defaultSelectedAssetData', props.defaultSelectedAssetData);
+ }
if (!_.isUndefined(props.additionalAssetMetaDataMap)) {
assert.isValidAssetMetaDataMap('props.additionalAssetMetaDataMap', props.additionalAssetMetaDataMap);
}
@@ -18,6 +20,9 @@ export const render = (props: ZeroExInstantOverlayProps, selector: string = DEFA
if (!_.isUndefined(props.networkId)) {
assert.isNumber('props.networkId', props.networkId);
}
+ if (!_.isUndefined(props.availableAssetDatas)) {
+ assert.areValidAssetDatas('availableAssetDatas', props.availableAssetDatas);
+ }
if (!_.isUndefined(props.onClose)) {
assert.isFunction('props.onClose', props.onClose);
}
diff --git a/packages/instant/src/redux/actions.ts b/packages/instant/src/redux/actions.ts
index 885f09c7c..879af07fc 100644
--- a/packages/instant/src/redux/actions.ts
+++ b/packages/instant/src/redux/actions.ts
@@ -4,7 +4,7 @@ import * as _ from 'lodash';
import { BigNumberInput } from '../util/big_number_input';
-import { ActionsUnion } from '../types';
+import { ActionsUnion, Asset } from '../types';
export interface PlainAction<T extends string> {
type: T;
@@ -32,6 +32,7 @@ export enum ActionTypes {
SET_BUY_ORDER_STATE_SUCCESS = 'SET_BUY_ORDER_STATE_SUCCESS',
UPDATE_LATEST_BUY_QUOTE = 'UPDATE_LATEST_BUY_QUOTE',
UPDATE_SELECTED_ASSET = 'UPDATE_SELECTED_ASSET',
+ SET_AVAILABLE_ASSETS = 'SET_AVAILABLE_ASSETS',
SET_QUOTE_REQUEST_STATE_PENDING = 'SET_QUOTE_REQUEST_STATE_PENDING',
SET_QUOTE_REQUEST_STATE_FAILURE = 'SET_QUOTE_REQUEST_STATE_FAILURE',
SET_ERROR_MESSAGE = 'SET_ERROR_MESSAGE',
@@ -51,7 +52,8 @@ export const actions = {
setBuyOrderStateFailure: (txHash: string) => createAction(ActionTypes.SET_BUY_ORDER_STATE_FAILURE, txHash),
setBuyOrderStateSuccess: (txHash: string) => createAction(ActionTypes.SET_BUY_ORDER_STATE_SUCCESS, txHash),
updateLatestBuyQuote: (buyQuote?: BuyQuote) => createAction(ActionTypes.UPDATE_LATEST_BUY_QUOTE, buyQuote),
- updateSelectedAsset: (assetData?: string) => createAction(ActionTypes.UPDATE_SELECTED_ASSET, assetData),
+ updateSelectedAsset: (asset: Asset) => createAction(ActionTypes.UPDATE_SELECTED_ASSET, asset),
+ setAvailableAssets: (availableAssets: Asset[]) => createAction(ActionTypes.SET_AVAILABLE_ASSETS, availableAssets),
setQuoteRequestStatePending: () => createAction(ActionTypes.SET_QUOTE_REQUEST_STATE_PENDING),
setQuoteRequestStateFailure: () => createAction(ActionTypes.SET_QUOTE_REQUEST_STATE_FAILURE),
setErrorMessage: (errorMessage: string) => createAction(ActionTypes.SET_ERROR_MESSAGE, errorMessage),
diff --git a/packages/instant/src/redux/async_data.ts b/packages/instant/src/redux/async_data.ts
index 4ed89bdc3..0e05c13da 100644
--- a/packages/instant/src/redux/async_data.ts
+++ b/packages/instant/src/redux/async_data.ts
@@ -1,22 +1,37 @@
+import * as _ from 'lodash';
+
import { BIG_NUMBER_ZERO } from '../constants';
+import { assetUtils } from '../util/asset';
import { coinbaseApi } from '../util/coinbase_api';
+import { errorFlasher } from '../util/error_flasher';
-import { ActionTypes } from './actions';
-
+import { actions } from './actions';
import { Store } from './store';
export const asyncData = {
- fetchAndDispatchToStore: async (store: Store) => {
- let ethUsdPrice = BIG_NUMBER_ZERO;
+ fetchEthPriceAndDispatchToStore: async (store: Store) => {
try {
- ethUsdPrice = await coinbaseApi.getEthUsdPrice();
+ const ethUsdPrice = await coinbaseApi.getEthUsdPrice();
+ store.dispatch(actions.updateEthUsdPrice(ethUsdPrice));
} catch (e) {
- // ignore
- } finally {
- store.dispatch({
- type: ActionTypes.UPDATE_ETH_USD_PRICE,
- data: ethUsdPrice,
- });
+ const errorMessage = 'Error fetching ETH/USD price';
+ errorFlasher.flashNewErrorMessage(store.dispatch, errorMessage);
+ store.dispatch(actions.updateEthUsdPrice(BIG_NUMBER_ZERO));
+ }
+ },
+ fetchAvailableAssetDatasAndDispatchToStore: async (store: Store) => {
+ const { assetBuyer, assetMetaDataMap, network } = store.getState();
+ if (!_.isUndefined(assetBuyer)) {
+ try {
+ const assetDatas = await assetBuyer.getAvailableAssetDatasAsync();
+ const assets = assetUtils.createAssetsFromAssetDatas(assetDatas, assetMetaDataMap, network);
+ store.dispatch(actions.setAvailableAssets(assets));
+ } catch (e) {
+ const errorMessage = 'Could not find any assets';
+ errorFlasher.flashNewErrorMessage(store.dispatch, errorMessage);
+ // On error, just specify that none are available
+ store.dispatch(actions.setAvailableAssets([]));
+ }
}
},
};
diff --git a/packages/instant/src/redux/reducer.ts b/packages/instant/src/redux/reducer.ts
index d1537b49b..179c5902e 100644
--- a/packages/instant/src/redux/reducer.ts
+++ b/packages/instant/src/redux/reducer.ts
@@ -15,7 +15,6 @@ import {
OrderProcessState,
OrderState,
} from '../types';
-import { assetUtils } from '../util/asset';
import { BigNumberInput } from '../util/big_number_input';
import { Action, ActionTypes } from './actions';
@@ -25,6 +24,7 @@ export interface State {
assetBuyer?: AssetBuyer;
assetMetaDataMap: ObjectMap<AssetMetaData>;
selectedAsset?: Asset;
+ availableAssets?: Asset[];
selectedAssetAmount?: BigNumberInput;
buyOrderState: OrderState;
ethUsdPrice?: BigNumber;
@@ -38,6 +38,7 @@ export interface State {
export const INITIAL_STATE: State = {
network: Network.Mainnet,
selectedAssetAmount: undefined,
+ availableAssets: undefined,
assetMetaDataMap,
buyOrderState: { processState: OrderProcessState.NONE },
ethUsdPrice: undefined,
@@ -160,18 +161,9 @@ export const reducer = (state: State = INITIAL_STATE, action: Action): State =>
latestErrorDisplayStatus: 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,
+ selectedAsset: action.data,
};
case ActionTypes.RESET_AMOUNT:
return {
@@ -181,6 +173,11 @@ export const reducer = (state: State = INITIAL_STATE, action: Action): State =>
buyOrderState: { processState: OrderProcessState.NONE },
selectedAssetAmount: undefined,
};
+ case ActionTypes.SET_AVAILABLE_ASSETS:
+ return {
+ ...state,
+ availableAssets: action.data,
+ };
default:
return state;
}
diff --git a/packages/instant/src/style/theme.ts b/packages/instant/src/style/theme.ts
index e81694620..d10c9b72c 100644
--- a/packages/instant/src/style/theme.ts
+++ b/packages/instant/src/style/theme.ts
@@ -1,6 +1,6 @@
import * as styledComponents from 'styled-components';
-const { default: styled, css, keyframes, ThemeProvider } = styledComponents;
+const { default: styled, css, keyframes, withTheme, ThemeProvider } = styledComponents;
export type Theme = { [key in ColorOption]: string };
@@ -10,6 +10,7 @@ export enum ColorOption {
lightGrey = 'lightGrey',
grey = 'grey',
feintGrey = 'feintGrey',
+ lightestGrey = 'lightestGrey',
darkGrey = 'darkGrey',
white = 'white',
lightOrange = 'lightOrange',
@@ -17,11 +18,12 @@ export enum ColorOption {
}
export const theme: Theme = {
- primaryColor: '#512D80',
+ primaryColor: '#333',
black: 'black',
lightGrey: '#999999',
grey: '#666666',
feintGrey: '#DEDEDE',
+ lightestGrey: '#EEEEEE',
darkGrey: '#333333',
white: 'white',
lightOrange: '#F9F2ED',
@@ -31,4 +33,4 @@ export const theme: Theme = {
export const transparentWhite = 'rgba(255,255,255,0.3)';
export const overlayBlack = 'rgba(0, 0, 0, 0.6)';
-export { styled, css, keyframes, ThemeProvider };
+export { styled, css, keyframes, withTheme, ThemeProvider };
diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts
index 82dc77d2e..6d66be305 100644
--- a/packages/instant/src/types.ts
+++ b/packages/instant/src/types.ts
@@ -45,6 +45,7 @@ export interface ERC20AssetMetaData {
decimals: number;
primaryColor?: string;
symbol: string;
+ name: string;
}
export interface ERC721AssetMetaData {
@@ -81,6 +82,8 @@ export enum ZeroExInstantError {
InsufficientETH = 'INSUFFICIENT_ETH',
}
+export type SimpleHandler = () => void;
+
export interface AffiliateInfo {
feeRecipient: string;
feePercentage: number;
diff --git a/packages/instant/src/util/assert.ts b/packages/instant/src/util/assert.ts
index 20f8ddaee..d02f58625 100644
--- a/packages/instant/src/util/assert.ts
+++ b/packages/instant/src/util/assert.ts
@@ -8,12 +8,15 @@ import { AffiliateInfo, AssetMetaData } from '../types';
export const assert = {
...sharedAssert,
- isValidLiquiditySource(variableName: string, liquiditySource: string | SignedOrder[]): void {
- if (_.isString(liquiditySource)) {
- sharedAssert.isUri(variableName, liquiditySource);
+ isValidOrderSource(variableName: string, orderSource: string | SignedOrder[]): void {
+ if (_.isString(orderSource)) {
+ sharedAssert.isUri(variableName, orderSource);
return;
}
- sharedAssert.doesConformToSchema(variableName, liquiditySource, schemas.signedOrdersSchema);
+ sharedAssert.doesConformToSchema(variableName, orderSource, schemas.signedOrdersSchema);
+ },
+ areValidAssetDatas(variableName: string, assetDatas: string[]): void {
+ _.forEach(assetDatas, (assetData, index) => assert.isHexString(`${variableName}[${index}]`, assetData));
},
isValidAssetMetaDataMap(variableName: string, metaDataMap: ObjectMap<AssetMetaData>): void {
_.forEach(metaDataMap, (metaData, assetData) => {
diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts
index 630103c7b..fbfbb19f3 100644
--- a/packages/instant/src/util/asset.ts
+++ b/packages/instant/src/util/asset.ts
@@ -5,7 +5,31 @@ import { assetDataNetworkMapping } from '../data/asset_data_network_mapping';
import { Asset, AssetMetaData, ERC20Asset, Network, ZeroExInstantError } from '../types';
export const assetUtils = {
- createAssetFromAssetData: (
+ createAssetsFromAssetDatas: (
+ assetDatas: string[],
+ assetMetaDataMap: ObjectMap<AssetMetaData>,
+ network: Network,
+ ): Asset[] => {
+ const arrayOfAssetOrUndefined = _.map(assetDatas, assetData =>
+ assetUtils.createAssetFromAssetDataIfExists(assetData, assetMetaDataMap, network),
+ );
+ return _.compact(arrayOfAssetOrUndefined);
+ },
+ createAssetFromAssetDataIfExists: (
+ assetData: string,
+ assetMetaDataMap: ObjectMap<AssetMetaData>,
+ network: Network,
+ ): Asset | undefined => {
+ const metaData = assetUtils.getMetaDataIfExists(assetData, assetMetaDataMap, network);
+ if (_.isUndefined(metaData)) {
+ return;
+ }
+ return {
+ assetData,
+ metaData,
+ };
+ },
+ createAssetFromAssetDataOrThrow: (
assetData: string,
assetMetaDataMap: ObjectMap<AssetMetaData>,
network: Network,
@@ -16,19 +40,33 @@ export const assetUtils = {
};
},
getMetaDataOrThrow: (assetData: string, metaDataMap: ObjectMap<AssetMetaData>, network: Network): AssetMetaData => {
+ const metaDataIfExists = assetUtils.getMetaDataIfExists(assetData, metaDataMap, network);
+ if (_.isUndefined(metaDataIfExists)) {
+ throw new Error(ZeroExInstantError.AssetMetaDataNotAvailable);
+ }
+ return metaDataIfExists;
+ },
+ getMetaDataIfExists: (
+ assetData: string,
+ metaDataMap: ObjectMap<AssetMetaData>,
+ network: Network,
+ ): AssetMetaData | undefined => {
let mainnetAssetData: string | undefined = assetData;
if (network !== Network.Mainnet) {
- const mainnetAssetDataIfExists = assetUtils.getAssociatedAssetDataIfExists(assetData, network);
+ const mainnetAssetDataIfExists = assetUtils.getAssociatedAssetDataIfExists(
+ assetData.toLowerCase(),
+ 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);
+ return;
}
- const metaData = metaDataMap[mainnetAssetData];
+ const metaData = metaDataMap[mainnetAssetData.toLowerCase()];
if (_.isUndefined(metaData)) {
- throw new Error(ZeroExInstantError.AssetMetaDataNotAvailable);
+ return;
}
return metaData;
},
@@ -63,4 +101,11 @@ export const assetUtils = {
}
return assetDataGroupIfExists[Network.Mainnet];
},
+ getERC20AssetsFromAssets: (assets: Asset[]): ERC20Asset[] => {
+ const erc20sOrUndefined = _.map(
+ assets,
+ asset => (asset.metaData.assetProxyId === AssetProxyId.ERC20 ? (asset as ERC20Asset) : undefined),
+ );
+ return _.compact(erc20sOrUndefined);
+ },
};
diff --git a/packages/instant/test/util/asset.test.ts b/packages/instant/test/util/asset.test.ts
index c7db7eba7..4229b24ed 100644
--- a/packages/instant/test/util/asset.test.ts
+++ b/packages/instant/test/util/asset.test.ts
@@ -9,6 +9,7 @@ const ZRX_META_DATA: ERC20AssetMetaData = {
assetProxyId: AssetProxyId.ERC20,
symbol: 'zrx',
decimals: 18,
+ name: '0x',
};
const ZRX_ASSET: Asset = {
assetData: ZRX_ASSET_DATA,