QubicTypeScript

Branded types

Identity, Seed, TxHash, Base64, HexPublicKey — compile-time branded strings that prevent mixing up incompatible values.

TypeScript treats all strings as structurally equivalent. Without branding, the compiler happily accepts buildTransaction({ destination: seedString }) — the wrong type in the wrong slot, and there's no error until runtime.

Branded types fix this by attaching a phantom property that only exists in the type system:

type Identity = string & { readonly __brand: "Identity" }

The brand has zero runtime cost. It only affects the compiler. The value itself is still a plain string — but now Seed and Identity are distinct types, and swapping them is a compile error.

The bug brands prevent

Without brands:

// Both are `string` — compiler sees no problem
async function transfer(from: string, to: string, amount: bigint) { ... }

const seed = "aaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
const identity = "CFBMEMZOIDEXQAUXYYSZIURADQLAPWPMNJXQSNVQZAHYVOPYUKKJBJUCTVJL"

// Arguments swapped — compiles fine, fails at runtime or worse: silently corrupts the transaction
await transfer(identity, seed, 1000n)

With brands:

async function transfer(from: Seed, to: Identity, amount: bigint) { ... }

// Type error: Argument of type 'Identity' is not assignable to parameter of type 'Seed'
await transfer(identity, seed, 1000n)

The compiler catches the swap before the code ever runs.

Types

TypeFormatDescription
Identity60 uppercase charsQubic address derived from a public key
Seed55 lowercase charsPrivate key material — treat like a password
TxHash60 lowercase charsTransaction identifier
Base64standard base64Encoded transaction payload for RPC broadcast
HexPublicKey64 hex chars32-byte FourQ public key as a lowercase hex string

Getting branded values

You can't just write const id: Identity = someString — that's a type error. Use a constructor or type guard to validate and produce a branded value:

import { toIdentity, toSeed, toTxHash, toBase64 } from "@qubic.org/types"
import { isIdentity } from "@qubic.org/types"

// Constructor: validates and brands, throws on bad input
const id = toIdentity(req.body.destination)
const seed = toSeed(process.env.WALLET_SEED!)

// Guard: validates and narrows, returns boolean
if (isIdentity(raw)) {
  // raw: Identity inside this block
}

For values you know are valid at compile time (e.g. hardcoded test fixtures), use an assertion:

const testIdentity = "CFBMEMZOIDEXQAUXYYSZIURADQLAPWPMNJXQSNVQZAHYVOPYUKKJBJUCTVJL" as Identity

This skips validation entirely — only use it where you control the value.

HexPublicKey

HexPublicKey is distinct from Identity. Both represent a public key, but in different encodings:

  • Identity — the Qubic-specific 60-char base-26 encoding, used in wallet addresses and transfer destinations
  • HexPublicKey — 64 lowercase hex characters, the raw bytes of the 32-byte FourQ public key as hex

They are not interchangeable. Passing a HexPublicKey where an Identity is expected is a compile error.

On this page