npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gulla0/cardano-error-normalizer

v0.3.0

Published

Normalize heterogeneous Cardano stack errors into a stable CardanoAppError shape.

Readme

Cardano Error Normalizer

A small TypeScript library that normalizes heterogeneous Cardano stack errors into a stable CardanoAppError shape.

Current package version in this repository: 0.3.0.

Install

npm install @gulla0/cardano-error-normalizer

Quickstart (Recommended: Preset + Wrapper)

Use wrapper presets first so context is mostly automatic.

import {
  cip30WalletPreset,
  isCardanoAppError,
  meshProviderPreset
} from "@gulla0/cardano-error-normalizer";

const safeMeshProvider = meshProviderPreset(rawMeshProvider, {
  provider: "blockfrost"
});

const safeWalletApi = cip30WalletPreset(rawWalletApi, {
  walletHint: "eternl"
});

try {
  await safeMeshProvider.submitTx(txCborHex);
} catch (err) {
  if (isCardanoAppError(err)) {
    console.error(err.code, err.resolution?.steps);
    throw err;
  }

  throw err;
}

What You Get (CardanoAppError)

walletHint from context maps to error.wallet?.name unless an adapter provides a richer wallet object.

{
  "name": "CardanoAppError",
  "source": "provider_submit",
  "stage": "submit",
  "code": "QUOTA_EXCEEDED",
  "severity": "warn",
  "message": "Daily request limit has been exceeded",
  "timestamp": "2026-02-19T12:00:00.000Z",
  "network": "preprod",
  "provider": "blockfrost",
  "wallet": { "name": "eternl" },
  "resolution": {
    "title": "Upgrade or wait for quota reset",
    "steps": ["Verify project quota", "Retry after reset window"]
  },
  "meta": {
    "blockfrostReason": "daily_limit",
    "safeProviderWrapped": true,
    "safeProviderMethod": "submitTx"
  }
}

Canonical Integration Path

  1. meshProviderPreset(...) / cip30WalletPreset(...)
  2. withErrorSafety(...) for non-preset objects
  3. createNormalizer({ defaults }) for manual boundaries
  4. @gulla0/cardano-error-normalizer/react for React operations

Wrapper-First Usage

Preset Helpers (Preferred)

import {
  cip30WalletPreset,
  meshProviderPreset
} from "@gulla0/cardano-error-normalizer";

const safeMeshProvider = meshProviderPreset(rawMeshProvider, {
  provider: "blockfrost",
  network: "preprod"
});

const safeWalletApi = cip30WalletPreset(rawWalletApi, {
  walletHint: "eternl",
  network: "preprod"
});

meshProviderPreset maps provider methods (for example submitTx, fetchAddressUTxOs) to provider context. cip30WalletPreset maps CIP-30 wallet methods (for example getUtxos, signTx, submitTx) to wallet context.

Generic Wrapper

import { withErrorSafety } from "@gulla0/cardano-error-normalizer";

const safeProvider = withErrorSafety(rawProvider, {
  ctx: {
    source: "provider_submit",
    stage: "submit",
    provider: "blockfrost",
    network: "preprod"
  },
  onError(normalized, details) {
    console.error("normalized provider error", {
      method: details.method,
      code: normalized.code,
      meta: normalized.meta
    });
  }
});

await safeProvider.submitTx(txCborHex);

withErrorSafety rethrows CardanoAppError and annotates meta.safeProviderWrapped=true and meta.safeProviderMethod.

Advanced: Dynamic ctx(method)

import { withErrorSafety } from "@gulla0/cardano-error-normalizer";

const safeProvider = withErrorSafety(rawProvider, {
  ctx: (method) => ({
    source: method === "submitTx" ? "provider_submit" : "provider_query",
    stage: method === "submitTx" ? "submit" : "build",
    provider: "blockfrost",
    network: "preprod"
  })
});

Use this only when presets are not suitable.

Manual Boundary Usage

import { createNormalizer } from "@gulla0/cardano-error-normalizer";

const normalizer = createNormalizer({
  config: { includeFingerprint: true },
  defaults: {
    source: "provider_submit",
    stage: "submit",
    provider: "blockfrost",
    network: "preprod"
  }
});

try {
  await provider.submitTx(txCborHex);
} catch (err) {
  throw normalizer.normalize(err, { walletHint: "eternl" });
}

React Usage

Use the React subpath with explicit hook bindings from React:

import { useCallback, useState } from "react";
import { useCardanoError } from "@gulla0/cardano-error-normalizer/react";

const tx = useCardanoError({
  operation: submitTx,
  defaults: { source: "provider_submit", stage: "submit" },
  config: {
    hooks: { useState, useCallback }
  }
});

useCardanoError returns loading, data, error, run, normalize, and reset. run(...) rethrows normalized CardanoAppError.

Legacy runtimes that expose globalThis.React can use compatibility mode:

import { useCardanoError } from "@gulla0/cardano-error-normalizer/react/compat";

Context Contract

| Field | Required | Notes | | --- | --- | --- | | source | yes (defaulted) | Defaults to provider_query if omitted. | | stage | yes (defaulted) | Defaults to build if omitted. | | provider | no | Copied to CardanoAppError.provider unless adapter overrides it. | | network | no | Defaults to unknown in final output. | | walletHint | no | Hint only; lands in CardanoAppError.wallet.name when no richer wallet data exists. | | txHash | no | Copied to CardanoAppError.txHash. | | timestamp | no | Uses current ISO timestamp when omitted. |

