import { ECSignature, Order, ZeroEx } from '0x.js'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import Dialog from 'material-ui/Dialog'; import Divider from 'material-ui/Divider'; import * as React from 'react'; import * as ReactGA from 'react-ga'; import { Blockchain } from 'ts/blockchain'; import { ExpirationInput } from 'ts/components/inputs/expiration_input'; import { HashInput } from 'ts/components/inputs/hash_input'; import { IdenticonAddressInput } from 'ts/components/inputs/identicon_address_input'; import { TokenAmountInput } from 'ts/components/inputs/token_amount_input'; import { TokenInput } from 'ts/components/inputs/token_input'; import { OrderJSON } from 'ts/components/order_json'; import { Alert } from 'ts/components/ui/alert'; import { HelpTooltip } from 'ts/components/ui/help_tooltip'; import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button'; import { SwapIcon } from 'ts/components/ui/swap_icon'; import { Dispatcher } from 'ts/redux/dispatcher'; import { portalOrderSchema } from 'ts/schemas/portal_order_schema'; import { validator } from 'ts/schemas/validator'; import { AlertTypes, BlockchainErrs, HashData, Side, SideToAssetToken, Token, TokenByAddress } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; enum SigningState { UNSIGNED, SIGNING, SIGNED, } interface GenerateOrderFormProps { blockchain: Blockchain; blockchainErr: BlockchainErrs; blockchainIsLoaded: boolean; dispatcher: Dispatcher; hashData: HashData; orderExpiryTimestamp: BigNumber; networkId: number; userAddress: string; orderECSignature: ECSignature; orderTakerAddress: string; orderSalt: BigNumber; sideToAssetToken: SideToAssetToken; tokenByAddress: TokenByAddress; lastForceTokenStateRefetch: number; } interface GenerateOrderFormState { globalErrMsg: string; shouldShowIncompleteErrs: boolean; signingState: SigningState; } export class GenerateOrderForm extends React.Component { constructor(props: GenerateOrderFormProps) { super(props); this.state = { globalErrMsg: '', shouldShowIncompleteErrs: false, signingState: SigningState.UNSIGNED, }; } public componentDidMount() { window.scrollTo(0, 0); } public render() { const dispatcher = this.props.dispatcher; const depositTokenAddress = this.props.sideToAssetToken[Side.Deposit].address; const depositToken = this.props.tokenByAddress[depositTokenAddress]; const receiveTokenAddress = this.props.sideToAssetToken[Side.Receive].address; const receiveToken = this.props.tokenByAddress[receiveTokenAddress]; const takerExplanation = 'If a taker is specified, only they are
\ allowed to fill this order. If no taker is
\ specified, anyone is able to fill it.'; const exchangeContractIfExists = this.props.blockchain.getExchangeContractAddressIfExists(); const initialTakerAddress = this.props.orderTakerAddress === ZeroEx.NULL_ADDRESS ? '' : this.props.orderTakerAddress; return (

Generate an order

