Examples
Self-contained, runnable examples for @tokenops/sdk/fhe-vesting. The hardcoded private keys are the well-known Anvil/Hardhat defaults — public, do not use with real funds.
Deploy a manager and create a schedule
Admin: deploy a confidential vesting manager clone from the factory, then create a one-year linear vesting schedule for a recipient.
Prerequisites
pnpm fhevm:up
npx tsx examples/fhe-vesting-create.ts
Expected output
---- RESULT ----
token: 0x…
manager: 0x…
factory tx: 0x…
vestingId: 0x0000…0000
createVesting tx: 0x…
gas used: ~1.09M
encrypted total: 0x…
total view tx: 0x…
DONE, next: run examples/fhe-airdrop-set.ts to create an airdrop
encrypted total is the ACL-granted handle returned by getTotalAllocation. total view tx is the on-chain ACL grant transaction — the recipient now holds persistent ACL on that handle.
fhe-vesting-create.ts
import { parseEventLogs, type Hex } from "viem";
import { keccak256, stringToHex } from "viem";
import { asVestingId } from "@tokenops/sdk";
import {
createConfidentialVestingFactoryClient,
createConfidentialVestingManagerClient,
confidentialVestingManagerAbi,
FeeType,
TokenOpsSdkError,
} from "@tokenops/sdk/fhe-vesting";
import { createMockEncryptor } from "@tokenops/sdk/fhe";
import { ConsoleTelemetry } from "@tokenops/sdk/telemetry";
import {
anvilAccount,
assertAnvilLive,
deployContract,
exitWithError,
loadArtifact,
loadDotEnvLocal,
makeLocalClients,
writeState,
} from "./_shared/run-helpers.js";
loadDotEnvLocal();
async function main(): Promise<void> {
await assertAnvilLive();
const telemetry = new ConsoleTelemetry();
const { publicClient, walletClient } = makeLocalClients();
const admin = walletClient("admin");
const feeCollector = anvilAccount("feeCollector");
const recipient = anvilAccount("recipient1").address;
const ext = loadArtifact(
"tokenops-fhe-vesting-v2",
"vesting/ConfidentialVestingManagerExtension.sol/ConfidentialVestingManagerExtension.json",
);
const impl = loadArtifact(
"tokenops-fhe-vesting-v2",
"vesting/ConfidentialVestingManager.sol/ConfidentialVestingManager.json",
);
const factoryArt = loadArtifact(
"tokenops-fhe-vesting-v2",
"factory/ConfidentialVestingFactory.sol/ConfidentialVestingFactory.json",
);
const tokenArt = loadArtifact(
"tokenops-fhe-vesting-v2",
"test/MockERC7984Token.sol/MockERC7984Token.json",
);
if (!ext || !impl || !factoryArt || !tokenArt) {
exitWithError(
"Missing tokenops-fhe-vesting-v2 artifacts.",
"Clone https://github.com/VestingLabs/tokenops-fhe-vesting-v2 next to tokenops-sdk and run `pnpm compile`.",
);
}
const extension = await deployContract(publicClient, admin, ext, []);
const managerImpl = await deployContract(publicClient, admin, impl, [extension]);
const factoryAddress = await deployContract(publicClient, admin, factoryArt, [
managerImpl,
admin.account.address,
feeCollector.address,
0n,
0n,
FeeType.Gas,
]);
const token = await deployContract(publicClient, admin, tokenArt, ["TestToken", "TTK"]);
// ---- SDK starts here ----
const encryptor = await createMockEncryptor();
const factory = createConfidentialVestingFactoryClient({
publicClient,
walletClient: admin,
address: factoryAddress,
telemetry,
});
const userSalt: Hex = keccak256(stringToHex(`example-${Date.now()}`));
let managerAddress: `0x${string}`;
let factoryTxHash: Hex;
try {
const result = await factory.createManager({ token, userSalt });
managerAddress = result.manager;
factoryTxHash = result.hash;
} catch (err) {
if (err instanceof TokenOpsSdkError) exitWithError(err.message);
throw err;
}
const manager = createConfidentialVestingManagerClient({
publicClient,
walletClient: admin,
address: managerAddress,
encryptor,
telemetry,
});
const setOperatorHash = await admin.writeContract({
address: token,
abi: tokenArt.abi,
functionName: "setOperator",
args: [managerAddress, (1n << 48n) - 1n],
});
await publicClient.waitForTransactionReceipt({ hash: setOperatorHash });
const mintHash = await admin.writeContract({
address: token,
abi: tokenArt.abi,
functionName: "mint",
args: [admin.account.address, 1_000_000_000n],
});
await publicClient.waitForTransactionReceipt({ hash: mintHash });
const now = Math.floor(Date.now() / 1000);
const vestingHash = await manager.createVesting({
params: {
recipient,
startTimestamp: now,
endTimestamp: now + 365 * 86400,
cliffSeconds: 0,
releaseIntervalSecs: 86400,
timelockSeconds: 0,
initialUnlockBps: 0,
cliffAmountBps: 0,
isRevocable: true,
},
amount: 1_000_000n,
});
const receipt = await publicClient.waitForTransactionReceipt({ hash: vestingHash });
const events = parseEventLogs({
abi: confidentialVestingManagerAbi,
eventName: "VestingCreated",
logs: receipt.logs,
});
if (events.length === 0) exitWithError("VestingCreated event missing from createVesting receipt");
const vestingId = asVestingId(events[0]!.args.vestingId);
// Encrypted-view pattern: the RECIPIENT calls getTotalAllocation, which
// grants FHE.allow(handle, msg.sender) on-chain. The SDK extracts the handle
// from the receipt's ACL.Allowed event, not from simulation.
const recipientWallet = walletClient("recipient1");
const recipientManager = createConfidentialVestingManagerClient({
publicClient,
walletClient: recipientWallet,
address: managerAddress,
encryptor,
});
const totalView = await recipientManager.getTotalAllocation({ vestingId });
writeState({
token,
managerAddress,
vestingId,
sampleEncryptedHandle: totalView.handle,
sampleContract: managerAddress,
sampleUser: recipientWallet.account.address,
});
console.log("");
console.log("---- RESULT ----");
console.log(`token: ${token}`);
console.log(`manager: ${managerAddress}`);
console.log(`factory tx: ${factoryTxHash}`);
console.log(`vestingId: ${vestingId}`);
console.log(`createVesting tx: ${vestingHash}`);
console.log(`gas used: ${receipt.gasUsed.toString()}`);
console.log(`encrypted total: ${totalView.handle}`);
console.log(`total view tx: ${totalView.hash}`);
console.log("");
console.log("DONE, next: run examples/fhe-airdrop-set.ts to create an airdrop");
}
main().catch((err) => {
console.error(err);
process.exit(1);
});