Airdrop · Flows

The five airdrop flows.

Confidential cohort airdrops collapse to these five paths, each with hooks, a recipe, and a demo story chapter.

Prerequisite: operator approval

Before creating, funding, or dispersing, the token must approve this contract as an operator: call token.setOperator(<contract address>, deadline) first. Without it the transaction reverts with ERC7984UnauthorizedSpender (0x79f2cb38).

The SDK surfaces the missing approval as: “The token has not approved this contract as an operator. Call setOperator() (see /fhe operators) before this transaction.

Do not route these calls through Multicall3. aggregate3 makes the Multicall3 contract the msg.sender, which breaks operator approvals and per-user clone authorization. Use the built-in batch functions instead: batchCreateVesting, disperse, batchDiscloseToParty.

  1. 1

    Deploy a clone

    Operator calls the factory to mint a per-cohort airdrop clone. Clone holds the encrypted cohort balance + the claim signature registry.

    1. factory.createConfidentialAirdrop({ params, userSalt }) where params = { token, startTimestamp, endTimestamp, canExtendClaimWindow, admin }, CREATE2 deterministic
    2. Receipt's ConfidentialAirdropCreated event carries the clone address
    3. params.admin gets DEFAULT_ADMIN_ROLE; the factory's feeCollector gets FEE_COLLECTOR_ROLE
  2. 2

    Fund the pot

    Operator transfers encrypted tokens into the clone. The pot's plaintext total stays opaque; only per-recipient ACL on claim reveals fragments.

    1. Operator's wallet calls setOperator on the source token (one-time per chain)
    2. useFundConfidentialAirdrop().mutate({ token, params, userSalt, deployer, gasFee, amount }) — the full FundAirdropArgs; SDK encrypts amount, submits transfer
    3. Receipt's ConfidentialAirdropFunded event flagged with the clone address
  3. 3

    Sign claim authorizations

    An address with DEFAULT_ADMIN_ROLE signs an EIP-712 typed message per recipient. The signature is portable, distribute via email, QR, link, anything. No on-chain registration step.

    1. Off-chain: build the Claim struct {recipient, encryptedAmount}
    2. useSignClaimAuthorization signs over the typed-data domain
    3. Ship the signature + handle to the recipient
  4. 4

    Recipient claims

    Recipient submits the signature + the encrypted-amount handle. Clone verifies signer ∈ DEFAULT_ADMIN_ROLE, marks the signature claimed, transfers.

    1. Recipient calls useClaim with the signature payload
    2. Clone ECDSA.recovers the signer, checks role, checks no double-claim
    3. Atomic confidential transfer to recipient (FHE.allowTransient to the token + confidentialTransfer); decrypt access comes from the separate getClaimAmount write
  5. 5

    Admin recovery + roles

    Drain the clone's full confidential token balance back to the operator (callable any time by DEFAULT_ADMIN_ROLE), withdraw collected gas fees, sweep accidentally sent tokens, and manage role membership.

    1. useWithdraw (DEFAULT_ADMIN_ROLE) drains the entire confidential balance to a recipient
    2. useWithdrawOtherToken / useWithdrawOtherConfidentialToken to sweep accidentally sent tokens
    3. useAirdropGrantRole / useAirdropRevokeRole for admin or fee-collector rotation
    4. Pausable surface via useSetPaused (DEFAULT_ADMIN_ROLE) if a fault is detected