Expiration
{this.state.globalErrMsg !== '' && ( )}
); } private _onTokenAmountChange(token: Token, side: Side, isValid: boolean, amount?: BigNumber) { this.props.dispatcher.updateChosenAssetToken(side, { address: token.address, amount, }); } private _onCloseOrderJSONDialog() { // Upon closing the order JSON dialog, we update the orderSalt stored in the Redux store // with a new value so that if a user signs the identical order again, the newly signed // orderHash will not collide with the previously generated orderHash. this.props.dispatcher.updateOrderSalt(ZeroEx.generatePseudoRandomSalt()); this.setState({ signingState: SigningState.UNSIGNED, }); } private async _onSignClickedAsync(): Promise { if (this.props.blockchainErr !== BlockchainErrs.NoError) { this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); return false; } // Check if all required inputs were supplied const debitToken = this.props.sideToAssetToken[Side.Deposit]; const [debitBalance, debitAllowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( this.props.userAddress, debitToken.address, ); const receiveAmount = this.props.sideToAssetToken[Side.Receive].amount; if ( !_.isUndefined(debitToken.amount) && !_.isUndefined(receiveAmount) && debitToken.amount.gt(0) && receiveAmount.gt(0) && this.props.userAddress !== '' && debitBalance.gte(debitToken.amount) && debitAllowance.gte(debitToken.amount) ) { const didSignSuccessfully = await this._signTransactionAsync(); if (didSignSuccessfully) { const networkName = constants.NETWORK_NAME_BY_ID[this.props.networkId]; const eventLabel = `${this.props.tokenByAddress[debitToken.address].symbol}-${networkName}`; ReactGA.event({ category: 'Portal', action: 'Sign Order Success', label: eventLabel, value: debitToken.amount.toNumber(), }); this.setState({ globalErrMsg: '', shouldShowIncompleteErrs: false, }); } return didSignSuccessfully; } else { let globalErrMsg = 'You must fix the above errors in order to generate a valid order'; if (this.props.userAddress === '') { globalErrMsg = 'You must enable wallet communication'; this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); } ReactGA.event({ category: 'Portal', action: 'Sign Order Failure', label: globalErrMsg, }); this.setState({ globalErrMsg, shouldShowIncompleteErrs: true, }); return false; } } private async _signTransactionAsync(): Promise { this.setState({ signingState: SigningState.SIGNING, }); const exchangeContractAddr = this.props.blockchain.getExchangeContractAddressIfExists(); if (_.isUndefined(exchangeContractAddr)) { this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); this.setState({ signingState: SigningState.UNSIGNED, }); return false; } const hashData = this.props.hashData; const zeroExOrder: Order = { exchangeContractAddress: exchangeContractAddr, expirationUnixTimestampSec: hashData.orderExpiryTimestamp, feeRecipient: hashData.feeRecipientAddress, maker: hashData.orderMakerAddress, makerFee: hashData.makerFee, makerTokenAddress: hashData.depositTokenContractAddr, makerTokenAmount: hashData.depositAmount, salt: hashData.orderSalt, taker: hashData.orderTakerAddress, takerFee: hashData.takerFee, takerTokenAddress: hashData.receiveTokenContractAddr, takerTokenAmount: hashData.receiveAmount, }; const orderHash = ZeroEx.getOrderHashHex(zeroExOrder); let globalErrMsg = ''; try { const ecSignature = await this.props.blockchain.signOrderHashAsync(orderHash); const order = utils.generateOrder( exchangeContractAddr, this.props.sideToAssetToken, hashData.orderExpiryTimestamp, this.props.orderTakerAddress, this.props.userAddress, hashData.makerFee, hashData.takerFee, hashData.feeRecipientAddress, ecSignature, this.props.tokenByAddress, hashData.orderSalt, ); const validationResult = validator.validate(order, portalOrderSchema); if (validationResult.errors.length > 0) { globalErrMsg = 'Order signing failed. Please refresh and try again'; utils.consoleLog(`Unexpected error occured: Order validation failed: ${validationResult.errors}`); } } catch (err) { const errMsg = `${err}`; if (utils.didUserDenyWeb3Request(errMsg)) { globalErrMsg = 'User denied sign request'; } else { globalErrMsg = 'An unexpected error occured. Please try refreshing the page'; utils.consoleLog(`Unexpected error occured: ${err}`); utils.consoleLog(err.stack); await errorReporter.reportAsync(err); } } this.setState({ signingState: globalErrMsg === '' ? SigningState.SIGNED : SigningState.UNSIGNED, globalErrMsg, }); return globalErrMsg === ''; } private _updateOrderAddress(address?: string): void { if (!_.isUndefined(address)) { const normalizedAddress = _.isEmpty(address) ? ZeroEx.NULL_ADDRESS : address; this.props.dispatcher.updateOrderTakerAddress(normalizedAddress); } } }