TESTNET
Markets
Trade
Lending Vaults
More
User Docs Developer Docs Sdk API Docs Help
Overview
Overview
Architecture
Authentication
Client configuration
Overview
Environments
Installation
Market data
Market data services
Authentication model
Quickstart
Trading
Trading services
Account services
Catalog & precision
Streaming
Accounts & balances
Funding services
Deposits & withdrawals
Realtime client
Errors
Server-side usage
Error handling
  1. Typescript
  2. /
  3. Authentication model

Authentication model

This page explains how Polyester authentication works underneath the SDK's APIs. For the task-oriented version, see the Authentication guide.

Identity: the smart account

A Polyester account is a Safe smart account on Polyester Chain. The wallet key your user holds (MetaMask, Turnkey, a raw private key) is the owner of that Safe — it controls the account but is not the account itself.

The Safe's address is computed deterministically from the owner address, a salt nonce, and the environment's Safe deployment configuration. That has two consequences the SDK leans on:

  • No deployment needed to authenticate. The address exists (counterfactually) before any on-chain transaction. Different saltNonce values derive different accounts (used for subaccounts).
  • Signatures must verify against an undeployed contract. The SDK produces ERC-6492-wrapped Safe signatures, which carry the factory data a verifier needs to check a signature for a contract that isn't deployed yet.

The AccountSigner interface abstracts all of this: an accountAddress, an optional ownerAddress, an environment fingerprint, and a signMessage function. Anything that can produce a valid Safe signature can be an account signer — the built-in createPolyesterAccountSigner covers the standard single-owner case with zero RPC calls.

Wallet login: nonce → signature → JWT

  1. The client requests a login nonce for the smart-account address (auth.requestLoginNonce). Nonces are single-use and expire in ~5 minutes.
  2. The account signer signs the nonce message.
  3. The signed nonce is exchanged for a JWT session token bound to the account.
  4. The browser client stores the token (in memory by default, or in a cookie via createCookieAuthTokenStorage), attaches it as Authorization: Bearer … on every request, and uses it to authorize private realtime channels.

Session tokens are environment-fingerprint-bound in storage: a token minted on one environment is invisible to clients constructed for another. Refreshing a session (auth.refreshSession) is the same signature dance with a fresh nonce.

API keys: Ed25519 request signing

API keys skip JWTs entirely — every request is signed individually:

  1. You generate an Ed25519 keypair locally (apiKeys.generateKeypair) and register only the public key. The secret never leaves your process.
  2. For each request the SDK builds a canonical string — timestamp \n method \n path \n sorted-query \n sha256(body) — signs it with the secret key, and sends three headers: X-API-KEY-ID, X-API-TIMESTAMP, X-API-SIGNATURE.
  3. The backend verifies the signature against the registered public key.

Because the timestamp is part of the signature, replay windows are tight; because the body hash is included, payloads are tamper-evident. Keys can carry IP whitelists, expirations, and policies (scoped markets, actions, and limits).

WebSocket authentication reuses the same contract: connection- and subscription-token requests are signed the same way.

Sessions on servers

Browser sessions surface to your server as cookies. The SDK deliberately splits what they prove:

  • The auth token cookie holds the JWT — real proof, verified by the backend on every call.
  • The session cookie holds display data (username, active account, addresses) — unsigned, client-writable, and typed as display-only (ServerSessionSnapshot).

PolyesterServerClient reads both, uses the JWT for calls, and exposes verifySession() for a backend-confirmed identity. Authorization is always the backend's decision; the SDK just carries intent.

Assurance layers: MFA

Some operations require more than a valid session. Polyester has two elevation mechanisms, and the SDK maps each to a distinct error:

  • Session elevation (SessionElevationRequiredError) — the session itself must have passed an MFA challenge recently. Complete a challenge with purpose "sessionElevation"; the session is upgraded.
  • Fresh step-up (StepUpRequiredError) — a one-use proof bound to a single protected request. Complete a challenge with purpose "freshStepUp" and retry the call with the returned stepUpToken (sent as the X-Auth-Step-Up header).

Factors are TOTP or passkeys, with one-time recovery codes as backup — all managed through client.mfa. Subaccount owners can additionally require MFA from delegated members.

Subaccounts and delegation

Subaccounts are full smart accounts derived from the same owner with different salt nonces, tied to the main account. Creating one is itself a signed operation (proof you control the new address). Access can be delegated to other users with roles, and constrained with policy templates — see Accounts & balances.

Previous

Market data services

Next

Quickstart

  • Identity: the smart account
  • Wallet login: nonce → signature → JWT
  • API keys: Ed25519 request signing
  • Sessions on servers
  • Assurance layers: MFA
  • Subaccounts and delegation