Skip to main content

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.

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:
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.
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.
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:
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 throws 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.
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:
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 by staging-integration/staging-errors.test.ts at commit f40d80e (2026-03-09). Runs in CI on every PR from stagingmain against live staging. Covers constructor validation (no env needed), transfer rejection on empty encryptKey, getWallet returning null for missing users, and getTokenBalance rejecting invalid addresses. Zero gas — every assertion fails before reaching the chain.