Recipe · Setup · Node

Server-side encryptor

In Node there's no wagmi, no React context, no Privy. Eager-inject the encryptor at startup; the SDK client carries it for the lifetime of the process.

Why eager works here#

The Pitfall #3 lazy-factory pattern exists because React + Vue + signals contexts can resolve the encryptor at different times than the SDK client. Node owns its own dependency graph: the private key loads, the RPC URL resolves, the encryptor stands up, the SDK consumes it, all in deterministic order at startup. No mismatch possible.

server/index.ts
ts
import { createPublicClient, createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { sepolia } from "viem/chains";
import { createSepoliaEncryptor } from "@tokenops/sdk/fhe";

const account = privateKeyToAccount(process.env.SEPOLIA_PRIVATE_KEY as `0x${string}`);

const publicClient = createPublicClient({
  chain: sepolia,
  transport: http(process.env.SEPOLIA_RPC_URL),
});

const walletClient = createWalletClient({
  account,
  chain: sepolia,
  transport: http(process.env.SEPOLIA_RPC_URL),
});

// Eager, the encryptor's lifetime matches the SDK client's. Lazy
// factories add no value server-side.
const encryptor = await createSepoliaEncryptor({
  publicClient,
  walletClient,
});

import { createConfidentialVestingManagerClient } from "@tokenops/sdk/fhe-vesting";

const manager = createConfidentialVestingManagerClient({
  publicClient,
  walletClient,
  address: process.env.MANAGER_ADDRESS as `0x${string}`,
  encryptor,
});

// Now any manager call is encryptor-aware:
await manager.createVesting({
  params: {
    recipient: "0x...",
    startTimestamp: Math.floor(Date.now() / 1000),
    endTimestamp: Math.floor(Date.now() / 1000) + 30 * 24 * 3600,
    cliffSeconds: 0,
    releaseIntervalSecs: 3600,
    timelockSeconds: 0,
    initialUnlockBps: 0,
    cliffAmountBps: 0,
    isRevocable: false,
  },
  amount: 50_000n,
});

Use cases#

  • Server-side admin signer for useSignClaimAuthorization: sign the EIP-712 message off-chain, hand the signature to the frontend.
  • Indexer / worker: open vestings in bulk from a CSV upload, batch with useBatchCreateVesting's headless equivalent on the manager client.
  • Test suite: hit a real Sepolia factory under vitest with describeSepoliaFheVestingFullfrom the SDK's test helpers.

See also