diff options
Diffstat (limited to 'packages/instant')
19 files changed, 119 insertions, 73 deletions
diff --git a/packages/instant/README.md b/packages/instant/README.md index 32abf76e0..8832e562d 100644 --- a/packages/instant/README.md +++ b/packages/instant/README.md @@ -17,9 +17,12 @@ The package is available as a UMD module named `zeroExInstant` at https://instan <body> <div id="zeroExInstantContainer"></div> <script> - zeroExInstant.render({ - // Initialization options - }, '#zeroExInstantContainer'); + zeroExInstant.render( + { + // Initialization options + }, + '#zeroExInstantContainer', + ); </script> </body> ``` diff --git a/packages/instant/package.json b/packages/instant/package.json index 0a5e152ca..0888fc6a6 100644 --- a/packages/instant/package.json +++ b/packages/instant/package.json @@ -1,6 +1,6 @@ { "name": "@0x/instant", - "version": "1.0.4", + "version": "1.0.5", "engines": { "node": ">=6.12" }, @@ -41,15 +41,15 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/instant/README.md", "dependencies": { - "@0x/assert": "^1.0.20", - "@0x/asset-buyer": "^3.0.4", - "@0x/json-schemas": "^2.1.4", - "@0x/order-utils": "^3.0.7", - "@0x/subproviders": "^2.1.8", - "@0x/types": "^1.4.1", + "@0x/assert": "^1.0.21", + "@0x/asset-buyer": "^3.0.5", + "@0x/json-schemas": "^2.1.5", + "@0x/order-utils": "^3.1.0", + "@0x/subproviders": "^2.1.9", + "@0x/types": "^1.5.0", "@0x/typescript-typings": "^3.0.6", - "@0x/utils": "^2.0.8", - "@0x/web3-wrapper": "^3.2.1", + "@0x/utils": "^2.1.1", + "@0x/web3-wrapper": "^3.2.2", "bowser": "^1.9.4", "copy-to-clipboard": "^3.0.8", "ethereum-types": "^1.1.4", @@ -76,7 +76,7 @@ "@types/react-dom": "^16.0.8", "@types/react-redux": "^6.0.9", "@types/redux": "^3.6.0", - "@types/styled-components": "^4.0.1", + "@types/styled-components": "4.0.1", "awesome-typescript-loader": "^5.2.1", "dotenv-cli": "^1.4.0", "enzyme": "^3.6.0", diff --git a/packages/instant/src/components/animations/full_rotation.tsx b/packages/instant/src/components/animations/full_rotation.tsx index 9adb565f9..1dff1b1fc 100644 --- a/packages/instant/src/components/animations/full_rotation.tsx +++ b/packages/instant/src/components/animations/full_rotation.tsx @@ -14,10 +14,7 @@ to { } `; -export const FullRotation = - styled.div < - FullRotationProps > - ` +export const FullRotation = styled.div<FullRotationProps>` animation: ${rotatingKeyframes} 2s linear infinite; height: ${props => props.height}; width: ${props => props.width}; diff --git a/packages/instant/src/components/animations/position_animation.tsx b/packages/instant/src/components/animations/position_animation.tsx index 8b3b294b7..4f8f25679 100644 --- a/packages/instant/src/components/animations/position_animation.tsx +++ b/packages/instant/src/components/animations/position_animation.tsx @@ -95,10 +95,7 @@ const animationForSize = ( return animationSettingsForSize && mediaFn`${generatePositionAnimationCss(animationSettingsForSize)}`; }; -export const PositionAnimation = - styled.div < - PositionAnimationProps > - ` +export const PositionAnimation = styled.div<PositionAnimationProps>` && { ${props => props.zIndex && stylesForMedia<number>('z-index', props.zIndex)} ${props => defaultAnimation(props.positionSettings)} diff --git a/packages/instant/src/components/css_reset.tsx b/packages/instant/src/components/css_reset.tsx index 0bef85389..d1b20f4c9 100644 --- a/packages/instant/src/components/css_reset.tsx +++ b/packages/instant/src/components/css_reset.tsx @@ -4,9 +4,9 @@ import { createGlobalStyle } from '../style/theme'; export interface CSSResetProps {} /* -* Derived from -* https://github.com/jtrost/Complete-CSS-Reset -*/ + * Derived from + * https://github.com/jtrost/Complete-CSS-Reset + */ export const CSSReset = createGlobalStyle` .${INJECTED_DIV_CLASS} { a, abbr, area, article, aside, audio, b, bdo, blockquote, body, button, diff --git a/packages/instant/src/components/timed_progress_bar.tsx b/packages/instant/src/components/timed_progress_bar.tsx index b1644b871..287269af7 100644 --- a/packages/instant/src/components/timed_progress_bar.tsx +++ b/packages/instant/src/components/timed_progress_bar.tsx @@ -68,10 +68,7 @@ interface ProgressProps { animationSettings?: WidthAnimationSettings; } -export const Progress = - styled.div < - ProgressProps > - ` +export const Progress = styled.div<ProgressProps>` && { background-color: ${props => props.theme[ColorOption.primaryColor]}; border-radius: 6px; diff --git a/packages/instant/src/components/ui/circle.tsx b/packages/instant/src/components/ui/circle.tsx index 4f9f56f12..e4f2c5260 100644 --- a/packages/instant/src/components/ui/circle.tsx +++ b/packages/instant/src/components/ui/circle.tsx @@ -8,16 +8,15 @@ export interface CircleProps { } export const Circle = withTheme( - styled.div < - CircleProps > - ` - && { - width: ${props => props.diameter}px; - height: ${props => props.diameter}px; - background-color: ${props => (props.rawColor ? props.rawColor : props.theme[props.color || ColorOption.white])}; - border-radius: 50%; - } -`, + styled.div<CircleProps>` + && { + width: ${props => props.diameter}px; + height: ${props => props.diameter}px; + background-color: ${props => + props.rawColor ? props.rawColor : props.theme[props.color || ColorOption.white]}; + border-radius: 50%; + } + `, ); Circle.displayName = 'Circle'; diff --git a/packages/instant/src/components/ui/container.tsx b/packages/instant/src/components/ui/container.tsx index 58d7d5871..59b733f3e 100644 --- a/packages/instant/src/components/ui/container.tsx +++ b/packages/instant/src/components/ui/container.tsx @@ -51,10 +51,7 @@ const getBackgroundColor = (theme: any, backgroundColor?: ColorOption, rawBackgr return 'none'; }; -export const Container = - styled.div < - ContainerProps > - ` +export const Container = styled.div<ContainerProps>` && { box-sizing: border-box; ${props => cssRuleIfExists(props, 'flex-grow')} diff --git a/packages/instant/src/components/ui/flex.tsx b/packages/instant/src/components/ui/flex.tsx index 274c46b9e..145e654f1 100644 --- a/packages/instant/src/components/ui/flex.tsx +++ b/packages/instant/src/components/ui/flex.tsx @@ -14,10 +14,7 @@ export interface FlexProps { flexGrow?: number | string; } -export const Flex = - styled.div < - FlexProps > - ` +export const Flex = styled.div<FlexProps>` && { display: ${props => (props.inline ? 'inline-flex' : 'flex')}; flex-direction: ${props => props.direction}; diff --git a/packages/instant/src/components/ui/input.tsx b/packages/instant/src/components/ui/input.tsx index 53c43ea0b..024e81b15 100644 --- a/packages/instant/src/components/ui/input.tsx +++ b/packages/instant/src/components/ui/input.tsx @@ -14,10 +14,7 @@ export interface InputProps extends React.HTMLAttributes<HTMLInputElement> { onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void; } -export const Input = - styled.input < - InputProps > - ` +export const Input = styled.input<InputProps>` && { all: initial; font-size: ${props => props.fontSize}; @@ -32,10 +29,11 @@ export const Input = color: ${props => props.theme[props.fontColor || 'white']} !important; opacity: 0.5 !important; } - &::-webkit-outer-spin-button, &::-webkit-inner-spin-button { + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; - } + } } `; diff --git a/packages/instant/src/components/ui/overlay.tsx b/packages/instant/src/components/ui/overlay.tsx index 0b5eaf299..0b1be6a65 100644 --- a/packages/instant/src/components/ui/overlay.tsx +++ b/packages/instant/src/components/ui/overlay.tsx @@ -12,10 +12,7 @@ export interface OverlayProps { showMaxWidth?: ScreenWidths; } -export const Overlay = - styled.div < - OverlayProps > - ` +export const Overlay = styled.div<OverlayProps>` && { position: fixed; top: 0; diff --git a/packages/instant/src/components/ui/text.tsx b/packages/instant/src/components/ui/text.tsx index 282477758..ca120f3bd 100644 --- a/packages/instant/src/components/ui/text.tsx +++ b/packages/instant/src/components/ui/text.tsx @@ -31,10 +31,7 @@ export const Text: React.StatelessComponent<TextProps> = ({ href, onClick, ...re }; const opacityOnHoverAmount = 0.5; -export const StyledText = - styled.div < - TextProps > - ` +export const StyledText = styled.div<TextProps>` && { font-family: 'Inter UI', sans-serif; font-style: ${props => props.fontStyle}; diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts index 22f0cb6a4..bfd9e9098 100644 --- a/packages/instant/src/constants.ts +++ b/packages/instant/src/constants.ts @@ -16,6 +16,7 @@ export const ONE_SECOND_MS = 1000; export const ONE_MINUTE_MS = ONE_SECOND_MS * 60; export const GIT_SHA = process.env.GIT_SHA; export const NODE_ENV = process.env.NODE_ENV; +export const SLIPPAGE_PERCENTAGE = 0.2; export const NPM_PACKAGE_VERSION = process.env.NPM_PACKAGE_VERSION; export const DEFAULT_UNKOWN_ASSET_NAME = '???'; export const ACCOUNT_UPDATE_INTERVAL_TIME_MS = ONE_SECOND_MS * 5; @@ -73,5 +74,6 @@ export const PROVIDER_TYPE_TO_NAME: { [key in ProviderType]: string } = { [ProviderType.CoinbaseWallet]: 'Coinbase Wallet', [ProviderType.Parity]: 'Parity', [ProviderType.TrustWallet]: 'Trust Wallet', + [ProviderType.Opera]: 'Opera Wallet', [ProviderType.Fallback]: 'Fallback', }; diff --git a/packages/instant/src/containers/latest_error.tsx b/packages/instant/src/containers/latest_error.tsx index 6da4558ef..57a2dbdc2 100644 --- a/packages/instant/src/containers/latest_error.tsx +++ b/packages/instant/src/containers/latest_error.tsx @@ -62,4 +62,7 @@ const mapDispatchToProps = (dispatch: Dispatch<Action>, _ownProps: LatestErrorPr }, }); -export const LatestError = connect(mapStateToProps, mapDispatchToProps)(LatestErrorComponent); +export const LatestError = connect( + mapStateToProps, + mapDispatchToProps, +)(LatestErrorComponent); diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts index ae672c919..f07a407da 100644 --- a/packages/instant/src/types.ts +++ b/packages/instant/src/types.ts @@ -183,6 +183,7 @@ export enum ProviderType { CoinbaseWallet = 'COINBASE_WALLET', Cipher = 'CIPHER', TrustWallet = 'TRUST_WALLET', + Opera = 'OPERA', Fallback = 'FALLBACK', } diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts index faaeb7c22..709561dbc 100644 --- a/packages/instant/src/util/asset.ts +++ b/packages/instant/src/util/asset.ts @@ -1,8 +1,10 @@ -import { AssetBuyerError } from '@0x/asset-buyer'; +import { AssetBuyerError, InsufficientAssetLiquidityError } from '@0x/asset-buyer'; import { AssetProxyId, ObjectMap } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; import * as _ from 'lodash'; -import { DEFAULT_UNKOWN_ASSET_NAME } from '../constants'; +import { BIG_NUMBER_ZERO, DEFAULT_UNKOWN_ASSET_NAME } from '../constants'; import { assetDataNetworkMapping } from '../data/asset_data_network_mapping'; import { Asset, AssetMetaData, ERC20Asset, Network, ZeroExInstantError } from '../types'; @@ -102,15 +104,29 @@ export const assetUtils = { return assetDataGroupIfExists[Network.Mainnet]; }, getERC20AssetsFromAssets: (assets: Asset[]): ERC20Asset[] => { - const erc20sOrUndefined = _.map( - assets, - asset => (asset.metaData.assetProxyId === AssetProxyId.ERC20 ? (asset as ERC20Asset) : undefined), + const erc20sOrUndefined = _.map(assets, asset => + asset.metaData.assetProxyId === AssetProxyId.ERC20 ? (asset as ERC20Asset) : undefined, ); return _.compact(erc20sOrUndefined); }, assetBuyerErrorMessage: (asset: ERC20Asset, error: Error): string | undefined => { if (error.message === AssetBuyerError.InsufficientAssetLiquidity) { const assetName = assetUtils.bestNameForAsset(asset, 'of this asset'); + if ( + error instanceof InsufficientAssetLiquidityError && + error.amountAvailableToFill.greaterThan(BIG_NUMBER_ZERO) + ) { + const unitAmountAvailableToFill = Web3Wrapper.toUnitAmount( + error.amountAvailableToFill, + asset.metaData.decimals, + ); + const roundedUnitAmountAvailableToFill = unitAmountAvailableToFill.round(2, BigNumber.ROUND_DOWN); + + if (roundedUnitAmountAvailableToFill.greaterThan(BIG_NUMBER_ZERO)) { + return `There are only ${roundedUnitAmountAvailableToFill} ${assetName} available to buy`; + } + } + return `Not enough ${assetName} available`; } else if (error.message === AssetBuyerError.InsufficientZrxLiquidity) { return 'Not enough ZRX available'; diff --git a/packages/instant/src/util/buy_quote_updater.ts b/packages/instant/src/util/buy_quote_updater.ts index 6191c92e3..37974e71c 100644 --- a/packages/instant/src/util/buy_quote_updater.ts +++ b/packages/instant/src/util/buy_quote_updater.ts @@ -5,6 +5,7 @@ import * as _ from 'lodash'; import { Dispatch } from 'redux'; import { oc } from 'ts-optchain'; +import { SLIPPAGE_PERCENTAGE } from '../constants'; import { Action, actions } from '../redux/actions'; import { AffiliateInfo, ERC20Asset, QuoteFetchOrigin } from '../types'; import { analytics } from '../util/analytics'; @@ -33,8 +34,12 @@ export const buyQuoteUpdater = { } const feePercentage = oc(options.affiliateInfo).feePercentage(); let newBuyQuote: BuyQuote | undefined; + const slippagePercentage = SLIPPAGE_PERCENTAGE; try { - newBuyQuote = await assetBuyer.getBuyQuoteAsync(asset.assetData, baseUnitValue, { feePercentage }); + newBuyQuote = await assetBuyer.getBuyQuoteAsync(asset.assetData, baseUnitValue, { + feePercentage, + slippagePercentage, + }); } catch (error) { const errorMessage = assetUtils.assetBuyerErrorMessage(asset, error); diff --git a/packages/instant/src/util/env.ts b/packages/instant/src/util/env.ts index aedf4f5d6..7d4f836ff 100644 --- a/packages/instant/src/util/env.ts +++ b/packages/instant/src/util/env.ts @@ -42,18 +42,21 @@ export const envUtil = { } }, getProviderType(provider: Provider): ProviderType | undefined { + const anyProvider = provider as any; if (provider.constructor.name === 'EthereumProvider') { return ProviderType.Mist; - } else if ((provider as any).isTrust) { + } else if (anyProvider.isTrust) { return ProviderType.TrustWallet; - } else if ((provider as any).isParity) { + } else if (anyProvider.isParity) { return ProviderType.Parity; - } else if ((provider as any).isMetaMask) { + } else if (anyProvider.isMetaMask) { return ProviderType.MetaMask; } else if (!_.isUndefined(_.get(window, 'SOFA'))) { return ProviderType.CoinbaseWallet; } else if (!_.isUndefined(_.get(window, '__CIPHER__'))) { return ProviderType.Cipher; + } else if (envUtil.getBrowser() === Browser.Opera && !anyProvider.isMetaMask) { + return ProviderType.Opera; } return; }, diff --git a/packages/instant/test/util/asset.test.ts b/packages/instant/test/util/asset.test.ts index fc4e4e2e4..402a556d5 100644 --- a/packages/instant/test/util/asset.test.ts +++ b/packages/instant/test/util/asset.test.ts @@ -1,5 +1,6 @@ -import { AssetBuyerError } from '@0x/asset-buyer'; +import { AssetBuyerError, BigNumber, InsufficientAssetLiquidityError } from '@0x/asset-buyer'; import { AssetProxyId, ObjectMap } from '@0x/types'; +import { Web3Wrapper } from '@0x/web3-wrapper'; import { Asset, AssetMetaData, ERC20Asset, ERC20AssetMetaData, Network, ZeroExInstantError } from '../../src/types'; import { assetUtils } from '../../src/util/asset'; @@ -19,6 +20,16 @@ const ZRX_ASSET: ERC20Asset = { const META_DATA_MAP: ObjectMap<AssetMetaData> = { [ZRX_ASSET_DATA]: ZRX_META_DATA, }; +const WAX_ASSET: ERC20Asset = { + assetData: '0xf47261b000000000000000000000000039bb259f66e1c59d5abef88375979b4d20d98022', + metaData: { + assetProxyId: AssetProxyId.ERC20, + decimals: 8, + primaryColor: '#EDB740', + symbol: 'wax', + name: 'WAX', + }, +}; describe('assetDataUtil', () => { describe('bestNameForAsset', () => { @@ -47,13 +58,39 @@ describe('assetDataUtil', () => { }); }); describe('assetBuyerErrorMessage', () => { - it('should return message for InsufficientAssetLiquidity', () => { + it('should return message for generic InsufficientAssetLiquidity error', () => { const insufficientAssetError = new Error(AssetBuyerError.InsufficientAssetLiquidity); expect(assetUtils.assetBuyerErrorMessage(ZRX_ASSET, insufficientAssetError)).toEqual( 'Not enough ZRX available', ); }); - it('should return message for InsufficientAssetLiquidity', () => { + describe('InsufficientAssetLiquidityError', () => { + it('should return custom message for token w/ 18 decimals', () => { + const amountAvailable = Web3Wrapper.toBaseUnitAmount(new BigNumber(20.059), 18); + expect( + assetUtils.assetBuyerErrorMessage(ZRX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)), + ).toEqual('There are only 20.05 ZRX available to buy'); + }); + it('should return custom message for token w/ 18 decimals and small amount available', () => { + const amountAvailable = Web3Wrapper.toBaseUnitAmount(new BigNumber(0.01), 18); + expect( + assetUtils.assetBuyerErrorMessage(ZRX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)), + ).toEqual('There are only 0.01 ZRX available to buy'); + }); + it('should return custom message for token w/ 8 decimals', () => { + const amountAvailable = Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 8); + expect( + assetUtils.assetBuyerErrorMessage(WAX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)), + ).toEqual('There are only 3 WAX available to buy'); + }); + it('should return generic message when amount available rounds to zero', () => { + const amountAvailable = Web3Wrapper.toBaseUnitAmount(new BigNumber(0.002), 18); + expect( + assetUtils.assetBuyerErrorMessage(ZRX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)), + ).toEqual('Not enough ZRX available'); + }); + }); + it('should return message for InsufficientZrxLiquidity', () => { const insufficientZrxError = new Error(AssetBuyerError.InsufficientZrxLiquidity); expect(assetUtils.assetBuyerErrorMessage(ZRX_ASSET, insufficientZrxError)).toEqual( 'Not enough ZRX available', |