diff --git a/src/pages/TrueSightV2/components/TutorialModal.tsx b/src/pages/TrueSightV2/components/TutorialModal.tsx
deleted file mode 100644
index 1e517119bb..0000000000
--- a/src/pages/TrueSightV2/components/TutorialModal.tsx
+++ /dev/null
@@ -1,451 +0,0 @@
-import { Trans } from '@lingui/macro'
-import React, { useEffect, useLayoutEffect, useReducer } from 'react'
-import { X } from 'react-feather'
-import { useMedia } from 'react-use'
-import { Text } from 'rebass'
-import styled, { keyframes } from 'styled-components'
-
-import tutorial1 from 'assets/images/truesight-v2/tutorial_1.png'
-import tutorial2 from 'assets/images/truesight-v2/tutorial_2.png'
-import tutorial3 from 'assets/images/truesight-v2/tutorial_3.png'
-import tutorial4 from 'assets/images/truesight-v2/tutorial_4.png'
-import tutorial6 from 'assets/images/truesight-v2/tutorial_6.png'
-import { ButtonOutlined, ButtonPrimary } from 'components/Button'
-import ApeIcon from 'components/Icons/ApeIcon'
-import Modal from 'components/Modal'
-import Row, { RowBetween } from 'components/Row'
-import useTheme from 'hooks/useTheme'
-import { ApplicationModal } from 'state/application/actions'
-import { useModalOpen, useToggleModal } from 'state/application/hooks'
-import { MEDIA_WIDTHS } from 'theme'
-
-const Wrapper = styled.div`
- border-radius: 20px;
- background-color: ${({ theme }) => theme.tableHeader};
- padding: 20px;
- display: flex;
- flex-direction: column;
- gap: 16px;
- width: min(95vw, 808px);
-
- ${({ theme }) => theme.mediaWidth.upToSmall`
- min-height: 70vh;
- `}
-`
-const fadeInScale = keyframes`
- 0% { opacity: 0; transform:scale(0.7) }
- 100% { opacity: 1; transform:scale(1)}
-`
-const fadeInLeft = keyframes`
- 0% { opacity: 0.5; transform:translateX(calc(-100% - 40px)) }
- 100% { opacity: 1; transform:translateX(0)}
-`
-const fadeOutRight = keyframes`
- 0% { opacity: 1; transform:translateX(0);}
- 100% { opacity: 0.5; transform:translateX(calc(100% + 40px)); visibility:hidden; }
-`
-const fadeInRight = keyframes`
- 0% { opacity: 0.5; transform:translateX(calc(100% + 40px)) }
- 100% { opacity: 1; transform:translateX(0)}
-`
-const fadeOutLeft = keyframes`
- 0% { opacity: 1; transform:translateX(0);}
- 100% { opacity: 0.5; transform:translateX(calc(-100% - 40px)); visibility:hidden; }
-`
-
-const StepWrapper = styled.div`
- padding: 0;
- margin: 0;
- box-sizing: content-box;
- display: flex;
- flex-direction: column;
- gap: 20px;
- width: 100%;
- height: fit-content;
- &.fadeInScale {
- animation: ${fadeInScale} 0.3s ease;
- }
- &.fadeOutRight {
- animation: ${fadeOutRight} 0.5s ease;
- }
- &.fadeOutLeft {
- animation: ${fadeOutLeft} 0.5s ease;
- }
- &.fadeInRight {
- animation: ${fadeInRight} 0.5s ease;
- }
- &.fadeInLeft {
- animation: ${fadeInLeft} 0.5s ease;
- }
- img {
- object-fit: contain;
- }
- b {
- font-weight: 500;
- color: ${({ theme }) => theme.text};
- }
- p {
- margin-bottom: 16px;
- }
-
- ${({ theme }) => theme.mediaWidth.upToSmall`
- p {
- margin-bottom: 0px;
- }
- `}
-`
-
-const StepDot = styled.div<{ active?: boolean }>`
- height: 8px;
- width: 8px;
- border-radius: 50%;
- background-color: ${({ theme, active }) => (active ? theme.primary : theme.subText)};
-`
-
-const steps = [
- {
- image: tutorial2,
- text: (
-
-
- Whether you're looking to identify new tokens to trade, or get alpha on a specific token, KyberAI has it
- all! KyberAI currently provides trading insights on 4000+ tokens across 7 blockchains!
-
{' '}
-
- For traders who are in discovery mode, start with the Rankings section. Here you will see top tokens under
- each of the 7 categories -{' '}
- Bullish, Bearish, Top CEX Inflow, Top CEX Outflow, Top Traded, Trending Soon, Currently Trending. We
- update the token rankings multiple times a day!
-
{' '}
-
- For traders looking to spot alpha on specific tokens, start with the Explore section. You will find a number
- of On-Chain and Technical insights on your token that you can look at to make an informed trading decision.
-
-
- ),
- },
- {
- image: tutorial3,
- text: (
-
-
- A unique trading insight offered by KyberAI is the KyberScore. KyberScore uses AI to measure the
- upcoming trend (bullish or bearish) of a token by taking into account multiple on-chain and off-chain
- indicators. The score ranges from 0 to 100. Higher the score, more bullish the token in the short-term.
-
{' '}
-
- Each token supported by KyberAI is assigned a KyberScore. It refreshes multiple times a day as we collect more
- data on the token. You can find the KyberScore of a token in the Rankings or Explore section.
- Read more about the calculation here.
-
{' '}
-
- Note: KyberScore should not be considered as financial advice
-
-
- ),
- },
- {
- image: tutorial4,
- text: (
-
-
- For traders, analyzing & interpreting on-chain data can be very powerful. It helps us see what whales, smart
- money and other traders are up to. And so, KyberAI has cherry picked the best on-chain indicators to help
- traders like you spot alpha on your tokens. Check out the On-Chain Analysis tab of the Explore{' '}
- section!
-
-
- The best traders combine on-chain analysis with technical analysis (TA). TA is used to identify trading
- opportunities by evaluating price charts, price trends, patterns etc. KyberAI makes TA easy for traders. Check
- out the Technical Analysis tab of the Explore section!
-
-
- ),
- },
- {
- image: tutorial6,
- text: (
-
- That's not all! Here are a few handy tips so you can get the most out of KyberAI:
{' '}
-
- -
- Use the search bar to search for any token you'd like to explore. KyberAI supports 4000+ tokens!
-
- -
- Subscribe to receive daily emails on the top tokens as recommended by KyberAI!
-
- -
- Monitoring the price of a token? Set a price alert, sit back, and we'll notify you!
-
- -
- Create a watchlist of your favorite tokens, and access it quickly!
-
-
{' '}
- If you wish to view this guide again, you can enable it from the settings.
-
- Ape Smart with KyberAI.
-
-
- ),
- },
-]
-
-enum AnimationState {
- Idle,
- Animating,
- Animated,
-}
-enum SwipeDirection {
- LEFT,
- RIGHT,
-}
-
-type TutorialAnimationState = {
- step: number
- animationState: AnimationState
- swipe: SwipeDirection
-}
-const initialState = {
- step: 0,
- animationState: AnimationState.Idle,
- swipe: SwipeDirection.LEFT,
-}
-enum ActionTypes {
- INITIAL = 'INITIAL',
- START = 'START',
- NEXT_STEP = 'NEXT_STEP',
- PREV_STEP = 'PREV_STEP',
- ANIMATION_END = 'ANIMATION_END',
-}
-function reducer(state: TutorialAnimationState, action: ActionTypes) {
- switch (action) {
- case ActionTypes.INITIAL:
- return {
- ...initialState,
- }
- case ActionTypes.START:
- return {
- step: 1,
- animationState: AnimationState.Idle,
- swipe: SwipeDirection.LEFT,
- }
- case ActionTypes.NEXT_STEP:
- if (state.step < steps.length && state.animationState !== AnimationState.Animating) {
- return {
- step: state.step + 1,
- animationState: AnimationState.Animating,
- swipe: SwipeDirection.LEFT,
- }
- }
- break
- case ActionTypes.PREV_STEP:
- if (state.animationState !== AnimationState.Animating) {
- return {
- step: state.step - 1,
- animationState: AnimationState.Animating,
- swipe: SwipeDirection.RIGHT,
- }
- }
- break
- case ActionTypes.ANIMATION_END:
- return { ...state, animationState: AnimationState.Idle }
-
- default:
- throw new Error()
- }
- return state
-}
-
-const StepContent = ({ step, ...rest }: { step: number; [k: string]: any }) => {
- const theme = useTheme()
- const { image, text } = steps[step - 1]
- const above768 = useMedia(`(min-width: ${MEDIA_WIDTHS.upToSmall}px)`)
- return (
-
-
-
- {text}
-
-
- )
-}
-
-const TutorialModal = () => {
- const theme = useTheme()
- const isOpen = useModalOpen(ApplicationModal.KYBERAI_TUTORIAL)
- const toggle = useToggleModal(ApplicationModal.KYBERAI_TUTORIAL)
- const [{ step, animationState, swipe }, dispatch] = useReducer(reducer, initialState)
- const lastStep =
- animationState === AnimationState.Animating ? (swipe === SwipeDirection.LEFT ? step - 1 : step + 1) : undefined
-
- const above768 = useMedia(`(min-width: ${MEDIA_WIDTHS.upToSmall}px)`)
-
- useEffect(() => {
- if (!localStorage.getItem('showedKyberAITutorial')) {
- // auto show for first time all user
- toggle()
- localStorage.setItem('showedKyberAITutorial', '1')
- }
- }, [toggle])
-
- useLayoutEffect(() => {
- if (isOpen) {
- dispatch(ActionTypes.INITIAL)
- }
- }, [isOpen])
-
- return (
-
-
-
-
-
- Welcome to
- KyberAI
-
-
- beta
-
-
-
-
-
-
- {step === 0 && (
- <>
-
-
-
-
- We're thrilled to have you onboard and can't wait for you to start exploring the world of
- trading powered by KyberAI. We've created this short
- tutorial for you to highlight KyberAI's main features. Ready?
-
-
-
-
-
-
- Maybe later
-
-
- {
- dispatch(ActionTypes.START)
- }}
- >
-
- Let's get started
-
-
-
- >
- )}
- {step > 0 && (
- <>
-
- {animationState === AnimationState.Animating && (
- <>
-
- dispatch(ActionTypes.ANIMATION_END)}
- style={{ position: 'absolute', top: 0, left: 0, backgroundColor: theme.tableHeader }}
- />
- >
- )}
-
-
-
- {steps.map((a, index) => (
-
- ))}
-
-
- dispatch(ActionTypes.PREV_STEP)}>
-
- Back
-
-
- {
- if (step < steps.length) {
- dispatch(ActionTypes.NEXT_STEP)
- } else {
- toggle()
- }
- }}
- >
-
- {step === steps.length ? Let's go! : Next}
-
-
-
- >
- )}
-
-
- )
-}
-
-export default React.memo(TutorialModal)
diff --git a/src/pages/TrueSightV2/components/TutorialModalKyberAI.tsx b/src/pages/TrueSightV2/components/TutorialModalKyberAI.tsx
new file mode 100644
index 0000000000..8f066f18be
--- /dev/null
+++ b/src/pages/TrueSightV2/components/TutorialModalKyberAI.tsx
@@ -0,0 +1,166 @@
+import { Trans } from '@lingui/macro'
+import { useEffect } from 'react'
+import { isMobile } from 'react-device-detect'
+import { CSSProperties } from 'styled-components'
+
+import tutorial1 from 'assets/images/truesight-v2/tutorial_1.png'
+import tutorial2 from 'assets/images/truesight-v2/tutorial_2.png'
+import tutorial3 from 'assets/images/truesight-v2/tutorial_3.png'
+import tutorial4 from 'assets/images/truesight-v2/tutorial_4.png'
+import tutorial6 from 'assets/images/truesight-v2/tutorial_6.png'
+import ApeIcon from 'components/Icons/ApeIcon'
+import TutorialModal from 'components/TutorialModal'
+import useTheme from 'hooks/useTheme'
+import { ApplicationModal } from 'state/application/actions'
+import { useModalOpen, useToggleModal } from 'state/application/hooks'
+
+const Step1 = () => {
+ const theme = useTheme()
+ return (
+
+ We're thrilled to have you onboard and can't wait for you to start exploring the world of trading
+ powered by KyberAI. We've created this short tutorial for you to
+ highlight KyberAI's main features. Ready?
+
+ )
+}
+
+const textStyle: CSSProperties = {
+ height: isMobile ? '35vh' : '202px',
+}
+
+const steps = [
+ { image: tutorial1, text: , textStyle: { ...textStyle, height: 'auto' } },
+ {
+ image: tutorial2,
+ textStyle,
+ text: (
+
+
+ Whether you're looking to identify new tokens to trade, or get alpha on a specific token, KyberAI has it
+ all! KyberAI currently provides trading insights on 4000+ tokens across 7 blockchains!
+
{' '}
+
+ For traders who are in discovery mode, start with the Rankings section. Here you will see top tokens under
+ each of the 7 categories -{' '}
+ Bullish, Bearish, Top CEX Inflow, Top CEX Outflow, Top Traded, Trending Soon, Currently Trending. We
+ update the token rankings multiple times a day!
+
{' '}
+
+ For traders looking to spot alpha on specific tokens, start with the Explore section. You will find a number
+ of On-Chain and Technical insights on your token that you can look at to make an informed trading decision.
+
+
+ ),
+ },
+ {
+ image: tutorial3,
+ textStyle,
+ text: (
+
+
+ A unique trading insight offered by KyberAI is the KyberScore. KyberScore uses AI to measure the
+ upcoming trend (bullish or bearish) of a token by taking into account multiple on-chain and off-chain
+ indicators. The score ranges from 0 to 100. Higher the score, more bullish the token in the short-term.
+
{' '}
+
+ Each token supported by KyberAI is assigned a KyberScore. It refreshes multiple times a day as we collect more
+ data on the token. You can find the KyberScore of a token in the Rankings or Explore section.
+ Read more about the calculation here.
+
{' '}
+
+ Note: KyberScore should not be considered as financial advice
+
+
+ ),
+ },
+ {
+ image: tutorial4,
+ textStyle,
+ text: (
+
+
+ For traders, analyzing & interpreting on-chain data can be very powerful. It helps us see what whales, smart
+ money and other traders are up to. And so, KyberAI has cherry picked the best on-chain indicators to help
+ traders like you spot alpha on your tokens. Check out the On-Chain Analysis tab of the Explore{' '}
+ section!
+
+
+ The best traders combine on-chain analysis with technical analysis (TA). TA is used to identify trading
+ opportunities by evaluating price charts, price trends, patterns etc. KyberAI makes TA easy for traders. Check
+ out the Technical Analysis tab of the Explore section!
+
+
+ ),
+ },
+ {
+ image: tutorial6,
+ textStyle,
+ text: (
+
+ That's not all! Here are a few handy tips so you can get the most out of KyberAI:
{' '}
+
+ -
+ Use the search bar to search for any token you'd like to explore. KyberAI supports 4000+ tokens!
+
+ -
+ Subscribe to receive daily emails on the top tokens as recommended by KyberAI!
+
+ -
+ Monitoring the price of a token? Set a price alert, sit back, and we'll notify you!
+
+ -
+ Create a watchlist of your favorite tokens, and access it quickly!
+
+
{' '}
+ If you wish to view this guide again, you can enable it from the settings.
+
+ Ape Smart with KyberAI.
+
+
+ ),
+ },
+]
+
+const TutorialModalKyberAI = () => {
+ const isOpen = useModalOpen(ApplicationModal.KYBERAI_TUTORIAL)
+ const toggle = useToggleModal(ApplicationModal.KYBERAI_TUTORIAL)
+ const theme = useTheme()
+
+ useEffect(() => {
+ if (!localStorage.getItem('showedKyberAITutorial')) {
+ // auto show for first time all user
+ toggle()
+ localStorage.setItem('showedKyberAITutorial', '1')
+ }
+ }, [toggle])
+
+ return (
+
+
+ Welcome to
+ KyberAI
+
+
+ beta
+
+ >
+ }
+ />
+ )
+}
+export default TutorialModalKyberAI
diff --git a/src/pages/TrueSightV2/components/WatchlistButton.tsx b/src/pages/TrueSightV2/components/WatchlistButton.tsx
index 2c5f419ce2..c32550d744 100644
--- a/src/pages/TrueSightV2/components/WatchlistButton.tsx
+++ b/src/pages/TrueSightV2/components/WatchlistButton.tsx
@@ -505,7 +505,7 @@ function WatchlistButton({
{
;(isWatched || !isReachMaxLimit) && setOpenMenu(true)
}}
@@ -531,7 +531,7 @@ function WatchlistButton({
onSelect(watchlists, watched)
}}
>
-
+
{watchlists.name} ({watchlists.assetNumber})
)
diff --git a/src/pages/TrueSightV2/components/WatchlistStar.tsx b/src/pages/TrueSightV2/components/WatchlistStar.tsx
index 0e04c14cbf..1c55d4c0db 100644
--- a/src/pages/TrueSightV2/components/WatchlistStar.tsx
+++ b/src/pages/TrueSightV2/components/WatchlistStar.tsx
@@ -4,14 +4,14 @@ import { useTheme } from 'styled-components'
export const StarWithAnimation = ({
loading,
- watched,
+ active: watched,
onClick,
size,
disabled,
wrapperStyle,
stopPropagation,
}: {
- watched: boolean
+ active: boolean
loading?: boolean
onClick?: (e: any) => void
size?: number
diff --git a/src/pages/TrueSightV2/components/index.tsx b/src/pages/TrueSightV2/components/index.tsx
index 2259916d83..b843526fdf 100644
--- a/src/pages/TrueSightV2/components/index.tsx
+++ b/src/pages/TrueSightV2/components/index.tsx
@@ -33,10 +33,9 @@ export const StyledSectionWrapper = styled.div<{ show?: boolean }>`
display: flex;
flex-direction: column;
gap: 16px;
- height: 580px;
`
-export const SectionTitle = styled.div`
+const SectionTitle = styled.div`
font-size: 16px;
line-height: 20px;
font-weight: 500;
@@ -45,7 +44,7 @@ export const SectionTitle = styled.div`
border-bottom: 1px solid ${({ theme }) => theme.border + '80'};
color: ${({ theme }) => theme.text};
`
-export const SectionDescription = styled.div<{ show?: boolean }>`
+const SectionDescription = styled.div<{ show?: boolean }>`
font-size: 14px;
line-height: 20px;
text-overflow: ellipsis;
@@ -78,7 +77,7 @@ const ButtonWrapper = styled.div`
}
`
-export const FullscreenButton = React.memo(function FCButton({
+const FullscreenButton = React.memo(function FCButton({
elementRef,
onClick,
}: {
@@ -168,7 +167,7 @@ export const SectionWrapper = ({
const docsLink = activeTab === ChartTab.Second && !!docsLinks[1] ? docsLinks[1] : docsLinks[0]
return (
-
+
{above768 ? (
<>
{/* DESKTOP */}
diff --git a/src/pages/TrueSightV2/components/table/LiquidityMarkets.tsx b/src/pages/TrueSightV2/components/table/LiquidityMarkets.tsx
index 0129e209bd..392d046d6e 100644
--- a/src/pages/TrueSightV2/components/table/LiquidityMarkets.tsx
+++ b/src/pages/TrueSightV2/components/table/LiquidityMarkets.tsx
@@ -16,8 +16,9 @@ import useTheme from 'hooks/useTheme'
import { useGetLiquidityMarketsQuery as useGetLiquidityMarketsCoinmarketcap } from 'pages/TrueSightV2/hooks/useCoinmarketcapData'
import useKyberAIAssetOverview from 'pages/TrueSightV2/hooks/useKyberAIAssetOverview'
import { ChartTab } from 'pages/TrueSightV2/types'
-import { colorFundingRateText, formatShortNum, formatTokenPrice, navigateToSwapPage } from 'pages/TrueSightV2/utils'
+import { colorFundingRateText, formatShortNum, formatTokenPrice } from 'pages/TrueSightV2/utils'
import { MEDIA_WIDTHS } from 'theme'
+import { navigateToSwapPage } from 'utils/redirect'
import { LoadingHandleWrapper } from '.'
diff --git a/src/pages/TrueSightV2/components/table/index.tsx b/src/pages/TrueSightV2/components/table/index.tsx
index 7c980a060d..a4185ab8e9 100644
--- a/src/pages/TrueSightV2/components/table/index.tsx
+++ b/src/pages/TrueSightV2/components/table/index.tsx
@@ -31,10 +31,10 @@ import {
colorFundingRateText,
formatLocaleStringNum,
formatTokenPrice,
- navigateToSwapPage,
} from 'pages/TrueSightV2/utils'
import { ExternalLink } from 'theme'
import { getEtherscanLink, shortenAddress } from 'utils'
+import { navigateToSwapPage } from 'utils/redirect'
import { getProxyTokenLogo } from 'utils/tokenInfo'
import ChevronIcon from '../ChevronIcon'
diff --git a/src/pages/TrueSightV2/index.tsx b/src/pages/TrueSightV2/index.tsx
index 5a9c82b4a8..0b7a8b2ef8 100644
--- a/src/pages/TrueSightV2/index.tsx
+++ b/src/pages/TrueSightV2/index.tsx
@@ -10,12 +10,12 @@ import Row, { RowBetween, RowFit } from 'components/Row'
import { APP_PATHS } from 'constants/index'
import useTheme from 'hooks/useTheme'
import SubscribeButtonKyberAI from 'pages/TrueSightV2/components/SubscireButtonKyberAI'
+import TutorialModalKyberAI from 'pages/TrueSightV2/components/TutorialModalKyberAI'
import { MEDIA_WIDTHS } from 'theme'
import TrueSightWidget from './components/KyberAIWidget'
import NewUpdateAnnoucement from './components/NewUpdateAnnoucement'
import SearchWithDropDown from './components/SearchWithDropDown'
-import TutorialModal from './components/TutorialModal'
import SingleToken from './pages/SingleToken'
import TokenAnalysisList from './pages/TokenAnalysisList'
@@ -118,7 +118,7 @@ export default function TrueSightV2() {
{isExplore ? : }
-
+
>
diff --git a/src/pages/TrueSightV2/pages/SingleToken.tsx b/src/pages/TrueSightV2/pages/SingleToken.tsx
index 8f8fd01128..acff69904c 100644
--- a/src/pages/TrueSightV2/pages/SingleToken.tsx
+++ b/src/pages/TrueSightV2/pages/SingleToken.tsx
@@ -18,6 +18,7 @@ import { MIXPANEL_TYPE, useMixpanelKyberAI } from 'hooks/useMixpanel'
import useTheme from 'hooks/useTheme'
import { PROFILE_MANAGE_ROUTES } from 'pages/NotificationCenter/const'
import { MEDIA_WIDTHS } from 'theme'
+import { navigateToSwapPage } from 'utils/redirect'
import { escapeScriptHtml } from 'utils/string'
import DisplaySettings from '../components/DisplaySettings'
@@ -32,7 +33,6 @@ import { DEFAULT_EXPLORE_PAGE_TOKEN, MIXPANEL_KYBERAI_TAG, NETWORK_IMAGE_URL, NE
import useChartStatesReducer, { ChartStatesContext } from '../hooks/useChartStatesReducer'
import useKyberAIAssetOverview from '../hooks/useKyberAIAssetOverview'
import { DiscoverTokenTab, IAssetOverview } from '../types'
-import { navigateToSwapPage } from '../utils'
import LiquidityAnalysis from './LiquidityAnalysis'
import OnChainAnalysis from './OnChainAnalysis'
import TechnicalAnalysis from './TechnicalAnalysis'
diff --git a/src/pages/TrueSightV2/pages/TokenAnalysisList.tsx b/src/pages/TrueSightV2/pages/TokenAnalysisList.tsx
index cb71924c7e..16ece5fca1 100644
--- a/src/pages/TrueSightV2/pages/TokenAnalysisList.tsx
+++ b/src/pages/TrueSightV2/pages/TokenAnalysisList.tsx
@@ -1,27 +1,26 @@
import { Trans, t } from '@lingui/macro'
-import { motion } from 'framer-motion'
import { rgba } from 'polished'
-import React, { ReactNode, useCallback, useEffect, useMemo, useRef, useState, useTransition } from 'react'
+import React, { useCallback, useEffect, useMemo, useRef, useState, useTransition } from 'react'
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
import { useEffectOnce, useMedia } from 'react-use'
import { Text } from 'rebass'
-import styled, { DefaultTheme, css } from 'styled-components'
+import styled, { css } from 'styled-components'
-import { ReactComponent as DropdownSVG } from 'assets/svg/down.svg'
import Column from 'components/Column'
import Icon from 'components/Icons/Icon'
import AnimatedLoader from 'components/Loader/AnimatedLoader'
import Pagination from 'components/Pagination'
import Row, { RowFit } from 'components/Row'
-import TabButton from 'components/TabButton'
-import { APP_PATHS, ICON_ID, SORT_DIRECTION } from 'constants/index'
+import TabDraggable, { TabITem } from 'components/Section/TabDraggable'
+import { APP_PATHS, SORT_DIRECTION } from 'constants/index'
import { MIXPANEL_TYPE, useMixpanelKyberAI } from 'hooks/useMixpanel'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import useTheme from 'hooks/useTheme'
import { StyledSectionWrapper } from 'pages/TrueSightV2/components'
import TokenFilter from 'pages/TrueSightV2/components/TokenFilter'
import { MEDIA_WIDTHS } from 'theme'
+import { navigateToSwapPage } from 'utils/redirect'
import FeedbackSurvey from '../components/FeedbackSurvey'
import KyberAIShareModal from '../components/KyberAIShareModal'
@@ -33,7 +32,7 @@ import { DEFAULT_PARAMS_BY_TAB, KYBERAI_LISTYPE_TO_MIXPANEL, SORT_FIELD, Z_INDEX
import { useTokenListQuery } from '../hooks/useKyberAIData'
import useRenderRankingList from '../hooks/useRenderRankingList'
import { IKyberScoreChart, ITokenList, KyberAIListType } from '../types'
-import { navigateToSwapPage, useFormatParamsFromUrl } from '../utils'
+import { useFormatParamsFromUrl } from '../utils'
const SIZE_MOBILE = '1080px'
@@ -219,32 +218,6 @@ const ActionButton = styled.button<{ color: string }>`
gap: 4px;
`
-const TabWrapper = styled(motion.div)`
- overflow: auto;
- cursor: grab;
- display: inline-flex;
- width: fit-content;
- position: relative;
- scroll-snap-type: x mandatory;
- scroll-behavior: smooth;
- min-width: 100%;
- > * {
- flex: 1 0 fit-content;
- scroll-snap-align: start;
- }
- &.no-scroll {
- scroll-snap-type: unset;
- scroll-behavior: unset;
- > * {
- scroll-snap-align: unset;
- }
- }
- ${({ theme }) => theme.mediaWidth.upToSmall`
- min-width: initial;
- flex: 1;
- `}
-`
-
const LoadingWrapper = styled(Row)`
position: absolute;
inset: 0 0 0 0;
@@ -262,226 +235,125 @@ const LoadingWrapper = styled(Row)`
`}
`
-const ARROW_SIZE = 40
+const getTokenTypeList = (): TabITem[] => [
+ { type: KyberAIListType.MYWATCHLIST, icon: 'star', title: t`My Watchlist` },
+ { type: KyberAIListType.ALL, title: t`All` },
+ {
+ type: KyberAIListType.BULLISH,
+ title: t`Bullish`,
+ icon: 'bullish',
+ tooltip: theme => (
+
+ Tokens with the highest chance of price increase in the next 24H
+ (highest KyberScore).
+
+ ),
+ },
+ {
+ type: KyberAIListType.BEARISH,
+ title: t`Bearish`,
+ icon: 'bearish',
+ tooltip: theme => (
+
+ Tokens with the highest chance of price decrease in the next 24H
+ (lowest KyberScore).
+
+ ),
+ },
+ {
+ type: KyberAIListType.KYBERSWAP_DELTA,
+ title: t`KyberScore Delta`,
+ icon: 'bearish',
+ tooltip: theme => (
+
+
+ Tokens with a significant change in KyberScore between two
+ consecutive time periods. This may indicate a change in trend of the token.
+
+
+ ),
+ },
+ {
+ type: KyberAIListType.TOP_CEX_INFLOW,
+ title: t`Top CEX Positive Netflow`,
+ icon: 'download',
+ tooltip: theme => (
+
+ Tokens with the highest net deposits to Centralized Exchanges in the
+ last 3 Days. Possible incoming sell pressure.
+
+ ),
+ },
+ {
+ type: KyberAIListType.TOP_CEX_OUTFLOW,
+ title: t`Top CEX Negative Netflow`,
+ icon: 'upload',
+ tooltip: theme => (
+
+ Tokens with the highest net withdrawals from Centralized Exchanges in
+ the last 3 Days. Possible buy pressure.
+
+ ),
+ },
+ {
+ type: KyberAIListType.FUNDING_RATE,
+ title: t`Funding Rates`,
+ icon: 'coin-bag',
+ tooltip: () => (
+
+
+ Tokens with funding rates on centralized exchanges. Positive funding rate suggests traders are bullish &
+ vice-versa for negative rates. Extreme rates may result in leveraged positions getting squeezed.
+
+
+ ),
+ },
+ {
+ type: KyberAIListType.TOP_TRADED,
+ title: t`Top Traded`,
+ icon: 'coin-bag',
+ tooltip: theme => (
+
+ Tokens with the highest 24H trading volume.
+
+ ),
+ },
+ {
+ type: KyberAIListType.TRENDING_SOON,
+ title: t`Trending Soon`,
+ icon: 'trending-soon',
+ tooltip: theme => (
+
+ Tokens that could be trending in the near future. Trending indicates
+ interest in a token - it doesnt imply bullishness or bearishness.
+
+ ),
+ },
+ {
+ type: KyberAIListType.TRENDING,
+ title: t`Currently Trending`,
+ icon: 'flame',
+ tooltip: theme => (
+
+ Tokens that are currently trending in the market.
+
+ ),
+ },
+]
const TokenListDraggableTabs = ({ tab, setTab }: { tab: KyberAIListType; setTab: (type: KyberAIListType) => void }) => {
- const theme = useTheme()
const mixpanelHandler = useMixpanelKyberAI()
- const [showScrollRightButton, setShowScrollRightButton] = useState(false)
- const [scrollLeftValue, setScrollLeftValue] = useState(0)
- const wrapperRef = useRef(null)
- const tabListRef = useRef([])
-
- const tokenTypeList: {
- type: KyberAIListType
- icon?: ICON_ID
- tooltip?: (theme: DefaultTheme) => ReactNode
- title: string
- }[] = [
- { type: KyberAIListType.MYWATCHLIST, icon: 'star', title: t`My Watchlist` },
- { type: KyberAIListType.ALL, title: t`All` },
- {
- type: KyberAIListType.BULLISH,
- title: t`Bullish`,
- icon: 'bullish',
- tooltip: theme => (
-
- Tokens with the highest chance of price increase in the next 24H
- (highest KyberScore).
-
- ),
- },
- {
- type: KyberAIListType.BEARISH,
- title: t`Bearish`,
- icon: 'bearish',
- tooltip: theme => (
-
- Tokens with the highest chance of price decrease in the next 24H
- (lowest KyberScore).
-
- ),
- },
- {
- type: KyberAIListType.KYBERSWAP_DELTA,
- title: t`KyberScore Delta`,
- icon: 'bearish',
- tooltip: theme => (
-
-
- Tokens with a significant change in KyberScore between two
- consecutive time periods. This may indicate a change in trend of the token.
-
-
- ),
- },
- {
- type: KyberAIListType.TOP_CEX_INFLOW,
- title: t`Top CEX Positive Netflow`,
- icon: 'download',
- tooltip: theme => (
-
- Tokens with the highest net deposits to Centralized Exchanges in
- the last 3 Days. Possible incoming sell pressure.
-
- ),
- },
- {
- type: KyberAIListType.TOP_CEX_OUTFLOW,
- title: t`Top CEX Negative Netflow`,
- icon: 'upload',
- tooltip: theme => (
-
- Tokens with the highest net withdrawals from Centralized Exchanges
- in the last 3 Days. Possible buy pressure.
-
- ),
- },
- {
- type: KyberAIListType.FUNDING_RATE,
- title: t`Funding Rates`,
- icon: 'coin-bag',
- tooltip: () => (
-
-
- Tokens with funding rates on centralized exchanges. Positive funding rate suggests traders are bullish &
- vice-versa for negative rates. Extreme rates may result in leveraged positions getting squeezed.
-
-
- ),
- },
- {
- type: KyberAIListType.TOP_TRADED,
- title: t`Top Traded`,
- icon: 'coin-bag',
- tooltip: theme => (
-
- Tokens with the highest 24H trading volume.
-
- ),
- },
- {
- type: KyberAIListType.TRENDING_SOON,
- title: t`Trending Soon`,
- icon: 'trending-soon',
- tooltip: theme => (
-
- Tokens that could be trending in the near future. Trending
- indicates interest in a token - it doesnt imply bullishness or bearishness.
-
- ),
- },
- {
- type: KyberAIListType.TRENDING,
- title: t`Currently Trending`,
- icon: 'flame',
- tooltip: theme => (
-
- Tokens that are currently trending in the market.
-
- ),
- },
- ]
-
- useEffect(() => {
- wrapperRef.current?.scrollTo({ left: scrollLeftValue, behavior: 'smooth' })
- }, [scrollLeftValue])
-
- useEffect(() => {
- const wRef = wrapperRef.current
- if (!wRef) return
- const handleWheel = (e: any) => {
- e.preventDefault()
- setScrollLeftValue(prev => Math.min(Math.max(prev + e.deltaY, 0), wRef.scrollWidth - wRef.clientWidth))
- }
- if (wRef) {
- wRef.addEventListener('wheel', handleWheel)
- }
- return () => wRef?.removeEventListener('wheel', handleWheel)
- }, [])
-
- useEffect(() => {
- const handleResize = () => {
- setShowScrollRightButton(
- Boolean(wrapperRef.current?.clientWidth && wrapperRef.current?.clientWidth < wrapperRef.current?.scrollWidth),
- )
- }
- handleResize()
- window.addEventListener('resize', handleResize)
- return () => {
- window.removeEventListener('resize', handleResize)
- }
- }, [])
-
- const indexActive = tokenTypeList.findIndex(e => e.type === tab)
-
+ const onTabClick = (fromTab: KyberAIListType, toTab: KyberAIListType) => {
+ mixpanelHandler(MIXPANEL_TYPE.KYBERAI_RANKING_CATEGORY_CLICK, {
+ from_cate: KYBERAI_LISTYPE_TO_MIXPANEL[fromTab],
+ to_cate: KYBERAI_LISTYPE_TO_MIXPANEL[toTab],
+ source: KYBERAI_LISTYPE_TO_MIXPANEL[fromTab],
+ })
+ }
return (
-
- e.preventDefault()}
- style={{ paddingRight: showScrollRightButton ? ARROW_SIZE : undefined }}
- >
- {tokenTypeList.map(({ type, title, tooltip }, index) => {
- const props = {
- onClick: () => {
- mixpanelHandler(MIXPANEL_TYPE.KYBERAI_RANKING_CATEGORY_CLICK, {
- from_cate: KYBERAI_LISTYPE_TO_MIXPANEL[tab],
- to_cate: KYBERAI_LISTYPE_TO_MIXPANEL[type],
- source: KYBERAI_LISTYPE_TO_MIXPANEL[tab],
- })
- setTab(type)
- if (!wrapperRef.current) return
- const tabRef = tabListRef.current[index]
- const wRef = wrapperRef.current
- if (tabRef.offsetLeft < wRef.scrollLeft) {
- setScrollLeftValue(tabRef.offsetLeft)
- }
- if (wRef.scrollLeft + wRef.clientWidth < tabRef.offsetLeft + tabRef.offsetWidth) {
- setScrollLeftValue(tabRef.offsetLeft + tabRef.offsetWidth - wRef.offsetWidth)
- }
- },
- }
- return (
-
- {
- if (el) {
- tabListRef.current[index] = el
- }
- }}
- />
-
- )
- })}
-
- {showScrollRightButton && (
- {
- setScrollLeftValue(prev => prev + 120)
- }}
- />
- )}
-
+
+ {...{ activeTab: tab, onChange: setTab, trackingChangeTab: onTabClick, tabs: getTokenTypeList() }}
+ />
)
}
diff --git a/src/pages/TrueSightV2/utils/index.tsx b/src/pages/TrueSightV2/utils/index.tsx
index 6d1ac618e1..4da3878fbf 100644
--- a/src/pages/TrueSightV2/utils/index.tsx
+++ b/src/pages/TrueSightV2/utils/index.tsx
@@ -106,16 +106,6 @@ export const getErrorMessage = (error: any) => {
return mapErr[code] || t`Error occur, please try again.`
}
-export const navigateToSwapPage = ({ address, chain }: { address?: string; chain?: string }) => {
- if (!address || !chain) return
- const wethAddress = WETH[NETWORK_TO_CHAINID[chain]].address
- const formattedChain = chain === 'bsc' ? 'bnb' : chain
- window.open(
- window.location.origin +
- `${APP_PATHS.SWAP}/${formattedChain}?inputCurrency=${wethAddress}&outputCurrency=${address}`,
- '_blank',
- )
-}
export const navigateToLimitPage = ({ address, chain }: { address?: string; chain?: string }) => {
if (!address || !chain) return
const wethAddress = WETH[NETWORK_TO_CHAINID[chain]].address
diff --git a/src/services/portfolio.ts b/src/services/portfolio.ts
new file mode 100644
index 0000000000..0aa029bfb7
--- /dev/null
+++ b/src/services/portfolio.ts
@@ -0,0 +1,338 @@
+import { ChainId } from '@kyberswap/ks-sdk-core'
+import { createApi } from '@reduxjs/toolkit/query/react'
+import { baseQueryOauthDynamic } from 'services/baseQueryOauth'
+
+import { BFF_API } from 'constants/env'
+import { RTK_QUERY_TAGS } from 'constants/index'
+import {
+ LiquidityDataResponse,
+ NFTBalance,
+ NFTTokenDetail,
+ NftCollectionResponse,
+ Portfolio,
+ PortfolioChainBalanceResponse,
+ PortfolioSearchData,
+ PortfolioSetting,
+ PortfolioWallet,
+ PortfolioWalletBalanceResponse,
+ TokenAllowAnceResponse,
+ TransactionHistoryResponse,
+} from 'pages/NotificationCenter/Portfolio/type'
+
+const KRYSTAL_API = 'https://api.krystal.app/all'
+const portfolioApi = createApi({
+ reducerPath: 'portfolioApi',
+ baseQuery: baseQueryOauthDynamic({ baseUrl: `${BFF_API}/v1/portfolio-service` }),
+ tagTypes: [
+ RTK_QUERY_TAGS.GET_LIST_PORTFOLIO,
+ RTK_QUERY_TAGS.GET_LIST_WALLET_PORTFOLIO,
+ RTK_QUERY_TAGS.GET_SETTING_PORTFOLIO,
+ RTK_QUERY_TAGS.GET_FAVORITE_PORTFOLIO,
+ ],
+ endpoints: builder => ({
+ getMyPortfolios: builder.query({
+ query: () => ({
+ url: '/portfolios',
+ authentication: true,
+ }),
+ transformResponse: (data: any) => data?.data?.portfolios,
+ providesTags: [RTK_QUERY_TAGS.GET_LIST_PORTFOLIO],
+ }),
+ getPortfolioById: builder.query({
+ query: ({ id }) => ({
+ url: `/portfolios/${id}`,
+ authentication: true,
+ }),
+ transformResponse: (data: any) => data?.data,
+ }),
+ searchPortfolio: builder.query({
+ query: params => ({
+ url: `/search`,
+ params,
+ authentication: true,
+ }),
+ transformResponse: (data: any) => data?.data,
+ }),
+ getTrendingPortfolios: builder.query({
+ query: () => ({
+ url: `/search/trending`,
+ authentication: true,
+ }),
+ transformResponse: (data: any) => data?.data,
+ }),
+ getFavoritesPortfolios: builder.query({
+ query: () => ({
+ url: `/search/favorites`,
+ authentication: true,
+ }),
+ transformResponse: (data: any) => data?.data,
+ providesTags: [RTK_QUERY_TAGS.GET_FAVORITE_PORTFOLIO],
+ }),
+ toggleFavoritePortfolio: builder.mutation<{ id: string }, { value: string; isAdd: boolean }>({
+ query: ({ isAdd, ...body }) => ({
+ url: '/favorites',
+ method: isAdd ? 'POST' : 'DELETE',
+ body,
+ authentication: true,
+ }),
+ transformResponse: (data: any) => data?.data,
+ invalidatesTags: [RTK_QUERY_TAGS.GET_FAVORITE_PORTFOLIO],
+ }),
+ createPortfolio: builder.mutation<{ id: string }, { name: string }>({
+ query: body => ({
+ url: '/portfolios',
+ method: 'POST',
+ body,
+ authentication: true,
+ }),
+ transformResponse: (data: any) => data?.data,
+ invalidatesTags: [RTK_QUERY_TAGS.GET_LIST_PORTFOLIO],
+ }),
+ clonePortfolio: builder.mutation({
+ query: body => ({
+ url: '/portfolios/clone',
+ method: 'POST',
+ body,
+ authentication: true,
+ }),
+ invalidatesTags: [RTK_QUERY_TAGS.GET_LIST_PORTFOLIO],
+ }),
+ updatePortfolio: builder.mutation({
+ query: ({ id, ...body }) => ({
+ url: `/portfolios/${id}`,
+ method: 'PUT',
+ body,
+ authentication: true,
+ }),
+ invalidatesTags: [RTK_QUERY_TAGS.GET_LIST_PORTFOLIO],
+ }),
+ deletePortfolio: builder.mutation({
+ query: id => ({
+ url: `/portfolios/${id}`,
+ method: 'DELETE',
+ authentication: true,
+ }),
+ invalidatesTags: [RTK_QUERY_TAGS.GET_LIST_PORTFOLIO],
+ }),
+ // setting
+ getPortfoliosSettings: builder.query({
+ query: () => ({
+ url: '/settings',
+ authentication: true,
+ }),
+ transformResponse: (data: any) => data?.data,
+ providesTags: [RTK_QUERY_TAGS.GET_SETTING_PORTFOLIO],
+ }),
+ updatePortfoliosSettings: builder.mutation({
+ query: body => ({
+ url: `/settings`,
+ method: 'PUT',
+ body,
+ authentication: true,
+ }),
+ invalidatesTags: [RTK_QUERY_TAGS.GET_SETTING_PORTFOLIO],
+ }),
+ // wallets
+ getWalletsPortfolios: builder.query({
+ query: ({ portfolioId }) => ({
+ url: `/portfolios/${portfolioId}/wallets`,
+ authentication: true,
+ }),
+ transformResponse: (data: any) => data?.data?.wallets,
+ providesTags: [RTK_QUERY_TAGS.GET_LIST_WALLET_PORTFOLIO],
+ }),
+ addWalletToPortfolio: builder.mutation(
+ {
+ query: ({ portfolioId, ...body }) => ({
+ url: `/portfolios/${portfolioId}/wallets`,
+ method: 'POST',
+ body,
+ authentication: true,
+ }),
+ invalidatesTags: [RTK_QUERY_TAGS.GET_LIST_WALLET_PORTFOLIO],
+ },
+ ),
+ updateWalletToPortfolio: builder.mutation<
+ Portfolio,
+ { portfolioId: string; walletAddress: string; nickName: string }
+ >({
+ query: ({ portfolioId, walletAddress, ...body }) => ({
+ url: `/portfolios/${portfolioId}/wallets/${walletAddress}`,
+ method: 'PUT',
+ body,
+ authentication: true,
+ }),
+ invalidatesTags: [RTK_QUERY_TAGS.GET_LIST_WALLET_PORTFOLIO],
+ }),
+ removeWalletFromPortfolio: builder.mutation({
+ query: ({ portfolioId, walletAddress }) => ({
+ url: `/portfolios/${portfolioId}/wallets/${walletAddress}`,
+ method: 'DELETE',
+ authentication: true,
+ }),
+ invalidatesTags: [RTK_QUERY_TAGS.GET_LIST_WALLET_PORTFOLIO],
+ }),
+ // metadata
+ getRealtimeBalance: builder.query({
+ query: ({ walletAddresses }) => ({
+ url: `${BFF_API}/v1/wallet-service/balances/realtime/total`,
+ params: { walletAddresses: walletAddresses.join(',') },
+ authentication: true,
+ }),
+ transformResponse: (data: any) => data?.data,
+ }),
+ getTokenAllocation: builder.query<
+ PortfolioWalletBalanceResponse,
+ { walletAddresses: string[]; chainIds: ChainId[] }
+ >({
+ query: ({ walletAddresses, chainIds }) => ({
+ url: `${BFF_API}/v1/wallet-service/balances/realtime/tokens`,
+ params: { walletAddresses: walletAddresses.join(','), chainIds: chainIds.join(',') },
+ authentication: true,
+ }),
+ transformResponse: (data: any) => data?.data,
+ }),
+ getChainsAllocation: builder.query<
+ PortfolioChainBalanceResponse,
+ { walletAddresses: string[]; chainIds: ChainId[] }
+ >({
+ query: ({ walletAddresses, chainIds }) => ({
+ url: `${BFF_API}/v1/wallet-service/balances/realtime/chains`,
+ params: { walletAddresses: walletAddresses.join(','), chainIds: chainIds.join(',') },
+ authentication: true,
+ }),
+ transformResponse: (data: any) => data?.data,
+ }),
+ getWalletsAllocation: builder.query<
+ PortfolioWalletBalanceResponse,
+ { walletAddresses: string[]; chainIds: ChainId[] }
+ >({
+ query: ({ walletAddresses, chainIds }) => ({
+ url: `${BFF_API}/v1/wallet-service/balances/realtime/wallets`,
+ params: { walletAddresses: walletAddresses.join(','), chainIds: chainIds.join(',') },
+ authentication: true,
+ }),
+ transformResponse: (data: any) => data?.data,
+ }),
+ getTokenApproval: builder.query({
+ query: params => ({
+ url: `${KRYSTAL_API}/v1/approval/list`,
+ params,
+ }),
+ transformResponse: (data: any) => data?.data,
+ }),
+ getNftCollections: builder.query<
+ NftCollectionResponse,
+ { addresses: string[]; chainIds?: ChainId[]; page: number; pageSize: number; search: string }
+ >({
+ query: params => ({
+ url: `${KRYSTAL_API}/v1/balance/listNftCollection`,
+ params: { ...params, withNft: false },
+ }),
+ transformResponse: (data: any) => {
+ data.data = data.data.map((chain: any) => chain.balances).flat()
+ return data
+ },
+ }),
+ getNftCollectionDetail: builder.query<
+ NFTBalance,
+ {
+ address: string
+ chainId: ChainId
+ page: number
+ pageSize: number
+ search: string
+ collectionAddress: string
+ }
+ >({
+ query: params => ({
+ url: `${KRYSTAL_API}/v1/balance/listNftInCollection`,
+ params,
+ }),
+ transformResponse: (data: any) => data?.data,
+ }),
+ getNftDetail: builder.query<
+ NFTTokenDetail,
+ {
+ address: string
+ chainId: ChainId | undefined
+ tokenID: string
+ }
+ >({
+ query: params => ({
+ url: `${KRYSTAL_API}/v1/nft/getNftDetail`,
+ params,
+ }),
+ transformResponse: (data: any) => data?.data,
+ }),
+ getTransactions: builder.query<
+ TransactionHistoryResponse,
+ {
+ walletAddress: string
+ chainIds?: ChainId[]
+ limit: number
+ endTime: number
+ tokenAddress?: string
+ tokenSymbol?: string
+ }
+ >({
+ query: params => ({
+ url: `${KRYSTAL_API}/v1/txHistory/getHistory`,
+ params,
+ }),
+ }),
+ // liquidity
+ getLiquidityPortfolio: builder.query<
+ LiquidityDataResponse,
+ {
+ addresses: string[]
+ chainIds?: ChainId[]
+ quoteSymbols?: string
+ offset?: number
+ orderBy?: string
+ orderASC?: boolean
+ positionStatus?: string
+ limit?: number
+ protocols?: string
+ q?: string
+ }
+ >({
+ query: params => ({
+ url: `${KRYSTAL_API}/v2/balance/lp`,
+ params,
+ }),
+ }),
+ }),
+})
+
+export const {
+ useGetMyPortfoliosQuery,
+ useLazyGetMyPortfoliosQuery,
+ useCreatePortfolioMutation,
+ useUpdatePortfolioMutation,
+ useGetRealtimeBalanceQuery,
+ useLazyGetTokenApprovalQuery,
+ useGetTransactionsQuery,
+ useDeletePortfolioMutation,
+ useAddWalletToPortfolioMutation,
+ useGetWalletsPortfoliosQuery,
+ useRemoveWalletFromPortfolioMutation,
+ useUpdateWalletToPortfolioMutation,
+ useGetPortfolioByIdQuery,
+ useClonePortfolioMutation,
+ useGetPortfoliosSettingsQuery,
+ useUpdatePortfoliosSettingsMutation,
+ useGetFavoritesPortfoliosQuery,
+ useGetTrendingPortfoliosQuery,
+ useSearchPortfolioQuery,
+ useToggleFavoritePortfolioMutation,
+ useGetNftCollectionsQuery,
+ useGetNftCollectionDetailQuery,
+ useGetNftDetailQuery,
+ useGetTokenAllocationQuery,
+ useGetChainsAllocationQuery,
+ useGetWalletsAllocationQuery,
+ useGetLiquidityPortfolioQuery,
+} = portfolioApi
+
+export default portfolioApi
diff --git a/src/services/social.ts b/src/services/social.ts
index 6fea844576..36ec377094 100644
--- a/src/services/social.ts
+++ b/src/services/social.ts
@@ -6,6 +6,7 @@ import { BFF_API } from 'constants/env'
export enum SHARE_TYPE {
KYBER_AI = 'KYBER_AI',
MY_EARNINGS = 'MY_EARNINGS',
+ PORTFOLIO = 'PORTFOLIO',
}
const SocialApi = createApi({
diff --git a/src/state/index.ts b/src/state/index.ts
index 4b605c7c8e..f80400607d 100644
--- a/src/state/index.ts
+++ b/src/state/index.ts
@@ -15,6 +15,7 @@ import ksSettingApi from 'services/ksSetting'
import kyberAISubscriptionApi from 'services/kyberAISubscription'
import kyberDAO from 'services/kyberDAO'
import limitOrderApi from 'services/limitOrder'
+import portfolioApi from 'services/portfolio'
import priceAlertApi from 'services/priceAlert'
import routeApi from 'services/route'
import socialApi from 'services/social'
@@ -128,6 +129,7 @@ const store = configureStore({
[blockServiceApi.reducerPath]: blockServiceApi.reducer,
[blackjackApi.reducerPath]: blackjackApi.reducer,
[knProtocolApi.reducerPath]: knProtocolApi.reducer,
+ [portfolioApi.reducerPath]: portfolioApi.reducer,
},
middleware: getDefaultMiddleware =>
getDefaultMiddleware({ thunk: true, immutableCheck: false, serializableCheck: false })
@@ -153,6 +155,7 @@ const store = configureStore({
.concat(tokenApi.middleware)
.concat(blockServiceApi.middleware)
.concat(blackjackApi.middleware)
+ .concat(portfolioApi.middleware)
.concat(knProtocolApi.middleware),
preloadedState,
})
diff --git a/src/state/transactions/hooks.tsx b/src/state/transactions/hooks.tsx
index 456bd85d35..57e8ca874a 100644
--- a/src/state/transactions/hooks.tsx
+++ b/src/state/transactions/hooks.tsx
@@ -76,22 +76,6 @@ export function useAllTransactions(allChain = false): GroupedTxsByHash | undefin
}, [allChain, transactions, chainId, account])
}
-export function useSortRecentTransactions(recentOnly = true, allChain = false) {
- const allTransactions = useAllTransactions(allChain)
- const { account } = useActiveWeb3React()
- return useMemo(() => {
- const txGroups: TransactionDetails[][] = allTransactions
- ? (Object.values(allTransactions).filter(Boolean) as TransactionDetails[][])
- : []
- return txGroups
- .filter(txs => {
- const isMyGroup = isOwnTransactionGroup(txs, account)
- return recentOnly ? isTransactionGroupRecent(txs) && isMyGroup : isMyGroup
- })
- .sort(newTransactionsGroupFirst)
- }, [allTransactions, recentOnly, account])
-}
-
export function useIsTransactionPending(transactionHash?: string): boolean {
const transactions = useAllTransactions()
@@ -104,20 +88,7 @@ export function useIsTransactionPending(transactionHash?: string): boolean {
}
function isOwnTransactionGroup(txs: TransactionDetails[], account: string | undefined): boolean {
- return !!account && txs[0]?.from === account && !!txs[0]?.group
-}
-
-/**
- * Returns whether a transaction happened in the last day (86400 seconds * 1000 milliseconds / second)
- * @param tx to check for recency
- */
-function isTransactionGroupRecent(txs: TransactionDetails[]): boolean {
- return new Date().getTime() - (txs[0]?.addedTime ?? 0) < 86_400_000
-}
-
-// we want the latest one to come first, so return negative if a is after b
-function newTransactionsGroupFirst(a: TransactionDetails[], b: TransactionDetails[]) {
- return (b[0]?.addedTime ?? 0) - (a[0]?.addedTime ?? 0)
+ return !!account && txs[0]?.from === account
}
/**
diff --git a/src/state/transactions/reducer.ts b/src/state/transactions/reducer.ts
index df7c6e293d..ab2c610a18 100644
--- a/src/state/transactions/reducer.ts
+++ b/src/state/transactions/reducer.ts
@@ -2,7 +2,6 @@ import { ChainId } from '@kyberswap/ks-sdk-core'
import { createReducer } from '@reduxjs/toolkit'
import { findTx } from 'utils'
-import { getTransactionGroupByType } from 'utils/transaction'
import {
addTransaction,
@@ -55,7 +54,6 @@ export default createReducer(initialState, builder =>
addedTime: Date.now(),
chainId,
extraInfo,
- group: getTransactionGroupByType(type),
})
chainTxs[txs[0].hash] = txs
transactions[chainId] = clearOldTransactions(chainTxs)
diff --git a/src/state/transactions/type.ts b/src/state/transactions/type.ts
index 2554be947c..00ff0ad89d 100644
--- a/src/state/transactions/type.ts
+++ b/src/state/transactions/type.ts
@@ -69,7 +69,6 @@ export type TransactionExtraInfo = (
| TransactionExtraInfoHarvestFarm
| TransactionExtraInfoStakeFarm
) & {
- actuallySuccess?: boolean
needCheckSubgraph?: boolean
arbitrary?: any // To store anything arbitrary, so it has any type
}
@@ -87,7 +86,6 @@ export interface TransactionDetails {
nonce?: number
sentAtBlock?: number
extraInfo?: TransactionExtraInfo
- group: TRANSACTION_GROUP
chainId: ChainId
}
@@ -112,13 +110,6 @@ export type TransactionPayload = TransactionHistory & {
chainId: ChainId
}
-/**
- * when you put a new type, let's do:
- * 1. classify it by putting it into GROUP_TRANSACTION_BY_TYPE
- * 2. add a case in SUMMARY in TransactionPopup.tsx to render notification detail by type
- * 3. add a case in RENDER_DESCRIPTION_MAP in TransactionItem.tsx to render transaction detail by type
- * if you forgot. typescript error will occur.
- */
export enum TRANSACTION_TYPE {
WRAP_TOKEN = 'Wrap Token',
UNWRAP_TOKEN = 'Unwrap Token',
@@ -158,60 +149,3 @@ export enum TRANSACTION_TYPE {
CANCEL_LIMIT_ORDER = 'Cancel Limit Order',
TRANSFER_TOKEN = 'Send',
}
-
-export const GROUP_TRANSACTION_BY_TYPE = {
- SWAP: [
- TRANSACTION_TYPE.SWAP,
- TRANSACTION_TYPE.WRAP_TOKEN,
- TRANSACTION_TYPE.UNWRAP_TOKEN,
- TRANSACTION_TYPE.CROSS_CHAIN_SWAP,
- ],
- LIQUIDITY: [
- TRANSACTION_TYPE.CLASSIC_ADD_LIQUIDITY,
- TRANSACTION_TYPE.CLASSIC_CREATE_POOL,
- TRANSACTION_TYPE.ELASTIC_CREATE_POOL,
- TRANSACTION_TYPE.ELASTIC_ADD_LIQUIDITY,
- TRANSACTION_TYPE.CLASSIC_REMOVE_LIQUIDITY,
- TRANSACTION_TYPE.ELASTIC_REMOVE_LIQUIDITY,
- TRANSACTION_TYPE.ELASTIC_INCREASE_LIQUIDITY,
- TRANSACTION_TYPE.ELASTIC_ZAP_IN_LIQUIDITY,
- TRANSACTION_TYPE.ELASTIC_DEPOSIT_LIQUIDITY,
- TRANSACTION_TYPE.ELASTIC_WITHDRAW_LIQUIDITY,
- TRANSACTION_TYPE.STAKE,
- TRANSACTION_TYPE.UNSTAKE,
- TRANSACTION_TYPE.HARVEST,
- TRANSACTION_TYPE.ELASTIC_COLLECT_FEE,
- TRANSACTION_TYPE.ELASTIC_FORCE_WITHDRAW_LIQUIDITY,
- ],
- KYBERDAO: [
- TRANSACTION_TYPE.KYBERDAO_STAKE,
- TRANSACTION_TYPE.KYBERDAO_UNSTAKE,
- TRANSACTION_TYPE.KYBERDAO_DELEGATE,
- TRANSACTION_TYPE.KYBERDAO_UNDELEGATE,
- TRANSACTION_TYPE.KYBERDAO_MIGRATE,
- TRANSACTION_TYPE.KYBERDAO_VOTE,
- TRANSACTION_TYPE.KYBERDAO_CLAIM,
- TRANSACTION_TYPE.KYBERDAO_CLAIM_GAS_REFUND,
- ],
- OTHER: [
- // to make sure you don't forgot
- TRANSACTION_TYPE.APPROVE,
- TRANSACTION_TYPE.CLAIM_REWARD,
- TRANSACTION_TYPE.BRIDGE,
- TRANSACTION_TYPE.CANCEL_LIMIT_ORDER,
- TRANSACTION_TYPE.TRANSFER_TOKEN,
- ],
-}
-
-export enum TRANSACTION_GROUP {
- SWAP = 'swap',
- LIQUIDITY = 'liquidity',
- KYBERDAO = 'kyber_dao',
- OTHER = 'other',
-}
-
-const totalType = Object.values(TRANSACTION_TYPE).length
-const totalClassify = Object.values(GROUP_TRANSACTION_BY_TYPE).reduce((total, element) => total + element.length, 0)
-if (totalType !== totalClassify) {
- throw new Error('Please set up group of the new transaction. Put your new type into GROUP_TRANSACTION_BY_TYPE')
-}
diff --git a/src/utils/formatBalance.ts b/src/utils/formatBalance.ts
index f809e0f97e..cf364355e5 100644
--- a/src/utils/formatBalance.ts
+++ b/src/utils/formatBalance.ts
@@ -1,5 +1,6 @@
import { BigNumber } from '@ethersproject/bignumber'
import { Fraction } from '@kyberswap/ks-sdk-core'
+import { BigNumberish } from 'ethers'
import { formatUnits } from 'ethers/lib/utils'
import JSBI from 'jsbi'
import Numeral from 'numeral'
@@ -81,6 +82,6 @@ export const fixedFormatting = (value: BigNumber, decimals: number) => {
return parseFloat(res).toString()
}
-export const formatUnitsToFixed = (amount: BigNumber, decimals?: number, decimalPlaces?: number) => {
+export const formatUnitsToFixed = (amount: BigNumberish, decimals?: number, decimalPlaces?: number) => {
return (+(+formatUnits(amount, decimals)).toFixed(decimalPlaces ?? 3)).toString()
}
diff --git a/src/utils/redirect.ts b/src/utils/redirect.ts
index d472a54b66..551dff0fb6 100644
--- a/src/utils/redirect.ts
+++ b/src/utils/redirect.ts
@@ -1,9 +1,12 @@
-import { ChainId } from '@kyberswap/ks-sdk-core'
+import { ChainId, WETH } from '@kyberswap/ks-sdk-core'
import { useCallback } from 'react'
import { useNavigate } from 'react-router-dom'
+import { APP_PATHS } from 'constants/index'
import { useActiveWeb3React } from 'hooks'
+import { NETWORKS_INFO } from 'hooks/useChainsConfig'
import { useChangeNetwork } from 'hooks/web3/useChangeNetwork'
+import { getChainIdFromSlug } from 'utils/string'
const whiteListDomains = [/https:\/\/(.+?\.)?kyberswap\.com$/, /https:\/\/(.+)\.kyberengineering\.io$/]
@@ -74,3 +77,14 @@ export const useNavigateToUrl = () => {
[changeNetwork, currentChain, redirect],
)
}
+
+export const navigateToSwapPage = ({ address, chain }: { address?: string; chain?: string | number }) => {
+ if (!address || !chain) return
+ const chainId: ChainId | undefined = !isNaN(+chain) ? +chain : getChainIdFromSlug(chain as string)
+ if (!chainId) return
+ window.open(
+ window.location.origin +
+ `${APP_PATHS.SWAP}/${NETWORKS_INFO[chainId].route}?inputCurrency=${WETH[chainId].address}&outputCurrency=${address}`,
+ '_blank',
+ )
+}
diff --git a/src/utils/string.ts b/src/utils/string.ts
index 3e0f7d6358..98eb447d28 100644
--- a/src/utils/string.ts
+++ b/src/utils/string.ts
@@ -42,10 +42,17 @@ export const isEmailValid = (value: string | undefined) =>
(value || '').trim().match(/^\w+([\.-]?\w)*@\w+([\.-]?\w)*(\.\w{2,10})+$/)
export const getChainIdFromSlug = (network: string | undefined): ChainId | undefined => {
- return SUPPORTED_NETWORKS.find(chainId => NETWORKS_INFO[chainId].route === network)
+ return network === 'bsc'
+ ? ChainId.BSCMAINNET
+ : SUPPORTED_NETWORKS.find(chainId => NETWORKS_INFO[chainId].route === network)
}
export function capitalizeFirstLetter(str?: string) {
const string = str || ''
return string.charAt(0).toUpperCase() + string.slice(1)
}
+
+export function isULIDString(str = '') {
+ const ulidPattern = /[0-7][0-9A-HJKMNP-TV-Z]{25}/
+ return ulidPattern.test(str)
+}
diff --git a/src/utils/transaction.ts b/src/utils/transaction.ts
index dc47bc3e2b..dd5d5909dd 100644
--- a/src/utils/transaction.ts
+++ b/src/utils/transaction.ts
@@ -1,18 +1,6 @@
import { ethers } from 'ethers'
-import {
- GROUP_TRANSACTION_BY_TYPE,
- TRANSACTION_GROUP,
- TRANSACTION_TYPE,
- TransactionDetails,
-} from 'state/transactions/type'
-
-export const getTransactionGroupByType = (type: TRANSACTION_TYPE) => {
- if (GROUP_TRANSACTION_BY_TYPE.SWAP.includes(type)) return TRANSACTION_GROUP.SWAP
- if (GROUP_TRANSACTION_BY_TYPE.LIQUIDITY.includes(type)) return TRANSACTION_GROUP.LIQUIDITY
- if (GROUP_TRANSACTION_BY_TYPE.KYBERDAO.includes(type)) return TRANSACTION_GROUP.KYBERDAO
- return TRANSACTION_GROUP.OTHER
-}
+import { TransactionDetails } from 'state/transactions/type'
export const getTransactionStatus = (transaction: TransactionDetails) => {
const pending = !transaction?.receipt
diff --git a/src/utils/useEstimateGasTxs.ts b/src/utils/useEstimateGasTxs.ts
index 02a8d57d82..f1afb46702 100644
--- a/src/utils/useEstimateGasTxs.ts
+++ b/src/utils/useEstimateGasTxs.ts
@@ -1,12 +1,19 @@
import { WETH } from '@kyberswap/ks-sdk-core'
import { BigNumber, ethers } from 'ethers'
-import { useCallback, useMemo } from 'react'
+import { useCallback, useEffect, useMemo, useState } from 'react'
import { useActiveWeb3React, useWeb3React } from 'hooks'
import { useTokenPrices } from 'state/tokenPrices/hooks'
-type EstimateParams = { contractAddress: string; encodedData: string; value?: BigNumber }
-function useEstimateGasTxs(): (v: EstimateParams) => Promise<{ gas: BigNumber | null; gasInUsd: number | null }> {
+type EstimateParams = {
+ contractAddress?: string
+ encodedData?: string
+ value?: BigNumber
+ estimateGasFn?: () => Promise
+}
+export function useLazyEstimateGasTxs(): (
+ v: EstimateParams,
+) => Promise<{ gas: BigNumber | null; gasInUsd: number | null }> {
const { account, chainId } = useActiveWeb3React()
const { library } = useWeb3React()
@@ -15,24 +22,20 @@ function useEstimateGasTxs(): (v: EstimateParams) => Promise<{ gas: BigNumber |
const usdPriceNative = tokensPrices[WETH[chainId].wrapped.address] ?? 0
return useCallback(
- async ({ contractAddress, encodedData, value = BigNumber.from(0) }: EstimateParams) => {
- const estimateGasOption = {
- from: account,
- to: contractAddress,
- data: encodedData,
- value,
- }
+ async ({ contractAddress, encodedData, value = BigNumber.from(0), estimateGasFn }: EstimateParams) => {
let formatGas: number | null = null
let gas: BigNumber | null = null
try {
- if (!account || !library) throw new Error()
- const [estimateGas, gasPrice] = await Promise.all([
- library.getSigner().estimateGas(estimateGasOption),
- library.getSigner().getGasPrice(),
- ])
+ if (!account || !library || (!estimateGasFn && !contractAddress)) throw new Error()
+ const getGasFee = estimateGasFn
+ ? estimateGasFn()
+ : library.getSigner().estimateGas({ from: account, to: contractAddress, data: encodedData, value })
+ const [estimateGas, gasPrice] = await Promise.all([getGasFee, library.getSigner().getGasPrice()])
gas = gasPrice && estimateGas ? estimateGas.mul(gasPrice) : null
formatGas = gas ? parseFloat(ethers.utils.formatEther(gas)) : null
- } catch (error) {}
+ } catch (error) {
+ console.log('estimate gas err:', error)
+ }
return {
gas,
@@ -42,4 +45,30 @@ function useEstimateGasTxs(): (v: EstimateParams) => Promise<{ gas: BigNumber |
[account, library, usdPriceNative],
)
}
+
+function useEstimateGasTxs({ contractAddress, value, encodedData, estimateGasFn }: EstimateParams) {
+ const [gasInfo, setGasInfo] = useState<{ gas: BigNumber | null; gasInUsd: number | null }>({
+ gas: null,
+ gasInUsd: null,
+ })
+
+ const estimateGas = useLazyEstimateGasTxs()
+
+ const params = useMemo(() => {
+ return { contractAddress, value, encodedData, estimateGasFn }
+ }, [contractAddress, value, encodedData, estimateGasFn])
+
+ useEffect(() => {
+ const controller = new AbortController()
+ const getGasFee = async () => {
+ const data = await estimateGas(params)
+ if (controller.signal.aborted) return
+ setGasInfo(data)
+ }
+ getGasFee()
+ return () => controller.abort()
+ }, [params, estimateGas])
+
+ return gasInfo
+}
export default useEstimateGasTxs