Errors
The hierarchy
import {
ExplorerError,
HTTPError,
AuthError,
RateLimitError,
PlanRestrictedError,
NotFoundError,
UnsupportedChainError,
UnsupportedOperationError,
UnknownProviderError,
} from "@agntn/explorers";
| Class | When | Extra |
|---|---|---|
HTTPError | a transport failure with a status | statusCode, rawUrl, body |
AuthError | credentials missing or rejected, 401, 403 | |
RateLimitError | 429 or an explorer saying so in words | retryAfter in seconds |
PlanRestrictedError | valid key, but the read needs a paid tier | |
NotFoundError | 404, an unknown address, hash, block or an ENS name that does not resolve | |
UnsupportedChainError | this provider does not serve that chain | |
UnsupportedOperationError | this provider does not have that operation | |
UnknownProviderError | create() 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.