speeding up getAllTokens by using multicall instead of sequentially calling details for each token

This commit is contained in:
2025-12-19 10:35:59 -04:00
parent 1ac26aeec0
commit 0e5921255e

View File

@@ -198,88 +198,131 @@ export function useGetPoolsByToken(tokenAddress: `0x${string}` | undefined, offs
return; 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<string, { symbol: string | null; decimals: number | null }>();
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 // Map to store available tokens with their swap routes
const tokenRoutesMap = new Map<string, AvailableToken>(); const tokenRoutesMap = new Map<string, AvailableToken>();
// For each working pool, fetch all tokens and track indices // For each working pool, process tokens
for (const poolAddress of workingPools) { for (let poolIdx = 0; poolIdx < workingPools.length; poolIdx++) {
try { const poolAddress = workingPools[poolIdx];
const tokensInPool = await publicClient.readContract({ const poolTokensResult = poolTokensResults[poolIdx];
address: poolAddress,
abi: IPartyPoolABI,
functionName: 'allTokens',
}) as readonly `0x${string}`[];
// Find the input token index in this pool if (poolTokensResult.status !== 'success') {
const inputTokenIndex = tokensInPool.findIndex( console.error('Failed to fetch tokens for pool', poolAddress);
(token) => token.toLowerCase() === tokenAddress.toLowerCase() continue;
); }
if (inputTokenIndex === -1) { const tokensInPool = poolTokensResult.result as readonly `0x${string}`[];
console.error('Input token not found in pool', poolAddress);
// 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; continue;
} }
// Process each token in the pool const outputTokenData = tokenDataMap.get(outputTokenAddress.toLowerCase());
for (let outputTokenIndex = 0; outputTokenIndex < tokensInPool.length; outputTokenIndex++) { const outputTokenSymbol = outputTokenData?.symbol ?? null;
const outputTokenAddress = tokensInPool[outputTokenIndex]; const outputTokenDecimal = outputTokenData?.decimals ?? null;
// Skip if it's the same as the input token // Skip tokens with the same symbol as the selected token
if (outputTokenIndex === inputTokenIndex) { if (!outputTokenSymbol || outputTokenSymbol === selectedTokenSymbol) {
continue; continue;
} }
// Get the symbol of this token // Skip tokens if decimals failed to load
const outputTokenSymbol = await publicClient.readContract({ 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, address: outputTokenAddress,
abi: ERC20ABI, symbol: outputTokenSymbol,
functionName: 'symbol', swapRoutes: [],
}).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,
}); });
} }
} 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; 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[] = []; const details: TokenDetails[] = [];
// Make individual calls for each token
for (let i = 0; i < tokens.length; i++) { for (let i = 0; i < tokens.length; i++) {
const tokenAddress = tokens[i]; const baseIndex = i * 4;
try { const nameResult = results[baseIndex];
const [name, symbol, decimals, balance] = await Promise.all([ const symbolResult = results[baseIndex + 1];
publicClient.readContract({ const decimalsResult = results[baseIndex + 2];
address: tokenAddress, const balanceResult = results[baseIndex + 3];
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)),
]);
details.push({ details.push({
address: tokenAddress, address: tokens[i],
name: name as string, name: nameResult.status === 'success' ? (nameResult.result as string) : 'Unknown',
symbol: symbol as string, symbol: symbolResult.status === 'success' ? (symbolResult.result as string) : '???',
decimals: Number(decimals), decimals: decimalsResult.status === 'success' ? Number(decimalsResult.result) : 18,
balance: balance as bigint, balance: balanceResult.status === 'success' ? (balanceResult.result as bigint) : BigInt(0),
index: i, 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,
});
}
} }
setTokenDetails(details); setTokenDetails(details);