Skip to main content

API Conventions

Consistent API design across all modules.

Naming Conventions

Constructors

PatternUsageExample
Type(value)Primary constructorAddress("0x...")
Type.from(value)Explicit variantAddress.from("0x...")
Type.fromX(value)From specific typeAddress.fromHex("0x...")
// ✅ Preferred - direct call
const addr = Address("0x742d35Cc6634C0532925a3b844Bc9e7595f251e3");

// ✅ Also valid - explicit
const addr = Address.from("0x742d35Cc6634C0532925a3b844Bc9e7595f251e3");

// ✅ Specific format
const addr = Address.fromHex("0x742d35Cc6634C0532925a3b844Bc9e7595f251e3");
const addr = Address.fromBytes(bytes);
const addr = Address.fromPublicKey(pubkey);

Converters

PatternUsageExample
Type.toX(value)Convert to XAddress.toHex(addr)
Type.asX(value)View as X (no copy)Uint256.asBigInt(uint)
// Convert (may allocate)
const hex = Address.toHex(addr);
const bytes = Address.toBytes(addr);
const checksummed = Address.toChecksummed(addr);

// View (no allocation, same underlying data)
const bigint = Uint256.asBigInt(uint);

Predicates

PatternUsageReturns
Type.is(value)Type guardvalue is Type
Type.isValid(value)Validationboolean
Type.isX(value)Specific checkboolean
// Type guard - narrows type
if (Address.is(value)) {
  // value is Address here
}

// Validation - just boolean
if (Address.isValid(input)) {
  const addr = Address(input);  // Safe to construct
}

// Specific checks
if (Address.isChecksum(addr)) { ... }
if (Address.isZero(addr)) { ... }

Comparisons

PatternUsage
Type.equals(a, b)Equality check
Type.compare(a, b)Ordering (-1, 0, 1)
Type.lt(a, b)Less than
Type.gt(a, b)Greater than
// Equality
Address.equals(a, b);  // true/false

// Ordering (for sortable types)
Uint256.compare(a, b);  // -1, 0, or 1
Uint256.lt(a, b);       // a < b
Uint256.gt(a, b);       // a > b

Function Signatures

Input Flexibility

Public functions accept flexible inputs:
// Public API - accepts various inputs
export function toHex(value: AddressInput): Hex {
  return _toHex(from(value));  // Converts internally
}

// AddressInput = Address | Hex | Uint8Array | `0x${string}`

Internal Functions

Internal functions require exact types:
// Internal - requires branded type
export function _toHex(address: Address): Hex {
  // No conversion needed
}

Naming Convention

// Public wrapper (flexible input)
export function toHex(value: AddressInput): Hex;

// Internal (strict input, underscore prefix)
export function _toHex(address: Address): Hex;

Error Handling

Error Types

Each module defines its own error class:
// primitives/Address/errors.ts
export class InvalidAddressError extends Error {
  readonly name = "InvalidAddressError";

  constructor(value: unknown) {
    super(`Invalid address: ${formatValue(value)}`);
  }
}

export class ChecksumMismatchError extends Error {
  readonly name = "ChecksumMismatchError";

  constructor(expected: string, actual: string) {
    super(`Checksum mismatch: expected ${expected}, got ${actual}`);
  }
}

When to Throw

// ✅ Throw on invalid input
export function fromHex(hex: string): Address {
  if (!isValidHex(hex)) {
    throw new InvalidAddressError(hex);
  }
  // ...
}

// ✅ Return boolean for validation
export function isValid(value: unknown): boolean {
  // Never throws
  return typeof value === "string" && isValidHex(value);
}

// ✅ Return null for "try" variants
export function tryFromHex(hex: string): Address | null {
  if (!isValid(hex)) return null;
  return fromHex(hex);
}

Error Messages

// ✅ Clear, actionable message
throw new InvalidAddressError("0x123");
// "Invalid address: 0x123"

// ✅ Truncate long values
throw new InvalidAddressError(veryLongString);
// "Invalid address: 0x742d35Cc6634C0532925a3b8... (truncated)"

