WorkspaceWorkspace

Workspace

The Workspace class — the top-level encrypted container holding accounts, networks, assets, config and a logger — with open/unlock/lock/close and the locked/closed lifecycle.

A Workspace is the top-level encrypted container. Everything else in the library hangs off it: the accounts it holds, the networks they transact on, the imported assets, the workspace-wide config, and a logger. You obtain one with Workspace.open() and drive it through a small session lifecycle — unlock, lock, close.

open-and-use.ts
import { Workspace } from "wative-core";

const ws = await Workspace.open({ path: "./my-wallet", password: "wsp-pwd" });

const account = await ws.accounts.create("Trading Desk", "wsp-pwd", MNEMONIC);
console.log(ws.accounts.length); // 1

await ws.lock();

Lifecycle

A workspace is always in one of three states, exposed by two boolean getters:

GetterTypeMeaning
ws.lockedbooleantrue when there is no live session. A freshly opened no-password workspace is locked; so is one after lock().
ws.closedbooleantrue after close(). Terminal — the backing store has been released and cannot be reopened. Every closed workspace also reads as locked, so code that only checks locked still behaves.

The transitions:

Open — Workspace.open(...) returns an unlocked workspace when a password is supplied, or a locked one when it is omitted.
Unlock — await ws.unlock(password) starts a live session on a locked (but not closed) workspace.
Lock — await ws.lock() ends the session and wipes in-memory secrets. The workspace can be unlocked again.
Close — await ws.close() ends the session and releases the provider's store. The workspace is finished.

ws.accounts, ws.networks, ws.logger

These are the primary handles on a live workspace. ws.config and ws.logger require an unlocked session — reading either while locked throws WORKSPACE_LOCKED. The others — ws.accounts, ws.networks, ws.rootDescriptor and ws.damagedAccountSlugs — read even while locked (the collections simply read as empty until unlock).

AccessorTypeNotes
ws.accountsAccountCollectionThe account registry. See Accounts.
ws.networksNetworkCollectionBuilt-in plus user-added networks. See Networks.
ws.loggerLoggerWorkspace-scoped logger, materialized on first read after unlock.
ws.configWorkspaceConfigA frozen snapshot of workspace settings — see Workspace Config.
ws.rootDescriptorstringA human-readable descriptor of where this workspace lives.
ws.damagedAccountSlugsReadonlyArray<string>Slugs of account records present on disk that could not be opened (read-only).

ws.accounts and ws.networks are read-only accessors over private backing fields. There is no setter: ws.accounts = [] throws a TypeError in strict mode rather than silently replacing the collection.

The logger auto-persists its configuration. Mutating it — setLevel, setSinks — writes the new LoggerConfig back through the provider, so it survives a lock/reopen cycle:

logger-persists.ts
await ws.logger.setLevel("debug");

await ws.lock();
const reopened = await Workspace.open({ path: "./my-wallet", password: "wsp-pwd" });
reopened.logger.config.minLevel; // "debug"

Stop emission entirely with ws.logger.setSinks([]).

lock() vs close()

Both end the current session; the difference is what happens to the storage backend.

lock()close()
Wipes in-memory secrets (password, mnemonics, keys)yesyes
Closes logger sinksyesyes
Releases the provider claimyesyes
Reopenable with unlock(password)yesno
Calls Provider.close() (releases the store)noyes
State afterwardlocked === trueclosed === true

close() is idempotent and single-flight — concurrent calls share one teardown and all resolve when it is done. Calling lock() on an already-closed workspace is a no-op, not an error.

Session keys

A workspace on a v3 provider (IdbProviderV3 in the browser, or HybridProviderV3 in Node) can hand out a 32-byte session key that reopens it later without the password. It is the resume path for a session that must survive the password leaving memory — for example restoring an unlocked wallet after a browser extension's background page is evicted, without re-prompting the user.

session-key.ts
// `ws` is unlocked and backed by a v3 provider.
const key = ws.exportSessionKey(); // Uint8Array(32) — store it somewhere memory-only

// Later, with no password available — reopen the container from the key alone…
const resumed = await Workspace.openWithSessionKey(await IdbProviderV3.create("my-wallet"), key);

// …or resume a workspace handle you still hold:
await ws.unlockWithSessionKey(key);
MethodSignatureNotes
ws.exportSessionKey()(): Uint8ArrayA copy of the container key. Requires an unlocked session (throws WORKSPACE_LOCKED when locked) and a v3 container (throws UNSUPPORTED_OP on v2 — it has no single key).
Workspace.openWithSessionKey(provider, key)static (provider: Provider, key: Uint8Array): Promise<Workspace>Opens an existing container from the key alone.
ws.unlockWithSessionKey(key)(key: Uint8Array): Promise<this>Resumes a locked (not closed) workspace from the key; idempotent if already unlocked.

Argon2 does not re-run on the key path. A wrong key is refused with BAD_PASSWORD and a non-32-byte key with PARAMETER_ERROR; either way the workspace stays locked, never half-open. The key opens the same records the password does, so guard it as carefully as the unlocked vault — it is not the password and cannot be reversed to it, but anything holding it can read every record. An account that carries its own password still needs that password after a key resume.

Re-read handles after unlock

lock() replaces the account and network collections with fresh, empty ones and wipes the previous session's state. Any reference you captured before locking is now stale, and the same is true across an idle-lock followed by unlock() — hydration builds fresh instances. Always re-read the handle from the workspace after a session boundary rather than reusing a cached reference.

reopen-and-reread.ts
let ws = await Workspace.open({ path: "./my-wallet", password: "wsp-pwd" });
const hd = await ws.accounts.create("Desk One", "wsp-pwd", MNEMONIC);

await ws.lock();
ws = await Workspace.open({ path: "./my-wallet", password: "wsp-pwd" });

// Re-read the account off the reopened workspace — `hd` above is stale.
const reopened = ws.accounts.bySlug(hd.slug);

What a stale handle does if you do reuse it is deliberately loud, never silent:

  • A cached ws.networks collection refuses mutations with UNSUPPORTED_OP ("this networks collection is no longer live"). Re-read ws.networks.
  • A cached account or address handle refuses operations that need key material with ACCOUNT_LOCKED, even while the reopened workspace is unlocked and holds a fresh twin.

Last updated on