Missing image.cachedUrl |
I
Build a complete wallet portfolio tracker using Alchemy Enhanced APIs.
ReadWriteEditBash(npm:*)Grep
Alchemy Core Workflow A — Wallet Portfolio Tracker
Overview
Primary workflow: build a wallet portfolio tracker using Alchemy's Enhanced APIs. Combines getTokenBalances, getNftsForOwner, getAssetTransfers, and token metadata to create a complete wallet view across ERC-20, ERC-721, and ERC-1155 assets.
Prerequisites
- Completed
alchemy-install-auth setup
alchemy-sdk installed
- Understanding of Ethereum address format and token standards
Instructions
Step 1: Portfolio Data Fetcher
// src/portfolio/fetcher.ts
import { Alchemy, Network, AssetTransfersCategory } from 'alchemy-sdk';
const alchemy = new Alchemy({
apiKey: process.env.ALCHEMY_API_KEY,
network: Network.ETH_MAINNET,
});
interface TokenHolding {
contractAddress: string;
symbol: string;
name: string;
balance: number;
decimals: number;
}
interface NftHolding {
contractAddress: string;
collectionName: string;
tokenId: string;
name: string;
imageUrl: string | null;
}
interface WalletPortfolio {
address: string;
ethBalance: string;
tokens: TokenHolding[];
nfts: NftHolding[];
recentTransactions: any[];
fetchedAt: string;
}
async function fetchPortfolio(address: string): Promise<WalletPortfolio> {
// Parallel fetch all portfolio data
const [ethBalance, tokenBalances, nftResponse, transfers] = await Promise.all([
alchemy.core.getBalance(address),
alchemy.core.getTokenBalances(address),
alchemy.nft.getNftsForOwner(address, { pageSize: 100 }),
alchemy.core.getAssetTransfers({
fromAddress: address,
category: [
AssetTransfersCategory.EXTERNAL,
AssetTransfersCategory.ERC20,
AssetTransfersCategory.ERC721,
],
maxCount: 25,
order: 'desc',
}),
]);
// Resolve token metadata
const tokens: TokenHolding[] = [];
for (const tb of tokenBalances.tokenBalances) {
if (tb.tokenBalance && tb.tokenBalance !== '0x0') {
const metadata = await alchemy.core.getTokenMetadata(tb.contractAddress);
const balance = parseInt(tb.tokenBalance, 16) / Math.pow(10, metadata.decimals || 18);
if (balance > 0.001) { // Filter dust
tokens.push({
contractAddress: tb.contractAddress,
symbol: metadata.symbol || 'UNKNOWN',
name: metadata.name || 'Unknown Token',
balance,
decimals: metadata.decimals || 18,
});
}
}
}
// Map NFTs
const nfts: NftHolding[] = nftResponse.ownedNfts.map(nft => ({
contractAddress: nft.contract.address,
collectionName: nft.contract.name || 'Unknown Collection',
tokenId: nft.tokenId,
name: nft.name || `#${nft.tokenId}`,
imageUrl: nft.image?.cachedUrl || null,
}));
return {
address,
ethBalance: (parseInt(ethBal
Build NFT collection explorer and smart contract interaction with Alchemy.
ReadWriteEditBash(npm:*)Grep
Alchemy Core Workflow B — NFT & Smart Contract Interaction
Overview
Build NFT collection explorers and smart contract read operations using Alchemy's NFT API and core JSON-RPC methods.
Prerequisites
- Completed
alchemy-install-auth setup
- Familiarity with
alchemy-core-workflow-a
- Understanding of ERC-721 and ERC-1155 standards
Instructions
Step 1: NFT Collection Explorer
// src/nft/collection-explorer.ts
import { Alchemy, Network } from 'alchemy-sdk';
const alchemy = new Alchemy({
apiKey: process.env.ALCHEMY_API_KEY,
network: Network.ETH_MAINNET,
});
async function exploreCollection(contractAddress: string) {
const metadata = await alchemy.nft.getContractMetadata(contractAddress);
return {
address: contractAddress,
name: metadata.name || 'Unknown',
symbol: metadata.symbol || '',
totalSupply: metadata.totalSupply || '0',
tokenType: metadata.tokenType,
floorPrice: metadata.openSeaMetadata?.floorPrice || null,
description: metadata.openSeaMetadata?.description || '',
imageUrl: metadata.openSeaMetadata?.imageUrl || null,
};
}
async function getCollectionNfts(contractAddress: string, limit: number = 20) {
const response = await alchemy.nft.getNftsForContract(contractAddress, { limit });
return response.nfts.map(nft => ({
tokenId: nft.tokenId,
name: nft.name || `#${nft.tokenId}`,
description: nft.description,
image: nft.image?.cachedUrl || nft.image?.originalUrl,
attributes: nft.raw?.metadata?.attributes || [],
}));
}
// Example: Bored Ape Yacht Club
const BAYC = '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D';
exploreCollection(BAYC).then(console.log).catch(console.error);
Step 2: Batch NFT Metadata
// src/nft/batch-metadata.ts
import { Alchemy, Network } from 'alchemy-sdk';
const alchemy = new Alchemy({
apiKey: process.env.ALCHEMY_API_KEY,
network: Network.ETH_MAINNET,
});
async function batchGetNftMetadata(
tokens: Array<{ contractAddress: string; tokenId: string }>
) {
const results = await alchemy.nft.getNftMetadataBatch(
tokens.map(t => ({ contractAddress: t.contractAddress, tokenId: t.tokenId }))
);
return results.map(nft => ({
contract: nft.contract.address,
tokenId: nft.tokenId,
name: nft.name,
image: nft.image?.cachedUrl,
tokenType: nft.tokenType,
collection: nft.contract.name,
}));
}
Step 3: Smart Contract Read via Ethers + Alchemy Provider
// src/contracts/read-contract.ts
import { Alchemy, Network } from 'alchemy-sdk';
import { ethers } from 'ethers';
const alchemy = new Alchemy({
apiKey: process.env.ALCHEMY_API_KEY,
network: Network.ETH_MAINNET,
});
async function readErc20Cont
Optimize Alchemy API costs through CU budgeting, caching, and plan selection.
ReadWriteEditBash(npm:*)
Alchemy Cost Tuning
Overview
Alchemy pricing is based on Compute Units (CU). Different API methods have different CU costs. Optimize by caching, batching, choosing cheaper methods, and right-sizing your plan.
Plan Comparison
| Plan |
CU/sec |
Monthly CU |
Price |
Best For |
| Free |
330 |
300M |
$0 |
Dev/prototyping |
| Growth |
660 |
1.2B |
$49/mo |
Small dApps |
| Scale |
Custom |
Custom |
Custom |
High-traffic apps |
CU Cost Reference (Top Methods)
| Method |
CU |
Optimization |
eth_blockNumber |
10 |
Cache 12s (1 block) |
eth_getBalance |
19 |
Cache 30s |
eth_call |
26 |
Cache based on use case |
getTokenBalances |
50 |
Cache 60s; batch addresses |
getNftsForOwner |
50 |
Cache 5 min |
getTokenMetadata |
50 |
Cache 24h (rarely changes) |
getAssetTransfers |
150 |
Cache aggressively; paginate |
getNftMetadataBatch |
50 |
Use batch over individual calls |
Instructions
Step 1: CU Usage Monitor
// src/cost/cu-monitor.ts
const CU_COSTS: Record<string, number> = {
'eth_blockNumber': 10, 'eth_getBalance': 19, 'eth_call': 26,
'getTokenBalances': 50, 'getNftsForOwner': 50, 'getTokenMetadata': 50,
'getAssetTransfers': 150, 'getNftMetadataBatch': 50,
};
class CuMonitor {
private usage: Array<{ method: string; cu: number; timestamp: number }> = [];
record(method: string): void {
this.usage.push({ method, cu: CU_COSTS[method] || 26, timestamp: Date.now() });
}
getHourlyReport(): { totalCu: number; byMethod: Record<string, number> } {
const cutoff = Date.now() - 3600000;
const recent = this.usage.filter(u => u.timestamp > cutoff);
const byMethod: Record<string, number> = {};
let totalCu = 0;
for (const u of recent) {
byMethod[u.method] = (byMethod[u.method] || 0) + u.cu;
totalCu += u.cu;
}
return { totalCu, byMethod };
}
getMonthlyProjection(): { projectedMonthly: number; planRecommendation: string } {
const hourly = this.getHourlyReport();
const projectedMonthly = hourly.totalCu * 24 * 30;
let recommendation = 'Free';
if (projectedMonthly > 300_000_000) recommendation = 'Growth';
if (projectedMonthly > 1_200_000_000
Collect Alchemy SDK debug evidence for troubleshooting and support tickets.
ReadWriteEditBash(curl:*)Bash(node:*)Grep
Alchemy Debug Bundle
Overview
Collect diagnostic data for Alchemy support tickets: connectivity tests, SDK version, network status, CU usage, and recent error logs.
Instructions
Step 1: Debug Bundle Generator
// src/debug/alchemy-debug.ts
import { Alchemy, Network } from 'alchemy-sdk';
interface DebugBundle {
timestamp: string;
sdkVersion: string;
environment: Record<string, string>;
connectivity: Record<string, any>;
networkStatus: Record<string, any>;
}
async function generateDebugBundle(): Promise<DebugBundle> {
const alchemy = new Alchemy({
apiKey: process.env.ALCHEMY_API_KEY,
network: Network.ETH_MAINNET,
});
const bundle: DebugBundle = {
timestamp: new Date().toISOString(),
sdkVersion: require('alchemy-sdk/package.json').version,
environment: {
nodeVersion: process.version,
platform: process.platform,
apiKeySet: process.env.ALCHEMY_API_KEY ? 'yes (redacted)' : 'NO — missing',
network: process.env.ALCHEMY_NETWORK || 'ETH_MAINNET',
},
connectivity: {},
networkStatus: {},
};
// Test core connectivity
try {
const start = Date.now();
const blockNumber = await alchemy.core.getBlockNumber();
bundle.connectivity.core = {
status: 'ok',
latencyMs: Date.now() - start,
latestBlock: blockNumber,
};
} catch (err: any) {
bundle.connectivity.core = { status: 'failed', error: err.message };
}
// Test Enhanced API
try {
const start = Date.now();
await alchemy.core.getTokenBalances('0x0000000000000000000000000000000000000000');
bundle.connectivity.enhancedApi = { status: 'ok', latencyMs: Date.now() - start };
} catch (err: any) {
bundle.connectivity.enhancedApi = { status: 'failed', error: err.message };
}
// Test NFT API
try {
const start = Date.now();
await alchemy.nft.getContractMetadata('0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D');
bundle.connectivity.nftApi = { status: 'ok', latencyMs: Date.now() - start };
} catch (err: any) {
bundle.connectivity.nftApi = { status: 'failed', error: err.message };
}
// Multi-network status
for (const [name, network] of Object.entries({
ethereum: Network.ETH_MAINNET,
polygon: Network.MATIC_MAINNET,
arbitrum: Network.ARB_MAINNET,
})) {
try {
const client = new Alchemy({ apiKey: process.env.ALCHEMY_API_KEY, network });
const block = await client.core.getBlockNumber();
bundle.networkStatus[name] = { status: 'ok', block };
} catch (err: any) {
bundle.networkStatus[name] = { status: 'failed', error: err.message };
}
}
const filename = `alchemy-debug-${Date.now()}.json`;
require('fs').writeFileSync(filename, JSON.stringify(bundle, null, 2));
console.log(`Debug bundle
Deploy Alchemy-powered Web3 applications to Vercel, Cloud Run, and AWS.
ReadWriteEditBash(vercel:*)Bash(gcloud:*)Bash(docker:*)
Alchemy Deploy Integration
Overview
Deploy Alchemy-powered dApps with proper API key security. The API key must stay server-side — never ship it to the browser.
Instructions
Step 1: Vercel Deployment
# Add Alchemy API key as Vercel secret
vercel secrets add alchemy_api_key "your-api-key"
vercel link
vercel --prod
// vercel.json
{
"env": { "ALCHEMY_API_KEY": "@alchemy_api_key" },
"functions": { "api/**/*.ts": { "maxDuration": 30 } }
}
// api/balance/[address].ts — Vercel serverless function
import { Alchemy, Network } from 'alchemy-sdk';
const alchemy = new Alchemy({
apiKey: process.env.ALCHEMY_API_KEY,
network: Network.ETH_MAINNET,
});
export default async function handler(req: any, res: any) {
const { address } = req.query;
if (!/^0x[a-fA-F0-9]{40}$/.test(address)) {
return res.status(400).json({ error: 'Invalid address' });
}
const balance = await alchemy.core.getBalance(address);
res.json({ balance: balance.toString() });
}
Step 2: Cloud Run Deployment
# Build and deploy
gcloud builds submit --tag gcr.io/${PROJECT_ID}/alchemy-dapp
gcloud run deploy alchemy-dapp \
--image gcr.io/${PROJECT_ID}/alchemy-dapp \
--region us-central1 \
--set-secrets=ALCHEMY_API_KEY=alchemy-api-key:latest \
--allow-unauthenticated
Step 3: Health Check
// api/health.ts
import { Alchemy, Network } from 'alchemy-sdk';
export default async function handler(_req: any, res: any) {
try {
const alchemy = new Alchemy({ apiKey: process.env.ALCHEMY_API_KEY, network: Network.ETH_MAINNET });
const block = await alchemy.core.getBlockNumber();
res.json({ status: 'healthy', latestBlock: block });
} catch {
res.status(503).json({ status: 'unhealthy' });
}
}
Output
- Vercel deployment with API key in server-side functions
- Cloud Run with GCP Secret Manager
- Health check endpoint verifying Alchemy connectivity
Resources
Next Steps
For webhook handling, see alchemy-webhooks-events.
Create a minimal Alchemy Web3 example: get ETH balance, fetch NFTs, read token balances.
ReadWriteEditBash(npm:*)
Alchemy Hello World
Overview
Minimal working examples with the real Alchemy SDK: get ETH balance, fetch NFTs for a wallet, read ERC-20 token balances, and subscribe to pending transactions.
Prerequisites
- Completed
alchemy-install-auth setup
alchemy-sdk installed (npm install alchemy-sdk)
- Valid API key configured
Instructions
Step 1: Get ETH Balance
// src/hello-world/get-balance.ts
import { Alchemy, Network, Utils } from 'alchemy-sdk';
const alchemy = new Alchemy({
apiKey: process.env.ALCHEMY_API_KEY,
network: Network.ETH_MAINNET,
});
async function getBalance(address: string) {
const balanceWei = await alchemy.core.getBalance(address);
const balanceEth = Utils.formatEther(balanceWei);
console.log(`Balance of ${address}: ${balanceEth} ETH`);
return balanceEth;
}
// Query Vitalik's address
getBalance('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045').catch(console.error);
Step 2: Fetch NFTs for a Wallet
// src/hello-world/get-nfts.ts
import { Alchemy, Network } from 'alchemy-sdk';
const alchemy = new Alchemy({
apiKey: process.env.ALCHEMY_API_KEY,
network: Network.ETH_MAINNET,
});
async function getNftsForOwner(owner: string) {
const nfts = await alchemy.nft.getNftsForOwner(owner);
console.log(`Total NFTs: ${nfts.totalCount}`);
for (const nft of nfts.ownedNfts.slice(0, 5)) {
console.log(` ${nft.contract.name} — ${nft.name || nft.tokenId}`);
console.log(` Contract: ${nft.contract.address}`);
console.log(` Token ID: ${nft.tokenId}`);
if (nft.image?.cachedUrl) {
console.log(` Image: ${nft.image.cachedUrl}`);
}
}
return nfts;
}
getNftsForOwner('vitalik.eth').catch(console.error);
Step 3: Get ERC-20 Token Balances
// src/hello-world/get-tokens.ts
import { Alchemy, Network } from 'alchemy-sdk';
const alchemy = new Alchemy({
apiKey: process.env.ALCHEMY_API_KEY,
network: Network.ETH_MAINNET,
});
async function getTokenBalances(address: string) {
const balances = await alchemy.core.getTokenBalances(address);
console.log(`Token balances for ${address}:`);
for (const token of balances.tokenBalances.slice(0, 10)) {
if (token.tokenBalance && token.tokenBalance !== '0x0') {
// Get token metadata for human-readable names
const metadata = await alchemy.core.getTokenMetadata(token.contractAddress);
const balance = parseInt(token.tokenBalance, 16) / Math.pow(10, metadata.decimals || 18);
console.log(` ${metadata.symbol}: ${balance.toFixed(4)}`);
}
}
}
getTokenBalances('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045').catch(console.error);
Step 4: Get Latest Block Info
Install the Alchemy SDK and configure API key authentication for Web3 development.
ReadWriteEditBash(npm:*)Bash(pip:*)Grep
Alchemy Install & Auth
Overview
Install the alchemy-sdk npm package and configure API authentication. Alchemy provides blockchain infrastructure (RPC nodes, Enhanced APIs, NFT APIs, webhooks) across Ethereum, Polygon, Arbitrum, Optimism, Base, and Solana.
Prerequisites
- Node.js 18+ (or Python 3.10+ for alchemy-sdk Python)
- Free Alchemy account at alchemy.com
- API key from Alchemy Dashboard
Instructions
Step 1: Install the Alchemy SDK
# JavaScript/TypeScript (official SDK)
npm install alchemy-sdk
# Python (community SDK)
pip install alchemy-sdk
Step 2: Create an Alchemy App
- Go to dashboard.alchemy.com
- Click "Create new app"
- Select chain: Ethereum, Polygon, Arbitrum, Optimism, Base, or Solana
- Select network: Mainnet, Sepolia (testnet), or Mumbai
- Copy the API key
Step 3: Configure Environment
# Set environment variable
export ALCHEMY_API_KEY="your-api-key-here"
# Or create .env file
cat > .env << 'EOF'
ALCHEMY_API_KEY=your-api-key-here
ALCHEMY_NETWORK=ETH_MAINNET
EOF
echo ".env" >> .gitignore
Step 4: Initialize and Verify
// src/alchemy-client.ts
import { Alchemy, Network } from 'alchemy-sdk';
const alchemy = new Alchemy({
apiKey: process.env.ALCHEMY_API_KEY,
network: Network.ETH_MAINNET,
});
// Verify connection
async function verifyConnection() {
const blockNumber = await alchemy.core.getBlockNumber();
console.log(`Connected! Latest block: ${blockNumber}`);
return blockNumber;
}
verifyConnection().catch(console.error);
Step 5: Multi-Chain Configuration
// src/config/chains.ts
import { Alchemy, Network } from 'alchemy-sdk';
// Create clients for multiple chains
const chains = {
ethereum: new Alchemy({ apiKey: process.env.ALCHEMY_API_KEY, network: Network.ETH_MAINNET }),
polygon: new Alchemy({ apiKey: process.env.ALCHEMY_API_KEY, network: Network.MATIC_MAINNET }),
arbitrum: new Alchemy({ apiKey: process.env.ALCHEMY_API_KEY, network: Network.ARB_MAINNET }),
optimism: new Alchemy({ apiKey: process.env.ALCHEMY_API_KEY, network: Network.OPT_MAINNET }),
base: new Alchemy({ apiKey: process.env.ALCHEMY_API_KEY, network: Network.BASE_MAINNET }),
};
export default chains;
Supported Networks
| Chain |
Network Enum |
Mainnet |
Testnet |
| Ethereum |
ETH_MAINNET |
Yes |
Sepolia |
| Polygon |
MATIC_MAINNET |
Yes |
Amoy |
| Arbitrum |
AR
Set up local Web3 development workflow with Alchemy, Hardhat, and testnets.
ReadWriteEditBash(npm:*)Bash(npx:*)Grep
Alchemy Local Dev Loop
Overview
Local Web3 development workflow using Alchemy as the RPC provider with Hardhat for local testing, Sepolia testnet for staging, and hot-reload for rapid iteration.
Prerequisites
- Completed
alchemy-install-auth setup
- Node.js 18+
- Alchemy API key with Sepolia testnet app
Instructions
Step 1: Initialize Hardhat Project with Alchemy
mkdir web3-project && cd web3-project
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
npm install alchemy-sdk dotenv
npx hardhat init # Select TypeScript project
Step 2: Configure Hardhat with Alchemy RPC
// hardhat.config.ts
import { HardhatUserConfig } from 'hardhat/config';
import '@nomicfoundation/hardhat-toolbox';
import 'dotenv/config';
const config: HardhatUserConfig = {
solidity: '0.8.24',
networks: {
hardhat: {
forking: {
url: `https://eth-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`,
blockNumber: 19000000, // Pin block for reproducible tests
},
},
sepolia: {
url: `https://eth-sepolia.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`,
accounts: process.env.DEPLOYER_PRIVATE_KEY ? [process.env.DEPLOYER_PRIVATE_KEY] : [],
},
},
};
export default config;
Step 3: Alchemy-Powered Test Helper
// test/helpers/alchemy-helper.ts
import { Alchemy, Network } from 'alchemy-sdk';
const alchemy = new Alchemy({
apiKey: process.env.ALCHEMY_API_KEY,
network: Network.ETH_SEPOLIA,
});
export async function getTestnetBalance(address: string): Promise<string> {
const balance = await alchemy.core.getBalance(address);
return (parseInt(balance.toString()) / 1e18).toFixed(4);
}
export async function waitForTransaction(txHash: string): Promise<any> {
return alchemy.core.waitForTransaction(txHash, 1, 60000);
}
export { alchemy };
Step 4: Development Scripts
{
"scripts": {
"dev": "npx hardhat node --fork https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}",
"test": "npx hardhat test",
"test:watch": "npx hardhat test --watch",
"deploy:sepolia": "npx hardhat run scripts/deploy.ts --network sepolia",
"verify": "npx hardhat verify --network sepolia"
}
}
Step 5: Mainnet Fork Testing
// test/fork-test.ts
import { expect } from 'chai';
import { ethers } from 'hardhat';
describe('Mainnet Fork Tests', () => {
it('should read USDC balance on forked mainnet', async () => {
const USDC = '0xA0b86991c6218b36c1d19D4a2
Optimize Alchemy SDK performance with caching, batching, and multi-chain parallelism.
ReadWriteEditBash(npm:*)
Alchemy Performance Tuning
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 |
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 Promi
Execute production readiness checklist for Alchemy-powered dApps.
ReadWriteEditBash(curl:*)Grep
Alchemy Production Checklist
Pre-Launch Checklist
API & Infrastructure
- [ ] API key restricted to production domains in Alchemy Dashboard
- [ ] Separate Alchemy apps for dev/staging/prod environments
- [ ] Rate limit headroom verified (< 70% of CU/sec budget)
- [ ] Retry logic with exponential backoff implemented
- [ ] Error monitoring configured (Sentry, Datadog, etc.)
- [ ] Webhook endpoints HTTPS-only with signature verification
Security
- [ ] API key NOT in frontend code — proxied through backend
- [ ] Private keys in secret manager (not env files)
- [ ] All user-supplied addresses validated and checksummed
- [ ] No
console.log of sensitive data in production builds
- [ ] npm audit clean — no critical vulnerabilities
Smart Contracts (if applicable)
- [ ] Contracts audited by reputable firm
- [ ] Deployed and verified on Etherscan/Polygonscan
- [ ] Admin keys secured in multi-sig wallet
- [ ] Emergency pause function tested
Performance
- [ ] Response caching for frequently-queried data (balances, metadata)
- [ ] Connection pooling for provider instances
- [ ] Batch requests where possible (NFT metadata, balances)
- [ ] WebSocket reconnection logic for real-time subscriptions
Validation Script
// src/prod/readiness.ts
import { Alchemy, Network } from 'alchemy-sdk';
async function checkReadiness(): Promise<void> {
const checks: Array<{ name: string; pass: boolean; detail: string }> = [];
// 1. API connectivity
const alchemy = new Alchemy({ apiKey: process.env.ALCHEMY_API_KEY, network: Network.ETH_MAINNET });
try {
const block = await alchemy.core.getBlockNumber();
checks.push({ name: 'API Connectivity', pass: true, detail: `Block ${block}` });
} catch (err: any) {
checks.push({ name: 'API Connectivity', pass: false, detail: err.message });
}
// 2. Enhanced API
try {
await alchemy.core.getTokenBalances('0x0000000000000000000000000000000000000000');
checks.push({ name: 'Enhanced API', pass: true, detail: 'getTokenBalances works' });
} catch { checks.push({ name: 'Enhanced API', pass: false, detail: 'Enhanced API unavailable' }); }
// 3. NFT API
try {
await alchemy.nft.getContractMetadata('0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D');
checks.push({ name: 'NFT API', pass: true, detail: 'getContractMetadata works' });
} catch { checks.push({ name: 'NFT API', pass: false, detail: 'NFT API unavailable' }); }
// 4. API key not in build output
const fs = await import('fs');
const buildDir = './dist';
if (fs.existsSync(buildDir)) {
const content = fs.readdirSync(buildDir, {
Implement Alchemy Compute Unit (CU) rate limiting and request throttling.
ReadWriteEditBash(npm:*)
Alchemy Rate Limits
Overview
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.
Compute Unit Costs
| 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 Limits
| Plan |
CU/sec |
Monthly CU |
Price |
| Free |
330 |
300M |
$0 |
| Growth |
660 |
1.2B |
$49/mo |
| Scale |
Custom |
Custom |
Custom |
Instructions
Step 1: CU-Aware Request Throttler
// 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 };
Step 2: Batch Optimizer
// src/alchemy/batch-optimizer.ts
import { Alchemy } from 'alchemy-sdk';
// Instead of N individual calls, batch when possible
async function batchGetBalances(
alchemy: Alchemy,
addresses: stri
Implement reference architecture for Alchemy-powered Web3 applications.
ReadWriteEdit
Alchemy Reference Architecture
System Architecture
┌─────────────────────────────────────────────────────────┐
│ Frontend (React/Next.js) │
│ - Wallet connection (MetaMask, WalletConnect) │
│ - Portfolio dashboard │
│ - NFT gallery │
│ - Transaction history │
└────────────────────────┬────────────────────────────────┘
│ HTTPS (no API key exposed)
┌────────────────────────▼────────────────────────────────┐
│ API Layer (Next.js/Express) │
│ - /api/balance/:address │
│ - /api/nfts/:owner │
│ - /api/tokens/:address │
│ - /api/transactions/:address │
│ - /webhooks/alchemy (webhook receiver) │
└───────┬──────────┬──────────┬───────────────────────────┘
│ │ │
┌────▼───┐ ┌───▼────┐ ┌──▼──────┐
│Alchemy │ │Alchemy │ │Alchemy │
│Core API│ │NFT API │ │Notify │
│(RPC) │ │ │ │(Webhooks│
└────────┘ └────────┘ └─────────┘
ETH/Polygon/ARB/OP/Base
Project Structure
web3-dapp/
├── src/
│ ├── alchemy/
│ │ ├── client-factory.ts # Multi-chain Alchemy client factory
│ │ ├── cache.ts # Response caching with TTL
│ │ ├── throttler.ts # CU-aware rate limiter
│ │ └── errors.ts # Error classification
│ ├── portfolio/
│ │ ├── fetcher.ts # Wallet portfolio aggregator
│ │ ├── transactions.ts # Transaction history analyzer
│ │ └── multi-chain.ts # Cross-chain balance aggregator
│ ├── nft/
│ │ ├── collection.ts # NFT collection explorer
│ │ ├── batch-metadata.ts # Batch metadata fetcher
│ │ └── verify-ownership.ts # NFT ownership verification
│ ├── contracts/
│ │ ├── read-contract.ts # Smart contract read operations
│ │ └── abis/ # Contract ABI files
│ ├── webhooks/
│ │ ├── handler.ts # Webhook endpoint
│ │ ├── verify.ts # HMAC signature verification
│ │ └── event-router.ts # Event type routing
│ ├── security/
│ │ ├── validators.ts # Input validation (addresses, blocks)
│ │ └── proxy.ts # API key proxy for frontend
│ └── api/ # API route handlers
├── tests/
│ ├── unit/ # Unit tests (mocked Alchemy)
│ ├── fork/ # Mainnet fork tests (Hardhat)
│ └── integration/ # Sepolia integration tests
├── contracts/ # Solidity contracts (if applicable)
├── hardhat.config.ts # Hardhat + Alchemy fork config
└── package.json
Key Design Decisions
| Decision |
Apply production-ready Alchemy SDK patterns for Web3 applications.
ReadWriteEdit
Alchemy SDK Patterns
Overview
Production patterns for the alchemy-sdk package: singleton clients, multi-chain factories, response caching, and type-safe contract wrappers.
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
Apply Web3 security best practices for Alchemy-powered applications.
ReadWriteEditGrep
Alchemy Security Basics
Overview
Web3 security practices for Alchemy-powered applications: API key protection, private key management, input validation, and smart contract interaction safety.
Security Checklist
| Category |
Requirement |
Priority |
| API keys |
Never expose in client-side code |
Critical |
| Private keys |
Use environment vars or secret manager |
Critical |
| Addresses |
Validate and checksum all inputs |
High |
| RPC calls |
Never pass user input directly to RPC |
High |
| Webhooks |
Verify HMAC signatures |
High |
| Dependencies |
Audit npm packages for supply chain |
Medium |
Instructions
Step 1: API Key Protection
// WRONG — API key in frontend code
// const alchemy = new Alchemy({ apiKey: 'demo123' }); // NEVER DO THIS
// RIGHT — API key in backend proxy
// src/api/proxy.ts
import express from 'express';
import { Alchemy, Network } from 'alchemy-sdk';
const app = express();
const alchemy = new Alchemy({
apiKey: process.env.ALCHEMY_API_KEY, // Server-side only
network: Network.ETH_MAINNET,
});
// Proxy endpoint — frontend calls this instead of Alchemy directly
app.get('/api/balance/:address', async (req, res) => {
const { address } = req.params;
if (!/^0x[a-fA-F0-9]{40}$/.test(address)) {
return res.status(400).json({ error: 'Invalid address format' });
}
const balance = await alchemy.core.getBalance(address);
res.json({ balance: balance.toString() });
});
// Alchemy Dashboard: restrict API key to specific domains/IPs
// Dashboard > App > Settings > Allowed Domains
Step 2: Input Validation for Blockchain Queries
// src/security/validators.ts
import { ethers } from 'ethers';
function validateAddress(input: string): string {
if (!ethers.isAddress(input)) throw new Error(`Invalid address: ${input}`);
return ethers.getAddress(input); // Returns checksummed address
}
function validateBlockNumber(input: string | number): string {
if (input === 'latest' || input === 'pending' || input === 'earliest') return input;
const num = typeof input === 'string' ? parseInt(input) : input;
if (isNaN(num) || num < 0) throw new Error(`Invalid block number: ${input}`);
return `0x${num.toString(16)}`;
}
function validateTokenId(input: string): string {
if (!/^\d+$/.test(input) && !input.startsWith('0x')) {
throw new Error(`Invalid token ID: ${input}`);
}
return input;
}
export { validateAddress, validateBlockNumber, validateTokenId };
Step 3: Private Key Safety
Migrate from alchemy-sdk v2 to v3 and handle breaking changes.
ReadWriteEditBash(npm:*)Grep
Alchemy Upgrade & Migration
Overview
Migration guide for Alchemy SDK upgrades and deprecated package transitions. The alchemy-web3 package is deprecated — migrate to alchemy-sdk.
Migration Paths
| From |
To |
Complexity |
alchemy-web3 |
alchemy-sdk |
High (different API surface) |
alchemy-sdk v2 → v3 |
alchemy-sdk v3 |
Medium (some breaking changes) |
| Direct JSON-RPC |
alchemy-sdk |
Low (SDK wraps same methods) |
Instructions
Step 1: Migrate from alchemy-web3 to alchemy-sdk
// BEFORE: alchemy-web3 (DEPRECATED)
// import { createAlchemyWeb3 } from '@alch/alchemy-web3';
// const web3 = createAlchemyWeb3(`https://eth-mainnet.g.alchemy.com/v2/${apiKey}`);
// const balance = await web3.eth.getBalance(address);
// const nfts = await web3.alchemy.getNfts({ owner });
// AFTER: alchemy-sdk
import { Alchemy, Network } from 'alchemy-sdk';
const alchemy = new Alchemy({
apiKey: process.env.ALCHEMY_API_KEY,
network: Network.ETH_MAINNET,
});
// Core methods — same JSON-RPC, different API
const balance = await alchemy.core.getBalance(address);
// Enhanced APIs — reorganized under namespaces
const nfts = await alchemy.nft.getNftsForOwner(owner);
// WebSockets — now under alchemy.ws
alchemy.ws.on({ method: 'eth_subscribe', params: ['newHeads'] }, (block) => {
console.log('New block:', block);
});
Step 2: API Surface Changes
// Key namespace changes in alchemy-sdk:
// Core (JSON-RPC wrapper)
alchemy.core.getBlockNumber();
alchemy.core.getBalance(address);
alchemy.core.getTokenBalances(address);
alchemy.core.getTokenMetadata(contractAddress);
alchemy.core.getAssetTransfers({ fromAddress, category });
// NFT (dedicated namespace)
alchemy.nft.getNftsForOwner(owner);
alchemy.nft.getNftsForContract(contract);
alchemy.nft.getContractMetadata(contract);
alchemy.nft.getNftMetadataBatch(tokens);
alchemy.nft.getOwnersForNft(contract, tokenId);
// WebSocket (real-time)
alchemy.ws.on(filter, callback);
alchemy.ws.once(filter, callback);
alchemy.ws.removeAllListeners();
// Notify (webhooks — requires authToken)
alchemy.notify.getAllWebhooks();
alchemy.notify.createWebhook(config);
Step 3: Dependency Cleanup
# Remove deprecated packages
npm uninstall @alch/alchemy-web3 alchemy-web3
# Install current SDK
npm install alchemy-sdk
# Check for leftover imports
grep -rn "alchemy-web3\|@alch/alchemy" src/ --include='*.ts' --include='*.js'
# Update ethers if needed (alchemy-sdk works with ethers v5 and v6)
npm install ethers@6
Step
Implement Alchemy Notify webhooks for real-time blockchain event notifications.
ReadWriteEditBash(curl:*)
Alchemy Webhooks & Events (Notify API)
Overview
Alchemy Notify provides real-time push notifications for on-chain events. Instead of polling, receive webhook callbacks for wallet activity, mined transactions, dropped transactions, and smart contract events.
Webhook Types
| Type |
Trigger |
Use Case |
| Address Activity |
ETH/token transfer to/from address |
Wallet notifications |
| Mined Transaction |
Transaction confirmed on-chain |
Payment confirmation |
| Dropped Transaction |
Transaction removed from mempool |
Failed tx alerting |
| NFT Activity |
NFT transfer events |
Marketplace notifications |
| Custom Webhook |
GraphQL-defined filter |
Complex event tracking |
Instructions
Step 1: Create Webhook via Dashboard or API
# Create Address Activity webhook via Alchemy API
curl -X POST "https://dashboard.alchemy.com/api/create-webhook" \
-H "X-Alchemy-Token: ${ALCHEMY_AUTH_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"network": "ETH_MAINNET",
"webhook_type": "ADDRESS_ACTIVITY",
"webhook_url": "https://your-app.com/webhooks/alchemy",
"addresses": [
"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
]
}'
Step 2: Webhook Handler with Signature Verification
// src/webhooks/alchemy-handler.ts
import express from 'express';
import crypto from 'crypto';
const router = express.Router();
router.post('/webhooks/alchemy',
express.raw({ type: 'application/json' }),
async (req, res) => {
// Verify webhook signature
const signature = req.headers['x-alchemy-signature'] as string;
if (!verifySignature(req.body.toString(), signature)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(req.body.toString());
await processAlchemyEvent(event);
res.status(200).json({ ok: true });
}
);
function verifySignature(body: string, signature: string): boolean {
const signingKey = process.env.ALCHEMY_WEBHOOK_SIGNING_KEY!;
const hmac = crypto.createHmac('sha256', signingKey);
hmac.update(body, 'utf8');
const digest = hmac.digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest));
}
Step 3: Event Router
// src/webhooks/event-router.ts
interface AlchemyWebhookEvent {
webhookId: string;
id: string;
createdAt: string;
type: 'ADDRESS_ACTIVITY' | 'MINED_TRANSACTION' | 'DROPPED_TRANSACTION'
Ready to use alchemy-pack?
|
|
|