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 staging → main 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.