home thoughts library

My Journey to Painless Error Handling in Solana Programs with Anchor and TypeScript

Apr 16, 2025

·

8 min read

tl;dr: Sharing my experience on how to handle errors in Solana programs using Anchor and TypeScript to make your SDK user-friendly and foolproof.

My Journey to Painless Error Handling in Solana Programs with Anchor and TypeScript

Hey there, fellow Solana devs! I recently wrote a thread on X that totally changed how I think about error handling in Solana programs. I’ve been building dApps on Solana for a while now, and let me tell you—error handling has always been a pain in the 🍑. Cryptic error codes, vague messages, and endless debugging sessions were driving me up the wall. But my thread opened my eyes to a better way, and I’m super excited to share what I’ve learned with you!

In this post, I’ll walk you through how I used Anchor and TypeScript to create meaningful, foolproof error-handling tools for my Solana program SDK. My goal? To make life easier for both myself and the developers using my SDK, while ensuring end-users get clear, helpful error messages. Let’s dive in!

Why I Care About Error Handling in Solana

I’ve been working on Solana for a while now, and I love how fast and powerful it is for building dApps. But here’s the thing—error handling can be a total nightmare if you don’t set it up right. When something goes wrong in a Solana program, you might get errors from different places:

  • Program Errors: Stuff like “Invalid authority” or “Insufficient funds” coming from the on-chain program.
  • SDK Errors: Issues in my client-side SDK, like missing configs or invalid inputs.
  • Unexpected Errors: Random things like browser quirks or network hiccups.

Without good error handling, I’d end up staring at raw error codes like 6002 or Attempt to debit an account but found no record of a prior credit with no clue what went wrong. Worse, my dApp users would see unhelpful messages like “Something broke, sorry!” in the UI. I knew I had to do better—for my sanity and for anyone using my SDK.

So, my mission became clear: create an error-handling system that’s:

  • Easy to understand with human-readable messages.
  • Strongly typed for TypeScript (because I love type safety!).
  • Able to handle both program and SDK errors smoothly.
  • Great for debugging and user-friendly for my dApp’s UI.

Step 1: Getting Cozy with Anchor, IDL, and ProgramError

Since I’m using Anchor to build my Solana programs (it’s a lifesaver, by the way), I already had a head start. Anchor generates an Interface Description Language (IDL) file for your program, which includes all your instructions, accounts, and—most importantly—errors. If you define errors in your program with #[error_code], they show up in the idl.json file like this:

{
  "errors": [
    { "code": 6001, "name": "InvalidAuthority", "msg": "Invalid authority" },
    { "code": 6002, "name": "InvalidVerifier", "msg": "Invalid verifier" }
  ]
}

Anchor also gives you a ProgramError constructor in its TypeScript SDK, which grabs error data from blockchain logs. This was my starting point for catching and handling errors thrown by my program.

Step 2: Catching Program Errors Like a Pro

When a transaction fails on Solana—like if I pass an invalid authority—Anchor’s TypeScript SDK throws a ProgramError. I wanted to catch these errors and make them useful, so here’s what I did:

if (error instanceof ProgramError) {
  const errorCode = error.code; // e.g., 6001
  // Look up the error in the IDL to get a friendly name and message
  const errorName = idl.errors.find((err) => err.code === errorCode)?.name;
  if (errorName) {
    const prettyError = {
      name: errorName, // e.g., "InvalidAuthority"
      message: idl.errors.find((err) => err.code === errorCode)?.msg, // e.g., "Invalid authority"
      details: error.logs, // Blockchain logs for debugging
      stack: error.programErrorStack, // Stack trace for debugging
    };
    // Now I can log this or show it in my dApp's UI
    console.log(prettyError);
  }
}

Here’s the breakdown of what I’m doing:

  • I check if the error is a ProgramError.
  • I grab the errorCode (like 6001 for InvalidAuthority).
  • I use the IDL to find the error’s name and message.
  • I create a prettyError object that’s both dev-friendly (with logs and stack traces) and user-friendly (with a clear message).

This way, I’m not stuck decoding raw error codes anymore—my errors are meaningful!

Step 3: Handling SDK Errors (Because I Don’t Want to Pay for Your API Usage!)

Not all errors come from the blockchain. Sometimes, the issue is in my SDK. For example, I don’t want to pay for Solana API usage for my SDK users, so I require them to pass a Connection object. If they don’t, I need to throw a custom SDK error.

