Vesting · Errors · 15@tokenops/sdk/fhe-vesting
Vesting errors you can catch by class.
Catch these by class reference; each carries the offending value (vestingId, unlocksAt, feeType, etc.) so your UI can render a message specific to the failure mode.
For the catch-ladder pattern + how SDK-level and generic-fallback errors fit alongside these, read Concepts › Typed errors + recovery.
| Class | When thrown | Recovery |
|---|---|---|
| VestingNotFoundError | vestingId does not exist on this manager clone. | User is likely on the wrong manager. Offer the correct address; the SDK exposes useRecipientVestings to discover their vestings on a manager. |
| NotVestingRecipientError | useClaim/usePartialClaim called by an account that is not the schedule's recipient. | On the claim revert path err.context carries { method, contractAddress, vestingId } (the contract's NotRecipient is nullary). caller / expectedRecipient are populated by preflightClaim, not this error — read them from a preflight check. Only the recipient can claim; surface 'this vesting belongs to another account' rather than a claim button. useClaim |
| ClaimLockedError | The schedule’s timelock has not elapsed yet (block timestamp < startTimestamp + timelock). | err.context.unlocksAt is the Unix timestamp the lock lifts. Render a countdown. useClaim |
| VestingExpiredError | useRevokeVesting / useBatchRevokeVesting called on a schedule that has already passed its endTimestamp. Claiming on an ended schedule does not revert. | There is nothing left to revoke once a schedule has ended — hide the revoke action after endTimestamp. (Claiming an ended schedule is allowed and pays out any unclaimed vested balance.) |
| VestingRevokedError | Operator tried to revoke a vesting that was already revoked, or useSplitVesting was called on an already-revoked source schedule. | Read state via useGetVestedAmount / useGetClaimableAmount — both honor revocation. Render 'revoked' instead of a claim button. |
| VestingNotRevocableError | useRevokeVesting called on a vesting created with isRevocable=false. | Revocability is set at vesting open and immutable. The UI should hide the revoke action unless useVestingInfo(…).data?.isRevocable is true. |
| TransferAlreadyPendingError | useInitiateVestingTransfer called while a pending transfer already exists for this vestingId. | Check usePendingVestingTransfer first; cancel the prior one before opening a new transfer. |
| NotPendingRecipientError | useAcceptVestingTransfer called by an address that is not the pending recipient of the transfer. | err.context carries { method, contractAddress, vestingId } here (the contract's NotPendingRecipient is nullary). Only the pending recipient can accept — read usePendingVestingTransfer to surface the address the initiator named. useAcceptVestingTransfer |
| TransferExpiredError | useAcceptVestingTransfer called after the transfer's expiry timestamp. | err.context carries { method, contractAddress, vestingId } (the contract's TransferExpired is nullary — the mapper does not populate an expiredAt). Read usePendingVestingTransfer for the expiry. An expired offer stays in storage, so re-running useInitiateVestingTransfer reverts TransferAlreadyPending until the current recipient calls useCancelVestingTransfer to clear it — only then can a fresh offer be issued. |
| NoPendingTransferError | useCancelVestingTransfer called when no transfer is pending for this vestingId. (useAcceptVestingTransfer with no pending transfer reverts NotPendingRecipient instead — the empty slot's newRecipient is the zero address, which never matches the caller.) | err.context carries method, contractAddress, and vestingId. Gate the cancel action on usePendingVestingTransfer returning a pending offer. |
| AccessDeniedError | Caller lacks the role the call requires (VESTING_CREATOR_ROLE, REVOKER_ROLE, PAUSER_ROLE, DISCLOSURE_ADMIN_ROLE, …). | err.context.role is the bytes32 role identifier. Check useHasRole + the role-constants table on the manager. |
| BatchTooLargeError | useBatchCreateVesting passed more entries than the manager allows in a single tx. | Chunk the batch. The on-chain cap is encoded in the contract; surface a 'too many entries' UI rather than the raw error. |
| FeatureDisabledError | Operator called a feature the manager wasn't configured for (e.g. split on a non-split manager). | Configuration flags are immutable at clone-time. Hide the action in the UI if useManagerConfig flags it disabled. |
| InsufficientBalanceError | useCreateVesting: encrypted source balance is too low for the requested amount. | Re-read the encrypted balance with useDecryptedHandle; surface the cap to the user before they submit. |
| MissingEncryptorError | A write hook that encrypts client-side ran without an encryptor injected. | Pass encryptor in ManagerHookOptions (parent scope) or via args.encryptor on the mutate call. See /concepts/encryptor. |
TokenOpsSdkError and carries the offending values under err.context — render specific messages instead of generic "transaction failed."Read the catch ladder