Skip to main content

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 /limits for 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 /chains and 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: and nep245: formats and can infer raw addresses from asset IDs when needed. Also adds lowercase variants for Solana program IDs and short native aliases like 111...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...111 on 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., 1eth, 137pol) and supports alternative chain IDs for matching
  • GasZip: Maps human-readable names to numeric IDs from its /chains response 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.code and 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 NodeError when 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

  1. Cache Frequently Accessed Data: Cache token lists, chain mappings, and limits
  2. Minimize API Calls: Batch requests when possible
  3. Use Efficient Data Structures: Use Sets and Maps for fast lookups
  4. Optimize Validation: Make isSwapEnabled() as fast as possible

Security Considerations

  1. Validate All Inputs: Never trust external input
  2. Sanitize Addresses: Normalize and validate addresses
  3. Handle Secrets Safely: Use environment variables for API keys
  4. Rate Limiting: Respect API rate limits

Next Steps