2.0 RC docsView 1.x docs
v2.0.0-rc.1 release candidate
The API is frozen and later candidates carry fixes only, except the receipt-free and Safe create surface of /fhe-airdrop, which is @beta. Published on the next dist-tag; latest stays on 1.6.0 until 2.0.0.
WriteuseMutationencrypts input

useBuildMerkleCampaign

Encrypt a roster and build the Merkle tree those ciphertexts belong to, for an instance that already exists — the mutable-root path.

Import
@tokenops/sdk/fhe-airdrop/react
Return
{ mutate, mutateAsync, isPending, error, data }
Lifecycle
Write

Description

Encrypt a roster and build the Merkle tree those ciphertexts belong to, for an instance that already exists — the mutable-root path. Publish the returned root with useSetMerkleRoot, or let useRotateMerkleRoot build and publish in one call. For an instance whose root is fixed at create time use usePlanMerkleCampaign: there is no address to encrypt against yet.

The returned `entries` ARE the campaign — persist them before the component unmounts. The instance stores only the 32-byte root; there is no getter and no event that maps an account to its handle, input proof or Merkle branch. A recipient who does not hold their own (handle, inputProof, merkleProof) triple can never claim, and no on-chain data reconstructs it. Losing this result to a re-render is the likeliest way to brick a campaign.

A mutation rather than a query: encryption is randomised, so the same roster builds a different root every call and there is no cache identity to key.

Each entry is { account, handle, inputProof, merkleProof } - account selects the leaf and the cumulative accounting. One relayer request per 32 recipients, issued concurrently; every leaf is submittable by any address.

Invalidates: nothing. Off-chain work only; the root is not published here.

Signature

@tokenops/sdk/fhe-airdrop/react
ts
function useBuildMerkleCampaign(options?: UseBuildMerkleCampaignOptions): UseMutationResult<BuiltCampaign, Error, BuildMerkleCampaignVariables>;

Parameters

Shape of the object you pass to .mutate(args).

PropertyTypeDescription
encryptorEncryptorSourceEager or lazy encryptor. Wire it lazily so the live React context is read at submit time rather than captured at mount: const sdk = useZamaSDK() once, then encryptor: () => sdk.

Run it

Connect your wallet and click Build and publish a root — this dispatches a real Sepolia transaction through the same runner the stories use.

Interactive · live Sepolia tx
Loading editor…
Edit any input above, then press ⌘↵ to run.
Console0 lines
No output yet. Run the snippet to see logs stream in.

Example

@tokenops/sdk/fhe-airdrop/react · @example
tsx
const build = useBuildMerkleCampaign({ encryptor: () => sdk });
const { root, entries } = await build.mutateAsync({ instance: airdrop, recipients });
await persist(entries); // before anything else

Pulled directly from the hook's TSDoc block — the same snippet your IDE shows on hover.

Errors

This mutation can reject with SDK-level, product-level, or generic-fallback errors. Product classes carry the offending value as fields — render them inline instead of a generic "transaction failed." See Airdrop v2 › Errors for the per-class recovery table.

Invalidation recipe

After this mutation succeeds, invalidate the queries it affects so consumer UI re-fetches fresh state. The SDK never auto-invalidates — that's a consumer decision (different apps cache different shapes).

patterns/invalidation.ts
ts
import { useQueryClient } from "@tanstack/react-query";

const queryClient = useQueryClient();
const buildMerkleCampaign = useBuildMerkleCampaign(/* options */);

buildMerkleCampaign.mutate(args, {
  onSuccess() {
    // Coarse invalidation: refresh every cached read on this product surface.
    queryClient.invalidateQueries({
      queryKey: ["tokenops-sdk", "fhe-airdrop"],
    });
  },
});

See also

Other Write hooks in airdrop v2: