Skip to content
Join the waitlistWaitlist

Veto and reason codes

Every code Guard, the risk engine and the browser tools use to say why an entry was refused or resized.

A refusal always carries a code (stable, for programs) and a reason (a sentence, for people). Codes are snake_case and never change meaning; new ones may be added.

Codes marked run a tiny example through the real risk engine in your browser.

Veto in crates/zunder-risk/src/engine.rs. The codes are the variant names in snake_case, as the WebAssembly build serialises them (VetoCode in crates/zunder-risk-wasm/src/api.rs).

CodeRuleReason (the engine’s own)Example
halted_for_day(h)trading is halted for the daydown 6% since 00:00 UTC
stopped(i)trading is stopped until a manual review25% below the peak
stop_on_wrong_side(g)the stop is not on the losing side of the entry pricebuy at 100, stop at 101
open_risk_exhausted(e)the open-risk budget is used upstops already risk 6%
leverage_exhausted(c)the leverage cap is reachedpositions already worth 5× equity
below_minimum(g)the sized quantity is below the venue minimumthe allowed size is worth less than the minimum order
unprotected_position(e)an open position has no protective stop, or its price is at or through itan ETH position without a stop blocks a BTC entry
invalid_request(g)the sizing request contains a negative or non-positive amounta price of 0
overflow(g)the numbers are too large or too small to size safelyabsurd inputs

crates/zunder-risk-wasm/src/judge.rs, which judges trades on this website (backtest, Watch). Guard’s own policy answers with its own codes, below.

CodeRuleExample reason
coin_not_allowed(a)HYPE is not on the market allowlist
no_protective_stop(b)no stop order protects the ETH position
liquidation_too_close(d)liquidation is 7% from the price; the minimum is 10%
position_cap_reached(f)a position may be worth at most 200% of account value
equity_not_positive(g)the account has no positive equity

Resizes carry the rule that bound the size instead of a code: max_leverage, max_open_risk, max_position_size or max_loss_per_trade, with a reason such as “a stop-out may lose at most 2% of equity”.

crates/zunder-risk-wasm/src/account.rs. Not refusals: warnings about the account as a whole.

CodeMeaning
halted_for_daythe daily loss stop is reached
stoppedthe drawdown halt is reached
position_without_stopa position has no protective stop
open_risk_over_capopen risk is over your cap
leverage_over_capleverage is over your cap

What a running Guard answers with: in every reply that refuses or changes a request (the code field, and in the text Zunder Guard veto [code]: …), in its decision events and on /guard/decision. Generated from zunder_guard_core::codes in Guard’s source; a test fails if code and this table drift apart. Engine refusals reach a bot under Guard’s names (daily_loss_stop, drawdown_halt, stop_wrong_side, open_risk, leverage, below_minimum, unprotected_position, invalid).

