Prerequisites
Calm authenticates Privy users via Privy’s identity tokens — short-lived JWTs that carry the user’ssub and linked_accounts
(including the wallet and email). They are not the same as Privy
access tokens. You only need to flip this once per Privy app.
Open the Privy dashboard
Sign in at dashboard.privy.io and
pick your app.
Installation
To add Calm to your project, install the required packages.- Privy is the identity provider the SDK reads the user, wallet, and identity token from.
- TanStack Query is an async state manager that handles requests, caching, and more.
Import the stylesheet
Import the Calm stylesheet once at your app root (Next.jslayout.tsx, React main.tsx):
@import it from your own CSS file:
Wrap App in <PrivyCalmProvider>
Place <PrivyCalmProvider> inside <PrivyProvider> and
<QueryClientProvider>.
app/layout.tsx
Open the onramp
The provider always renders its children. Wrap any trigger element in<CalmOnramp> to open the deposit modal, and use
useReady() to gate the trigger on whether the Calm
context is live (i.e. Privy reports a signed-in user with a linked
wallet).
Props
Your publishable key —
calm_public_(live|dev)_<32 hex>. Identifies
the Calm tenant. Sent to the API on every session creation via
the X-Calm-Publishable-Key header.Source fiat currency for the bank-deposit onramp.
Initial destination chain for auto-converted USDC. Optional —
defaults to
999 (HyperEVM). Once mounted the SDK owns the chain;
switch it at runtime via
useCalm().setChain(...).The destination chain defaults to HyperEVM (
999) with mode set
to hypercore. It’s independent of the Privy wallet’s current
chain — to deliver USDC elsewhere, override initialChain, or call
useCalm().setChain(...) at runtime (and
setMode(...) when staying on chain 999).Initial Hyperliquid execution layer. Optional — defaults to
"hypercore" when initialChain is 999. Silently ignored on
every other chain because mode only carries meaning on Hyperliquid
(the SDK exposes mode: null off 999). Switch at runtime via
useCalm().setMode(...), which throws when
called with chain !== 999.Override the Calm API root. Use
https://api.sandbox.calmtreasury.xyz for the sandbox environment.useReady
boolean. false until Privy is ready, the user is
authenticated, the identity token is issued, and a wallet is linked;
true once <PrivyCalmProvider> has a configured Calm context. Safe
to call before any provider mounts — defaults to false.
Use it to render your own loading UI in place of the silent null
the provider used to return.
Errors
The provider’screateSession throws on any non-2xx response. The
useSession hook surfaces the error in
result.error. Common codes:
error.code | Meaning |
|---|---|
invalid_token | Privy identity token couldn’t be verified — token missing claims, signature invalid, or past exp. Check the Privy dashboard toggle above. |
wallet_not_linked | The wallet on the session URL isn’t in the Privy user’s linked accounts. Usually means the identity-token toggle is off and the wallet didn’t ride inside the JWT. |
invalid_publishable_key | calmKey is malformed, unknown, or revoked. Check your Calm dashboard. |
publishable_key_missing | No X-Calm-Publishable-Key header reached the API — usually a bundler stripping the env var. Check process.env.NEXT_PUBLIC_CALM_KEY. |
origin_not_allowed | Your page’s Origin isn’t on the publishable key’s allowlist. Live keys require HTTPS; add the origin in the dashboard. |
refresh_wallet_mismatch | The calm_refresh cookie is bound to a different wallet than the one signing in now (common after a Privy account switch). Call useCalm().logout() before mounting the provider for the new wallet. |

