From 65c80cb347c432ccbf832fdb7039d03b9eb6d709 Mon Sep 17 00:00:00 2001 From: denis-orbs Date: Sat, 2 Mar 2024 16:51:03 +0200 Subject: [PATCH] pancake limit --- packages/dapp-example/src/hooks.tsx | 24 +++---- packages/lib/src/components/Components.tsx | 12 +++- packages/lib/src/context.tsx | 8 +-- packages/lib/src/hooks.ts | 2 + packages/lib/src/store.ts | 19 +++++- packages/lib/src/types.ts | 1 + packages/pancake/src/PancakeOrders.tsx | 59 +++------------- packages/pancake/src/index.tsx | 44 +++++++++--- packages/pancake/src/styles.tsx | 78 +++++++++++++++++++++- 9 files changed, 162 insertions(+), 85 deletions(-) diff --git a/packages/dapp-example/src/hooks.tsx b/packages/dapp-example/src/hooks.tsx index b0f74c3b..03d9120b 100644 --- a/packages/dapp-example/src/hooks.tsx +++ b/packages/dapp-example/src/hooks.tsx @@ -194,31 +194,31 @@ export const usePriceUSD = (address?: string) => { function delay(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } + +const handleAddress = (address?: string) => { + if (address === "BNB") return zeroAddress; + return address; +}; export const useTrade = (fromToken?: TokenData, toToken?: TokenData, srcAmount?: string) => { - const fromTokenUsd = usePriceUSD(fromToken?.address); - const toTokenUsd = usePriceUSD(toToken?.address); + const fromTokenUsd = usePriceUSD(handleAddress(fromToken?.address)); + const toTokenUsd = usePriceUSD(handleAddress(toToken?.address)); const { chainId } = useWeb3React(); - const { isLimitOrder, limitDstPriceFor1Src } = store.useTwapStore((s) => ({ - isLimitOrder: s.isLimitOrder, - limitDstPriceFor1Src: s.getLimitPrice(false).limitPrice, - })); const query = useQuery({ - queryKey: ["useTrade", fromToken?.address, toToken?.address, srcAmount, chainId, isLimitOrder], + queryKey: ["useTrade", fromToken?.address, toToken?.address, srcAmount, chainId], queryFn: async () => { await delay(1000); const result = convertDecimals( - !isLimitOrder - ? BigNumber(srcAmount!) - .times(fromTokenUsd || "0") - .div(toTokenUsd || "0") - : BigNumber(srcAmount || "0").times(limitDstPriceFor1Src), + BigNumber(srcAmount!) + .times(fromTokenUsd || "0") + .div(toTokenUsd || "0"), fromToken!.decimals, toToken!.decimals ).integerValue(BigNumber.ROUND_FLOOR); return result.toString(); }, + refetchInterval: 3_000, enabled: !!fromToken && !!toToken && !!srcAmount && !!fromTokenUsd && !!toTokenUsd, }); diff --git a/packages/lib/src/components/Components.tsx b/packages/lib/src/components/Components.tsx index f85f458e..94ba8a6f 100644 --- a/packages/lib/src/components/Components.tsx +++ b/packages/lib/src/components/Components.tsx @@ -183,7 +183,7 @@ const SrcTokenInput = (props: { className?: string; placeholder?: string }) => { }; const DstTokenInput = (props: { className?: string; placeholder?: string; decimalScale?: number }) => { - const { token, amount, srcAmount, isLimitOrder } = useTwapStore((store) => ({ + const { token, amount, isLimitOrder } = useTwapStore((store) => ({ token: store.dstToken, amount: store.getDstAmountUi(), srcAmount: store.srcAmountUi, @@ -755,6 +755,7 @@ export const OrderSummaryTokenDisplay = ({ isSrc, usdSuffix, usdPrefix }: { isSr const amount = isSrc ? srcAmount : dstAmount; const prefix = isSrc ? "" : isLimitOrder ? "≥ " : "~ "; + const _amount = useFormatNumber({ value: amount, decimalScale: 5 }); return ( @@ -766,7 +767,7 @@ export const OrderSummaryTokenDisplay = ({ isSrc, usdSuffix, usdPrefix }: { isSr {prefix && {prefix}} - {amount} + {_amount} @@ -1092,10 +1093,15 @@ export const CopyTokenAddress = ({ isSrc }: { isSrc: boolean }) => { }; export const ResetLimitButton = ({ children }: { children?: ReactNode }) => { - const setLimitOrderPriceUi = useTwapStore((store) => store.setLimitOrderPriceUi); + const { setLimitOrderPriceUi, dstAmountFromDex, setOutAmount } = useTwapStore((store) => ({ + setLimitOrderPriceUi: store.setLimitOrderPriceUi, + dstAmountFromDex: store.dstAmountFromDex, + setOutAmount: store.setOutAmount, + })); const { custom } = useLimitPrice(); const onClick = () => { setLimitOrderPriceUi(); + setOutAmount(dstAmountFromDex); }; if (!custom) return null; diff --git a/packages/lib/src/context.tsx b/packages/lib/src/context.tsx index 8bc78ee8..c766baa7 100644 --- a/packages/lib/src/context.tsx +++ b/packages/lib/src/context.tsx @@ -41,12 +41,12 @@ const useLimitPriceUpdater = () => { }; const Listener = (props: TwapLibProps) => { - const { srcToken, dstToken, srcAmount, updateState } = useTwapStore((s) => ({ + const { srcToken, dstToken, srcAmount, setOutAmount } = useTwapStore((s) => ({ srcToken: s.srcToken, dstToken: s.dstToken, srcAmount: s.getSrcAmount().toString(), - updateState: s.updateState, + setOutAmount: s.setOutAmount, })); const setTokensFromDappCallback = useSetTokensFromDapp(); @@ -55,8 +55,8 @@ const Listener = (props: TwapLibProps) => { const result = props.useTrade?.(srcToken, dstToken, srcAmount === "0" ? undefined : srcAmount); useEffect(() => { - updateState({ dstAmountLoading: result?.isLoading, dstAmount: result?.outAmount }); - }, [result?.isLoading, result?.outAmount, updateState]); + setOutAmount(result?.outAmount, result?.isLoading); + }, [result?.isLoading, result?.outAmount, setOutAmount]); useEffect(() => { updateStoreOveride(props.storeOverride); diff --git a/packages/lib/src/hooks.ts b/packages/lib/src/hooks.ts index 98158ad1..93c483d0 100644 --- a/packages/lib/src/hooks.ts +++ b/packages/lib/src/hooks.ts @@ -589,6 +589,8 @@ export const useSetTokensFromDapp = () => { if (srcTokenAddressOrSymbol) { const srcToken = getTokenFromTokensList(tokensList, srcTokenAddressOrSymbol); + console.log({ srcToken }); + setSrcToken(srcToken); } if (dstTokenAddressOrSymbol) { diff --git a/packages/lib/src/store.ts b/packages/lib/src/store.ts index 526162a4..3ff713bf 100644 --- a/packages/lib/src/store.ts +++ b/packages/lib/src/store.ts @@ -49,6 +49,7 @@ const initialState: State = { orderCreatedTimestamp: undefined, dstAmount: undefined, dstAmountLoading: false, + dstAmountFromDex: undefined, }; export const useTwapStore = create( @@ -75,8 +76,15 @@ export const useTwapStore = create( dstUsd: get().dstUsd, srcBalance: get().srcBalance, dstBalance: get().dstBalance, + wrongNetwork: get().wrongNetwork, }); }, + setOutAmount: (dstAmount?: string, dstAmountLoading?: boolean) => { + set({ dstAmountFromDex: dstAmount }); + if (!get().limitPriceUi.custom) { + set({ dstAmount, dstAmountLoading, limitPriceUi: { ...get().limitPriceUi, priceUi: (get() as any).getMarketPrice(false).marketPriceUi } }); + } + }, updateState: (values: Partial) => set({ ...values }), setOrderCreatedTimestamp: (orderCreatedTimestamp: number) => set({ orderCreatedTimestamp }), reset: (storeOverride: StoreOverride) => @@ -90,7 +98,7 @@ export const useTwapStore = create( set({ dstToken, limitPriceUi: { ...get().limitPriceUi, custom: false } }); }, setSrcAmountUi: (srcAmountUi: string) => { - set({ srcAmountUi, dstAmountLoading: true }); + set({ srcAmountUi, dstAmountLoading: true, limitPriceUi: { ...get().limitPriceUi, priceUi: "", custom: false } }); (get() as any).setChunks(get().chunks); }, setSrcBalance: (srcBalance: BN) => set({ srcBalance }), @@ -151,10 +159,15 @@ export const useTwapStore = create( getIsPartialFillWarning: () => (get() as any).getChunks() * (get() as any).getFillDelayUiMillis() > (get() as any).getDurationMillis(), setDisclaimerAccepted: (disclaimerAccepted: boolean) => set({ disclaimerAccepted }), setWrongNetwork: (wrongNetwork?: boolean) => set({ wrongNetwork }), - setLimitPriceUi: (limitPriceUi: { priceUi: string; inverted: boolean }) => set({ limitPriceUi: { ...limitPriceUi, custom: true } }), + setLimitPriceUi: (limitPriceUi: { priceUi: string; inverted: boolean }) => + set({ + limitPriceUi: { ...limitPriceUi, custom: true }, + dstAmount: BN((get() as any).getSrcAmount() || "0") + .times(limitPriceUi.priceUi || "0") + .toString(), + }), setChunks: (chunks: number) => set({ chunks: Math.min(chunks, (get() as any).getMaxPossibleChunks()) }), setDuration: (customDuration: Duration) => set({ customDuration }), - setDstAmount: (dstAmount: string) => set({ dstAmount }), getMaxPossibleChunks: () => (get().lib && get().srcToken ? get().lib!.maxPossibleChunks(get().srcToken!, amountBN(get().srcToken, get().srcAmountUi), get().srcUsd) : 1), getChunks: () => { const srcUsd = get().srcUsd; diff --git a/packages/lib/src/types.ts b/packages/lib/src/types.ts index 71907b88..783507ca 100644 --- a/packages/lib/src/types.ts +++ b/packages/lib/src/types.ts @@ -283,6 +283,7 @@ export interface State { showLoadingModal: boolean; showSuccessModal: boolean; dstAmount?: string; + dstAmountFromDex?: string; dstAmountLoading?: boolean; } diff --git a/packages/pancake/src/PancakeOrders.tsx b/packages/pancake/src/PancakeOrders.tsx index 0760ac39..dd4b536e 100644 --- a/packages/pancake/src/PancakeOrders.tsx +++ b/packages/pancake/src/PancakeOrders.tsx @@ -1,13 +1,8 @@ import * as React from "react"; -import Button from "@mui/material/Button"; -import Menu from "@mui/material/Menu"; -import MenuItem from "@mui/material/MenuItem"; import { Box, Fade, styled } from "@mui/material"; -import { hooks, OrdersContainer, OrdersPortal, SelectedOrders, store, OrdersHeader } from "@orbs-network/twap-ui"; -import { IoMdArrowDropdown } from "@react-icons/all-files/io/IoMdArrowDropdown"; -import { StyledOrders, StyledOrdersMenuButton } from "./styles"; +import { hooks, OrdersPortal, SelectedOrders, store } from "@orbs-network/twap-ui"; +import { StyledOrders, StyledOrdersHeader, StyledOrdersTab, StyledOrdersTabs } from "./styles"; import { Styles } from "@orbs-network/twap-ui"; -import { Components } from "@orbs-network/twap-ui"; export default function PancakeOrders() { const [anchorEl, setAnchorEl] = React.useState(null); @@ -18,9 +13,9 @@ export default function PancakeOrders() { return ( -
+ -
+ {/* */} @@ -33,14 +28,6 @@ export default function PancakeOrders() { const StyledBody = styled(Styles.StyledColumnFlex)({ padding: 20, alignItems: "center", - gap: 20, -}); - -const Header = styled(Styles.StyledRowFlex)({ - background: "#372f47", - borderTopLeftRadius: 16, - borderTopRightRadius: 16, - height: 48, }); const Tabs = () => { @@ -56,42 +43,14 @@ const Tabs = () => { }; return ( - + {Object.keys(tabs).map((key, index) => { - const amount = tabs[key as keyof typeof tabs]; - const label = ` ${key} (${amount})`; - console.log(key, selectedTab); - return ( - onSelect(index)}> - {label} - + onSelect(index)}> + {key} + ); })} - + ); }; - -const StyledTab = styled(Box)<{ selected: number }>(({ selected }) => ({ - cursor: "pointer", - background: selected ? "#27262c" : "transparent", - height: "100%", - padding: " 0px 24px", - display: "flex", - alignItems: "center", - borderTopLeftRadius: 16, - borderTopRightRadius: 16, - flex: 1, - justifyContent: "center", -})); - -const StyledTabs = styled(Box)({ - display: "flex", - alignItems: "center", - width: "100%", - justifyContent: "space-between", - height: "100%", - flex: 1, -}); - -const StyledMenuButtonContainer = styled("div")({}); diff --git a/packages/pancake/src/index.tsx b/packages/pancake/src/index.tsx index fe670ab5..0decf93c 100644 --- a/packages/pancake/src/index.tsx +++ b/packages/pancake/src/index.tsx @@ -12,7 +12,9 @@ import { StyledChunksSlider, StyledColumnFlex, StyledLimitPrice, - StyledLimitPriceInput, + StyledLimitPriceBody, + StyledLimitPriceBottom, + StyledLimitPriceLabel, StyledMarketPriceContainer, StyledOutputAddress, StyledPoweredBy, @@ -25,6 +27,7 @@ import { StyledTotalChunks, StyledTradeSize, } from "./styles"; +import { FaExchangeAlt } from "@react-icons/all-files/fa/FaExchangeAlt"; import { JSXElementConstructor, memo, ReactNode, useCallback, useEffect, useMemo, useState } from "react"; import { @@ -40,7 +43,7 @@ import { StyledTokenSelect, StyledUSD, } from "./styles"; -import { chainId, isNativeAddress, zeroAddress } from "@defi.org/web3-candies"; +import { chainId, isNativeAddress, TokenERC1155, zeroAddress } from "@defi.org/web3-candies"; import { Configs, TokenData } from "@orbs-network/twap"; import { createContext, useContext } from "react"; import Web3 from "web3"; @@ -52,7 +55,6 @@ import { AiOutlineArrowDown } from "@react-icons/all-files/ai/AiOutlineArrowDown import { GrPowerReset } from "@react-icons/all-files/gr/GrPowerReset"; import { BsQuestionCircle } from "@react-icons/all-files/bs/BsQuestionCircle"; import PancakeOrders from "./PancakeOrders"; -import { amountUi } from "@orbs-network/twap-ui"; const uiPreferences: TwapContextUIPreferences = { usdSuffix: " USD", @@ -173,7 +175,9 @@ const SrcTokenPercentSelector = () => { return ( {PERCENT.map((p) => { - const selected = percent === p.value || (p.value === 1 && BN(getMaxSrcInputAmount || 0).isEqualTo(srcAmount)); + const selected = BN(srcAmount || "0").isZero() + ? false + : Math.round(percent * 100) === p.value * 100 || (p.value === 1 && BN(getMaxSrcInputAmount || 0).isEqualTo(srcAmount)); return ( onClick(p.value)}> {p.text} @@ -230,6 +234,15 @@ const TWAP = memo((props: AdapterProps) => { return props.isDarkTheme ? darkTheme : lightTheme; }, [props.isDarkTheme]); + const useTrade = (fromToken?: TokenData, toToken?: TokenData, value?: string) => { + const _fromToken = useMemo(() => { + if (!fromToken) return undefined; + const address = isNativeAddress(fromToken?.address || "") ? "BNB" : fromToken?.address; + return fromToken ? { ...fromToken, address } : undefined; + }, [fromToken]); + return props.useTrade!(_fromToken, toToken, value); + }; + return ( { onDstTokenSelected={props.onDstTokenSelected} usePriceUSD={props.usePriceUSD} onSrcTokenSelected={props.onSrcTokenSelected} - useTrade={props.useTrade} + useTrade={useTrade} isDarkTheme={props.isDarkTheme} > @@ -420,13 +433,14 @@ const TradeInterval = () => { const LimitPrice = ({ limitOnly }: { limitOnly?: boolean }) => { const isLimitOrder = store.useTwapStore((store) => store.isLimitOrder); + const { leftToken, rightToken, onChange, limitPrice, toggleInverted } = hooks.useLimitPrice(); return ( - + @@ -436,18 +450,28 @@ const LimitPrice = ({ limitOnly }: { limitOnly?: boolean }) => { - + {!limitOnly && } {isLimitOrder && ( - - - + + + + + +

+ {rightToken?.symbol} Per {leftToken?.symbol} +

+ +
+
)}
); }; +console.log(Components.Base); + export { TWAP, Orders }; diff --git a/packages/pancake/src/styles.tsx b/packages/pancake/src/styles.tsx index ed5f1863..ad061ce3 100644 --- a/packages/pancake/src/styles.tsx +++ b/packages/pancake/src/styles.tsx @@ -146,6 +146,7 @@ export const configureStyles = (theme: Theme) => { ".twap-orders-list": { padding: 0, width: "100%", + gap: "15px!important", }, ".twap-order-token-display": { flex: "unset!important", @@ -202,7 +203,7 @@ export const configureStyles = (theme: Theme) => { padding: 15, transition: "0.2s all", color: `${styles.primaryTextColor}!important`, - background: styles.cardColor, + background: darkMode ? "#362F47" : "#EEEAF4", }, ".twap-order-progress": { background: darkMode ? "#2D2836!important" : "#eeeaf4!important", @@ -245,7 +246,8 @@ export const configureStyles = (theme: Theme) => { padding: "0px!important", }, ".twap-time-selector-list-item": { - p: { color: styles.primaryTextColor }, + p: { color: styles.primaryTextColor, fontWeight: "400!important" }, + "&:hover": { background: darkMode ? "rgba(255,255,255, 0.06)" : "rgba(0,0,0, 0.06)", }, @@ -490,6 +492,10 @@ export const StyledLimitPrice = styled(Styles.StyledRowFlex)(({ theme }) => { "*": { color: styles.primaryTextColor, }, + input: { + position: "relative", + top: -2, + }, }, ".twap-token-logo": { display: "none", @@ -712,11 +718,12 @@ export const StyledTimeSelectContainer = styled(Styles.StyledRowFlex)({ height: "100%", p: { fontSize: "13px!important", + fontWeight: 400, }, }, ".twap-input": { input: { - paddingRight: 6, + paddingRight: 3, }, }, }); @@ -725,3 +732,68 @@ export const StyledTimeSelectHeader = styled(Card.Header)({ marginTop: 1, width: "auto", }); + +export const StyledOrdersHeader = styled(Box)(({ theme }) => { + const styles = baseStyles(theme); + return { + display: "flex", + flexDirection: "column", + background: styles.darkMode ? "#372f47" : "#eeeaf4", + borderTopLeftRadius: 16, + borderTopRightRadius: 16, + height: 48, + }; +}); + +export const StyledOrdersTab = styled(Box)<{ selected: number }>(({ selected, theme }) => { + const styles = baseStyles(theme); + const color = styles.darkMode ? "#b8add2" : "#7a6eaa"; + const selectedColor = styles.darkMode ? "#f4eeff" : "#280d5f"; + return { + cursor: "pointer", + background: !selected ? "transparent" : styles.darkMode ? "#27262c" : "white", + height: "100%", + padding: " 0px 24px", + display: "flex", + alignItems: "center", + borderTopLeftRadius: 16, + borderTopRightRadius: 16, + flex: 1, + justifyContent: "center", + fontWeight: 500, + color: !selected ? color : selectedColor, + }; +}); + +export const StyledOrdersTabs = styled(Box)({ + display: "flex", + alignItems: "center", + width: "100%", + justifyContent: "space-between", + height: "100%", + flex: 1, +}); + +export const StyledLimitPriceBody = styled(Card.Body)({ + padding: "10px 10px", + input: { + textAlign: "right", + }, +}); + +export const StyledLimitPriceBottom = styled(Styles.StyledRowFlex)({ + gap: 10, + justifyContent: "flex-end", + cursor: "pointer", + p: { + fontSize: 14, + fontWeight: 500, + padding: 0, + margin: 0, + }, +}); + +export const StyledLimitPriceLabel = styled(Styles.StyledRowFlex)({ + width: "auto", + minHeight: 24, +});