Skip to content
markpaper

src/orders/errors.ts

v0.3.0 · 2.6 KB

Download file
// Errors thrown by the orders module for invalid inputs and unusable responses.
//
// Market conditions (no mid price, size below the minimum, an exchange rejection, an unknown
// transport outcome) are NOT thrown: they come back as results, so one bad order never aborts the
// rest of a batch (a throw would also drop the reduce-only closes of that batch).

/** Machine-readable error code. */
export type HlOrderErrorCode =
  /** A caller-supplied argument is malformed or contradictory; nothing was sent. */
  | 'INVALID_ARGUMENT'
  /** An /info answer has an unexpected shape; it must not be read as "flat" or "no orders". */
  | 'INVALID_RESPONSE'
  /** The agent address is not in `extraAgents` of the master account. */
  | 'AGENT_NOT_LISTED'
  /** The agent is listed but its `validUntil` is in the past. */
  | 'AGENT_EXPIRED'
  /** `extraAgents` could not be read; approval is unproven. */
  | 'AGENT_QUERY_FAILED'
  /** A built order failed the final pre-send self-check (a bug, never expected). */
  | 'INVALID_ORDER';

/** Error thrown by the orders module. */
export class HlOrderError extends Error {
  override readonly name = 'HlOrderError';
  readonly code: HlOrderErrorCode;

  constructor(code: HlOrderErrorCode, message: string, options?: { cause?: unknown }) {
    super(message, options);
    this.code = code;
  }
}

/** @internal Throws `INVALID_ARGUMENT`. */
export function invalidArgument(message: string): never {
  throw new HlOrderError('INVALID_ARGUMENT', message);
}

/** @internal Short, log-safe description of an unexpected value. */
export function describeValue(value: unknown): string {
  if (value === null) return 'null';
  if (Array.isArray(value)) return 'array';
  if (typeof value === 'string') return JSON.stringify(value.length > 40 ? `${value.slice(0, 40)}...` : value);
  if (typeof value === 'object') return 'object';
  return String(value);
}

/** @internal Message of an unknown thrown value. */
export function messageOf(err: unknown): string {
  if (typeof err === 'object' && err !== null && typeof (err as { message?: unknown }).message === 'string') {
    return (err as { message: string }).message;
  }
  return String(err);
}

const ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;

/** @internal Validates a 0x + 40 hex address and lowercases it (HL compares addresses lowercase). */
export function normalizeAddress(value: unknown, field: string): `0x${string}` {
  if (typeof value !== 'string' || !ADDRESS_RE.test(value)) {
    invalidArgument(`Invalid ${field} ${describeValue(value)}: expected 0x + 40 hex characters`);
  }
  return value.toLowerCase() as `0x${string}`;
}
All files