Why Branded Types Exist
TypeScript uses structural typing — two types with the same underlying representation are treated as identical by the compiler. This means a rawbigint that represents an encrypted balance ciphertext is structurally the same as one that represents a Poseidon encryption key, or a U64 token amount. Without extra guardrails, you can pass the wrong value and TypeScript will not warn you.
The SDK uses branded types (also called nominal types) to prevent this class of silent bug. Every cryptographic and numeric value in the SDK carries a phantom brand that makes it distinct from every other value with the same base type — at compile time, with zero runtime overhead.
The Type Hierarchy
The SDK’s branded types form a hierarchy. A more specific type is always assignable to its parent, but a parent is never assignable to a more specific child without an explicit helper call. Integer types (base:bigint):
U64— 64-bit unsigned integer (0to2^64 - 1). Used for token amounts and instruction parameters.U128— 128-bit unsigned integer. Used for instruction seeds and generation indices.U256— 256-bit unsigned integer. Used for UTXO commitment preimages and random generation seeds.
bigint, extend U256):
Bn254FieldElement— BN254 scalar field element. Strict upper bound: the BN254 field prime.Curve25519FieldElement— Curve25519 field element. Strict upper bound:2^255 - 19.
Bn254FieldElement):
PoseidonKey— Encryption key for the Poseidon cipher scheme. Derived from the user’s master seed.
Curve25519FieldElement):
RcPlaintext— An unencrypted balance value.RcCiphertext— An encrypted balance value stored on-chain.RcKey— A session encryption key derived from the Poseidon key and account nonce.RcEncryptionNonce— The monotonically increasing counter (derived fromgenerationIndex) that ensures each encryption produces a distinct ciphertext.
Uint8Array or bigint):
OptionalData32— A 32-byte opaque payload stored alongside deposits and claims.MicroLamportsPerAcu— Priority fee in micro-lamports per Arcium Computation Unit.
Uint8Array):
X25519PublicKey,X25519PrivateKey,SharedSecret— 32-byte X25519 values.- Sized little-endian/big-endian variants (
U64LeBytes,U256BeBytes, etc.) for serialisation.
The create* Helper Functions
Raw bigint or Uint8Array values you read from on-chain accounts, compute locally, or receive from external sources are unbranded. Before passing them to any SDK function that expects a branded type, run them through the corresponding create* helper.
Each helper:
- Validates the value at runtime (range check, type check, length check).
- Brands the value — returns it typed as the specific branded type.
- Throws a descriptive error (never silently returns
undefined) if validation fails.
@umbra-privacy/sdk:
Mathematics Helpers
These are the helpers you will use most often. Token amounts, offsets, and protocol-level integers all go through here.Field Element Helpers
Rescue Cipher Helpers
The Rescue Cipher encrypts on-chain encrypted balances. Keeping plaintexts, ciphertexts, keys, and nonces as distinct types prevents the most dangerous class of encryption bug — passing a ciphertext where the decryptor expects a key.Protocol Helpers
Limb Helper
Common Patterns
Converting a user-supplied number to U64
User-facing forms and external APIs often produce plainnumber or string. Always validate before passing to the SDK:
Reading on-chain data and re-branding
Account data fetched via RPC arrives as rawbigint. Validate before any cryptographic operation:
The assert* vs create* distinction
The SDK exports both families. Use create* (not assert*) in expression contexts — assert* returns void and forces a two-step pattern:
Error handling
All helpers throw a typed error on failure. Catch at the boundary where you receive external input:Passing branded values through the SDK
Once a value is branded, you can pass it directly to any SDK function that accepts that type — no additional wrapping needed:What the SDK Does Not Accept
You cannot pass a plainbigint where a branded type is required — the TypeScript compiler will reject the call:
create* helpers perform is not optional — it is the mechanism that guarantees protocol invariants hold before values reach the on-chain program.