Resolution Hints (Authority Level)

  • resolution from canonical error code mapping is authoritative for that code family.
  • resolution attached after heuristic message matching (smartMatcher) is best-effort guidance.
  • For UNKNOWN, treat resolution as optional troubleshooting help, not truth.

Debug and Trace Modes

import { withErrorSafety } from "@gulla0/cardano-error-normalizer";

const safeProvider = withErrorSafety(rawProvider, {
  ctx: { source: "provider_submit", stage: "submit", provider: "blockfrost" },
  normalizerConfig: {
    debug: true,
    parseTraces: true
  }
});

Warning: debug may log portions of raw error payloads (input, context, output). Do not enable in production unless that is acceptable for your data-handling policy.

Rendering Actionable Hints

import {
  isCardanoAppError,
  withErrorSafety
} from "@gulla0/cardano-error-normalizer";

try {
  await withErrorSafety(provider, {
    ctx: { source: "provider_submit", stage: "submit" }
  }).submitTx(txCborHex);
} catch (err) {
  if (!isCardanoAppError(err)) {
    throw err;
  }

  const title = err.resolution?.title ?? "Troubleshoot transaction failure";
  const steps = err.resolution?.steps ?? ["Inspect logs and retry"];
  renderHintCard({ title, steps });
}

Default Adapter Order

createNormalizer({ config }) runs adapters in this order:

  1. fromMeshError (unwrap nested errors first)
  2. fromWalletError
  3. fromBlockfrostError
  4. fromNodeStringError
  5. fallback to UNKNOWN

Wallet Mapping Table (CIP-30/CIP-95)

| Wallet error family | Numeric code | Meaning | CardanoErrorCode | | --- | ---: | --- | --- | | APIError | -1 | InvalidRequest | WALLET_INVALID_REQUEST | | APIError | -2 | InternalError | WALLET_INTERNAL | | APIError | -3 | Refused | WALLET_REFUSED | | APIError | -4 | AccountChange | WALLET_ACCOUNT_CHANGED | | TxSignError | 1 | ProofGeneration | WALLET_SIGN_PROOF_GENERATION | | TxSignError | 2 | UserDeclined | WALLET_SIGN_USER_DECLINED | | TxSignError (CIP-95 ext) | 3 | DeprecatedCertificate | TX_LEDGER_VALIDATION_FAILED | | DataSignError (wallet-specific observed behavior) | 1 | ProofGeneration | WALLET_DATA_SIGN_PROOF_GENERATION | | DataSignError (wallet-specific observed behavior) | 2 | AddressNotPK | WALLET_DATA_SIGN_ADDRESS_NOT_PK | | DataSignError (wallet-specific observed behavior) | 3 | UserDeclined | WALLET_DATA_SIGN_USER_DECLINED | | PaginateError (wallet-specific observed behavior) | n/a (maxSize) | requested page exceeds range | WALLET_PAGINATION_OUT_OF_RANGE | | TxSendError | 1 | Refused | WALLET_SUBMIT_REFUSED | | TxSendError | 2 | Failure | WALLET_SUBMIT_FAILURE |

Submit-path disambiguation note: APIError code=-2 normally maps to WALLET_INTERNAL, but maps to WALLET_SUBMIT_FAILURE when submit intent is explicit (source=wallet_submit or stage=submit) or when info indicates submitTx.

Blockfrost Mapping Table

fromBlockfrostError uses key-based parsing (status_code, error, message) across nested payloads, so mapping does not depend on JSON property order.

| HTTP status | Meaning | CardanoErrorCode | Notes (meta) | | ---: | --- | --- | --- | | 400 | invalid request | BAD_REQUEST | | | 402 | daily request limit exceeded | QUOTA_EXCEEDED | blockfrostReason="daily_limit" | | 403 | not authenticated | UNAUTHORIZED | | | 404 | resource does not exist | NOT_FOUND | | | 418 | auto-banned after flooding | FORBIDDEN | blockfrostReason="auto_banned" | | 425 | mempool full | MEMPOOL_FULL | blockfrostReason="mempool_full" | | 429 | rate limited | RATE_LIMITED | | | 5xx | server side error | PROVIDER_INTERNAL | | | other 4xx | other client error | BAD_REQUEST | preserve raw |

Node String Heuristics

The following are heuristic regex matches (best-effort, not protocol-authoritative):

  • /DeserialiseFailure|DecoderFailure|expected word/i -> TX_DESERIALISE_FAILURE
  • /BadInputsUTxO/i -> TX_INPUTS_MISSING_OR_SPENT
  • /OutputTooSmallUTxO|BabbageOutputTooSmallUTxO/i -> TX_OUTPUT_TOO_SMALL
  • /ValueNotConservedUTxO/i -> TX_VALUE_NOT_CONSERVED
  • /ScriptFailure|PlutusFailure|EvaluationFailure|ValidationTagMismatch|redeemer.*execution units/i -> TX_SCRIPT_EVALUATION_FAILED
  • /ShelleyTxValidationError|ApplyTxError/i -> TX_LEDGER_VALIDATION_FAILED when no inner specific tag is found

Contributing / Local Development

Validate this repository locally:

npm install
npm test
npm run typecheck
npm run build

Capture runtime fixtures before rethrowing:

console.error("RUNTIME_ERROR_SAMPLE", {
  err,
  ctx: { source: "provider_submit", stage: "submit", provider: "blockfrost", network: "preprod" }
});

Example

See examples/mesh-blockfrost-eternl.ts in the repository for an end-to-end Mesh + Blockfrost + Eternl flow.