> ## Documentation Index
> Fetch the complete documentation index at: https://docs.calmtreasury.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Wagmi provider

> Add `<WagmiCalmProvider>` to a wagmi app.

## Generating the publishable key

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.

<Steps>
  <Step title="Open the Calm dashboard">
    Sign in to the [Calm dashboard](https://dashboard.calmtreasury.xyz) and open
    **Publishable keys**.
  </Step>

  <Step title="Fill out &#x22;Generate a new key&#x22;">
    * **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`).

    <Frame caption="Calm dashboard: Generate a new key, SIWE / none">
      <img src="https://mintcdn.com/calm/6g_rB5dy463sESxc/images/dashboard-generate-siwe-key.webp?fit=max&auto=format&n=6g_rB5dy463sESxc&q=85&s=5798ee29c40aee3d9a577703a9d1ba87" alt="Calm dashboard generate-key form with the SIWE / none wallet provider" width="1392" height="700" data-path="images/dashboard-generate-siwe-key.webp" />
    </Frame>
  </Step>

  <Step title="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>`.

    <Frame caption="Calm dashboard: the new key under Active keys">
      <img src="https://mintcdn.com/calm/6g_rB5dy463sESxc/images/dashboard-active-keys.webp?fit=max&auto=format&n=6g_rB5dy463sESxc&q=85&s=b9df667ddddeb8b22af37123331c0ed7" alt="A generated publishable key listed under Active keys with a copy button" width="1392" height="396" data-path="images/dashboard-active-keys.webp" />
    </Frame>
  </Step>
</Steps>

## Installation

To add Calm to your project, install the required packages.

<CodeGroup>
  ```sh bun theme={null}
  bun add @calm-xyz/react wagmi@^3 viem@2.x @tanstack/react-query@^5
  ```

  ```sh npm theme={null}
  npm install @calm-xyz/react wagmi@^3 viem@2.x @tanstack/react-query@^5
  ```

  ```sh pnpm theme={null}
  pnpm add @calm-xyz/react wagmi@^3 viem@2.x @tanstack/react-query@^5
  ```

  ```sh yarn theme={null}
  yarn add @calm-xyz/react wagmi@^3 viem@2.x @tanstack/react-query@^5
  ```
</CodeGroup>

* [Wagmi](https://wagmi.sh) is the wallet stack the SDK reads from for the connected account, chain, and signers.
* [Viem](https://viem.sh) is the TypeScript interface for Ethereum that wagmi uses for blockchain operations.
* [TanStack Query](https://tanstack.com/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`):

```tsx theme={null}
import "@calm-xyz/react/styles.css";
```

or `@import` it from your own CSS file:

```css theme={null}
@import "@calm-xyz/react/styles.css";
```

### Wrap App in `<WagmiCalmProvider>`

Place `<WagmiCalmProvider>` inside the `<WagmiProvider>` and
`<QueryClientProvider>` your wagmi app already sets up.

```tsx app/layout.tsx theme={null}
"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>
  );
}
```

<Note>
  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.
</Note>

<Warning>
  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.
</Warning>

### Open the onramp

Wrap any trigger element in `<CalmOnramp>` to open the deposit modal, and
gate it on the Calm session via [`useSession`](/sdk/hooks/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.

<CodeGroup>
  ```tsx app/page.tsx theme={null}
  "use client";
  import { CalmOnramp, useCalm, useSession } from "@calm-xyz/react";

  function DepositButton() {
    const { address } = useCalm();
    const session = useSession({ address });
    const ready = !!session.data && !session.isError;
    if (!ready) return <button type="button" disabled>Loading…</button>;
    return (
      <CalmOnramp>
        <button type="button">Deposit funds</button>
      </CalmOnramp>
    );
  }

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

  ```tsx app/layout.tsx theme={null}
  "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
                calmKey={process.env.NEXT_PUBLIC_CALM_KEY!}
                currency="usd"
              >
                {children}
              </WagmiCalmProvider>
            </QueryClientProvider>
          </WagmiProvider>
        </body>
      </html>
    );
  }
  ```
</CodeGroup>

## Props

<ParamField path="calmKey" type="string" required>
  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.
</ParamField>

<ParamField path="currency" type="&#x22;usd&#x22; | &#x22;gbp&#x22; | &#x22;eur&#x22;" required>
  Source fiat currency for the bank-deposit onramp.
</ParamField>

<ParamField path="chain" type="number" default="1337">
  Destination chain id for the delivered USDC. Defaults to HyperCore
  (`1337`).
</ParamField>

<ParamField path="apiUrl" type="string" default="https://api.calmtreasury.xyz">
  Override the Calm API root. Use
  `https://api.sandbox.calmtreasury.xyz` for the sandbox environment.
</ParamField>

## Errors

The provider's session creation throws on any non-2xx response from the
API. The [`useSession`](/sdk/hooks/useSession) hook surfaces the error
in `result.error`. See [Errors](/sdk/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()`](/sdk/hooks/useSession#clear) and disconnect at the wagmi layer before re-signing. |
