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(ethBalance.toString()) / 1e18).toFixed(6),
tokens: tokens.sort((a, b) => b.balance - a.balance),
nfts,
recentTransactions: transfers.transfers,
fetchedAt: new Date().toISOString(),
};
}
export { fetchPortfolio, WalletPortfolio };
Step 2: Transaction History Analyzer
// src/portfolio/transactions.ts
import { Alchemy, Network, AssetTransfersCategory, SortingOrder } from 'alchemy-sdk';
const alchemy = new Alchemy({
apiKey: process.env.ALCHEMY_API_KEY,
network: Network.ETH_MAINNET,
});
async function getTransactionHistory(address: string, maxCount: number = 50) {
// Get both sent and received transactions
const [sent, received] = await Promise.all([
alchemy.core.getAssetTransfers({
fromAddress: address,
category: [AssetTransfersCategory.EXTERNAL, AssetTransfersCategory.ERC20],
maxCount,
order: 'desc',
}),
alchemy.core.getAssetTransfers({
toAddress: address,
category: [AssetTransfersCategory.EXTERNAL, AssetTransfersCategory.ERC20],
maxCount,
order: 'desc',
}),
]);
// Merge and sort by block number
const allTransfers = [
...sent.transfers.map(t => ({ ...t, direction: 'sent' as const })),
...received.transfers.map(t => ({ ...t, direction: 'received' as const })),
].sort((a, b) => parseInt(b.blockNum) - parseInt(a.blockNum));
return allTransfers;
}
export { getTransactionHistory };
Step 3: Multi-Chain Portfolio Aggregator
// src/portfolio/multi-chain.ts
import { Alchemy, Network } from 'alchemy-sdk';
const CHAINS = [
{ name: 'Ethereum', network: Network.ETH_MAINNET },
{ name: 'Polygon', network: Network.MATIC_MAINNET },
{ name: 'Arbitrum', network: Network.ARB_MAINNET },
{ name: 'Optimism', network: Network.OPT_MAINNET },
{ name: 'Base', network: Network.BASE_MAINNET },
];
async function getMultiChainBalances(address: string) {
const results = await Promise.allSettled(
CHAINS.map(async (chain) => {
const client = new Alchemy({
apiKey: process.env.ALCHEMY_API_KEY,
network: chain.network,
});
const balance = await client.core.getBalance(address);
const tokens = await client.core.getTokenBalances(address);
const nonZeroTokens = tokens.tokenBalances.filter(
t => t.tokenBalance && t.tokenBalance !== '0x0'
);
return {
chain: chain.name,
nativeBalance: (parseInt(balance.toString()) / 1e18).toFixed(6),
tokenCount: nonZeroTokens.length,
};
})
);
return results
.filter((r): r is PromiseFulfilledResult<any> => r.status === 'fulfilled')
.map(r => r.value);
}
export { getMultiChainBalances };
Output
- Complete wallet portfolio: ETH + ERC-20 tokens + NFTs
- Transaction history with sent/received classification
- Multi-chain balance aggregation across 5 networks
- Sorted holdings with dust filtering
Error Handling
| Error |
Cause |
Solution |
429 Rate Limit |
Too many metadata calls |
Batch with delays or cache metadata |
| Empty token list |
Address has no tokens |
Verify address is correct |
| Missing NFT images |
IPFS gateway timeout |
Use Alchemy's cached URL fallback |
getAssetTransfers empty |
Wrong category filter |
Include all relevant categories |
Resources
Next Steps
For NFT minting and smart contract interaction, see alchemy-core-workflow-b.
1---2name: alchemy-core-workflow-a3description: |4 Build a complete wallet portfolio tracker using Alchemy Enhanced APIs.5 Use when implementing token balance dashboards, NFT galleries,6 transaction history views, or wallet analytics applications.7 Trigger: "alchemy wallet tracker", "alchemy portfolio", "alchemy token dashboard",8 "alchemy transaction history", "build dApp with alchemy".9allowed-tools: Read, Write, Edit, Bash(npm:*), Grep10version: 1.0.011license: MIT12author: Jeremy Longshore <jeremy@intentsolutions.io>13tags: [saas, blockchain, web3, alchemy, defi, portfolio]14compatible-with: claude-code15---1617# Alchemy Core Workflow A — Wallet Portfolio Tracker1819## Overview2021Primary 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.2223## Prerequisites2425- Completed `alchemy-install-auth` setup26- `alchemy-sdk` installed27- Understanding of Ethereum address format and token standards2829## Instructions3031### Step 1: Portfolio Data Fetcher3233```typescript34// src/portfolio/fetcher.ts35import { Alchemy, Network, AssetTransfersCategory } from 'alchemy-sdk';3637const alchemy = new Alchemy({38 apiKey: process.env.ALCHEMY_API_KEY,39 network: Network.ETH_MAINNET,40});4142interface TokenHolding {43 contractAddress: string;44 symbol: string;45 name: string;46 balance: number;47 decimals: number;48}4950interface NftHolding {51 contractAddress: string;52 collectionName: string;53 tokenId: string;54 name: string;55 imageUrl: string | null;56}5758interface WalletPortfolio {59 address: string;60 ethBalance: string;61 tokens: TokenHolding[];62 nfts: NftHolding[];63 recentTransactions: any[];64 fetchedAt: string;65}6667async function fetchPortfolio(address: string): Promise<WalletPortfolio> {68 // Parallel fetch all portfolio data69 const [ethBalance, tokenBalances, nftResponse, transfers] = await Promise.all([70 alchemy.core.getBalance(address),71 alchemy.core.getTokenBalances(address),72 alchemy.nft.getNftsForOwner(address, { pageSize: 100 }),73 alchemy.core.getAssetTransfers({74 fromAddress: address,75 category: [76 AssetTransfersCategory.EXTERNAL,77 AssetTransfersCategory.ERC20,78 AssetTransfersCategory.ERC721,79 ],80 maxCount: 25,81 order: 'desc',82 }),83 ]);8485 // Resolve token metadata86 const tokens: TokenHolding[] = [];87 for (const tb of tokenBalances.tokenBalances) {88 if (tb.tokenBalance && tb.tokenBalance !== '0x0') {89 const metadata = await alchemy.core.getTokenMetadata(tb.contractAddress);90 const balance = parseInt(tb.tokenBalance, 16) / Math.pow(10, metadata.decimals || 18);91 if (balance > 0.001) { // Filter dust92 tokens.push({93 contractAddress: tb.contractAddress,94 symbol: metadata.symbol || 'UNKNOWN',95 name: metadata.name || 'Unknown Token',96 balance,97 decimals: metadata.decimals || 18,98 });99 }100 }101 }102103 // Map NFTs104 const nfts: NftHolding[] = nftResponse.ownedNfts.map(nft => ({105 contractAddress: nft.contract.address,106 collectionName: nft.contract.name || 'Unknown Collection',107 tokenId: nft.tokenId,108 name: nft.name || `#${nft.tokenId}`,109 imageUrl: nft.image?.cachedUrl || null,110 }));111112 return {113 address,114 ethBalance: (parseInt(ethBalance.toString()) / 1e18).toFixed(6),115 tokens: tokens.sort((a, b) => b.balance - a.balance),116 nfts,117 recentTransactions: transfers.transfers,118 fetchedAt: new Date().toISOString(),119 };120}121122export { fetchPortfolio, WalletPortfolio };123```124125### Step 2: Transaction History Analyzer126127```typescript128// src/portfolio/transactions.ts129import { Alchemy, Network, AssetTransfersCategory, SortingOrder } from 'alchemy-sdk';130131const alchemy = new Alchemy({132 apiKey: process.env.ALCHEMY_API_KEY,133 network: Network.ETH_MAINNET,134});135136async function getTransactionHistory(address: string, maxCount: number = 50) {137 // Get both sent and received transactions138 const [sent, received] = await Promise.all([139 alchemy.core.getAssetTransfers({140 fromAddress: address,141 category: [AssetTransfersCategory.EXTERNAL, AssetTransfersCategory.ERC20],142 maxCount,143 order: 'desc',144 }),145 alchemy.core.getAssetTransfers({146 toAddress: address,147 category: [AssetTransfersCategory.EXTERNAL, AssetTransfersCategory.ERC20],148 maxCount,149 order: 'desc',150 }),151 ]);152153 // Merge and sort by block number154 const allTransfers = [155 ...sent.transfers.map(t => ({ ...t, direction: 'sent' as const })),156 ...received.transfers.map(t => ({ ...t, direction: 'received' as const })),157 ].sort((a, b) => parseInt(b.blockNum) - parseInt(a.blockNum));158159 return allTransfers;160}161162export { getTransactionHistory };163```164165### Step 3: Multi-Chain Portfolio Aggregator166167```typescript168// src/portfolio/multi-chain.ts169import { Alchemy, Network } from 'alchemy-sdk';170171const CHAINS = [172 { name: 'Ethereum', network: Network.ETH_MAINNET },173 { name: 'Polygon', network: Network.MATIC_MAINNET },174 { name: 'Arbitrum', network: Network.ARB_MAINNET },175 { name: 'Optimism', network: Network.OPT_MAINNET },176 { name: 'Base', network: Network.BASE_MAINNET },177];178179async function getMultiChainBalances(address: string) {180 const results = await Promise.allSettled(181 CHAINS.map(async (chain) => {182 const client = new Alchemy({183 apiKey: process.env.ALCHEMY_API_KEY,184 network: chain.network,185 });186 const balance = await client.core.getBalance(address);187 const tokens = await client.core.getTokenBalances(address);188 const nonZeroTokens = tokens.tokenBalances.filter(189 t => t.tokenBalance && t.tokenBalance !== '0x0'190 );191 return {192 chain: chain.name,193 nativeBalance: (parseInt(balance.toString()) / 1e18).toFixed(6),194 tokenCount: nonZeroTokens.length,195 };196 })197 );198199 return results200 .filter((r): r is PromiseFulfilledResult<any> => r.status === 'fulfilled')201 .map(r => r.value);202}203204export { getMultiChainBalances };205```206207## Output208209- Complete wallet portfolio: ETH + ERC-20 tokens + NFTs210- Transaction history with sent/received classification211- Multi-chain balance aggregation across 5 networks212- Sorted holdings with dust filtering213214## Error Handling215216| Error | Cause | Solution |217|-------|-------|----------|218| `429 Rate Limit` | Too many metadata calls | Batch with delays or cache metadata |219| Empty token list | Address has no tokens | Verify address is correct |220| Missing NFT images | IPFS gateway timeout | Use Alchemy's cached URL fallback |221| `getAssetTransfers` empty | Wrong category filter | Include all relevant categories |222223## Resources224225- [Alchemy Enhanced APIs](https://www.alchemy.com/docs)226- [Alchemy NFT API](https://www.alchemy.com/docs/reference/nft-api-quickstart)227- [Alchemy Asset Transfers](https://www.alchemy.com/docs/reference/sdk-getassettransfers)228229## Next Steps230231For NFT minting and smart contract interaction, see `alchemy-core-workflow-b`.232