ReferenceBranded Types

Branded Types

The nominal branded primitives — Slug, EvmAddress, SvmAddress, ChainId, AssetId, ChainVM, VmToken, NetworkLike and Settling — and constructing them from plain values with an `as` cast.

Several of the library's identifiers are plain strings and numbers at runtime but carry a distinct type — a Slug is not interchangeable with any old string, and a ChainId is not any old number. This is nominal branding: a compile-time tag that stops you from passing, say, a raw address where a slug is expected, even though both are strings once the code runs.

You rarely construct these yourself — they come back already branded from the library (address.publicKey is an EvmAddress, network.chainId is a ChainId). You only need the as cast in the one direction the compiler cannot infer: handing a plain literal into an API that asks for a branded value.

What branding is

A branded type intersects the underlying primitive with a phantom marker field:

branding.ts
type Brand<T, B extends string> = T & { readonly __brand: B };

type Slug    = Brand<string, "Slug">;
type ChainId = Brand<number, "ChainId">;

The __brand field never exists at runtime — it is erased with all other types. Its only job is to make Slug and string structurally different to the type checker, so an accidental swap is a compile error rather than a silent bug.

The branded primitives

These five are true nominal brands over a single primitive.

TypeUnderlying primitivePurpose
SlugstringA validated, URL-safe identifier for a persisted record — an account, network, or asset key. Used as the lookup key in bySlug() and record storage.
EvmAddressstringA checksummed 0x EVM account address. What address.publicKey returns on an EVM address.
SvmAddressstringA base58 Solana account address. What address.publicKey returns on an SVM address.
ChainIdnumberAn EVM-style numeric chain id (for example 1 for Ethereum mainnet).
AssetIdnumberThe numeric id of a tracked asset within a workspace.

The utility types

The remaining four are exported alongside the brands but are plain type aliases — a union or an interface, not a Brand<T, B>.

TypeDefinitionPurpose
ChainVM"evm" | "svm" | "sui"The closed set of built-in virtual machines. "sui" is a reserved placeholder; its implementation is not shipped yet.
VmTokenChainVM | (string & {})The open vm token. The (string & {}) arm keeps evm / svm / sui autocompleting while still accepting any string a third party registers a dialect for. Used at construction and boundary sites.
NetworkLikeNetwork | ChainId | stringAnything that can identify a network — resolved internally to a Slug.
Settling<T>interface (see below)The live-async pattern returned where a caller wants a settling object rather than a raw Promise.

NetworkLike resolution

Wherever an API takes a NetworkLike, the value is resolved to a network slug by this order:

InputResolved by
a Networkits network.slug
a ChainId (number)looked up by chainId — workspace overrides, then built-ins
a hex string (e.g. "0x1", "0x156")parsed as a hex chain id, then looked up
a slug string (e.g. "ethereum", "base")treated as the slug directly

If nothing matches, the call throws WativeError("UNSUPPORTED_NETWORK", …).

Settling<T>

settling.ts
interface Settling<T> {
  readonly settled: boolean;
  readonly value: T | null;
  confirm(): Promise<T>;
  abort(): void;
}

Settling<T> is handed back by live-async methods such as address.refreshBalances(). Read value for the latest resolved value, await confirm() for the settled result, or abort() to stop. TransactionTracker is a richer Settling<TransactionReceipt>.

refresh.ts
const settling = evm.refreshBalances();      // Settling<readonly AssetBalance[]>
const balances = await settling.confirm();    // the settled value

Constructing a branded value with as

When you already hold a plain string or number and need to pass it where a branded type is expected, assert the brand with as. This is the idiom used throughout the test suite:

as-cast.ts
import { Network } from "wative-core";
import type { Slug, ChainId, AssetId } from "wative-core";

// A plain string literal where a Slug is expected.
const base = ws.networks.bySlug("base" as Slug);

// A numeric chain id where a ChainId is expected.
const net = new Network({
  slug: "my-testnet",
  name: "My Testnet",
  chainId: 7_654_321 as ChainId,
  rpcUrl: "https://rpc.example",
  nativeCurrency: { name: "Test", symbol: "TST", decimals: 18 },
  vm: "evm",
});

// A numeric asset id.
await ws.dropAsset(50_000 as AssetId);

A small helper reads well when you cast the same brand repeatedly:

helper.ts
const slug = (s: string): Slug => s as Slug;
ws.networks.bySlug(slug("arbitrum-sepolia"));

For VmToken, any string is assignable, but a Network whose vm is a token with no registered dialect is refused at construction — register a dialect first.

Last updated on