diff --git a/src/app/(walletConnected)/liquidity-pools/deposit/[poolId]/page.tsx b/src/app/(walletConnected)/liquidity-pools/deposit/[poolId]/page.tsx index a8d3831..d8c9bc6 100644 --- a/src/app/(walletConnected)/liquidity-pools/deposit/[poolId]/page.tsx +++ b/src/app/(walletConnected)/liquidity-pools/deposit/[poolId]/page.tsx @@ -13,7 +13,11 @@ import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import LPSlippageDialog from '@/features/liquidityPool/components/LPSlippageDialog' import LPSuccessDialog from '@/features/liquidityPool/components/LPSuccessDialog' -import { useDepositToPool, useGetPoolById } from '@/features/liquidityPool/services' +import { + useDepositToPool, + useGetPoolById, + useGetUserPoolStatsById +} from '@/features/liquidityPool/services' import { PoolData } from '@/features/liquidityPool/types' import SwapItem from '@/features/swap/components/SwapItem' import { useGetUserBalanceByMint, useGetCoinGeckoTokenPrice } from '@/features/swap/services' @@ -92,14 +96,22 @@ export default function LiquidityPoolDeposit({ params }: { params: { poolId: str totalValue: string } | null>(null) - const getMintABalance = useGetUserBalanceByMint({ mintAddress: poolDetailData?.mintA.address ?? '' }) - const getMintBBalance = useGetUserBalanceByMint({ mintAddress: poolDetailData?.mintB.address ?? '' }) + const getMintABalance = useGetUserBalanceByMint({ + mintAddress: poolDetailData?.mintA.address ?? '' + }) + const getMintBBalance = useGetUserBalanceByMint({ + mintAddress: poolDetailData?.mintB.address ?? '' + }) // Get token prices from CoinGecko const getMintATokenPrice = useGetCoinGeckoTokenPrice({ - coinGeckoId: poolDetailData?.mintA.address ? getCoinGeckoId(poolDetailData.mintA.address) : undefined + coinGeckoId: poolDetailData?.mintA.address + ? getCoinGeckoId(poolDetailData.mintA.address) + : undefined }) const getMintBTokenPrice = useGetCoinGeckoTokenPrice({ - coinGeckoId: poolDetailData?.mintB.address ? getCoinGeckoId(poolDetailData.mintB.address) : undefined + coinGeckoId: poolDetailData?.mintB.address + ? getCoinGeckoId(poolDetailData.mintB.address) + : undefined }) // Convert balance from daltons to UI units using token decimals @@ -122,6 +134,33 @@ export default function LiquidityPoolDeposit({ params }: { params: { poolId: str const depositMutation = useDepositToPool() + const mintAPoolAmount = poolDetailData + ? isPoolData(poolDetailData) + ? poolDetailData.mintAmountA + : Number(poolDetailData.reserveA) / Math.pow(10, poolDetailData.mintA.decimals) + : 0 + + const mintBPoolAmount = poolDetailData + ? isPoolData(poolDetailData) + ? poolDetailData.mintAmountB + : Number(poolDetailData.reserveB) / Math.pow(10, poolDetailData.mintB.decimals) + : 0 + + const getUserStats = useGetUserPoolStatsById({ + poolId, + reserveA: mintAPoolAmount, + reserveB: mintBPoolAmount, + mintA: poolDetailData?.mintA, + mintB: poolDetailData?.mintB, + feeRate: poolDetailData?.feeRate ?? 0, + volume24h: !poolDetailData + ? 0 + : isPoolData(poolDetailData) + ? poolDetailData.day.volume + : poolDetailData.volume24h, + isUserStats: true + }) + // Auto-calculate optimal ratio useEffect(() => { if (!poolDetailData || isCalculating) return @@ -131,8 +170,12 @@ export default function LiquidityPoolDeposit({ params }: { params: { poolId: str let reserveA: bigint, reserveB: bigint if (isPoolData(poolDetailData)) { - reserveA = BigInt(Math.floor(poolDetailData.mintAmountA * Math.pow(10, poolDetailData.mintA.decimals))) - reserveB = BigInt(Math.floor(poolDetailData.mintAmountB * Math.pow(10, poolDetailData.mintB.decimals))) + reserveA = BigInt( + Math.floor(poolDetailData.mintAmountA * Math.pow(10, poolDetailData.mintA.decimals)) + ) + reserveB = BigInt( + Math.floor(poolDetailData.mintAmountB * Math.pow(10, poolDetailData.mintB.decimals)) + ) } else { reserveA = pool.reserveA || BigInt(0) reserveB = pool.reserveB || BigInt(0) @@ -207,8 +250,12 @@ export default function LiquidityPoolDeposit({ params }: { params: { poolId: str mintB: poolDetailData.mintB, tokenAccountA: '', tokenAccountB: '', - reserveA: BigInt(Math.floor(poolDetailData.mintAmountA * Math.pow(10, poolDetailData.mintA.decimals))), - reserveB: BigInt(Math.floor(poolDetailData.mintAmountB * Math.pow(10, poolDetailData.mintB.decimals))), + reserveA: BigInt( + Math.floor(poolDetailData.mintAmountA * Math.pow(10, poolDetailData.mintA.decimals)) + ), + reserveB: BigInt( + Math.floor(poolDetailData.mintAmountB * Math.pow(10, poolDetailData.mintB.decimals)) + ), feeRate: poolDetailData.feeRate, tvl: poolDetailData.tvl, volume24h: poolDetailData.day?.volume ?? 0, @@ -252,7 +299,9 @@ export default function LiquidityPoolDeposit({ params }: { params: { poolId: str - + ) return ( -
+
-
-
-

Pool balances

- - - - - -

- Total value of tokens currently held in this pool, displayed per token. -

-
-
-
- -
-
- -
- -
- -
- -
-
-

Links

-
-
-
- {`${baseMint?.name} { - e.currentTarget.src = '/icon-placeholder.svg' - }} - /> -

{baseMint?.symbol}

-
-
- - {shortenAddress(baseMint?.address ?? '', 7)} - - -
-
-
-
- {`${quoteMint?.name} { - e.currentTarget.src = '/icon-placeholder.svg' - }} - /> -

{quoteMint?.symbol}

+ setIsMyStats(value === 'my-stats')} + > + + + Pool Stats + + + My Stats + + + +
+
+

Pool balances

+ + + + + +

+ Total value of tokens currently held in this pool, displayed per token. +

+
+
-
- - {shortenAddress(quoteMint?.address ?? '', 7)} - - + +
+
+ +
+ +
+ +
+ +
+
+

+ Links +

+
+
+
+ {`${baseMint?.name} { + e.currentTarget.src = '/icon-placeholder.svg' + }} + /> +

{baseMint?.symbol}

+
+
+ + {shortenAddress(baseMint?.address ?? '', 7)} + + +
+
+
+
+ {`${quoteMint?.name} { + e.currentTarget.src = '/icon-placeholder.svg' + }} + /> +

{quoteMint?.symbol}

+
+
+ + {shortenAddress(quoteMint?.address ?? '', 7)} + + +
+
+
+
+
+ +
+
+

Your Deposits

+ + + + + +

+ Total value of tokens currently you deposit in this pool, displayed per token. +

+
+
-
-
-
+ +
+
+ +
+ +
+ +
+ +
+ +

Transactions

- + {getTransactionsByPoolId.isLoading ? ( + + ) : ( + + )}
) diff --git a/src/constants/service.ts b/src/constants/service.ts index ea9ea9e..65736a4 100644 --- a/src/constants/service.ts +++ b/src/constants/service.ts @@ -44,6 +44,7 @@ const POOL_SERVICE_KEY = { GET_POOL_BY_ID: 'get-pool-by-id', GET_POOL_BY_MINT: 'get-pool-by-mint', GET_POOL_STATS: 'get-pool-stats', + GET_USER_POOL_STATS: 'get-user-pool-stats', GET_TRANSACTIONS_BY_POOL_ID: 'get-transations-by-pool-id', DEPOSIT_LIQUIDITY: 'deposit-liquidity' } as const diff --git a/src/features/liquidityPool/components/DataTable.tsx b/src/features/liquidityPool/components/DataTable.tsx index 14e3c80..23369b1 100644 --- a/src/features/liquidityPool/components/DataTable.tsx +++ b/src/features/liquidityPool/components/DataTable.tsx @@ -37,9 +37,22 @@ import { DropdownMenuLabel } from '@/components/ui/dropdown-menu' import { Input } from '@/components/ui/input' -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select' import { Skeleton } from '@/components/ui/skeleton' -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow +} from '@/components/ui/table' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' import { useIsMobile } from '@/hooks/isMobile' import { useDebounce } from '@/hooks/useDebounce' @@ -78,7 +91,11 @@ interface AdvancedFilters { } // Enhanced global filter function -function enhancedGlobalFilterFn(row: any, columnId: string, filterValue: string) { +function enhancedGlobalFilterFn( + row: any, + columnId: string, + filterValue: string +) { const { mintA, mintB, id } = row.original as PoolListProps const search = filterValue.toLowerCase() @@ -175,7 +192,13 @@ function TableSkeleton() { ) } -function EmptyState({ hasFilters, onClearFilters }: { hasFilters: boolean; onClearFilters: () => void }) { +function EmptyState({ + hasFilters, + onClearFilters +}: { + hasFilters: boolean + onClearFilters: () => void +}) { return (
@@ -364,7 +387,8 @@ export function DataTable({ const filteredPools = table.getFilteredRowModel().rows.length const currentPage = table.getState().pagination.pageIndex + 1 const totalPages = table.getPageCount() - const startIndex = table.getState().pagination.pageIndex * table.getState().pagination.pageSize + 1 + const startIndex = + table.getState().pagination.pageIndex * table.getState().pagination.pageSize + 1 const endIndex = Math.min(startIndex + table.getState().pagination.pageSize - 1, filteredPools) if (isLoading) { @@ -395,7 +419,9 @@ export function DataTable({
setAdvancedFilters((prev) => ({ ...prev, type: value }))} + onValueChange={(value: FilterType) => + setAdvancedFilters((prev) => ({ ...prev, type: value })) + } > @@ -500,7 +528,12 @@ export function DataTable({ - @@ -728,7 +761,12 @@ export function DataTable({ )} -
@@ -739,7 +777,9 @@ export function DataTable({

- {filteredPools === totalPools ? `${totalPools} total pools` : `${filteredPools} of ${totalPools} pools`} + {filteredPools === totalPools + ? `${totalPools} total pools` + : `${filteredPools} of ${totalPools} pools`}

{enablePagination && filteredPools > 0 && (

@@ -784,11 +824,16 @@ export function DataTable({ {headerGroup.headers.map((header) => ( - {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} + {header.isPlaceholder + ? null + : flexRender(header.column.columnDef.header, header.getContext())} ))} Actions diff --git a/src/features/liquidityPool/components/PoolDetailSkeleton.tsx b/src/features/liquidityPool/components/PoolDetailSkeleton.tsx index ec9ef6f..c2d7ebb 100644 --- a/src/features/liquidityPool/components/PoolDetailSkeleton.tsx +++ b/src/features/liquidityPool/components/PoolDetailSkeleton.tsx @@ -1,68 +1,76 @@ import { Skeleton } from '@/components/ui/skeleton' -export default function PoolDetailSkeleton() { +export function PoolDetailTransactionSkeleton() { + return ( +

+ {/* Table header */} +
+ {['Time', 'Type', 'USD', 'USDC', 'Wallet'].map((col) => ( + + ))} +
+ {/* Table rows */} + {[...Array(8)].map((_, row) => ( +
+ {[...Array(5)].map((_, col) => ( + + ))} +
+ ))} +
+ ) +} + +export function InitialPoolDetailSkeleton() { return (
{/* Header */}
-
- - - - +
+ + + +
-
- - +
+ +
- {/* Pool stats card */} -
-
+
+
- +
+ + +
+
-
{[...Array(4)].map((_, i) => (
- +
))}
{/* Links */} -
+
{[...Array(2)].map((_, i) => (
- - - + + +
))}
{/* Transactions table */} -
- -
- {/* Table header */} -
- {['Time', 'Type', 'USD', 'USDC', 'Wallet'].map((col) => ( - - ))} -
- {/* Table rows */} - {[...Array(8)].map((_, row) => ( -
- {[...Array(5)].map((_, col) => ( - - ))} -
- ))} -
+
+ +
) diff --git a/src/features/liquidityPool/onchain.ts b/src/features/liquidityPool/onchain.ts index 6fec617..852a174 100644 --- a/src/features/liquidityPool/onchain.ts +++ b/src/features/liquidityPool/onchain.ts @@ -1,10 +1,21 @@ import { struct, u8, blob } from '@bbachain/buffer-layout' -import { getAccount, getMint } from '@bbachain/spl-token' -import { PROGRAM_ID as TOKEN_SWAP_PROGRAM_ID } from '@bbachain/spl-token-swap' +import { + ASSOCIATED_TOKEN_PROGRAM_ID, + getAccount, + getAssociatedTokenAddress, + getMint, + TOKEN_PROGRAM_ID +} from '@bbachain/spl-token' +import { PROGRAM_ID as TOKEN_SWAP_PROGRAM_ID, TokenSwap } from '@bbachain/spl-token-swap' import { Connection, PublicKey } from '@bbachain/web3.js' +import axios from 'axios' +import ENDPOINTS from '@/constants/endpoint' +import { getTokenAccounts } from '@/lib/tokenAccount' import { getTokenByAddress, generateTokenDisplayName } from '@/staticData/tokens' +import { getCoinGeckoId } from '../swap/utils' + import { MintInfo } from './types' // Layout for parsing TokenSwap account data @@ -16,92 +27,97 @@ const uint64 = (property: string = 'uint64') => { return blob(8, property) } - interface RawTokenSwap { - version: number - isInitialized: boolean - bumpSeed: number - poolTokenProgramId: Uint8Array - tokenAccountA: Uint8Array - tokenAccountB: Uint8Array - tokenPool: Uint8Array - mintA: Uint8Array - mintB: Uint8Array - feeAccount: Uint8Array - tradeFeeNumerator: Uint8Array - tradeFeeDenominator: Uint8Array - ownerTradeFeeNumerator: Uint8Array - ownerTradeFeeDenominator: Uint8Array - ownerWithdrawFeeNumerator: Uint8Array - ownerWithdrawFeeDenominator: Uint8Array - hostFeeNumerator: Uint8Array - hostFeeDenominator: Uint8Array - curveType: number - curveParameters: Uint8Array - } +interface RawTokenSwap { + version: number + isInitialized: boolean + bumpSeed: number + poolTokenProgramId: Uint8Array + tokenAccountA: Uint8Array + tokenAccountB: Uint8Array + tokenPool: Uint8Array + mintA: Uint8Array + mintB: Uint8Array + feeAccount: Uint8Array + tradeFeeNumerator: Uint8Array + tradeFeeDenominator: Uint8Array + ownerTradeFeeNumerator: Uint8Array + ownerTradeFeeDenominator: Uint8Array + ownerWithdrawFeeNumerator: Uint8Array + ownerWithdrawFeeDenominator: Uint8Array + hostFeeNumerator: Uint8Array + hostFeeDenominator: Uint8Array + curveType: number + curveParameters: Uint8Array +} - export const TokenSwapLayout = struct([ - u8('version'), - u8('isInitialized'), - u8('bumpSeed'), - publicKey('poolTokenProgramId'), - publicKey('tokenAccountA'), - publicKey('tokenAccountB'), - publicKey('tokenPool'), - publicKey('mintA'), - publicKey('mintB'), - publicKey('feeAccount'), - uint64('tradeFeeNumerator'), - uint64('tradeFeeDenominator'), - uint64('ownerTradeFeeNumerator'), - uint64('ownerTradeFeeDenominator'), - uint64('ownerWithdrawFeeNumerator'), - uint64('ownerWithdrawFeeDenominator'), - uint64('hostFeeNumerator'), - uint64('hostFeeDenominator'), - u8('curveType'), - blob(32, 'curveParameters') - ]) - - export interface OnchainPoolData { - address: string - programId: string - swapData: RawTokenSwap - mintA: MintInfo - mintB: MintInfo - tokenAccountA: string - tokenAccountB: string - reserveA: bigint - reserveB: bigint - feeRate: number - tvl: number - volume24h: number - fees24h: number - apr24h: number - } +export const TokenSwapLayout = struct([ + u8('version'), + u8('isInitialized'), + u8('bumpSeed'), + publicKey('poolTokenProgramId'), + publicKey('tokenAccountA'), + publicKey('tokenAccountB'), + publicKey('tokenPool'), + publicKey('mintA'), + publicKey('mintB'), + publicKey('feeAccount'), + uint64('tradeFeeNumerator'), + uint64('tradeFeeDenominator'), + uint64('ownerTradeFeeNumerator'), + uint64('ownerTradeFeeDenominator'), + uint64('ownerWithdrawFeeNumerator'), + uint64('ownerWithdrawFeeDenominator'), + uint64('hostFeeNumerator'), + uint64('hostFeeDenominator'), + u8('curveType'), + blob(32, 'curveParameters') +]) + +export interface OnchainPoolData { + address: string + programId: string + swapData: RawTokenSwap + mintA: MintInfo + mintB: MintInfo + tokenAccountA: string + tokenAccountB: string + reserveA: bigint + reserveB: bigint + feeRate: number + tvl: number + volume24h: number + fees24h: number + apr24h: number +} - /** - * Fetch all pool accounts from the Token Swap Program - */ - export async function getPoolAccounts(connection: Connection): Promise> { - try { - const accounts = await connection.getProgramAccounts(TOKEN_SWAP_PROGRAM_ID, { - filters: [ - { - dataSize: TokenSwapLayout.span // Only get accounts with correct data size - } - ] - }) - return accounts - } catch (error) { - console.error('Error fetching pool accounts:', error) - throw new Error('Failed to fetch pool accounts from onchain') - } +/** + * Fetch all pool accounts from the Token Swap Program + */ +export async function getPoolAccounts( + connection: Connection +): Promise> { + try { + const accounts = await connection.getProgramAccounts(TOKEN_SWAP_PROGRAM_ID, { + filters: [ + { + dataSize: TokenSwapLayout.span // Only get accounts with correct data size + } + ] + }) + return accounts + } catch (error) { + console.error('Error fetching pool accounts:', error) + throw new Error('Failed to fetch pool accounts from onchain') } +} /** * Parse raw pool account data into structured format */ -export function parsePoolData(pubkey: PublicKey, accountData: Buffer): RawTokenSwap & { address: string } { +export function parsePoolData( + pubkey: PublicKey, + accountData: Buffer +): RawTokenSwap & { address: string } { try { const data = new Uint8Array(accountData.buffer, accountData.byteOffset, accountData.byteLength) const swapData = TokenSwapLayout.decode(data) @@ -119,7 +135,10 @@ export function parsePoolData(pubkey: PublicKey, accountData: Buffer): RawTokenS /** * Get token mint information for a given mint address */ -export async function getTokenMintInfo(connection: Connection, mintAddress: PublicKey): Promise { +export async function getTokenMintInfo( + connection: Connection, + mintAddress: PublicKey +): Promise { try { const addressStr = mintAddress.toBase58() @@ -165,7 +184,10 @@ export async function getTokenMintInfo(connection: Connection, mintAddress: Publ /** * Get token account balance for reserve calculation */ -export async function getTokenAccountBalance(connection: Connection, tokenAccount: PublicKey): Promise { +export async function getTokenAccountBalance( + connection: Connection, + tokenAccount: PublicKey +): Promise { try { const account = await getAccount(connection, tokenAccount) return account.amount @@ -206,8 +228,8 @@ export function calculateTVL( reserveB: bigint, mintA: MintInfo, mintB: MintInfo, - priceA: number = 1, - priceB: number = 1 + priceA: number, + priceB: number ): number { const balanceA = Number(reserveA) / Math.pow(10, mintA.decimals) const balanceB = Number(reserveB) / Math.pow(10, mintB.decimals) @@ -240,6 +262,46 @@ export function calculatePoolMetrics( } } +const cache = new Map() + +const STALE_TIME = 60000 // 1 minute +const REFETCH_INTERVAL = 300000 // 5 minutes + +async function getCoinGeckoTokenPrice(id: string | undefined): Promise { + if (!id || id === '') return 1 + + const now = Date.now() + const cached = cache.get(id) + + // If cached and not stale + if (cached && now - cached.timestamp < STALE_TIME) { + return cached.price + } + + try { + const res = await axios.get(ENDPOINTS.COIN_GECKO.GET_SIMPLE_PRICE, { + params: { + ids: id, + vs_currencies: 'usd' + } + }) + + const usdRate = res.data[id]?.usd ?? 1 + + // Update cache + cache.set(id, { price: usdRate, timestamp: now }) + + return usdRate + } catch (error) { + // If fetch fails and we have a recent cached value, return it + if (cached && now - cached.timestamp < REFETCH_INTERVAL) { + return cached.price + } + // Otherwise fallback to default + return 1 + } +} + /** * Process a single pool account into OnchainPoolData */ @@ -264,8 +326,14 @@ export async function processPoolAccount( const tokenAccountBKey = new PublicKey(parsedData.tokenAccountB) // Convert fee numerator/denominator from Uint8Array to bigint - const tradeFeeNumerator = new DataView(parsedData.tradeFeeNumerator.buffer).getBigUint64(0, true) - const tradeFeeDenominator = new DataView(parsedData.tradeFeeDenominator.buffer).getBigUint64(0, true) + const tradeFeeNumerator = new DataView(parsedData.tradeFeeNumerator.buffer).getBigUint64( + 0, + true + ) + const tradeFeeDenominator = new DataView(parsedData.tradeFeeDenominator.buffer).getBigUint64( + 0, + true + ) // Get mint information for both tokens const [mintA, mintB] = await Promise.all([ @@ -279,9 +347,11 @@ export async function processPoolAccount( getTokenAccountBalance(connection, tokenAccountBKey) ]) + const mintAPrice = await getCoinGeckoTokenPrice(getCoinGeckoId(mintA.address)) + const mintBPrice = await getCoinGeckoTokenPrice(getCoinGeckoId(mintB.address)) // Calculate metrics const feeRate = calculateFeeRate(tradeFeeNumerator, tradeFeeDenominator) - const tvl = calculateTVL(reserveA, reserveB, mintA, mintB) + const tvl = calculateTVL(reserveA, reserveB, mintA, mintB, mintAPrice, mintBPrice) const metrics = calculatePoolMetrics(tvl, feeRate) return { @@ -336,3 +406,30 @@ export async function getAllPoolsFromOnchain(connection: Connection): Promise { + const tokenSwapState = await TokenSwap.fromAccountAddress(connection, lpMintAddress) + const mintInfo = await getMint(connection, tokenSwapState.poolMint) + const decimals = mintInfo.decimals + const rawSupply = mintInfo.supply + const totalSupply = Number(rawSupply) / Math.pow(10, decimals) + return totalSupply +} + +export const getUserLPTokens = async ( + connection: Connection, + lpMintAddress: PublicKey, + userPublicKey: PublicKey +): Promise => { + const tokenSwapState = await TokenSwap.fromAccountAddress(connection, lpMintAddress) + const userTokenAccountAddress = await getAssociatedTokenAddress( + tokenSwapState.poolMint, + userPublicKey + ) + const tokenAccountInfo = await getAccount(connection, userTokenAccountAddress) + const mintInfo = await getMint(connection, tokenSwapState.poolMint) + const decimals = mintInfo.decimals + const rawAmount = tokenAccountInfo.amount + const userBalance = Number(rawAmount) / Math.pow(10, decimals) + return userBalance +} diff --git a/src/features/liquidityPool/services.ts b/src/features/liquidityPool/services.ts index 928568b..2c0ee28 100644 --- a/src/features/liquidityPool/services.ts +++ b/src/features/liquidityPool/services.ts @@ -34,11 +34,18 @@ import { getWBBAMintAddress } from '@/staticData/tokens' -import { useGetCoinGeckoTokenPrice } from '../swap/services' +import { useGetCoinGeckoTokenPrice, useGetUserBalanceByMint } from '../swap/services' import { getCoinGeckoId } from '../swap/utils' +import { getLPTokenData } from '../tokens/utils' import { TransactionListProps } from './components/TransactionColumns' -import { getAllPoolsFromOnchain, OnchainPoolData } from './onchain' +import { + getAllPoolsFromOnchain, + getTotalLPSupply, + getUserLPTokens, + OnchainPoolData, + parsePoolData +} from './onchain' import { MintInfo, PoolData, @@ -279,6 +286,85 @@ export const useGetPoolById = ({ poolId }: { poolId: string }) => { }) } +export const useGetUserPoolStatsById = ({ + poolId, + reserveA, + reserveB, + mintA, + mintB, + volume24h, + feeRate, + isUserStats +}: { + poolId: string + reserveA: number + reserveB: number + mintA: MintInfo | undefined + mintB: MintInfo | undefined + volume24h: number + feeRate: number + isUserStats: boolean +}) => { + const { connection } = useConnection() + const { publicKey: ownerAddress } = useWallet() + const baseUSDValue = useGetCoinGeckoTokenPrice({ + coinGeckoId: mintA ? getCoinGeckoId(mintA.address) : '' + }) + + const quoteUSDValue = useGetCoinGeckoTokenPrice({ + coinGeckoId: mintB ? getCoinGeckoId(mintB.address) : '' + }) + + const baseInitialPrice = baseUSDValue.data ?? 0 + const quoteInitialPrice = quoteUSDValue.data ?? 0 + + const areTokenPricesReady = baseUSDValue.isSuccess && quoteUSDValue.isSuccess + + return useQuery({ + enabled: + Boolean(poolId) && + reserveA > 0 && + reserveB > 0 && + isUserStats === true && + Boolean(ownerAddress) && + areTokenPricesReady, + queryKey: [ + SERVICES_KEY.POOL.GET_USER_POOL_STATS, + poolId, + ownerAddress?.toBase58(), + reserveA, + reserveB + ], + queryFn: async () => { + if (!ownerAddress) { + throw new Error('Wallet not connected. Please connect your wallet to create a pool.') + } + + const lpMint = new PublicKey(poolId) + const userLPToken = await getUserLPTokens(connection, lpMint, ownerAddress) + const lpTokenSupply = await getTotalLPSupply(connection, lpMint) + + const userShare = userLPToken / lpTokenSupply + const userMintAReserve = userShare * reserveA + const userMintBReserve = userShare * reserveB + const userReserveTotal = + userMintAReserve * baseInitialPrice + userMintBReserve * quoteInitialPrice + + const totalDailyFees = volume24h * feeRate + const dailyFeeEarnings = totalDailyFees * userShare + + return { + userShare: userShare * 100, + userLPToken, + userMintAReserve, + userMintBReserve, + userReserveTotal, + dailyFeeEarnings + } + } + }) +} + // Pool refresh mutation export const useRefreshPools = () => { const queryClient = useQueryClient() @@ -344,12 +430,16 @@ export const usePoolsBackgroundSync = () => { export const useGetTransactionsByPoolId = ({ poolId, baseMint, - quoteMint + quoteMint, + isFilteredByOwner }: { poolId: string baseMint: MintInfo | undefined quoteMint: MintInfo | undefined + isFilteredByOwner?: boolean }) => { + const { publicKey: ownerAddress } = useWallet() + const isValidParams = !!poolId?.trim() && !!baseMint?.address?.trim() && !!quoteMint?.address?.trim() @@ -372,7 +462,8 @@ export const useGetTransactionsByPoolId = ({ SERVICES_KEY.POOL.GET_TRANSACTIONS_BY_POOL_ID, poolId, baseMint?.address, - quoteMint?.address + quoteMint?.address, + isFilteredByOwner ? ownerAddress?.toBase58() : null ], enabled: isValidParams && areTokenPricesReady, ...CACHE_CONFIG, @@ -388,7 +479,7 @@ export const useGetTransactionsByPoolId = ({ console.log('Transaction count:', transactionData?.length) - const responseData = transactionData + let responseData = transactionData .map((tx) => { const parsedData = processTransactionData(tx, baseMint, quoteMint) if (!parsedData) return null @@ -404,6 +495,10 @@ export const useGetTransactionsByPoolId = ({ }) .filter((item): item is TransactionListProps => item !== null) + if (isFilteredByOwner && ownerAddress) { + responseData = responseData.filter((item) => item.wallet === ownerAddress.toBase58()) + } + return { message: 'Successfully get transaction data', data: responseData } } catch (error) { console.error('Failed to fetch transactions:', error)