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 typedQubicErrorsubclass on bad input. Use these at trust boundaries (form submissions, API responses). - Guards (
isIdentity,isTxHash, …) returntrueorfalsewithout throwing. Use these for filtering or conditional logic on untrusted data.
Installation
bun add @qubic.org/typesnpm install @qubic.org/typespnpm add @qubic.org/typesAPI reference
| Page | What's covered |
|---|---|
| Branded types | Identity, Seed, TxHash, Base64 — constructors and guards |
| Constants | PROTOCOL, CONTRACT_INDEX, LOG_TYPE |
| Errors | QubicError 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 hereFilter 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)
}