diff options
-rw-r--r-- | packages/instant/src/components/buy_button.tsx | 29 | ||||
-rw-r--r-- | packages/instant/src/components/buy_order_state_buttons.tsx | 12 | ||||
-rw-r--r-- | packages/instant/src/constants.ts | 2 | ||||
-rw-r--r-- | packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts | 23 | ||||
-rw-r--r-- | packages/instant/src/containers/selected_erc20_asset_amount_input.ts | 3 | ||||
-rw-r--r-- | packages/instant/src/types.ts | 5 | ||||
-rw-r--r-- | packages/instant/src/util/address.ts | 6 | ||||
-rw-r--r-- | packages/instant/src/util/balance.ts | 13 | ||||
-rw-r--r-- | packages/instant/src/util/format.ts | 6 | ||||
-rw-r--r-- | packages/instant/test/util/format.test.ts | 8 | ||||
-rw-r--r-- | yarn.lock | 41 |
11 files changed, 79 insertions, 69 deletions
diff --git a/packages/instant/src/components/buy_button.tsx b/packages/instant/src/components/buy_button.tsx index 9e06604e0..13c1dc88c 100644 --- a/packages/instant/src/components/buy_button.tsx +++ b/packages/instant/src/components/buy_button.tsx @@ -4,6 +4,9 @@ import * as React from 'react'; import { WEB_3_WRAPPER_TRANSACTION_FAILED_ERROR_MSG_PREFIX } from '../constants'; import { ColorOption } from '../style/theme'; +import { ZeroExInstantError } from '../types'; +import { getBestAddress } from '../util/address'; +import { balanceUtil } from '../util/balance'; import { util } from '../util/util'; import { web3Wrapper } from '../util/web3_wrapper'; @@ -12,7 +15,8 @@ import { Button, Text } from './ui'; export interface BuyButtonProps { buyQuote?: BuyQuote; assetBuyer?: AssetBuyer; - onAwaitingSignature: (buyQuote: BuyQuote) => void; + onValidationPending: (buyQuote: BuyQuote) => void; + onValidationFail: (buyQuote: BuyQuote, errorMessage: AssetBuyerError | ZeroExInstantError) => void; onSignatureDenied: (buyQuote: BuyQuote) => void; onBuyProcessing: (buyQuote: BuyQuote, txHash: string) => void; onBuySuccess: (buyQuote: BuyQuote, txHash: string) => void; @@ -42,14 +46,27 @@ export class BuyButton extends React.Component<BuyButtonProps> { return; } + this.props.onValidationPending(buyQuote); + const takerAddress = await getBestAddress(); + + const hasSufficentEth = await balanceUtil.hasSufficentEth(takerAddress, buyQuote, web3Wrapper); + if (!hasSufficentEth) { + this.props.onValidationFail(buyQuote, ZeroExInstantError.InsufficientETH); + return; + } + let txHash: string | undefined; - this.props.onAwaitingSignature(buyQuote); try { - txHash = await assetBuyer.executeBuyQuoteAsync(buyQuote); + txHash = await assetBuyer.executeBuyQuoteAsync(buyQuote, { takerAddress }); } catch (e) { - if (e instanceof Error && e.message === AssetBuyerError.SignatureRequestDenied) { - this.props.onSignatureDenied(buyQuote); - return; + if (e instanceof Error) { + if (e.message === AssetBuyerError.SignatureRequestDenied) { + this.props.onSignatureDenied(buyQuote); + return; + } else if (e.message === AssetBuyerError.TransactionValueTooLow) { + this.props.onValidationFail(buyQuote, AssetBuyerError.TransactionValueTooLow); + return; + } } throw e; } diff --git a/packages/instant/src/components/buy_order_state_buttons.tsx b/packages/instant/src/components/buy_order_state_buttons.tsx index 758eabcb7..d01e9ff57 100644 --- a/packages/instant/src/components/buy_order_state_buttons.tsx +++ b/packages/instant/src/components/buy_order_state_buttons.tsx @@ -1,4 +1,4 @@ -import { AssetBuyer, BuyQuote } from '@0x/asset-buyer'; +import { AssetBuyer, AssetBuyerError, BuyQuote } from '@0x/asset-buyer'; import * as React from 'react'; import { BuyButton } from '../components/buy_button'; @@ -7,7 +7,7 @@ import { Flex } from '../components/ui/flex'; import { PlacingOrderButton } from '../components/placing_order_button'; import { ColorOption } from '../style/theme'; -import { OrderProcessState } from '../types'; +import { OrderProcessState, ZeroExInstantError } from '../types'; import { Button } from './ui/button'; import { Text } from './ui/text'; @@ -17,7 +17,8 @@ export interface BuyOrderStateButtonProps { buyOrderProcessingState: OrderProcessState; assetBuyer?: AssetBuyer; onViewTransaction: () => void; - onAwaitingSignature: (buyQuote: BuyQuote) => void; + onValidationPending: (buyQuote: BuyQuote) => void; + onValidationFail: (buyQuote: BuyQuote, errorMessage: AssetBuyerError | ZeroExInstantError) => void; onSignatureDenied: (buyQuote: BuyQuote) => void; onBuyProcessing: (buyQuote: BuyQuote, txHash: string) => void; onBuySuccess: (buyQuote: BuyQuote, txHash: string) => void; @@ -45,7 +46,7 @@ export const BuyOrderStateButtons: React.StatelessComponent<BuyOrderStateButtonP props.buyOrderProcessingState === OrderProcessState.PROCESSING ) { return <SecondaryButton onClick={props.onViewTransaction}>View Transaction</SecondaryButton>; - } else if (props.buyOrderProcessingState === OrderProcessState.AWAITING_SIGNATURE) { + } else if (props.buyOrderProcessingState === OrderProcessState.VALIDATING) { return <PlacingOrderButton />; } @@ -53,7 +54,8 @@ export const BuyOrderStateButtons: React.StatelessComponent<BuyOrderStateButtonP <BuyButton buyQuote={props.buyQuote} assetBuyer={props.assetBuyer} - onAwaitingSignature={props.onAwaitingSignature} + onValidationPending={props.onValidationPending} + onValidationFail={props.onValidationFail} onSignatureDenied={props.onSignatureDenied} onBuyProcessing={props.onBuyProcessing} onBuySuccess={props.onBuySuccess} diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts index 48d0d4aa2..e4281d54a 100644 --- a/packages/instant/src/constants.ts +++ b/packages/instant/src/constants.ts @@ -1,5 +1,5 @@ import { BigNumber } from '@0x/utils'; export const BIG_NUMBER_ZERO = new BigNumber(0); -export const ethDecimals = 18; +export const ETH_DECIMALS = 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_order_state_buttons.ts b/packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts index 10734df96..500d6b88a 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 @@ -1,17 +1,16 @@ -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 { connect } from 'react-redux'; import { Dispatch } from 'redux'; +import { BuyOrderStateButtons } from '../components/buy_order_state_buttons'; import { Action, actions } from '../redux/actions'; import { State } from '../redux/reducer'; -import { OrderProcessState, OrderState } from '../types'; +import { OrderProcessState, OrderState, ZeroExInstantError } from '../types'; import { errorFlasher } from '../util/error_flasher'; import { etherscanUtil } from '../util/etherscan'; -import { BuyOrderStateButtons } from '../components/buy_order_state_buttons'; - interface ConnectedState { buyQuote?: BuyQuote; buyOrderProcessingState: OrderProcessState; @@ -20,12 +19,13 @@ interface ConnectedState { } interface ConnectedDispatch { - onAwaitingSignature: (buyQuote: BuyQuote) => void; + onValidationPending: (buyQuote: BuyQuote) => void; onSignatureDenied: (buyQuote: BuyQuote) => void; onBuyProcessing: (buyQuote: BuyQuote, txHash: string) => void; onBuySuccess: (buyQuote: BuyQuote, txHash: string) => void; onBuyFailure: (buyQuote: BuyQuote, txHash: string) => void; onRetry: () => void; + onValidationFail: (buyQuote: BuyQuote, errorMessage: AssetBuyerError | ZeroExInstantError) => void; } export interface SelectedAssetBuyOrderStateButtons {} const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyOrderStateButtons): ConnectedState => ({ @@ -55,8 +55,8 @@ const mapDispatchToProps = ( dispatch: Dispatch<Action>, ownProps: SelectedAssetBuyOrderStateButtons, ): ConnectedDispatch => ({ - onAwaitingSignature: (buyQuote: BuyQuote) => { - const newOrderState: OrderState = { processState: OrderProcessState.AWAITING_SIGNATURE }; + onValidationPending: (buyQuote: BuyQuote) => { + const newOrderState: OrderState = { processState: OrderProcessState.VALIDATING }; dispatch(actions.updateBuyOrderState(newOrderState)); }, onBuyProcessing: (buyQuote: BuyQuote, txHash: string) => { @@ -72,6 +72,15 @@ const mapDispatchToProps = ( const errorMessage = 'You denied this transaction'; errorFlasher.flashNewErrorMessage(dispatch, errorMessage); }, + onValidationFail: (buyQuote, error) => { + dispatch(actions.updateBuyOrderState({ processState: OrderProcessState.NONE })); + if (error === ZeroExInstantError.InsufficientETH) { + const errorMessage = "You don't have enough ETH"; + errorFlasher.flashNewErrorMessage(dispatch, errorMessage); + } else { + errorFlasher.flashNewErrorMessage(dispatch); + } + }, onRetry: () => { dispatch(actions.resetAmount()); }, 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 1e93a6deb..4767b15d4 100644 --- a/packages/instant/src/containers/selected_erc20_asset_amount_input.ts +++ b/packages/instant/src/containers/selected_erc20_asset_amount_input.ts @@ -7,6 +7,7 @@ import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; +import { ERC20AssetAmountInput } from '../components/erc20_asset_amount_input'; import { Action, actions } from '../redux/actions'; import { State } from '../redux/reducer'; import { ColorOption } from '../style/theme'; @@ -15,8 +16,6 @@ import { assetUtils } from '../util/asset'; import { BigNumberInput } from '../util/big_number_input'; import { errorFlasher } from '../util/error_flasher'; -import { ERC20AssetAmountInput } from '../components/erc20_asset_amount_input'; - export interface SelectedERC20AssetAmountInputProps { fontColor?: ColorOption; startingFontSizePx: number; diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts index e02a815f9..336465e43 100644 --- a/packages/instant/src/types.ts +++ b/packages/instant/src/types.ts @@ -10,14 +10,14 @@ export enum AsyncProcessState { export enum OrderProcessState { NONE = 'None', - AWAITING_SIGNATURE = 'Awaiting Signature', + VALIDATING = 'Validating', PROCESSING = 'Processing', SUCCESS = 'Success', FAILURE = 'Failure', } interface OrderStatePreTx { - processState: OrderProcessState.NONE | OrderProcessState.AWAITING_SIGNATURE; + processState: OrderProcessState.NONE | OrderProcessState.VALIDATING; } interface OrderStatePostTx { processState: OrderProcessState.PROCESSING | OrderProcessState.SUCCESS | OrderProcessState.FAILURE; @@ -72,4 +72,5 @@ export enum Network { export enum ZeroExInstantError { AssetMetaDataNotAvailable = 'ASSET_META_DATA_NOT_AVAILABLE', + InsufficientETH = 'INSUFFICIENT_ETH', } diff --git a/packages/instant/src/util/address.ts b/packages/instant/src/util/address.ts new file mode 100644 index 000000000..14d42d8c0 --- /dev/null +++ b/packages/instant/src/util/address.ts @@ -0,0 +1,6 @@ +import { web3Wrapper } from '../util/web3_wrapper'; + +export const getBestAddress = async (): Promise<string | undefined> => { + const addresses = await web3Wrapper.getAvailableAddressesAsync(); + return addresses[0]; +}; diff --git a/packages/instant/src/util/balance.ts b/packages/instant/src/util/balance.ts new file mode 100644 index 000000000..533656858 --- /dev/null +++ b/packages/instant/src/util/balance.ts @@ -0,0 +1,13 @@ +import { BuyQuote } from '@0x/asset-buyer'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as _ from 'lodash'; + +export const balanceUtil = { + hasSufficentEth: async (takerAddress: string | undefined, buyQuote: BuyQuote, web3Wrapper: Web3Wrapper) => { + if (_.isUndefined(takerAddress)) { + return false; + } + const balanceWei = await web3Wrapper.getBalanceInWeiAsync(takerAddress); + return balanceWei >= buyQuote.worstCaseQuoteInfo.totalEthAmount; + }, +}; diff --git a/packages/instant/src/util/format.ts b/packages/instant/src/util/format.ts index ca7c01359..4a48dec9d 100644 --- a/packages/instant/src/util/format.ts +++ b/packages/instant/src/util/format.ts @@ -2,7 +2,7 @@ import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as _ from 'lodash'; -import { ethDecimals } from '../constants'; +import { ETH_DECIMALS } from '../constants'; export const format = { ethBaseAmount: ( @@ -13,7 +13,7 @@ export const format = { if (_.isUndefined(ethBaseAmount)) { return defaultText; } - const ethUnitAmount = Web3Wrapper.toUnitAmount(ethBaseAmount, ethDecimals); + const ethUnitAmount = Web3Wrapper.toUnitAmount(ethBaseAmount, ETH_DECIMALS); return format.ethUnitAmount(ethUnitAmount, decimalPlaces); }, ethUnitAmount: ( @@ -36,7 +36,7 @@ export const format = { if (_.isUndefined(ethBaseAmount) || _.isUndefined(ethUsdPrice)) { return defaultText; } - const ethUnitAmount = Web3Wrapper.toUnitAmount(ethBaseAmount, ethDecimals); + const ethUnitAmount = Web3Wrapper.toUnitAmount(ethBaseAmount, ETH_DECIMALS); return format.ethUnitAmountInUsd(ethUnitAmount, ethUsdPrice, decimalPlaces); }, ethUnitAmountInUsd: ( diff --git a/packages/instant/test/util/format.test.ts b/packages/instant/test/util/format.test.ts index 2c9294c78..c346b7604 100644 --- a/packages/instant/test/util/format.test.ts +++ b/packages/instant/test/util/format.test.ts @@ -1,15 +1,15 @@ import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; -import { ethDecimals } from '../../src/constants'; +import { ETH_DECIMALS } from '../../src/constants'; import { format } from '../../src/util/format'; const BIG_NUMBER_ONE = new BigNumber(1); const BIG_NUMBER_DECIMAL = new BigNumber(0.432414); const BIG_NUMBER_IRRATIONAL = new BigNumber(5.3014059295032); -const ONE_ETH_IN_BASE_UNITS = Web3Wrapper.toBaseUnitAmount(BIG_NUMBER_ONE, ethDecimals); -const DECIMAL_ETH_IN_BASE_UNITS = Web3Wrapper.toBaseUnitAmount(BIG_NUMBER_DECIMAL, ethDecimals); -const IRRATIONAL_ETH_IN_BASE_UNITS = Web3Wrapper.toBaseUnitAmount(BIG_NUMBER_IRRATIONAL, ethDecimals); +const ONE_ETH_IN_BASE_UNITS = Web3Wrapper.toBaseUnitAmount(BIG_NUMBER_ONE, ETH_DECIMALS); +const DECIMAL_ETH_IN_BASE_UNITS = Web3Wrapper.toBaseUnitAmount(BIG_NUMBER_DECIMAL, ETH_DECIMALS); +const IRRATIONAL_ETH_IN_BASE_UNITS = Web3Wrapper.toBaseUnitAmount(BIG_NUMBER_IRRATIONAL, ETH_DECIMALS); const BIG_NUMBER_FAKE_ETH_USD_PRICE = new BigNumber(2.534); describe('format', () => { @@ -1900,10 +1900,6 @@ aes-js@^0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-0.2.4.tgz#94b881ab717286d015fa219e08fb66709dda5a3d" -aes-js@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/aes-js/-/aes-js-3.1.1.tgz#89fd1f94ae51b4c72d62466adc1a7323ff52f072" - agent-base@4, agent-base@^4.1.0, agent-base@~4.2.0: version "4.2.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" @@ -3341,7 +3337,7 @@ bs-logger@0.x: dependencies: fast-json-stable-stringify "^2.0.0" -bs58@=4.0.1, bs58@^4.0.0: +bs58@=4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" dependencies: @@ -3364,14 +3360,6 @@ bs58check@^1.0.8: bs58 "^3.1.0" create-hash "^1.1.0" -bs58check@^2.1.2: - version "2.1.2" - resolved "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" - dependencies: - bs58 "^4.0.0" - create-hash "^1.1.0" - safe-buffer "^5.1.2" - bser@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" @@ -5959,19 +5947,6 @@ ethereumjs-wallet@0.6.0: utf8 "^2.1.1" uuid "^2.0.1" -ethereumjs-wallet@~0.6.0: - version "0.6.2" - resolved "https://registry.npmjs.org/ethereumjs-wallet/-/ethereumjs-wallet-0.6.2.tgz#67244b6af3e8113b53d709124b25477b64aeccda" - dependencies: - aes-js "^3.1.1" - bs58check "^2.1.2" - ethereumjs-util "^5.2.0" - hdkey "^1.0.0" - safe-buffer "^5.1.2" - scrypt.js "^0.2.0" - utf8 "^3.0.0" - uuid "^3.3.2" - ethers@~4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.4.tgz#d3f85e8b27f4b59537e06526439b0fb15b44dc65" @@ -6776,7 +6751,7 @@ ganache-core@0xProject/ganache-core#monorepo-dep: ethereumjs-tx "0xProject/ethereumjs-tx#fake-tx-include-signature-by-default" ethereumjs-util "^5.2.0" ethereumjs-vm "2.3.5" - ethereumjs-wallet "~0.6.0" + ethereumjs-wallet "0.6.0" fake-merkle-patricia-tree "~1.0.1" heap "~0.2.6" js-scrypt "^0.2.0" @@ -7501,14 +7476,6 @@ hdkey@^0.7.0, hdkey@^0.7.1: coinstring "^2.0.0" secp256k1 "^3.0.1" -hdkey@^1.0.0: - version "1.1.0" - resolved "https://registry.npmjs.org/hdkey/-/hdkey-1.1.0.tgz#e74e7b01d2c47f797fa65d1d839adb7a44639f29" - dependencies: - coinstring "^2.0.0" - safe-buffer "^5.1.1" - secp256k1 "^3.0.1" - he@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" @@ -15595,10 +15562,6 @@ utf8@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/utf8/-/utf8-2.1.2.tgz#1fa0d9270e9be850d9b05027f63519bf46457d96" -utf8@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" - util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" |