whisk
Hooks

usePreflight

Read-only pre-signature checks — balance, native gas, and wallet chain alignment. Catches problems before the user signs anything that's doomed to fail.

usePreflight runs the checks that prevent the most common user complaint: "I signed and it just failed". It inspects the wallet's balance, native gas, and current chain ID against the quote on the Review screen, and returns a list of issues — each tagged as either blocking (Send disabled) or warning (Send proceeds, user informed).

Read-only. Zero gas. Zero transactions. Just RPC reads via wagmi and @solana/web3.js.

Signature

function usePreflight(
  quote: Quote | undefined,
  walletAddress: string | undefined,
): PreflightResult;

quote typically comes from state.quote when state is in review. walletAddress comes from useWhisk().address.

Result

type PreflightCheckId = "balance" | "gas" | "chain";
type PreflightStatus = "ok" | "warning" | "blocking";

type PreflightCheck = {
  id: PreflightCheckId;
  status: PreflightStatus;
  message: string; // short, inline-friendly
};

type PreflightResult = {
  checks: PreflightCheck[]; // only failing/warning ones appear here
  hasBlocking: boolean; // true → gate the Send button
  isLoading: boolean; // true while balances are still being fetched
};

What gets checked

CheckSourceStatus when wrongWhy
BalanceSource chain USDC/EURC/USDT balance read via wagmiblockingNo point letting the user sign a doomed tx
GasSource chain native balance heuristicwarningWallets sometimes auto-fund — don't false-positive
Chainwagmi's useChainId() vs source chain's evmChainIdblockingSigning on the wrong chain confuses the wallet and the user

Solana sources skip the chain check (no wagmi equivalent for Solana clusters); other checks still run via useChainBalance.

Example: inline notices on a custom Review screen

"use client";

import { useWhisk, usePreflight } from "@usewhisk/react";

export function CustomReview() {
  const { state, actions, address } = useWhisk();
  const quote = state.kind === "review" ? state.quote : undefined;
  const preflight = usePreflight(quote, address);

  if (state.kind !== "review") return null;

  return (
    <div>
      {/* …quote summary, fees, etc… */}

      {preflight.checks.map((c) => (
        <p
          key={c.id}
          className={
            c.status === "blocking" ? "text-red-500" : "text-amber-500"
          }
        >
          {c.message}
        </p>
      ))}

      <button
        onClick={actions.send}
        disabled={preflight.hasBlocking || preflight.isLoading}
      >
        {preflight.hasBlocking ? "Resolve issues above" : "Send"}
      </button>
    </div>
  );
}

The <WhiskSend> widget wires usePreflight into the default Review screen automatically — failing checks render as inline notices, the Send button gates on hasBlocking, and the button label flips to "Resolve issues above" when blocked.

Notes

  • The hook never throws. Network errors during balance reads surface as isLoading: true for longer, then resolve to ok when the read succeeds.
  • Returns an empty checks array (and hasBlocking: false) when quote is undefined or no source chain can be determined. Safe to call unconditionally.
  • The gas check is intentionally a heuristic, not a precise estimate. Calculating actual gas cost requires running the tx — which would defeat the point of a pre-signature check.

On this page