Skip to main content
AI/MLjeremylongshore

alchemy-performance-tuning

'Optimize Alchemy SDK performance with caching, batching, and multi-chain

Stars
2,267
Source
jeremylongshore/claude-code-plugins-plus-skills
Updated
2026-05-31
Slug
jeremylongshore--claude-code-plugins-plus-skills--alchemy-performance-tuning
View on GitHubRaw SKILL.md

// install — copy + paste into any project

mkdir -p .claude/skills && curl -fsSL https://raw.githubusercontent.com/jeremylongshore/claude-code-plugins-plus-skills/HEAD/plugins/saas-packs/alchemy-pack/skills/alchemy-performance-tuning/SKILL.md -o .claude/skills/alchemy-performance-tuning.md

Drops the SKILL.md into .claude/skills/alchemy-performance-tuning.md. Works with Claude Code, Cursor, and any agent that loads SKILL.md files from .claude/skills/.

Alchemy Performance Tuning

Overview

Optimize a Web3 application's response time and provider use through freshness-aware caching, bounded parallelism, batching, and real-time subscriptions. Measure improvements against an approved baseline rather than assuming fewer calls always preserves correct chain state.

Performance Targets

Operation Target Latency CU Cost
getBlockNumber < 50ms 10
getBalance < 100ms 19
getTokenBalances < 200ms 50
getNftsForOwner < 300ms 50
getAssetTransfers < 500ms 150
Multi-chain portfolio < 2s ~400

Prerequisites

  • A representative, non-sensitive benchmark workload with baseline latency, cache-hit, error-rate, and compute-unit measurements.
  • An explicit freshness policy for balances, blocks, transfers, ownership, and metadata, approved by the product owner.
  • Monitoring and a rollback flag that can disable cache, batching, or WebSocket changes if correctness or provider behavior regresses.

Instructions

Step 1: Response Caching with TTL

// src/performance/cache.ts
import { Alchemy, Network } from 'alchemy-sdk';

class BlockchainCache {
  private store = new Map<string, { data: any; expiry: number }>();

  // Different TTLs for different data freshness needs
  private TTL: Record<string, number> = {
    blockNumber: 12000,     // 12s (~1 block)
    balance: 30000,         // 30s
    tokenBalances: 60000,   // 60s
    nftOwnership: 300000,   // 5 min (NFTs transfer less frequently)
    contractMetadata: 3600000, // 1 hour (rarely changes)
    tokenMetadata: 86400000,   // 24 hours (almost never changes)
  };

  async cached<T>(category: string, key: string, fetcher: () => Promise<T>): Promise<T> {
    const cacheKey = `${category}:${key}`;
    const entry = this.store.get(cacheKey);
    if (entry && entry.expiry > Date.now()) return entry.data;

    const data = await fetcher();
    this.store.set(cacheKey, { data, expiry: Date.now() + (this.TTL[category] || 30000) });
    return data;
  }

  invalidate(category: string): void {
    for (const key of this.store.keys()) {
      if (key.startsWith(`${category}:`)) this.store.delete(key);
    }
  }
}

const cache = new BlockchainCache();
export { cache };

Step 2: Parallel Multi-Chain Fetching

// src/performance/parallel-fetch.ts
import { Alchemy, Network } from 'alchemy-sdk';
import { cache } from './cache';

const CHAINS = [
  { name: 'ethereum', network: Network.ETH_MAINNET },
  { name: 'polygon', network: Network.MATIC_MAINNET },
  { name: 'arbitrum', network: Network.ARB_MAINNET },
  { name: 'base', network: Network.BASE_MAINNET },
];

async function multiChainBalance(address: string) {
  const results = await Promise.allSettled(
    CHAINS.map(chain =>
      cache.cached('balance', `${chain.name}:${address}`, async () => {
        const client = new Alchemy({ apiKey: process.env.ALCHEMY_API_KEY, network: chain.network });
        const bal = await client.core.getBalance(address);
        return { chain: chain.name, balance: (parseInt(bal.toString()) / 1e18).toFixed(6) };
      })
    )
  );

  return results
    .filter((r): r is PromiseFulfilledResult<any> => r.status === 'fulfilled')
    .map(r => r.value);
}

Step 3: Batch NFT Metadata (Reduce CU)

// src/performance/batch-nft.ts
import { Alchemy, Network } from 'alchemy-sdk';

const alchemy = new Alchemy({ apiKey: process.env.ALCHEMY_API_KEY, network: Network.ETH_MAINNET });

// SLOW: Individual calls = 50 CU each
// async function slowGetMetadata(tokens) {
//   return Promise.all(tokens.map(t => alchemy.nft.getNftMetadata(t.contract, t.tokenId)));
// }

// FAST: Batch call = 50 CU total for up to 100 tokens
async function fastGetMetadata(tokens: Array<{ contractAddress: string; tokenId: string }>) {
  return alchemy.nft.getNftMetadataBatch(tokens);
}

Step 4: WebSocket for Real-Time Data

// src/performance/realtime.ts
import { Alchemy, AlchemySubscription, Network } from 'alchemy-sdk';

const alchemy = new Alchemy({ apiKey: process.env.ALCHEMY_API_KEY, network: Network.ETH_MAINNET });

// Use WebSocket subscriptions instead of polling
function watchAddress(address: string, onActivity: (tx: any) => void) {
  alchemy.ws.on(
    {
      method: AlchemySubscription.PENDING_TRANSACTIONS,
      toAddress: address,
    },
    (tx) => onActivity(tx)
  );
}

// Auto-reconnect on disconnect
alchemy.ws.on('close', () => {
  console.log('WebSocket disconnected — reconnecting in 5s');
  setTimeout(() => alchemy.ws.connect(), 5000);
});

Output

  • TTL-based response cache matching data freshness requirements
  • Parallel multi-chain fetching (4 chains in < 2s)
  • Batch NFT metadata (100x CU reduction)
  • WebSocket subscriptions replacing polling

Examples

Benchmark a public test address across the four listed networks before and after enabling the balance cache. Confirm the cached run reduces provider calls while its displayed data never exceeds the approved 30-second freshness window, and verify a single failed chain remains visibly unavailable rather than silently omitted. Next, send a small synthetic NFT list through the batch path and compare response count with individual calls. If cache age, error rate, or WebSocket reconnect behavior violates the defined threshold, disable that optimization using the rollback flag and investigate from aggregate metrics.

Error Handling

Failure Response
Cache entry exceeds its freshness policy Invalidate it and refresh from the provider before rendering a result.
One chain query fails Preserve successful-chain results and surface an explicit unavailable state for the failed chain.
WebSocket repeatedly disconnects Use bounded reconnect backoff, alert on sustained failure, and fall back to rate-limited polling.
Batch call partially fails Keep successful results, retry only eligible failed items, and respect the provider limit.

Resources

Next Steps

For cost optimization, see alchemy-cost-tuning.