@tokenops/sdk/fhe-vesting
Typed wrapper for the ConfidentialVesting contract system. The factory deploys per-user LibClone manager instances; the manager holds all vesting schedules for one token. Amounts are kept confidential on-chain as euint64 ciphertexts — the SDK encrypts plaintext bigint inputs before submitting transactions.
Sepolia factory: 0xA87701CE9A52D43681600583a99c85b50DbE3150 — auto-resolved from DEPLOYED_ADDRESSES.fheVesting.confidentialVestingFactory[11155111] when publicClient.chain.id === 11155111. Pass address only to override (custom deployment, fork test). Mainnet: not yet deployed (null in src/core/addresses.ts) — pass address explicitly until the mainnet factory ships.
30-second example
import { createPublicClient, createWalletClient, http, parseEventLogs } from "viem";
import { sepolia } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
import { RelayerNode, SepoliaConfig } from "@zama-fhe/sdk/node";
import {
createConfidentialVestingFactoryClient,
createConfidentialVestingManagerClient,
confidentialVestingManagerAbi,
} from "@tokenops/sdk/fhe-vesting";
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const rpcUrl = process.env.RPC_URL!;
const publicClient = createPublicClient({ chain: sepolia, transport: http(rpcUrl) });
const walletClient = createWalletClient({ account, chain: sepolia, transport: http(rpcUrl) });
// SepoliaConfig provides all required contract addresses and the public relayer URL.
const encryptor = new RelayerNode({
transports: { [SepoliaConfig.chainId]: { ...SepoliaConfig, network: rpcUrl } },
getChainId: async () => sepolia.id,
});
// Deploy the clone and read its address from the ManagerCreated event in the
// receipt. The factory packs the deployment block number into the clone's
// immutable args, so predicting the address before mining is unreliable on a
// live chain — factory.createManager always does the receipt parse for you.
const factory = createConfidentialVestingFactoryClient({ publicClient, walletClient });
const token = "0xYourERC7984Token" as `0x${string}`;
const userSalt = "0x0000000000000000000000000000000000000000000000000000000000000001" as const;
const { manager: managerAddress } = await factory.createManager({
token,
userSalt,
});
const manager = createConfidentialVestingManagerClient({
publicClient,
walletClient,
address: managerAddress,
encryptor,
});
// amount is raw token units: 1_000_000n = 1 USDC at 6 decimals, 1e18 for 18-decimal tokens.
const vestingHash = await manager.createVesting({
params: {
recipient: "0x0000000000000000000000000000000000000001" as `0x${string}`,
startTimestamp: Math.floor(Date.now() / 1000),
endTimestamp: Math.floor(Date.now() / 1000) + 365 * 86400,
cliffSeconds: 90 * 86400,
releaseIntervalSecs: 86400,
timelockSeconds: 0,
initialUnlockBps: 0,
cliffAmountBps: 0,
isRevocable: true,
},
amount: 1_000_000n,
});
// Extract vestingId from the VestingCreated event — needed for all subsequent calls.
const receipt = await publicClient.waitForTransactionReceipt({ hash: vestingHash });
const events = parseEventLogs({
abi: confidentialVestingManagerAbi,
eventName: "VestingCreated",
logs: receipt.logs,
});
const vestingId = events[0]!.args.vestingId; // bytes32 Hex — save this for claim/split/disclose