aboutsummaryrefslogtreecommitdiffstats
path: root/packages/instant
diff options
context:
space:
mode:
authorBrandon Millman <brandon.millman@gmail.com>2018-11-07 12:25:30 +0800
committerBrandon Millman <brandon.millman@gmail.com>2018-11-08 01:32:49 +0800
commitdfbf10c94bfbbdbca353531c5cae6707e05981f0 (patch)
tree6b1f2832742517b280d7a7594adfa6fec4ea7bef /packages/instant
parentc30dca69619bf87ed198cf375d21593213798113 (diff)
downloaddexon-sol-tools-dfbf10c94bfbbdbca353531c5cae6707e05981f0.tar
dexon-sol-tools-dfbf10c94bfbbdbca353531c5cae6707e05981f0.tar.gz
dexon-sol-tools-dfbf10c94bfbbdbca353531c5cae6707e05981f0.tar.bz2
dexon-sol-tools-dfbf10c94bfbbdbca353531c5cae6707e05981f0.tar.lz
dexon-sol-tools-dfbf10c94bfbbdbca353531c5cae6707e05981f0.tar.xz
dexon-sol-tools-dfbf10c94bfbbdbca353531c5cae6707e05981f0.tar.zst
dexon-sol-tools-dfbf10c94bfbbdbca353531c5cae6707e05981f0.zip
feat(instant): fallback to an empty wallet provider when none is injected
Diffstat (limited to 'packages/instant')
-rw-r--r--packages/instant/package.json1
-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/zero_ex_instant_provider.tsx86
-rw-r--r--packages/instant/src/constants.ts8
-rw-r--r--packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts46
-rw-r--r--packages/instant/src/containers/selected_erc20_asset_amount_input.ts9
-rw-r--r--packages/instant/src/redux/async_data.ts23
-rw-r--r--packages/instant/src/redux/reducer.ts291
-rw-r--r--packages/instant/src/redux/store.ts7
-rw-r--r--packages/instant/src/types.ts26
-rw-r--r--packages/instant/src/util/asset_buyer_factory.ts17
-rw-r--r--packages/instant/src/util/injected_provider.ts16
-rw-r--r--packages/instant/src/util/provider_factory.ts34
14 files changed, 331 insertions, 243 deletions
diff --git a/packages/instant/package.json b/packages/instant/package.json
index f90990649..0f6f125fa 100644
--- a/packages/instant/package.json
+++ b/packages/instant/package.json
@@ -49,6 +49,7 @@
"@0x/asset-buyer": "^2.1.0",
"@0x/json-schemas": "^2.0.0",
"@0x/order-utils": "^2.0.0",
+ "@0x/subproviders": "^2.1.0",
"@0x/types": "^1.2.0",
"@0x/typescript-typings": "^3.0.3",
"@0x/utils": "^2.0.3",
diff --git a/packages/instant/src/components/buy_button.tsx b/packages/instant/src/components/buy_button.tsx
index e6d50de96..515cd18e9 100644
--- a/packages/instant/src/components/buy_button.tsx
+++ b/packages/instant/src/components/buy_button.tsx
@@ -17,7 +17,7 @@ import { Text } from './ui/text';
export interface BuyButtonProps {
buyQuote?: BuyQuote;
- assetBuyer?: AssetBuyer;
+ assetBuyer: AssetBuyer;
affiliateInfo?: AffiliateInfo;
onValidationPending: (buyQuote: BuyQuote) => void;
onValidationFail: (buyQuote: BuyQuote, errorMessage: AssetBuyerError | ZeroExInstantError) => void;
@@ -34,7 +34,7 @@ export class BuyButton extends React.Component<BuyButtonProps> {
onBuyFailure: util.boundNoop,
};
public render(): React.ReactNode {
- const shouldDisableButton = _.isUndefined(this.props.buyQuote) || _.isUndefined(this.props.assetBuyer);
+ const shouldDisableButton = _.isUndefined(this.props.buyQuote);
return (
<Button width="100%" onClick={this._handleClick} isDisabled={shouldDisableButton}>
<Text fontColor={ColorOption.white} fontWeight={600} fontSize="20px">
@@ -46,11 +46,13 @@ export class BuyButton extends React.Component<BuyButtonProps> {
private readonly _handleClick = async () => {
// The button is disabled when there is no buy quote anyway.
const { buyQuote, assetBuyer, affiliateInfo } = this.props;
- if (_.isUndefined(buyQuote) || _.isUndefined(assetBuyer)) {
+ if (_.isUndefined(buyQuote)) {
return;
}
this.props.onValidationPending(buyQuote);
+
+ // TODO(bmillman): move address and balance fetching to the async state
const web3Wrapper = new Web3Wrapper(assetBuyer.provider);
const takerAddress = await getBestAddress(web3Wrapper);
diff --git a/packages/instant/src/components/buy_order_state_buttons.tsx b/packages/instant/src/components/buy_order_state_buttons.tsx
index 2759ba341..3d0ede7b1 100644
--- a/packages/instant/src/components/buy_order_state_buttons.tsx
+++ b/packages/instant/src/components/buy_order_state_buttons.tsx
@@ -15,7 +15,7 @@ import { Text } from './ui/text';
export interface BuyOrderStateButtonProps {
buyQuote?: BuyQuote;
buyOrderProcessingState: OrderProcessState;
- assetBuyer?: AssetBuyer;
+ assetBuyer: AssetBuyer;
affiliateInfo?: AffiliateInfo;
onViewTransaction: () => void;
onValidationPending: (buyQuote: BuyQuote) => void;
diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx
index 0b9408329..ed0fabdf3 100644
--- a/packages/instant/src/components/zero_ex_instant_provider.tsx
+++ b/packages/instant/src/components/zero_ex_instant_provider.tsx
@@ -1,23 +1,22 @@
-import { AssetBuyer } from '@0x/asset-buyer';
-import { ObjectMap, SignedOrder } from '@0x/types';
+import { ObjectMap } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { Provider } from 'ethereum-types';
import * as _ from 'lodash';
import * as React from 'react';
import { Provider as ReduxProvider } from 'react-redux';
-import { oc } from 'ts-optchain';
import { SelectedAssetThemeProvider } from '../containers/selected_asset_theme_provider';
import { asyncData } from '../redux/async_data';
-import { INITIAL_STATE, State } from '../redux/reducer';
+import { DEFAULT_STATE, DefaultState, State } from '../redux/reducer';
import { store, Store } from '../redux/store';
import { fonts } from '../style/fonts';
-import { AffiliateInfo, AssetMetaData, Network } from '../types';
+import { AffiliateInfo, AssetMetaData, Network, OrderSource, ProviderState, ProviderType } from '../types';
import { assetUtils } from '../util/asset';
+import { assetBuyerFactory } from '../util/asset_buyer_factory';
import { errorFlasher } from '../util/error_flasher';
import { gasPriceEstimator } from '../util/gas_price_estimator';
-import { getInjectedProvider } from '../util/injected_provider';
+import { providerFactory } from '../util/provider_factory';
fonts.include();
@@ -25,7 +24,7 @@ export type ZeroExInstantProviderProps = ZeroExInstantProviderRequiredProps &
Partial<ZeroExInstantProviderOptionalProps>;
export interface ZeroExInstantProviderRequiredProps {
- orderSource: string | SignedOrder[];
+ orderSource: OrderSource;
}
export interface ZeroExInstantProviderOptionalProps {
@@ -41,30 +40,43 @@ 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: Proper wallet connect flow
- const provider = props.provider || getInjectedProvider();
- const assetBuyerOptions = {
- networkId,
- };
- let assetBuyer;
- if (_.isString(props.orderSource)) {
- assetBuyer = AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(
- provider,
- props.orderSource,
- assetBuyerOptions,
- );
+ private static _mergeDefaultStateWithProps(
+ props: ZeroExInstantProviderProps,
+ defaultState: DefaultState = DEFAULT_STATE,
+ ): State {
+ // use the networkId passed in with the props, otherwise default to that of the default state (1, mainnet)
+ const networkId = props.networkId || defaultState.network;
+ // construct the ProviderState
+ let provider: Provider;
+ let providerType: ProviderType;
+ if (!_.isUndefined(props.provider)) {
+ provider = props.provider;
+ providerType = ProviderType.Props;
} else {
- assetBuyer = AssetBuyer.getAssetBuyerForProvidedOrders(provider, props.orderSource, assetBuyerOptions);
+ const injectedProviderIfExists = providerFactory.getInjectedProviderIfExists();
+ if (!_.isUndefined(injectedProviderIfExists)) {
+ provider = injectedProviderIfExists;
+ providerType = ProviderType.Injected;
+ } else {
+ provider = providerFactory.getFallbackNoSigningProvider(networkId);
+ providerType = ProviderType.FallbackEmptyWallet;
+ }
}
+ const providerState: ProviderState = {
+ provider,
+ type: providerType,
+ web3Wrapper: new Web3Wrapper(provider),
+ assetBuyer: assetBuyerFactory.getAssetBuyer(provider, props.orderSource, networkId),
+ };
+ // merge the additional additionalAssetMetaDataMap with our default map
const completeAssetMetaDataMap = {
...props.additionalAssetMetaDataMap,
- ...state.assetMetaDataMap,
+ ...defaultState.assetMetaDataMap,
};
+ // construct the final state
const storeStateFromProps: State = {
- ...state,
- assetBuyer,
+ ...defaultState,
+ providerState,
network: networkId,
selectedAsset: _.isUndefined(props.defaultSelectedAssetData)
? undefined
@@ -74,7 +86,7 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
networkId,
),
selectedAssetAmount: _.isUndefined(props.defaultAssetBuyAmount)
- ? state.selectedAssetAmount
+ ? undefined
: new BigNumber(props.defaultAssetBuyAmount),
availableAssets: _.isUndefined(props.availableAssetDatas)
? undefined
@@ -86,10 +98,9 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
}
constructor(props: ZeroExInstantProviderProps) {
super(props);
- const initialAppState = ZeroExInstantProvider._mergeInitialStateWithProps(this.props, INITIAL_STATE);
+ const initialAppState = ZeroExInstantProvider._mergeDefaultStateWithProps(this.props);
this._store = store.create(initialAppState);
}
-
public componentDidMount(): void {
const state = this._store.getState();
// tslint:disable-next-line:no-floating-promises
@@ -108,7 +119,6 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
// tslint:disable-next-line:no-floating-promises
this._flashErrorIfWrongNetwork();
}
-
public render(): React.ReactNode {
return (
<ReduxProvider store={this._store}>
@@ -116,19 +126,15 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
</ReduxProvider>
);
}
-
private readonly _flashErrorIfWrongNetwork = async (): Promise<void> => {
const msToShowError = 30000; // 30 seconds
- const network = this._store.getState().network;
- const assetBuyerIfExists = this._store.getState().assetBuyer;
- const providerIfExists = oc(assetBuyerIfExists).provider();
- if (!_.isUndefined(providerIfExists)) {
- const web3Wrapper = new Web3Wrapper(providerIfExists);
- 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);
- }
+ const state = this._store.getState();
+ const network = state.network;
+ const web3Wrapper = state.providerState.web3Wrapper;
+ 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/constants.ts b/packages/instant/src/constants.ts
index 15105b65c..00521b56e 100644
--- a/packages/instant/src/constants.ts
+++ b/packages/instant/src/constants.ts
@@ -1,4 +1,7 @@
import { BigNumber } from '@0x/utils';
+
+import { Network } from './types';
+
export const BIG_NUMBER_ZERO = new BigNumber(0);
export const ETH_DECIMALS = 18;
export const DEFAULT_ZERO_EX_CONTAINER_SELECTOR = '#zeroExInstantContainer';
@@ -13,3 +16,8 @@ export const ETH_GAS_STATION_API_BASE_URL = 'https://ethgasstation.info';
export const COINBASE_API_BASE_URL = 'https://api.coinbase.com/v2';
export const PROGRESS_STALL_AT_WIDTH = '95%';
export const PROGRESS_FINISH_ANIMATION_TIME_MS = 200;
+export const ETHEREUM_NODE_URL_BY_NETWORK = {
+ [Network.Mainnet]: 'https://mainnet.infura.io/',
+ [Network.Kovan]: 'https://kovan.infura.io/',
+};
+export const BLOCK_POLLING_INTERVAL_MS = 10000; // 10s
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 1f9bb2cc2..c3a5e88b9 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
@@ -14,7 +14,7 @@ import { etherscanUtil } from '../util/etherscan';
interface ConnectedState {
buyQuote?: BuyQuote;
buyOrderProcessingState: OrderProcessState;
- assetBuyer?: AssetBuyer;
+ assetBuyer: AssetBuyer;
affiliateInfo?: AffiliateInfo;
onViewTransaction: () => void;
}
@@ -29,29 +29,31 @@ interface ConnectedDispatch {
onValidationFail: (buyQuote: BuyQuote, errorMessage: AssetBuyerError | ZeroExInstantError) => void;
}
export interface SelectedAssetBuyOrderStateButtons {}
-const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyOrderStateButtons): ConnectedState => ({
- buyOrderProcessingState: state.buyOrderState.processState,
- assetBuyer: state.assetBuyer,
- buyQuote: state.latestBuyQuote,
- affiliateInfo: state.affiliateInfo,
- onViewTransaction: () => {
- if (
- state.assetBuyer &&
- (state.buyOrderState.processState === OrderProcessState.Processing ||
+const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyOrderStateButtons): ConnectedState => {
+ const assetBuyer = state.providerState.assetBuyer;
+ return {
+ buyOrderProcessingState: state.buyOrderState.processState,
+ assetBuyer,
+ buyQuote: state.latestBuyQuote,
+ affiliateInfo: state.affiliateInfo,
+ onViewTransaction: () => {
+ if (
+ state.buyOrderState.processState === OrderProcessState.Processing ||
state.buyOrderState.processState === OrderProcessState.Success ||
- state.buyOrderState.processState === OrderProcessState.Failure)
- ) {
- const etherscanUrl = etherscanUtil.getEtherScanTxnAddressIfExists(
- state.buyOrderState.txHash,
- state.assetBuyer.networkId,
- );
- if (etherscanUrl) {
- window.open(etherscanUrl, '_blank');
- return;
+ state.buyOrderState.processState === OrderProcessState.Failure
+ ) {
+ const etherscanUrl = etherscanUtil.getEtherScanTxnAddressIfExists(
+ state.buyOrderState.txHash,
+ assetBuyer.networkId,
+ );
+ if (etherscanUrl) {
+ window.open(etherscanUrl, '_blank');
+ return;
+ }
}
- }
- },
-});
+ },
+ };
+};
const mapDispatchToProps = (
dispatch: Dispatch<Action>,
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 58257cd27..784eb4bd0 100644
--- a/packages/instant/src/containers/selected_erc20_asset_amount_input.ts
+++ b/packages/instant/src/containers/selected_erc20_asset_amount_input.ts
@@ -23,7 +23,7 @@ export interface SelectedERC20AssetAmountInputProps {
}
interface ConnectedState {
- assetBuyer?: AssetBuyer;
+ assetBuyer: AssetBuyer;
value?: BigNumber;
asset?: ERC20Asset;
isDisabled: boolean;
@@ -33,7 +33,7 @@ interface ConnectedState {
interface ConnectedDispatch {
updateBuyQuote: (
- assetBuyer?: AssetBuyer,
+ assetBuyer: AssetBuyer,
value?: BigNumber,
asset?: ERC20Asset,
affiliateInfo?: AffiliateInfo,
@@ -59,8 +59,9 @@ const mapStateToProps = (state: State, _ownProps: SelectedERC20AssetAmountInputP
? (state.selectedAsset as ERC20Asset)
: undefined;
const numberOfAssetsAvailable = _.isUndefined(state.availableAssets) ? undefined : state.availableAssets.length;
+ const assetBuyer = state.providerState.assetBuyer;
return {
- assetBuyer: state.assetBuyer,
+ assetBuyer,
value: state.selectedAssetAmount,
asset: selectedAsset,
isDisabled,
@@ -128,7 +129,7 @@ const mapDispatchToProps = (
// reset our buy state
dispatch(actions.setBuyOrderStateNone());
- if (!_.isUndefined(value) && value.greaterThan(0) && !_.isUndefined(asset) && !_.isUndefined(assetBuyer)) {
+ if (!_.isUndefined(value) && value.greaterThan(0) && !_.isUndefined(asset)) {
// even if it's debounced, give them the illusion it's loading
dispatch(actions.setQuoteRequestStatePending());
// tslint:disable-next-line:no-floating-promises
diff --git a/packages/instant/src/redux/async_data.ts b/packages/instant/src/redux/async_data.ts
index 0e05c13da..c3d190af2 100644
--- a/packages/instant/src/redux/async_data.ts
+++ b/packages/instant/src/redux/async_data.ts
@@ -20,18 +20,17 @@ export const asyncData = {
}
},
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([]));
- }
+ const { providerState, assetMetaDataMap, network } = store.getState();
+ const assetBuyer = providerState.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 1e4d7549e..4a939839a 100644
--- a/packages/instant/src/redux/reducer.ts
+++ b/packages/instant/src/redux/reducer.ts
@@ -1,4 +1,4 @@
-import { AssetBuyer, BuyQuote } from '@0x/asset-buyer';
+import { BuyQuote } from '@0x/asset-buyer';
import { AssetProxyId, ObjectMap } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
@@ -14,172 +14,181 @@ import {
Network,
OrderProcessState,
OrderState,
+ ProviderState,
} from '../types';
import { Action, ActionTypes } from './actions';
-export interface State {
+// State that is required and we have defaults for, before props are passed in
+export interface DefaultState {
network: Network;
- assetBuyer?: AssetBuyer;
assetMetaDataMap: ObjectMap<AssetMetaData>;
- selectedAsset?: Asset;
- availableAssets?: Asset[];
- selectedAssetAmount?: BigNumber;
buyOrderState: OrderState;
- ethUsdPrice?: BigNumber;
- latestBuyQuote?: BuyQuote;
- quoteRequestState: AsyncProcessState;
- latestErrorMessage?: string;
latestErrorDisplayStatus: DisplayStatus;
- affiliateInfo?: AffiliateInfo;
+ quoteRequestState: AsyncProcessState;
+}
+
+// State that is required but needs to be derived from the props
+interface PropsDerivedState {
+ providerState: ProviderState;
}
-export const INITIAL_STATE: State = {
+// State that is optional
+interface OptionalState {
+ selectedAsset: Asset;
+ availableAssets: Asset[];
+ selectedAssetAmount: BigNumber;
+ ethUsdPrice: BigNumber;
+ latestBuyQuote: BuyQuote;
+ latestErrorMessage: string;
+ affiliateInfo: AffiliateInfo;
+}
+
+export type State = DefaultState & PropsDerivedState & Partial<OptionalState>;
+
+export const DEFAULT_STATE: DefaultState = {
network: Network.Mainnet,
- selectedAssetAmount: undefined,
- availableAssets: undefined,
assetMetaDataMap,
buyOrderState: { processState: OrderProcessState.None },
- ethUsdPrice: undefined,
- latestBuyQuote: undefined,
- latestErrorMessage: undefined,
latestErrorDisplayStatus: DisplayStatus.Hidden,
quoteRequestState: AsyncProcessState.None,
- affiliateInfo: undefined,
};
-export const reducer = (state: State = INITIAL_STATE, action: Action): State => {
- switch (action.type) {
- case ActionTypes.UPDATE_ETH_USD_PRICE:
- return {
- ...state,
- ethUsdPrice: action.data,
- };
- case ActionTypes.UPDATE_SELECTED_ASSET_AMOUNT:
- return {
- ...state,
- selectedAssetAmount: action.data,
- };
- case ActionTypes.UPDATE_LATEST_BUY_QUOTE:
- const newBuyQuoteIfExists = action.data;
- const shouldUpdate =
- _.isUndefined(newBuyQuoteIfExists) || doesBuyQuoteMatchState(newBuyQuoteIfExists, state);
- if (shouldUpdate) {
+export const createReducer = (initialState: State) => {
+ const reducer = (state: State = initialState, action: Action): State => {
+ switch (action.type) {
+ case ActionTypes.UPDATE_ETH_USD_PRICE:
return {
...state,
- latestBuyQuote: newBuyQuoteIfExists,
- quoteRequestState: AsyncProcessState.Success,
+ ethUsdPrice: action.data,
};
- } else {
- return state;
- }
-
- case ActionTypes.SET_QUOTE_REQUEST_STATE_PENDING:
- return {
- ...state,
- latestBuyQuote: undefined,
- quoteRequestState: AsyncProcessState.Pending,
- };
- case ActionTypes.SET_QUOTE_REQUEST_STATE_FAILURE:
- return {
- ...state,
- latestBuyQuote: undefined,
- quoteRequestState: AsyncProcessState.Failure,
- };
- case ActionTypes.SET_BUY_ORDER_STATE_NONE:
- return {
- ...state,
- buyOrderState: { processState: OrderProcessState.None },
- };
- case ActionTypes.SET_BUY_ORDER_STATE_VALIDATING:
- return {
- ...state,
- buyOrderState: { processState: OrderProcessState.Validating },
- };
- case ActionTypes.SET_BUY_ORDER_STATE_PROCESSING:
- const processingData = action.data;
- const { startTimeUnix, expectedEndTimeUnix } = processingData;
- return {
- ...state,
- buyOrderState: {
- processState: OrderProcessState.Processing,
- txHash: processingData.txHash,
- progress: {
- startTimeUnix,
- expectedEndTimeUnix,
- },
- },
- };
- case ActionTypes.SET_BUY_ORDER_STATE_FAILURE:
- const failureTxHash = action.data;
- if ('txHash' in state.buyOrderState) {
- if (state.buyOrderState.txHash === failureTxHash) {
- const { txHash, progress } = state.buyOrderState;
+ case ActionTypes.UPDATE_SELECTED_ASSET_AMOUNT:
+ return {
+ ...state,
+ selectedAssetAmount: action.data,
+ };
+ case ActionTypes.UPDATE_LATEST_BUY_QUOTE:
+ const newBuyQuoteIfExists = action.data;
+ const shouldUpdate =
+ _.isUndefined(newBuyQuoteIfExists) || doesBuyQuoteMatchState(newBuyQuoteIfExists, state);
+ if (shouldUpdate) {
return {
...state,
- buyOrderState: {
- processState: OrderProcessState.Failure,
- txHash,
- progress,
- },
+ latestBuyQuote: newBuyQuoteIfExists,
+ quoteRequestState: AsyncProcessState.Success,
};
+ } else {
+ return state;
}
- }
- return state;
- case ActionTypes.SET_BUY_ORDER_STATE_SUCCESS:
- const successTxHash = action.data;
- if ('txHash' in state.buyOrderState) {
- if (state.buyOrderState.txHash === successTxHash) {
- const { txHash, progress } = state.buyOrderState;
- return {
- ...state,
- buyOrderState: {
- processState: OrderProcessState.Success,
- txHash,
- progress,
+
+ case ActionTypes.SET_QUOTE_REQUEST_STATE_PENDING:
+ return {
+ ...state,
+ latestBuyQuote: undefined,
+ quoteRequestState: AsyncProcessState.Pending,
+ };
+ case ActionTypes.SET_QUOTE_REQUEST_STATE_FAILURE:
+ return {
+ ...state,
+ latestBuyQuote: undefined,
+ quoteRequestState: AsyncProcessState.Failure,
+ };
+ case ActionTypes.SET_BUY_ORDER_STATE_NONE:
+ return {
+ ...state,
+ buyOrderState: { processState: OrderProcessState.None },
+ };
+ case ActionTypes.SET_BUY_ORDER_STATE_VALIDATING:
+ return {
+ ...state,
+ buyOrderState: { processState: OrderProcessState.Validating },
+ };
+ case ActionTypes.SET_BUY_ORDER_STATE_PROCESSING:
+ const processingData = action.data;
+ const { startTimeUnix, expectedEndTimeUnix } = processingData;
+ return {
+ ...state,
+ buyOrderState: {
+ processState: OrderProcessState.Processing,
+ txHash: processingData.txHash,
+ progress: {
+ startTimeUnix,
+ expectedEndTimeUnix,
},
- };
+ },
+ };
+ case ActionTypes.SET_BUY_ORDER_STATE_FAILURE:
+ const failureTxHash = action.data;
+ if ('txHash' in state.buyOrderState) {
+ if (state.buyOrderState.txHash === failureTxHash) {
+ const { txHash, progress } = state.buyOrderState;
+ return {
+ ...state,
+ buyOrderState: {
+ processState: OrderProcessState.Failure,
+ txHash,
+ progress,
+ },
+ };
+ }
}
- }
- return state;
- case ActionTypes.SET_ERROR_MESSAGE:
- return {
- ...state,
- latestErrorMessage: action.data,
- latestErrorDisplayStatus: DisplayStatus.Present,
- };
- case ActionTypes.HIDE_ERROR:
- return {
- ...state,
- latestErrorDisplayStatus: DisplayStatus.Hidden,
- };
- case ActionTypes.CLEAR_ERROR:
- return {
- ...state,
- latestErrorMessage: undefined,
- latestErrorDisplayStatus: DisplayStatus.Hidden,
- };
- case ActionTypes.UPDATE_SELECTED_ASSET:
- return {
- ...state,
- selectedAsset: action.data,
- };
- case ActionTypes.RESET_AMOUNT:
- return {
- ...state,
- latestBuyQuote: undefined,
- quoteRequestState: AsyncProcessState.None,
- buyOrderState: { processState: OrderProcessState.None },
- selectedAssetAmount: undefined,
- };
- case ActionTypes.SET_AVAILABLE_ASSETS:
- return {
- ...state,
- availableAssets: action.data,
- };
- default:
- return state;
- }
+ return state;
+ case ActionTypes.SET_BUY_ORDER_STATE_SUCCESS:
+ const successTxHash = action.data;
+ if ('txHash' in state.buyOrderState) {
+ if (state.buyOrderState.txHash === successTxHash) {
+ const { txHash, progress } = state.buyOrderState;
+ return {
+ ...state,
+ buyOrderState: {
+ processState: OrderProcessState.Success,
+ txHash,
+ progress,
+ },
+ };
+ }
+ }
+ return state;
+ case ActionTypes.SET_ERROR_MESSAGE:
+ return {
+ ...state,
+ latestErrorMessage: action.data,
+ latestErrorDisplayStatus: DisplayStatus.Present,
+ };
+ case ActionTypes.HIDE_ERROR:
+ return {
+ ...state,
+ latestErrorDisplayStatus: DisplayStatus.Hidden,
+ };
+ case ActionTypes.CLEAR_ERROR:
+ return {
+ ...state,
+ latestErrorMessage: undefined,
+ latestErrorDisplayStatus: DisplayStatus.Hidden,
+ };
+ case ActionTypes.UPDATE_SELECTED_ASSET:
+ return {
+ ...state,
+ selectedAsset: action.data,
+ };
+ case ActionTypes.RESET_AMOUNT:
+ return {
+ ...state,
+ latestBuyQuote: undefined,
+ quoteRequestState: AsyncProcessState.None,
+ buyOrderState: { processState: OrderProcessState.None },
+ selectedAssetAmount: undefined,
+ };
+ case ActionTypes.SET_AVAILABLE_ASSETS:
+ return {
+ ...state,
+ availableAssets: action.data,
+ };
+ default:
+ return state;
+ }
+ };
+ return reducer;
};
const doesBuyQuoteMatchState = (buyQuote: BuyQuote, state: State): boolean => {
diff --git a/packages/instant/src/redux/store.ts b/packages/instant/src/redux/store.ts
index 01deb8690..20710765d 100644
--- a/packages/instant/src/redux/store.ts
+++ b/packages/instant/src/redux/store.ts
@@ -2,12 +2,13 @@ import * as _ from 'lodash';
import { createStore, Store as ReduxStore } from 'redux';
import { devToolsEnhancer } from 'redux-devtools-extension/developmentOnly';
-import { reducer, State } from './reducer';
+import { createReducer, State } from './reducer';
export type Store = ReduxStore<State>;
export const store = {
- create: (state: State): Store => {
- return createStore(reducer, state, devToolsEnhancer({}));
+ create: (initialState: State): Store => {
+ const reducer = createReducer(initialState);
+ return createStore(reducer, initialState, devToolsEnhancer({}));
},
};
diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts
index 04469f8b9..0ab227310 100644
--- a/packages/instant/src/types.ts
+++ b/packages/instant/src/types.ts
@@ -1,4 +1,7 @@
-import { AssetProxyId, ObjectMap } from '@0x/types';
+import { AssetBuyer } from '@0x/asset-buyer';
+import { AssetProxyId, ObjectMap, SignedOrder } from '@0x/types';
+import { Web3Wrapper } from '@0x/web3-wrapper';
+import { Provider } from 'ethereum-types';
// Reusable
export type Maybe<T> = T | undefined;
@@ -89,3 +92,24 @@ export interface AffiliateInfo {
feeRecipient: string;
feePercentage: number;
}
+
+/**
+ * Our provider may be of 3 different types:
+ * Props: the provider was passed in a prop to the component
+ * Injected: the provider was injected into `window`
+ * FallbackEmptyWallet: the provider has no wallet but responds to data requests using infura
+ */
+export enum ProviderType {
+ Props = 'PROPS',
+ Injected = 'INJECTED',
+ FallbackEmptyWallet = 'FALLBACK_EMPTY_WALLET',
+}
+
+export interface ProviderState {
+ type: ProviderType;
+ provider: Provider;
+ assetBuyer: AssetBuyer;
+ web3Wrapper: Web3Wrapper;
+}
+
+export type OrderSource = string | SignedOrder[];
diff --git a/packages/instant/src/util/asset_buyer_factory.ts b/packages/instant/src/util/asset_buyer_factory.ts
new file mode 100644
index 000000000..5ba46223c
--- /dev/null
+++ b/packages/instant/src/util/asset_buyer_factory.ts
@@ -0,0 +1,17 @@
+import { AssetBuyer, AssetBuyerOpts } from '@0x/asset-buyer';
+import { Provider } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { Network, OrderSource } from '../types';
+
+export const assetBuyerFactory = {
+ getAssetBuyer: (provider: Provider, orderSource: OrderSource, network: Network): AssetBuyer => {
+ const assetBuyerOptions: Partial<AssetBuyerOpts> = {
+ networkId: network,
+ };
+ const assetBuyer = _.isString(orderSource)
+ ? AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(provider, orderSource, assetBuyerOptions)
+ : AssetBuyer.getAssetBuyerForProvidedOrders(provider, orderSource, assetBuyerOptions);
+ return assetBuyer;
+ },
+};
diff --git a/packages/instant/src/util/injected_provider.ts b/packages/instant/src/util/injected_provider.ts
deleted file mode 100644
index 40f9e2da5..000000000
--- a/packages/instant/src/util/injected_provider.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { Provider } from 'ethereum-types';
-import * as _ from 'lodash';
-
-export const getInjectedProvider = (): Provider => {
- const injectedProviderIfExists = (window as any).ethereum;
- if (!_.isUndefined(injectedProviderIfExists)) {
- // TODO: call enable here when implementing wallet connection flow
- return injectedProviderIfExists;
- }
- const injectedWeb3IfExists = (window as any).web3;
- if (!_.isUndefined(injectedWeb3IfExists.currentProvider)) {
- return injectedWeb3IfExists.currentProvider;
- } else {
- throw new Error(`No injected web3 found`);
- }
-};
diff --git a/packages/instant/src/util/provider_factory.ts b/packages/instant/src/util/provider_factory.ts
new file mode 100644
index 000000000..603f7674d
--- /dev/null
+++ b/packages/instant/src/util/provider_factory.ts
@@ -0,0 +1,34 @@
+import { EmptyWalletSubprovider, RPCSubprovider, Web3ProviderEngine } from '@0x/subproviders';
+import { Provider } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { BLOCK_POLLING_INTERVAL_MS, ETHEREUM_NODE_URL_BY_NETWORK } from '../constants';
+import { Maybe, Network } from '../types';
+
+export const providerFactory = {
+ getInjectedProviderIfExists: (): Maybe<Provider> => {
+ const injectedProviderIfExists = (window as any).ethereum;
+ if (!_.isUndefined(injectedProviderIfExists)) {
+ return injectedProviderIfExists;
+ }
+ const injectedWeb3IfExists = (window as any).web3;
+ if (!_.isUndefined(injectedWeb3IfExists) && !_.isUndefined(injectedWeb3IfExists.currentProvider)) {
+ return injectedWeb3IfExists.currentProvider;
+ }
+ return undefined;
+ },
+ getFallbackNoSigningProvider: (network: Network): Provider => {
+ const providerEngine = new Web3ProviderEngine({
+ pollingInterval: BLOCK_POLLING_INTERVAL_MS,
+ });
+ // Intercept calls to `eth_accounts` and always return empty
+ providerEngine.addProvider(new EmptyWalletSubprovider());
+ // Construct an RPC subprovider, all data based requests will be sent via the RPCSubprovider
+ // TODO(bmillman): make this more resilient to infura failures
+ const rpcUrl = ETHEREUM_NODE_URL_BY_NETWORK[network];
+ providerEngine.addProvider(new RPCSubprovider(rpcUrl));
+ // // Start the Provider Engine
+ providerEngine.start();
+ return providerEngine;
+ },
+};