Alchemy SDK Patterns
SkillDev toolsGives your agent ready-made code patterns for building Web3 apps with the Alchemy SDK.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Alchemy SDK Patterns skill
About this capability
Route Alchemy work to viem, direct Data APIs, Wallet APIs v5, or Solana Web3.js without reviving the archived SDK. Use when designing shared clients and adapters. Trigger with "Alchemy SDK pattern", "replace alchemy-sdk", or "choose an Alchemy client".
What this skill tells your AI
The instructions your AI receives, as published by jeremylongshore/tons-of-skills-marketplace in skills/.curated/alchemy-sdk-patterns/SKILL.md and read by ahel’s review.
Overview
Production patterns for the alchemy-sdk package: singleton clients, multi-chain factories, response caching, and type-safe contract wrappers.
Prerequisites
- A server-side environment with a managed provider key and a typed list of supported chains; do not create browser-side clients with the key.
- A freshness policy for each cached response type and a test fixture for both successful and failed provider calls.
- Input validation at the application boundary before an address, collection, or network value reaches the client factory or query builder.
Instructions
Step 1: Multi-Chain Client Factory
// src/alchemy/client-factory.ts
import { Alchemy, Network } from 'alchemy-sdk';
type ChainName = 'ethereum' | 'polygon' | 'arbitrum' | 'optimism' | 'base';
const NETWORK_MAP: Record<ChainName, Network> = {
ethereum: Network.ETH_MAINNET,
polygon: Network.MATIC_MAINNET,
arbitrum: Network.ARB_MAINNET,
optimism: Network.OPT_MAINNET,
base: Network.BASE_MAINNET,
};
class AlchemyClientFactory {
private static clients = new Map<string, Alchemy>();
static getClient(chain: ChainName): Alchemy {
if (!this.clients.has(chain)) {
this.clients.set(chain, new Alchemy({
apiKey: process.env.ALCHEMY_API_KEY,
network: NETWORK_MAP[chain],
maxRetries: 3,
}));
}
return this.clients.get(chain)!;
}
static getAllClients(): Map<ChainName, Alchemy> {
for (const chain of Object.keys(NETWORK_MAP) as ChainName[]) {
this.getClient(chain);
}
return this.clients as Map<ChainName, Alchemy>;
}
}
export { AlchemyClientFactory, ChainName };
Step 2: Response Caching Layer
// src/alchemy/cache.ts
interface CacheEntry<T> { data: T; expiresAt: number; }
class AlchemyCache {
private cache = new Map<string, CacheEntry<any>>();
private defaultTtlMs: number;
constructor(defaultTtlMs: number = 30000) { // 30s default
this.defaultTtlMs = defaultTtlMs;
}
async getOrFetch<T>(key: string, fetcher: () => Promise<T>, ttlMs?: number): Promise<T> {
const cached = this.cache.get(key);
if (cached && cached.expiresAt > Date.now()) return cached.data;
const data = await fetcher();
this.cache.set(key, { data, expiresAt: Date.now() + (ttlMs || this.defaultTtlMs) });
return data;
}
invalidate(keyPrefix: string): void {
for (const key of this.cache.keys()) {
if (key.startsWith(keyPrefix)) this.cache.delete(key);
}
}
}
// Usage with Alchemy
const cache = new AlchemyCache();
async function getCachedBalance(alchemy: Alchemy, address: string): Promise<string> {
return cache.getOrFetch(
`balance:${address}`,
async () => {
const balance = await alchemy.core.getBalance(address);
return (parseInt(balance.toString()) / 1e18).toFixed(6);
},
15000 // 15s cache for balances
);
}
export { AlchemyCache, getCachedBalance };
Step 3: Typed NFT Query Builder
// src/alchemy/nft-query.ts
import { Alchemy, NftOrdering } from 'alchemy-sdk';
class NftQueryBuilder {
private alchemy: Alchemy;
private _owner?: string;
private _contracts: string[] = [];
private _pageSize = 20;
private _excludeFilters: string[] = [];
constructor(alchemy: Alchemy) { this.alchemy = alchemy; }
forOwner(address: string): this { this._owner = address; return this; }
inCollection(contractAddress: string): this { this._contracts.push(contractAddress); return this; }
pageSize(size: number): this { this._pageSize = size; return this; }
excludeSpam(): this { this._excludeFilters.push('SPAM'); return this; }
async execute() {
if (!this._owner) throw new Error('Owner address required');
return this.alchemy.nft.getNftsForOwner(this._owner, {
contractAddresses: this._contracts.length > 0 ? this._contracts : undefined,
pageSize: this._pageSize,
excludeFilters: this._excludeFilters as any[],
});
}
}
// Usage:
// const nfts = await new NftQueryBuilder(alchemy)
// .forOwner('vitalik.eth')
// .excludeSpam()
// .pageSize(50)
// .execute();
Step 4: Error Classification
// src/alchemy/errors.ts
type AlchemyErrorType = 'rate_limit' | 'auth' | 'network' | 'invalid_params' | 'server' | 'unknown';
function classifyError(error: any): { type: AlchemyErrorType; retryable: boolean; message: string } {
const status = error.response?.status || error.code;
if (status === 429) return { type: 'rate_limit', retryable: true, message: 'Rate limit exceeded' };
if (status === 401 || status === 403) return { type: 'auth', retryable: false, message: 'Invalid API key' };
if (status >= 500) return { type: 'server', retryable: true, message: 'Alchemy server error' };
if (error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT') return { type: 'network', retryable: true, message: 'Network error' };
if (error.message?.includes('invalid params')) return { type: 'invalid_params', retryable: false, message: error.message };
return { type: 'unknown', retryable: false, message: error.message };
}
export { classifyError, AlchemyErrorType };
Output
- Multi-chain client factory with lazy initialization
- Response cache with configurable TTL
- Type-safe NFT query builder pattern
- Structured error classification for retry decisions
Examples
In a staging service, request a balance for a public test address twice through
getCachedBalance, assert the second request is a cache hit within the
approved TTL, then invalidate the prefix and verify the next request reaches
the provider. Use the factory only for a configured chain and reject an
unrecognized chain at the route boundary. Inject a 429 and a 401 into the
error classifier to prove that only the rate-limit case is retryable. If a
cached value breaches its freshness rule, a chain is not configured, or a key
reaches a client artifact, disable the route and repair that boundary first.
Error Handling
| Failure | Response |
|---|---|
| Provider rate limit or transient server error | Use bounded retry/backoff and retain a sanitized outcome metric. |
| Authentication or authorization failure | Stop retries, verify managed-secret configuration, and rotate a suspected exposure. |
| Invalid address, contract, or chain input | Reject before creating a provider operation. |
| Cache contains stale or incompatible data | Invalidate it and refetch according to the declared freshness policy. |
Resources
Next Steps
Apply patterns in alchemy-core-workflow-a for real portfolio tracking.
Signals
- GitHub stars
- 3k
- Forks
- 396
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
alchemy-sdk-patterns- Source
- github.com/jeremylongshore/tons-of-skills-marketplace