diff --git a/.env b/.env index ff60e51de2..86f7fed140 100644 --- a/.env +++ b/.env @@ -39,3 +39,4 @@ VITE_CAMPAIGN_URL=https://kyberswap-arbitrum-stip.kyberengineering.io/api VITE_REFERRAL_URL=https://referral.kyberswap.com/api VITE_TOKEN_API_URL=https://pre-token-api.kyberengineering.io/api +VITE_ZAP_EARN_URL=https://pre-zap-earn-service.kyberengineering.io/api diff --git a/.env.dev b/.env.dev index 72870471f9..a4b261fd98 100644 --- a/.env.dev +++ b/.env.dev @@ -40,3 +40,4 @@ VITE_CAMPAIGN_URL=https://kyberswap-arbitrum-stip.kyberengineering.io/api VITE_REFERRAL_URL=https://referral.kyberswap.com/api VITE_TOKEN_API_URL=https://pre-token-api.kyberengineering.io/api +VITE_ZAP_EARN_URL=https://pre-zap-earn-service.kyberengineering.io/api diff --git a/.env.production b/.env.production index adadcd6137..f73fef8b46 100644 --- a/.env.production +++ b/.env.production @@ -39,3 +39,4 @@ VITE_CAMPAIGN_URL=https://kyberswap-arbitrum-stip.kyberengineering.io/api VITE_REFERRAL_URL=https://referral.kyberswap.com/api VITE_TOKEN_API_URL=https://token-api.kyberengineering.io/api +VITE_ZAP_EARN_URL=https://pre-zap-earn-service.kyberengineering.io/api diff --git a/.env.stg b/.env.stg index 9b57746e81..fdddd58f3f 100644 --- a/.env.stg +++ b/.env.stg @@ -37,3 +37,4 @@ VITE_CAMPAIGN_URL=https://kyberswap-arbitrum-stip.kyberengineering.io/api VITE_REFERRAL_URL=https://referral.kyberswap.com/api VITE_TOKEN_API_URL=https://pre-token-api.kyberengineering.io/api +VITE_ZAP_EARN_URL=https://pre-zap-earn-service.kyberengineering.io/api diff --git a/package.json b/package.json index 0e843f7d37..5b26b022dc 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", + "kyberswap-liquidity-widgets": "^1.1.46", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/src/assets/images/earn-bg.png b/src/assets/images/earn-bg.png new file mode 100644 index 0000000000..086ac0666e Binary files /dev/null and b/src/assets/images/earn-bg.png differ diff --git a/src/assets/svg/cursor.svg b/src/assets/svg/cursor.svg new file mode 100644 index 0000000000..274df257e6 --- /dev/null +++ b/src/assets/svg/cursor.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svg/fire.svg b/src/assets/svg/fire.svg new file mode 100644 index 0000000000..6553da38b9 --- /dev/null +++ b/src/assets/svg/fire.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/help-circle.svg b/src/assets/svg/help-circle.svg new file mode 100644 index 0000000000..9f74035e94 --- /dev/null +++ b/src/assets/svg/help-circle.svg @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/src/assets/svg/ic_left_arrow.svg b/src/assets/svg/ic_left_arrow.svg new file mode 100644 index 0000000000..6b8df42051 --- /dev/null +++ b/src/assets/svg/ic_left_arrow.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/assets/svg/ic_pool_high_apr.svg b/src/assets/svg/ic_pool_high_apr.svg new file mode 100644 index 0000000000..a53bbd26a8 --- /dev/null +++ b/src/assets/svg/ic_pool_high_apr.svg @@ -0,0 +1,14 @@ + + + + + + + \ No newline at end of file diff --git a/src/assets/svg/ic_pool_highlighted.svg b/src/assets/svg/ic_pool_highlighted.svg new file mode 100644 index 0000000000..f75f1817b9 --- /dev/null +++ b/src/assets/svg/ic_pool_highlighted.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/src/assets/svg/ic_pool_low_volatility.svg b/src/assets/svg/ic_pool_low_volatility.svg new file mode 100644 index 0000000000..943eeeb223 --- /dev/null +++ b/src/assets/svg/ic_pool_low_volatility.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/svg/ic_pool_solid_earning.svg b/src/assets/svg/ic_pool_solid_earning.svg new file mode 100644 index 0000000000..6c9f4f6298 --- /dev/null +++ b/src/assets/svg/ic_pool_solid_earning.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/src/assets/svg/ic_user_earn_position.svg b/src/assets/svg/ic_user_earn_position.svg new file mode 100644 index 0000000000..a04d03038d --- /dev/null +++ b/src/assets/svg/ic_user_earn_position.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/liquidity-pools.svg b/src/assets/svg/liquidity-pools.svg new file mode 100644 index 0000000000..bd62353ea6 --- /dev/null +++ b/src/assets/svg/liquidity-pools.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/svg/liquidity-positions.svg b/src/assets/svg/liquidity-positions.svg new file mode 100644 index 0000000000..a66214e69c --- /dev/null +++ b/src/assets/svg/liquidity-positions.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/svg/low-volatility.svg b/src/assets/svg/low-volatility.svg new file mode 100644 index 0000000000..75a48cfbba --- /dev/null +++ b/src/assets/svg/low-volatility.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/svg/play-icon.svg b/src/assets/svg/play-icon.svg new file mode 100644 index 0000000000..496aec879e --- /dev/null +++ b/src/assets/svg/play-icon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/svg/solid-earning.svg b/src/assets/svg/solid-earning.svg new file mode 100644 index 0000000000..d3b6facf36 --- /dev/null +++ b/src/assets/svg/solid-earning.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svg/staking.svg b/src/assets/svg/staking.svg new file mode 100644 index 0000000000..f4812c0fac --- /dev/null +++ b/src/assets/svg/staking.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/CoinbaseSubscribeBtn.tsx b/src/components/CoinbaseSubscribeBtn.tsx index 8925a40f54..c955cab921 100644 --- a/src/components/CoinbaseSubscribeBtn.tsx +++ b/src/components/CoinbaseSubscribeBtn.tsx @@ -71,8 +71,8 @@ export default function CoinbaseSubscribeBtn({ onlyShowIfNotSubscribe = false }: size={13} text={ !isSubscribed - ? "Subscribe to receive Kyberswap's updates directly on your Coinbase Wallet." - : "Unsubscribe to stop receiving Kyberswap's updates directly on your Coinbase Wallet." + ? "Subscribe to receive KyberSwap's updates directly on your Coinbase Wallet." + : "Unsubscribe to stop receiving KyberSwap's updates directly on your Coinbase Wallet." } /> )} diff --git a/src/components/Copy/index.tsx b/src/components/Copy/index.tsx index e4cca029de..3977b466e8 100644 --- a/src/components/Copy/index.tsx +++ b/src/components/Copy/index.tsx @@ -106,7 +106,7 @@ const CopyHelper = forwardRef(function CopyHelper( ) return ( - + e.stopPropagation()} margin={margin} style={style}> {text ? ( {copyIcon} {text} diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx index d2381332b1..e53bb9c0d5 100644 --- a/src/components/Header/index.tsx +++ b/src/components/Header/index.tsx @@ -8,7 +8,7 @@ import styled from 'styled-components' import Announcement from 'components/Announcement' import SelectNetwork from 'components/Header/web3/SelectNetwork' import SelectWallet from 'components/Header/web3/SelectWallet' -import Menu from 'components/Menu' +import Menu, { NewLabel } from 'components/Menu' import Row, { RowFixed } from 'components/Row' import { AGGREGATOR_ANALYTICS_URL, APP_PATHS } from 'constants/index' import { Z_INDEXS } from 'constants/styles' @@ -18,7 +18,6 @@ import { useHolidayMode } from 'state/user/hooks' import { MEDIA_WIDTHS } from 'theme' import AboutNavGroup from './groups/AboutNavGroup' -import CampaignNavGroup from './groups/CampaignNavGroup' import KyberDAONavGroup from './groups/KyberDaoGroup' import SwapNavGroup from './groups/SwapNavGroup' import { StyledNavExternalLink, StyledNavLink } from './styleds' @@ -208,10 +207,12 @@ export default function Header() { {!isPartnerSwap && ( - + + Earn + New + Market - Analytics diff --git a/src/components/Image/index.tsx b/src/components/Image/index.tsx new file mode 100644 index 0000000000..7a5ec977bc --- /dev/null +++ b/src/components/Image/index.tsx @@ -0,0 +1,32 @@ +import HelpIcon from 'assets/svg/help-circle.svg' + +export function Image({ + src, + alt, + className, + width, + height, + style, +}: { + src: string + alt: string + className?: string + width?: string + height?: string + style?: React.CSSProperties +}) { + return ( + {alt} { + currentTarget.onerror = null // prevents looping + currentTarget.src = HelpIcon + }} + /> + ) +} diff --git a/src/components/Menu/index.tsx b/src/components/Menu/index.tsx index f9cdea3c50..fa6cd53325 100644 --- a/src/components/Menu/index.tsx +++ b/src/components/Menu/index.tsx @@ -220,7 +220,6 @@ export default function Menu() { const showAbout = useMedia(`(max-width: ${MEDIA_WIDTHS.upToExtraSmall}px)`) const showBlog = useMedia(`(max-width: ${MEDIA_WIDTHS.upToExtraSmall}px)`) const showAnalytics = useMedia(`(max-width: ${MEDIA_WIDTHS.upToExtraSmall}px)`) - const showCampaigns = useMedia(`(max-width: ${MEDIA_WIDTHS.upToXXSmall}px)`) const bridgeLink = networkInfo.bridgeURL const toggleClaimPopup = useToggleModal(ApplicationModal.CLAIM_POPUP) @@ -349,26 +348,23 @@ export default function Menu() { )} - {showCampaigns && ( - - } - title={ - - Campaigns - New - - } - link="#" - options={[ - { link: APP_PATHS.AGGREGATOR_CAMPAIGN, label: t`Aggregator Trading` }, - { link: APP_PATHS.LIMIT_ORDER_CAMPAIGN, label: t`Limit Order` }, - { link: APP_PATHS.REFFERAL_CAMPAIGN, label: t`Referral` }, - { link: APP_PATHS.MY_DASHBOARD, label: t`My Dashboard`, external: true }, - ]} - /> - - )} + + } + title={ + + Campaigns + + } + link="#" + options={[ + { link: APP_PATHS.AGGREGATOR_CAMPAIGN, label: t`Aggregator Trading` }, + { link: APP_PATHS.LIMIT_ORDER_CAMPAIGN, label: t`Limit Order` }, + { link: APP_PATHS.REFFERAL_CAMPAIGN, label: t`Referral` }, + { link: APP_PATHS.MY_DASHBOARD, label: t`My Dashboard`, external: true }, + ]} + /> + {bridgeLink && ( diff --git a/src/components/Modal/index.tsx b/src/components/Modal/index.tsx index 4c68967782..8a825c2468 100644 --- a/src/components/Modal/index.tsx +++ b/src/components/Modal/index.tsx @@ -24,9 +24,20 @@ const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ zindex: string | num const AnimatedDialogContent = motion(DialogContent) // destructure to not pass custom props to Dialog DOM element const StyledDialogContent = styled( - ({ borderRadius, minHeight, maxHeight, maxWidth, width, height, bgColor, mobile, isOpen, margin, ...rest }) => ( - - ), + ({ + borderRadius, + minHeight, + maxHeight, + maxWidth, + width, + height, + bgColor, + mobile, + isOpen, + margin, + mobileFullWidth, + ...rest + }) => , ).attrs({ 'aria-label': 'dialog', })` @@ -61,8 +72,13 @@ const StyledDialogContent = styled( width: ${width || '65vw'}; margin: 0; `} - ${({ theme, mobile, borderRadius }) => theme.mediaWidth.upToSmall` - width: 85vw; + ${({ theme, mobile, borderRadius, mobileFullWidth }) => theme.mediaWidth.upToSmall` + ${ + !mobileFullWidth && + ` + width: 85vw; + ` + } ${ mobile && ` @@ -95,6 +111,7 @@ export interface ModalProps { enableSwipeGesture?: boolean bypassScrollLock?: boolean bypassFocusLock?: boolean + mobileFullWidth?: boolean } export default function Modal({ @@ -118,6 +135,7 @@ export default function Modal({ enableSwipeGesture = false, bypassScrollLock = false, bypassFocusLock = false, + mobileFullWidth = false, }: ModalProps) { const animateValues = { initial: { opacity: 0 }, @@ -160,6 +178,7 @@ export default function Modal({ bgColor={bgColor} borderRadius={borderRadius} mobile={isMobile} + mobileFullWidth={mobileFullWidth} className={className} {...animateValues} > diff --git a/src/components/SwapForm/AddMEVProtectionModal.tsx b/src/components/SwapForm/AddMEVProtectionModal.tsx index bcb1d33279..b7f71e3577 100644 --- a/src/components/SwapForm/AddMEVProtectionModal.tsx +++ b/src/components/SwapForm/AddMEVProtectionModal.tsx @@ -55,7 +55,7 @@ export default function AddMEVProtectionModal({ isOpen, onClose }: { isOpen: boo return } - const name = 'Ethereum Mainnet (Kyberswap RPC)' + const name = 'Ethereum Mainnet (KyberSwap RPC)' mixpanelHandler(MIXPANEL_TYPE.MEV_ADD_CLICK_MODAL, { type: name }) addNewNetwork( ChainId.MAINNET, diff --git a/src/constants/index.ts b/src/constants/index.ts index 8a32a77b5f..ea33bf743e 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -169,6 +169,11 @@ export const APP_PATHS = { LIMIT_ORDER_CAMPAIGN: '/campaigns/limit-order', REFFERAL_CAMPAIGN: '/campaigns/referrals', MY_DASHBOARD: '/campaigns/dashboard', + + EARN: '/earns', + EARN_POOLS: '/earns/pools', + EARN_POSITIONS: '/earns/positions', + EARN_POSITION_DETAIL: '/earns/position/:id', } as const export const TERM_FILES_PATH = { diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 1e01308fe7..f262792ac9 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -62,6 +62,11 @@ const NotificationCenter = lazy(() => import('pages/NotificationCenter')) const Campaign = lazy(() => import('pages/Campaign')) const CampaignMyDashboard = lazy(() => import('pages/Campaign/MyDashboard')) +const Earns = lazy(() => import('pages/Earns')) +const EarnPoolExplorer = lazy(() => import('pages/Earns/PoolExplorer')) +const EarnUserPositions = lazy(() => import('pages/Earns/UserPositions')) +const EarnPositionDetail = lazy(() => import('pages/Earns/PositionDetail')) + const AppWrapper = styled.div` display: flex; flex-flow: column; @@ -317,6 +322,11 @@ export default function App() { } /> } /> + } /> + } /> + } /> + } /> + } /> diff --git a/src/pages/Earns/PoolExplorer/DropdownMenu.tsx b/src/pages/Earns/PoolExplorer/DropdownMenu.tsx new file mode 100644 index 0000000000..a02809dc4a --- /dev/null +++ b/src/pages/Earns/PoolExplorer/DropdownMenu.tsx @@ -0,0 +1,152 @@ +import { useEffect, useMemo, useRef, useState } from 'react' +import { useMedia } from 'react-use' +import styled from 'styled-components' + +import { ReactComponent as DropdownSVG } from 'assets/svg/down.svg' +import { MEDIA_WIDTHS } from 'theme' + +const DropdownWrapper = styled.div` + position: relative; + width: fit-content; +` + +const DropdownTitleWrapper = styled.div` + background: ${({ theme }) => theme.background}; + border-radius: 30px; + padding: 6px 12px; + font-size: 14px; + cursor: pointer; + color: ${({ theme }) => theme.subText}; + display: flex; + align-items: center; + justify-content: center; +` + +const DropdownTitle = styled.div<{ width?: number }>` + width: ${({ width }) => (width ? `${width}px` : '')}; + min-width: ${({ width }) => (!width ? '100px' : 'max-content')}; + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + text-transform: capitalize; + + ${({ theme }) => theme.mediaWidth.upToExtraSmall` + min-width: max-content; + `} +` + +const DropdownIcon = styled(DropdownSVG)<{ open: boolean }>` + transform: ${({ open }) => (open ? 'rotate(180deg)' : 'rotate(0deg)')}; + transition: transform 0.3s; +` + +const ItemIcon = styled.img` + width: 18px; + height: 18px; +` + +const DropdownContent = styled.div<{ alignLeft: boolean }>` + position: absolute; + top: 42px; + left: 0; + background: ${({ theme }) => theme.background}; + border-radius: 24px; + padding: 8px 12px; + font-size: 14px; + color: ${({ theme }) => theme.text}; + width: max-content; + display: flex; + flex-direction: column; + align-items: ${({ alignLeft }) => (alignLeft ? 'flex-start' : 'center')}; + gap: 4px; + max-height: 218px; + overflow-y: auto; + z-index: 100; + filter: brightness(1.2); +` + +const DropdownContentItem = styled.div` + padding: 8px; + border-radius: 30px; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + cursor: pointer; + text-transform: capitalize; + + &:hover { + background: ${({ theme }) => theme.tableHeader}; + } +` + +export interface MenuOption { + label: string + value: string | number + icon?: string +} + +const DropdownMenu = ({ + options, + value, + width, + alignLeft = false, + onChange, +}: { + options: MenuOption[] + value: string | number + width?: number + alignLeft?: boolean + onChange: (value: string | number) => void +}) => { + const upToExtraSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToExtraSmall}px)`) + + const [open, setOpen] = useState(false) + const ref = useRef(null) + + const optionValue = useMemo(() => options.find(option => option.value === value), [options, value]) + + const handleOpenChange = () => setOpen(!open) + + const handleSelectItem = (newValue: string | number) => { + onChange(newValue) + setOpen(false) + } + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (!ref?.current?.contains(event.target as Node)) setOpen(false) + } + + document.addEventListener('mousedown', handleClickOutside) + + return () => { + document.removeEventListener('mousedown', handleClickOutside) + } + }, [ref]) + + return ( + + + + {optionValue?.icon && } + {(!upToExtraSmall || !optionValue?.icon) && optionValue?.label} + + + + {open && ( + + {options.map((option: MenuOption) => ( + handleSelectItem(option.value)}> + {option.icon && } + {option.label} + + ))} + + )} + + ) +} + +export default DropdownMenu diff --git a/src/pages/Earns/PoolExplorer/TableContent.tsx b/src/pages/Earns/PoolExplorer/TableContent.tsx new file mode 100644 index 0000000000..bb763f3f7e --- /dev/null +++ b/src/pages/Earns/PoolExplorer/TableContent.tsx @@ -0,0 +1,265 @@ +import { useEffect, useMemo, useState } from 'react' +import { Star } from 'react-feather' +import { useMedia } from 'react-use' +import { Flex, Text } from 'rebass' +import { useGetDexListQuery } from 'services/ksSetting' +import { EarnPool, useAddFavoriteMutation, usePoolsExplorerQuery, useRemoveFavoriteMutation } from 'services/zapEarn' + +import { NotificationType } from 'components/Announcement/type' +import CopyHelper from 'components/Copy' +import { Image } from 'components/Image' +import Loader from 'components/Loader' +import { NETWORKS_INFO } from 'constants/networks' +import { useActiveWeb3React, useWeb3React } from 'hooks' +import useTheme from 'hooks/useTheme' +import { useNotify, useWalletModalToggle } from 'state/application/hooks' +import { MEDIA_WIDTHS } from 'theme' +import { formatDisplayNumber } from 'utils/numbers' + +import { + Apr, + CurrencyRoundedImage, + CurrencySecondImage, + FeeTier, + MobileTableBottomRow, + MobileTableRow, + SymbolText, + TableBody, + TableRow, +} from './styles' +import useFilter from './useFilter' + +const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPool) => void }) => { + const theme = useTheme() + const { account } = useActiveWeb3React() + const { library } = useWeb3React() + const { filters } = useFilter() + const notify = useNotify() + const toggleWalletModal = useWalletModalToggle() + + const dexList = useGetDexListQuery({ + chainId: NETWORKS_INFO[filters.chainId].ksSettingRoute, + }) + const { data: poolData, refetch } = usePoolsExplorerQuery(filters, { pollingInterval: 5 * 60_000 }) + const [addFavorite] = useAddFavoriteMutation() + const [removeFavorite] = useRemoveFavoriteMutation() + + const [favoriteLoading, setFavoriteLoading] = useState([]) + const [delayFavorite, setDelayFavorite] = useState(false) + + const upToMedium = useMedia(`(max-width: ${MEDIA_WIDTHS.upToMedium}px)`) + + const tablePoolData = useMemo(() => { + return (poolData?.data?.pools || []).map(pool => ({ + ...pool, + dexLogo: dexList.data?.find(dex => dex.dexId === pool.exchange)?.logoURL || '', + dexName: dexList.data?.find(dex => dex.dexId === pool.exchange)?.name || '', + })) + }, [poolData, dexList]) + + const handleFavorite = async (e: React.MouseEvent, pool: EarnPool) => { + e.stopPropagation() + if (favoriteLoading.includes(pool.address) || delayFavorite) return + handleAddFavoriteLoading(pool.address) + + if (!account) { + toggleWalletModal() + handleRemoveFavoriteLoading(pool.address) + return + } + + let signature = '' + let msg = '' + + const key = `poolExplorer_${account}` + try { + const data = JSON.parse(localStorage.getItem(key) || '') + if (data.issuedAt) { + const expire = new Date(data.issuedAt) + expire.setDate(expire.getDate() + 7) + const now = new Date() + if (expire > now) { + signature = data.signature + msg = data.msg + } + } + } catch { + handleRemoveFavoriteLoading(pool.address) + } + if (!signature) { + const issuedAt = new Date().toISOString() + msg = `Click sign to add favorite pools at Kyberswap.com without logging in.\nThis request won’t trigger any blockchain transaction or cost any gas fee. Expires in 7 days. \n\nIssued at: ${issuedAt}` + signature = await library?.send('personal_sign', [`0x${Buffer.from(msg, 'utf8').toString('hex')}`, account]) + localStorage.setItem( + key, + JSON.stringify({ + signature, + msg, + issuedAt, + }), + ) + } + + const isPoolFavorite = !!pool.favorite?.isFavorite + setDelayFavorite(true) + await (isPoolFavorite ? removeFavorite : addFavorite)({ + chainId: filters.chainId, + userAddress: account, + poolAddress: pool.address, + message: msg, + signature, + }) + .then(res => { + if ((res as any).error) { + notify( + { + title: `${!isPoolFavorite ? 'Add' : 'Remove'} failed`, + summary: (res as any).error.data.message || 'Some thing went wrong', + type: NotificationType.ERROR, + }, + 8000, + ) + } else refetch() + }) + .catch(err => { + // localStorage.removeItem(key) + console.log(err) + notify( + { + title: `${!isPoolFavorite ? 'Add' : 'Remove'} failed`, + summary: err.message || 'Some thing went wrong', + type: NotificationType.ERROR, + }, + 8000, + ) + }) + .finally(() => handleRemoveFavoriteLoading(pool.address)) + } + const handleAddFavoriteLoading = (poolAddress: string) => { + if (!favoriteLoading.includes(poolAddress)) setFavoriteLoading([...favoriteLoading, poolAddress]) + } + const handleRemoveFavoriteLoading = (poolAddress: string) => + setFavoriteLoading(favoriteLoading.filter(address => address !== poolAddress)) + + useEffect(() => { + if (delayFavorite) + setTimeout(() => { + setDelayFavorite(false) + }, 500) + }, [delayFavorite]) + + if (!tablePoolData?.length) + return ( + + No data found + + ) + + if (upToMedium) + return ( + + {tablePoolData.map((pool, index) => ( + onOpenZapInWidget(pool)}> + + + + + + + + + + {pool.tokens?.[0]?.symbol}/{pool.tokens?.[1]?.symbol} + + + + + + {pool.feeTier}% + + + + + 0}> + {formatDisplayNumber(pool.apr, { significantDigits: pool.apr < 1 ? 2 : pool.apr < 10 ? 3 : 4 })}% + + handleFavorite(e, pool)} + /> + + + + + Earn Fees + {formatDisplayNumber(pool.earnFee, { style: 'currency', significantDigits: 6 })} + + + TVL + {formatDisplayNumber(pool.tvl, { style: 'currency', significantDigits: 6 })} + + + Volume + {formatDisplayNumber(pool.volume, { style: 'currency', significantDigits: 6 })} + + + + ))} + + ) + + return ( + + {tablePoolData.map(pool => ( + onOpenZapInWidget(pool)}> + + + {pool.dexName} + + + + + + + + {pool.tokens?.[0]?.symbol}/{pool.tokens?.[1]?.symbol} + + {pool.feeTier}% + + 0}> + {formatDisplayNumber(pool.apr, { significantDigits: pool.apr < 1 ? 2 : pool.apr < 10 ? 3 : 4 })}% + + + {formatDisplayNumber(pool.earnFee, { style: 'currency', significantDigits: 6 })} + + + {formatDisplayNumber(pool.tvl, { style: 'currency', significantDigits: 6 })} + + + {formatDisplayNumber(pool.volume, { style: 'currency', significantDigits: 6 })} + + + {favoriteLoading.includes(pool.address) ? ( + + ) : ( + handleFavorite(e, pool)} + /> + )} + + + ))} + + ) +} + +export default TableContent diff --git a/src/pages/Earns/PoolExplorer/index.tsx b/src/pages/Earns/PoolExplorer/index.tsx new file mode 100644 index 0000000000..ea346b8080 --- /dev/null +++ b/src/pages/Earns/PoolExplorer/index.tsx @@ -0,0 +1,278 @@ +import { t } from '@lingui/macro' +import 'kyberswap-liquidity-widgets/dist/style.css' +import { useEffect, useState } from 'react' +import { Star } from 'react-feather' +import { useNavigate, useSearchParams } from 'react-router-dom' +import { useMedia } from 'react-use' +import { Flex, Text } from 'rebass' +import { usePoolsExplorerQuery } from 'services/zapEarn' + +import { ReactComponent as IconHighAprPool } from 'assets/svg/ic_pool_high_apr.svg' +import { ReactComponent as IconHighlightedPool } from 'assets/svg/ic_pool_highlighted.svg' +import { ReactComponent as IconLowVolatility } from 'assets/svg/ic_pool_low_volatility.svg' +import { ReactComponent as IconSolidEarningPool } from 'assets/svg/ic_pool_solid_earning.svg' +import { ReactComponent as IconUserEarnPosition } from 'assets/svg/ic_user_earn_position.svg' +import Pagination from 'components/Pagination' +import Search from 'components/Search' +import { MouseoverTooltip } from 'components/Tooltip' +import { APP_PATHS } from 'constants/index' +import useDebounce from 'hooks/useDebounce' +import useTheme from 'hooks/useTheme' +import useSupportedDexesAndChains from 'pages/Earns/PoolExplorer/useSupportedDexesAndChains' +import SortIcon, { Direction } from 'pages/MarketOverview/SortIcon' +import { MEDIA_WIDTHS } from 'theme' + +import useLiquidityWidget from '../useLiquidityWidget' +import DropdownMenu, { MenuOption } from './DropdownMenu' +import TableContent from './TableContent' +import { + ContentWrapper, + HeadSection, + PoolPageWrapper, + TableHeader, + TableWrapper, + Tag, + TagContainer, + UserPositionButton, +} from './styles' +import useFilter from './useFilter' + +export enum FilterTag { + HIGHLIGHTED_POOL = 'highlighted_pool', + HIGH_APR = 'high_apr', + SOLID_EARNING = 'solid_earning', + LOW_VOLATILITY = 'low_volatility', +} + +export enum SortBy { + APR = 'apr', + EARN_FEE = 'earn_fee', + TVL = 'tvl', + VOLUME = 'volume', +} + +const filterTags = [ + { + label: 'Highlighted Pools', + value: FilterTag.HIGHLIGHTED_POOL, + icon: , + tooltip: '', + }, + { + label: 'High APR', + value: FilterTag.HIGH_APR, + icon: , + tooltip: 'Top 100 Pools with assets that offer exceptionally high APYs', + }, + { + label: 'Solid Earning', + value: FilterTag.SOLID_EARNING, + icon: , + tooltip: 'Top 100 pools that have the high total earned fee in the last 7 days', + }, + { + label: 'Low Volatility', + value: FilterTag.LOW_VOLATILITY, + icon: , + tooltip: 'Top 100 highest TVL Pools consisting of stable coins or correlated pairs', + }, +] + +export const timings: MenuOption[] = [ + { label: '24h', value: '24h' }, + { label: '7d', value: '7d' }, + { label: '30d', value: '30d' }, +] + +const Earn = () => { + const navigate = useNavigate() + const [search, setSearch] = useState('') + const deboundedSearch = useDebounce(search, 300) + const [searchParams] = useSearchParams() + const theme = useTheme() + const { filters, updateFilters } = useFilter(setSearch) + const { liquidityWidget, handleOpenZapInWidget } = useLiquidityWidget() + const { data: poolData } = usePoolsExplorerQuery(filters, { pollingInterval: 5 * 60_000 }) + const { supportedDexes, supportedChains } = useSupportedDexesAndChains(filters) + + const upToExtraSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToExtraSmall}px)`) + const upToMedium = useMedia(`(max-width: ${MEDIA_WIDTHS.upToMedium}px)`) + const upToLarge = useMedia(`(max-width: ${MEDIA_WIDTHS.upToLarge}px)`) + + const onChainChange = (newChainId: string | number) => { + updateFilters('chainId', newChainId.toString()) + } + const onProtocolChange = (newProtocol: string | number) => { + updateFilters('protocol', newProtocol.toString()) + } + const onIntervalChange = (newInterval: string | number) => { + updateFilters('interval', newInterval.toString()) + } + const onSortChange = (sortBy: string) => { + if (!filters.sortBy || filters.sortBy !== sortBy) { + updateFilters('sortBy', sortBy) + updateFilters('orderBy', Direction.DESC) + return + } + if (filters.orderBy === Direction.DESC) { + updateFilters('orderBy', Direction.ASC) + return + } + updateFilters('sortBy', '') + updateFilters('orderBy', '') + } + + useEffect(() => { + if (searchParams.get('q') && !search) { + setSearch(searchParams.get('q') || '') + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + useEffect(() => { + if (filters.q !== deboundedSearch) { + updateFilters('q', deboundedSearch || '') + } + }, [deboundedSearch, filters.q, updateFilters]) + + return ( + + {liquidityWidget} + +
+ + {t`Earning with Smart Liquidity Providing`} + + + {t`KyberSwap Zap: Instantly and easily add liquidity to high-APY pools using any token or a combination of tokens.`} + +
+ + + updateFilters('tag', '')}> + {t`All pools`} + + + updateFilters('tag', 'favorite')}> + + + + {filterTags.map((item, index) => + !upToMedium ? ( + + updateFilters('tag', item.value)} + > + {!upToExtraSmall && item.icon} + {item.label} + + + ) : ( + updateFilters('tag', item.value)} + > + {!upToExtraSmall && item.icon} + {item.label} + + ), + )} + + {!upToLarge && ( + navigate({ pathname: APP_PATHS.EARN_POSITIONS })}> + + {t`My Positions`} + + )} + + + + + + + + setSearch(val)} + style={{ height: '36px' }} + /> + + + + {!upToMedium && ( + + Protocol + Pair + onSortChange(SortBy.APR)} + > + APR + + + onSortChange(SortBy.EARN_FEE)} + > + Earn Fees + + + onSortChange(SortBy.TVL)} + > + TVL + + + onSortChange(SortBy.VOLUME)} + > + Volume + + +
+ + )} + + + updateFilters('page', newPage.toString())} + totalCount={poolData?.data?.pagination?.totalItems || 0} + currentPage={filters.page || 1} + pageSize={filters.limit || 10} + /> + + + {t`KyberSwap provides tools for tracking & adding liquidity to third-party Protocols. For any pool-related concerns, please contact the respective Liquidity Protocol directly.`} + + ) +} + +export default Earn diff --git a/src/pages/Earns/PoolExplorer/styles.tsx b/src/pages/Earns/PoolExplorer/styles.tsx new file mode 100644 index 0000000000..3a5ac9745a --- /dev/null +++ b/src/pages/Earns/PoolExplorer/styles.tsx @@ -0,0 +1,179 @@ +import { rgba } from 'polished' +import styled from 'styled-components' + +import { Image } from 'components/Image' + +export const PoolPageWrapper = styled.div` + padding: 32px 24px 50px; + width: 100%; + max-width: 1500px; + display: flex; + flex-direction: column; + gap: 16px; + + ${({ theme }) => theme.mediaWidth.upToSmall` + padding: 24px 16px 100px; + `} +` + +export const LiquidityWidgetWrapper = styled.div` + width: 100%; + display: flex; + justify-content: center; +` + +export const HeadSection = styled.div` + display: flex; + width: 100%; + align-items: center; + justify-content: space-between; +` + +export const TagContainer = styled.div` + display: flex; + gap: 1rem; + width: 100%; + overflow-x: auto; + + ${({ theme }) => theme.mediaWidth.upToSmall` + gap: 0.75rem; + `} +` + +export const Tag = styled.div<{ active: boolean }>` + background: ${({ theme, active }) => (active ? rgba(theme.primary, 0.2) : theme.background)}; + border: 1px solid ${({ theme, active }) => (active ? theme.primary : 'transparent')}; + border-radius: 12px; + padding: 4px 16px; + font-size: 14px; + cursor: pointer; + color: ${({ theme, active }) => (active ? theme.text : theme.subText)}; + font-weight: ${({ active }) => (active ? '500' : '400')}; + display: flex; + align-items: center; + gap: 8px; + flex: 0 0 auto; + line-height: 28px; + height: 42px; + + ${({ theme }) => theme.mediaWidth.upToMedium` + height: 38px; + `} +` + +export const UserPositionButton = styled.div` + display: flex; + align-items: center; + gap: 10px; + background-color: ${({ theme }) => rgba(theme.primary, 0.1)}; + color: ${({ theme }) => theme.subText}; + border-radius: 12px; + padding: 8px 16px; + width: max-content; + font-size: 14px; + cursor: pointer; + + :hover { + filter: brightness(1.1); + } +` + +export const TableWrapper = styled.div` + background: ${({ theme }) => rgba(theme.background, 0.8)}; + border-radius: 16px; + overflow: hidden; + + ${({ theme }) => theme.mediaWidth.upToMedium` + margin: 0 -16px; + border-radius: 0; + `} +` + +export const ContentWrapper = styled.div`` + +export const TableHeader = styled.div` + display: grid; + grid-template-columns: 1fr 1fr 0.5fr 1fr 1fr 1fr 80px; + align-items: center; + color: ${({ theme }) => theme.subText}; + border-bottom: 1px solid ${({ theme }) => theme.tableHeader}; + padding-bottom: 24px; + margin: 24px; + margin-bottom: 0; + + ${({ theme }) => theme.mediaWidth.upToLarge` + grid-template-columns: 1fr 1.2fr 0.5fr 1fr 1fr 1fr 80px; + `} +` + +export const TableBody = styled.div` + max-height: 720px; + overflow-y: auto; +` + +export const TableRow = styled.div` + display: grid; + grid-template-columns: 1fr 1fr 0.5fr 1fr 1fr 1fr 80px; + padding: 24px; + cursor: pointer; + + :hover { + background: #31cb9e1a; + } + + ${({ theme }) => theme.mediaWidth.upToLarge` + grid-template-columns: 1fr 1.2fr 0.5fr 1fr 1fr 1fr 80px; + `} +` + +export const FeeTier = styled.div` + border-radius: 30px; + padding: 4px 8px; + font-size: 12px; + background: ${({ theme }) => rgba(theme.white, 0.04)}; + color: ${({ theme }) => theme.subText}; + width: fit-content; + + ${({ theme }) => theme.mediaWidth.upToExtraSmall` + font-size: 14px; + `} +` + +export const CurrencyRoundedImage = styled(Image)` + border-radius: 50%; + width: 24px; + height: 24px; +` + +export const CurrencySecondImage = styled(CurrencyRoundedImage)` + position: relative; + left: -6px; +` + +export const SymbolText = styled.div` + max-width: 115px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +` + +export const Apr = styled.div<{ positive: boolean }>` + display: flex; + justify-content: flex-end; + color: ${({ positive, theme }) => (positive ? theme.primary : theme.red)}; +` + +export const MobileTableRow = styled.div` + padding: 28px 24px 0; + cursor: pointer; + + :hover { + background: ${({ theme }) => rgba(theme.primary, 0.2)}; + } +` +export const MobileTableBottomRow = styled.div<{ withoutBorder: boolean }>` + display: grid; + grid-template-columns: 1.5fr 1fr 1fr; + padding: 16px 0; + border-bottom: ${({ withoutBorder, theme }) => (withoutBorder ? 'none' : `1px solid ${theme.tableHeader}`)}; +` diff --git a/src/pages/Earns/PoolExplorer/useFilter.ts b/src/pages/Earns/PoolExplorer/useFilter.ts new file mode 100644 index 0000000000..e635b70e0d --- /dev/null +++ b/src/pages/Earns/PoolExplorer/useFilter.ts @@ -0,0 +1,65 @@ +import { ChainId } from '@kyberswap/ks-sdk-core' +import { useCallback, useMemo } from 'react' +import { useSearchParams } from 'react-router-dom' +import { earnSupportedChains } from 'services/krystalEarn' +import { QueryParams } from 'services/zapEarn' + +import { useActiveWeb3React } from 'hooks' +import { Direction } from 'pages/MarketOverview/SortIcon' + +import { FilterTag, SortBy, timings } from '.' + +export default function useFilter(setSearch?: (search: string) => void) { + const [searchParams, setSearchParams] = useSearchParams() + const { account, chainId } = useActiveWeb3React() + + const filters: QueryParams = useMemo(() => { + return { + chainId: +( + searchParams.get('chainId') || (chainId && earnSupportedChains.includes(chainId) ? chainId : ChainId.MAINNET) + ), + page: +(searchParams.get('page') || 1), + limit: 10, + interval: searchParams.get('interval') || (timings[0].value as string), + protocol: searchParams.get('protocol') || '', + userAddress: account, + tag: searchParams.get('tag') || '', + sortBy: searchParams.get('sortBy') || (!searchParams.get('tag') ? SortBy.TVL : ''), + orderBy: searchParams.get('orderBy') || (!searchParams.get('tag') ? Direction.DESC : ''), + q: searchParams.get('q')?.trim() || '', + } + }, [searchParams, account, chainId]) + + const updateFilters = useCallback( + (key: keyof QueryParams, value: string) => { + if (!value) { + searchParams.delete(key) + if (key === 'tag') { + searchParams.set('sortBy', SortBy.TVL) + searchParams.set('orderBy', Direction.DESC) + } + } else { + searchParams.set(key, value) + if (key === 'chainId') searchParams.delete('protocol') + if (key === 'tag') { + searchParams.delete('sortBy') + searchParams.delete('orderBy') + if (setSearch) setSearch('') + if (value === FilterTag.LOW_VOLATILITY) { + searchParams.set('sortBy', SortBy.APR) + searchParams.set('orderBy', Direction.DESC) + } + } + } + if (key !== 'sortBy' && key !== 'orderBy' && key !== 'page') searchParams.delete('page') + + setSearchParams(searchParams) + }, + [setSearchParams, searchParams, setSearch], + ) + + return { + filters, + updateFilters, + } +} diff --git a/src/pages/Earns/PoolExplorer/useSupportedDexesAndChains.ts b/src/pages/Earns/PoolExplorer/useSupportedDexesAndChains.ts new file mode 100644 index 0000000000..c0aa06a4b2 --- /dev/null +++ b/src/pages/Earns/PoolExplorer/useSupportedDexesAndChains.ts @@ -0,0 +1,40 @@ +import { useMemo } from 'react' +import { useGetDexListQuery } from 'services/ksSetting' +import { QueryParams, useSupportedProtocolsQuery } from 'services/zapEarn' + +import { NETWORKS_INFO } from 'constants/networks' +import useChainsConfig from 'hooks/useChainsConfig' + +const useSupportedDexesAndChains = (filters: QueryParams) => { + const { supportedChains } = useChainsConfig() + const dexList = useGetDexListQuery({ + chainId: NETWORKS_INFO[filters.chainId].ksSettingRoute, + }) + const { data: supportedProtocols } = useSupportedProtocolsQuery() + + const supportedDexes = useMemo(() => { + if (!supportedProtocols?.data?.chains) return [] + const parsedProtocols = + supportedProtocols.data.chains[filters.chainId]?.protocols?.map(item => ({ + label: (dexList?.data?.find(dex => dex.dexId === item.id)?.name || item.name).replaceAll('-', ' '), + value: item.id, + })) || [] + return [{ label: 'All Protocols', value: '' }].concat(parsedProtocols) + }, [filters.chainId, supportedProtocols, dexList]) + + const chains = useMemo( + () => + supportedChains + .map(chain => ({ + label: chain.name, + value: chain.chainId, + icon: chain.icon, + })) + .filter(chain => supportedProtocols?.data?.chains?.[chain.value]), + [supportedChains, supportedProtocols], + ) + + return { supportedDexes, supportedChains: chains } +} + +export default useSupportedDexesAndChains diff --git a/src/pages/Earns/PositionDetail/Header.tsx b/src/pages/Earns/PositionDetail/Header.tsx new file mode 100644 index 0000000000..330b89765b --- /dev/null +++ b/src/pages/Earns/PositionDetail/Header.tsx @@ -0,0 +1,81 @@ +import { ChainId } from '@kyberswap/ks-sdk-core' +import { t } from '@lingui/macro' +import { useNavigate } from 'react-router-dom' +import { useMedia } from 'react-use' +import { Flex, Text } from 'rebass' +import { EarnSupportedProtocols, PositionStatus, earnSupportedProtocols } from 'services/krystalEarn' + +import CopyHelper from 'components/Copy' +import { MouseoverTooltipDesktopOnly } from 'components/Tooltip' +import useTheme from 'hooks/useTheme' +import { MEDIA_WIDTHS } from 'theme' +import { shortenAddress } from 'utils' + +import { ParsedPosition } from '.' +import { CurrencyRoundedImage, CurrencySecondImage } from '../PoolExplorer/styles' +import { Badge, BadgeType, ChainImage, DexImage, ImageContainer, PositionOverview } from '../UserPositions/styles' +import { DexInfo, IconArrowLeft } from './styles' + +const PositionDetailHeader = ({ position }: { position: ParsedPosition }) => { + const theme = useTheme() + const navigate = useNavigate() + const upToSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToSmall}px)`) + + const onOpenPositionInDexSite = () => { + if (!position || !earnSupportedProtocols.includes(position.dex)) return + + if (position.dex === EarnSupportedProtocols.UNISWAP_V3) + window.open(`https://app.uniswap.org/positions/v3/${position.chainName}/${position.id}`) + else if (position.dex === EarnSupportedProtocols.SUSHISWAP_V3) + window.open(`https://www.sushi.com/${position.chainName}/pool/v3/${position.poolAddress}/${position.id}`) + else if (position.dex === EarnSupportedProtocols.PANCAKESWAP_V3) + window.open(`https://pancakeswap.finance/liquidity/${position.id}`) + } + + return ( + + navigate(-1)} /> + + + + + + + + + {position.token0Symbol}/{position.token1Symbol} + + {position.poolFee && {position.poolFee}%} + + + + #24654 + + + ● {position.status === PositionStatus.IN_RANGE ? t`In range` : t`Out of range`} + + + + {position.poolAddress ? shortenAddress(position.chainId as ChainId, position.poolAddress, 4) : ''} + + + + + + + + {position.dex} + + + + + + + ) +} + +export default PositionDetailHeader diff --git a/src/pages/Earns/PositionDetail/LeftSection.tsx b/src/pages/Earns/PositionDetail/LeftSection.tsx new file mode 100644 index 0000000000..952f19e6f3 --- /dev/null +++ b/src/pages/Earns/PositionDetail/LeftSection.tsx @@ -0,0 +1,141 @@ +import { t } from '@lingui/macro' +import { useMemo } from 'react' +import { Flex, Text } from 'rebass' +import { usePositionEarningStatisticsQuery } from 'services/krystalEarn' + +import InfoHelper from 'components/InfoHelper' +import useTheme from 'hooks/useTheme' +import { formatDisplayNumber } from 'utils/numbers' + +import { ParsedPosition } from '.' +import { DexImage } from '../UserPositions/styles' +import { InfoLeftColumn, InfoRight, InfoSection, InfoSectionFirstFormat, VerticalDivider } from './styles' + +const LeftSection = ({ position }: { position: ParsedPosition }) => { + const theme = useTheme() + + const { data: positionEarningStatistics } = usePositionEarningStatisticsQuery( + { + tokenAddress: position.tokenAddress, + tokenId: position.id, + chainId: position.chainId, + }, + { skip: !position, pollingInterval: 15_000 }, + ) + + const earning = useMemo(() => { + if (!positionEarningStatistics || !positionEarningStatistics.length) return {} + + const reversedPositionEarningStatistics = [...positionEarningStatistics].reverse() + const earning24h = reversedPositionEarningStatistics[0].totalEarning + const earning7d = reversedPositionEarningStatistics.slice(0, 7).reduce((a, b) => a + b.totalEarning, 0) + + return { + earning24h, + earning7d, + } + }, [positionEarningStatistics]) + + return ( + + + + {t`Total Liquidity`} + + + + {formatDisplayNumber(position.totalValue, { + style: 'currency', + significantDigits: 4, + })} + + + + {formatDisplayNumber(position.token0TotalAmount, { significantDigits: 6 })} + {position.token0Symbol} + + + + {formatDisplayNumber(position.token1TotalAmount, { significantDigits: 6 })} + {position.token1Symbol} + + + + + + + {t`Est. Position APR`} + + + + 0 ? theme.primary : theme.text}> + {formatDisplayNumber(position.apr * 100, { + significantDigits: position.apr < 0.01 ? 2 : position.apr < 0.1 ? 3 : 4, + })} + % + + + + + {t`Fee Earn`} + + + + + 1 {t`day`} + + {formatDisplayNumber(earning.earning24h, { significantDigits: 4, style: 'currency' })} + + + + + 7 {t`days`} + + {formatDisplayNumber(earning.earning7d, { significantDigits: 4, style: 'currency' })} + + + + + {t`All`} + + + {formatDisplayNumber(position.totalEarnedFee, { style: 'currency', significantDigits: 4 })} + + + + + + + {t`Total Unclaimed Fee`} + + + + {formatDisplayNumber(position.totalUnclaimedFee, { style: 'currency', significantDigits: 4 })} + + + {formatDisplayNumber(position.token0UnclaimedAmount, { significantDigits: 4 })} + {position.token0Symbol} + + {formatDisplayNumber(position.token0UnclaimedValue, { + style: 'currency', + significantDigits: 4, + })} + + + + {formatDisplayNumber(position.token1UnclaimedAmount, { significantDigits: 4 })} + {position.token1Symbol} + + {formatDisplayNumber(position.token1UnclaimedValue, { + style: 'currency', + significantDigits: 4, + })} + + + + + + ) +} + +export default LeftSection diff --git a/src/pages/Earns/PositionDetail/RightSection.tsx b/src/pages/Earns/PositionDetail/RightSection.tsx new file mode 100644 index 0000000000..81b23aca95 --- /dev/null +++ b/src/pages/Earns/PositionDetail/RightSection.tsx @@ -0,0 +1,71 @@ +import { t } from '@lingui/macro' +import { useState } from 'react' +import { Flex, Text } from 'rebass' + +import { Swap as SwapIcon } from 'components/Icons' +import useTheme from 'hooks/useTheme' +import { formatDisplayNumber } from 'utils/numbers' + +import { ParsedPosition } from '.' +import { InfoRightColumn, InfoSection, InfoSectionSecondFormat, RevertIconWrapper } from './styles' + +const RightSection = ({ position }: { position: ParsedPosition }) => { + const theme = useTheme() + const [revert, setRevert] = useState(false) + + return ( + + + + + {t`Current Price`} + + + {formatDisplayNumber(!revert ? position.pairRate : 1 / position.pairRate, { + significantDigits: 6, + })} + + + {!revert ? position.token0Symbol : position.token1Symbol} per{' '} + {!revert ? position.token1Symbol : position.token0Symbol} + + setRevert(!revert)}> + + + + + + + + {t`Min Price`} + + + {formatDisplayNumber(!revert ? position.minPrice : 1 / position.maxPrice, { + significantDigits: 6, + })} + + + {!revert ? position.token0Symbol : position.token1Symbol}/ + {!revert ? position.token1Symbol : position.token0Symbol} + + + + + {t`Max Price`} + + + {formatDisplayNumber(!revert ? position.maxPrice : 1 / position.minPrice, { + significantDigits: 6, + })} + + + {!revert ? position.token0Symbol : position.token1Symbol}/ + {!revert ? position.token1Symbol : position.token0Symbol} + + + + + ) +} + +export default RightSection diff --git a/src/pages/Earns/PositionDetail/index.tsx b/src/pages/Earns/PositionDetail/index.tsx new file mode 100644 index 0000000000..cc501b3fa6 --- /dev/null +++ b/src/pages/Earns/PositionDetail/index.tsx @@ -0,0 +1,145 @@ +import { t } from '@lingui/macro' +import { useEffect, useMemo, useRef } from 'react' +import { useParams } from 'react-router-dom' +import { useUserPositionQuery } from 'services/krystalEarn' + +import LocalLoader from 'components/LocalLoader' +import { useActiveWeb3React } from 'hooks' + +import { EmptyPositionText, PositionPageWrapper } from '../UserPositions/styles' +import useLiquidityWidget from '../useLiquidityWidget' +import PositionDetailHeader from './Header' +import LeftSection from './LeftSection' +import RightSection from './RightSection' +import { MainSection, PositionAction, PositionActionWrapper, PositionDetailWrapper } from './styles' + +export interface ParsedPosition { + id: string + dex: string + dexImage: string + chainId: number + chainName: string + chainLogo: string + poolAddress: string + tokenAddress: string + token0Logo: string + token1Logo: string + token0Symbol: string + token1Symbol: string + poolFee: number + status: string + totalValue: number + apr: number + token0TotalAmount: number + token1TotalAmount: number + minPrice: number + maxPrice: number + pairRate: number + totalUnclaimedFee: number + token0UnclaimedAmount: number + token1UnclaimedAmount: number + token0UnclaimedValue: number + token1UnclaimedValue: number + totalEarnedFee: number +} + +const PositionDetail = () => { + const firstLoading = useRef(false) + + const { account } = useActiveWeb3React() + const { id } = useParams() + const { liquidityWidget, handleOpenZapInWidget } = useLiquidityWidget() + const { data: userPosition, isLoading } = useUserPositionQuery( + { addresses: account || '', positionId: id }, + { skip: !account, pollingInterval: 15_000 }, + ) + + const position: ParsedPosition | undefined = useMemo(() => { + if (!userPosition?.[0]) return + const position = userPosition?.[0] + + return { + id: position.tokenId, + dex: position.pool.project || '', + dexImage: position.pool.projectLogo || '', + chainId: position.chainId, + chainName: position.chainName, + chainLogo: position.chainLogo || '', + poolAddress: position.pool.poolAddress || '', + tokenAddress: position.tokenAddress, + token0Logo: position.pool.tokenAmounts[0]?.token.logo || '', + token1Logo: position.pool.tokenAmounts[1]?.token.logo || '', + token0Symbol: position.pool.tokenAmounts[0]?.token.symbol || '', + token1Symbol: position.pool.tokenAmounts[1]?.token.symbol || '', + poolFee: position.pool.fees?.[0], + status: position.status, + totalValue: position.currentPositionValue, + apr: position.apr || 0, + token0TotalAmount: position + ? position.currentAmounts[0]?.quotes.usd.value / position.currentAmounts[0]?.quotes.usd.price + : 0, + token1TotalAmount: position + ? position.currentAmounts[1]?.quotes.usd.value / position.currentAmounts[1]?.quotes.usd.price + : 0, + minPrice: position.minPrice || 0, + maxPrice: position.maxPrice || 0, + pairRate: position.pool.price || 0, + totalUnclaimedFee: + (position.feePending?.[0].quotes.usd.value || 0) + (position.feePending?.[1].quotes.usd.value || 0), + token0UnclaimedAmount: position.feePending[0]?.quotes.usd.value / position.feePending[0]?.quotes.usd.price, + token1UnclaimedAmount: position.feePending[1]?.quotes.usd.value / position.feePending[1]?.quotes.usd.price, + token0UnclaimedValue: position.feePending[0]?.quotes.usd.value, + token1UnclaimedValue: position.feePending[1]?.quotes.usd.value, + totalEarnedFee: + position.feePending.reduce((a, b) => a + b.quotes.usd.value, 0) + + position.feesClaimed.reduce((a, b) => a + b.quotes.usd.value, 0), + } + }, [userPosition]) + + const onOpenIncreaseLiquidityWidget = () => { + if (!position) return + handleOpenZapInWidget( + { + exchange: position.dex, + chainId: position.chainId, + address: position.poolAddress, + }, + position.id, + ) + } + + useEffect(() => { + if (!firstLoading.current && !isLoading) { + firstLoading.current = true + } + }, [isLoading]) + + return ( + <> + {liquidityWidget} + + {isLoading && !firstLoading.current ? ( + + ) : !!position ? ( + <> + + + + + + + + {t`Remove Liquidity`} + {t`Add Liquidity`} + + + + ) : ( + {t`No position found`} + )} + + + ) +} + +export default PositionDetail diff --git a/src/pages/Earns/PositionDetail/styles.tsx b/src/pages/Earns/PositionDetail/styles.tsx new file mode 100644 index 0000000000..1ee758effe --- /dev/null +++ b/src/pages/Earns/PositionDetail/styles.tsx @@ -0,0 +1,141 @@ +import styled from 'styled-components' + +import { ReactComponent as IconArrowLeftSvg } from 'assets/svg/ic_left_arrow.svg' + +export const IconArrowLeft = styled(IconArrowLeftSvg)` + cursor: pointer; + position: relative; + top: 5px; + color: ${({ theme }) => theme.subText}; + + :hover { + filter: brightness(1.5); + } +` + +export const DexInfo = styled.div<{ openable: boolean }>` + display: flex; + align-items: center; + gap: 6px; + color: ${({ theme }) => theme.text}; + + ${({ openable }) => openable && 'cursor: pointer;'} + :hover { + ${({ openable }) => openable && 'filter: brightness(1.2);'} + } +` + +export const PositionDetailWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 36px; + padding: 36px; + border-radius: 20px; + background-color: ${({ theme }) => theme.background}; + width: 100%; + + ${({ theme }) => theme.mediaWidth.upToMedium` + padding: 24px; + `} + + ${({ theme }) => theme.mediaWidth.upToSmall` + border-radius: 0; + margin: 0 -16px; + width: calc(100% + 32px); + padding: 20px 16px; + `} +` + +export const MainSection = styled.div` + display: flex; + gap: 36px; + + ${({ theme }) => theme.mediaWidth.upToMedium` + flex-direction: column; + `} +` + +export const InfoColumn = styled.div` + display: flex; + flex-direction: column; + gap: 16px; +` + +export const InfoLeftColumn = styled(InfoColumn)` + flex: 0 1 35%; +` + +export const InfoRightColumn = styled(InfoColumn)` + flex: 1 1 65%; +` + +export const InfoSection = styled.div` + border-radius: 16px; + padding: 16px 24px; + border: 1px solid ${({ theme }) => theme.tabActive}; +` + +export const InfoSectionFirstFormat = styled(InfoSection)` + display: flex; + align-items: flex-start; + justify-content: space-between; +` + +export const InfoSectionSecondFormat = styled(InfoSection)` + flex: 1 1 50%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +` + +export const InfoRight = styled.div` + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 8px; +` + +export const VerticalDivider = styled.div` + width: 1px; + height: 32px; + background: ${({ theme }) => theme.tabActive}; +` + +export const RevertIconWrapper = styled.div` + cursor: pointer; + + :hover { + filter: brightness(0.9); + } +` + +export const PositionActionWrapper = styled.div` + display: flex; + align-items: center; + justify-content: flex-end; + gap: 24px; + + ${({ theme }) => theme.mediaWidth.upToSmall` + gap: 16px; + `} +` + +export const PositionAction = styled.button<{ outline?: boolean }>` + border-radius: 24px; + padding: 10px 18px; + background-color: ${({ theme }) => theme.primary}; + border: 1px solid ${({ theme }) => theme.primary}; + cursor: pointer; + + ${({ outline }) => outline && 'background-color: transparent;'} + ${({ outline, theme }) => outline && `color: ${theme.primary};`} + + ${({ theme }) => theme.mediaWidth.upToSmall` + width: 100%; + `} + + :hover { + filter: brightness(1.2); + } +` diff --git a/src/pages/Earns/UserPositions/index.tsx b/src/pages/Earns/UserPositions/index.tsx new file mode 100644 index 0000000000..8582d9a75f --- /dev/null +++ b/src/pages/Earns/UserPositions/index.tsx @@ -0,0 +1,255 @@ +import { t } from '@lingui/macro' +import { useEffect, useRef } from 'react' +import { Minus, Plus } from 'react-feather' +import { useNavigate } from 'react-router-dom' +import { useMedia } from 'react-use' +import { Flex, Text } from 'rebass' +import { EarnPosition, PositionStatus, useUserPositionQuery } from 'services/krystalEarn' + +import CopyHelper from 'components/Copy' +import LocalLoader from 'components/LocalLoader' +import { MouseoverTooltipDesktopOnly } from 'components/Tooltip' +import { APP_PATHS } from 'constants/index' +import { useActiveWeb3React } from 'hooks' +import useTheme from 'hooks/useTheme' +import { MEDIA_WIDTHS } from 'theme' +import { shortenAddress } from 'utils' +import { formatDisplayNumber } from 'utils/numbers' + +import { CurrencyRoundedImage, CurrencySecondImage } from '../PoolExplorer/styles' +import useLiquidityWidget from '../useLiquidityWidget' +import { + Badge, + BadgeType, + ChainImage, + DexImage, + Divider, + EmptyPositionText, + ImageContainer, + MyLiquidityWrapper, + PositionAction, + PositionOverview, + PositionPageWrapper, + PositionRow, + PositionValueLabel, + PositionValueWrapper, +} from './styles' + +const MyPositions = () => { + const theme = useTheme() + const navigate = useNavigate() + const upToLarge = useMedia(`(max-width: ${MEDIA_WIDTHS.upToLarge}px)`) + const upToSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToSmall}px)`) + + const { account } = useActiveWeb3React() + const { liquidityWidget, handleOpenZapInWidget } = useLiquidityWidget() + const firstLoading = useRef(false) + + const { data: userPosition, isLoading } = useUserPositionQuery( + { addresses: account || '' }, + { skip: !account, pollingInterval: 15_000 }, + ) + + const onOpenIncreaseLiquidityWidget = (e: React.MouseEvent, position: EarnPosition) => { + e.stopPropagation() + handleOpenZapInWidget( + { + exchange: position.pool.project || '', + chainId: position.chainId, + address: position.pool.poolAddress, + }, + position.tokenId, + ) + } + + useEffect(() => { + if (!firstLoading.current && !isLoading) { + firstLoading.current = true + } + }, [isLoading]) + + return ( + <> + {liquidityWidget} + +
+ + {t`My Liquidity`} + + + {t`KyberSwap Zap: Instantly and easily add liquidity to high-APY pools using any token or a combination of tokens.`} + +
+ + + {isLoading && !firstLoading.current ? ( + + ) : userPosition && userPosition.length > 0 ? ( + userPosition.map(position => { + const { id, status, chainId: poolChainId } = position + const positionId = position.tokenId + const chainImage = position.chainLogo + const dexImage = position.pool.projectLogo + const dexVersion = position.pool.project?.split(' ')?.[1] || '' + const token0Logo = position.pool.tokenAmounts[0]?.token.logo + const token1Logo = position.pool.tokenAmounts[1]?.token.logo + const token0Symbol = position.pool.tokenAmounts[0]?.token.symbol + const token1Symbol = position.pool.tokenAmounts[1]?.token.symbol + const poolFee = position.pool.fees?.[0] + const poolAddress = position.pool.poolAddress + const totalValue = position.currentPositionValue + const token0TotalAmount = + position.currentAmounts[0]?.quotes.usd.value / position.currentAmounts[0]?.quotes.usd.price + const token1TotalAmount = + position.currentAmounts[1]?.quotes.usd.value / position.currentAmounts[1]?.quotes.usd.price + const token0EarnedAmount = + position.feePending[0]?.quotes.usd.value / position.feePending[0]?.quotes.usd.price + + position.feesClaimed[0]?.quotes.usd.value / position.feesClaimed[0]?.quotes.usd.price + const token1EarnedAmount = + position.feePending[1]?.quotes.usd.value / position.feePending[1]?.quotes.usd.price + + position.feesClaimed[1]?.quotes.usd.value / position.feesClaimed[1]?.quotes.usd.price + const totalEarnedFee = + position.feePending.reduce((a, b) => a + b.quotes.usd.value, 0) + + position.feesClaimed.reduce((a, b) => a + b.quotes.usd.value, 0) + + return ( + navigate({ pathname: APP_PATHS.EARN_POSITION_DETAIL.replace(':id', id) })} + > + + + + + + + + + {token0Symbol}/{token1Symbol} + + {poolFee && {poolFee}%} + + ● {status === PositionStatus.IN_RANGE ? t`In range` : t`Out of range`} + + + + + + + {dexVersion} + + + + #{positionId} + + + {shortenAddress(poolChainId, poolAddress, 4)} + + + + + {upToLarge && !upToSmall && ( + + + + + onOpenIncreaseLiquidityWidget(e, position)}> + + + + )} + + {t`Value`} + + + {formatDisplayNumber(token0TotalAmount, { significantDigits: 6 })} {token0Symbol} + + + {formatDisplayNumber(token1TotalAmount, { significantDigits: 6 })} {token1Symbol} + + + } + width="fit-content" + placement="bottom" + > + + {formatDisplayNumber(totalValue, { + style: 'currency', + significantDigits: 4, + })} + + + + + {t`Earned Fee`} + + + {formatDisplayNumber(token0EarnedAmount, { significantDigits: 6 })} {token0Symbol} + + + {formatDisplayNumber(token1EarnedAmount, { significantDigits: 6 })} {token1Symbol} + + + } + width="fit-content" + placement="bottom" + > + {formatDisplayNumber(totalEarnedFee, { style: 'currency', significantDigits: 4 })} + + + + Balance + + + {formatDisplayNumber(token0TotalAmount, { significantDigits: 4 })} {token0Symbol} + + {upToSmall && } + + {formatDisplayNumber(token1TotalAmount, { significantDigits: 4 })} {token1Symbol} + + + + {(upToSmall || !upToLarge) && ( + + + + + + + + onOpenIncreaseLiquidityWidget(e, position)}> + + + + + )} + + ) + }) + ) : ( + You haven't had any positions yet! + )} + + + {t`KyberSwap provides tools for tracking & adding liquidity to third-party Protocols. For any pool-related concerns, please contact the respective Liquidity Protocol directly.`} +
+ + ) +} + +export default MyPositions diff --git a/src/pages/Earns/UserPositions/styles.tsx b/src/pages/Earns/UserPositions/styles.tsx new file mode 100644 index 0000000000..1bc3dbed88 --- /dev/null +++ b/src/pages/Earns/UserPositions/styles.tsx @@ -0,0 +1,195 @@ +import { rgba } from 'polished' +import styled from 'styled-components' + +import { PoolPageWrapper } from '../PoolExplorer/styles' + +export const PositionPageWrapper = styled(PoolPageWrapper)` + padding: 24px 6rem 50px; + + ${({ theme }) => theme.mediaWidth.upToSmall` + padding: 24px 16px 100px; + `} +` + +export const MyLiquidityWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 1rem; + max-height: 539px; + overflow-y: auto; + + ${({ theme }) => theme.mediaWidth.upToSmall` + max-height: unset; + `} +` + +export const PositionRow = styled.div` + display: grid; + grid-template-columns: 2fr 1fr 1fr 1fr 75px; + grid-template-rows: 1fr; + background-color: ${({ theme }) => rgba(theme.background, 0.8)}; + border-radius: 20px; + padding: 16px 28px; + row-gap: 8px; + + ${({ theme }) => theme.mediaWidth.upToLarge` + justify-content: flex-start; + grid-template-columns: repeat(3, 1fr); + grid-template-rows: 1fr 1fr; + `} + + ${({ theme }) => theme.mediaWidth.upToSmall` + display: flex; + flex-direction: column; + row-gap: 16px; + padding: 16px; + `} + + &:hover { + cursor: pointer; + filter: brightness(1.1); + } +` + +export const PositionOverview = styled.div` + display: flex; + flex-direction: column; + gap: 8px; + + ${({ theme }) => theme.mediaWidth.upToLarge` + grid-column: span 2; + `} +` + +export const ImageContainer = styled.div` + position: relative; + top: 2px; +` + +export const ChainImage = styled.img` + width: 12px; + height: 12px; + border-radius: 50%; + position: relative; + left: -14px; + top: 4px; +` + +export const DexImage = styled.img` + width: 16px; + height: 16px; +` + +export enum BadgeType { + PRIMARY = 'primary', + WARNING = 'warning', + SECONDARY = 'secondary', +} + +export const Badge = styled.div<{ type?: BadgeType }>` + border-radius: 30px; + padding: 4px 12px; + background-color: ${({ theme }) => rgba(theme.white, 0.04)}; + color: ${({ theme }) => theme.subText}; + font-size: 12px; + display: flex; + align-items: center; + + ${({ type, theme }) => { + switch (type) { + case BadgeType.PRIMARY: + return ` + background-color: ${rgba(theme.primary, 0.2)}; + color: ${theme.primary}; + ` + case BadgeType.WARNING: + return ` + background-color: ${rgba(theme.warning, 0.2)}; + color: ${theme.warning}; + ` + case BadgeType.SECONDARY: + return ` + color: #2C9CE4; + ` + default: + return '' + } + }} + + ${({ theme }) => theme.mediaWidth.upToXXSmall` + padding: 4px 9px; + `} +` + +export const PositionValueWrapper = styled.div<{ align?: string }>` + display: flex; + align-items: flex-start; + justify-content: flex-start; + gap: 8px; + padding-top: 8px; + + ${({ align }) => (align ? `justify-content: ${align};` : '')} + + ${({ theme }) => theme.mediaWidth.upToSmall` + justify-content: space-between; + padding-top: 0; + `} +` + +export const PositionValueLabel = styled.p` + font-size: 14px; + margin: 0; + color: ${({ theme }) => theme.subText}; + position: relative; + top: 1px; + + ${({ theme }) => theme.mediaWidth.upToSmall` + font-size: 16px; + top: 0; + `} +` + +export const PositionAction = styled.div<{ primary?: boolean }>` + display: flex; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; + border-radius: 12px; + background-color: ${({ theme, primary }) => (primary ? rgba(theme.primary, 0.2) : theme.tabActive)}; + color: ${({ theme, primary }) => (primary ? theme.primary : theme.subText)}; + + &:hover { + cursor: pointer; + filter: brightness(1.2); + } + + &:active { + filter: brightness(1.05); + } + + ${({ theme }) => theme.mediaWidth.upToSmall` + width: 36px; + height: 36px; + `} +` + +export const Divider = styled.div` + height: 16px; + width: 1px; + background: ${({ theme }) => theme.tabActive}; + margin: 0 14px; + position: relative; + top: 1px; +` + +export const EmptyPositionText = styled.div` + display: flex; + justify-content: center; + align-items: center; + background-color: ${({ theme }) => rgba(theme.background, 0.4)}; + color: ${({ theme }) => theme.subText}; + border-radius: 20px; + height: 160px; + margin: 20px 0; +` diff --git a/src/pages/Earns/index.tsx b/src/pages/Earns/index.tsx new file mode 100644 index 0000000000..68042219e8 --- /dev/null +++ b/src/pages/Earns/index.tsx @@ -0,0 +1,567 @@ +import { ChainId } from '@kyberswap/ks-sdk-core' +import { rgba } from 'polished' +import { useNavigate } from 'react-router-dom' +import { useMedia } from 'react-use' +import { Box, Flex, Text } from 'rebass' +import { EarnPool, useExplorerLandingQuery } from 'services/zapEarn' +import styled, { keyframes } from 'styled-components' + +import bg from 'assets/images/earn-bg.png' +import CursorIcon from 'assets/svg/cursor.svg' +import FireIcon from 'assets/svg/fire.svg' +import LiquidityPoolIcon from 'assets/svg/liquidity-pools.svg' +import LiquidityPosIcon from 'assets/svg/liquidity-positions.svg' +import LowVolatilityIcon from 'assets/svg/low-volatility.svg' +import PlayIcon from 'assets/svg/play-icon.svg' +import RocketIcon from 'assets/svg/rocket.svg' +import SolidEarningIcon from 'assets/svg/solid-earning.svg' +import StakingIcon from 'assets/svg/staking.svg' +import { ButtonPrimary } from 'components/Button' +import LocalLoader from 'components/LocalLoader' +import { APP_PATHS } from 'constants/index' +import { NETWORKS_INFO } from 'hooks/useChainsConfig' +import useTheme from 'hooks/useTheme' +import { MEDIA_WIDTHS } from 'theme' +import { formatDisplayNumber } from 'utils/numbers' + +import { FilterTag } from './PoolExplorer' +import useLiquidityWidget from './useLiquidityWidget' + +const WrapperBg = styled.div` + background-image: url(${bg}); + background-size: 100% auto; + background-repeat: repeat-y; + width: 100vw; +` + +const Container = styled.div` + max-width: 1152px; + padding: 60px 16px; + margin: auto; + text-align: center; + + ${({ theme }) => theme.mediaWidth.upToXXSmall` + padding: 36px 12px; + `} +` + +/* Spin animation */ +const spin = keyframes` + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +` + +const BorderWrapper = styled.div` + padding: 1px; + position: relative; + background-clip: padding-box; + border-radius: 20px; + overflow: hidden; + + ::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + padding: 1px; /* Border width */ + background: linear-gradient(306.9deg, #262525 38.35%, rgba(49, 203, 158, 0.06) 104.02%), + radial-gradient(58.61% 54.58% at 30.56% 0%, rgba(49, 203, 158, 0.6) 0%, rgba(0, 0, 0, 0) 100%); + mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0); /* Mask to avoid background bleed */ + -webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0); /* Mask to avoid background bleed */ + z-index: -1; + } + + :hover::before { + top: -20%; + left: -20%; + right: -20%; + bottom: -20%; + padding: 1px; /* Border width */ + background: linear-gradient(306.9deg, #262525 38.35%, rgba(49, 203, 158, 0.6) 104.02%), + radial-gradient(58.61% 54.58% at 30.56% 0%, rgba(49, 203, 158, 1) 0%, rgba(0, 0, 0, 0) 100%); + + animation: ${spin} 2s linear infinite; /* Spin animation */ + } +` + +const OverviewWrapper = styled.div` + box-sizing: border-box; + margin: 0; + min-width: 0; + display: grid; + grid-template-columns: repeat(3, 1fr); + margin-top: 64px; + gap: 20px; + + ${({ theme }) => theme.mediaWidth.upToSmall` + display: flex; + flex-direction: column; + `} + + ${({ theme }) => theme.mediaWidth.upToXXSmall` + margin-top: 40px; + gap: 16px; + `} +` + +const PoolWrapper = styled.div` + border-radius: 20px; + position: relative; + overflow: hidden; + padding: 1px; + transition: box-shadow 0.3s ease, transform 0.3s ease, background 0.3s ease; + + :hover { + box-shadow: 0px 12px 64px 0px rgba(71, 32, 139, 0.8); + ::before { + background: linear-gradient(215.58deg, #262525 -9.03%, rgba(148, 115, 221, 0.6) 59.21%), + radial-gradient(58.61% 54.58% at 30.56% 0%, rgba(130, 71, 229, 1) 0%, rgba(0, 0, 0, 0) 100%); + } + } + + /* Create the gradient border effect using ::before */ + ::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border-radius: 20px; + padding: 1px; + + background: linear-gradient(215.58deg, #262525 -9.03%, rgba(148, 115, 221, 0.2) 59.21%), + radial-gradient(58.61% 54.58% at 30.56% 0%, rgba(130, 71, 229, 0.6) 0%, rgba(0, 0, 0, 0) 100%); + mask-composite: destination-out; + -webkit-mask-composite: destination-out; + z-index: -1; /* Position behind the content */ + } +` + +const CardWrapper = styled.div` + border-radius: 20px; + + background: linear-gradient(119.08deg, rgba(20, 29, 27, 1) -0.89%, rgba(14, 14, 14, 1) 132.3%); + padding: 0 36px 44px 50px; + text-align: left; + min-height: 360px; + display: flex; + flex-direction: column; + overflow: hidden; + height: 100%; + + cursor: url(${CursorIcon}), auto; + button { + cursor: url(${CursorIcon}), auto; + } + + ${({ theme }) => theme.mediaWidth.upToMedium` + padding: 0 36px 40px; + `} + + ${({ theme }) => theme.mediaWidth.upToSmall` + padding: 0 36px 28px; + min-height: 285px; + `} + + ${({ theme }) => theme.mediaWidth.upToXXSmall` + padding: 0 30px 24px; + min-height: unset; + height: fit-content; + `} +` + +const ButtonPrimaryStyled = styled(ButtonPrimary)` + margin-top: auto; + width: 132px; + height: 36px; + + ${({ theme }) => theme.mediaWidth.upToXXSmall` + margin-top: 18px; + `} +` + +const ListPoolWrapper = styled.div` + padding: 20px; + border-radius: 20px; + height: 100%; + background: linear-gradient(119.08deg, rgba(20, 29, 27, 1) -0.89%, rgba(14, 14, 14, 1) 132.3%); + cursor: url(${CursorIcon}), auto; + + ${({ theme }) => theme.mediaWidth.upToMedium` + padding: 12px; + `} + + ${({ theme }) => theme.mediaWidth.upToSmall` + padding: 18px; + `} +` + +const PoolRow = styled(Flex)` + gap: 12px; + align-items: center; + border-radius: 999px; + padding: 8px 16px; + + :hover { + background: #31cb9e1a; + } +` + +const Tag = styled.div` + border-radius: 999px; + background: ${({ theme }) => rgba(theme.text, 0.1)}; + color: ${({ theme }) => theme.subText}; + padding: 4px 8px; + font-size: 12px; +` + +const Icon = ({ icon, size = 'medium' }: { icon: string; size: 'small' | 'medium' }) => { + return ( + + + icon + + + ) +} + +const Card = ({ + title, + icon, + desc, + action, +}: { + title: string + icon: string + desc: string + action: { text: string; disabled?: boolean; onClick: () => void } +}) => { + const theme = useTheme() + const upToMedium = useMedia(`(max-width: ${MEDIA_WIDTHS.upToMedium}px)`) + const upToSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToSmall}px)`) + + return ( + !action.disabled && action.onClick()}> + + + + + + + + {title} + + + {desc} + + {action.text} + + + ) +} + +export default function Earns() { + const navigate = useNavigate() + const theme = useTheme() + const { isLoading, data } = useExplorerLandingQuery() + + const title = (_title: string, icon: string) => ( + <> + + + {_title} + + + + ) + + const highlightedPools = (data?.data?.highlightedPools || []).slice(0, 9) + const highAprPool = (data?.data?.highAPR || []).slice(0, 5) + const lowVolatilityPool = [...(data?.data?.lowVolatility || [])].sort((a, b) => b.apr - a.apr).slice(0, 5) + const solidEarningPool = (data?.data?.solidEarning || []).slice(0, 5) + + const upToSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToSmall}px)`) + const upToXXSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToXXSmall}px)`) + + return ( + + + + Maximize Your Earnings in DeFi + + + Unlock the full potential of your assets. Offering data, tools, and utilities—centered around Zap + technology—to help you maximize earnings from your liquidity across various DeFi protocols. + + + + navigate({ pathname: APP_PATHS.EARN_POOLS }), + }} + /> + navigate({ pathname: APP_PATHS.EARN_POSITIONS }), + }} + /> + {}, + disabled: true, + }} + /> + + + + { + navigate({ + pathname: APP_PATHS.EARN_POOLS, + search: `tag=${FilterTag.HIGHLIGHTED_POOL}`, + }) + }} + > + {title('Highlighted Pools', FireIcon)} + {isLoading ? ( + + ) : ( + + {highlightedPools.map(pool => ( + + ))} + + )} + + + + + + { + navigate({ + pathname: APP_PATHS.EARN_POOLS, + search: `tag=${FilterTag.HIGH_APR}`, + }) + }} + > + {title('High APR', RocketIcon)} + {isLoading ? ( + + ) : ( + + {highAprPool.map(pool => ( + + ))} + + )} + + + + + { + navigate({ + pathname: APP_PATHS.EARN_POOLS, + search: `tag=${FilterTag.LOW_VOLATILITY}`, + }) + }} + > + {title('Low Volatility', LowVolatilityIcon)} + {isLoading ? ( + + ) : ( + + {lowVolatilityPool.map(pool => ( + + ))} + + )} + + + + + { + navigate({ + pathname: APP_PATHS.EARN_POOLS, + search: `tag=${FilterTag.SOLID_EARNING}`, + }) + }} + > + {title('Solid Earning', SolidEarningIcon)} + {isLoading ? ( + + ) : ( + + {solidEarningPool.map(pool => ( + + ))} + + )} + + + + + { + navigate({ + pathname: APP_PATHS.EARN_POOLS, + }) + }} + sx={{ + cursor: 'pointer', + border: `1px solid ${theme.primary}`, + margin: 'auto', + marginTop: '40px', + borderRadius: '999px', + height: '56px', + background: rgba(theme.primary, 0.2), + fontSize: '16px', + fontWeight: 500, + color: theme.primary, + alignItems: 'center', + padding: '1rem 2rem', + width: 'fit-content', + }} + > + EXPLORE POOLS + play + + + + ) +} + +const PoolItem = ({ pool }: { pool: EarnPool }) => { + const theme = useTheme() + const { liquidityWidget, handleOpenZapInWidget } = useLiquidityWidget() + + return ( + { + e.stopPropagation() + handleOpenZapInWidget({ + exchange: pool.exchange, + chainId: pool.chainId, + address: pool.address, + }) + }} + > + {liquidityWidget} + + + + + + {pool.tokens?.[0].symbol} /{' '} + + {pool.tokens?.[1].symbol} + + + {pool.feeTier}% + + + {formatDisplayNumber(pool.apr, { significantDigits: pool.apr < 1 ? 2 : pool.apr < 10 ? 3 : 4 })}% + + + ) +} diff --git a/src/pages/Earns/useLiquidityWidget.tsx b/src/pages/Earns/useLiquidityWidget.tsx new file mode 100644 index 0000000000..976296523e --- /dev/null +++ b/src/pages/Earns/useLiquidityWidget.tsx @@ -0,0 +1,74 @@ +import { ChainId, LiquidityWidget, PoolType } from 'kyberswap-liquidity-widgets' +import { useState } from 'react' + +import { NotificationType } from 'components/Announcement/type' +import Modal from 'components/Modal' +import { NETWORKS_INFO } from 'constants/networks' +import { useWeb3React } from 'hooks' +import { useNotify, useWalletModalToggle } from 'state/application/hooks' + +import useFilter from './PoolExplorer/useFilter' + +interface LiquidityParams { + provider: any + poolAddress: string + chainId: ChainId + source: string + poolType: PoolType + positionId?: string + onDismiss: () => void + onConnectWallet: () => void +} + +const useLiquidityWidget = () => { + const { library } = useWeb3React() + const toggleWalletModal = useWalletModalToggle() + const notify = useNotify() + const { filters } = useFilter() + + const [liquidityParams, setLiquidityParams] = useState(null) + + const handleCloseZapInWidget = () => setLiquidityParams(null) + const handleOpenZapInWidget = ( + pool: { exchange: string; chainId?: number; address: string }, + positionId?: string, + ) => { + const supportedDexs = Object.keys(PoolType).map(item => item.replace('DEX_', '').replace('V3', '').toLowerCase()) + const formattedExchange = pool.exchange.toLowerCase().replaceAll('_', '').replaceAll('-', '').replaceAll('v3', '') + const dex = supportedDexs.find(item => formattedExchange.includes(item) || item.includes(formattedExchange)) + if (!dex) { + notify( + { + title: `Open pool detail failed`, + summary: `Protocol ${pool.exchange} on ${ + NETWORKS_INFO[String(pool?.chainId || filters.chainId) as unknown as keyof typeof NETWORKS_INFO]?.name || + 'this network' + } is not supported`, + type: NotificationType.ERROR, + }, + 8000, + ) + return + } + setLiquidityParams({ + provider: library, + poolAddress: pool.address, + chainId: (pool.chainId || filters.chainId) as ChainId, + source: 'kyberswap-demo-zap', + poolType: PoolType[`DEX_${dex.toUpperCase()}V3` as keyof typeof PoolType], + positionId, + onDismiss: handleCloseZapInWidget, + onConnectWallet: toggleWalletModal, + }) + } + + const liquidityWidget = liquidityParams ? ( + + + + ) : null + + return { liquidityWidget, liquidityParams, handleOpenZapInWidget } +} + +export default useLiquidityWidget diff --git a/src/services/krystalEarn.ts b/src/services/krystalEarn.ts new file mode 100644 index 0000000000..8540a931ea --- /dev/null +++ b/src/services/krystalEarn.ts @@ -0,0 +1,160 @@ +import { ChainId } from '@kyberswap/ks-sdk-core' +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' + +export enum EarnSupportedProtocols { + UNISWAP_V3 = 'Uniswap V3', + PANCAKESWAP_V3 = 'PancakeSwap V3', + SUSHISWAP_V3 = 'SushiSwap V3', +} +export const earnSupportedChains = [ChainId.MAINNET, ChainId.BASE] +export const earnSupportedProtocols = [ + EarnSupportedProtocols.UNISWAP_V3, + EarnSupportedProtocols.PANCAKESWAP_V3, + EarnSupportedProtocols.SUSHISWAP_V3, +] + +export enum PositionStatus { + IN_RANGE = 'IN_RANGE', + OUT_RANGE = 'OUT_RANGE', +} + +export interface PositionAmount { + token: { + address: string + symbol: string + name: string + decimals: number + logo: string + tag: string + price: number + } + tokenType: string + tokenID: string + balance: string + quotes: { + usd: { + symbol: string + marketPrice: number + price: number + priceChange24hPercentage: number + value: number + timestamp: number + } + } +} + +export interface EarnPosition { + [x: string]: any + chainName: 'eth' + chainId: number + chainLogo: string + userAddress: string + id: string + tokenAddress: string + tokenId: string + liquidity: string + minPrice: number + maxPrice: number + currentAmounts: Array + providedAmounts: Array + feePending: Array + feesClaimed: Array + farmRewardsPending: Array + farmRewardsClaimed: Array + feeEarned24h: Array + farmReward24h: Array + createdTime: number + lastUpdateBlock: number + openedBlock: number + openedTime: number + closedBlock: number + closedTime: number + closedPrice: number + farming: boolean + impermanentLoss: number + apr: number + feeApr: number + farmApr: number + pnl: number + initialUnderlyingValue: number + currentUnderlyingValue: number + currentPositionValue: number + compareWithHodl: number + returnOnInvestment: number + totalDepositValue: number + totalWithdrawValue: number + yesterdayEarning: number + earning24h: number + status: PositionStatus + avgConvertPrice: number + isConvertedFromToken0: boolean + gasUsed: number + isSupportAutomation: boolean + hasAutomationOrder: boolean + pool: { + id: string + poolAddress: string + price: number + tokenAmounts: Array + farmRewardTokens: Array + fees: Array + rewards24h: Array + tickSpacing: number + project: string + projectLogo: string + projectAddress: string + showWarning: boolean + tvl: number + farmAddress: string + tag: string + } +} + +export interface PositionEarning { + date: string + timestamp: number + totalFeeEarning: number + totalFarmEarning: number + totalEarning: number + earningByDay: number +} + +const krystalEarnServiceApi = createApi({ + reducerPath: 'krystalEarnServiceApi', + baseQuery: fetchBaseQuery({ + baseUrl: 'https://api.krystal.app/all', + }), + keepUnusedDataFor: 1, + endpoints: builder => ({ + userPosition: builder.query, { addresses: string; positionId?: string }>({ + query: params => ({ + url: `/v1/lp/userPositions`, + params: { + ...params, + chainIds: earnSupportedChains, + protocols: earnSupportedProtocols, + quoteSymbol: 'usd', + offset: 0, + orderBy: 'liquidity', + orderASC: false, + positionStatus: 'open', + }, + }), + transformResponse: (response: { positions: Array }) => response.positions, + }), + positionEarningStatistics: builder.query< + Array, + { tokenAddress: string; tokenId: string; chainId: string | number } + >({ + query: params => ({ + url: `/v1/balance/positionEarningStatistic`, + params, + }), + transformResponse: (response: { data: Array }) => response.data, + }), + }), +}) + +export const { useUserPositionQuery, usePositionEarningStatisticsQuery } = krystalEarnServiceApi + +export default krystalEarnServiceApi diff --git a/src/services/ksSetting.ts b/src/services/ksSetting.ts index 9a45b370a0..57cc9f0460 100644 --- a/src/services/ksSetting.ts +++ b/src/services/ksSetting.ts @@ -211,6 +211,7 @@ export const { useLazyGetKyberswapConfigurationQuery, useGetKyberswapGlobalConfigurationQuery, useLazyGetTokenListQuery, + useGetDexListQuery, useGetTokenListQuery, useImportTokenMutation, useLazyGetTopTokensQuery, diff --git a/src/services/zapEarn.ts b/src/services/zapEarn.ts new file mode 100644 index 0000000000..c9d80a70ca --- /dev/null +++ b/src/services/zapEarn.ts @@ -0,0 +1,133 @@ +import { ChainId } from '@kyberswap/ks-sdk-core' +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' + +interface ExplorerLandingResponse { + data: { + highlightedPools: Array + solidEarning: Array + highAPR: Array + lowVolatility: Array + } +} + +interface SupportedChainsResponse { + code: number + message: string + data: { + chains: { + [chainId: string]: { + chainId: number + protocols: Array<{ id: string; name: string }> + } + } + } + requestId: string +} + +export interface QueryParams { + chainId: ChainId + page?: number + limit?: number + interval: string + protocol: string + userAddress?: string + tag?: string + sortBy?: string + orderBy?: string + q?: string +} + +export interface EarnPool { + address: string + earnFee: number + exchange: string + type: string + feeTier: number + volume: number + apr: number + liquidity: number + tvl: number + chainId?: number + favorite?: { + chainId: number + isFavorite: boolean + } + tokens: Array<{ + address: string + logoURI: string + symbol: string + }> +} + +interface PoolsExplorerResponse { + code: number + message: string + data: { + pools: Array + pagination: { + totalItems: number + } + } + requestId: string +} + +interface AddRemoveFavoriteParams { + chainId: ChainId + message: string + signature: string + poolAddress: string + userAddress: string +} + +const zapEarnServiceApi = createApi({ + reducerPath: 'zapEarnServiceApi', + baseQuery: fetchBaseQuery({ + baseUrl: import.meta.env.VITE_ZAP_EARN_URL, + }), + keepUnusedDataFor: 1, + endpoints: builder => ({ + explorerLanding: builder.query({ + query: () => ({ + url: `/v1/explorer/landing-page`, + }), + }), + supportedProtocols: builder.query({ + query: () => ({ + url: `/v1/protocol`, + }), + }), + poolsExplorer: builder.query({ + query: params => ({ + url: `/v1/explorer/pools`, + params: { + ...params, + orderBy: params.orderBy?.toUpperCase() || '', + }, + }), + }), + addFavorite: builder.mutation({ + query: body => ({ + method: 'POST', + body, + url: `/v1/favorite`, + }), + }), + removeFavorite: builder.mutation({ + query: body => ({ + method: 'DELETE', + body, + url: `/v1/favorite`, + }), + }), + }), +}) + +export const { + useExplorerLandingQuery, + useSupportedProtocolsQuery, + usePoolsExplorerQuery, + useAddFavoriteMutation, + useRemoveFavoriteMutation, +} = zapEarnServiceApi + +export default zapEarnServiceApi diff --git a/src/state/index.ts b/src/state/index.ts index d7a9ab8863..869599ca23 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -11,6 +11,7 @@ import crosschainApi from 'services/crossChain' import externalApi from 'services/externalApi' import geckoTerminalApi from 'services/geckoTermial' import identifyApi from 'services/identity' +import krystalEarnServiceApi from 'services/krystalEarn' import ksSettingApi from 'services/ksSetting' import kyberDAO from 'services/kyberDAO' import limitOrderApi from 'services/limitOrder' @@ -20,6 +21,7 @@ import referralApi from 'services/referral' import routeApi from 'services/route' import socialApi from 'services/social' import tokenApi from 'services/token' +import zapEarnServiceApi from 'services/zapEarn' import { ENV_LEVEL } from 'constants/env' import { ENV_TYPE } from 'constants/type' @@ -108,6 +110,8 @@ const store = configureStore({ topTokens, [routeApi.reducerPath]: routeApi.reducer, [tokenApi.reducerPath]: tokenApi.reducer, + [zapEarnServiceApi.reducerPath]: zapEarnServiceApi.reducer, + [krystalEarnServiceApi.reducerPath]: krystalEarnServiceApi.reducer, [referralApi.reducerPath]: referralApi.reducer, [campaignApi.reducerPath]: campaignApi.reducer, [commonServiceApi.reducerPath]: commonServiceApi.reducer, @@ -133,6 +137,8 @@ const store = configureStore({ .concat(routeApi.middleware) .concat(socialApi.middleware) .concat(tokenApi.middleware) + .concat(zapEarnServiceApi.middleware) + .concat(krystalEarnServiceApi.middleware) .concat(referralApi.middleware) .concat(campaignApi.middleware) .concat(commonServiceApi.middleware) diff --git a/yarn.lock b/yarn.lock index a8954c4b1f..d04832532c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,6 +38,11 @@ resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.9.4.tgz#aae21cb858bbb0411949d5b7b3051f4209043f62" integrity sha512-UK0bHA7hh9cR39V+4gl2/NnBBjoXIxkuWAPCaY4X7fbH4L/azIi7ilWOCjMUYfpJgraLUAqkRi2BqrjME8Rynw== +"@adraffy/ens-normalize@^1.10.1": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz#42cc67c5baa407ac25059fcd7d405cc5ecdb0c33" + integrity sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg== + "@ampproject/remapping@^2.1.0", "@ampproject/remapping@^2.2.0": version "2.2.1" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" @@ -2585,7 +2590,7 @@ ethereum-cryptography "^2.0.0" micro-ftch "^0.3.1" -"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.12", "@ethersproject/abi@^5.7.0": +"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.12", "@ethersproject/abi@^5.5.0", "@ethersproject/abi@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== @@ -2776,7 +2781,7 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.0.5": +"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.0.5", "@ethersproject/providers@^5.7.2": version "5.7.2" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== @@ -3736,7 +3741,7 @@ tiny-warning "^1.0.3" toformat "^2.0.0" -"@kyberswap/ks-sdk-core@1.1.6", "@kyberswap/ks-sdk-core@^1.0.5": +"@kyberswap/ks-sdk-core@1.1.6", "@kyberswap/ks-sdk-core@^1.0.5", "@kyberswap/ks-sdk-core@^1.1.5": version "1.1.6" resolved "https://registry.yarnpkg.com/@kyberswap/ks-sdk-core/-/ks-sdk-core-1.1.6.tgz#14b03c00408973c66df7b896b94fa4430d7d460b" integrity sha512-VuG2xvNPY+/Ls+5Lrr41MuEFnJ/fdvpmXioflefICNU/n8UaNwB2QuD0+ozFFOflnEP3hIf712JDGmgRt+T1SA== @@ -4193,6 +4198,13 @@ dependencies: "@noble/hashes" "1.4.0" +"@noble/curves@1.6.0", "@noble/curves@^1.6.0", "@noble/curves@~1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.6.0.tgz#be5296ebcd5a1730fccea4786d420f87abfeb40b" + integrity sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ== + dependencies: + "@noble/hashes" "1.5.0" + "@noble/curves@^1.4.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.5.0.tgz#7a9b9b507065d516e6dce275a1e31db8d2a100dd" @@ -4200,13 +4212,6 @@ dependencies: "@noble/hashes" "1.4.0" -"@noble/curves@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.6.0.tgz#be5296ebcd5a1730fccea4786d420f87abfeb40b" - integrity sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ== - dependencies: - "@noble/hashes" "1.5.0" - "@noble/hashes@1.3.2", "@noble/hashes@^1", "@noble/hashes@^1.0.0", "@noble/hashes@~1.3.2": version "1.3.2" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" @@ -4217,7 +4222,7 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== -"@noble/hashes@1.5.0", "@noble/hashes@^1.5.0": +"@noble/hashes@1.5.0", "@noble/hashes@^1.5.0", "@noble/hashes@~1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== @@ -4253,11 +4258,85 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@openzeppelin/contracts@3.4.1-solc-0.7-2": + version "3.4.1-solc-0.7-2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.1-solc-0.7-2.tgz#371c67ebffe50f551c3146a9eec5fe6ffe862e92" + integrity sha512-tAG9LWg8+M2CMu7hIsqHPaTyG4uDzjr6mhvH96LvOpLZZj6tgzTluBt+LsCf1/QaYrlis6pITvpIaIhE+iZB+Q== + "@openzeppelin/contracts@3.4.2-solc-0.7": version "3.4.2-solc-0.7" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2-solc-0.7.tgz#38f4dbab672631034076ccdf2f3201fab1726635" integrity sha512-W6QmqgkADuFcTLzHL8vVoNBtkwjvQRpYIAom7KiUNoLKghyx3FgH0GBjt8NRvigV1ZmMOBllvE1By1C+bi8WpA== +"@pancakeswap/chains@0.4.6", "@pancakeswap/chains@^0.4.6": + version "0.4.6" + resolved "https://registry.yarnpkg.com/@pancakeswap/chains/-/chains-0.4.6.tgz#47b98b147d237e147ff59a265ed3c95d1b8d0537" + integrity sha512-2uflmzHY+rno4+wTQL0ae4c4tbA/r5aGxWg9dNI/A4mS2jx/0lVbELwpSYughK2zcb6MbDbXkAZuLaNKhZe/Yg== + +"@pancakeswap/sdk@5.8.9", "@pancakeswap/sdk@^5.8.2": + version "5.8.9" + resolved "https://registry.yarnpkg.com/@pancakeswap/sdk/-/sdk-5.8.9.tgz#06bc66b4a3c40c087dd367d0a00cfcfd4cb6d4f2" + integrity sha512-Aj5N4AYsuA9mIEcpt+vchmNSfSI2r5j7QevX2NWUqk5uZUvTZLGOAcJcxIdKC7q8nUtWDT4cmSzZAszl6rfvSg== + dependencies: + "@pancakeswap/chains" "^0.4.6" + "@pancakeswap/swap-sdk-core" "1.3.0" + "@pancakeswap/swap-sdk-evm" "1.0.6" + "@pancakeswap/v2-sdk" "1.0.6" + big.js "^5.2.2" + decimal.js-light "^2.5.0" + tiny-invariant "^1.3.0" + tiny-warning "^1.0.3" + toformat "^2.0.0" + viem "^2.21.22" + +"@pancakeswap/swap-sdk-core@1.3.0", "@pancakeswap/swap-sdk-core@^1.2.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@pancakeswap/swap-sdk-core/-/swap-sdk-core-1.3.0.tgz#51a4d86278632085ff49318e1f8497159a6c8eea" + integrity sha512-nkeDs3GyNfvRGsTbTAO30yl6ccOTr5WQERsKAaxKTe1fbGvpDRFLo3nlR1ZddRCj+RYZSS+B4Sll4A39k7nFDQ== + dependencies: + big.js "^5.2.2" + decimal.js-light "^2.5.0" + tiny-invariant "^1.3.0" + tiny-warning "^1.0.3" + toformat "^2.0.0" + +"@pancakeswap/swap-sdk-evm@1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@pancakeswap/swap-sdk-evm/-/swap-sdk-evm-1.0.6.tgz#532a23ccaf31abeb46ac67df6af2c00d22fc112f" + integrity sha512-q4kTonZ5DOt/0FdMB19faZC4P9v5WidTnS7vlPalMguUXyAj79U1Hh1N37wqa6LcWFz2lTBnOrkaDst72h9MqA== + dependencies: + "@pancakeswap/chains" "0.4.6" + "@pancakeswap/swap-sdk-core" "1.3.0" + tiny-invariant "^1.3.0" + tiny-warning "^1.0.3" + viem "^2.21.22" + +"@pancakeswap/v2-sdk@1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@pancakeswap/v2-sdk/-/v2-sdk-1.0.6.tgz#e9dbaeb853cf01852d42a21d64160db00ce7e16e" + integrity sha512-4MmazJLSiN3JgRNtJ9eHB93GnQDmQudm0CDkBfnp3cOjqP1QRIFFdk8mkwoD4PxfrHmozWlzL/0eHT5l553kQw== + dependencies: + "@pancakeswap/chains" "0.4.6" + "@pancakeswap/swap-sdk-core" "1.3.0" + "@pancakeswap/swap-sdk-evm" "1.0.6" + tiny-invariant "^1.3.0" + viem "^2.21.22" + +"@pancakeswap/v3-sdk@^3.8.3": + version "3.8.12" + resolved "https://registry.yarnpkg.com/@pancakeswap/v3-sdk/-/v3-sdk-3.8.12.tgz#c65ec4e97411f04bb7c5745c730e544af603da68" + integrity sha512-mweDAjb7fzpVFBbMEOBqUCW2jVqxIGnazMIn4WH2jl2gQImf2Jjgwy6JJYV6QcfDkWNV5zNhQ6y62LhQbQh/fQ== + dependencies: + "@pancakeswap/chains" "0.4.6" + "@pancakeswap/sdk" "5.8.9" + "@pancakeswap/swap-sdk-core" "1.3.0" + big.js "^5.2.2" + decimal.js-light "^2.5.0" + tiny-invariant "^1.3.0" + tiny-warning "^1.0.3" + toformat "^2.0.0" + viem "^2.21.22" + "@parcel/watcher-android-arm64@2.4.1": version "2.4.1" resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.1.tgz#c2c19a3c442313ff007d2d7a9c2c1dd3e1c9ca84" @@ -4375,6 +4454,11 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45" integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw== +"@popperjs/core@^2.11.8": + version "2.11.8" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== + "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" @@ -4435,6 +4519,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/number@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.1.0.tgz#1e95610461a09cdf8bb05c152e76ca1278d5da46" + integrity sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ== + "@radix-ui/primitive@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.1.tgz#e46f9958b35d10e9f6dc71c497305c22e3e55dbd" @@ -4442,6 +4531,26 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/primitive@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.0.tgz#42ef83b3b56dccad5d703ae8c42919a68798bbe2" + integrity sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA== + +"@radix-ui/react-accordion@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-accordion/-/react-accordion-1.2.1.tgz#5c942c42c24267376b26204ec6847b17d15659b3" + integrity sha512-bg/l7l5QzUjgsh8kjwDFommzAshnUsuVMV5NM56QVCm+7ZckYdd9P/ExR8xG/Oup0OajVxNLaHJ1tb8mXk+nzQ== + dependencies: + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-collapsible" "1.1.1" + "@radix-ui/react-collection" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.1" + "@radix-ui/react-direction" "1.1.0" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-controllable-state" "1.1.0" + "@radix-ui/react-arrow@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz#c24f7968996ed934d57fe6cde5d6ec7266e1d25d" @@ -4450,6 +4559,20 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-primitive" "1.0.3" +"@radix-ui/react-collapsible@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-collapsible/-/react-collapsible-1.1.1.tgz#1382cc9ec48f8b473c14f3779d317f0cdf6da5e9" + integrity sha512-1///SnrfQHJEofLokyczERxQbWfCGQlQ2XsCZMucVs6it+lq9iw4vXy+uDn1edlb58cOZOWSldnfPAYcT4O/Yg== + dependencies: + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.1" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-presence" "1.1.1" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-controllable-state" "1.1.0" + "@radix-ui/react-use-layout-effect" "1.1.0" + "@radix-ui/react-collection@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.0.3.tgz#9595a66e09026187524a36c6e7e9c7d286469159" @@ -4461,6 +4584,16 @@ "@radix-ui/react-primitive" "1.0.3" "@radix-ui/react-slot" "1.0.2" +"@radix-ui/react-collection@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.1.0.tgz#f18af78e46454a2360d103c2251773028b7724ed" + integrity sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw== + dependencies: + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-slot" "1.1.0" + "@radix-ui/react-compose-refs@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz#7ed868b66946aa6030e580b1ffca386dd4d21989" @@ -4468,6 +4601,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-compose-refs@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz#656432461fc8283d7b591dcf0d79152fae9ecc74" + integrity sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw== + "@radix-ui/react-context@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.0.1.tgz#fe46e67c96b240de59187dcb7a1a50ce3e2ec00c" @@ -4475,6 +4613,16 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-context@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.0.tgz#6df8d983546cfd1999c8512f3a8ad85a6e7fcee8" + integrity sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A== + +"@radix-ui/react-context@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.1.tgz#82074aa83a472353bb22e86f11bcbd1c61c4c71a" + integrity sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q== + "@radix-ui/react-direction@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.1.tgz#9cb61bf2ccf568f3421422d182637b7f47596c9b" @@ -4482,6 +4630,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-direction@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.1.0.tgz#a7d39855f4d077adc2a1922f9c353c5977a09cdc" + integrity sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg== + "@radix-ui/react-dismissable-layer@1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.4.tgz#883a48f5f938fa679427aa17fcba70c5494c6978" @@ -4511,6 +4664,11 @@ "@radix-ui/react-primitive" "1.0.3" "@radix-ui/react-use-callback-ref" "1.0.1" +"@radix-ui/react-icons@^1.3.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-icons/-/react-icons-1.3.1.tgz#462c85fd726a77854cd5956e29eb19a575aa7ce5" + integrity sha512-QvYompk0X+8Yjlo/Fv4McrzxohDdM5GgLHyQcPpcsPvlOSXCGFjdbuyGL5dzRbg0GpknAjQJJZzdiRK7iWVuFQ== + "@radix-ui/react-id@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0" @@ -4519,6 +4677,13 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-layout-effect" "1.0.1" +"@radix-ui/react-id@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.1.0.tgz#de47339656594ad722eb87f94a6b25f9cffae0ed" + integrity sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA== + dependencies: + "@radix-ui/react-use-layout-effect" "1.1.0" + "@radix-ui/react-popper@1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.2.tgz#4c0b96fcd188dc1f334e02dba2d538973ad842e9" @@ -4544,6 +4709,14 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-primitive" "1.0.3" +"@radix-ui/react-presence@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.1.tgz#98aba423dba5e0c687a782c0669dcd99de17f9b1" + integrity sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A== + dependencies: + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-use-layout-effect" "1.1.0" + "@radix-ui/react-primitive@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz#d49ea0f3f0b2fe3ab1cb5667eb03e8b843b914d0" @@ -4552,6 +4725,13 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-slot" "1.0.2" +"@radix-ui/react-primitive@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz#fe05715faa9203a223ccc0be15dc44b9f9822884" + integrity sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw== + dependencies: + "@radix-ui/react-slot" "1.1.0" + "@radix-ui/react-roving-focus@1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz#e90c4a6a5f6ac09d3b8c1f5b5e81aab2f0db1974" @@ -4568,6 +4748,21 @@ "@radix-ui/react-use-callback-ref" "1.0.1" "@radix-ui/react-use-controllable-state" "1.0.1" +"@radix-ui/react-scroll-area@^1.1.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.1.tgz#610c53e07d017e24b62bd73a0a6eb23fa7331b3b" + integrity sha512-FnM1fHfCtEZ1JkyfH/1oMiTcFBQvHKl4vD9WnpwkLgtF+UmnXMCad6ECPTaAjcDjam+ndOEJWgHyKDGNteWSHw== + dependencies: + "@radix-ui/number" "1.1.0" + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.1" + "@radix-ui/react-direction" "1.1.0" + "@radix-ui/react-presence" "1.1.1" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-callback-ref" "1.1.0" + "@radix-ui/react-use-layout-effect" "1.1.0" + "@radix-ui/react-select@^1.2.2": version "1.2.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-select/-/react-select-1.2.2.tgz#caa981fa0d672cf3c1b2a5240135524e69b32181" @@ -4612,6 +4807,13 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-compose-refs" "1.0.1" +"@radix-ui/react-slot@1.1.0", "@radix-ui/react-slot@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.1.0.tgz#7c5e48c36ef5496d97b08f1357bb26ed7c714b84" + integrity sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw== + dependencies: + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-toggle-group@1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-toggle-group/-/react-toggle-group-1.0.4.tgz#f5b5c8c477831b013bec3580c55e20a68179d6ec" @@ -4657,6 +4859,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-use-callback-ref@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz#bce938ca413675bc937944b0d01ef6f4a6dc5bf1" + integrity sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw== + "@radix-ui/react-use-controllable-state@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz#ecd2ced34e6330caf89a82854aa2f77e07440286" @@ -4665,6 +4872,13 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-callback-ref" "1.0.1" +"@radix-ui/react-use-controllable-state@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz#1321446857bb786917df54c0d4d084877aab04b0" + integrity sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw== + dependencies: + "@radix-ui/react-use-callback-ref" "1.1.0" + "@radix-ui/react-use-escape-keydown@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz#217b840c250541609c66f67ed7bab2b733620755" @@ -4680,6 +4894,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-use-layout-effect@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz#3c2c8ce04827b26a39e442ff4888d9212268bd27" + integrity sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w== + "@radix-ui/react-use-previous@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-1.0.1.tgz#b595c087b07317a4f143696c6a01de43b0d0ec66" @@ -4809,6 +5028,11 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.3.tgz#8584115565228290a6c6c4961973e0903bb3df2f" integrity sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q== +"@scure/base@~1.1.7", "@scure/base@~1.1.8": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.9.tgz#e5e142fbbfe251091f9c5f1dd4c834ac04c3dbd1" + integrity sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg== + "@scure/bip32@1.3.2": version "1.3.2" resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.2.tgz#90e78c027d5e30f0b22c1f8d50ff12f3fb7559f8" @@ -4827,6 +5051,15 @@ "@noble/hashes" "~1.4.0" "@scure/base" "~1.1.6" +"@scure/bip32@1.5.0", "@scure/bip32@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.5.0.tgz#dd4a2e1b8a9da60e012e776d954c4186db6328e6" + integrity sha512-8EnFYkqEQdnkuGBVpCzKxyIwDCBLDVj3oiX0EKUFre/tOjL/Hqba1D6n/8RcmaQy4f95qQFrO2A8Sr6ybh4NRw== + dependencies: + "@noble/curves" "~1.6.0" + "@noble/hashes" "~1.5.0" + "@scure/base" "~1.1.7" + "@scure/bip39@1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.1.tgz#5cee8978656b272a917b7871c981e0541ad6ac2a" @@ -4843,6 +5076,14 @@ "@noble/hashes" "~1.4.0" "@scure/base" "~1.1.6" +"@scure/bip39@1.4.0", "@scure/bip39@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.4.0.tgz#664d4f851564e2e1d4bffa0339f9546ea55960a6" + integrity sha512-BEEm6p8IueV/ZTfQLp/0vhw4NPnT9oWf5+28nvmeUICjP99f4vr2d+qc7AVGDDtwRep6ifR43Yed9ERVmiITzw== + dependencies: + "@noble/hashes" "~1.5.0" + "@scure/base" "~1.1.8" + "@sentry-internal/browser-utils@8.34.0": version "8.34.0" resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.34.0.tgz#36a50d503ad4ad51fce22e80670f8fd6fd195a27" @@ -7433,7 +7674,49 @@ resolved "https://registry.yarnpkg.com/@uniswap/lib/-/lib-4.0.1-alpha.tgz#2881008e55f075344675b3bca93f020b028fbd02" integrity sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA== -"@uniswap/v2-core@1.0.1": +"@uniswap/sdk-core@5.8.1": + version "5.8.1" + resolved "https://registry.yarnpkg.com/@uniswap/sdk-core/-/sdk-core-5.8.1.tgz#48fd7a1246c2fd972cbdd78d9ca35b2414786ef6" + integrity sha512-Woi4IOGPEIz9Jsmc4sUIRoX7Iwz90p/u3NjdUYxwucb3nf3fnsZTvrDzbYdvi11nsTeQVIJnIW1C4T0Ae+bLwA== + dependencies: + "@ethersproject/address" "^5.0.2" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "5.7.0" + "@ethersproject/strings" "5.7.0" + big.js "^5.2.2" + decimal.js-light "^2.5.0" + jsbi "^3.1.4" + tiny-invariant "^1.1.0" + toformat "^2.0.0" + +"@uniswap/sdk-core@^5.8.1": + version "5.9.0" + resolved "https://registry.yarnpkg.com/@uniswap/sdk-core/-/sdk-core-5.9.0.tgz#8f1edf4d0e94b314f4394fa5abe0bf5fc9c5a79a" + integrity sha512-OME7WR6+5QwQs45A2079r+/FS0zU944+JCQwUX9GyIriCxqw2pGu4F9IEqmlwD+zSIMml0+MJnJJ47pFgSyWDw== + dependencies: + "@ethersproject/address" "^5.0.2" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "5.7.0" + "@ethersproject/strings" "5.7.0" + big.js "^5.2.2" + decimal.js-light "^2.5.0" + jsbi "^3.1.4" + tiny-invariant "^1.1.0" + toformat "^2.0.0" + +"@uniswap/swap-router-contracts@^1.3.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@uniswap/swap-router-contracts/-/swap-router-contracts-1.3.1.tgz#0ebbb93eb578625618ed9489872de381f9c66fb4" + integrity sha512-mh/YNbwKb7Mut96VuEtL+Z5bRe0xVIbjjiryn+iMMrK2sFKhR4duk/86mEz0UO5gSx4pQIw9G5276P5heY/7Rg== + dependencies: + "@openzeppelin/contracts" "3.4.2-solc-0.7" + "@uniswap/v2-core" "^1.0.1" + "@uniswap/v3-core" "^1.0.0" + "@uniswap/v3-periphery" "^1.4.4" + dotenv "^14.2.0" + hardhat-watcher "^2.1.1" + +"@uniswap/v2-core@1.0.1", "@uniswap/v2-core@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@uniswap/v2-core/-/v2-core-1.0.1.tgz#af8f508bf183204779938969e2e54043e147d425" integrity sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q== @@ -7443,6 +7726,22 @@ resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.0.tgz#6c24adacc4c25dceee0ba3ca142b35adbd7e359d" integrity sha512-kSC4djMGKMHj7sLMYVnn61k9nu+lHjMIxgg9CDQT+s2QYLoA56GbSK9Oxr+qJXzzygbkrmuY6cwgP6cW2JXPFA== +"@uniswap/v3-core@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.1.tgz#b6d2bdc6ba3c3fbd610bdc502395d86cd35264a0" + integrity sha512-7pVk4hEm00j9tc71Y9+ssYpO6ytkeI0y7WE9P6UcmNzhxPePwyAxImuhVsTqWK9YFvzgtvzJHi64pBl4jUzKMQ== + +"@uniswap/v3-periphery@^1.0.1", "@uniswap/v3-periphery@^1.1.1", "@uniswap/v3-periphery@^1.4.4": + version "1.4.4" + resolved "https://registry.yarnpkg.com/@uniswap/v3-periphery/-/v3-periphery-1.4.4.tgz#d2756c23b69718173c5874f37fd4ad57d2f021b7" + integrity sha512-S4+m+wh8HbWSO3DKk4LwUCPZJTpCugIsHrWR86m/OrUyvSqGDTXKFfc2sMuGXCZrD1ZqO3rhQsKgdWg3Hbb2Kw== + dependencies: + "@openzeppelin/contracts" "3.4.2-solc-0.7" + "@uniswap/lib" "^4.0.1-alpha" + "@uniswap/v2-core" "^1.0.1" + "@uniswap/v3-core" "^1.0.0" + base64-sol "1.0.1" + "@uniswap/v3-periphery@^1.3.0": version "1.4.3" resolved "https://registry.yarnpkg.com/@uniswap/v3-periphery/-/v3-periphery-1.4.3.tgz#a6da4632dbd46b139cc13a410e4ec09ad22bd19f" @@ -7454,6 +7753,29 @@ "@uniswap/v3-core" "1.0.0" base64-sol "1.0.1" +"@uniswap/v3-sdk@3.18.1": + version "3.18.1" + resolved "https://registry.yarnpkg.com/@uniswap/v3-sdk/-/v3-sdk-3.18.1.tgz#cb714b252336ba662a3298c0525f6668101b0fef" + integrity sha512-TGrKLToSWwfx6VV2d7fh4kwQMlgspXTLE49ep5zfYODVVqV6WhrRdbteHb3e0bjdjxGSj0gzoLmhsjmoJTE1/g== + dependencies: + "@ethersproject/abi" "^5.5.0" + "@ethersproject/solidity" "^5.0.9" + "@uniswap/sdk-core" "^5.8.1" + "@uniswap/swap-router-contracts" "^1.3.0" + "@uniswap/v3-periphery" "^1.1.1" + "@uniswap/v3-staker" "1.0.0" + tiny-invariant "^1.1.0" + tiny-warning "^1.0.3" + +"@uniswap/v3-staker@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@uniswap/v3-staker/-/v3-staker-1.0.0.tgz#9a6915ec980852479dfc903f50baf822ff8fa66e" + integrity sha512-JV0Qc46Px5alvg6YWd+UIaGH9lDuYG/Js7ngxPit1SPaIP30AlVer1UYB7BRYeUVVxE+byUyIeN5jeQ7LLDjIw== + dependencies: + "@openzeppelin/contracts" "3.4.1-solc-0.7-2" + "@uniswap/v3-core" "1.0.0" + "@uniswap/v3-periphery" "^1.0.1" + "@use-gesture/core@10.2.27": version "10.2.27" resolved "https://registry.yarnpkg.com/@use-gesture/core/-/core-10.2.27.tgz#0f24b17c036cd828ba07e3451ff45e2df959c6f5" @@ -7942,6 +8264,11 @@ abitype@1.0.5: resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.5.tgz#29d0daa3eea867ca90f7e4123144c1d1270774b6" integrity sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw== +abitype@1.0.6, abitype@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.6.tgz#76410903e1d88e34f1362746e2d407513c38565b" + integrity sha512-MMSqYh4+C/aVqI2RQaWqbvI4Kxo5cQV40WQ4QFtDnNzCkqChm8MuENhElmynZlO0qUy/ObkEUaXtKqYnx1Kp3A== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -9200,6 +9527,13 @@ citty@^0.1.5, citty@^0.1.6: dependencies: consola "^3.2.3" +class-variance-authority@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/class-variance-authority/-/class-variance-authority-0.7.0.tgz#1c3134d634d80271b1837452b06d821915954522" + integrity sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A== + dependencies: + clsx "2.0.0" + classlist-polyfill@^1.0.3: version "1.2.0" resolved "https://registry.yarnpkg.com/classlist-polyfill/-/classlist-polyfill-1.2.0.tgz#935bc2dfd9458a876b279617514638bcaa964a2e" @@ -9326,11 +9660,21 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== +clsx@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b" + integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q== + clsx@^1.1.1, clsx@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== +clsx@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -10072,6 +10416,42 @@ d3@^7.6.1: d3-transition "3" d3-zoom "3" +d3@^7.9.0: + version "7.9.0" + resolved "https://registry.yarnpkg.com/d3/-/d3-7.9.0.tgz#579e7acb3d749caf8860bd1741ae8d371070cd5d" + integrity sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA== + dependencies: + d3-array "3" + d3-axis "3" + d3-brush "3" + d3-chord "3" + d3-color "3" + d3-contour "4" + d3-delaunay "6" + d3-dispatch "3" + d3-drag "3" + d3-dsv "3" + d3-ease "3" + d3-fetch "3" + d3-force "3" + d3-format "3" + d3-geo "3" + d3-hierarchy "3" + d3-interpolate "3" + d3-path "3" + d3-polygon "3" + d3-quadtree "3" + d3-random "3" + d3-scale "4" + d3-scale-chromatic "3" + d3-selection "3" + d3-shape "3" + d3-time "3" + d3-time-format "4" + d3-timer "3" + d3-transition "3" + d3-zoom "3" + damerau-levenshtein@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" @@ -10552,6 +10932,11 @@ dotenv-parse-variables@^2.0.0: debug "^4.3.1" is-string-and-not-blank "^0.0.2" +dotenv@^14.2.0: + version "14.3.2" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-14.3.2.tgz#7c30b3a5f777c79a3429cb2db358eef6751e8369" + integrity sha512-vwEppIphpFdvaMCaHfCEv9IgwcxMljMw2TnAQBB4VWPvzXQLTb82jwmdOKzlEVUL3gNFT4l4TPKO+Bn+sqcrVQ== + dotenv@^16.0.0, dotenv@^16.3.1: version "16.3.1" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" @@ -11316,7 +11701,7 @@ ethers@6.8.1, ethers@^6.0.0: tslib "2.4.0" ws "8.5.0" -ethers@^5.4.6, ethers@^5.6.9, ethers@^5.7.2: +ethers@^5.4.6, ethers@^5.6.9, ethers@^5.7.0, ethers@^5.7.2: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== @@ -12500,6 +12885,13 @@ handlebars@^4.7.7: optionalDependencies: uglify-js "^3.1.4" +hardhat-watcher@^2.1.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/hardhat-watcher/-/hardhat-watcher-2.5.0.tgz#3ee76c3cb5b99f2875b78d176207745aa484ed4a" + integrity sha512-Su2qcSMIo2YO2PrmJ0/tdkf+6pSt8zf9+4URR5edMVti6+ShI8T3xhPrwugdyTOFuyj8lKHrcTZNKUFYowYiyA== + dependencies: + chokidar "^3.5.3" + has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" @@ -13416,6 +13808,11 @@ isows@1.0.4: resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.4.tgz#810cd0d90cc4995c26395d2aa4cfa4037ebdf061" integrity sha512-hEzjY+x9u9hPmBom9IIAqdJCwNLax+xrPb51vEPpERoFlIxgmZcHzsT5jKG06nvInKOBGvReAVz80Umed5CczQ== +isows@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.6.tgz#0da29d706fa51551c663c627ace42769850f86e7" + integrity sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw== + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -13854,6 +14251,33 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== +kyberswap-liquidity-widgets@^1.1.46: + version "1.1.46" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.46.tgz#1cf01a03a851c31928f587284d0cb6ca5359fdd9" + integrity sha512-U+nr2R4X+zQTnbFS/rkER/aLBN/DJC0AmwgX2XZtSeBXgOx3BE8RSxK/MYIT0ZhzyWOWgQJb5w7sQb7058T1vQ== + dependencies: + "@ethersproject/providers" "^5.7.2" + "@kyberswap/ks-sdk-core" "^1.1.5" + "@pancakeswap/sdk" "^5.8.2" + "@pancakeswap/swap-sdk-core" "^1.2.0" + "@pancakeswap/v3-sdk" "^3.8.3" + "@popperjs/core" "^2.11.8" + "@radix-ui/react-accordion" "^1.2.1" + "@radix-ui/react-icons" "^1.3.0" + "@radix-ui/react-scroll-area" "^1.1.0" + "@radix-ui/react-slot" "^1.1.0" + "@uniswap/sdk-core" "5.8.1" + "@uniswap/v3-sdk" "3.18.1" + class-variance-authority "^0.7.0" + clsx "^2.1.1" + d3 "^7.9.0" + ethers "^5.7.0" + jsbi "^3.2.5" + lodash.partition "^4.6.0" + numeral "^2.0.6" + polished "^4.3.1" + react-popper "^2.3.0" + language-subtag-registry@~0.3.2: version "0.3.22" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d" @@ -14102,6 +14526,11 @@ lodash.once@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== +lodash.partition@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.partition/-/lodash.partition-4.6.0.tgz#a38e46b73469e0420b0da1212e66d414be364ba4" + integrity sha512-35L3dSF3Q6V1w5j6V3NhNlQjzsRDC/pYKCTdYTmwqSib+Q8ponkAmt/PwEOq3EmI38DSCl+SkIVwLd+uSlVdrg== + lodash.pick@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" @@ -15450,6 +15879,19 @@ ospath@^1.2.2: resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" integrity sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA== +ox@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ox/-/ox-0.1.2.tgz#0f791be2ccabeaf4928e6d423498fe1c8094e560" + integrity sha512-ak/8K0Rtphg9vnRJlbOdaX9R7cmxD2MiSthjWGaQdMk3D7hrAlDoM+6Lxn7hN52Za3vrXfZ7enfke/5WjolDww== + dependencies: + "@adraffy/ens-normalize" "^1.10.1" + "@noble/curves" "^1.6.0" + "@noble/hashes" "^1.5.0" + "@scure/bip32" "^1.5.0" + "@scure/bip39" "^1.4.0" + abitype "^1.0.6" + eventemitter3 "5.0.1" + p-cancelable@^0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" @@ -15913,6 +16355,13 @@ polished@^4.2.2: dependencies: "@babel/runtime" "^7.17.8" +polished@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/polished/-/polished-4.3.1.tgz#5a00ae32715609f83d89f6f31d0f0261c6170548" + integrity sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA== + dependencies: + "@babel/runtime" "^7.17.8" + pony-cause@^2.1.10: version "2.1.11" resolved "https://registry.yarnpkg.com/pony-cause/-/pony-cause-2.1.11.tgz#d69a20aaccdb3bdb8f74dd59e5c68d8e6772e4bd" @@ -16620,7 +17069,7 @@ react-native-webview@^11.26.0: escape-string-regexp "2.0.0" invariant "2.2.4" -react-popper@^2.2.3: +react-popper@^2.2.3, react-popper@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba" integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q== @@ -18348,6 +18797,11 @@ tiny-invariant@^1.1.0, tiny-invariant@^1.3.1: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== +tiny-invariant@^1.3.0: + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + tiny-warning@^1.0.0, tiny-warning@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" @@ -19191,6 +19645,21 @@ viem@^2.1.1, viem@^2.20.0: webauthn-p256 "0.0.5" ws "8.17.1" +viem@^2.21.22: + version "2.21.45" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.21.45.tgz#7a445428d4909cc334f231ee916ede1b69190603" + integrity sha512-I+On/IiaObQdhDKWU5Rurh6nf3G7reVkAODG5ECIfjsrGQ3EPJnxirUPT4FNV6bWER5iphoG62/TidwuTSOA1A== + dependencies: + "@noble/curves" "1.6.0" + "@noble/hashes" "1.5.0" + "@scure/bip32" "1.5.0" + "@scure/bip39" "1.4.0" + abitype "1.0.6" + isows "1.0.6" + ox "0.1.2" + webauthn-p256 "0.0.10" + ws "8.18.0" + vite-plugin-checker@^0.5.6: version "0.5.6" resolved "https://registry.yarnpkg.com/vite-plugin-checker/-/vite-plugin-checker-0.5.6.tgz#233978091dfadef0873f0a8aacfe7fc431212b95" @@ -19358,6 +19827,14 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +webauthn-p256@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/webauthn-p256/-/webauthn-p256-0.0.10.tgz#877e75abe8348d3e14485932968edf3325fd2fdd" + integrity sha512-EeYD+gmIT80YkSIDb2iWq0lq2zbHo1CxHlQTeJ+KkCILWpVy3zASH3ByD4bopzfk0uCwXxLqKGLqp2W4O28VFA== + dependencies: + "@noble/curves" "^1.4.0" + "@noble/hashes" "^1.4.0" + webauthn-p256@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/webauthn-p256/-/webauthn-p256-0.0.5.tgz#0baebd2ba8a414b21cc09c0d40f9dd0be96a06bd" @@ -19652,6 +20129,11 @@ ws@8.17.1, ws@~8.17.1: resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== +ws@8.18.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + ws@8.5.0: version "8.5.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f"