Best Practices
This page covers development guidelines and best practices for node integration.
State Management
Always Sync State on Initialization
async syncState(): Promise<NodeResult<void>> {
try {
// Primary sync
await this.fetchFromPrimarySource();
} catch (error) {
console.warn(`Primary sync failed: ${error.message}`);
// Fallback to secondary source
try {
await this.fetchFromSecondarySource();
} catch (fallbackError) {
return new NodeError(
`All sync methods failed: ${fallbackError.message}`,
NodeError.errorCodes.NodeStateError
);
}
}
}
Tips from Existing Nodes
- Meson: Caches
/limitsfor 5 minutes and gracefully uses stale cache if the API is down - NEAR: Merges API asset IDs with raw contract addresses (and adds native token variants) to maximize matching
- GasZip: Builds dynamic chain name↔ID mappings from
/chainsand exposes both numeric and string chain IDs
Token Management
Normalize Token Addresses
private normalizeTokenAddress(address: string, chainId: string): string {
// Handle native tokens
if (address === '0x0000000000000000000000000000000000000000') {
return this.getNativeTokenAddress(chainId);
}
// Normalize EVM addresses
if (address.startsWith('0x')) {
return address.toLowerCase();
}
return address;
}
Token Nuances
- NEAR: Supports both
nep141:andnep245:formats and can infer raw addresses from asset IDs when needed. Also adds lowercase variants for Solana program IDs and short native aliases like111...111 - Meson: Lets callers pass EVM token contract addresses or simple symbols like
usdc, resolving to the provider's expected format - GasZip: Treats native addresses as chain-dependent (e.g.,
111...111on Solana) but also accepts the EVM zero-address as a cross-chain friendly fallback
Chain ID Handling
Support Multiple Formats
private normalizeChainId(chainId: string): string {
const chainIdMap: Record<string, string> = {
'1': 'ethereum',
'137': 'polygon',
'56': 'bsc',
};
return chainIdMap[chainId] || chainId.toLowerCase();
}
Chain ID Handling Examples
- NEAR: Normalizes multiple aliases (e.g.,
1→eth,137→pol) and supports alternative chain IDs for matching - GasZip: Maps human-readable names to numeric IDs from its
/chainsresponse and exposes reverse mappings for display and compatibility
API Integration
Implement Proper Retry Logic
private async makeApiCall<T>(url: string, options: RequestInit): Promise<NodeResult<T>> {
const maxRetries = this.nodeConfig.retryConfig?.maxRetries || 3;
let lastError: Error;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, {
...options,
timeout: this.nodeConfig.timeout,
});
if (response.ok) {
return await response.json();
}
lastError = new Error(`HTTP ${response.status}: ${response.statusText}`);
// Don't retry on client errors
if (response.status >= 400 && response.status < 500) {
break;
}
} catch (error) {
lastError = error as Error;
}
// Wait before retry
if (attempt < maxRetries - 1) {
await new Promise(resolve =>
setTimeout(resolve, this.nodeConfig.retryConfig?.retryDelay || 1000)
);
}
}
return new NodeError(
`API call failed after ${maxRetries} attempts: ${lastError.message}`,
NodeError.errorCodes.NetworkError
);
}
API Considerations
- Meson: Enriches API errors with decoded
error.codeand friendly messages (e.g., amount-over-limit) and pre-validates against/limits - NEAR: Sets reasonable timeouts and distinguishes between 400 vs. 500 responses to guide callers
- GasZip: Uses AbortController timeouts and returns structured
NodeErrorwhen the request aborts
Logging and Debugging
Use Consistent Logging Format
private log(level: 'info' | 'warn' | 'error', message: string, data?: any) {
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}] [${this.id}] [${level.toUpperCase()}] ${message}`;
if (data) {
console[level](logMessage, data);
} else {
console[level](logMessage);
}
}
Debugging Pointers
- Validate address formats early (see
Near.validateAndFormatAddress) - Log normalized chain IDs and derived asset IDs to quickly spot mismatches
- For quote APIs, log the fully constructed request object/body and a redacted response
Performance Considerations
- Cache Frequently Accessed Data: Cache token lists, chain mappings, and limits
- Minimize API Calls: Batch requests when possible
- Use Efficient Data Structures: Use Sets and Maps for fast lookups
- Optimize Validation: Make
isSwapEnabled()as fast as possible
Security Considerations
- Validate All Inputs: Never trust external input
- Sanitize Addresses: Normalize and validate addresses
- Handle Secrets Safely: Use environment variables for API keys
- Rate Limiting: Respect API rate limits
Next Steps
- Troubleshooting - Common issues and solutions
- Testing - Write comprehensive tests