Every SDK call that submits an encrypted input takes an encryptor option. The option accepts either:
- An eager instance:
Encryptor - A lazy factory:
() => Encryptor | undefined
React: always use a lazy factory#
React renders the SDK client at mount, but the wallet context that drives the encryptor only fully resolves later. Eager passes capture a stale or undefined encryptor.
import { useZamaSDK } from "@zama-fhe/react-sdk";
import { useCreateVesting } from "@tokenops/sdk/fhe-vesting/react";
export function CreateButton({ managerAddress }) {
const zamaSDK = useZamaSDK();
const create = useCreateVesting({
address: managerAddress,
// Lazy factory — the SDK calls this AT submit time, not at mount time.
encryptor: () => zamaSDK.relayer,
});
return <button onClick={() => create.mutate(args)}>Create</button>;
}The arrow function defers resolution. When the user clicks the button, the SDK calls encryptor(), which now returns the live relayer from zamaSDK.relayer. Same pattern works for Vue (read a ref) and signals (read a subscription).
Node + tests: eager is correct#
Server-side code controls its own dependency graph. The encryptor is created at startup; the SDK client is created right after; both live the lifetime of the process. Lazy resolution has no value here.
import { createSepoliaEncryptor } from "@tokenops/sdk/fhe";
import { createConfidentialVestingManagerClient } from "@tokenops/sdk/fhe-vesting";
// Server-side: the encryptor's lifetime matches the SDK client's. Eager is fine.
const encryptor = await createSepoliaEncryptor();
const manager = createConfidentialVestingManagerClient({
publicClient,
walletClient,
address: managerAddress,
encryptor,
});Available encryptor flavours#
Three drop-in adapters ship in @tokenops/sdk/fhe:
createSepoliaEncryptorWeb — the browser flavour. Wraps Zama's RelayerWeb. Its options accept a walletClient, but the current implementation discards it — it is not used to sign the per-encrypt authorization. The story flows on this site use it.
createSepoliaEncryptor — Node flavour. Connects to the Zama public Sepolia relayer. No credentials required; optionally accepts relayerUrl to target a private relayer fork. Same Encryptor interface, no wallet client.
createMockEncryptor — the test flavour. Bound to a deterministic local FHEVM (Anvil + forge-fhevm). Use it directly in vitest suites against a pnpm fhevm:up Anvil node — there is no wrapping test-suite helper exported from the SDK. Never use in production code.