Disperse 2.0 type surface.
Every receipt-free write has a mined result and a Pending* twin that share one pending discriminant. Args that take an object accept a per-call gas; the preflight report carries typed blockerErrors only.
Args· 9
DisperseArgs{ token, mode, recipients, amounts, gasFeeOverride?, account?, gas? }Plaintext amounts in token base units; the SDK encrypts them. gasFeeOverride, when set, must equal recipients.length * gasFeeWei: the contract reverts on any other ETH value. At most 32 recipients in "direct", 30 in the wallet modes. disperse() takes DisperseArgs & ReceiptMode.
DisperseMode"wallet" | "wallet-token-fee" | "direct"Picks the entry point: subwallets with a per-recipient ETH fee, subwallets with a BPS token fee on the subtotals, or a direct confidentialTransferFrom with the ETH fee.
RegisterArgs{ token, account?, gas? }Token the new subwallets are approved for. register() takes RegisterArgs & ReceiptMode.
PreflightDisperseArgs{ user, token, recipients, amounts, mode }Same units and mode as the disperse you are about to send, so the report reflects the real call.
CalculateFeeArgs{ user, mode, recipients: number, totalTokens? }totalTokens is required for "wallet-token-fee".
DiscloseHandleArgs{ handle, party, account?, gas? }Grant persistent ACL on one handle the caller already holds. Append-only.
BatchDiscloseHandlesArgs{ handles, party, account?, gas? }The same grant on many handles in one transaction.
GetEncryptedFeeReserveArgs{ token, account?, gas? }Args of getEncryptedFeeReserve and useAccessEncryptedFeeReserve.
WithdrawTokenFeeArgs{ token, to, account?, gas? } & ({ amount, encryptor? } | { encryptedInput })Exactly one of a plaintext amount or a pre-built encryptedInput; passing both fails to compile. Build the input with encryptUint64 (or encryptUint64Batch) from @tokenops/sdk/fhe-disperse, which returns an EncryptedInput.
Results· 10
RegisterResult{ hash, wallets: [Address, Address] }Mined register(). wallets comes from the caller's own UserRegistered event.
PendingRegisterResult{ hash, wallets: undefined, pending: true }register() with waitForReceipt: false. From a Safe, hash is the safeTxHash.
DisperseResult{ hash, distributions: DisperseDistribution[] }Mined disperse(). Never empty: a receipt without a distribution event throws ReceiptEventNotFoundError.
PendingDisperseResult{ hash, distributions: undefined, pending: true }disperse() with waitForReceipt: false.
DisperseDistribution{ wallet?, recipients, requested, transferred }One row per WalletDistribution (up to two) or DirectDistribution. Decrypt requested with the singleton as contract, transferred with the token.
WithdrawTokenFeeResult{ hash, transferredHandle: Hex }Always a Hex on a mined result; may be less than requested because the contract caps at the reserve.
PendingWithdrawTokenFeeResult{ hash, transferredHandle: undefined, pending: true }withdrawTokenFee() with waitForReceipt: false.
DisclosureResult{ hash, discloser, party, disclosedHandles }Parsed from HandlesDisclosedToParty. Also exported from /fhe-disperse/react as the data type of both disclosure hooks.
PendingDisclosureResult{ hash, discloser: undefined, party: undefined, disclosedHandles: undefined, pending: true }Either disclosure with waitForReceipt: false.
EncryptedViewResult{ hash, handle }Returned by getEncryptedFeeReserve / useAccessEncryptedFeeReserve. Pass handle to a user-decrypt.
Preflight· 4
PreflightReport{ isUserRegistered, wallets, predictedWallets, hasApprovedSubwallets, hasApprovedSingleton: boolean, feeEth, feeTokenAmount?, batchLimit, batchOk, recipientChecks, amountsOk, subtotals?, ready, blockerErrors }hasApprovedSingleton is checked in every mode. batchOk is false over the on-chain cap or one input proof. Branch on each blockerErrors entry's code.
RecipientCheck{ address, ok, reason? }Per-recipient structural check (zero address and similar).
SubwalletApprovalState{ wallet0, wallet1, both }Returned by hasApprovedSubwallets; mirrored on PreflightReport.hasApprovedSubwallets.
PreflightResult{ ready, blockers: TokenOpsSdkError[] }The shared preflight contract from core, re-exported here. The disperse report names the same list blockerErrors.
Fees + config· 6
ConfidentialDisperseClientConfig{ publicClient, walletClient?, address?, chainId?, encryptor?, aclAddress?, gasHeadroomPercent?, telemetry? }Client constructor. chainId also picks the FHEVM ACL address.
FeeConfig{ gasFeeEnabled, tokenFeeEnabled, defaultGasFee, defaultTokenFee }Round-trips between getFeeConfig() and setFeeConfig().
UserFees{ isCustom, gasFeeWei, tokenFeeBps, gasFeeEnabled, tokenFeeEnabled }Resolved per-user fees with the global toggles applied.
CustomFee{ enabled, gasFeeWei, tokenFeeBps }Raw per-user override, before the global toggles.
CalculatedFee{ ethValue, tokenFeeAmount? }ethValue covers the gas fee; tokenFeeAmount is set only for "wallet-token-fee".
BatchLimits{ holding, direct, tokenFee }On-chain per-mode caps. 0n means no cap; the one-input-proof limit applies regardless.
Receipts, gas + hook options· 6
ReceiptModeReceiptMode<W>: { waitForReceipt: false } when W is false, otherwise { waitForReceipt?: W }The option kept off the *Args types. The default ReceiptMode is { waitForReceipt?: boolean }; Args & ReceiptMode with a runtime boolean gets the Mined | Pending union, narrowed by pending.
Concept primer →PendingWrite / MinedWrite{ pending: true } / { pending?: never }Every Pending* result extends PendingWrite and every mined result MinedWrite, so if (result.pending) narrows any of them.
GasOverride{ gas?: bigint }Per-call gas limit, sent as is instead of the padded estimate.
Concept primer →GasHeadroomOption{ gasHeadroomPercent?: number }Percent added to every estimate. Defaults to DEFAULT_GAS_HEADROOM_PERCENT (25).
Concept primer →DisperseHookOptionsBaseHookOptions & { encryptor?, aclAddress?, gasHeadroomPercent? }Options of the write hooks. Import from @tokenops/sdk/fhe-disperse/react, not the root subpath. BaseHookOptions is { address?, chainId?, telemetry? } and is exported there too.
ReadHookQueryOptions{ query?: Omit<UseQueryOptions, 'queryKey' | 'queryFn'> }query on every read hook. Import from @tokenops/sdk/fhe-disperse/react, not the root subpath. query.enabled can turn a ready query off, never an unready one on.
Concept primer →