Account scoping
A Polyester user has one main account and any number of subaccounts. Almost every
authenticated read/write accepts an optional account field that says which one the call is
about:
await client.balances.list(); // the "active" account (see below)
await client.balances.list({ account: "main" }); // force the main account
await client.balances.list({ account: { subaccountId } }); // a specific subaccountThe default, "active", resolves through the client:
PolyesterBrowserClient— the account the user selected viaauth.switchAccount(...).PolyesterServerClient— the main account, unless you opt into the display session's active account withuseDisplaySessionActiveAccountAsDefault.PolyesterClient— the main account (no resolver).
account field expresses intent; the backend independently verifies the
authenticated caller may access that account.Subaccounts
Full lifecycle management via client.subaccounts:
const { subaccounts, totalCreated } = await client.subaccounts.list();
const detail = await client.subaccounts.get({ subaccountId }); // + apiKeys, policy, members, invites
await client.subaccounts.update({ subaccountId, label: "market-making" });
await client.subaccounts.delete({ subaccountId });Creating a subaccount requires a signed smart-account proof. In the browser the auth service does the signing for you:
const { subaccountId } = await client.auth.createSubaccount({
accountSigner: subaccountSigner, // derived with a distinct saltNonce
label: "experiments",
});Sharing and roles
Subaccounts can be shared with other users by invitation, with per-member roles and an optional MFA requirement:
await client.subaccounts.inviteMember({ subaccountId, username, role });
await client.subaccounts.listInvites({ direction: "outgoing" });
await client.subaccounts.respondInvite({ inviteId, accept: true });
await client.subaccounts.updateMemberRole({ subaccountId, accountId, role });
await client.subaccounts.setMemberMfaRequirement({ subaccountId, accountId, required: true });
await client.subaccounts.removeMember({ subaccountId, accountId });
// audit trail
const { events } = await client.subaccounts.listEvents({ subaccountId });Policies
Policies are reusable permission templates — market allowlists, allowed actions, exposure and transfer limits — that attach to subaccounts or API keys:
// Subaccount policies
const policies = await client.subaccounts.policies.list();
const policy = await client.subaccounts.policies.create({ /* scopes, actions, limits */ });
await client.subaccounts.policies.apply({ subaccountId, policyId: policy.policyId });
// API key policies
await client.apiKeys.policies.create({ /* ... */ });
await client.apiKeys.policies.apply({ keyId, policyId });Balances
const balances = await client.balances.list();
for (const b of balances) {
console.log(b); // per-asset balances as decimal strings
}History comes in two shapes — per-asset balance history and account equity history — both columnar and chart-ready:
const history = await client.balances.getBalanceHistory({
startTsSec,
endTsSec,
});
const equity = await client.balances.getEquityHistory({
startTsSec,
endTsSec,
});Live updates:
const unsubscribe = client.balances.subscribe({
accountId,
onEvent: (balance) => updateUI(balance),
});Resolving other accounts
To send an internal transfer you need a destination. client.accounts.resolve turns whatever the
user typed — username, account id, or smart-account address — into concrete destinations:
const matches = await client.accounts.resolve({
query: "hunter",
includeSubaccounts: true,
});Profile
const profile = await client.auth.profile.get();
await client.auth.profile.update({ /* mutable fields */ });
const history = await client.auth.profile.getUsernameHistory();Address book
Saved transfer destinations with tags, whitelists, and recent counterparties live under client.addressBook — see the Account services reference for the full method
list. The composite getView() powers a dashboard in one call, and subscribeViewInvalidations tells you when to refetch it.
MFA
client.mfa manages TOTP and passkey enrollment, challenges, recovery codes, and step-up proofs.
The common flow — a mutation throws StepUpRequiredError, you complete a challenge and retry
with stepUpToken — is covered in Error handling.