First, I defined my SDK errors in a structured way:

const sdkErrors = [
  { code: 1000, name: "InvalidConfig", msg: "Invalid SDK configuration" },
  { code: 1001, name: "InvalidInitiator", msg: "Invalid initiator account" },
  { code: 1002, name: "InvalidVerifier", msg: "Invalid verifier account" },
  { code: 1003, name: "InvalidAuthority", msg: "Invalid authority account" },
] as const;

type SdkErrorName = typeof sdkErrors[number]["name"];

Then, I created a custom SdkError class that extends JavaScript’s Error:

class SdkError extends Error {
  public code: number;
  public override name: string;
  public message: string;
  public originalError?: Error;

  constructor(code: number, name: string, message: string, originalError?: Error) {
    super(message);
    this.name = `SdkError[${name}]`;
    this.code = code;
    this.message = message;
    this.originalError = originalError;
  }
}

function createSdkError<T extends SdkErrorName>(name: T) {
  const foundSdkError = sdkErrors.find((error) => error.name === name);
  if (!foundSdkError) {
    throw new Error(`Unknown SDK error: ${name}`);
  }
  return new SdkError(foundSdkError.code, foundSdkError.name, foundSdkError.msg);
}

Now I can throw an SdkError when something’s wrong in my SDK. For example:

class MySDK {
  private readonly config: Record<string, unknown>;

  constructor(config: Record<string, unknown>) {
    if (!config.connection) {
      throw createSdkError("InvalidConfig");
    }
    this.config = config;
  }
}

This throws a clean error with a name (SdkError[InvalidConfig]), code (1000), and message (Invalid SDK configuration).

Step 4: Catching SDK Errors with Ease

I catch SdkError the same way I catch ProgramError:

try {
  const sdk = new MySDK({}); // Missing connection, will throw SdkError
} catch (error) {
  if (error instanceof ProgramError) {
    // Handle program errors (like in Step 2)
  } else if (error instanceof SdkError) {
    // Handle SDK errors
    console.log({
      name: error.name, // e.g., "SdkError[InvalidConfig]"
      code: error.code, // e.g., 1000
      message: error.message, // e.g., "Invalid SDK configuration"
    });
  } else {
    // Handle unexpected errors
    throw error; // Pass it up the chain
  }
}

This keeps my error handling consistent and makes sure SDK errors are just as clear as program errors.

Step 5: Dealing with the Unexpected

Not every error is a ProgramError or SdkError. Sometimes, I run into browser errors, Node.js issues, or random exceptions. For those, I just throw them up the chain so a generic error handler can deal with them:

try {
  // My blockchain or SDK code
} catch (error) {
  if (error instanceof ProgramError) {
    // Handle program errors
  } else if (error instanceof SdkError) {
    // Handle SDK errors
  } else {
    // Let someone else deal with this
    throw error;
  }
}

For my front-end dApps, I also added a little helper function to handle UI stuff—like toggling components or logging out users:

function handleFrontendError(error: unknown): { shouldLogout: boolean; message: string } {
  if (error instanceof SdkError && error.name === "SdkError[InvalidConfig]") {
    return { shouldLogout: true, message: "Please reconfigure your SDK and try again." };
  }
  return { shouldLogout: false, message: "An unexpected error occurred." };
}

My Top Tips for Solana Error Handling

After all this, here are the best practices I’ve picked up:

  1. Always Export Your Errors: Make sure you define errors in your program with #[error_code] so they show up in the IDL.
  2. Use TypeScript for Safety: TypeScript makes my error handling so much cleaner and safer.
  3. Make Errors Human-Readable: Combine error names and messages for clear feedback.
  4. Add Debugging Info: Include logs and stack traces for easier debugging.
  5. Test Everything: I always test my error handling by simulating failures (like passing invalid data).
  6. Keep It Stateless: These error messages are stateless and not cached, so I’m careful not to overuse them.

Wrapping Up

Error handling in Solana doesn’t have to be a nightmare anymore! Through my experience, I learned how to use Anchor’s ProgramError, create my own SdkError, and build a system that’s both developer-friendly and user-friendly. Whether I’m debugging a failed transaction or showing an error in my dApp’s UI, I now have tools that make the process painless.

If you're struggling with error handling in Solana, I hope my journey helps you out. And if you're stuck, feel free to reach out—I'm happy to chat about Solana, Anchor, or anything Web3! 🚀