feat(instant): fetch account address at startup and drive account state changes
This commit is contained in:
@@ -16,6 +16,7 @@ import { Button } from './ui/button';
|
||||
import { Text } from './ui/text';
|
||||
|
||||
export interface BuyButtonProps {
|
||||
accountAddress?: string;
|
||||
buyQuote?: BuyQuote;
|
||||
assetBuyer: AssetBuyer;
|
||||
affiliateInfo?: AffiliateInfo;
|
||||
@@ -34,7 +35,8 @@ export class BuyButton extends React.Component<BuyButtonProps> {
|
||||
onBuyFailure: util.boundNoop,
|
||||
};
|
||||
public render(): React.ReactNode {
|
||||
const shouldDisableButton = _.isUndefined(this.props.buyQuote);
|
||||
const { buyQuote, accountAddress } = this.props;
|
||||
const shouldDisableButton = _.isUndefined(buyQuote) || _.isUndefined(accountAddress);
|
||||
return (
|
||||
<Button width="100%" onClick={this._handleClick} isDisabled={shouldDisableButton}>
|
||||
<Text fontColor={ColorOption.white} fontWeight={600} fontSize="20px">
|
||||
@@ -45,30 +47,25 @@ 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)) {
|
||||
const { buyQuote, assetBuyer, affiliateInfo, accountAddress } = this.props;
|
||||
if (_.isUndefined(buyQuote) || _.isUndefined(accountAddress)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.onValidationPending(buyQuote);
|
||||
|
||||
// TODO(bmillman): move address and balance fetching to the async state
|
||||
// TODO(bmillman): move balance fetching to the async state and get rid of web3 wrapper here
|
||||
const web3Wrapper = new Web3Wrapper(assetBuyer.provider);
|
||||
const takerAddress = await getBestAddress(web3Wrapper);
|
||||
|
||||
const hasSufficientEth = await balanceUtil.hasSufficientEth(takerAddress, buyQuote, web3Wrapper);
|
||||
const hasSufficientEth = await balanceUtil.hasSufficientEth(accountAddress, buyQuote, web3Wrapper);
|
||||
if (!hasSufficientEth) {
|
||||
this.props.onValidationFail(buyQuote, ZeroExInstantError.InsufficientETH);
|
||||
return;
|
||||
}
|
||||
|
||||
let txHash: string | undefined;
|
||||
const gasInfo = await gasPriceEstimator.getGasInfoAsync();
|
||||
const feeRecipient = oc(affiliateInfo).feeRecipient();
|
||||
try {
|
||||
txHash = await assetBuyer.executeBuyQuoteAsync(buyQuote, {
|
||||
feeRecipient,
|
||||
takerAddress,
|
||||
takerAddress: accountAddress,
|
||||
gasPrice: gasInfo.gasPriceInWei,
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -83,7 +80,6 @@ export class BuyButton extends React.Component<BuyButtonProps> {
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
const startTimeUnix = new Date().getTime();
|
||||
const expectedEndTimeUnix = startTimeUnix + gasInfo.estimatedTimeMs;
|
||||
this.props.onBuyProcessing(buyQuote, txHash, startTimeUnix, expectedEndTimeUnix);
|
||||
@@ -96,7 +92,6 @@ export class BuyButton extends React.Component<BuyButtonProps> {
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
this.props.onBuySuccess(buyQuote, txHash);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Flex } from './ui/flex';
|
||||
import { Text } from './ui/text';
|
||||
|
||||
export interface BuyOrderStateButtonProps {
|
||||
accountAddress?: string;
|
||||
buyQuote?: BuyQuote;
|
||||
buyOrderProcessingState: OrderProcessState;
|
||||
assetBuyer: AssetBuyer;
|
||||
@@ -52,6 +53,7 @@ export const BuyOrderStateButtons: React.StatelessComponent<BuyOrderStateButtonP
|
||||
|
||||
return (
|
||||
<BuyButton
|
||||
accountAddress={props.accountAddress}
|
||||
buyQuote={props.buyQuote}
|
||||
assetBuyer={props.assetBuyer}
|
||||
affiliateInfo={props.affiliateInfo}
|
||||
|
||||
@@ -92,12 +92,12 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
asyncData.fetchAvailableAssetDatasAndDispatchToStore(this._store);
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
asyncData.fetchAccountInfoAndDispatchToStore(this._store);
|
||||
// warm up the gas price estimator cache just in case we can't
|
||||
// grab the gas price estimate when submitting the transaction
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
gasPriceEstimator.getGasInfoAsync();
|
||||
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
this._flashErrorIfWrongNetwork();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { Network } from './types';
|
||||
import { AccountNotReady, AccountState, Network } from './types';
|
||||
|
||||
export const BIG_NUMBER_ZERO = new BigNumber(0);
|
||||
export const ETH_DECIMALS = 18;
|
||||
@@ -21,3 +21,15 @@ export const ETHEREUM_NODE_URL_BY_NETWORK = {
|
||||
[Network.Kovan]: 'https://kovan.infura.io/',
|
||||
};
|
||||
export const BLOCK_POLLING_INTERVAL_MS = 10000; // 10s
|
||||
export const NO_ACCOUNT: AccountNotReady = {
|
||||
state: AccountState.None,
|
||||
};
|
||||
export const LOADING_ACCOUNT: AccountNotReady = {
|
||||
state: AccountState.Loading,
|
||||
};
|
||||
export const LOCKED_ACCOUNT: AccountNotReady = {
|
||||
state: AccountState.Locked,
|
||||
};
|
||||
export const ERROR_ACCOUNT: AccountNotReady = {
|
||||
state: AccountState.Error,
|
||||
};
|
||||
|
||||
@@ -7,11 +7,12 @@ import { Dispatch } from 'redux';
|
||||
import { BuyOrderStateButtons } from '../components/buy_order_state_buttons';
|
||||
import { Action, actions } from '../redux/actions';
|
||||
import { State } from '../redux/reducer';
|
||||
import { AffiliateInfo, OrderProcessState, ZeroExInstantError } from '../types';
|
||||
import { AccountState, AffiliateInfo, OrderProcessState, ZeroExInstantError } from '../types';
|
||||
import { errorFlasher } from '../util/error_flasher';
|
||||
import { etherscanUtil } from '../util/etherscan';
|
||||
|
||||
interface ConnectedState {
|
||||
accountAddress?: string;
|
||||
buyQuote?: BuyQuote;
|
||||
buyOrderProcessingState: OrderProcessState;
|
||||
assetBuyer: AssetBuyer;
|
||||
@@ -31,7 +32,10 @@ interface ConnectedDispatch {
|
||||
export interface SelectedAssetBuyOrderStateButtons {}
|
||||
const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyOrderStateButtons): ConnectedState => {
|
||||
const assetBuyer = state.providerState.assetBuyer;
|
||||
const account = state.providerState.account;
|
||||
const accountAddress = account.state === AccountState.Ready ? account.address : undefined;
|
||||
return {
|
||||
accountAddress,
|
||||
buyOrderProcessingState: state.buyOrderState.processState,
|
||||
assetBuyer,
|
||||
buyQuote: state.latestBuyQuote,
|
||||
|
||||
@@ -21,6 +21,10 @@ function createAction<T extends string, P>(type: T, data?: P): PlainAction<T> |
|
||||
}
|
||||
|
||||
export enum ActionTypes {
|
||||
SET_ACCOUNT_STATE_LOADING = 'SET_ACCOUNT_STATE_LOADING',
|
||||
SET_ACCOUNT_STATE_LOCKED = 'SET_ACCOUNT_STATE_LOCKED',
|
||||
SET_ACCOUNT_STATE_ERROR = 'SET_ACCOUNT_STATE_ERROR',
|
||||
SET_ACCOUNT_STATE_READY = 'SET_ACCOUNT_STATE_READY',
|
||||
UPDATE_ETH_USD_PRICE = 'UPDATE_ETH_USD_PRICE',
|
||||
UPDATE_SELECTED_ASSET_AMOUNT = 'UPDATE_SELECTED_ASSET_AMOUNT',
|
||||
SET_BUY_ORDER_STATE_NONE = 'SET_BUY_ORDER_STATE_NONE',
|
||||
@@ -40,6 +44,10 @@ export enum ActionTypes {
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
setAccountStateLoading: () => createAction(ActionTypes.SET_ACCOUNT_STATE_LOADING),
|
||||
setAccountStateLocked: () => createAction(ActionTypes.SET_ACCOUNT_STATE_LOCKED),
|
||||
setAccountStateError: () => createAction(ActionTypes.SET_ACCOUNT_STATE_ERROR),
|
||||
setAccountStateReady: (address: string) => createAction(ActionTypes.SET_ACCOUNT_STATE_READY, address),
|
||||
updateEthUsdPrice: (price?: BigNumber) => createAction(ActionTypes.UPDATE_ETH_USD_PRICE, price),
|
||||
updateSelectedAssetAmount: (amount?: BigNumber) => createAction(ActionTypes.UPDATE_SELECTED_ASSET_AMOUNT, amount),
|
||||
setBuyOrderStateNone: () => createAction(ActionTypes.SET_BUY_ORDER_STATE_NONE),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { BIG_NUMBER_ZERO } from '../constants';
|
||||
import { AccountState } from '../types';
|
||||
import { assetUtils } from '../util/asset';
|
||||
import { coinbaseApi } from '../util/coinbase_api';
|
||||
import { errorFlasher } from '../util/error_flasher';
|
||||
@@ -33,4 +34,24 @@ export const asyncData = {
|
||||
store.dispatch(actions.setAvailableAssets([]));
|
||||
}
|
||||
},
|
||||
fetchAccountInfoAndDispatchToStore: async (store: Store) => {
|
||||
const { providerState } = store.getState();
|
||||
const web3Wrapper = providerState.web3Wrapper;
|
||||
if (providerState.account.state !== AccountState.Loading) {
|
||||
store.dispatch(actions.setAccountStateLoading());
|
||||
}
|
||||
let availableAddresses: string[];
|
||||
try {
|
||||
availableAddresses = await web3Wrapper.getAvailableAddressesAsync();
|
||||
} catch (e) {
|
||||
store.dispatch(actions.setAccountStateError());
|
||||
return;
|
||||
}
|
||||
if (!_.isEmpty(availableAddresses)) {
|
||||
const activeAddress = availableAddresses[0];
|
||||
store.dispatch(actions.setAccountStateReady(activeAddress));
|
||||
} else {
|
||||
store.dispatch(actions.setAccountStateLocked());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,8 +4,12 @@ import { BigNumber } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { ERROR_ACCOUNT, LOADING_ACCOUNT, LOCKED_ACCOUNT } from '../constants';
|
||||
import { assetMetaDataMap } from '../data/asset_meta_data_map';
|
||||
import {
|
||||
Account,
|
||||
AccountReady,
|
||||
AccountState,
|
||||
AffiliateInfo,
|
||||
Asset,
|
||||
AssetMetaData,
|
||||
@@ -57,6 +61,18 @@ export const DEFAULT_STATE: DefaultState = {
|
||||
export const createReducer = (initialState: State) => {
|
||||
const reducer = (state: State = initialState, action: Action): State => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.SET_ACCOUNT_STATE_LOADING:
|
||||
return reduceStateWithAccount(state, LOADING_ACCOUNT);
|
||||
case ActionTypes.SET_ACCOUNT_STATE_LOCKED:
|
||||
return reduceStateWithAccount(state, LOCKED_ACCOUNT);
|
||||
case ActionTypes.SET_ACCOUNT_STATE_ERROR:
|
||||
return reduceStateWithAccount(state, ERROR_ACCOUNT);
|
||||
case ActionTypes.SET_ACCOUNT_STATE_READY:
|
||||
const account: AccountReady = {
|
||||
state: AccountState.Ready,
|
||||
address: action.data,
|
||||
};
|
||||
return reduceStateWithAccount(state, account);
|
||||
case ActionTypes.UPDATE_ETH_USD_PRICE:
|
||||
return {
|
||||
...state,
|
||||
@@ -80,7 +96,6 @@ export const createReducer = (initialState: State) => {
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
|
||||
case ActionTypes.SET_QUOTE_REQUEST_STATE_PENDING:
|
||||
return {
|
||||
...state,
|
||||
@@ -191,6 +206,18 @@ export const createReducer = (initialState: State) => {
|
||||
return reducer;
|
||||
};
|
||||
|
||||
const reduceStateWithAccount = (state: State, account: Account) => {
|
||||
const oldProviderState = state.providerState;
|
||||
const newProviderState: ProviderState = {
|
||||
...oldProviderState,
|
||||
account,
|
||||
};
|
||||
return {
|
||||
...state,
|
||||
providerState: newProviderState,
|
||||
};
|
||||
};
|
||||
|
||||
const doesBuyQuoteMatchState = (buyQuote: BuyQuote, state: State): boolean => {
|
||||
const selectedAssetIfExists = state.selectedAsset;
|
||||
const selectedAssetAmountIfExists = state.selectedAssetAmount;
|
||||
|
||||
@@ -2,18 +2,12 @@ import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import { Provider } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { LOADING_ACCOUNT, NO_ACCOUNT } from '../constants';
|
||||
import { AccountNotReady, AccountState, Maybe, Network, OrderSource, ProviderState } from '../types';
|
||||
|
||||
import { assetBuyerFactory } from './asset_buyer_factory';
|
||||
import { providerFactory } from './provider_factory';
|
||||
|
||||
const LOADING_ACCOUNT: AccountNotReady = {
|
||||
state: AccountState.Loading,
|
||||
};
|
||||
const NO_ACCOUNT: AccountNotReady = {
|
||||
state: AccountState.None,
|
||||
};
|
||||
|
||||
export const providerStateFactory = {
|
||||
getInitialProviderState: (orderSource: OrderSource, network: Network, provider?: Provider): ProviderState => {
|
||||
if (!_.isUndefined(provider)) {
|
||||
|
||||
Reference in New Issue
Block a user