Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

[instant][types][order-utils][asset-buyer] Move over and clean up features from zrx-buyer #1131

Merged
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
19f6190
feat: Move over features from zrx-buyer
fragosti Oct 11, 2018
63652df
feat: adjust amount input width
fragosti Oct 11, 2018
0edd9b3
feat: debounce the fetching of new quotes
fragosti Oct 11, 2018
1c92ae0
fix: export BuyQuoteInfo from asset-buyer
fragosti Oct 12, 2018
03b235b
feat: populate order details with information from worst buy quote
fragosti Oct 12, 2018
09c5ae4
feat: have coinbase API return BigNumber for eth-usd price endpoint
fragosti Oct 12, 2018
f3391e1
feat: make redux actions type-sage
fragosti Oct 12, 2018
025614a
feat: Add AssetData type as union of ERC721AssetData and ERC20AssetData
fragosti Oct 12, 2018
ccf021b
feat: use new AssetData type from types package
fragosti Oct 12, 2018
f395414
feat: model asset meta data and add dynamic assetData state
fragosti Oct 12, 2018
e7130af
Merge branch 'development' of https://github.com/0xProject/0x-monorep…
fragosti Oct 15, 2018
43f8f2a
feat: add changelog entries for changed packages
fragosti Oct 15, 2018
fcf3451
Add tnxHash to buy button callbacks
fragosti Oct 16, 2018
ac3bfdf
Put boundNoop in a util file
fragosti Oct 16, 2018
fa18db8
Rename OrderDetailsRow to EthAmountRow
fragosti Oct 16, 2018
18667d7
Hide USD price when ETH-USD price is not available
fragosti Oct 16, 2018
f2e5fd8
Add ts-optchain and use it instead of lodash get
fragosti Oct 16, 2018
875f621
Remove expiry buffer seconds option from AssetBuyer init
fragosti Oct 16, 2018
d268e19
Merge branch 'development' of https://github.com/0xProject/0x-monorep…
fragosti Oct 16, 2018
dbf5be6
Merge branch 'development' of https://github.com/0xProject/0x-monorep…
fragosti Oct 16, 2018
2610868
Add tests for format and use toFixed instead of round for usd
fragosti Oct 16, 2018
c328616
Run tests on circle CI
fragosti Oct 16, 2018
6a89935
Remove order-utils from dependencies
fragosti Oct 16, 2018
d2adbc3
chore: temporarily increase the bundle size for instant
fragosti Oct 16, 2018
009b5b5
feat: export AssetData from utils
fragosti Oct 17, 2018
8ba6534
feat: export AssetData from order-utils
fragosti Oct 17, 2018
eda0b3e
fix: dont use enum string as type as typedoc gets confused
fragosti Oct 17, 2018
32beeae
Bump max bundle size for instant
fragosti Oct 17, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/asset-buyer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export {
AssetBuyerError,
AssetBuyerOpts,
BuyQuote,
BuyQuoteInfo,
BuyQuoteExecutionOpts,
BuyQuoteRequestOpts,
OrderProvider,
Expand Down
1 change: 1 addition & 0 deletions packages/instant/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"homepage": "https://github.com/0xProject/0x-monorepo/packages/instant/README.md",
"dependencies": {
"@0xproject/asset-buyer": "^2.0.0",
"@0xproject/order-utils": "^1.0.7",
"@0xproject/types": "^1.1.4",
"@0xproject/typescript-typings": "^2.0.2",
"@0xproject/utils": "^2.0.2",
Expand Down
11 changes: 6 additions & 5 deletions packages/instant/src/components/amount_input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ export interface AmountInputProps {
fontColor?: ColorOption;
fontSize?: string;
value?: BigNumber;
onChange?: (value?: BigNumber) => void;
onChange: (value?: BigNumber) => void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is value undefined if the input is empty?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep! This is what made most sense to me.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just curious -- why move this from being optional to being required but having a default no-op? is this a pattern we want to follow for callbacks?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So yea this is a bit confusing. @types/react and ts > 3.0.0 added understanding of defaultProps. So the way you think about the Props interface is that it's the internal interface (in this case, callback is not optional). However, if you have a default value the compiler knows that you actually don't have to pass the prop in (ie. the external interface is a function of internal interface + defaultProps).

So in practice it's not required, has default no-ops, so internally you can assume it's not undefined.

And yea I like this pattern :P

}

export class AmountInput extends React.Component<AmountInputProps> {
public static defaultProps = {
onChange: _.noop.bind(_),
};
public render(): React.ReactNode {
const { fontColor, fontSize, value } = this.props;
return (
Expand All @@ -24,7 +27,7 @@ export class AmountInput extends React.Component<AmountInputProps> {
onChange={this._handleChange}
value={!_.isUndefined(value) ? value.toString() : ''}
placeholder="0.00"
width="2em"
width="2.2em"
/>
</Container>
);
Expand All @@ -40,8 +43,6 @@ export class AmountInput extends React.Component<AmountInputProps> {
return;
}
}
if (!_.isUndefined(this.props.onChange)) {
this.props.onChange(bigNumberValue);
}
this.props.onChange(bigNumberValue);
};
}
51 changes: 51 additions & 0 deletions packages/instant/src/components/asset_amount_input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { AssetProxyId } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import * as React from 'react';

import { assetMetaData } from '../data/asset_meta_data';
import { ColorOption } from '../style/theme';

import { AmountInput, AmountInputProps } from './amount_input';
import { Container, Text } from './ui';

export interface AssetAmountInputProps extends AmountInputProps {
assetData?: string;
onChange: (value?: BigNumber, assetData?: string) => void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we ever going to have a case where we have a value but not an assetData?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In practice no, but those two things can be undefined independently and that case needs to be handled somewhere. The other option is to not render something, but in the event that we are rendering, we need to handle it here.

}

export class AssetAmountInput extends React.Component<AssetAmountInputProps> {
public static defaultProps = {
onChange: _.noop.bind(_),
};
public render(): React.ReactNode {
const { assetData, onChange, ...rest } = this.props;
return (
<Container>
<AmountInput {...rest} onChange={this._handleChange} />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a design / polish nit, I think we want the text in this input to be right justified so that if you have typed just '5' then the character '5' is close to the symbol label

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do in a future PR (added to my list)

<Container display="inline-block" marginLeft="10px">
<Text fontSize={rest.fontSize} fontColor={ColorOption.white} textTransform="uppercase">
{this._getLabel()}
</Text>
</Container>
</Container>
);
}
private readonly _getLabel = (): string => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this can have a clearer name like _getAssetSymbolLabel

const unknownLabel = '???';
if (_.isUndefined(this.props.assetData)) {
return unknownLabel;
}
const metaData = assetMetaData[this.props.assetData];
if (_.isUndefined(metaData)) {
return unknownLabel;
}
if (metaData.assetProxyId === AssetProxyId.ERC20) {
return metaData.symbol;
}
return unknownLabel;
};
private readonly _handleChange = (value?: BigNumber): void => {
this.props.onChange(value, this.props.assetData);
};
}
59 changes: 48 additions & 11 deletions packages/instant/src/components/buy_button.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,56 @@
import { BuyQuote } from '@0xproject/asset-buyer';
import * as _ from 'lodash';
import * as React from 'react';

import { ColorOption } from '../style/theme';
import { assetBuyer } from '../util/asset_buyer';
import { web3Wrapper } from '../util/web3_wrapper';

import { Button, Container, Text } from './ui';

export interface BuyButtonProps {}
export interface BuyButtonProps {
buyQuote?: BuyQuote;
onClick: (buyQuote: BuyQuote) => void;
onBuySuccess: (buyQuote: BuyQuote) => void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we might want to return the txHash as a param for the callback as well

onBuyFailure: (buyQuote: BuyQuote) => void;
text: string;
}

export const BuyButton: React.StatelessComponent<BuyButtonProps> = props => (
<Container padding="20px" width="100%">
<Button width="100%">
<Text fontColor={ColorOption.white} fontWeight={600} fontSize="20px">
Buy
</Text>
</Button>
</Container>
);
const boundNoop = _.noop.bind(_);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we extract this out into a util or something since we are using it in multiple components? Also do we necessarily need to bind here? I suppose the linter may complain?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The linter complains yes.


BuyButton.displayName = 'BuyButton';
export class BuyButton extends React.Component<BuyButtonProps> {
public static defaultProps = {
onClick: boundNoop,
onBuySuccess: boundNoop,
onBuyFailure: boundNoop,
};
public render(): React.ReactNode {
const shouldDisableButton = _.isUndefined(this.props.buyQuote);
return (
<Container padding="20px" width="100%">
<Button width="100%" onClick={this._handleClick} isDisabled={shouldDisableButton}>
<Text fontColor={ColorOption.white} fontWeight={600} fontSize="20px">
{this.props.text}
</Text>
</Button>
</Container>
);
}
private readonly _handleClick = async () => {
// The button is disabled when there is no buy quote anyway.
if (_.isUndefined(this.props.buyQuote)) {
return;
}
this.props.onClick(this.props.buyQuote);
try {
const txnHash = await assetBuyer.executeBuyQuoteAsync(this.props.buyQuote, {
// HACK: There is a calculation issue in asset-buyer. ETH is refunded anyway so just over-estimate.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets not commit this, the fix should be up from the protocol side early this week

ethAmount: this.props.buyQuote.worstCaseQuoteInfo.totalEthAmount.mul(2),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this ethAmount should come from the buyQuoteInfo stored in redux so there is less chance we end up executing a buy thats different from what we are presenting in the order details

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not technically possible in this implementation, but yes the API allows that... I feel like that's ok?

});
await web3Wrapper.awaitTransactionSuccessAsync(txnHash);
} catch {
this.props.onBuyFailure(this.props.buyQuote);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this supposed to be if the tx fails? I'm not 100% sure if awaitTransactionSuccessAsync will throw. We might want to use web3wrapper.awaitTransactionMinedAsync instead. In other news I'll be adding some hooks to assetBuyer.executeBuyQuoteAsync so we can know when the user has been asked to sign a transaction and when they have failed to do so

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It throws.

Sweet!

}
this.props.onBuySuccess(this.props.buyQuote);
};
}
36 changes: 25 additions & 11 deletions packages/instant/src/components/instant_heading.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import * as React from 'react';

import { SelectedAssetAmountInput } from '../containers/selected_asset_amount_input';
import { ColorOption } from '../style/theme';
import { format } from '../util/format';

import { Container, Flex, Text } from './ui';

export interface InstantHeadingProps {}
export interface InstantHeadingProps {
selectedAssetAmount?: BigNumber;
totalEthBaseAmount?: BigNumber;
ethUsdPrice?: BigNumber;
}

const displaytotalEthBaseAmount = ({ selectedAssetAmount, totalEthBaseAmount }: InstantHeadingProps): string => {
if (_.isUndefined(selectedAssetAmount)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this logic just live in format?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see format as just formatting ethereum amounts into strings (including ETH dollar amounts). This case is handling the input not having any value, which seems to specific.

return '0 ETH';
}
return format.ethBaseAmount(totalEthBaseAmount, 4, '...loading');
};

const displayUsdAmount = ({ totalEthBaseAmount, selectedAssetAmount, ethUsdPrice }: InstantHeadingProps): string => {
if (_.isUndefined(selectedAssetAmount)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

return '$0.00';
}
return format.ethBaseAmountInUsd(totalEthBaseAmount, ethUsdPrice, 2, '...loading');
};

export const InstantHeading: React.StatelessComponent<InstantHeadingProps> = props => (
<Container backgroundColor={ColorOption.primaryColor} padding="20px" width="100%" borderRadius="3px 3px 0px 0px">
Expand All @@ -22,22 +43,15 @@ export const InstantHeading: React.StatelessComponent<InstantHeadingProps> = pro
</Text>
</Container>
<Flex direction="row" justify="space-between">
<Container>
<SelectedAssetAmountInput fontSize="45px" />
<Container display="inline-block" marginLeft="10px">
<Text fontSize="45px" fontColor={ColorOption.white} textTransform="uppercase">
rep
</Text>
</Container>
</Container>
<SelectedAssetAmountInput fontSize="45px" />
<Flex direction="column" justify="space-between">
<Container marginBottom="5px">
<Text fontSize="16px" fontColor={ColorOption.white} fontWeight={500}>
0 ETH
{displaytotalEthBaseAmount(props)}
</Text>
</Container>
<Text fontSize="16px" fontColor={ColorOption.white} opacity={0.7}>
$0.00
{displayUsdAmount(props)}
</Text>
</Flex>
</Flex>
Expand Down
89 changes: 61 additions & 28 deletions packages/instant/src/components/order_details.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,85 @@
import { BuyQuoteInfo } from '@0xproject/asset-buyer';
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import * as React from 'react';

import { ColorOption } from '../style/theme';
import { format } from '../util/format';

import { Container, Flex, Text } from './ui';

export interface OrderDetailsProps {}

export const OrderDetails: React.StatelessComponent<OrderDetailsProps> = props => (
<Container padding="20px" width="100%">
<Container marginBottom="10px">
<Text
letterSpacing="1px"
fontColor={ColorOption.primaryColor}
fontWeight={600}
textTransform="uppercase"
fontSize="14px"
>
Order Details
</Text>
</Container>
<OrderDetailsRow name="Token Price" primaryValue=".013 ETH" secondaryValue="$24.32" />
<OrderDetailsRow name="Fee" primaryValue=".005 ETH" secondaryValue="$1.04" />
<OrderDetailsRow name="Total Cost" primaryValue="1.66 ETH" secondaryValue="$589.56" shouldEmphasize={true} />
</Container>
);
export interface OrderDetailsProps {
buyQuoteInfo?: BuyQuoteInfo;
ethUsdPrice?: BigNumber;
}

OrderDetails.displayName = 'OrderDetails';
export class OrderDetails extends React.Component<OrderDetailsProps> {
public render(): React.ReactNode {
const { buyQuoteInfo, ethUsdPrice } = this.props;
const ethAssetPrice = _.get(buyQuoteInfo, 'ethPerAssetPrice');
const ethTokenFee = _.get(buyQuoteInfo, 'feeEthAmount');
const totalEthAmount = _.get(buyQuoteInfo, 'totalEthAmount');
return (
<Container padding="20px" width="100%">
<Container marginBottom="10px">
<Text
letterSpacing="1px"
fontColor={ColorOption.primaryColor}
fontWeight={600}
textTransform="uppercase"
fontSize="14px"
>
Order Details
</Text>
</Container>
<OrderDetailsRow
name="Token Price"
ethAmount={ethAssetPrice}
ethUsdPrice={ethUsdPrice}
shouldConvertEthToUnitAmount={false}
/>
<OrderDetailsRow name="Fee" ethAmount={ethTokenFee} ethUsdPrice={ethUsdPrice} />
<OrderDetailsRow
name="Total Cost"
ethAmount={totalEthAmount}
ethUsdPrice={ethUsdPrice}
shouldEmphasize={true}
/>
</Container>
);
}
}

export interface OrderDetailsRowProps {
name: string;
primaryValue: string;
secondaryValue: string;
ethAmount?: BigNumber;
shouldConvertEthToUnitAmount?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this mean? comments for this props definition would be good

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just renamed everything hopefully this is clearer.

ethUsdPrice?: BigNumber;
shouldEmphasize?: boolean;
}

export const OrderDetailsRow: React.StatelessComponent<OrderDetailsRowProps> = props => {
const fontWeight = props.shouldEmphasize ? 700 : 400;
export const OrderDetailsRow: React.StatelessComponent<OrderDetailsRowProps> = ({
name,
ethAmount,
shouldConvertEthToUnitAmount,
ethUsdPrice,
shouldEmphasize,
}) => {
const fontWeight = shouldEmphasize ? 700 : 400;
const usdFormatter = shouldConvertEthToUnitAmount ? format.ethBaseAmountInUsd : format.ethUnitAmountInUsd;
const ethFormatter = shouldConvertEthToUnitAmount ? format.ethBaseAmount : format.ethUnitAmount;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to bind format here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't use this in format, and even if we did we would be fine because we are using arrow functions. In general I think it's a bit awkward to use the function keyword in es6 since we have classes and arrow functions. In es5, all we had was function and that was for both classes and functions.

return (
<Container padding="10px 0px" borderTop="1px dashed" borderColor={ColorOption.feintGrey}>
<Flex justify="space-between">
<Text fontWeight={fontWeight} fontColor={ColorOption.grey}>
{props.name}
{name}
</Text>
<Container>
<Container marginRight="3px" display="inline-block">
<Text fontColor={ColorOption.lightGrey}>({props.secondaryValue}) </Text>
<Text fontColor={ColorOption.lightGrey}>({usdFormatter(ethAmount, ethUsdPrice)})</Text>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are missing the ETH/USD conversion we probably shouldn't render the parentheses

</Container>
<Text fontWeight={fontWeight} fontColor={ColorOption.grey}>
{props.primaryValue}
{ethFormatter(ethAmount)}
</Text>
</Container>
</Flex>
Expand All @@ -57,6 +89,7 @@ export const OrderDetailsRow: React.StatelessComponent<OrderDetailsRowProps> = p

OrderDetailsRow.defaultProps = {
shouldEmphasize: false,
shouldConvertEthToUnitAmount: true,
};

OrderDetailsRow.displayName = 'OrderDetailsRow';
3 changes: 3 additions & 0 deletions packages/instant/src/components/zero_ex_instant.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import * as React from 'react';
import { Provider } from 'react-redux';

import { asyncData } from '../redux/async_data';
import { store } from '../redux/store';
import { fonts } from '../style/fonts';
import { theme, ThemeProvider } from '../style/theme';

import { ZeroExInstantContainer } from './zero_ex_instant_container';

fonts.include();
// tslint:disable-next-line:no-floating-promises
asyncData.fetchAndDispatchToStore();

export interface ZeroExInstantProps {}

Expand Down
13 changes: 7 additions & 6 deletions packages/instant/src/components/zero_ex_instant_container.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import * as React from 'react';

import { LatestBuyQuoteOrderDetails } from '../containers/latest_buy_quote_order_details';
import { SelectedAssetBuyButton } from '../containers/selected_asset_buy_button';
import { SelectedAssetInstantHeading } from '../containers/selected_asset_instant_heading';

import { ColorOption } from '../style/theme';

import { BuyButton } from './buy_button';
import { InstantHeading } from './instant_heading';
import { OrderDetails } from './order_details';
import { Container, Flex } from './ui';

export interface ZeroExInstantContainerProps {}

export const ZeroExInstantContainer: React.StatelessComponent<ZeroExInstantContainerProps> = props => (
<Container hasBoxShadow={true} width="350px" backgroundColor={ColorOption.white} borderRadius="3px">
<Flex direction="column" justify="flex-start">
<InstantHeading />
<OrderDetails />
<BuyButton />
<SelectedAssetInstantHeading />
<LatestBuyQuoteOrderDetails />
<SelectedAssetBuyButton />
</Flex>
</Container>
);
Loading