useWhiskSwap
Same-chain swap state machine, in hook form.
The state machine behind <SwapTab>. Reach for it when you want
your own UI but don't want to rewrite the estimate/review/swap
ceremony.
import { useWhiskSwap } from "@usewhisk/react";What it returns
type UseWhiskSwapResult = {
state: SwapState;
estimate: (input: SwapInput) => Promise<void>;
swap: () => Promise<void>;
back: () => void;
reset: () => void;
};
type SwapInput = {
chain: Chain;
tokenIn: Token | string; // alias or contract address
tokenOut: Token | string;
amountIn: string;
kitKey: string;
slippageBps?: number; // optional, falls back to App Kit's default
stopLimit?: string; // explicit min-out, overrides slippageBps
};
type SwapState =
| { kind: "idle" }
| { kind: "estimating" }
| { kind: "review"; estimate: SwapEstimate }
| { kind: "swapping"; estimate: SwapEstimate }
| {
kind: "succeeded";
estimate: SwapEstimate;
txHash?: string;
explorerUrl?: string;
amountOut?: string;
}
| { kind: "failed"; error: WhiskError };The hook owns the state machine. It does not own input state.
You pass the full SwapInput on every estimate() call, which
means you control the input UI: debouncing, slippage controls,
custom token addresses, whatever fits your product. The hook just
runs the state.
swap() uses the last estimate. Call it from the review state;
calling it from any other kind is a no-op.
A minimal swap UI
"use client";
import { useState } from "react";
import { useWhiskSwap } from "@usewhisk/react";
export function MySwap({ kitKey }: { kitKey: string }) {
const { state, estimate, swap, back, reset } = useWhiskSwap();
const [amountIn, setAmountIn] = useState("");
const onGetQuote = () =>
estimate({
chain: "Base",
tokenIn: "USDC",
tokenOut: "EURC",
amountIn,
kitKey,
});
if (state.kind === "succeeded") {
return (
<div>
Done. Got {state.amountOut} {state.estimate.tokenOut}.{" "}
<button onClick={reset}>Swap again</button>
</div>
);
}
if (state.kind === "failed") {
return (
<div>
Failed: {state.error.message} <button onClick={reset}>Try again</button>
</div>
);
}
if (state.kind === "review") {
return (
<div>
<p>You'll receive ≈ {state.estimate.amountOut}.</p>
<button onClick={back}>Back</button>
<button onClick={swap}>Confirm</button>
</div>
);
}
return (
<div>
<input
value={amountIn}
onChange={(e) => setAmountIn(e.target.value)}
placeholder="0.00"
/>
<button onClick={onGetQuote} disabled={state.kind !== "idle"}>
{state.kind === "estimating" ? "Quoting…" : "Get quote"}
</button>
{state.kind === "swapping" ? <p>Swapping…</p> : null}
</div>
);
}Slippage and stop-limit
Both live on SwapInput and are optional:
slippageBpsis basis points the swap is willing to slip. Pass100for 1%,25for 0.25%. Omit to use App Kit's default.stopLimitis an explicit minimum output, expressed in destination-token units (e.g."0.98"). When set, it overridesslippageBps. Use this when your UI exposes a precise floor rather than a percentage.
Errors
estimate() and swap() never throw. Failures land in state.kind === "failed" with state.error as a typed WhiskError subclass:
ConfigErrorwhenkitKeyis missing or the chain isn't swap-capable.UserRejectedErrorwhen the user cancels the wallet prompt.NetworkErrorfor RPC and pricing-service failures.BridgeStepErrorif the on-chain swap reverts.
See Errors for the full subclass set.
Need a kit key? Grab one at console.circle.com — free.
