skeletonv0.1 · skeleton docs
Errors
Every error code, where it happens, and whether retrying helps.
Codes are grouped by where in the lifecycle they surface: 1xx source (Solana), 2xx transport and timing, 3xx destination verification, 4xx hook execution, 5xx return path.
| Code | Name | Stage | Retry | Meaning |
|---|---|---|---|---|
WH-101 | CALLER_NOT_APPROVED | 2 | no | The source app is not registered with the router, or it has been paused. Register the app in the developer console, or ask the app owner to unpause it. |
WH-102 | INSUFFICIENT_FEE | 2 | yes | The source transaction did not include enough to cover message and delivery fees. Re-quote the route and resend with the quoted fee. |
WH-201 | CALL_EXPIRED | 6 | new-call | The HookCall's expiry passed before the destination gateway could execute it. The gateway refused to run a stale call. Prepare a new call with a fresh nonce and a longer TTL. Expired calls are never executed late. |
WH-202 | RELAY_DELAYED | 5 | wait | The message is signed, but the executor hasn't delivered it within the route's expected window. No action needed yet. If the call expires first, it will close as WH-201. Manual redelivery is planned. |
WH-203 | RELAY_FAILED | 5 | yes | The executor attempted delivery and failed. The signed message is still valid. Request redelivery. The call ID is unchanged, so a retry can't run the hook twice. |
WH-301 | VAA_INVALID | 6 | no | The gateway rejected the message: signatures, emitter or chain didn't match the registered router. Nothing to retry. This indicates a misconfigured route or a forged message. |
WH-302 | REPLAY_REJECTED | 6 | no | This call ID has already been consumed by the gateway. The second delivery was rejected so the hook could not run twice. Nothing to do. See the original execution for its receipt. |
WH-303 | HOOK_NOT_FOUND | 6 | no | The hook ID or version is not in the registry on the destination chain. Check the hook ID and version in the registry. |
WH-304 | HOOK_PAUSED | 6 | later | The hook is registered but currently paused by its owner. Retry with a new call after the hook is unpaused. |
WH-305 | PERMISSION_DENIED | 6 | no | This app is not on the hook's allowlist for the requested action. Ask the hook owner to allowlist your app ID. |
WH-306 | SPEND_CAP_EXCEEDED | 6 | new-call | maxSpend is above the hook's per-call cap or the app's remaining daily cap. Lower maxSpend, or wait for the daily cap to reset. |
WH-401 | HOOK_REVERTED | 7 | depends | The gateway delivered the call and the hook itself reverted. The gateway caught the revert and emitted a failed receipt, so the Solana side still closes. Read the revert reason in the receipt. Fix the input and send a new call; retrying the same input will revert again. |
WH-402 | ADAPTER_ERROR | 7 | depends | The adapter could not decode the payload or forward it to the hook. Check the payload against the action's input schema. |
WH-403 | PARTIAL_EXECUTION | 7 | depends | Some sub-operations succeeded and some didn't. The receipt lists which. Inspect the output. Only retry the failed parts, with a new call. |
WH-404 | OUT_OF_GAS | 7 | yes | Execution exceeded the gas limit set for delivery. Re-quote with a higher gas limit. |
WH-501 | RECEIPT_PENDING | 10 | wait | The hook ran and a receipt was emitted, but it hasn't been recorded on Solana yet. Wait. Return-path latency depends on the destination chain's finality setting. |
WH-502 | RECEIPT_REJECTED | 10 | yes | The Solana router rejected the receipt message. Redeliver the receipt. The hook will not run again. |
Retry advice
| Advice | Meaning |
|---|---|
yes | Safe to retry. The call ID is unchanged, so the hook still can't run twice. |
no | Retrying won't change the result. |
wait | Not terminal. The system is still working on it. |
new-call | Send a new HookCall (new nonce), usually with adjusted parameters. |
later | Retry with a new call once the condition clears (e.g. hook unpaused). |
depends | Read the revert data or output; fix the input before sending a new call. |
In the SDK
import { WormHookError } from "@wormhook/sdk";
try {
const receipt = await execution.waitForReceipt();
if (receipt.status === "failed") {
// the hook reverted or the call expired: a receipt, not an exception
}
} catch (e) {
if (e instanceof WormHookError) {
e.code; // "WH-302"
e.info.retry; // "no"
e.info.remedy; // human-readable next step
}
}waitForReceipt() throws only when no receipt can exist for this delivery (WH-302), or when a stalled relay passes the client's timeout (WH-202).
Demo executions
| Code | Example |
|---|---|
WH-201 | Expired before execution |
WH-202 | Relay pending |
WH-302 | Duplicate delivery rejected |
WH-401 | Hook reverted |
WH-403 | Partial execution |