The four disperse flows.
The singleton + wallet-pair architecture collapses every disperse campaign to four paths, each demoed end-to-end.
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
Register (one-time per user)
Operator calls register(token) on the disperse singleton. Singleton deploys a deterministic wallet pair (wallet0 + wallet1) as ERC-1167 minimal-proxy clones via Clones.cloneDeterministic (OpenZeppelin): predictable addresses via Clones.predictDeterministicAddress, no per-recipient deploy.
- useRegister({ token }) — submits the registration tx
- Singleton clones WALLET_IMPLEMENTATION twice (wallet0 + wallet1) under deterministic salts
- useGetWallets({ user }) returns the deployed pair
- 2
Approve the wallet pair
ERC-7984 operator authorization is the pre-step for any flow that pulls funds. register() has the wallet pair approve the singleton as their operator for its token; wallet-mode disperses then pull the batch through wallet0/wallet1 at send time.
- setOperator (re-exported from @tokenops/sdk/fhe-disperse) for ERC-7984 operator grants
- useApproveTokenOnWallets({ token }) approves the registered pair for additional tokens
- Verify with useHasApprovedSubwallets({ user, token })
- 3
Disperse to N recipients
One call, N transfers. SDK builds the encrypted batch input proof; singleton verifies + atomically transfers to every recipient, splitting wallet-mode batches across wallet0/wallet1.
- usePreflightDisperse({ user, token, recipients, amounts, mode }) returns the full PreflightReport
- useDisperse.mutate({ token, mode, recipients, amounts }) encrypts the batch in one input proof, submits
- Receipt's WalletDistribution / DirectDistribution events list per-recipient handles
- 4
Recover + fee management
Pull funds back from a subwallet after a campaign closes; PAUSER_ROLE pause/unpause; fee withdrawal.
- useRecoverFromWallets({ token, to }) returns residual balance to the `to` address you specify
- Fee-collector flow: useWithdrawTokenFee / useWithdrawGasFee
- usePause / useUnpause for incident response