> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chipipay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Handling — Node.js

> Concrete examples of every failure path the SDK throws — constructor validation, transfer rejection, missing wallets — plus the typed error classes so you can branch on them with `instanceof`.

The SDK throws fast and throws specifically. Constructor validation rejects bad config before any network call; API calls throw a `ChipiApiError` with a usable `status` code; missing resources return `null` (not a throw) where it makes sense. This page walks the patterns you'll actually hit.

## The error hierarchy

Concrete classes live in `@chipi-stack/shared` and all extend a base `ChipiError`:

```ts theme={null}
import {
  ChipiError,           // base — { code: string, status?: number }
  ChipiApiError,        // HTTP-level failures — carries the HTTP status (its
                        //   constructor takes `status: number`, though the
                        //   inherited type signature is `status?: number`)
  ChipiAuthError,       // 401 from the API
  ChipiValidationError, // 400 from the API
  ChipiWalletError,     // wallet ops (encryption, lookup)
  ChipiTransactionError,// transfer / contract calls
  ChipiSessionError,    // session keys
  ChipiSkuError,        // sku purchases / catalog
  WalletNotCompatibleError,    // < ChipiWalletError
  WalletClassHashNotFoundError,
  PaymasterIncompatibleError,
} from "@chipi-stack/shared";
```

Every `ChipiError` has `.code` (string) and `.message`. `ChipiApiError` carries `.status` (HTTP status code) — its constructor requires it, though the inherited type makes it optional, so devs reading the field still need a fallback (`err.status ?? 500`) to satisfy strict TypeScript. Use `instanceof` for type narrowing or pattern-match on `.code` / `.name` / `.status`.

## Constructor validation

The `ChipiServerSDK` constructor throws synchronously on bad config — before any network call. This is intentional: catch typos in your env vars at boot, not at first request.

```ts theme={null}
import { ChipiServerSDK } from "@chipi-stack/backend";

// Throws: empty / invalid keys
try {
  new ChipiServerSDK({
    apiPublicKey: "invalid-key-12345",
    apiSecretKey: "invalid-secret-12345",
  });
} catch (e) {
  // catch binds `e` as `unknown` under TS strict mode — narrow before reading
  const message = e instanceof Error ? e.message : String(e);
  console.error("SDK config rejected:", message);
  process.exit(1);
}

// Throws: empty secret
try {
  new ChipiServerSDK({
    apiPublicKey: "pk_live_xxx",
    apiSecretKey: "",
  });
} catch {
  console.error("Missing CHIPI_SECRET_KEY in env");
}
```

Wire this into your bootstrap so the process can't start with broken auth.

## API-level rejections

API calls throw `ChipiApiError` (or one of the more specific subclasses) when the server returns a non-2xx. The `status` field tells you which HTTP code — branch on it.

```ts theme={null}
import { ChipiApiError, ChipiAuthError } from "@chipi-stack/shared";

try {
  await sdk.transfer({
    params: {
      encryptKey: "",  // ← invalid: empty PIN
      wallet: userWallet,
      token: "USDC",
      recipient: "0x...",
      amount: 1000n,
    },
  });
} catch (err) {
  if (err instanceof ChipiAuthError) {
    // 401 — your CHIPI_SECRET_KEY is wrong / revoked / scoped wrong
    return res.status(401).send("Auth misconfigured");
  }
  if (err instanceof ChipiApiError && err.status === 400) {
    // Validation failure on the request body
    return res.status(400).send(err.message);
  }
  if (err instanceof ChipiApiError && err.status === 404) {
    // Resource missing
    return res.status(404).send("Wallet not found");
  }
  throw err; // unexpected — let it bubble
}
```

## `getWallet` returns `null`, doesn't throw

For lookups that "might not exist", the SDK returns `null` rather than throwing. This is true for `getWallet` — wrap your code accordingly:

```ts theme={null}
const wallet = await sdk.getWallet({ externalUserId: "non-existent-user" });
if (!wallet) {
  // No wallet for this user — your call to make: create one, or ask them to onboard
  return res.status(404).send("User has no wallet");
}
// wallet is non-null here
```

`null` is a happy-path signal here, not an error. The same call only `throw`s if the API itself failed (auth, network, 5xx) — which you'd catch with the `ChipiApiError` patterns above.

## Invalid on-chain inputs throw before sending

Inputs that don't pass on-chain validation (malformed addresses, unknown tokens) reject **before** the paymaster fires the transaction — you don't pay gas for a rejected request.

```ts theme={null}
import { Chain, ChainToken } from "@chipi-stack/backend";

try {
  await sdk.getTokenBalance({
    walletPublicKey: "0xinvalid",  // ← not a valid Starknet address
    chainToken: ChainToken.USDC,
    chain: Chain.STARKNET,
  });
} catch (err) {
  // Throws synchronously with a validation error — no on-chain cost
  const message = err instanceof Error ? err.message : String(err);
  console.error("Bad address:", message);
}
```

## Putting it together

A small middleware that maps every Chipi error class to the right HTTP response:

```ts theme={null}
import {
  ChipiError,
  ChipiApiError,
  ChipiAuthError,
  ChipiValidationError,
  ChipiWalletError,
} from "@chipi-stack/shared";

export function chipiErrorHandler(err: unknown, req, res, next) {
  if (err instanceof ChipiAuthError) {
    return res.status(401).json({ error: err.code, message: err.message });
  }
  if (err instanceof ChipiValidationError) {
    return res.status(400).json({ error: err.code, message: err.message });
  }
  if (err instanceof ChipiApiError) {
    return res.status(err.status ?? 500).json({ error: err.code, message: err.message });
  }
  if (err instanceof ChipiWalletError) {
    return res.status(409).json({ error: err.code, message: err.message });
  }
  if (err instanceof ChipiError) {
    return res.status(500).json({ error: err.code, message: err.message });
  }
  next(err); // not a Chipi error — let your generic handler take it
}
```

> ✅ Verified against the live API on **2026-05-11**.
