diff --git a/frontend/src/components/BalanceDisplay.tsx b/frontend/src/components/BalanceDisplay.tsx index c5160c4..f3d5173 100644 --- a/frontend/src/components/BalanceDisplay.tsx +++ b/frontend/src/components/BalanceDisplay.tsx @@ -24,9 +24,8 @@ interface SelectedLockData { } // Helper function to format time remaining -function formatTimeRemaining(expiryTimestamp: number): string { - const now = Math.floor(Date.now() / 1000); - const diff = expiryTimestamp - now; +function formatTimeRemaining(expiryTimestamp: number, currentTime: number): string { + const diff = expiryTimestamp - currentTime; if (diff <= 0) return 'Ready'; @@ -86,7 +85,7 @@ export function BalanceDisplay({ const [selectedLock, setSelectedLock] = useState( null ); - const [, setCurrentTime] = useState(() => Math.floor(Date.now() / 1000)); + const [currentTime, setCurrentTime] = useState(() => Math.floor(Date.now() / 1000)); const [isSessionIdDialogOpen, setIsSessionIdDialogOpen] = useState(false); const handleDisableWithdrawal = useCallback( @@ -100,8 +99,8 @@ export function BalanceDisplay({ showNotification({ type: 'success', - title: 'Forced Withdrawal Disabled', - message: 'Your forced withdrawal has been disabled', + title: 'Resource Lock Reactivated', + message: 'Your resource lock has been reactivated', }); } catch (error) { console.error('Error disabling forced withdrawal:', error); @@ -153,13 +152,13 @@ export function BalanceDisplay({ return balances.map((balance) => ({ ...balance, timeRemaining: balance.withdrawableAt - ? formatTimeRemaining(parseInt(balance.withdrawableAt)) + ? formatTimeRemaining(parseInt(balance.withdrawableAt), currentTime) : '', resetPeriodFormatted: balance.resourceLock?.resetPeriod ? formatResetPeriod(balance.resourceLock.resetPeriod) : '', })); - }, [balances]); + }, [balances, currentTime]); if (!isConnected) return null; @@ -201,10 +200,9 @@ export function BalanceDisplay({ ); const withdrawableAt = parseInt(balance.withdrawableAt || '0'); - const now = Math.floor(Date.now() / 1000); const canExecuteWithdrawal = parseInt(balance.withdrawalStatus.toString()) !== 0 && - withdrawableAt <= now; + withdrawableAt <= currentTime; return (
{balance.withdrawalStatus === 0 ? 'Active' - : withdrawableAt <= now - ? 'Withdrawal Ready' - : `Withdrawal Pending (${balance.timeRemaining})`} + : withdrawableAt <= currentTime + ? 'Forced Withdrawals Enabled' + : `Forced Withdrawals Enabled in ${formatTimeRemaining(withdrawableAt, currentTime)}`}
diff --git a/frontend/src/components/DepositForm.tsx b/frontend/src/components/DepositForm.tsx index 4b740ae..6737aa1 100644 --- a/frontend/src/components/DepositForm.tsx +++ b/frontend/src/components/DepositForm.tsx @@ -1,16 +1,23 @@ import { useState } from 'react'; -import { useAccount, useBalance } from 'wagmi'; -import { formatEther, parseEther, parseUnits } from 'viem'; +import { useAccount, useBalance, useChainId } from 'wagmi'; +import { formatEther, parseEther, parseUnits, isAddress } from 'viem'; import { useCompact } from '../hooks/useCompact'; import { useNotification } from '../hooks/useNotification'; import { useERC20 } from '../hooks/useERC20'; import { useAllocatorAPI } from '../hooks/useAllocatorAPI'; +const chainNames = { + '1': 'Ethereum', + '10': 'Optimism', + '8453': 'Base', +}; + type TokenType = 'native' | 'erc20'; export function DepositForm() { const { address, isConnected } = useAccount(); const { data: ethBalance } = useBalance({ address }); + const chainId = useChainId(); const [amount, setAmount] = useState(''); const [tokenType, setTokenType] = useState('native'); const [tokenAddress, setTokenAddress] = useState(''); @@ -72,7 +79,8 @@ export function DepositForm() { : BigInt(0); if (rawBalance && parsedAmount > balanceBigInt) { - return { type: 'error', message: 'Insufficient Balance' }; + const chainName = chainNames[chainId.toString()] || `Chain ${chainId}`; + return { type: 'error', message: `Insufficient ${symbol || 'token'} balance on ${chainName}` }; } if (parsedAmount > allowanceBigInt) { return { type: 'warning', message: 'Insufficient Allowance' }; @@ -88,7 +96,8 @@ export function DepositForm() { try { const parsedAmount = parseEther(amount); if (parsedAmount > ethBalance.value) { - return { type: 'error', message: 'Insufficient ETH Balance' }; + const chainName = chainNames[chainId.toString()] || `Chain ${chainId}`; + return { type: 'error', message: `Insufficient native token balance on ${chainName}` }; } return null; } catch { @@ -254,47 +263,6 @@ export function DepositForm() { - {/* Amount Input */} -
- - setAmount(e.target.value)} - placeholder="0.0" - className={`w-full px-3 py-2 bg-gray-800 border rounded-lg text-gray-300 focus:outline-none transition-colors ${ - amountValidation?.type === 'error' - ? 'border-red-500 focus:border-red-500' - : amountValidation?.type === 'warning' - ? 'border-yellow-500 focus:border-yellow-500' - : 'border-gray-700 focus:border-[#00ff00]' - }`} - /> - {amountValidation && ( -

- {amountValidation.message} -

- )} -
- {/* Token Address Input (for ERC20) */} {tokenType === 'erc20' && (
@@ -306,30 +274,77 @@ export function DepositForm() { value={tokenAddress} onChange={(e) => setTokenAddress(e.target.value)} placeholder="0x..." - className="w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-gray-300 focus:outline-none focus:border-[#00ff00] transition-colors" + className={`w-full px-3 py-2 bg-gray-800 border rounded-lg text-gray-300 focus:outline-none transition-colors ${ + !isValid && tokenAddress && !isLoadingToken + ? 'border-red-500 focus:border-red-500' + : 'border-gray-700 focus:border-[#00ff00]' + }`} /> {tokenAddress && !isValid && !isLoadingToken && ( -

- Invalid ERC20 token address -

+

Invalid token address

)} - {tokenAddress && isLoadingToken && ( -

- Retrieving token information... -

+ {isLoadingToken && isAddress(tokenAddress) && ( +

Loading token info...

)} - {isValid && ( -
-

- {name} ({symbol}) -

-

Balance: {balance || '0'}

-

Allowance: {allowance || '0'}

+ {isValid && name && symbol && ( +
+
+ {name} ({symbol}) + + Allowance on The Compact: {Number(allowance || '0').toLocaleString(undefined, { + maximumFractionDigits: 6, + minimumFractionDigits: 0 + })} {symbol} + +
)}
)} + {/* Amount Input */} + {(tokenType === 'native' || (tokenType === 'erc20' && isValid)) && ( +
+ + setAmount(e.target.value)} + placeholder="0.0" + className={`w-full px-3 py-2 bg-gray-800 border rounded-lg text-gray-300 focus:outline-none transition-colors ${ + amountValidation?.type === 'error' + ? 'border-red-500 focus:border-red-500' + : amountValidation?.type === 'warning' + ? 'border-yellow-500 focus:border-yellow-500' + : 'border-gray-700 focus:border-[#00ff00]' + }`} + /> + {amountValidation && ( +

+ {amountValidation.message} +

+ )} +
+ )} + {/* Submit Buttons */} {amountValidation?.type === 'warning' && ( @@ -515,11 +525,11 @@ export function Transfer({ )} {withdrawalStatus !== 0 && ( )}
diff --git a/frontend/src/hooks/useERC20.ts b/frontend/src/hooks/useERC20.ts index a89d3db..47aed81 100644 --- a/frontend/src/hooks/useERC20.ts +++ b/frontend/src/hooks/useERC20.ts @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; import { useReadContract, useWriteContract, useAccount } from 'wagmi'; -import { formatUnits, parseUnits } from 'viem'; +import { formatUnits, parseUnits, isAddress } from 'viem'; import { ERC20_ABI } from '../constants/contracts'; export function useERC20(tokenAddress?: `0x${string}`) { @@ -13,56 +13,79 @@ export function useERC20(tokenAddress?: `0x${string}`) { const [allowance, setAllowance] = useState(); const [rawBalance, setRawBalance] = useState(); const [rawAllowance, setRawAllowance] = useState(); + const [isLoading, setIsLoading] = useState(false); + + const shouldLoad = Boolean(tokenAddress && isAddress(tokenAddress)); // Read token info - const { data: decimalsData } = useReadContract({ + const { data: decimalsData, isLoading: isLoadingDecimals } = useReadContract({ address: tokenAddress, abi: ERC20_ABI, functionName: 'decimals', query: { - enabled: Boolean(tokenAddress), + enabled: shouldLoad, }, }); - const { data: symbolData } = useReadContract({ + const { data: symbolData, isLoading: isLoadingSymbol } = useReadContract({ address: tokenAddress, abi: ERC20_ABI, functionName: 'symbol', query: { - enabled: Boolean(tokenAddress), + enabled: shouldLoad, }, }); - const { data: nameData } = useReadContract({ + const { data: nameData, isLoading: isLoadingName } = useReadContract({ address: tokenAddress, abi: ERC20_ABI, functionName: 'name', query: { - enabled: Boolean(tokenAddress), + enabled: shouldLoad, }, }); - const { data: balanceData } = useReadContract({ + const { data: balanceData, isLoading: isLoadingBalance } = useReadContract({ address: tokenAddress, abi: ERC20_ABI, functionName: 'balanceOf', args: [address!], query: { - enabled: Boolean(tokenAddress && address), + enabled: shouldLoad && Boolean(address), }, }); - const { data: allowanceData } = useReadContract({ + const { data: allowanceData, isLoading: isLoadingAllowance } = useReadContract({ address: tokenAddress, abi: ERC20_ABI, functionName: 'allowance', args: [address!, tokenAddress!], query: { - enabled: Boolean(tokenAddress && address), + enabled: shouldLoad && Boolean(address), }, }); - const { writeContract } = useWriteContract(); + // Update loading state + useEffect(() => { + if (!shouldLoad) { + setIsLoading(false); + return; + } + setIsLoading( + isLoadingDecimals || + isLoadingSymbol || + isLoadingName || + isLoadingBalance || + isLoadingAllowance + ); + }, [ + shouldLoad, + isLoadingDecimals, + isLoadingSymbol, + isLoadingName, + isLoadingBalance, + isLoadingAllowance + ]); // Update state when data changes useEffect(() => { @@ -71,10 +94,10 @@ export function useERC20(tokenAddress?: `0x${string}`) { setDecimals(Number(decimalsData)); setSymbol(symbolData as string); setName(nameData as string); - } else { + } else if (shouldLoad) { setIsValid(false); } - }, [decimalsData, symbolData, nameData]); + }, [decimalsData, symbolData, nameData, shouldLoad]); // Update balance useEffect(() => { @@ -92,6 +115,8 @@ export function useERC20(tokenAddress?: `0x${string}`) { } }, [allowanceData, decimals]); + const { writeContract } = useWriteContract(); + const approve = async () => { if (!tokenAddress || !address) throw new Error('Not ready'); @@ -115,6 +140,6 @@ export function useERC20(tokenAddress?: `0x${string}`) { rawBalance, rawAllowance, approve, - isLoading: !decimalsData || !symbolData || !nameData, + isLoading, }; }