// ❌ Vague message
throw new Error("Invalid input");

// ❌ Technical jargon
throw new Error("EIP-55 checksum validation failed for mixed-case input");

Return Types

Branded Types

Always return branded types, not plain arrays:
// ✅ Returns branded type
export function fromHex(hex: string): Address {
  // ...
  return bytes as Address;
}

// ❌ Returns plain Uint8Array
export function fromHex(hex: string): Uint8Array {
  // Loses type safety
}

Immutability

Returned values should be treated as immutable:
const addr = Address.fromHex("0x...");

// ❌ Don't mutate
addr[0] = 0x00;  // Type system doesn't prevent this, but don't do it

// ✅ Create new if needed
const modified = new Uint8Array(addr);
modified[0] = 0x00;

Documentation

JSDoc for .js Files

// toHex.js

/**
 * Convert address to checksummed hex string.
 *
 * @param {import('./AddressType.js').Address} address - The address to convert
 * @returns {string} EIP-55 checksummed hex string
 * @throws {TypeError} If address is not a valid Address
 *
 * @example
 * const hex = toHex(address);
 * // "0x742d35Cc6634C0532925a3b844Bc9e7595f251e3"
 */
export function toHex(address) {
  // ...
}

TypeScript Types in index.ts

// index.ts

/**
 * Convert address to checksummed hex string
 *
 * @example
 * ```ts
 * import * as Address from "@voltaire/primitives/Address";
 *
 * const hex = Address.toHex("0x742d35cc6634c0532925a3b844bc9e7595f251e3");
 * // "0x742d35Cc6634C0532925a3b844Bc9e7595f251e3"
 * ```
 */
export function toHex(value: AddressInput): string {
  return _toHex(from(value));
}

Constants

Named Constants

// constants.ts
export const ZERO_ADDRESS = Address(new Uint8Array(20));
export const MAX_ADDRESS = Address(new Uint8Array(20).fill(0xff));

// Precompile addresses
export const ECRECOVER_ADDRESS = Address.fromHex("0x0000000000000000000000000000000000000001");
export const SHA256_ADDRESS = Address.fromHex("0x0000000000000000000000000000000000000002");

Export in Index

// index.ts
export { ZERO_ADDRESS, MAX_ADDRESS } from "./constants.js";

Module Structure

Standard Exports

Every primitive module exports:
// Type
export type { Address } from "./AddressType.js";

// Constructors
export { from } from "./from.js";
export { from as Address } from "./from.js";
export { fromHex } from "./fromHex.js";
export { fromBytes } from "./fromBytes.js";

// Internal methods
export { toHex as _toHex } from "./toHex.js";
export { equals as _equals } from "./equals.js";

// Public wrappers
export function toHex(value: AddressInput): string { ... }
export function equals(a: AddressInput, b: AddressInput): boolean { ... }

// Predicates
export { is } from "./is.js";
export { isValid } from "./isValid.js";

// Constants
export { ZERO_ADDRESS } from "./constants.js";

Namespace Usage

// User imports as namespace
import * as Address from "@voltaire/primitives/Address";

// All functions available
Address.fromHex("0x...");
Address.toHex(addr);
Address.equals(a, b);
Address.isValid(input);
Address.ZERO_ADDRESS;

Async Conventions

Sync by Default

Most functions are synchronous:
// ✅ Sync - no I/O needed
export function hash(data: Uint8Array): Uint8Array { ... }
export function verify(sig: Signature): boolean { ... }

Async When Required

Use async only for I/O:
// ✅ Async - requires network
export async function fetchAddress(name: string): Promise<Address> {
  const response = await fetch(`https://api.ens.domains/${name}`);
  // ...
}

// ✅ Async - WASM loading
export async function loadWasm(): Promise<void> {
  await WebAssembly.instantiate(wasmBytes);
}

Naming for Async

// Sync version
export function hash(data: Uint8Array): Uint8Array;

// Async version (when both exist)
export async function hashAsync(data: Uint8Array): Promise<Uint8Array>;