diff --git a/apps/kyberswap-interface/.env.production b/apps/kyberswap-interface/.env.production index a978bc570e..f4df4765b0 100644 --- a/apps/kyberswap-interface/.env.production +++ b/apps/kyberswap-interface/.env.production @@ -46,3 +46,5 @@ VITE_ZAP_EARN_URL=https://zap-earn-service-v3.kyberengineering.io/api VITE_AFFILIATE_SERVICE=https://affiliate-service.kyberengineering.io/api VITE_SOLANA_RPC=https://solana.kyberengineering.io + +VITE_SMART_EXIT_API_URL=https://pre-conditional-order.kyberengineering.io/api diff --git a/apps/kyberswap-interface/package.json b/apps/kyberswap-interface/package.json index 498bfc3208..9efd72687f 100644 --- a/apps/kyberswap-interface/package.json +++ b/apps/kyberswap-interface/package.json @@ -60,6 +60,7 @@ "@kyberswap/ks-sdk-core": "1.1.14", "@kyberswap/ks-sdk-elastic": "^1.1.2", "@kyberswap/liquidity-chart": "workspace:*", + "@kyberswap/price-slider": "workspace:*", "@kyberswap/liquidity-widgets": "workspace:*", "@kyberswap/oauth2": "1.0.2", "@kyberswap/zap-migration-widgets": "workspace:*", diff --git a/apps/kyberswap-interface/src/assets/svg/earn/ic_list_smart_exit.svg b/apps/kyberswap-interface/src/assets/svg/earn/ic_list_smart_exit.svg new file mode 100644 index 0000000000..0b8607e6aa --- /dev/null +++ b/apps/kyberswap-interface/src/assets/svg/earn/ic_list_smart_exit.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/apps/kyberswap-interface/src/assets/svg/earn/ic_smart_exit.svg b/apps/kyberswap-interface/src/assets/svg/earn/ic_smart_exit.svg new file mode 100644 index 0000000000..58ead3adba --- /dev/null +++ b/apps/kyberswap-interface/src/assets/svg/earn/ic_smart_exit.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/kyberswap-interface/src/components/Header/groups/EarnNavGroup.tsx b/apps/kyberswap-interface/src/components/Header/groups/EarnNavGroup.tsx index 0dfd7fdb29..6d7f632317 100644 --- a/apps/kyberswap-interface/src/components/Header/groups/EarnNavGroup.tsx +++ b/apps/kyberswap-interface/src/components/Header/groups/EarnNavGroup.tsx @@ -7,6 +7,7 @@ import { ReactComponent as OverviewIcon } from 'assets/svg/earn/ic_earn_overview import { ReactComponent as PoolsIcon } from 'assets/svg/earn/ic_earn_pools.svg' import { ReactComponent as PositionsIcon } from 'assets/svg/earn/ic_earn_positions.svg' import { ReactComponent as FarmingIcon } from 'assets/svg/earn/ic_farming.svg' +import { ReactComponent as ListSmartExitIcon } from 'assets/svg/earn/ic_list_smart_exit.svg' import { ReactComponent as KemIcon } from 'assets/svg/kyber/kem.svg' import NavGroup from 'components/Header/groups/NavGroup' import { DropdownTextAnchor, StyledNavLink } from 'components/Header/styleds' @@ -24,6 +25,7 @@ const EarnNavGroup = () => { APP_PATHS.EARN_POOLS, APP_PATHS.EARN_POSITIONS, APP_PATHS.EARN_POSITION_DETAIL, + APP_PATHS.EARN_SMART_EXIT, ].some(path => pathname.includes(path)) return ( @@ -98,6 +100,12 @@ const EarnNavGroup = () => { {t`My Positions`} + + + + {t`Smart Exit Orders`} + + } /> diff --git a/apps/kyberswap-interface/src/components/SwapForm/SlippageSetting.tsx b/apps/kyberswap-interface/src/components/SwapForm/SlippageSetting.tsx index 323cefc0c1..b91225d240 100644 --- a/apps/kyberswap-interface/src/components/SwapForm/SlippageSetting.tsx +++ b/apps/kyberswap-interface/src/components/SwapForm/SlippageSetting.tsx @@ -31,7 +31,7 @@ const highlight = keyframes` ` //transition: transform 300ms; -const DropdownIcon = styled.div` +export const DropdownIcon = styled.div` margin-left: 6px; border-radius: 50%; width: 12px; @@ -42,7 +42,7 @@ const DropdownIcon = styled.div` padding: 2px; transition: all 0.2s ease-in-out; - color: ${({ theme }) => theme.subText}; + color: ${({ theme }) => theme.white2}; &[data-flip='true'] { transform: rotate(180deg); } @@ -51,6 +51,10 @@ const DropdownIcon = styled.div` background: ${({ theme }) => rgba(theme.primary, 0.6)}; animation: ${highlight} 2s infinite alternate ease-in-out; } + + &[data-warning='true'] { + color: ${({ theme }) => rgba(theme.warning, 0.9)}; + } ` type Props = { diff --git a/apps/kyberswap-interface/src/components/swapv2/LimitOrder/ExpirePicker.tsx b/apps/kyberswap-interface/src/components/swapv2/LimitOrder/ExpirePicker.tsx index 7910d259f3..a5a82be28e 100644 --- a/apps/kyberswap-interface/src/components/swapv2/LimitOrder/ExpirePicker.tsx +++ b/apps/kyberswap-interface/src/components/swapv2/LimitOrder/ExpirePicker.tsx @@ -1,7 +1,7 @@ import { Trans } from '@lingui/macro' import dayjs from 'dayjs' import { rgba } from 'polished' -import { useCallback, useEffect, useMemo, useState } from 'react' +import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react' import { Calendar, X } from 'react-feather' import { Flex, Text } from 'rebass' import styled from 'styled-components' @@ -69,12 +69,16 @@ export default function DateTimePicker({ onSetDate, expire, defaultDate, + defaultOptions, + title, }: { isOpen: boolean onDismiss: () => void onSetDate: (val: Date | number) => void expire: number defaultDate?: Date + title?: ReactNode + defaultOptions?: { label: string; value: number }[] }) { const today = new Date() const minDate = new Date(today.getFullYear(), today.getMonth(), today.getDate()) @@ -103,8 +107,11 @@ export default function DateTimePicker({ } const onSelectDefaultOption = useCallback((value: number) => { - setDefaultExpire(value) - const date = new Date(Date.now() + value * 1000) + // value can be either seconds (duration) or timestamp (absolute date in seconds) + // If value is greater than a reasonable duration threshold, treat it as a timestamp + const isTimestamp = value > 1000000000 // Timestamps in seconds (10+ digits, represents Sep 2001+) + if (!isTimestamp) setDefaultExpire(value) + const date = isTimestamp ? new Date(value) : new Date(Date.now() + value * 1000) setDate(date) setHour(date.getHours()) setMin(date.getMinutes()) @@ -158,9 +165,7 @@ export default function DateTimePicker({ - - Customize the Expiry Time - + {title || Customize the Expiry Time} @@ -168,7 +173,7 @@ export default function DateTimePicker({ Default Options - {getExpireOptions().map(opt => ( + {(defaultOptions || getExpireOptions()).map(opt => ( - + - + - Order will Expire on + {title ? Order will trigger on : Order will Expire on} {dayjs(expireResult).format('DD/MM/YYYY HH:mm')} diff --git a/apps/kyberswap-interface/src/constants/env.ts b/apps/kyberswap-interface/src/constants/env.ts index 0f538f60dd..4c8fc0f7e5 100644 --- a/apps/kyberswap-interface/src/constants/env.ts +++ b/apps/kyberswap-interface/src/constants/env.ts @@ -57,6 +57,7 @@ export const REFERRAL_URL = required('REFERRAL_URL') export const TOKEN_API_URL = required('TOKEN_API_URL') export const AFFILIATE_SERVICE_URL = required('AFFILIATE_SERVICE') export const SOLANA_RPC = required('SOLANA_RPC') +export const SMART_EXIT_API_URL = required('SMART_EXIT_API_URL') type FirebaseConfig = { apiKey: string diff --git a/apps/kyberswap-interface/src/constants/index.ts b/apps/kyberswap-interface/src/constants/index.ts index 899c6c140d..a7d41580ae 100644 --- a/apps/kyberswap-interface/src/constants/index.ts +++ b/apps/kyberswap-interface/src/constants/index.ts @@ -187,6 +187,7 @@ export const APP_PATHS = { EARN_POOLS: '/earn/pools', EARN_POSITIONS: '/earn/positions', EARN_POSITION_DETAIL: '/earn/position/:positionId/:chainId/:exchange', + EARN_SMART_EXIT: '/earn/smart-exit', EARNS: '/earns', EARNS_POOLS: '/earns/pools', EARNS_POSITIONS: '/earns/positions', @@ -249,6 +250,9 @@ export const RTK_QUERY_TAGS = { GET_LIST_ORDERS: 'GET_LIST_ORDERS', GET_ORDERS_BY_TOKEN_PAIR: 'GET_ORDERS_BY_TOKEN_PAIR', + // smart exit + GET_SMART_EXIT_ORDERS: 'GET_SMART_EXIT_ORDERS', + GET_FARM_V2: 'GET_FARM_V2', } diff --git a/apps/kyberswap-interface/src/hooks/usePermitNft.ts b/apps/kyberswap-interface/src/hooks/usePermitNft.ts new file mode 100644 index 0000000000..93ba7e5e90 --- /dev/null +++ b/apps/kyberswap-interface/src/hooks/usePermitNft.ts @@ -0,0 +1,318 @@ +import { BigNumber } from '@ethersproject/bignumber' +import { t } from '@lingui/macro' +import { defaultAbiCoder, splitSignature } from 'ethers/lib/utils' +import { useCallback, useMemo, useState } from 'react' + +import { NotificationType } from 'components/Announcement/type' +import { useActiveWeb3React, useWeb3React } from 'hooks' +import { useReadingContract } from 'hooks/useContract' +import { useNotify } from 'state/application/hooks' +import { useSingleCallResult } from 'state/multicall/hooks' +import { friendlyError } from 'utils/errorMessage' + +export enum PermitNftState { + NOT_APPLICABLE = 'not_applicable', + READY_TO_SIGN = 'ready_to_sign', + SIGNING = 'signing', + SIGNED = 'signed', + ERROR = 'error', +} + +export interface PermitNftParams { + contractAddress: string + tokenId: string + spender: string + deadline: number + version?: 'v3' | 'v4' | 'auto' // specify version or auto-detect +} + +export interface PermitNftResult { + deadline: number + nonce: BigNumber + signature: string + permitData: string +} + +// NFT Position Manager ABI for permit functionality +const NFT_PERMIT_ABI = [ + 'function name() view returns (string)', + 'function nonces(address owner, uint256 word) view returns (uint256 bitmap)', // V4 unordered nonces + 'function positions(uint256 tokenId) view returns (uint96 nonce, address operator, address token0, address token1, uint24 fee, int24 tickLower, int24 tickUpper, uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, uint128 tokensOwed0, uint128 tokensOwed1)', // V3 ordered nonces + 'function DOMAIN_SEPARATOR() view returns (bytes32)', // V3 domain separator + 'function PERMIT_TYPEHASH() view returns (bytes32)', // V3 permit typehash + 'function permit(address spender, uint256 tokenId, uint256 deadline, uint256 nonce, bytes signature) payable', +] + +export const usePermitNft = ({ contractAddress, tokenId, spender, deadline, version = 'auto' }: PermitNftParams) => { + const { account, chainId } = useActiveWeb3React() + const { library } = useWeb3React() + const notify = useNotify() + const [isSigningInProgress, setIsSigningInProgress] = useState(false) + const [permitData, setPermitData] = useState(null) + const [detectedVersion, setDetectedVersion] = useState<'v3' | 'v4' | null>(null) + + const nftContract = useReadingContract(contractAddress, NFT_PERMIT_ABI) + + // Get nonces bitmap for word 0 (V4 style) + const noncesState = useSingleCallResult(nftContract, 'nonces', [account, 0]) + // Get position data (V3 style) - only call if we have a tokenId + const positionsState = useSingleCallResult(nftContract, 'positions', tokenId ? [tokenId] : undefined) + const nameState = useSingleCallResult(nftContract, 'name', []) + // Get V3 specific data + const domainSeparatorState = useSingleCallResult(nftContract, 'DOMAIN_SEPARATOR', []) + const permitTypehashState = useSingleCallResult(nftContract, 'PERMIT_TYPEHASH', []) + + // Auto-detect version based on available data + const actualVersion = useMemo(() => { + if (version !== 'auto') return version + + if (detectedVersion) return detectedVersion + + if (noncesState?.result && !noncesState.error) { + setDetectedVersion('v4') + return 'v4' + } + + // Try to detect based on available data + if (positionsState?.result && !positionsState.error) { + setDetectedVersion('v3') + return 'v3' + } + return 'v4' // Default to v4 if uncertain + }, [version, detectedVersion, positionsState, noncesState]) + + const permitState = useMemo(() => { + if (!account || !contractAddress || !tokenId || !spender) { + return PermitNftState.NOT_APPLICABLE + } + if (isSigningInProgress) { + return PermitNftState.SIGNING + } + if (permitData) { + return PermitNftState.SIGNED + } + return PermitNftState.READY_TO_SIGN + }, [account, contractAddress, tokenId, spender, isSigningInProgress, permitData]) + + // Get nonce based on version + const getNonce = useCallback((): BigNumber | null => { + if (actualVersion === 'v3') { + // Use ordered nonce from positions function + if (positionsState?.result?.[0] !== undefined) { + return BigNumber.from(positionsState.result[0]) + } + } else if (actualVersion === 'v4') return BigNumber.from(Math.floor(Date.now() / 1000)) + + return null + }, [actualVersion, positionsState?.result]) + + const signPermitNft = useCallback(async (): Promise => { + if (!library || !account || !chainId || !nameState?.result?.[0]) { + console.error('Missing required data for NFT permit') + return null + } + + // Check version-specific requirements + if ( + actualVersion === 'v3' && + (!positionsState?.result || !domainSeparatorState?.result || !permitTypehashState?.result) + ) { + console.error('Missing V3 contract data for NFT permit') + return null + } + if (actualVersion === 'v4' && !noncesState?.result?.[0]) { + console.error('Missing nonces data for V4 NFT permit') + return null + } + + if (permitState !== PermitNftState.READY_TO_SIGN) { + console.error('NFT permit not ready to sign') + return null + } + + setIsSigningInProgress(true) + + try { + const nonce = getNonce() + + if (!nonce) { + throw new Error(`Failed to get nonce for ${actualVersion}`) + } + + const permitDeadline = deadline + + let signature: string + let permitData: string + + if (actualVersion === 'v3') { + // V3 uses EIP-712 but with simpler domain structure + const contractName = nameState.result[0] + + // V3 domain structure (simpler than V4) + const domain = { + name: contractName, + version: '1', + chainId, + verifyingContract: contractAddress, + } + + const types = { + Permit: [ + { name: 'spender', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, + ], + } + + const message = { + spender, + tokenId, + nonce: nonce.toString(), + deadline: permitDeadline, + } + + const typedData = JSON.stringify({ + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + ...types, + }, + domain, + primaryType: 'Permit', + message, + }) + + console.log(`Signing ${actualVersion} NFT permit with data:`, typedData) + + const flatSig = await library.send('eth_signTypedData_v4', [account.toLowerCase(), typedData]) + + const sig = splitSignature(flatSig) + // V3 permit data: encode(deadline, v, r, s) + permitData = defaultAbiCoder.encode( + ['uint256', 'uint8', 'bytes32', 'bytes32'], + [permitDeadline, sig.v, sig.r, sig.s], + ) + signature = flatSig + } else { + // V4 uses EIP-712 typed data signing (keep existing working implementation) + const contractName = nameState.result[0] + + const domain = { + name: contractName, + chainId, + verifyingContract: contractAddress, + } + + const types = { + Permit: [ + { name: 'spender', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, + ], + } + + const message = { + spender, + tokenId, + nonce: nonce.toString(), + deadline: permitDeadline, + } + + const typedData = JSON.stringify({ + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + ...types, + }, + domain, + primaryType: 'Permit', + message, + }) + + console.log(`Signing ${actualVersion} NFT permit with data:`, typedData) + + signature = await library.send('eth_signTypedData_v4', [account.toLowerCase(), typedData]) + + // V4 permit data: encode(deadline, nonce, signature) - keep existing working format + permitData = defaultAbiCoder.encode(['uint256', 'uint256', 'bytes'], [permitDeadline, nonce, signature]) + } + + const v = actualVersion.toUpperCase() + notify({ + type: NotificationType.SUCCESS, + title: t`NFT Permit Signed`, + summary: t`Successfully signed ${v} permit for NFT #${tokenId}`, + }) + + const result = { + deadline: permitDeadline, + nonce, + signature, + permitData, + } + + setPermitData(result) + return result + } catch (error) { + const message = friendlyError(error) + console.error('NFT Permit error:', { message, error }) + + notify({ + title: t`NFT Permit Error`, + summary: message, + type: NotificationType.ERROR, + }) + + return null + } finally { + setIsSigningInProgress(false) + } + }, [ + account, + chainId, + library, + contractAddress, + tokenId, + spender, + deadline, + permitState, + actualVersion, + noncesState?.result, + positionsState?.result, + nameState?.result, + domainSeparatorState?.result, + permitTypehashState?.result, + getNonce, + notify, + ]) + + // Check readiness based on version + const isReady = useMemo(() => { + if (permitState !== PermitNftState.READY_TO_SIGN || !nameState?.result) { + return false + } + + if (actualVersion === 'v3') { + return !!positionsState?.result + } else { + return !!noncesState?.result + } + }, [permitState, nameState?.result, actualVersion, positionsState?.result, noncesState?.result]) + + return { + permitState, + signPermitNft, + permitData, + isReady, + version: actualVersion, + } +} diff --git a/apps/kyberswap-interface/src/pages/App.tsx b/apps/kyberswap-interface/src/pages/App.tsx index 123cc03c3f..52549b247a 100644 --- a/apps/kyberswap-interface/src/pages/App.tsx +++ b/apps/kyberswap-interface/src/pages/App.tsx @@ -68,6 +68,7 @@ const Earns = lazy(() => import('pages/Earns/Landing')) const EarnPoolExplorer = lazy(() => import('pages/Earns/PoolExplorer')) const EarnUserPositions = lazy(() => import('pages/Earns/UserPositions')) const EarnPositionDetail = lazy(() => import('pages/Earns/PositionDetail')) +const SmartExit = lazy(() => import('pages/Earns/SmartExitOrders')) const AppWrapper = styled.div` display: flex; @@ -358,6 +359,7 @@ export default function App() { } /> } /> } /> + } /> } /> } /> diff --git a/apps/kyberswap-interface/src/pages/Earns/PositionDetail/Header.tsx b/apps/kyberswap-interface/src/pages/Earns/PositionDetail/Header.tsx index 563a018dc4..3a188f3ccd 100644 --- a/apps/kyberswap-interface/src/pages/Earns/PositionDetail/Header.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/PositionDetail/Header.tsx @@ -26,10 +26,16 @@ const PositionDetailHeader = ({ position, isLoading, initialLoading, + rightComponent, + showBackIcon = true, + style = {}, }: { position?: ParsedPosition isLoading: boolean initialLoading: boolean + rightComponent?: React.ReactNode + showBackIcon?: boolean + style?: React.CSSProperties }) => { const theme = useTheme() const navigate = useNavigate() @@ -98,10 +104,11 @@ const PositionDetailHeader = ({ alignItems="center" justifyContent="space-between" marginBottom={1} + style={style} > - navigate(hadForceLoading ? -2 : -1)} /> + {showBackIcon && navigate(hadForceLoading ? -2 : -1)} />} {initialLoading ? ( @@ -181,12 +188,16 @@ const PositionDetailHeader = ({ {isLoading && !initialLoading && } - } - text={t`My Positions`} - to={APP_PATHS.EARN_POSITIONS} - /> + {rightComponent ? ( + rightComponent + ) : ( + } + text={t`My Positions`} + to={APP_PATHS.EARN_POSITIONS} + /> + )} ) } diff --git a/apps/kyberswap-interface/src/pages/Earns/PositionDetail/RightSection.tsx b/apps/kyberswap-interface/src/pages/Earns/PositionDetail/RightSection.tsx index 19ff33605a..de90d71891 100644 --- a/apps/kyberswap-interface/src/pages/Earns/PositionDetail/RightSection.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/PositionDetail/RightSection.tsx @@ -137,7 +137,7 @@ const RightSection = ({ }, [defaultRevertChecked, pool, chainId, stableCoins]) const isUnfinalized = position?.isUnfinalized - const isUniV4 = EARN_DEXES[exchange as Exchange]?.isForkFrom === CoreProtocol.UniswapV4 + const isUniV4 = position?.pool.isUniv4 const isClosed = position?.status === PositionStatus.CLOSED const isOutRange = position?.status === PositionStatus.OUT_RANGE diff --git a/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/Filter.tsx b/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/Filter.tsx new file mode 100644 index 0000000000..db9a23fefd --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/Filter.tsx @@ -0,0 +1,106 @@ +import { ChainId } from '@kyberswap/ks-sdk-core' +import { useEffect } from 'react' +import { useMedia } from 'react-use' +import { Flex } from 'rebass' + +import { NETWORKS_INFO } from 'hooks/useChainsConfig' +import { DexType } from 'pages/Earns/SmartExitOrders/useSmartExitFilter' +import DropdownMenu from 'pages/Earns/components/DropdownMenu' +import { AllChainsOption } from 'pages/Earns/hooks/useSupportedDexesAndChains' +import { OrderStatus, SmartExitFilter } from 'pages/Earns/types' +import { MEDIA_WIDTHS } from 'theme' + +const ORDER_STATUS = [ + { label: 'All Status', value: '' }, + { label: 'Active', value: OrderStatus.OrderStatusOpen }, + { label: 'Executed', value: OrderStatus.OrderStatusDone }, + { label: 'Expired', value: OrderStatus.OrderStatusExpired }, + { label: 'Cancelled', value: OrderStatus.OrderStatusCancelled }, +] + +const SUPPORTED_CHAINS = [ChainId.BSCMAINNET, ChainId.BASE].map(chainId => ({ + label: NETWORKS_INFO[chainId].name, + value: chainId.toString(), + icon: NETWORKS_INFO[chainId].icon, +})) + +const SUPPORTED_PROTOCOLS = [ + { label: 'All Protocols', value: '' }, + { label: 'Uniswap V3', value: DexType.DexTypeUniswapV3 }, + { label: 'Uniswap V4', value: DexType.DexTypeUniswapV4 }, + { label: 'Uniswap V4 FairFlow', value: DexType.DexTypeUniswapV4FairFlow }, + { label: 'PancakeSwap V3', value: DexType.DexTypePancakeV3 }, +] + +export default function Filter({ + filters, + updateFilters, +}: { + filters: SmartExitFilter + updateFilters: (key: keyof SmartExitFilter, value: string | number) => void +}) { + // const [searchParams] = useSearchParams() + // const [search, setSearch] = useState('') + // const deboundedSearch = useDebounce(search, 300) + const upToSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToSmall}px)`) + + // useEffect(() => { + // if (filters.q !== deboundedSearch) { + // updateFilters('q', deboundedSearch || '') + // } + // }, [deboundedSearch, filters.q, updateFilters]) + + // useEffect(() => { + // if (searchParams.get('q') && !search) { + // setSearch(searchParams.get('q') || '') + // } + // // eslint-disable-next-line react-hooks/exhaustive-deps + // }, []) + + useEffect(() => { + if ( + filters.dexTypes && + !SUPPORTED_PROTOCOLS.slice(1) + .map(item => item.value) + .filter(Boolean) + .includes(filters.dexTypes) + ) { + updateFilters('dexTypes', '') + } + }, [filters.dexTypes, updateFilters]) + + return ( + + + value !== filters.chainIds && updateFilters('chainIds', value)} + /> + value !== filters.dexTypes && updateFilters('dexTypes', value)} + /> + { + value !== filters.status && updateFilters('status', value) + }} + /> + + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/index.tsx b/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/index.tsx new file mode 100644 index 0000000000..6d25894335 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/index.tsx @@ -0,0 +1,472 @@ +import { Trans, t } from '@lingui/macro' +import dayjs from 'dayjs' +import React, { useState } from 'react' +import { Trash2, X } from 'react-feather' +import { useNavigate } from 'react-router' +import { useMedia } from 'react-use' +import { Flex, Text } from 'rebass' +import { + useCancelSmartExitOrderMutation, + useGetSmartExitCancelSignMessageMutation, + useGetSmartExitOrdersQuery, +} from 'services/smartExit' +import { useUserPositionsQuery } from 'services/zapEarn' +import styled from 'styled-components' + +import { NotificationType } from 'components/Announcement/type' +import { ButtonOutlined, ButtonPrimary } from 'components/Button' +import LocalLoader from 'components/LocalLoader' +import Modal from 'components/Modal' +import Pagination from 'components/Pagination' +import TokenLogo from 'components/TokenLogo' +import { useActiveWeb3React, useWeb3React } from 'hooks' +import useTheme from 'hooks/useTheme' +import { useChangeNetwork } from 'hooks/web3/useChangeNetwork' +import { PoolPageWrapper, TableWrapper } from 'pages/Earns/PoolExplorer/styles' +import { IconArrowLeft } from 'pages/Earns/PositionDetail/styles' +import Filter from 'pages/Earns/SmartExitOrders/Filter' +import useSmartExitFilter from 'pages/Earns/SmartExitOrders/useSmartExitFilter' +import { Badge, BadgeType, ImageContainer } from 'pages/Earns/UserPositions/styles' +import { EarnChain, Exchange } from 'pages/Earns/constants' +import { OrderStatus, PositionStatus, SmartExitOrder } from 'pages/Earns/types' +import { useNotify } from 'state/application/hooks' +import { MEDIA_WIDTHS } from 'theme' +import { enumToArrayOfValues } from 'utils' +import { friendlyError } from 'utils/errorMessage' + +const Trash = styled.div` + width: 20px; + height: 20px; + cursor: pointer; + color: ${({ theme }) => theme.subText}; + + :hover { + color: ${({ theme }) => theme.red}; + } +` + +const TableHeader = styled.div` + display: grid; + grid-template-columns: 1fr 1fr 0.5fr 40px; + color: ${({ theme }) => theme.subText}; + padding: 16px 0; + gap: 1rem; + border-bottom: 1px solid ${({ theme }) => theme.border}; +` + +const TableRow = styled(TableHeader)` + align-items: center; + color: ${({ theme }) => theme.text}; +` + +const SmartExit = () => { + const theme = useTheme() + const navigate = useNavigate() + const { account, chainId } = useActiveWeb3React() + const { library } = useWeb3React() + const notify = useNotify() + + const { filters, updateFilters } = useSmartExitFilter() + + const [showCancelConfirm, setShowCancelConfirm] = useState(null) + const [removing, setRemoving] = useState(false) + const [currentPage, setCurrentPage] = useState(1) + + const pageSize = 10 // Fixed page size + + const [getCancelSignMsg] = useGetSmartExitCancelSignMessageMutation() + const [cancelOrder] = useCancelSmartExitOrderMutation() + + const { changeNetwork } = useChangeNetwork() + const handleRemove = async () => { + if (!showCancelConfirm || !account || !library) return + + if (showCancelConfirm?.chainId && +chainId !== +showCancelConfirm.chainId) { + changeNetwork(+showCancelConfirm.chainId) + return + } + + setRemoving(true) + + try { + // Step 1: Get sign message from API + const signMessageResult = await getCancelSignMsg({ + chainId: showCancelConfirm.chainId, + userWallet: account, + orderId: +showCancelConfirm.id, + }).unwrap() + + const typedData = signMessageResult.message + + if (!typedData || !typedData.domain || !typedData.types || !typedData.message) { + throw new Error('Failed to get valid typed data from API') + } + + // Step 2: Sign the typed data + console.log('Signing cancel typed data:', typedData) + const signature = await library.send('eth_signTypedData_v4', [account, JSON.stringify(typedData)]) + + // Step 3: Cancel the order with signature + await cancelOrder({ + orderId: +showCancelConfirm.id, + chainId: showCancelConfirm.chainId, + userWallet: account, + signature, + }).unwrap() + + notify({ + type: NotificationType.SUCCESS, + title: t`Smart Exit Order Cancelled`, + summary: t`Your smart exit order has been successfully cancelled.`, + }) + + setShowCancelConfirm(null) + } catch (error) { + const message = friendlyError(error) + console.error('Cancel smart exit order error:', { message, error }) + + notify({ + title: t`Cancel Smart Exit Error`, + summary: message, + type: NotificationType.ERROR, + }) + } finally { + setRemoving(false) + } + } + + // Fetch smart exit orders + const { + data: ordersData, + isLoading: smartExitLoading, + isFetching, + error: ordersError, + } = useGetSmartExitOrdersQuery( + { + chainIds: filters.chainIds || undefined, + userWallet: account || '', + status: filters.status || undefined, + dexTypes: filters.dexTypes || undefined, + page: currentPage, + pageSize, + }, + { + skip: !account, + pollingInterval: 30000, // Poll every 30 seconds + }, + ) + + const orders = ordersData?.orders || [] + const totalItems = ordersData?.totalItems || 0 + + const earnSupportedChains = enumToArrayOfValues(EarnChain, 'number') + const earnSupportedExchanges = enumToArrayOfValues(Exchange) + const { data: userPosition, isLoading: userPosLoading } = useUserPositionsQuery( + { + chainIds: earnSupportedChains.join(','), + addresses: account || '', + protocols: earnSupportedExchanges.join(','), + positionStatus: 'all', + }, + { + skip: !account, + pollingInterval: 15_000, + }, + ) + + const loading = smartExitLoading || userPosLoading || isFetching + + const upToMedium = useMedia(`(max-width: ${MEDIA_WIDTHS.upToMedium}px)`) + + return ( + + + navigate(-1)} /> + + Smart Exit Orders + + + + { + updateFilters(...args) + }} + /> + + + {!upToMedium && ( + + + Position + + + Conditional + + + Status + +
+
+ )} + + {loading ? ( + + + + ) : ordersError || orders?.length === 0 ? ( + + + No smart exit orders found + + + ) : ( + orders.map(order => { + const posDetail = userPosition?.find(us => order.positionId === us.id) + if (!posDetail) return null + const token0 = posDetail.currentAmounts[0].token + const token1 = posDetail.currentAmounts[1].token + const tokenId = order.positionId.split('-')[1] + + const { conditions, op } = order.condition.logical + + const protocol = (() => { + switch (posDetail.pool.exchange) { + case Exchange.DEX_UNISWAPV3: + case Exchange.DEX_PANCAKESWAPV3: + return 'V3' + case Exchange.DEX_UNISWAP_V4: + return 'V4' + case Exchange.DEX_UNISWAP_V4_FAIRFLOW: + case Exchange.DEX_PANCAKE_INFINITY_CL_FAIRFLOW: + return 'FairFlow' + default: + return posDetail.pool.exchange + } + })() + const posStatus = posDetail.status || PositionStatus.IN_RANGE + const title = ( + <> + + + + + + + + {token0.symbol}/{token1.symbol} + + Fee {posDetail?.pool.tickSpacing / 10_0}% + + + + + {protocol} #{tokenId} + + + ●{' '} + {posStatus === PositionStatus.IN_RANGE + ? t`In range` + : posStatus === PositionStatus.OUT_RANGE + ? t`Out of range` + : t`Closed`} + + + + ) + + const condition = ( + + {conditions.map((c, i) => { + if (c.field.type === 'fee_yield') + return ( + + The{' '} + + fee yield ≥ {Number(c.field.value.gte.toFixed(2))}% + {' '} + {i !== conditions.length - 1 && ( + + {op.toUpperCase()} + + )} + + ) + + if (c.field.type === 'pool_price') + return ( + + Pool price is between{' '} + + {c.field.value.gte} + {' '} + and{' '} + + {c.field.value.lte} + {' '} + {i !== conditions.length - 1 && ( + + {op.toUpperCase()} + + )} + + ) + + if (c.field.type === 'time') + return ( + + + {c.field.value.lte > 0 && c.field.value.lte < 4914460753 ? ( + <> + Before{' '} + + {dayjs(c.field.value.lte * 1000).format('DD/MM/YYYY HH:mm:ss')} + + + ) : null} + + {c.field.value.gte > 0 && c.field.value.gte < 4914460753 ? ( + + After{' '} + + {dayjs(c.field.value.gte * 1000).format('DD/MM/YYYY HH:mm:ss')} + + + ) : null} + + {i !== conditions.length - 1 && ( + + {op.toUpperCase()} + + )} + + ) + return null + })} + + ) + const status = ( + + {order.status === OrderStatus.OrderStatusOpen + ? 'Active' + : order.status === OrderStatus.OrderStatusDone + ? 'Executed' + : order.status === OrderStatus.OrderStatusCancelled + ? 'Cancelled' + : order.status === OrderStatus.OrderStatusExpired + ? 'Expired' + : order.status} + + ) + + const actionDelete = ( + { + setShowCancelConfirm(order) + }} + role="button" + > + + + + + ) + + if (upToMedium) + return ( + +
{title}
+ {condition} + + {status} + {order.status === OrderStatus.OrderStatusOpen ? actionDelete :
} + + + ) + + return ( + +
{title}
+ + {condition} + + {status} + {order.status === OrderStatus.OrderStatusOpen ? actionDelete :
} + + ) + }) + )} + + + + + setShowCancelConfirm(null)}> + + + + Removing a Smart Exit + + setShowCancelConfirm(null)} /> + + Are you sure you want to remove this Smart Exit? + + setShowCancelConfirm(null)}> + Cancel + + + {removing ? ( + Removing... + ) : showCancelConfirm?.chainId && +chainId !== +showCancelConfirm.chainId ? ( + Switch Chain + ) : ( + Remove + )} + + + + + + ) +} + +export default SmartExit diff --git a/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/useSmartExitFilter.ts b/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/useSmartExitFilter.ts new file mode 100644 index 0000000000..2646c53e41 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/useSmartExitFilter.ts @@ -0,0 +1,53 @@ +import { useCallback, useMemo } from 'react' +import { useSearchParams } from 'react-router-dom' + +import { SmartExitFilter } from 'pages/Earns/types' + +export enum DexType { + DexTypeUniswapV3 = 'DexTypeUniswapV3', + DexTypeUniswapV4 = 'DexTypeUniswapV4', + DexTypeUniswapV4FairFlow = 'DexTypeUniswapV4FairFlow', + DexTypePancakeV3 = 'DexTypePancakeV3', + DexTypePancakeInfinityCL = 'DexTypePancakeInfinityCL', + DexTypePancakeInfinityCLFairFlow = 'DexTypePancakeInfinityCLFairFlow', +} + +export default function useSmartExitFilter() { + const [searchParams, setSearchParams] = useSearchParams() + + const filters: SmartExitFilter = useMemo( + () => ({ + chainIds: searchParams.get('chainIds') || '', + dexTypes: searchParams.get('dexTypes') || '', + status: searchParams.get('status') || '', + // q: searchParams.get('q') || '', + // sortBy: searchParams.get('sortBy') || SortBy.VALUE, + // orderBy: searchParams.get('orderBy') || Direction.DESC, + page: +(searchParams.get('page') || 1), + }), + [searchParams], + ) + + const updateFilters = useCallback( + (key: keyof SmartExitFilter, value: string | number) => { + console.log(value, key) + if (!value) searchParams.delete(key) + else searchParams.set(key, value.toString()) + + // if ((key !== 'sortBy' && key !== 'orderBy' && key !== 'page') || (key === 'page' && value === 1)) + // searchParams.delete('page') + + // const orderBy = searchParams.get('orderBy') + // const sortBy = searchParams.get('sortBy') + // if (orderBy === Direction.DESC && sortBy === SortBy.VALUE) { + // searchParams.delete('orderBy') + // searchParams.delete('sortBy') + // } + + setSearchParams(searchParams) + }, + [searchParams, setSearchParams], + ) + + return { filters, updateFilters } +} diff --git a/apps/kyberswap-interface/src/pages/Earns/UserPositions/DropdownAction.tsx b/apps/kyberswap-interface/src/pages/Earns/UserPositions/DropdownAction.tsx index 953b238d44..64886f516e 100644 --- a/apps/kyberswap-interface/src/pages/Earns/UserPositions/DropdownAction.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/UserPositions/DropdownAction.tsx @@ -2,15 +2,23 @@ import { t } from '@lingui/macro' import { useCallback, useEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' import { Minus, MoreVertical, Plus } from 'react-feather' +import { useNavigate } from 'react-router' import { useMedia } from 'react-use' import { Text } from 'rebass' import styled from 'styled-components' import { ReactComponent as IconClaimRewards } from 'assets/svg/earn/ic_claim.svg' import { ReactComponent as IconClaimFees } from 'assets/svg/earn/ic_earn_claim_fees.svg' +import { ReactComponent as ListSmartExitIcon } from 'assets/svg/earn/ic_list_smart_exit.svg' import { ReactComponent as IconReposition } from 'assets/svg/earn/ic_reposition.svg' +import { ReactComponent as IconSmartExit } from 'assets/svg/earn/ic_smart_exit.svg' import Loader from 'components/Loader' +import { MouseoverTooltip } from 'components/Tooltip' +import { APP_PATHS } from 'constants/index' +import { useActiveWeb3React } from 'hooks' import useTheme from 'hooks/useTheme' +import { DexMapping } from 'pages/Earns/components/SmartExit/useSmartExit' +import { EARN_CHAINS, EarnChain } from 'pages/Earns/constants' import { ParsedPosition, PositionStatus } from 'pages/Earns/types' import { MEDIA_WIDTHS } from 'theme' @@ -114,13 +122,16 @@ const DropdownAction = ({ position, onOpenIncreaseLiquidityWidget, onOpenZapOut, + onOpenSmartExit, onOpenReposition, claimFees: { onClaimFee, feesClaimDisabled, feesClaiming, positionThatClaimingFees }, claimRewards: { onClaimRewards, rewardsClaimDisabled, rewardsClaiming, positionThatClaimingRewards }, + hasActiveSmartExitOrder, }: { position: ParsedPosition onOpenIncreaseLiquidityWidget: (e: React.MouseEvent, position: ParsedPosition) => void onOpenZapOut: (e: React.MouseEvent, position: ParsedPosition) => void + onOpenSmartExit: (e: React.MouseEvent, position: ParsedPosition) => void onOpenReposition: (e: React.MouseEvent, position: ParsedPosition) => void claimFees: { onClaimFee: (e: React.MouseEvent, position: ParsedPosition) => void @@ -134,8 +145,10 @@ const DropdownAction = ({ rewardsClaiming: boolean positionThatClaimingRewards: ParsedPosition | null } + hasActiveSmartExitOrder: boolean }) => { const theme = useTheme() + const { account } = useActiveWeb3React() const [open, setOpen] = useState(false) const [portalPosition, setPortalPosition] = useState({ top: 0, left: 0 }) const ref = useRef(null) @@ -143,6 +156,8 @@ const DropdownAction = ({ const tickingRef = useRef(false) const upToExtraSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToExtraSmall}px)`) + const navigate = useNavigate() + const updatePortalPosition = useCallback(() => { if (!ref.current) return const rect = ref.current.getBoundingClientRect() @@ -225,75 +240,118 @@ const DropdownAction = ({ return () => document.removeEventListener('mousedown', handleClickOutside) }, [ref]) - const renderActionItems = () => ( - <> - { - e.stopPropagation() - handleAction(e, onOpenIncreaseLiquidityWidget) - }} - > - - {position.status === PositionStatus.CLOSED ? t`Add Liquidity` : t`Increase Liquidity`} - - { - e.stopPropagation() - if (position.status === PositionStatus.CLOSED) return - handleAction(e, onOpenZapOut) - }} - > - - {t`Remove Liquidity`} - - { - e.stopPropagation() - if (!feesClaimDisabled) { - handleAction(e, onClaimFee) - } else e.preventDefault() - }} - > - {feesClaiming && positionThatClaimingFees && positionThatClaimingFees.tokenId === position.tokenId ? ( + const increaseDisabled = position.status === PositionStatus.CLOSED && position.pool.isUniv4 + const removeDisabled = position.status === PositionStatus.CLOSED + const repositionDisabled = position.status === PositionStatus.CLOSED || position.pool.isUniv2 + const smartExitDisabled = + !Object.keys(DexMapping).includes(position.dex.id) || + !EARN_CHAINS[position.chain.id as unknown as EarnChain].smartExitSupported || + position.status === PositionStatus.CLOSED || + (position.stakingOwner ? account !== position.stakingOwner : false) + + const dexName = position.dex.name + const chainName = position.chain.name + const actionItems = [ + { + label: t`Increase Liquidity`, + disabled: increaseDisabled, + icon: , + onClick: (e: React.MouseEvent) => { + e.stopPropagation() + if (increaseDisabled) return + handleAction(e, onOpenIncreaseLiquidityWidget) + }, + }, + { + label: t`Remove Liquidity`, + disabled: removeDisabled, + icon: , + onClick: (e: React.MouseEvent) => { + e.stopPropagation() + if (removeDisabled) return + handleAction(e, onOpenZapOut) + }, + }, + { + label: t`Claim Fees`, + disabled: feesClaimDisabled, + icon: + feesClaiming && positionThatClaimingFees && positionThatClaimingFees.tokenId === position.tokenId ? ( ) : ( - )} - {t`Claim Fees`} - - { - e.stopPropagation() - if (!rewardsClaimDisabled) { - handleAction(e, onClaimRewards) - } else e.preventDefault() - }} - > - {rewardsClaiming && positionThatClaimingRewards && positionThatClaimingRewards.tokenId === position.tokenId ? ( + ), + onClick: (e: React.MouseEvent) => { + e.stopPropagation() + if (!feesClaimDisabled) handleAction(e, onClaimFee) + else e.preventDefault() + }, + }, + { + label: t`Claim Rewards`, + disabled: rewardsClaimDisabled, + icon: + rewardsClaiming && positionThatClaimingRewards && positionThatClaimingRewards.tokenId === position.tokenId ? ( ) : ( - )} - {t`Claim Rewards`} - - {!position.pool.isUniv2 ? ( - { - e.stopPropagation() - if (position.status === PositionStatus.CLOSED) return - handleAction(e, onOpenReposition) - }} - > - - {t`Reposition`} + ), + onClick: (e: React.MouseEvent) => { + e.stopPropagation() + if (!rewardsClaimDisabled) handleAction(e, onClaimRewards) + else e.preventDefault() + }, + }, + { + label: t`Reposition`, + disabled: repositionDisabled, + icon: , + onClick: (e: React.MouseEvent) => { + e.stopPropagation() + if (repositionDisabled) return + handleAction(e, onOpenReposition) + }, + }, + { + label: hasActiveSmartExitOrder ? t`View Smart Exit Orders` : t`Smart Exit`, + disabled: smartExitDisabled, + disabledTooltip: !Object.keys(DexMapping).includes(position.dex.id) + ? t`Smart Exit is currently not supported on ${dexName}` + : !EARN_CHAINS[position.chain.id as unknown as EarnChain].smartExitSupported + ? t`Smart Exit is currently not supported on ${chainName}` + : position.stakingOwner && account !== position.stakingOwner + ? t`Position is in farming in another protocol` + : '', + icon: hasActiveSmartExitOrder ? : , + onClick: (e: React.MouseEvent) => { + e.stopPropagation() + e.preventDefault() + if (hasActiveSmartExitOrder) { + navigate(APP_PATHS.EARN_SMART_EXIT) + return + } + if (smartExitDisabled) return + handleAction(e, onOpenSmartExit) + }, + }, + ] + + const renderActionItems = () => + actionItems.map((item, index) => + item.disabledTooltip ? ( + + + {item.icon} + {item.label} + + + ) : ( + + {item.icon} + {item.label} - ) : null} - - ) + ), + ) return ( diff --git a/apps/kyberswap-interface/src/pages/Earns/UserPositions/TableContent.tsx b/apps/kyberswap-interface/src/pages/Earns/UserPositions/TableContent.tsx index 83a8a08fbc..41e76fd71e 100644 --- a/apps/kyberswap-interface/src/pages/Earns/UserPositions/TableContent.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/UserPositions/TableContent.tsx @@ -6,6 +6,7 @@ import { ArrowRight, ArrowRightCircle } from 'react-feather' import { Link } from 'react-router-dom' import { useMedia } from 'react-use' import { Flex, Text } from 'rebass' +import { useGetSmartExitOrdersQuery } from 'services/smartExit' import { ReactComponent as IconEarnNotFound } from 'assets/svg/earn/ic_earn_not_found.svg' import { ReactComponent as FarmingIcon } from 'assets/svg/kyber/kem.svg' @@ -35,6 +36,7 @@ import { import AprDetailTooltip from 'pages/Earns/components/AprDetailTooltip' import PositionSkeleton from 'pages/Earns/components/PositionSkeleton' import RewardSyncing from 'pages/Earns/components/RewardSyncing' +import { SmartExit } from 'pages/Earns/components/SmartExit' import { EARN_DEXES, LIMIT_TEXT_STYLES } from 'pages/Earns/constants' import { CoreProtocol } from 'pages/Earns/constants/coreProtocol' import useCollectFees from 'pages/Earns/hooks/useCollectFees' @@ -44,7 +46,7 @@ import useMerklRewards from 'pages/Earns/hooks/useMerklRewards' import { ZapInInfo } from 'pages/Earns/hooks/useZapInWidget' import useZapMigrationWidget from 'pages/Earns/hooks/useZapMigrationWidget' import { ZapOutInfo } from 'pages/Earns/hooks/useZapOutWidget' -import { FeeInfo, ParsedPosition, PositionStatus, SuggestedPool } from 'pages/Earns/types' +import { FeeInfo, OrderStatus, ParsedPosition, PositionStatus, SuggestedPool } from 'pages/Earns/types' import { getUnclaimedFeesInfo } from 'pages/Earns/utils/fees' import { checkEarlyPosition } from 'pages/Earns/utils/position' import { useWalletModalToggle } from 'state/application/hooks' @@ -80,6 +82,21 @@ export default function TableContent({ const [positionThatClaimingFees, setPositionThatClaimingFees] = useState(null) const [positionThatClaimingRewards, setPositionThatClaimingRewards] = useState(null) const [positionToMigrate, setPositionToMigrate] = useState(null) + const [smartExitPosition, setSmartExitPosition] = useState(null) + + const { data: smartExitOrders } = useGetSmartExitOrdersQuery( + { + userWallet: account || '', + positionIds: positions?.map(pos => pos.id) || [], + status: OrderStatus.OrderStatusOpen, + page: 1, + pageSize: positions?.length || 10, + }, + { + skip: (positions?.length || 0) === 0, + }, + ) + const smartExitPosIds = smartExitOrders?.orders.map(order => order.positionId) || [] const { claimModal: claimFeesModal, @@ -284,6 +301,7 @@ export default function TableContent({ {claimRewardsModal} {zapMigrationWidget} {migrationModal} + {smartExitPosition && setSmartExitPosition(null)} />}
{account && positions && positions.length > 0 @@ -319,8 +337,12 @@ export default function TableContent({ const actions = ( { + setSmartExitPosition(position) + }} onOpenReposition={handleReposition} claimFees={{ onClaimFee: handleClaimFees, diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Actions.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Actions.tsx new file mode 100644 index 0000000000..438e215ce8 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Actions.tsx @@ -0,0 +1,31 @@ +import { Trans } from '@lingui/macro' +import { Flex, Text } from 'rebass' + +import { ButtonOutlined, ButtonPrimary } from 'components/Button' + +export default function Actions({ + onDismiss, + onPreview, + disabled, + feeLoading, +}: { + onDismiss: () => void + onPreview: () => void + disabled: boolean + feeLoading: boolean +}) { + return ( + + + + Cancel + + + + + {feeLoading ? Estimating fee... : Preview} + + + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx new file mode 100644 index 0000000000..b0de40f0af --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx @@ -0,0 +1,274 @@ +import { Trans, t } from '@lingui/macro' +import dayjs from 'dayjs' +import { X } from 'react-feather' +import { useNavigate } from 'react-router' +import { Box, Flex, Text } from 'rebass' + +import { ButtonOutlined, ButtonPrimary } from 'components/Button' +import { CheckCircle } from 'components/Icons' +import TokenLogo from 'components/TokenLogo' +import { MouseoverTooltip, TextDashed } from 'components/Tooltip' +import { APP_PATHS } from 'constants/index' +import { useActiveWeb3React } from 'hooks' +import { PermitNftState, usePermitNft } from 'hooks/usePermitNft' +import useTheme from 'hooks/useTheme' +import { useChangeNetwork } from 'hooks/web3/useChangeNetwork' +import { Badge, ImageContainer } from 'pages/Earns/UserPositions/styles' +import { useSmartExit } from 'pages/Earns/components/SmartExit/useSmartExit' +import { SMART_EXIT_ADDRESS } from 'pages/Earns/constants' +import { + ConditionType, + FeeYieldCondition, + Metric, + ParsedPosition, + PriceCondition, + SelectedMetric, + TimeCondition, +} from 'pages/Earns/types' + +export const Confirmation = ({ + selectedMetrics, + position, + deadline, + conditionType, + feeSettings: { protocolFee, maxFeesPercentage }, + onDismiss, +}: { + selectedMetrics: SelectedMetric[] + position: ParsedPosition + conditionType: ConditionType + deadline: number + feeSettings: { protocolFee: number; maxFeesPercentage: number } + onDismiss: () => void +}) => { + const theme = useTheme() + const navigate = useNavigate() + const { chainId } = useActiveWeb3React() + const { changeNetwork } = useChangeNetwork() + + const { permitState, signPermitNft, permitData } = usePermitNft({ + contractAddress: position.id.split('-')[0], + tokenId: position.tokenId, + spender: SMART_EXIT_ADDRESS, + deadline, + }) + + const { createSmartExitOrder, isCreating, isSuccess } = useSmartExit({ + position, + selectedMetrics, + conditionType, + deadline, + permitData: permitData?.permitData, + signature: permitData?.signature, + }) + + const [metric1, metric2] = selectedMetrics + + const feeYieldCondition1 = metric1.condition as FeeYieldCondition + const priceCondition1 = metric1.condition as PriceCondition + const timeCondition1 = metric1.condition as TimeCondition + const feeYieldCondition2 = metric2?.condition as FeeYieldCondition + const priceCondition2 = metric2?.condition as PriceCondition + const timeCondition2 = metric2?.condition as TimeCondition + + const displayTime = dayjs(deadline * 1000).format('DD/MM/YYYY HH:mm:ss') + + if (isSuccess) + return ( + <> + +
+ +
+ + + + + + Condition saved + + + + + Your Smart Exit condition has been created successfully. + + + + + Cancel + + { + navigate(APP_PATHS.EARN_SMART_EXIT) + }} + > + View All Condition(s) + + + + ) + + return ( + <> + + + Confirmation + + + + + + Exit + + + + + + + + {position.token0.symbol}/{position.token1.symbol} + + Fee {position?.pool.fee}% + + When + + + + {metric1.metric === Metric.FeeYield && The fee yield ≥ {feeYieldCondition1}%} + {metric1.metric === Metric.Time && ( + <> + {timeCondition1.condition.charAt(0).toUpperCase() + timeCondition1.condition.slice(1)} + {dayjs(timeCondition1.time).format('DD/MM/YYYY HH:mm:ss')} + + )} + {metric1.metric === Metric.PoolPrice && ( + <> + + Pool price is between + + + {priceCondition1.gte} and {priceCondition1.lte} {position.token0.symbol}/{position.token1.symbol} + + + )} + {metric2 && ( + <> + + + {conditionType === ConditionType.And ? And : Or} + + + + {metric2.metric === Metric.FeeYield && The fee yield ≥ {feeYieldCondition2}%} + {metric2.metric === Metric.Time && ( + <> + {timeCondition2.condition.charAt(0).toUpperCase() + timeCondition2.condition.slice(1)} + {dayjs(timeCondition2.time).format('DD/MM/YYYY HH:mm:ss')} + + )} + {metric2.metric === Metric.PoolPrice && ( + <> + + Pool price is between + + + {priceCondition2.gte} and {priceCondition2.lte} {position.token0.symbol}/{position.token1.symbol} + + + )} + + )} + + + + + Platform Fee + + + {protocolFee}% + + + + + + + Expires in + + + + {displayTime} + + + + + + The information is intended solely for your reference at the time you are viewing. It is your responsibility + to verify all information before making decisions + + + + { + if (!maxFeesPercentage) return + if (chainId !== position.chain.id) { + changeNetwork(position.chain.id) + return + } + + if (permitState === PermitNftState.SIGNED && permitData) { + // Create smart exit order + await createSmartExitOrder({ maxFeesPercentage: [maxFeesPercentage, maxFeesPercentage] }) + return + } + if (permitState === PermitNftState.READY_TO_SIGN) { + await signPermitNft() + } + }} + > + {chainId !== position.chain.id ? ( + Switch Network + ) : isCreating ? ( + Creating Order... + ) : permitState === PermitNftState.SIGNED ? ( + Confirm Smart Exit + ) : permitState === PermitNftState.SIGNING ? ( + Signing... + ) : ( + Permit NFT + )} + + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/ExpireSetting.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/ExpireSetting.tsx new file mode 100644 index 0000000000..2d3e3547a3 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/ExpireSetting.tsx @@ -0,0 +1,136 @@ +import { Trans, t } from '@lingui/macro' +import dayjs from 'dayjs' +import { useMemo, useState } from 'react' +import { Flex, Text } from 'rebass' + +import { DefaultSlippageOption } from 'components/SlippageControl' +import { MouseoverTooltip, TextDashed } from 'components/Tooltip' +import DateTimePicker from 'components/swapv2/LimitOrder/ExpirePicker' +import { TIMES_IN_SECS } from 'constants/index' +import useTheme from 'hooks/useTheme' +import { formatTimeDuration } from 'utils/time' + +export const DEFAULT_TIME_OPTIONS = [ + 5 * TIMES_IN_SECS.ONE_MIN, + 10 * TIMES_IN_SECS.ONE_MIN, + TIMES_IN_SECS.ONE_HOUR, + TIMES_IN_SECS.ONE_DAY, + 3 * TIMES_IN_SECS.ONE_DAY, + 7 * TIMES_IN_SECS.ONE_DAY, + 30 * TIMES_IN_SECS.ONE_DAY, +].map(e => ({ value: e, label: formatTimeDuration(e) })) + +export default function ExpireSetting({ + expireTime, + setExpireTime, +}: { + expireTime: number + setExpireTime: (v: number) => void +}) { + const theme = useTheme() + const [openDatePicker, setOpenDatePicker] = useState(false) + + const displayTime = useMemo( + () => + expireTime % TIMES_IN_SECS.ONE_DAY === 0 + ? `${expireTime / TIMES_IN_SECS.ONE_DAY}D` + : dayjs(expireTime).format('DD/MM/YYYY HH:mm:ss'), + [expireTime], + ) + + return ( + <> + setOpenDatePicker(false)} + onSetDate={(val: Date | number) => setExpireTime(typeof val === 'number' ? val : val.getTime())} + expire={expireTime} + /> + + + + + Expires in + + + + + + {displayTime} + + + + + + + + {[ + { label: '7D', value: TIMES_IN_SECS.ONE_DAY * 7 }, + { label: '30D', value: TIMES_IN_SECS.ONE_DAY * 30 }, + { label: '90D', value: TIMES_IN_SECS.ONE_DAY * 90 }, + { + label: 'Custom', + onSelect: () => { + setOpenDatePicker(true) + }, + }, + ].map((item: any) => { + return ( + { + if (item.label === 'Custom') item.onSelect() + else setExpireTime(item.value) + }} + data-active={ + item.label === 'Custom' ? expireTime % TIMES_IN_SECS.ONE_DAY != 0 : item.value === expireTime + } + > + {item.label} + + ) + })} + + + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/GasSetting.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/GasSetting.tsx new file mode 100644 index 0000000000..397f840502 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/GasSetting.tsx @@ -0,0 +1,167 @@ +import { Trans, t } from '@lingui/macro' +import { rgba } from 'polished' +import { useState } from 'react' +import { Box, Flex, Text } from 'rebass' + +import Input from 'components/NumericalInput' +import { DropdownIcon } from 'components/SwapForm/SlippageSetting' +import useTheme from 'hooks/useTheme' +import { SmartExitFee } from 'pages/Earns/types' +import { formatDisplayNumber } from 'utils/numbers' + +export default function GasSetting({ + feeInfo, + multiplier, + setMultiplier, + customGasPercent, + setCustomGasPercent, +}: { + feeInfo: SmartExitFee | null + multiplier: number + setMultiplier: (value: number) => void + customGasPercent: string + setCustomGasPercent: (value: string) => void +}) { + const theme = useTheme() + const [feeSettingExpanded, setFeeSettingExpanded] = useState(false) + + const isWarningGas = feeInfo && customGasPercent && parseFloat(customGasPercent) < (feeInfo.gas.percentage || 0) + const isHighlightGas = + feeInfo && + !feeSettingExpanded && + (customGasPercent ? parseFloat(customGasPercent) > feeInfo.gas.percentage : multiplier > 1) + + return ( + <> + + {t`Max Execution Gas`}: + {!feeInfo ? ( + -- + ) : ( + setFeeSettingExpanded(e => !e)} style={{ cursor: 'default' }}> + + {customGasPercent + ? customGasPercent + : formatDisplayNumber(feeInfo.gas.percentage * multiplier, { significantDigits: 2 })} + % + + + (~ + {formatDisplayNumber( + feeInfo.gas.usd * + (customGasPercent ? parseFloat(customGasPercent) / feeInfo.gas.percentage : multiplier), + { significantDigits: 2, style: 'currency' }, + )} + ) + + + + + + + + )} + + + + {[1, 1.5, 2, 3].map(item => { + const isSelected = !customGasPercent && multiplier === item + return ( + { + setCustomGasPercent('') + setMultiplier(item) + }} + sx={{ + borderRadius: '999px', + border: `1px solid ${isSelected ? theme.primary : theme.border}`, + backgroundColor: isSelected ? theme.primary + '20' : 'transparent', + padding: '6px 4px', + color: isSelected ? theme.primary : theme.subText, + fontSize: '12px', + fontWeight: '500', + cursor: 'pointer', + textAlign: 'center', + flex: 1, + '&:hover': { + backgroundColor: theme.primary + '10', + }, + }} + > + {formatDisplayNumber(item * (feeInfo?.gas.percentage || 0), { significantDigits: 2 })}% + + ) + })} + + {/* Custom option */} + + setCustomGasPercent(v)} + placeholder={t`Custom`} + style={{ + width: '100%', + background: 'transparent', + fontSize: '12px', + }} + /> + + % + + + + + + {t`Current est. gas`} = {formatDisplayNumber(feeInfo?.gas.percentage || 0, { significantDigits: 2 })}% + + + + The buffer amount is recommended. The order will{' '} + + not execute + {' '} + if the actual cost exceeds this. + + + + The actual gas cost will be deducted from your outputs when the order executes. + + + + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx new file mode 100644 index 0000000000..4abb293f76 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx @@ -0,0 +1,494 @@ +import { Label, RadioGroup, RadioGroupItem } from '@kyber/ui' +import { nearestUsableTick, priceToClosestTick, tickToPrice } from '@kyber/utils/dist/uniswapv3' +import UniswapPriceSlider from '@kyberswap/price-slider' +import '@kyberswap/price-slider/style.css' +import { Trans, t } from '@lingui/macro' +import dayjs from 'dayjs' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { Calendar } from 'react-feather' +import { Box, Flex, Text } from 'rebass' + +import Divider from 'components/Divider' +import DateTimePicker from 'components/swapv2/LimitOrder/ExpirePicker' +import useTheme from 'hooks/useTheme' +import { DEFAULT_TIME_OPTIONS } from 'pages/Earns/components/SmartExit/ExpireSetting' +import { CustomInput, CustomSelect } from 'pages/Earns/components/SmartExit/styles' +import { getDefaultCondition } from 'pages/Earns/components/SmartExit/utils' +import { + ConditionType, + FeeYieldCondition, + Metric, + ParsedPosition, + PriceCondition, + SelectedMetric, + TimeCondition, +} from 'pages/Earns/types' +import { ButtonText } from 'theme' + +const supportedMetrics = [Metric.FeeYield, Metric.PoolPrice, Metric.Time] + +export const Metrics = ({ + position, + selectedMetrics, + setSelectedMetrics, + conditionType, + setConditionType, +}: { + position: ParsedPosition + selectedMetrics: SelectedMetric[] + setSelectedMetrics: (value: SelectedMetric[]) => void + conditionType: ConditionType + setConditionType: (v: ConditionType) => void +}) => { + const theme = useTheme() + const [metric1, metric2] = selectedMetrics + + const onChangeMetric1 = (value: SelectedMetric) => setSelectedMetrics(metric2 ? [value, metric2] : [value]) + const onChangeMetric2 = (value: SelectedMetric) => setSelectedMetrics([metric1, value]) + const onRemoveMetric2 = () => setSelectedMetrics([metric1]) + const onAddMetric2 = () => { + const newMetric = supportedMetrics.filter(item => item === Metric.PoolPrice || item !== metric1.metric)[0] + const newCondition = getDefaultCondition(newMetric) + if (!newCondition) return + setSelectedMetrics([metric1, { metric: newMetric, condition: newCondition }]) + } + + return ( + + + + {metric2 ? ( + <> + + setConditionType(v as ConditionType)}> + + + + + + + + + + Remove Condition + + + + + + ) : ( + + + Add Condition 2 + + )} + + ) +} + +const MetricSelect = ({ + metric, + setMetric, + selectedMetric, + position, +}: { + metric: SelectedMetric + setMetric: (value: SelectedMetric) => void + selectedMetric?: SelectedMetric + position: ParsedPosition +}) => { + const metricOptions = useMemo( + () => + [ + { + label: t`Fee Yield`, + value: Metric.FeeYield, + }, + { + label: t`Pool Price`, + value: Metric.PoolPrice, + }, + { + label: t`Time`, + value: Metric.Time, + }, + ].filter(item => item.value === Metric.PoolPrice || item.value !== selectedMetric?.metric), + [selectedMetric], + ) + + return ( + <> + + + Select Metric + + { + if (value === metric.metric) return + const newMetric = value as Metric + const condition = getDefaultCondition(newMetric) + if (!condition) return + setMetric({ metric: newMetric, condition }) + }} + value={metric.metric} + menuStyle={{ width: '250px' }} + /> + + + {metric.metric === Metric.FeeYield && } + + {metric.metric === Metric.PoolPrice && } + + {metric.metric === Metric.Time && } + + ) +} + +const FeeYieldInput = ({ + metric, + setMetric, +}: { + metric: SelectedMetric + setMetric: (value: SelectedMetric) => void +}) => { + const theme = useTheme() + const feeYieldCondition = metric.condition as FeeYieldCondition + + return ( + <> + + + Exit when fee yield ≥ + + + { + const value = e.target.value + // Only allow numbers and decimal point + if (/^\d*\.?\d*$/.test(value)) { + const numValue = parseFloat(value) + // Limit to 1-100% + if (value === '' || numValue > 0) { + setMetric({ ...metric, condition: value }) + } + } + }} + placeholder="0" + /> + + % + + + + + {[5, 10, 15, 20].map(item => { + const isSelected = metric.condition === item.toString() + + return ( + setMetric({ ...metric, condition: item.toString() })} + sx={{ + borderRadius: '999px', + border: `1px solid ${isSelected ? theme.primary : theme.border}`, + backgroundColor: isSelected ? theme.primary + '20' : 'transparent', + padding: '4px 12px', + color: isSelected ? theme.primary : theme.subText, + fontSize: '12px', + fontWeight: '500', + cursor: 'pointer', + '&:hover': { + backgroundColor: theme.primary + '10', + }, + }} + > + {item}% + + ) + })} + + + ) +} + +const PriceInput = ({ + metric, + setMetric, + position, +}: { + metric: SelectedMetric + setMetric: (value: SelectedMetric) => void + position: ParsedPosition +}) => { + const priceCondition = metric.condition as PriceCondition + + const [lowerTick, setLowerTickState] = useState() + const [upperTick, setUpperTickState] = useState() + + // Local input state for debouncing + const [inputMinPrice, setInputMinPrice] = useState(priceCondition?.gte ?? '') + const [inputMaxPrice, setInputMaxPrice] = useState(priceCondition?.lte ?? '') + + // Track change source to prevent circular updates + const changeSourceRef = useRef<'input' | 'slider' | null>(null) + + // Sync local input with external price changes (from slider) + useEffect(() => { + if (changeSourceRef.current === 'slider' && priceCondition?.gte) { + setInputMinPrice(priceCondition.gte) + } + }, [priceCondition?.gte]) + + useEffect(() => { + if (changeSourceRef.current === 'slider' && priceCondition?.lte) { + setInputMaxPrice(priceCondition.lte) + } + }, [priceCondition?.lte]) + + // Debounce input price updates + useEffect(() => { + if (changeSourceRef.current === 'slider' || metric.metric !== Metric.PoolPrice) return + const timer = setTimeout(() => { + if (inputMinPrice !== priceCondition?.gte) { + setMetric({ ...metric, condition: { ...priceCondition, gte: inputMinPrice } }) + } + }, 300) + return () => clearTimeout(timer) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [inputMinPrice]) + + useEffect(() => { + if (changeSourceRef.current === 'slider' || metric.metric !== Metric.PoolPrice) return + const timer = setTimeout(() => { + if (inputMaxPrice !== priceCondition?.lte) { + setMetric({ ...metric, condition: { ...priceCondition, lte: inputMaxPrice } }) + } + }, 300) + return () => clearTimeout(timer) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [inputMaxPrice]) + + const currentTick = useMemo( + () => + nearestUsableTick( + priceToClosestTick( + position.priceRange.current.toString(), + position.token0.decimals, + position.token1.decimals, + ) || 0, + position.pool.tickSpacing, + ), + [position.pool.tickSpacing, position.priceRange, position.token0.decimals, position.token1.decimals], + ) + + const priceToTick = useCallback( + (price: string) => { + if (!price) return undefined + return nearestUsableTick( + priceToClosestTick(price, position.token0.decimals, position.token1.decimals) || 0, + position.pool.tickSpacing, + ) + }, + [position.pool.tickSpacing, position.token0.decimals, position.token1.decimals], + ) + + // Wrapper to set tick from slider (updates price) + const setLowerTick = useCallback( + (tick: number | undefined) => { + changeSourceRef.current = 'slider' + setLowerTickState(tick) + if (tick !== undefined) { + const price = tickToPrice(tick, position.token0.decimals, position.token1.decimals, false) + setMetric({ ...metric, condition: { ...priceCondition, gte: price } }) + } + // Reset source after React batch update + setTimeout(() => { + changeSourceRef.current = null + }, 0) + }, + [metric, priceCondition, position.token0.decimals, position.token1.decimals, setMetric], + ) + + const setUpperTick = useCallback( + (tick: number | undefined) => { + changeSourceRef.current = 'slider' + setUpperTickState(tick) + if (tick !== undefined) { + const price = tickToPrice(tick, position.token0.decimals, position.token1.decimals, false) + setMetric({ ...metric, condition: { ...priceCondition, lte: price } }) + } + setTimeout(() => { + changeSourceRef.current = null + }, 0) + }, + [metric, priceCondition, position.token0.decimals, position.token1.decimals, setMetric], + ) + + // Sync tick from price input (only when source is input, not slider) + useEffect(() => { + if (changeSourceRef.current === 'slider') return + if (priceCondition?.gte) { + const tick = priceToTick(priceCondition.gte) + if (tick !== undefined && tick !== lowerTick) { + setLowerTickState(tick) + } + } + }, [priceCondition?.gte, priceToTick, lowerTick]) + + useEffect(() => { + if (changeSourceRef.current === 'slider') return + if (priceCondition?.lte) { + const tick = priceToTick(priceCondition.lte) + if (tick !== undefined && tick !== upperTick) { + setUpperTickState(tick) + } + } + }, [priceCondition?.lte, priceToTick, upperTick]) + + return ( + <> + + Exit when the pool price is between + + + { + const value = e.target.value + // Only allow numbers and decimal point + if (/^\d*\.?\d*$/.test(value)) { + setInputMinPrice(value) + } + }} + /> + - + { + const value = e.target.value + // Only allow numbers and decimal point + if (/^\d*\.?\d*$/.test(value)) { + setInputMaxPrice(value) + } + }} + /> + + {position.token0.symbol}/{position.token1.symbol} + + + + + ) +} + +const TimeInput = ({ metric, setMetric }: { metric: SelectedMetric; setMetric: (value: SelectedMetric) => void }) => { + const theme = useTheme() + const [openDatePicker, setOpenDatePicker] = useState(false) + + const timeOptions = useMemo( + () => [ + { + label: t`Before`, + value: 'before', + }, + { + label: t`After`, + value: 'after', + }, + ], + [], + ) + const timeCondition = metric.condition as TimeCondition + + return ( + <> + + + Exit this position + + { + setMetric({ ...metric, condition: { ...timeCondition, condition: value } }) + }} + value={timeCondition.condition} + menuStyle={{ width: '250px' }} + /> + + + Set Schedule + + setOpenDatePicker(true)} + > + + {timeCondition.time === null ? 'DD/MM/YYYY' : dayjs(timeCondition.time).format('DD/MM/YYYY HH:mm:ss')} + + + + + Time Setup} + isOpen={openDatePicker} + onDismiss={() => setOpenDatePicker(false)} + onSetDate={(val: Date | number) => { + setMetric({ + ...metric, + condition: { ...timeCondition, time: typeof val === 'number' ? val : val.getTime() }, + }) + }} + expire={ + timeCondition.time || 5 * 60 // 5min + } + defaultOptions={DEFAULT_TIME_OPTIONS} + /> + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/PoolPrice.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/PoolPrice.tsx new file mode 100644 index 0000000000..3859c48122 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/PoolPrice.tsx @@ -0,0 +1,75 @@ +import { Trans } from '@lingui/macro' +import { useState } from 'react' +import { Flex, Text } from 'rebass' + +import { ReactComponent as RevertPriceIcon } from 'assets/svg/earn/ic_revert_price.svg' +import Divider from 'components/Divider' +import InfoHelper from 'components/InfoHelper' +import useTheme from 'hooks/useTheme' +import { RevertIconWrapper } from 'pages/Earns/PositionDetail/styles' +import PriceRange from 'pages/Earns/UserPositions/PriceRange' +import { PositionValueWrapper } from 'pages/Earns/UserPositions/styles' +import { CustomBox } from 'pages/Earns/components/SmartExit/styles' +import { ParsedPosition } from 'pages/Earns/types' +import { ExternalLink } from 'theme' +import { formatDisplayNumber } from 'utils/numbers' + +export default function PoolPrice({ position }: { position: ParsedPosition }) { + const theme = useTheme() + const [revertPrice, setRevertPrice] = useState(false) + + return ( + + + + Current Price + + + 1 {revertPrice ? position.token1.symbol : position.token0.symbol} ={' '} + {formatDisplayNumber(revertPrice ? 1 / position.priceRange.current : position.priceRange.current, { + significantDigits: 6, + })}{' '} + {revertPrice ? position.token0.symbol : position.token1.symbol} + + setRevertPrice(!revertPrice)}> + + + + + + + + + + + + + Earning Fee Yield{' '} + + + Based on the amount of fee tokens your position has earned compared with your initial deposit. + + + + Details + + + + } + />{' '} + + {position.earningFeeYield.toFixed(2)}% + + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/PositionLiquidity.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/PositionLiquidity.tsx new file mode 100644 index 0000000000..ed522934aa --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/PositionLiquidity.tsx @@ -0,0 +1,54 @@ +import { Trans } from '@lingui/macro' +import { Flex, Text } from 'rebass' + +import TokenLogo from 'components/TokenLogo' +import useTheme from 'hooks/useTheme' +import { CustomBox } from 'pages/Earns/components/SmartExit/styles' +import { ParsedPosition } from 'pages/Earns/types' +import { formatDisplayNumber } from 'utils/numbers' + +export default function PositionLiquidity({ position }: { position: ParsedPosition }) { + const theme = useTheme() + + return ( + + + + Your Position Liquidity + + {formatDisplayNumber(position.totalValue, { style: 'currency', significantDigits: 4 })} + + + + + {position.token0.symbol} + + + {formatDisplayNumber(position.token0.totalProvide, { significantDigits: 6 })} + + {formatDisplayNumber(position.token0.price * position.token0.totalProvide, { + style: 'currency', + significantDigits: 6, + })} + + + + + + + {position.token1.symbol} + + + + {formatDisplayNumber(position.token1.totalProvide, { significantDigits: 6 })} + + {formatDisplayNumber(position.token1.price * position.token1.totalProvide, { + style: 'currency', + significantDigits: 6, + })} + + + + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx new file mode 100644 index 0000000000..f79f2a6bf3 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx @@ -0,0 +1,191 @@ +import { Trans } from '@lingui/macro' +import { useEffect, useMemo, useRef, useState } from 'react' +import { X } from 'react-feather' +import { Flex, Text } from 'rebass' + +import Divider from 'components/Divider' +import Modal from 'components/Modal' +import { TIMES_IN_SECS } from 'constants/index' +import PositionDetailHeader from 'pages/Earns/PositionDetail/Header' +import Actions from 'pages/Earns/components/SmartExit/Actions' +import { Confirmation } from 'pages/Earns/components/SmartExit/Confirmation' +import ExpireSetting from 'pages/Earns/components/SmartExit/ExpireSetting' +import GasSetting from 'pages/Earns/components/SmartExit/GasSetting' +import { Metrics } from 'pages/Earns/components/SmartExit/Metrics' +import PoolPrice from 'pages/Earns/components/SmartExit/PoolPrice' +import PositionLiquidity from 'pages/Earns/components/SmartExit/PositionLiquidity' +import { ContentWrapper } from 'pages/Earns/components/SmartExit/styles' +import { useSmartExit } from 'pages/Earns/components/SmartExit/useSmartExit' +import { defaultFeeYieldCondition } from 'pages/Earns/components/SmartExit/utils' +import { + ConditionType, + FeeYieldCondition, + Metric, + ParsedPosition, + PriceCondition, + SelectedMetric, + SmartExitFee, + TimeCondition, +} from 'pages/Earns/types' + +export const SmartExit = ({ position, onDismiss }: { position: ParsedPosition; onDismiss: () => void }) => { + const [selectedMetrics, setSelectedMetrics] = useState([ + { metric: Metric.FeeYield, condition: defaultFeeYieldCondition }, + ]) + const [conditionType, setConditionType] = useState(ConditionType.And) + + const [expireTime, setExpireTime] = useState(TIMES_IN_SECS.ONE_DAY * 30) + const deadline = useMemo(() => { + const today = new Date() + today.setUTCHours(0, 0, 0, 0) + const time = [7 * TIMES_IN_SECS.ONE_DAY, 30 * TIMES_IN_SECS.ONE_DAY, 90 * TIMES_IN_SECS.ONE_DAY].includes( + expireTime, + ) + ? Math.floor(today.getTime()) + expireTime * 1000 + : expireTime + + return Math.floor(time / 1000) + }, [expireTime]) + + const invalidYieldCondition = useMemo(() => { + const feeYieldMetric = selectedMetrics.find(metric => metric.metric === Metric.FeeYield) + const feeYieldCondition = feeYieldMetric?.condition as FeeYieldCondition + return feeYieldMetric && (!feeYieldCondition || parseFloat(feeYieldCondition) === 0) + }, [selectedMetrics]) + + const invalidPriceCondition = useMemo(() => { + const priceMetric = selectedMetrics.find(metric => metric.metric === Metric.PoolPrice) + const priceCondition = priceMetric?.condition as PriceCondition + return ( + priceMetric && + (!priceCondition.gte || !priceCondition.lte || Number(priceCondition.gte) > Number(priceCondition.lte)) + ) + }, [selectedMetrics]) + + const invalidTimeCondition = useMemo(() => { + const timeMetric = selectedMetrics.find(metric => metric.metric === Metric.Time) + const timeCondition = timeMetric?.condition as TimeCondition + return timeMetric && !timeCondition.time + }, [selectedMetrics]) + + const [showConfirm, setShowConfirm] = useState(false) + + // Gas estimation + selection state + const [feeInfo, setFeeInfo] = useState(null) + const [feeLoading, setFeeLoading] = useState(false) + const [multiplier, setMultiplier] = useState(2) + const [customGasPercent, setCustomGasPercent] = useState('') + const intervalRef = useRef(null) + + const { estimateFee } = useSmartExit({ + position, + selectedMetrics, + conditionType, + deadline, + }) + + const disabled = invalidYieldCondition || invalidPriceCondition || invalidTimeCondition || !feeInfo || feeLoading + + // Auto-estimate when metrics are valid + useEffect(() => { + if (invalidYieldCondition || invalidPriceCondition || invalidTimeCondition) return + + const call = async () => { + if (feeLoading || feeInfo) return + setFeeLoading(true) + try { + const res = await estimateFee() + setFeeInfo(res) + } catch { + if (feeInfo) setFeeInfo(null) + } finally { + setFeeLoading(false) + } + } + + call() + intervalRef.current = window.setInterval(call, 15000) + + return () => { + if (intervalRef.current) { + window.clearInterval(intervalRef.current) + intervalRef.current = null + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [invalidYieldCondition || invalidPriceCondition || invalidTimeCondition]) + + return ( + + + {showConfirm ? ( + setShowConfirm(false)} + feeSettings={{ + protocolFee: feeInfo?.protocol.percentage || 0, + maxFeesPercentage: + (customGasPercent ? parseFloat(customGasPercent) : (feeInfo?.gas.percentage || 0) * multiplier) + + (feeInfo?.protocol.percentage || 0), + }} + /> + ) : ( + <> + + + Set Up Smart Exit + + + + + } + /> + + + + + + + + + + + + + + + + + !disabled && setShowConfirm(true)} + disabled={disabled} + feeLoading={feeLoading} + /> + + )} + + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/styles.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/styles.tsx new file mode 100644 index 0000000000..564cc77bf2 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/styles.tsx @@ -0,0 +1,41 @@ +import styled from 'styled-components' + +import Input from 'components/Input' +import Select from 'components/Select' + +export const ContentWrapper = styled.div` + margin-top: 20px; + display: flex; + flex-direction: row; + gap: 20px; + + ${({ theme }) => theme.mediaWidth.upToSmall` + flex-direction: column; + `} +` + +export const CustomBox = styled.div` + border-radius: 1rem; + border: 1px solid ${({ theme }) => theme.border}; + padding: 12px; + flex-direction: column; + gap: 0.5rem; + display: flex; +` + +export const CustomInput = styled(Input)` + border: none; + padding: 8px 16px; + border-radius: 12px; + font-size: 16px; + color: ${({ theme }) => theme.text}; + flex: 1; +` + +export const CustomSelect = styled(Select)` + width: 100%; + padding: 4px 16px; + font-size: 14px; + color: ${({ theme }) => theme.text}; + flex: 1; +` diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts new file mode 100644 index 0000000000..79fc277041 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts @@ -0,0 +1,351 @@ +import { t } from '@lingui/macro' +import { useCallback, useState } from 'react' +import { + SmartExitFeeParams, + useEstimateSmartExitFeeMutation, + useGetSmartExitSignMessageMutation, +} from 'services/smartExit' + +import { NotificationType } from 'components/Announcement/type' +import { SMART_EXIT_API_URL } from 'constants/env' +import { useActiveWeb3React, useWeb3React } from 'hooks' +import { DexType } from 'pages/Earns/SmartExitOrders/useSmartExitFilter' +import { Exchange } from 'pages/Earns/constants' +import { + ConditionType, + FeeYieldCondition, + Metric, + ParsedPosition, + PriceCondition, + SelectedMetric, + SmartExitFee, + TimeCondition, +} from 'pages/Earns/types' +import { useNotify } from 'state/application/hooks' +import { friendlyError } from 'utils/errorMessage' +import { getReadingContractWithCustomChain } from 'utils/getContract' + +export interface UseSmartExitParams { + position: ParsedPosition + selectedMetrics: SelectedMetric[] + conditionType: ConditionType + deadline: number + permitData?: string + signature?: string +} + +export enum SmartExitState { + IDLE = 'idle', + CREATING = 'creating', + SUCCESS = 'success', + ERROR = 'error', +} + +export const DexMapping: Record = { + [Exchange.DEX_UNISWAPV3]: DexType.DexTypeUniswapV3, + [Exchange.DEX_UNISWAP_V4]: DexType.DexTypeUniswapV4, + [Exchange.DEX_UNISWAP_V4_FAIRFLOW]: DexType.DexTypeUniswapV4FairFlow, + [Exchange.DEX_PANCAKESWAPV3]: DexType.DexTypePancakeV3, + [Exchange.DEX_PANCAKE_INFINITY_CL]: DexType.DexTypePancakeInfinityCL, + [Exchange.DEX_PANCAKE_INFINITY_CL_FAIRFLOW]: DexType.DexTypePancakeInfinityCLFairFlow, +} + +// Position Manager ABI for liquidity fetching +const POSITION_MANAGER_ABI = [ + 'function positions(uint256 tokenId) view returns (uint96 nonce, address operator, address token0, address token1, uint24 fee, int24 tickLower, int24 tickUpper, uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, uint128 tokensOwed0, uint128 tokensOwed1)', // V3 + 'function getPositionLiquidity(uint256 tokenId) view returns (uint128 liquidity)', // V4 +] + +export const useSmartExit = ({ + position, + selectedMetrics, + conditionType, + deadline, + permitData, + signature, +}: UseSmartExitParams) => { + const { account } = useActiveWeb3React() + const { library } = useWeb3React() + const notify = useNotify() + const [state, setState] = useState(SmartExitState.IDLE) + const [orderId, setOrderId] = useState(null) + const [getSignMessage] = useGetSmartExitSignMessageMutation() + const [estimateFeeMutation] = useEstimateSmartExitFeeMutation() + + const getDexType = useCallback((dexId: string) => { + // Map dex IDs to API format + return DexMapping[dexId] || dexId + }, []) + + // Detect version from dex type + const dexType = getDexType(position.dex.id) + + const buildConditions = useCallback(() => { + const conditions: Array<{ + field: { + type: Metric + value: any + } + }> = [] + + // Add selected metric conditions + selectedMetrics.forEach(metric => { + const feeYieldCondition = metric.condition as FeeYieldCondition + const priceCondition = metric.condition as PriceCondition + const timeCondition = metric.condition as TimeCondition + + switch (metric.metric) { + case Metric.FeeYield: + if (feeYieldCondition) { + conditions.push({ + field: { + type: Metric.FeeYield, + value: { + gte: parseFloat(feeYieldCondition), + }, + }, + }) + } + break + + case Metric.PoolPrice: + if (priceCondition.gte && priceCondition.lte) { + conditions.push({ + field: { + type: Metric.PoolPrice, + value: { + gte: parseFloat(priceCondition.gte), + lte: parseFloat(priceCondition.lte), + }, + }, + }) + } + break + + case Metric.Time: + if (timeCondition.time) { + const timeValue = Math.floor(timeCondition.time / 1000) + if (timeCondition.condition === 'before') { + conditions.push({ + field: { + type: Metric.Time, + value: { + lte: timeValue, + }, + }, + }) + } else { + conditions.push({ + field: { + type: Metric.Time, + value: { + gte: timeValue, + }, + }, + }) + } + } + break + } + }) + + return { + logical: { + op: conditionType, + conditions, + }, + } + }, [selectedMetrics, conditionType]) + + const createSmartExitOrder = useCallback( + async (opts: { maxFeesPercentage: number[] }): Promise => { + if (!account || !permitData || !signature || !library) { + console.error('Missing required data for smart exit order') + return false + } + const positionContract = getReadingContractWithCustomChain( + position.id.split('-')[0], + POSITION_MANAGER_ABI, + position.chain.id, + ) + if (!positionContract) { + console.error('Failed to get position contract') + return false + } + let liquidity = '' + if ([DexType.DexTypeUniswapV3, DexType.DexTypePancakeV3].includes(dexType)) { + const res = await positionContract.positions(position.tokenId) + liquidity = res.liquidity.toString() + } else { + const res = await positionContract.getPositionLiquidity(position.tokenId) + liquidity = res.toString() + } + if (!liquidity) { + console.log("Can't get liquidity of position") + return false + } + + setState(SmartExitState.CREATING) + + try { + // Step 1: Get sign message from API + const signMessageParams = { + chainId: position.chain.id, + userWallet: account, + dexType: getDexType(position.dex.id), + poolId: position.pool.address, + positionId: position.id, + removeLiquidity: liquidity, + unwrap: position.token0.isNative || position.token1.isNative, + permitData, + condition: buildConditions(), + deadline, + maxFeesPercentage: opts.maxFeesPercentage, + } + + console.log('Getting sign message with params:', signMessageParams) + + const signMessageResult = await getSignMessage(signMessageParams).unwrap() + const typedData = signMessageResult.message + + if (!typedData || !typedData.domain || !typedData.types || !typedData.message) { + throw new Error('Failed to get valid typed data from API') + } + + // Step 2: Sign the typed data + console.log('Signing typed data:', typedData) + const orderSignature = await library.send('eth_signTypedData_v4', [account, JSON.stringify(typedData)]) + + // Step 3: Create the order with both signatures + const orderParams: SmartExitFeeParams & { signature: string; maxFeesPercentage: number[] } = { + chainId: position.chain.id, + userWallet: account, + dexType: dexType, + poolId: position.pool.address, + positionId: position.id, + removeLiquidity: liquidity, + unwrap: false, + permitData, + condition: buildConditions(), + signature: orderSignature, + deadline, + maxFeesPercentage: opts.maxFeesPercentage, + } + + console.log('Creating smart exit order with params:', orderParams) + + const response = await fetch(`${SMART_EXIT_API_URL}/v1/orders/smart-exit`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(orderParams), + }) + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + throw new Error(errorData.message || `HTTP error! status: ${response.status}`) + } + + const result = await response.json() + setOrderId(result.orderId || result.id) + setState(SmartExitState.SUCCESS) + + notify({ + type: NotificationType.SUCCESS, + title: t`Smart Exit Order Created`, + summary: t`Your smart exit order has been successfully created and is now active.`, + }) + + return true + } catch (error) { + const message = friendlyError(error) + console.error('Smart exit order creation error:', { message, error }) + + setState(SmartExitState.ERROR) + notify({ + title: t`Smart Exit Order Error`, + summary: message, + type: NotificationType.ERROR, + }) + + return false + } + }, + [ + account, + permitData, + signature, + position, + buildConditions, + notify, + deadline, + library, + getSignMessage, + getDexType, + dexType, + ], + ) + + const reset = useCallback(() => { + setState(SmartExitState.IDLE) + setOrderId(null) + }, []) + + const estimateFee = useCallback(async (): Promise => { + const positionContract = getReadingContractWithCustomChain( + position.id.split('-')[0], + POSITION_MANAGER_ABI, + position.chain.id, + ) + if (!account || !positionContract) return null + + let liquidity = '' + if ([DexType.DexTypeUniswapV3, DexType.DexTypePancakeV3].includes(dexType)) { + const res = await positionContract.positions(position.tokenId) + liquidity = res.liquidity.toString() + } else { + const res = await positionContract.getPositionLiquidity(position.tokenId) + liquidity = res.toString() + } + + if (!liquidity) return null + + const payload = { + chainId: position.chain.id, + userWallet: account, + dexType: getDexType(position.dex.id), + poolId: position.pool.address, + positionId: position.id, + removeLiquidity: liquidity, + unwrap: position.token0.isNative || position.token1.isNative, + permitData: '0x', + condition: buildConditions(), + deadline, + } + + try { + const res = await estimateFeeMutation(payload).unwrap() + const isValid = + res && + typeof (res as any).gas === 'object' && + typeof (res as any).gas.usd === 'number' && + Number.isFinite((res as any).gas.usd) + if (!isValid) return null + return res + } catch (e) { + return null + } + }, [account, dexType, position, getDexType, buildConditions, estimateFeeMutation, deadline]) + + return { + state, + orderId, + createSmartExitOrder, + estimateFee, + reset, + isCreating: state === SmartExitState.CREATING, + isSuccess: state === SmartExitState.SUCCESS, + isError: state === SmartExitState.ERROR, + } +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/utils.ts b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/utils.ts new file mode 100644 index 0000000000..28c46cb304 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/utils.ts @@ -0,0 +1,18 @@ +import { FeeYieldCondition, Metric, PriceCondition, TimeCondition } from 'pages/Earns/types' + +export const defaultFeeYieldCondition: FeeYieldCondition = '0' +const defaultPriceCondition: PriceCondition = { gte: '', lte: '' } +const defaultTimeCondition: TimeCondition = { time: null, condition: 'after' } + +export const getDefaultCondition = (metric: Metric): FeeYieldCondition | PriceCondition | TimeCondition | null => { + switch (metric) { + case Metric.FeeYield: + return defaultFeeYieldCondition + case Metric.PoolPrice: + return defaultPriceCondition + case Metric.Time: + return defaultTimeCondition + default: + return null + } +} diff --git a/apps/kyberswap-interface/src/pages/Earns/constants/chains/arbitrum.ts b/apps/kyberswap-interface/src/pages/Earns/constants/chains/arbitrum.ts index 017def1a2f..5b01bb26d0 100644 --- a/apps/kyberswap-interface/src/pages/Earns/constants/chains/arbitrum.ts +++ b/apps/kyberswap-interface/src/pages/Earns/constants/chains/arbitrum.ts @@ -3,7 +3,8 @@ import { ETHER_ADDRESS } from 'constants/index' export default { nativeAddress: ETHER_ADDRESS.toLowerCase(), - farmingSupported: true, + farmingSupported: false, + smartExitSupported: false, univ4StateViewContract: '0x76fd297e2d437cd7f76d50f01afe6160f86e9990', logo: arbitrumLogo, } diff --git a/apps/kyberswap-interface/src/pages/Earns/constants/chains/avax.ts b/apps/kyberswap-interface/src/pages/Earns/constants/chains/avax.ts index 092dfe6767..71389026ac 100644 --- a/apps/kyberswap-interface/src/pages/Earns/constants/chains/avax.ts +++ b/apps/kyberswap-interface/src/pages/Earns/constants/chains/avax.ts @@ -3,6 +3,7 @@ import avaxLogo from 'assets/networks/avalanche.svg' export default { nativeAddress: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', farmingSupported: false, + smartExitSupported: false, univ4StateViewContract: '0xc3c9e198c735a4b97e3e683f391ccbdd60b69286', logo: avaxLogo, } diff --git a/apps/kyberswap-interface/src/pages/Earns/constants/chains/base.ts b/apps/kyberswap-interface/src/pages/Earns/constants/chains/base.ts index 17252190d8..7f02812f3a 100644 --- a/apps/kyberswap-interface/src/pages/Earns/constants/chains/base.ts +++ b/apps/kyberswap-interface/src/pages/Earns/constants/chains/base.ts @@ -4,6 +4,7 @@ import { ETHER_ADDRESS } from 'constants/index' export default { nativeAddress: ETHER_ADDRESS.toLowerCase(), farmingSupported: true, + smartExitSupported: true, univ4StateViewContract: '0xa3c0c9b65bad0b08107aa264b0f3db444b867a71', logo: baseLogo, } diff --git a/apps/kyberswap-interface/src/pages/Earns/constants/chains/bera.ts b/apps/kyberswap-interface/src/pages/Earns/constants/chains/bera.ts index e78496922a..20c1859ad6 100644 --- a/apps/kyberswap-interface/src/pages/Earns/constants/chains/bera.ts +++ b/apps/kyberswap-interface/src/pages/Earns/constants/chains/bera.ts @@ -4,6 +4,7 @@ import { ETHER_ADDRESS } from 'constants/index' export default { nativeAddress: ETHER_ADDRESS.toLowerCase(), farmingSupported: false, + smartExitSupported: false, univ4StateViewContract: null, logo: beraLogo, } diff --git a/apps/kyberswap-interface/src/pages/Earns/constants/chains/bsc.ts b/apps/kyberswap-interface/src/pages/Earns/constants/chains/bsc.ts index b50db9b6bd..bf55347fec 100644 --- a/apps/kyberswap-interface/src/pages/Earns/constants/chains/bsc.ts +++ b/apps/kyberswap-interface/src/pages/Earns/constants/chains/bsc.ts @@ -3,6 +3,7 @@ import bscLogo from 'assets/networks/bnb.svg' export default { nativeAddress: '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', farmingSupported: true, + smartExitSupported: true, univ4StateViewContract: '0xd13dd3d6e93f276fafc9db9e6bb47c1180aee0c4', logo: bscLogo, } diff --git a/apps/kyberswap-interface/src/pages/Earns/constants/chains/ethereum.ts b/apps/kyberswap-interface/src/pages/Earns/constants/chains/ethereum.ts index 4e9dc57740..60639a2c15 100644 --- a/apps/kyberswap-interface/src/pages/Earns/constants/chains/ethereum.ts +++ b/apps/kyberswap-interface/src/pages/Earns/constants/chains/ethereum.ts @@ -4,6 +4,7 @@ import { ETHER_ADDRESS } from 'constants/index' export default { nativeAddress: ETHER_ADDRESS.toLowerCase(), farmingSupported: true, + smartExitSupported: false, univ4StateViewContract: '0x7ffe42c4a5deea5b0fec41c94c136cf115597227', logo: ethereumLogo, } diff --git a/apps/kyberswap-interface/src/pages/Earns/constants/chains/matic.ts b/apps/kyberswap-interface/src/pages/Earns/constants/chains/matic.ts index 8e38c17190..03e0b14fe7 100644 --- a/apps/kyberswap-interface/src/pages/Earns/constants/chains/matic.ts +++ b/apps/kyberswap-interface/src/pages/Earns/constants/chains/matic.ts @@ -3,6 +3,7 @@ import maticLogo from 'assets/networks/polygon.svg' export default { nativeAddress: '0xcccccccccccccccccccccccccccccccccccccccc', farmingSupported: false, + smartExitSupported: false, univ4StateViewContract: '0x5ea1bd7974c8a611cbab0bdcafcb1d9cc9b3ba5a', logo: maticLogo, } diff --git a/apps/kyberswap-interface/src/pages/Earns/constants/chains/optimism.ts b/apps/kyberswap-interface/src/pages/Earns/constants/chains/optimism.ts index 89f5566579..3790196eaa 100644 --- a/apps/kyberswap-interface/src/pages/Earns/constants/chains/optimism.ts +++ b/apps/kyberswap-interface/src/pages/Earns/constants/chains/optimism.ts @@ -4,6 +4,7 @@ import { ETHER_ADDRESS } from 'constants/index' export default { nativeAddress: ETHER_ADDRESS.toLowerCase(), farmingSupported: false, + smartExitSupported: false, univ4StateViewContract: '0xc18a3169788f4f75a170290584eca6395c75ecdb', logo: optimismLogo, } diff --git a/apps/kyberswap-interface/src/pages/Earns/constants/index.ts b/apps/kyberswap-interface/src/pages/Earns/constants/index.ts index 6a5f483bbc..fb76d6312f 100644 --- a/apps/kyberswap-interface/src/pages/Earns/constants/index.ts +++ b/apps/kyberswap-interface/src/pages/Earns/constants/index.ts @@ -99,6 +99,7 @@ export const EARN_DEXES = new Proxy(EARN_DEXES_CONFIG as any, { export interface EarnChainInfo { nativeAddress: string farmingSupported: boolean + smartExitSupported: boolean univ4StateViewContract: string | null logo: string } @@ -130,3 +131,5 @@ export const LIMIT_TEXT_STYLES = { overflow: 'hidden', whiteSpace: 'nowrap', } + +export const SMART_EXIT_ADDRESS = '0x52ee3c8dd099ccb542c6227855d68c79e3e956f9' diff --git a/apps/kyberswap-interface/src/pages/Earns/types/index.ts b/apps/kyberswap-interface/src/pages/Earns/types/index.ts new file mode 100644 index 0000000000..bb5bb67844 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/types/index.ts @@ -0,0 +1,22 @@ +export * from 'pages/Earns/types/smartExit' +export * from 'pages/Earns/types/position' +export * from 'pages/Earns/types/pool' +export * from 'pages/Earns/types/reward' + +export interface FeeInfo { + balance0: string | number + balance1: string | number + amount0: string | number + amount1: string | number + value0: number + value1: number + totalValue: number +} + +export interface TokenInfo { + address: string + symbol: string + logo: string + decimals: number + chainId: number +} diff --git a/apps/kyberswap-interface/src/pages/Earns/types/pool.ts b/apps/kyberswap-interface/src/pages/Earns/types/pool.ts new file mode 100644 index 0000000000..0d9074083f --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/types/pool.ts @@ -0,0 +1,114 @@ +import { Exchange } from 'pages/Earns/constants' + +export enum ProgramType { + EG = 'eg', + LM = 'lm', +} + +export enum PAIR_CATEGORY { + STABLE = 'stablePair', + CORRELATED = 'correlatedPair', + EXOTIC = 'exoticPair', + HIGH_VOLATILITY = 'highVolatilityPair', + DEFAULT_EMPTY = '', // For Krystal data +} + +export interface EarnPool { + address: string + earnFee: number + exchange: Exchange + type: string + feeTier: number + volume: number + apr: number + kemEGApr: number + bonusApr: number + kemLMApr: number + liquidity: number + tvl: number + chainId?: number + favorite?: { + chainId: number + isFavorite: boolean + } + category?: PAIR_CATEGORY + programs?: Array + merklOpportunity?: MerklOpportunity + tokens: Array<{ + address: string + logoURI: string + symbol: string + decimals: number + }> + maxAprInfo?: { + tickLower: number + tickUpper: number + minPrice: string + maxPrice: string + apr: string + kemEGApr: string + kemLMApr: string + } + egUsd?: number +} + +export interface ParsedEarnPool extends EarnPool { + dexLogo: string + dexName: string + feeApr: number +} + +export interface PoolAprInterval { + '7d': number + '24h': number + all: number +} + +export interface MerklOpportunity { + type: string + chainId: number + identifier: string + status: string + action: string + tvl: number + apr: number + dailyRewards: number + depositUrl: string + liveCampaigns: number + protocol: { + id: string + name: string + } + rewardsRecord: { + total: number + timestamp: string | number + breakdowns: Array<{ + token: { + name: string + chainId: number + address: string + decimals: number + symbol: string + displaySymbol: string + verified: boolean + isTest: boolean + type: string + isNative: boolean + price: number + updatedAt: number + priceSource: string + } + amount: string + value: number + campaignId: string + distributionType: string + timestamp: string | number + }> + } + campaigns: Array<{ + id: string + startTimestamp: number + endTimestamp: number + apr: number + }> +} diff --git a/apps/kyberswap-interface/src/pages/Earns/types.ts b/apps/kyberswap-interface/src/pages/Earns/types/position.ts similarity index 59% rename from apps/kyberswap-interface/src/pages/Earns/types.ts rename to apps/kyberswap-interface/src/pages/Earns/types/position.ts index c2bb9c3a4e..db846fcc0c 100644 --- a/apps/kyberswap-interface/src/pages/Earns/types.ts +++ b/apps/kyberswap-interface/src/pages/Earns/types/position.ts @@ -1,20 +1,7 @@ import { NativeToken } from 'constants/networks/type' import { Exchange } from 'pages/Earns/constants' - -export enum PositionStatus { - IN_RANGE = 'IN_RANGE', - OUT_RANGE = 'OUT_RANGE', - CLOSED = 'CLOSED', -} - -export enum PositionHistoryType { - DEPOSIT = 'DEPOSIT', -} - -export enum ProgramType { - EG = 'eg', - LM = 'lm', -} +import { MerklOpportunity, PAIR_CATEGORY, PoolAprInterval, ProgramType } from 'pages/Earns/types/pool' +import { TokenRewardInfo } from 'pages/Earns/types/reward' export interface PositionFilter { chainIds?: string @@ -27,116 +14,22 @@ export interface PositionFilter { page: number } -export interface EarnPool { - address: string - earnFee: number - exchange: Exchange - type: string - feeTier: number - volume: number - apr: number - kemEGApr: number - bonusApr: number - kemLMApr: number - liquidity: number - tvl: number - chainId?: number - favorite?: { - chainId: number - isFavorite: boolean - } - category?: PAIR_CATEGORY - programs?: Array - merklOpportunity?: MerklOpportunity - tokens: Array<{ - address: string - logoURI: string - symbol: string - decimals: number - }> - maxAprInfo?: { - tickLower: number - tickUpper: number - minPrice: string - maxPrice: string - apr: string - kemEGApr: string - kemLMApr: string - } - egUsd?: number -} - -export interface ParsedEarnPool extends EarnPool { - dexLogo: string - dexName: string - feeApr: number -} - -export interface EarnPosition { - [x: string]: any - chainName: 'eth' - chainId: number - chainLogo: string - id: string - tokenAddress: string - tokenId: string - minPrice: number - maxPrice: number - currentAmounts: Array - feePending: Array - feesClaimed: Array - createdTime: number - stats: { - apr: PoolAprInterval - kemLMApr: PoolAprInterval - kemEGApr: PoolAprInterval - earning: PoolAprInterval - } - /** @deprecated */ - apr: number - /** @deprecated */ - kemEGApr: number - /** @deprecated */ - kemLMApr: number - /** @deprecated */ - earning24h: number - /** @deprecated */ - earning7d: number - currentPositionValue: number - status: PositionStatus - pool: { - id: string - poolAddress: string - price: number - tokenAmounts: Array - fees: Array - tickSpacing: number - exchange: Exchange - projectLogo: string - category: PAIR_CATEGORY - programs?: Array - merklOpportunity?: MerklOpportunity - } - suggestionPool: SuggestedPool | null - latestBlock: number - createdAtBlock: number -} - -export enum PAIR_CATEGORY { - STABLE = 'stablePair', - CORRELATED = 'correlatedPair', - EXOTIC = 'exoticPair', - HIGH_VOLATILITY = 'highVolatilityPair', - DEFAULT_EMPTY = '', // For Krystal data +export enum PositionStatus { + IN_RANGE = 'IN_RANGE', + OUT_RANGE = 'OUT_RANGE', + CLOSED = 'CLOSED', } export const DEFAULT_PARSED_POSITION: ParsedPosition = { id: '', tokenId: '', + stakingOwner: undefined, + earningFeeYield: 0, pool: { fee: 0, address: '', isUniv2: false, + isUniv4: false, isFarming: false, isFarmingLm: false, nativeToken: { @@ -229,10 +122,13 @@ export const DEFAULT_PARSED_POSITION: ParsedPosition = { export interface ParsedPosition { id: string tokenId: string + stakingOwner?: string + earningFeeYield: number pool: { fee: number address: string isUniv2: boolean + isUniv4: boolean isFarming: boolean isFarmingLm: boolean nativeToken: NativeToken @@ -299,53 +195,54 @@ export interface ParsedPosition { txHash?: string } -export interface MerklOpportunity { - type: string +export interface EarnPosition { + [x: string]: any + chainName: 'eth' chainId: number - identifier: string - status: string - action: string - tvl: number + chainLogo: string + id: string + tokenAddress: string + tokenId: string + minPrice: number + maxPrice: number + currentAmounts: Array + feePending: Array + feesClaimed: Array + createdTime: number + stats: { + apr: PoolAprInterval + kemLMApr: PoolAprInterval + kemEGApr: PoolAprInterval + earning: PoolAprInterval + } + /** @deprecated */ apr: number - dailyRewards: number - depositUrl: string - liveCampaigns: number - protocol: { + /** @deprecated */ + kemEGApr: number + /** @deprecated */ + kemLMApr: number + /** @deprecated */ + earning24h: number + /** @deprecated */ + earning7d: number + currentPositionValue: number + status: PositionStatus + pool: { id: string - name: string - } - rewardsRecord: { - total: number - timestamp: string | number - breakdowns: Array<{ - token: { - name: string - chainId: number - address: string - decimals: number - symbol: string - displaySymbol: string - verified: boolean - isTest: boolean - type: string - isNative: boolean - price: number - updatedAt: number - priceSource: string - } - amount: string - value: number - campaignId: string - distributionType: string - timestamp: string | number - }> + poolAddress: string + price: number + tokenAmounts: Array + fees: Array + tickSpacing: number + exchange: Exchange + projectLogo: string + category: PAIR_CATEGORY + programs?: Array + merklOpportunity?: MerklOpportunity } - campaigns: Array<{ - id: string - startTimestamp: number - endTimestamp: number - apr: number - }> + suggestionPool: SuggestedPool | null + latestBlock: number + createdAtBlock: number } export interface SuggestedPool { @@ -361,12 +258,6 @@ export interface SuggestedPool { } } -export interface PoolAprInterval { - '7d': number - '24h': number - all: number -} - interface Token { address: string symbol: string @@ -398,79 +289,6 @@ interface PositionAmount { } } -export interface FeeInfo { - balance0: string | number - balance1: string | number - amount0: string | number - amount1: string | number - value0: number - value1: number - totalValue: number -} - -export interface RewardInfo { - totalUsdValue: number - totalLmUsdValue: number - totalEgUsdValue: number - claimableUsdValue: number - claimedUsdValue: number - inProgressUsdValue: number - pendingUsdValue: number - vestingUsdValue: number - waitingUsdValue: number - nfts: Array - chains: Array - tokens: Array - egTokens: Array - lmTokens: Array -} - -export interface ChainRewardInfo { - chainId: number - chainName: string - chainLogo: string - claimableUsdValue: number - tokens: Array -} - -export interface NftRewardInfo { - nftId: string - chainId: number - totalUsdValue: number - totalLmUsdValue: number - totalEgUsdValue: number - claimedUsdValue: number - inProgressUsdValue: number - pendingUsdValue: number - vestingUsdValue: number - waitingUsdValue: number - claimableUsdValue: number - unclaimedUsdValue: number - - tokens: Array - egTokens: Array - lmTokens: Array -} - -export interface TokenRewardInfo { - symbol: string - logo: string - address: string - chainId: number - - totalAmount: number - claimableAmount: number - unclaimedAmount: number - pendingAmount: number - vestingAmount: number - waitingAmount: number - claimableUsdValue: number -} - -export interface TokenInfo { - address: string - symbol: string - logo: string - decimals: number - chainId: number +export enum PositionHistoryType { + DEPOSIT = 'DEPOSIT', } diff --git a/apps/kyberswap-interface/src/pages/Earns/types/reward.ts b/apps/kyberswap-interface/src/pages/Earns/types/reward.ts new file mode 100644 index 0000000000..ae35aded7c --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/types/reward.ts @@ -0,0 +1,58 @@ +export interface RewardInfo { + totalUsdValue: number + totalLmUsdValue: number + totalEgUsdValue: number + claimableUsdValue: number + claimedUsdValue: number + inProgressUsdValue: number + pendingUsdValue: number + vestingUsdValue: number + waitingUsdValue: number + nfts: Array + chains: Array + tokens: Array + egTokens: Array + lmTokens: Array +} + +export interface ChainRewardInfo { + chainId: number + chainName: string + chainLogo: string + claimableUsdValue: number + tokens: Array +} + +export interface NftRewardInfo { + nftId: string + chainId: number + totalUsdValue: number + totalLmUsdValue: number + totalEgUsdValue: number + claimedUsdValue: number + inProgressUsdValue: number + pendingUsdValue: number + vestingUsdValue: number + waitingUsdValue: number + claimableUsdValue: number + unclaimedUsdValue: number + + tokens: Array + egTokens: Array + lmTokens: Array +} + +export interface TokenRewardInfo { + symbol: string + logo: string + address: string + chainId: number + + totalAmount: number + claimableAmount: number + unclaimedAmount: number + pendingAmount: number + vestingAmount: number + waitingAmount: number + claimableUsdValue: number +} diff --git a/apps/kyberswap-interface/src/pages/Earns/types/smartExit.ts b/apps/kyberswap-interface/src/pages/Earns/types/smartExit.ts new file mode 100644 index 0000000000..8e24091c01 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/types/smartExit.ts @@ -0,0 +1,74 @@ +export enum Metric { + FeeYield = 'fee_yield', + PoolPrice = 'pool_price', + Time = 'time', +} + +export enum ConditionType { + And = 'and', + Or = 'or', +} + +export enum OrderStatus { + OrderStatusOpen = 'OrderStatusOpen', + OrderStatusDone = 'OrderStatusDone', + OrderStatusCancelled = 'OrderStatusCancelled', + OrderStatusExpired = 'OrderStatusExpired', +} + +export interface SmartExitFilter { + chainIds?: string + status?: string + dexTypes?: string + page: number +} + +export interface SmartExitFee { + protocol: { percentage: number; category: string } + gas: { percentage: number; usd: number; wei: string } +} + +export interface SmartExitCondition { + logical: { + op: ConditionType + conditions: Array<{ + field: { + type: Metric + value: any + } + }> + } +} + +export interface SmartExitOrder { + id: string + chainId: number + userWallet: string + dexType: string + poolId: string + positionId: string + removeLiquidity: string + unwrap: boolean + condition: SmartExitCondition + status: OrderStatus + createdAt: number + updatedAt: number + deadline: number +} + +export interface SelectedMetric { + metric: Metric + condition: FeeYieldCondition | PriceCondition | TimeCondition +} + +export type FeeYieldCondition = string + +export interface PriceCondition { + gte: string + lte: string +} + +export interface TimeCondition { + time: number | null + condition: 'after' | 'before' +} diff --git a/apps/kyberswap-interface/src/pages/Earns/utils/position.ts b/apps/kyberswap-interface/src/pages/Earns/utils/position.ts index 579f8cca2c..d55fb92aba 100644 --- a/apps/kyberswap-interface/src/pages/Earns/utils/position.ts +++ b/apps/kyberswap-interface/src/pages/Earns/utils/position.ts @@ -45,21 +45,15 @@ export const parsePosition = ({ }): ParsedPosition => { const forceClosed = isClosedFromRpc && position.status !== PositionStatus.CLOSED - const currentAmounts = position.currentAmounts - const feePending = position.feePending - const feesClaimed = position.feesClaimed - const pool = position.pool - const tokenAmounts = pool.tokenAmounts + const { currentAmounts, feePending, feesClaimed, pool, providedAmounts: initAmounts } = position + const { tokenAmounts, price: poolPrice } = pool const token0Data = tokenAmounts[0]?.token const token1Data = tokenAmounts[1]?.token - const currentAmount0 = currentAmounts[0] - const currentAmount1 = currentAmounts[1] + const [currentAmount0, currentAmount1] = currentAmounts - const feePending0 = feePending[0] - const feePending1 = feePending[1] - const feesClaimed0 = feesClaimed[0] - const feesClaimed1 = feesClaimed[1] + const [feePending0, feePending1] = feePending + const [feesClaimed0, feesClaimed1] = feesClaimed const token0CurrentQuote = currentAmount0?.quotes.usd const token1CurrentQuote = currentAmount1?.quotes.usd @@ -125,6 +119,7 @@ export const parsePosition = ({ const dex = pool.exchange || '' const isUniv2 = EARN_DEXES[dex].isForkFrom === CoreProtocol.UniswapV2 + const isUniv4 = EARN_DEXES[dex].isForkFrom === CoreProtocol.UniswapV4 const programs = pool.programs || [] const isFarming = programs.includes(ProgramType.EG) || programs.includes(ProgramType.LM) @@ -169,9 +164,19 @@ export const parsePosition = ({ feePending.reduce((sum, fee) => sum + fee.quotes.usd.value, 0) + feesClaimed.reduce((sum, fee) => sum + fee.quotes.usd.value, 0) - const chainId = position.chainId as keyof typeof NETWORKS_INFO + const chainId = position.chainId as unknown as keyof typeof NETWORKS_INFO const nativeToken = NETWORKS_INFO[chainId]?.nativeToken + // %Yield = [(f₀ + f₁/P) / (t₀ + t₁/P)] × 100 + const token0Decimal = feePending[0].token.decimals + const token1Decimal = feePending[1].token.decimals + const f0 = +feePending[0].balance / 10 ** token0Decimal + const f1 = +feePending[1].balance / 10 ** token1Decimal + const p = +poolPrice + const t0 = +initAmounts[0].balance / 10 ** token0Decimal + const t1 = +initAmounts[1].balance / 10 ** token1Decimal + const earningFeeYield = (100 * (f0 + f1 / p)) / (t0 + t1 / p) + const tickLower = pool.tickSpacing === 0 || isUniv2 ? undefined @@ -190,11 +195,14 @@ export const parsePosition = ({ const minTick = pool.tickSpacing === 0 ? MIN_TICK : nearestUsableTick(MIN_TICK, pool.tickSpacing) const maxTick = pool.tickSpacing === 0 ? MAX_TICK : nearestUsableTick(MAX_TICK, pool.tickSpacing) + const [id, stakingOwner] = position.id.split('_') const parsedStatus = forceClosed ? PositionStatus.CLOSED : isUniv2 ? PositionStatus.IN_RANGE : position.status return { - id: position.id, + id, tokenId: position.tokenId, + stakingOwner, + earningFeeYield, pool: { fee: pool.fees?.[0], address: pool.poolAddress, @@ -204,6 +212,7 @@ export const parsePosition = ({ isFarming, isFarmingLm, isUniv2, + isUniv4, }, dex: { id: dex, diff --git a/apps/kyberswap-interface/src/services/smartExit.ts b/apps/kyberswap-interface/src/services/smartExit.ts new file mode 100644 index 0000000000..c65b7595c6 --- /dev/null +++ b/apps/kyberswap-interface/src/services/smartExit.ts @@ -0,0 +1,203 @@ +import { ChainId } from '@kyberswap/ks-sdk-core' +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' + +import { SMART_EXIT_API_URL } from 'constants/env' +import { RTK_QUERY_TAGS } from 'constants/index' +import { SmartExitCondition, SmartExitFee, SmartExitOrder } from 'pages/Earns/types' + +interface SmartExitOrderResponse { + orders: SmartExitOrder[] + totalItems: number + totalPages: number + currentPage: number + pageSize: number +} + +interface SmartExitOrderParams { + chainIds?: string + userWallet: string + status?: string + dexTypes?: string + page?: number + pageSize?: number + positionIds?: string[] +} + +export interface SmartExitFeeParams { + chainId: number + userWallet: string + dexType: string + poolId: string + positionId: string + removeLiquidity: string + unwrap: boolean + permitData: string + condition: SmartExitCondition + deadline: number +} + +const smartExitApi = createApi({ + reducerPath: 'smartExitApi', + baseQuery: fetchBaseQuery({ baseUrl: SMART_EXIT_API_URL }), + tagTypes: [RTK_QUERY_TAGS.GET_SMART_EXIT_ORDERS], + endpoints: builder => ({ + getSmartExitOrders: builder.query({ + query: ({ chainIds, dexTypes, userWallet, status, page = 1, pageSize = 10, positionIds }) => { + const params = new URLSearchParams({ + userWallet, + page: page.toString(), + pageSize: pageSize.toString(), + }) + + if (status) params.append('status', status) + if (dexTypes) params.append('dexTypes', dexTypes) + if (chainIds) params.append('chainIds', chainIds) + + // Handle array by appending each value separately + if (positionIds) { + positionIds.forEach(id => params.append('positionIds', id)) + } + + return { + url: `/v1/orders/smart-exit?${params.toString()}`, + } + }, + transformResponse: (data: any) => { + const orders = data?.data?.orders || [] + const totalItems = data?.data?.pagination?.totalItems || orders.length + + // Ensure chainId is properly typed + orders.forEach((order: any) => { + order.chainId = Number(order.chainId) as ChainId + }) + + return { + orders, + totalItems, + totalPages: Math.ceil(totalItems / (data?.data?.pageSize || data?.pageSize || 10)), + currentPage: data?.data?.currentPage || data?.currentPage || 1, + pageSize: data?.data?.pageSize || data?.pageSize || 10, + } + }, + providesTags: [RTK_QUERY_TAGS.GET_SMART_EXIT_ORDERS], + }), + + createSmartExitOrder: builder.mutation< + { id: string; orderId: string }, + SmartExitFeeParams & { signature: string; maxFeesPercentage?: number[] } + >({ + query: body => ({ + url: '/v1/orders/smart-exit', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body, + }), + transformResponse: (data: any) => ({ + id: data?.data?.id || data?.id, + orderId: data?.data?.orderId || data?.orderId, + }), + invalidatesTags: [RTK_QUERY_TAGS.GET_SMART_EXIT_ORDERS], + }), + + estimateSmartExitFee: builder.mutation({ + query: body => ({ + url: '/v1/orders/smart-exit/estimate-fee', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body, + }), + transformResponse: (data: any) => ({ + protocol: data?.data?.protocol ?? {}, + gas: data?.data?.gas ?? {}, + }), + }), + + getSmartExitSignMessage: builder.mutation< + { + message: { + domain: any + types: any + message: any + primaryType: string + } + }, + SmartExitFeeParams & { maxFeesPercentage?: number[] } + >({ + query: body => ({ + url: '/v1/orders/smart-exit/sign-message', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body, + }), + transformResponse: (data: any) => ({ + message: data?.data || data?.message, + }), + }), + + getSmartExitCancelSignMessage: builder.mutation< + { + message: { + domain: any + types: any + message: any + primaryType: string + } + }, + { + chainId: number + userWallet: string + orderId: number + } + >({ + query: body => ({ + url: `/v1/orders/smart-exit/cancel/sign-message`, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body, + }), + transformResponse: (data: any) => ({ + message: data?.data || data?.message, + }), + }), + + cancelSmartExitOrder: builder.mutation< + any, + { + orderId: number + chainId: number + userWallet: string + signature: string + } + >({ + query: body => ({ + url: `/v1/orders/smart-exit/cancel`, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body, + }), + invalidatesTags: [RTK_QUERY_TAGS.GET_SMART_EXIT_ORDERS], + }), + }), +}) + +export const { + useGetSmartExitOrdersQuery, + useLazyGetSmartExitOrdersQuery, + useCreateSmartExitOrderMutation, + useGetSmartExitSignMessageMutation, + useGetSmartExitCancelSignMessageMutation, + useCancelSmartExitOrderMutation, + useEstimateSmartExitFeeMutation, +} = smartExitApi + +export default smartExitApi diff --git a/apps/kyberswap-interface/src/services/zapEarn.ts b/apps/kyberswap-interface/src/services/zapEarn.ts index 34ec47575d..7362f6365f 100644 --- a/apps/kyberswap-interface/src/services/zapEarn.ts +++ b/apps/kyberswap-interface/src/services/zapEarn.ts @@ -58,6 +58,7 @@ export interface PositionQueryParams { protocols: string q?: string positionId?: string + positionStatus?: string } interface PositionHistoryParams { diff --git a/apps/kyberswap-interface/src/state/index.ts b/apps/kyberswap-interface/src/state/index.ts index 72b19106de..719ced5372 100644 --- a/apps/kyberswap-interface/src/state/index.ts +++ b/apps/kyberswap-interface/src/state/index.ts @@ -22,6 +22,7 @@ import referralApi from 'services/referral' import rewardServiceApi from 'services/reward' import rewardMerklApi from 'services/rewardMerkl' import routeApi from 'services/route' +import smartExitApi from 'services/smartExit' import socialApi from 'services/social' import tokenApi from 'services/token' import zapEarnServiceApi from 'services/zapEarn' @@ -119,6 +120,7 @@ const store = configureStore({ [commonServiceApi.reducerPath]: commonServiceApi.reducer, [blackjackApi.reducerPath]: blackjackApi.reducer, [marketOverviewApi.reducerPath]: marketOverviewApi.reducer, + [smartExitApi.reducerPath]: smartExitApi.reducer, }, middleware: getDefaultMiddleware => getDefaultMiddleware({ thunk: true, immutableCheck: false, serializableCheck: false }) @@ -153,7 +155,8 @@ const store = configureStore({ .concat(campaignApi.middleware) .concat(commonServiceApi.middleware) .concat(blackjackApi.middleware) - .concat(marketOverviewApi.middleware), + .concat(marketOverviewApi.middleware) + .concat(smartExitApi.middleware), preloadedState, }) diff --git a/apps/kyberswap-interface/src/theme/color.ts b/apps/kyberswap-interface/src/theme/color.ts index dd912bc990..397b3d4eae 100644 --- a/apps/kyberswap-interface/src/theme/color.ts +++ b/apps/kyberswap-interface/src/theme/color.ts @@ -5,6 +5,7 @@ export function colors() { return { // base white, + white2: '#fafafa', black, // text diff --git a/package.json b/package.json index 9891eb68f8..d3edfb5436 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ }, "scripts": { "build": "turbo build", - "build-package": "pnpm --parallel -r --filter \"@kyber/svgr-esbuild-plugin\" --filter \"@kyber/schema\" build && pnpm --parallel -r --filter \"@kyber/utils\" --filter \"@kyber/ui\" build && pnpm --filter \"@kyberswap/liquidity-chart\" build && pnpm --parallel --filter \"@kyberswap/liquidity-widgets\" --filter \"@kyberswap/zap-migration-widgets\" --filter \"@kyberswap/zap-out-widgets\" --filter \"@kyberswap/pancake-liquidity-widgets\" --filter \"@kyberswap/compounding-widget\" build", + "build-package": "pnpm --parallel -r --filter \"@kyber/svgr-esbuild-plugin\" --filter \"@kyber/schema\" build && pnpm --parallel -r --filter \"@kyber/utils\" --filter \"@kyber/ui\" build && pnpm --filter \"@kyberswap/price-slider\" build && pnpm --filter \"@kyberswap/liquidity-chart\" build && pnpm --parallel --filter \"@kyberswap/liquidity-widgets\" --filter \"@kyberswap/zap-migration-widgets\" --filter \"@kyberswap/zap-out-widgets\" --filter \"@kyberswap/pancake-liquidity-widgets\" --filter \"@kyberswap/compounding-widget\" build", "lint": "turbo lint", "dev": "turbo dev", "type-check": "turbo type-check" diff --git a/packages/price-slider/.eslintrc.cjs b/packages/price-slider/.eslintrc.cjs new file mode 100644 index 0000000000..f934cce541 --- /dev/null +++ b/packages/price-slider/.eslintrc.cjs @@ -0,0 +1,4 @@ +/* eslint-env node */ +module.exports = { + extends: ['@kyber/eslint-config/index'], +}; diff --git a/packages/price-slider/.prettierrc.cjs b/packages/price-slider/.prettierrc.cjs new file mode 100644 index 0000000000..6e86a44df2 --- /dev/null +++ b/packages/price-slider/.prettierrc.cjs @@ -0,0 +1 @@ +module.exports = require('@kyber/eslint-config/prettier'); diff --git a/packages/price-slider/package.json b/packages/price-slider/package.json new file mode 100644 index 0000000000..aaacba39b4 --- /dev/null +++ b/packages/price-slider/package.json @@ -0,0 +1,57 @@ +{ + "name": "@kyberswap/price-slider", + "version": "1.0.0", + "license": "MIT", + "type": "module", + "exports": { + ".": { + "import": "./dist/price-slider.js", + "require": "./dist/price-slider.cjs" + }, + "./style.css": "./dist/price-slider.css" + }, + "main": "./dist/price-slider.cjs", + "module": "./dist/price-slider.js", + "types": "./dist/price-slider.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "lint": "eslint --ext .ts,.tsx src", + "format": "prettier --write src", + "prepublishOnly": "tsc && tsup", + "type-check": "tsc --noEmit" + }, + "dependencies": { + "@kyber/ui": "workspace:^" + }, + "devDependencies": { + "@kyber/eslint-config": "workspace:*", + "@kyber/tailwind-config": "workspace:^", + "@kyber/typescript-config": "workspace:*", + "@kyber/utils": "workspace:^", + "@types/eslint": "^8.56.5", + "@types/node": "^20.11.24", + "@types/react": "^18.0.17", + "@types/react-dom": "^18.0.6", + "autoprefixer": "^10.4.20", + "eslint": "^8.57.0", + "postcss": "^8.4.47", + "tailwindcss": "^3.4.13", + "tsup": "^8.3.0", + "typescript": "5.3.2", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.4.0", + "prettier": "^3.5.3", + "@trivago/prettier-plugin-sort-imports": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + } +} diff --git a/packages/price-slider/postcss.config.js b/packages/price-slider/postcss.config.js new file mode 100644 index 0000000000..6b83666f1d --- /dev/null +++ b/packages/price-slider/postcss.config.js @@ -0,0 +1,19 @@ +const prefixOverrideList = ['html', 'body', "[role='portal']"]; + +export default { + plugins: { + 'postcss-import': {}, + 'tailwindcss/nesting': {}, + tailwindcss: { config: 'tailwind.config.ts' }, + 'postcss-prefix-selector': { + prefix: '.ks-ps-style', + transform(prefix, selector, prefixedSelector) { + if (prefixOverrideList.includes(selector)) { + return prefix; + } + return prefixedSelector; + }, + }, + autoprefixer: {}, + }, +}; diff --git a/packages/price-slider/src/components/PriceAxis.tsx b/packages/price-slider/src/components/PriceAxis.tsx new file mode 100644 index 0000000000..5771b982f5 --- /dev/null +++ b/packages/price-slider/src/components/PriceAxis.tsx @@ -0,0 +1,179 @@ +import React, { useMemo } from 'react'; + +import { tickToPrice } from '@kyber/utils/dist/uniswapv3'; + +import { MAX_AXIS_TICK_COUNT, MIN_AXIS_TICK_COUNT } from '@/constants'; +import type { PriceAxisProps } from '@/types'; +import { formatAxisPrice } from '@/utils'; + +/** + * Calculate the optimal number of ticks and minimum gap based on price range + * More ticks for small ranges, fewer for large ranges + */ +const getOptimalTickConfig = (minPrice: number, maxPrice: number): { tickCount: number; minGapPercent: number } => { + if (minPrice <= 0 || maxPrice <= 0) { + return { tickCount: 7, minGapPercent: 15 }; + } + + // Calculate how many orders of magnitude the prices span + const priceRatio = maxPrice / minPrice; + const ordersOfMagnitude = Math.log10(priceRatio); + + // Very small range (< 0.5 orders): many ticks, small gap + if (ordersOfMagnitude <= 0.5) { + return { tickCount: MAX_AXIS_TICK_COUNT, minGapPercent: 12 }; + } + // Small range (0.5 - 1 order): good amount of ticks + if (ordersOfMagnitude <= 1) { + return { tickCount: 9, minGapPercent: 14 }; + } + // Medium range (1 - 2 orders) + if (ordersOfMagnitude <= 2) { + return { tickCount: 7, minGapPercent: 16 }; + } + // Large range (2 - 4 orders) + if (ordersOfMagnitude <= 4) { + return { tickCount: 5, minGapPercent: 20 }; + } + // Very large range (4 - 8 orders) + if (ordersOfMagnitude <= 8) { + return { tickCount: 3, minGapPercent: 30 }; + } + // Extreme range (> 8 orders): just first and last + return { tickCount: MIN_AXIS_TICK_COUNT, minGapPercent: 40 }; +}; + +/** + * Calculate tick positions for the axis + * Uses tick-space for even distribution (matching the slider) + * When invertPrice, positions are flipped so lower inverted price is on left + */ +const calculateAxisTicks = ( + viewRange: { min: number; max: number }, + token0Decimals: number, + token1Decimals: number, + count: number, + invertPrice?: boolean, +): Array<{ tick: number; price: number; position: number }> => { + const tickRange = viewRange.max - viewRange.min; + if (tickRange <= 0) return []; + + const step = tickRange / (count - 1); + const ticks: Array<{ tick: number; price: number; position: number }> = []; + + for (let i = 0; i < count; i++) { + const tick = Math.round(viewRange.min + step * i); + const price = +tickToPrice(tick, token0Decimals, token1Decimals, invertPrice); + const normalPosition = ((tick - viewRange.min) / tickRange) * 100; + // When invertPrice, flip position so lower inverted price (from higher tick) is on left + const position = invertPrice ? 100 - normalPosition : normalPosition; + + ticks.push({ tick, price, position }); + } + + return ticks; +}; + +/** + * Filter ticks to ensure minimum spacing between labels + * Only shows labels that have sufficient gap from previous label + */ +const filterOverlappingTicks = ( + ticks: Array<{ tick: number; price: number; position: number }>, + minGapPercent: number, +): Array<{ tick: number; price: number; position: number; showLabel: boolean }> => { + if (ticks.length === 0) return []; + + const result: Array<{ tick: number; price: number; position: number; showLabel: boolean }> = []; + let lastLabelPosition = -Infinity; + + for (let i = 0; i < ticks.length; i++) { + const tick = ticks[i]; + const isFirst = i === 0; + const isLast = i === ticks.length - 1; + const gap = tick.position - lastLabelPosition; + + // First tick always shows label + if (isFirst) { + lastLabelPosition = tick.position; + result.push({ ...tick, showLabel: true }); + continue; + } + + // Last tick: only show if enough gap, otherwise hide + if (isLast) { + const showLabel = gap >= minGapPercent; + if (showLabel) lastLabelPosition = tick.position; + result.push({ ...tick, showLabel }); + continue; + } + + // Middle ticks: show if enough gap from previous label + const showLabel = gap >= minGapPercent; + if (showLabel) lastLabelPosition = tick.position; + result.push({ ...tick, showLabel }); + } + + return result; +}; + +/** + * Price axis component that displays price scale below the slider + * Uses tick-based positioning to match the slider exactly + * Dynamically reduces tick count when price range is very large + */ +function PriceAxis({ viewRange, token0Decimals, token1Decimals, invertPrice }: PriceAxisProps) { + const axisTicks = useMemo(() => { + // Get min and max prices to determine optimal tick config + const minPrice = +tickToPrice(Math.round(viewRange.min), token0Decimals, token1Decimals, invertPrice); + const maxPrice = +tickToPrice(Math.round(viewRange.max), token0Decimals, token1Decimals, invertPrice); + const { tickCount, minGapPercent } = getOptimalTickConfig( + Math.min(minPrice, maxPrice), + Math.max(minPrice, maxPrice), + ); + + const ticks = calculateAxisTicks(viewRange, token0Decimals, token1Decimals, tickCount, invertPrice); + // Sort by position ascending for proper overlap filtering + const sortedTicks = [...ticks].sort((a, b) => a.position - b.position); + return filterOverlappingTicks(sortedTicks, minGapPercent); + }, [viewRange, token0Decimals, token1Decimals, invertPrice]); + + return ( +
+ {/* Axis Line */} +
+ + {/* Ticks and Labels */} + {axisTicks.map(({ tick, price, position, showLabel }, index) => { + // Only render if within visible range + if (position < -2 || position > 102) return null; + + // Determine alignment: first label align left, last label align right, others center + const isFirst = index === 0; + const isLast = index === axisTicks.length - 1; + const alignClass = isFirst ? 'left-0' : isLast ? 'right-0' : '-translate-x-1/2'; + + return ( + + {/* Tick Mark */} +
+ {/* Label */} + {showLabel && ( +
+ {formatAxisPrice(price)} +
+ )} + + ); + })} +
+ ); +} + +export default React.memo(PriceAxis); diff --git a/packages/price-slider/src/components/Skeleton.tsx b/packages/price-slider/src/components/Skeleton.tsx new file mode 100644 index 0000000000..d842256fd6 --- /dev/null +++ b/packages/price-slider/src/components/Skeleton.tsx @@ -0,0 +1,84 @@ +import React from 'react'; + +import { Skeleton } from '@kyber/ui'; + +import { SKELETON_AXIS_POSITIONS } from '@/constants'; + +/** + * Skeleton loading state for the price slider + * Uses Skeleton component from @kyber/ui for shimmer animation + */ +function PriceSliderSkeleton() { + return ( +
+ {/* Slider Area */} +
+ {/* Track */} + + + {/* Range */} + + + {/* Current Price Marker */} + + + {/* Lower Price Label */} + + + {/* Upper Price Label */} + + + {/* Lower Handle */} + + + {/* Upper Handle */} + +
+ + {/* Axis Container */} +
+ {/* Axis Line */} + + + {/* Axis Ticks and Labels */} + {SKELETON_AXIS_POSITIONS.map((pos, index) => { + const isFirst = index === 0; + const isLast = index === SKELETON_AXIS_POSITIONS.length - 1; + const alignClass = isFirst ? 'left-0' : isLast ? 'right-0' : '-translate-x-1/2'; + + return ( + + {/* Tick */} + + {/* Label */} + + + ); + })} +
+
+ ); +} + +export default React.memo(PriceSliderSkeleton); diff --git a/packages/price-slider/src/components/UniswapPriceSlider.tsx b/packages/price-slider/src/components/UniswapPriceSlider.tsx new file mode 100644 index 0000000000..f0137ac4a7 --- /dev/null +++ b/packages/price-slider/src/components/UniswapPriceSlider.tsx @@ -0,0 +1,416 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; + +import { MAX_TICK, MIN_TICK, tickToPrice } from '@kyber/utils/dist/uniswapv3'; + +import PriceAxis from '@/components/PriceAxis'; +import PriceSliderSkeleton from '@/components/Skeleton'; +import { AUTO_CENTER_PADDING, EDGE_THRESHOLD, MIN_HANDLE_DISTANCE_MULTIPLIER } from '@/constants'; +import { useDebouncedTicks, useSmoothZoom, useTickPositionConverter } from '@/hooks'; +import type { HandleType, UniswapPriceSliderProps, ViewRange } from '@/types'; +import { brushHandlePath, formatDisplayNumber, getEdgeIntensity } from '@/utils'; + +function UniswapPriceSlider({ + pool, + invertPrice, + lowerTick, + upperTick, + setLowerTick, + setUpperTick, + className, +}: UniswapPriceSliderProps) { + const { tickSpacing, token0Decimals, token1Decimals, currentTick } = pool; + + const [viewRange, setViewRange] = useState(null); + const [isDragging, setIsDragging] = useState(null); + + const sliderRef = useRef(null); + const isInitialized = useRef(false); + const viewRangeRef = useRef(viewRange); + const lastAdjustedTicksRef = useRef<{ lower: number; upper: number } | null>(null); + + // Keep viewRangeRef in sync with viewRange state + useEffect(() => { + viewRangeRef.current = viewRange; + }, [viewRange]); + + const { startSmoothZoom } = useSmoothZoom(viewRange, setViewRange); + + const { + internalLowerTick, + internalUpperTick, + debouncedSetLowerTick, + debouncedSetUpperTick, + flushDebouncedValues, + getTargetTicks, + } = useDebouncedTicks(lowerTick, upperTick, setLowerTick, setUpperTick, isDragging !== null); + + const { getPositionFromTick, getTickFromPosition } = useTickPositionConverter(viewRange, tickSpacing, invertPrice); + + const ticksReady = lowerTick !== undefined && upperTick !== undefined && upperTick > lowerTick; + + // Initialize View Range + useEffect(() => { + if (isInitialized.current || !ticksReady) return; + + const tickRange = Math.abs(upperTick - lowerTick); + const padding = Math.max(tickRange * 0.5, tickSpacing * 50); + + const minTick = Math.min(lowerTick, upperTick, currentTick); + const maxTick = Math.max(lowerTick, upperTick, currentTick); + + setViewRange({ + min: Math.max(MIN_TICK, minTick - padding), + max: Math.min(MAX_TICK, maxTick + padding), + }); + isInitialized.current = true; + }, [lowerTick, upperTick, currentTick, tickSpacing, ticksReady]); + + // Auto-adjust viewRange when ticks change from outside (e.g., input fields) + useEffect(() => { + // Skip if not initialized, dragging, or ticks not ready + if (!isInitialized.current || isDragging || !ticksReady || !viewRange) return; + + // Skip if already adjusted for these exact tick values + const lastAdjusted = lastAdjustedTicksRef.current; + if (lastAdjusted && lastAdjusted.lower === lowerTick && lastAdjusted.upper === upperTick) return; + + const currentRange = viewRange.max - viewRange.min; + const lowerPos = ((lowerTick - viewRange.min) / currentRange) * 100; + const upperPos = ((upperTick - viewRange.min) / currentRange) * 100; + + // Check if handles are outside visible area or positioned poorly + const handleOutsideLeft = lowerPos < -5 || upperPos < -5; + const handleOutsideRight = lowerPos > 105 || upperPos > 105; + const handleSpan = Math.abs(upperPos - lowerPos); + const idealHandleSpan = 100 - 2 * AUTO_CENTER_PADDING; + const handlesTooClose = handleSpan < idealHandleSpan * 0.4; // Much smaller than ideal + const handlesTooFar = handleSpan > idealHandleSpan * 2; // Much larger than ideal + + // If adjustment needed, calculate new viewRange + if (handleOutsideLeft || handleOutsideRight || handlesTooClose || handlesTooFar) { + const tickDistance = Math.abs(upperTick - lowerTick); + const handleCenter = (lowerTick + upperTick) / 2; + + const idealPadding = tickDistance * (AUTO_CENTER_PADDING / (100 - 2 * AUTO_CENTER_PADDING)); + const minPadding = Math.max(tickDistance * 0.3, tickSpacing * 20); + const padding = Math.max(idealPadding, minPadding); + + const targetMin = Math.max(MIN_TICK, handleCenter - tickDistance / 2 - padding); + const targetMax = Math.min(MAX_TICK, handleCenter + tickDistance / 2 + padding); + + // Mark these ticks as adjusted to prevent re-triggering + lastAdjustedTicksRef.current = { lower: lowerTick, upper: upperTick }; + startSmoothZoom(targetMin, targetMax); + } + }, [lowerTick, upperTick, isDragging, ticksReady, viewRange, tickSpacing, startSmoothZoom]); + + const handleMouseDown = useCallback( + (handle: 'lower' | 'upper') => (e: React.MouseEvent) => { + e.preventDefault(); + setIsDragging(handle); + }, + [], + ); + + const handleTouchStart = useCallback( + (handle: 'lower' | 'upper') => (e: React.TouchEvent) => { + e.preventDefault(); + setIsDragging(handle); + }, + [], + ); + + // Shared logic for handling drag movement (mouse or touch) + const handleDragMove = useCallback( + (clientX: number) => { + if (!isDragging || !sliderRef.current || !viewRange || lowerTick === undefined || upperTick === undefined) return; + + const rect = sliderRef.current.getBoundingClientRect(); + const x = clientX - rect.left; + const position = Math.max(0, Math.min(100, (x / rect.width) * 100)); + const newTick = getTickFromPosition(position); + + const currentRange = viewRange.max - viewRange.min; + + // Check if near edges for zoom out + const isNearLeftEdge = position < EDGE_THRESHOLD; + const isNearRightEdge = position > 100 - EDGE_THRESHOLD; + const edgeIntensity = getEdgeIntensity(position, EDGE_THRESHOLD); + + // Zoom out when near edges (zoom-in is handled by auto-center on mouse up) + if (isNearLeftEdge || isNearRightEdge) { + const baseExpansion = currentRange * 0.25; + const expansion = baseExpansion * edgeIntensity; + + let targetMin = viewRange.min; + let targetMax = viewRange.max; + + if (isNearLeftEdge && viewRange.min > MIN_TICK) { + targetMin = Math.max(MIN_TICK, viewRange.min - expansion); + } + if (isNearRightEdge && viewRange.max < MAX_TICK) { + targetMax = Math.min(MAX_TICK, viewRange.max + expansion); + } + + startSmoothZoom(targetMin, targetMax); + } + + // Update tick values (with minimum distance between handles) + if (isDragging === 'lower') { + const maxLower = (internalUpperTick ?? upperTick ?? 0) - tickSpacing * MIN_HANDLE_DISTANCE_MULTIPLIER; + debouncedSetLowerTick(Math.min(newTick, maxLower)); + } else { + const minUpper = (internalLowerTick ?? lowerTick ?? 0) + tickSpacing * MIN_HANDLE_DISTANCE_MULTIPLIER; + debouncedSetUpperTick(Math.max(newTick, minUpper)); + } + }, + [ + debouncedSetLowerTick, + debouncedSetUpperTick, + getTickFromPosition, + internalLowerTick, + internalUpperTick, + isDragging, + lowerTick, + startSmoothZoom, + tickSpacing, + upperTick, + viewRange, + ], + ); + + const handleMouseMove = useCallback( + (e: MouseEvent) => { + handleDragMove(e.clientX); + }, + [handleDragMove], + ); + + const handleTouchMove = useCallback( + (e: TouchEvent) => { + if (e.touches.length > 0) { + handleDragMove(e.touches[0].clientX); + } + }, + [handleDragMove], + ); + + const handleMouseUp = useCallback(() => { + // Get the TARGET tick values (what user intended), not the animated values + const { lowerTick: targetLower, upperTick: targetUpper } = getTargetTicks(); + + // Flush to apply target values immediately + flushDebouncedValues(); + setIsDragging(null); + + // Use target ticks for auto-center calculation + const finalLowerTick = targetLower ?? lowerTick; + const finalUpperTick = targetUpper ?? upperTick; + + if (finalLowerTick === undefined || finalUpperTick === undefined) return; + + // Use setTimeout to ensure state has updated before calculating positions + setTimeout(() => { + // Use ref to get the LATEST viewRange (not stale closure value) + const currentViewRange = viewRangeRef.current; + if (!currentViewRange) return; + + const tickDistance = Math.abs(finalUpperTick - finalLowerTick); + const handleCenter = (finalLowerTick + finalUpperTick) / 2; + + // Calculate ideal padding (25% on each side = handles take up 50% of view) + const idealPadding = tickDistance * (AUTO_CENTER_PADDING / (100 - 2 * AUTO_CENTER_PADDING)); + const minPadding = Math.max(tickDistance * 0.3, tickSpacing * 20); + const padding = Math.max(idealPadding, minPadding); + + const targetMin = Math.max(MIN_TICK, handleCenter - tickDistance / 2 - padding); + const targetMax = Math.min(MAX_TICK, handleCenter + tickDistance / 2 + padding); + + // Calculate current positions using LATEST viewRange from ref + const currentRange = currentViewRange.max - currentViewRange.min; + const rawLowerPos = ((finalLowerTick - currentViewRange.min) / currentRange) * 100; + const rawUpperPos = ((finalUpperTick - currentViewRange.min) / currentRange) * 100; + + // Account for invertPrice: when inverted, positions are flipped + const currentLowerPos = invertPrice ? 100 - rawLowerPos : rawLowerPos; + const currentUpperPos = invertPrice ? 100 - rawUpperPos : rawUpperPos; + + // Left/right padding based on visual positions (not tick order) + const leftPadding = Math.min(currentLowerPos, currentUpperPos); + const rightPadding = 100 - Math.max(currentLowerPos, currentUpperPos); + const handleSpan = Math.abs(currentUpperPos - currentLowerPos); // % of view that handles span + + // Ideal handle span is 50% (100 - 2 * AUTO_CENTER_PADDING) + const idealHandleSpan = 100 - 2 * AUTO_CENTER_PADDING; + const handlesTooClose = handleSpan < idealHandleSpan * 0.6; // Less than 60% of ideal = too zoomed out + const handlesTooFar = handleSpan > idealHandleSpan * 1.5; // More than 150% of ideal = too zoomed in + + // Check if rebalancing is needed + const needsRebalance = + leftPadding < EDGE_THRESHOLD + 5 || // Near left edge + rightPadding < EDGE_THRESHOLD + 5 || // Near right edge + leftPadding < 0 || // Handle outside left + rightPadding < 0 || // Handle outside right + handlesTooClose || // Handles too close together (need zoom in) + handlesTooFar || // Handles too far apart (need zoom out) + (leftPadding > 5 && rightPadding > 5 && (leftPadding / rightPadding > 2.5 || rightPadding / leftPadding > 2.5)); // Imbalanced + + if (needsRebalance) { + startSmoothZoom(targetMin, targetMax); + } + }, 50); + }, [flushDebouncedValues, getTargetTicks, invertPrice, lowerTick, startSmoothZoom, tickSpacing, upperTick]); + + useEffect(() => { + if (!isDragging) return; + + // Set grabbing cursor on body to persist while dragging outside handle + document.body.style.cursor = 'grabbing'; + document.body.style.userSelect = 'none'; + + // Mouse events + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + + // Touch events + document.addEventListener('touchmove', handleTouchMove, { passive: false }); + document.addEventListener('touchend', handleMouseUp); + document.addEventListener('touchcancel', handleMouseUp); + + return () => { + // Reset cursor when dragging ends + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + + // Remove mouse events + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + + // Remove touch events + document.removeEventListener('touchmove', handleTouchMove); + document.removeEventListener('touchend', handleMouseUp); + document.removeEventListener('touchcancel', handleMouseUp); + }; + }, [isDragging, handleMouseMove, handleMouseUp, handleTouchMove]); + + if (!ticksReady || !viewRange) { + return ; + } + + // Use internal ticks for smooth visual updates during dragging + const displayLowerTick = internalLowerTick ?? lowerTick; + const displayUpperTick = internalUpperTick ?? upperTick; + + // Calculate prices (with invertPrice applied) + const lowerTickPrice = tickToPrice(Math.round(displayLowerTick), token0Decimals, token1Decimals, invertPrice); + const upperTickPrice = tickToPrice(Math.round(displayUpperTick), token0Decimals, token1Decimals, invertPrice); + const currentPrice = tickToPrice(currentTick, token0Decimals, token1Decimals, invertPrice); + + // Calculate positions (flipped when invertPrice=true by the hook) + const lowerPosition = getPositionFromTick(displayLowerTick); + const upperPosition = getPositionFromTick(displayUpperTick); + const currentPosition = getPositionFromTick(currentTick); + + // When invertPrice, positions are flipped so: + // - lowerTick (higher inverted price) is on the RIGHT + // - upperTick (lower inverted price) is on the LEFT + // This means left position = min of the two, right position = max of the two + const leftPosition = Math.min(lowerPosition, upperPosition); + const rightPosition = Math.max(lowerPosition, upperPosition); + + // Determine which tick is at which visual position + const isLowerOnLeft = lowerPosition <= upperPosition; + const leftPrice = isLowerOnLeft ? lowerTickPrice : upperTickPrice; + const rightPrice = isLowerOnLeft ? upperTickPrice : lowerTickPrice; + const leftHandleType: 'lower' | 'upper' = isLowerOnLeft ? 'lower' : 'upper'; + const rightHandleType: 'lower' | 'upper' = isLowerOnLeft ? 'upper' : 'lower'; + + return ( +
+ {/* Slider Wrapper */} +
+ {/* Track */} +
+ + {/* Range */} +
+ + {/* Current Price Marker */} +
+
+ {/* Arrow indicator */} +
+
+ {/* Tooltip */} +
+ {formatDisplayNumber(currentPrice, { significantDigits: 6 })} +
+
+ + {/* Left Price Label */} +
+ {formatDisplayNumber(leftPrice, { significantDigits: 6 })} +
+ + {/* Right Price Label */} +
+ {formatDisplayNumber(rightPrice, { significantDigits: 6 })} +
+ + {/* Left Handle (green) */} +
+ + + +
+ + {/* Right Handle (blue) */} +
+ + + +
+
+ + +
+ ); +} + +export default UniswapPriceSlider; diff --git a/packages/price-slider/src/constants/index.ts b/packages/price-slider/src/constants/index.ts new file mode 100644 index 0000000000..6dd258b782 --- /dev/null +++ b/packages/price-slider/src/constants/index.ts @@ -0,0 +1,43 @@ +// Animation Constants +/** Duration for zoom/auto-center animation in milliseconds */ +export const ZOOM_DURATION = 400; + +/** Delay before committing tick changes to parent in milliseconds */ +export const DEBOUNCE_DELAY = 150; + +/** Minimum LERP factor when handle is far from target (slower movement) */ +export const HANDLE_LERP_MIN = 0.15; + +/** Maximum LERP factor when handle is close to target (faster movement) */ +export const HANDLE_LERP_MAX = 0.4; + +/** Maximum ticks per frame to prevent jumpy handle movement */ +export const MAX_TICK_SPEED = 2000; + +// Slider Behavior Constants +/** Percentage from edge that triggers zoom out (ensures price labels visible) */ +export const EDGE_THRESHOLD = 18; + +/** Percentage padding on each side when auto-centering after drag */ +export const AUTO_CENTER_PADDING = 25; + +/** Minimum tick spacings between handles to prevent overlap (keep small for large tickSpacing pools) */ +export const MIN_HANDLE_DISTANCE_MULTIPLIER = 1; + +// Dynamic LERP Constants +/** Distance threshold (in ticks) for minimum lerp factor */ +export const LERP_FAR_THRESHOLD = 5000; + +/** Distance threshold (in ticks) for maximum lerp factor */ +export const LERP_CLOSE_THRESHOLD = 100; + +// Price Axis Constants +/** Maximum number of ticks on price axis for small ranges */ +export const MAX_AXIS_TICK_COUNT = 11; + +/** Minimum number of ticks on price axis for extreme ranges */ +export const MIN_AXIS_TICK_COUNT = 2; + +// Skeleton Constants +/** Positions for skeleton axis ticks (percentage) */ +export const SKELETON_AXIS_POSITIONS = [0, 16.6, 33.3, 50, 66.6, 83.3, 100]; diff --git a/packages/price-slider/src/hooks/index.ts b/packages/price-slider/src/hooks/index.ts new file mode 100644 index 0000000000..b08011d1e1 --- /dev/null +++ b/packages/price-slider/src/hooks/index.ts @@ -0,0 +1,3 @@ +export { useSmoothZoom } from '@/hooks/useSmoothZoom'; +export { useDebouncedTicks } from '@/hooks/useDebouncedTicks'; +export { useTickPositionConverter } from '@/hooks/useTickPositionConverter'; diff --git a/packages/price-slider/src/hooks/useDebouncedTicks.ts b/packages/price-slider/src/hooks/useDebouncedTicks.ts new file mode 100644 index 0000000000..2d8010a5a7 --- /dev/null +++ b/packages/price-slider/src/hooks/useDebouncedTicks.ts @@ -0,0 +1,205 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { + DEBOUNCE_DELAY, + HANDLE_LERP_MAX, + HANDLE_LERP_MIN, + LERP_CLOSE_THRESHOLD, + LERP_FAR_THRESHOLD, + MAX_TICK_SPEED, +} from '@/constants'; + +/** + * Hook for smooth tick updates with animation and debouncing + * Handles move slowly/smoothly towards target position + */ +export const useDebouncedTicks = ( + lowerTick: number | undefined, + upperTick: number | undefined, + setLowerTick: (tick: number) => void, + setUpperTick: (tick: number) => void, + isDragging: boolean, +) => { + const debounceTimerRef = useRef(null); + const animationRef = useRef(null); + + // Target ticks (where user wants to go) + const targetLowerTickRef = useRef(lowerTick); + const targetUpperTickRef = useRef(upperTick); + + // Current internal tick values (tracked via refs for animation loop) + const internalLowerRef = useRef(lowerTick); + const internalUpperRef = useRef(upperTick); + + // Internal tick state for React rendering + const [internalLowerTick, setInternalLowerTick] = useState(lowerTick); + const [internalUpperTick, setInternalUpperTick] = useState(upperTick); + + // Helper: calculate dynamic lerp factor based on distance + const getDynamicLerp = useCallback((diff: number): number => { + const absDiff = Math.abs(diff); + if (absDiff > LERP_FAR_THRESHOLD) return HANDLE_LERP_MIN; + if (absDiff < LERP_CLOSE_THRESHOLD) return HANDLE_LERP_MAX; + const t = (absDiff - LERP_CLOSE_THRESHOLD) / (LERP_FAR_THRESHOLD - LERP_CLOSE_THRESHOLD); + return HANDLE_LERP_MAX - t * (HANDLE_LERP_MAX - HANDLE_LERP_MIN); + }, []); + + // Animation function for smooth handle movement + const animateHandles = useCallback(() => { + let needsAnimation = false; + + // Update lower tick + if (internalLowerRef.current !== undefined && targetLowerTickRef.current !== undefined) { + const current = internalLowerRef.current; + const target = targetLowerTickRef.current; + const diff = target - current; + + if (Math.abs(diff) >= 1) { + needsAnimation = true; + const lerpFactor = getDynamicLerp(diff); + const lerpMovement = diff * lerpFactor; + const cappedMovement = Math.sign(lerpMovement) * Math.min(Math.abs(lerpMovement), MAX_TICK_SPEED); + const newValue = current + cappedMovement; + internalLowerRef.current = newValue; + setInternalLowerTick(newValue); + } else if (current !== target) { + internalLowerRef.current = target; + setInternalLowerTick(target); + } + } + + // Update upper tick + if (internalUpperRef.current !== undefined && targetUpperTickRef.current !== undefined) { + const current = internalUpperRef.current; + const target = targetUpperTickRef.current; + const diff = target - current; + + if (Math.abs(diff) >= 1) { + needsAnimation = true; + const lerpFactor = getDynamicLerp(diff); + const lerpMovement = diff * lerpFactor; + const cappedMovement = Math.sign(lerpMovement) * Math.min(Math.abs(lerpMovement), MAX_TICK_SPEED); + const newValue = current + cappedMovement; + internalUpperRef.current = newValue; + setInternalUpperTick(newValue); + } else if (current !== target) { + internalUpperRef.current = target; + setInternalUpperTick(target); + } + } + + if (needsAnimation) { + animationRef.current = requestAnimationFrame(animateHandles); + } else { + animationRef.current = null; + } + }, [getDynamicLerp]); + + // Start animation if not already running + const startAnimation = useCallback(() => { + if (!animationRef.current) { + animationRef.current = requestAnimationFrame(animateHandles); + } + }, [animateHandles]); + + // Sync internal state with props when not dragging + useEffect(() => { + if (!isDragging) { + targetLowerTickRef.current = lowerTick; + targetUpperTickRef.current = upperTick; + internalLowerRef.current = lowerTick; + internalUpperRef.current = upperTick; + setInternalLowerTick(lowerTick); + setInternalUpperTick(upperTick); + } + }, [lowerTick, upperTick, isDragging]); + + // Smooth update functions - set target and start animation + const debouncedSetLowerTick = useCallback( + (tick: number) => { + targetLowerTickRef.current = tick; + startAnimation(); + + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + debounceTimerRef.current = setTimeout(() => { + setLowerTick(tick); + }, DEBOUNCE_DELAY); + }, + [setLowerTick, startAnimation], + ); + + const debouncedSetUpperTick = useCallback( + (tick: number) => { + targetUpperTickRef.current = tick; + startAnimation(); + + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + debounceTimerRef.current = setTimeout(() => { + setUpperTick(tick); + }, DEBOUNCE_DELAY); + }, + [setUpperTick, startAnimation], + ); + + // Flush debounced values immediately + const flushDebouncedValues = useCallback(() => { + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + debounceTimerRef.current = null; + } + + // Set final values from targets + const finalLower = targetLowerTickRef.current; + const finalUpper = targetUpperTickRef.current; + + if (finalLower !== undefined && finalLower !== lowerTick) { + setLowerTick(finalLower); + internalLowerRef.current = finalLower; + setInternalLowerTick(finalLower); + } + if (finalUpper !== undefined && finalUpper !== upperTick) { + setUpperTick(finalUpper); + internalUpperRef.current = finalUpper; + setInternalUpperTick(finalUpper); + } + + // Stop animation + if (animationRef.current) { + cancelAnimationFrame(animationRef.current); + animationRef.current = null; + } + }, [lowerTick, upperTick, setLowerTick, setUpperTick]); + + // Cleanup on unmount + useEffect(() => { + return () => { + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + if (animationRef.current) { + cancelAnimationFrame(animationRef.current); + } + }; + }, []); + + // Get target ticks (what user actually wants, not the animated value) + const getTargetTicks = useCallback(() => { + return { + lowerTick: targetLowerTickRef.current, + upperTick: targetUpperTickRef.current, + }; + }, []); + + return { + internalLowerTick, + internalUpperTick, + debouncedSetLowerTick, + debouncedSetUpperTick, + flushDebouncedValues, + getTargetTicks, + }; +}; diff --git a/packages/price-slider/src/hooks/useSmoothZoom.ts b/packages/price-slider/src/hooks/useSmoothZoom.ts new file mode 100644 index 0000000000..971995d61f --- /dev/null +++ b/packages/price-slider/src/hooks/useSmoothZoom.ts @@ -0,0 +1,82 @@ +import { Dispatch, SetStateAction, useCallback, useEffect, useRef } from 'react'; + +import { ZOOM_DURATION } from '@/constants'; +import type { ViewRange } from '@/types'; + +/** Easing function: ease-out cubic for smooth deceleration */ +const easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3); + +/** + * Hook for smooth zoom animation using easing function + * Uses ease-out for natural deceleration feel + */ +export const useSmoothZoom = ( + viewRange: ViewRange | null, + setViewRange: Dispatch>, +) => { + const zoomAnimationRef = useRef(null); + const targetViewRangeRef = useRef(null); + const startViewRangeRef = useRef(null); + const startTimeRef = useRef(0); + + const animateZoom = useCallback(() => { + if (!targetViewRangeRef.current || !startViewRangeRef.current) { + zoomAnimationRef.current = null; + return; + } + + const now = performance.now(); + const elapsed = now - startTimeRef.current; + const progress = Math.min(elapsed / ZOOM_DURATION, 1); + const easedProgress = easeOutCubic(progress); + + const start = startViewRangeRef.current; + const target = targetViewRangeRef.current; + + const newMin = start.min + (target.min - start.min) * easedProgress; + const newMax = start.max + (target.max - start.max) * easedProgress; + + setViewRange({ min: newMin, max: newMax }); + + if (progress < 1) { + // Continue animation + zoomAnimationRef.current = requestAnimationFrame(animateZoom); + } else { + // Animation complete - set exact target values + setViewRange(target); + targetViewRangeRef.current = null; + startViewRangeRef.current = null; + zoomAnimationRef.current = null; + } + }, [setViewRange]); + + const startSmoothZoom = useCallback( + (targetMin: number, targetMax: number) => { + // If already animating, use current position as new start + if (zoomAnimationRef.current && viewRange) { + startViewRangeRef.current = viewRange; + } else if (viewRange) { + startViewRangeRef.current = viewRange; + } + + targetViewRangeRef.current = { min: targetMin, max: targetMax }; + startTimeRef.current = performance.now(); + + if (!zoomAnimationRef.current) { + zoomAnimationRef.current = requestAnimationFrame(animateZoom); + } + }, + [animateZoom, viewRange], + ); + + // Cleanup animation on unmount + useEffect(() => { + return () => { + if (zoomAnimationRef.current) { + cancelAnimationFrame(zoomAnimationRef.current); + } + }; + }, []); + + return { startSmoothZoom }; +}; diff --git a/packages/price-slider/src/hooks/useTickPositionConverter.ts b/packages/price-slider/src/hooks/useTickPositionConverter.ts new file mode 100644 index 0000000000..dd0bf082f6 --- /dev/null +++ b/packages/price-slider/src/hooks/useTickPositionConverter.ts @@ -0,0 +1,39 @@ +import { useCallback } from 'react'; + +import { nearestUsableTick } from '@kyber/utils/dist/uniswapv3'; + +import type { ViewRange } from '@/types'; + +/** + * Hook for converting between tick and position + * When invertPrice = true, the entire visual is flipped: + * - Lower tick (higher inverted price) appears on the RIGHT + * - Upper tick (lower inverted price) appears on the LEFT + * - Axis shows inverted prices from low (left) to high (right) + */ +export const useTickPositionConverter = (viewRange: ViewRange | null, tickSpacing: number, invertPrice?: boolean) => { + const getPositionFromTick = useCallback( + (tick: number): number => { + if (!viewRange) return 50; + const { min, max } = viewRange; + const normalPosition = ((tick - min) / (max - min)) * 100; + // When invertPrice, flip the position so higher inverted price is on the right + return invertPrice ? 100 - normalPosition : normalPosition; + }, + [viewRange, invertPrice], + ); + + const getTickFromPosition = useCallback( + (position: number): number => { + if (!viewRange) return 0; + const { min, max } = viewRange; + // When invertPrice, flip the position first + const actualPosition = invertPrice ? 100 - position : position; + const tick = min + (actualPosition / 100) * (max - min); + return nearestUsableTick(Math.round(tick), tickSpacing); + }, + [viewRange, tickSpacing, invertPrice], + ); + + return { getPositionFromTick, getTickFromPosition }; +}; diff --git a/packages/price-slider/src/index.ts b/packages/price-slider/src/index.ts new file mode 100644 index 0000000000..0c764f4fc0 --- /dev/null +++ b/packages/price-slider/src/index.ts @@ -0,0 +1,4 @@ +import '@/styles.css'; + +export { default } from '@/components/UniswapPriceSlider'; +export { default as UniswapPriceSlider } from '@/components/UniswapPriceSlider'; diff --git a/packages/price-slider/src/styles.css b/packages/price-slider/src/styles.css new file mode 100644 index 0000000000..b5c61c9567 --- /dev/null +++ b/packages/price-slider/src/styles.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/packages/price-slider/src/types/index.ts b/packages/price-slider/src/types/index.ts new file mode 100644 index 0000000000..ee183d8fe1 --- /dev/null +++ b/packages/price-slider/src/types/index.ts @@ -0,0 +1,46 @@ +/** + * View range representing the visible tick range on the slider + */ +export interface ViewRange { + min: number; + max: number; +} + +/** + * Pool information required for price calculations + */ +export interface PoolInfo { + tickSpacing: number; + token0Decimals: number; + token1Decimals: number; + currentTick: number; +} + +/** + * Props for the UniswapPriceSlider component + */ +export interface UniswapPriceSliderProps { + pool: PoolInfo; + invertPrice?: boolean; + lowerTick?: number; + upperTick?: number; + setLowerTick: (tick: number) => void; + setUpperTick: (tick: number) => void; + /** Optional class name for custom styling */ + className?: string; +} + +/** + * Props for the PriceAxis component + */ +export interface PriceAxisProps { + viewRange: ViewRange; + token0Decimals: number; + token1Decimals: number; + invertPrice?: boolean; +} + +/** + * Handle type for dragging state + */ +export type HandleType = 'lower' | 'upper' | null; diff --git a/packages/price-slider/src/utils/index.ts b/packages/price-slider/src/utils/index.ts new file mode 100644 index 0000000000..b178e0d54e --- /dev/null +++ b/packages/price-slider/src/utils/index.ts @@ -0,0 +1,128 @@ +/** + * Generate SVG path for brush handle (oval shape with rounded ends) + */ +export const brushHandlePath = (height: number): string => { + return [ + `M 0.5 0`, + `Q 0 0 0 1.5`, // Rounded top-left corner + `v 3.5`, + `C -5 5 -5 17 0 17`, // Oval + `v ${height - 19}`, + `Q 0 ${height} 0.5 ${height}`, // Rounded bottom-left corner + `Q 1 ${height} 1 ${height - 1.5}`, // Rounded bottom-right corner + `V 17`, + `C 6 17 6 5 1 5`, + `V 1.5`, + `Q 1 0 0.5 0`, // Rounded top-right corner + ].join(' '); +}; + +/** + * Format number with comma separators + */ +const formatWithCommas = (num: number, decimals = 0): string => { + const fixed = num.toFixed(decimals); + const parts = fixed.split('.'); + parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ','); + return parts.join('.'); +}; + +/** + * Format price for axis display - user-friendly format + * Shows more detail for smaller ranges, uses abbreviations for large numbers + * Note: Prices in Uniswap context are always positive + */ +export const formatAxisPrice = (price: number): string => { + if (price === 0) return '0'; + if (!isFinite(price)) return '∞'; + + // For astronomically large numbers, show a capped display + if (price >= 1e18) return '>999Q'; + // Quadrillions (10^15) + if (price >= 1e15) { + const val = price / 1e15; + return (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'Q'; + } + // Trillions (10^12) + if (price >= 1e12) { + const val = price / 1e12; + return (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'T'; + } + // Billions (10^9) + if (price >= 1e9) { + const val = price / 1e9; + return (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'B'; + } + // Millions (10^6) + if (price >= 1e6) { + const val = price / 1e6; + return (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'M'; + } + // 100K - 999K: use K suffix + if (price >= 100000) { + return Math.round(price / 1000) + 'K'; + } + // 10K - 99.9K: show as "12.5K" with more precision + if (price >= 10000) { + return (price / 1000).toFixed(1) + 'K'; + } + // 1K - 9.9K: show full number with comma (like "2,500" or "3,750") + if (price >= 1000) { + // Round to nearest 10 for cleaner display + return formatWithCommas(Math.round(price / 10) * 10); + } + // 100 - 999: show full number + if (price >= 100) { + return Math.round(price).toString(); + } + // 10 - 99.99: show with 1 decimal + if (price >= 10) { + return price.toFixed(1); + } + // 1 - 9.99: show with 2 decimals + if (price >= 1) { + return price.toFixed(2); + } + // Small decimals + if (price >= 0.01) { + return price.toFixed(4); + } + if (price >= 0.0001) { + return price.toFixed(5); + } + // For extremely small numbers, show a floor display + if (price < 1e-8) { + return '<0.00001'; + } + return price.toPrecision(3); +}; + +/** + * Calculate edge intensity for zoom behavior + * Returns 0-1 based on how close position is to edge + */ +export const getEdgeIntensity = (position: number, edgeThreshold: number): number => { + if (position < edgeThreshold) { + return 1 - position / edgeThreshold; + } + if (position > 100 - edgeThreshold) { + return (position - (100 - edgeThreshold)) / edgeThreshold; + } + return 0; +}; + +/** + * Format display number with significant digits + */ +export const formatDisplayNumber = (value: number | string, options?: { significantDigits?: number }): string => { + const num = typeof value === 'string' ? parseFloat(value) : value; + if (isNaN(num) || !isFinite(num)) return '0'; + + const significantDigits = options?.significantDigits ?? 6; + + if (num === 0) return '0'; + if (Math.abs(num) < 1e-10) return '<0.0000001'; + if (Math.abs(num) >= 1e15) return num.toExponential(2); + + return num.toPrecision(significantDigits).replace(/\.?0+$/, ''); +}; diff --git a/packages/price-slider/tailwind.config.ts b/packages/price-slider/tailwind.config.ts new file mode 100644 index 0000000000..92425727e9 --- /dev/null +++ b/packages/price-slider/tailwind.config.ts @@ -0,0 +1,10 @@ +import type { Config } from 'tailwindcss'; + +import sharedConfig from '@kyber/tailwind-config'; + +const config: Pick = { + content: ['./src/**/*.tsx'], + presets: [sharedConfig], +}; + +export default config; diff --git a/packages/price-slider/tsconfig.json b/packages/price-slider/tsconfig.json new file mode 100644 index 0000000000..052970bf20 --- /dev/null +++ b/packages/price-slider/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "@kyber/typescript-config/react-library.json", + "compilerOptions": { + "target": "ES2020", + "outDir": "dist", + "jsx": "react-jsx", + "paths": { + "@/*": ["./src/*"], + "react": ["./node_modules/@types/react"] + } + }, + "include": ["src", ".eslintrc.cjs"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/price-slider/tsup.config.ts b/packages/price-slider/tsup.config.ts new file mode 100644 index 0000000000..a43cf2302b --- /dev/null +++ b/packages/price-slider/tsup.config.ts @@ -0,0 +1,29 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: { 'price-slider': 'src/index.ts' }, + format: ['esm', 'cjs'], + outDir: 'dist', + target: 'esnext', + clean: true, + dts: true, + minify: false, + sourcemap: true, + onSuccess: 'tsc --noEmit', + external: ['react', 'react-dom'], + noExternal: ['@kyber/utils', '@kyber/eslint-config', '@kyber/tailwind-config'], + esbuildOptions(options) { + options.globalName = 'PriceSlider'; + options.define = { + global: 'globalThis', + }; + options.supported = { + bigint: true, + }; + }, + banner: { + js: ` + // eslint-disable + `, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d1979a8fa0..3d541daee7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,7 +40,7 @@ importers: version: 0.1.2 '@esbuild-plugins/node-globals-polyfill': specifier: ^0.2.3 - version: 0.2.3(esbuild@0.27.0) + version: 0.2.3(esbuild@0.24.0) '@ethersproject/abi': specifier: ^5.8.0 version: 5.8.0 @@ -98,6 +98,9 @@ importers: '@kyberswap/oauth2': specifier: 1.0.2 version: 1.0.2 + '@kyberswap/price-slider': + specifier: workspace:* + version: link:../../packages/price-slider '@kyberswap/zap-migration-widgets': specifier: workspace:* version: link:../../packages/zap-migration-widgets @@ -725,7 +728,7 @@ importers: dependencies: '@esbuild-plugins/node-globals-polyfill': specifier: ^0.1.1 - version: 0.1.1(esbuild@0.27.0) + version: 0.1.1(esbuild@0.24.0) '@kyberswap/widgets': specifier: workspace:* version: link:../../packages/swap-widgets @@ -941,7 +944,7 @@ importers: version: 0.2.3(@babel/core@7.26.0) esbuild-sass-plugin: specifier: ^3.3.1 - version: 3.3.1(esbuild@0.27.0)(sass-embedded@1.93.3) + version: 3.3.1(esbuild@0.24.0)(sass-embedded@1.93.3) eslint: specifier: ^8.55.0 version: 8.57.0 @@ -989,10 +992,10 @@ importers: devDependencies: '@vercel/style-guide': specifier: ^5.2.0 - version: 5.2.0(eslint@8.57.1)(prettier@3.5.3)(typescript@5.9.3) + version: 5.2.0(eslint@8.57.0)(prettier@3.5.3)(typescript@5.1.6) eslint-config-turbo: specifier: ^1.12.4 - version: 1.13.4(eslint@8.57.1) + version: 1.13.4(eslint@8.57.0) packages/config-tailwind: devDependencies: @@ -1134,7 +1137,7 @@ importers: version: 10.4.20(postcss@8.4.49) esbuild-sass-plugin: specifier: ^3.3.1 - version: 3.3.1(esbuild@0.27.0)(sass-embedded@1.93.3) + version: 3.3.1(esbuild@0.24.0)(sass-embedded@1.93.3) eslint: specifier: ^8.57.0 version: 8.57.0 @@ -1243,7 +1246,7 @@ importers: version: 0.2.3(@babel/core@7.26.0) esbuild-sass-plugin: specifier: ^3.3.1 - version: 3.3.1(esbuild@0.27.0)(sass-embedded@1.93.3) + version: 3.3.1(esbuild@0.24.0)(sass-embedded@1.93.3) eslint: specifier: ^8.55.0 version: 8.57.0 @@ -1343,7 +1346,7 @@ importers: version: 10.4.20(postcss@8.4.49) esbuild-sass-plugin: specifier: ^3.3.1 - version: 3.3.1(esbuild@0.27.0)(sass-embedded@1.93.3) + version: 3.3.1(esbuild@0.24.0)(sass-embedded@1.93.3) eslint: specifier: ^8.55.0 version: 8.57.0 @@ -1372,6 +1375,82 @@ importers: specifier: 5.3.2 version: 5.3.2 + packages/price-slider: + dependencies: + '@kyber/ui': + specifier: workspace:^ + version: link:../ui + react: + specifier: '>=16.8.0' + version: 18.3.1 + devDependencies: + '@kyber/eslint-config': + specifier: workspace:* + version: link:../config-eslint + '@kyber/tailwind-config': + specifier: workspace:^ + version: link:../config-tailwind + '@kyber/typescript-config': + specifier: workspace:* + version: link:../config-typescript + '@kyber/utils': + specifier: workspace:^ + version: link:../utils + '@trivago/prettier-plugin-sort-imports': + specifier: ^3.3.1 + version: 3.4.0(prettier@3.5.3) + '@types/eslint': + specifier: ^8.56.5 + version: 8.56.12 + '@types/node': + specifier: ^20.11.24 + version: 20.19.25 + '@types/react': + specifier: ^18.0.17 + version: 18.3.12 + '@types/react-dom': + specifier: ^18.0.6 + version: 18.3.1 + '@typescript-eslint/eslint-plugin': + specifier: ^6.14.0 + version: 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.3.2) + '@typescript-eslint/parser': + specifier: ^6.14.0 + version: 6.21.0(eslint@8.57.0)(typescript@5.3.2) + autoprefixer: + specifier: ^10.4.20 + version: 10.4.20(postcss@8.4.49) + eslint: + specifier: ^8.57.0 + version: 8.57.0 + eslint-config-prettier: + specifier: ^9.1.0 + version: 9.1.0(eslint@8.57.0) + eslint-plugin-prettier: + specifier: ^5.4.0 + version: 5.4.0(@types/eslint@8.56.12)(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.5.3) + eslint-plugin-react: + specifier: ^7.33.2 + version: 7.37.2(eslint@8.57.0) + eslint-plugin-react-hooks: + specifier: ^4.6.0 + version: 4.6.2(eslint@8.57.0) + postcss: + specifier: ^8.4.47 + version: 8.4.49 + prettier: + specifier: ^3.5.3 + version: 3.5.3 + tailwindcss: + specifier: ^3.4.13 + version: 3.4.14 + tsup: + specifier: ^8.3.0 + version: 8.3.5(postcss@8.4.49)(typescript@5.3.2) + typescript: + specifier: 5.3.2 + version: 5.3.2 + packages/schema: dependencies: react: @@ -1871,7 +1950,7 @@ importers: version: 0.2.3(@babel/core@7.26.0) esbuild-sass-plugin: specifier: ^3.3.1 - version: 3.3.1(esbuild@0.27.0)(sass-embedded@1.93.3) + version: 3.3.1(esbuild@0.24.0)(sass-embedded@1.93.3) eslint: specifier: ^8.55.0 version: 8.57.0 @@ -1995,7 +2074,7 @@ importers: version: 0.2.3(@babel/core@7.26.0) esbuild-sass-plugin: specifier: ^3.3.1 - version: 3.3.1(esbuild@0.27.0)(sass-embedded@1.93.3) + version: 3.3.1(esbuild@0.24.0)(sass-embedded@1.93.3) eslint: specifier: ^8.55.0 version: 8.57.0 @@ -2216,20 +2295,6 @@ packages: semver: 6.3.1 dev: true - /@babel/eslint-parser@7.25.9(@babel/core@7.26.0)(eslint@8.57.1): - resolution: {integrity: sha512-5UXfgpK0j0Xr/xIdgdLEhOFxaDZ0bRPWJJchRpqOSur/3rZoPbqqki5mm0p4NE2cs28krBEiSM2MB7//afRSQQ==} - engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} - peerDependencies: - '@babel/core': ^7.11.0 - eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 - dependencies: - '@babel/core': 7.26.0 - '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 - eslint: 8.57.1 - eslint-visitor-keys: 2.1.0 - semver: 6.3.1 - dev: true - /@babel/generator@7.17.7: resolution: {integrity: sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==} engines: {node: '>=6.9.0'} @@ -2276,8 +2341,8 @@ packages: resolution: {integrity: sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.25.9(supports-color@5.5.0) - '@babel/types': 7.26.0 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color dev: true @@ -2320,6 +2385,24 @@ packages: - supports-color dev: true + /@babel/helper-create-class-features-plugin@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-member-expression-to-functions': 7.25.9 + '@babel/helper-optimise-call-expression': 7.25.9 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.28.5) + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==} engines: {node: '>=6.9.0'} @@ -2350,6 +2433,18 @@ packages: semver: 6.3.1 dev: true + /@babel/helper-create-regexp-features-plugin@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.25.9 + regexpu-core: 6.1.1 + semver: 6.3.1 + dev: true + /@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==} engines: {node: '>=6.9.0'} @@ -2370,9 +2465,24 @@ packages: '@babel/core': 7.26.0 '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) lodash.debounce: 4.0.8 - resolve: 1.22.8 + resolve: 1.22.11 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/helper-define-polyfill-provider@0.6.3(@babel/core@7.28.5): + resolution: {integrity: sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + debug: 4.4.3(supports-color@8.1.1) + lodash.debounce: 4.0.8 + resolve: 1.22.11 transitivePeerDependencies: - supports-color dev: true @@ -2483,6 +2593,20 @@ packages: transitivePeerDependencies: - supports-color + /@babel/helper-module-transforms@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-module-transforms@7.28.3(@babel/core@7.26.0): resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} engines: {node: '>=6.9.0'} @@ -2541,7 +2665,21 @@ packages: '@babel/core': 7.26.0 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-wrap-function': 7.25.9 - '@babel/traverse': 7.25.9(supports-color@5.5.0) + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-wrap-function': 7.25.9 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color dev: true @@ -2574,6 +2712,20 @@ packages: - supports-color dev: true + /@babel/helper-replace-supers@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-member-expression-to-functions': 7.25.9 + '@babel/helper-optimise-call-expression': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-replace-supers@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==} engines: {node: '>=6.9.0'} @@ -2654,8 +2806,8 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.25.9 - '@babel/traverse': 7.25.9(supports-color@5.5.0) - '@babel/types': 7.26.0 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color dev: true @@ -2720,6 +2872,19 @@ packages: - supports-color dev: true + /@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==} engines: {node: '>=6.9.0'} @@ -2743,6 +2908,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==} engines: {node: '>=6.9.0'} @@ -2763,6 +2938,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==} engines: {node: '>=6.9.0'} @@ -2787,6 +2972,20 @@ packages: - supports-color dev: true + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==} engines: {node: '>=6.9.0'} @@ -2814,6 +3013,19 @@ packages: - supports-color dev: true + /@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.3(@babel/core@7.26.0): resolution: {integrity: sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==} engines: {node: '>=6.9.0'} @@ -2917,6 +3129,15 @@ packages: '@babel/core': 7.26.0 dev: true + /@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.5): + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + dev: true + /@babel/plugin-proposal-private-property-in-object@7.21.11(@babel/core@7.26.0): resolution: {integrity: sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==} engines: {node: '>=6.9.0'} @@ -3027,6 +3248,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-syntax-flow@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-B+O2DnPc0iG+YXFqOxv2WNuNU97ToWjOomUQ78DouOENWUaM5sVrmet9mcomUGQFwpJd//gvUagXBSdzO1fRKg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-syntax-flow@7.27.1(@babel/core@7.28.5): resolution: {integrity: sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA==} engines: {node: '>=6.9.0'} @@ -3047,6 +3278,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-syntax-import-assertions@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==} engines: {node: '>=6.9.0'} @@ -3067,6 +3308,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==} engines: {node: '>=6.9.0'} @@ -3139,7 +3390,6 @@ packages: dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.25.9 - dev: false /@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5): resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} @@ -3305,6 +3555,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.0): resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} engines: {node: '>=6.9.0'} @@ -3316,6 +3576,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.5): + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.26.0): resolution: {integrity: sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==} engines: {node: '>=6.9.0'} @@ -3326,6 +3597,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==} engines: {node: '>=6.9.0'} @@ -3350,6 +3631,20 @@ packages: - supports-color dev: true + /@babel/plugin-transform-async-generator-functions@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.28.5) + '@babel/traverse': 7.25.9(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-async-generator-functions@7.28.0(@babel/core@7.26.0): resolution: {integrity: sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==} engines: {node: '>=6.9.0'} @@ -3378,6 +3673,20 @@ packages: - supports-color dev: true + /@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-async-to-generator@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==} engines: {node: '>=6.9.0'} @@ -3402,6 +3711,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-block-scoped-functions@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==} engines: {node: '>=6.9.0'} @@ -3422,6 +3741,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-block-scoping@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-block-scoping@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==} engines: {node: '>=6.9.0'} @@ -3445,6 +3774,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==} engines: {node: '>=6.9.0'} @@ -3471,6 +3813,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-class-static-block@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-class-static-block@7.28.3(@babel/core@7.26.0): resolution: {integrity: sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==} engines: {node: '>=6.9.0'} @@ -3501,6 +3856,23 @@ packages: - supports-color dev: true + /@babel/plugin-transform-classes@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.28.5) + '@babel/traverse': 7.25.9(supports-color@5.5.0) + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-classes@7.28.4(@babel/core@7.26.0): resolution: {integrity: sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==} engines: {node: '>=6.9.0'} @@ -3529,6 +3901,17 @@ packages: '@babel/template': 7.25.9 dev: true + /@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/template': 7.25.9 + dev: true + /@babel/plugin-transform-computed-properties@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==} engines: {node: '>=6.9.0'} @@ -3550,6 +3933,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==} engines: {node: '>=6.9.0'} @@ -3574,6 +3967,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-dotall-regex@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==} engines: {node: '>=6.9.0'} @@ -3595,6 +3999,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==} engines: {node: '>=6.9.0'} @@ -3616,6 +4030,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==} engines: {node: '>=6.9.0'} @@ -3637,6 +4062,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==} engines: {node: '>=6.9.0'} @@ -3673,6 +4108,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-exponentiation-operator@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-exponentiation-operator@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==} engines: {node: '>=6.9.0'} @@ -3693,6 +4141,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==} engines: {node: '>=6.9.0'} @@ -3714,6 +4172,17 @@ packages: '@babel/plugin-syntax-flow': 7.26.0(@babel/core@7.26.0) dev: true + /@babel/plugin-transform-flow-strip-types@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-/VVukELzPDdci7UUsWQaSkhgnjIWXnIyRpM02ldxaVoFK96c41So8JcKT3m0gYjyv7j5FNPGS5vfELrWalkbDA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-flow': 7.26.0(@babel/core@7.28.5) + dev: true + /@babel/plugin-transform-for-of@7.25.9(@babel/core@7.26.0): resolution: {integrity: sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==} engines: {node: '>=6.9.0'} @@ -3727,6 +4196,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-for-of@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-for-of@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==} engines: {node: '>=6.9.0'} @@ -3754,6 +4236,20 @@ packages: - supports-color dev: true + /@babel/plugin-transform-function-name@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-function-name@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==} engines: {node: '>=6.9.0'} @@ -3778,6 +4274,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-json-strings@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-json-strings@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==} engines: {node: '>=6.9.0'} @@ -3798,6 +4304,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-literals@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-literals@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==} engines: {node: '>=6.9.0'} @@ -3818,6 +4334,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-logical-assignment-operators@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-logical-assignment-operators@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==} engines: {node: '>=6.9.0'} @@ -3838,6 +4364,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-member-expression-literals@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==} engines: {node: '>=6.9.0'} @@ -3861,6 +4397,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-modules-amd@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==} engines: {node: '>=6.9.0'} @@ -3888,6 +4437,20 @@ packages: - supports-color dev: true + /@babel/plugin-transform-modules-commonjs@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-simple-access': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==} engines: {node: '>=6.9.0'} @@ -3916,6 +4479,21 @@ packages: - supports-color dev: true + /@babel/plugin-transform-modules-systemjs@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-modules-systemjs@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==} engines: {node: '>=6.9.0'} @@ -3944,6 +4522,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-modules-umd@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==} engines: {node: '>=6.9.0'} @@ -3968,6 +4559,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==} engines: {node: '>=6.9.0'} @@ -3989,6 +4591,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-new-target@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-new-target@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==} engines: {node: '>=6.9.0'} @@ -4009,6 +4621,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-nullish-coalescing-operator@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-nullish-coalescing-operator@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==} engines: {node: '>=6.9.0'} @@ -4029,6 +4651,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-numeric-separator@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==} engines: {node: '>=6.9.0'} @@ -4051,6 +4683,18 @@ packages: '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.0) dev: true + /@babel/plugin-transform-object-rest-spread@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.28.5) + dev: true + /@babel/plugin-transform-object-rest-spread@7.28.4(@babel/core@7.26.0): resolution: {integrity: sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==} engines: {node: '>=6.9.0'} @@ -4080,6 +4724,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-object-super@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-object-super@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==} engines: {node: '>=6.9.0'} @@ -4103,6 +4760,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-optional-catch-binding@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-optional-catch-binding@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==} engines: {node: '>=6.9.0'} @@ -4126,6 +4793,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-optional-chaining@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-optional-chaining@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==} engines: {node: '>=6.9.0'} @@ -4149,6 +4829,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-parameters@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-parameters@7.27.7(@babel/core@7.26.0): resolution: {integrity: sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==} engines: {node: '>=6.9.0'} @@ -4172,6 +4862,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==} engines: {node: '>=6.9.0'} @@ -4199,6 +4902,20 @@ packages: - supports-color dev: true + /@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-private-property-in-object@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==} engines: {node: '>=6.9.0'} @@ -4223,6 +4940,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==} engines: {node: '>=6.9.0'} @@ -4339,6 +5066,17 @@ packages: regenerator-transform: 0.15.2 dev: true + /@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + regenerator-transform: 0.15.2 + dev: true + /@babel/plugin-transform-regenerator@7.28.4(@babel/core@7.26.0): resolution: {integrity: sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==} engines: {node: '>=6.9.0'} @@ -4360,6 +5098,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-regexp-modifiers@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==} engines: {node: '>=6.9.0'} @@ -4381,6 +5130,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==} engines: {node: '>=6.9.0'} @@ -4418,6 +5177,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==} engines: {node: '>=6.9.0'} @@ -4441,6 +5210,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-spread@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-spread@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==} engines: {node: '>=6.9.0'} @@ -4464,6 +5246,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==} engines: {node: '>=6.9.0'} @@ -4484,6 +5276,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-template-literals@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==} engines: {node: '>=6.9.0'} @@ -4504,6 +5306,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-typeof-symbol@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==} engines: {node: '>=6.9.0'} @@ -4530,6 +5342,22 @@ packages: - supports-color dev: true + /@babel/plugin-transform-typescript@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.26.0): resolution: {integrity: sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==} engines: {node: '>=6.9.0'} @@ -4540,6 +5368,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==} engines: {node: '>=6.9.0'} @@ -4561,6 +5399,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-unicode-property-regex@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==} engines: {node: '>=6.9.0'} @@ -4583,6 +5432,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==} engines: {node: '>=6.9.0'} @@ -4605,6 +5465,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-unicode-sets-regex@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==} engines: {node: '>=6.9.0'} @@ -4696,6 +5567,86 @@ packages: - supports-color dev: true + /@babel/preset-env@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.26.2 + '@babel/core': 7.28.5 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.5) + '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.28.5) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.28.5) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.28.5) + '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-async-generator-functions': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-block-scoped-functions': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-block-scoping': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-class-static-block': 7.26.0(@babel/core@7.28.5) + '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-dotall-regex': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-exponentiation-operator': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-for-of': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-json-strings': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-member-expression-literals': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-modules-amd': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-modules-systemjs': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-modules-umd': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-new-target': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-nullish-coalescing-operator': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-object-rest-spread': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-object-super': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-regenerator': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-regexp-modifiers': 7.26.0(@babel/core@7.28.5) + '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-template-literals': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-typeof-symbol': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.28.5) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.28.5) + babel-plugin-polyfill-corejs2: 0.4.12(@babel/core@7.28.5) + babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.28.5) + babel-plugin-polyfill-regenerator: 0.6.3(@babel/core@7.28.5) + core-js-compat: 3.39.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/preset-env@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==} engines: {node: '>=6.9.0'} @@ -4777,16 +5728,16 @@ packages: - supports-color dev: true - /@babel/preset-flow@7.25.9(@babel/core@7.26.0): + /@babel/preset-flow@7.25.9(@babel/core@7.28.5): resolution: {integrity: sha512-EASHsAhE+SSlEzJ4bzfusnXSHiU+JfAYzj+jbw2vgQKgq5HrUr8qs+vgtiEL5dOH6sEweI+PNt2D7AqrDSHyqQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-validator-option': 7.25.9 - '@babel/plugin-transform-flow-strip-types': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-flow-strip-types': 7.25.9(@babel/core@7.28.5) dev: true /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.26.0): @@ -4796,7 +5747,18 @@ packages: dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 - '@babel/types': 7.26.0 + '@babel/types': 7.28.5 + esutils: 2.0.3 + dev: true + + /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.28.5): + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/types': 7.28.5 esutils: 2.0.3 dev: true @@ -4833,13 +5795,29 @@ packages: - supports-color dev: true - /@babel/register@7.25.9(@babel/core@7.26.0): + /@babel/preset-typescript@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-typescript': 7.25.9(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/register@7.25.9(@babel/core@7.28.5): resolution: {integrity: sha512-8D43jXtGsYmEeDvm4MWHYUpWf8iiXgWYx3fW7E7Wb7Oe6FWqJPl5K6TuFW0dOwNZzEE5rjlaSJYH9JjrUKJszA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.5 clone-deep: 4.0.1 find-cache-dir: 2.1.0 make-dir: 2.1.0 @@ -5077,7 +6055,7 @@ packages: istanbul-lib-coverage: 3.2.2 js-yaml: 4.1.0 nyc: 15.1.0 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) transitivePeerDependencies: - supports-color dev: true @@ -5181,7 +6159,7 @@ packages: bluebird: 3.7.1 debug: 4.4.0(supports-color@5.5.0) lodash: 4.17.21 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) transitivePeerDependencies: - supports-color dev: true @@ -5367,20 +6345,20 @@ packages: resolution: {integrity: sha512-Bz1zLGEqBQ0BVkqt1OgMxdBOE3BdUWUd7Ly9Ecr/aUwkA8AV1w1XzBMe4xblmJHnB1XXNlPH4SraXCvO+q0Mig==} dev: false - /@esbuild-plugins/node-globals-polyfill@0.1.1(esbuild@0.27.0): + /@esbuild-plugins/node-globals-polyfill@0.1.1(esbuild@0.24.0): resolution: {integrity: sha512-MR0oAA+mlnJWrt1RQVQ+4VYuRJW/P2YmRTv1AsplObyvuBMnPHiizUF95HHYiSsMGLhyGtWufaq2XQg6+iurBg==} peerDependencies: esbuild: '*' dependencies: - esbuild: 0.27.0 + esbuild: 0.24.0 dev: false - /@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.27.0): + /@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.24.0): resolution: {integrity: sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==} peerDependencies: esbuild: '*' dependencies: - esbuild: 0.27.0 + esbuild: 0.24.0 dev: false /@esbuild/aix-ppc64@0.19.12: @@ -5409,14 +6387,6 @@ packages: requiresBuild: true optional: true - /@esbuild/aix-ppc64@0.27.0: - resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - requiresBuild: true - optional: true - /@esbuild/android-arm64@0.17.19: resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} engines: {node: '>=12'} @@ -5461,14 +6431,6 @@ packages: requiresBuild: true optional: true - /@esbuild/android-arm64@0.27.0: - resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - requiresBuild: true - optional: true - /@esbuild/android-arm@0.15.18: resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} engines: {node: '>=12'} @@ -5522,14 +6484,6 @@ packages: requiresBuild: true optional: true - /@esbuild/android-arm@0.27.0: - resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - requiresBuild: true - optional: true - /@esbuild/android-x64@0.17.19: resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} engines: {node: '>=12'} @@ -5574,14 +6528,6 @@ packages: requiresBuild: true optional: true - /@esbuild/android-x64@0.27.0: - resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - requiresBuild: true - optional: true - /@esbuild/darwin-arm64@0.17.19: resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} engines: {node: '>=12'} @@ -5626,14 +6572,6 @@ packages: requiresBuild: true optional: true - /@esbuild/darwin-arm64@0.27.0: - resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - optional: true - /@esbuild/darwin-x64@0.17.19: resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} engines: {node: '>=12'} @@ -5678,14 +6616,6 @@ packages: requiresBuild: true optional: true - /@esbuild/darwin-x64@0.27.0: - resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - requiresBuild: true - optional: true - /@esbuild/freebsd-arm64@0.17.19: resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} engines: {node: '>=12'} @@ -5730,14 +6660,6 @@ packages: requiresBuild: true optional: true - /@esbuild/freebsd-arm64@0.27.0: - resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - optional: true - /@esbuild/freebsd-x64@0.17.19: resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} engines: {node: '>=12'} @@ -5782,14 +6704,6 @@ packages: requiresBuild: true optional: true - /@esbuild/freebsd-x64@0.27.0: - resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - optional: true - /@esbuild/linux-arm64@0.17.19: resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} engines: {node: '>=12'} @@ -5834,14 +6748,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-arm64@0.27.0: - resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-arm@0.17.19: resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} engines: {node: '>=12'} @@ -5886,14 +6792,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-arm@0.27.0: - resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-ia32@0.17.19: resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} engines: {node: '>=12'} @@ -5938,14 +6836,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-ia32@0.27.0: - resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-loong64@0.15.18: resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==} engines: {node: '>=12'} @@ -5999,14 +6889,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-loong64@0.27.0: - resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-mips64el@0.17.19: resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} engines: {node: '>=12'} @@ -6051,14 +6933,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-mips64el@0.27.0: - resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-ppc64@0.17.19: resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} engines: {node: '>=12'} @@ -6103,14 +6977,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-ppc64@0.27.0: - resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-riscv64@0.17.19: resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} engines: {node: '>=12'} @@ -6155,14 +7021,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-riscv64@0.27.0: - resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-s390x@0.17.19: resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} engines: {node: '>=12'} @@ -6207,14 +7065,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-s390x@0.27.0: - resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-x64@0.17.19: resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} engines: {node: '>=12'} @@ -6259,22 +7109,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-x64@0.27.0: - resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - requiresBuild: true - optional: true - - /@esbuild/netbsd-arm64@0.27.0: - resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - requiresBuild: true - optional: true - /@esbuild/netbsd-x64@0.17.19: resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} engines: {node: '>=12'} @@ -6319,14 +7153,6 @@ packages: requiresBuild: true optional: true - /@esbuild/netbsd-x64@0.27.0: - resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - optional: true - /@esbuild/openbsd-arm64@0.24.0: resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==} engines: {node: '>=18'} @@ -6335,14 +7161,6 @@ packages: requiresBuild: true optional: true - /@esbuild/openbsd-arm64@0.27.0: - resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - requiresBuild: true - optional: true - /@esbuild/openbsd-x64@0.17.19: resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} engines: {node: '>=12'} @@ -6387,22 +7205,6 @@ packages: requiresBuild: true optional: true - /@esbuild/openbsd-x64@0.27.0: - resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - optional: true - - /@esbuild/openharmony-arm64@0.27.0: - resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - requiresBuild: true - optional: true - /@esbuild/sunos-x64@0.17.19: resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} engines: {node: '>=12'} @@ -6447,14 +7249,6 @@ packages: requiresBuild: true optional: true - /@esbuild/sunos-x64@0.27.0: - resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - requiresBuild: true - optional: true - /@esbuild/win32-arm64@0.17.19: resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} engines: {node: '>=12'} @@ -6499,14 +7293,6 @@ packages: requiresBuild: true optional: true - /@esbuild/win32-arm64@0.27.0: - resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - requiresBuild: true - optional: true - /@esbuild/win32-ia32@0.17.19: resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} engines: {node: '>=12'} @@ -6551,14 +7337,6 @@ packages: requiresBuild: true optional: true - /@esbuild/win32-ia32@0.27.0: - resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - requiresBuild: true - optional: true - /@esbuild/win32-x64@0.17.19: resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} engines: {node: '>=12'} @@ -6603,14 +7381,6 @@ packages: requiresBuild: true optional: true - /@esbuild/win32-x64@0.27.0: - resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - requiresBuild: true - optional: true - /@eslint-community/eslint-utils@4.4.1(eslint@8.57.0): resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -6620,16 +7390,6 @@ packages: eslint: 8.57.0 eslint-visitor-keys: 3.4.3 - /@eslint-community/eslint-utils@4.4.1(eslint@8.57.1): - resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - dependencies: - eslint: 8.57.1 - eslint-visitor-keys: 3.4.3 - dev: true - /@eslint-community/eslint-utils@4.4.1(eslint@9.14.0): resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -6640,16 +7400,6 @@ packages: eslint-visitor-keys: 3.4.3 dev: true - /@eslint-community/eslint-utils@4.9.0(eslint@8.57.1): - resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - dependencies: - eslint: 8.57.1 - eslint-visitor-keys: 3.4.3 - dev: true - /@eslint-community/regexpp@4.12.1: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -6712,11 +7462,6 @@ packages: resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - /@eslint/js@8.57.1: - resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - /@eslint/js@9.14.0: resolution: {integrity: sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -8142,18 +8887,6 @@ packages: transitivePeerDependencies: - supports-color - /@humanwhocodes/config-array@0.13.0: - resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead - dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.3(supports-color@8.1.1) - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - dev: true - /@humanwhocodes/module-importer@1.0.1: resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} @@ -8216,7 +8949,7 @@ packages: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.19.25 + '@types/node': 22.10.7 jest-mock: 29.7.0 dev: false @@ -8226,7 +8959,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.19.25 + '@types/node': 22.10.7 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -8609,7 +9342,7 @@ packages: '@ledgerhq/errors': 6.19.1 '@ledgerhq/logs': 6.12.0 rxjs: 7.8.1 - semver: 7.6.3 + semver: 7.7.3 dev: false /@ledgerhq/devices@8.4.5: @@ -8627,7 +9360,7 @@ packages: '@ledgerhq/errors': 6.21.0 '@ledgerhq/logs': 6.13.0 rxjs: 7.8.1 - semver: 7.6.3 + semver: 7.7.3 dev: false /@ledgerhq/errors@6.19.1: @@ -9254,7 +9987,7 @@ packages: bufferutil: 4.0.9 cross-fetch: 4.0.0 date-fns: 2.30.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) eciesjs: 0.3.21 eventemitter2: 6.4.9 readable-stream: 3.6.2 @@ -9277,7 +10010,7 @@ packages: bufferutil: 4.0.9 cross-fetch: 4.0.0 date-fns: 2.30.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) eciesjs: 0.4.14 eventemitter2: 6.4.9 readable-stream: 3.6.2 @@ -9336,7 +10069,7 @@ packages: '@types/dom-screen-wake-lock': 1.0.3 bowser: 2.11.0 cross-fetch: 4.0.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) eciesjs: 0.3.21 eth-rpc-errors: 4.0.3 eventemitter2: 6.4.9 @@ -9374,7 +10107,7 @@ packages: '@paulmillr/qr': 0.2.1 bowser: 2.11.0 cross-fetch: 4.0.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) eciesjs: 0.4.14 eth-rpc-errors: 4.0.3 eventemitter2: 6.4.9 @@ -9419,9 +10152,9 @@ packages: '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 '@types/debug': 4.1.12 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) pony-cause: 2.1.11 - semver: 7.6.3 + semver: 7.7.3 uuid: 9.0.1 transitivePeerDependencies: - supports-color @@ -9436,9 +10169,9 @@ packages: '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 '@types/debug': 4.1.12 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) pony-cause: 2.1.11 - semver: 7.6.3 + semver: 7.7.3 uuid: 9.0.1 transitivePeerDependencies: - supports-color @@ -13075,7 +13808,7 @@ packages: rollup: 3.29.5 dev: false - /@rollup/pluginutils@5.1.3(rollup@3.29.5): + /@rollup/pluginutils@5.1.3: resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==} engines: {node: '>=14.0.0'} peerDependencies: @@ -13087,6 +13820,20 @@ packages: '@types/estree': 1.0.6 estree-walker: 2.0.2 picomatch: 4.0.2 + dev: true + + /@rollup/pluginutils@5.1.3(rollup@3.29.5): + resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.2 rollup: 3.29.5 /@rollup/rollup-android-arm-eabi@4.25.0: @@ -13887,7 +14634,7 @@ packages: peerDependencies: typescript: '>=5' dependencies: - chalk: 5.4.1 + chalk: 5.6.2 commander: 12.1.0 typescript: 5.3.3 dev: false @@ -14244,7 +14991,7 @@ packages: '@solana/rpc-spec': 2.1.1(typescript@5.3.3) '@solana/rpc-spec-types': 2.1.1(typescript@5.3.3) typescript: 5.3.3 - undici-types: 7.10.0 + undici-types: 7.16.0 dev: false /@solana/rpc-types@2.1.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.3.3): @@ -15671,15 +16418,15 @@ packages: /@storybook/codemod@7.6.20: resolution: {integrity: sha512-8vmSsksO4XukNw0TmqylPmk7PxnfNfE21YsxFa7mnEBmEKQcZCQsNil4ZgWfG0IzdhTfhglAN4r++Ew0WE+PYA==} dependencies: - '@babel/core': 7.26.0 - '@babel/preset-env': 7.26.0(@babel/core@7.26.0) - '@babel/types': 7.26.0 + '@babel/core': 7.28.5 + '@babel/preset-env': 7.26.0(@babel/core@7.28.5) + '@babel/types': 7.28.5 '@storybook/csf': 0.1.13 '@storybook/csf-tools': 7.6.20 '@storybook/node-logger': 7.6.20 '@storybook/types': 7.6.20 '@types/cross-spawn': 6.0.6 - cross-spawn: 7.0.5 + cross-spawn: 7.0.6 globby: 11.1.0 jscodeshift: 0.15.2(@babel/preset-env@7.26.0) lodash: 4.17.21 @@ -15791,7 +16538,7 @@ packages: pretty-hrtime: 1.0.3 prompts: 2.4.2 read-pkg-up: 7.0.1 - semver: 7.6.3 + semver: 7.7.3 telejson: 7.2.0 tiny-invariant: 1.3.3 ts-dedent: 2.2.0 @@ -15958,7 +16705,7 @@ packages: vite: ^3.0.0 || ^4.0.0 || ^5.0.0 dependencies: '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@5.3.3)(vite@4.5.9) - '@rollup/pluginutils': 5.1.3(rollup@3.29.5) + '@rollup/pluginutils': 5.1.3 '@storybook/builder-vite': 7.6.20(typescript@5.3.3)(vite@4.5.9) '@storybook/react': 7.6.20(react-dom@18.3.1)(react@18.3.1)(typescript@5.3.3) '@vitejs/plugin-react': 3.1.0(vite@4.5.9) @@ -16360,7 +17107,7 @@ packages: resolution: {integrity: sha512-1hnUxxjd83EAxbL4a0JDJoD3Dao3hmjvyvyEV8PzWmLK3B9m9NPlW7GKjFyoWE8nM7HnXzPcmmSyOW8yOddSXw==} engines: {node: '>=10'} dependencies: - '@babel/types': 7.26.0 + '@babel/types': 7.28.5 entities: 4.5.0 dev: true @@ -17635,7 +18382,7 @@ packages: /@types/eslint-scope@3.7.7: resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} dependencies: - '@types/eslint': 9.6.1 + '@types/eslint': 8.56.12 '@types/estree': 1.0.8 dev: true @@ -17646,13 +18393,6 @@ packages: '@types/json-schema': 7.0.15 dev: true - /@types/eslint@9.6.1: - resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} - dependencies: - '@types/estree': 1.0.8 - '@types/json-schema': 7.0.15 - dev: true - /@types/estree@0.0.51: resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==} dev: true @@ -17662,7 +18402,6 @@ packages: /@types/estree@1.0.8: resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - dev: true /@types/express-serve-static-core@4.19.6: resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} @@ -17867,6 +18606,7 @@ packages: resolution: {integrity: sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==} dependencies: undici-types: 6.21.0 + dev: true /@types/node@22.10.2: resolution: {integrity: sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==} @@ -17884,12 +18624,6 @@ packages: dependencies: undici-types: 6.19.8 - /@types/node@24.10.1: - resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} - dependencies: - undici-types: 7.16.0 - dev: false - /@types/normalize-package-data@2.4.4: resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} dev: true @@ -18166,7 +18900,7 @@ packages: /@types/webpack-sources@3.2.3: resolution: {integrity: sha512-4nZOdMwSPHZ4pTEZzSp0AsTM4K7Qmu40UKW4tJDiOVs20UzYF9l+qUe4s0ftfN0pin06n+5cWWDJXH+sbhAiDw==} dependencies: - '@types/node': 20.17.6 + '@types/node': 22.10.7 '@types/source-list-map': 0.1.6 source-map: 0.7.6 dev: true @@ -18174,7 +18908,7 @@ packages: /@types/webpack@4.41.40: resolution: {integrity: sha512-u6kMFSBM9HcoTpUXnL6mt2HSzftqb3JgYV6oxIgL2dl6sX6aCa5k6SOkzv5DuZjBTPUE/dJltKtwwuqrkZHpfw==} dependencies: - '@types/node': 20.17.6 + '@types/node': 22.10.7 '@types/tapable': 1.0.12 '@types/uglify-js': 3.17.5 '@types/webpack-sources': 3.2.3 @@ -18217,7 +18951,7 @@ packages: resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} requiresBuild: true dependencies: - '@types/node': 20.17.6 + '@types/node': 22.10.7 dev: true optional: true @@ -18232,17 +18966,17 @@ packages: typescript: optional: true dependencies: - '@eslint-community/regexpp': 4.12.1 + '@eslint-community/regexpp': 4.12.2 '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.3.2) '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.0)(typescript@5.3.2) '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.3.2) - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.2 natural-compare-lite: 1.4.0 - semver: 7.6.3 + semver: 7.7.3 tsutils: 3.21.0(typescript@5.3.2) typescript: 5.3.2 transitivePeerDependencies: @@ -18335,35 +19069,6 @@ packages: - supports-color dev: true - /@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.1)(typescript@5.9.3): - resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.0(supports-color@5.5.0) - eslint: 8.57.1 - graphemer: 1.4.0 - ignore: 5.3.2 - natural-compare: 1.4.0 - semver: 7.6.3 - ts-api-utils: 1.4.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/eslint-plugin@8.14.0(@typescript-eslint/parser@8.14.0)(eslint@9.14.0)(typescript@5.3.2): resolution: {integrity: sha512-tqp8H7UWFaZj0yNO6bycd5YjMwxa6wIHOLZvWPkidwbgLCsBMetQoGj7DPuAlWa2yGO3H48xmPwjhsSPPCGU5w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -18485,27 +19190,6 @@ packages: - supports-color dev: true - /@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3): - resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.0(supports-color@5.5.0) - eslint: 8.57.1 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/parser@8.14.0(eslint@9.14.0)(typescript@5.3.2): resolution: {integrity: sha512-2p82Yn9juUJq0XynBXtFCyrBDb6/dJombnz6vbo6mgQEtWHfvHbQuEa9kAOVIt1c9YFwi7H6WxtPj1kg+80+RA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -18630,26 +19314,6 @@ packages: - supports-color dev: true - /@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.9.3): - resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) - '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) - debug: 4.4.0(supports-color@5.5.0) - eslint: 8.57.1 - ts-api-utils: 1.4.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/type-utils@8.14.0(eslint@9.14.0)(typescript@5.3.2): resolution: {integrity: sha512-Xcz9qOtZuGusVOH5Uk07NGs39wrKkf3AxlkK79RBK6aJC1l03CobXjJbwBPSidetAOV+5rEVuiT1VSBUOAsanQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -18661,7 +19325,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 8.14.0(typescript@5.3.2) '@typescript-eslint/utils': 8.14.0(eslint@9.14.0)(typescript@5.3.2) - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) ts-api-utils: 1.4.0(typescript@5.3.2) typescript: 5.3.2 transitivePeerDependencies: @@ -18683,7 +19347,7 @@ packages: engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: true - /@typescript-eslint/typescript-estree@5.62.0(typescript@5.3.2): + /@typescript-eslint/typescript-estree@5.62.0(typescript@5.1.6): resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -18694,16 +19358,17 @@ packages: dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.6.3 - tsutils: 3.21.0(typescript@5.3.2) - typescript: 5.3.2 + semver: 7.7.3 + tsutils: 3.21.0(typescript@5.1.6) + typescript: 5.1.6 transitivePeerDependencies: - supports-color + dev: true - /@typescript-eslint/typescript-estree@5.62.0(typescript@5.3.3): + /@typescript-eslint/typescript-estree@5.62.0(typescript@5.3.2): resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -18714,17 +19379,16 @@ packages: dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.6.3 - tsutils: 3.21.0(typescript@5.3.3) - typescript: 5.3.3 + semver: 7.7.3 + tsutils: 3.21.0(typescript@5.3.2) + typescript: 5.3.2 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/typescript-estree@5.62.0(typescript@5.9.3): + /@typescript-eslint/typescript-estree@5.62.0(typescript@5.3.3): resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -18735,12 +19399,12 @@ packages: dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.6.3 - tsutils: 3.21.0(typescript@5.9.3) - typescript: 5.9.3 + semver: 7.7.3 + tsutils: 3.21.0(typescript@5.3.3) + typescript: 5.3.3 transitivePeerDependencies: - supports-color dev: true @@ -18789,28 +19453,6 @@ packages: - supports-color dev: true - /@typescript-eslint/typescript-estree@6.21.0(typescript@5.9.3): - resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.0(supports-color@5.5.0) - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.3 - semver: 7.6.3 - ts-api-utils: 1.4.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/typescript-estree@8.14.0(typescript@5.3.2): resolution: {integrity: sha512-OPXPLYKGZi9XS/49rdaCbR5j/S14HazviBlUQFvSKz3npr3NikF+mrgK7CFVur6XEt95DZp/cmke9d5i3vtVnQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -18822,18 +19464,18 @@ packages: dependencies: '@typescript-eslint/types': 8.14.0 '@typescript-eslint/visitor-keys': 8.14.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) fast-glob: 3.3.2 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.6.3 + semver: 7.7.3 ts-api-utils: 1.4.0(typescript@5.3.2) typescript: 5.3.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.3.2): + /@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.1.6): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -18844,7 +19486,7 @@ packages: '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.2) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.1.6) eslint: 8.57.0 eslint-scope: 5.1.1 semver: 7.6.3 @@ -18853,7 +19495,7 @@ packages: - typescript dev: true - /@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.3.3): + /@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.3.2): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -18864,7 +19506,7 @@ packages: '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.3) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.2) eslint: 8.57.0 eslint-scope: 5.1.1 semver: 7.6.3 @@ -18873,19 +19515,19 @@ packages: - typescript dev: true - /@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@5.9.3): + /@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.3.3): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.0) '@types/json-schema': 7.0.15 '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.9.3) - eslint: 8.57.1 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.3) + eslint: 8.57.0 eslint-scope: 5.1.1 semver: 7.6.3 transitivePeerDependencies: @@ -18931,25 +19573,6 @@ packages: - typescript dev: true - /@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.9.3): - resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) - '@types/json-schema': 7.0.15 - '@types/semver': 7.5.8 - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) - eslint: 8.57.1 - semver: 7.6.3 - transitivePeerDependencies: - - supports-color - - typescript - dev: true - /@typescript-eslint/utils@8.14.0(eslint@9.14.0)(typescript@5.3.2): resolution: {integrity: sha512-OGqj6uB8THhrHj0Fk27DcHPojW7zKwKkPmHXHvQ58pLYp4hy8CSUdTKykKeh+5vFqTTVmjz0zCOOPKRovdsgHA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -18992,10 +19615,6 @@ packages: /@ungap/structured-clone@1.2.0: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - /@ungap/structured-clone@1.3.0: - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - dev: true - /@uniswap/lib@4.0.1-alpha: resolution: {integrity: sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA==} engines: {node: '>=10'} @@ -19069,7 +19688,7 @@ packages: '@vanilla-extract/css': 1.14.0 dev: false - /@vercel/style-guide@5.2.0(eslint@8.57.1)(prettier@3.5.3)(typescript@5.9.3): + /@vercel/style-guide@5.2.0(eslint@8.57.0)(prettier@3.5.3)(typescript@5.1.6): resolution: {integrity: sha512-fNSKEaZvSkiBoF6XEefs8CcgAV9K9e+MbcsDZjUsktHycKdA0jvjAzQi1W/FzLS+Nr5zZ6oejCwq/97dHUKe0g==} engines: {node: '>=16'} peerDependencies: @@ -19088,27 +19707,27 @@ packages: optional: true dependencies: '@babel/core': 7.26.0 - '@babel/eslint-parser': 7.25.9(@babel/core@7.26.0)(eslint@8.57.1) + '@babel/eslint-parser': 7.25.9(@babel/core@7.26.0)(eslint@8.57.0) '@rushstack/eslint-patch': 1.10.4 - '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) - eslint: 8.57.1 - eslint-config-prettier: 9.1.0(eslint@8.57.1) + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.1.6) + '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.1.6) + eslint: 8.57.0 + eslint-config-prettier: 9.1.0(eslint@8.57.0) eslint-import-resolver-alias: 1.1.2(eslint-plugin-import@2.31.0) - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.31.0)(eslint@8.57.1) - eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) - eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.57.1)(typescript@5.9.3) - eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) - eslint-plugin-playwright: 0.16.0(eslint-plugin-jest@27.9.0)(eslint@8.57.1) - eslint-plugin-react: 7.37.2(eslint@8.57.1) - eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) - eslint-plugin-testing-library: 6.4.0(eslint@8.57.1)(typescript@5.9.3) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.31.0)(eslint@8.57.0) + eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.57.0)(typescript@5.1.6) + eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.0) + eslint-plugin-playwright: 0.16.0(eslint-plugin-jest@27.9.0)(eslint@8.57.0) + eslint-plugin-react: 7.37.2(eslint@8.57.0) + eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) + eslint-plugin-testing-library: 6.4.0(eslint@8.57.0)(typescript@5.1.6) eslint-plugin-tsdoc: 0.2.17 - eslint-plugin-unicorn: 48.0.1(eslint@8.57.1) + eslint-plugin-unicorn: 48.0.1(eslint@8.57.0) prettier: 3.5.3 prettier-plugin-packagejson: 2.5.3(prettier@3.5.3) - typescript: 5.9.3 + typescript: 5.1.6 transitivePeerDependencies: - eslint-import-resolver-node - eslint-import-resolver-webpack @@ -19479,7 +20098,7 @@ packages: engines: {node: '>=16'} hasBin: true dependencies: - chalk: 5.4.1 + chalk: 5.6.2 commander: 13.1.0 dev: false @@ -21856,8 +22475,8 @@ packages: peerDependencies: postcss: ^8.1.0 dependencies: - browserslist: 4.24.2 - caniuse-lite: 1.0.30001680 + browserslist: 4.28.0 + caniuse-lite: 1.0.30001757 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -21926,12 +22545,12 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} - /babel-core@7.0.0-bridge.0(@babel/core@7.26.0): + /babel-core@7.0.0-bridge.0(@babel/core@7.28.5): resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.5 dev: true /babel-jest@29.7.0(@babel/core@7.26.0): @@ -21980,7 +22599,7 @@ packages: '@babel/core': 7.26.0 find-cache-dir: 4.0.0 schema-utils: 4.3.3 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) dev: true /babel-plugin-emotion@10.2.2: @@ -22039,7 +22658,7 @@ packages: dependencies: '@babel/runtime': 7.27.0 cosmiconfig: 6.0.0 - resolve: 1.22.8 + resolve: 1.22.11 dev: false /babel-plugin-macros@3.1.0: @@ -22063,6 +22682,19 @@ packages: - supports-color dev: true + /babel-plugin-polyfill-corejs2@0.4.12(@babel/core@7.28.5): + resolution: {integrity: sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/compat-data': 7.26.2 + '@babel/core': 7.28.5 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.28.5) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + /babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.26.0): resolution: {integrity: sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==} peerDependencies: @@ -22088,6 +22720,18 @@ packages: - supports-color dev: true + /babel-plugin-polyfill-corejs3@0.10.6(@babel/core@7.28.5): + resolution: {integrity: sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.28.5) + core-js-compat: 3.39.0 + transitivePeerDependencies: + - supports-color + dev: true + /babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.26.0): resolution: {integrity: sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==} peerDependencies: @@ -22111,6 +22755,17 @@ packages: - supports-color dev: true + /babel-plugin-polyfill-regenerator@0.6.3(@babel/core@7.28.5): + resolution: {integrity: sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + dev: true + /babel-plugin-polyfill-regenerator@0.6.5(@babel/core@7.26.0): resolution: {integrity: sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==} peerDependencies: @@ -22802,7 +23457,7 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001680 + caniuse-lite: 1.0.30001757 electron-to-chromium: 1.5.57 node-releases: 2.0.18 update-browserslist-db: 1.1.1(browserslist@4.24.2) @@ -23048,6 +23703,7 @@ packages: /caniuse-lite@1.0.30001680: resolution: {integrity: sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==} + dev: false /caniuse-lite@1.0.30001757: resolution: {integrity: sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==} @@ -23202,7 +23858,7 @@ packages: engines: {node: '>=12.13.0'} hasBin: true dependencies: - '@types/node': 24.10.1 + '@types/node': 22.10.7 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -23218,7 +23874,7 @@ packages: /chromium-edge-launcher@0.2.0: resolution: {integrity: sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==} dependencies: - '@types/node': 20.19.25 + '@types/node': 22.10.7 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -23657,7 +24313,7 @@ packages: /core-js-compat@3.39.0: resolution: {integrity: sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==} dependencies: - browserslist: 4.24.2 + browserslist: 4.28.0 dev: true /core-js-compat@3.47.0: @@ -24796,7 +25452,7 @@ packages: hasBin: true dependencies: address: 1.2.2 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color dev: true @@ -25696,19 +26352,19 @@ packages: peerDependencies: esbuild: '>=0.12 <1' dependencies: - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) esbuild: 0.18.20 transitivePeerDependencies: - supports-color dev: true - /esbuild-sass-plugin@3.3.1(esbuild@0.27.0)(sass-embedded@1.93.3): + /esbuild-sass-plugin@3.3.1(esbuild@0.24.0)(sass-embedded@1.93.3): resolution: {integrity: sha512-SnO1ls+d52n6j8gRRpjexXI8MsHEaumS0IdDHaYM29Y6gakzZYMls6i9ql9+AWMSQk/eryndmUpXEgT34QrX1A==} peerDependencies: esbuild: '>=0.20.1' sass-embedded: ^1.71.1 dependencies: - esbuild: 0.27.0 + esbuild: 0.24.0 resolve: 1.22.8 safe-identifier: 0.4.2 sass: 1.80.7 @@ -25934,39 +26590,6 @@ packages: '@esbuild/win32-ia32': 0.24.0 '@esbuild/win32-x64': 0.24.0 - /esbuild@0.27.0: - resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==} - engines: {node: '>=18'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.0 - '@esbuild/android-arm': 0.27.0 - '@esbuild/android-arm64': 0.27.0 - '@esbuild/android-x64': 0.27.0 - '@esbuild/darwin-arm64': 0.27.0 - '@esbuild/darwin-x64': 0.27.0 - '@esbuild/freebsd-arm64': 0.27.0 - '@esbuild/freebsd-x64': 0.27.0 - '@esbuild/linux-arm': 0.27.0 - '@esbuild/linux-arm64': 0.27.0 - '@esbuild/linux-ia32': 0.27.0 - '@esbuild/linux-loong64': 0.27.0 - '@esbuild/linux-mips64el': 0.27.0 - '@esbuild/linux-ppc64': 0.27.0 - '@esbuild/linux-riscv64': 0.27.0 - '@esbuild/linux-s390x': 0.27.0 - '@esbuild/linux-x64': 0.27.0 - '@esbuild/netbsd-arm64': 0.27.0 - '@esbuild/netbsd-x64': 0.27.0 - '@esbuild/openbsd-arm64': 0.27.0 - '@esbuild/openbsd-x64': 0.27.0 - '@esbuild/openharmony-arm64': 0.27.0 - '@esbuild/sunos-x64': 0.27.0 - '@esbuild/win32-arm64': 0.27.0 - '@esbuild/win32-ia32': 0.27.0 - '@esbuild/win32-x64': 0.27.0 - /escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -26048,15 +26671,6 @@ packages: eslint: 8.57.0 dev: true - /eslint-config-prettier@9.1.0(eslint@8.57.1): - resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' - dependencies: - eslint: 8.57.1 - dev: true - /eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.27.1)(@babel/plugin-transform-react-jsx@7.27.1)(eslint@8.57.0)(typescript@5.3.2): resolution: {integrity: sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==} engines: {node: '>=14.0.0'} @@ -26101,22 +26715,13 @@ packages: eslint-plugin-turbo: 1.13.4(eslint@8.57.0) dev: true - /eslint-config-turbo@1.13.4(eslint@8.57.1): - resolution: {integrity: sha512-+we4eWdZlmlEn7LnhXHCIPX/wtujbHCS7XjQM/TN09BHNEl2fZ8id4rHfdfUKIYTSKyy8U/nNyJ0DNoZj5Q8bw==} - peerDependencies: - eslint: '>6.6.0' - dependencies: - eslint: 8.57.1 - eslint-plugin-turbo: 1.13.4(eslint@8.57.1) - dev: true - /eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0): resolution: {integrity: sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w==} engines: {node: '>= 4'} peerDependencies: eslint-plugin-import: '>=1.4.0' dependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) dev: true /eslint-import-resolver-node@0.3.9: @@ -26157,7 +26762,7 @@ packages: - eslint-import-resolver-webpack - supports-color - /eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.31.0)(eslint@8.57.1): + /eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.31.0)(eslint@8.57.0): resolution: {integrity: sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -26173,9 +26778,9 @@ packages: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.0(supports-color@5.5.0) enhanced-resolve: 5.17.1 - eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint: 8.57.0 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 @@ -26216,7 +26821,7 @@ packages: transitivePeerDependencies: - supports-color - /eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + /eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} engines: {node: '>=4'} peerDependencies: @@ -26237,11 +26842,11 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.1.6) debug: 3.2.7(supports-color@8.1.1) - eslint: 8.57.1 + eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.31.0)(eslint@8.57.0) transitivePeerDependencies: - supports-color dev: true @@ -26253,14 +26858,14 @@ packages: postcss: 7.0.39 requireindex: 1.1.0 - /eslint-plugin-eslint-comments@3.2.0(eslint@8.57.1): + /eslint-plugin-eslint-comments@3.2.0(eslint@8.57.0): resolution: {integrity: sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==} engines: {node: '>=6.5.0'} peerDependencies: eslint: '>=4.19.1' dependencies: escape-string-regexp: 1.0.5 - eslint: 8.57.1 + eslint: 8.57.0 ignore: 5.3.2 dev: true @@ -26315,7 +26920,7 @@ packages: - eslint-import-resolver-webpack - supports-color - /eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + /eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} engines: {node: '>=4'} peerDependencies: @@ -26326,16 +26931,16 @@ packages: optional: true dependencies: '@rtsao/scc': 1.1.0 - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.1.6) array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 array.prototype.flatmap: 1.3.2 debug: 3.2.7(supports-color@8.1.1) doctrine: 2.1.0 - eslint: 8.57.1 + eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -26373,7 +26978,7 @@ packages: - typescript dev: true - /eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.57.1)(typescript@5.9.3): + /eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.57.0)(typescript@5.1.6): resolution: {integrity: sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -26386,9 +26991,9 @@ packages: jest: optional: true dependencies: - '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.9.3) - eslint: 8.57.1 + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.1.6) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.1.6) + eslint: 8.57.0 transitivePeerDependencies: - supports-color - typescript @@ -26417,30 +27022,6 @@ packages: safe-regex-test: 1.0.3 string.prototype.includes: 2.0.1 - /eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1): - resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} - engines: {node: '>=4.0'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 - dependencies: - aria-query: 5.3.2 - array-includes: 3.1.8 - array.prototype.flatmap: 1.3.2 - ast-types-flow: 0.0.8 - axe-core: 4.10.2 - axobject-query: 4.1.0 - damerau-levenshtein: 1.0.8 - emoji-regex: 9.2.2 - eslint: 8.57.1 - hasown: 2.0.2 - jsx-ast-utils: 3.3.5 - language-tags: 1.0.9 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - safe-regex-test: 1.0.3 - string.prototype.includes: 2.0.1 - dev: true - /eslint-plugin-lingui@0.2.2(eslint@8.57.0)(typescript@5.3.3): resolution: {integrity: sha512-orWu6o/IkCckLD0owLt+8fKy6SsIqIR9kz2L36KbCuAIfWg3dLRWC+XvuqQWEgJ4raRt+eoomTMunUJiBeogDQ==} engines: {node: '>=16.0.0'} @@ -26452,7 +27033,7 @@ packages: - typescript dev: true - /eslint-plugin-playwright@0.16.0(eslint-plugin-jest@27.9.0)(eslint@8.57.1): + /eslint-plugin-playwright@0.16.0(eslint-plugin-jest@27.9.0)(eslint@8.57.0): resolution: {integrity: sha512-DcHpF0SLbNeh9MT4pMzUGuUSnJ7q5MWbP8sSEFIMS6j7Ggnduq8ghNlfhURgty4c1YFny7Ge9xYTO1FSAoV2Vw==} peerDependencies: eslint: '>=7' @@ -26461,8 +27042,8 @@ packages: eslint-plugin-jest: optional: true dependencies: - eslint: 8.57.1 - eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.57.1)(typescript@5.9.3) + eslint: 8.57.0 + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.57.0)(typescript@5.1.6) dev: true /eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0)(eslint@8.57.0)(prettier@2.8.8): @@ -26512,15 +27093,6 @@ packages: dependencies: eslint: 8.57.0 - /eslint-plugin-react-hooks@4.6.2(eslint@8.57.1): - resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} - engines: {node: '>=10'} - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - dependencies: - eslint: 8.57.1 - dev: true - /eslint-plugin-react-hooks@5.1.0-rc-fb9a90fa48-20240614(eslint@9.14.0): resolution: {integrity: sha512-xsiRwaDNF5wWNC4ZHLut+x/YcAxksUd9Rizt7LaEn3bV8VyYRpXnRJQlLOfYaVy9esk4DFP4zPPnoNVjq5Gc0w==} engines: {node: '>=10'} @@ -26572,33 +27144,6 @@ packages: string.prototype.matchall: 4.0.11 string.prototype.repeat: 1.0.0 - /eslint-plugin-react@7.37.2(eslint@8.57.1): - resolution: {integrity: sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==} - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - dependencies: - array-includes: 3.1.8 - array.prototype.findlast: 1.2.5 - array.prototype.flatmap: 1.3.2 - array.prototype.tosorted: 1.1.4 - doctrine: 2.1.0 - es-iterator-helpers: 1.2.0 - eslint: 8.57.1 - estraverse: 5.3.0 - hasown: 2.0.2 - jsx-ast-utils: 3.3.5 - minimatch: 3.1.2 - object.entries: 1.1.8 - object.fromentries: 2.0.8 - object.values: 1.2.0 - prop-types: 15.8.1 - resolve: 2.0.0-next.5 - semver: 6.3.1 - string.prototype.matchall: 4.0.11 - string.prototype.repeat: 1.0.0 - dev: true - /eslint-plugin-storybook@0.6.15(eslint@8.57.0)(typescript@5.3.3): resolution: {integrity: sha512-lAGqVAJGob47Griu29KXYowI4G7KwMoJDOkEip8ujikuDLxU+oWJ1l0WL6F2oDO4QiyUFXvtDkEkISMOPzo+7w==} engines: {node: 12.x || 14.x || >= 16} @@ -26628,14 +27173,14 @@ packages: - typescript dev: true - /eslint-plugin-testing-library@6.4.0(eslint@8.57.1)(typescript@5.9.3): + /eslint-plugin-testing-library@6.4.0(eslint@8.57.0)(typescript@5.1.6): resolution: {integrity: sha512-yeWF+YgCgvNyPNI9UKnG0FjeE2sk93N/3lsKqcmR8dSfeXJwFT5irnWo7NjLf152HkRzfoFjh3LsBUrhvFz4eA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0, npm: '>=6'} peerDependencies: eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 dependencies: - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.9.3) - eslint: 8.57.1 + '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.1.6) + eslint: 8.57.0 transitivePeerDependencies: - supports-color - typescript @@ -26657,26 +27202,17 @@ packages: eslint: 8.57.0 dev: true - /eslint-plugin-turbo@1.13.4(eslint@8.57.1): - resolution: {integrity: sha512-82GfMzrewI/DJB92Bbch239GWbGx4j1zvjk1lqb06lxIlMPnVwUHVwPbAnLfyLG3JuhLv9whxGkO/q1CL18JTg==} - peerDependencies: - eslint: '>6.6.0' - dependencies: - dotenv: 16.0.3 - eslint: 8.57.1 - dev: true - - /eslint-plugin-unicorn@48.0.1(eslint@8.57.1): + /eslint-plugin-unicorn@48.0.1(eslint@8.57.0): resolution: {integrity: sha512-FW+4r20myG/DqFcCSzoumaddKBicIPeFnTrifon2mWIzlfyvzwyqZjqVP7m4Cqr/ZYisS2aiLghkUWaPg6vtCw==} engines: {node: '>=16'} peerDependencies: eslint: '>=8.44.0' dependencies: '@babel/helper-validator-identifier': 7.25.9 - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.0) ci-info: 3.9.0 clean-regexp: 1.0.0 - eslint: 8.57.1 + eslint: 8.57.0 esquery: 1.6.0 indent-string: 4.0.0 is-builtin-module: 3.2.1 @@ -26809,54 +27345,6 @@ packages: transitivePeerDependencies: - supports-color - /eslint@8.57.1: - resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. - hasBin: true - dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1) - '@eslint-community/regexpp': 4.12.2 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.1 - '@humanwhocodes/config-array': 0.13.0 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.3.0 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.3(supports-color@8.1.1) - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.6.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.24.0 - graphemer: 1.4.0 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.1 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - strip-ansi: 6.0.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - dev: true - /eslint@9.14.0: resolution: {integrity: sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -26970,7 +27458,7 @@ packages: /estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 dev: true /esutils@2.0.3: @@ -27205,7 +27693,7 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} dependencies: - cross-spawn: 7.0.5 + cross-spawn: 7.0.6 get-stream: 6.0.1 human-signals: 2.1.0 is-stream: 2.0.1 @@ -27366,7 +27854,7 @@ packages: engines: {node: '>= 10.17.0'} hasBin: true dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.3(supports-color@8.1.1) get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -27834,7 +28322,7 @@ packages: resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==} engines: {node: '>=8.0.0'} dependencies: - cross-spawn: 7.0.5 + cross-spawn: 7.0.6 signal-exit: 3.0.7 dev: true @@ -27842,7 +28330,7 @@ packages: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} dependencies: - cross-spawn: 7.0.5 + cross-spawn: 7.0.6 signal-exit: 4.1.0 dev: true @@ -28679,7 +29167,7 @@ packages: pretty-error: 2.1.2 tapable: 1.1.3 util.promisify: 1.0.0 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) dev: true /html-webpack-plugin@5.6.5(webpack@5.103.0): @@ -28699,7 +29187,7 @@ packages: lodash: 4.17.21 pretty-error: 4.0.0 tapable: 2.3.0 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) dev: true /html2canvas@1.4.1: @@ -28823,7 +29311,7 @@ packages: engines: {node: '>= 6.0.0'} dependencies: agent-base: 5.1.1 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color dev: true @@ -29149,7 +29637,7 @@ packages: /is-bun-module@1.2.1: resolution: {integrity: sha512-AmidtEM6D6NmUiLOvvU7+IePxjEjOzra2h0pSrsfSAcXwl/83zLLXDByafUJy9k/rKK0pvXMLdwKwGHlX2Ke6Q==} dependencies: - semver: 7.6.3 + semver: 7.7.3 /is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} @@ -29173,7 +29661,6 @@ packages: engines: {node: '>= 0.4'} dependencies: hasown: 2.0.2 - dev: true /is-data-view@1.0.1: resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} @@ -29622,7 +30109,7 @@ packages: resolution: {integrity: sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==} engines: {node: '>=8'} dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -29647,7 +30134,7 @@ packages: engines: {node: '>=8'} dependencies: archy: 1.0.0 - cross-spawn: 7.0.5 + cross-spawn: 7.0.6 istanbul-lib-coverage: 3.2.2 p-map: 3.0.0 rimraf: 3.0.2 @@ -29667,7 +30154,7 @@ packages: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} engines: {node: '>=10'} dependencies: - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -29752,7 +30239,7 @@ packages: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.19.25 + '@types/node': 22.10.7 jest-mock: 29.7.0 jest-util: 29.7.0 dev: false @@ -29807,7 +30294,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.19.25 + '@types/node': 22.10.7 jest-util: 29.7.0 dev: false @@ -29841,7 +30328,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 20.19.25 + '@types/node': 22.10.7 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true @@ -29944,13 +30431,6 @@ packages: dependencies: argparse: 2.0.1 - /js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} - hasBin: true - dependencies: - argparse: 2.0.1 - dev: true - /jsbi@3.2.5: resolution: {integrity: sha512-aBE4n43IPvjaddScbvWRA2YlTzKEynHzu7MqOyTipdHucf/VxS63ViCjxYRg86M8Rxwbt/GfzHl1kKERkt45fQ==} dev: false @@ -29976,18 +30456,18 @@ packages: '@babel/preset-env': optional: true dependencies: - '@babel/core': 7.26.0 - '@babel/parser': 7.26.2 - '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-nullish-coalescing-operator': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.26.0) + '@babel/core': 7.28.5 + '@babel/parser': 7.28.5 + '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-nullish-coalescing-operator': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.28.5) '@babel/preset-env': 7.26.0(@babel/core@7.26.0) - '@babel/preset-flow': 7.25.9(@babel/core@7.26.0) - '@babel/preset-typescript': 7.26.0(@babel/core@7.26.0) - '@babel/register': 7.25.9(@babel/core@7.26.0) - babel-core: 7.0.0-bridge.0(@babel/core@7.26.0) + '@babel/preset-flow': 7.25.9(@babel/core@7.28.5) + '@babel/preset-typescript': 7.26.0(@babel/core@7.28.5) + '@babel/register': 7.25.9(@babel/core@7.28.5) + babel-core: 7.0.0-bridge.0(@babel/core@7.28.5) chalk: 4.1.2 flow-parser: 0.252.0 graceful-fs: 4.2.11 @@ -30629,7 +31109,7 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} dependencies: - semver: 7.6.3 + semver: 7.7.3 dev: true /make-error-cause@2.3.0: @@ -31359,7 +31839,7 @@ packages: resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} dependencies: '@types/debug': 4.1.12 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) decode-named-character-reference: 1.0.2 micromark-core-commonmark: 1.1.0 micromark-factory-space: 1.1.0 @@ -31947,7 +32427,7 @@ packages: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.8 + resolve: 1.22.11 semver: 5.7.2 validate-npm-package-license: 3.0.4 dev: true @@ -33398,7 +33878,7 @@ packages: engines: {node: '>=8.16.0'} dependencies: '@types/mime-types': 2.1.4 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) extract-zip: 1.7.0 https-proxy-agent: 4.0.0 mime: 2.6.0 @@ -34843,7 +35323,7 @@ packages: /resolve@1.19.0: resolution: {integrity: sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==} dependencies: - is-core-module: 2.15.1 + is-core-module: 2.16.1 path-parse: 1.0.7 dev: true @@ -34855,7 +35335,6 @@ packages: is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - dev: true /resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} @@ -34981,7 +35460,7 @@ packages: dependencies: open: 8.4.2 picomatch: 2.3.1 - source-map: 0.7.4 + source-map: 0.7.6 yargs: 17.7.2 dev: false @@ -35504,7 +35983,6 @@ packages: resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} hasBin: true - dev: false /send@0.19.0: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} @@ -35827,7 +36305,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.3 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) socks: 2.8.5 transitivePeerDependencies: - supports-color @@ -35890,7 +36368,7 @@ packages: git-hooks-list: 3.1.0 globby: 13.2.2 is-plain-obj: 4.1.0 - semver: 7.6.3 + semver: 7.7.3 sort-object-keys: 1.1.3 dev: true @@ -35917,15 +36395,9 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - /source-map@0.7.4: - resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} - engines: {node: '>= 8'} - dev: false - /source-map@0.7.6: resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} engines: {node: '>= 12'} - dev: true /source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} @@ -35980,7 +36452,7 @@ packages: /spdy-transport@3.0.0: resolution: {integrity: sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==} dependencies: - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) detect-node: 2.1.0 hpack.js: 2.1.6 obuf: 1.1.2 @@ -35994,7 +36466,7 @@ packages: resolution: {integrity: sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==} engines: {node: '>=6.0.0'} dependencies: - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) handle-thing: 2.0.1 http-deceiver: 1.2.7 select-hose: 2.0.0 @@ -36010,7 +36482,7 @@ packages: webpack: ^1 || ^2 || ^3 || ^4 || ^5 dependencies: chalk: 4.1.2 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) dev: true /split-on-first@1.1.0: @@ -36717,7 +37189,7 @@ packages: unique-string: 2.0.0 dev: true - /terser-webpack-plugin@5.3.14(esbuild@0.27.0)(webpack@5.103.0): + /terser-webpack-plugin@5.3.14(esbuild@0.24.0)(webpack@5.103.0): resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -36734,12 +37206,12 @@ packages: optional: true dependencies: '@jridgewell/trace-mapping': 0.3.31 - esbuild: 0.27.0 + esbuild: 0.24.0 jest-worker: 27.5.1 schema-utils: 4.3.3 serialize-javascript: 6.0.2 terser: 5.44.1 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) dev: true /terser@4.8.1: @@ -37059,15 +37531,6 @@ packages: typescript: 5.3.2 dev: true - /ts-api-utils@1.4.0(typescript@5.9.3): - resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' - dependencies: - typescript: 5.9.3 - dev: true - /ts-dedent@2.2.0: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} engines: {node: '>=6.10'} @@ -37250,33 +37713,33 @@ packages: - yaml dev: true - /tsutils@3.21.0(typescript@5.3.2): + /tsutils@3.21.0(typescript@5.1.6): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 - typescript: 5.3.2 + typescript: 5.1.6 + dev: true - /tsutils@3.21.0(typescript@5.3.3): + /tsutils@3.21.0(typescript@5.3.2): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 - typescript: 5.3.3 - dev: true + typescript: 5.3.2 - /tsutils@3.21.0(typescript@5.9.3): + /tsutils@3.21.0(typescript@5.3.3): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 - typescript: 5.9.3 + typescript: 5.3.3 dev: true /tunnel-agent@0.6.0: @@ -37559,12 +38022,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - /typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true - dev: true - /ua-is-frozen@0.1.2: resolution: {integrity: sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==} dev: false @@ -37659,10 +38116,7 @@ packages: /undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - - /undici-types@7.10.0: - resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} - dev: false + dev: true /undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} @@ -38638,7 +39092,7 @@ packages: peerDependencies: vite: ^2.6.0 || 3 || 4 dependencies: - '@rollup/pluginutils': 5.1.3(rollup@3.29.5) + '@rollup/pluginutils': 5.1.3 '@svgr/core': 6.5.1 vite: 4.5.9(@types/node@20.17.6) transitivePeerDependencies: @@ -38651,7 +39105,7 @@ packages: peerDependencies: vite: '>=2.6.0' dependencies: - '@rollup/pluginutils': 5.1.3(rollup@3.29.5) + '@rollup/pluginutils': 5.1.3 '@svgr/core': 8.1.0(typescript@5.3.2) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0) vite: 5.4.11(@types/node@20.17.6) @@ -39082,7 +39536,7 @@ packages: mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 4.3.0 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) dev: true /webpack-dev-server@4.15.2(debug@4.3.7)(webpack@5.103.0): @@ -39126,7 +39580,7 @@ packages: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) webpack-dev-middleware: 5.3.4(webpack@5.103.0) ws: 8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) transitivePeerDependencies: @@ -39154,7 +39608,7 @@ packages: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} dev: true - /webpack@5.103.0(esbuild@0.27.0): + /webpack@5.103.0(esbuild@0.24.0): resolution: {integrity: sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==} engines: {node: '>=10.13.0'} hasBin: true @@ -39186,7 +39640,7 @@ packages: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.0 - terser-webpack-plugin: 5.3.14(esbuild@0.27.0)(webpack@5.103.0) + terser-webpack-plugin: 5.3.14(esbuild@0.24.0)(webpack@5.103.0) watchpack: 2.4.4 webpack-sources: 3.3.3 transitivePeerDependencies: