import React, { useCallback, useMemo, useState, useEffect } from "react"; import styled, { DefaultTheme } from "styled-components/macro"; import { darken } from "polished"; import { ArrowRight } from "react-feather"; import { Text } from "rebass"; import { RowBetween } from "../../Row"; import { RangeOrderData as Order } from "@gelatonetwork/range-orders-lib"; import useTheme from "../../../hooks/useTheme"; import { useCurrency } from "../../../hooks/Tokens"; import CurrencyLogo from "../../CurrencyLogo"; import { useGelatoRangeOrdersHandlers } from "../../../hooks/gelato"; import { CurrencyAmount, Price } from "@uniswap/sdk-core"; import ConfirmCancellationModal from "../ConfirmCancellationModal"; import { useTradeExactIn } from "../../../hooks/useTrade"; import { Dots } from "../../order/styleds"; import { useWeb3 } from "../../../web3"; import { ButtonGray } from "../../Button"; import { useIsTransactionPending } from "../../../state/gtransactions/hooks"; import { ExplorerDataType, getExplorerLink, } from "../../../utils/getExplorerLink"; import TradePrice from "../../order/TradePrice"; import useGelatoRangeOrdersLib from "../../../hooks/gelato/useGelatoRangeOrdersLib"; import { usePoolContract } from "../../../hooks/useContract"; import { BigNumber, constants } from "ethers"; import { MAX_FEE_AMOUNTS } from "../../../constants/misc"; import { getAdjustAmountFrom18 } from "../../../utils/adjustCurrencyDecimals"; const handleColorType = (status: string, theme: DefaultTheme) => { switch (status) { case "open": return theme.blue1; case "executed": return theme.green1; case "cancelled": return theme.red1; case "pending": return theme.yellow1; default: return theme.text3; } }; const OrderPanel = styled.div` ${({ theme }) => theme.flexColumnNoWrap} position: relative; border-radius: "16px"; background-color: ${() => "transparent"}; z-index: 1; width: "100%"; `; const Container = styled.div<{ hideInput: boolean }>` border-radius: ${({ hideInput }) => (hideInput ? "16px" : "20px")}; border: 1px solid ${" transparent"}; background-color: ${({ theme }) => theme.bg1}; width: ${({ hideInput }) => (hideInput ? "100%" : "initial")}; :focus, :hover { border: 1px solid ${({ theme, hideInput }) => (hideInput ? " transparent" : theme.bg3)}; } `; const LabelRow = styled.div` ${({ theme }) => theme.flexRowNoWrap} align-items: center; color: ${({ theme }) => theme.text1}; font-size: 0.75rem; line-height: 1rem; padding: 0 1rem 1rem; span:hover { cursor: pointer; color: ${({ theme }) => darken(0.2, theme.text2)}; } `; const Aligner = styled.span` display: flex; align-items: center; justify-content: space-between; width: 100%; `; const StyledTokenName = styled.span<{ active?: boolean }>` ${({ active }) => active ? " margin: 0 0.25rem 0 0.25rem;" : " margin: 0 0.25rem 0 0.25rem;"} font-size: ${({ active }) => (active ? "14px" : "14px")}; `; const OrderRow = styled(LabelRow)` justify-content: flex-end; margin-top: -8px; `; const OrderStatus = styled.span<{ status: string; clickable: boolean }>` font-size: 0.825rem; font-weight: 600; padding: 0.5rem; border-radius: 8px; cursor: ${({ clickable }) => (clickable ? "pointer" : "default")}; color: ${({ status, theme }) => handleColorType(status, theme)}; border: 1px solid ${({ status, theme }) => handleColorType(status, theme)}; width: fit-content; justify-self: flex-end; text-transform: uppercase; :hover { content: "Reply!"; border: 1px solid ${({ status, theme, clickable }) => clickable ? handleColorType("cancelled", theme) : handleColorType(status, theme)}; color: ${({ status, theme, clickable }) => clickable ? handleColorType("cancelled", theme) : handleColorType(status, theme)}; } `; export const ArrowWrapper = styled.div` padding: 4px; border-radius: 12px; height: 32px; width: 32px; margin-right: 2px; margin-left: -10px; background-color: ${({ theme }) => theme.bg1}; border: 4px solid ${({ theme }) => theme.bg1}; `; const CurrencySelect = styled(ButtonGray)<{ selected: boolean; hideInput?: boolean; }>` align-items: center; font-size: 24px; font-weight: 500; background-color: ${({ selected, theme }) => selected ? theme.bg0 : theme.primary1}; color: ${({ selected, theme }) => (selected ? theme.text1 : theme.white)}; border-radius: 16px; box-shadow: ${({ selected }) => selected ? "none" : "0px 6px 10px rgba(0, 0, 0, 0.075)"}; box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075); outline: none; cursor: default; user-select: none; border: none; height: ${({ hideInput }) => (hideInput ? "2.8rem" : "2.4rem")}; width: ${({ hideInput }) => (hideInput ? "100%" : "initial")}; padding: 0 8px; justify-content: space-between; margin-right: ${({ hideInput }) => (hideInput ? "0" : "12px")}; &:focus { box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075); background-color: ${({ selected, theme }) => selected ? theme.bg0 : theme.primary1}; } :hover { background-color: ${({ selected, theme }) => selected ? theme.bg0 : theme.primary1}; } `; const Spacer = styled.div` flex: 1 1 auto; `; export default function OrderCard({ order }: { order: Order }) { const theme = useTheme(); const { chainId, handler } = useWeb3(); const [ showExecutionPriceInverted, setShowExecutionPriceInverted, ] = useState(false); const [ showCurrentPriceInverted, setShowCurrentPriceInverted, ] = useState(true); const { handleRangeOrderCancellation } = useGelatoRangeOrdersHandlers(); const gelatoLibrary = useGelatoRangeOrdersLib(); const poolContract = usePoolContract(order.pool); const [token0, setToken0] = useState(); const [token1, setToken1] = useState(); const [minReturnRaw, setMinReturn] = useState(); const inputToken = useCurrency(order.zeroForOne ? token0 : token1); const outputToken = useCurrency(order.zeroForOne ? token1 : token0); const { minReturn } = useMemo(() => { if (!outputToken || !minReturnRaw) return { minReturn: undefined, slippagePercentage: undefined, gelatoFeePercentage: undefined, }; return { minReturn: getAdjustAmountFrom18( minReturnRaw.toString(), outputToken?.decimals ), }; }, [minReturnRaw, outputToken]); const inputAmount = useMemo( () => inputToken && order.amountIn ? CurrencyAmount.fromRawAmount(inputToken, order.amountIn.toString()) : undefined, [inputToken, order.amountIn] ); const outputAmount = useMemo( () => outputToken && minReturn ? CurrencyAmount.fromRawAmount(outputToken, minReturn.toString()) : undefined, [outputToken, minReturn] ); useEffect(() => { async function getTokenList() { if (poolContract) { const t0 = await poolContract.token0(); setToken0(t0); const t1 = await poolContract.token1(); setToken1(t1); } } getTokenList(); }, [poolContract]); useEffect(() => { async function getMinReturn() { if ( gelatoLibrary && chainId && order.pool && typeof order.zeroForOne !== "undefined" && order.tickThreshold && order.amountIn && order.receiver ) { const mr = await gelatoLibrary.getMinReturn({ pool: order.pool, zeroForOne: order.zeroForOne, tickThreshold: order.tickThreshold.toNumber(), amountIn: order.amountIn, minLiquidity: constants.Zero, receiver: order.receiver, maxFeeAmount: BigNumber.from(MAX_FEE_AMOUNTS[chainId].toString()), }); setMinReturn(mr); } } getMinReturn(); }, [chainId, gelatoLibrary, order]); const executionPrice = useMemo( () => outputAmount && outputAmount.greaterThan(0) && inputAmount ? new Price({ baseAmount: outputAmount, quoteAmount: inputAmount, }) : undefined, [inputAmount, outputAmount] ); const trade = useTradeExactIn(inputAmount, outputToken ?? undefined, handler); const isSubmissionPending = useIsTransactionPending( order.submittedTxHash ? order.submittedTxHash.toString() : undefined ); const isCancellationPending = useIsTransactionPending( order.cancelledTxHash ? order.cancelledTxHash.toString() : undefined ); // modal and loading const [ { showConfirm, cancellationErrorMessage, attemptingTxn, txHash }, setCancellationState, ] = useState<{ showConfirm: boolean; attemptingTxn: boolean; cancellationErrorMessage: string | undefined; txHash: string | undefined; }>({ showConfirm: false, attemptingTxn: false, cancellationErrorMessage: undefined, txHash: undefined, }); const handleConfirmDismiss = useCallback(() => { setCancellationState({ showConfirm: false, attemptingTxn, cancellationErrorMessage, txHash, }); }, [attemptingTxn, cancellationErrorMessage, txHash]); const handleCancellation = useCallback(() => { if (!handleRangeOrderCancellation) { return; } setCancellationState({ attemptingTxn: true, showConfirm, cancellationErrorMessage: undefined, txHash: undefined, }); const orderDetails = inputToken?.symbol && outputToken?.symbol && inputAmount && outputAmount ? { inputTokenSymbol: inputToken.symbol, outputTokenSymbol: outputToken.symbol, inputAmount: inputAmount.toSignificant(4), outputAmount: outputAmount.toSignificant(4), } : undefined; handleRangeOrderCancellation(order, orderDetails) .then(({ hash }) => { setCancellationState({ attemptingTxn: false, showConfirm, cancellationErrorMessage: undefined, txHash: hash, }); }) .catch((error) => { setCancellationState({ attemptingTxn: false, showConfirm, cancellationErrorMessage: error.message, txHash: undefined, }); }); }, [ handleRangeOrderCancellation, showConfirm, inputToken, outputToken, inputAmount, outputAmount, order, ]); const OrderCard = ({ showStatusButton = true, }: { showStatusButton?: boolean; }) => ( {inputToken ? ( {inputToken?.name ?? } ) : ( )} {outputToken ? ( {outputToken.name ?? } ) : ( )} {showStatusButton ? ( { if (!chainId) return; if (order.status === "submitted" && !isSubmissionPending) setCancellationState({ attemptingTxn: false, cancellationErrorMessage: undefined, showConfirm: true, txHash: undefined, }); else if (order.status === "submitted" && isSubmissionPending) window.open( getExplorerLink( chainId, order.submittedTxHash ? order.submittedTxHash.toString() : "", ExplorerDataType.TRANSACTION ), "_blank" ); else if (order.status === "cancelled" && order.cancelledTxHash) window.open( getExplorerLink( chainId, order.cancelledTxHash ? order.cancelledTxHash.toString() : "", ExplorerDataType.TRANSACTION ), "_blank" ); else if (order.status === "executed" && order.executedTxHash) window.open( getExplorerLink( chainId, order.executedTxHash ? order.executedTxHash.toString() : "", ExplorerDataType.TRANSACTION ), "_blank" ); }} status={ isCancellationPending || isSubmissionPending ? "pending" : order.status ?? "pending" } > {isSubmissionPending ? "pending" : isCancellationPending ? "cancelling" : order.status === "submitted" ? "cancel" : order.status} {isSubmissionPending || isCancellationPending ? : null} ) : null} {`Sell ${inputAmount ? inputAmount.toSignificant(4) : "-"} ${ inputAmount?.currency.symbol ?? "" } for ${outputAmount ? outputAmount.toSignificant(4) : "-"} ${ outputAmount?.currency.symbol ?? "" }`} Current price: {trade ? ( ) : ( )} {order.status === "submitted" ? ( Execution price: {executionPrice ? ( ) : ( )} ) : null} ); return ( <> ( <>
)} /> ); }