diff --git a/src/app/BridgeSwap/components/ActionButton.tsx b/src/app/BridgeSwap/components/ActionButton.tsx index 93469f878..76598cff7 100644 --- a/src/app/BridgeSwap/components/ActionButton.tsx +++ b/src/app/BridgeSwap/components/ActionButton.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { actionMode } from "@/recoil/bridgeSwap/atom"; import { Button } from "@chakra-ui/react"; import { useRecoilValue } from "recoil"; @@ -31,7 +31,7 @@ export default function ActionButton() { const { isNotSupportForSwap } = useBridgeSupport(); const [isLoading] = useIsLoading(); - const [isDisabled, setIsDisabled] = useState(true); + // const [isDisabled, setIsDisabled] = useState(true); const { isBalanceOver, isInputZero } = useInputBalanceCheck(); const txPending = useRecoilValue(txPendingStatus); const { outToken, inToken } = useInOutTokens(); @@ -42,29 +42,23 @@ export default function ActionButton() { const needToOpenWithdrawModal = mode === "Withdraw"; const needToOpenDepositModal = mode === "Deposit"; const needToOpenSwapModal = mode === "Swap"; - const isL2 = inNetwork?.layer === "L2" || outNetwork?.layer === "L2"; //checks if the action is L2 - const deactivateButton = status === "Active" && isL2; //when the maintenance banner is active, this will disable the action button related to all L2 actions - useEffect(() => { - const timeoutId = setTimeout(() => { - const disabled = - !isReady || - isApproved === false || - (mode === "Swap" && isLoading) || - isNotSupportForSwap || - isBalanceOver || - txPending || - (mode === "Swap" && outToken === null) || - isInputZero || - (mode === "Swap" && isTONatPair) || - deactivateButton; - setIsDisabled(disabled); - }, 200); - return () => { - clearTimeout(timeoutId); - }; + const isDisabled = useMemo(() => { + if ((mode === "Withdraw" || !isConnected) && !isInputZero) return false; + const disabled = + !isReady || + isApproved === false || + (mode === "Swap" && isLoading) || + isNotSupportForSwap || + isBalanceOver || + txPending || + (mode === "Swap" && outToken === null) || + isInputZero || + (mode === "Swap" && isTONatPair) || + deactivateButton; + return disabled; }, [ isReady, isApproved, @@ -76,17 +70,65 @@ export default function ActionButton() { isInputZero, mode, outToken, + isConnected, ]); + + // useEffect(() => { + // const timeoutId = setTimeout(() => { + // const disabled = + // !isReady || + // isApproved === false || + // (mode === "Swap" && isLoading) || + // isNotSupportForSwap || + // isBalanceOver || + // txPending || + // (mode === "Swap" && outToken === null) || + // isInputZero || + // (mode === "Swap" && isTONatPair) || + // deactivateButton; + // setIsDisabled(disabled); + // }, 200); + + // return () => { + // clearTimeout(timeoutId); + // }; + // }, [ + // isReady, + // isApproved, + // isLoading, + // isNotSupportForSwap, + // isBalanceOver, + // txPending, + // isTONatPair, + // isInputZero, + // mode, + // outToken, + // ]); + const { onClick } = useCallBridgeSwapAction(); const { connetAndDisconntWallet } = useConnectWallet(); - - { - /** add coming code @Robert */ - } const { onOpenCTOptionModal } = useCTOptionModal(); const handleConfirm = useHandleConfirm(); const { onOpenSwapConfirmModal } = useSwapConfirmModal(); + const buttonAction = useCallback(() => { + if (!isConnected) return connetAndDisconntWallet(); + if (needToOpenWithdrawModal) return onOpenCTOptionModal(); + if (needToOpenDepositModal) + return handleConfirm(Action.Deposit, Status.Initiate); + if (needToOpenSwapModal) return onOpenSwapConfirmModal(); + return onClick(); + }, [ + isConnected, + needToOpenWithdrawModal, + needToOpenDepositModal, + needToOpenSwapModal, + inToken, + outToken, + inNetwork, + outNetwork + ]); + return ( <> {/** FW UI test End @Robert*/} @@ -100,27 +142,17 @@ export default function ActionButton() { _disabled={{}} bgColor={!isConnected ? "#007AFF" : isDisabled ? "#17181D" : "#007AFF"} color={!isConnected ? "fff" : isDisabled ? "#8E8E92" : "#fff"} - isDisabled={!isConnected ? false : isDisabled} - onClick={ - isConnected === false - ? () => connetAndDisconntWallet() - : needToOpenWithdrawModal - ? () => onOpenCTOptionModal() - : needToOpenDepositModal - ? () => handleConfirm(Action.Deposit, Status.Initiate) - : needToOpenSwapModal - ? () => onOpenSwapConfirmModal() - : onClick - } + isDisabled={isDisabled} + onClick={buttonAction} > {!isConnected && "Connect Wallet"} {!isConnected ? null : isConnected && mode === null - ? "Select Network" - : mode === "Withdraw" - ? "Next" - : mode?.replaceAll("ETH-", "")}{" "} + ? "Select Network" + : mode === "Withdraw" + ? "Next" + : mode?.replaceAll("ETH-", "")}{" "} {deactivateButton ? "(Service under maintenance)" : ""} {/* {'(Service under maintenance)'} */} diff --git a/src/assets/icons/lamp.svg b/src/assets/icons/lamp.svg new file mode 100644 index 000000000..6f40e0ebc --- /dev/null +++ b/src/assets/icons/lamp.svg @@ -0,0 +1,8 @@ + + + + + + + diff --git a/src/components/tooltip/CustomTooltip.tsx b/src/components/tooltip/CustomTooltip.tsx index 7fec777ba..be9daa901 100644 --- a/src/components/tooltip/CustomTooltip.tsx +++ b/src/components/tooltip/CustomTooltip.tsx @@ -18,7 +18,10 @@ export default function CustomTooltip(props: { px?: string; py?: string; tooltipLineHeight?: string; + left?: string; }; + labelStyle?: CSSProperties; + tooltipArrowStyle?: CSSProperties; needArrow?: boolean; }) { const [isOpen, setIsOpen] = useState(false); @@ -47,6 +50,7 @@ export default function CustomTooltip(props: { color={"#fff"} pos={"relative"} {...props.style} + style={props.labelStyle} > {props.content} {arrowNeeded && isOpen && ( - + {"TooltipArrow"} )} diff --git a/src/components/ui/GetHelp.tsx b/src/components/ui/GetHelp.tsx new file mode 100644 index 000000000..6da2aa7f8 --- /dev/null +++ b/src/components/ui/GetHelp.tsx @@ -0,0 +1,13 @@ +import { Box } from "@chakra-ui/react"; +import LampIcon from "@/assets/icons/lamp.svg"; +import Image from "next/image"; +import Link from "next/link"; +import { GoogleFormURL } from "@/constant/url"; + +export default function GetHelp() { + return ( + + Lamp + + ); +} diff --git a/src/constant/url/index.ts b/src/constant/url/index.ts new file mode 100644 index 000000000..8f56fd5f2 --- /dev/null +++ b/src/constant/url/index.ts @@ -0,0 +1,2 @@ +export const GoogleFormURL = + "https://docs.google.com/forms/u/1/d/e/1FAIpQLSfCUJjuABK0Locc3Fqwr2W5eHI-Hpj6wiiGceBr1e4q4g9nmg/viewform?usp=send_form"; diff --git a/src/graphql/thegraph/apollosForCT.ts b/src/graphql/thegraph/apollosForCT.ts index f7255fae7..1b1453ef1 100644 --- a/src/graphql/thegraph/apollosForCT.ts +++ b/src/graphql/thegraph/apollosForCT.ts @@ -36,8 +36,32 @@ const authMiddleware = (chainId: SupportedChainId) => return forward(operation); }); +const cacheL1 = new InMemoryCache({ + typePolicies: { + Query: { + fields: { + requestCTs: { + merge(existing = [], incoming) { + return incoming; + }, + }, + cancelCTs: { + merge(existing = [], incoming) { + return incoming; + }, + }, + providerClaimCTs: { + merge(existing = [], incoming) { + return incoming; + }, + }, + }, + }, + }, +}); + const apolloClient_Ethereum = new ApolloClient({ - cache: new InMemoryCache(), + cache: cacheL1, link: concat(authMiddleware(SupportedChainId.MAINNET), httpLink), }); @@ -47,7 +71,7 @@ const apolloClient_Titan = new ApolloClient({ }); const apolloClient_Sepolia = new ApolloClient({ - cache: new InMemoryCache(), + cache: cacheL1, link: concat(authMiddleware(SupportedChainId.SEPOLIA), httpLink), }); diff --git a/src/recoil/history/transaction.ts b/src/recoil/history/transaction.ts index ba122fa62..392abae19 100644 --- a/src/recoil/history/transaction.ts +++ b/src/recoil/history/transaction.ts @@ -1,4 +1,4 @@ -import { Action, CT_ACTION, HISTORY_SORT } from "@/staging/types/transaction"; +import { Action, CT_ACTION, DepositTransactionHistory, HISTORY_SORT, WithdrawTransactionHistory } from "@/staging/types/transaction"; import { atom } from "recoil"; export const userTransactions = atom({ @@ -15,3 +15,14 @@ export const selectedTransactionCategory = atom({ key: "selectedTransactionCategory", default: Action.Deposit, }); + + +export const depositTxHistory = atom({ + key: "depositTransactionHistory", + default: null, +}); + +export const withdrawTxHistory = atom({ + key: "withdrawTransactionHistory", + default: null, +}); \ No newline at end of file diff --git a/src/recoil/modal/atom.ts b/src/recoil/modal/atom.ts index 5958912fd..c42429aee 100644 --- a/src/recoil/modal/atom.ts +++ b/src/recoil/modal/atom.ts @@ -103,9 +103,13 @@ export const ctRefreshModalStatus = atom<{ }, }); -export const ctOptionModalStatus = atom({ +export const ctOptionModalStatus = atom<{ + isOpen: boolean; +}>({ key: "ctOptionModalStatus", - default: false, + default: { + isOpen: false, + }, }); export const ctUpdateFeeModalStatus = atom<{ diff --git a/src/staging/components/cross-trade/components/core/comfirm/CTConfirmCrossTradeFooter.tsx b/src/staging/components/cross-trade/components/core/comfirm/CTConfirmCrossTradeFooter.tsx index c780c6b85..fc781b41d 100644 --- a/src/staging/components/cross-trade/components/core/comfirm/CTConfirmCrossTradeFooter.tsx +++ b/src/staging/components/cross-trade/components/core/comfirm/CTConfirmCrossTradeFooter.tsx @@ -40,6 +40,8 @@ import useMediaView from "@/hooks/mediaView/useMediaView"; import { useCrossTradeContract } from "@/staging/hooks/useCrossTradeContracts"; import { ATOM_CT_GAS_provideCT } from "@/recoil/crosstrade/networkFee"; import { calculateGasMarginBigInt } from "@/utils/txn/calculateGasMargin"; +import { recommendFeeConfig } from "@/staging/constants/fee"; +import { CTTransactionType } from "@/types/crossTrade/contracts"; export type ContractWrite = (args: { args: any[]; @@ -193,22 +195,22 @@ export default function CTConfirmCrossTradeFooter( const msgValue = isUpdateFee ? editedCTAmount : BigInt(subgraphData._ctAmount); - console.log( - "--provideCT params--", - ZERO_ADDRESS, - ZERO_ADDRESS, - subgraphData._requester, - subgraphData._totalAmount, - subgraphData._ctAmount, - _editedAmount, - subgraphData._saleCount, - subgraphData._l2chainId, - 500000, - subgraphData._hashValue, - { - value: msgValue, - } - ); + // console.log( + // "--provideCT params--", + // ZERO_ADDRESS, + // ZERO_ADDRESS, + // subgraphData._requester, + // subgraphData._totalAmount, + // subgraphData._ctAmount, + // _editedAmount, + // subgraphData._saleCount, + // subgraphData._l2chainId, + // 500000, + // subgraphData._hashValue, + // { + // value: msgValue, + // } + // ); const args = [ ZERO_ADDRESS, @@ -242,18 +244,18 @@ export default function CTConfirmCrossTradeFooter( gas: estimatedGasUsageWithBuffer, }); } - console.log("--provideCT params--", { - _l1token: subgraphData._l1token, - _l2token: subgraphData._l2token, - _requester: subgraphData._requester, - _totalAmount: subgraphData._totalAmount, - _ctAmount: subgraphData._ctAmount, - _editedAmount: _editedAmount, - _saleCount: subgraphData._saleCount, - _l2chainId: subgraphData._l2chainId, - _minGasLimit: 500000, - _hashValue: subgraphData._hashValue, - }); + // console.log("--provideCT params--", { + // _l1token: subgraphData._l1token, + // _l2token: subgraphData._l2token, + // _requester: subgraphData._requester, + // _totalAmount: subgraphData._totalAmount, + // _ctAmount: subgraphData._ctAmount, + // _editedAmount: _editedAmount, + // _saleCount: subgraphData._saleCount, + // _l2chainId: subgraphData._l2chainId, + // _minGasLimit: 500000, + // _hashValue: subgraphData._hashValue, + // }); const args = [ subgraphData._l1token, @@ -293,14 +295,14 @@ export default function CTConfirmCrossTradeFooter( const ctAmount = BigInt(txData.inToken.amount) - BigInt(txData.serviceFee.toString()); - console.log( - "--requestRegisteredToken params--", - txData.outToken.address, - txData.inToken.address, - txData.inToken.amount, - ctAmount, - txData.outNetwork - ); + // console.log( + // "--requestRegisteredToken params--", + // txData.outToken.address, + // txData.inToken.address, + // txData.inToken.amount, + // ctAmount, + // txData.outNetwork + // ); if (inTokenIsETH) { return requestRegisteredToken({ @@ -324,6 +326,17 @@ export default function CTConfirmCrossTradeFooter( ], }); } catch (e) { + const isNetworkFeeError = + String(e).includes("Could not find an Account") || + String(e).includes("ContractFunctionExecutionError"); + + if (isNetworkFeeError) { + const estimatedGasUsageWithBuffer = calculateGasMarginBigInt( + BigInt(recommendFeeConfig.gas[CTTransactionType.provideCT] * 1.25) + ); + return setProvideCTGas(estimatedGasUsageWithBuffer); + } + console.log("**error**"); console.log(e); setModalOpen("error"); diff --git a/src/staging/components/cross-trade/components/core/comfirm/CTConfirmDetail.tsx b/src/staging/components/cross-trade/components/core/comfirm/CTConfirmDetail.tsx index fd004a0e8..de6c0f81f 100644 --- a/src/staging/components/cross-trade/components/core/comfirm/CTConfirmDetail.tsx +++ b/src/staging/components/cross-trade/components/core/comfirm/CTConfirmDetail.tsx @@ -342,6 +342,15 @@ export default function CTConfirmDetail({ : CTTransactionType.requestRegisteredToken ); + const showServiceFee = useMemo(() => { + return ( + !isCanceled && + (updateFee || + (modalType === ModalType.History && !isInCT_Provide(status))) && + !isProvide + ); + }, [isCanceled, updateFee, modalType, status, isProvide]); + return ( )} - {!isCanceled && updateFee && !isProvide && ( + {showServiceFee && ( { const needToShowTimeDisplay = (title === "refund" || title === "return") && isActive; - const initialTimeDisplay = formatTimeDisplay(getRemainTime(txData)); + const initialTimeDisplay = getRemainTime(txData); const { isTimeOver } = useTimeOver({ timeStamp: Number(blockTimestamp), //refund and return have same timeBuffer as 300s @@ -101,7 +101,7 @@ const TransactionItem = (props: TransactionItemProps) => { lineHeight={"20px"} color={isOnError || isTimeOver ? "#DD3A44" : "#fff"} > - {timeDisplay} + {timeDisplay.time} ); }, [timeDisplay, needToShowTimeDisplay, isTimeOver]); @@ -238,9 +238,9 @@ export default function CTConfirmHistoryFooter(props: { const blockTimestamp = key === "refund" ? //@ts-ignore - txData.blockTimestamps["cancelRequest"] + txData.blockTimestamps["cancelRequest"] : //@ts-ignore - txData.blockTimestamps[key]; + txData.blockTimestamps[key]; const isCancelCompleted = isInCT_REQUEST_CANCEL(txData.status) && key === "completed"; if (typeof hash === "string") { diff --git a/src/staging/components/cross-trade/components/core/main/CTMain.tsx b/src/staging/components/cross-trade/components/core/main/CTMain.tsx index 6504d6ca7..153ccb52d 100644 --- a/src/staging/components/cross-trade/components/core/main/CTMain.tsx +++ b/src/staging/components/cross-trade/components/core/main/CTMain.tsx @@ -61,6 +61,10 @@ export default function CTMain() { } }, [requestList]); + /** + * TO DO: refactor sort process to be moved into a custom hook + * + */ useEffect(() => { if (isDescSortedProvide === null) return; if (isDescSortedProvide) { @@ -211,6 +215,9 @@ export default function CTMain() { : chainNameOut === "TITAN_SEPOLIA" ? "Titan Sepolia" : capitalizeFirstLetter(chainNameOut); + const isNegativeProfit = item.profit?.percent + ? Number(item.profit?.percent) < 0 + : false; return ( Receive on {displayNetworkNameIn} - + {formatNumber(formattedAmount)} {item.outToken.symbol} - - ( - - - +{formatProfit(item.profit?.percent)}% - - - ) + + {!isNegativeProfit && "+"} + {formatProfit(item.profit?.percent)}% @@ -387,15 +394,15 @@ export default function CTMain() { + Total amount to receive, including the service
fee. It takes at least 15 minutes to receive
{" "} (depending on the L2 sequencer).
} style={{ - width: "268px", - height: "70px", + width: "289px", + height: "74px", tooltipLineHeight: "normal", py: "10px", px: "8px", @@ -436,8 +443,32 @@ export default function CTMain() { color={"#A0A3AD"} letterSpacing={0} > - Net Profit (%) + Net Profit + + + Net Profit = Receive - Provide - txn fee + +
+ Net profit for provider is heavily depends on the +
+ transaction fee, which is a highly volatile value. +
+ Please double-check it before providing. +
+ } + style={{ + width: "300px", + height: "92px", + tooltipLineHeight: "normal", + py: "10px", + px: "8px", + }} + containerSyle={{ marginLeft: "2px" }} + /> @@ -535,6 +566,7 @@ export default function CTMain() { diff --git a/src/staging/components/cross-trade/components/core/main/CTMainProvider.tsx b/src/staging/components/cross-trade/components/core/main/CTMainProvider.tsx index ecdac9ee4..bd05e6741 100644 --- a/src/staging/components/cross-trade/components/core/main/CTMainProvider.tsx +++ b/src/staging/components/cross-trade/components/core/main/CTMainProvider.tsx @@ -167,7 +167,7 @@ export default function CTProvider({ gap={"8px"} flexShrink={0} borderRadius={"6px"} - bg={bgColor} + bg={isInRelay ? "none" : bgColor} border={ isInRelay ? "none" diff --git a/src/staging/components/cross-trade/components/core/main/TokenDetail.tsx b/src/staging/components/cross-trade/components/core/main/TokenDetail.tsx index 5619e23d1..6a80b2369 100644 --- a/src/staging/components/cross-trade/components/core/main/TokenDetail.tsx +++ b/src/staging/components/cross-trade/components/core/main/TokenDetail.tsx @@ -20,6 +20,7 @@ interface TokenDetailProps { providingUSD?: number; recevingUSD?: number; profit?: Profit; + provideCTTxnCost?: number; } const Percentage = (props: { percent: string }) => { @@ -55,18 +56,54 @@ const USDValue = (props: { usdAmount: string }) => { ); }; -const NetProfit = (props: { percent: string; usdAmount: string }) => { +const NetProfit = (props: { + percent: string; + usdAmount: string; + provideCTTxnCost?: number; +}) => { return ( - - - - + + + + + } + tooltipLabel={ + + + Estimated net profit based on transaction +
+ {`fee of $ + ${commafy(props.provideCTTxnCost)} + Actual value may differ based on + other factors (priority fee, storage refund, etc)`} +
+
+ } + needArrow={true} + labelStyle={{ + left: "-17px", + width: "291px", + height: "74px", + }} + tooltipArrowStyle={{ + left: "25px", + }} + >
); }; export default function TokenDetail(props: TokenDetailProps) { - const { token, network, profit, isProvide, providingUSD, recevingUSD } = - props; + const { + token, + network, + profit, + isProvide, + providingUSD, + recevingUSD, + provideCTTxnCost, + } = props; const isProfit = profit !== undefined; const formattedAmount = token @@ -88,7 +125,11 @@ export default function TokenDetail(props: TokenDetailProps) { if (isProfit) { return ( - + ); } diff --git a/src/staging/components/cross-trade/components/core/option/CTOptionCrossDetail.tsx b/src/staging/components/cross-trade/components/core/option/CTOptionCrossDetail.tsx index dc4f8a173..78b35d221 100644 --- a/src/staging/components/cross-trade/components/core/option/CTOptionCrossDetail.tsx +++ b/src/staging/components/cross-trade/components/core/option/CTOptionCrossDetail.tsx @@ -34,6 +34,7 @@ interface AdditionalCrossProps { handleButtonMainClick: (value: ButtonTypeMain) => void; activeSubButtonValue: ButtonTypeSub; handleButtonSubClick: (value: ButtonTypeSub) => void; + recommendedCTAmount: string | undefined; } export default function CTOptionCrossDetail( @@ -48,11 +49,13 @@ export default function CTOptionCrossDetail( // 현재 props.inputValue가 1일때만 WarningType이 critical일때만, recommend 변경 타입 보여주는걸로 디자인 시연. // 추후 price api가 먹통 됬을때 해당 조건 주면 됨 // const isDisabledRecommend = props.inputValue === "1"; - const isDisabledRecommend = props.recommnededFee === undefined ? true : false; + const isDisabledRecommend = + props.recommendedCTAmount === undefined ? true : false; const { inToken } = useInOutTokens(); const receiveTokenValue = useMemo(() => { - if (isRecommendActive && !isDisabledRecommend) return props.recommnededFee; + if (isRecommendActive && !isDisabledRecommend) + return props.recommendedCTAmount; const inputValue = props.inputValue; if ( @@ -258,45 +261,14 @@ export default function CTOptionCrossDetail( - {isAdvancedActive && ( - - - - Service fee - - - - The service fee incentivizes the liquidity provider - - to accept the request. The amount received - on L1 is calculated after deducting this fee. - - } - style={{ - width: "304px", - height: "74px", - tooltipLineHeight: "18px", - px: "8px", - py: "10px", - }} - /> - - - - )} + {!withdrawalIsTooSmall && ( { + const isBiggerThan100 = value > 100; + if (isBiggerThan100) return `> 100`; + const isMinus = value < 0; + if (isMinus) return "> 100"; + return commafy(value); +}; export default function CTOptionInput(props: CTInputProps) { const { @@ -16,19 +28,76 @@ export default function CTOptionInput(props: CTInputProps) { inputWarningCheck, onInputChange, inTokenSymbol, + isAdvancedActive, + recommnededFee, } = props; + const { inToken } = useInOutTokens(); const [isFocused, setIsFocused] = useState(false); const inputValue = useMemo(() => { + if (!isAdvancedActive && recommnededFee) + return `${recommnededFee.slice(0, 12)}...`; if (isFocused) return _inputValue; if (_inputValue.length > 12) { return `${_inputValue.slice(0, 12)}...`; } return _inputValue; - }, [_inputValue, isFocused]); + }, [_inputValue, isFocused, isAdvancedActive, recommnededFee]); + + const totalAmount = useMemo(() => { + if (inToken) { + return inToken.parsedAmount; + } + }, [inToken]); + + const receviedRatio = useMemo(() => { + if (totalAmount && inputValue) { + const ratio = + (Number(inputValue.replaceAll("...", "")) / Number(totalAmount)) * 100; + return formatPercentage(ratio); + } + return "-"; + }, [totalAmount, inputValue]); return ( - <> + + + + Service fee + + + The service fee incentivizes the liquidity provider + to accept the request. The amount received + on L1 is calculated after deducting this fee. + + } + style={{ + width: "304px", + height: "74px", + tooltipLineHeight: "18px", + px: "8px", + py: "10px", + }} + /> + + {receviedRatio}% + + { setIsFocused(true); }} + disabled={!isAdvancedActive} onBlur={() => setIsFocused(false)} onChange={onInputChange} value={inputValue} color={ - inputWarningCheck == WarningType.Critical ? "#DD3A44" : "#FFFFFF" + inputWarningCheck == WarningType.Critical && isAdvancedActive + ? "#DD3A44" + : "#FFFFFF" } _hover={{}} _placeholder={{ @@ -83,7 +155,7 @@ export default function CTOptionInput(props: CTInputProps) { )} - {inputWarningCheck == WarningType.Critical ? ( + {isAdvancedActive && inputWarningCheck == WarningType.Critical ? ( - ) : inputWarningCheck == WarningType.Normal ? ( + ) : isAdvancedActive && inputWarningCheck == WarningType.Normal ? ( ) : null} - + ); } diff --git a/src/staging/components/cross-trade/components/core/option/index.tsx b/src/staging/components/cross-trade/components/core/option/index.tsx index 1ed0deb5e..9ce1f97c6 100644 --- a/src/staging/components/cross-trade/components/core/option/index.tsx +++ b/src/staging/components/cross-trade/components/core/option/index.tsx @@ -36,6 +36,7 @@ import useConnectedNetwork, { useInOutNetwork } from "@/hooks/network"; import { ethers } from "ethers"; import { useRecommendFee } from "../../../hooks/useRecommendFee"; import { useWhiteListToken } from "@/staging/hooks/useWhiteListToken"; +import useInputBalanceCheck from "@/hooks/token/useInputCheck"; export default function CTOptionModal() { const { mobileView } = useMediaView(); @@ -152,12 +153,11 @@ export default function CTOptionModal() { } }; - const { connectedChainId } = useConnectedNetwork(); const [inputWarningCheck, setInputWarningCheck] = useState( "" ); - const serviceFeeIsNotOver = useMemo(() => { + const serviceFeeIsOver = useMemo(() => { if (inToken?.parsedAmount) { return Number(inToken.parsedAmount) - Number(serviceFee) <= 0; } @@ -169,16 +169,26 @@ export default function CTOptionModal() { } }, [serviceFee, recommendedFee]); + const isRecommendedFeeOver = useMemo(() => { + if (recommendedFee && inToken?.parsedAmount) { + return Number(recommendedFee) > Number(inToken.parsedAmount); + } + }, [recommendedFee, inToken?.parsedAmount]); + useEffect(() => { { - if (serviceFeeIsNotOver) - return setInputWarningCheck(WarningType.Critical); + if (serviceFeeIsOver) return setInputWarningCheck(WarningType.Critical); if (isLessThanRecommendedFee) return setInputWarningCheck(WarningType.Normal); return setInputWarningCheck(""); } // Reset inputWarningCheck when the modal is reopened - }, [serviceFeeIsNotOver, isLessThanRecommendedFee, ctOptionModal]); + }, [ + serviceFeeIsOver, + isLessThanRecommendedFee, + ctOptionModal, + activeSubButtonValue, + ]); useEffect(() => { if (ctOptionModal) { @@ -186,16 +196,16 @@ export default function CTOptionModal() { } }, [ctOptionModal]); + const { isBalanceOver } = useInputBalanceCheck(); + const { isWhiteListToken } = useWhiteListToken(); + const btnDisabled = useMemo(() => { + if (isBalanceOver) return true; if (activeMainButtonValue === ButtonTypeMain.Standard) { return false; } if (activeSubButtonValue === ButtonTypeSub.Recommend) { - return ( - !recommendedCtAmount || - !recommendedFee || - inputWarningCheck === WarningType.Critical - ); + return !recommendedCtAmount || !recommendedFee || isRecommendedFeeOver; } if (activeSubButtonValue === ButtonTypeSub.Advanced) { return ( @@ -211,21 +221,13 @@ export default function CTOptionModal() { inputWarningCheck, recommendedCtAmount, recommendedFee, + isRecommendedFeeOver, + isBalanceOver, ]); - const { isWhiteListToken } = useWhiteListToken(); - // const isSupportedNetworkForCT = useMemo( - // () => - // (connectedChainId && - // connectedChainId === SupportedChainId.TITAN_SEPOLIA) || - // connectedChainId === SupportedChainId.TITAN, - // [connectedChainId] - // ); - const isSupportedNetworkForCT = true; - return ( - {!isSupportedNetworkForCT || !isWhiteListToken ? ( + {!isWhiteListToken ? ( - ) : activeMainButtonValue === ButtonTypeMain.Standard ? ( - ) : ( )} - {"Next"} + {isBalanceOver ? "Insufficient Balance" : "Next"} diff --git a/src/staging/components/cross-trade/hooks/useCTOptionModal.ts b/src/staging/components/cross-trade/hooks/useCTOptionModal.ts index c49dfec32..8218fd6d9 100644 --- a/src/staging/components/cross-trade/hooks/useCTOptionModal.ts +++ b/src/staging/components/cross-trade/hooks/useCTOptionModal.ts @@ -6,11 +6,11 @@ export default function useCTOption() { const [ctOptionModal, setCTOptionModal] = useRecoilState(ctOptionModalStatus); const onOpenCTOptionModal = () => { - setCTOptionModal(true); + setCTOptionModal({ isOpen: true }); }; const onCloseCTOptionModal = useCallback(() => { - setCTOptionModal(false); + setCTOptionModal({ isOpen: false }); }, []); return { diff --git a/src/staging/components/cross-trade/hooks/useRecommendFee.ts b/src/staging/components/cross-trade/hooks/useRecommendFee.ts index 9de5e8290..4ba27fc01 100644 --- a/src/staging/components/cross-trade/hooks/useRecommendFee.ts +++ b/src/staging/components/cross-trade/hooks/useRecommendFee.ts @@ -1,4 +1,3 @@ -import { BigNumber } from "@ethersproject/bignumber"; import { useGetMarketPrice } from "@/hooks/price/useGetMarketPrice"; import { recommendFeeConfig } from "@/staging/constants/fee"; import { CTTransactionType } from "@/types/crossTrade/contracts"; @@ -12,10 +11,19 @@ import { SupportedChainId } from "@/types/network/supportedNetwork"; import { useRelayGasCost } from "../../new-confirm/hooks/useGetGas"; export const useRecommendFee = (params: { - totalAmount: number; - tokenAddress: string; + totalAmount?: number; + tokenAddress?: string; }) => { const { totalAmount, tokenAddress } = params; + + if (totalAmount === undefined || tokenAddress === undefined) + return { + recommendedCtAmount: undefined, + recommendedFee: undefined, + estimatedGasFeeETH: undefined, + provideCTTxnUSDCost: undefined, + }; + const { isConnectedToMainNetwork } = useConnectedNetwork(); const { data: feeData } = useFeeData({ chainId: isConnectedToMainNetwork @@ -43,30 +51,32 @@ export const useRecommendFee = (params: { withdrawCost: { totalGasCost: standardWithdrawGasCost }, } = useRelayGasCost(); - const isGasFeeLessThanOrEqual = useMemo(() => { - if (feeData?.gasPrice && standardWithdrawGasCost) { - const gasUsage = recommendFeeConfig.gas[CTTransactionType.provideCT]; - const gasPrice = formatUnits(feeData.gasPrice.toString(), 9); - const gasFee = gasUsage * Number(gasPrice) * Math.pow(10, -9) * 1.25; - const gasFeeDecimal = new Decimal(gasFee); - const standardWithdrawGasCostDecimal = new Decimal( - standardWithdrawGasCost - ); - const isGasFeeLessThanOrEqual = gasFeeDecimal.lessThanOrEqualTo( - standardWithdrawGasCostDecimal - ); - return isGasFeeLessThanOrEqual; - } - }, [feeData?.gasPrice, standardWithdrawGasCost]); + // const isGasFeeLessThanOrEqual = useMemo(() => { + // if (feeData?.gasPrice && standardWithdrawGasCost) { + // const gasUsage = recommendFeeConfig.gas[CTTransactionType.provideCT]; + // const gasPrice = formatUnits(feeData.gasPrice.toString(), 9); + // const gasFee = gasUsage * Number(gasPrice) * Math.pow(10, -9) * 1.25; + // const gasFeeDecimal = new Decimal(gasFee); + // const standardWithdrawGasCostDecimal = new Decimal( + // standardWithdrawGasCost + // ); + // const isGasFeeLessThanOrEqual = gasFeeDecimal.lessThanOrEqualTo( + // standardWithdrawGasCostDecimal + // ); + // return isGasFeeLessThanOrEqual; + // } + // }, [feeData?.gasPrice, standardWithdrawGasCost]); const additionalFeeRatio = useMemo(() => { if (hasRecomendFee) { - const additionalFee = isGasFeeLessThanOrEqual ? 0 : -0.4; + // const additionalFee = isGasFeeLessThanOrEqual ? 0 : -0.4; + const additionalFee = 0; + return ( recommendFeeConfig.fee[tokenInfo?.tokenSymbol as string] - additionalFee ); } - }, [hasRecomendFee, isGasFeeLessThanOrEqual]); + }, [hasRecomendFee]); const additionalFee = useMemo(() => { if (totalTokenAmountInUSD && additionalFeeRatio && tokenInfo?.decimals) { @@ -87,12 +97,7 @@ export const useRecommendFee = (params: { return gasFee; } - }, [ - feeData, - recommendFeeConfig.gas, - standardWithdrawGasCost, - isGasFeeLessThanOrEqual, - ]); + }, [feeData, recommendFeeConfig.gas, standardWithdrawGasCost]); const { tokenPriceWithAmount: provideCTTxnCost } = useGetMarketPrice({ tokenName: "ethereum", diff --git a/src/staging/components/cross-trade/types/index.ts b/src/staging/components/cross-trade/types/index.ts index 9767ca777..de256d5e7 100644 --- a/src/staging/components/cross-trade/types/index.ts +++ b/src/staging/components/cross-trade/types/index.ts @@ -33,6 +33,7 @@ export interface CTInputProps { onInputChange: (e: ChangeEvent) => void; onInputFocus?: (e: FocusEvent) => void; recommnededFee?: string; + isAdvancedActive?: boolean; } export enum ButtonTypeMain { diff --git a/src/staging/components/new-confirm/components/core/other/ConditionalBox.tsx b/src/staging/components/new-confirm/components/core/other/ConditionalBox.tsx index 9ffa056be..69a8e7293 100644 --- a/src/staging/components/new-confirm/components/core/other/ConditionalBox.tsx +++ b/src/staging/components/new-confirm/components/core/other/ConditionalBox.tsx @@ -16,15 +16,18 @@ import { useCountdown } from "@/staging/hooks/useCountdown"; import Lightbulb from "@/assets/icons/newHistory/lightbulb.svg"; import Refresh from "@/assets/icons/newHistory/refresh.svg"; import { useCalendar } from "@/staging/hooks/useGoogleCalendar"; +import GetHelp from "@/components/ui/GetHelp"; interface ConditionalBoxProps { type: "wait" | "timer" | "box"; transactionData: TransactionHistory; waitMessage?: string | undefined; + timeDisplay: string; + isCountDown: boolean; } export default function ConditionalBox(props: ConditionalBoxProps) { - const { type, transactionData, waitMessage } = props; + const { type, transactionData, waitMessage, timeDisplay, isCountDown } = props; if (type === "wait") { return ( @@ -46,13 +49,6 @@ export default function ConditionalBox(props: ConditionalBoxProps) { const remainTime = getRemainTime(transactionData); const isZeroTime = remainTime <= 0; - const timeDisplay = isZeroTime - ? "00 : 00" - : useCountdown( - formatTimeDisplay(remainTime), - Boolean(transactionData.errorMessage) - ); - const errorRollup = transactionData.errorMessage && transactionData.status === Status.Rollup; @@ -90,8 +86,8 @@ export default function ConditionalBox(props: ConditionalBoxProps) { "days", 0 ) * - 60) * - 1000 + 60) * + 1000 ); } return null; @@ -115,33 +111,14 @@ export default function ConditionalBox(props: ConditionalBoxProps) { fontWeight={600} fontSize='11px' lineHeight='22px' - color={errorRollup ? "#DD3A44" : "#FFFFFF"} + color={errorRollup || !isCountDown ? "#DD3A44" : "#FFFFFF"} whiteSpace='nowrap' overflow='hidden' > {timeDisplay} - {errorRollup && ( - - {"Lightbulb"} - - )} - {refreshRollup && ( - - {"Refresh"} - + {(errorRollup || !isCountDown) && ( + )} {calendarButton && ( { + if (transactionData) { + return getRemainTime(transactionData); + } + return 0; + }, [transactionData?.status, depositWithdrawConfirmModal.isOpen]); + const { time: timeDisplay, isCountDown } = useCountdown( + remainTime, + false, + transactionData + ); + const gasCostData: GasCostData = useMemo(() => { const formatValue = (value: string | undefined | null) => value == null || value === "0" || value === "-" ? "NA" : value; @@ -132,14 +146,16 @@ export default function DepositWithdrawConfirmModal() { gasCostData={gasCostData} /> {(statuses.length === 2 && index === 0) || - (statuses.length === 3 && index < 2) + (statuses.length === 3 && index < 2) ? typeValue !== undefined && ( - - ) + + ) : null} ); diff --git a/src/staging/components/new-confirm/utils/index.ts b/src/staging/components/new-confirm/utils/index.ts new file mode 100644 index 000000000..4321f5b08 --- /dev/null +++ b/src/staging/components/new-confirm/utils/index.ts @@ -0,0 +1,13 @@ +import { + Action, + TransactionHistory, +} from "@/staging/types/transaction"; +export const getBridgeL2ChainId = (tx?: TransactionHistory | null) => { + if (!tx) return null; + return tx.action === Action.Deposit ? tx.outNetwork : tx.inNetwork; +}; + +export const getBridgeL1ChainId = (tx?: TransactionHistory) => { + if (!tx) return null; + return tx.action === Action.Deposit ? tx.inNetwork : tx.outNetwork; +}; \ No newline at end of file diff --git a/src/staging/components/new-history/components/core/index.tsx b/src/staging/components/new-history/components/core/index.tsx index e6cdd549b..8664b8430 100644 --- a/src/staging/components/new-history/components/core/index.tsx +++ b/src/staging/components/new-history/components/core/index.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from "react"; +import React, { useEffect, useMemo } from "react"; import { Flex, Box, Text } from "@chakra-ui/react"; import { Action, @@ -6,6 +6,8 @@ import { CT_PROVIDE, CT_REQUEST, CT_REQUEST_CANCEL, + isDepositTransactionHistory, + isWithdrawTransactionHistory, Status, } from "@/staging/types/transaction"; import Pending from "@/staging/components/new-history/components/core/pending"; @@ -88,10 +90,14 @@ export default function AccountHistoryNew() { transaction.status === CT_REQUEST.Completed || transaction.status === CT_REQUEST_CANCEL.Completed || transaction.status === CT_PROVIDE.Completed; - + const key = + isDepositTransactionHistory(transaction) || + isWithdrawTransactionHistory(transaction) + ? transaction.transactionHashes.initialTransactionHash + : index; return ( { + return getRemainTime(transactionData); + }, [transactionData.status]); + const initialTimeDisplay = shouldCountdown ? // Value needed for countdown - formatTimeDisplay(getRemainTime(transactionData)) + formatTimeDisplay(getRemainTime(transactionData)) : // If not active and status is Finalized, display empty value (!isActive && label.toString() === "Finalize") || (isActive && label === CT_REQUEST.WaitForReceive) - ? "" - : // Otherwise, display formatted date as all are completed + ? "" + : // Otherwise, display formatted date as all are completed formatDateToYMD( Number( isWithdrawTransactionHistory(transactionData) || @@ -156,14 +160,15 @@ export default function StatusComponent( needToCheck: shouldCountdown, }); - const countdownTimeDisplay = useCountdown( - initialTimeDisplay, - Boolean(transactionData.errorMessage) || isTimeOver + const { time, isCountDown } = useCountdown( + remainTime, + Boolean(transactionData.errorMessage), + transactionData ); // Output variable const timeDisplay = shouldCountdown - ? countdownTimeDisplay + ? time : initialTimeDisplay; // Calendar start time @@ -181,8 +186,8 @@ export default function StatusComponent( "days", 0 ) * - 60) * - 1000 + 60) * + 1000 ); } return null; @@ -220,7 +225,7 @@ export default function StatusComponent( // Show claim button when Finalized status is complete const claimReadyButton = label === Status.Finalize && - timeDisplay === "00 : 00" && + remainTime < 0 && transactionData.action === Action.Withdraw; const { isOnOfficialStandard } = useHistoryTab(); @@ -230,9 +235,8 @@ export default function StatusComponent( case CT_REQUEST.Request: return "Request"; case CT_REQUEST.UpdateFee: - return `Update ${ - updateFeeCount && updateFeeCount > 1 ? ` x ${updateFeeCount}` : "" - }`; + return `Update ${updateFeeCount && updateFeeCount > 1 ? ` x ${updateFeeCount}` : "" + }`; case CT_REQUEST.WaitForReceive: return "Waiting"; default: @@ -273,8 +277,8 @@ export default function StatusComponent( !isActive && label === Status.Finalize ? "#A0A3AD" : isOnOfficialStandard - ? "#007AFF" - : "#DB00FF" + ? "#007AFF" + : "#DB00FF" } /> @@ -303,8 +307,8 @@ export default function StatusComponent( )} {isError && } - {refreshRollup && } - {calendarButton && ( + {!claimReadyButton && shouldCountdown && !isCountDown && } + {!claimReadyButton && calendarButton && ( )} diff --git a/src/staging/components/new-history/utils/getTimeDisplay.ts b/src/staging/components/new-history/utils/getTimeDisplay.ts index ed49e31d4..f5487ba70 100644 --- a/src/staging/components/new-history/utils/getTimeDisplay.ts +++ b/src/staging/components/new-history/utils/getTimeDisplay.ts @@ -7,13 +7,15 @@ import { isInCT_Provide, CT_Provide_History, } from "@/staging/types/transaction"; -import { TRANSACTION_CONSTANTS } from "@/staging/constants/transactionTime"; +import { TESTNET_TRANSACTION_CONSTANTS, TRANSACTION_CONSTANTS } from "@/staging/constants/transactionTime"; import { convertTimeToMinutes } from "@/staging/components/new-history/utils/timeUtils"; import { TransactionStatus, getStatusValue, } from "@/staging/components/new-history/utils/historyStatus"; import { utcToZonedTime } from "date-fns-tz"; +import { getBridgeL1ChainId } from "../../new-confirm/utils"; +import { SupportedChainId } from "@/types/network/supportedNetwork"; // status 별로 변수 넣는 함수 export function getRemainTime(transactionData: TransactionHistory): number { @@ -22,6 +24,8 @@ export function getRemainTime(transactionData: TransactionHistory): number { transactionData.action, transactionData.status ); + const l1ChainId = getBridgeL1ChainId(transactionData); + const txConst = l1ChainId === SupportedChainId.MAINNET ? TRANSACTION_CONSTANTS : TESTNET_TRANSACTION_CONSTANTS; // 상수를 통해 정해진 시간을 추가해준다. switch (statusValue) { @@ -33,7 +37,7 @@ export function getRemainTime(transactionData: TransactionHistory): number { const timeValue = calculateInitialTime( statusValue, transactionData.blockTimestamps.initialCompletedTimestamp, - TRANSACTION_CONSTANTS.WITHDRAW.ROLLUP_SECS, + txConst.WITHDRAW.ROLLUP_SECS, Boolean(transactionData.errorMessage) ); return timeValue; @@ -47,7 +51,7 @@ export function getRemainTime(transactionData: TransactionHistory): number { const timeValue = calculateInitialTime( statusValue, transactionData.blockTimestamps.rollupCompletedTimestamp, - TRANSACTION_CONSTANTS.WITHDRAW.CHALLENGE_SECS + txConst.WITHDRAW.CHALLENGE_SECS ); return timeValue; } @@ -60,7 +64,7 @@ export function getRemainTime(transactionData: TransactionHistory): number { const timeValue = calculateInitialTime( statusValue, transactionData.blockTimestamps.initialCompletedTimestamp, - TRANSACTION_CONSTANTS.DEPOSIT.INITIAL_SECS + txConst.DEPOSIT.INITIAL_SECS ); return timeValue; @@ -75,7 +79,7 @@ export function getRemainTime(transactionData: TransactionHistory): number { const timeValue = calculateInitialTime( statusValue, CT_Request_TransactionData.blockTimestamps.cancelRequest, - TRANSACTION_CONSTANTS.CROSS_TRADE.CANCEL_REQUEST, + txConst.CROSS_TRADE.CANCEL_REQUEST, Boolean(transactionData.errorMessage) ); return timeValue; @@ -90,7 +94,7 @@ export function getRemainTime(transactionData: TransactionHistory): number { const timeValue = calculateInitialTime( statusValue, CT_Request_TransactionData.blockTimestamps.provide, - TRANSACTION_CONSTANTS.CROSS_TRADE.RETURN_LIQUIDITY, + txConst.CROSS_TRADE.RETURN_LIQUIDITY, Boolean(transactionData.errorMessage) ); return timeValue; diff --git a/src/staging/constants/fee.ts b/src/staging/constants/fee.ts index 97b4c01d3..9d83629a6 100644 --- a/src/staging/constants/fee.ts +++ b/src/staging/constants/fee.ts @@ -14,10 +14,10 @@ export const recommendFeeConfig = { gas: crossTradeGasFee, fee: { //percentage of the total amount - TON: 0.97534, - USDC: 0.53425, - USDT: 0.53425, - ETH: 0.47671, + TON: 6.476712, + USDC: 1.385342, + USDT: 1.385342, + ETH: 1.578767, }, } as { gas: CrossTradeGasFee; diff --git a/src/staging/constants/transactionTime.ts b/src/staging/constants/transactionTime.ts index 5912c327d..144ddc1af 100644 --- a/src/staging/constants/transactionTime.ts +++ b/src/staging/constants/transactionTime.ts @@ -14,7 +14,7 @@ export const TRANSACTION_CONSTANTS = { // Set to 15sec time-buffer for UI purposes, although the spec is 900 seconds CROSS_TRADE: { PROVIDE: 915, //15 minutes in seconds - REQUEST: 915, + REQUEST: 915, CANCEL_REQUEST: 915, RETURN_LIQUIDITY: 915, }, @@ -24,7 +24,7 @@ export const TESTNET_TRANSACTION_CONSTANTS = { ...TRANSACTION_CONSTANTS, DEPOSIT: { INITIAL_MINUTES: 5, - INITIAL_SECS: 300, + INITIAL_SECS: 315, }, WITHDRAW: { ...TRANSACTION_CONSTANTS.WITHDRAW, diff --git a/src/staging/hooks/useBridgeHistory.ts b/src/staging/hooks/useBridgeHistory.ts index c6fd4e10c..0084546a9 100644 --- a/src/staging/hooks/useBridgeHistory.ts +++ b/src/staging/hooks/useBridgeHistory.ts @@ -12,7 +12,7 @@ import { WithdrawTransactionHistory, } from "../types/transaction"; import { ApolloError, useQuery } from "@apollo/client"; -import { useAccount } from "wagmi"; +import { useAccount, useNetwork } from "wagmi"; import { subgraphApolloClientsForHistory } from "@/graphql/thegraph/apolloForHistory"; import useConnectedNetwork from "@/hooks/network"; import { SupportedChainId } from "@/types/network/supportedNetwork"; @@ -59,6 +59,9 @@ import { getDecodedDepositLog } from "@/utils/history/getDecodedDepositLog"; import { mock_depositRequest } from "@/test/deposit/_mock/mockdata"; import { mock_withdrawData } from "@/test/withdraw/_mock/mockdata"; import { getDecodedStandardBridgeLog } from "@/utils/history/getDecodBridgeHistoryLog"; +import { getSortedTxHistory, getSortedTxListByDate } from "../utils/history"; +import { useRecoilState } from "recoil"; +import { depositTxHistory, withdrawTxHistory } from "@/recoil/history/transaction"; const getApolloClient = (chainId: number) => { return subgraphApolloClientsForHistory[chainId]; @@ -108,37 +111,54 @@ const errorHandler = (error: ApolloError) => { export const useSubgraph = () => { const { address } = useAccount(); + const [pollCount, setPollCount] = useState(0); const { L1_CLIENT, L2_CLIENT } = useGetApolloClient(); const { isConnectedToMainNetwork } = useConnectedNetwork(); const L1Bridge = isConnectedToMainNetwork ? MAINNET_CONTRACTS.L1Bridge : SEPOLIA_CONTRACTS.L1Bridge_TITAN_SEPOLIA; - + useEffect(() => { + setPollCount(0); + const refetchDepositHistory = async () => { + refetchL1TitanData(); + } + const refetchWithdrawHistory = async () => { + refetchL2TitanData(); + } + const interval = setInterval(() => { + setPollCount(prev => prev + 1); + refetchDepositHistory(); + refetchWithdrawHistory(); + }, 12000); + return () => clearInterval(interval); + }, []) const { data: _l1Data, loading: _l1Loading, error: _l1Error, + refetch: refetchL1TitanData } = useQuery(FETCH_USER_TRANSACTIONS_L1, { variables: { formattedAddress: formatAddress(address), L1Bridge, account: address, }, - pollInterval: 13000, + // pollInterval: 13000, client: L1_CLIENT, }); const { data: _l2Data, loading: _l2Loading, error: _l2Error, + refetch: refetchL2TitanData } = useQuery(FETCH_USER_TRANSACTIONS_L2, { variables: { formattedAddress: formatAddress(address), L1Bridge, account: address, }, - pollInterval: 13000, + // pollInterval: 13000, client: L2_CLIENT, }); @@ -158,15 +178,14 @@ export const useSubgraph = () => { l2Data: _l2Data, l2Loading: _l2Loading, l2_error: _l2Error, + pollCount }; }; export const useWithdrawData = () => { - const [withdrawHistory, setWithdrawHistory] = useState< - WithdrawTransactionHistory[] | [] | null - >(null); + const [withdrawHistory, setWithdrawHistory] = useRecoilState(withdrawTxHistory); - const { l2Data } = useSubgraph(); + const { l2Data, pollCount } = useSubgraph(); const { isConnectedToMainNetwork } = useConnectedNetwork(); const { L2Provider } = useProvier(); @@ -271,11 +290,9 @@ export const useWithdrawData = () => { const filteredResult = result.filter( (tx) => !(tx instanceof Error) && tx !== undefined && tx !== null ); - const sortedResult = filteredResult.sort( - (currentTx, previousTx) => - previousTx.blockTimestamps.initialCompletedTimestamp - - currentTx.blockTimestamps.initialCompletedTimestamp - ); + const sortedResult = getSortedTxListByDate( + filteredResult + ) as WithdrawTransactionHistory[]; if (sortedResult) return setWithdrawHistory(sortedResult); return setWithdrawHistory([]); @@ -286,17 +303,14 @@ export const useWithdrawData = () => { fetchData().catch((error) => { console.error("Error in fetching withdraw data", error); }); - }, [l2Data, isConnectedToMainNetwork, L2Provider]); - + }, [l2Data, isConnectedToMainNetwork, L2Provider, pollCount]); return { withdrawHistory }; }; export const useDepositData = () => { - const [depositHistory, setDepositHistory] = useState< - DepositTransactionHistory[] | [] | null - >(null); + const [depositHistory, setDepositHistory] = useRecoilState(depositTxHistory) const { isConnectedToMainNetwork } = useConnectedNetwork(); - const { l1Data } = useSubgraph(); + const { l1Data, pollCount } = useSubgraph(); const { L1Provider } = useProvier(); const fetchData = useCallback(async () => { @@ -406,7 +420,7 @@ export const useDepositData = () => { fetchData().catch((error) => { console.error("Error in fetching deposit data", error); }); - }, [l1Data, isConnectedToMainNetwork, L1Provider]); + }, [l1Data, isConnectedToMainNetwork, L1Provider, pollCount]); return { depositHistory }; }; diff --git a/src/staging/hooks/useCountdown.ts b/src/staging/hooks/useCountdown.ts index b213c3783..72839cc4e 100644 --- a/src/staging/hooks/useCountdown.ts +++ b/src/staging/hooks/useCountdown.ts @@ -1,92 +1,38 @@ import { useState, useEffect } from "react"; - -export function useCountdown(initialTime: string, errorType?: boolean) { - const [time, setTime] = useState(initialTime); - const [format, setFormat] = useState( - initialTime.split(":").length === 3 ? "HH : MM : SS" : "MM : SS" +import { formatTimeDisplay } from "../utils/formatTimeDisplay"; +import { TransactionHistory } from "../types/transaction"; + +export function useCountdown( + initialTime: number, + errorType?: boolean, + tx?: TransactionHistory +) { + const [isCountDown, setIsCountDown] = useState( + initialTime > 0 ? true : false ); + const [time, setTime] = useState(Math.abs(initialTime)); useEffect(() => { - if (!errorType && (time === "00 : 00" || time === "00 : 00 : 00")) return; + setTime(Math.abs(initialTime)); + setIsCountDown(initialTime > 0); + }, [tx?.status, initialTime]); + useEffect(() => { const countdown = setInterval(() => { - const timeParts = time.split(" : ").map(Number); - - if (timeParts.length === 3) { - // HH : MM : SS 형식일 경우 - const [hours, minutes, seconds] = timeParts; - let totalSeconds = hours * 3600 + minutes * 60 + seconds; - - if (!errorType) { - totalSeconds -= 1; - } else { - totalSeconds += 1; - } - - const newHours = Math.floor(totalSeconds / 3600); - const newMinutes = Math.floor((totalSeconds % 3600) / 60); - const newSeconds = totalSeconds % 60; - - if (newHours === 0) { - setFormat("MM : SS"); - setTime( - `${String(newMinutes).padStart(2, "0")} : ${String( - newSeconds - ).padStart(2, "0")}` - ); - } else { - const formattedHours = String(newHours).padStart(2, "0"); - const formattedMinutes = String(newMinutes).padStart(2, "0"); - const formattedSeconds = String(newSeconds).padStart(2, "0"); - - setTime( - `${formattedHours} : ${formattedMinutes} : ${formattedSeconds}` - ); - } - - if (!errorType && totalSeconds <= 0) { - clearInterval(countdown); - } + if (isCountDown) { + setTime((prev) => { + if (prev <= 0) { + setIsCountDown(false); + return 0; // Stop at 0 + } + return prev - 1; + }); } else { - // MM : SS 형식일 경우 - const [minutes, seconds] = timeParts; - let totalSeconds = minutes * 60 + seconds; - - if (!errorType) { - totalSeconds -= 1; - } else { - totalSeconds += 1; - } - - const newMinutes = Math.floor(totalSeconds / 60); - const newSeconds = totalSeconds % 60; - - if (newMinutes >= 60) { - const newHours = Math.floor(newMinutes / 60); - const remainingMinutes = newMinutes % 60; - - setFormat("HH : MM : SS"); - setTime( - `${String(newHours).padStart(2, "0")} : ${String( - remainingMinutes - ).padStart(2, "0")} : ${String(newSeconds).padStart(2, "0")}` - ); - } else { - setTime( - `${String(newMinutes).padStart(2, "0")} : ${String( - newSeconds - ).padStart(2, "0")}` - ); - } - - if (!errorType && totalSeconds <= 0) { - clearInterval(countdown); - } + setTime((prev) => prev + 1); } }, 1000); - return () => clearInterval(countdown); - }, [time, errorType]); + }, [isCountDown]); - return time; -} + return { time: formatTimeDisplay(time), isCountDown }; +} \ No newline at end of file diff --git a/src/staging/hooks/useCrossTrade.ts b/src/staging/hooks/useCrossTrade.ts index 78b17536d..0377187b1 100644 --- a/src/staging/hooks/useCrossTrade.ts +++ b/src/staging/hooks/useCrossTrade.ts @@ -154,6 +154,7 @@ export const useCrossTradeData_L1 = (parmas: { isHistory?: boolean }) => { account: address as string, } : undefined, + fetchPolicy: "cache-first", }); return { data, loading, error }; @@ -176,6 +177,7 @@ export const useCrossTradeData_L2 = (parmas: { isHistory?: boolean }) => { account: address, } : undefined, + fetchPolicy: "cache-first", }); return { data, loading, error }; @@ -199,6 +201,7 @@ export const useRequestData = ( const [requestList, setRequestList] = useState(null); const [isLoading, setIsLoading] = useState(true); const { provideCTTxnCost } = useProvideCTGas(); + const fetchRequestList = useCallback(async () => { try { if (error || _l1Error) return null; @@ -341,7 +344,7 @@ export const useRequestData = ( amount: profitAmount.toString(), symbol: isETH ? "ETH" : (tokenInfo?.tokenSymbol as string), usd: profitUSD, - percent: percent.toFixed(30), + percent: percent.toFixed(4), decimals: tokenInfo?.decimals as number, }, blockTimestamps: Number(item.blockTimestamp), @@ -357,6 +360,7 @@ export const useRequestData = ( initialCTAmount: item._ctAmount, editedCTAmount: ctAmount, isNetaveProfit: profitRatio.toFixed(30).includes("-"), + provideCTTxnCost: txnCost, }; }); @@ -364,6 +368,10 @@ export const useRequestData = ( (item) => !item.isCanceled && !item.isProvided ); + trimedResult.sort( + (a, b) => Number(b.profit.percent) - Number(a.profit.percent) + ); + setIsLoading(false); return setRequestList(trimedResult); } diff --git a/src/staging/types/crossTrade.ts b/src/staging/types/crossTrade.ts index 74bde8b2f..75a7a0762 100644 --- a/src/staging/types/crossTrade.ts +++ b/src/staging/types/crossTrade.ts @@ -37,4 +37,5 @@ export interface CrossTradeData { initialCTAmount: string; editedCTAmount: bigint; isNetaveProfit: boolean; + provideCTTxnCost: number; } diff --git a/src/staging/utils/history.ts b/src/staging/utils/history.ts new file mode 100644 index 000000000..07cd6b294 --- /dev/null +++ b/src/staging/utils/history.ts @@ -0,0 +1,48 @@ +import { + isDepositTransactionHistory, + StandardHistory, + Status, +} from "../types/transaction"; + +export const getSortedTxHistory = (list: StandardHistory[]) => { + const actionItems = list.filter((tx) => isActiveTxItem(tx.status)); + const nonActiveItems = list.filter((tx) => !isActiveTxItem(tx.status)); + const sortedActiveItems = getSortedTxListByDate(actionItems); + const sortedNonActiveItems = getSortedTxListByDate(nonActiveItems); + return [...sortedActiveItems, ...sortedNonActiveItems]; +}; + +export const isActiveTxItem = (status: Status) => { + return status !== Status.Completed; +}; + +export const getSortedTxListByDate = (transactions: StandardHistory[]) => { + return transactions.sort((a, b) => { + // Get the latest timestamp for transaction a + const latestA = isDepositTransactionHistory(a) + ? Math.max( + a.blockTimestamps.initialCompletedTimestamp, + a.blockTimestamps?.finalizedCompletedTimestamp ?? 0 + ) + : Math.max( + a.blockTimestamps.initialCompletedTimestamp, + a.blockTimestamps.rollupCompletedTimestamp ?? 0, + a.blockTimestamps.finalizedCompletedTimestamp ?? 0 + ); + + // Get the latest timestamp for transaction b + const latestB = isDepositTransactionHistory(b) + ? Math.max( + b.blockTimestamps.initialCompletedTimestamp, + b.blockTimestamps?.finalizedCompletedTimestamp ?? 0 + ) + : Math.max( + b.blockTimestamps.initialCompletedTimestamp, + b.blockTimestamps?.rollupCompletedTimestamp ?? 0, + b.blockTimestamps.finalizedCompletedTimestamp ?? 0 + ); + + // Sort in descending order (latest first) + return latestB - latestA; + }); +};