Skip to main content

Installation

To add Calm to your project, install the required packages.
bun add @calm-xyz/react wagmi@^3 viem@2.x @tanstack/react-query@^5
  • Wagmi is the wallet stack the SDK reads from for the connected account, chain, and signers.
  • Viem is the TypeScript interface for Ethereum that wagmi uses for blockchain operations.
  • 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.js layout.tsx, React main.tsx):
import "@calm-xyz/react/styles.css";
or @import it from your own CSS file:
@import "@calm-xyz/react/styles.css";

Wrap App in <WagmiCalmProvider>

Place <WagmiCalmProvider> inside your wagmi tree — the <WagmiProvider> + <QueryClientProvider> pair wagmi requires.
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>
  );
}

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. wagmi has a connected account).
"use client";
import { CalmOnramp } from "@calm-xyz/react";
import { useReady } from "@calm-xyz/react/wagmi";

function DepositButton() {
  const ready = useReady();
  if (!ready) return <button type="button" disabled>Loading…</button>;
  return (
    <CalmOnramp>
      <button type="button">Deposit funds</button>
    </CalmOnramp>
  );
}

export default function Page() {
  return <DepositButton />;
}

Props

calmKey
string
required
Your publishable key — calm_public_(live|dev)_<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.
currency
"usd" | "gbp" | "eur"
required
Source fiat currency for the bank-deposit onramp.
initialChain
1 | 8453 | 42161 | 999
default:"999"
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 wallet’s wagmi chain — to deliver USDC elsewhere, override initialChain, or call useCalm().setChain(...) at runtime (and setMode(...) when staying on chain 999).
initialMode
"hyperevm" | "hypercore"
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.
baseUrl
string
default:"https://api.calmtreasury.xyz"
Override the Calm API root. Use https://api.sandbox.calmtreasury.xyz for the sandbox environment.

useReady

import { useReady } from "@calm-xyz/react/wagmi";

const ready = useReady();
Returns boolean. false until wagmi reports a connected account and <WagmiCalmProvider> has a configured Calm context; true once it does. 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’s session creation throws on any non-2xx response from the API. The useSession hook surfaces the error in result.error. Common codes:
error.codeMeaning
binding_expiredThe nonce expired (5-minute TTL) before the user signed. Re-mount or retry.
siwe_invalidThe signed message failed verification (wrong nonce, wrong address recovered, time bounds, unparseable input).
invalid_publishable_keycalmKey is malformed, unknown, or revoked. Check your Calm dashboard.
publishable_key_missingNo 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_allowedYour page’s Origin isn’t on the publishable key’s allowlist. Live keys require HTTPS; add the origin in the dashboard.
refresh_wallet_mismatchThe calm_refresh cookie is bound to a different wallet than the one signing in now. Call useCalm().logout() before re-signing as the new wallet.