Concept · Preflight

Catch the obvious before paying gas

Preflight runs the bounded checks a contract revert would tell you about anyway — but cheaper, faster, and with structured hints you can render inline.

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#

components/DisperseButton.tsx
tsx
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.

See also