The calmKey you pass to <WagmiCalmProvider> is a publishable key
minted in the Calm dashboard. Wagmi apps sign in with SIWE, so the key
needs no identity-provider binding.
1
Open the Calm dashboard
Sign in to the Calm dashboard and open
Publishable keys.
2
Fill out "Generate a new key"
Environment — Sandbox to start, Live for production. Each
environment issues its own key (calm_public_sandbox_… /
calm_public_live_…).
Wallet provider — leave it on SIWE / none. Wagmi keys
authenticate by wallet signature, with no IdP tenant to bind.
Allowed origin(optional) — the origin your app is served
from. Live keys require an https:// origin; in Sandbox you can
leave it blank to skip the Origin check (e.g. for
http://localhost).
Calm dashboard: Generate a new key, SIWE / none
3
Generate and copy the key
Click Generate. The new key appears under Active keys — copy
it and pass it as calmKey. It looks like
calm_public_sandbox_<32 hex>.
Place <WagmiCalmProvider> inside the <WagmiProvider> and
<QueryClientProvider> your wagmi app already sets up.
app/layout.tsx
"use client";import { WagmiProvider, createConfig, http } from "wagmi";import { base, mainnet } from "wagmi/chains";import { QueryClient, QueryClientProvider } from "@tanstack/react-query";import { WagmiCalmProvider } from "@calm-xyz/react/wagmi";const config = createConfig({ chains: [mainnet, base], transports: { [mainnet.id]: http(), [base.id]: http(), },});const queryClient = new QueryClient();export default function RootLayout({ children,}: { children: React.ReactNode;}) { return ( <html lang="en"> <body> <WagmiProvider config={config}> <QueryClientProvider client={queryClient}> {/* <WagmiCalmProvider> must be wrapped in <WagmiProvider> and <QueryClientProvider> — it reads wagmi context and uses react-query under the hood. */} <WagmiCalmProvider calmKey={process.env.NEXT_PUBLIC_CALM_KEY!} currency="usd" > {children} </WagmiCalmProvider> </QueryClientProvider> </WagmiProvider> </body> </html> );}
Unlike the Privy and Dynamic providers (which take an rpcUrls prop),
<WagmiCalmProvider> reads swap transaction receipts through your wagmi
config — the per-chain transports above, routed by chain id. For
production, point each swap source chain at a reliable RPC (e.g.
http("https://…")) instead of the keyless http() default so the
confirm step doesn’t stall.
Mount <WagmiCalmProvider> only while a wallet is connected — it reads
the address from useAccount() at mount and requires it to be
defined. Gate the mount on useAccount().status === "connected";
mounting with no connected account throws.
Wrap any trigger element in <CalmOnramp> to open the deposit modal, and
gate it on the Calm session via useSession.useSession creates the session automatically as soon as a wallet is
connected — it runs the sign-in handshake on mount, not on a click. You
gate the trigger on the result only so the modal can’t open before its
requests would authenticate: the button stays disabled until
session.data is ready.
Your publishable key — calm_public_(live|sandbox)_<32 hex>. Embedded
in the SIWE message’s Resources field as calm:credential:<calmKey>
so the wallet attests the tenant key as part of the same signature
that proves wallet ownership.
The provider’s session creation throws on any non-2xx response from the
API. The useSession hook surfaces the error
in result.error. See Errors for the full code table;
the most common from this shell:
error.code
Meaning
binding_expired
The nonce expired (5-minute TTL) before the user signed. Re-mount or retry.
siwe_invalid
The signed message failed verification (wrong nonce, wrong address recovered, time bounds, unparseable input).
invalid_publishable_key
calmKey is malformed, unknown, or revoked. Check your Calm dashboard.
publishable_key_missing
No calm:credential:<key> entry in the SIWE message’s Resources field. Usually a bundler stripping process.env.NEXT_PUBLIC_CALM_KEY — verify the env var is present at build time.
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_invalid
The calm_refresh cookie is missing, expired, or bound to a different wallet. Call useSession({ address }).clear() and disconnect at the wagmi layer before re-signing.