From 90f6c9fa60111b77b3600804160cf185c9a4d3c6 Mon Sep 17 00:00:00 2001 From: Chef Jerry <144641937+ChefJerry@users.noreply.github.com> Date: Wed, 8 Jan 2025 14:57:23 +0800 Subject: [PATCH] feat: contract hooks (#11121) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ## PR-Codex overview This PR introduces functionality for an Initial DEX Offering (IDO) feature. It adds new hooks, constants, and contract interactions related to IDOs, allowing users to deposit, claim, and retrieve information about their participation in IDOs. ### Detailed summary - Added `getIDOAddress` function in `addressHelpers.ts`. - Introduced IDO-related constants in `contracts.ts`. - Created `useIDOContract` hook in `useContract.ts`. - Added hooks for user info, status, deposit, and claim related to IDOs. - Defined IDO ABI in `ido.ts`. - Implemented queries for IDO pool info and user details. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` --- apps/web/src/config/abi/ido.ts | 260 ++++++++++++++++++ apps/web/src/config/constants/contracts.ts | 3 + apps/web/src/hooks/useContract.ts | 8 + apps/web/src/utils/addressHelpers.ts | 4 + apps/web/src/utils/contractHelpers.ts | 15 +- .../src/views/Idos/hooks/ido/usdIDOStatus.ts | 29 ++ .../Idos/hooks/ido/useIDOClaimCallback.tsx | 30 ++ .../src/views/Idos/hooks/ido/useIDOConfig.ts | 40 +++ .../views/Idos/hooks/ido/useIDOCurrencies.ts | 69 +++++ .../Idos/hooks/ido/useIDODepositCallback.tsx | 40 +++ .../views/Idos/hooks/ido/useIDOPoolInfo.ts | 94 +++++++ .../views/Idos/hooks/ido/useIDOUserInfo.ts | 27 ++ .../views/Idos/hooks/ido/useIDOUserStatus.ts | 57 ++++ 13 files changed, 674 insertions(+), 2 deletions(-) create mode 100644 apps/web/src/config/abi/ido.ts create mode 100644 apps/web/src/views/Idos/hooks/ido/usdIDOStatus.ts create mode 100644 apps/web/src/views/Idos/hooks/ido/useIDOClaimCallback.tsx create mode 100644 apps/web/src/views/Idos/hooks/ido/useIDOConfig.ts create mode 100644 apps/web/src/views/Idos/hooks/ido/useIDOCurrencies.ts create mode 100644 apps/web/src/views/Idos/hooks/ido/useIDODepositCallback.tsx create mode 100644 apps/web/src/views/Idos/hooks/ido/useIDOPoolInfo.ts create mode 100644 apps/web/src/views/Idos/hooks/ido/useIDOUserInfo.ts create mode 100644 apps/web/src/views/Idos/hooks/ido/useIDOUserStatus.ts 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> 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 +} + +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'), ) + } + }, [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 | undefined + maxStakePerUser: CurrencyAmount | 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 => { + 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) => { + 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'), ) + } + }, + [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 => { + 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 => { + 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 + offeringCurrency: Currency | undefined + stakeTax: CurrencyAmount + stakeRefund: CurrencyAmount +} + +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, + }) +}