Prerequisites
Calm authenticates Dynamic users via the JWT Dynamic mints on sign-in. The JWT carries the user’ssub, verified_credentials
(including the linked EVM wallet), and the Dynamic environment id —
which is exactly what the SDK forwards to Calm.
The only setup is making sure Dynamic is configured for EVM wallets
so an Ethereum address appears in verified_credentials.
Installation
To add Calm to your project, install the required packages.- Dynamic is the identity provider the SDK reads the user, wallet, and JWT from.
@dynamic-labs/ethereumregisters the EVM connectors so the user’s wallet rides inside the JWT’sverified_credentials.- 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 <DynamicCalmProvider>
Place <DynamicCalmProvider> inside <DynamicContextProvider> 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. Dynamic reports a signed-in user with an EVM
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 Dynamic 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 Dynamic is ready, the user is
authenticated, the JWT is issued, and an EVM wallet is connected;
true once <DynamicCalmProvider> 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 | Dynamic JWT couldn’t be verified — token missing claims, signature invalid, or past exp. |
wallet_not_linked | The wallet on the session URL isn’t an EVM entry in the Dynamic user’s verified_credentials. Usually means EthereumWalletConnectors isn’t registered in your Dynamic config so no EVM wallet rides 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 Dynamic account switch). Call useCalm().logout() before mounting the provider for the new wallet. |