Utilities and Services
The Standards SDK includes several utility classes and services that provide essential functionality across the SDK components.
Mirror Node Service
The HederaMirrorNode
class provides a streamlined interface for interacting with Hedera Mirror Nodes, offering methods to retrieve account information, topic messages, and pricing data.
Initialization
import { HederaMirrorNode } from '@hashgraphonline/standards-sdk';
import { Logger } from '@hashgraphonline/standards-sdk';
const logger = new Logger({ module: 'MyApp', level: 'info' });
// Basic initialization
const mirrorNode = new HederaMirrorNode('testnet', logger);
// With custom mirror node configuration
const customMirrorNode = new HederaMirrorNode('mainnet', logger, {
customUrl: 'https://mainnet.hedera.api.hgraph.dev/v1/<API-KEY>',
apiKey: 'your-api-key-here'
});
Custom Mirror Node Providers
The SDK supports integration with custom mirror node providers through flexible configuration:
// Using HGraph with API key in URL
const hgraphMirrorNode = new HederaMirrorNode('mainnet', logger, {
customUrl: 'https://mainnet.hedera.api.hgraph.dev/v1/<API-KEY>',
apiKey: 'your-hgraph-api-key'
});
// Using custom provider with headers
const customProviderMirrorNode = new HederaMirrorNode('mainnet', logger, {
customUrl: 'https://custom-mirror-node.com',
apiKey: 'your-api-key',
headers: {
'X-Custom-Header': 'value',
'X-Another-Header': 'another-value'
}
});
The configuration supports:
- Custom URLs: Override the default Hedera mirror node endpoints
- API Keys: Authentication via URL replacement or headers
- Custom Headers: Additional headers for custom provider requirements
Account Information
Retrieve account information, including balances:
// Get account details including balance
const accountInfo = await mirrorNode.requestAccount('0.0.123456');
console.log(
'Account balance:',
accountInfo.balance.balance / 100_000_000,
'HBAR'
);
// Get account memo
const memo = await mirrorNode.getAccountMemo('0.0.123456');
console.log('Account memo:', memo);
// Get account public key
const publicKey = await mirrorNode.getPublicKey('0.0.123456');
console.log('Public key:', publicKey.toString());
Topic Information
Retrieve topic information and messages:
// Get topic info
const topicInfo = await mirrorNode.getTopicInfo('0.0.123456');
console.log('Topic memo:', topicInfo.memo);
// Get topic fees
const fees = await mirrorNode.getTopicFees('0.0.123456');
console.log('Topic fees:', fees);
// Get topic messages with filtering options
const messages = await mirrorNode.getTopicMessages('0.0.123456', {
sequenceNumber: 'gt:100', // Messages after sequence number 100
limit: 50,
order: 'desc'
});
console.log('Topic messages:', messages);
// Get topic messages by filter with time range
const filteredMessages = await mirrorNode.getTopicMessagesByFilter('0.0.123456', {
startTime: '1629400000.000000000',
endTime: '1629500000.000000000',
limit: 100,
order: 'asc'
});
HBAR Pricing
Get current HBAR price for calculating fees:
// Get current HBAR price in USD
const hbarPrice = await mirrorNode.getHBARPrice(new Date());
console.log('Current HBAR price: $', hbarPrice);
// Calculate cost of HBAR needed for operations
const hbarNeeded = 5; // 5 HBAR
console.log('Cost in USD:', hbarNeeded * hbarPrice);
Transaction Information
Retrieve transaction details and scheduled transactions:
// Get transaction by ID or hash
const transaction = await mirrorNode.getTransaction('0.0.123@1234567890.123456789');
console.log('Transaction details:', transaction);
// Get transaction by timestamp
const transactions = await mirrorNode.getTransactionByTimestamp('1629400000.000000000');
console.log('Transactions at timestamp:', transactions);
// Get scheduled transaction info
const scheduleInfo = await mirrorNode.getScheduleInfo('0.0.123456');
console.log('Schedule info:', scheduleInfo);
// Check scheduled transaction status
const status = await mirrorNode.getScheduledTransactionStatus('0.0.123456');
console.log('Schedule executed:', status.executed);
console.log('Executed date:', status.executedDate);
console.log('Schedule deleted:', status.deleted);
Token and NFT Operations
Manage tokens and NFTs:
// Get token information
const tokenInfo = await mirrorNode.getTokenInfo('0.0.123456');
console.log('Token name:', tokenInfo?.name);
console.log('Token symbol:', tokenInfo?.symbol);
// Get account balance in HBAR
const hbarBalance = await mirrorNode.getAccountBalance('0.0.123456');
console.log('HBAR balance:', hbarBalance);
// Get account tokens
const tokens = await mirrorNode.getAccountTokens('0.0.123456', 100);
console.log('Account tokens:', tokens);
// Get account NFTs
const nfts = await mirrorNode.getAccountNfts('0.0.123456', '0.0.789012', 50);
console.log('Account NFTs:', nfts);
// Get specific NFT info
const nftInfo = await mirrorNode.getNftInfo('0.0.123456', 1);
console.log('NFT metadata:', nftInfo?.metadata);
// Get NFTs by token
const tokenNfts = await mirrorNode.getNftsByToken('0.0.123456', {
accountId: '0.0.789012',
limit: 100,
order: 'desc'
});
// Validate NFT ownership
const ownedNft = await mirrorNode.validateNFTOwnership(
'0.0.123456', // accountId
'0.0.789012', // tokenId
1 // serialNumber
);
console.log('NFT owned:', ownedNft !== null);
Airdrop Operations
Manage token airdrops:
// Get outstanding airdrops sent by an account
const sentAirdrops = await mirrorNode.getOutstandingTokenAirdrops('0.0.123456', {
limit: 50,
order: 'desc',
tokenId: '0.0.789012'
});
// Get pending airdrops received by an account
const receivedAirdrops = await mirrorNode.getPendingTokenAirdrops('0.0.123456', {
limit: 50,
order: 'asc',
senderId: '0.0.789012'
});
Smart Contract Operations
Interact with smart contracts:
// Read from a smart contract (eth_call equivalent)
const contractResult = await mirrorNode.readSmartContractQuery(
'0.0.123456', // contract ID or EVM address
'0x06fdde03', // function selector (e.g., name())
'0.0.789012', // payer account ID
{
gas: 50000,
value: 0
}
);
console.log('Contract result:', contractResult?.result);
// Get contract information
const contract = await mirrorNode.getContract('0.0.123456');
console.log('Contract bytecode:', contract?.bytecode);
// Get contract results
const contractResults = await mirrorNode.getContractResults({
from: '0x1234567890abcdef',
limit: 10,
order: 'desc'
});
// Get specific contract result
const result = await mirrorNode.getContractResult('0x1234567890abcdef');
// Get contract state
const state = await mirrorNode.getContractState('0.0.123456', {
slot: '0x0',
limit: 100
});
// Get contract logs
const logs = await mirrorNode.getContractLogs({
topic0: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
limit: 50
});
// Get contract actions
const actions = await mirrorNode.getContractActions('0x1234567890abcdef', {
limit: 25
});
// Get opcode traces
const opcodes = await mirrorNode.getOpcodeTraces('0x1234567890abcdef', {
stack: true,
memory: true,
storage: true
});
Network and Block Information
Retrieve network and blockchain data:
// Get blocks
const blocks = await mirrorNode.getBlocks({
limit: 10,
order: 'desc'
});
// Get specific block
const block = await mirrorNode.getBlock('12345'); // by number or hash
console.log('Block hash:', block?.hash);
// Get network information
const networkInfo = await mirrorNode.getNetworkInfo();
console.log('Network nodes:', networkInfo?.nodes);
// Get network fees
const fees = await mirrorNode.getNetworkFees();
console.log('Current fees:', fees?.fees);
// Get network supply
const supply = await mirrorNode.getNetworkSupply();
console.log('Total supply:', supply?.total_supply);
// Get network stake
const stake = await mirrorNode.getNetworkStake();
console.log('Total stake:', stake?.total_stake_rewarded);
Logger
The Logger
class provides structured and level-based logging capabilities to help debug applications:
Basic Usage
import { Logger } from '@hashgraphonline/standards-sdk';
// Create a logger instance
const logger = new Logger({
module: 'MyComponent',
level: 'debug', // trace, debug, info, warn, error, fatal
});
// Log at different levels
logger.debug('Detailed information for debugging');
logger.info('General information about application progress');
logger.warn('Warning situations that might cause issues');
logger.error('Error events that might still allow the application to continue');
Structured Logging
The logger supports structured data in logs:
// Log with contextual data
logger.info('User authentication successful', {
userId: '12345',
loginTime: new Date().toISOString(),
ipAddress: '192.168.1.1',
});
// Error logging with details
try {
// Operation that might fail
} catch (error) {
logger.error('Failed to process transaction', {
transactionId: 'tx-12345',
error: error.message,
stack: error.stack,
});
}
Singleton Pattern
The logger supports a singleton pattern for consistent logging across modules:
// In module 1
const logger1 = Logger.getInstance({ module: 'Module1', level: 'info' });
// In module 2
const logger2 = Logger.getInstance({ module: 'Module2' });
// Same logger instance is used and can be configured once
Progress Reporter
The ProgressReporter
class provides a standardized way to report progress for long-running operations:
Basic Usage
import { ProgressReporter } from '@hashgraphonline/standards-sdk';
// Create a progress reporter
const reporter = new ProgressReporter({
module: 'FileUpload',
callback: (data) => {
console.log(`${data.stage}: ${data.message} (${data.progressPercent}%)`);
// Update UI with progress
updateProgressBar(data.progressPercent);
},
logger: myLogger, // Optional
});
// Report progress stages
reporter.preparing('Preparing file upload', 0);
reporter.submitting('Uploading file to server', 25);
reporter.confirming('Verifying upload integrity', 75);
reporter.completed('File upload complete');
// Or report progress manually
reporter.report({
stage: 'processing',
message: 'Analyzing file contents',
progressPercent: 50,
details: { fileSize: '2.5MB', fileType: 'image/jpeg' },
});
Sub-Progress Tracking
Track progress of sub-tasks with percentage scaling:
// Create main progress reporter
const mainReporter = new ProgressReporter({
module: 'Registration',
callback: updateMainProgressUI,
});
// Create sub-reporter for a specific phase (20-50% of main progress)
const profileProgress = mainReporter.createSubProgress({
minPercent: 20,
maxPercent: 50,
logPrefix: 'Profile',
});
// Sub-reporter percentages are automatically scaled
profileProgress.preparing('Uploading profile picture', 0); // Reports 20% on main
profileProgress.submitting('Processing profile', 50); // Reports 35% on main
profileProgress.completed('Profile completed'); // Reports 50% on main
Error Handling
Report failures with the progress reporter:
try {
// Long-running operation
reporter.preparing('Starting operation', 0);
// Simulated error
throw new Error('Network connection failed');
} catch (error) {
// Report failure with details
reporter.failed(`Operation failed: ${error.message}`, {
error: error.message,
timestamp: new Date().toISOString(),
});
}
API Reference
Mirror Node Service
interface MirrorNodeConfig {
/** Custom mirror node URL. Can include <API-KEY> placeholder for URL-based API keys. */
customUrl?: string;
/** API key for authentication. Will be used in both Authorization header and URL replacement. */
apiKey?: string;
/** Additional custom headers to include with requests. */
headers?: Record<string, string>;
}
interface RetryConfig {
maxRetries?: number;
initialDelayMs?: number;
maxDelayMs?: number;
backoffFactor?: number;
}
class HederaMirrorNode {
constructor(
network: 'mainnet' | 'testnet',
logger?: Logger,
config?: MirrorNodeConfig
);
// Configuration
getBaseUrl(): string;
configureMirrorNode(config: MirrorNodeConfig): void;
configureRetry(config: RetryConfig): void;
// Account Operations
getPublicKey(accountId: string): Promise<PublicKey>;
getAccountMemo(accountId: string): Promise<string | null>;
requestAccount(accountId: string): Promise<AccountResponse>;
getAccountBalance(accountId: string): Promise<number | null>;
checkKeyListAccess(keyBytes: Buffer, userPublicKey: PublicKey): Promise<boolean>;
// Topic Operations
getTopicInfo(topicId: string): Promise<TopicResponse>;
getTopicFees(topicId: string): Promise<CustomFees | null>;
getTopicMessages(topicId: string, options?: {
sequenceNumber?: string | number;
limit?: number;
order?: 'asc' | 'desc';
}): Promise<HCSMessage[]>;
getTopicMessagesByFilter(topicId: string, options?: {
sequenceNumber?: string;
startTime?: string;
endTime?: string;
limit?: number;
order?: 'asc' | 'desc';
}): Promise<HCSMessage[] | null>;
// Transaction Operations
getTransaction(transactionIdOrHash: string): Promise<HederaTransaction | null>;
getTransactionByTimestamp(timestamp: string): Promise<HederaTransaction[]>;
getScheduleInfo(scheduleId: string): Promise<ScheduleInfo | null>;
getScheduledTransactionStatus(scheduleId: string): Promise<{
executed: boolean;
executedDate?: Date;
deleted: boolean;
}>;
// Token Operations
getTokenInfo(tokenId: string): Promise<TokenInfoResponse | null>;
getAccountTokens(accountId: string, limit?: number): Promise<AccountTokenBalance[] | null>;
getHBARPrice(date: Date): Promise<number | null>;
// NFT Operations
getAccountNfts(accountId: string, tokenId?: string, limit?: number): Promise<NftDetail[] | null>;
validateNFTOwnership(accountId: string, tokenId: string, serialNumber: number): Promise<NftDetail | null>;
getNftInfo(tokenId: string, serialNumber: number): Promise<NftInfo | null>;
getNftsByToken(tokenId: string, options?: {
accountId?: string;
limit?: number;
order?: 'asc' | 'desc';
serialNumber?: string;
}): Promise<NftInfo[] | null>;
// Airdrop Operations
getOutstandingTokenAirdrops(accountId: string, options?: {
limit?: number;
order?: 'asc' | 'desc';
receiverId?: string;
serialNumber?: string;
tokenId?: string;
}): Promise<TokenAirdrop[] | null>;
getPendingTokenAirdrops(accountId: string, options?: {
limit?: number;
order?: 'asc' | 'desc';
senderId?: string;
serialNumber?: string;
tokenId?: string;
}): Promise<TokenAirdrop[] | null>;
// Smart Contract Operations
readSmartContractQuery(
contractIdOrAddress: string,
functionSelector: string,
payerAccountId: string,
options?: {
estimate?: boolean;
block?: string;
value?: number;
gas?: number;
gasPrice?: number;
}
): Promise<ContractCallQueryResponse | null>;
getContract(contractIdOrAddress: string, timestamp?: string): Promise<ContractEntity | null>;
getContracts(options?: {
contractId?: string;
limit?: number;
order?: 'asc' | 'desc';
}): Promise<ContractEntity[] | null>;
getContractResults(options?: {
from?: string;
blockHash?: string;
blockNumber?: string;
internal?: boolean;
limit?: number;
order?: 'asc' | 'desc';
timestamp?: string;
transactionIndex?: number;
}): Promise<ContractResult[] | null>;
getContractResult(transactionIdOrHash: string, nonce?: number): Promise<ContractResult | null>;
getContractResultsByContract(contractIdOrAddress: string, options?: {
blockHash?: string;
blockNumber?: string;
from?: string;
internal?: boolean;
limit?: number;
order?: 'asc' | 'desc';
timestamp?: string;
transactionIndex?: number;
}): Promise<ContractResult[] | null>;
getContractState(contractIdOrAddress: string, options?: {
limit?: number;
order?: 'asc' | 'desc';
slot?: string;
timestamp?: string;
}): Promise<ContractState[] | null>;
getContractActions(transactionIdOrHash: string, options?: {
index?: string;
limit?: number;
order?: 'asc' | 'desc';
}): Promise<ContractAction[] | null>;
getContractLogs(options?: {
index?: string;
limit?: number;
order?: 'asc' | 'desc';
timestamp?: string;
topic0?: string;
topic1?: string;
topic2?: string;
topic3?: string;
transactionHash?: string;
}): Promise<ContractLog[] | null>;
getContractLogsByContract(contractIdOrAddress: string, options?: {
index?: string;
limit?: number;
order?: 'asc' | 'desc';
timestamp?: string;
topic0?: string;
topic1?: string;
topic2?: string;
topic3?: string;
}): Promise<ContractLog[] | null>;
getOpcodeTraces(transactionIdOrHash: string, options?: {
stack?: boolean;
memory?: boolean;
storage?: boolean;
}): Promise<OpcodesResponse | null>;
// Network Operations
getBlocks(options?: {
limit?: number;
order?: 'asc' | 'desc';
timestamp?: string;
blockNumber?: string;
}): Promise<Block[] | null>;
getBlock(blockNumberOrHash: string): Promise<Block | null>;
getNetworkInfo(): Promise<NetworkInfo | null>;
getNetworkFees(timestamp?: string): Promise<NetworkFees | null>;
getNetworkSupply(timestamp?: string): Promise<NetworkSupply | null>;
getNetworkStake(timestamp?: string): Promise<NetworkStake | null>;
}
Logger Class
class Logger {
constructor(options: LoggerOptions);
static getInstance(options?: LoggerOptions): Logger;
debug(message: string, meta?: any): void;
info(message: string, meta?: any): void;
warn(message: string, meta?: any): void;
error(message: string, meta?: any): void;
setLevel(level: LogLevel): void;
getLevel(): LogLevel;
}
Progress Reporter
class ProgressReporter {
constructor(options: ProgressReporterOptions);
preparing(stage: string, progressPercent: number): void;
submitting(stage: string, progressPercent: number): void;
confirming(stage: string, progressPercent: number): void;
completed(stage: string): void;
failed(stage: string, error: Error): void;
report(data: ProgressData): void;
createSubProgress(options: SubProgressOptions): ProgressReporter;
}
Integration Examples
Custom Logging Integration
import { Logger, LogLevel } from '@hashgraphonline/standards-sdk';
// Create a custom logging solution that integrates with an external service
function setupLogging() {
const logService = {
sendLog: (level, message, meta) => {
// Format timestamp
const timestamp = new Date().toISOString();
// Format metadata
const metaString = meta ? JSON.stringify(meta) : '';
// Prepare log entry
const logEntry = {
timestamp,
level: LogLevel[level],
message,
metadata: meta,
};
// In a real implementation, you might send this to a logging service
console.log(
`[${timestamp}] [${LogLevel[level]}] ${message} ${metaString}`
);
// Example: Send to external service
// fetch('https://logging-service.example.com/log', {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify(logEntry),
// }).catch(console.error);
},
};
// Configure the global logger instance
Logger.getInstance({
module: 'AppName',
level:
process.env.NODE_ENV === 'production' ? LogLevel.INFO : LogLevel.DEBUG,
callback: (level, message, meta) => {
logService.sendLog(level, message, meta);
},
});
console.log('Logging system initialized');
}
NFT Collection Explorer
import { HederaMirrorNode, Logger } from '@hashgraphonline/standards-sdk';
async function exploreNFTCollection(tokenId: string, accountId: string) {
const logger = new Logger({ module: 'NFTExplorer' });
const mirrorNode = new HederaMirrorNode('mainnet', logger);
try {
// Get token information
const tokenInfo = await mirrorNode.getTokenInfo(tokenId);
console.log(`Collection: ${tokenInfo?.name} (${tokenInfo?.symbol})`);
console.log(`Total Supply: ${tokenInfo?.total_supply}`);
// Get NFTs owned by account
const ownedNfts = await mirrorNode.getAccountNfts(accountId, tokenId, 100);
console.log(`Account owns ${ownedNfts?.length || 0} NFTs from this collection`);
// Check specific NFT ownership
for (const nft of ownedNfts || []) {
const nftInfo = await mirrorNode.getNftInfo(tokenId, nft.serial_number);
console.log(`NFT #${nft.serial_number}:`);
console.log(` Metadata: ${nft.token_uri}`);
console.log(` Created: ${nftInfo?.created_timestamp}`);
}
} catch (error) {
logger.error('Failed to explore NFT collection', error);
}
}
Transaction Monitor
import { HederaMirrorNode, Logger } from '@hashgraphonline/standards-sdk';
async function monitorAccountTransactions(accountId: string) {
const logger = new Logger({ module: 'TransactionMonitor' });
const mirrorNode = new HederaMirrorNode('mainnet', logger);
// Monitor scheduled transactions
const checkScheduledTransactions = async () => {
try {
// This would require additional API endpoints to list scheduled transactions
// For now, check specific schedule IDs
const scheduleId = '0.0.123456';
const status = await mirrorNode.getScheduledTransactionStatus(scheduleId);
if (!status.executed && !status.deleted) {
logger.info(`Schedule ${scheduleId} is still pending`);
} else if (status.executed) {
logger.info(`Schedule ${scheduleId} executed at ${status.executedDate}`);
}
} catch (error) {
logger.error('Failed to check scheduled transactions', error);
}
};
// Run checks periodically
setInterval(checkScheduledTransactions, 60000); // Every minute
}
Smart Contract Analytics
import { HederaMirrorNode, Logger } from '@hashgraphonline/standards-sdk';
async function analyzeContractActivity(contractAddress: string) {
const logger = new Logger({ module: 'ContractAnalytics' });
const mirrorNode = new HederaMirrorNode('mainnet', logger);
try {
// Get contract information
const contract = await mirrorNode.getContract(contractAddress);
console.log(`Contract: ${contract?.contract_id}`);
console.log(`Created: ${contract?.created_timestamp}`);
// Get recent contract results
const results = await mirrorNode.getContractResultsByContract(contractAddress, {
limit: 10,
order: 'desc'
});
console.log(`\nRecent transactions:`);
for (const result of results || []) {
console.log(` Hash: ${result.hash}`);
console.log(` From: ${result.from}`);
console.log(` Gas Used: ${result.gas_used}`);
console.log(` Status: ${result.status}`);
}
// Get contract logs for specific events
const transferEventTopic = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';
const logs = await mirrorNode.getContractLogsByContract(contractAddress, {
topic0: transferEventTopic,
limit: 5
});
console.log(`\nRecent Transfer events: ${logs?.length || 0}`);
} catch (error) {
logger.error('Failed to analyze contract', error);
}
}
Airdrop Manager
import { HederaMirrorNode, Logger } from '@hashgraphonline/standards-sdk';
async function manageAirdrops(accountId: string) {
const logger = new Logger({ module: 'AirdropManager' });
const mirrorNode = new HederaMirrorNode('mainnet', logger);
try {
// Check outstanding airdrops sent
const sentAirdrops = await mirrorNode.getOutstandingTokenAirdrops(accountId, {
limit: 50
});
console.log(`Outstanding airdrops sent: ${sentAirdrops?.length || 0}`);
for (const airdrop of sentAirdrops || []) {
console.log(` Token: ${airdrop.token_id} to ${airdrop.receiver_id}`);
console.log(` Amount: ${airdrop.amount}`);
}
// Check pending airdrops to claim
const pendingAirdrops = await mirrorNode.getPendingTokenAirdrops(accountId, {
limit: 50
});
console.log(`\nPending airdrops to claim: ${pendingAirdrops?.length || 0}`);
for (const airdrop of pendingAirdrops || []) {
console.log(` Token: ${airdrop.token_id} from ${airdrop.sender_id}`);
console.log(` Amount: ${airdrop.amount}`);
}
} catch (error) {
logger.error('Failed to manage airdrops', error);
}
}
Best Practices
Logging
-
Use Appropriate Log Levels: Use debug for detailed information, info for general operational messages, warn for potential issues, and error for problems requiring attention.
-
Include Context: Add relevant metadata to log messages to provide context:
logger.info('Processing transaction', {
transactionId,
accountId,
timestamp: new Date().toISOString(),
}); -
Error Handling: Always log errors with their full stack traces:
try {
await someOperation();
} catch (error) {
logger.error('Operation failed', {
error: error.message,
stack: error.stack,
});
}
Mirror Node Usage
-
Custom Provider Configuration: When using custom mirror node providers, configure retry and rate limiting appropriately:
// Configure custom mirror node with retry settings
const mirrorNode = new HederaMirrorNode('mainnet', logger, {
customUrl: 'https://custom-provider.com/api/v1',
apiKey: process.env.MIRROR_NODE_API_KEY,
headers: {
'X-Provider-Id': 'my-app-id'
}
});
// Configure retry behavior for the custom provider
mirrorNode.configureRetry({
maxRetries: 5,
initialDelayMs: 500,
maxDelayMs: 30000,
backoffFactor: 2
}); -
Dynamic Configuration: Update mirror node configuration at runtime:
// Start with default Hedera mirror node
const mirrorNode = new HederaMirrorNode('mainnet', logger);
// Switch to custom provider based on conditions
if (highTrafficMode) {
mirrorNode.configureMirrorNode({
customUrl: 'https://high-performance-mirror.com/api',
apiKey: process.env.HIGH_PERF_API_KEY
});
} -
Caching: Implement caching for frequently accessed mirror node data:
const cache = new Map();
const CACHE_TTL = 60000; // 1 minute
async function getCachedTopicInfo(topicId) {
const cacheKey = `topic:${topicId}`;
const now = Date.now();
if (cache.has(cacheKey)) {
const { data, timestamp } = cache.get(cacheKey);
if (now - timestamp < CACHE_TTL) {
return data;
}
}
const info = await mirrorNode.getTopicInfo(topicId);
cache.set(cacheKey, { data: info, timestamp: now });
return info;
} -
Rate Limiting: Implement rate limiting for mirror node requests:
const pendingRequests = new Set();
const MAX_CONCURRENT = 5;
const RATE_LIMIT_DELAY = 100; // ms
async function rateLimitedRequest(fn) {
while (pendingRequests.size >= MAX_CONCURRENT) {
await new Promise((resolve) => setTimeout(resolve, RATE_LIMIT_DELAY));
}
const requestId = Date.now() + Math.random();
pendingRequests.add(requestId);
try {
return await fn();
} finally {
pendingRequests.delete(requestId);
}
}
// Usage
const topicInfo = await rateLimitedRequest(() =>
mirrorNode.getTopicInfo(topicId)
); -
Error Handling: The mirror node service includes built-in retry logic, but you can add additional handling:
// Configure retry behavior
mirrorNode.configureRetry({
maxRetries: 5,
initialDelayMs: 1000,
maxDelayMs: 30000,
backoffFactor: 2
});
// Add custom error handling
async function safeApiCall<T>(operation: () => Promise<T>): Promise<T | null> {
try {
return await operation();
} catch (error) {
logger.error('Mirror node API call failed', {
error: error.message,
stack: error.stack
});
// Handle specific error cases
if (error.message.includes('429')) {
logger.warn('Rate limit reached, implementing backoff');
await new Promise(resolve => setTimeout(resolve, 5000));
return await safeApiCall(operation);
}
return null;
}
}
// Usage
const messages = await safeApiCall(() =>
mirrorNode.getTopicMessages(topicId)
); -
Batch Operations: Efficiently handle multiple requests:
async function batchGetTokenInfo(tokenIds: string[]) {
const results = new Map<string, any>();
const batchSize = 5;
for (let i = 0; i < tokenIds.length; i += batchSize) {
const batch = tokenIds.slice(i, i + batchSize);
// Process batch in parallel
const batchResults = await Promise.allSettled(
batch.map(tokenId => mirrorNode.getTokenInfo(tokenId))
);
// Store results
batch.forEach((tokenId, index) => {
const result = batchResults[index];
if (result.status === 'fulfilled') {
results.set(tokenId, result.value);
} else {
logger.error(`Failed to get info for token ${tokenId}`, result.reason);
}
});
// Rate limit between batches
if (i + batchSize < tokenIds.length) {
await new Promise(resolve => setTimeout(resolve, 100));
}
}
return results;
}