-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
486 additions
and
75 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// Default to production URL | ||
const DEFAULT_GRAPHQL_URL = 'https://the-compact-indexer-2.ponder-dev.com/' | ||
|
||
interface Config { | ||
graphqlUrl: string | ||
} | ||
|
||
export const config: Config = { | ||
graphqlUrl: import.meta.env.VITE_GRAPHQL_INDEXER_URL || DEFAULT_GRAPHQL_URL, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,80 +1,150 @@ | ||
import { useState, useEffect, useRef, useCallback } from 'react'; | ||
import { useAccount } from 'wagmi'; | ||
import { useState, useEffect, useRef, useCallback } from 'react' | ||
import { useAccount } from 'wagmi' | ||
import { useResourceLocks } from './useResourceLocks' | ||
import { formatUnits } from 'viem' | ||
|
||
interface Token { | ||
tokenAddress: string | ||
name: string | ||
symbol: string | ||
decimals: number | ||
} | ||
|
||
interface ResourceLock { | ||
resetPeriod: number | ||
isMultichain: boolean | ||
} | ||
|
||
interface Balance { | ||
chainId: string; | ||
lockId: string; | ||
allocatableBalance: string; | ||
allocatedBalance: string; | ||
balanceAvailableToAllocate: string; | ||
withdrawalStatus: number; | ||
chainId: string | ||
lockId: string | ||
allocatableBalance: string | ||
allocatedBalance: string | ||
balanceAvailableToAllocate: string | ||
withdrawalStatus: number | ||
// Token details from indexer | ||
token?: Token | ||
// Resource lock details from indexer | ||
resourceLock?: ResourceLock | ||
// Formatted balances using token decimals | ||
formattedAllocatableBalance?: string | ||
formattedAllocatedBalance?: string | ||
formattedAvailableBalance?: string | ||
} | ||
|
||
interface UseBalancesResult { | ||
balances: Balance[]; | ||
error: string | null; | ||
isLoading: boolean; | ||
balances: Balance[] | ||
error: string | null | ||
isLoading: boolean | ||
} | ||
|
||
interface ResourceLockItem { | ||
resourceLock: { | ||
lockId: string | ||
token: Token | ||
resetPeriod: number | ||
isMultichain: boolean | ||
} | ||
} | ||
|
||
export function useBalances(): UseBalancesResult { | ||
const { address, isConnected } = useAccount(); | ||
const [balances, setBalances] = useState<Balance[]>([]); | ||
const [error, setError] = useState<string | null>(null); | ||
const [isLoading, setIsLoading] = useState(false); | ||
const isFetchingRef = useRef(false); | ||
|
||
const fetchBalances = useCallback(async () => { | ||
if (!isConnected || !address || isFetchingRef.current) return; | ||
const { address, isConnected } = useAccount() | ||
const [balances, setBalances] = useState<Balance[]>([]) | ||
const [error, setError] = useState<string | null>(null) | ||
const [isLoading, setIsLoading] = useState(false) | ||
const isFetchingRef = useRef(false) | ||
|
||
// Get resource lock details from indexer | ||
const { data: resourceLocksData, error: resourceLocksError, isLoading: resourceLocksLoading } = useResourceLocks() | ||
|
||
const fetchBalances = useCallback(async (): Promise<void> => { | ||
if (!isConnected || !address || isFetchingRef.current) return | ||
|
||
isFetchingRef.current = true; | ||
isFetchingRef.current = true | ||
|
||
try { | ||
const sessionId = localStorage.getItem(`session-${address}`); | ||
const sessionId = localStorage.getItem(`session-${address}`) | ||
if (!sessionId) { | ||
throw new Error('No session ID found'); | ||
throw new Error('No session ID found') | ||
} | ||
|
||
const response = await fetch('/balances', { | ||
headers: { | ||
'x-session-id': sessionId | ||
} | ||
}); | ||
}) | ||
|
||
if (!response.ok) throw new Error('Failed to fetch balances.'); | ||
if (!response.ok) throw new Error('Failed to fetch balances.') | ||
|
||
const data = await response.json(); | ||
const data = await response.json() | ||
|
||
// Only update state if data has actually changed | ||
setBalances(prevBalances => { | ||
const newBalances = data.balances; | ||
const hasChanged = JSON.stringify(prevBalances) !== JSON.stringify(newBalances); | ||
return hasChanged ? newBalances : prevBalances; | ||
}); | ||
const newBalances = data.balances.map((balance: Balance) => { | ||
// Find matching resource lock from indexer data | ||
const resourceLock = resourceLocksData?.resourceLocks.items.find( | ||
(item: ResourceLockItem) => item.resourceLock.lockId === balance.lockId | ||
) | ||
|
||
if (resourceLock) { | ||
const token = resourceLock.resourceLock.token | ||
const decimals = token.decimals | ||
|
||
return { | ||
...balance, | ||
token, | ||
resourceLock: { | ||
resetPeriod: resourceLock.resourceLock.resetPeriod, | ||
isMultichain: resourceLock.resourceLock.isMultichain | ||
}, | ||
formattedAllocatableBalance: formatUnits(BigInt(balance.allocatableBalance), decimals), | ||
formattedAllocatedBalance: formatUnits(BigInt(balance.allocatedBalance), decimals), | ||
formattedAvailableBalance: formatUnits(BigInt(balance.balanceAvailableToAllocate), decimals) | ||
} | ||
} | ||
|
||
return balance | ||
}) | ||
|
||
const hasChanged = JSON.stringify(prevBalances) !== JSON.stringify(newBalances) | ||
return hasChanged ? newBalances : prevBalances | ||
}) | ||
|
||
setError(null); | ||
setError(null) | ||
} catch (err) { | ||
setError(err instanceof Error ? err.message : 'Failed to fetch balances'); | ||
setError(err instanceof Error ? err.message : 'Failed to fetch balances') | ||
} finally { | ||
isFetchingRef.current = false; | ||
isFetchingRef.current = false | ||
} | ||
}, [isConnected, address]); | ||
}, [isConnected, address, resourceLocksData]) | ||
|
||
useEffect(() => { | ||
// Initial load should show loading state | ||
if (isConnected && address) { | ||
setIsLoading(true); | ||
fetchBalances().finally(() => setIsLoading(false)); | ||
setIsLoading(true) | ||
void fetchBalances().finally(() => setIsLoading(false)) | ||
} | ||
|
||
// Set up polling interval | ||
const intervalId = setInterval(fetchBalances, 5000); | ||
const intervalId = setInterval(() => void fetchBalances(), 1000) // Poll every second | ||
|
||
// Cleanup on unmount or address change | ||
return () => { | ||
clearInterval(intervalId); | ||
isFetchingRef.current = false; | ||
}; | ||
}, [fetchBalances, isConnected, address]); | ||
clearInterval(intervalId) | ||
isFetchingRef.current = false | ||
} | ||
}, [fetchBalances, isConnected, address]) | ||
|
||
// Set error from resource locks if present | ||
useEffect(() => { | ||
if (resourceLocksError) { | ||
setError(resourceLocksError instanceof Error ? resourceLocksError.message : 'Failed to fetch resource locks') | ||
} | ||
}, [resourceLocksError]) | ||
|
||
return { balances, error, isLoading }; | ||
return { | ||
balances, | ||
error, | ||
isLoading: isLoading || resourceLocksLoading | ||
} | ||
} |
Oops, something went wrong.