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.
Shared base surface documented on EcdsaAirdropClient
MerkleAirdropClient extends the same AirdropBaseClient as the ECDSA variant: pause, extendClaimWindow, withdrawConfidential, rescueERC20, refreshComplianceBalance, the balance disclosure methods, and the full roles surface all apply here too. See EcdsaAirdropClient for that full listing rather than repeating it here.
Airdrop v2 · Client@tokenops/sdk/fhe-airdrop

MerkleAirdropClient

Extends AirdropBaseClient with no extra config field - dedup/signer concepts don't apply to proof-authorized claims. A leaf commits (instance, account, handle), where handle encrypts the account's CUMULATIVE allocation, never a per-drop tranche; a claim pays total minus whatever was already delivered.

Construct

Headless TS — non-React consumers (Node, Vite, server workers). React hosts use the per-hook surface; same method names, lazy encryptor.

@tokenops/sdk/fhe-airdrop
ts
import { createMerkleAirdropClient } from "@tokenops/sdk/fhe-airdrop";

const client = createMerkleAirdropClient({
  publicClient,
  walletClient,
  // address optional: resolved from publicClient.chain.id
});

Methods

Write · 2

Root rotation is a top-up, never a clawback

rotateMerkleRoot (from @tokenops/sdk/fhe-airdrop, not a client method) rebuilds the full campaign against a live MerkleAirdropClient and calls setMerkleRoot in one step, publishing the new root last so a failed rebuild never touches the live campaign. claimedAmount is keyed by account, not by root, so it survives rotation - a claim against a new root only pays the increase over what was already delivered, and a total lower than what a recipient already received pays nothing. One leaf per recipient per root: two leaves for the same recipient pay the max of the two totals, never their sum.

@tokenops/sdk/fhe-airdrop
ts
import { rotateMerkleRoot } from "@tokenops/sdk/fhe-airdrop";

const rotated = await rotateMerkleRoot({
  airdrop, // a MerkleAirdropClient
  recipients, // full updated roster - cumulative totals, not deltas
  encryptor,
});
// rotated.root is already published on-chain when this resolves

React callers use useRotateMerkleRoot. Building a campaign for an instance that doesn't exist yet (root baked into createMerkleAirdrop) goes through planMerkleCampaign instead - see ConfidentialAirdropFactoryClient for address prediction.