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

usePlanMerkleCampaign

Build a campaign for a Merkle instance that does not exist yet, so its root can be baked in at create time — the immutable-root path.

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

Description

Build a campaign for a Merkle instance that does not exist yet, so its root can be baked in at create time — the immutable-root path.

That path is otherwise circular: the root must be known before createMerkleAirdrop, the root depends on every ciphertext, and every ciphertext binds to an instance address the create has not produced. This hook breaks it with the factory's CREATE2 prediction oracle, then feeds root to useCreateMerkleAirdrop (or useCreateAndFundMerkleAirdrop) with the SAME mode, creator and userSalt.

Pass the returned plan to that create as plan (and to usePreflightCreateAirdrop). The entries are bound to plan.predictedAddress, so the create pins it, checks the root, and refuses to send once the factory's Merkle implementation has moved since planning (PredictionDriftError). expected: { airdrop: plan.predictedAddress } is the lower-level equivalent of the pin alone.

Surface `PredictionDriftError`, never swallow it. It means the factory's Merkle implementation pointer moved mid-build, so every entry is bound to an address the create will not produce. There is no on-chain repair on an immutable-root instance — the only remedy is a fresh plan.

The returned `entries` ARE the campaign — persist them. The chain keeps only the root, so a recipient without their own (handle, inputProof, merkleProof) triple can never claim. Also pick a fresh userSalt per campaign: two campaigns sharing (variant, mode, creator, userSalt) predict the same address however different their params.

One relayer request per 32 recipients, issued concurrently; every leaf is submittable by any address.

Invalidates: nothing. Two init-code-hash reads and a prediction — no state moves until the create lands.

Signature

@tokenops/sdk/fhe-airdrop/react
ts
function usePlanMerkleCampaign(options?: AirdropClientOptions): UseMutationResult<PlannedCampaign, Error, PlanMerkleCampaignVariables>;

Parameters

Shape of the object you pass to .mutate(args) is the SDK type PlanMerkleCampaignVariables. Inspect the type for the full shape (discriminated unions collapse to a tagged variant at call time).

Want to run a similar shape interactively? The Playground ships 12 ready presets across vesting / airdrop / disperse / faucet — deploy a manager, create a vesting, claim, and run the product equivalents. The deep-link above auto-selects the closest preset to usePlanMerkleCampaign; pick another from the dropdown if you'd rather start there.

Example

@tokenops/sdk/fhe-airdrop/react · @example
tsx
const plan = usePlanMerkleCampaign({ encryptor: () => sdk });
const create = useCreateMerkleAirdrop();
const planned = await plan.mutateAsync({
  params, mode: "clone", creator, userSalt, recipients,
});
await create.mutateAsync({
  params: { ...params, merkleRoot: planned.root, isMerkleRootMutable: false },
  mode: "clone",
  userSalt,
  plan: planned,
});

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 planMerkleCampaign = usePlanMerkleCampaign(/* options */);

planMerkleCampaign.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: