Recipe · Integration · Next 14+

Next.js App Router integration

The whole thing fits in 30 lines: provider tree at the root, lazy-factory encryptor at each call site. Server components stay untouched, the SDK runs in client components only.

1. Provider tree#

WagmiProvider → QueryClientProvider → ZamaSDKProvider. Mount these in a "use client" file at the root of your app's layout. Order matters: wagmi's hooks need QueryClient; Zama's relayer needs wagmi's wallet state.

app/providers.tsx
tsx
// app/providers.tsx
"use client";

import { WagmiProvider } from "wagmi";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ZamaSDKProvider } from "@zama-fhe/react-sdk";
import { wagmiConfig } from "@/lib/wagmi";

const queryClient = new QueryClient();

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <WagmiProvider config={wagmiConfig}>
      <QueryClientProvider client={queryClient}>
        <ZamaSDKProvider>{children}</ZamaSDKProvider>
      </QueryClientProvider>
    </WagmiProvider>
  );
}

In app/layout.tsx, wrap {children} in <Providers>.

2. Use SDK hooks in client components#

Hooks live at @tokenops/sdk/<product>/react. Every hook is a TanStack Query primitive, mutations are useMutation, reads are useQuery. Use them like any other wagmi or viem hook.

components/CreateVestingButton.tsx
tsx
// components/CreateVestingButton.tsx
"use client";

import { useZamaSDK } from "@zama-fhe/react-sdk";
import { useCreateVesting } from "@tokenops/sdk/fhe-vesting/react";
import type { Address } from "viem";

export function CreateVestingButton({
  managerAddress,
  recipient,
}: {
  managerAddress: Address;
  recipient: Address;
}) {
  const zamaSDK = useZamaSDK();

  const create = useCreateVesting({
    address: managerAddress,
    // Lazy factory: resolves the encryptor at submit time, not at mount.
    encryptor: () => zamaSDK.relayer,
  });

  return (
    <button
      disabled={create.isPending}
      onClick={() =>
        create.mutate({
          params: {
            recipient,
            startTimestamp: Math.floor(Date.now() / 1000),
            endTimestamp: Math.floor(Date.now() / 1000) + 365 * 24 * 3600,
            cliffSeconds: 0,
            releaseIntervalSecs: 86_400,
            timelockSeconds: 0,
            initialUnlockBps: 0,
            cliffAmountBps: 0,
            isRevocable: false,
          },
          amount: 100_000n,
        })
      }
    >
      {create.isPending ? "Submitting…" : "Create vesting"}
    </button>
  );
}
Interactive · createVesting on Sepolia
Loading editor…
Edit any input above, then press to run.
Console0 lines
No output yet. Run the snippet to see logs stream in.

What server components can do#

Plenty, just not the encrypted writes. The headless client classes work fine on the server for reads: balance lookups, deployment metadata, vesting schedule shapes, address registry access. Anything that doesn't submit a tx is fair game in a server component or server action.

See also