React Hooks
Hooks live at @tokenops/sdk/fhe-vesting/react. The optional peer dependencies react, wagmi, and @tanstack/react-query must be installed. @zama-fhe/react-sdk is an optional peer for FHE write flows.
Wrap your app in wagmi's <WagmiProvider> + <QueryClientProvider> + @zama-fhe/react-sdk's <ZamaProvider> before rendering components that use encrypted hooks.
Hook catalogue
| Family | Hooks |
|---|---|
| Factory reads | useConfidentialVestingFactoryImplementation, useFactoryDefaultGasFee, useFactoryDefaultTokenFee, useFactoryDefaultFeeType, useFactoryFeeCollector, useFactoryCustomFee, usePredictManagerAddress, useFactoryInitCodeHash |
| Factory writes | useCreateManager, useCreateManagerAndGetAddress, useSetDefaultGasFee, useSetDefaultTokenFee, useSetDefaultFeeType, useResetGasFee, useResetTokenFee, useSetCustomFee, useDisableCustomFee, useSetFeeCollector |
| Manager reads | useManagerToken, useManagerFeeType, useManagerFee, useManagerFeeInfo, useManagerDeploymentBlockNumber, useManagerIsSplitEnabled, useManagerIsPausable, useManagerPaused, useManagerMaxBatchSize, useManagerMaxRevokeBatchSize, useVestingInfo, useAllRecipients, useIsRecipient, useRecipientVestings, usePendingVestingTransfer, useHasRole, useRoleConstants |
| Manager encrypted views | useGetVestedAmount, useGetClaimableAmount, useGetTotalAllocation, useGetSettledAmount, useAdminGetVestedAmount, useAdminGetClaimableAmount, useAdminGetTotalAllocation, useAdminGetSettledAmount, useAdminGetTokenBalance |
| Manager writes | useCreateVesting, useBatchCreateVesting, useClaim, useAdminClaim, usePartialClaim, useAdminPartialClaim, useRevokeVesting, useBatchRevokeVesting, useSplitVesting, useInitiateVestingTransfer, useAcceptVestingTransfer, useCancelVestingTransfer, useDirectVestingTransfer, useDiscloseToParty, useBatchDiscloseToParty, useAdminDiscloseToParty, useAdminBatchDiscloseToParty, useDiscloseHandleToParty, useBatchDiscloseHandlesToParty, useWithdrawAdmin, useWithdrawOtherToken, useWithdrawOtherConfidentialToken, useWithdrawGasFee, useWithdrawTokenFee, useSetMaxBatchSize, useSetMaxRevokeBatchSize, useTransferFeeCollectorRole, usePause, useUnpause, useGrantRole, useRevokeRole, useRenounceRole |
All utility types EncryptorSource, Encryptor, VestingParams, VestingInfo, EncryptedViewResult, DisclosureType, FeeType, and scaleRatio are re-exported from @tokenops/sdk/fhe-vesting/react.
Deploy a manager
useCreateManagerAndGetAddress deploys a manager clone and learns its address from the receipt event.
import { useQueryClient } from "@tanstack/react-query";
import { useCreateManagerAndGetAddress } from "@tokenops/sdk/fhe-vesting/react";
function DeployManagerButton({
token,
userSalt,
onDeployed,
}: {
token: `0x${string}`;
userSalt: `0x${string}`;
onDeployed: (manager: `0x${string}`) => void;
}) {
const queryClient = useQueryClient();
// No options needed on Sepolia — the hook resolves the factory address
// from DEPLOYED_ADDRESSES using the connected chain. Pass `address` to override.
const create = useCreateManagerAndGetAddress();
return (
<button
onClick={() =>
create.mutate(
{ token, userSalt },
{
onSuccess: ({ manager }) => {
queryClient.invalidateQueries({ queryKey: ["tokenops-sdk", "fhe-vesting"] });
onDeployed(manager);
},
},
)
}
disabled={create.isPending}
>
{create.isPending ? "Deploying…" : "Deploy manager"}
</button>
);
}
create.data is typed as { hash: Hex; manager: Address } after success.
Create a vesting schedule
For any hook that submits encrypted inputs, pass encryptor as a lazy factory so the hook always picks up the live Zama React SDK context at submit time:
import { useZamaSDK } from "@zama-fhe/react-sdk";
import { useQueryClient } from "@tanstack/react-query";
import { useCreateVesting, type VestingParams } from "@tokenops/sdk/fhe-vesting/react";
function CreateVestingForm({ managerAddress }: { managerAddress: `0x${string}` }) {
const queryClient = useQueryClient();
const zamaSDK = useZamaSDK();
const create = useCreateVesting({
address: managerAddress,
encryptor: () => zamaSDK.relayer,
});
const params: VestingParams = {
recipient: "0xRecipient" as `0x${string}`,
startTimestamp: Math.floor(Date.now() / 1000),
endTimestamp: Math.floor(Date.now() / 1000) + 365 * 86400,
cliffSeconds: 0,
releaseIntervalSecs: 86400,
timelockSeconds: 0,
initialUnlockBps: 0,
cliffAmountBps: 0,
isRevocable: false,
};
return (
<button
onClick={() =>
create.mutate(
{ params, amount: 1_000_000n },
{
onSuccess: () =>
queryClient.invalidateQueries({ queryKey: ["tokenops-sdk", "fhe-vesting"] }),
},
)
}
disabled={create.isPending}
>
{create.isPending ? "Creating…" : "Create vesting"}
</button>
);
}
Encrypted view pattern
Encrypted view hooks submit a transaction — they are useMutation instances, not read queries. The contract performs FHE.allow(handle, msg.sender) on-chain; the SDK extracts the handle from the receipt's ACL.Allowed event. Pass data.handle to the Zama relayer's userDecrypt to read the plaintext.
import { useZamaSDK } from "@zama-fhe/react-sdk";
import { useGetClaimableAmount } from "@tokenops/sdk/fhe-vesting/react";
function ClaimableAmountButton({
managerAddress,
vestingId,
}: {
managerAddress: `0x${string}`;
vestingId: `0x${string}`;
}) {
const zamaSDK = useZamaSDK();
const getClaimable = useGetClaimableAmount({ address: managerAddress });
async function handleReveal() {
const { handle } = await getClaimable.mutateAsync({ vestingId });
// Pass handle to zamaSDK.relayer.userDecrypt to read the plaintext
// See https://docs.zama.ai/protocol/relayer-sdk-guides/fhevm-relayer/user-decrypt
console.log("encrypted claimable handle:", handle);
}
return (
<button onClick={handleReveal} disabled={getClaimable.isPending}>
Reveal claimable amount
</button>
);
}
The same pattern applies to all nine encrypted-view hooks. None can be swapped for useQuery — simulation diverges from execution on encrypted handles.
Address override
Factory hooks resolve the Sepolia factory address automatically. Manager hooks always require address:
// Factory hook — address resolves automatically on Sepolia
const defaultGasFee = useFactoryDefaultGasFee();
// Factory hook — address override for a custom deployment
const customFee = useFactoryCustomFee({ address: "0xMyFactory", creator: account });
// Manager hook — address is always required
const token = useManagerToken({ address: managerAddress });
Encryptor source
The following mutation hooks require an encryptor to be available at call time:
useCreateVesting, useBatchCreateVesting, usePartialClaim, useAdminPartialClaim, useSplitVesting, useWithdrawAdmin, useWithdrawTokenFee
Pass encryptor at hook level in ManagerHookOptions:
const zamaSDK = useZamaSDK();
const create = useCreateVesting({
address: managerAddress,
encryptor: () => zamaSDK.relayer, // lazy, called per-encryption
});
If encryptor is absent at both hook level and call time, the SDK throws: Missing encryptor, pass encryptor to the hook config or to the mutation args.
Query keys
The key shape for every hook in this subpath:
["tokenops-sdk", "fhe-vesting", "<methodName>", chainId, address?.toLowerCase(), ...primitiveArgs]
Invalidate everything for a manager after a write:
queryClient.invalidateQueries({ queryKey: ["tokenops-sdk", "fhe-vesting"] });
Narrow to a specific method and address:
queryClient.invalidateQueries({
queryKey: ["tokenops-sdk", "fhe-vesting", "manager:token", chainId, managerAddress.toLowerCase()],
});
All read hooks ship with staleTime: 0 — chain state is treated as live, so a focus-revalidation re-fires the underlying RPC call.
Read-after-write on load-balanced RPCs
Read hooks issue plain eth_calls against the wagmi public client. On providers that fan reads across read-replicas (Alchemy, Infura), a query that fires immediately after a write receipt confirms can return stale state.
This is an RPC-layer artefact, not an SDK bug. For post-mutation state, prefer parsing the relevant event from the write receipt (VestingCreated, VestingTransferInitiated, etc.) and using that as the immediate truth; the cached query will catch up on the next render once the replica converges.