import React, { Fragment, useState, useCallback, useEffect } from "react"; import { Currency, CurrencyAmount, Percent, TradeType, } from "@uniswap/sdk-core"; import AppBody from "./AppBody"; import SwapHeader from "../order/SwapHeader"; import { BottomGrouping } from "../order/styleds"; import { ArrowWrapper, Wrapper, Dots, SwapCallbackError, } from "../order/styleds"; import { AutoColumn } from "../Column"; import CurrencyInputPanel from "../CurrencyInputPanel"; import FeeInputPanel from "../FeeInputPanel"; import { useGelatoRangeOrders } from "../../hooks/gelato"; import { Field } from "../../state/gorder/actions"; import { maxAmountSpend } from "../../utils/maxAmountSpend"; import { useUSDCValue } from "../../hooks/useUSDCPrice"; import { Divide, X, Minus, ArrowDown, CheckCircle, HelpCircle, } from "react-feather"; import { Text } from "rebass"; import useTheme from "../../hooks/useTheme"; import { tryParseAmount } from "../../utils/tryParseAmount"; import useGasOverhead from "../../hooks/useGasOverhead"; import { useIsSwapUnsupported } from "../../hooks/useIsSwapUnsupported"; import { ButtonConfirmed, ButtonError, ButtonLight, ButtonPrimary, } from "../Button"; import { TYPE } from "../../theme"; import { useWeb3 } from "../../web3"; import { GreyCard } from "../Card"; import { ApprovalState, useApproveCallbackFromInputCurrencyAmount, } from "../../hooks/useApproveCallback"; import { AutoRow } from "../Row"; import CurrencyLogo from "../CurrencyLogo"; import Loader from "../Loader"; import { MouseoverTooltip } from "../Tooltip"; import { Trade } from "@uniswap/v2-sdk"; import { BigNumber } from "ethers"; import ConfirmSwapModal from "../order/ConfirmSwapModal"; interface GelatoRangeOrderProps { showCommonBases?: boolean; } enum Rate { DIV = "DIV", MUL = "MUL", } export default function GelatoRangeOrder({ showCommonBases = true, }: GelatoRangeOrderProps) { const theme = useTheme(); const { account, toggleWalletModal } = useWeb3(); const [activeTab, setActiveTab] = useState<"sell" | "buy">("sell"); const recipient = account ?? null; const { handlers: { handleInput, updateRange, handleRateType, handleCurrencySelection, handleSwitchTokens, handleRangeOrderSubmission, handleRangeSelection, }, derivedOrderInfo: { parsedAmounts, currencies, formattedAmounts, currencyBalances, price, trade, rawAmounts, maxFeeAmount, zeroForOne, inputError, }, orderState: { independentField, rateType, rangeLowerEnabled, rangeUpperEnabled, }, } = useGelatoRangeOrders(); const fiatValueInput = useUSDCValue(parsedAmounts.input); const maxInputAmount: CurrencyAmount | undefined = maxAmountSpend( currencyBalances.input ); const showMaxButton = Boolean( maxInputAmount?.greaterThan(0) && !parsedAmounts.input?.equalTo(maxInputAmount) ); const desiredRateInCurrencyAmount = tryParseAmount( trade?.outputAmount.toSignificant(6), currencies.output ); const fiatValueDesiredRate = useUSDCValue(desiredRateInCurrencyAmount); const currentMarketRate = trade?.executionPrice ?? undefined; const pct = currentMarketRate && price ? price.subtract(currentMarketRate).divide(currentMarketRate) : undefined; const percentageRateDifference = pct ? new Percent(pct.numerator, pct.denominator) : undefined; const swapIsUnsupported = useIsSwapUnsupported( currencies?.input, currencies?.output ); const userHasSpecifiedInputOutput = Boolean( currencies.input && currencies.output ); const routeNotFound = !trade?.route; const isLoadingRoute = userHasSpecifiedInputOutput && ((parsedAmounts.input && !parsedAmounts.output) || (!parsedAmounts.input && parsedAmounts.output)); const handleActiveTab = (tab: "sell" | "buy") => { if (activeTab === tab) return; handleRateType(rateType, price); setActiveTab(tab); }; const handleTypeInput = useCallback( (value: string) => { handleInput(Field.INPUT, value); }, [handleInput] ); const handleMaxInput = useCallback(() => { maxInputAmount && handleInput(Field.INPUT, maxInputAmount.toExact()); }, [maxInputAmount, handleInput]); const handleInputSelect = useCallback( (inputCurrency) => { handleCurrencySelection(Field.INPUT, inputCurrency); }, [handleCurrencySelection] ); const handleTypeDesiredRate = useCallback( (value: string) => { handleInput(Field.PRICE, value); }, [handleInput] ); const handleTypeOutput = useCallback( (value: string) => { handleInput(Field.OUTPUT, value); }, [handleInput] ); const handleOutputSelect = useCallback( (outputCurrency) => handleCurrencySelection(Field.OUTPUT, outputCurrency), [handleCurrencySelection] ); const handleRangeSelect = useCallback( (rangePrice) => { const { tick }: { tick: string; price: string } = rangePrice; handleRangeSelection(tick); }, [handleRangeSelection] ); const { gasPrice, realExecutionPrice, realExecutionPriceAsString, } = useGasOverhead(parsedAmounts.input, parsedAmounts.output, rateType); // modal and loading const [ { showConfirm, tradeToConfirm, swapErrorMessage, attemptingTxn, txHash }, setSwapState, ] = useState<{ showConfirm: boolean; tradeToConfirm: Trade | undefined; attemptingTxn: boolean; swapErrorMessage: string | undefined; txHash: string | undefined; }>({ showConfirm: false, tradeToConfirm: undefined, attemptingTxn: false, swapErrorMessage: undefined, txHash: undefined, }); const handleAcceptChanges = useCallback(() => { setSwapState({ tradeToConfirm: trade as any, swapErrorMessage, txHash, attemptingTxn, showConfirm, }); }, [attemptingTxn, showConfirm, swapErrorMessage, trade, txHash]); const [ approvalState, approveCallback, ] = useApproveCallbackFromInputCurrencyAmount(parsedAmounts.input); // const inputError = false; const isValid = !inputError; // check if user has gone through approval process, used to show two step buttons, reset on token change const [approvalSubmitted, setApprovalSubmitted] = useState(false); // mark when a user has submitted an approval, reset onTokenSelection for input field useEffect(() => { if (approvalState === ApprovalState.PENDING) { setApprovalSubmitted(true); } }, [approvalState, approvalSubmitted]); useEffect(() => { updateRange(); }, [updateRange]); const showApproveFlow = !inputError && (approvalState === ApprovalState.NOT_APPROVED || approvalState === ApprovalState.PENDING || (approvalSubmitted && approvalState === ApprovalState.APPROVED)); const handleApprove = useCallback(async () => { await approveCallback(); }, [approveCallback]); const handleConfirmDismiss = useCallback(() => { setSwapState({ showConfirm: false, tradeToConfirm, attemptingTxn, swapErrorMessage, txHash, }); // if there was a tx hash, we want to clear the input if (txHash) { handleInput(Field.INPUT, ""); } }, [attemptingTxn, handleInput, swapErrorMessage, tradeToConfirm, txHash]); const handleSwap = useCallback(() => { if (!handleRangeOrderSubmission) { return; } setSwapState({ attemptingTxn: true, tradeToConfirm, showConfirm, swapErrorMessage: undefined, txHash: undefined, }); try { if (!currencies.input?.wrapped.address) { throw new Error("Invalid input currency"); } if (!currencies.output?.wrapped.address) { throw new Error("Invalid output currency"); } if (!rawAmounts.input) { throw new Error("Invalid input amount"); } if (!rawAmounts.output) { throw new Error("Invalid output amount"); } handleRangeOrderSubmission({ inputCurrency: currencies.input, outputCurrency: currencies.output, inputAmount: BigNumber.from(rawAmounts.input), }) .then(({ hash }: any) => { setSwapState({ attemptingTxn: false, tradeToConfirm, showConfirm, swapErrorMessage: undefined, txHash: hash, }); }) .catch((error: any) => { setSwapState({ attemptingTxn: false, tradeToConfirm, showConfirm, swapErrorMessage: error.data ? error.data.message ?? error.message : error.message, txHash: undefined, }); }); } catch (error: any) { setSwapState({ attemptingTxn: false, tradeToConfirm, showConfirm, swapErrorMessage: error.message, txHash: undefined, }); } }, [ handleRangeOrderSubmission, tradeToConfirm, showConfirm, currencies, rawAmounts.input, rawAmounts.output, ]); return ( {/* TODO: ConfirmSwapModal */}
{rateType === Rate.MUL ? ( ) : ( )} {/* Range price selector */} {maxFeeAmount && ( console.log()} value={maxFeeAmount} /> )} { handleSwitchTokens(); }} color={ currencies.input && currencies.output ? theme.text1 : theme.text3 } />
{swapIsUnsupported ? ( Unsupported Asset ) : !account ? ( Connect Wallet ) : routeNotFound && isLoadingRoute ? ( Loading ) : showApproveFlow ? ( {/* we need to shorten this string on mobile */} {approvalState === ApprovalState.APPROVED ? `You can now use your ${currencies.input?.symbol} to place orders.` : `Allow the Gelato Range Orders to use your ${currencies.input?.symbol}.`} {approvalState === ApprovalState.PENDING || (approvalSubmitted && approvalState === ApprovalState.NOT_APPROVED) ? ( ) : approvalSubmitted && approvalState === ApprovalState.APPROVED ? ( ) : ( )} { setSwapState({ tradeToConfirm: trade, attemptingTxn: false, swapErrorMessage: undefined, showConfirm: true, txHash: undefined, }); }} id="range-order-button" disabled={ !isValid || approvalState !== ApprovalState.APPROVED } error={false} > {inputError ? inputError : `Place order`} ) : ( { setSwapState({ tradeToConfirm: trade, attemptingTxn: false, swapErrorMessage: undefined, showConfirm: true, txHash: undefined, }); }} id="range-order-button" disabled={!isValid} error={false} > {inputError ? inputError : `Place order`} )} {swapErrorMessage && isValid ? ( ) : null}
); }