From alchemy-pack
Implements CU-aware throttling for Alchemy API calls using Bottleneck to avoid 429 errors and optimize concurrent blockchain queries within plan limits.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin alchemy-packThis skill is limited to using the following tools:
Alchemy uses Compute Units (CU) to measure API usage. Different methods cost different CU amounts. Rate limits are per-second, and exceeding them returns 429 errors.
Optimizes Alchemy API costs using CU monitoring in TypeScript, caching strategies, batching, and plan recommendations for dApps.
Integrates Alchemy APIs for EVM JSON-RPC calls, token balances, NFT ownership/metadata, transfers, prices, portfolio data, transaction simulation, webhooks, Solana RPC. Covers base URLs, auth, endpoints, pagination. Requires $ALCHEMY_API_KEY.
Integrates Alchemy APIs into application code (servers, backends, dApps, scripts) via API key. Covers EVM JSON-RPC, Token/NFT/Transfers/Prices/Portfolio APIs, Solana RPC/DAS/gRPC, Sui gRPC, Wallets. Requires $ALCHEMY_API_KEY.
Share bugs, ideas, or general feedback.
Alchemy uses Compute Units (CU) to measure API usage. Different methods cost different CU amounts. Rate limits are per-second, and exceeding them returns 429 errors.
| Method | CU Cost | Category |
|---|---|---|
eth_blockNumber | 10 | Core |
eth_getBalance | 19 | Core |
eth_call | 26 | Core |
eth_getTransactionReceipt | 15 | Core |
getTokenBalances | 50 | Enhanced |
getTokenMetadata | 50 | Enhanced |
getAssetTransfers | 150 | Enhanced |
getNftsForOwner | 50 | NFT |
getNftMetadataBatch | 50 | NFT |
getContractMetadata | 50 | NFT |
| Plan | CU/sec | Monthly CU | Price |
|---|---|---|---|
| Free | 330 | 300M | $0 |
| Growth | 660 | 1.2B | $49/mo |
| Scale | Custom | Custom | Custom |
// src/alchemy/throttler.ts
import Bottleneck from 'bottleneck';
const CU_COSTS: Record<string, number> = {
'eth_blockNumber': 10,
'eth_getBalance': 19,
'eth_call': 26,
'getTokenBalances': 50,
'getAssetTransfers': 150,
'getNftsForOwner': 50,
};
// Free tier: 330 CU/sec = ~16 getBalance calls/sec
const limiter = new Bottleneck({
reservoir: 330, // CU budget per interval
reservoirRefreshInterval: 1000, // Refresh every second
reservoirRefreshAmount: 330, // Reset to max CU/sec
maxConcurrent: 10, // Max parallel requests
minTime: 50, // Min 50ms between requests
});
limiter.on('depleted', () => {
console.warn('CU budget depleted — queueing requests');
});
async function throttledAlchemyCall<T>(
method: string,
operation: () => Promise<T>,
): Promise<T> {
const cost = CU_COSTS[method] || 26; // Default to eth_call cost
return limiter.schedule({ weight: cost }, operation);
}
export { throttledAlchemyCall, limiter };
// src/alchemy/batch-optimizer.ts
import { Alchemy } from 'alchemy-sdk';
// Instead of N individual calls, batch when possible
async function batchGetBalances(
alchemy: Alchemy,
addresses: string[],
): Promise<Map<string, string>> {
const results = new Map<string, string>();
// Process in chunks to stay under rate limit
const CHUNK_SIZE = 10;
for (let i = 0; i < addresses.length; i += CHUNK_SIZE) {
const chunk = addresses.slice(i, i + CHUNK_SIZE);
const balances = await Promise.all(
chunk.map(addr => alchemy.core.getBalance(addr))
);
chunk.forEach((addr, idx) => {
results.set(addr, (parseInt(balances[idx].toString()) / 1e18).toFixed(6));
});
// Pause between chunks to stay under CU limit
if (i + CHUNK_SIZE < addresses.length) {
await new Promise(r => setTimeout(r, 200));
}
}
return results;
}
export { batchGetBalances };
// src/alchemy/retry.ts
async function withAlchemyRetry<T>(
operation: () => Promise<T>,
maxRetries: number = 5,
): Promise<T> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (err: any) {
if (err.response?.status !== 429 || attempt === maxRetries) throw err;
const retryAfter = parseInt(err.response.headers?.['retry-after'] || '1');
const jitter = Math.random() * 500;
const delay = retryAfter * 1000 + jitter;
console.log(`Rate limited — retry ${attempt}/${maxRetries} in ${delay.toFixed(0)}ms`);
await new Promise(r => setTimeout(r, delay));
}
}
throw new Error('Unreachable');
}
For security best practices, see alchemy-security-basics.