Error Handling

Handle errors gracefully in your application.

Overview

The SDK can throw various errors during transaction execution. This guide covers common errors and how to handle them.

Error Categories

Insufficient Funds

User doesn't have enough balance:

typescript
try {
  await sdk.placeWagerV2({
    bet: betAddress,
    amount: 1000,
    side: Outcome.For,
    signers: [wallet],
  });
} catch (error) {
  if (error.message.includes("insufficient funds") ||
      error.message.includes("0x1")) {
    console.log("Not enough USDC to place this wager");
    // Show deposit UI
  }
}

Invalid State

Operation not allowed in current bet state:

typescript
try {
  await sdk.placeWagerV2({
    bet: betAddress,
    amount: 25,
    side: Outcome.For,
    signers: [wallet],
  });
} catch (error) {
  if (error.message.includes("bet is not pending")) {
    console.log("This bet is no longer accepting wagers");
  } else if (error.message.includes("already resolving")) {
    console.log("Voting has already started");
  }
}

Account Errors

Missing or invalid accounts:

typescript
try {
  await sdk.accounts.betV2.single(invalidAddress);
} catch (error) {
  if (error.message.includes("Account does not exist")) {
    console.log("Bet not found at this address");
  }
}

Network Errors

RPC or network issues:

typescript
try {
  await sdk.placeWagerV2({...});
} catch (error) {
  if (error.message.includes("failed to send transaction") ||
      error.message.includes("timeout")) {
    console.log("Network error, please try again");
  } else if (error.message.includes("blockhash not found")) {
    console.log("Transaction expired, please retry");
  }
}

Comprehensive Error Handler

typescript
interface SDKError {
  type: "INSUFFICIENT_FUNDS" | "INVALID_STATE" | "ACCOUNT_NOT_FOUND" | 
        "ALREADY_EXISTS" | "PERMISSION_DENIED" | "NETWORK" | "UNKNOWN";
  message: string;
  original: Error;
}

function parseSDKError(error: Error): SDKError {
  const message = error.message.toLowerCase();

  // Insufficient funds
  if (message.includes("insufficient") || message.includes("0x1")) {
    return {
      type: "INSUFFICIENT_FUNDS",
      message: "Not enough balance for this operation",
      original: error,
    };
  }

  // Invalid state transitions
  if (message.includes("not pending") || 
      message.includes("not resolving") ||
      message.includes("already resolved")) {
    return {
      type: "INVALID_STATE",
      message: "Operation not allowed in current state",
      original: error,
    };
  }

  // Account not found
  if (message.includes("account does not exist") ||
      message.includes("account not found")) {
    return {
      type: "ACCOUNT_NOT_FOUND",
      message: "The requested account was not found",
      original: error,
    };
  }

  // Already exists
  if (message.includes("already in use") ||
      message.includes("already exists")) {
    return {
      type: "ALREADY_EXISTS",
      message: "This account already exists",
      original: error,
    };
  }

  // Permission errors
  if (message.includes("not authorized") ||
      message.includes("owner mismatch")) {
    return {
      type: "PERMISSION_DENIED",
      message: "You don't have permission for this action",
      original: error,
    };
  }

  // Network errors
  if (message.includes("timeout") ||
      message.includes("failed to send") ||
      message.includes("blockhash")) {
    return {
      type: "NETWORK",
      message: "Network error, please try again",
      original: error,
    };
  }

  return {
    type: "UNKNOWN",
    message: error.message,
    original: error,
  };
}

Usage Example

typescript
async function placeWagerSafely(
  betAddress: PublicKey,
  amount: number,
  side: Outcome
) {
  try {
    const txHash = await sdk.placeWagerV2({
      bet: betAddress,
      amount,
      side,
      signers: [wallet],
    });
    return { success: true, txHash };
  } catch (error) {
    const parsed = parseSDKError(error as Error);

    switch (parsed.type) {
      case "INSUFFICIENT_FUNDS":
        return { success: false, error: "Please add more USDC" };
      case "INVALID_STATE":
        return { success: false, error: "Bet is no longer accepting wagers" };
      case "NETWORK":
        return { success: false, error: "Please check your connection" };
      default:
        console.error("Unexpected error:", parsed.original);
        return { success: false, error: "Something went wrong" };
    }
  }
}

Program Error Codes

Common Anchor program error codes:

CodeDescription
0x0Generic error
0x1Insufficient funds
0x64(100) Account already initialized
0x65(101) Invalid state transition
0x66(102) Unauthorized
0x67(103) Invalid argument

Validation Before Sending

Validate conditions before sending transactions:

typescript
async function validateWager(
  betAddress: PublicKey,
  amount: number
): Promise<{ valid: boolean; error?: string }> {
  // Check bet status
  const bet = await sdk.accounts.betV2.single(betAddress);
  if (bet.status !== MarketStatus.Pending) {
    return { valid: false, error: "Bet is not accepting wagers" };
  }

  // Check user balance
  const tokenAccount = await getAccount(connection, userTokenAddress);
  const balance = Number(tokenAccount.amount) / 1e6;
  if (balance < amount) {
    return { valid: false, error: `Insufficient balance: ${balance} USDC` };
  }

  // Check user has program account
  try {
    await sdk.accounts.user.single(userAddress);
  } catch {
    return { valid: false, error: "Please create a program user first" };
  }

  return { valid: true };
}

Logging Errors

Implement proper error logging:

typescript
function logError(context: string, error: Error) {
  const parsed = parseSDKError(error);

  console.error({
    context,
    type: parsed.type,
    message: parsed.message,
    stack: error.stack,
    timestamp: new Date().toISOString(),
  });

  // Send to error tracking service
  // Sentry.captureException(error, { extra: { context, parsed } });
}