ConfidentialAirdropFactoryClient
One factory serves both claim variants. createEcdsaAirdrop/createMerkleAirdrop take a CommonAirdropParams (token, startTime, endTime, canExtendClaimWindow, unwrappable, complianceAdmin, maxAcceptedGasFee) with no admin field - the factory injects admin = msg.sender, and every create returns { hash, airdrop, complianceManager, managerImplementation, complianceDelegate }. Every create commits to the instance address, the compliance-manager implementation and the compliance delegate, and the factory reverts before consuming the salt if any of them drifted. Everything below setup and prediction is protocol-operator surface: fee policy, implementation pointers, compliance-wiring policy, and upgradeability policy, none of it existed on the v1 factory. The admin setters are positional and take an optional account then gas; gasHeadroomPercent on the config pads every write. The factory address resolves from DEPLOYED_ADDRESSES on mainnet and Sepolia.
Construct
Headless TS — non-React consumers (Node, Vite, server workers). React hosts use the per-hook surface; same method names, lazy encryptor.
import { createConfidentialAirdropFactoryClient } from "@tokenops/sdk/fhe-airdrop";
const client = createConfidentialAirdropFactoryClient({
publicClient,
walletClient,
// address optional: resolved from publicClient.chain.id
});Methods
Setup · 7
- Deploy a signature-authorized campaign clone. Quotes the three create commitments at send time unless pinned with expected, and throws CreateCommitmentMismatchError (context.field: airdrop | complianceManagerImpl | complianceDelegate) on drift. A resolved fee above the factory's maxGasFee ceiling is reported before the creator's own maxAcceptedGasFee. Refuses before sending what preflightCreate blocks: a zero token or signer, an endTime less than 60 seconds past the latest block, an invalid window. Pass waitForReceipt: false for a Safe signer (@beta): it returns a PendingCreateAirdropResult (pending: true) with every address taken from the commitments. A mined receipt that lacks or repeats ConfidentialAirdropCreated / ComplianceManagerCloned throws ReceiptEventNotFoundError / ReceiptEventAmbiguousError.
client.createEcdsaAirdrop()React:useCreateEcdsaAirdrop - Deploy a Merkle-proof campaign clone. Same commitments, fee checks and receipt-free option as createEcdsaAirdrop. Takes plan?: PlannedCampaign - it pins expected.airdrop to plan.predictedAddress, requires params.merkleRoot === plan.root (InvalidArgumentError before any RPC), and refuses the send with PredictionDriftError once the Merkle init-code hash has moved.
client.createMerkleAirdrop()React:useCreateMerkleAirdrop - createEcdsaAirdrop plus an atomic fund transfer in one call. Same preflight refusals and waitForReceipt option.
client.createAndFundEcdsaAirdrop()React:useCreateAndFundEcdsaAirdrop - createMerkleAirdrop plus an atomic fund transfer in one call. Takes the same plan and waitForReceipt options.
client.createAndFundMerkleAirdrop()React:useCreateAndFundMerkleAirdrop - Move a plaintext amount or a pre-encrypted input into any existing instance's pool. Pulls the token the factory recorded at create (tokenOf), not one you name.
client.fundAirdrop()React:useFundAirdrop client.quoteCreateCommitments()The CreateCommitments a create would be sent with, for { variant, params, mode, deployer, userSalt, expected? }. preflightCreate returns the same value as commitments.client.resolveComplianceDelegate()The compliance delegate a create from creator would commit to, folding a creator policy over the factory default.
Introspection · 5
client.predictEcdsaAirdropAddress()CREATE2 address an ECDSA create would deploy to, given the same params, mode, deployer, and userSalt.client.predictMerkleAirdropAddress()CREATE2 address a Merkle create would deploy to.client.getEcdsaInitCodeHash()Init-code hash behind an ECDSA prediction - read before and after a campaign build to detect an implementation swap mid-flight.client.getMerkleInitCodeHash()Init-code hash behind a Merkle prediction, same drift-check role.client.predictComplianceManagerClone()Free function from @tokenops/sdk/fhe-airdrop/advanced: the compliance clone address the factory deploys for { factory, managerImplementation, airdrop }.
Configure · 15
client.setFeeCollector()Set the account seeded with FEE_COLLECTOR_ROLE on instances created after this call. Existing instances keep their collectors, and the fee goes to whatever recipient withdrawGasFee names. FEE_MANAGER_ROLE.client.setDefaultGasFee()Set the chain-wide default per-claim ETH fee new instances are created with. FEE_MANAGER_ROLE.client.setCustomFee()Override the default fee for one creator. FEE_MANAGER_ROLE.client.disableCustomFee()Clear a creator's fee override, falling back to the default. FEE_MANAGER_ROLE.client.setMaxGasFee()Set the ceiling every configurable fee and every create-time resolved fee must sit under. DEFAULT_ADMIN_ROLE, deliberately not FEE_MANAGER_ROLE - the fee manager moves fees only inside a range the admin owns. Lowering it below a configured fee succeeds and does not rewrite that fee: the stale fee stays in storage and the next create reverts GasFeeExceedsMaximum (InvalidArgumentError on gasFee), so lower the configured fees in the same operation and read them back.client.setEcdsaImplementation()Point future ECDSA clones at a new implementation. IMPL_MANAGER_ROLE.client.setMerkleImplementation()Point future Merkle clones at a new implementation. IMPL_MANAGER_ROLE.client.setComplianceManagerImpl()Point future per-instance compliance clones at a new implementation. COMPLIANCE_WIRING_ROLE.client.setComplianceDelegate()Rotate the platform delegate wired into future instances' compliance clones. The constructor already seeds a non-zero delegate with compliance ON, so there is no post-deploy wiring step; this rotates but can never clear it. COMPLIANCE_WIRING_ROLE.client.setDefaultDelegateToCompliance()Chain-wide default for whether new instances wire in the platform delegate. COMPLIANCE_WIRING_ROLE.client.setCompliancePolicy()Override the default delegate-to-compliance policy for one creator. COMPLIANCE_WIRING_ROLE.client.clearCompliancePolicy()Clear a creator's compliance-policy override, falling back to the default. COMPLIANCE_WIRING_ROLE.client.setDefaultUpgradeable()Chain-wide default for whether creators may deploy mode: "uups" instances. UPGRADE_MANAGER_ROLE.client.setUpgradeabilityPolicy()Override the UUPS-allowed policy for one creator. UPGRADE_MANAGER_ROLE.client.clearUpgradeabilityPolicy()Clear a creator's upgradeability override, falling back to the default. UPGRADE_MANAGER_ROLE.
Read · 20
client.getCustomFee()Read one creator's fee override, { enabled, gasFee }.- Current fee-collector address. Bundled with defaultGasFee in useFactoryFees.
client.feeCollector()React:useFactoryFees - Current chain-wide default per-claim ETH fee. Bundled with feeCollector in useFactoryFees.
client.defaultGasFee()React:useFactoryFees - The factory's ceiling on every configurable fee and every create-time resolved fee. Bundled with feeCollector and defaultGasFee in useFactoryFees.
client.maxGasFee()React:useFactoryFees - The fee a create* from this creator would freeze in right now - their enabled override, else the default. This is the number CommonAirdropParams.maxAcceptedGasFee is checked against, so it is what to show a creator before they choose a bound. A snapshot, not a quote: FEE_MANAGER_ROLE can move it between this read and the create, which is what the bound exists to catch.
client.resolveGasFee()React:useResolveGasFee client.ecdsaImplementation()ECDSA implementation pointer. Bundled with the other two in useFactoryImplementations.client.merkleImplementation()Merkle implementation pointer, same bundle.client.complianceManagerImpl()Compliance-manager implementation pointer, same bundle.client.complianceDelegate()The platform delegate wired into future clones (distinct from a per-clone ComplianceManagerClient.complianceDelegate()).client.getCompliancePolicy()Admin view of one creator's compliance-policy override plus the resolved effective value.- Whether a creator's next create* will wire the platform delegate - the cheap creator-facing read.
client.effectiveDelegateToCompliance()React:useEffectiveDelegateToCompliance client.defaultUpgradeable()Chain-wide default for mode: "uups" creates. Bundled into the admin-panel useFactoryUpgradeabilityPolicy read.client.getUpgradeabilityPolicy()One creator's upgradeability override plus the resolved effective value, same bundle.- Whether a creator's next create* may use mode: "uups" - the cheap creator-facing read a create form checks before offering it.
client.effectiveUpgradeable()React:useEffectiveUpgradeable - Total instances deployed by this factory. Bundled with airdrops in useFactoryRegistry.
client.airdropCount()React:useFactoryRegistry - One deployed instance address by registry index.
client.airdropAt()React:useFactoryRegistry - Paged list of deployed instance addresses; clamps to the tail rather than reverting on an out-of-range offset.
client.airdrops()React:useFactoryRegistry - Whether THIS factory created the candidate address - the genuineness check. Nothing observable at an instance proves which factory deployed it, and a claim against a look-alike succeeds and delivers nothing rather than reverting, so address this read to a factory from the SDK's own registry, never one the instance names. preflightClaim folds it in when you pass a factory.
client.isAirdrop()React:useIsAirdrop - Resolve an instance's compliance-manager clone address. Zero address means not deployed by this factory - a weaker provenance signal than isAirdrop. A zero answer is never cached, so it updates once the create lands.
client.complianceManagerOf()React:useComplianceManagerOf - The token the factory recorded for an instance at create - the one fundAirdrop pulls. Zero for an address this factory did not create.
client.tokenOf()React:useTokenOf
Roles · RBAC · 13
client.FEE_MANAGER_ROLE()Role bytes32 for fee administration. Bundled with the other four factory role constants.client.IMPL_MANAGER_ROLE()Role bytes32 for implementation-pointer administration.client.COMPLIANCE_WIRING_ROLE()Role bytes32 for compliance-wiring policy administration.client.UPGRADE_MANAGER_ROLE()Role bytes32 for upgradeability-policy administration.client.DEFAULT_ADMIN_ROLE()OpenZeppelin's default admin role - administers all four operational roles above.client.hasRole()Check whether an address holds a given factory role.client.getRoleAdmin()Which role administers a given role. Always DEFAULT_ADMIN_ROLE for all five factory roles - the factory has no self-administered role.client.getRoleMemberCount()Number of holders of a role. Bundled with member enumeration in useFactoryRoleMembers.client.getRoleMember()One role holder by enumeration index.client.getRoleMembers()Every current holder of a role - the only way to answer who holds this, no roleHolders() view exists.client.grantRole()Grant a factory role to an address. DEFAULT_ADMIN_ROLE.client.revokeRole()Revoke a factory role from an address. The factory floors DEFAULT_ADMIN_ROLE at one member: revoking the sole holder reverts LastAdmin. Hand over by granting the successor first.client.renounceRole()Give up a role yourself. The same LastAdmin floor applies: the sole DEFAULT_ADMIN_ROLE holder cannot renounce, so grant the successor first, then renounce.