Extreme MVP of buying a ERC721 works

This commit is contained in:
fragosti 2019-04-05 15:55:10 -07:00
parent fdcad84cee
commit 90ad681a9e
10 changed files with 120 additions and 37 deletions

View File

@ -245,7 +245,7 @@ export class DutchAuctionContract extends BaseContract {
return contractInstance;
}
constructor(abi: ContractAbi, address: string, supportedProvider: SupportedProvider, txDefaults?: Partial<TxData>) {
super('DutchAuction', abi, address, providerUtils.standardizeOrThrow(supportedProvider), txDefaults);
super('DutchAuction', abi, address, supportedProvider, txDefaults);
classUtils.bindAll(this, ['_abiEncoderByFunctionSignature', 'address', 'abi', '_web3Wrapper']);
}
} // tslint:disable:max-file-line-count

View File

@ -74,6 +74,26 @@
EXPONENTIAL_AT: 1000,
DECIMAL_PLACES: 78,
});
const nftOrders = [
{
makerAddress: '0x34a745008a643eebc58920eaa29fb1165b4a288e',
takerAddress: '0x0000000000000000000000000000000000000000',
makerFee: '0',
takerFee: '0',
senderAddress: '0x0000000000000000000000000000000000000000',
makerAssetAmount: '1',
takerAssetAmount: '25000000000000000',
makerAssetData:
'0x02571792000000000000000000000000f5b0a3efb8e8e4c201e2a935f110eaaf3ffecb8d0000000000000000000000000000000000000000000000000000000000008849',
takerAssetData: '0xf47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
exchangeAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b',
feeRecipientAddress: '0x66a836664adc7c525c0cc4527dee8619d4faf669',
salt: '73683692897280689979350475364949461624706477613931387930893215569576520901319',
expirationTimeSeconds: '1557096652',
signature:
'0x1bf37ac01ba863c08f2ddba776b263af4f960326ed8d8a0a651acb4c7bce54592e5bd9826fb117ccd0d40392d26339de6e3377721fabbb3107043e4de444b24ae602',
},
];
const providedOrders = [
// Order selling REP
{
@ -168,7 +188,8 @@
};
}
const renderOptionsOverrides = {
orderSource: orderSourceOverride === 'provided' ? providedOrders : orderSourceOverride,
// orderSource: orderSourceOverride === 'provided' ? providedOrders : orderSourceOverride,
orderSource: nftOrders,
networkId: +queryParams.getQueryParamValue('networkId') || undefined,
defaultAssetBuyAmount: +queryParams.getQueryParamValue('defaultAssetBuyAmount') || undefined,
availableAssetDatas: availableAssetDatasString ? JSON.parse(availableAssetDatasString) : undefined,
@ -176,6 +197,15 @@
affiliateInfo: affiliateInfoOverride,
shouldDisablePushToHistory: !!queryParams.getQueryParamValue('shouldDisablePushToHistory'),
walletDisplayName: queryParams.getQueryParamValue('walletDisplayName') || undefined,
additionalAssetMetaDataMap: {
'0x02571792000000000000000000000000f5b0a3efb8e8e4c201e2a935f110eaaf3ffecb8d0000000000000000000000000000000000000000000000000000000000008849': {
assetProxyId: '0x02571792',
name: 'Axie #34889',
imageUrl:
'https://storage.opensea.io/0xf5b0a3efb8e8e4c201e2a935f110eaaf3ffecb8d/34889-1549990035.png',
primaryColor: '#b5d868',
},
},
};
return renderOptionsOverrides;
};

View File

