Config keys
Every key of guard.toml with its type, default and bounds, as Guard's code checks them.
guard.toml lives in Guard’s home (~/.zunder-guard unless --home or ZUNDER_GUARD_HOME says otherwise; --config names another file). zunder-guard init writes it; zunder-guard check-config checks it and prints its rules.
Conventions
Section titled “Conventions”- Fractions are fractions.
0.02means 2%. Basis points end in_bps. - Decimals are strings.
max_loss_at_stop = "0.02", not0.02. A bare float is refused, and so is a TOML date, so no value is rounded on the way in. - Unknown keys are refused, so a typo is an error, not a silently ignored setting.
- Every value is checked at start, against an upper bound as well as a lower one. A value out of bounds is refused, never clamped.
- A missing key takes its default, except
network,accountand[auth] clients, which have none.
Example
Section titled “Example”What init writes for a paper-mode Guard that reads a testnet account, with the default rules. Your file has your own addresses.
network = "testnet"mode = "paper"account = "0xYourAccountAddress"allow_mainnet = falselisten = "127.0.0.1:8547"state_dir = "."sync_seconds = 5ip_share = "1"pairing_sha3 = "…64 hex digits…"
[auth]clients = ["0xTheClientKeysAddress"]nonce_max_age_ms = 30000nonce_max_ahead_ms = 5000
[policy]max_leverage = "5"max_loss_at_stop = "0.02"stop = "attach"default_stop_distance = "0.02"min_liquidation_distance = "0.10"max_position_of_account = "2"max_open_risk = "0.06"daily_loss_stop = "0.06"drawdown_halt = "0.25"markets = "all"entry_price_bound = "0.005"fee_bps = "4.5"slippage_bps = "1"stop_slippage = "0.10"exit_slippage = "0.05"Top-level keys
Section titled “Top-level keys”GuardConfig in crates/zunder-guard/src/config.rs.
| Key | Type | Default | Bounds and checks |
|---|---|---|---|
network | string | none: required | "testnet" or "mainnet": the network of the account |
mode | string | "paper" | "paper", "testnet" or "mainnet". A sending mode must be the account’s network. run sends only with the same --network on its command line |
account | string | none: required | 0x and 40 hex digits: the main wallet’s address, not the API wallet’s |
api_wallet | string | unset | 0x and 40 hex digits. Needed to send: the key Guard is given must be this API wallet’s. key check records it |
allow_mainnet | boolean | false | true only in a mainnet config; refused in a testnet config |
listen | string | "127.0.0.1:8547" | an IP address with a port, loopback only. run --listen overrides it at start |
state_dir | path | "guard-state" (init writes ".") | not empty. A relative path is taken from the config file’s directory. Holds the journals and the kill file kill |
sync_seconds | integer | 5 | 5 to 300: seconds between two reads of the account (Guard reads less often when its ip_share or its HIP-3 dexes need it) |
ip_share | decimal | "1" | above 0, at most 1: the part of the IP address’s request weight (Hyperliquid’s 1,200 a minute) this Guard may spend. Several Guards on one machine: 1/N each, as a decimal ("0.5", "0.3333"). Every budget is fitted to it; a share too small to keep Guard safe is refused: below 0.291 with the main dex alone (three Guards per address at most), 0.466 with one HIP-3 dex (two), 0.749 with two (one). init --ip-share writes it, run --ip-share overrides it |
emergency_dir | path | <state_dir>/emergency | not state_dir itself. Where Guard records the protective actions it sends (flattening, stops, closes) while the decision journal cannot be written, in emergency- and the journal’s file name; best a directory on another disk that Guard may write. A relative path is taken from the config file’s directory |
licence | string | unset | a licence key (zgl1_…); not a secret |
pairing_sha3 | string | unset | 64 hex digits: the hash of the pairing code init or pair showed once |
[auth]
Section titled “[auth]”AuthConfig in crates/zunder-guard-core/src/auth.rs.
| Key | Type | Default | Bounds |
|---|---|---|---|
clients | array of strings | none: at least one | 1 to 64 addresses, 0x and 40 hex digits each: the client keys’ addresses (init, pair and client add add them) |
nonce_max_age_ms | integer | 30000 | 1,000 to 300,000: how old a request’s nonce may be |
nonce_max_ahead_ms | integer | 5000 | 100 to 60,000: how far ahead of Guard’s clock a nonce may be |
[policy]
Section titled “[policy]”Policy in crates/zunder-guard-core/src/policy.rs. The first ten keys are the nine rules (the stop rule has two); they travel in a rules code. The rest are Guard’s cost and execution assumptions, which no rules code carries.
| Key | Type | Default | Bounds | Rule |
|---|---|---|---|---|
max_leverage | decimal | "5" | above 0, at most 10, at most four decimal places | (c) max leverage |
max_loss_at_stop | decimal | "0.02" | above 0, at most 0.05; at most max_open_risk | (g) max loss at the stop |
stop | string | "attach" | "attach" or "refuse" | (b) an entry without a stop |
default_stop_distance | decimal | "0.02" | above 0, at most 0.5; below min_liquidation_distance | (b) where an attached stop goes |
min_liquidation_distance | decimal | "0.10" | 0.01 to 0.5; above default_stop_distance | (d) |
max_position_of_account | decimal | "2" | above 0, at most 10; at most max_leverage | (f) max position, 2 is 200% of equity |
max_open_risk | decimal | "0.06" | above 0, at most 0.2; at least max_loss_at_stop | (e) |
daily_loss_stop | decimal | "0.06" | above 0, at most 0.15 | (h) |
drawdown_halt | decimal | "0.25" | above 0, at most 0.5 | (i) |
markets | "all" or array of strings | "all" | "all", or 1 to 32 market names (1 to 32 characters each) | (a) |
entry_price_bound | decimal | "0.005" | 0.0005 to 0.05 | how far beyond the mid an entry’s limit may lie; a limit further out is pulled in |
fee_bps | decimal | "4.5" | 0 to 50 | taker fee per side, counted into each entry’s risk |
slippage_bps | decimal | "1" | 0 to 100 | expected slippage per side, counted likewise |
stop_slippage | decimal | "0.10" | 0.05 to 0.2 | how far beyond its trigger Guard’s market stop may fill; the liquidation must lie beyond that |
exit_slippage | decimal | "0.05" | 0.01 to 0.1 | how far from the mid Guard’s own closing orders may fill when it flattens |
max_trading_equity_usd | decimal | unset (no cap) | above 0, at most 2500 | the equity cap, in USDC |
max_loss_at_stop, default_stop_distance, min_liquidation_distance, max_position_of_account, max_open_risk, daily_loss_stop and drawdown_halt take at most six decimal places (four in percent, as a rules code writes them).
Mainnet
Section titled “Mainnet”A config with mode = "mainnet" is checked further (check_mainnet in config.rs):
allow_mainnet = true,account,api_walletandmax_trading_equity_usdare all required;- no rule may be looser than the mainnet ceiling (
Policy::within_mainnet_ceiling; today equal to the defaults, but pinned on its own, so changing a default never loosens mainnet):max_leverage,max_loss_at_stop,max_open_risk,daily_loss_stop,drawdown_halt,max_position_of_account,entry_price_boundanddefault_stop_distanceat most the ceiling;min_liquidation_distance,fee_bps,slippage_bps,stop_slippageandexit_slippageat least it; no HIP-3 market.
A config alone never sends to mainnet: run also needs --network mainnet, the key on standard input and ZUNDER_MAINNET_CONFIRM naming the account (Paper, testnet and mainnet).
Not in guard.toml
Section titled “Not in guard.toml”There is no [risk] section, no relay and no telemetry setting. The keys risk_per_trade, drawdown_stop, stop_policy, max_position_fraction, round_trip_cost_bps and allowed_coins belong to the risk engine’s own config and to the website’s judge, not to Guard; in guard.toml they are refused as unknown keys.
This page as plain Markdown, for people and LLMs: /docs/reference/config.md