CodeOutcomeWhen
allowedforwardedwithin every rule; forwarded as sent
resizedforwardedforwarded with changes, listed in changes: a size cut to the rules, a stop attached, a limit price pulled in, a stop’s limit widened, an order made reduce-only or cut to the position
reduce_only_unjudgedforwardedthe account could not be read; reduce-only orders are forwarded as sent, unjudged, so closing never waits
malformedrefused before judgingthe request is not a well-formed Hyperliquid request (unknown fields, wrong types, bad prices, too many orders)
funds_or_permissionsrefused before judgingan action that moves funds or grants permissions (withdrawals, transfers, agent or builder approvals, referrers, vaults, staking): never forwarded
unsupported_actionrefused before judgingan action Guard does not forward (TWAP and others)
vaultrefused before judginga request for a vault or sub-account (vaultAddress): Guard trades the configured account only
auth_bad_signaturerefused before judgingthe signature recovers no key
auth_unknown_signerrefused before judgingthe signature recovers a key that is not one of Guard’s clients
auth_replayrefused before judgingthe nonce is not above every nonce this client used before
auth_nonce_too_oldrefused before judgingthe nonce lies more than 30 s behind Guard’s clock
auth_nonce_too_newrefused before judgingthe nonce lies more than 5 s ahead of Guard’s clock
auth_nonce_before_startrefused before judgingthe nonce is from before Guard’s start (plus 5 s): no request survives a restart
auth_expiredrefused before judgingthe request’s expiresAfter has passed
auth_too_largerefused before judgingthe request is too large to hash and check
kill_switchvetoedthe kill switch is pulled: nothing opens until a person removes the kill file and restarts Guard
daily_loss_stopvetoedthe daily loss stop is reached: nothing opens until the next UTC day
drawdown_haltvetoedthe drawdown halt is reached: nothing opens until a person’s review
journalvetoeda journal does not allow it: when the risk journal is not ready, entries are refused; when a decision cannot be written to the decision journal (or its sync takes longer than 2 s), every request is refused, closes and cancels included, until a restart on an intact journal (Guard’s own flattening and protection go on, recorded in the emergency log)
market_not_allowedvetoedthe market is not on the rules’ market list
unknown_marketvetoedthe asset is not a perp the venue lists
unsupported_marketvetoeda spot pair or a HIP-4 outcome (Guard trades perps), or a HIP-3 dex that margins in another token than USDC
dex_not_allowedvetoeda HIP-3 dex’s perp on a dex the rules’ markets do not name (dex:* or dex:COIN): Guard neither reads nor forwards anything there but cancels
market_haltedvetoeda HIP-3 market its deployer halted and settled (or delisted): nothing opens there
open_interest_capvetoeda HIP-3 market at its open-interest cap: the venue takes no order that adds to it
dex_marginvetoedthe HIP-3 dex’s own margin account cannot fund the venue’s minimum order at the leverage Guard sets: move USDC to that dex first
thin_bookvetoeda HIP-3 book too thin for the stop: the position, with what rests on its side, may take at most half the depth between the price and the stop’s worst fill (or the book could not be read)
unsupported_ordervetoedan entry that is not a limit order (a trigger entry)
account_unknownvetoedthe account is not in standard mode, or its equity is not positive: entries cannot be sized
account_unreadablevetoedthe account could not be read from the venue (reduce-only orders still go, as reduce_only_unjudged)
no_pricevetoedno mid price for the market
rate_limitedvetoedGuard’s budget of the venue’s request weight for bots’ requests is spent (reading the account, a HIP-3 entry’s book, and what a forwarded request sends; the rest of the venue’s limit is kept for Guard’s own protection); try again in a few seconds (reduce-only orders still go, unjudged)
stop_requiredvetoedan entry without a stop under the stop policy refuse (a stop-limit is no stop)
stop_wrong_sidevetoedthe stop is not on the losing side of the entry and the mid
stop_removedvetoeda cancel or modify would leave a position, or a resting entry, without a stop covering all of it
stop_loosenedvetoeda modify would move a stop further away, shrink it, or turn it into something that is not a market stop; or the change would raise the risk to the stops beyond the open-risk budget
guard_stopvetoeda cancel of Guard’s own stop while its position is open
open_riskvetoedthe open-risk budget is used up (risk engine)
leveragevetoedthe leverage cap is reached (risk engine); an open position runs above the cap; or a leverage update above the cap, or one that raises an open position’s leverage
position_capvetoedthe position is at the rules’ largest position already
liquidation_too_closevetoedno isolated leverage of 1x or more puts the liquidation beyond the stop’s worst fill and the minimum distance; adding to an open position would leave its liquidation too close; or a leverage update an entry resting on the coin could not take
cross_marginvetoedthe position is on cross margin, or a leverage update asks for cross: Guard trades isolated
below_minimumvetoedthe size the rules allow (or its stop, at its worst fill) is worth less than the venue’s minimum order
unprotected_positionvetoeda position has no stop covering all of it: no new entry until it has one
unprotected_ordervetoedan order rests that could open a position without a stop: no new entry until it is cancelled or has one
flipvetoedthe order would turn a position around: close it first (reduce-only), then enter
one_entry_per_actionvetoedmore than one entry in one action
modify_entryvetoeda modify of a resting entry: cancel it and send a new one
unknown_ordervetoeda modify of an order Guard cannot see resting
margin_removalvetoedupdateIsolatedMargin that removes margin
schedule_cancelvetoedscheduleCancel that sets a time (it would cancel the stops too); clearing it is allowed
client_buildervetoedan entry carrying a builder field of its own (in ccxt: options.builderFee = false); from an exit the field is removed instead
fee_not_approvedvetoeda new entry, or a modify, while Guard charges its builder fee and the account’s approval of it (maxBuilderFee) is not confirmed: approve it with the main wallet at https://zunder-design-preview.pages.dev/approve; exits, closes, new stops and flattens are never refused for it
invalidvetoedthe request cannot be judged as sent: TP/SL that do not belong to the entry, two stops, the entry not first in normalTpsl, Guard’s stop-id prefix, a change of market or side in a modify, numbers out of range
venue_refused_leveragenot sentthe venue did not confirm the isolated leverage the entry needs, so the entry was not sent
venue_unreachablenot sentthe venue could not be reached or answered with an error, or the request could not be signed; when no answer came at all it may have reached the venue, so check open orders before sending again

In Hyperliquid’s own error format, so a tool that handles Hyperliquid errors handles Guard’s, with the code as a field of its own beside the text:

{"status": "err", "code": "stop_required", "response": "Zunder Guard veto [stop_required]: the policy requires every entry to carry a stop loss (a normalTpsl child with tpsl \"sl\")"}

In paper mode nothing is sent and every reply says what would have happened: {"status": "err", "code": "resized", "verdict": "resize", "requested_size": "10", "size": "2.5537", "response": "Zunder Guard paper mode: would resize [resized]: … (nothing was sent)"}. A forwarded request gets the venue’s reply with code, verdict and the sizes added. Details: the contract in Guard’s docs/guard.md.

This page as plain Markdown, for people and LLMs: /docs/reference/veto-codes.md