Guide

Errors

One hierarchy for thirteen ways to fail, normalizeError for the rest, and API keys stripped from every message before it exists.

The hierarchy

import {
  ExplorerError,
  HTTPError,
  AuthError,
  RateLimitError,
  PlanRestrictedError,
  NotFoundError,
  UnsupportedChainError,
  UnsupportedOperationError,
  UnknownProviderError,
} from "@agntn/explorers";
ClassWhenExtra
HTTPErrora transport failure with a statusstatusCode, rawUrl, body
AuthErrorcredentials missing or rejected, 401, 403
RateLimitError429 or an explorer saying so in wordsretryAfter in seconds
PlanRestrictedErrorvalid key, but the read needs a paid tier
NotFoundError404, an unknown address, hash, block or an ENS name that does not resolve
UnsupportedChainErrorthis provider does not serve that chain
UnsupportedOperationErrorthis provider does not have that operation
UnknownProviderErrorcreate() or resolveProvider() asked for a name nobody registered

Every one extends ExplorerError and carries provider, so a catch can be as broad or as narrow as the caller likes.

try {
  await provider.getTxDetail?.(hash, "ethereum");
} catch (error) {
  if (error instanceof RateLimitError) {
    console.log(error.retryAfter); // seconds, when the explorer said
  }
  if (error instanceof NotFoundError) {
    // the hash is unknown to this explorer
  }
}

normalizeError

Providers do not write their own classification. normalizeError(error, provider, url) turns whatever ofetch or a provider's own check threw into the hierarchy: 404 or "not found" in the message becomes NotFoundError, 429 or "rate limit" becomes RateLimitError, 401, 403 or "unauthorized" becomes AuthError, anything else with a status or a URL becomes HTTPError, and an ExplorerError passes through untouched. A failure with no status and no URL stays a plain ExplorerError with the original message.

Keys never leak

ExplorerError's constructor runs every message through one redaction: api_key, apikey, key, secret and token query parameters become REDACTED. HTTPError does the same to rawUrl and to the response body, in case the explorer echoed the request back. Helius carries its key as api-key in the query, which is exactly the case this guards. Log an error, paste it in an issue, fine.

Two retries, both explicit

withProvider() retries an automatic selection once after RateLimitError or PlanRestrictedError, on the next provider that serves the chain. Nothing else is retried, and nothing at all is retried under an explicit provider. The HTTP client itself never retries. If you want more, wrap the call; the library will not do it behind your back.

@agntn/explorers·MIT license· Read-only. Addresses you type go to a public explorer API, never to a wallet.