aboutsummaryrefslogtreecommitdiffstats
path: root/packages/instant/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/instant/src')
-rw-r--r--packages/instant/src/components/buy_button.tsx59
-rw-r--r--packages/instant/src/components/instant_heading.tsx39
-rw-r--r--packages/instant/src/components/zero_ex_instant.tsx2
-rw-r--r--packages/instant/src/components/zero_ex_instant_container.tsx7
-rw-r--r--packages/instant/src/constants.ts4
-rw-r--r--packages/instant/src/containers/selected_asset_amount_input.ts57
-rw-r--r--packages/instant/src/containers/selected_asset_amount_input.tsx36
-rw-r--r--packages/instant/src/containers/selected_asset_buy_button.ts59
-rw-r--r--packages/instant/src/containers/selected_asset_instant_heading.ts26
-rw-r--r--packages/instant/src/redux/async_data.ts22
-rw-r--r--packages/instant/src/redux/reducer.ts19
-rw-r--r--packages/instant/src/types.ts10
-rw-r--r--packages/instant/src/util/asset_buyer.ts11
-rw-r--r--packages/instant/src/util/coinbase_api.ts8
-rw-r--r--packages/instant/src/util/provider.ts12
-rw-r--r--packages/instant/src/util/web3_wrapper.ts5
16 files changed, 321 insertions, 55 deletions
diff --git a/packages/instant/src/components/buy_button.tsx b/packages/instant/src/components/buy_button.tsx
index 5a32b9575..097b8e547 100644
--- a/packages/instant/src/components/buy_button.tsx
+++ b/packages/instant/src/components/buy_button.tsx
@@ -1,19 +1,56 @@
+import { BuyQuote } from '@0xproject/asset-buyer';
+import * as _ from 'lodash';
import * as React from 'react';
import { ColorOption } from '../style/theme';
+import { assetBuyer } from '../util/asset_buyer';
+import { web3Wrapper } from '../util/web3_wrapper';
import { Button, Container, Text } from './ui';
-export interface BuyButtonProps {}
+export interface BuyButtonProps {
+ buyQuote?: BuyQuote;
+ onClick: (buyQuote: BuyQuote) => void;
+ onBuySuccess: (buyQuote: BuyQuote) => void;
+ onBuyFailure: (buyQuote: BuyQuote) => void;
+ text: string;
+}
-export const BuyButton: React.StatelessComponent<BuyButtonProps> = props => (
- <Container padding="20px" width="100%">
- <Button width="100%">
- <Text fontColor={ColorOption.white} fontWeight={600} fontSize="20px">
- Buy
- </Text>
- </Button>
- </Container>
-);
+const boundNoop = _.noop.bind(_);
-BuyButton.displayName = 'BuyButton';
+export class BuyButton extends React.Component<BuyButtonProps> {
+ public static defaultProps = {
+ onClick: boundNoop,
+ onBuySuccess: boundNoop,
+ onBuyFailure: boundNoop,
+ };
+ public render(): React.ReactNode {
+ const shouldDisableButton = _.isUndefined(this.props.buyQuote);
+ return (
+ <Container padding="20px" width="100%">
+ <Button width="100%" onClick={this._handleClick} isDisabled={shouldDisableButton}>
+ <Text fontColor={ColorOption.white} fontWeight={600} fontSize="20px">
+ {this.props.text}
+ </Text>
+ </Button>
+ </Container>
+ );
+ }
+ private readonly _handleClick = async () => {
+ // The button is disabled when there is no buy quote anyway.
+ if (_.isUndefined(this.props.buyQuote)) {
+ return;
+ }
+ this.props.onClick(this.props.buyQuote);
+ try {
+ const txnHash = await assetBuyer.executeBuyQuoteAsync(this.props.buyQuote, {
+ // HACK: There is a calculation issue in asset-buyer. ETH is refunded anyway so just over-estimate.
+ ethAmount: this.props.buyQuote.worstCaseQuoteInfo.totalEthAmount.mul(2),
+ });
+ await web3Wrapper.awaitTransactionSuccessAsync(txnHash);
+ } catch {
+ this.props.onBuyFailure(this.props.buyQuote);
+ }
+ this.props.onBuySuccess(this.props.buyQuote);
+ };
+}
diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx
index be0414b8d..cde3862c7 100644
--- a/packages/instant/src/components/instant_heading.tsx
+++ b/packages/instant/src/components/instant_heading.tsx
@@ -1,11 +1,42 @@
+import { BigNumber } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import * as _ from 'lodash';
import * as React from 'react';
+import { ethDecimals } from '../constants';
import { SelectedAssetAmountInput } from '../containers/selected_asset_amount_input';
import { ColorOption } from '../style/theme';
import { Container, Flex, Text } from './ui';
-export interface InstantHeadingProps {}
+export interface InstantHeadingProps {
+ selectedAssetAmount?: BigNumber;
+ totalEthBaseAmount?: BigNumber;
+ ethUsdPrice?: BigNumber;
+}
+
+const displaytotalEthBaseAmount = ({ selectedAssetAmount, totalEthBaseAmount }: InstantHeadingProps): string => {
+ if (_.isUndefined(selectedAssetAmount)) {
+ return '0 ETH';
+ }
+ if (_.isUndefined(totalEthBaseAmount)) {
+ return '...loading';
+ }
+ const ethUnitAmount = Web3Wrapper.toUnitAmount(totalEthBaseAmount, ethDecimals);
+ const roundedAmount = ethUnitAmount.round(4);
+ return `${roundedAmount} ETH`;
+};
+
+const displayUsdAmount = ({ totalEthBaseAmount, selectedAssetAmount, ethUsdPrice }: InstantHeadingProps): string => {
+ if (_.isUndefined(selectedAssetAmount)) {
+ return '$0.00';
+ }
+ if (_.isUndefined(totalEthBaseAmount) || _.isUndefined(ethUsdPrice)) {
+ return '...loading';
+ }
+ const ethUnitAmount = Web3Wrapper.toUnitAmount(totalEthBaseAmount, ethDecimals);
+ return `$${ethUnitAmount.mul(ethUsdPrice).round(2)}`;
+};
export const InstantHeading: React.StatelessComponent<InstantHeadingProps> = props => (
<Container backgroundColor={ColorOption.primaryColor} padding="20px" width="100%" borderRadius="3px 3px 0px 0px">
@@ -26,18 +57,18 @@ export const InstantHeading: React.StatelessComponent<InstantHeadingProps> = pro
<SelectedAssetAmountInput fontSize="45px" />
<Container display="inline-block" marginLeft="10px">
<Text fontSize="45px" fontColor={ColorOption.white} textTransform="uppercase">
- rep
+ zrx
</Text>
</Container>
</Container>
<Flex direction="column" justify="space-between">
<Container marginBottom="5px">
<Text fontSize="16px" fontColor={ColorOption.white} fontWeight={500}>
- 0 ETH
+ {displaytotalEthBaseAmount(props)}
</Text>
</Container>
<Text fontSize="16px" fontColor={ColorOption.white} opacity={0.7}>
- $0.00
+ {displayUsdAmount(props)}
</Text>
</Flex>
</Flex>
diff --git a/packages/instant/src/components/zero_ex_instant.tsx b/packages/instant/src/components/zero_ex_instant.tsx
index 0e6230d1b..17ef737c9 100644
--- a/packages/instant/src/components/zero_ex_instant.tsx
+++ b/packages/instant/src/components/zero_ex_instant.tsx
@@ -1,6 +1,7 @@
import * as React from 'react';
import { Provider } from 'react-redux';
+import { asyncData } from '../redux/async_data';
import { store } from '../redux/store';
import { fonts } from '../style/fonts';
import { theme, ThemeProvider } from '../style/theme';
@@ -8,6 +9,7 @@ import { theme, ThemeProvider } from '../style/theme';
import { ZeroExInstantContainer } from './zero_ex_instant_container';
fonts.include();
+asyncData.fetchAndDispatchToStore();
export interface ZeroExInstantProps {}
diff --git a/packages/instant/src/components/zero_ex_instant_container.tsx b/packages/instant/src/components/zero_ex_instant_container.tsx
index 716227b51..4ff8b51bd 100644
--- a/packages/instant/src/components/zero_ex_instant_container.tsx
+++ b/packages/instant/src/components/zero_ex_instant_container.tsx
@@ -1,5 +1,8 @@
import * as React from 'react';
+import { SelectedAssetBuyButton } from '../containers/selected_asset_buy_button';
+import { SelectedAssetInstantHeading } from '../containers/selected_asset_instant_heading';
+
import { ColorOption } from '../style/theme';
import { BuyButton } from './buy_button';
@@ -12,9 +15,9 @@ export interface ZeroExInstantContainerProps {}
export const ZeroExInstantContainer: React.StatelessComponent<ZeroExInstantContainerProps> = props => (
<Container hasBoxShadow={true} width="350px" backgroundColor={ColorOption.white} borderRadius="3px">
<Flex direction="column" justify="flex-start">
- <InstantHeading />
+ <SelectedAssetInstantHeading />
<OrderDetails />
- <BuyButton />
+ <SelectedAssetBuyButton />
</Flex>
</Container>
);
diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts
new file mode 100644
index 000000000..397c9b07a
--- /dev/null
+++ b/packages/instant/src/constants.ts
@@ -0,0 +1,4 @@
+export const sraApiUrl = 'https://api.radarrelay.com/0x/v2/';
+export const zrxContractAddress = '0xe41d2489571d322189246dafa5ebde1f4699f498';
+export const zrxDecimals = 18;
+export const ethDecimals = 18;
diff --git a/packages/instant/src/containers/selected_asset_amount_input.ts b/packages/instant/src/containers/selected_asset_amount_input.ts
new file mode 100644
index 000000000..b731b889a
--- /dev/null
+++ b/packages/instant/src/containers/selected_asset_amount_input.ts
@@ -0,0 +1,57 @@
+import { BigNumber } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import * as _ from 'lodash';
+import * as React from 'react';
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
+
+import { zrxContractAddress, zrxDecimals } from '../constants';
+import { State } from '../redux/reducer';
+import { ColorOption } from '../style/theme';
+import { Action, ActionTypes, AsyncProcessState } from '../types';
+import { assetBuyer } from '../util/asset_buyer';
+
+import { AmountInput } from '../components/amount_input';
+
+export interface SelectedAssetAmountInputProps {
+ fontColor?: ColorOption;
+ fontSize?: string;
+}
+
+interface ConnectedState {
+ value?: BigNumber;
+}
+
+interface ConnectedDispatch {
+ onChange?: (value?: BigNumber) => void;
+}
+
+const mapStateToProps = (state: State, _ownProps: SelectedAssetAmountInputProps): ConnectedState => ({
+ value: state.selectedAssetAmount,
+});
+
+const mapDispatchToProps = (dispatch: Dispatch<Action>): ConnectedDispatch => ({
+ onChange: async value => {
+ // Update the input
+ dispatch({ type: ActionTypes.UPDATE_SELECTED_ASSET_AMOUNT, data: value });
+ // invalidate the last buy quote.
+ dispatch({ type: ActionTypes.UPDATE_LATEST_BUY_QUOTE, data: undefined });
+ // reset our buy state
+ dispatch({ type: ActionTypes.UPDATE_SELECTED_ASSET_BUY_STATE, data: AsyncProcessState.NONE });
+ if (!_.isUndefined(value)) {
+ // get a new buy quote.
+ const baseUnitValue = Web3Wrapper.toBaseUnitAmount(value, zrxDecimals);
+ const newBuyQuote = await assetBuyer.getBuyQuoteForERC20TokenAddressAsync(
+ zrxContractAddress,
+ baseUnitValue,
+ );
+ // invalidate the last buy quote.
+ dispatch({ type: ActionTypes.UPDATE_LATEST_BUY_QUOTE, data: newBuyQuote });
+ }
+ },
+});
+
+export const SelectedAssetAmountInput: React.ComponentClass<SelectedAssetAmountInputProps> = connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(AmountInput);
diff --git a/packages/instant/src/containers/selected_asset_amount_input.tsx b/packages/instant/src/containers/selected_asset_amount_input.tsx
deleted file mode 100644
index 800a4c568..000000000
--- a/packages/instant/src/containers/selected_asset_amount_input.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { BigNumber } from '@0xproject/utils';
-import * as React from 'react';
-import { connect } from 'react-redux';
-import { Dispatch } from 'redux';
-
-import { State } from '../redux/reducer';
-import { ColorOption } from '../style/theme';
-import { Action, ActionTypes } from '../types';
-
-import { AmountInput } from '../components/amount_input';
-
-export interface SelectedAssetAmountInputProps {
- fontColor?: ColorOption;
- fontSize?: string;
-}
-
-interface ConnectedState {
- value?: BigNumber;
-}
-
-interface ConnectedDispatch {
- onChange?: (value?: BigNumber) => void;
-}
-
-const mapStateToProps = (state: State, _ownProps: SelectedAssetAmountInputProps): ConnectedState => ({
- value: state.selectedAssetAmount,
-});
-
-const mapDispatchToProps = (dispatch: Dispatch<Action>): ConnectedDispatch => ({
- onChange: value => dispatch({ type: ActionTypes.UPDATE_SELECTED_ASSET_AMOUNT, data: value }),
-});
-
-export const SelectedAssetAmountInput: React.ComponentClass<SelectedAssetAmountInputProps> = connect(
- mapStateToProps,
- mapDispatchToProps,
-)(AmountInput);
diff --git a/packages/instant/src/containers/selected_asset_buy_button.ts b/packages/instant/src/containers/selected_asset_buy_button.ts
new file mode 100644
index 000000000..f537294e4
--- /dev/null
+++ b/packages/instant/src/containers/selected_asset_buy_button.ts
@@ -0,0 +1,59 @@
+import { BuyQuote } from '@0xproject/asset-buyer';
+import * as _ from 'lodash';
+import * as React from 'react';
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
+
+import { State } from '../redux/reducer';
+import { Action, ActionTypes, AsyncProcessState } from '../types';
+import { assetBuyer } from '../util/asset_buyer';
+import { web3Wrapper } from '../util/web3_wrapper';
+
+import { BuyButton } from '../components/buy_button';
+
+export interface SelectedAssetBuyButtonProps {}
+
+interface ConnectedState {
+ text: string;
+ buyQuote?: BuyQuote;
+}
+
+interface ConnectedDispatch {
+ onClick: (buyQuote: BuyQuote) => void;
+ onBuySuccess: (buyQuote: BuyQuote) => void;
+ onBuyFailure: (buyQuote: BuyQuote) => void;
+}
+
+const textForState = (state: AsyncProcessState): string => {
+ switch (state) {
+ case AsyncProcessState.NONE:
+ return 'Buy';
+ case AsyncProcessState.PENDING:
+ return '...Loading';
+ case AsyncProcessState.SUCCESS:
+ return 'Success!';
+ case AsyncProcessState.FAILURE:
+ return 'Failed';
+ default:
+ return 'Buy';
+ }
+};
+
+const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyButtonProps): ConnectedState => ({
+ text: textForState(state.selectedAssetBuyState),
+ buyQuote: state.latestBuyQuote,
+});
+
+const mapDispatchToProps = (dispatch: Dispatch<Action>, ownProps: SelectedAssetBuyButtonProps): ConnectedDispatch => ({
+ onClick: buyQuote =>
+ dispatch({ type: ActionTypes.UPDATE_SELECTED_ASSET_BUY_STATE, data: AsyncProcessState.PENDING }),
+ onBuySuccess: buyQuote =>
+ dispatch({ type: ActionTypes.UPDATE_SELECTED_ASSET_BUY_STATE, data: AsyncProcessState.SUCCESS }),
+ onBuyFailure: buyQuote =>
+ dispatch({ type: ActionTypes.UPDATE_SELECTED_ASSET_BUY_STATE, data: AsyncProcessState.FAILURE }),
+});
+
+export const SelectedAssetBuyButton: React.ComponentClass<SelectedAssetBuyButtonProps> = connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(BuyButton);
diff --git a/packages/instant/src/containers/selected_asset_instant_heading.ts b/packages/instant/src/containers/selected_asset_instant_heading.ts
new file mode 100644
index 000000000..a9e853fe1
--- /dev/null
+++ b/packages/instant/src/containers/selected_asset_instant_heading.ts
@@ -0,0 +1,26 @@
+import { BigNumber } from '@0xproject/utils';
+import * as _ from 'lodash';
+import * as React from 'react';
+import { connect } from 'react-redux';
+
+import { State } from '../redux/reducer';
+
+import { InstantHeading } from '../components/instant_heading';
+
+export interface InstantHeadingProps {}
+
+interface ConnectedState {
+ selectedAssetAmount?: BigNumber;
+ totalEthBaseAmount?: BigNumber;
+ ethUsdPrice?: BigNumber;
+}
+
+const mapStateToProps = (state: State, _ownProps: InstantHeadingProps): ConnectedState => ({
+ selectedAssetAmount: state.selectedAssetAmount,
+ totalEthBaseAmount: _.get(state, 'latestBuyQuote.worstCaseQuoteInfo.totalEthAmount'),
+ ethUsdPrice: state.ethUsdPrice,
+});
+
+export const SelectedAssetInstantHeading: React.ComponentClass<InstantHeadingProps> = connect(mapStateToProps)(
+ InstantHeading,
+);
diff --git a/packages/instant/src/redux/async_data.ts b/packages/instant/src/redux/async_data.ts
new file mode 100644
index 000000000..3fde2d2e5
--- /dev/null
+++ b/packages/instant/src/redux/async_data.ts
@@ -0,0 +1,22 @@
+import { BigNumber } from '@0xproject/utils';
+
+import { ActionTypes } from '../types';
+import { coinbaseApi } from '../util/coinbase_api';
+
+import { store } from './store';
+
+export const asyncData = {
+ fetchAndDispatchToStore: async () => {
+ let ethUsdPriceStr = '0';
+ try {
+ ethUsdPriceStr = await coinbaseApi.getEthUsdPrice();
+ } catch (e) {
+ // ignore
+ } finally {
+ store.dispatch({
+ type: ActionTypes.UPDATE_ETH_USD_PRICE,
+ data: new BigNumber(ethUsdPriceStr),
+ });
+ }
+ },
+};
diff --git a/packages/instant/src/redux/reducer.ts b/packages/instant/src/redux/reducer.ts
index 5026895ae..c0c4b06ad 100644
--- a/packages/instant/src/redux/reducer.ts
+++ b/packages/instant/src/redux/reducer.ts
@@ -1,16 +1,21 @@
+import { BuyQuote } from '@0xproject/asset-buyer';
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
-import { Action, ActionTypes } from '../types';
+import { Action, ActionTypes, AsyncProcessState } from '../types';
export interface State {
- ethUsdPrice?: string;
selectedAssetAmount?: BigNumber;
+ selectedAssetBuyState: AsyncProcessState;
+ ethUsdPrice?: BigNumber;
+ latestBuyQuote?: BuyQuote;
}
export const INITIAL_STATE: State = {
ethUsdPrice: undefined,
+ selectedAssetBuyState: AsyncProcessState.NONE,
selectedAssetAmount: undefined,
+ latestBuyQuote: undefined,
};
export const reducer = (state: State = INITIAL_STATE, action: Action): State => {
@@ -25,6 +30,16 @@ export const reducer = (state: State = INITIAL_STATE, action: Action): State =>
...state,
selectedAssetAmount: action.data,
};
+ case ActionTypes.UPDATE_LATEST_BUY_QUOTE:
+ return {
+ ...state,
+ latestBuyQuote: action.data,
+ };
+ case ActionTypes.UPDATE_SELECTED_ASSET_BUY_STATE:
+ return {
+ ...state,
+ selectedAssetBuyState: action.data,
+ };
default:
return state;
}
diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts
index d150bd8ab..bbf31cbf4 100644
--- a/packages/instant/src/types.ts
+++ b/packages/instant/src/types.ts
@@ -1,6 +1,16 @@
+// Reusable
+export enum AsyncProcessState {
+ NONE,
+ PENDING,
+ SUCCESS,
+ FAILURE,
+}
+
export enum ActionTypes {
UPDATE_ETH_USD_PRICE,
UPDATE_SELECTED_ASSET_AMOUNT,
+ UPDATE_SELECTED_ASSET_BUY_STATE,
+ UPDATE_LATEST_BUY_QUOTE,
}
export interface Action {
diff --git a/packages/instant/src/util/asset_buyer.ts b/packages/instant/src/util/asset_buyer.ts
new file mode 100644
index 000000000..b030501c4
--- /dev/null
+++ b/packages/instant/src/util/asset_buyer.ts
@@ -0,0 +1,11 @@
+import { AssetBuyer } from '@0xproject/asset-buyer';
+
+import { sraApiUrl } from '../constants';
+
+import { getProvider } from './provider';
+
+const provider = getProvider();
+
+export const assetBuyer = AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(provider, sraApiUrl, {
+ expiryBufferSeconds: 300,
+});
diff --git a/packages/instant/src/util/coinbase_api.ts b/packages/instant/src/util/coinbase_api.ts
new file mode 100644
index 000000000..63c8077da
--- /dev/null
+++ b/packages/instant/src/util/coinbase_api.ts
@@ -0,0 +1,8 @@
+const baseEndpoint = 'https://api.coinbase.com/v2';
+export const coinbaseApi = {
+ getEthUsdPrice: async (): Promise<string> => {
+ const res = await fetch(`${baseEndpoint}/prices/ETH-USD/buy`);
+ const resJson = await res.json();
+ return resJson.data.amount;
+ },
+};
diff --git a/packages/instant/src/util/provider.ts b/packages/instant/src/util/provider.ts
new file mode 100644
index 000000000..49705fd11
--- /dev/null
+++ b/packages/instant/src/util/provider.ts
@@ -0,0 +1,12 @@
+import { Provider } from 'ethereum-types';
+
+export const getProvider = (): Provider => {
+ const injectedWeb3 = (window as any).web3 || undefined;
+ try {
+ // Use MetaMask/Mist provider
+ return injectedWeb3.currentProvider;
+ } catch (err) {
+ // Throws when user doesn't have MetaMask/Mist running
+ throw new Error(`No injected web3 found: ${err}`);
+ }
+};
diff --git a/packages/instant/src/util/web3_wrapper.ts b/packages/instant/src/util/web3_wrapper.ts
new file mode 100644
index 000000000..d7e43521f
--- /dev/null
+++ b/packages/instant/src/util/web3_wrapper.ts
@@ -0,0 +1,5 @@
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+
+import { getProvider } from './provider';
+
+export const web3Wrapper = new Web3Wrapper(getProvider());