2.0 RC docsView 1.x docs
Recipe · Claim as the recipient

Claim as the recipient

Check your leaf, claim it, and optionally redirect or unwrap - only you can do the last two.

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.

Check, then claim#

Your entry is everything the claim submits: the encrypted amount handle, its input proof, and the Merkle proof for your leaf. Because anyone may submit an entry, someone may already have claimed it for you - check getClaimedAmount first. A leaf that was already settled pays an encrypted zero and still costs the fee.

claim.ts
ts
import { createMerkleAirdropClient } from "@tokenops/sdk/fhe-airdrop";
import { isEncryptedValueZero } from "@zama-fhe/sdk";

const merkle = createMerkleAirdropClient({ publicClient, walletClient, address: airdrop });

// entry is yours: { account, handle, inputProof, merkleProof }, sent to you
// by the campaign operator. account must be your address.
const delivered = await merkle.getClaimedAmount(entry.account);

if (!isEncryptedValueZero(delivered)) {
  // Something was already delivered to you - on this root or an earlier one.
  // Decrypt it and compare with your entry's cumulative total (the operator
  // tells you that number off-chain): equal means this leaf is settled,
  // lower means a top-up from a rotated root is still outstanding.
  // zama is the recipient's ZamaSDK (built with their signer).
  const values = await zama.decryption.decryptValues([
    { encryptedValue: delivered, contractAddress: airdrop },
  ]);
  const paid = values[delivered];
  if (BigInt(paid) >= myCumulativeTotal) return;
}

await merkle.claim({ entry }); // paid to entry.account

The zero handle answers only "has anything ever been delivered to this account". Amounts are cumulative and the delivered total is kept per account across roots, so after the operator rotates to a new root with a higher total for you, the handle is non-zero and a claim still pays the increase. That is why a non-zero answer is decrypted and compared rather than treated as "done".

Send the payout somewhere else#

to defaults to entry.account. Only you, as that account, may set it to a different address.

redirect.ts
ts
// Only you may redirect your own payout. Anyone else passing `to`
// is refused with UnauthorizedRedirectError before the transaction is sent.
await merkle.claim({ entry, to: vault });

Unwrap to the underlying ERC-20#

claimAndUnwrap settles the leaf and starts an unwrap in one transaction. The unwrapped amount becomes public inside that claim transaction - the emitted unwrapRequestId is its handle - not at finalizeUnwrap, which only releases the ERC-20. Only a plain claim keeps the amount confidential.

unwrap.ts
ts
// Self-only: claimAndUnwrap takes no claim identity, so the sender is the
// account. The campaign must have been created with unwrappable: true.
const hash = await merkle.claimAndUnwrap({ entry });
await publicClient.waitForTransactionReceipt({ hash });

// The claim starts an unwrap on the wrapper token; finalize it to release
// the underlying ERC-20.
const { unwrapRequestId } = await merkle.readUnwrapRequest(hash);
await zama.createWrappedToken(wrapper).finalizeUnwrap(unwrapRequestId);

See also