The three shapes#
Every SDK write goes through viem; viem normalises wallet, network, and contract errors into distinct classes you can instanceof-check.
import {
UserRejectedRequestError,
HttpRequestError,
ContractFunctionRevertedError,
} from "viem";
const create = useCreateVesting({ address: managerAddress });
create.mutate(args, {
onError: (err) => {
if (err instanceof UserRejectedRequestError) {
// User clicked Cancel in the wallet, silent recovery, no toast.
return;
}
if (err instanceof HttpRequestError) {
// Network blip, retry the SAME mutation.
toast.error("Network blip. Retry?");
return;
}
if (err instanceof ContractFunctionRevertedError) {
// Contract reverted, show the decoded reason inline.
toast.error(`Contract: ${err.shortMessage}`);
return;
}
// SDK-level (encryptor / RPC / setup), surface the message.
toast.error(err.message);
},
});User rejected, be silent#
UserRejectedRequestErrormeans the user clicked Cancel. They KNOW the tx didn't go, a toast is noise. Reset any inline pending state and otherwise stay quiet.
Network, offer retry#
HttpRequestErroris RPC-side failure. Don't retry automatically without telling the user, the wallet UI already consumed their approval, so a silent retry could mint a duplicate tx if the first one secretly succeeded mid-failure.
Revert, show the reason#
ContractFunctionRevertedError.shortMessage carries the decoded revert reason. Pair with the product's typed error list (see /vesting/errorsetc.) to catch known classes BEFORE the viem fallback, then this branch is the "we haven't typed this revert yet" surface.