1. Viem clients#
Anything you build with viem already has these, bring your own. Otherwise: stand them up like this. Sepolia for free; mainnet only for /fhe-disperse today.
import { createPublicClient, createWalletClient, custom, http } from "viem";
import { sepolia } from "viem/chains";
export const publicClient = createPublicClient({
chain: sepolia,
transport: http(),
});
export const walletClient = createWalletClient({
chain: sepolia,
transport: custom(window.ethereum),
});2. Encryptor#
The browser encryptor wraps Zama's RelayerWeb. The user signs an authorization once; subsequent encryptions reuse that signature for the relayer.
import { createSepoliaEncryptorWeb } from "@tokenops/sdk/fhe";
import { publicClient, walletClient } from "./viem";
// Browser-flavour encryptor wraps Zama RelayerWeb. Resolves the user's
// wallet to sign the per-encrypt authorization.
export const encryptor = await createSepoliaEncryptorWeb({
publicClient,
walletClient,
});3. Deploy + vest#
Two SDK calls, one tx each. The amount is encrypted client-side; the contract stores a 32-byte handle and grants ACL to the operator + the recipient atomically.
import {
createConfidentialVestingFactoryClient,
createConfidentialVestingManagerClient,
FeeType,
} from "@tokenops/sdk/fhe-vesting";
// Pre-deployed Sepolia factory, DEPLOYED_ADDRESSES resolves it by chainId.
const factory = createConfidentialVestingFactoryClient({
publicClient,
walletClient,
});
// 1. Deploy a manager clone (one tx).
const { hash: deployHash, manager: managerAddress } =
await factory.createManagerAndGetAddress({
token: "0xYOUR_ERC7984_TOKEN_ON_SEPOLIA",
userSalt: "0x".padEnd(66, "0").slice(0, 65) + "a", // any unique 32-byte salt
});
// 2. Mount a per-clone manager client.
const manager = createConfidentialVestingManagerClient({
publicClient,
walletClient,
address: managerAddress,
encryptor,
});
// 3. Create a vesting, the amount is encrypted client-side before submit.
const { hash: vestingHash, vestingId } = await manager.createVesting({
params: {
recipient: "0xRECIPIENT_ADDRESS",
startTimestamp: Math.floor(Date.now() / 1000),
endTimestamp: Math.floor(Date.now() / 1000) + 365 * 24 * 3600,
cliffSeconds: 0,
releaseIntervalSecs: 86_400, // unlock per-day
timelockSeconds: 0,
initialUnlockBps: 0,
cliffAmountBps: 0,
isRevocable: false,
},
amount: 100_000n, // bigint, encrypted to euint64 before submit
});
console.log({ deployHash, managerAddress, vestingHash, vestingId });Run it live#
Connect your wallet (top-right pill) on Sepolia, edit the token field if you have your own ERC-7984 deployment, and click Run on Sepolia. The same Monaco editor + runner that powers the stories, the tx broadcasts for real.
What you have now#
deployHash + vestingHash on Etherscan, a vestingId you can pass to useGetVestedAmount / useClaim, and a manager clone you can keep opening vestings inside without re-deploying. The recipient can now call useDecryptedHandle against the vesting's encrypted view handles to see their balance.