diff --git a/apps/web/package.json b/apps/web/package.json index 91d459399457a..5c2497ccdddaa 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -31,9 +31,6 @@ "not op_mini all" ] }, - "engines": { - "node": ">=16.0.0" - }, "dependencies": { "@binance/w3w-utils": "1.1.4", "@binance/w3w-wagmi-connector-v2": "^1.2.3", diff --git a/apps/web/src/components/AdPanel/Ads/AdSolv.tsx b/apps/web/src/components/AdPanel/Ads/AdSolv.tsx new file mode 100644 index 0000000000000..bb263b2a14a64 --- /dev/null +++ b/apps/web/src/components/AdPanel/Ads/AdSolv.tsx @@ -0,0 +1,33 @@ +import { useTranslation } from '@pancakeswap/localization' +import { BodyText } from '../BodyText' +import { AdButton } from '../Button' +import { AdCard } from '../Card' +import { Countdown } from '../Countdown' +import { AdPlayerProps } from '../types' +import { getImageUrl } from '../utils' + +export const AdSolv = (props: AdPlayerProps) => { + const { t } = useTranslation() + + return ( + + + {t('%token% IFO starts in', { + token: 'SOLV', + })} + + + + + + {t('Get Started')} + + + ) +} diff --git a/apps/web/src/components/AdPanel/config.tsx b/apps/web/src/components/AdPanel/config.tsx index f182c753c39ba..6b2b03a5674ee 100644 --- a/apps/web/src/components/AdPanel/config.tsx +++ b/apps/web/src/components/AdPanel/config.tsx @@ -1,11 +1,10 @@ import { useMatchBreakpoints } from '@pancakeswap/uikit' import { useMemo } from 'react' import { AdCakeStaking } from './Ads/AdCakeStaking' -import { AdOptionsTrading } from './Ads/AdOptionsTrading' import { AdPCSX } from './Ads/AdPCSX' -import { AdRocker } from './Ads/AdRocker' +import { AdSolv } from './Ads/AdSolv' import { AdSpringboard } from './Ads/AdSpringboard' -import { AdTradingCompetitionBfg, AdTradingCompetitionVinu } from './Ads/AdTradingCompetition' +import { AdTradingCompetitionVinu } from './Ads/AdTradingCompetition' import { ExpandableAd } from './Expandable/ExpandableAd' import { shouldRenderOnPages } from './renderConditions' @@ -45,18 +44,9 @@ export const useAdConfig = () => { component: , }, { - id: 'ad-bfg-tc', - component: , + id: 'ad-ifo-solv', + component: , }, - - // { - // id: 'ad-mev', - // component: , - // }, - // { - // id: 'prediction-telegram-bot', - // component: , - // }, { id: 'pcsx', component: , @@ -65,15 +55,6 @@ export const useAdConfig = () => { id: 'cake-staking', component: , }, - { - id: 'clamm-options-trading', - component: , - }, - - { - id: 'rocker-meme-career', - component: , - }, ], [shouldRenderOnPage], ) diff --git a/apps/web/src/components/Menu/config/config.ts b/apps/web/src/components/Menu/config/config.ts index c4dbd23b27634..ec492386f9603 100644 --- a/apps/web/src/components/Menu/config/config.ts +++ b/apps/web/src/components/Menu/config/config.ts @@ -164,7 +164,6 @@ const config: ( ], items: [ { - status: { text: t('New'), color: 'success' }, label: t('Springboard'), href: 'https://springboard.pancakeswap.finance', type: DropdownMenuItemType.EXTERNAL_LINK, @@ -201,6 +200,7 @@ const config: ( label: t('IFO'), href: '/ifo', image: '/images/ifos/ifo-bunny.png', + status: { text: t('SOON'), color: 'warning' }, overrideSubNavItems: [ { label: t('Latest'), diff --git a/apps/web/src/components/PhishingWarningBanner/SolvStrip.tsx b/apps/web/src/components/PhishingWarningBanner/SolvStrip.tsx new file mode 100644 index 0000000000000..3bf226101e3a6 --- /dev/null +++ b/apps/web/src/components/PhishingWarningBanner/SolvStrip.tsx @@ -0,0 +1,62 @@ +import { useTranslation } from '@pancakeswap/localization' +import { Box, Link, Text } from '@pancakeswap/uikit' +import { VerticalDivider } from '@pancakeswap/widgets-internal' + +const TextHighlight = ({ text, highlights }: { text: string; highlights: string[] }) => { + const prts = text.split(new RegExp(`(${highlights.join('|')})`, 'g')) + return prts.map((prt, i) => { + const key = `${prt}-${i}` + if (highlights.includes(prt)) { + return ( + + {prt} + + ) + } + return ( + + {prt} + + ) + }) +} +export const SolvStrip = () => { + const { t } = useTranslation() + + return ( + + {' '} + + {t('Join Now')} + + + + {t('Learn More')} + + + ) +} diff --git a/apps/web/src/components/PhishingWarningBanner/index.tsx b/apps/web/src/components/PhishingWarningBanner/index.tsx index 052990fb7cc26..092e084a0c814 100644 --- a/apps/web/src/components/PhishingWarningBanner/index.tsx +++ b/apps/web/src/components/PhishingWarningBanner/index.tsx @@ -8,10 +8,11 @@ import 'swiper/css/effect-fade' import { ASSET_CDN } from 'config/constants/endpoints' import { Countdown } from './Countdown' +import { SolvStrip } from './SolvStrip' import { Step1 } from './Step1' import { Step2 } from './Step2' import { Step3 } from './Step3' -import { TradingCompetitionBfg, TradingCompetitionVinu } from './TradingCompetition' +import { TradingCompetitionVinu } from './TradingCompetition' const Container = styled(Flex).withConfig({ shouldForwardProp: (prop) => !['$background'].includes(prop) })<{ $background?: string @@ -99,6 +100,12 @@ type BannerConfig = { } const CONFIG: BannerConfig[] = [ + { + component: SolvStrip, + stripeImage: `${ASSET_CDN}/web/phishing-warning/solv.png?v=1`, + stripeImageWidth: '92px', + stripeImageAlt: 'SOLV IFO', + }, { component: Step1, stripeImage: `${ASSET_CDN}/web/phishing-warning/phishing-warning-bunny-1.png`, @@ -117,12 +124,6 @@ const CONFIG: BannerConfig[] = [ stripeImageWidth: '92px', stripeImageAlt: 'PCSX', }, - { - component: TradingCompetitionBfg, - stripeImage: `${ASSET_CDN}/web/promotions/bfg_competition.png`, - stripeImageWidth: '92px', - stripeImageAlt: 'bfg_competition', - }, { component: TradingCompetitionVinu, stripeImage: `${ASSET_CDN}/web/promotions/vinu_competition.png`, diff --git a/apps/web/src/views/Ifos/components/IfoFoldableCard/IfoPoolCard/IfoCardDetails.tsx b/apps/web/src/views/Ifos/components/IfoFoldableCard/IfoPoolCard/IfoCardDetails.tsx index d51e1b0eac374..6fae3ddb0f81a 100644 --- a/apps/web/src/views/Ifos/components/IfoFoldableCard/IfoPoolCard/IfoCardDetails.tsx +++ b/apps/web/src/views/Ifos/components/IfoFoldableCard/IfoPoolCard/IfoCardDetails.tsx @@ -4,7 +4,6 @@ import { CAKE } from '@pancakeswap/tokens' import { Box, Flex, IfoSkeletonCardDetails, Skeleton, Text, TooltipText, useTooltip } from '@pancakeswap/uikit' import { BIG_ONE_HUNDRED } from '@pancakeswap/utils/bigNumber' import { formatNumber, getBalanceNumber } from '@pancakeswap/utils/formatBalance' -import { DAY_IN_SECONDS } from '@pancakeswap/utils/getTimePeriods' import BigNumber from 'bignumber.js' import { useStablecoinPrice } from 'hooks/useStablecoinPrice' import { ReactNode, useMemo } from 'react' @@ -141,6 +140,23 @@ const MaxTokenEntry = ({ ) } +const useTimeUntilDiffTime = (diffTime: number): string => { + const { t } = useTranslation() + if (diffTime <= 0) { + return t('The time has already passed.') + } + + const diffInHours = Math.floor(diffTime / (60 * 60)) + const diffInDays = Math.floor(diffInHours / 24) + const unitDay = t('day(s)') + const unitHour = t('hour(s)') + + if (diffInDays >= 1) { + return `${diffInDays} ${unitDay}` + } + return `${diffInHours} ${unitHour}` +} + const IfoCardDetails: React.FC> = ({ isEligible, poolId, @@ -220,7 +236,8 @@ const IfoCardDetails: React.FC> = ( ) const durationInSeconds = ifo.version >= 3.2 ? poolCharacteristic?.vestingInformation?.duration ?? 0 : 0 - const vestingDays = Math.ceil(durationInSeconds / DAY_IN_SECONDS) + // const vestingDays = Math.ceil(durationInSeconds / DAY_IN_SECONDS) + const vestingCountdown = useTimeUntilDiffTime(durationInSeconds) /* Format end */ const renderBasedOnIfoStatus = () => { @@ -276,9 +293,9 @@ const IfoCardDetails: React.FC> = ( /> diff --git a/apps/web/src/views/Ifos/components/IfoFoldableCard/IfoPoolCard/IfoCardTokens.tsx b/apps/web/src/views/Ifos/components/IfoFoldableCard/IfoPoolCard/IfoCardTokens.tsx index 1abaeb7e3dd7c..cfa088ab1c6a5 100644 --- a/apps/web/src/views/Ifos/components/IfoFoldableCard/IfoPoolCard/IfoCardTokens.tsx +++ b/apps/web/src/views/Ifos/components/IfoFoldableCard/IfoPoolCard/IfoCardTokens.tsx @@ -1,6 +1,6 @@ import { Ifo, PoolIds, cakeBnbLpToken } from '@pancakeswap/ifos' import { useTranslation } from '@pancakeswap/localization' -import { Token } from '@pancakeswap/sdk' +import { ChainId, Token } from '@pancakeswap/sdk' import { bscTokens } from '@pancakeswap/tokens' import { AutoRenewIcon, @@ -229,9 +229,9 @@ const IfoCardTokens: React.FC> = ({ hasProfile ) { // If Cross-Chain IFO - // if (ifo.chainId !== ChainId.BSC) { - message = - // } + if (ifo.chainId !== ChainId.BSC) { + message = + } // Phase this out later, as it applies at the same time // else message = } diff --git a/apps/web/src/views/Ifos/components/IfoVesting/VestingPeriod/Claim.tsx b/apps/web/src/views/Ifos/components/IfoVesting/VestingPeriod/Claim.tsx index ff3d0b3e7a047..cc5363292a0db 100644 --- a/apps/web/src/views/Ifos/components/IfoVesting/VestingPeriod/Claim.tsx +++ b/apps/web/src/views/Ifos/components/IfoVesting/VestingPeriod/Claim.tsx @@ -17,6 +17,7 @@ interface Props { claimableAmount: string isVestingInitialized: boolean fetchUserVestingData: () => void + enabled: boolean } const ClaimButton: React.FC> = ({ @@ -25,6 +26,7 @@ const ClaimButton: React.FC> = ({ claimableAmount, isVestingInitialized, fetchUserVestingData, + enabled, }) => { const { account, chain } = useWeb3React() const { t } = useTranslation() @@ -85,10 +87,10 @@ const ClaimButton: React.FC> = ({ width="100%" onClick={handleClaim} isLoading={isPending} - disabled={isReady} + disabled={isReady && !enabled} endIcon={isPending ? : null} > - {t('Claim %symbol%', { symbol: token.symbol })} + {t('Claim')} ) } diff --git a/apps/web/src/views/Ifos/components/IfoVesting/VestingPeriod/Info.tsx b/apps/web/src/views/Ifos/components/IfoVesting/VestingPeriod/Info.tsx index 4341072af6b96..f88faf915ab4f 100644 --- a/apps/web/src/views/Ifos/components/IfoVesting/VestingPeriod/Info.tsx +++ b/apps/web/src/views/Ifos/components/IfoVesting/VestingPeriod/Info.tsx @@ -1,18 +1,16 @@ -import { useMemo } from 'react' -import { styled } from 'styled-components' -import { useTranslation } from '@pancakeswap/localization' -import { Flex, Text, Progress, Tag } from '@pancakeswap/uikit' -import { VestingData } from 'views/Ifos/hooks/vesting/fetchUserWalletIfoData' import { PoolIds } from '@pancakeswap/ifos' -import { getFullDisplayBalance } from '@pancakeswap/utils/formatBalance' +import { useTranslation } from '@pancakeswap/localization' +import { Flex, Progress, Tag, Text } from '@pancakeswap/uikit' +import dayjs from 'dayjs' import { useCurrentBlock } from 'state/block/hooks' +import { styled } from 'styled-components' import useGetPublicIfoV3Data from 'views/Ifos/hooks/v3/useGetPublicIfoData' -import BigNumber from 'bignumber.js' -import dayjs from 'dayjs' +import { VestingData } from 'views/Ifos/hooks/vesting/fetchUserWalletIfoData' import { useQuery } from '@tanstack/react-query' -import Claim from './Claim' +import { getVestingInfo } from 'views/Ifos/hooks/getVestingInfo' import { isBasicSale } from '../../../hooks/v7/helpers' +import Claim from './Claim' const WhiteCard = styled.div` background: ${({ theme }) => theme.colors.backgroundAlt}; @@ -41,16 +39,13 @@ const Info: React.FC> = ({ ifoBasicSaleType, }) => { const { t } = useTranslation() - const { token } = data.ifo const { vestingStartTime } = data.userVestingData - const { - isVestingInitialized, - vestingComputeReleasableAmount, - offeringAmountInToken, - vestingInformationPercentage, - vestingReleased, - vestingInformationDuration, - } = data.userVestingData[poolId] + const { vestingInformationDuration } = data.userVestingData[poolId] + const { isVestingInitialized, isVestingOver, received, claimable, remaining, percentage } = getVestingInfo( + poolId, + data, + ) + const labelText = poolId === PoolIds.poolUnlimited ? t('Public Sale') @@ -74,46 +69,6 @@ const Info: React.FC> = ({ const currentTimeStamp = Date.now() const timeCliff = vestingStartTime === 0 ? currentTimeStamp : (vestingStartTime + (cliff ?? 0)) * 1000 const timeVestingEnd = (vestingStartTime + vestingInformationDuration) * 1000 - const isVestingOver = currentTimeStamp > timeVestingEnd - - const vestingPercentage = useMemo( - () => new BigNumber(vestingInformationPercentage).times(0.01), - [vestingInformationPercentage], - ) - - const releasedAtSaleEnd = useMemo(() => { - return new BigNumber(offeringAmountInToken).times(new BigNumber(1).minus(vestingPercentage)) - }, [offeringAmountInToken, vestingPercentage]) - - const amountReleased = useMemo(() => { - return new BigNumber(releasedAtSaleEnd).plus(vestingReleased).plus(vestingComputeReleasableAmount) - }, [releasedAtSaleEnd, vestingReleased, vestingComputeReleasableAmount]) - - const received = useMemo(() => { - const alreadyClaimed = new BigNumber(releasedAtSaleEnd).plus(vestingReleased) - return alreadyClaimed.gt(0) ? getFullDisplayBalance(alreadyClaimed, token.decimals, 4) : '0' - }, [token, releasedAtSaleEnd, vestingReleased]) - - const claimable = useMemo(() => { - const remain = new BigNumber(offeringAmountInToken).minus(amountReleased) - const claimableAmount = isVestingOver ? vestingComputeReleasableAmount.plus(remain) : vestingComputeReleasableAmount - return claimableAmount.gt(0) ? getFullDisplayBalance(claimableAmount, token.decimals, 4) : '0' - }, [offeringAmountInToken, amountReleased, isVestingOver, vestingComputeReleasableAmount, token.decimals]) - - const remaining = useMemo(() => { - const remain = new BigNumber(offeringAmountInToken).minus(amountReleased) - return remain.gt(0) ? getFullDisplayBalance(remain, token.decimals, 4) : '0' - }, [token, offeringAmountInToken, amountReleased]) - - const percentage = useMemo(() => { - const total = new BigNumber(received).plus(claimable).plus(remaining) - const receivedPercentage = new BigNumber(received).div(total).times(100).toNumber() - const amountAvailablePercentage = new BigNumber(claimable).div(total).times(100).toNumber() - return { - receivedPercentage, - amountAvailablePercentage: receivedPercentage + amountAvailablePercentage, - } - }, [received, claimable, remaining]) if (claimable === '0' && remaining === '0') { return null @@ -170,6 +125,7 @@ const Info: React.FC> = ({ > = ({ ifoBas const { data, fetchUserVestingData } = useFetchVestingData() useEffect(() => { - // When switch account need init if (account) { setIsFirstTime(true) fetchUserVestingData() } }, [account, fetchUserVestingData, setIsFirstTime]) + const hasClaimable = getHasClaimable([PoolIds.poolBasic, PoolIds.poolUnlimited], data) + const cardStatus = useMemo(() => { if (account) { - if (data.length > 0) return IfoVestingStatus[VestingStatus.HAS_TOKENS_CLAIM] - if (data.length === 0 && !isFirstTime) return IfoVestingStatus[VestingStatus.ENDED] + if (hasClaimable) { + return IfoVestingStatus[VestingStatus.HAS_TOKENS_CLAIM] + } + if (!hasClaimable && !isFirstTime) return IfoVestingStatus[VestingStatus.ENDED] } return IfoVestingStatus[VestingStatus.NOT_TOKENS_CLAIM] - }, [data, account, isFirstTime]) + }, [data, account, isFirstTime, hasClaimable]) const handleFetchUserVesting = useCallback(() => { setIsFirstTime(false) @@ -122,15 +127,15 @@ const IfoVesting: React.FC> = ({ ifoBas )} {cardStatus.status === VestingStatus.HAS_TOKENS_CLAIM && ( - {data.map((ifo, index) => ( + {data && ( - ))} + )} )} {cardStatus.status === VestingStatus.ENDED && } diff --git a/apps/web/src/views/Ifos/components/VeCakeCard.tsx b/apps/web/src/views/Ifos/components/VeCakeCard.tsx index ebdfbb3dde92f..8f082af48cee9 100644 --- a/apps/web/src/views/Ifos/components/VeCakeCard.tsx +++ b/apps/web/src/views/Ifos/components/VeCakeCard.tsx @@ -1,24 +1,24 @@ -import { Ifo } from '@pancakeswap/widgets-internal' import { ChainId } from '@pancakeswap/chains' -import { Button } from '@pancakeswap/uikit' import { useTranslation } from '@pancakeswap/localization' +import { CAKE } from '@pancakeswap/tokens' +import { Button } from '@pancakeswap/uikit' +import { formatBigInt } from '@pancakeswap/utils/formatBalance' +import { Ifo } from '@pancakeswap/widgets-internal' +import BigNumber from 'bignumber.js' import Link from 'next/link' +import { useMemo } from 'react' import { SpaceProps } from 'styled-system' -import { useAccount } from 'wagmi' import { Address } from 'viem' -import { useMemo } from 'react' -import BigNumber from 'bignumber.js' -import { CAKE } from '@pancakeswap/tokens' -import { formatBigInt } from '@pancakeswap/utils/formatBalance' +import { useAccount } from 'wagmi' -import { useCakePrice } from 'hooks/useCakePrice' -import { useActiveChainId } from 'hooks/useActiveChainId' import ConnectWalletButton from 'components/ConnectWalletButton' +import { useActiveChainId } from 'hooks/useActiveChainId' +import { useCakePrice } from 'hooks/useCakePrice' // TODO should be common hooks -import { useCakeLockStatus } from 'views/CakeStaking/hooks/useVeCakeUserInfo' import { useIsMigratedToVeCake } from 'views/CakeStaking/hooks/useIsMigratedToVeCake' import { useIsUserDelegated } from 'views/CakeStaking/hooks/useIsUserDelegated' +import { useCakeLockStatus } from 'views/CakeStaking/hooks/useVeCakeUserInfo' import { useUserIfoInfo } from '../hooks/useUserIfoInfo' @@ -70,7 +70,7 @@ export function VeCakeCard({ ifoAddress }: Props) { return nativeUnlockTime }, [hasProxyCakeButNoNativeVeCake, nativeUnlockTime, proxyUnlockTime]) - const { snapshotTime, credit, veCake } = useUserIfoInfo({ ifoAddress, chainId }) + const { snapshotTime, credit, veCake, ratio } = useUserIfoInfo({ ifoAddress, chainId }) const creditBN = useMemo( () => credit && new BigNumber(credit.numerator.toString()).div(credit.decimalScale.toString()), [credit], @@ -88,7 +88,7 @@ export function VeCakeCard({ ifoAddress }: Props) { return ( - + {isConnected && hasICake && totalLockCake ? ( diff --git a/apps/web/src/views/Ifos/hooks/getVestingInfo.ts b/apps/web/src/views/Ifos/hooks/getVestingInfo.ts new file mode 100644 index 0000000000000..18beb8328c689 --- /dev/null +++ b/apps/web/src/views/Ifos/hooks/getVestingInfo.ts @@ -0,0 +1,74 @@ +import { PoolIds } from '@pancakeswap/ifos' +import { getFullDisplayBalance } from '@pancakeswap/utils/formatBalance' +import BigNumber from 'bignumber.js' +import { VestingData } from './vesting/fetchUserWalletIfoData' + +export const getVestingInfo = (poolId: PoolIds, data: VestingData) => { + const { token } = data.ifo + const { vestingStartTime } = data.userVestingData + const { + isVestingInitialized, + vestingComputeReleasableAmount, + offeringAmountInToken, + vestingInformationPercentage, + vestingReleased, + vestingInformationDuration, + } = data.userVestingData[poolId] + + const currentTimeStamp = Date.now() + const timeVestingEnd = (vestingStartTime + vestingInformationDuration) * 1000 + const isVestingOver = currentTimeStamp > timeVestingEnd + + const vestingPercentage = new BigNumber(vestingInformationPercentage).times(0.01) + + const releasedAtSaleEnd = new BigNumber(offeringAmountInToken).times(new BigNumber(1).minus(vestingPercentage)) + + const amountReleased = new BigNumber(releasedAtSaleEnd).plus(vestingReleased).plus(vestingComputeReleasableAmount) + + const alreadyClaimed = new BigNumber(releasedAtSaleEnd).plus(vestingReleased) + const received = alreadyClaimed.gt(0) ? getFullDisplayBalance(alreadyClaimed, token.decimals, 4) : '0' + + const remain = new BigNumber(offeringAmountInToken).minus(amountReleased) + const claimableAmount = isVestingOver + ? new BigNumber(vestingComputeReleasableAmount).plus(remain) + : vestingComputeReleasableAmount + const claimable = claimableAmount.gt(0) ? getFullDisplayBalance(claimableAmount, token.decimals, 4) : '0' + + const remaining = remain.gt(0) ? getFullDisplayBalance(remain, token.decimals, 4) : '0' + + const total = new BigNumber(received).plus(claimable).plus(remaining) + const percentage = total.eq(0) + ? { + receivedPercentage: 0, + amountAvailablePercentage: 0, + } + : { + receivedPercentage: new BigNumber(received).div(total).times(100).toNumber(), + amountAvailablePercentage: + new BigNumber(received).div(total).times(100).toNumber() + + new BigNumber(claimable).div(total).times(100).toNumber(), + } + + return { + isVestingInitialized, + isVestingOver, + vestingPercentage, + releasedAtSaleEnd, + amountReleased, + received, + claimable, + remaining, + percentage, + } +} + +export const getHasClaimable = (poolIds: PoolIds[], data: VestingData | null) => { + if (!data) { + return false + } + + return poolIds.some((poolId) => { + const { claimable } = getVestingInfo(poolId, data) + return claimable !== '0' + }) +} diff --git a/apps/web/src/views/Ifos/hooks/useUserIfoInfo.ts b/apps/web/src/views/Ifos/hooks/useUserIfoInfo.ts index 66f9e7ec218e2..7eafa92d76ae4 100644 --- a/apps/web/src/views/Ifos/hooks/useUserIfoInfo.ts +++ b/apps/web/src/views/Ifos/hooks/useUserIfoInfo.ts @@ -5,22 +5,24 @@ import { useQuery } from '@tanstack/react-query' import BigNumber from 'bignumber.js' import { useMemo } from 'react' import { Address } from 'viem' -import { useAccount } from 'wagmi' import { getViemClients } from 'utils/viem' +import { useAccount } from 'wagmi' type ICakeRatioParams = { chainId?: ChainId } export function useICakeRatio({ chainId }: ICakeRatioParams) { + const { address: account } = useAccount() const { data } = useQuery({ - queryKey: [chainId, 'current-ifo-ratio'], + queryKey: [chainId, account, 'current-ifo-ratio'], queryFn: () => getCurrentIfoRatio({ chainId, provider: getViemClients, + account, }), enabled: Boolean(chainId), diff --git a/apps/web/src/views/Ifos/hooks/v8/fetchIfoData.ts b/apps/web/src/views/Ifos/hooks/v8/fetchIfoData.ts new file mode 100644 index 0000000000000..399eccf2b8f48 --- /dev/null +++ b/apps/web/src/views/Ifos/hooks/v8/fetchIfoData.ts @@ -0,0 +1,182 @@ +import { Ifo, ifoV8ABI } from '@pancakeswap/ifos' +import { BIG_ZERO } from '@pancakeswap/utils/bigNumber' +import BigNumber from 'bignumber.js' +import { publicClient } from 'utils/wagmi' +import { Address } from 'viem' + +export const fetchIfoData = async (account: Address, ifo: Ifo, version: number, chainId: number) => { + const { address } = ifo + const client = publicClient({ chainId }) + const [userInfo, amounts] = await client.multicall({ + contracts: [ + { + address, + abi: ifoV8ABI, + functionName: 'viewUserInfo', + args: [account, [0, 1]], + }, + { + address, + abi: ifoV8ABI, + functionName: 'viewUserOfferingAndRefundingAmountsForPools', + args: [account, [0, 1]], + }, + ], + allowFailure: false, + }) + + let basicId: Address | null = null + let unlimitedId: Address | null = null + if (version >= 3.2) { + const [basicIdDataResult, unlimitedIdDataResult] = await client.multicall({ + contracts: [ + { + address, + abi: ifoV8ABI, + functionName: 'computeVestingScheduleIdForAddressAndPid', + args: [account, 0], + }, + { + address, + abi: ifoV8ABI, + functionName: 'computeVestingScheduleIdForAddressAndPid', + args: [account, 1], + }, + ], + }) + + basicId = basicIdDataResult.result ?? null + unlimitedId = unlimitedIdDataResult.result ?? null + } + + basicId = basicId || '0x' + unlimitedId = unlimitedId || '0x' + + let isQualifiedNFT: boolean = false + let isQualifiedPoints: boolean = false + + let basicSchedule: VestingSchedule | null = null + let unlimitedSchedule: VestingSchedule | null = null + + let basicReleasableAmount: bigint | null = null + let unlimitedReleasableAmount: bigint | null = null + + if (version >= 3.1) { + const [ + isQualifiedNFTResult, + isQualifiedPointsResult, + basicScheduleResult, + unlimitedScheduleResult, + basicReleasableAmountResult, + unlimitedReleasableAmountResult, + ] = await client.multicall({ + contracts: [ + { + address, + abi: ifoV8ABI, + functionName: 'isQualifiedNFT', + args: [account], + }, + { + address, + abi: ifoV8ABI, + functionName: 'isQualifiedPoints', + args: [account], + }, + { + address, + abi: ifoV8ABI, + functionName: 'getVestingSchedule', + args: [basicId], + }, + { + address, + abi: ifoV8ABI, + functionName: 'getVestingSchedule', + args: [unlimitedId], + }, + { + address, + abi: ifoV8ABI, + functionName: 'computeReleasableAmount', + args: [basicId], + }, + { + address, + abi: ifoV8ABI, + functionName: 'computeReleasableAmount', + args: [unlimitedId], + }, + ], + allowFailure: true, + }) + + isQualifiedNFT = isQualifiedNFTResult.result || false + isQualifiedPoints = isQualifiedPointsResult.result || false + basicSchedule = basicScheduleResult.result || null + unlimitedSchedule = unlimitedScheduleResult.result || null + basicReleasableAmount = basicReleasableAmountResult.result || null + unlimitedReleasableAmount = unlimitedReleasableAmountResult.result || null + } + + return { + basicPoolData: preparePoolData( + 0, + userInfo, + amounts, + basicSchedule, + basicReleasableAmount, + basicId, + isQualifiedNFT, + isQualifiedPoints, + ), + unlimitedPoolData: preparePoolData(1, userInfo, amounts, unlimitedSchedule, unlimitedReleasableAmount, unlimitedId), + } +} + +interface VestingSchedule { + released: bigint + amountTotal: bigint + isVestingInitialized: boolean +} + +const preparePoolData = ( + poolId: 0 | 1, + userInfo: any, + amounts: any, + schedule: any, + releasableAmount: bigint | null, + vestingId: Address | null, + isQualifiedNFT?: boolean, + isQualifiedPoints?: boolean, +) => { + const hasClaimed = userInfo[1][poolId] + const [offeringRaw, refundingRaw, taxRaw] = amounts[poolId] + const vestingReleasedBn = schedule ? new BigNumber(schedule.released.toString()) : BIG_ZERO + const vestingTotalBn = schedule ? new BigNumber(schedule.amountTotal.toString()) : BIG_ZERO + const releasableBn = releasableAmount ? new BigNumber(releasableAmount.toString()) : BIG_ZERO + + const data = { + amountTokenCommittedInLP: new BigNumber(userInfo[0][poolId].toString()), + offeringAmountInToken: new BigNumber(offeringRaw.toString()), + refundingAmountInLP: new BigNumber(refundingRaw.toString()), + taxAmountInLP: new BigNumber(taxRaw.toString()), + hasClaimed, + isPendingTx: false, + vestingReleased: vestingReleasedBn, + vestingAmountTotal: vestingTotalBn, + isVestingInitialized: schedule ? schedule.isVestingInitialized : false, + vestingId: vestingId ? vestingId.toString() : '0', + vestingComputeReleasableAmount: releasableBn, + } + + if (poolId === 0 && isQualifiedNFT !== undefined && isQualifiedPoints !== undefined) { + return { + ...data, + isQualifiedNFT, + isQualifiedPoints, + } + } + + return data +} diff --git a/apps/web/src/views/Ifos/hooks/v8/useGetWalletIfoData.ts b/apps/web/src/views/Ifos/hooks/v8/useGetWalletIfoData.ts index 2b7af58d91510..b00a658e99b4d 100644 --- a/apps/web/src/views/Ifos/hooks/v8/useGetWalletIfoData.ts +++ b/apps/web/src/views/Ifos/hooks/v8/useGetWalletIfoData.ts @@ -1,20 +1,18 @@ -import { Ifo, PoolIds, ifoV8ABI } from '@pancakeswap/ifos' +import { Ifo, PoolIds } from '@pancakeswap/ifos' import { BIG_ZERO } from '@pancakeswap/utils/bigNumber' import BigNumber from 'bignumber.js' +import { useActiveChainId } from 'hooks/useActiveChainId' import { useERC20, useIfoV8Contract } from 'hooks/useContract' import { useCallback, useEffect, useMemo, useState } from 'react' -import { useAppDispatch } from 'state' -import { fetchCakeVaultUserData } from 'state/pools' -import { publicClient } from 'utils/wagmi' -import { Address } from 'viem' import { useAccount } from 'wagmi' -import { useActiveChainId } from 'hooks/useActiveChainId' - +import { useAppDispatch } from 'state' +import { fetchCakeVaultUserData } from 'state/pools' import { WalletIfoData, WalletIfoState } from '../../types' import useIfoAllowance from '../useIfoAllowance' import { useIfoCredit } from '../useIfoCredit' import { useIfoSourceChain } from '../useIfoSourceChain' +import { fetchIfoData } from './fetchIfoData' const initialState = { isInitialized: false, @@ -30,6 +28,8 @@ const initialState = { isVestingInitialized: false, vestingId: '0', vestingComputeReleasableAmount: BIG_ZERO, + isQualifiedNFT: false, + isQualifiedPoints: false, }, poolUnlimited: { amountTokenCommittedInLP: BIG_ZERO, @@ -46,27 +46,23 @@ const initialState = { }, } -/** - * Gets all data from an IFO related to a wallet - */ const useGetWalletIfoData = (ifo: Ifo): WalletIfoData => { const { chainId: currentChainId } = useActiveChainId() const [state, setState] = useState(initialState) const dispatch = useAppDispatch() - const { chainId } = ifo + const { chainId, version, address } = ifo const creditAmount = useIfoCredit({ chainId, ifoAddress: ifo.address }) const credit = useMemo( - () => (creditAmount && BigNumber(creditAmount.quotient.toString())) ?? BIG_ZERO, + () => (creditAmount && new BigNumber(creditAmount.quotient.toString())) ?? BIG_ZERO, [creditAmount], ) const sourceChain = useIfoSourceChain(chainId) - const { address, currency, version } = ifo - const { address: account } = useAccount() - const contract = useIfoV8Contract(address, { chainId }) - const currencyContract = useERC20(currency.address, { chainId }) - const allowance = useIfoAllowance(currencyContract, address) + + const contract = useIfoV8Contract(ifo.address, { chainId }) + const currencyContract = useERC20(ifo.currency.address, { chainId }) + const allowance = useIfoAllowance(currencyContract, ifo.address) const setPendingTx = (status: boolean, poolId: PoolIds) => setState((prevState) => ({ @@ -87,174 +83,6 @@ const useGetWalletIfoData = (ifo: Ifo): WalletIfoData => { })) } - const fetchIfoData = useCallback(async () => { - if (!account) { - return - } - const client = publicClient({ chainId }) - - const [userInfo, amounts] = await client.multicall({ - contracts: [ - { - address, - abi: ifoV8ABI, - functionName: 'viewUserInfo', - args: [account, [0, 1]], - }, - { - address, - abi: ifoV8ABI, - functionName: 'viewUserOfferingAndRefundingAmountsForPools', - args: [account, [0, 1]], - }, - ], - allowFailure: false, - }) - - let basicId: Address | null = null - let unlimitedId: Address | null = null - if (version >= 3.2) { - const [basicIdDataResult, unlimitedIdDataResult] = await client.multicall({ - contracts: [ - { - address, - abi: ifoV8ABI, - functionName: 'computeVestingScheduleIdForAddressAndPid', - args: [account, 0], - }, - { - address, - abi: ifoV8ABI, - functionName: 'computeVestingScheduleIdForAddressAndPid', - args: [account, 1], - }, - ], - }) - - basicId = basicIdDataResult.result ?? null - unlimitedId = unlimitedIdDataResult.result ?? null - } - - basicId = basicId || '0x' - unlimitedId = unlimitedId || '0x' - - let [ - isQualifiedNFT, - isQualifiedPoints, - basicSchedule, - unlimitedSchedule, - basicReleasableAmount, - unlimitedReleasableAmount, - ]: [boolean | undefined, boolean | undefined, any | null, any | null, any | null, any | null] = [ - false, - false, - null, - null, - null, - null, - ] - - if (version >= 3.1) { - const [ - isQualifiedNFTResult, - isQualifiedPointsResult, - basicScheduleResult, - unlimitedScheduleResult, - basicReleasableAmountResult, - unlimitedReleasableAmountResult, - ] = await client.multicall({ - contracts: [ - { - address, - abi: ifoV8ABI, - functionName: 'isQualifiedNFT', - args: [account], - }, - { - abi: ifoV8ABI, - address, - functionName: 'isQualifiedPoints', - args: [account], - }, - { - abi: ifoV8ABI, - address, - functionName: 'getVestingSchedule', - args: [basicId], - }, - { - abi: ifoV8ABI, - address, - functionName: 'getVestingSchedule', - args: [unlimitedId], - }, - { - abi: ifoV8ABI, - address, - functionName: 'computeReleasableAmount', - args: [basicId], - }, - { - abi: ifoV8ABI, - address, - functionName: 'computeReleasableAmount', - args: [unlimitedId], - }, - ], - allowFailure: true, - }) - - isQualifiedNFT = isQualifiedNFTResult.result - isQualifiedPoints = isQualifiedPointsResult.result - basicSchedule = basicScheduleResult.result - unlimitedSchedule = unlimitedScheduleResult.result - basicReleasableAmount = basicReleasableAmountResult.result - unlimitedReleasableAmount = unlimitedReleasableAmountResult.result - } - - dispatch(fetchCakeVaultUserData({ account, chainId: sourceChain })) - - setState( - (prevState) => - ({ - ...prevState, - isInitialized: true, - poolBasic: { - ...prevState.poolBasic, - amountTokenCommittedInLP: new BigNumber(userInfo[0][0].toString()), - offeringAmountInToken: new BigNumber(amounts[0][0].toString()), - refundingAmountInLP: new BigNumber(amounts[0][1].toString()), - taxAmountInLP: new BigNumber(amounts[0][2].toString()), - hasClaimed: userInfo[1][0], - isQualifiedNFT, - isQualifiedPoints, - vestingReleased: basicSchedule ? new BigNumber(basicSchedule.released.toString()) : BIG_ZERO, - vestingAmountTotal: basicSchedule ? new BigNumber(basicSchedule.amountTotal.toString()) : BIG_ZERO, - isVestingInitialized: basicSchedule ? basicSchedule.isVestingInitialized : false, - vestingId: basicId ? basicId.toString() : '0', - vestingComputeReleasableAmount: basicReleasableAmount - ? new BigNumber(basicReleasableAmount.toString()) - : BIG_ZERO, - }, - poolUnlimited: { - ...prevState.poolUnlimited, - amountTokenCommittedInLP: new BigNumber(userInfo[0][1].toString()), - offeringAmountInToken: new BigNumber(amounts[1][0].toString()), - refundingAmountInLP: new BigNumber(amounts[1][1].toString()), - taxAmountInLP: new BigNumber(amounts[1][2].toString()), - hasClaimed: userInfo[1][1], - vestingReleased: unlimitedSchedule ? new BigNumber(unlimitedSchedule.released.toString()) : BIG_ZERO, - vestingAmountTotal: unlimitedSchedule ? new BigNumber(unlimitedSchedule.amountTotal.toString()) : BIG_ZERO, - isVestingInitialized: unlimitedSchedule ? unlimitedSchedule.isVestingInitialized : false, - vestingId: unlimitedId ? unlimitedId.toString() : '0', - vestingComputeReleasableAmount: unlimitedReleasableAmount - ? new BigNumber(unlimitedReleasableAmount.toString()) - : BIG_ZERO, - }, - } as any), - ) - }, [account, address, dispatch, version, chainId, sourceChain]) - const resetIfoData = useCallback(() => { setState({ ...initialState }) }, []) @@ -266,7 +94,35 @@ const useGetWalletIfoData = (ifo: Ifo): WalletIfoData => { creditLeft: BigNumber.maximum(BIG_ZERO, creditLeftWithNegative), } - useEffect(() => resetIfoData(), [currentChainId, account, resetIfoData]) + useEffect(() => { + if (account) { + fetchIfoData(account, ifo, ifo.version, chainId).then(({ basicPoolData, unlimitedPoolData }) => { + setState((prevState) => ({ + ...prevState, + isInitialized: true, + poolBasic: { ...prevState.poolBasic, ...basicPoolData }, + poolUnlimited: { ...prevState.poolUnlimited, ...unlimitedPoolData }, + })) + }) + } + }, [account, chainId, dispatch, ifo, sourceChain]) + + useEffect(() => { + resetIfoData() + }, [currentChainId, account, resetIfoData]) + const fetch = useCallback(async () => { + if (!account) { + return + } + const data = await fetchIfoData(account, ifo, version, chainId) + dispatch(fetchCakeVaultUserData({ account, chainId: sourceChain })) + setState((prevState) => ({ + ...prevState, + isInitialized: true, + poolBasic: { ...prevState.poolBasic, ...data.basicPoolData }, + poolUnlimited: { ...prevState.poolUnlimited, ...data.unlimitedPoolData }, + })) + }, [account, address, dispatch, version, chainId, sourceChain]) return { ...state, @@ -274,8 +130,8 @@ const useGetWalletIfoData = (ifo: Ifo): WalletIfoData => { contract, setPendingTx, setIsClaimed, - fetchIfoData, resetIfoData, + fetchIfoData: fetch, ifoCredit, version: 8, } diff --git a/apps/web/src/views/Ifos/hooks/vesting/useFetchVestingData.ts b/apps/web/src/views/Ifos/hooks/vesting/useFetchVestingData.ts index b60a6d9f92414..fe97b33dc80ca 100644 --- a/apps/web/src/views/Ifos/hooks/vesting/useFetchVestingData.ts +++ b/apps/web/src/views/Ifos/hooks/vesting/useFetchVestingData.ts @@ -1,93 +1,71 @@ -import { useMemo } from 'react' -import { useAccount } from 'wagmi' -import { Ifo, PoolIds } from '@pancakeswap/ifos' -import BigNumber from 'bignumber.js' +import { PoolIds, UserVestingData } from '@pancakeswap/ifos' import { useQuery } from '@tanstack/react-query' +import BigNumber from 'bignumber.js' +import { useAccount } from 'wagmi' -import { useIfoConfigsAcrossChains } from 'hooks/useIfoConfig' import { FAST_INTERVAL } from 'config/constants' import { useActiveChainId } from 'hooks/useActiveChainId' +import { useIfoConfigsAcrossChains } from 'hooks/useIfoConfig' + +import { fetchUserWalletIfoData, VestingData } from './fetchUserWalletIfoData' -import { fetchUserWalletIfoData } from './fetchUserWalletIfoData' +const POOLS = [PoolIds.poolBasic, PoolIds.poolUnlimited] + +const isPoolEligible = (poolId: PoolIds, userVestingData: UserVestingData) => { + const poolData = userVestingData[poolId] + const currentTimeStamp = Date.now() + if (!poolData) return false + + if (poolData.offeringAmountInToken.gt(0) || poolData.vestingComputeReleasableAmount.gt(0)) { + return true + } + + const vestingStartTime = new BigNumber(userVestingData.vestingStartTime) + const vestingEndTime = vestingStartTime.plus(poolData.vestingInformationDuration).times(1000) + + return vestingEndTime.gte(currentTimeStamp) +} + +const isEligibleVestingData = (ifo: VestingData) => { + const { userVestingData } = ifo + return POOLS.some((poolId) => isPoolEligible(poolId, userVestingData)) +} const useFetchVestingData = () => { const { address: account } = useAccount() + const { chainId } = useActiveChainId() const configs = useIfoConfigsAcrossChains() - const allVestingIfo = useMemo( - () => configs?.filter((ifo) => ifo.version >= 3.2 && ifo.vestingTitle) || [], - [configs], - ) - const { data: vestingData, refetch } = useQuery({ - queryKey: ['vestingData', account], + // Filter IFOs that are version >= 3.2 and have a vesting title + const allVestingIfo = configs?.filter((ifo) => ifo.version >= 3.2 && ifo.vestingTitle) || [] + const { data: vestingData, refetch } = useQuery({ + queryKey: ['vestingData', account], queryFn: async () => { - const allData = await Promise.all( - allVestingIfo.map(async (ifo) => { - const response = await fetchUserWalletIfoData(ifo, account) - return response - }), - ) - - const currentTimeStamp = Date.now() - - return allData.filter( - // eslint-disable-next-line array-callback-return, consistent-return - (ifo) => { - const { userVestingData } = ifo - if ( - userVestingData[PoolIds.poolBasic].offeringAmountInToken.gt(0) || - userVestingData[PoolIds.poolUnlimited].offeringAmountInToken.gt(0) - ) { - if ( - userVestingData[PoolIds.poolBasic].vestingComputeReleasableAmount.gt(0) || - userVestingData[PoolIds.poolUnlimited].vestingComputeReleasableAmount.gt(0) - ) { - return ifo - } - const vestingStartTime = new BigNumber(userVestingData.vestingStartTime) - const isPoolUnlimitedLive = vestingStartTime - .plus(userVestingData[PoolIds.poolUnlimited].vestingInformationDuration) - .times(1000) - .gte(currentTimeStamp) - if (isPoolUnlimitedLive) return ifo - const isPoolBasicLive = vestingStartTime - .plus(userVestingData[PoolIds.poolBasic].vestingInformationDuration) - .times(1000) - .gte(currentTimeStamp) - if (isPoolBasicLive) return ifo - return false - } - return false - }, - ) - }, + const allDataSettled = await Promise.allSettled(allVestingIfo.map((ifo) => fetchUserWalletIfoData(ifo, account))) + const allData = allDataSettled + .filter((result) => result.status === 'fulfilled') + .map((result) => (result as PromiseFulfilledResult).value) + const filteredData = allData.filter((x) => x.ifo.isActive).filter(isEligibleVestingData) + + const sortedData = filteredData.toSorted((a, b) => { + if (a.ifo.chainId === chainId && b.ifo.chainId !== chainId) return -1 + if (a.ifo.chainId !== chainId && b.ifo.chainId === chainId) return 1 + return 0 + }) + + return sortedData[0] + }, enabled: Boolean(account), refetchOnWindowFocus: false, refetchInterval: FAST_INTERVAL, staleTime: FAST_INTERVAL, }) - // Sort by active chain - const data = useMemo( - () => - vestingData && - vestingData.toSorted((a, b) => { - if (a.ifo.chainId === chainId && b.ifo.chainId !== chainId) { - return -1 - } - if (a.ifo.chainId !== chainId && b.ifo.chainId === chainId) { - return 1 - } - return 0 - }), - [chainId, vestingData], - ) - return { - data: data || [], + data: vestingData || null, fetchUserVestingData: refetch, } } diff --git a/packages/achievements/src/campaigns.ts b/packages/achievements/src/campaigns.ts index 0477ba2f39313..6e037c850f56b 100644 --- a/packages/achievements/src/campaigns.ts +++ b/packages/achievements/src/campaigns.ts @@ -10,6 +10,12 @@ import { Campaign } from './types' */ export const campaigns: Campaign[] = [ + { + id: '512600000', + type: 'ifo', + title: 'SOLV', + badge: 'ifo-solv.svg', + }, { id: '511110000', type: 'ifo', diff --git a/packages/ifos/src/abis/ICake.ts b/packages/ifos/src/abis/ICake.ts index e74bc66728a3c..a60f5af92ec1f 100644 --- a/packages/ifos/src/abis/ICake.ts +++ b/packages/ifos/src/abis/ICake.ts @@ -4,6 +4,15 @@ export const iCakeABI = [ stateMutability: 'nonpayable', type: 'constructor', }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'delegator', type: 'address' }, + { indexed: true, internalType: 'address', name: 'VECakeUser', type: 'address' }, + ], + name: 'Approve', + type: 'event', + }, { anonymous: false, inputs: [ @@ -13,6 +22,16 @@ export const iCakeABI = [ name: 'OwnershipTransferred', type: 'event', }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'user', type: 'address' }, + { indexed: true, internalType: 'address', name: 'oldDelegator', type: 'address' }, + { indexed: true, internalType: 'address', name: 'delegator', type: 'address' }, + ], + name: 'UpdateDelegator', + type: 'event', + }, { anonymous: false, inputs: [{ indexed: true, internalType: 'address', name: 'newAddress', type: 'address' }], @@ -25,6 +44,15 @@ export const iCakeABI = [ name: 'UpdateRatio', type: 'event', }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'userAddress', type: 'address' }, + { indexed: false, internalType: 'uint256', name: 'ratio', type: 'uint256' }, + ], + name: 'UpdateUserRatioOverride', + type: 'event', + }, { inputs: [], name: 'MIN_CEILING_DURATION', @@ -46,6 +74,34 @@ export const iCakeABI = [ stateMutability: 'view', type: 'function', }, + { + inputs: [{ internalType: 'address', name: '_VECakeUser', type: 'address' }], + name: 'approveToVECakeUser', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: '', type: 'address' }], + name: 'delegated', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: '', type: 'address' }], + name: 'delegator', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: '', type: 'address' }], + name: 'delegatorApprove', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, { inputs: [{ internalType: 'address', name: '_user', type: 'address' }], name: 'getUserCredit', @@ -80,6 +136,13 @@ export const iCakeABI = [ stateMutability: 'view', type: 'function', }, + { + inputs: [{ internalType: 'address', name: '_user', type: 'address' }], + name: 'getVeCakeUser', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, { inputs: [], name: 'ifoDeployerAddress', @@ -101,7 +164,32 @@ export const iCakeABI = [ stateMutability: 'view', type: 'function', }, + { inputs: [], name: 'removeDelegator', outputs: [], stateMutability: 'nonpayable', type: 'function' }, { inputs: [], name: 'renounceOwnership', outputs: [], stateMutability: 'nonpayable', type: 'function' }, + { + inputs: [{ internalType: 'address', name: '_delegator', type: 'address' }], + name: 'setDelegator', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'VECakeUser', type: 'address' }, + { internalType: 'address', name: 'delegator', type: 'address' }, + ], + internalType: 'struct ICakeV3.DelegatorConfig[]', + name: '_delegatorConfigs', + type: 'tuple[]', + }, + ], + name: 'setDelegators', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, { inputs: [{ internalType: 'address', name: 'newOwner', type: 'address' }], name: 'transferOwnership', @@ -123,6 +211,23 @@ export const iCakeABI = [ stateMutability: 'nonpayable', type: 'function', }, + { + inputs: [ + { internalType: 'address', name: '_user', type: 'address' }, + { internalType: 'uint256', name: '_newRatio', type: 'uint256' }, + ], + name: 'updateUserRatio', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: '', type: 'address' }], + name: 'userRatioOverride', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, { inputs: [], name: 'veCakeAddress', diff --git a/packages/ifos/src/abis/IfoV8.ts b/packages/ifos/src/abis/IfoV8.ts index f71358530c4e4..88d5e05b5a969 100644 --- a/packages/ifos/src/abis/IfoV8.ts +++ b/packages/ifos/src/abis/IfoV8.ts @@ -1,4 +1,46 @@ export const ifoV8ABI = [ + { inputs: [], stateMutability: 'nonpayable', type: 'constructor' }, + { inputs: [], name: 'AddressesLengthNotCorrect', type: 'error' }, + { inputs: [], name: 'AlreadyHarvested', type: 'error' }, + { inputs: [], name: 'AlreadyInitialized', type: 'error' }, + { inputs: [], name: 'AmountMustExceedZero', type: 'error' }, + { inputs: [], name: 'CanNotBeLPToken', type: 'error' }, + { inputs: [], name: 'CanNotBeOfferingToken', type: 'error' }, + { inputs: [], name: 'DidNotParticipate', type: 'error' }, + { inputs: [], name: 'EndTimeTooFar', type: 'error' }, + { inputs: [], name: 'FlatTaxRateMustBe0WhenHasTaxIsFalse', type: 'error' }, + { inputs: [], name: 'FlatTaxRateMustBeLessThan1e12', type: 'error' }, + { inputs: [], name: 'IFOHasEnded', type: 'error' }, + { inputs: [], name: 'IFOHasStarted', type: 'error' }, + { inputs: [], name: 'MustHaveAnActiveProfile', type: 'error' }, + { inputs: [], name: 'NFTRequirementsMustBeMetForHarvest', type: 'error' }, + { inputs: [], name: 'NFTTokenIdNotSameAsRegistered', type: 'error' }, + { inputs: [], name: 'NFTUsedByAnotherAddressAlready', type: 'error' }, + { inputs: [], name: 'NewAmountAboveUserLimit', type: 'error' }, + { inputs: [], name: 'NotEnoughIFOCreditLeft', type: 'error' }, + { inputs: [], name: 'NotEnoughLPTokens', type: 'error' }, + { inputs: [], name: 'NotEnoughOfferingTokens', type: 'error' }, + { inputs: [], name: 'NotFactory', type: 'error' }, + { inputs: [], name: 'NotMeetAnyoneOfRequiredConditions', type: 'error' }, + { inputs: [], name: 'OnlyOwner', type: 'error' }, + { inputs: [], name: 'PoolIdNotValid', type: 'error' }, + { inputs: [], name: 'PoolNotSet', type: 'error' }, + { inputs: [], name: 'ProfileNotActive', type: 'error' }, + { inputs: [], name: 'ShouldNotLargerThanTheNumberOfPools', type: 'error' }, + { inputs: [], name: 'StartAndEndTimestampsLengthNotCorrect', type: 'error' }, + { inputs: [], name: 'StartTimeMustGreaterThanCurrentBlockTime', type: 'error' }, + { inputs: [], name: 'StartTimeMustInferiorToEndTime', type: 'error' }, + { inputs: [], name: 'TokensNotDepositedProperly', type: 'error' }, + { inputs: [], name: 'TooEarly', type: 'error' }, + { inputs: [], name: 'TooLate', type: 'error' }, + { inputs: [], name: 'VestingDurationMustExceeds0', type: 'error' }, + { inputs: [], name: 'VestingIsRevoked', type: 'error' }, + { inputs: [], name: 'VestingNotEnoughToRelease', type: 'error' }, + { inputs: [], name: 'VestingNotExist', type: 'error' }, + { inputs: [], name: 'VestingOnlyBeneficiaryOrOwnerCanRelease', type: 'error' }, + { inputs: [], name: 'VestingPercentageShouldRangeIn0And100', type: 'error' }, + { inputs: [], name: 'VestingSlicePerSecondsMustBeExceeds1', type: 'error' }, + { inputs: [], name: 'VestingSlicePerSecondsMustBeInteriorDuration', type: 'error' }, { anonymous: false, inputs: [ diff --git a/packages/ifos/src/constants/contracts.ts b/packages/ifos/src/constants/contracts.ts index 05fb13a109c45..414616c83e78d 100644 --- a/packages/ifos/src/constants/contracts.ts +++ b/packages/ifos/src/constants/contracts.ts @@ -8,7 +8,7 @@ export type ContractAddresses = { } export const ICAKE = { - [ChainId.BSC]: '0x9C46110Eb094866922045729AEFcdff91f2CFFF5', + [ChainId.BSC]: '0xd68A31C3f2b61C621a7619b6F4667a2aee959132', [ChainId.BSC_TESTNET]: '0x5FB0b7a782c2f192493d86922dD3873b6392C8e8', [ChainId.GOERLI]: '0x45A33F911F9E2ea404303920E9775467518a4ed7', [ChainId.ARBITRUM_ONE]: '0x747A78d0EB1adAeca945ECc578feB17065a1dA39', diff --git a/packages/ifos/src/constants/ifos/bsc.ts b/packages/ifos/src/constants/ifos/bsc.ts index 1ac960a75d79d..87e056a432979 100644 --- a/packages/ifos/src/constants/ifos/bsc.ts +++ b/packages/ifos/src/constants/ifos/bsc.ts @@ -4,6 +4,31 @@ import { BaseIfoConfig } from '../../types' import { cakeBnbLpToken } from '../lpTokens' export const ifos: BaseIfoConfig[] = [ + { + id: 'solv', + address: '0xb0De22aAe05789C13E900688b420F1Bb7c2C3889', + isActive: true, + cIFO: false, + plannedStartTime: new Date('2025-01-16T10:00:00Z').getTime() / 1000, + poolBasic: { + raiseAmount: '$10,000', + }, + poolUnlimited: { + raiseAmount: '$90,000', + additionalClaimingFee: true, + }, + name: 'SOLV', + currency: bscTokens.cake, + token: bscTokens.solv, + campaignId: '512600000', + articleUrl: 'https://forum.pancakeswap.finance/t/solv-ifo-discussion-thread/993', + tokenOfferingPrice: 0.0310559006, + version: 8, + twitterUrl: 'https://twitter.com/SolvProtocol/', + description: + 'Solv is a Bitcoin staking protocol that unlocks liquidity and maximizes the utility of idle Bitcoin assets', + vestingTitle: 'SOLV is Solv Protocol’s native utility token meant for governance, staking, and fee discounts', + }, { id: 'lista', address: '0x232c577a3A9c4ecbeeb213E1eb5519cB0C2FDb0F', diff --git a/packages/ifos/src/queries/fetchUserIfo.ts b/packages/ifos/src/queries/fetchUserIfo.ts index 35443fb797f02..9cd408f01e8d1 100644 --- a/packages/ifos/src/queries/fetchUserIfo.ts +++ b/packages/ifos/src/queries/fetchUserIfo.ts @@ -105,12 +105,23 @@ export async function getUserIfoInfo({ account, ifo, chainId, provider }: GetIfo } } -export async function getCurrentIfoRatio({ chainId, provider }: Omit): Promise { +export async function getCurrentIfoRatio({ chainId, provider, account }: Params): Promise { if (!chainId) { return 0 } try { const ifoCreditContract = getIfoCreditAddressContract(chainId, provider) + if (account) { + const [specialRatio, precision, ratio] = await Promise.all([ + // @ts-ignore + ifoCreditContract.read.userRatioOverride([account]), + // @ts-ignore + ifoCreditContract.read.RATION_PRECISION(), + // @ts-ignore + ifoCreditContract.read.ratio(), + ]) + return new BigNumber((specialRatio || ratio).toString()).div(new BigNumber(precision.toString())).toNumber() + } const [ratio, precision] = await Promise.all([ // @ts-ignore ifoCreditContract.read.ratio(), diff --git a/packages/localization/src/config/translations.json b/packages/localization/src/config/translations.json index be65e30e66f8a..7f97569b35449 100644 --- a/packages/localization/src/config/translations.json +++ b/packages/localization/src/config/translations.json @@ -3678,5 +3678,12 @@ "Please fix form errors.": "Please fix form errors.", "Title is required": "Title is required", "Content is required": "Content is required", - "Please provide valid choices": "Please provide valid choices" + "Please provide valid choices": "Please provide valid choices", + "The vested tokens will be released linearly over a period of %countdown%.": "The vested tokens will be released linearly over a period of %countdown%.", + "%token% IFO starts in": "%token% IFO starts in", + "Join the SOLV Token Launch (IFO) on BNB Chain PancakeSwap": "Join the SOLV Token Launch (IFO) on BNB Chain PancakeSwap", + "The time has already passed.": "The time has already passed.", + "day(s)": "day(s)", + "hour(s)": "hour(s)", + "SOON": "SOON" } diff --git a/packages/tokens/src/constants/bsc.ts b/packages/tokens/src/constants/bsc.ts index 84bced710e837..da766a8084e05 100644 --- a/packages/tokens/src/constants/bsc.ts +++ b/packages/tokens/src/constants/bsc.ts @@ -3341,4 +3341,20 @@ export const bscTokens = { 'YieldNest: BNB Liquid Restaking', 'https://app.yieldnest.finance/restake/ynBNB', ), + listapie: new ERC20Token( + ChainId.BSC, + '0xFceB31A79F71AC9CBDCF853519c1b12D379EdC46', + 18, + 'LISTA-PIE', + 'Lista Pie', + 'https://lista.org/', + ), + solv: new ERC20Token( + ChainId.BSC, + '0x04830A96a23EA718fAA695a5AAe74695AAE3A23f', + 18, + 'SOLV', + 'Solv', + 'https://solv.finance/', + ), } diff --git a/packages/uikit/src/widgets/Ifo/IfoGetTokenModal.tsx b/packages/uikit/src/widgets/Ifo/IfoGetTokenModal.tsx index b57bafb04e5d3..ef49807a56b92 100644 --- a/packages/uikit/src/widgets/Ifo/IfoGetTokenModal.tsx +++ b/packages/uikit/src/widgets/Ifo/IfoGetTokenModal.tsx @@ -1,11 +1,11 @@ import { useTranslation } from "@pancakeswap/localization"; -import { useMatchBreakpoints } from "../../contexts"; -import { Modal, ModalBody } from "../Modal"; -import { Text } from "../../components/Text"; -import { Link } from "../../components/Link"; import { Button } from "../../components/Button"; import { Image } from "../../components/Image"; +import { Link } from "../../components/Link"; import { OpenNewIcon } from "../../components/Svg"; +import { Text } from "../../components/Text"; +import { useMatchBreakpoints } from "../../contexts"; +import { Modal, ModalBody } from "../Modal"; interface Props { symbol: string; @@ -38,7 +38,7 @@ const IfoGetTokenModal: React.FC> = ({ symbol, ad mt="1rem" as={Link} external - href="https://bridge.pancakeswap.finance/" + href="https://pancakeswap.finance/bridge" color="invertedContrast" endIcon={} minWidth="100%" // Bypass the width="fit-content" on Links diff --git a/packages/widgets-internal/ifo/ICakeInfo.tsx b/packages/widgets-internal/ifo/ICakeInfo.tsx index 2bc2af3d1da70..e17a71984e20a 100644 --- a/packages/widgets-internal/ifo/ICakeInfo.tsx +++ b/packages/widgets-internal/ifo/ICakeInfo.tsx @@ -1,9 +1,9 @@ -import { ReactNode, useMemo } from "react"; -import { FlexGap, Text, useTooltip } from "@pancakeswap/uikit"; -import { SpaceProps } from "styled-system"; import { useTranslation } from "@pancakeswap/localization"; +import { FlexGap, Text, useTooltip } from "@pancakeswap/uikit"; import { formatUnixTimestamp } from "@pancakeswap/utils/formatTimestamp"; +import { ReactNode, useMemo } from "react"; import styled from "styled-components"; +import { SpaceProps } from "styled-system"; type Props = { // Unix timestamp of the snapshot