diff --git a/package.json b/package.json index 76904dc65..1c22b3e57 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "kwenta", - "version": "7.9.3", + "version": "7.9.5", "description": "Kwenta", "main": "index.js", "scripts": { diff --git a/packages/app/package.json b/packages/app/package.json index 088bbb23c..92b7b946b 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@kwenta/app", - "version": "7.9.3", + "version": "7.9.5", "scripts": { "dev": "next", "build": "next build", diff --git a/packages/app/src/components/ColoredPrice.tsx b/packages/app/src/components/ColoredPrice.tsx index b7896f7b4..b0cb589b2 100644 --- a/packages/app/src/components/ColoredPrice.tsx +++ b/packages/app/src/components/ColoredPrice.tsx @@ -2,12 +2,13 @@ import styled from 'styled-components' import { PriceChange } from 'state/prices/types' +import { Body } from './Text' + export const getColorFromPriceChange = (change?: PriceChange) => { return !change ? 'white' : change === 'up' ? 'green' : 'red' } -const ColoredPrice = styled.div<{ priceChange?: PriceChange }>` - font-size: 13px; +const ColoredPrice = styled(Body)<{ priceChange?: PriceChange }>` font-family: ${(props) => props.theme.fonts.mono}; color: ${(props) => { const color = getColorFromPriceChange(props.priceChange) diff --git a/packages/app/src/components/Text/Body.tsx b/packages/app/src/components/Text/Body.tsx index fd04b8733..b1941d59f 100644 --- a/packages/app/src/components/Text/Body.tsx +++ b/packages/app/src/components/Text/Body.tsx @@ -1,8 +1,10 @@ import { ComponentType, memo } from 'react' import styled, { css } from 'styled-components' +export type FontSize = 'xsmall' | 'small' | 'medium' | 'large' + export type BodyProps = React.HTMLAttributes & { - size?: 'xsmall' | 'small' | 'medium' | 'large' + size?: FontSize weight?: 'regular' | 'bold' | 'black' color?: 'primary' | 'secondary' | 'tertiary' | 'positive' | 'negative' | 'preview' type?: 'p' | 'span' @@ -51,7 +53,7 @@ const Body: React.FC = memo( ) ) -const sizeMap = { xsmall: 10, small: 12, medium: 13, large: 15 } as const +export const fontSizeMap = { xsmall: 10, small: 12, medium: 13, large: 15 } as const const getFontFamily = (weight: NonNullable, mono?: boolean) => { return mono ? (weight !== 'regular' ? 'monoBold' : 'mono') : weight @@ -73,7 +75,7 @@ const BODY_STYLE = css` ${(props) => css` color: ${props.theme.colors.selectedTheme.newTheme.text[props.$color]}; - font-size: ${props.$fontSize ?? sizeMap[props.$size]}px; + font-size: ${props.$fontSize ?? fontSizeMap[props.$size]}px; font-family: ${props.theme.fonts[getFontFamily(props.$weight, props.$mono)]}; ${props.$capitalized && css` diff --git a/packages/app/src/constants/ui.ts b/packages/app/src/constants/ui.ts index 2cf53ed5f..47892e15c 100644 --- a/packages/app/src/constants/ui.ts +++ b/packages/app/src/constants/ui.ts @@ -17,6 +17,3 @@ export enum zIndex { } export const STAKING_DISABLED = false - -// This flag controls the one-click swap-deposit-trade feature -export const SWAP_DEPOSIT_TRADE_ENABLED = false diff --git a/packages/app/src/pages/dashboard/staking.tsx b/packages/app/src/pages/dashboard/staking.tsx index 82e8c37ae..640bb616d 100644 --- a/packages/app/src/pages/dashboard/staking.tsx +++ b/packages/app/src/pages/dashboard/staking.tsx @@ -26,7 +26,7 @@ import { selectStakingV1, selectTotalVestable, } from 'state/staking/selectors' -import { selectRedirectToMigration } from 'state/stakingMigration/selectors' +import { selectStartMigration } from 'state/stakingMigration/selectors' import media from 'styles/media' import MigratePage from './migrate' @@ -45,7 +45,7 @@ const StakingPage: StakingComponent = () => { const kwentaRewards = useAppSelector(selectKwentaRewards) const stakedResetTime = useAppSelector(selectStakedResetTime) const stakingV1 = useAppSelector(selectStakingV1) - const redirectToMigration = useAppSelector(selectRedirectToMigration) + const startMigration = useAppSelector(selectStartMigration) useFetchStakeMigrateData() @@ -70,8 +70,8 @@ const StakingPage: StakingComponent = () => { const timeLeft = useMemo( () => - stakedResetTime > new Date().getTime() / 1000 - ? formatTruncatedDuration(stakedResetTime - new Date().getTime() / 1000) + stakedResetTime > Date.now() / 1000 + ? formatTruncatedDuration(stakedResetTime - Date.now() / 1000) : NO_VALUE, [stakedResetTime] ) @@ -200,7 +200,7 @@ const StakingPage: StakingComponent = () => { } }, [currentTab, handleChangeTab, stakingInfo, stakingV1, t]) - return redirectToMigration ? ( + return startMigration ? ( ) : ( <> diff --git a/packages/app/src/queries/rates/utils.ts b/packages/app/src/queries/rates/utils.ts index 390e24882..dca028cbe 100644 --- a/packages/app/src/queries/rates/utils.ts +++ b/packages/app/src/queries/rates/utils.ts @@ -31,6 +31,7 @@ export const mapPythCandles = (candleData: PythResponse): Candle[] => { export const formatPythSymbol = (asset: string): string => { if (asset === 'ETHBTC') return 'Crypto.ETH/BTC' + if (asset === 'STETH/ETH') return 'Crypto.STETH/ETH' const prefix = Object.keys(NON_CRYPTO_ASSET_TYPES).find((type) => NON_CRYPTO_ASSET_TYPES[type].includes(asset) diff --git a/packages/app/src/sections/dashboard/DashboardLayout.tsx b/packages/app/src/sections/dashboard/DashboardLayout.tsx index ff05454ea..21340cf5c 100644 --- a/packages/app/src/sections/dashboard/DashboardLayout.tsx +++ b/packages/app/src/sections/dashboard/DashboardLayout.tsx @@ -11,11 +11,7 @@ import ROUTES from 'constants/routes' import AppLayout from 'sections/shared/Layout/AppLayout' import { useAppSelector } from 'state/hooks' import { selectStakingMigrationRequired } from 'state/staking/selectors' -import { - selectInMigrationPeriod, - selectIsMigrationPeriodStarted, - selectStartMigration, -} from 'state/stakingMigration/selectors' +import { selectStartMigration } from 'state/stakingMigration/selectors' import { LeftSideContent, PageContent } from 'styles/common' import Links from './Links' @@ -34,10 +30,8 @@ const Tabs = Object.values(Tab) const DashboardLayout: FC<{ children?: ReactNode }> = ({ children }) => { const { t } = useTranslation() const router = useRouter() - const stakngMigrationRequired = useAppSelector(selectStakingMigrationRequired) + const stakingMigrationRequired = useAppSelector(selectStakingMigrationRequired) const startMigration = useAppSelector(selectStartMigration) - const isMigrationPeriodStarted = useAppSelector(selectIsMigrationPeriodStarted) - const inMigrationPeriod = useAppSelector(selectInMigrationPeriod) const tabQuery = useMemo(() => { if (router.pathname) { @@ -76,17 +70,14 @@ const DashboardLayout: FC<{ children?: ReactNode }> = ({ children }) => { label: t('dashboard.tabs.staking'), active: activeTab === Tab.Stake, href: ROUTES.Dashboard.Stake, - hidden: - (stakngMigrationRequired || startMigration) && - isMigrationPeriodStarted && - inMigrationPeriod, + hidden: startMigration, }, { name: Tab.Migrate, label: t('dashboard.tabs.migrate'), active: activeTab === Tab.Migrate, href: ROUTES.Dashboard.Migrate, - hidden: !stakngMigrationRequired, + hidden: !stakingMigrationRequired && !startMigration, }, { name: Tab.Governance, @@ -96,14 +87,7 @@ const DashboardLayout: FC<{ children?: ReactNode }> = ({ children }) => { external: true, }, ], - [ - t, - activeTab, - stakngMigrationRequired, - startMigration, - isMigrationPeriodStarted, - inMigrationPeriod, - ] + [t, activeTab, startMigration, stakingMigrationRequired] ) const visibleTabs = TABS.filter(({ hidden }) => !hidden) diff --git a/packages/app/src/sections/dashboard/FuturesMarketsTable.tsx b/packages/app/src/sections/dashboard/FuturesMarketsTable.tsx index e38010e56..271326dc5 100644 --- a/packages/app/src/sections/dashboard/FuturesMarketsTable.tsx +++ b/packages/app/src/sections/dashboard/FuturesMarketsTable.tsx @@ -92,7 +92,6 @@ const FuturesMarketsTable: React.FC = ({ search }) => { router.push(ROUTES.Markets.MarketPair(row.original.asset, accountType)) }} @@ -138,7 +137,9 @@ const FuturesMarketsTable: React.FC = ({ search }) => cell: (cellProps) => { return ( - {formatDollars(cellProps.row.original.price, { suggestDecimals: true })} + {formatDollars(cellProps.row.original.price, { + suggestDecimalsForAsset: cellProps.row.original.asset, + })} ) }, diff --git a/packages/app/src/sections/dashboard/FuturesPositionsTable/FuturesPositionsTable.tsx b/packages/app/src/sections/dashboard/FuturesPositionsTable/FuturesPositionsTable.tsx index ce3aca356..d34b21296 100644 --- a/packages/app/src/sections/dashboard/FuturesPositionsTable/FuturesPositionsTable.tsx +++ b/packages/app/src/sections/dashboard/FuturesPositionsTable/FuturesPositionsTable.tsx @@ -123,7 +123,7 @@ const FuturesPositionsTable: FC = ({ {formatDollars(cellProps.row.original.marketPrice, { - suggestDecimals: true, + suggestDecimalsForAsset: cellProps.row.original.market.asset, })} diff --git a/packages/app/src/sections/dashboard/Stake/StakingPortfolio.tsx b/packages/app/src/sections/dashboard/Stake/StakingPortfolio.tsx index cfd72b333..52428981d 100644 --- a/packages/app/src/sections/dashboard/Stake/StakingPortfolio.tsx +++ b/packages/app/src/sections/dashboard/Stake/StakingPortfolio.tsx @@ -30,14 +30,16 @@ const StakingPortfolio: FC = memo(({ title, cardsInfo }) {icon} - {card.map(({ key, title, value, onClick }) => ( - - {title} - - {value} - - - ))} + {card + .filter((info) => !info.hidden) + .map(({ key, title, value, onClick }) => ( + + {title} + + {value} + + + ))} ))} diff --git a/packages/app/src/sections/dashboard/Stake/StakingTab.tsx b/packages/app/src/sections/dashboard/Stake/StakingTab.tsx index ad589c83f..3c92ce3de 100644 --- a/packages/app/src/sections/dashboard/Stake/StakingTab.tsx +++ b/packages/app/src/sections/dashboard/Stake/StakingTab.tsx @@ -10,10 +10,11 @@ import { Body, Heading } from 'components/Text' import { STAKING_DISABLED } from 'constants/ui' import { StakingCard } from 'sections/dashboard/Stake/card' import { useAppDispatch, useAppSelector } from 'state/hooks' -import { claimStakingRewards, claimStakingRewardsV2 } from 'state/staking/actions' +import { claimStakingRewards, claimStakingRewardsV2, compoundRewards } from 'state/staking/actions' import { selectApy, selectClaimableBalance, + selectIsCompoundingRewards, selectIsGettingReward, selectStakedKwentaBalance, selectStakingV1, @@ -30,6 +31,7 @@ const StakingTab = () => { const claimableBalance = useAppSelector(selectClaimableBalance) const stakedKwentaBalance = useAppSelector(selectStakedKwentaBalance) const isClaimingReward = useAppSelector(selectIsGettingReward) + const isCompoundingRewards = useAppSelector(selectIsCompoundingRewards) const stakingV1 = useAppSelector(selectStakingV1) const apy = useAppSelector(selectApy) @@ -41,6 +43,10 @@ const StakingTab = () => { } }, [dispatch, stakingV1]) + const handleCompoundReward = useCallback(() => { + dispatch(compoundRewards()) + }, [dispatch]) + const stakingAndRewardsInfo: StakingCards[] = useMemo( () => [ { @@ -116,6 +122,19 @@ const StakingTab = () => { > {t('dashboard.stake.tabs.staking.claim')} + {!stakingV1 && ( + + )} diff --git a/packages/app/src/sections/dashboard/Stake/TradingRewardsTab.tsx b/packages/app/src/sections/dashboard/Stake/TradingRewardsTab.tsx deleted file mode 100644 index 2d8cfa777..000000000 --- a/packages/app/src/sections/dashboard/Stake/TradingRewardsTab.tsx +++ /dev/null @@ -1,302 +0,0 @@ -import { ZERO_WEI } from '@kwenta/sdk/constants' -import { formatDollars, formatPercent, truncateNumbers } from '@kwenta/sdk/utils' -import { formatTruncatedDuration } from '@kwenta/sdk/utils' -import { wei } from '@synthetixio/wei' -import { BigNumber } from 'ethers' -import { formatEther } from 'ethers/lib/utils.js' -import { useCallback, useMemo, FC, memo } from 'react' -import { useTranslation } from 'react-i18next' -import styled from 'styled-components' - -import HelpIcon from 'assets/svg/app/question-mark.svg' -import Button from 'components/Button' -import { FlexDivRow } from 'components/layout/flex' -import { SplitContainer } from 'components/layout/grid' -import { MobileHiddenView, MobileOnlyView } from 'components/Media' -import { Body, LogoText } from 'components/Text' -import Tooltip from 'components/Tooltip/Tooltip' -import { EXTERNAL_LINKS } from 'constants/links' -import Connector from 'containers/Connector' -import useGetFile from 'queries/files/useGetFile' -import useGetFuturesFee from 'queries/staking/useGetFuturesFee' -import useGetFuturesFeeForAccount from 'queries/staking/useGetFuturesFeeForAccount' -import { - FuturesFeeForAccountProps, - FuturesFeeProps, - TradingRewardProps, -} from 'queries/staking/utils' -import { StakingCard } from 'sections/dashboard/Stake/card' -import { useAppDispatch, useAppSelector } from 'state/hooks' -import { claimMultipleKwentaRewards } from 'state/staking/actions' -import { selectEpochPeriod, selectKwentaRewards, selectResetTime } from 'state/staking/selectors' -import media from 'styles/media' - -const TradingRewardsTab: FC = memo( - ({ period = 0, start = 0, end = Math.floor(Date.now() / 1000) }) => { - const { t } = useTranslation() - const { walletAddress, network } = Connector.useContainer() - const dispatch = useAppDispatch() - - const resetTime = useAppSelector(selectResetTime) - const kwentaRewards = useAppSelector(selectKwentaRewards) - const epochPeriod = useAppSelector(selectEpochPeriod) - - const futuresFeeQuery = useGetFuturesFeeForAccount(walletAddress!, start, end) - const futuresFeePaid = useMemo(() => { - const t: FuturesFeeForAccountProps[] = futuresFeeQuery.data ?? [] - - return t - .map((trade) => formatEther(trade.feesPaid.sub(trade.keeperFeesPaid).toString())) - .reduce((acc, curr) => acc.add(wei(curr)), ZERO_WEI) - }, [futuresFeeQuery.data]) - - const totalFuturesFeeQuery = useGetFuturesFee(start, end) - const totalFuturesFeePaid = useMemo(() => { - const t: FuturesFeeProps[] = totalFuturesFeeQuery.data ?? [] - return t - .map((trade) => formatEther(trade.feesKwenta.toString())) - .reduce((acc, curr) => acc.add(wei(curr)), ZERO_WEI) - }, [totalFuturesFeeQuery.data]) - - const estimatedRewardQuery = useGetFile( - `/${network.id === 420 ? `goerli-` : ''}epoch-current.json` - ) - const estimatedReward = useMemo( - () => BigNumber.from(estimatedRewardQuery?.data?.claims[walletAddress!]?.amount ?? 0), - [estimatedRewardQuery?.data?.claims, walletAddress] - ) - const weeklyRewards = useMemo( - () => BigNumber.from(estimatedRewardQuery?.data?.tokenTotal ?? 0), - [estimatedRewardQuery?.data?.tokenTotal] - ) - - const claimDisabled = useMemo(() => kwentaRewards.lte(0), [kwentaRewards]) - - const handleClaim = useCallback(() => { - dispatch(claimMultipleKwentaRewards()) - }, [dispatch]) - - const ratio = useMemo(() => { - return wei(weeklyRewards).gt(0) ? wei(estimatedReward).div(wei(weeklyRewards)) : ZERO_WEI - }, [estimatedReward, weeklyRewards]) - - const showEstimatedValue = useMemo(() => wei(period).eq(epochPeriod), [epochPeriod, period]) - - return ( - - - -
- {t('dashboard.stake.tabs.trading-rewards.claimable-rewards-all')} - {truncateNumbers(kwentaRewards, 4)} -
-
- {t('dashboard.stake.tabs.trading-rewards.trading-activity-reset')} - - {resetTime > new Date().getTime() / 1000 - ? formatTruncatedDuration(resetTime - new Date().getTime() / 1000) - : t('dashboard.stake.tabs.trading-rewards.pending-for-rewards')} - -
-
- - - -
- - - -
- - {t('dashboard.stake.tabs.trading-rewards.future-fee-paid', { - EpochPeriod: period, - })} - <CustomStyledTooltip - preset="bottom" - width="260px" - height="auto" - content={t('dashboard.stake.tabs.trading-rewards.trading-rewards-tooltip')} - > - <WithCursor cursor="help"> - <SpacedHelpIcon /> - </WithCursor> - </CustomStyledTooltip> - - {formatDollars(futuresFeePaid, { minDecimals: 2 })} -
-
- - {t('dashboard.stake.tabs.trading-rewards.fees-paid', { EpochPeriod: period })} - - {formatDollars(totalFuturesFeePaid, { minDecimals: 2 })} -
- {showEstimatedValue ? ( - <> -
- - {t('dashboard.stake.tabs.trading-rewards.estimated-rewards')} - <CustomStyledTooltip - preset="bottom" - width="260px" - height="auto" - content={t('dashboard.stake.tabs.trading-rewards.estimated-info')} - > - <WithCursor cursor="help"> - <SpacedHelpIcon /> - </WithCursor> - </CustomStyledTooltip> - - - {truncateNumbers(wei(estimatedReward), 4)} - -
-
- - {t('dashboard.stake.tabs.trading-rewards.estimated-reward-share', { - EpochPeriod: period, - })} - - {formatPercent(ratio, { minDecimals: 2 })} -
- - ) : null} -
- -
-
- - - -
- - {t('dashboard.stake.tabs.trading-rewards.future-fee-paid-mobile')} - <CustomStyledTooltip - width="200px" - height="auto" - left="-100px !important" - content={t('dashboard.stake.tabs.trading-rewards.trading-rewards-tooltip')} - > - <WithCursor cursor="help"> - <SpacedHelpIcon /> - </WithCursor> - </CustomStyledTooltip> - - {formatDollars(futuresFeePaid, { minDecimals: 2 })} -
-
- {t('dashboard.stake.tabs.trading-rewards.fees-paid-mobile')} - {formatDollars(totalFuturesFeePaid, { minDecimals: 2 })} -
- {showEstimatedValue ? ( - <> -
- - {t('dashboard.stake.tabs.trading-rewards.estimated-rewards')} - <CustomStyledTooltip - width="260px" - height="auto" - content={t('dashboard.stake.tabs.trading-rewards.estimated-info')} - > - <WithCursor cursor="help"> - <SpacedHelpIcon /> - </WithCursor> - </CustomStyledTooltip> - - {truncateNumbers(wei(estimatedReward), 4)} -
-
- - {t('dashboard.stake.tabs.trading-rewards.estimated-reward-share-mobile', { - EpochPeriod: period, - })} - - {formatPercent(ratio, { minDecimals: 2 })} -
- - ) : null} -
- -
-
-
- ) - } -) - -const CustomStyledTooltip = styled(Tooltip)` - padding: 10px; - white-space: normal; - left: -80px; - ${media.lessThan('md')` - width: 310px; - left: -220px; - `} -` - -const WithCursor = styled.div<{ cursor: 'help' }>` - cursor: ${(props) => props.cursor}; -` - -const CardGridContainer = styled(StakingCard)` - display: flex; - flex-direction: column; - justify-content: space-between; - height: 240px; -` - -const Value = styled(Body).attrs({ weight: 'bold', mono: true })` - color: ${(props) => props.theme.colors.selectedTheme.yellow}; - font-size: 26px; - line-height: initial; -` - -const Title = styled(Body).attrs({ size: 'medium' })` - color: ${(props) => props.theme.colors.selectedTheme.title}; - white-space: nowrap; -` - -const CardGrid = styled.div` - display: grid; - grid-auto-flow: column; - grid-template-rows: 1fr 1fr; - grid-template-columns: 1fr 1fr; - - & > div { - margin-bottom: 20px; - } - - ${media.lessThan('md')` - column-gap: 10px; - `} -` - -const SpacedHelpIcon = styled(HelpIcon)` - margin-left: 8px; -` - -export default TradingRewardsTab diff --git a/packages/app/src/sections/futures/ClosePositionModal/ClosePositionModal.tsx b/packages/app/src/sections/futures/ClosePositionModal/ClosePositionModal.tsx index b0870ca44..24c2195da 100644 --- a/packages/app/src/sections/futures/ClosePositionModal/ClosePositionModal.tsx +++ b/packages/app/src/sections/futures/ClosePositionModal/ClosePositionModal.tsx @@ -242,7 +242,9 @@ export default function ClosePositionModal() { /> {previewTrade?.exceedsPriceProtection && ( diff --git a/packages/app/src/sections/futures/MarketDetails/MarketDetails.tsx b/packages/app/src/sections/futures/MarketDetails/MarketDetails.tsx index 2a9430d37..2c9b2fc28 100644 --- a/packages/app/src/sections/futures/MarketDetails/MarketDetails.tsx +++ b/packages/app/src/sections/futures/MarketDetails/MarketDetails.tsx @@ -79,12 +79,15 @@ const MarketDetails: React.FC = () => { const MarketPriceDetail: React.FC = memo(({ mobile }) => { const markPrice = useAppSelector(selectSkewAdjustedPriceInfo) + const asset = useAppSelector(selectMarketAsset) return ( ) @@ -92,12 +95,15 @@ const MarketPriceDetail: React.FC = memo(({ mobile }) => { const IndexPriceDetail: React.FC = memo(({ mobile }) => { const indexPrice = useAppSelector(selectMarketPriceInfo) + const asset = useAppSelector(selectMarketAsset) return ( ) }) diff --git a/packages/app/src/sections/futures/Trade/MarketsDropdown.tsx b/packages/app/src/sections/futures/Trade/MarketsDropdown.tsx index 37adbcabd..b49a37fe3 100644 --- a/packages/app/src/sections/futures/Trade/MarketsDropdown.tsx +++ b/packages/app/src/sections/futures/Trade/MarketsDropdown.tsx @@ -162,7 +162,9 @@ const MarketsDropdown: React.FC = ({ mobile }) => { key: market.marketKey, description: getSynthDescription(market.asset, t), priceNum: basePriceRate?.price.toNumber() ?? 0, - price: formatDollars(basePriceRate?.price ?? '0', { suggestDecimals: true }), + price: formatDollars(basePriceRate?.price ?? '0', { + suggestDecimalsForAsset: market.asset, + }), change: change.toNumber(), priceDirection: basePriceRate?.change ?? null, isMarketClosed: market.isSuspended, @@ -243,7 +245,7 @@ const MarketsDropdown: React.FC = ({ mobile }) => { )} ), - size: 30, + size: 21, }, { header: () => {t('futures.markets-drop-down.market')}, @@ -254,10 +256,10 @@ const MarketsDropdown: React.FC = ({ mobile }) => { - {getDisplayAsset(row.original.asset)} + {getDisplayAsset(row.original.asset)} ), - size: 65, + size: 75, }, { header: () => {t('futures.markets-drop-down.price')}, @@ -267,13 +269,16 @@ const MarketsDropdown: React.FC = ({ mobile }) => { cell: (cellProps) => { return (
- + {cellProps.row.original.price}
) }, - size: 100, + size: 90, }, { header: () => ( @@ -290,6 +295,7 @@ const MarketsDropdown: React.FC = ({ mobile }) => { futuresClosureReason={row.original.closureReason} fallbackComponent={ = ({ mobile }) => { accessorKey: 'change', sortingFn: 'basic', enableSorting: true, - size: 60, + size: 45, }, ]} data={options} diff --git a/packages/app/src/sections/futures/Trade/MarketsDropdownSelector.tsx b/packages/app/src/sections/futures/Trade/MarketsDropdownSelector.tsx index a0f10d34c..bebb16f79 100644 --- a/packages/app/src/sections/futures/Trade/MarketsDropdownSelector.tsx +++ b/packages/app/src/sections/futures/Trade/MarketsDropdownSelector.tsx @@ -54,7 +54,9 @@ const MarketsDropdownSelector: FC = (props) => (
- {formatDollars(props.priceDetails.priceInfo?.price ?? '0', { suggestDecimals: true })} + {formatDollars(props.priceDetails.priceInfo?.price ?? '0', { + suggestDecimalsForAsset: props.asset, + })} {formatPercent(props.priceDetails.oneDayChange)} diff --git a/packages/app/src/sections/futures/Trade/PreviewRows.tsx b/packages/app/src/sections/futures/Trade/PreviewRows.tsx index 33bcb016f..ff12f63e9 100644 --- a/packages/app/src/sections/futures/Trade/PreviewRows.tsx +++ b/packages/app/src/sections/futures/Trade/PreviewRows.tsx @@ -1,3 +1,4 @@ +import { FuturesMarketAsset } from '@kwenta/sdk/dist/types' import { formatDollars, formatPercent } from '@kwenta/sdk/utils' import Wei from '@synthetixio/wei' import { memo } from 'react' @@ -24,11 +25,15 @@ export const PriceImpactRow = memo(({ priceImpact }: { priceImpact: Wei | undefi ) }) -export const FillPriceRow = memo(({ fillPrice }: { fillPrice: Wei | undefined }) => { - return ( - - ) -}) +export const FillPriceRow = memo( + ({ fillPrice, asset }: { fillPrice: Wei | undefined; asset?: FuturesMarketAsset }) => { + return ( + + ) + } +) diff --git a/packages/app/src/sections/futures/Trade/SmartMarginTradePanelPreview.tsx b/packages/app/src/sections/futures/Trade/SmartMarginTradePanelPreview.tsx index 0a55dcd7e..71674864c 100644 --- a/packages/app/src/sections/futures/Trade/SmartMarginTradePanelPreview.tsx +++ b/packages/app/src/sections/futures/Trade/SmartMarginTradePanelPreview.tsx @@ -10,6 +10,7 @@ import { InfoBoxRow } from 'components/InfoBox' import { FlexDivRow, FlexDivRowCentered } from 'components/layout/flex' import { Body } from 'components/Text' import ROUTES from 'constants/routes' +import { selectMarketAsset } from 'state/futures/common/selectors' import { selectTradePreview } from 'state/futures/smartMargin/selectors' import { useAppSelector } from 'state/hooks' import { @@ -24,12 +25,13 @@ import { FillPriceRow, LiquidationRow, PriceImpactRow } from './PreviewRows' export const SmartMarginTradePanelPreview = memo(() => { const potentialTradeDetails = useAppSelector(selectTradePreview) + const asset = useAppSelector(selectMarketAsset) return ( - + diff --git a/packages/app/src/sections/futures/Trade/TradeBalanceSmartMargin.tsx b/packages/app/src/sections/futures/Trade/TradeBalanceSmartMargin.tsx index 2e04d9dc1..2440e1ded 100644 --- a/packages/app/src/sections/futures/Trade/TradeBalanceSmartMargin.tsx +++ b/packages/app/src/sections/futures/Trade/TradeBalanceSmartMargin.tsx @@ -20,6 +20,7 @@ import { selectTotalAvailableMargin, selectLockedMarginInMarkets, selectSmartMarginAccount, + selectKeeperEthBalance, } from 'state/futures/smartMargin/selectors' import { useAppDispatch, useAppSelector } from 'state/hooks' @@ -55,6 +56,7 @@ const TradeBalance = memo(() => { const { deviceType } = useWindowSize() const accountMargin = useAppSelector(selectTotalAvailableMargin) const lockedMargin = useAppSelector(selectLockedMarginInMarkets) + const ethBal = useAppSelector(selectKeeperEthBalance) const openModal = useAppSelector(selectShowModal) const { isWalletConnected } = Connector.useContainer() const { openConnectModal } = useConnectModal() @@ -67,8 +69,8 @@ const TradeBalance = memo(() => { }, [deviceType]) const isDepositRequired = useMemo(() => { - return accountMargin.lt(MIN_MARGIN_AMOUNT) && lockedMargin.eq(0) - }, [accountMargin, lockedMargin]) + return accountMargin.lt(MIN_MARGIN_AMOUNT) && lockedMargin.eq(0) && ethBal.eq(0) + }, [accountMargin, lockedMargin, ethBal]) const dismissModal = useCallback(() => { dispatch(setOpenModal(null)) @@ -144,81 +146,46 @@ const TradeBalance = memo(() => { ) : ( <> - {isMobile ? ( - - + + + + + {t('futures.market.trade.trade-balance.available-margin')} + + + {formatDollars(accountMargin)} + + + + + {lockedMargin.gt(0) && ( + - - {t('futures.market.trade.trade-balance.available-margin')}: + + {t('futures.market.trade.trade-balance.locked-margin')} - - {formatDollars(accountMargin)} - + + + - {lockedMargin.gt(0) && ( - - - {t('futures.market.trade.trade-balance.locked-margin')}: - - - - {formatDollars(lockedMargin)} - - - - - - - )} - - - - ) : ( - - - - - {t('futures.market.trade.trade-balance.available-margin')} - - - {formatDollars(accountMargin)} - - - - - {lockedMargin.gt(0) && ( - - - - {t('futures.market.trade.trade-balance.locked-margin')} - - - - - - - {formatDollars(lockedMargin)} - - - )} - - - )} + + {formatDollars(lockedMargin)} + + + )} + + )} diff --git a/packages/app/src/sections/futures/TradeConfirmation/TradeConfirmationModal.tsx b/packages/app/src/sections/futures/TradeConfirmation/TradeConfirmationModal.tsx index c2b212be2..a837865a4 100644 --- a/packages/app/src/sections/futures/TradeConfirmation/TradeConfirmationModal.tsx +++ b/packages/app/src/sections/futures/TradeConfirmation/TradeConfirmationModal.tsx @@ -343,11 +343,14 @@ const OrderPriceRow = () => { const EstimatedFillPriceRow = () => { const potentialTradeDetails = useAppSelector(selectTradePreview) + const marketAsset = useAppSelector(selectMarketAsset) return ( ) } diff --git a/packages/app/src/sections/futures/TradeSmartMargin/SmartMarginInfoBox.tsx b/packages/app/src/sections/futures/TradeSmartMargin/SmartMarginInfoBox.tsx index b1c58f48e..912dd7aef 100644 --- a/packages/app/src/sections/futures/TradeSmartMargin/SmartMarginInfoBox.tsx +++ b/packages/app/src/sections/futures/TradeSmartMargin/SmartMarginInfoBox.tsx @@ -3,11 +3,9 @@ import React, { memo } from 'react' import PencilButton from 'components/Button/PencilButton' import { InfoBoxRow } from 'components/InfoBox' -import { SWAP_DEPOSIT_TRADE_ENABLED } from 'constants/ui' import { setOpenModal } from 'state/app/reducer' import { selectShowModal } from 'state/app/selectors' import { selectSusdBalance } from 'state/balances/selectors' -import { selectSwapDepositBalanceQuote } from 'state/futures/smartMargin/selectors' import { selectAvailableMarginInMarkets, selectSmartMarginBalanceInfo, @@ -15,7 +13,6 @@ import { import { useAppDispatch, useAppSelector } from 'state/hooks' import ManageKeeperBalanceModal from './ManageKeeperBalanceModal' -import SwapDepositTokenSelector from './SwapDepositTokenSelector' function SmartMarginInfoBox() { const dispatch = useAppDispatch() @@ -25,17 +22,10 @@ function SmartMarginInfoBox() { const { freeMargin } = useAppSelector(selectSmartMarginBalanceInfo) const idleMarginInMarkets = useAppSelector(selectAvailableMarginInMarkets) const walletBal = useAppSelector(selectSusdBalance) - const quotedBal = useAppSelector(selectSwapDepositBalanceQuote) return ( <> - : null} - textValue={formatDollars( - SWAP_DEPOSIT_TRADE_ENABLED && quotedBal?.susdQuote ? quotedBal.susdQuote : walletBal - )} - /> + = memo(({ rounded = false, noBottom = true }) => { > {cellProps.row.original.market ? ( = memo(({ rounded = false, noBottom = true }) => { > {cellProps.row.original.market ? ( = memo(({ positions }: Props) => { } > { + ({ marketKey, marketName, marketAsset, side, infoLabel, badge, price, priceInfo }: Props) => { return ( @@ -36,6 +37,7 @@ const TableMarketDetails = memo( {formatDollars(price, { suggestDecimals: true, + suggestDecimalsForAsset: marketAsset, })} )} diff --git a/packages/app/src/sections/referrals/AffiliatesProfiles.tsx b/packages/app/src/sections/referrals/AffiliatesProfiles.tsx index 32c541223..6f689cd8a 100644 --- a/packages/app/src/sections/referrals/AffiliatesProfiles.tsx +++ b/packages/app/src/sections/referrals/AffiliatesProfiles.tsx @@ -27,8 +27,8 @@ import ReferralTiersProgressBar from './ReferralTiersProgressBar' const AffiliatesProfiles = memo(() => { const { t } = useTranslation() const tier = useAppSelector(selectReferralNft) - const mockReferralsRewards = useAppSelector(selectReferralCodes) - const referredCount = mockReferralsRewards.reduce( + const referralsRewards = useAppSelector(selectReferralCodes) + const referredCount = referralsRewards.reduce( (acc, { referredCount }) => acc + Number(referredCount), 0 ) diff --git a/packages/app/src/sections/referrals/ReferralRewardsHistory.tsx b/packages/app/src/sections/referrals/ReferralRewardsHistory.tsx index a34739c76..798e24893 100644 --- a/packages/app/src/sections/referrals/ReferralRewardsHistory.tsx +++ b/packages/app/src/sections/referrals/ReferralRewardsHistory.tsx @@ -59,7 +59,9 @@ const ReferralRewardsHistory: FC = memo(({ data }) {t('referrals.table.header.total-volume')} ), cell: (cellProps) => ( - {formatDollars(cellProps.getValue(), { maxDecimals: 2 })} + + {formatDollars(Number(cellProps.getValue()), { maxDecimals: 2 })} + ), accessorKey: 'referralVolume', }, diff --git a/packages/app/src/sections/referrals/ReferralsTabs.tsx b/packages/app/src/sections/referrals/ReferralsTabs.tsx index 00993280b..5592e0324 100644 --- a/packages/app/src/sections/referrals/ReferralsTabs.tsx +++ b/packages/app/src/sections/referrals/ReferralsTabs.tsx @@ -1,10 +1,12 @@ import { formatDollars, formatNumber, formatPercent } from '@kwenta/sdk/utils' -import { FC, memo, useMemo } from 'react' +import { useRouter } from 'next/router' +import { FC, memo, useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' import TabButton from 'components/Button/TabButton' import { TabPanel } from 'components/Tab' +import ROUTES from 'constants/routes' import { useAppSelector } from 'state/hooks' import { selectBoostNft, @@ -22,6 +24,7 @@ import ReferralRewardsHistory from './ReferralRewardsHistory' import { ReferralsHeading } from './ReferralsHeading' import ReferrersDashboard from './ReferrersDashboard' import { ReferralsTab } from './types' +import { calculateTotal } from './utils' type ReferralsTabsProp = { currentTab: ReferralsTab @@ -30,14 +33,18 @@ type ReferralsTabsProp = { const ReferralsTabs: FC = memo(({ currentTab, onChangeTab }) => { const { t } = useTranslation() + const router = useRouter() const referralsCodes = useAppSelector(selectReferralCodes) const referralsEpoch = useAppSelector(selectReferralEpoch) - const { totalVolume, totalRewards, totalTraders } = useAppSelector(selectCumulativeStatsByCode) + const { totalVolume, totalTraders } = useAppSelector(selectCumulativeStatsByCode) + const totalRewards = calculateTotal(referralsEpoch, 'earnedRewards') const hasMinted = useAppSelector(selectMintedBoostNft) const boostNftTier = useAppSelector(selectBoostNft) const { boost, icon } = REFFERAL_TIERS[boostNftTier] + const goToRewardsTab = useCallback(() => router.push(ROUTES.Dashboard.TradingRewards), [router]) + const tradersMetrics = useMemo( () => [ { @@ -57,13 +64,11 @@ const ReferralsTabs: FC = memo(({ currentTab, onChangeTab }) key: 'kwenta-earned', label: t('referrals.traders.dashboard.kwenta-earned'), value: formatNumber(totalRewards, { suggestDecimals: true }), - buttonLabel: t('referrals.traders.claim'), - onClick: () => {}, - active: false, - loading: false, + buttonLabel: t('referrals.traders.staking'), + onClick: goToRewardsTab, }, ], - [boost, hasMinted, icon, t, totalRewards, totalVolume] + [boost, goToRewardsTab, hasMinted, icon, t, totalRewards, totalVolume] ) const affiliatesMetrics = useMemo( diff --git a/packages/app/src/sections/referrals/ReferrersDashboard.tsx b/packages/app/src/sections/referrals/ReferrersDashboard.tsx index 1792b3356..ea6089dc3 100644 --- a/packages/app/src/sections/referrals/ReferrersDashboard.tsx +++ b/packages/app/src/sections/referrals/ReferrersDashboard.tsx @@ -17,7 +17,7 @@ type ReferrersDashboardProps = { const ReferrersDashboard: FC = memo(({ data }) => { return ( - {data.map(({ key, label, value, buttonLabel, icon, active, onClick, loading }) => ( + {data.map(({ key, label, value, buttonLabel, icon, onClick }) => ( {icon} @@ -30,15 +30,7 @@ const ReferrersDashboard: FC = memo(({ data }) => { {buttonLabel && ( - )} diff --git a/packages/app/src/state/futures/hooks.ts b/packages/app/src/state/futures/hooks.ts index 9ee0df3c8..93b65b90f 100644 --- a/packages/app/src/state/futures/hooks.ts +++ b/packages/app/src/state/futures/hooks.ts @@ -1,6 +1,5 @@ import { FuturesMarginType } from '@kwenta/sdk/types' -import { SWAP_DEPOSIT_TRADE_ENABLED } from 'constants/ui' import { fetchCrossMarginAccountData, fetchCrossMarginMarketData, @@ -20,6 +19,7 @@ import { } from 'state/referrals/action' import { fetchStakeMigrateData } from 'state/staking/actions' import { + selectEpochPeriod, selectSelectedEpoch, selectStakingSupportedNetwork, selectTradingRewardsSupportedNetwork, @@ -37,12 +37,10 @@ import { fetchSmartMarginAccountData, fetchSmartMarginMarketData, fetchSmartMarginOpenOrders, - fetchSwapDepositBalanceQuote, } from './smartMargin/actions' import { selectSmartMarginAccount, selectSmartMarginSupportedNetwork, - selectSelectedSwapDepositToken, } from './smartMargin/selectors' // TODO: Optimise polling and queries @@ -56,7 +54,6 @@ export const usePollMarketFuturesData = () => { const selectedAccountType = useAppSelector(selectFuturesType) const networkSupportsSmartMargin = useAppSelector(selectSmartMarginSupportedNetwork) const networkSupportsCrossMargin = useAppSelector(selectCrossMarginSupportedNetwork) - const swapDepositToken = useAppSelector(selectSelectedSwapDepositToken) const networkSupportTradingRewards = useAppSelector(selectTradingRewardsSupportedNetwork) useFetchAction(fetchBoostNftMinted, { @@ -142,12 +139,6 @@ export const usePollMarketFuturesData = () => { intervalTime: 30000, disabled: !wallet, }) - - usePollAction('fetchSwapDepositBalanceQuote', fetchSwapDepositBalanceQuote, { - dependencies: [swapDepositToken, wallet], - intervalTime: 10 * 60 * 1000, - disabled: !wallet || !swapDepositToken || !SWAP_DEPOSIT_TRADE_ENABLED, - }) } export const usePollDashboardFuturesData = () => { @@ -224,10 +215,11 @@ export const useFetchStakeMigrateData = () => { export const useFetchReferralData = () => { const networkId = useAppSelector(selectNetwork) const wallet = useAppSelector(selectWallet) + const period = useAppSelector(selectEpochPeriod) const networkSupportTradingRewards = useAppSelector(selectTradingRewardsSupportedNetwork) useFetchAction(fetchAllReferralData, { - dependencies: [networkId, wallet], - disabled: !networkSupportTradingRewards, + dependencies: [networkId, wallet, period], + disabled: !networkSupportTradingRewards || !wallet || !period, }) } diff --git a/packages/app/src/state/futures/smartMargin/actions.ts b/packages/app/src/state/futures/smartMargin/actions.ts index 90bcfd3ea..2f06c5cd7 100644 --- a/packages/app/src/state/futures/smartMargin/actions.ts +++ b/packages/app/src/state/futures/smartMargin/actions.ts @@ -39,7 +39,6 @@ import Wei, { wei } from '@synthetixio/wei' import { debounce } from 'lodash' import { notifyError } from 'components/ErrorNotifier' -import { SWAP_QUOTE_BUFFER } from 'constants/defaults' import { monitorAndAwaitTransaction } from 'state/app/helpers' import { handleTransactionError, @@ -99,7 +98,7 @@ import { clearSmartMarginTradePreviews, setKeeperDeposit, } from './reducer' -import { selectSelectedSwapDepositToken, selectSwapDepositBalance } from './selectors' +import { selectSelectedSwapDepositToken } from './selectors' import { selectSmartMarginAccount, selectSmartMarginMarginDelta, @@ -1108,7 +1107,6 @@ export const submitSmartMarginOrder = createAsyncThunk RootState, key: FuturesMarketKey) key: market.marketKey, } } - -export const fetchSwapDepositBalanceQuote = createAsyncThunk< - { - rate: string - susdQuote: string - }, - void, - ThunkConfig ->('futures/fetchSwapDepositBalanceQuote', async (_, { getState, extra: { sdk } }) => { - const state = getState() - const token = selectSelectedSwapDepositToken(state) - const balance = selectSwapDepositBalance(state) - if (token === SwapDepositToken.SUSD || balance.eq(0)) - return { - rate: '1', - susdQuote: balance.toString(), - } - - const susdQuote = await sdk.futures.getSwapDepositQuote(token, balance) - const rate = susdQuote.div(balance) - - return { - rate: rate.toString(), - susdQuote: susdQuote.sub(susdQuote.mul(SWAP_QUOTE_BUFFER).div(100)).toString(), - } -}) diff --git a/packages/app/src/state/futures/smartMargin/reducer.ts b/packages/app/src/state/futures/smartMargin/reducer.ts index efface66b..c60ac3cb1 100644 --- a/packages/app/src/state/futures/smartMargin/reducer.ts +++ b/packages/app/src/state/futures/smartMargin/reducer.ts @@ -39,7 +39,6 @@ import { fetchFundingRatesHistory, fetchFuturesFees, fetchFuturesFeesForAccount, - fetchSwapDepositBalanceQuote, } from './actions' import { SmartMarginAccountData, @@ -115,7 +114,6 @@ export const SMART_MARGIN_INITIAL_STATE: SmartMarginState = { }, futuresFees: '0', futuresFeesForAccount: '0', - swapDepositBalanceQuote: undefined, swapDepositSlippage: 0.15, swapDepositCustomSlippage: '', } @@ -529,21 +527,6 @@ const smartMarginSlice = createSlice({ error: 'Failed to fetch fee data for the account', } }) - - builder.addCase(fetchSwapDepositBalanceQuote.pending, (futuresState) => { - futuresState.queryStatuses.swapDepositBalanceQuote = LOADING_STATUS - }) - builder.addCase(fetchSwapDepositBalanceQuote.fulfilled, (futuresState, action) => { - futuresState.queryStatuses.swapDepositBalanceQuote = SUCCESS_STATUS - futuresState.swapDepositBalanceQuote = action.payload - }) - builder.addCase(fetchSwapDepositBalanceQuote.rejected, (futuresState) => { - futuresState.swapDepositBalanceQuote = undefined - futuresState.queryStatuses.swapDepositBalanceQuote = { - status: FetchStatus.Error, - error: 'Failed to fetch quote for the swap deposit token', - } - }) }, }) diff --git a/packages/app/src/state/futures/smartMargin/selectors.ts b/packages/app/src/state/futures/smartMargin/selectors.ts index e6145e1b5..e093a775c 100644 --- a/packages/app/src/state/futures/smartMargin/selectors.ts +++ b/packages/app/src/state/futures/smartMargin/selectors.ts @@ -19,7 +19,6 @@ import Wei, { wei } from '@synthetixio/wei' import { FuturesPositionTablePosition, FuturesPositionTablePositionActive } from 'types/futures' import { DEFAULT_DELAYED_CANCEL_BUFFER } from 'constants/defaults' -import { SWAP_DEPOSIT_TRADE_ENABLED } from 'constants/ui' import { selectSusdBalance } from 'state/balances/selectors' import { EST_KEEPER_GAS_FEE } from 'state/constants' import { @@ -545,18 +544,6 @@ export const selectAvailableMarginInMarkets = selectIdleMarginInMarkets() export const selectLockedMarginInMarkets = selectIdleMarginInMarkets(true) -export const selectSwapDepositBalanceQuote = createSelector( - (state: RootState) => state.smartMargin.swapDepositBalanceQuote, - (quote) => { - return quote - ? { - susdQuote: wei(quote.susdQuote), - rate: wei(quote.rate), - } - : undefined - } -) - export const selectSelectedSwapDepositToken = (state: RootState) => state.futures.selectedSwapDepositToken @@ -591,14 +578,8 @@ export const selectTotalAvailableMargin = createSelector( selectAvailableMarginInMarkets, selectSmartMarginBalanceInfo, selectSusdBalance, - selectSwapDepositBalanceQuote, - selectSelectedSwapDepositToken, - (idleInMarkets, { freeMargin }, susdBalance, balanceQuote, selectedToken) => { - const walletBalance = - !SWAP_DEPOSIT_TRADE_ENABLED || selectedToken === SwapDepositToken.SUSD - ? susdBalance - : balanceQuote?.susdQuote ?? wei(0) - return walletBalance.add(idleInMarkets).add(freeMargin) + (idleInMarkets, { freeMargin }, susdBalance) => { + return susdBalance.add(idleInMarkets).add(freeMargin) } ) diff --git a/packages/app/src/state/futures/smartMargin/types.ts b/packages/app/src/state/futures/smartMargin/types.ts index e60c31d40..383ebb3b5 100644 --- a/packages/app/src/state/futures/smartMargin/types.ts +++ b/packages/app/src/state/futures/smartMargin/types.ts @@ -155,10 +155,6 @@ export type SmartMarginState = { } futuresFees: string futuresFeesForAccount: string - swapDepositBalanceQuote?: { - susdQuote: string - rate: string - } swapDepositSlippage: number swapDepositCustomSlippage: string } diff --git a/packages/app/src/state/referrals/action.ts b/packages/app/src/state/referrals/action.ts index 47ff2d57b..5a896853d 100644 --- a/packages/app/src/state/referrals/action.ts +++ b/packages/app/src/state/referrals/action.ts @@ -1,3 +1,4 @@ +import { REFERRAL_PROGRAM_START_EPOCH } from '@kwenta/sdk/constants' import { TransactionStatus } from '@kwenta/sdk/types' import { createAsyncThunk } from '@reduxjs/toolkit' import { wei } from '@synthetixio/wei' @@ -12,7 +13,11 @@ import { import { calculateTotal } from 'sections/referrals/utils' import { monitorAndAwaitTransaction } from 'state/app/helpers' import { handleTransactionError, setOpenModal, setTransaction } from 'state/app/reducer' -import { selectTradingRewardsSupportedNetwork } from 'state/staking/selectors' +import { + selectEpochData, + selectEpochPeriod, + selectTradingRewardsSupportedNetwork, +} from 'state/staking/selectors' import { ThunkConfig } from 'state/types' import { selectWallet } from 'state/wallet/selectors' import logError from 'utils/logError' @@ -146,27 +151,40 @@ export const fetchBoostNftMinted = createAsyncThunk( } ) -//TODO: Need to calculate by epoch export const fetchReferralEpoch = createAsyncThunk( 'referrals/fetchReferralEpoch', async (_, { getState, extra: { sdk } }) => { try { const wallet = selectWallet(getState()) if (!wallet) return [] - const { epochPeriod: currentEpoch } = await sdk.kwentaToken.getStakingData() - const referralEpoch = await sdk.referrals.getCumulativeStatsByReferrer(wallet) - const referralVolume = calculateTotal(referralEpoch, 'referralVolume') - const referredCount = calculateTotal(referralEpoch, 'referredCount') - return referralEpoch.length > 0 - ? [ - { - epoch: currentEpoch.toString(), - referralVolume: referralVolume.toString(), - referredCount: referredCount.toString(), - earnedRewards: '0', - }, - ] - : [] + const epochData = selectEpochData(getState()) + if (!epochData) return [] + const epochPeriod = selectEpochPeriod(getState()) + const statsPerEpoch: ReferralsRewardsPerEpoch[] = await Promise.all( + epochData.slice(REFERRAL_PROGRAM_START_EPOCH).map(async ({ period, start, end }) => { + const referralEpoch = await sdk.referrals.getCumulativeStatsByReferrerAndEpochTime( + wallet, + start, + end + ) + + const kwentaRewards = + period !== Number(epochPeriod) + ? await sdk.kwentaToken.getKwentaRewardsByEpoch(period) + : wei(0) + const referralVolume = calculateTotal(referralEpoch, 'referralVolume') + const referredCount = calculateTotal(referralEpoch, 'referredCount') + + return { + epoch: period.toString(), + referralVolume: referralVolume.toString(), + referredCount: referredCount.toString(), + earnedRewards: kwentaRewards.toString(), + } + }) + ) + + return statsPerEpoch } catch (err) { logError(err) notifyError('Failed to fetch referral rewards per epoch', err) @@ -213,8 +231,9 @@ export const fetchReferralCodes = createAsyncThunk { try { const wallet = selectWallet(getState()) - if (!wallet) return [] - return await sdk.referrals.getCumulativeStatsByReferrer(wallet) + const period = Number(selectEpochPeriod(getState())) + if (!wallet || !period) return [] + return await sdk.referrals.getCumulativeStatsByReferrerAndEpoch(wallet, period) } catch (err) { logError(err) notifyError('Failed to fetch cumulative stats by referral codes', err) diff --git a/packages/app/src/state/staking/__tests__/selectors.test.ts b/packages/app/src/state/staking/__tests__/selectors.test.ts new file mode 100644 index 000000000..3f540aa33 --- /dev/null +++ b/packages/app/src/state/staking/__tests__/selectors.test.ts @@ -0,0 +1,82 @@ +import { toWei } from '@kwenta/sdk/utils' + +import { selectInMigrationPeriod, selectStartMigration } from 'state/stakingMigration/selectors' + +import { + selectCanVestBeforeMigration, + selectClaimableBalance, + selectKwentaBalance, + selectStakingMigrationRequired, + selectStakingV1, +} from '../selectors' + +describe('Staking Selectors', () => { + let mockState: any + + beforeEach(() => { + mockState = { + wallet: { + walletAddress: '0x123', + }, + staking: { + v1: { + claimableBalance: '100', + stakedKwentaBalance: '200', + totalVestable: '300', + }, + v2: { + claimableBalance: '400', + stakedKwentaBalance: '500', + totalVestable: '600', + }, + kwentaBalance: '700', + }, + stakingMigration: { + selectedUserMigrationInfo: { + '0x123': { + totalEscrowUnmigrated: '1000', + migrationPeriod: { + start: Math.floor(new Date('2023-01-01T12:00:00Z').getTime()), + end: Math.floor(new Date('2023-01-14T12:00:00Z').getTime()), + }, + }, + }, + }, + } + }) + + it('should select staking migration required correctly', () => { + const result = selectStakingMigrationRequired(mockState) + expect(result).toBeTruthy() + }) + + it('should select Kwenta balance correctly', () => { + const result = selectKwentaBalance(mockState) + expect(result).toEqual(toWei('700')) + }) + + it('should select claimable balance correctly', () => { + const result = selectClaimableBalance(mockState) + expect(result).toEqual(toWei('100')) + }) + + it('should select start migration correctly', () => { + const result = selectStartMigration(mockState) + expect(result).toBeTruthy() + }) + + it('should select in migration period as true when in migration period', () => { + const result = selectInMigrationPeriod(mockState) + expect(result).toBeTruthy() + }) + + it('should select can vest before migration correctly', () => { + const result = selectCanVestBeforeMigration(mockState) + expect(result).toBeFalsy() + }) + + it('should select staking V1 correctly with new selectors', () => { + const result = selectStakingV1(mockState) + expect(result).toBeTruthy() + }) +}) diff --git a/packages/app/src/state/staking/actions.ts b/packages/app/src/state/staking/actions.ts index 4903e7716..7ab8e9e9d 100644 --- a/packages/app/src/state/staking/actions.ts +++ b/packages/app/src/state/staking/actions.ts @@ -357,19 +357,23 @@ export const fetchClaimableRewards = createAsyncThunk 'staking/claimMultipleAllRewards', async (_, { dispatch, getState, extra: { sdk } }) => { const { - staking: { claimableKwentaRewards, claimableOpRewards, claimableSnxOpRewards }, + staking: { + claimableKwentaRewards, + claimableKwentaRewardsV2, + claimableOpRewards, + claimableSnxOpRewards, + }, } = getState() const { hash } = await sdk.kwentaToken.claimMultipleAllRewards([ claimableKwentaRewards, + claimableKwentaRewardsV2, claimableOpRewards, claimableSnxOpRewards, ]) diff --git a/packages/app/src/state/staking/reducer.ts b/packages/app/src/state/staking/reducer.ts index ec79ba7c7..589d0a4a1 100644 --- a/packages/app/src/state/staking/reducer.ts +++ b/packages/app/src/state/staking/reducer.ts @@ -80,6 +80,7 @@ export const ZERO_CLAIMABLE_REWARDS = { opRewards: '0', snxOpRewards: '0', claimableKwentaRewards: [], + claimableKwentaRewardsV2: [], claimableOpRewards: [], claimableSnxOpRewards: [], } @@ -200,6 +201,7 @@ const stakingSlice = createSlice({ }) builder.addCase(fetchClaimableRewards.fulfilled, (state, action) => { state.claimableKwentaRewards = action.payload.claimableKwentaRewards + state.claimableKwentaRewardsV2 = action.payload.claimableKwentaRewardsV2 state.claimableOpRewards = action.payload.claimableOpRewards state.claimableSnxOpRewards = action.payload.claimableSnxOpRewards state.kwentaRewards = action.payload.kwentaRewards diff --git a/packages/app/src/state/staking/selectors.ts b/packages/app/src/state/staking/selectors.ts index eee2e2d95..0165fc3bf 100644 --- a/packages/app/src/state/staking/selectors.ts +++ b/packages/app/src/state/staking/selectors.ts @@ -9,7 +9,6 @@ import { selectInMigrationPeriod, selectIsMigrationPeriodStarted, selectNeedEscrowMigratorApproval, - selectNumberOfRegisteredEntries, selectNumberOfUnmigratedRegisteredEntries, selectNumberOfUnregisteredEntries, selectNumberOfUnvestedRegisteredEntries, @@ -81,10 +80,7 @@ export const selectStakingMigrationRequired = createSelector( export const selectStakingV1 = createSelector( selectStakingMigrationRequired, selectStartMigration, - selectIsMigrationPeriodStarted, - selectInMigrationPeriod, - (stakingMigrationRequired, startMigration, isMigrationPeriodStarted, inMigrationPeriod) => - (stakingMigrationRequired || startMigration) && (isMigrationPeriodStarted || !inMigrationPeriod) + (stakingMigrationRequired, startMigration) => stakingMigrationRequired || startMigration ) export const selectKwentaBalance = createSelector( @@ -274,7 +270,7 @@ export const selectSelectedEpoch = createSelector( export const selectIsTimeLeftInCooldown = createSelector( selectStakedResetTime, - (stakedResetTime) => stakedResetTime > new Date().getTime() / 1000 + (stakedResetTime) => stakedResetTime > Date.now() / 1000 ) export const selectCanStakeKwenta = createSelector( @@ -389,9 +385,9 @@ export const selectIsClaimingAllRewards = createSelector( ) export const selectCanVestBeforeMigration = createSelector( - selectNumberOfRegisteredEntries, selectStakingV1, - (numberOfRegisteredEntries, stakingV1) => stakingV1 && numberOfRegisteredEntries === 0 + selectStartMigration, + (stakingV1, startMigration) => stakingV1 && !startMigration ) export const selectTradingRewardsSupportedNetwork = (state: RootState) => diff --git a/packages/app/src/state/staking/types.ts b/packages/app/src/state/staking/types.ts index 5f6602d31..6216726de 100644 --- a/packages/app/src/state/staking/types.ts +++ b/packages/app/src/state/staking/types.ts @@ -32,6 +32,7 @@ export type ClaimableRewards = { opRewards: string snxOpRewards: string claimableKwentaRewards: ClaimParams[] + claimableKwentaRewardsV2: ClaimParams[] claimableOpRewards: ClaimParams[] claimableSnxOpRewards: ClaimParams[] } diff --git a/packages/app/src/state/stakingMigration/actions.ts b/packages/app/src/state/stakingMigration/actions.ts index 493d2341a..bca5e8471 100644 --- a/packages/app/src/state/stakingMigration/actions.ts +++ b/packages/app/src/state/stakingMigration/actions.ts @@ -83,7 +83,7 @@ export const migrateEntries = createAsyncThunk( const supportedNetwork = selectStakingSupportedNetwork(getState()) if (!supportedNetwork) throw new Error( - 'Registering entries is unsupported on this network. Please switch to Optimism.' + 'Migrating entries is unsupported on this network. Please switch to Optimism.' ) try { diff --git a/packages/app/src/state/stakingMigration/selectors.ts b/packages/app/src/state/stakingMigration/selectors.ts index e0ef2aa77..b399213f7 100644 --- a/packages/app/src/state/stakingMigration/selectors.ts +++ b/packages/app/src/state/stakingMigration/selectors.ts @@ -101,7 +101,7 @@ export const selectMigrationSecondsLeft = createSelector( selectMigrationStartTime, selectMigrationDeadline, (migrationStartTime, migrationDeadline) => { - return migrationStartTime > 0 ? Math.ceil(migrationDeadline - new Date().getTime() / 1000) : 0 + return migrationStartTime > 0 ? Math.ceil(migrationDeadline - Date.now() / 1000) : 0 } ) @@ -110,11 +110,6 @@ export const selectInMigrationPeriod = createSelector( (migrationSecondsLeft) => migrationSecondsLeft > 0 ) -export const selectStartMigration = createSelector( - selectNumberOfUnmigratedRegisteredEntries, - (numberOfUnmigratedEntries) => numberOfUnmigratedEntries > 0 -) - const selectRegisteredVestingEntryIDs = createSelector( selectWallet, selectSelectedUserMigrationInfo, @@ -172,7 +167,7 @@ export const selectTotalEscrowUnmigrated = createSelector( } ) -export const selectRedirectToMigration = createSelector( +export const selectStartMigration = createSelector( selectTotalEscrowUnmigrated, selectInMigrationPeriod, (totalEscrowUnmigrated, inMigrationPeriod) => totalEscrowUnmigrated.gt(0) && inMigrationPeriod diff --git a/packages/app/src/translations/en.json b/packages/app/src/translations/en.json index 0f54b9539..f852d8ca4 100644 --- a/packages/app/src/translations/en.json +++ b/packages/app/src/translations/en.json @@ -1272,6 +1272,7 @@ "total-volume": "Total Trading Volume", "kwenta-earned": "Total $KWENTA Earned", "claim": "Claim", + "staking": "Staking ↗", "level-up": "Level Up", "dashboard": { "rewards-boost": "Rewards Boost", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index de5acc6d8..e6d05dab4 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@kwenta/sdk", - "version": "1.0.10", + "version": "1.0.11", "description": "SDK for headless interaction with Kwenta", "main": "dist/index.js", "directories": { diff --git a/packages/sdk/src/constants/futures.ts b/packages/sdk/src/constants/futures.ts index d9cb4b042..5645ad7e3 100644 --- a/packages/sdk/src/constants/futures.ts +++ b/packages/sdk/src/constants/futures.ts @@ -809,7 +809,7 @@ export const MARKETS: Record = { [FuturesMarketKey.sSTETHETHPERP]: { key: FuturesMarketKey.sSTETHETHPERP, asset: FuturesMarketAsset.STETHETH, - supports: 'testnet', + supports: 'both', version: 2, pythIds: { mainnet: '0x3af6a3098c56f58ff47cc46dee4a5b1910e5c157f7f0b665952445867470d61f', diff --git a/packages/sdk/src/constants/staking.ts b/packages/sdk/src/constants/staking.ts index 58a5ca0b4..cd3a9675d 100644 --- a/packages/sdk/src/constants/staking.ts +++ b/packages/sdk/src/constants/staking.ts @@ -17,6 +17,8 @@ export const TRADING_REWARDS_RATIO = 0.05 export const STAKING_HIGH_GAS_LIMIT = BigNumber.from('400000') export const STAKING_LOW_GAS_LIMIT = BigNumber.from('200000') export const TRADING_REWARDS_CUTOFF_EPOCH = 13 +export const STAKING_V2_REWARDS_CUTOFF_EPOCH = 44 export const OP_REWARDS_CUTOFF_EPOCH = 22 +export const REFERRAL_PROGRAM_START_EPOCH = 44 export const SUPPLY_RATE = wei(1).sub(wei(DECAY_RATE)) diff --git a/packages/sdk/src/contracts/constants.ts b/packages/sdk/src/contracts/constants.ts index 28a78672c..35ec218f2 100644 --- a/packages/sdk/src/contracts/constants.ts +++ b/packages/sdk/src/contracts/constants.ts @@ -152,6 +152,9 @@ export const ADDRESSES: Record> = { TradingRewardsPerpsV2: { 10: '0x2787CC20e5ECb4BF1bfB79eAE284201027683179', }, + TradingRewardsStakingV2: { + 10: '0x195f6F7ca2268e1DEC03352786C350Eb61aBa307', + }, OpRewards: { 10: '0x1964cF9d0c5c268DcF5a5d37F13600483237f6F5', }, diff --git a/packages/sdk/src/contracts/index.ts b/packages/sdk/src/contracts/index.ts index f5bc645c1..a079d63cf 100644 --- a/packages/sdk/src/contracts/index.ts +++ b/packages/sdk/src/contracts/index.ts @@ -172,6 +172,12 @@ export const getContractsByNetwork = ( provider ) : undefined, + MultipleMerkleDistributorStakingV2: ADDRESSES.TradingRewardsStakingV2[networkId] + ? MultipleMerkleDistributorPerpsV2__factory.connect( + ADDRESSES.TradingRewardsStakingV2[networkId], + provider + ) + : undefined, MultipleMerkleDistributorOp: ADDRESSES.OpRewards[networkId] ? MultipleMerkleDistributorOp__factory.connect(ADDRESSES.OpRewards[networkId], provider) : undefined, @@ -264,6 +270,12 @@ export const getMulticallContractsByNetwork = (networkId: NetworkId) => { MultipleMerkleDistributorPerpsV2ABI ) : undefined, + MultipleMerkleDistributorStakingV2: ADDRESSES.TradingRewardsStakingV2[networkId] + ? new EthCallContract( + ADDRESSES.TradingRewardsStakingV2[networkId], + MultipleMerkleDistributorPerpsV2ABI + ) + : undefined, MultipleMerkleDistributorOp: ADDRESSES.OpRewards[networkId] ? new EthCallContract(ADDRESSES.OpRewards[networkId], MultipleMerkleDistributorOpABI) : undefined, @@ -306,6 +318,21 @@ export const getMulticallContractsByNetwork = (networkId: NetworkId) => { BoostNft: ADDRESSES.BoostNft[networkId] ? new EthCallContract(ADDRESSES.BoostNft[networkId], BoostNftABI) : undefined, + SUSD: ADDRESSES.SUSD[networkId] + ? new EthCallContract(ADDRESSES.SUSD[networkId], ERC20ABI) + : undefined, + USDC: ADDRESSES.USDC[networkId] + ? new EthCallContract(ADDRESSES.USDC[networkId], ERC20ABI) + : undefined, + USDT: ADDRESSES.USDT[networkId] + ? new EthCallContract(ADDRESSES.USDT[networkId], ERC20ABI) + : undefined, + DAI: ADDRESSES.DAI[networkId] + ? new EthCallContract(ADDRESSES.DAI[networkId], ERC20ABI) + : undefined, + LUSD: ADDRESSES.LUSD[networkId] + ? new EthCallContract(ADDRESSES.LUSD[networkId], ERC20ABI) + : undefined, } } diff --git a/packages/sdk/src/queries/futures.ts b/packages/sdk/src/queries/futures.ts index a2c6032f8..2f642d81e 100644 --- a/packages/sdk/src/queries/futures.ts +++ b/packages/sdk/src/queries/futures.ts @@ -7,7 +7,7 @@ import { ISOLATED_MARGIN_FRAGMENT, DEFAULT_NUMBER_OF_TRADES, } from '../constants/futures' -import { FuturesMarketAsset, FuturesMarketKey, FuturesTradeByReferral } from '../types/futures' +import { FuturesMarketAsset, FuturesMarketKey } from '../types/futures' import { mapMarginTransfers, mapSmartMarginTransfers } from '../utils/futures' import { FuturesAccountType, getFuturesPositions, getFuturesTrades } from '../utils/subgraph' @@ -225,53 +225,3 @@ export const queryFundingRateHistory = async ( fundingRate: Number(x.fundingRate), })) } - -export const queryVolumeByTrader = async ( - sdk: KwentaSDK, - trader: string, - mintedTime: string -): Promise => { - let queryResponseCount = 0 - let lastMintedAtInSeconds = Math.floor(Number(mintedTime)) - const currentTimeInSeconds = Math.floor(new Date().getTime() / 1000) - const futuresTrades: FuturesTradeByReferral[] = [] - - do { - const response: { futuresTrades: FuturesTradeByReferral[] } = await request( - sdk.futures.futuresGqlEndpoint, - gql` - query futuresTrades($minTimestamp: BigInt!, $maxTimestamp: BigInt!, $account: String!) { - futuresTrades( - where: { - timestamp_gt: $minTimestamp - timestamp_lte: $maxTimestamp - trackingCode: "0x4b57454e54410000000000000000000000000000000000000000000000000000" - account: $account - } - orderBy: timestamp - orderDirection: asc - first: 1000 - ) { - timestamp - account - size - price - } - } - `, - { - minTimestamp: lastMintedAtInSeconds, - maxTimestamp: currentTimeInSeconds, - account: trader, - } - ) - - queryResponseCount = response.futuresTrades.length - if (queryResponseCount > 0) { - lastMintedAtInSeconds = Number(response.futuresTrades[queryResponseCount - 1].timestamp) - futuresTrades.push(...response.futuresTrades) - } - } while (queryResponseCount === 1000) - - return futuresTrades -} diff --git a/packages/sdk/src/queries/referrals.ts b/packages/sdk/src/queries/referrals.ts index 8b7ba3c32..28b35e6f2 100644 --- a/packages/sdk/src/queries/referrals.ts +++ b/packages/sdk/src/queries/referrals.ts @@ -57,19 +57,26 @@ export const queryCodesByReferrer = async ( return response?.boostReferrers.map(({ id }) => parseBytes32String(id)) || [] } -export const queryTradersByCode = async (sdk: KwentaSDK, code: string): Promise => { +export const queryTradersByCode = async ( + sdk: KwentaSDK, + code: string, + epochStart?: number, + epochEnd?: number +): Promise => { let queryResponseCount = 0 const currentTimeInSeconds = Math.floor(new Date().getTime() / 1000) + const end = epochEnd ? Math.min(currentTimeInSeconds, epochEnd) : currentTimeInSeconds const boostHolders: BoostHolder[] = [] - let maxTimestamp = currentTimeInSeconds + const minTimestamp = epochStart || 0 + let maxTimestamp = end do { const response: { boostHolders: BoostHolder[] } = await request( sdk.referrals.referralsGqlEndpoint, gql` - query boostHolders($code: String!, $maxTimestamp: BigInt!) { + query boostHolders($code: String!, $minTimestamp: BigInt!, $maxTimestamp: BigInt!) { boostHolders( - where: { lastMintedAt_lte: $maxTimestamp, code: $code } + where: { lastMintedAt_lte: $maxTimestamp, lastMintedAt_gt: $minTimestamp, code: $code } orderBy: lastMintedAt orderDirection: desc first: 1000 @@ -79,7 +86,7 @@ export const queryTradersByCode = async (sdk: KwentaSDK, code: string): Promise< } } `, - { code: formatBytes32String(code), maxTimestamp } + { code: formatBytes32String(code), maxTimestamp, minTimestamp } ) queryResponseCount = response.boostHolders.length diff --git a/packages/sdk/src/queries/utils.ts b/packages/sdk/src/queries/utils.ts deleted file mode 100644 index e5916d79e..000000000 --- a/packages/sdk/src/queries/utils.ts +++ /dev/null @@ -1,25 +0,0 @@ -export async function limitConcurrency( - tasks: T[], - handler: (task: T) => Promise, - limit: number -): Promise { - const results: R[] = [] - const taskQueue: T[] = [...tasks] - - const executeTask = async () => { - if (taskQueue.length === 0) return - - const task = taskQueue.pop() - if (task !== undefined) { - const result = await handler(task) - results.push(result) - } - - await executeTask() - } - - const initialPromises = Array.from({ length: limit }).map(() => executeTask()) - await Promise.all(initialPromises) - - return results -} diff --git a/packages/sdk/src/services/futures.ts b/packages/sdk/src/services/futures.ts index 15e1c871a..22a367e5c 100644 --- a/packages/sdk/src/services/futures.ts +++ b/packages/sdk/src/services/futures.ts @@ -516,39 +516,43 @@ export default class FuturesService { smartMarginAddress, this.sdk.context.provider ) - const { SUSD, USDC, USDT, DAI, LUSD } = this.sdk.context.contracts + + const { SUSD, USDC, USDT, DAI, LUSD } = this.sdk.context.multicallContracts if (!SUSD || !USDC || !USDT || !DAI || !LUSD) throw new Error(UNSUPPORTED_NETWORK) - // TODO: EthCall const [ freeMargin, - keeperEthBal, - walletEthBal, - susdBalance, - allowance, - usdcBalance, - usdcAllowance, - // usdtBalance, - // usdtAllowance, - daiBalance, - daiAllowance, - // lusdBalance, - // lusdAllowance, + [ + keeperEthBal, + walletEthBal, + susdBalance, + allowance, + usdcBalance, + usdcAllowance, + // usdtBalance, + // usdtAllowance, + daiBalance, + daiAllowance, + // lusdBalance, + // lusdAllowance, + ], ] = await Promise.all([ smartMarginAccountContract.freeMargin(), - this.sdk.context.provider.getBalance(smartMarginAddress), - this.sdk.context.provider.getBalance(walletAddress), - SUSD.balanceOf(walletAddress), - SUSD.allowance(walletAddress, smartMarginAccountContract.address), - USDC.balanceOf(walletAddress), - USDC.allowance(walletAddress, PERMIT2_ADDRESS), - // USDT.balanceOf(walletAddress), - // USDT.allowance(walletAddress, PERMIT2_ADDRESS), - DAI.balanceOf(walletAddress), - DAI.allowance(walletAddress, PERMIT2_ADDRESS), - // LUSD.balanceOf(walletAddress), - // LUSD.allowance(walletAddress, PERMIT2_ADDRESS), + this.sdk.context.multicallProvider.all([ + this.sdk.context.multicallProvider.getEthBalance(smartMarginAddress), + this.sdk.context.multicallProvider.getEthBalance(walletAddress), + SUSD.balanceOf(walletAddress), + SUSD.allowance(walletAddress, smartMarginAccountContract.address), + USDC.balanceOf(walletAddress), + USDC.allowance(walletAddress, PERMIT2_ADDRESS), + // USDT.balanceOf(walletAddress), + // USDT.allowance(walletAddress, PERMIT2_ADDRESS), + DAI.balanceOf(walletAddress), + DAI.allowance(walletAddress, PERMIT2_ADDRESS), + // LUSD.balanceOf(walletAddress), + // LUSD.allowance(walletAddress, PERMIT2_ADDRESS), + ]), ]) return { diff --git a/packages/sdk/src/services/kwentaToken.ts b/packages/sdk/src/services/kwentaToken.ts index 2091e501a..8de56c1a7 100644 --- a/packages/sdk/src/services/kwentaToken.ts +++ b/packages/sdk/src/services/kwentaToken.ts @@ -13,7 +13,9 @@ import { DEFAULT_NUMBER_OF_FUTURES_FEE, EPOCH_START, OP_REWARDS_CUTOFF_EPOCH, + STAKING_V2_REWARDS_CUTOFF_EPOCH, TRADING_REWARDS_CUTOFF_EPOCH, + REFERRAL_PROGRAM_START_EPOCH, WEEK, } from '../constants/staking' import { ContractName } from '../contracts' @@ -583,12 +585,14 @@ export default class KwentaTokenService { public async getClaimableAllRewards( epochPeriod: number, + isStakingV2: boolean = false, isOp: boolean = false, isSnx: boolean = false, cutoffPeriod: number = 0 ) { const { MultipleMerkleDistributorPerpsV2, + MultipleMerkleDistributorStakingV2, MultipleMerkleDistributorOp, MultipleMerkleDistributorSnxOp, } = this.sdk.context.multicallContracts @@ -596,6 +600,7 @@ export default class KwentaTokenService { if ( !MultipleMerkleDistributorPerpsV2 || + !MultipleMerkleDistributorStakingV2 || !MultipleMerkleDistributorOp || !MultipleMerkleDistributorSnxOp ) { @@ -606,7 +611,9 @@ export default class KwentaTokenService { const adjustedPeriods = isOp ? periods.slice(OP_REWARDS_CUTOFF_EPOCH) - : periods.slice(TRADING_REWARDS_CUTOFF_EPOCH) + : isStakingV2 + ? periods.slice(STAKING_V2_REWARDS_CUTOFF_EPOCH) + : periods.slice(TRADING_REWARDS_CUTOFF_EPOCH, STAKING_V2_REWARDS_CUTOFF_EPOCH) const fileNames = adjustedPeriods.map( (i) => @@ -624,6 +631,8 @@ export default class KwentaTokenService { ? isSnx ? index : index + OP_REWARDS_CUTOFF_EPOCH + : isStakingV2 + ? index + STAKING_V2_REWARDS_CUTOFF_EPOCH : index + TRADING_REWARDS_CUTOFF_EPOCH return { ...response.data, period } } catch (err) { @@ -637,11 +646,9 @@ export default class KwentaTokenService { .filter(Boolean) .map((d) => { const reward = d.claims[walletAddress] - if (reward) { return [reward.index, walletAddress, reward.amount, reward.proof, d.period] } - return null }) .filter((x): x is ClaimParams => !!x) @@ -652,6 +659,8 @@ export default class KwentaTokenService { ? isSnx ? MultipleMerkleDistributorSnxOp.isClaimed(reward[0], reward[4]) : MultipleMerkleDistributorOp.isClaimed(reward[0], reward[4]) + : isStakingV2 + ? MultipleMerkleDistributorStakingV2.isClaimed(reward[0], reward[4]) : MultipleMerkleDistributorPerpsV2.isClaimed(reward[0], reward[4]) ) ) @@ -671,6 +680,65 @@ export default class KwentaTokenService { return { claimableRewards, totalRewards } } + public async getKwentaRewardsByEpoch(epochPeriod: number) { + const { walletAddress } = this.sdk.context + + const fileName = `${ + this.sdk.context.networkId === 420 ? `goerli-` : '' + }epoch-${epochPeriod}.json` + + try { + const response = await awsClient.get(fileName) + const rewards = response.data.claims[walletAddress] + return rewards ? weiFromWei(rewards.amount) : ZERO_WEI + } catch (err) { + this.sdk.context.logError(err) + return ZERO_WEI + } + } + + public async getKwentaRewardsByTraders(epochPeriod: number, traders: string[]) { + const periods = Array.from(new Array(Number(epochPeriod)), (_, i) => i) + const adjustedPeriods = periods.slice(REFERRAL_PROGRAM_START_EPOCH) + const fileNames = adjustedPeriods.map( + (i) => `${this.sdk.context.networkId === 420 ? `goerli-` : ''}epoch-${i}.json` + ) + + try { + const responses: EpochData[] = await Promise.all( + fileNames.map(async (fileName) => { + try { + const response = await awsClient.get(fileName) + return { ...response.data } + } catch (err) { + this.sdk.context.logError(err) + return null + } + }) + ) + + const rewards = traders.map((walletAddress) => { + const lowerCaseWalletAddress = walletAddress.toLowerCase() + return responses + .filter(Boolean) + .map(({ claims }) => { + const lowerCaseClaims = Object.fromEntries( + Object.entries(claims).map(([key, value]) => [key.toLowerCase(), value]) + ) + const reward = lowerCaseClaims[lowerCaseWalletAddress] + return reward ? reward.amount : '0' + }) + .reduce((acc, amount) => (amount ? acc.add(weiFromWei(amount)) : acc), ZERO_WEI) + }) + return rewards + .flat() + .reduce((total, next) => (next ? total.add(weiFromWei(next)) : total), ZERO_WEI) + } catch (err) { + this.sdk.context.logError(err) + return ZERO_WEI + } + } + public async claimKwentaRewards(claimableRewards: ClaimParams[]) { const { MultipleMerkleDistributorPerpsV2 } = this.sdk.context.contracts @@ -689,6 +757,7 @@ export default class KwentaTokenService { const { BatchClaimer, MultipleMerkleDistributorPerpsV2, + MultipleMerkleDistributorStakingV2, MultipleMerkleDistributorOp, MultipleMerkleDistributorSnxOp, } = this.sdk.context.contracts @@ -696,6 +765,7 @@ export default class KwentaTokenService { if ( !BatchClaimer || !MultipleMerkleDistributorPerpsV2 || + !MultipleMerkleDistributorStakingV2 || !MultipleMerkleDistributorOp || !MultipleMerkleDistributorSnxOp ) { @@ -705,6 +775,7 @@ export default class KwentaTokenService { return this.sdk.transactions.createContractTxn(BatchClaimer, 'claimMultiple', [ [ MultipleMerkleDistributorPerpsV2.address, + MultipleMerkleDistributorStakingV2.address, MultipleMerkleDistributorOp.address, MultipleMerkleDistributorSnxOp.address, ], diff --git a/packages/sdk/src/services/referrals.ts b/packages/sdk/src/services/referrals.ts index 26a5e82ba..d18158aef 100644 --- a/packages/sdk/src/services/referrals.ts +++ b/packages/sdk/src/services/referrals.ts @@ -1,7 +1,12 @@ import { formatBytes32String } from '@ethersproject/strings' import KwentaSDK from '..' import * as sdkErrors from '../common/errors' -import { getReferralStatisticsByAccount, getReferralsGqlEndpoint } from '../utils/referrals' +import { + getReferralStatisticsByAccount, + getReferralStatisticsByAccountAndEpoch, + getReferralStatisticsByAccountAndEpochTime, + getReferralsGqlEndpoint, +} from '../utils/referrals' import { queryBoostNftTierByHolder, queryCodesByReferrer, @@ -168,4 +173,31 @@ export default class ReferralsService { } return getReferralStatisticsByAccount(this.sdk, account) } + + /** + * Retrieve the cumulative statistics for a given referrer. + * @param account - The account of the referrer. + * @param period - The epoch period. + * @returns Object containing total referrerd account and total referral volumes per code + */ + public getCumulativeStatsByReferrerAndEpoch(account: string, period: number) { + if (!this.sdk.context.contracts.BoostNft) { + throw new Error(sdkErrors.UNSUPPORTED_NETWORK) + } + return getReferralStatisticsByAccountAndEpoch(this.sdk, account, period) + } + + /** + * Retrieve the cumulative statistics for a given referrer. + * @param account - The account of the referrer. + * @param start - The start epoch timestamp. + * @param end - The end epoch timestamp. + * @returns Object containing total referrerd account and total referral volumes per code + */ + public getCumulativeStatsByReferrerAndEpochTime(account: string, start: number, end: number) { + if (!this.sdk.context.contracts.BoostNft) { + throw new Error(sdkErrors.UNSUPPORTED_NETWORK) + } + return getReferralStatisticsByAccountAndEpochTime(this.sdk, account, start, end) + } } diff --git a/packages/sdk/src/utils/futures.ts b/packages/sdk/src/utils/futures.ts index ba69aba00..565d456df 100644 --- a/packages/sdk/src/utils/futures.ts +++ b/packages/sdk/src/utils/futures.ts @@ -140,12 +140,14 @@ export const marketsForNetwork = (networkId: number, logError: IContext['logErro export const getMarketName = (asset: FuturesMarketAsset | null) => { if (asset === 'ETHBTC') return 'ETH/BTC' + if (asset === 'STETHETH') return 'stETH/ETH' return `${getDisplayAsset(asset)}/sUSD` } export const getDisplayAsset = (asset: string | null) => { if (!asset) return null if (asset === 'STETH') return 'stETH' + if (asset === 'STETHETH') return 'stETH/ETH' return asset[0] === 's' ? asset.slice(1) : asset } diff --git a/packages/sdk/src/utils/number.ts b/packages/sdk/src/utils/number.ts index e542c740c..398b83a2a 100644 --- a/packages/sdk/src/utils/number.ts +++ b/packages/sdk/src/utils/number.ts @@ -12,6 +12,7 @@ import { } from '../constants/number' import { isFiatCurrency } from './exchange' +import { FuturesMarketAsset } from '../types' export type TruncateUnits = 1e3 | 1e6 | 1e9 | 1e12 @@ -40,6 +41,7 @@ export type FormatNumberOptions = { prefix?: string suffix?: string suggestDecimals?: boolean + suggestDecimalsForAsset?: FuturesMarketAsset } & TruncatedOptions export type FormatCurrencyOptions = { @@ -48,6 +50,7 @@ export type FormatCurrencyOptions = { sign?: string currencyKey?: string suggestDecimals?: boolean + suggestDecimalsForAsset?: FuturesMarketAsset } & TruncatedOptions export const SHORT_CRYPTO_CURRENCY_DECIMALS = 4 @@ -63,6 +66,10 @@ export const truncateNumbers = (value: WeiSource, maxDecimalDigits: number) => { return value.toString() } +const DecimalsForAsset: Partial> = { + [FuturesMarketAsset.STETHETH]: 8, +} + /** * ethers utils.commify method will reduce the decimals of a number to one digit if those decimals are zero. * This helper is used to reverse this behavior in order to display the specified decimals in the output. @@ -86,11 +93,20 @@ export const commifyAndPadDecimals = (value: string, decimals: number) => { return formatted } +const getDecimalsForFormatting = (value: Wei, options?: FormatNumberOptions) => { + if (options?.truncation) return options?.truncation.decimals + if (options?.suggestDecimalsForAsset) { + const decimals = DecimalsForAsset[options.suggestDecimalsForAsset] + return decimals ?? suggestedDecimals(value) + } + if (options?.suggestDecimals) return suggestedDecimals(value) + return options?.minDecimals ?? DEFAULT_NUMBER_DECIMALS +} + export const formatNumber = (value: WeiSource, options?: FormatNumberOptions) => { const prefix = options?.prefix const suffix = options?.suffix const truncateThreshold = options?.truncateOver ?? 0 - const suggestDecimals = options?.suggestDecimals let truncation = options?.truncation let weiValue = wei(0) @@ -120,11 +136,7 @@ export const formatNumber = (value: WeiSource, options?: FormatNumberOptions) => const weiBeforeAsString = truncation ? weiValue.abs().div(truncation.divisor) : weiValue.abs() - const defaultDecimals = truncation - ? truncation.decimals - : suggestDecimals - ? suggestedDecimals(weiBeforeAsString) - : options?.minDecimals ?? DEFAULT_NUMBER_DECIMALS + const defaultDecimals = getDecimalsForFormatting(weiBeforeAsString, { ...options, truncation }) const decimals = options?.maxDecimals ? Math.min(defaultDecimals, options.maxDecimals) @@ -150,8 +162,7 @@ export const formatCryptoCurrency = (value: WeiSource, options?: FormatCurrencyO prefix: options?.sign, suffix: options?.currencyKey, minDecimals: options?.minDecimals ?? DEFAULT_CRYPTO_DECIMALS, - maxDecimals: options?.maxDecimals, - suggestDecimals: options?.suggestDecimals, + ...options, }) export const formatFiatCurrency = (value: WeiSource, options?: FormatCurrencyOptions) => diff --git a/packages/sdk/src/utils/referrals.ts b/packages/sdk/src/utils/referrals.ts index 2508d0f96..7c7bdf006 100644 --- a/packages/sdk/src/utils/referrals.ts +++ b/packages/sdk/src/utils/referrals.ts @@ -1,10 +1,10 @@ +import request, { gql } from 'graphql-request' import KwentaSDK from '..' import { REFERRALS_ENDPOINTS } from '../constants/referrals' -import { queryVolumeByTrader } from '../queries/futures' import { queryCodesByReferrer, queryTradersByCode } from '../queries/referrals' -import { limitConcurrency } from '../queries/utils' import { FuturesTradeByReferral } from '../types/futures' import { ReferralCumulativeStats } from '../types/referrals' +import { ZERO_WEI } from '../constants' const calculateTraderVolume = (futuresTrades: FuturesTradeByReferral[]) => { return futuresTrades.reduce((acc, trade) => { @@ -16,22 +16,89 @@ export const getReferralsGqlEndpoint = (networkId: number) => { return REFERRALS_ENDPOINTS[networkId] || REFERRALS_ENDPOINTS[10] } -export const getReferralStatisticsByAccount = async ( +const getCumulativeStatsByCode = async ( sdk: KwentaSDK, - account: string + account: string, + epochStart?: number, + epochEnd?: number, + epochPeriod?: number ): Promise => { const codes = await queryCodesByReferrer(sdk, account) - - return await Promise.all( + const currentTimeInSeconds = Math.floor(new Date().getTime() / 1000) + return Promise.all( codes.map(async (code) => { - const traders = await queryTradersByCode(sdk, code) + const traders = await queryTradersByCode(sdk, code, epochStart, epochEnd) + const totalRewards = epochPeriod + ? await sdk.kwentaToken.getKwentaRewardsByTraders( + epochPeriod, + traders.map(({ id }) => id) + ) + : ZERO_WEI + + const traderVolumeQueries = await Promise.all( + traders.map(({ id, lastMintedAt }) => { + const start = epochStart ? Math.max(Number(lastMintedAt), epochStart) : lastMintedAt + const end = epochEnd ? Math.min(currentTimeInSeconds, epochEnd) : currentTimeInSeconds + return gql` + user_${id}: futuresTrades( + first: 1000 + where: { + timestamp_gt: ${start} + timestamp_lte: ${end} + trackingCode: "0x4b57454e54410000000000000000000000000000000000000000000000000000" + account: "${id}" + } + orderBy: timestamp + orderDirection: asc + ) { + timestamp + account + size + price + } + ` + }) + ) + + const response: Record = await request( + sdk.futures.futuresGqlEndpoint, + gql` + query totalFuturesTrades { + ${traderVolumeQueries.join('')} + } + ` + ) + + const totalTrades = response ? Object.values(response).flat() : [] + const totalVolume = calculateTraderVolume(totalTrades) return { code, referredCount: traders.length.toString(), - referralVolume: '0', - earnedRewards: '0', + referralVolume: totalVolume.toString(), + earnedRewards: totalRewards.toString(), } }) ) } + +export const getReferralStatisticsByAccount = (sdk: KwentaSDK, account: string) => { + return getCumulativeStatsByCode(sdk, account) +} + +export const getReferralStatisticsByAccountAndEpochTime = ( + sdk: KwentaSDK, + account: string, + epochStart: number, + epochEnd: number +) => { + return getCumulativeStatsByCode(sdk, account, epochStart, epochEnd) +} + +export const getReferralStatisticsByAccountAndEpoch = ( + sdk: KwentaSDK, + account: string, + epochPeriod?: number +) => { + return getCumulativeStatsByCode(sdk, account, undefined, undefined, epochPeriod) +}