diff --git a/src/components/Announcement/Popups/index.tsx b/src/components/Announcement/Popups/index.tsx index 3d597f7bfc..b2c89487ae 100644 --- a/src/components/Announcement/Popups/index.tsx +++ b/src/components/Announcement/Popups/index.tsx @@ -6,6 +6,7 @@ import CenterPopup from 'components/Announcement/Popups/CenterPopup' import SnippetPopup from 'components/Announcement/Popups/SnippetPopup' import { PopupType, PrivateAnnouncementType } from 'components/Announcement/type' import { ButtonEmpty } from 'components/Button' +import useNotificationLimitOrder from 'components/swapv2/LimitOrder/useNotificationLimitOrder' import { TIMES_IN_SECS } from 'constants/index' import { Z_INDEXS } from 'constants/styles' import { useActiveWeb3React } from 'hooks' @@ -149,6 +150,7 @@ export default function Popups() { } }, [account, isShowTutorial, addPopup, chainId, userInfo?.identityId]) + useNotificationLimitOrder() const totalTopRightPopup = topRightPopups.length return ( diff --git a/src/components/Header/groups/SwapNavGroup.tsx b/src/components/Header/groups/SwapNavGroup.tsx index 3a37c0c16c..90c5bf3da3 100644 --- a/src/components/Header/groups/SwapNavGroup.tsx +++ b/src/components/Header/groups/SwapNavGroup.tsx @@ -21,7 +21,7 @@ import { useActiveWeb3React } from 'hooks' import useMixpanel, { MIXPANEL_TYPE } from 'hooks/useMixpanel' import { useTutorialSwapGuide } from 'state/tutorial/hooks' import { useIsDarkMode } from 'state/user/hooks' -import { getLimitOrderContract } from 'utils' +import { isSupportLimitOrder } from 'utils' import { DropdownTextAnchor, StyledNavLink } from '../styleds' import NavGroup from './NavGroup' @@ -89,7 +89,7 @@ const SwapNavGroup = () => { - {getLimitOrderContract(chainId) && ( + {isSupportLimitOrder(chainId) && ( (needCheckActuallyPending ? null : pendingRpc) const dispatch = useDispatch() - const { cancellingOrdersIds, cancellingOrdersNonces, loading } = cancellingOrderInfo + const { loading, isOrderCancelling } = cancellingOrderInfo const interval = useRef() @@ -68,7 +68,7 @@ function StatusIcon({ switch (type) { case TRANSACTION_TYPE.CANCEL_LIMIT_ORDER: const orderId = extraInfo?.arbitrary?.order_id - isPending = cancellingOrdersIds.includes(orderId) || cancellingOrdersNonces.length > 0 + isPending = isOrderCancelling(orderId) break case TRANSACTION_TYPE.BRIDGE: { const { data: response } = await axios.get(`${BFF_API}/v1/cross-chain-history/multichain-transfers/${hash}`) @@ -95,7 +95,7 @@ function StatusIcon({ console.error('Checking txs status error: ', error) interval.current && clearInterval(interval.current) } - }, [cancellingOrdersIds, cancellingOrdersNonces, chainId, dispatch, transaction, extraInfo, hash, type, loading]) + }, [isOrderCancelling, chainId, dispatch, transaction, extraInfo, hash, type, loading]) const checkStatusDebounced = useMemo(() => debounce(checkStatus, 1000), [checkStatus]) diff --git a/src/components/swapv2/LimitOrder/EditOrderModal.tsx b/src/components/swapv2/LimitOrder/EditOrderModal.tsx index 3af151040f..191113f482 100644 --- a/src/components/swapv2/LimitOrder/EditOrderModal.tsx +++ b/src/components/swapv2/LimitOrder/EditOrderModal.tsx @@ -1,8 +1,8 @@ import { Trans } from '@lingui/macro' import { ethers } from 'ethers' -import { useEffect, useState } from 'react' import { X } from 'react-feather' import { Flex, Text } from 'rebass' +import { useGetTotalActiveMakingAmountQuery } from 'services/limitOrder' import styled from 'styled-components' import Modal from 'components/Modal' @@ -13,7 +13,6 @@ import { TransactionFlowState } from 'types/TransactionFlowState' import LimitOrderForm, { Label } from './LimitOrderForm' import { calcInvert, calcPercentFilledOrder, calcRate, removeTrailingZero } from './helpers' -import { getTotalActiveMakingAmount } from './request' import { LimitOrder, LimitOrderStatus, RateInfo } from './type' const Wrapper = styled.div` @@ -61,15 +60,10 @@ export default function EditOrderModal({ const defaultRate: RateInfo = { rate, invertRate: calcInvert(rate), invert: false } const filled = currencyOut ? calcPercentFilledOrder(filledTakingAmount, takingAmount, currencyOut.decimals) : 0 - const [defaultActiveMakingAmount, setDefaultActiveMakingAmount] = useState('') - - // prefetch - useEffect(() => { - if (!currencyIn || !account) return - getTotalActiveMakingAmount(chainId, currencyIn.wrapped.address, account) - .then(({ activeMakingAmount }) => setDefaultActiveMakingAmount(activeMakingAmount)) - .catch(console.error) - }, [currencyIn, account, chainId]) + const { data: defaultActiveMakingAmount } = useGetTotalActiveMakingAmountQuery( + { chainId, tokenAddress: currencyIn?.wrapped.address ?? '', account: account ?? '' }, + { skip: !currencyIn || !account }, + ) return ( diff --git a/src/components/swapv2/LimitOrder/LimitOrderForm.tsx b/src/components/swapv2/LimitOrder/LimitOrderForm.tsx index e8d7989645..c6dddfa0e0 100644 --- a/src/components/swapv2/LimitOrder/LimitOrderForm.tsx +++ b/src/components/swapv2/LimitOrder/LimitOrderForm.tsx @@ -8,6 +8,12 @@ import { rgba } from 'polished' import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Info, Repeat } from 'react-feather' import { Flex, Text } from 'rebass' +import { + useCreateOrderMutation, + useCreateOrderSignatureMutation, + useGetLOContractAddressQuery, + useGetTotalActiveMakingAmountQuery, +} from 'services/limitOrder' import styled from 'styled-components' import { NotificationType } from 'components/Announcement/type' @@ -40,7 +46,6 @@ import { useLimitActionHandlers, useLimitState } from 'state/limit/hooks' import { tryParseAmount } from 'state/swap/hooks' import { useCurrencyBalance } from 'state/wallet/hooks' import { TransactionFlowState } from 'types/TransactionFlowState' -import { getLimitOrderContract } from 'utils' import { subscribeNotificationOrderCancelled, subscribeNotificationOrderExpired } from 'utils/firebase' import { maxAmountSpend } from 'utils/maxAmountSpend' @@ -63,7 +68,6 @@ import { parseFraction, removeTrailingZero, } from './helpers' -import { clearCacheActiveMakingAmount, getMessageSignature, getTotalActiveMakingAmount, submitOrder } from './request' import { CreateOrderParam, LimitOrder, RateInfo } from './type' export const Label = styled.div` @@ -144,7 +148,6 @@ const LimitOrderForm = function LimitOrderForm({ const [inputAmount, setInputAmount] = useState(defaultInputAmount) const [outputAmount, setOuputAmount] = useState(defaultOutputAmount) - const [activeOrderMakingAmount, setActiveOrderMakingAmount] = useState(defaultActiveMakingAmount) const [rateInfo, setRateInfo] = useState(defaultRate) const displayRate = rateInfo.invert ? rateInfo.invertRate : rateInfo.rate @@ -160,6 +163,12 @@ const LimitOrderForm = function LimitOrderForm({ const { loading: loadingTrade, tradeInfo } = useBaseTradeInfoLimitOrder(currencyIn, currencyOut) const deltaRate = useGetDeltaRateLimitOrder({ marketPrice: tradeInfo, rateInfo }) + const { data: activeOrderMakingAmount = defaultActiveMakingAmount, refetch: getActiveMakingAmount } = + useGetTotalActiveMakingAmountQuery( + { chainId, tokenAddress: currencyIn?.wrapped.address ?? '', account: account ?? '' }, + { skip: !currencyIn || !account }, + ) + const { execute: onWrap, inputError: wrapInputError } = useWrapCallback(currencyIn, currencyOut, inputAmount, true) const showWrap = !!currencyIn?.isNative @@ -276,10 +285,13 @@ const LimitOrderForm = function LimitOrderForm({ } const parseInputAmount = tryParseAmount(inputAmount, currencyIn ?? undefined) + const { data, isError } = useGetLOContractAddressQuery(chainId) + const limitOrderContract = isError ? undefined : data + const currentAllowance = useTokenAllowance( currencyIn as Token, account ?? undefined, - getLimitOrderContract(chainId) ?? '', + limitOrderContract, ) as CurrencyAmount const parsedActiveOrderMakingAmount = useMemo(() => { @@ -323,11 +335,7 @@ const LimitOrderForm = function LimitOrderForm({ } }, [currencyIn?.isNative, currentAllowance, parseInputAmount, parsedActiveOrderMakingAmount]) - const [approval, approveCallback] = useApproveCallback( - parseInputAmount, - getLimitOrderContract(chainId) ?? '', - !enoughAllowance, - ) + const [approval, approveCallback] = useApproveCallback(parseInputAmount, limitOrderContract || '', !enoughAllowance) const { inputError, outPutError } = useValidateInputError({ inputAmount, @@ -380,20 +388,6 @@ const LimitOrderForm = function LimitOrderForm({ } } - const getActiveMakingAmount = useCallback( - async (currencyIn: Currency) => { - try { - const address = currencyIn?.wrapped.address - if (!address || !account) return - const { activeMakingAmount } = await getTotalActiveMakingAmount(chainId, address, account) - setActiveOrderMakingAmount(activeMakingAmount) - } catch (error) { - console.log(error) - } - }, - [account, chainId], - ) - const onResetForm = () => { setInputAmount(defaultInputAmount) setOuputAmount(defaultOutputAmount) @@ -415,6 +409,7 @@ const LimitOrderForm = function LimitOrderForm({ [setFlowState], ) + const [getMessageSignature] = useCreateOrderSignatureMutation() const signOrder = async (params: CreateOrderParam) => { const { currencyIn, currencyOut, inputAmount, outputAmount, signature, salt } = params if (signature && salt) return { signature, salt } @@ -428,7 +423,7 @@ const LimitOrderForm = function LimitOrderForm({ outputAmount, )} ${currencyOut.symbol}`, })) - const messagePayload = await getMessageSignature(payload) + const messagePayload = await getMessageSignature(payload).unwrap() const rawSignature = await library.send('eth_signTypedData_v4', [account, JSON.stringify(messagePayload)]) @@ -442,6 +437,7 @@ const LimitOrderForm = function LimitOrderForm({ return { signature: ethers.utils.hexlify(bytes), salt: messagePayload?.message?.salt } } + const [submitOrder] = useCreateOrderMutation() const onSubmitCreateOrder = async (params: CreateOrderParam) => { try { const { currencyIn, currencyOut, account, inputAmount, outputAmount, expiredAt } = params @@ -452,7 +448,7 @@ const LimitOrderForm = function LimitOrderForm({ const { signature, salt } = await signOrder(params) const payload = getPayloadCreateOrder(params) setFlowState(state => ({ ...state, pendingText: t`Placing order` })) - const response = await submitOrder({ ...payload, salt, signature }) + const response = await submitOrder({ ...payload, salt, signature }).unwrap() setFlowState(state => ({ ...state, showConfirm: false })) notify( @@ -468,6 +464,7 @@ const LimitOrderForm = function LimitOrderForm({ return response?.id } catch (error) { handleError(error) + return } } @@ -526,19 +523,16 @@ const LimitOrderForm = function LimitOrderForm({ const refreshActiveMakingAmount = useMemo( () => debounce(() => { - clearCacheActiveMakingAmount() - if (currencyIn) { - getActiveMakingAmount(currencyIn) - } + try { + getActiveMakingAmount() + } catch (error) {} }, 100), - [currencyIn, getActiveMakingAmount], + [getActiveMakingAmount], ) - const isInit = useRef(false) useEffect(() => { - if (isInit.current && currencyIn) getActiveMakingAmount(currencyIn) // skip the first time - isInit.current = true - }, [currencyIn, getActiveMakingAmount, isEdit]) + if (currencyIn) refreshActiveMakingAmount() + }, [currencyIn, refreshActiveMakingAmount, isEdit]) // use ref to prevent too many api call when firebase update status const refSubmitCreateOrder = useRef(onSubmitCreateOrder) diff --git a/src/components/swapv2/LimitOrder/ListOrder/index.tsx b/src/components/swapv2/LimitOrder/ListOrder/index.tsx index cb5f9467d7..3960f5551e 100644 --- a/src/components/swapv2/LimitOrder/ListOrder/index.tsx +++ b/src/components/swapv2/LimitOrder/ListOrder/index.tsx @@ -1,16 +1,15 @@ import { Trans, t } from '@lingui/macro' import { BigNumber } from 'ethers' -import { debounce } from 'lodash' import { rgba } from 'polished' import { stringify } from 'querystring' -import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react' +import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react' import { isMobile } from 'react-device-detect' import { Info, Trash } from 'react-feather' import { useNavigate } from 'react-router-dom' import { Flex, Text } from 'rebass' +import { useGetEncodeDataMutation, useGetListOrdersQuery, useInsertCancellingOrderMutation } from 'services/limitOrder' import styled from 'styled-components' -import { NotificationType } from 'components/Announcement/type' import { ButtonEmpty } from 'components/Button' import Column from 'components/Column' import LocalLoader from 'components/LocalLoader' @@ -21,33 +20,29 @@ import SubscribeNotificationButton from 'components/SubscribeButton' import LIMIT_ORDER_ABI from 'constants/abis/limit_order.json' import { EMPTY_ARRAY, TRANSACTION_STATE_DEFAULT } from 'constants/index' import { useActiveWeb3React, useWeb3React } from 'hooks' -import { useContract } from 'hooks/useContract' import useMixpanel, { MIXPANEL_TYPE } from 'hooks/useMixpanel' import useParsedQueryString from 'hooks/useParsedQueryString' -import { useNotify } from 'state/application/hooks' +import useShowLoadingAtLeastTime from 'hooks/useShowLoadingAtLeastTime' import { useLimitState } from 'state/limit/hooks' import { useTokenPricesWithLoading } from 'state/tokenPrices/hooks' -import { useAllTransactions, useTransactionAdder } from 'state/transactions/hooks' +import { useTransactionAdder } from 'state/transactions/hooks' import { TRANSACTION_TYPE } from 'state/transactions/type' import { TransactionFlowState } from 'types/TransactionFlowState' -import { findTx, getLimitOrderContract } from 'utils' import { subscribeNotificationOrderCancelled, subscribeNotificationOrderExpired, subscribeNotificationOrderFilled, } from 'utils/firebase' +import { getContract } from 'utils/getContract' import { sendEVMTransaction } from 'utils/sendTransaction' -import { getTransactionStatus } from 'utils/transaction' import EditOrderModal from '../EditOrderModal' import CancelOrderModal from '../Modals/CancelOrderModal' import { ACTIVE_ORDER_OPTIONS, CLOSE_ORDER_OPTIONS } from '../const' import { calcPercentFilledOrder, formatAmountOrder, getErrorMessage, isActiveStatus } from '../helpers' -import { ackNotificationOrder, getEncodeData, getListOrder, insertCancellingOrder } from '../request' import { LimitOrder, LimitOrderStatus, ListOrderHandle } from '../type' import useCancellingOrders from '../useCancellingOrders' import OrderItem from './OrderItem' -import SummaryNotify from './SummaryNotify' import TabSelector from './TabSelector' import TableHeader from './TableHeader' @@ -132,20 +127,32 @@ export default forwardRef(function ListLimitOrder(props, ref) { const [keyword, setKeyword] = useState('') const [isOpenCancel, setIsOpenCancel] = useState(false) const [isOpenEdit, setIsOpenEdit] = useState(false) - const limitOrderContract = useContract(getLimitOrderContract(chainId) ?? '', LIMIT_ORDER_ABI) - const notify = useNotify() const { ordersUpdating } = useLimitState() const addTransactionWithType = useTransactionAdder() const { isOrderCancelling, setCancellingOrders, cancellingOrdersIds } = useCancellingOrders() - const transactions = useAllTransactions() const { mixpanelHandler } = useMixpanel() - const [orders, setOrders] = useState([]) - const [totalOrder, setTotalOrder] = useState(0) + const { + data: { orders = [], totalOrder = 0 } = {}, + isFetching, + refetch: refetchOrders, + } = useGetListOrdersQuery( + { + chainId, + maker: account, + status: orderType, + query: keyword, + page: curPage, + pageSize: PAGE_SIZE, + }, + { skip: !account, refetchOnFocus: true }, + ) + + const loading = useShowLoadingAtLeastTime(isFetching) + const [flowState, setFlowState] = useState(TRANSACTION_STATE_DEFAULT) const [currentOrder, setCurrentOrder] = useState() const [isCancelAll, setIsCancelAll] = useState(false) - const [loading, setLoading] = useState(true) const tokenAddresses = useMemo(() => { const activeOrders = orders.filter(checkOrderActive) @@ -185,211 +192,36 @@ export default forwardRef(function ListLimitOrder(props, ref) { setCurPage(1) } - const controller = useRef(new AbortController()) - const fetchListOrder = useCallback( - async (orderType: LimitOrderStatus, query: string, curPage: number) => { - try { - let orders: LimitOrder[] = [] - let totalItems = 0 - if (account) { - controller.current.abort() - controller.current = new AbortController() - const response = await getListOrder( - { - chainId, - maker: account, - status: orderType, - query, - page: curPage, - pageSize: PAGE_SIZE, - }, - controller.current.signal, - ) - orders = response.orders ?? [] - totalItems = response.pagination.totalItems ?? 0 - } - setOrders(orders) - setTotalOrder(totalItems) - } catch (error) { - if (error?.name === 'AbortError') return - console.error(error) - } - setLoading(false) - }, - [account, chainId], - ) - - const fetchListOrderDebounce = useMemo(() => debounce(fetchListOrder, 400), [fetchListOrder]) - useEffect(() => { - setLoading(true) - setOrders([]) - fetchListOrderDebounce(orderType, keyword, curPage) - }, [orderType, keyword, fetchListOrderDebounce, curPage]) - useEffect(() => { onReset() - }, [chainId]) + }, [chainId, orderType]) const refreshListOrder = useCallback(() => { - onReset() - fetchListOrderDebounce(orderType, '', 1) - }, [fetchListOrderDebounce, orderType]) + try { + onReset() + refetchOrders() + } catch (error) {} + }, [refetchOrders]) useImperativeHandle(ref, () => ({ refreshListOrder, })) - const isTransactionFailed = (txHash: string) => { - const transactionInfo = findTx(transactions, txHash) - return transactionInfo ? getTransactionStatus(transactionInfo).error : false - } - - const isTxFailed = useRef(isTransactionFailed) - isTxFailed.current = isTransactionFailed - - const showedNotificationOrderIds = useRef<{ [id: string]: boolean }>({}) - const ackNotiLocal = (id: string | number) => { - showedNotificationOrderIds.current = { ...showedNotificationOrderIds.current, [id]: true } - } - useEffect(() => { if (!account) return - const unsubscribeCancelled = subscribeNotificationOrderCancelled(account, chainId, data => { - refreshListOrder() - const cancelAllData = data?.all?.[0] - const cancelAllSuccess = cancelAllData?.isSuccessful - if (cancelAllSuccess !== undefined) { - // not show Notification when cancel failed because duplicate. - if ( - !isTxFailed.current(cancelAllData?.txHash ?? '') && - !showedNotificationOrderIds.current[cancelAllData.id ?? ''] - ) { - notify( - { - type: cancelAllSuccess ? NotificationType.WARNING : NotificationType.ERROR, - title: cancelAllSuccess ? t`Order Cancelled` : t`Cancel Orders Failed`, - summary: ( - - ), - }, - 10000, - ) - } - const nonces = - data?.all.map((e: { id: string }) => { - ackNotiLocal(e.id) - return e.id - }) ?? [] - if (nonces.length) { - ackNotificationOrder(nonces, account, chainId, LimitOrderStatus.CANCELLED).catch(console.error) - } - } - + const callback = (data: any) => { const orders: LimitOrder[] = data?.orders ?? [] - const orderCancelSuccess = orders.filter(e => e.isSuccessful && !showedNotificationOrderIds.current[e.id]) - const orderCancelFailed = orders.filter( - e => !e.isSuccessful && !isTxFailed.current(e.txHash) && !showedNotificationOrderIds.current[e.id], - ) - - if (orderCancelSuccess.length) - notify( - { - type: NotificationType.WARNING, - title: t`Order Cancelled`, - summary: , - }, - 10000, - ) - if (orderCancelFailed.length) - notify( - { - type: NotificationType.ERROR, - title: t`Order Cancel Failed`, - summary: , - }, - 10000, - ) - if (orders.length) - ackNotificationOrder( - orders.map(({ id }) => { - ackNotiLocal(id) - return id.toString() - }), - account, - chainId, - LimitOrderStatus.CANCELLED, - ).catch(console.error) - }) - const unsubscribeExpired = subscribeNotificationOrderExpired(account, chainId, data => { - refreshListOrder() - const orders: LimitOrder[] = data?.orders ?? [] - if (orders.length) { - notify( - { - type: NotificationType.WARNING, - title: t`Order Expired`, - summary: , - }, - 10000, - ) - ackNotificationOrder( - orders.map(e => e.id.toString()), - account, - chainId, - LimitOrderStatus.EXPIRED, - ).catch(console.error) - } - }) - const unsubscribeFilled = subscribeNotificationOrderFilled(account, chainId, data => { - refreshListOrder() - const orders: LimitOrder[] = data?.orders ?? [] - const orderFilled = orders.filter( - order => order.status === LimitOrderStatus.FILLED || order.takingAmount === order.filledTakingAmount, - ) - const orderPartialFilled = orders.filter( - order => order.status === LimitOrderStatus.PARTIALLY_FILLED || order.takingAmount !== order.filledTakingAmount, - ) - if (orderFilled.length) { - notify( - { - type: NotificationType.SUCCESS, - title: t`Order Filled`, - summary: , - }, - 10000, - ) - } - orderPartialFilled.forEach(order => { - notify( - { - type: NotificationType.SUCCESS, - title: t`Order Partially Filled`, - summary: , - }, - 10000, - ) - }) - if (orders.length) { - ackNotificationOrder( - orders.map(e => e.uuid), - account, - chainId, - LimitOrderStatus.FILLED, - ).catch(console.error) - } - }) + if (orders.length) refreshListOrder() + } + const unsubscribeCancelled = subscribeNotificationOrderCancelled(account, chainId, refreshListOrder) + const unsubscribeExpired = subscribeNotificationOrderExpired(account, chainId, callback) + const unsubscribeFilled = subscribeNotificationOrderFilled(account, chainId, callback) return () => { unsubscribeCancelled?.() unsubscribeExpired?.() unsubscribeFilled?.() } - }, [account, chainId, notify, refreshListOrder]) + }, [account, chainId, refreshListOrder]) const hideConfirmCancel = useCallback(() => { setFlowState(state => ({ ...state, showConfirm: false })) @@ -446,8 +278,10 @@ export default forwardRef(function ListLimitOrder(props, ref) { return orders.filter(e => !isOrderCancelling(e)).length }, [orders, isOrderCancelling]) + const [insertCancellingOrder] = useInsertCancellingOrderMutation() + const [getEncodeData] = useGetEncodeDataMutation() const requestCancelOrder = async (order: LimitOrder | undefined) => { - if (!library || !account || !limitOrderContract) return Promise.reject('Wrong input') + if (!library || !account) return Promise.reject('Wrong input') setFlowState(state => ({ ...state, @@ -456,58 +290,69 @@ export default forwardRef(function ListLimitOrder(props, ref) { attemptingTxn: true, })) - const [{ encodedData }, nonce] = await Promise.all([ - getEncodeData([order?.id].filter(Boolean) as number[], isCancelAll), - isCancelAll ? limitOrderContract.nonce(account) : Promise.resolve(BigNumber.from(0)), - ]) - - const response = await sendEVMTransaction( - account, - library, - getLimitOrderContract(chainId) ?? '', - encodedData, - BigNumber.from(0), - ) const newOrders = isCancelAll ? orders.map(e => e.id) : order?.id ? [order?.id] : [] - setCancellingOrders({ orderIds: cancellingOrdersIds.concat(newOrders) }) - - if (response?.hash) { - insertCancellingOrder({ - maker: account, - chainId: chainId.toString(), - txHash: response.hash, - [isCancelAll ? 'nonce' : 'orderIds']: isCancelAll ? nonce?.toNumber() : newOrders, - }) + + const sendTransaction = async (encodedData: string, contract: string, payload: any) => { + const response = await sendEVMTransaction(account, library, contract, encodedData, BigNumber.from(0)) + if (response?.hash) { + insertCancellingOrder({ + maker: account, + chainId: chainId.toString(), + txHash: response.hash, + contractAddress: contract ?? '', + ...payload, + }).unwrap() + } + + if (response) { + const { + makerAssetDecimals, + takerAssetDecimals, + takerAssetSymbol, + takingAmount, + makingAmount, + takerAsset, + makerAssetSymbol, + makerAsset, + } = order || ({} as LimitOrder) + const amountIn = order ? formatAmountOrder(makingAmount, makerAssetDecimals) : '' + const amountOut = order ? formatAmountOrder(takingAmount, takerAssetDecimals) : '' + addTransactionWithType({ + ...response, + type: TRANSACTION_TYPE.CANCEL_LIMIT_ORDER, + extraInfo: order + ? { + tokenAddressIn: makerAsset, + tokenAddressOut: takerAsset, + tokenSymbolIn: makerAssetSymbol, + tokenSymbolOut: takerAssetSymbol, + tokenAmountIn: amountIn, + tokenAmountOut: amountOut, + arbitrary: getPayloadTracking(order), + } + : { arbitrary: { totalOrder } }, + }) + } } - if (response) { - const { - makerAssetDecimals, - takerAssetDecimals, - takerAssetSymbol, - takingAmount, - makingAmount, - takerAsset, - makerAssetSymbol, - makerAsset, - } = order || ({} as LimitOrder) - const amountIn = order ? formatAmountOrder(makingAmount, makerAssetDecimals) : '' - const amountOut = order ? formatAmountOrder(takingAmount, takerAssetDecimals) : '' - addTransactionWithType({ - ...response, - type: TRANSACTION_TYPE.CANCEL_LIMIT_ORDER, - extraInfo: order - ? { - tokenAddressIn: makerAsset, - tokenAddressOut: takerAsset, - tokenSymbolIn: makerAssetSymbol, - tokenSymbolOut: takerAssetSymbol, - tokenAmountIn: amountIn, - tokenAmountOut: amountOut, - arbitrary: getPayloadTracking(order), - } - : { arbitrary: { totalOrder } }, - }) + + if (isCancelAll) { + const contracts = [...new Set(orders.map(e => e.contractAddress))] + for (const address of contracts) { + const limitOrderContract = getContract(address, LIMIT_ORDER_ABI, library, account) + const [{ encodedData }, nonce] = await Promise.all([ + getEncodeData({ orderIds: [], isCancelAll }).unwrap(), + limitOrderContract?.nonce?.(account), + ]) + await sendTransaction(encodedData, address, { nonce: nonce.toNumber() }) + } + } else { + const { encodedData } = await getEncodeData({ + orderIds: [order?.id].filter(Boolean) as number[], + isCancelAll, + }).unwrap() + await sendTransaction(encodedData, order?.contractAddress ?? '', { orderIds: newOrders }) } + setCancellingOrders(cancellingOrdersIds.concat(newOrders)) return } diff --git a/src/components/swapv2/LimitOrder/request.ts b/src/components/swapv2/LimitOrder/request.ts deleted file mode 100644 index c1675a3985..0000000000 --- a/src/components/swapv2/LimitOrder/request.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { ChainId } from '@kyberswap/ks-sdk-core' -import axios from 'axios' -import { stringify } from 'querystring' - -import { LIMIT_ORDER_API_READ, LIMIT_ORDER_API_WRITE } from 'constants/env' - -import { LimitOrder, LimitOrderStatus } from './type' - -const formatData = (data: any) => data.data.data -export const getListOrder = ( - params: any, - signal: AbortSignal, -): Promise<{ orders: LimitOrder[]; pagination: { totalItems: number } }> => { - return fetch(`${LIMIT_ORDER_API_READ}/v1/orders?${stringify(params)}`, { signal }) - .then(data => data.json()) - .then(data => data.data) - .then(data => { - data.orders.forEach((order: any) => { - order.chainId = Number(order.chainId) as ChainId - }) - - return data - }) -} - -export const getNumberOfInsufficientFundOrders = async ( - params: { - chainId: ChainId - maker: string - }, - signal?: AbortSignal, -): Promise => { - return fetch(`${LIMIT_ORDER_API_READ}/v1/orders/insufficient-funds?${stringify(params)}`, { signal }) - .then(data => data.json()) - .then(data => data?.data?.total || 0) -} - -export const submitOrder = (data: any) => { - return axios.post(`${LIMIT_ORDER_API_WRITE}/v1/orders`, data).then(formatData) -} - -export const getMessageSignature = (data: any) => { - return axios.post(`${LIMIT_ORDER_API_WRITE}/v1/orders/sign-message`, data).then(formatData) -} - -let activeMakingAmountCache: { [address: string]: string } = {} -export const clearCacheActiveMakingAmount = () => { - activeMakingAmountCache = {} -} - -export const getTotalActiveMakingAmount = async ( - chainId: ChainId, - tokenAddress: string, - account: string, -): Promise<{ activeMakingAmount: string }> => { - if (activeMakingAmountCache[tokenAddress]) { - return { activeMakingAmount: activeMakingAmountCache[tokenAddress] } - } - return axios - .get(`${LIMIT_ORDER_API_READ}/v1/orders/active-making-amount`, { - params: { - chainId: chainId + '', - makerAsset: tokenAddress, - maker: account, - }, - }) - .then(formatData) - .then(data => { - activeMakingAmountCache[tokenAddress] = data.activeMakingAmount - return data - }) -} - -export const getEncodeData = (orderIds: number[], isCancelAll = false) => { - const method = isCancelAll ? 'increase-nonce' : 'cancel-batch-orders' - return axios.post(`${LIMIT_ORDER_API_READ}/v1/encode/${method}`, isCancelAll ? {} : { orderIds }).then(formatData) -} - -const mapPath: Partial> = { - [LimitOrderStatus.CANCELLED]: 'cancelled', - [LimitOrderStatus.EXPIRED]: 'expired', - [LimitOrderStatus.FILLED]: 'filled', -} -export const ackNotificationOrder = (docIds: string[], maker: string, chainId: ChainId, type: LimitOrderStatus) => { - return axios - .delete(`${LIMIT_ORDER_API_WRITE}/v1/events/${mapPath[type]}`, { - data: { maker, chainId: chainId + '', [type === LimitOrderStatus.FILLED ? 'uuids' : 'docIds']: docIds }, - }) - .then(formatData) -} - -export const insertCancellingOrder = (data: { - orderIds?: number[] - nonce?: number - maker: string - chainId: string - txHash: string -}) => { - return axios.post(`${LIMIT_ORDER_API_WRITE}/v1/orders/cancelling`, data).then(formatData) -} diff --git a/src/components/swapv2/LimitOrder/type.ts b/src/components/swapv2/LimitOrder/type.ts index 383b5a16d7..a2211ce38c 100644 --- a/src/components/swapv2/LimitOrder/type.ts +++ b/src/components/swapv2/LimitOrder/type.ts @@ -44,6 +44,7 @@ export type LimitOrder = { isSuccessful: boolean uuid: string txHash: string + contractAddress: string } export type RateInfo = { diff --git a/src/components/swapv2/LimitOrder/useCancellingOrders.ts b/src/components/swapv2/LimitOrder/useCancellingOrders.ts index 4457b0988f..8107da276f 100644 --- a/src/components/swapv2/LimitOrder/useCancellingOrders.ts +++ b/src/components/swapv2/LimitOrder/useCancellingOrders.ts @@ -1,7 +1,8 @@ import { useCallback, useEffect, useMemo, useState } from 'react' +import { useGetLOContractAddressQuery } from 'services/limitOrder' import { useActiveWeb3React } from 'hooks' -import { subscribeCancellingOrders } from 'utils/firebase' +import { OrderNonces, subscribeCancellingOrders } from 'utils/firebase' import { isActiveStatus } from './helpers' import { LimitOrder } from './type' @@ -9,8 +10,7 @@ import { LimitOrder } from './type' export type CancellingOrderInfo = { loading: boolean cancellingOrdersIds: number[] - cancellingOrdersNonces: number[] - setCancellingOrders: (data: { orderIds?: number[]; nonces?: number[] }) => void + setCancellingOrders: (orderIds: number[]) => void isOrderCancelling: (order: LimitOrder) => boolean } @@ -18,35 +18,40 @@ export default function useCancellingOrders(): CancellingOrderInfo { const { account, chainId } = useActiveWeb3React() const [cancellingOrdersIds, setCancellingOrdersIds] = useState([]) - const [cancellingOrdersNonces, setCancellingOrdersNonces] = useState([]) + const [cancellingOrdersNonces, setCancellingOrdersNonces] = useState({}) const [loading, setLoading] = useState(true) + const { data = '', isError } = useGetLOContractAddressQuery(chainId) + const contract = isError ? '' : data - const setCancellingOrders = useCallback((data: { orderIds?: number[]; nonces?: number[] }) => { - if (data.orderIds) setCancellingOrdersIds(data.orderIds) - if (data.nonces) setCancellingOrdersNonces(data.nonces) + const setCancellingOrders = useCallback((orderIds: number[]) => { + setCancellingOrdersIds(orderIds) }, []) useEffect(() => { if (!account) return const unsubscribe = subscribeCancellingOrders(account, chainId, data => { setCancellingOrdersIds(data?.orderIds ?? []) - setCancellingOrdersNonces(data?.nonces ?? []) + setCancellingOrdersNonces(data?.noncesByContract ?? {}) setLoading(false) }) return () => unsubscribe?.() - }, [account, chainId, setCancellingOrders]) + }, [account, chainId]) const isOrderCancelling = useCallback( - (order: LimitOrder) => { + (order: LimitOrder | string | undefined) => { + if (!order) return false + const nonces = cancellingOrdersNonces[contract] || [] + if (typeof order === 'string') { + return cancellingOrdersIds.includes(+order) && nonces.length > 0 + } return ( - isActiveStatus(order.status) && - (cancellingOrdersNonces.includes(order.nonce) || cancellingOrdersIds?.includes(order.id)) + isActiveStatus(order.status) && (nonces?.includes?.(order.nonce) || cancellingOrdersIds?.includes(order.id)) ) }, - [cancellingOrdersNonces, cancellingOrdersIds], + [cancellingOrdersNonces, cancellingOrdersIds, contract], ) return useMemo(() => { - return { cancellingOrdersIds, cancellingOrdersNonces, loading, setCancellingOrders, isOrderCancelling } - }, [cancellingOrdersIds, cancellingOrdersNonces, loading, setCancellingOrders, isOrderCancelling]) + return { cancellingOrdersIds, loading, setCancellingOrders, isOrderCancelling } + }, [cancellingOrdersIds, loading, setCancellingOrders, isOrderCancelling]) } diff --git a/src/components/swapv2/LimitOrder/useNotificationLimitOrder.tsx b/src/components/swapv2/LimitOrder/useNotificationLimitOrder.tsx new file mode 100644 index 0000000000..a1b5909a88 --- /dev/null +++ b/src/components/swapv2/LimitOrder/useNotificationLimitOrder.tsx @@ -0,0 +1,178 @@ +import { t } from '@lingui/macro' +import { useCallback, useEffect, useRef } from 'react' +import { useAckNotificationOrderMutation } from 'services/limitOrder' + +import { NotificationType } from 'components/Announcement/type' +import { LimitOrder, LimitOrderStatus } from 'components/swapv2/LimitOrder/type' +import { useActiveWeb3React } from 'hooks' +import { useNotify } from 'state/application/hooks' +import { useAllTransactions } from 'state/transactions/hooks' +import { GroupedTxsByHash } from 'state/transactions/type' +import { findTx } from 'utils' +import { + subscribeNotificationOrderCancelled, + subscribeNotificationOrderExpired, + subscribeNotificationOrderFilled, +} from 'utils/firebase' +import { getTransactionStatus } from 'utils/transaction' + +import SummaryNotify from './ListOrder/SummaryNotify' + +const isTransactionFailed = (txHash: string, transactions: GroupedTxsByHash | undefined) => { + const transactionInfo = findTx(transactions, txHash) + return transactionInfo ? getTransactionStatus(transactionInfo).error : false +} + +const useNotificationLimitOrder = () => { + const notify = useNotify() + const { account, chainId } = useActiveWeb3React() + const showedNotificationOrderIds = useRef<{ [id: string]: boolean }>({}) + + const ackNotiLocal = useCallback((id: string | number) => { + showedNotificationOrderIds.current = { ...showedNotificationOrderIds.current, [id]: true } + }, []) + + const transactions = useAllTransactions() + + const [ackNotificationOrder] = useAckNotificationOrderMutation() + + useEffect(() => { + if (!account) return + const unsubscribeCancelled = subscribeNotificationOrderCancelled(account, chainId, data => { + const cancelAllData = data?.all?.[0] + const cancelAllSuccess = cancelAllData?.isSuccessful + if (cancelAllSuccess !== undefined) { + // not show Notification when cancel failed because duplicate. + if ( + !isTransactionFailed(cancelAllData?.txHash ?? '', transactions) && + !showedNotificationOrderIds.current[cancelAllData.id ?? ''] + ) { + notify( + { + type: cancelAllSuccess ? NotificationType.WARNING : NotificationType.ERROR, + title: cancelAllSuccess ? t`Order Cancelled` : t`Cancel Orders Failed`, + summary: ( + + ), + }, + 10000, + ) + } + const nonces = + data?.all.map((e: { id: string }) => { + ackNotiLocal(e.id) + return e.id + }) ?? [] + if (nonces.length) { + ackNotificationOrder({ docIds: nonces, maker: account, chainId, type: LimitOrderStatus.CANCELLED }).catch( + console.error, + ) + } + } + + const orders: LimitOrder[] = data?.orders ?? [] + const orderCancelSuccess = orders.filter(e => e.isSuccessful && !showedNotificationOrderIds.current[e.id]) + const orderCancelFailed = orders.filter( + e => + !e.isSuccessful && !isTransactionFailed(e.txHash, transactions) && !showedNotificationOrderIds.current[e.id], + ) + + if (orderCancelSuccess.length) + notify( + { + type: NotificationType.WARNING, + title: t`Order Cancelled`, + summary: , + }, + 10000, + ) + if (orderCancelFailed.length) + notify( + { + type: NotificationType.ERROR, + title: t`Order Cancel Failed`, + summary: , + }, + 10000, + ) + if (orders.length) + ackNotificationOrder({ + docIds: orders.map(({ id }) => { + ackNotiLocal(id) + return id.toString() + }), + maker: account, + chainId, + type: LimitOrderStatus.CANCELLED, + }).catch(console.error) + }) + const unsubscribeExpired = subscribeNotificationOrderExpired(account, chainId, data => { + const orders: LimitOrder[] = data?.orders ?? [] + if (orders.length) { + notify( + { + type: NotificationType.WARNING, + title: t`Order Expired`, + summary: , + }, + 10000, + ) + ackNotificationOrder({ + docIds: orders.map(e => e.id.toString()), + maker: account, + chainId, + type: LimitOrderStatus.EXPIRED, + }).catch(console.error) + } + }) + const unsubscribeFilled = subscribeNotificationOrderFilled(account, chainId, data => { + const orders: LimitOrder[] = data?.orders ?? [] + const orderFilled = orders.filter( + order => order.status === LimitOrderStatus.FILLED || order.takingAmount === order.filledTakingAmount, + ) + const orderPartialFilled = orders.filter( + order => order.status === LimitOrderStatus.PARTIALLY_FILLED || order.takingAmount !== order.filledTakingAmount, + ) + if (orderFilled.length) { + notify( + { + type: NotificationType.SUCCESS, + title: t`Order Filled`, + summary: , + }, + 10000, + ) + } + orderPartialFilled.forEach(order => { + notify( + { + type: NotificationType.SUCCESS, + title: t`Order Partially Filled`, + summary: , + }, + 10000, + ) + }) + if (orders.length) { + ackNotificationOrder({ + docIds: orders.map(e => e.uuid), + maker: account, + chainId, + type: LimitOrderStatus.FILLED, + }).catch(console.error) + } + }) + return () => { + unsubscribeCancelled?.() + unsubscribeExpired?.() + unsubscribeFilled?.() + } + }, [account, chainId, notify, ackNotificationOrder, ackNotiLocal, transactions]) +} +export default useNotificationLimitOrder diff --git a/src/constants/env.ts b/src/constants/env.ts index 27501c13fb..52f1659970 100644 --- a/src/constants/env.ts +++ b/src/constants/env.ts @@ -143,7 +143,12 @@ const ANNOUNCEMENT_TEMPLATE_IDS: { [key: string]: { [type: string]: string } } = }, } -export const ENV_KEY: 'production' | 'staging' | 'development' = import.meta.env.VITE_ENV +export enum EnvKeys { + PROD = 'production', + STG = 'staging', + DEV = 'development', +} +export const ENV_KEY: EnvKeys = import.meta.env.VITE_ENV export const getAnnouncementsTemplateIds = (type: PrivateAnnouncementType | 'EXCLUDE') => { return ANNOUNCEMENT_TEMPLATE_IDS[ENV_KEY]?.[type] diff --git a/src/constants/index.ts b/src/constants/index.ts index 01665a544c..25f90d0e0a 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -307,7 +307,7 @@ export const TRANSACTION_STATE_DEFAULT: TransactionFlowState = { export const CHAINS_SUPPORT_FEE_CONFIGS = [ChainId.OASIS, ChainId.VELAS, ChainId.AURORA, ChainId.CRONOS] export const CHAINS_SUPPORT_CROSS_CHAIN = - ENV.ENV_KEY === 'production' || ENV.ENV_KEY === 'staging' + ENV.ENV_KEY === ENV.EnvKeys.PROD || ENV.ENV_KEY === ENV.EnvKeys.STG ? [ ChainId.MAINNET, ChainId.BSCMAINNET, diff --git a/src/constants/networks/arbitrum.ts b/src/constants/networks/arbitrum.ts index be204b1d65..2ae1782a92 100644 --- a/src/constants/networks/arbitrum.ts +++ b/src/constants/networks/arbitrum.ts @@ -68,10 +68,7 @@ const arbitrumInfo: EVMNetworkInfo = { '0xf2BcDf38baA52F6b0C1Db5B025DfFf01Ae1d6dBd', ], }, - limitOrder: { - production: '0x227B0c196eA8db17A665EA6824D972A64202E936', - development: '0x9deCa89E0934a5E0F187a1865299a9a586550864', - }, + limitOrder: '*', averageBlockTimeInSeconds: 1, // TODO: check these info coingeckoNetworkId: 'arbitrum-one', coingeckoNativeTokenId: 'ethereum', diff --git a/src/constants/networks/aurora.ts b/src/constants/networks/aurora.ts index 28810ff814..d2c57244ae 100644 --- a/src/constants/networks/aurora.ts +++ b/src/constants/networks/aurora.ts @@ -62,10 +62,7 @@ const auroraInfo: EVMNetworkInfo = { routers: '0xC1e7dFE73E1598E3910EF4C7845B68A9Ab6F4c83', farms: [], }, - limitOrder: { - production: NOT_SUPPORT, - development: NOT_SUPPORT, - }, + limitOrder: NOT_SUPPORT, averageBlockTimeInSeconds: 1, coingeckoNetworkId: 'aurora', coingeckoNativeTokenId: 'ethereum', diff --git a/src/constants/networks/avax-testnet.ts b/src/constants/networks/avax-testnet.ts index e5e4fddc65..ed78e9ba48 100644 --- a/src/constants/networks/avax-testnet.ts +++ b/src/constants/networks/avax-testnet.ts @@ -59,10 +59,7 @@ const avaxTestnetInfo: EVMNetworkInfo = { routers: '0xd74134d330FB567abD08675b57dD588a7447b5Ac', farms: [], }, - limitOrder: { - production: NOT_SUPPORT, - development: NOT_SUPPORT, - }, + limitOrder: NOT_SUPPORT, averageBlockTimeInSeconds: 1.85, coingeckoNetworkId: 'avalanche', coingeckoNativeTokenId: 'avalanche-2', diff --git a/src/constants/networks/avax.ts b/src/constants/networks/avax.ts index 788137f7a6..6ee9d310f5 100644 --- a/src/constants/networks/avax.ts +++ b/src/constants/networks/avax.ts @@ -76,10 +76,7 @@ const avaxInfo: EVMNetworkInfo = { '0xf2BcDf38baA52F6b0C1Db5B025DfFf01Ae1d6dBd', ], }, - limitOrder: { - development: '0x1877Ec0770901cc6886FDA7E7525a78c2Ed4e975', - production: '0x227B0c196eA8db17A665EA6824D972A64202E936', - }, + limitOrder: '*', averageBlockTimeInSeconds: 1.85, coingeckoNetworkId: 'avalanche', coingeckoNativeTokenId: 'avalanche-2', diff --git a/src/constants/networks/base.ts b/src/constants/networks/base.ts index 74ad713985..8f288af089 100644 --- a/src/constants/networks/base.ts +++ b/src/constants/networks/base.ts @@ -58,10 +58,7 @@ const base: EVMNetworkInfo = { farmv2Quoter: '0x6AFeb9EDd6Cf44fA8E89b1eee28284e6dD7705C8', farmV2S: ['0x3D6AfE2fB73fFEd2E3dD00c501A174554e147a43', '0xf2BcDf38baA52F6b0C1Db5B025DfFf01Ae1d6dBd'], }, - limitOrder: { - production: NOT_SUPPORT, - development: NOT_SUPPORT, - }, + limitOrder: NOT_SUPPORT, averageBlockTimeInSeconds: 2, // dont use for base coingeckoNetworkId: 'base', coingeckoNativeTokenId: 'ethereum', diff --git a/src/constants/networks/bnb-testnet.ts b/src/constants/networks/bnb-testnet.ts index cb87ef22f5..95e9861b72 100644 --- a/src/constants/networks/bnb-testnet.ts +++ b/src/constants/networks/bnb-testnet.ts @@ -65,10 +65,7 @@ const bnbTestnetInfo: EVMNetworkInfo = { routers: '0x785b8893342dfEf9B5D565f67be971b859d34a15', farms: [], }, - limitOrder: { - development: NOT_SUPPORT, - production: NOT_SUPPORT, - }, + limitOrder: NOT_SUPPORT, averageBlockTimeInSeconds: 3, coingeckoNetworkId: 'binance-smart-chain', coingeckoNativeTokenId: 'binancecoin', diff --git a/src/constants/networks/bnb.ts b/src/constants/networks/bnb.ts index bb5d96704e..431fd0081d 100644 --- a/src/constants/networks/bnb.ts +++ b/src/constants/networks/bnb.ts @@ -68,10 +68,7 @@ const bnbInfo: EVMNetworkInfo = { farmv2Quoter: '0x6AFeb9EDd6Cf44fA8E89b1eee28284e6dD7705C8', farmV2S: ['0x3D6AfE2fB73fFEd2E3dD00c501A174554e147a43', '0xf2BcDf38baA52F6b0C1Db5B025DfFf01Ae1d6dBd'], }, - limitOrder: { - development: '0x26279604204aa9D3B530bcd8514fc4276bf0962C', - production: '0x227B0c196eA8db17A665EA6824D972A64202E936', - }, + limitOrder: '*', averageBlockTimeInSeconds: 3, coingeckoNetworkId: 'binance-smart-chain', coingeckoNativeTokenId: 'binancecoin', diff --git a/src/constants/networks/bttc.ts b/src/constants/networks/bttc.ts index 17153a778f..b405c0e960 100644 --- a/src/constants/networks/bttc.ts +++ b/src/constants/networks/bttc.ts @@ -63,10 +63,7 @@ const bttcInfo: EVMNetworkInfo = { routers: '0xF9c2b5746c946EF883ab2660BbbB1f10A5bdeAb4', farms: [], }, - limitOrder: { - production: NOT_SUPPORT, - development: NOT_SUPPORT, - }, + limitOrder: NOT_SUPPORT, averageBlockTimeInSeconds: 2, // TODO: check these info coingeckoNetworkId: 'tron', coingeckoNativeTokenId: 'bittorrent', diff --git a/src/constants/networks/cronos.ts b/src/constants/networks/cronos.ts index c6cebedcf0..65c085eb23 100644 --- a/src/constants/networks/cronos.ts +++ b/src/constants/networks/cronos.ts @@ -62,10 +62,7 @@ const cronosInfo: EVMNetworkInfo = { routers: '0xF9c2b5746c946EF883ab2660BbbB1f10A5bdeAb4', farms: [], }, - limitOrder: { - production: NOT_SUPPORT, - development: NOT_SUPPORT, - }, + limitOrder: NOT_SUPPORT, averageBlockTimeInSeconds: 6, coingeckoNetworkId: 'cronos', coingeckoNativeTokenId: 'crypto-com-chain', diff --git a/src/constants/networks/ethereum.ts b/src/constants/networks/ethereum.ts index a6577db62c..f610546459 100644 --- a/src/constants/networks/ethereum.ts +++ b/src/constants/networks/ethereum.ts @@ -2,7 +2,7 @@ import { ChainId } from '@kyberswap/ks-sdk-core' import EthereumLogo from 'assets/images/ethereum-logo.png' import Mainnet from 'assets/networks/mainnet-network.svg' -import { KYBER_DAO_STATS_API } from 'constants/env' +import { EnvKeys, KYBER_DAO_STATS_API } from 'constants/env' import { EVMNetworkInfo } from 'constants/networks/type' const EMPTY = '' @@ -69,7 +69,7 @@ const ethereumInfo: EVMNetworkInfo = { farmv2Quoter: '0x6AFeb9EDd6Cf44fA8E89b1eee28284e6dD7705C8', farmV2S: ['0x3D6AfE2fB73fFEd2E3dD00c501A174554e147a43'], }, - limitOrder: { development: NOT_SUPPORT, production: '0x227B0c196eA8db17A665EA6824D972A64202E936' }, + limitOrder: [EnvKeys.PROD], averageBlockTimeInSeconds: 13.13, coingeckoNetworkId: 'ethereum', coingeckoNativeTokenId: 'ethereum', diff --git a/src/constants/networks/fantom.ts b/src/constants/networks/fantom.ts index 4e62b5ef38..d1394e836c 100644 --- a/src/constants/networks/fantom.ts +++ b/src/constants/networks/fantom.ts @@ -59,10 +59,7 @@ const fantomInfo: EVMNetworkInfo = { routers: '0xF9c2b5746c946EF883ab2660BbbB1f10A5bdeAb4', farms: [], }, - limitOrder: { - development: '0x15a7e4A0BD7B96ada9db1219fA62c521bDCd8F81', - production: '0x227B0c196eA8db17A665EA6824D972A64202E936', - }, + limitOrder: '*', averageBlockTimeInSeconds: 1, coingeckoNetworkId: 'fantom', coingeckoNativeTokenId: 'fantom', diff --git "a/src/constants/networks/g\303\266rli.ts" "b/src/constants/networks/g\303\266rli.ts" index c7781c42cd..59a9d97004 100644 --- "a/src/constants/networks/g\303\266rli.ts" +++ "b/src/constants/networks/g\303\266rli.ts" @@ -2,6 +2,7 @@ import { ChainId } from '@kyberswap/ks-sdk-core' import EthereumLogo from 'assets/images/ethereum-logo.png' import Mainnet from 'assets/networks/mainnet-network.svg' +import { EnvKeys } from 'constants/env' import { EVMNetworkInfo } from 'constants/networks/type' const EMPTY = '' @@ -60,7 +61,7 @@ const görliInfo: EVMNetworkInfo = { farmv2Quoter: '0x1e9C12303855433052A31815Dc28C146aF3e9C1F', farmV2S: ['0xdd463A7a71122D0248f3Fa1eF975202bAEe74B46'], }, - limitOrder: { development: '0x43E49489dD38dbFF4Aef0d7FC34026aBEF0e1134', production: NOT_SUPPORT }, + limitOrder: [EnvKeys.DEV], averageBlockTimeInSeconds: 13.13, coingeckoNetworkId: 'ethereum', coingeckoNativeTokenId: 'ethereum', diff --git a/src/constants/networks/linea.ts b/src/constants/networks/linea.ts index 00513673e6..102cc15c67 100644 --- a/src/constants/networks/linea.ts +++ b/src/constants/networks/linea.ts @@ -58,10 +58,7 @@ const lineaInfo: EVMNetworkInfo = { farmv2Quoter: '0x6AFeb9EDd6Cf44fA8E89b1eee28284e6dD7705C8', farmV2S: ['0x3d6afe2fb73ffed2e3dd00c501a174554e147a43'], }, - limitOrder: { - production: NOT_SUPPORT, - development: NOT_SUPPORT, - }, + limitOrder: NOT_SUPPORT, averageBlockTimeInSeconds: 2, // TODO: check these info coingeckoNetworkId: NOT_SUPPORT, coingeckoNativeTokenId: NOT_SUPPORT, diff --git a/src/constants/networks/matic.ts b/src/constants/networks/matic.ts index 7d05a9e163..25ac1037b7 100644 --- a/src/constants/networks/matic.ts +++ b/src/constants/networks/matic.ts @@ -71,10 +71,7 @@ const maticInfo: EVMNetworkInfo = { '0xf2BcDf38baA52F6b0C1Db5B025DfFf01Ae1d6dBd', ], }, - limitOrder: { - development: '0x3C2E9227A6d3779e5b469E425CAa7067b40Ff124', - production: '0x227B0c196eA8db17A665EA6824D972A64202E936', - }, + limitOrder: '*', averageBlockTimeInSeconds: 2.6, coingeckoNetworkId: 'polygon-pos', coingeckoNativeTokenId: 'matic-network', diff --git a/src/constants/networks/mumbai.ts b/src/constants/networks/mumbai.ts index 5f6a045c48..23d02f27a3 100644 --- a/src/constants/networks/mumbai.ts +++ b/src/constants/networks/mumbai.ts @@ -59,7 +59,7 @@ const mumbaiInfo: EVMNetworkInfo = { routers: '0xC1e7dFE73E1598E3910EF4C7845B68A9Ab6F4c83', farms: [], }, - limitOrder: { development: NOT_SUPPORT, production: NOT_SUPPORT }, + limitOrder: NOT_SUPPORT, averageBlockTimeInSeconds: 2.6, coingeckoNetworkId: 'polygon-pos', coingeckoNativeTokenId: 'matic-network', diff --git a/src/constants/networks/oasis.ts b/src/constants/networks/oasis.ts index 25dfc2f9c7..d6d3ee62cf 100644 --- a/src/constants/networks/oasis.ts +++ b/src/constants/networks/oasis.ts @@ -60,10 +60,7 @@ const oasisInfo: EVMNetworkInfo = { routers: '0xF9c2b5746c946EF883ab2660BbbB1f10A5bdeAb4', farms: [], }, - limitOrder: { - development: NOT_SUPPORT, - production: NOT_SUPPORT, - }, + limitOrder: NOT_SUPPORT, averageBlockTimeInSeconds: 10, coingeckoNetworkId: 'oasis', coingeckoNativeTokenId: 'oasis-network', diff --git a/src/constants/networks/optimism.ts b/src/constants/networks/optimism.ts index 9e67cf54b1..289260256c 100644 --- a/src/constants/networks/optimism.ts +++ b/src/constants/networks/optimism.ts @@ -63,10 +63,7 @@ const optimismInfo: EVMNetworkInfo = { '0xf2BcDf38baA52F6b0C1Db5B025DfFf01Ae1d6dBd', ], }, - limitOrder: { - development: '0xAF800D3EB207BAFBadE540554DF8bDCe561166f8', - production: '0x227B0c196eA8db17A665EA6824D972A64202E936', - }, + limitOrder: '*', averageBlockTimeInSeconds: 120, coingeckoNetworkId: 'optimistic-ethereum', coingeckoNativeTokenId: 'ethereum', diff --git a/src/constants/networks/solana-devnet.ts b/src/constants/networks/solana-devnet.ts index d46f503bd8..39b3e011e8 100644 --- a/src/constants/networks/solana-devnet.ts +++ b/src/constants/networks/solana-devnet.ts @@ -35,7 +35,7 @@ const solanaInfo: SolanaNetworkInfo = { minForGas: 10 ** 7, }, aggregatorProgramAddress: 'GmgkeeJtcjHgeiSDdT5gxznUDr5ygq9jo8tmA4ny7ziv', - limitOrder: { development: NOT_SUPPORT, production: NOT_SUPPORT }, + limitOrder: NOT_SUPPORT, coingeckoNetworkId: 'solana', coingeckoNativeTokenId: 'solana', defaultRpcUrl: 'https://api.devnet.solana.com', diff --git a/src/constants/networks/solana.ts b/src/constants/networks/solana.ts index 489622a760..c5352dbf28 100644 --- a/src/constants/networks/solana.ts +++ b/src/constants/networks/solana.ts @@ -40,7 +40,7 @@ const solanaInfo: SolanaNetworkInfo = { // pool: 'EKdy97aMrjjxtq4CJh9vN24WuHVsuLz4qtDjyYqttviN', // router: '6VdLuZvVxdgFYQiCQ1VDBBdE27RahXzv2wCxwG4FAzAn', // }, - limitOrder: { development: NOT_SUPPORT, production: NOT_SUPPORT }, + limitOrder: NOT_SUPPORT, coingeckoNetworkId: 'solana', coingeckoNativeTokenId: 'solana', defaultRpcUrl: 'https://solana.kyberengineering.io', diff --git a/src/constants/networks/type.ts b/src/constants/networks/type.ts index 5c40c045b6..31dcc6a44a 100644 --- a/src/constants/networks/type.ts +++ b/src/constants/networks/type.ts @@ -1,6 +1,8 @@ import { ChainId } from '@kyberswap/ks-sdk-core' import { PublicKey } from '@solana/web3.js' +import { EnvKeys } from 'constants/env' + export interface NetworkInfo { readonly chainId: ChainId @@ -27,10 +29,7 @@ export interface NetworkInfo { readonly coingeckoNetworkId: string | null //https://api.coingecko.com/api/v3/asset_platforms readonly coingeckoNativeTokenId: string | null //https://api.coingecko.com/api/v3/coins/list readonly dexToCompare: string | null - readonly limitOrder: { - development: string | null - production: string | null - } + readonly limitOrder: null | '*' | EnvKeys[] readonly defaultRpcUrl: string // token: { // DAI: Token @@ -79,10 +78,7 @@ export interface EVMNetworkInfo extends NetworkInfo { readonly farmv2Quoter?: string readonly farmV2S?: string[] } - readonly limitOrder: { - development: string | null - production: string | null - } + readonly limitOrder: null | '*' | EnvKeys[] readonly averageBlockTimeInSeconds: number readonly deBankSlug: string readonly kyberDAO?: { diff --git a/src/constants/networks/velas.ts b/src/constants/networks/velas.ts index 267483b7cb..f829e0e899 100644 --- a/src/constants/networks/velas.ts +++ b/src/constants/networks/velas.ts @@ -60,10 +60,7 @@ const velasInfo: EVMNetworkInfo = { routers: '0xF9c2b5746c946EF883ab2660BbbB1f10A5bdeAb4', farms: [], }, - limitOrder: { - development: NOT_SUPPORT, - production: NOT_SUPPORT, - }, + limitOrder: NOT_SUPPORT, averageBlockTimeInSeconds: 0.4, coingeckoNetworkId: 'velas', coingeckoNativeTokenId: 'velas', diff --git a/src/constants/networks/zkevm.ts b/src/constants/networks/zkevm.ts index 806b74b0a4..8cc1f5b39c 100644 --- a/src/constants/networks/zkevm.ts +++ b/src/constants/networks/zkevm.ts @@ -61,10 +61,7 @@ const zkEvm: EVMNetworkInfo = { farmv2Quoter: '0x6AFeb9EDd6Cf44fA8E89b1eee28284e6dD7705C8', farmV2S: ['0x3D6AfE2fB73fFEd2E3dD00c501A174554e147a43', '0xf2BcDf38baA52F6b0C1Db5B025DfFf01Ae1d6dBd'], }, - limitOrder: { - production: NOT_SUPPORT, - development: NOT_SUPPORT, - }, + limitOrder: NOT_SUPPORT, averageBlockTimeInSeconds: 2, // TODO: check these info coingeckoNetworkId: 'polygon-zkevm', coingeckoNativeTokenId: 'ethereum', diff --git a/src/constants/networks/zksync.ts b/src/constants/networks/zksync.ts index 1a6f63a450..2f7e75612d 100644 --- a/src/constants/networks/zksync.ts +++ b/src/constants/networks/zksync.ts @@ -58,10 +58,7 @@ const zkSyncInfo: EVMNetworkInfo = { routers: '', farms: [], }, - limitOrder: { - development: NOT_SUPPORT, - production: NOT_SUPPORT, - }, + limitOrder: NOT_SUPPORT, averageBlockTimeInSeconds: 15, coingeckoNetworkId: 'zksync-ethereum', coingeckoNativeTokenId: 'ethereum', diff --git a/src/hooks/useShowLoadingAtLeastTime.ts b/src/hooks/useShowLoadingAtLeastTime.ts new file mode 100644 index 0000000000..1b6273a76a --- /dev/null +++ b/src/hooks/useShowLoadingAtLeastTime.ts @@ -0,0 +1,26 @@ +import { useEffect, useState } from 'react' + +/** + * This hook to ensure loading is displayed at least "duration" mili seconds + * @param loadingState: (ex: isFetch from rtk query) + * @param duration + * @returns + */ +export default function useShowLoadingAtLeastTime(loadingState = false, duration = 500) { + const [shouldShowLoading, setShouldShowLoading] = useState(true) + + useEffect(() => { + if (loadingState) setShouldShowLoading(true) + }, [loadingState]) + + useEffect(() => { + const existingTimeout = setTimeout(() => { + setShouldShowLoading(false) + }, duration) + return () => { + existingTimeout && clearTimeout(existingTimeout) + } + }, [duration, shouldShowLoading]) + + return shouldShowLoading || loadingState +} diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 4440f840cc..0d636745c7 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -38,10 +38,9 @@ import TruesightFooter from 'pages/TrueSightV2/components/TruesightFooter' import KyberAILandingPage from 'pages/TrueSightV2/pages/LandingPage' import { useHolidayMode } from 'state/user/hooks' import DarkModeQueryParamReader from 'theme/DarkModeQueryParamReader' -import { getLimitOrderContract, isAddressString, shortenAddress } from 'utils' +import { isAddressString, isSupportLimitOrder, shortenAddress } from 'utils' import ElasticLegacyNotice from './ElasticLegacy/ElasticLegacyNotice' -import Icons from './Icons' import VerifyAuth from './Verify/VerifyAuth' // test page for swap only through elastic @@ -76,6 +75,7 @@ const BuyCrypto = lazy(() => import('pages/BuyCrypto')) const Campaign = lazy(() => import('pages/Campaign')) const GrantProgramPage = lazy(() => import('pages/GrantProgram')) const NotificationCenter = lazy(() => import('pages/NotificationCenter')) +const Icons = lazy(() => import('./Icons')) const AppWrapper = styled.div` display: flex; @@ -321,7 +321,7 @@ export default function App() { } /> )} - {getLimitOrderContract(chainId) && ( + {isSupportLimitOrder(chainId) && ( } /> )} diff --git a/src/pages/Bridge/BridgeTransferHistory/index.tsx b/src/pages/Bridge/BridgeTransferHistory/index.tsx index dc89698b24..038e75ec15 100644 --- a/src/pages/Bridge/BridgeTransferHistory/index.tsx +++ b/src/pages/Bridge/BridgeTransferHistory/index.tsx @@ -1,9 +1,9 @@ import { rgba } from 'polished' -import { useEffect, useState } from 'react' import { Flex } from 'rebass' import styled from 'styled-components' import { useActiveWeb3React } from 'hooks' +import useShowLoadingAtLeastTime from 'hooks/useShowLoadingAtLeastTime' import NoData from 'pages/Bridge/BridgeTransferHistory/NoData' import Pagination from 'pages/Bridge/BridgeTransferHistory/Pagination' import TransferHistoryTable from 'pages/Bridge/BridgeTransferHistory/TransferHistoryTable' @@ -15,19 +15,9 @@ type Props = { } const TransferHistory: React.FC = ({ className }) => { const { account } = useActiveWeb3React() - const [shouldShowLoading, setShouldShowLoading] = useState(true) const response = useTransferHistory(account || '') const { isCompletelyEmpty, transfers } = response - - useEffect(() => { - // This is to ensure loading is displayed at least 0.5s - const existingTimeout = setTimeout(() => { - setShouldShowLoading(false) - }, 500) - return () => { - existingTimeout && clearTimeout(existingTimeout) - } - }, []) + const shouldShowLoading = useShowLoadingAtLeastTime() if (shouldShowLoading || isCompletelyEmpty) { return diff --git a/src/pages/CrossChain/TransfersHistory/History.tsx b/src/pages/CrossChain/TransfersHistory/History.tsx index e4e191adf8..ae7fae0bf1 100644 --- a/src/pages/CrossChain/TransfersHistory/History.tsx +++ b/src/pages/CrossChain/TransfersHistory/History.tsx @@ -1,10 +1,10 @@ import { t } from '@lingui/macro' import { rgba } from 'polished' -import { useEffect, useState } from 'react' import { Flex } from 'rebass' import styled from 'styled-components' import { useActiveWeb3React } from 'hooks' +import useShowLoadingAtLeastTime from 'hooks/useShowLoadingAtLeastTime' import NoData from 'pages/Bridge/BridgeTransferHistory/NoData' import Pagination from 'pages/Bridge/BridgeTransferHistory/Pagination' @@ -16,19 +16,9 @@ type Props = { } const TransferHistory: React.FC = ({ className }) => { const { account } = useActiveWeb3React() - const [shouldShowLoading, setShouldShowLoading] = useState(true) const response = useTransferHistory(account || '') const { isCompletelyEmpty, transfers } = response - - useEffect(() => { - // This is to ensure loading is displayed at least 0.5s - const existingTimeout = setTimeout(() => { - setShouldShowLoading(false) - }, 500) - return () => { - existingTimeout && clearTimeout(existingTimeout) - } - }, []) + const shouldShowLoading = useShowLoadingAtLeastTime() if (shouldShowLoading || isCompletelyEmpty) { return ( diff --git a/src/pages/SwapV3/Tabs/LimitTab.tsx b/src/pages/SwapV3/Tabs/LimitTab.tsx index 85e35dafe7..f2b1c1b2b5 100644 --- a/src/pages/SwapV3/Tabs/LimitTab.tsx +++ b/src/pages/SwapV3/Tabs/LimitTab.tsx @@ -1,16 +1,14 @@ import { Trans } from '@lingui/macro' import { rgba } from 'polished' -import { useEffect, useState } from 'react' import { useLocation } from 'react-router-dom' -import { useMountedState } from 'react-use' import { Text } from 'rebass' +import { useGetNumberOfInsufficientFundOrdersQuery } from 'services/limitOrder' import styled from 'styled-components' import { MouseoverTooltip } from 'components/Tooltip' -import { getNumberOfInsufficientFundOrders } from 'components/swapv2/LimitOrder/request' import { APP_PATHS } from 'constants/index' import { useActiveWeb3React } from 'hooks' -import { getLimitOrderContract } from 'utils' +import { isSupportLimitOrder } from 'utils' import { Tab } from './index' @@ -31,40 +29,18 @@ type Props = { export default function LimitTab({ onClick }: Props) { const { chainId, account } = useActiveWeb3React() const { pathname } = useLocation() - const [numberOfInsufficientFundOrders, setNumberOfInsufficientFundOrders] = useState(0) const isLimitPage = pathname.startsWith(APP_PATHS.LIMIT) - const isSupportLimitOrder = getLimitOrderContract(chainId) + const isSupport = isSupportLimitOrder(chainId) - const getMountedState = useMountedState() - - useEffect(() => { - if (!isSupportLimitOrder || !account) { - return - } - - const run = async () => { - try { - const num = await getNumberOfInsufficientFundOrders({ - chainId, - maker: account || '', - }) - - getMountedState() && setNumberOfInsufficientFundOrders(num) - } catch (e) { - console.error(e) - } - } - - run() - const interval = setInterval(run, 10_000) - - return () => { - clearInterval(interval) - } - }, [account, chainId, isSupportLimitOrder, getMountedState]) + const skip = !account || !isSupport + const { data } = useGetNumberOfInsufficientFundOrdersQuery( + { chainId, maker: account || '' }, + { skip, pollingInterval: 10_000 }, + ) + const numberOfInsufficientFundOrders = skip ? undefined : data - if (!isSupportLimitOrder) { + if (!isSupport) { return null } diff --git a/src/pages/SwapV3/Tabs/index.tsx b/src/pages/SwapV3/Tabs/index.tsx index 02fc97ef01..dd114da309 100644 --- a/src/pages/SwapV3/Tabs/index.tsx +++ b/src/pages/SwapV3/Tabs/index.tsx @@ -10,7 +10,7 @@ import { useActiveWeb3React } from 'hooks' import useParsedQueryString from 'hooks/useParsedQueryString' import { TAB } from 'pages/SwapV3' import LimitTab from 'pages/SwapV3/Tabs/LimitTab' -import { getLimitOrderContract } from 'utils' +import { isSupportLimitOrder } from 'utils' const TabContainer = styled.div` display: flex; @@ -102,7 +102,7 @@ export default function Tabs({ activeTab }: Props) { Swap - {getLimitOrderContract(chainId) && onClickTab(TAB.LIMIT)} />} + {isSupportLimitOrder(chainId) && onClickTab(TAB.LIMIT)} />} {CHAINS_SUPPORT_CROSS_CHAIN.includes(chainId) && ( onClickTab(TAB.CROSS_CHAIN)} isActive={isCrossChainPage}> diff --git a/src/pages/SwapV3/redirects.tsx b/src/pages/SwapV3/redirects.tsx index 5bf3940a14..6ea82086e8 100644 --- a/src/pages/SwapV3/redirects.tsx +++ b/src/pages/SwapV3/redirects.tsx @@ -2,7 +2,7 @@ import { Navigate, useLocation } from 'react-router-dom' import { APP_PATHS } from 'constants/index' import { useActiveWeb3React } from 'hooks' -import { getLimitOrderContract } from 'utils' +import { isSupportLimitOrder } from 'utils' // Redirects to swap-v3 but only replace the pathname export function RedirectPathToSwapV3Network() { @@ -12,7 +12,7 @@ export function RedirectPathToSwapV3Network() { let redirectTo = '' - if (pathname.startsWith(APP_PATHS.LIMIT) && getLimitOrderContract(chainId)) { + if (pathname.startsWith(APP_PATHS.LIMIT) && isSupportLimitOrder(chainId)) { redirectTo = APP_PATHS.LIMIT } else { redirectTo = APP_PATHS.SWAP diff --git a/src/pages/TrueSightV2/pages/TechnicalAnalysis.tsx b/src/pages/TrueSightV2/pages/TechnicalAnalysis.tsx index 3a183d1756..2393bf5cf1 100644 --- a/src/pages/TrueSightV2/pages/TechnicalAnalysis.tsx +++ b/src/pages/TrueSightV2/pages/TechnicalAnalysis.tsx @@ -11,7 +11,7 @@ import Row, { RowFit } from 'components/Row' import Toggle from 'components/Toggle' import { IChartingLibraryWidget } from 'components/TradingViewChart/charting_library/charting_library' import { useTokenAnalysisSettings } from 'state/user/hooks' -import { getLimitOrderContract } from 'utils' +import { isSupportLimitOrder } from 'utils' import { SectionWrapper } from '../components' import CexRekt from '../components/CexRekt' @@ -228,7 +228,7 @@ export default function TechnicalAnalysis() { ]} > - {chain && getLimitOrderContract(NETWORK_TO_CHAINID[chain]) && ( + {chain && isSupportLimitOrder(NETWORK_TO_CHAINID[chain]) && ( navigateToLimitPage({ address, chain })}> diff --git a/src/services/limitOrder.ts b/src/services/limitOrder.ts new file mode 100644 index 0000000000..665ec6dc5a --- /dev/null +++ b/src/services/limitOrder.ts @@ -0,0 +1,131 @@ +import { ChainId } from '@kyberswap/ks-sdk-core' +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' + +import { LimitOrder, LimitOrderStatus } from 'components/swapv2/LimitOrder/type' +import { LIMIT_ORDER_API_READ, LIMIT_ORDER_API_WRITE } from 'constants/env' + +const mapPath: Partial> = { + [LimitOrderStatus.CANCELLED]: 'cancelled', + [LimitOrderStatus.EXPIRED]: 'expired', + [LimitOrderStatus.FILLED]: 'filled', +} + +const limitOrderApi = createApi({ + reducerPath: 'limitOrderApi', + baseQuery: fetchBaseQuery({ baseUrl: '' }), + endpoints: builder => ({ + getLOContractAddress: builder.query({ + query: chainId => ({ + url: `${LIMIT_ORDER_API_READ}/v1/configs/contract-address`, + params: { chainId }, + }), + transformResponse: (data: any) => data?.data?.latest?.toLowerCase?.() ?? '', + }), + getListOrders: builder.query< + { orders: LimitOrder[]; totalOrder: number }, + { + chainId: ChainId + maker: string | undefined + status: string + query: string + page: number + pageSize: number + } + >({ + query: params => ({ + url: `${LIMIT_ORDER_API_READ}/v1/orders`, + params, + }), + transformResponse: ({ data }: any) => { + data.orders.forEach((order: any) => { + order.chainId = Number(order.chainId) as ChainId + }) + return { orders: data?.orders || [], totalOrder: data?.pagination?.totalItems || 0 } + }, + }), + getNumberOfInsufficientFundOrders: builder.query({ + query: params => ({ + url: `${LIMIT_ORDER_API_READ}/v1/orders/insufficient-funds`, + params, + }), + transformResponse: (data: any) => data?.data?.total || 0, + }), + insertCancellingOrder: builder.mutation< + any, + { + orderIds?: number[] + nonce?: number + maker: string + chainId: string + txHash: string + contractAddress: string + } + >({ + query: body => ({ + url: `${LIMIT_ORDER_API_WRITE}/v1/orders/cancelling`, + body, + method: 'POST', + }), + }), + createOrder: builder.mutation<{ id: number }, any>({ + query: body => ({ + url: `${LIMIT_ORDER_API_WRITE}/v1/orders`, + body, + method: 'POST', + }), + transformResponse: (data: any) => data?.data, + }), + createOrderSignature: builder.mutation({ + query: body => ({ + url: `${LIMIT_ORDER_API_WRITE}/v1/orders/sign-message`, + body, + method: 'POST', + }), + transformResponse: (data: any) => data?.data, + }), + getEncodeData: builder.mutation({ + query: ({ orderIds, isCancelAll = false }) => ({ + url: `${LIMIT_ORDER_API_READ}/v1/encode/${isCancelAll ? 'increase-nonce' : 'cancel-batch-orders'}`, + body: isCancelAll ? {} : { orderIds }, + method: 'POST', + }), + transformResponse: (data: any) => data?.data, + }), + ackNotificationOrder: builder.mutation< + any, + { docIds: string[]; maker: string; chainId: ChainId; type: LimitOrderStatus } + >({ + query: ({ maker, chainId, type, docIds }) => ({ + url: `${LIMIT_ORDER_API_WRITE}/v1/events/${mapPath[type]}`, + body: { maker, chainId: chainId + '', [type === LimitOrderStatus.FILLED ? 'uuids' : 'docIds']: docIds }, + method: 'DELETE', + }), + transformResponse: (data: any) => data?.data, + }), + getTotalActiveMakingAmount: builder.query({ + query: ({ chainId, tokenAddress, account }) => ({ + url: `${LIMIT_ORDER_API_READ}/v1/orders/active-making-amount`, + params: { + chainId: chainId + '', + makerAsset: tokenAddress, + maker: account, + }, + }), + transformResponse: (data: any) => data?.data?.activeMakingAmount, + }), + }), +}) + +export const { + useGetLOContractAddressQuery, + useGetListOrdersQuery, + useInsertCancellingOrderMutation, + useGetNumberOfInsufficientFundOrdersQuery, + useCreateOrderMutation, + useCreateOrderSignatureMutation, + useGetEncodeDataMutation, + useGetTotalActiveMakingAmountQuery, + useAckNotificationOrderMutation, +} = limitOrderApi + +export default limitOrderApi diff --git a/src/state/index.ts b/src/state/index.ts index 7b55412f68..2d505c263e 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -18,6 +18,7 @@ import geckoTerminalApi from '../services/geckoTermial' import identifyApi from '../services/identity' import ksSettingApi from '../services/ksSetting' import kyberDAO from '../services/kyberDAO' +import limitOrderApi from '../services/limitOrder' import socialApi from '../services/social' import application from './application/reducer' import authen from './authen/reducer' @@ -93,6 +94,7 @@ const store = configureStore({ [publicAnnouncementApi.reducerPath]: publicAnnouncementApi.reducer, [geckoTerminalApi.reducerPath]: geckoTerminalApi.reducer, [coingeckoApi.reducerPath]: coingeckoApi.reducer, + [limitOrderApi.reducerPath]: limitOrderApi.reducer, [kyberAIApi.reducerPath]: kyberAIApi.reducer, [kyberAISubscriptionApi.reducerPath]: kyberAISubscriptionApi.reducer, @@ -122,6 +124,7 @@ const store = configureStore({ .concat(save({ states: PERSISTED_KEYS, debounce: 100 })) .concat(geckoTerminalApi.middleware) .concat(coingeckoApi.middleware) + .concat(limitOrderApi.middleware) .concat(kyberAIApi.middleware) .concat(kyberAISubscriptionApi.middleware) .concat(announcementApi.middleware) diff --git a/src/utils/firebase.ts b/src/utils/firebase.ts index 836cc74f87..c2ccc42827 100644 --- a/src/utils/firebase.ts +++ b/src/utils/firebase.ts @@ -19,7 +19,7 @@ const dbLimitOrder = getFirestore(firebaseAppLimitOrder) const COLLECTIONS = { LO_CANCELLING_ORDERS: 'cancellingOrders', - LO_CANCELLED_ORDERS: 'cancelledEvents', + LO_CANCELLED_ORDERS: 'cancelledEventsByContract', LO_EXPIRED_ORDERS: 'expiredEvents', LO_FILLED_ORDERS: 'filledEvents', @@ -82,12 +82,13 @@ function subscribeListLimitOrder( all: [], } data.forEach((e: any) => { - if (e.id.startsWith('nonce')) { + if (e.id.includes('nonce')) { result.all.push(e as AllItem) } else { result.orders.push({ ...e, id: Number(e.id) } as LimitOrder) } }) + console.log(result) callback(result) }, ) @@ -95,10 +96,11 @@ function subscribeListLimitOrder( return unsubscribe } +export type OrderNonces = { [key: string]: number[] } export function subscribeCancellingOrders( account: string, chainId: ChainId, - callback: (data: { orderIds: number[]; nonces: number[] }) => void, + callback: (data: { orderIds: number[]; noncesByContract: OrderNonces }) => void, ) { return subscribeDocument( dbLimitOrder, diff --git a/src/utils/index.ts b/src/utils/index.ts index 5893723007..dc0a7a8e42 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -471,10 +471,10 @@ export const isChristmasTime = () => { return currentTime.month() === 11 && currentTime.date() >= 15 } -export const getLimitOrderContract = (chainId: ChainId): string | null => { - if (!SUPPORTED_NETWORKS.includes(chainId)) return null - const { production, development } = NETWORKS_INFO[chainId]?.limitOrder ?? {} - return ENV_KEY === 'production' || ENV_KEY === 'staging' ? production : development +export const isSupportLimitOrder = (chainId: ChainId): boolean => { + if (!SUPPORTED_NETWORKS.includes(chainId)) return false + const limitOrder = NETWORKS_INFO[chainId]?.limitOrder + return limitOrder === '*' || (limitOrder || []).includes(ENV_KEY) } export function openFullscreen(elem: any) {