Concept · Preflight

Catch the obvious before paying gas

Preflight runs the bounded checks a revert would surface anyway — cheaper, faster, and with structured inline hints.

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 whether the write is safe to submit, plus the typed errors that would have been thrown.

The result shape#

PreflightResult is a plain interface:

  • ready: booleantrue when no blockers were detected (green light to submit)
  • blockers: TokenOpsSdkError[] — the typed errors that would otherwise be thrown at write time; empty when ready is true

Each blocker is a TokenOpsSdkError with the same code, message, and context the write path would have thrown — so you branch on error.code with the same logic you use in onError. (The disperse report from usePreflightDisperse exposes this typed list as blockerErrors; its blockers field is a deprecated string[].)

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.ready) {
    return (
      <div role="alert">
        {preflight.data.blockerErrors.map((e) => (
          <p key={e.code}>{e.message}</p>
        ))}
      </div>
    );
  }
  return <button>All clear — disperse</button>;
}

Where preflight ships today#

fhe-disperse: usePreflightDisperse checks registration state, operator approval (both subwallets in wallet modes, or the singleton in direct mode), paused state, the ETH fee balance (publicClient.getBalance for the caller vs. the gas fee), and runs deterministic input validation (recipient/amount lengths, zero addresses, per-amount uint64 range, wallet-mode subtotal overflow, batch limit). It does not inspect token balances or an input proof — the single shared proof covering all encrypted amounts and subtotals is built later in disperse().

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