From 0e5921255e7f48db392d1ce428217cb4b2e6687d Mon Sep 17 00:00:00 2001 From: surbhi Date: Fri, 19 Dec 2025 10:35:59 -0400 Subject: [PATCH] speeding up getAllTokens by using multicall instead of sequentially calling details for each token --- src/hooks/usePartyPlanner.ts | 272 ++++++++++++++++++++--------------- 1 file changed, 157 insertions(+), 115 deletions(-) diff --git a/src/hooks/usePartyPlanner.ts b/src/hooks/usePartyPlanner.ts index 81c38e0..85675ff 100644 --- a/src/hooks/usePartyPlanner.ts +++ b/src/hooks/usePartyPlanner.ts @@ -198,88 +198,131 @@ export function useGetPoolsByToken(tokenAddress: `0x${string}` | undefined, offs return; } + // First, fetch all tokens from all working pools + const poolTokensContracts = workingPools.map(poolAddress => ({ + address: poolAddress, + abi: IPartyPoolABI, + functionName: 'allTokens', + })); + + const poolTokensResults = await publicClient.multicall({ + contracts: poolTokensContracts as any, + allowFailure: true, + }); + + // Build a flat list of all unique token addresses we need to query + const uniqueTokenAddresses = new Set<`0x${string}`>(); + uniqueTokenAddresses.add(tokenAddress); // Add input token + + poolTokensResults.forEach((result) => { + if (result.status === 'success') { + const tokens = result.result as readonly `0x${string}`[]; + tokens.forEach(token => uniqueTokenAddresses.add(token)); + } + }); + + const tokenAddressesArray = Array.from(uniqueTokenAddresses); + + // Build multicall for all token symbols and decimals + const tokenDataContracts = tokenAddressesArray.flatMap(addr => [ + { + address: addr, + abi: ERC20ABI, + functionName: 'symbol', + }, + { + address: addr, + abi: ERC20ABI, + functionName: 'decimals', + }, + ]); + + const tokenDataResults = await publicClient.multicall({ + contracts: tokenDataContracts as any, + allowFailure: true, + }); + + // Parse token data into a map + const tokenDataMap = new Map(); + for (let i = 0; i < tokenAddressesArray.length; i++) { + const symbolResult = tokenDataResults[i * 2]; + const decimalsResult = tokenDataResults[i * 2 + 1]; + tokenDataMap.set(tokenAddressesArray[i].toLowerCase(), { + symbol: symbolResult.status === 'success' ? (symbolResult.result as string) : null, + decimals: decimalsResult.status === 'success' ? Number(decimalsResult.result) : null, + }); + } + // Map to store available tokens with their swap routes const tokenRoutesMap = new Map(); - // For each working pool, fetch all tokens and track indices - for (const poolAddress of workingPools) { - try { - const tokensInPool = await publicClient.readContract({ - address: poolAddress, - abi: IPartyPoolABI, - functionName: 'allTokens', - }) as readonly `0x${string}`[]; + // For each working pool, process tokens + for (let poolIdx = 0; poolIdx < workingPools.length; poolIdx++) { + const poolAddress = workingPools[poolIdx]; + const poolTokensResult = poolTokensResults[poolIdx]; - // Find the input token index in this pool - const inputTokenIndex = tokensInPool.findIndex( - (token) => token.toLowerCase() === tokenAddress.toLowerCase() - ); + if (poolTokensResult.status !== 'success') { + console.error('Failed to fetch tokens for pool', poolAddress); + continue; + } - if (inputTokenIndex === -1) { - console.error('Input token not found in pool', poolAddress); + const tokensInPool = poolTokensResult.result as readonly `0x${string}`[]; + + // Find the input token index in this pool + const inputTokenIndex = tokensInPool.findIndex( + (token) => token.toLowerCase() === tokenAddress.toLowerCase() + ); + + if (inputTokenIndex === -1) { + console.error('Input token not found in pool', poolAddress); + continue; + } + + const inputTokenData = tokenDataMap.get(tokenAddress.toLowerCase()); + const inputTokenDecimal = inputTokenData?.decimals ?? null; + + // Process each token in the pool + for (let outputTokenIndex = 0; outputTokenIndex < tokensInPool.length; outputTokenIndex++) { + const outputTokenAddress = tokensInPool[outputTokenIndex]; + + // Skip if it's the same as the input token + if (outputTokenIndex === inputTokenIndex) { continue; } - // Process each token in the pool - for (let outputTokenIndex = 0; outputTokenIndex < tokensInPool.length; outputTokenIndex++) { - const outputTokenAddress = tokensInPool[outputTokenIndex]; + const outputTokenData = tokenDataMap.get(outputTokenAddress.toLowerCase()); + const outputTokenSymbol = outputTokenData?.symbol ?? null; + const outputTokenDecimal = outputTokenData?.decimals ?? null; - // Skip if it's the same as the input token - if (outputTokenIndex === inputTokenIndex) { - continue; - } + // Skip tokens with the same symbol as the selected token + if (!outputTokenSymbol || outputTokenSymbol === selectedTokenSymbol) { + continue; + } - // Get the symbol of this token - const outputTokenSymbol = await publicClient.readContract({ + // Skip tokens if decimals failed to load + if (inputTokenDecimal === null || outputTokenDecimal === null) { + console.error(`Failed to load decimals for token ${outputTokenAddress} or ${tokenAddress}`); + continue; + } + + // Create or update the available token entry + const tokenKey = outputTokenAddress.toLowerCase(); + if (!tokenRoutesMap.has(tokenKey)) { + tokenRoutesMap.set(tokenKey, { address: outputTokenAddress, - abi: ERC20ABI, - functionName: 'symbol', - }).catch(() => null); - - const inputTokenDecimal = await publicClient.readContract({ - address: tokenAddress, - abi: ERC20ABI, - functionName: 'decimals', - }).catch(() => null); - - const outputTokenDecimal = await publicClient.readContract({ - address: outputTokenAddress, - abi: ERC20ABI, - functionName: 'decimals', - }).catch(() => null); - - // Skip tokens with the same symbol as the selected token - if (!outputTokenSymbol || outputTokenSymbol === selectedTokenSymbol) { - continue; - } - - // Skip tokens if decimals failed to load - if (inputTokenDecimal === null || outputTokenDecimal === null) { - console.error(`Failed to load decimals for token ${outputTokenAddress} or ${tokenAddress}`); - continue; - } - - // Create or update the available token entry - const tokenKey = outputTokenAddress.toLowerCase(); - if (!tokenRoutesMap.has(tokenKey)) { - tokenRoutesMap.set(tokenKey, { - address: outputTokenAddress, - symbol: outputTokenSymbol, - swapRoutes: [], - }); - } - - // Add this swap route - tokenRoutesMap.get(tokenKey)!.swapRoutes.push({ - poolAddress, - inputTokenIndex, - outputTokenIndex, - inputTokenDecimal, - outputTokenDecimal, + symbol: outputTokenSymbol, + swapRoutes: [], }); } - } catch (err) { - console.error('Error fetching tokens from pool', poolAddress, err); + + // Add this swap route + tokenRoutesMap.get(tokenKey)!.swapRoutes.push({ + poolAddress, + inputTokenIndex, + outputTokenIndex, + inputTokenDecimal, + outputTokenDecimal, + }); } } @@ -326,55 +369,54 @@ export function useTokenDetails(userAddress: `0x${string}` | undefined) { return; } + // Build multicall contracts array - 4 calls per token (name, symbol, decimals, balanceOf) + const contracts = tokens.flatMap((tokenAddress) => [ + { + address: tokenAddress, + abi: ERC20ABI, + functionName: 'name', + }, + { + address: tokenAddress, + abi: ERC20ABI, + functionName: 'symbol', + }, + { + address: tokenAddress, + abi: ERC20ABI, + functionName: 'decimals', + }, + { + address: tokenAddress, + abi: ERC20ABI, + functionName: 'balanceOf', + args: [userAddress], + }, + ]); + + // Execute multicall + const results = await publicClient.multicall({ + contracts: contracts as any, + allowFailure: true, + }); + + // Parse results const details: TokenDetails[] = []; - - // Make individual calls for each token for (let i = 0; i < tokens.length; i++) { - const tokenAddress = tokens[i]; - try { - const [name, symbol, decimals, balance] = await Promise.all([ - publicClient.readContract({ - address: tokenAddress, - abi: ERC20ABI, - functionName: 'name', - }).catch(() => 'Unknown'), - publicClient.readContract({ - address: tokenAddress, - abi: ERC20ABI, - functionName: 'symbol', - }).catch(() => '???'), - publicClient.readContract({ - address: tokenAddress, - abi: ERC20ABI, - functionName: 'decimals', - }).catch(() => 18), - publicClient.readContract({ - address: tokenAddress, - abi: ERC20ABI, - functionName: 'balanceOf', - args: [userAddress], - }).catch(() => BigInt(0)), - ]); + const baseIndex = i * 4; + const nameResult = results[baseIndex]; + const symbolResult = results[baseIndex + 1]; + const decimalsResult = results[baseIndex + 2]; + const balanceResult = results[baseIndex + 3]; - details.push({ - address: tokenAddress, - name: name as string, - symbol: symbol as string, - decimals: Number(decimals), - balance: balance as bigint, - index: i, - }); - } catch (err) { - // Add token with fallback values if individual call fails - details.push({ - address: tokenAddress, - name: 'Unknown', - symbol: '???', - decimals: 18, - balance: BigInt(0), - index: i, - }); - } + details.push({ + address: tokens[i], + name: nameResult.status === 'success' ? (nameResult.result as string) : 'Unknown', + symbol: symbolResult.status === 'success' ? (symbolResult.result as string) : '???', + decimals: decimalsResult.status === 'success' ? Number(decimalsResult.result) : 18, + balance: balanceResult.status === 'success' ? (balanceResult.result as bigint) : BigInt(0), + index: i, + }); } setTokenDetails(details);