QubicTypeScript
Packages

@qubic.org/types

Branded primitive types, runtime constructors, and validation guards for Qubic network values.

Introduction

The problem with plain strings

Qubic uses several string-shaped values that look similar but mean very different things:

  • Identity — 60-char uppercase public key string (e.g. CFBMEMZOIDEX...)
  • TxHash — 60-char hex transaction hash
  • Seed — 55-char lowercase private key seed
  • Base64 — base64-encoded binary payload

Without branding, nothing stops you from passing a Seed where an Identity is expected. The mistake compiles, ships, and shows up as a subtle bug in production.

Branded types to the rescue

@qubic.org/types attaches a phantom type brand to each:

type Identity = string & { readonly __brand: "Identity" }
type TxHash   = string & { readonly __brand: "TxHash" }
type Seed     = string & { readonly __brand: "Seed" }
type Base64   = string & { readonly __brand: "Base64" }

TypeScript rejects seed where Identity is expected — at zero runtime cost. The brands exist only at type-check time and are erased in the emitted JavaScript.

Constructor vs guard

  • Constructors (toIdentity, toTxHash, …) validate the format at runtime and throw a typed QubicError subclass on bad input. Use these at trust boundaries (form submissions, API responses).
  • Guards (isIdentity, isTxHash, …) return true or false without throwing. Use these for filtering or conditional logic on untrusted data.

Installation

bun add @qubic.org/types
npm install @qubic.org/types
pnpm add @qubic.org/types

API reference

PageWhat's covered
Branded typesIdentity, Seed, TxHash, Base64 — constructors and guards
ConstantsPROTOCOL, CONTRACT_INDEX, LOG_TYPE
ErrorsQubicError hierarchy — base class and subclasses

All exports

import {
  // Branded types (TypeScript types only, no runtime value)
  type Identity, type Seed, type TxHash, type Base64,

  // Constructors — validate, brand, and return
  toIdentity, toSeed, toTxHash, toBase64,

  // Guards — return boolean, never throw
  isIdentity, isSeed, isTxHash, isBase64,

  // Constants
  PROTOCOL, CONTRACT_INDEX, LOG_TYPE,

  // Error classes
  QubicError,
  QubicIdentityError,
  QubicSeedError,
  QubicHashError,
  QubicBase64Error,
} from "@qubic.org/types"

Examples

Validate user input at a form boundary

import { toIdentity, QubicIdentityError } from "@qubic.org/types"

function parseAddressInput(raw: string) {
  try {
    return toIdentity(raw.trim().toUpperCase())
  } catch (e) {
    if (e instanceof QubicIdentityError) {
      return { error: "Invalid Qubic address — must be 60 uppercase characters." }
    }
    throw e
  }
}

const result = parseAddressInput(formValues.destination)
if ("error" in result) showFieldError(result.error)
else submitTransfer(result) // result is typed as Identity here

Filter an untrusted list

import { isIdentity, isTxHash } from "@qubic.org/types"

// User might paste anything — filter safely without try/catch
const maybeIdentities = rawInputs.filter(isIdentity)
const maybeTxHashes   = rawInputs.filter(isTxHash)

Use named constants instead of magic numbers

import { CONTRACT_INDEX, LOG_TYPE } from "@qubic.org/types"

// Readable contract references
const contractIndex = CONTRACT_INDEX.QEARN // 9

// Readable log type checks
bob.subscribe("eventLogs", identity, (events) => {
  for (const event of events) {
    if (event.eventType === LOG_TYPE.QU_TRANSFER) {
      // handle transfer
    }
  }
})

Type narrowing in a union handler

import { type Identity, type TxHash, isIdentity } from "@qubic.org/types"

function handle(value: Identity | TxHash) {
  if (isIdentity(value)) {
    // value: Identity
    return lookupBalance(value)
  }
  // value: TxHash
  return lookupTransaction(value)
}

On this page