diff options
author | fragosti <francesco.agosti93@gmail.com> | 2018-10-26 09:57:30 +0800 |
---|---|---|
committer | fragosti <francesco.agosti93@gmail.com> | 2018-10-26 09:57:30 +0800 |
commit | 30809e646be02025d6f9e9ed0ff214d9ace681c8 (patch) | |
tree | 2bd1446ac81a2319a672709ca8ef9cb2dd07c643 /packages/instant | |
parent | d5d99b9d2e3c793a95c68c1035246644b3ae80c6 (diff) | |
parent | 4a96dbe085004be49dbbaa435d4552a9c920d823 (diff) | |
download | dexon-sol-tools-30809e646be02025d6f9e9ed0ff214d9ace681c8.tar dexon-sol-tools-30809e646be02025d6f9e9ed0ff214d9ace681c8.tar.gz dexon-sol-tools-30809e646be02025d6f9e9ed0ff214d9ace681c8.tar.bz2 dexon-sol-tools-30809e646be02025d6f9e9ed0ff214d9ace681c8.tar.lz dexon-sol-tools-30809e646be02025d6f9e9ed0ff214d9ace681c8.tar.xz dexon-sol-tools-30809e646be02025d6f9e9ed0ff214d9ace681c8.tar.zst dexon-sol-tools-30809e646be02025d6f9e9ed0ff214d9ace681c8.zip |
Merge branch 'development' of https://github.com/0xProject/0x-monorepo into feature/instant/input-fees-rounding
Diffstat (limited to 'packages/instant')
12 files changed, 113 insertions, 51 deletions
diff --git a/packages/instant/src/components/buy_button.tsx b/packages/instant/src/components/buy_button.tsx index 9c42f3d87..a70269dde 100644 --- a/packages/instant/src/components/buy_button.tsx +++ b/packages/instant/src/components/buy_button.tsx @@ -1,7 +1,8 @@ -import { AssetBuyer, BuyQuote } from '@0x/asset-buyer'; +import { AssetBuyer, AssetBuyerError, BuyQuote } from '@0x/asset-buyer'; import * as _ from 'lodash'; import * as React from 'react'; +import { WEB_3_WRAPPER_TRANSACTION_FAILED_ERROR_MSG_PREFIX } from '../constants'; import { ColorOption } from '../style/theme'; import { util } from '../util/util'; import { web3Wrapper } from '../util/web3_wrapper'; @@ -11,9 +12,11 @@ import { Button, Text } from './ui'; export interface BuyButtonProps { buyQuote?: BuyQuote; assetBuyer?: AssetBuyer; - onClick: (buyQuote: BuyQuote) => void; - onBuySuccess: (buyQuote: BuyQuote, txnHash: string) => void; - onBuyFailure: (buyQuote: BuyQuote, tnxHash?: string) => void; + onAwaitingSignature: (buyQuote: BuyQuote) => void; + onSignatureDenied: (buyQuote: BuyQuote, preventedError: Error) => void; + onBuyProcessing: (buyQuote: BuyQuote, txHash: string) => void; + onBuySuccess: (buyQuote: BuyQuote, txHash: string) => void; + onBuyFailure: (buyQuote: BuyQuote, txHash: string) => void; } export class BuyButton extends React.Component<BuyButtonProps> { @@ -34,17 +37,33 @@ export class BuyButton extends React.Component<BuyButtonProps> { } private readonly _handleClick = async () => { // The button is disabled when there is no buy quote anyway. - if (_.isUndefined(this.props.buyQuote) || _.isUndefined(this.props.assetBuyer)) { + const { buyQuote, assetBuyer } = this.props; + if (_.isUndefined(buyQuote) || _.isUndefined(assetBuyer)) { return; } - this.props.onClick(this.props.buyQuote); - let txnHash; + + let txHash: string | undefined; + this.props.onAwaitingSignature(buyQuote); + try { + txHash = await assetBuyer.executeBuyQuoteAsync(buyQuote); + } catch (e) { + if (e instanceof Error && e.message === AssetBuyerError.SignatureRequestDenied) { + this.props.onSignatureDenied(buyQuote, e); + return; + } + throw e; + } + + this.props.onBuyProcessing(buyQuote, txHash); try { - txnHash = await this.props.assetBuyer.executeBuyQuoteAsync(this.props.buyQuote); - const txnReceipt = await web3Wrapper.awaitTransactionSuccessAsync(txnHash); - this.props.onBuySuccess(this.props.buyQuote, txnReceipt.transactionHash); - } catch { - this.props.onBuyFailure(this.props.buyQuote, txnHash); + await web3Wrapper.awaitTransactionSuccessAsync(txHash); + } catch (e) { + if (e instanceof Error && e.message.startsWith(WEB_3_WRAPPER_TRANSACTION_FAILED_ERROR_MSG_PREFIX)) { + this.props.onBuyFailure(buyQuote, txHash); + return; + } + throw e; } + this.props.onBuySuccess(buyQuote, txHash); }; } diff --git a/packages/instant/src/components/buy_order_state_button.tsx b/packages/instant/src/components/buy_order_state_button.tsx index 5bc965c7d..44115e5a1 100644 --- a/packages/instant/src/components/buy_order_state_button.tsx +++ b/packages/instant/src/components/buy_order_state_button.tsx @@ -4,18 +4,21 @@ import { PlacingOrderButton } from '../components/placing_order_button'; import { SelectedAssetBuyButton } from '../containers/selected_asset_buy_button'; import { SelectedAssetRetryButton } from '../containers/selected_asset_retry_button'; import { SelectedAssetViewTransactionButton } from '../containers/selected_asset_view_transaction_button'; -import { AsyncProcessState } from '../types'; +import { OrderProcessState } from '../types'; export interface BuyOrderStateButtonProps { - buyOrderProcessingState: AsyncProcessState; + buyOrderProcessingState: OrderProcessState; } export const BuyOrderStateButton: React.StatelessComponent<BuyOrderStateButtonProps> = props => { - if (props.buyOrderProcessingState === AsyncProcessState.FAILURE) { + if (props.buyOrderProcessingState === OrderProcessState.FAILURE) { return <SelectedAssetRetryButton />; - } else if (props.buyOrderProcessingState === AsyncProcessState.SUCCESS) { + } else if ( + props.buyOrderProcessingState === OrderProcessState.SUCCESS || + props.buyOrderProcessingState === OrderProcessState.PROCESSING + ) { return <SelectedAssetViewTransactionButton />; - } else if (props.buyOrderProcessingState === AsyncProcessState.PENDING) { + } else if (props.buyOrderProcessingState === OrderProcessState.AWAITING_SIGNATURE) { return <PlacingOrderButton />; } diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index df9856277..1ef276ff3 100644 --- a/packages/instant/src/components/instant_heading.tsx +++ b/packages/instant/src/components/instant_heading.tsx @@ -4,12 +4,13 @@ import * as React from 'react'; import { SelectedERC20AssetAmountInput } from '../containers/selected_erc20_asset_amount_input'; import { ColorOption } from '../style/theme'; -import { AsyncProcessState, OrderState } from '../types'; +import { AsyncProcessState, OrderProcessState, OrderState } from '../types'; import { format } from '../util/format'; import { AmountPlaceholder } from './amount_placeholder'; import { Container, Flex, Text } from './ui'; import { Icon } from './ui/icon'; +import { Spinner } from './ui/spinner'; export interface InstantHeadingProps { selectedAssetAmount?: BigNumber; @@ -70,9 +71,11 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> { private _renderIcon(): React.ReactNode { const processState = this.props.buyOrderState.processState; - if (processState === AsyncProcessState.FAILURE) { + if (processState === OrderProcessState.FAILURE) { return <Icon icon={'failed'} width={ICON_WIDTH} height={ICON_HEIGHT} color={ICON_COLOR} />; - } else if (processState === AsyncProcessState.SUCCESS) { + } else if (processState === OrderProcessState.PROCESSING) { + return <Spinner widthPx={ICON_HEIGHT} heightPx={ICON_HEIGHT} />; + } else if (processState === OrderProcessState.SUCCESS) { return <Icon icon={'success'} width={ICON_WIDTH} height={ICON_HEIGHT} color={ICON_COLOR} />; } return undefined; @@ -80,9 +83,11 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> { private _renderTopText(): React.ReactNode { const processState = this.props.buyOrderState.processState; - if (processState === AsyncProcessState.FAILURE) { + if (processState === OrderProcessState.FAILURE) { return 'Order failed'; - } else if (processState === AsyncProcessState.SUCCESS) { + } else if (processState === OrderProcessState.PROCESSING) { + return 'Processing Order...'; + } else if (processState === OrderProcessState.SUCCESS) { return 'Tokens received!'; } diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts index 31491c80a..48d0d4aa2 100644 --- a/packages/instant/src/constants.ts +++ b/packages/instant/src/constants.ts @@ -2,3 +2,4 @@ import { BigNumber } from '@0x/utils'; export const BIG_NUMBER_ZERO = new BigNumber(0); export const ethDecimals = 18; export const DEFAULT_ZERO_EX_CONTAINER_SELECTOR = '#zeroExInstantContainer'; +export const WEB_3_WRAPPER_TRANSACTION_FAILED_ERROR_MSG_PREFIX = 'Transaction failed'; diff --git a/packages/instant/src/containers/selected_asset_buy_button.ts b/packages/instant/src/containers/selected_asset_buy_button.ts index 71d2b8cf0..adcbd61bc 100644 --- a/packages/instant/src/containers/selected_asset_buy_button.ts +++ b/packages/instant/src/containers/selected_asset_buy_button.ts @@ -6,7 +6,7 @@ import { Dispatch } from 'redux'; import { Action, actions } from '../redux/actions'; import { State } from '../redux/reducer'; -import { AsyncProcessState } from '../types'; +import { OrderProcessState, OrderState } from '../types'; import { BuyButton } from '../components/buy_button'; @@ -18,9 +18,11 @@ interface ConnectedState { } interface ConnectedDispatch { - onClick: (buyQuote: BuyQuote) => void; - onBuySuccess: (buyQuote: BuyQuote, txnHash: string) => void; - onBuyFailure: (buyQuote: BuyQuote) => void; + onAwaitingSignature: (buyQuote: BuyQuote) => void; + onSignatureDenied: (buyQuote: BuyQuote, error: Error) => void; + onBuyProcessing: (buyQuote: BuyQuote, txHash: string) => void; + onBuySuccess: (buyQuote: BuyQuote, txHash: string) => void; + onBuyFailure: (buyQuote: BuyQuote, txHash: string) => void; } const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyButtonProps): ConnectedState => ({ @@ -29,10 +31,22 @@ const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyButtonProps): }); const mapDispatchToProps = (dispatch: Dispatch<Action>, ownProps: SelectedAssetBuyButtonProps): ConnectedDispatch => ({ - onClick: buyQuote => dispatch(actions.updateBuyOrderState({ processState: AsyncProcessState.PENDING })), - onBuySuccess: (buyQuote: BuyQuote, txnHash: string) => - dispatch(actions.updateBuyOrderState({ processState: AsyncProcessState.SUCCESS, txnHash })), - onBuyFailure: buyQuote => dispatch(actions.updateBuyOrderState({ processState: AsyncProcessState.FAILURE })), + onAwaitingSignature: (buyQuote: BuyQuote) => { + const newOrderState: OrderState = { processState: OrderProcessState.AWAITING_SIGNATURE }; + dispatch(actions.updateBuyOrderState(newOrderState)); + }, + onBuyProcessing: (buyQuote: BuyQuote, txHash: string) => { + const newOrderState: OrderState = { processState: OrderProcessState.PROCESSING, txHash }; + dispatch(actions.updateBuyOrderState(newOrderState)); + }, + onBuySuccess: (buyQuote: BuyQuote, txHash: string) => + dispatch(actions.updateBuyOrderState({ processState: OrderProcessState.SUCCESS, txHash })), + onBuyFailure: (buyQuote: BuyQuote, txHash: string) => + dispatch(actions.updateBuyOrderState({ processState: OrderProcessState.FAILURE, txHash })), + onSignatureDenied: (buyQuote, error) => { + dispatch(actions.resetAmount()); + dispatch(actions.setError(error)); + }, }); export const SelectedAssetBuyButton: React.ComponentClass<SelectedAssetBuyButtonProps> = connect( diff --git a/packages/instant/src/containers/selected_asset_buy_order_state_button.tsx b/packages/instant/src/containers/selected_asset_buy_order_state_button.tsx index f3efbb5d2..7faa79912 100644 --- a/packages/instant/src/containers/selected_asset_buy_order_state_button.tsx +++ b/packages/instant/src/containers/selected_asset_buy_order_state_button.tsx @@ -3,12 +3,12 @@ import * as React from 'react'; import { connect } from 'react-redux'; import { State } from '../redux/reducer'; -import { AsyncProcessState } from '../types'; +import { OrderProcessState } from '../types'; import { BuyOrderStateButton } from '../components/buy_order_state_button'; interface ConnectedState { - buyOrderProcessingState: AsyncProcessState; + buyOrderProcessingState: OrderProcessState; } export interface SelectedAssetButtonProps {} const mapStateToProps = (state: State, _ownProps: SelectedAssetButtonProps): ConnectedState => ({ diff --git a/packages/instant/src/containers/selected_asset_view_transaction_button.tsx b/packages/instant/src/containers/selected_asset_view_transaction_button.tsx index 6f42b9f85..064b877be 100644 --- a/packages/instant/src/containers/selected_asset_view_transaction_button.tsx +++ b/packages/instant/src/containers/selected_asset_view_transaction_button.tsx @@ -5,7 +5,7 @@ import { connect } from 'react-redux'; import { State } from '../redux/reducer'; import { ViewTransactionButton } from '../components/view_transaction_button'; -import { AsyncProcessState } from '../types'; +import { OrderProcessState } from '../types'; import { etherscanUtil } from '../util/etherscan'; export interface SelectedAssetViewTransactionButtonProps {} @@ -16,9 +16,13 @@ interface ConnectedState { const mapStateToProps = (state: State, _ownProps: {}): ConnectedState => ({ onClick: () => { - if (state.assetBuyer && state.buyOrderState.processState === AsyncProcessState.SUCCESS) { + if ( + state.assetBuyer && + (state.buyOrderState.processState === OrderProcessState.PROCESSING || + state.buyOrderState.processState === OrderProcessState.SUCCESS) + ) { const etherscanUrl = etherscanUtil.getEtherScanTxnAddressIfExists( - state.buyOrderState.txnHash, + state.buyOrderState.txHash, state.assetBuyer.networkId, ); if (etherscanUrl) { 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 078f96cd4..ee76e9d66 100644 --- a/packages/instant/src/containers/selected_erc20_asset_amount_input.ts +++ b/packages/instant/src/containers/selected_erc20_asset_amount_input.ts @@ -10,7 +10,7 @@ import { Dispatch } from 'redux'; import { Action, actions } from '../redux/actions'; import { State } from '../redux/reducer'; import { ColorOption } from '../style/theme'; -import { AsyncProcessState, ERC20Asset } from '../types'; +import { ERC20Asset, OrderProcessState } from '../types'; import { BigNumberInput } from '../util/big_number_input'; import { errorUtil } from '../util/error'; @@ -91,7 +91,7 @@ const mapDispatchToProps = ( // invalidate the last buy quote. dispatch(actions.updateLatestBuyQuote(undefined)); // reset our buy state - dispatch(actions.updateBuyOrderState({ processState: AsyncProcessState.NONE })); + dispatch(actions.updateBuyOrderState({ processState: OrderProcessState.NONE })); if (!_.isUndefined(value) && !_.isUndefined(asset) && !_.isUndefined(assetBuyer)) { // even if it's debounced, give them the illusion it's loading diff --git a/packages/instant/src/redux/reducer.ts b/packages/instant/src/redux/reducer.ts index 665cba257..d7e5bdfb5 100644 --- a/packages/instant/src/redux/reducer.ts +++ b/packages/instant/src/redux/reducer.ts @@ -4,7 +4,15 @@ import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import { assetMetaDataMap } from '../data/asset_meta_data_map'; -import { Asset, AssetMetaData, AsyncProcessState, DisplayStatus, Network, OrderState } from '../types'; +import { + Asset, + AssetMetaData, + AsyncProcessState, + DisplayStatus, + Network, + OrderProcessState, + OrderState, +} from '../types'; import { assetUtils } from '../util/asset'; import { BigNumberInput } from '../util/big_number_input'; @@ -28,7 +36,7 @@ export const INITIAL_STATE: State = { network: Network.Mainnet, selectedAssetAmount: undefined, assetMetaDataMap, - buyOrderState: { processState: AsyncProcessState.NONE }, + buyOrderState: { processState: OrderProcessState.NONE }, ethUsdPrice: undefined, latestBuyQuote: undefined, latestError: undefined, @@ -107,7 +115,7 @@ export const reducer = (state: State = INITIAL_STATE, action: Action): State => ...state, latestBuyQuote: undefined, quoteRequestState: AsyncProcessState.NONE, - buyOrderState: { processState: AsyncProcessState.NONE }, + buyOrderState: { processState: OrderProcessState.NONE }, selectedAssetAmount: undefined, }; default: diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts index c5521c63c..c63371fb4 100644 --- a/packages/instant/src/types.ts +++ b/packages/instant/src/types.ts @@ -8,18 +8,22 @@ export enum AsyncProcessState { FAILURE = 'Failure', } -interface RegularOrderState { - processState: AsyncProcessState.NONE | AsyncProcessState.PENDING; +export enum OrderProcessState { + NONE = 'None', + AWAITING_SIGNATURE = 'Awaiting Signature', + PROCESSING = 'Processing', + SUCCESS = 'Success', + FAILURE = 'Failure', } -interface SuccessfulOrderState { - processState: AsyncProcessState.SUCCESS; - txnHash: string; + +interface OrderStatePreTx { + processState: OrderProcessState.NONE | OrderProcessState.AWAITING_SIGNATURE; } -interface FailureOrderState { - processState: AsyncProcessState.FAILURE; - txnHash?: string; +interface OrderStatePostTx { + processState: OrderProcessState.PROCESSING | OrderProcessState.SUCCESS | OrderProcessState.FAILURE; + txHash: string; } -export type OrderState = RegularOrderState | SuccessfulOrderState | FailureOrderState; +export type OrderState = OrderStatePreTx | OrderStatePostTx; export enum DisplayStatus { Present, diff --git a/packages/instant/src/util/error.ts b/packages/instant/src/util/error.ts index 40fd24c7e..64c1f4885 100644 --- a/packages/instant/src/util/error.ts +++ b/packages/instant/src/util/error.ts @@ -46,6 +46,10 @@ const humanReadableMessageForError = (error: Error, asset?: Asset): string | und return `${assetName} is currently unavailable`; } + if (error.message === AssetBuyerError.SignatureRequestDenied) { + return 'You denied this transaction'; + } + return undefined; }; diff --git a/packages/instant/src/util/etherscan.ts b/packages/instant/src/util/etherscan.ts index ffb08a382..cfc2578a3 100644 --- a/packages/instant/src/util/etherscan.ts +++ b/packages/instant/src/util/etherscan.ts @@ -14,11 +14,11 @@ const etherscanPrefix = (networkId: number): string | undefined => { }; export const etherscanUtil = { - getEtherScanTxnAddressIfExists: (txnHash: string, networkId: number) => { + getEtherScanTxnAddressIfExists: (txHash: string, networkId: number) => { const prefix = etherscanPrefix(networkId); if (_.isUndefined(prefix)) { return; } - return `https://${prefix}etherscan.io/tx/${txnHash}`; + return `https://${prefix}etherscan.io/tx/${txHash}`; }, }; |