PolyesterServerClient is the core client plus session awareness: it parses the cookies your web
app's browser client sets, uses the bearer token for authenticated calls, and exposes what it
knows about the user for rendering.
One client per request
Create the client from the incoming request (or your framework's cookie API) in your request handler:
import { createPolyesterServerClientFromRequest, POLYESTER_TESTNET_ENVIRONMENT } from "@polyester/sdk";
export async function handle(request: Request) {
const client = createPolyesterServerClientFromRequest({
environment: POLYESTER_TESTNET_ENVIRONMENT,
request,
});
if (client.hasUsableBearerToken) {
const balances = await client.balances.list();
// ...
}
}Clients are cheap to construct — services, realtime, and the catalog are all built lazily on first access — so per-request creation is the intended pattern.
With a framework cookie API instead of a Request:
const client = createPolyesterServerClientFromCookies({
environment,
cookies, // anything with .get(name), or a Request, or a plain record
});Display session vs. authenticated session
Two cookies matter, and the SDK keeps them strictly apart:
| Cookie | Contents | Trust level |
|---|---|---|
| Session cookie | Who the user appears to be — username, active account, wallet addresses | Unsigned. Display only. |
| Auth token cookie | The JWT bearer token | Proof — but verify before trusting |
client.hasDisplaySession; // can I render a username immediately?
client.session; // the parsed ServerSessionSnapshot
client.hasBearerToken; // is a token present?
client.hasUsableBearerToken; // …and not expired?
const me = await client.verifySession(); // backend-verified identity, or nullUse the display session for instant, non-sensitive rendering (avatar, username) and verifySession() before anything that must be correct. Actual data requests are authorized by
the backend on every call regardless.
ServerSessionSnapshot) on purpose — authorization decisions belong to the backend,
which validates the bearer token on each request.Subaccount defaulting
By default, server clients scope calls to the main account. If your app's account switcher should carry over to SSR, opt in:
const client = createPolyesterServerClientFromRequest({
environment,
request,
useDisplaySessionActiveAccountAsDefault: true,
});This treats the display session's active account as caller intent for calls that omit account — the backend still enforces access.
Hydrating the browser
Two handoffs make client-side startup seamless:
Auth state — render without a flash of logged-out UI, then restore:
// browser, on mount
client.auth.hydrateAuthState({
mainAccountId,
username,
activeAccountId,
});
await client.auth.restoreSession();Catalog snapshot — skip the reference-data fetch on the client by baking the server's catalog into the page:
// server
await serverClient.catalog.ensureReady();
const snapshot = serverClient.catalog.snapshot();
// browser
const client = new PolyesterBrowserClient({
environment,
catalogSnapshot: snapshot,
});Lower-level helpers
The @polyester/sdk/server-session subpath exposes the primitives if you need them without a
client: parseSessionCookie(cookies, environment), emptyServerSessionSnapshot(), and isJwtValid(token). The root export also provides getBearerTokenFromCookies and the cookie
name constants (POLYESTER_AUTH_TOKEN_COOKIE_NAME, POLYESTER_SESSION_COOKIE_NAME).
Edge runtimes
The SDK runs on edge/serverless runtimes (Cloudflare Workers, Vercel Edge) — it is fetch-based, ESM-only, and defers heavy dependencies. Realtime subscriptions are not available during SSR; fetch snapshots server-side and subscribe in the browser (see Streaming).