API Reference
Exports
| Export | Kind | Description |
|---|---|---|
ConfidentialAirdropFactoryClient | class | Typed wrapper around the deployed factory. Methods: createConfidentialAirdrop (rich-return), createAndFundConfidentialAirdrop (rich-return), fundConfidentialAirdrop, predictAirdropAddress, fee management |
createConfidentialAirdropFactoryClient | function | Convenience constructor |
ConfidentialAirdropFactoryClientConfig | interface | Config for the factory client |
CreateAirdropArgs | interface | Args for factory.createConfidentialAirdrop |
CreateAirdropResult | interface | { hash, airdrop } returned by factory.createConfidentialAirdrop / createAndFundConfidentialAirdrop |
CreateAndFundAirdropArgs | interface | Args for factory.createAndFundConfidentialAirdrop |
FundAirdropArgs | interface | Args for factory.fundConfidentialAirdrop |
PredictAirdropArgs | interface | Args for factory.predictAirdropAddress (stable across blocks — block.number is not packed into immutable args) |
ConfidentialAirdropClient | class | Typed wrapper around a deployed airdrop clone |
createConfidentialAirdropClient | function | Convenience constructor |
ConfidentialAirdropClientConfig | interface | Config for the airdrop client |
ClaimArgs | interface | Args for airdrop.claim |
GetClaimAmountArgs | interface | Args for airdrop.getClaimAmount |
EncryptedViewResult | interface | { handle: Hex, hash: Hex } returned by getClaimAmount |
signClaimAuthorization | function | Build the EIP-712 admin signature for a claim |
encryptUint64 | function | Encrypt a single uint64 to an externalEuint64 handle + input proof |
encryptUint64Batch | function | Encrypt N uint64 values under a single input proof |
resolveEncryptor | function | Normalize an EncryptorSource to the live Encryptor |
Encryptor | interface | Structural interface for Zama RelayerWeb / RelayerNode / mock |
EncryptorSource | type | Encryptor | (() => Encryptor | undefined) — eager or lazy |
EncryptUint64Args | interface | Args for encryptUint64 |
EncryptUint64BatchArgs | interface | Args for encryptUint64Batch |
FheValueInput | type | Discriminated union of FHE input types |
AirdropParams | interface | Struct passed to factory create methods |
CustomFee | interface | Per-creator fee override from factory.getCustomFee |
EncryptedInput | interface | Single { handle: Hex, inputProof: Hex } |
EncryptedInputs | interface | Batch { handles: Hex[], inputProof: Hex } |
confidentialAirdropFactoryAbi | const | ABI for the factory contract |
confidentialAirdropCloneableAbi | const | ABI for the airdrop clone |
API reference
ConfidentialAirdropFactoryClient
Wraps the deployed ConfidentialAirdropFactory. Read methods use publicClient; write methods require walletClient.
Constructor / factory function
new ConfidentialAirdropFactoryClient(config: ConfidentialAirdropFactoryClientConfig)
createConfidentialAirdropFactoryClient(config: ConfidentialAirdropFactoryClientConfig)
config.address overrides the on-chain factory address. Omit it once the factory is deployed on the target chain — the client resolves the address from publicClient.chain.id automatically.
Writes
| Method | Description |
|---|---|
createConfidentialAirdrop(args) | Deploy a new airdrop clone |
createAndFundConfidentialAirdrop(args) | Deploy and fund in a single tx; requires factory operator approval on token |
fundConfidentialAirdrop(args) | Fund an existing (or pre-deployed) clone; requires factory operator approval on token |
setFeeCollector(newFeeCollector) | Change the fee collector address (FEE_MANAGER_ROLE) |
setDefaultGasFee(newGasFee) | Change the default per-claim gas fee (FEE_MANAGER_ROLE) |
setCustomFee(campaignCreator, gasFee) | Set a per-creator gas fee override (FEE_MANAGER_ROLE) |
disableCustomFee(campaignCreator) | Remove a per-creator fee override (FEE_MANAGER_ROLE) |
Reads
| Method | Returns | Description |
|---|---|---|
predictAirdropAddress(args) | Address | Compute deterministic clone address without deploying. gasFee must match what the factory would charge at deploy time. |
implementation() | Address | Current LibClone implementation address |
defaultGasFee() | bigint | Default per-claim gas fee in wei |
feeCollector() | Address | Current fee collector address |
getCustomFee(campaignCreator) | CustomFee | Per-creator fee override |
getInitCodeHash(args) | Hex | Init-code hash for CREATE2 address derivation |
ConfidentialAirdropClient
Wraps a deployed ConfidentialAirdropCloneable clone. Requires address (the clone address) in config.
Constructor / factory function
new ConfidentialAirdropClient(config: ConfidentialAirdropClientConfig)
createConfidentialAirdropClient(config: ConfidentialAirdropClientConfig)
config.aclAddress defaults to the chain's known FHEVM ACL address. Override only on custom networks.
Claim
| Method | Description |
|---|---|
claim({ encryptedInput, signature, value?, account? }) | Claim tokens using an EIP-712 admin signature. Submits the admin-issued encryptedInput verbatim (no re-encryption — the signature commits to the handle). Sends GAS_FEE() as msg.value unless value is overridden. |
getClaimAmount({ encryptedInput, signature, account? }) | Write transaction. Verify allocation and grant caller ACL on the encrypted handle. Returns { handle, hash }. Pass handle to Zama's userDecrypt to read the plaintext. See note below. |
:::note getClaimAmount is a write transaction
It submits a tx that calls FHE.allow(handle, msg.sender) on-chain so the Zama relayer can authorize your userDecrypt request. The handle is extracted from the receipt's ACL Allowed event — never from simulateContract (simulation returns a divergent handle that has no ACL grant). See FHEVM docs for the userDecrypt flow.
:::
Status checks (free reads)
| Method | Returns | Description |
|---|---|---|
isClaimWindowActive() | boolean | true if within time window and not paused |
hasClaimStarted() | boolean | true if past START_TIME |
hasClaimEnded() | boolean | true if past endTime |
isSignatureValid({ encryptedAmountHandle, signature, caller }) | boolean | Returns false (not a revert) if already claimed, window inactive, or signer lacks admin role. Caller-specific (the on-chain check binds to msg.sender) — pass the address that will submit the claim. Use for pre-claim UI validation before paying gas. |
isSignatureClaimed(user, encryptedAmountHandle) | boolean | Frontend default. Checks whether a claim has been consumed given the user address and the encrypted handle from the claim payload. The contract recomputes the struct hash internally. |
claimedSignatures(signatureHash) | boolean | Low-level storage check. Takes a pre-computed EIP-712 struct hash (bytes32). Use isSignatureClaimed instead unless you already hold the hash. |
Admin writes
| Method | Description |
|---|---|
withdraw(recipient) | Withdraw all confidential tokens. DEFAULT_ADMIN_ROLE only. |
setPaused(paused) | Pause or unpause claims. DEFAULT_ADMIN_ROLE only. |
extendClaimWindow(newEndTime) | Extend end time. Requires CAN_EXTEND_CLAIM_WINDOW == true. DEFAULT_ADMIN_ROLE only. |
withdrawGasFee(recipient, amount) | Withdraw ETH gas fees. Pass 0n to drain the full balance. FEE_COLLECTOR_ROLE only. |
withdrawOtherToken(tokenAddress, recipient) | Rescue accidentally sent ERC-20. DEFAULT_ADMIN_ROLE only. |
withdrawOtherConfidentialToken(tokenAddress, recipient) | Rescue accidentally sent ERC-7984. DEFAULT_ADMIN_ROLE only. |
Role management
| Method | Description |
|---|---|
hasRole(role, account) | Check role membership |
grantRole(role, accountTarget) | Grant role (admin only) |
revokeRole(role, accountTarget) | Revoke role (admin only) |
Immutable reads
| Method | Returns | Description |
|---|---|---|
token() | Address | ERC-7984 token this airdrop was deployed for |
gasFee() | bigint | Gas fee in wei required per claim |
startTime() | number | Claim start timestamp (unix) |
canExtendClaimWindow() | boolean | Whether admin can extend the claim window |
endTime() | number | Claim end timestamp (unix, mutable) |
isPaused() | boolean | Current pause state |
deploymentBlockNumber() | bigint | Block at which the clone was deployed |
domainSeparator() | Hex | EIP-712 domain separator (for off-chain signing tools) |
signClaimAuthorization
Build an EIP-712 admin signature authorizing a specific recipient to claim a specific encrypted amount.
import { signClaimAuthorization, encryptUint64 } from "@tokenops/sdk/fhe-airdrop";
const recipient = "0x0000000000000000000000000000000000000001" as `0x${string}`;
// Bind the input proof to the RECIPIENT. Zama proofs commit to
// (contractAddress, userAddress) at encrypt time, and `FHE.fromExternal`
// rejects proofs bound to any other address.
const encrypted = await encryptUint64({
encryptor,
contractAddress: airdropAddress,
userAddress: recipient,
value: 1_000_000n,
});
const signature = await signClaimAuthorization({
walletClient: adminWalletClient, // must have DEFAULT_ADMIN_ROLE on the clone
airdropAddress,
recipient,
encryptedAmountHandle: encrypted.handle,
});
// Deliver { encryptedInput: encrypted, signature } to the recipient.
The signature covers Claim(address recipient, bytes32 encryptedAmount) under the clone's EIP-712 domain (name: "ConfidentialAirdrop", version: "1", verifyingContract: airdropAddress). Each (recipient, encryptedAmountHandle) pair is single-use.
FHE and ACL pitfalls
These pitfalls apply to this subpath. See CLAUDE.md for the full list.
getClaimAmount uses a write transaction, not simulation. The contract calls FHE.allow(handle, msg.sender) inside the tx. The handle value is determined by an on-chain counter at execution time — simulation runs against a snapshot that diverges from the executed counter, so the simulated handle never receives an ACL grant. The SDK extracts the correct handle from the receipt's ACL.Allowed event. Do not use publicClient.simulateContract as a substitute.
ACL grants are append-only. Once getClaimAmount grants a handle to an address, that access cannot be revoked. Design your UX accordingly — do not call getClaimAmount speculatively.
euint64 overflow. Plaintext amount values must fit in uint64 (max 2^64 - 1 ≈ 1.8 × 10^19). For ERC-7984 tokens (6 decimals), this is a maximum of about 1.8 × 10^13 tokens — far above any realistic allocation. The SDK validates this and throws if the value overflows.
For the full FHE ACL specification and userDecrypt flow, see docs.zama.ai/fhevm.