Skip to main content

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

FamilyHooks
Factory readsuseConfidentialVestingFactoryImplementation, useFactoryDefaultGasFee, useFactoryDefaultTokenFee, useFactoryDefaultFeeType, useFactoryFeeCollector, useFactoryCustomFee, usePredictManagerAddress, useFactoryInitCodeHash
Factory writesuseCreateManager, useCreateManagerAndGetAddress, useSetDefaultGasFee, useSetDefaultTokenFee, useSetDefaultFeeType, useResetGasFee, useResetTokenFee, useSetCustomFee, useDisableCustomFee, useSetFeeCollector
Manager readsuseManagerToken, useManagerFeeType, useManagerFee, useManagerFeeInfo, useManagerDeploymentBlockNumber, useManagerIsSplitEnabled, useManagerIsPausable, useManagerPaused, useManagerMaxBatchSize, useManagerMaxRevokeBatchSize, useVestingInfo, useAllRecipients, useIsRecipient, useRecipientVestings, usePendingVestingTransfer, useHasRole, useRoleConstants
Manager encrypted viewsuseGetVestedAmount, useGetClaimableAmount, useGetTotalAllocation, useGetSettledAmount, useAdminGetVestedAmount, useAdminGetClaimableAmount, useAdminGetTotalAllocation, useAdminGetSettledAmount, useAdminGetTokenBalance
Manager writesuseCreateVesting, 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.