Skip to main content
You can handle errors gracefully using the SDK’s structured error classes. All errors extend the base WebacyError class, making it easy to catch and respond to different failure scenarios.

Error Classes

import {
  WebacyError,
  AuthenticationError,
  ValidationError,
  RateLimitError,
  NotFoundError,
  NetworkError,
} from '@webacy-xyz/sdk';
ErrorHTTP StatusDescription
AuthenticationError401Invalid or missing API key
ValidationError400Invalid input parameters
RateLimitError429Rate limit exceeded
NotFoundError404Resource not found
NetworkError-Network connectivity issues
WebacyErrorVariousBase class for all errors

Basic Error Handling

import { ThreatClient, ValidationError, RateLimitError } from '@webacy-xyz/sdk';

const client = new ThreatClient({ apiKey: process.env.WEBACY_API_KEY });

try {
  const result = await client.addresses.analyze('0x...');
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Invalid input:', error.message);
  } else if (error instanceof RateLimitError) {
    console.error('Rate limited, retry later');
  } else {
    console.error('Unexpected error:', error);
  }
}

Error Properties

All SDK errors include these properties:
try {
  await client.addresses.analyze('invalid-address');
} catch (error) {
  if (error instanceof WebacyError) {
    console.log(error.message);    // Human-readable message
    console.log(error.code);       // Programmatic error code
    console.log(error.status);     // HTTP status code (if applicable)
    console.log(error.requestId);  // Request ID for support
    console.log(error.endpoint);   // Failed endpoint
    console.log(error.cause);      // Original error (if wrapped)
  }
}

Recovery Suggestions

Errors provide recovery suggestions:
try {
  await client.addresses.analyze('invalid');
} catch (error) {
  if (error instanceof WebacyError) {
    const suggestion = error.getRecoverySuggestion();
    if (suggestion) {
      console.log('How to fix:', suggestion);
    }
  }
}

Specific Error Types

AuthenticationError

Thrown when the API key is invalid or missing.
import { ThreatClient, AuthenticationError } from '@webacy-xyz/sdk';

try {
  const client = new ThreatClient({ apiKey: 'invalid-key' });
  await client.addresses.analyze('0x...');
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.error('Invalid API key');
    // Prompt user to check their API key
  }
}

ValidationError

Thrown when input parameters are invalid.
import { ThreatClient, Chain, ValidationError } from '@webacy-xyz/sdk';

const client = new ThreatClient({ apiKey: 'your-api-key' });

try {
  await client.addresses.analyze('not-an-address', { chain: Chain.ETH });
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Validation failed:', error.message);
    console.log('Fix:', error.getRecoverySuggestion());
    // "Ensure the address is a valid Ethereum address (0x followed by 40 hex characters)"
  }
}

RateLimitError

Thrown when you exceed API rate limits. The SDK automatically retries rate-limited requests with exponential backoff.
import { ThreatClient, RateLimitError } from '@webacy-xyz/sdk';

const client = new ThreatClient({ apiKey: 'your-api-key' });

try {
  await client.addresses.analyze('0x...');
} catch (error) {
  if (error instanceof RateLimitError) {
    console.error('Rate limited');

    if (error.retryAfter) {
      console.log(`Retry after ${error.retryAfter} seconds`);
    }

    if (error.resetAt) {
      const resetDate = new Date(error.resetAt * 1000);
      console.log(`Limit resets at ${resetDate.toISOString()}`);
    }
  }
}

NotFoundError

Thrown when the requested resource doesn’t exist.
import { ThreatClient, NotFoundError } from '@webacy-xyz/sdk';

const client = new ThreatClient({ apiKey: 'your-api-key' });

try {
  await client.contracts.analyze('0xNonExistentContract...');
} catch (error) {
  if (error instanceof NotFoundError) {
    console.error('Contract not found');
  }
}

NetworkError

Thrown when there are network connectivity issues.
import { ThreatClient, NetworkError } from '@webacy-xyz/sdk';

const client = new ThreatClient({ apiKey: 'your-api-key' });

try {
  await client.addresses.analyze('0x...');
} catch (error) {
  if (error instanceof NetworkError) {
    console.error('Network error:', error.message);
    // Retry or notify user of connectivity issues
  }
}

Retryable Errors

Check if an error can be retried:
import { ThreatClient, WebacyError } from '@webacy-xyz/sdk';

const client = new ThreatClient({ apiKey: 'your-api-key' });

async function analyzeWithRetry(address: string, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await client.addresses.analyze(address);
    } catch (error) {
      if (error instanceof WebacyError && error.isRetryable() && attempt < maxRetries) {
        // Safe to retry (rate limits, network issues)
        const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }
      // Don't retry (validation errors, auth errors) or max retries reached
      throw error;
    }
  }
}
Retryable errors:
  • RateLimitError - Wait and retry
  • NetworkError - Retry after connectivity restored
Non-retryable errors:
  • AuthenticationError - Fix API key
  • ValidationError - Fix input
  • NotFoundError - Resource doesn’t exist

Automatic Retries

The SDK automatically retries failed requests with exponential backoff:
const client = new ThreatClient({
  apiKey: process.env.WEBACY_API_KEY,
  retry: {
    maxRetries: 3,         // Default: 3
    initialDelay: 1000,    // Default: 1000ms
    maxDelay: 30000,       // Default: 30000ms
    backoffMultiplier: 2,  // Default: 2
  },
});
Disable retries for specific requests using an AbortController:
const controller = new AbortController();

// Timeout after 5 seconds (no retries)
setTimeout(() => controller.abort(), 5000);

try {
  await client.addresses.analyze('0x...', {
    signal: controller.signal,
  });
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('Request timed out');
  }
}

Logging Errors

Convert errors to JSON for logging:
try {
  await client.addresses.analyze('0x...');
} catch (error) {
  if (error instanceof WebacyError) {
    // Structured logging
    console.error(JSON.stringify(error.toJSON(), null, 2));
    // {
    //   "name": "ValidationError",
    //   "message": "Invalid Ethereum address format",
    //   "code": "VALIDATION_ERROR",
    //   "status": 400,
    //   "requestId": "req_abc123",
    //   "endpoint": "/addresses/invalid"
    // }
  }
}

Full Example

import {
  ThreatClient,
  Chain,
  WebacyError,
  AuthenticationError,
  ValidationError,
  RateLimitError,
  NotFoundError,
  NetworkError,
} from '@webacy-xyz/sdk';

async function safeAnalyze(client: ThreatClient, address: string) {
  try {
    return await client.addresses.analyze(address);

  } catch (error) {
    // Handle specific error types
    if (error instanceof AuthenticationError) {
      throw new Error('Please check your API key configuration');
    }

    if (error instanceof ValidationError) {
      throw new Error(`Invalid address: ${error.getRecoverySuggestion()}`);
    }

    if (error instanceof RateLimitError) {
      const waitTime = error.retryAfter || 60;
      throw new Error(`Too many requests. Please wait ${waitTime} seconds.`);
    }

    if (error instanceof NotFoundError) {
      return null; // Address not found, return null
    }

    if (error instanceof NetworkError) {
      throw new Error('Network error. Please check your connection.');
    }

    // Log unexpected errors with request ID for support
    if (error instanceof WebacyError) {
      console.error('Unexpected error:', error.toJSON());
      if (error.requestId) {
        console.error(`Request ID for support: ${error.requestId}`);
      }
    }

    throw error;
  }
}