Why preflight#
Most reverts are knowable from off-chain state: insufficient balance, operator not approved, paused state, invalid recipient. Each one is a wallet-confirm tax on the user if you skip the check. Preflight runs them all in one round-trip and returns either { ok: true } or a list of structured reasons.
The result shape#
PreflightResult is a discriminated union:
{ ok: true, gasEstimate: bigint }: clear to send{ ok: false, reasons: PreflightReason[] }: blockers
Each PreflightReason carries code, severity, hint, and (when relevant) the offending value — so your UI can render a specific message instead of a generic "transaction failed."
When to run#
import { usePreflightDisperse } from "@tokenops/sdk/fhe-disperse/react";
function DisperseButton({ recipients, amounts }) {
const preflight = usePreflightDisperse({ recipients, amounts });
if (preflight.isPending) return <span>Checking…</span>;
if (preflight.data && !preflight.data.ok) {
return (
<div role="alert">
{preflight.data.reasons.map((r) => (
<p key={r.code}>{r.hint}</p>
))}
</div>
);
}
return <button>All clear — disperse</button>;
}Where preflight ships today#
fhe-disperse: usePreflightDisperse checks operator approval on both subwallets, ERC-7984 balance, paused state, and per-recipient input proof shape.
Other products use focused checks rather than a single preflight hook — see useManagerFeeInfo before a claim, useAirdropIsSignatureValid before an airdrop claim, and similar.