@ -1,10 +1,11 @@
import { AssetProxyId } from '@0x/types';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import * as React from 'react';
import { SelectedERC20AssetAmountInput } from '../containers/selected_erc20_asset_amount_input';
import { ColorOption } from '../style/theme';
import { AsyncProcessState, ERC20Asset, OrderProcessState, OrderState } from '../types';
import { Asset, AsyncProcessState, ERC20Asset, ERC721Asset, OrderProcessState, OrderState } from '../types';
import { format } from '../util/format';
import { AmountPlaceholder } from './amount_placeholder';
@ -15,6 +16,7 @@ import { Spinner } from './ui/spinner';
import { Text } from './ui/text';
export interface InstantHeadingProps {
selectedAsset?: Asset;
selectedAssetUnitAmount?: BigNumber;
totalEthBaseUnitAmount?: BigNumber;
ethUsdPrice?: BigNumber;
@ -30,9 +32,36 @@ const ICON_COLOR = ColorOption.white;
export class InstantHeading extends React.PureComponent<InstantHeadingProps, {}> {
public render(): React.ReactNode {
const iconOrAmounts = this._renderIcon() || this._renderAmountsSection();
const { selectedAsset } = this.props;
return (
<Container backgroundColor={ColorOption.primaryColor} width="100%" padding="20px">
{this._renderAssetHeadingContent()}
</Container>
);
}
private _renderAssetHeadingContent(): React.ReactNode {
const { selectedAsset } = this.props;
if (_.isUndefined(selectedAsset)) {
// TODO: Only the ERC20 flow supports selecting assets.
return this._renderERC20AssetHeading();
}
if (selectedAsset.metaData.assetProxyId === AssetProxyId.ERC20) {
return this._renderERC20AssetHeading();
} else if (selectedAsset.metaData.assetProxyId === AssetProxyId.ERC721) {
return this._renderERC721AssetHeading(selectedAsset as ERC721Asset);
}
return null;
}
private _renderERC721AssetHeading(asset: ERC721Asset): React.ReactNode {
return <Container><img src={asset.metaData.imageUrl}/> </Container>;
}
private _renderERC20AssetHeading(): React.ReactNode {
const iconOrAmounts = this._renderIcon() || this._renderAmountsSection();
return (
<React.Fragment>
<Container marginBottom="5px">
<Text
letterSpacing="1px"
@ -56,7 +85,7 @@ export class InstantHeading extends React.PureComponent<InstantHeadingProps, {}>
{iconOrAmounts}
</Flex>
</Flex>
</Container>
</React.Fragment>
);
}

View File

@ -1,3 +1,5 @@
import { AssetProxyId } from '@0x/types';
import * as _ from 'lodash';
import * as React from 'react';
import PoweredByLogo from '../assets/powered_by_0x.svg';
@ -11,7 +13,7 @@ import { SelectedAssetBuyOrderStateButtons } from '../containers/selected_asset_
import { SelectedAssetInstantHeading } from '../containers/selected_asset_instant_heading';
import { ColorOption } from '../style/theme';
import { zIndex } from '../style/z_index';
import { SlideAnimationState } from '../types';
import { Asset, SlideAnimationState } from '../types';
import { analytics, TokenSelectorClosedVia } from '../util/analytics';
import { CSSReset } from './css_reset';
@ -84,11 +86,15 @@ export class ZeroExInstantContainer extends React.PureComponent<
</React.Fragment>
);
}
private readonly _handleSymbolClick = (): void => {
analytics.trackTokenSelectorOpened();
this.setState({
tokenSelectionPanelAnimationState: 'slidIn',
});
private readonly _handleSymbolClick = (asset?: Asset): void => {
if (_.isUndefined(asset) || asset.metaData.assetProxyId === AssetProxyId.ERC20) {
analytics.trackTokenSelectorOpened();
this.setState({
tokenSelectionPanelAnimationState: 'slidIn',
});
} else if (asset.metaData.assetProxyId === AssetProxyId.ERC721) {
// TODO: Link open sea.
}
};
private readonly _handlePanelCloseClickedX = (): void => {
this._handlePanelClose(TokenSelectorClosedVia.ClickedX);

View File

@ -18,6 +18,7 @@ import { gasPriceEstimator } from '../util/gas_price_estimator';
import { Heartbeater } from '../util/heartbeater';
import { generateAccountHeartbeater, generateBuyQuoteHeartbeater } from '../util/heartbeater_factory';
import { providerStateFactory } from '../util/provider_state_factory';
import { AssetProxyId } from '@0x/types';
export type ZeroExInstantProviderProps = ZeroExInstantBaseConfig;
@ -46,22 +47,30 @@ export class ZeroExInstantProvider extends React.PureComponent<ZeroExInstantProv
..._.mapKeys(props.additionalAssetMetaDataMap || {}, (value, key) => key.toLowerCase()),
...defaultState.assetMetaDataMap,
};
const selectedAsset = _.isUndefined(props.defaultSelectedAssetData)
? undefined
: assetUtils.createAssetFromAssetDataOrThrow(
props.defaultSelectedAssetData,
completeAssetMetaDataMap,
networkId,
);
let selectedAssetUnitAmount: BigNumber | undefined = new BigNumber(1);
if (!_.isUndefined(selectedAsset) && selectedAsset.metaData.assetProxyId === AssetProxyId.ERC20) {
selectedAssetUnitAmount = _.isUndefined(props.defaultAssetBuyAmount)
? undefined
: new BigNumber(props.defaultAssetBuyAmount);
}
// construct the final state
const storeStateFromProps: State = {
...defaultState,
providerState,
network: networkId,
walletDisplayName: props.walletDisplayName,
selectedAsset: _.isUndefined(props.defaultSelectedAssetData)
? undefined
: assetUtils.createAssetFromAssetDataOrThrow(
props.defaultSelectedAssetData,
completeAssetMetaDataMap,
networkId,
),
selectedAssetUnitAmount: _.isUndefined(props.defaultAssetBuyAmount)
? undefined
: new BigNumber(props.defaultAssetBuyAmount),
selectedAsset,
selectedAssetUnitAmount,
availableAssets: _.isUndefined(props.availableAssetDatas)
? undefined
: assetUtils.createAssetsFromAssetDatas(props.availableAssetDatas, completeAssetMetaDataMap, networkId),

View File

@ -16,7 +16,8 @@ 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 ERC20_BUY_QUOTE_SLIPPAGE_PERCENTAGE = 0.2;
export const ERC721_BUY_QUOTE_SLIPPAGE_PERCENTAGE = 0;
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;

View File

@ -5,15 +5,16 @@ import { connect } from 'react-redux';
import { oc } from 'ts-optchain';
import { State } from '../redux/reducer';
import { AsyncProcessState, ERC20Asset, OrderState } from '../types';
import { Asset, AsyncProcessState, OrderState } from '../types';
import { InstantHeading } from '../components/instant_heading';
export interface InstantHeadingProps {
onSelectAssetClick?: (asset?: ERC20Asset) => void;
onSelectAssetClick?: (asset?: Asset) => void;
}
interface ConnectedState {
selectedAsset?: Asset;
selectedAssetUnitAmount?: BigNumber;
totalEthBaseUnitAmount?: BigNumber;
ethUsdPrice?: BigNumber;
@ -22,6 +23,7 @@ interface ConnectedState {
}
const mapStateToProps = (state: State, _ownProps: InstantHeadingProps): ConnectedState => ({
selectedAsset: state.selectedAsset,
selectedAssetUnitAmount: state.selectedAssetUnitAmount,
totalEthBaseUnitAmount: oc(state).latestBuyQuote.worstCaseQuoteInfo.totalEthAmount(),
ethUsdPrice: state.ethUsdPrice,

View File

@ -100,13 +100,12 @@ export const asyncData = {
!_.isUndefined(selectedAssetUnitAmount) &&
!_.isUndefined(selectedAsset) &&
selectedAssetUnitAmount.isGreaterThan(BIG_NUMBER_ZERO) &&
buyOrderState.processState === OrderProcessState.None &&
selectedAsset.metaData.assetProxyId === AssetProxyId.ERC20
buyOrderState.processState === OrderProcessState.None
) {
await buyQuoteUpdater.updateBuyQuoteAsync(
assetBuyer,
dispatch,
selectedAsset as ERC20Asset,
selectedAsset,
selectedAssetUnitAmount,
fetchOrigin,
{

View File

@ -109,17 +109,17 @@ export const assetUtils = {
);
return _.compact(erc20sOrUndefined);
},
assetBuyerErrorMessage: (asset: ERC20Asset, error: Error): string | undefined => {
assetBuyerErrorMessage: (asset: Asset, error: Error): string | undefined => {
if (error.message === AssetBuyerError.InsufficientAssetLiquidity) {
const assetName = assetUtils.bestNameForAsset(asset, 'of this asset');
if (
error instanceof InsufficientAssetLiquidityError &&
error.amountAvailableToFill.isGreaterThan(BIG_NUMBER_ZERO)
) {
const unitAmountAvailableToFill = Web3Wrapper.toUnitAmount(
error.amountAvailableToFill,
asset.metaData.decimals,
);
const unitAmountAvailableToFill =
asset.metaData.assetProxyId === AssetProxyId.ERC20
? Web3Wrapper.toUnitAmount(error.amountAvailableToFill, asset.metaData.decimals)
: error.amountAvailableToFill;
const roundedUnitAmountAvailableToFill = unitAmountAvailableToFill.decimalPlaces(
2,
BigNumber.ROUND_DOWN,

View File

@ -1,13 +1,14 @@
import { AssetBuyer, BuyQuote } from '@0x/asset-buyer';
import { AssetProxyId } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import * as _ from 'lodash';
import { Dispatch } from 'redux';
import { oc } from 'ts-optchain';
import { SLIPPAGE_PERCENTAGE } from '../constants';
import { ERC20_BUY_QUOTE_SLIPPAGE_PERCENTAGE, ERC721_BUY_QUOTE_SLIPPAGE_PERCENTAGE } from '../constants';
import { Action, actions } from '../redux/actions';
import { AffiliateInfo, ERC20Asset, QuoteFetchOrigin } from '../types';
import { AffiliateInfo, Asset, QuoteFetchOrigin } from '../types';
import { analytics } from '../util/analytics';
import { assetUtils } from '../util/asset';
import { errorFlasher } from '../util/error_flasher';
@ -17,7 +18,7 @@ export const buyQuoteUpdater = {
updateBuyQuoteAsync: async (
assetBuyer: AssetBuyer,
dispatch: Dispatch<Action>,
asset: ERC20Asset,
asset: Asset,
assetUnitAmount: BigNumber,
fetchOrigin: QuoteFetchOrigin,
options: {
@ -27,14 +28,20 @@ export const buyQuoteUpdater = {
},
): Promise<void> => {
// get a new buy quote.
const baseUnitValue = Web3Wrapper.toBaseUnitAmount(assetUnitAmount, asset.metaData.decimals);
const baseUnitValue =
asset.metaData.assetProxyId === AssetProxyId.ERC20
? Web3Wrapper.toBaseUnitAmount(assetUnitAmount, asset.metaData.decimals)
: assetUnitAmount;
if (options.setPending) {
// mark quote as pending
dispatch(actions.setQuoteRequestStatePending());
}
const feePercentage = oc(options.affiliateInfo).feePercentage();
let newBuyQuote: BuyQuote | undefined;
const slippagePercentage = SLIPPAGE_PERCENTAGE;
const slippagePercentage =
asset.metaData.assetProxyId === AssetProxyId.ERC20
? ERC20_BUY_QUOTE_SLIPPAGE_PERCENTAGE
: ERC721_BUY_QUOTE_SLIPPAGE_PERCENTAGE;
try {
newBuyQuote = await assetBuyer.getBuyQuoteAsync(asset.assetData, baseUnitValue, {
feePercentage,