Assets
The Asset class — listing a network's tokens, the live network.assets collection, editing and persisting tokens with asset.save() and workspace.updateAsset, adding tokens with addAsset, and the AssetInitObject, AssetUpdate, AssetAddInit, AssetBalance, AssetCollection and NetworkAssetCollection types.
An Asset is token metadata bound to a Network: its id, symbol, name, decimals, and contract address. Every workspace is seeded with a curated set of built-in tokens and lets you import your own. This page covers constructing an Asset, listing a network's tokens, the live network.assets collection, adding, editing, persisting, removing and reordering tokens, and the related types.
The Asset class
Asset is a value export of wative-core. Construct one either from an init object or from positional arguments.
import { Asset, Network } from "wative-core";
// Object form (preferred).
const pepe = new Asset({
id: 200,
symbol: "PEPE",
name: "Pepe",
decimals: 18,
contractAddress: "0x6982508145454Ce325dDbE47a25d4ec3d2311933",
network: Network.Ethereum,
});
// Positional form: (id, symbol, name, decimals, network, contractAddress?)
const same = new Asset(200, "PEPE", "Pepe", 18, Network.Ethereum, "0x6982508145454Ce325dDbE47a25d4ec3d2311933");AssetInitObject
The object accepted by the constructor.
| Field | Type | Required | Notes |
|---|---|---|---|
id | AssetId | yes | A non-negative integer. AssetId is a branded number. |
symbol | string | yes | 1–32 characters. |
name | string | yes | 1–128 characters. |
decimals | number | yes | Token decimals. |
network | Network | yes | Must be a Network instance. |
contractAddress | string | null | no | null for a native gas coin. Defaults to null. |
displayDecimals | number | no | Defaults to decimals; must be <= decimals. |
displayStatus | boolean | no | Defaults to true. |
On an EVM network the contractAddress must already be a valid EIP-55 checksummed address (all-lowercase and all-uppercase are also accepted as the no-checksum forms). A mixed-case address whose checksum fails is rejected rather than repaired — a single mistyped nibble would otherwise silently point the wallet at a different contract. An address on the wrong shape for the network's vm (EVM vs. SVM) also throws PARAMETER_ERROR.
Instance properties
| Property | Type | Notes |
|---|---|---|
id | AssetId | readonly |
contractAddress | string | null | readonly; canonicalized on construction (EIP-55 for EVM). null for a native coin. |
native | boolean | readonly; derived. true when the address is null or the network's native placeholder. |
symbol | string | |
name | string | |
decimals | number | |
displayDecimals | number | Defaults to decimals. |
displayStatus | boolean | Defaults to true. |
network | Network | readonly |
isBuiltin | boolean | readonly; derived. true for a built-in token — it cannot be dropped and only displayStatus / displayDecimals are editable. |
Identity (id, contractAddress, native, network) is fixed for the life of the token; the remaining fields are editable subject to the token's tier — a built-in token (asset.isBuiltin === true) allows only its display fields — see Which fields are editable.
Instance methods
| Method | Returns | Notes |
|---|---|---|
save() | Promise<Asset> | Persists edits to this token's editable fields and returns the refreshed asset. Rejects UNSUPPORTED_OP on an Asset not obtained from a workspace. See Editing a token. |
Listing a network's tokens — workspace.assets(network)
assets(network: NetworkLike): Promise<readonly Asset[]>network is a NetworkLike: either a Network instance or a network slug string (e.g. "sepolia", "arbitrum-sepolia"). The result is the built-in tokens for that network plus any you have imported.
import { Workspace, Network } from "wative-core";
const ws = await Workspace.open({ path: "./wallet", password: "wsp-pwd" });
// By Network instance…
const eth = await ws.assets(Network.Ethereum);
eth.map((a) => a.symbol); // ["ETH", "USDC", "USDT", "WETH"]
// …or by network slug.
const sepolia = await ws.assets("sepolia"); // length 1 — [ETH]
const arb = await ws.assets("arbitrum-sepolia");
const usdc = arb.find((a) => a.symbol === "USDC");
usdc?.decimals; // 6
usdc?.native; // false
usdc?.contractAddress; // "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d"The live token list — network.assets
On a network obtained from a workspace — ws.networks.bySlug("ethereum"), or any entry of ws.networks — network.assets is a live NetworkAssetCollection: a read-only array of that network's tokens with a byId lookup and four mutators that add, edit, remove and reorder tokens and persist the change. This is the primary way to manage a network's tokens.
interface NetworkAssetCollection extends AssetCollection {
add(init: Omit<AssetAddInit, "network">): Promise<Asset>;
update(id: AssetId, patch: AssetUpdate): Promise<Asset>;
drop(id: AssetId): Promise<void>;
move(id: AssetId, toIndex: number): Promise<void>;
}const eth = ws.networks.bySlug("ethereum");
// Add a token — the library allocates its id.
const pepe = await eth.assets.add({
symbol: "PEPE",
name: "Pepe",
decimals: 18,
contractAddress: "0x6982508145454Ce325dDbE47a25d4ec3d2311933",
});
eth.assets.byId(pepe.id)?.symbol; // "PEPE"
await eth.assets.update(pepe.id, { displayStatus: false });
await eth.assets.move(pepe.id, 0); // reorder within the network
await eth.assets.drop(pepe.id);
// It is still an array.
eth.assets.length;
eth.assets.map((a) => a.symbol);update and drop are scoped to that network — an id belonging to a different network rejects with RECORD_NOT_FOUND.
A standalone Network — a bare Network.Ethereum, or a new Network(...) not added to a workspace — exposes network.assets as a read-only template: it still iterates and supports byId, but add, update, drop and move reject with UNSUPPORTED_OP. Obtain the network from a workspace to edit its tokens.
Adding a custom token — addAsset
addAsset(init: AssetAddInit): Promise<Asset>Imports a user token from its details; the library allocates the token's id. The write is persisted immediately and the in-memory list rolls back if persistence fails; the imported Asset is returned. network.assets.add(init) is the same call with the network taken from the collection.
const pepe = await ws.addAsset({
network: Network.Ethereum,
symbol: "PEPE",
name: "Pepe",
decimals: 18,
contractAddress: "0x6982508145454Ce325dDbE47a25d4ec3d2311933",
});
pepe.id; // a library-allocated id (user ids start at 1000)
const eth = await ws.assets(Network.Ethereum);
eth.length; // 5 — the four built-ins plus PEPEAssetAddInit
The details accepted by addAsset. The caller never supplies an id — the library allocates it.
| Field | Type | Required | Notes |
|---|---|---|---|
network | NetworkLike | yes | The network to add the token to. Omitted when calling network.assets.add. |
symbol | string | yes | 1–32 characters. |
name | string | yes | 1–128 characters. |
decimals | number | yes | Token decimals. |
contractAddress | string | null | no | null for a native coin. Defaults to null. |
displayDecimals | number | no | Defaults to decimals; must be <= decimals. |
displayStatus | boolean | no | Defaults to true. |
addAsset throws a WativeError in these cases:
code | When |
|---|---|
PARAMETER_ERROR | The argument is not an Asset or an asset-init object. |
DUPLICATE_IDENTITY | The (network, contractAddress) pair is already present (including trying to claim the network's native slot). |
UNSUPPORTED_NETWORK | The network is not registered with this workspace. |
WORKSPACE_LOCKED | The workspace is locked — addAsset requires it unlocked. |
addAsset also accepts a pre-built Asset with a caller-chosen id — addAsset(asset: Asset) — but that form is deprecated and kept only for backward compatibility. Prefer the details form above (or network.assets.add), which lets the library own id allocation.
Address collisions are rejected
A token's (network, contractAddress) must be unique within the workspace, and the built-in tokens already occupy their addresses — re-using one rejects with DUPLICATE_IDENTITY. Because the library allocates ids, a caller can no longer collide on an id.
// This address is already the built-in USDC.
await ws.addAsset({
network: Network.Ethereum,
symbol: "USDC2",
name: "Other USDC",
decimals: 6,
contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
}); // rejects: WativeError { code: "DUPLICATE_IDENTITY" }Editing a token — updateAsset and asset.save()
updateAsset(id: AssetId, patch: AssetUpdate): Promise<Asset>Edit a token either by id through the workspace, or by mutating an Asset you already hold and calling save(). Both persist the change and return the refreshed Asset.
// By id, through the workspace (or network.assets.update(id, patch)).
await ws.updateAsset(pepe.id, { displayStatus: false, name: "Pepe Token" });
// Or edit a held Asset and save it.
const eth = await ws.assets(Network.Ethereum);
const token = eth.find((a) => a.id === pepe.id);
token.displayStatus = true;
await token.save();save() rejects with UNSUPPORTED_OP on an Asset that did not come from the workspace (one you constructed yourself).
Which fields are editable
A token's identity — id, contractAddress, native, network — never changes. The rest depends on the token:
| Token | Editable fields |
|---|---|
| User-added | symbol, name, decimals, displayDecimals, displayStatus |
| Built-in | displayStatus, displayDecimals only |
A write to a field the token does not allow — a built-in token's symbol, say — is refused with PARAMETER_ERROR; it is never silently dropped. An unknown id is RECORD_NOT_FOUND.
AssetUpdate
The patch accepted by updateAsset (and by network.assets.update and asset.save()). Every field is optional; identity fields are not accepted.
| Field | Type | Notes |
|---|---|---|
symbol | string | User tokens only. |
name | string | User tokens only. |
decimals | number | User tokens only. |
displayDecimals | number | Must be <= decimals. |
displayStatus | boolean |
Dropping a token — dropAsset
dropAsset(id: AssetId): Promise<void>Removes a user-imported token by id. Like addAsset, the write is persisted and rolls back on failure.
workspace.dropAsset is deprecated — prefer network.assets.drop(id) on a network obtained from the workspace. It still works and behaves identically.
await ws.addAsset(myToken); // id 300
await ws.dropAsset(300);
const eth = await ws.assets(Network.Ethereum);
eth.some((a) => a.id === 300); // falsecode | When |
|---|---|
UNSUPPORTED_OP | The id belongs to a built-in token — those are protected. |
RECORD_NOT_FOUND | No user asset with that id exists. |
WORKSPACE_LOCKED | The workspace is locked — dropAsset requires it unlocked. |
await ws.dropAsset(1); // rejects: WativeError { code: "UNSUPPORTED_OP" }Reordering tokens — moveAsset
moveAsset(network: NetworkLike, id: AssetId, toIndex: number): Promise<void>Changes the persisted order of one network's tokens — the order assets(network) returns. toIndex is the token's final 0-based position among that network's assets; moving it to its current index is a no-op. The write is persisted and rolls back on failure, and other networks' order is untouched.
workspace.moveAsset is deprecated — prefer network.assets.move(id, toIndex) on a network obtained from the workspace. It still works and behaves identically.
const eth = await ws.assets(Network.Ethereum);
await ws.moveAsset(Network.Ethereum, eth.at(-1).id, 0); // last → firstcode | When |
|---|---|
PARAMETER_ERROR | toIndex is out of range (negative, non-integer, or past the last asset). |
RECORD_NOT_FOUND | No asset with that id exists on the given network. |
WORKSPACE_LOCKED | The workspace is locked — moveAsset requires it unlocked. |
Finding an asset
The workspace's general filter helper resolves a single Asset by symbol or contract address (returning null when nothing matches):
const bySymbol = ws.filter("USDC", "Asset");
bySymbol?.symbol; // "USDC"
const byAddr = ws.filter("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "Asset");
byAddr?.symbol; // "USDC"AssetBalance
A read of an on-chain balance for one asset. Balances live on an address, not on the Asset itself.
| Field | Type | Description |
|---|---|---|
assetId | AssetId | Which asset the balance is for. |
raw | bigint | The balance in base units (unscaled by decimals). |
decimals | number | The asset's decimals, for formatting raw. |
displayStatus | boolean | Whether the asset is shown in balance listings. |
fetchedAt | number | When the balance was read. |
readError? | string | Optional. Present only when this asset's balance could not be read (a delisted/not-a-token contract, a malformed node response, or a transport failure that outlasted failover). When set, raw is 0n and is not a real balance; the other assets' balances are still returned. |
All fields are readonly; readError is optional and appears only on a failed read.
AssetCollection
interface AssetCollection extends ReadonlyArray<Asset> {
byId(id: AssetId): Asset | null;
}A read-only array of Asset with an added byId lookup. It is the type of an address's .assets property and the base of NetworkAssetCollection.
workspace.assets(network) returns a plain readonly Asset[], not an AssetCollection — use Array.prototype.find on it. byId is available on an address's .assets and on network.assets (a NetworkAssetCollection).
NetworkAssetCollection
interface NetworkAssetCollection extends AssetCollection {
add(init: Omit<AssetAddInit, "network">): Promise<Asset>;
update(id: AssetId, patch: AssetUpdate): Promise<Asset>;
drop(id: AssetId): Promise<void>;
move(id: AssetId, toIndex: number): Promise<void>;
}The type of network.assets: an AssetCollection plus the four mutators. On a workspace-attached network they add, edit, remove and reorder that network's tokens; on a standalone Network they reject with UNSUPPORTED_OP.