diff --git a/apps/web/src/config/abi/ido.ts b/apps/web/src/config/abi/ido.ts new file mode 100644 index 0000000000000..3ef309652f39f --- /dev/null +++ b/apps/web/src/config/abi/ido.ts @@ -0,0 +1,260 @@ +export const idoABI = [ + { inputs: [], stateMutability: 'nonpayable', type: 'constructor' }, + { inputs: [], name: 'AddressesLengthNotCorrect', type: 'error' }, + { inputs: [], name: 'AlreadyHarvested', type: 'error' }, + { inputs: [], name: 'AlreadyInitialized', type: 'error' }, + { inputs: [], name: 'AmountMustBeZero', type: 'error' }, + { inputs: [], name: 'AmountMustExceedZero', type: 'error' }, + { inputs: [], name: 'CanNotBeLPToken', type: 'error' }, + { inputs: [], name: 'CanNotBeOfferingToken', type: 'error' }, + { inputs: [], name: 'DidNotParticipate', type: 'error' }, + { inputs: [], name: 'EmptyUserAddress', type: 'error' }, + { inputs: [], name: 'EndTimeTooFar', type: 'error' }, + { inputs: [], name: 'FlatTaxRateMustBe0WhenHasTaxIsFalse', type: 'error' }, + { inputs: [], name: 'FlatTaxRateMustBeLessThan1e12', type: 'error' }, + { inputs: [], name: 'IDOHasEnded', type: 'error' }, + { inputs: [], name: 'IDOHasStarted', type: 'error' }, + { inputs: [], name: 'NewAmountAboveUserCap', type: 'error' }, + { inputs: [], name: 'NotEnoughLPTokens', type: 'error' }, + { inputs: [], name: 'NotEnoughOfferingTokens', type: 'error' }, + { inputs: [], name: 'NotFactory', type: 'error' }, + { inputs: [], name: 'NotMeetAnyoneOfRequiredConditions', type: 'error' }, + { inputs: [], name: 'PoolNotSet', 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' }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: 'address', name: 'tokenAddress', type: 'address' }, + { indexed: false, internalType: 'uint256', name: 'amountTokens', type: 'uint256' }, + ], + name: 'AdminTokenRecovery', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: 'uint256', name: 'amountLP', type: 'uint256' }, + { indexed: false, internalType: 'uint256', name: 'amountOfferingToken', type: 'uint256' }, + ], + name: 'AdminWithdraw', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'user', type: 'address' }, + { indexed: false, internalType: 'uint256', name: 'amount', type: 'uint256' }, + ], + name: 'Deposit', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'user', type: 'address' }, + { indexed: false, internalType: 'uint256', name: 'offeringAmount', type: 'uint256' }, + { indexed: false, internalType: 'uint256', name: 'excessAmount', type: 'uint256' }, + ], + name: 'Harvest', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: 'uint256', name: 'startTimestamp', type: 'uint256' }, + { indexed: false, internalType: 'uint256', name: 'endTimestamp', type: 'uint256' }, + ], + name: 'NewStartAndEndTimestamps', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'previousOwner', type: 'address' }, + { indexed: true, internalType: 'address', name: 'newOwner', type: 'address' }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: 'uint256', name: 'offeringAmountPool', type: 'uint256' }, + { indexed: false, internalType: 'uint256', name: 'raisingAmountPool', type: 'uint256' }, + ], + name: 'PoolParametersSet', + type: 'event', + }, + { + inputs: [], + name: 'MAX_BUFFER_SECONDS', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: '_poolInformation', + outputs: [ + { internalType: 'uint256', name: 'raisingAmountPool', type: 'uint256' }, + { internalType: 'uint256', name: 'offeringAmountPool', type: 'uint256' }, + { internalType: 'uint256', name: 'capPerUserInLP', type: 'uint256' }, + { internalType: 'bool', name: 'hasTax', type: 'bool' }, + { internalType: 'uint256', name: 'flatTaxRate', type: 'uint256' }, + { internalType: 'uint256', name: 'totalAmountPool', type: 'uint256' }, + { internalType: 'uint256', name: 'sumTaxesOverflow', type: 'uint256' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + name: 'addresses', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: '_amount', type: 'uint256' }], + name: 'depositPool', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'endTimestamp', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: '_lpAmount', type: 'uint256' }, + { internalType: 'uint256', name: '_offerAmount', type: 'uint256' }, + ], + name: 'finalWithdraw', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { inputs: [], name: 'harvestPool', outputs: [], stateMutability: 'nonpayable', type: 'function' }, + { + inputs: [ + { internalType: 'address[]', name: '_addresses', type: 'address[]' }, + { internalType: 'uint256[]', name: '_startAndEndTimestamps', type: 'uint256[]' }, + { internalType: 'uint256', name: '_maxBufferSeconds', type: 'uint256' }, + ], + name: 'initialize', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: '_tokenAddress', type: 'address' }, + { internalType: 'uint256', name: '_tokenAmount', type: 'uint256' }, + ], + name: 'recoverWrongTokens', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { inputs: [], name: 'renounceOwnership', outputs: [], stateMutability: 'nonpayable', type: 'function' }, + { + inputs: [ + { internalType: 'uint256', name: '_offeringAmountPool', type: 'uint256' }, + { internalType: 'uint256', name: '_raisingAmountPool', type: 'uint256' }, + { internalType: 'uint256', name: '_limitPerUserInLP', type: 'uint256' }, + { internalType: 'bool', name: '_hasTax', type: 'bool' }, + { internalType: 'uint256', name: '_flatTaxRate', type: 'uint256' }, + ], + name: 'setPool', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'startTimestamp', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'newOwner', type: 'address' }], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256[]', name: '_startAndEndTimestamps', type: 'uint256[]' }], + name: 'updateStartAndEndTimestamps', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'viewPoolInformation', + outputs: [ + { internalType: 'uint256', name: '', type: 'uint256' }, + { internalType: 'uint256', name: '', type: 'uint256' }, + { internalType: 'uint256', name: '', type: 'uint256' }, + { internalType: 'bool', name: '', type: 'bool' }, + { internalType: 'uint256', name: '', type: 'uint256' }, + { internalType: 'uint256', name: '', type: 'uint256' }, + { internalType: 'uint256', name: '', type: 'uint256' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'viewPoolTaxRateOverflow', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: '_user', type: 'address' }], + name: 'viewUserAllocation', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: '_user', type: 'address' }], + name: 'viewUserInfo', + outputs: [ + { internalType: 'uint256', name: '', type: 'uint256' }, + { internalType: 'bool', name: '', type: 'bool' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: '_user', type: 'address' }], + name: 'viewUserOfferingAndRefundingAmounts', + outputs: [ + { internalType: 'uint256', name: '', type: 'uint256' }, + { internalType: 'uint256', name: '', type: 'uint256' }, + { internalType: 'uint256', name: '', type: 'uint256' }, + ], + stateMutability: 'view', + type: 'function', + }, +] as const diff --git a/apps/web/src/config/constants/contracts.ts b/apps/web/src/config/constants/contracts.ts index e885c3cdc8576..5e8f31247c8c4 100644 --- a/apps/web/src/config/constants/contracts.ts +++ b/apps/web/src/config/constants/contracts.ts @@ -260,4 +260,7 @@ export default { [ChainId.ZKSYNC_TESTNET]: '0xbfcCF87Ee5cd03d4550Cc1526Bf152cc2EE1C7AB', [ChainId.ZKSYNC]: '0xB774c13bA5a665713037c42A12f0ED9De70585cB', }, + ido: { + [ChainId.BSC_TESTNET]: '0xCd2358dd6FD92447ACAdB9f1A437A658151a2C25', + }, } as const satisfies Record<string, Record<number, `0x${string}`>> diff --git a/apps/web/src/hooks/useContract.ts b/apps/web/src/hooks/useContract.ts index 96acc756051bd..cb7f9023dfa25 100644 --- a/apps/web/src/hooks/useContract.ts +++ b/apps/web/src/hooks/useContract.ts @@ -28,6 +28,7 @@ import { getFarmAuctionContract, getFixedStakingContract, getGaugesVotingContract, + getIDOContract, getIfoCreditAddressContract, getLotteryV2Contract, getMasterChefContract, @@ -587,3 +588,10 @@ export const useRevenueSharingPoolGatewayContract = () => { return useMemo(() => getRevenueSharingPoolGatewayContract(signer ?? undefined, chainId), [signer, chainId]) } + +export const useIDOContract = () => { + const { chainId } = useActiveChainId() + const { data: signer } = useWalletClient() + + return useMemo(() => getIDOContract(signer ?? undefined, chainId), [chainId, signer]) +} diff --git a/apps/web/src/utils/addressHelpers.ts b/apps/web/src/utils/addressHelpers.ts index d25adeb298ea5..bfe40fcd55d39 100644 --- a/apps/web/src/utils/addressHelpers.ts +++ b/apps/web/src/utils/addressHelpers.ts @@ -215,3 +215,7 @@ export const getRevenueSharingVeCakeAddressNoFallback = (chainId?: number) => { export const getRevenueSharingPoolGatewayAddress = (chainId?: number) => { return getAddressFromMap(addresses.revenueSharingPoolGateway, chainId) } + +export const getIDOAddress = (chainId?: number) => { + return getAddressFromMap(addresses.ido, chainId) +} diff --git a/apps/web/src/utils/contractHelpers.ts b/apps/web/src/utils/contractHelpers.ts index 6f786d854c97d..248a06ae57a57 100644 --- a/apps/web/src/utils/contractHelpers.ts +++ b/apps/web/src/utils/contractHelpers.ts @@ -16,15 +16,16 @@ import { getCalcGaugesVotingAddress, getCrossFarmingReceiverAddress, getCrossFarmingSenderAddress, + getCrossFarmingVaultAddress, getFarmAuctionAddress, getFixedStakingAddress, getGaugesVotingAddress, + getIDOAddress, getLotteryV2Address, getMasterChefV2Address, getMasterChefV3Address, getNftMarketAddress, getNftSaleAddress, - getCrossFarmingVaultAddress, getPancakeProfileAddress, getPancakeSquadAddress, getPancakeVeSenderV2Address, @@ -53,8 +54,8 @@ import { import { predictionsV1ABI, predictionsV2ABI, predictionsV3ABI } from '@pancakeswap/prediction' import { crossFarmingProxyABI } from 'config/abi/crossFarmingProxy' import { crossFarmingSenderABI } from 'config/abi/crossFarmingSender' -import { nftSaleABI } from 'config/abi/nftSale' import { crossFarmingVaultABI } from 'config/abi/crossFarmingVault' +import { nftSaleABI } from 'config/abi/nftSale' import { pointCenterIfoABI } from 'config/abi/pointCenterIfo' import { stableSwapNativeHelperABI } from 'config/abi/stableSwapNativeHelper' @@ -82,6 +83,7 @@ import { chainlinkOracleABI } from 'config/abi/chainlinkOracle' import { crossFarmingReceiverABI } from 'config/abi/crossFarmingReceiver' import { farmAuctionABI } from 'config/abi/farmAuction' import { fixedStakingABI } from 'config/abi/fixedStaking' +import { idoABI } from 'config/abi/ido' import { lotteryV2ABI } from 'config/abi/lotteryV2' import { lpTokenABI } from 'config/abi/lpTokenAbi' import { masterChefV2ABI } from 'config/abi/masterchefV2' @@ -592,3 +594,12 @@ export const getRevenueSharingPoolGatewayContract = (signer?: WalletClient, chai chainId, }) } + +export const getIDOContract = (signer?: WalletClient, chainId?: number) => { + return getContract({ + abi: idoABI, + address: getIDOAddress(chainId) ?? getIDOAddress(ChainId.BSC), + signer, + chainId, + }) +} diff --git a/apps/web/src/views/Idos/hooks/ido/usdIDOStatus.ts b/apps/web/src/views/Idos/hooks/ido/usdIDOStatus.ts new file mode 100644 index 0000000000000..12eb11833b83e --- /dev/null +++ b/apps/web/src/views/Idos/hooks/ido/usdIDOStatus.ts @@ -0,0 +1,29 @@ +import { type Currency, CurrencyAmount, Percent } from '@pancakeswap/swap-sdk-core' +import { useMemo } from 'react' +import { useIDOCurrencies } from './useIDOCurrencies' +import { useIDOPoolInfo } from './useIDOPoolInfo' + +export type IDOStatus = { + progress: Percent + currentStakedAmount: CurrencyAmount<Currency> +} + +export const useIDOStatus = () => { + const { stakeCurrency } = useIDOCurrencies() + const { data: poolInfo } = useIDOPoolInfo() + + const progress = useMemo(() => { + if (!poolInfo) return new Percent(0, 100) + return new Percent(poolInfo.totalAmountPool, poolInfo.raisingAmountPool) + }, [poolInfo]) + + const currentStakedAmount = useMemo(() => { + if (!stakeCurrency || !poolInfo) return undefined + return CurrencyAmount.fromRawAmount(stakeCurrency, poolInfo.totalAmountPool) + }, [poolInfo, stakeCurrency]) + + return { + progress, + currentStakedAmount, + } +} diff --git a/apps/web/src/views/Idos/hooks/ido/useIDOClaimCallback.tsx b/apps/web/src/views/Idos/hooks/ido/useIDOClaimCallback.tsx new file mode 100644 index 0000000000000..4f157a147841b --- /dev/null +++ b/apps/web/src/views/Idos/hooks/ido/useIDOClaimCallback.tsx @@ -0,0 +1,30 @@ +import { useTranslation } from '@pancakeswap/localization' +import { useToast } from '@pancakeswap/uikit' +import { ToastDescriptionWithTx } from 'components/Toast' +import useAccountActiveChain from 'hooks/useAccountActiveChain' +import useCatchTxError from 'hooks/useCatchTxError' +import { useIDOContract } from 'hooks/useContract' +import { useCallback } from 'react' + +export const useIDOClaimCallback = () => { + const idoContract = useIDOContract() + const { t } = useTranslation() + const { account } = useAccountActiveChain() + const { toastSuccess } = useToast() + const { fetchWithCatchTxError, loading: isPending } = useCatchTxError() + + const claim = useCallback(async () => { + if (!account || !idoContract) return + const receipt = await fetchWithCatchTxError(() => + idoContract.write.harvestPool({ + account, + chain: idoContract.chain, + }), + ) + if (receipt?.status) { + toastSuccess(t('Claim successful'), <ToastDescriptionWithTx txHash={receipt.transactionHash} />) + } + }, [account, idoContract, fetchWithCatchTxError, toastSuccess, t]) + + return { claim, isPending } +} diff --git a/apps/web/src/views/Idos/hooks/ido/useIDOConfig.ts b/apps/web/src/views/Idos/hooks/ido/useIDOConfig.ts new file mode 100644 index 0000000000000..d196403cdb7e0 --- /dev/null +++ b/apps/web/src/views/Idos/hooks/ido/useIDOConfig.ts @@ -0,0 +1,40 @@ +import { type Currency, CurrencyAmount, Price } from '@pancakeswap/swap-sdk-core' +import { useMemo } from 'react' +import { useIDOCurrencies } from './useIDOCurrencies' +import { useIDOPoolInfo } from './useIDOPoolInfo' + +export type IDOConfig = { + totalSale: bigint + startTimestamp: number + endTimestamp: number + duration: number + pricePerToken: Price<Currency, Currency> | undefined + maxStakePerUser: CurrencyAmount<Currency> | undefined +} + +export const useIDOConfig = () => { + const { data: poolInfo } = useIDOPoolInfo() + const { stakeCurrency, offeringCurrency } = useIDOCurrencies() + + return useMemo(() => { + return { + totalSale: poolInfo?.offeringAmountPool ?? 0n, + startTimestamp: poolInfo?.startTimestamp ?? 0, + endTimestamp: poolInfo?.endTimestamp ?? 0, + duration: + poolInfo?.endTimestamp && poolInfo?.startTimestamp ? poolInfo?.endTimestamp - poolInfo?.startTimestamp : 0, + pricePerToken: + stakeCurrency && offeringCurrency + ? new Price( + stakeCurrency, + offeringCurrency, + poolInfo?.raisingAmountPool ?? 0n, + poolInfo?.offeringAmountPool ?? 0n, + ) + : undefined, + maxStakePerUser: stakeCurrency + ? CurrencyAmount.fromRawAmount(stakeCurrency, poolInfo?.capPerUserInLP ?? 0n) + : undefined, + } satisfies IDOConfig + }, [poolInfo, stakeCurrency, offeringCurrency]) +} diff --git a/apps/web/src/views/Idos/hooks/ido/useIDOCurrencies.ts b/apps/web/src/views/Idos/hooks/ido/useIDOCurrencies.ts new file mode 100644 index 0000000000000..0d1c8dad4492c --- /dev/null +++ b/apps/web/src/views/Idos/hooks/ido/useIDOCurrencies.ts @@ -0,0 +1,69 @@ +import { useQuery } from '@tanstack/react-query' +import { QUERY_SETTINGS_IMMUTABLE } from 'config/constants' +import { useCurrency } from 'hooks/Tokens' +import { useActiveChainId } from 'hooks/useActiveChainId' +import { useIDOContract } from 'hooks/useContract' +import { getViemClients } from 'utils/viem' +import type { Address } from 'viem/accounts' + +type IDOAddresses = { + lpToken: Address + offeringToken: Address + adminAddress: Address +} + +export const useIDOAddresses = () => { + const { chainId } = useActiveChainId() + const idoContract = useIDOContract() + + return useQuery({ + queryKey: ['idoAddresses', chainId], + queryFn: async (): Promise<IDOAddresses> => { + const publicClient = getViemClients({ chainId }) + if (!idoContract || !publicClient) throw new Error('IDO contract not found') + + const [lpToken, offeringToken, adminAddress] = await publicClient.multicall({ + allowFailure: false, + contracts: [ + { + address: idoContract.address, + abi: idoContract.abi, + functionName: 'addresses', + args: [0n], + }, + { + address: idoContract.address, + abi: idoContract.abi, + functionName: 'addresses', + args: [1n], + }, + { + address: idoContract.address, + abi: idoContract.abi, + functionName: 'addresses', + args: [2n], + }, + ], + }) + + return { + lpToken, + offeringToken, + adminAddress, + } + }, + enabled: !!idoContract, + ...QUERY_SETTINGS_IMMUTABLE, + }) +} + +export const useIDOCurrencies = () => { + const { data: addresses } = useIDOAddresses() + const stakeCurrency = useCurrency(addresses?.lpToken) + const offeringCurrency = useCurrency(addresses?.offeringToken) + + return { + stakeCurrency, + offeringCurrency, + } +} diff --git a/apps/web/src/views/Idos/hooks/ido/useIDODepositCallback.tsx b/apps/web/src/views/Idos/hooks/ido/useIDODepositCallback.tsx new file mode 100644 index 0000000000000..2db83723dd913 --- /dev/null +++ b/apps/web/src/views/Idos/hooks/ido/useIDODepositCallback.tsx @@ -0,0 +1,40 @@ +import { useTranslation } from '@pancakeswap/localization' +import type { Currency, CurrencyAmount } from '@pancakeswap/swap-sdk-core' +import { useToast } from '@pancakeswap/uikit' +import { ToastDescriptionWithTx } from 'components/Toast' +import useAccountActiveChain from 'hooks/useAccountActiveChain' +import useCatchTxError from 'hooks/useCatchTxError' +import { useIDOContract } from 'hooks/useContract' +import { useCallback } from 'react' + +export const useIDODepositCallback = () => { + const idoContract = useIDOContract() + const { t } = useTranslation() + const { account } = useAccountActiveChain() + const { toastSuccess } = useToast() + const { fetchWithCatchTxError, loading: isPending } = useCatchTxError() + + const deposit = useCallback( + async (amount: CurrencyAmount<Currency>) => { + if (!account || !idoContract) return + const value = amount.currency.isNative ? amount.quotient : 0n + const amountPool = amount.currency.isNative ? 0n : amount.quotient + const receipt = await fetchWithCatchTxError(() => + idoContract.write.depositPool([amountPool], { + account, + chain: idoContract.chain, + value, + }), + ) + if (receipt?.status) { + toastSuccess(t('Deposit successful'), <ToastDescriptionWithTx txHash={receipt.transactionHash} />) + } + }, + [account, idoContract, fetchWithCatchTxError, toastSuccess, t], + ) + + return { + deposit, + isPending, + } +} diff --git a/apps/web/src/views/Idos/hooks/ido/useIDOPoolInfo.ts b/apps/web/src/views/Idos/hooks/ido/useIDOPoolInfo.ts new file mode 100644 index 0000000000000..1e94fb7f87ec2 --- /dev/null +++ b/apps/web/src/views/Idos/hooks/ido/useIDOPoolInfo.ts @@ -0,0 +1,94 @@ +import { useQuery } from '@tanstack/react-query' +import { useActiveChainId } from 'hooks/useActiveChainId' +import { useIDOContract } from 'hooks/useContract' +import { getViemClients } from 'utils/viem' + +export type IDOPoolInfo = { + /** + * Amount of tokens raised in the pool + */ + raisingAmountPool: bigint + /** + * Amount of tokens offered in the pool + */ + offeringAmountPool: bigint + /** + * Maximum amount of tokens a user can stake in the pool + */ + capPerUserInLP: bigint + /** + * Whether the pool has a tax + */ + hasTax: boolean + /** + * Flat tax rate + */ + flatTaxRate: bigint + /** + * Total amount of tokens staked in the pool + */ + totalAmountPool: bigint + /** + * Sum of taxes overflow + */ + sumTaxesOverflow: bigint + /** + * Start timestamp of the pool + */ + startTimestamp: number + /** + * End timestamp of the pool + */ + endTimestamp: number +} + +export const useIDOPoolInfo = () => { + const { chainId } = useActiveChainId() + const idoContract = useIDOContract() + + return useQuery({ + queryKey: ['idoPoolInfo', chainId], + queryFn: async (): Promise<IDOPoolInfo> => { + const publicClient = getViemClients({ chainId }) + if (!idoContract || !publicClient) throw new Error('IDO contract not found') + + const [ + [raisingAmountPool, offeringAmountPool, capPerUserInLP, hasTax, flatTaxRate, totalAmountPool, sumTaxesOverflow], + startTimestamp, + endTimestamp, + ] = await publicClient.multicall({ + contracts: [ + { + address: idoContract.address, + abi: idoContract.abi, + functionName: 'viewPoolInformation', + }, + { + address: idoContract.address, + abi: idoContract.abi, + functionName: 'startTimestamp', + }, + { + address: idoContract.address, + abi: idoContract.abi, + functionName: 'endTimestamp', + }, + ], + allowFailure: false, + }) + + return { + raisingAmountPool, + offeringAmountPool, + capPerUserInLP, + hasTax, + flatTaxRate, + totalAmountPool, + sumTaxesOverflow, + startTimestamp: Number(startTimestamp), + endTimestamp: Number(endTimestamp), + } + }, + enabled: !!idoContract, + }) +} diff --git a/apps/web/src/views/Idos/hooks/ido/useIDOUserInfo.ts b/apps/web/src/views/Idos/hooks/ido/useIDOUserInfo.ts new file mode 100644 index 0000000000000..dae7c8f08e188 --- /dev/null +++ b/apps/web/src/views/Idos/hooks/ido/useIDOUserInfo.ts @@ -0,0 +1,27 @@ +import { useQuery } from '@tanstack/react-query' +import useAccountActiveChain from 'hooks/useAccountActiveChain' +import { useIDOContract } from 'hooks/useContract' + +export type IDOUserInfo = { + amountPool: bigint + claimedPool: boolean +} + +export const useIDOUserInfo = () => { + const { chainId, account } = useAccountActiveChain() + const idoContract = useIDOContract() + + return useQuery({ + queryKey: ['idoUserInfo', account, chainId], + queryFn: async (): Promise<IDOUserInfo> => { + if (!account || !idoContract) throw new Error('IDO contract not found') + + const [amountPool, claimedPool] = await idoContract.read.viewUserInfo([account]) + return { + amountPool, + claimedPool, + } + }, + enabled: !!account && !!idoContract, + }) +} diff --git a/apps/web/src/views/Idos/hooks/ido/useIDOUserStatus.ts b/apps/web/src/views/Idos/hooks/ido/useIDOUserStatus.ts new file mode 100644 index 0000000000000..39a91d4cc2d54 --- /dev/null +++ b/apps/web/src/views/Idos/hooks/ido/useIDOUserStatus.ts @@ -0,0 +1,57 @@ +import { type Currency, CurrencyAmount } from '@pancakeswap/swap-sdk-core' +import { useQuery } from '@tanstack/react-query' +import useAccountActiveChain from 'hooks/useAccountActiveChain' +import { useIDOContract } from 'hooks/useContract' +import { useMemo } from 'react' +import { useIDOCurrencies } from './useIDOCurrencies' +import { useIDOUserInfo } from './useIDOUserInfo' + +export type IDOUserStatus = { + stakedAmount: CurrencyAmount<Currency> + offeringCurrency: Currency | undefined + stakeTax: CurrencyAmount<Currency> + stakeRefund: CurrencyAmount<Currency> +} + +export const useIDOUserStatus = () => { + const { data: userInfo } = useIDOUserInfo() + const { data: offeringAndRefundingAmounts } = useViewUserOfferingAndRefundingAmounts() + const { stakeCurrency } = useIDOCurrencies() + + const stakedAmount = useMemo(() => { + if (!stakeCurrency || !userInfo) return undefined + return CurrencyAmount.fromRawAmount(stakeCurrency, userInfo.amountPool) + }, [stakeCurrency, userInfo]) + + const stakeTax = useMemo(() => { + if (!stakeCurrency) return undefined + return CurrencyAmount.fromRawAmount(stakeCurrency, offeringAndRefundingAmounts?.userTaxAmount ?? 0n) + }, [stakeCurrency, offeringAndRefundingAmounts]) + + const stakeRefund = useMemo(() => { + if (!stakeCurrency) return undefined + return CurrencyAmount.fromRawAmount(stakeCurrency, offeringAndRefundingAmounts?.userRefundingAmount ?? 0n) + }, [stakeCurrency, offeringAndRefundingAmounts]) + + return { + stakedAmount, + stakeTax, + stakeRefund, + } +} + +const useViewUserOfferingAndRefundingAmounts = () => { + const idoContract = useIDOContract() + const { account } = useAccountActiveChain() + + return useQuery({ + queryKey: ['idoUserOfferingAndRefundingAmounts', idoContract?.address, account], + queryFn: async (): Promise<{ userOfferingAmount: bigint; userRefundingAmount: bigint; userTaxAmount: bigint }> => { + if (!idoContract) throw new Error('IDO contract not found') + const [userOfferingAmount, userRefundingAmount, userTaxAmount] = + await idoContract.read.viewUserOfferingAndRefundingAmounts([account!]) + return { userOfferingAmount, userRefundingAmount, userTaxAmount } + }, + enabled: !!account && !!idoContract, + }) +}