Skip to content

Docs

Torii Wallet flags and environment#

Names only. Values live in the process environment of the host that needs them. Never paste a value into a doc, a chat or a commit.

Wallet (API host)#

NameWhat it switches
TW_DOOR_ENABLEDDefault off. The one switch for the bot door: GET /torii/door, GET /torii/skill.md, GET /torii/mcp-tools and the mandate_* tools on /mcp?profile=full. Off: the three routes answer 404 like a missing path, the tools are not listed and a call to one is refused as not available. On (1, true, yes, on): all open. Read on every request, so it needs a restart of the API process, not a code change. The owner routes under /torii/mandates are not behind it
TW_MANDATE_FACTORY_ADDRESSThe vault factory. While unset, create-call answers configured: false and no vault exists
TW_USDC_ADDRESSUSDC address when it is not in deployments/base-sepolia.json. The deposit watcher falls back to the built-in Base Sepolia USDC (with a logged warning) when neither exists
TW_RPC_URLThe RPC the chain reader and receipt checks use
TW_BEST_WAY_INJSON {"chain","asset"} that fills a missing destination in deposit routes
TW_BUDGET_ENFORCEDefault off. On: a paid read over the agent's daily Cherry budget is refused (mandate_budget_exceeded)
TW_ALERTS_DRY_RUNDefault on (1): the alerts worker counts and sends nothing
TW_DEPOSIT_WATCH_DRY_RUN, TW_DEPOSIT_MAX_BLOCKS_PER_TICK (4000), TW_DEPOSIT_TICK_BUDGET_SEC (60)The deposit watcher's dry run; per tick it reads consecutive 200-block chunks (never above the eth_getLogs ceiling) until the block cap or the time budget, committing the checkpoint after each chunk
TW_VAULT_POLL_DRY_RUN, TW_VAULT_POLL_SEC, TW_VAULT_POLL_MAXThe per-mandate vault poller (nw_tw_vault_poll). Dry run is on by default
TW_VAULT_EVENTS_DRY_RUN, TW_VAULT_EVENTS_SEC, TW_VAULT_EVENTS_MAX_BLOCKS_PER_TICK (4000; old name TW_VAULT_EVENTS_MAX_BLOCKS), TW_VAULT_EVENTS_MAX_SPAN (200 blocks per eth_getLogs: the public Base Sepolia node's limit, a larger span needs a paid RPC; halved to 100 on -32005; a 413 for one address is halved once, then the chunk is left unscanned and retried next run), TW_VAULT_EVENTS_START_BLOCK, TW_VAULT_EVENTS_REVERTS (1; 0 turns the revert block scan off), TW_VAULT_EVENTS_REVERT_BLOCKS_PER_TICK (150; blocks the revert scan reads per run on its own stored block torii_vault_reverts, one eth_getBlockByNumber each; it does not limit the event range), TW_VAULT_EVENTS_REVERT_BACKFILL_MAX_BLOCKS (2000; the first revert backfill of a vault reads at most this many blocks before the revert cursor, older attempts from before the scan existed are not reconstructed), TW_VAULT_EVENTS_REVERT_BUDGET_SEC (60; the revert scan stops cleanly after this many seconds and resumes next run), TW_GETLOGS_MAX_ADDRESSES (20; addresses per eth_getLogs call; a 413 halves the address list first), TW_GETLOGS_MAX_SPAN (200; blocks per eth_getLogs call in every Torii chain read, deposit-watch included; the public node's limit; capped at 200 whatever the value unless TW_GETLOGS_ALLOW_LARGE_SPAN=1, set only with a paid RPC; every other span or chunk value is clamped to it)The factory-wide event sweep (torii_vault_events), run on Chuncheon, not on Render. Dry run (1) is the default: it reads and counts, writes nothing and does not move its stored block
TW_WORKER_SCHEMA_ENSUREDefault off. The four Chuncheon workers send no DDL; a missing table makes them log one line and exit 2. 1 runs the look-first ensure at start (testing only). Apply the schema as a job: python -m svc.common.schema_ensure torii_agent_wallet
TW_WORKER_STATEMENT_TIMEOUT_MS, TW_WORKER_LOCK_TIMEOUT_MSWorker DB session limits (20000, 5000). A tick that hits one is skipped, exit 0
TW_IDEMPOTENCY_TTL_SECDefault 900. An Idempotency-Key row still in_flight after this many seconds belonged to a call that crashed: the next retry takes it over instead of being told idempotency_in_flight forever. Checked on read, no sweep, no new column. Finished rows are never reclaimed
TW_HALL_SNAPSHOT_STMT_TIMEOUT_MSSession statement timeout of the Hall snapshot worker (nw_tw_hall_snapshot, run on Chuncheon every 2 min), default 30000. The worker fills nw_tw_hall_snapshot; the Hall routes only read it (pending until the first pass, stale after 10 min). No dry-run env: --once --dry-run on the command line
TW_HALL_SNAPSHOT_KEEP_GOOD_SECHall snapshot worker: an agent whose record cannot be built this pass keeps its last good stored row (flagged degraded, with that row's own computed_at) while the row is younger than this many seconds, default 3600; after that the honest record_error row replaces it
TW_APPROVAL_ENABLEDDefault on (1). Clause 6: a relayed execute-call above the mandate's approval amount is held until the owner decides. Off: nothing is held and clause 6 reads stored_pending
TW_APPROVAL_TTL_HOURS, TW_APPROVAL_MAX_OPENHow long a pending or approved approval item stays good (24), and how many pending items one mandate may hold (10). Past the limit the agent is refused with rate_limited
TW_KEEPER_MARK_DRY_RUN (1), TW_KEEPER_MARK_SIGNER (cdp; dev only against a local node), TW_KEEPER_MARK_DEV_KEY (hardhat rehearsal only), TW_KEEPER_MARK_MIN_DELTA_BPS (50), TW_KEEPER_MARK_MAX_AGE_SEC (3600), TW_KEEPER_MARK_MAX (50 mandates per tick), TW_KEEPER_MARK_SEC (300, loop mode only), TW_WORTH_MARK_HEARTBEAT_SEC (900)The keeper mark worker (python -m svc.worker.torii_keeper_mark --once, Chuncheon). Dry run is on by default: it reads the chain and logs the mark it would send. 0 sends markWorth from the CDP admin wallet, which must be the factory's keeper(). Worth = vault USDC balance (plus the owner's Hyperliquid account for a bound HL leg, see TW_HL_LEG_ENABLED); mandates with relay orders are not marked. Each tick it also stores one row per mandate in nw_tw_worth_marks (worth moved, or the last row is older than TW_WORTH_MARK_HEARTBEAT_SEC; dry run too; 45-day retention) for GET /torii/mandates/{id}/worth-series. Optional table: python -m svc.db.schema.apply_tw_live_schema (also creates nw_tw_worth_state, why a mandate has no mark, and nw_tw_agent_seen, the agent last-seen row)
TW_SETTLE_GRACE_ENABLEDThe API only (default 0). 1 makes the close-all plan skip the owner stop() step, and stop-call return the reason instead of calldata, between endsAt and endsAt plus one day. The deployed vault lets the owner stop at any time, so keep it 0 until the factory with SettleGraceActive is redeployed; switch it on together with that redeploy
TW_TEST_VENUE_TARGETSComma-separated addresses of the test venue contract ToriiMandateTarget. Empty (default): the test-venue exit adapter is inert. Set only on a local rig
TW_STOP_LINE_DRY_RUN, TW_STOP_LINE_SEC, TW_STOP_LINE_MAXThe stop-line keeper (python -m svc.worker.torii_stop_line --once, run on Chuncheon). Dry run is on by default (1): it reads positions and reports in its log what it would close; it sends no order and writes no row. Clause 7 reads stored_pending while it is on. 0 closes for real, with the relay's own key (NW_TORII_AGENT_KEY, NW_TORII_ACCOUNT_ADDRESS) and reduce-only orders only. Seconds between ticks (30); mandates per tick (20)
TW_MANDATE_FACTORY_V2_ADDRESSLeg money (needs TW_HL_LEG_ENABLED and TW_LEG_FUNDING=test). The v2 vault factory (ToriiMandateFactoryV2). Unset or malformed: the create-call answers configured: false while funding is on. Receipts and the event sweep match both factories
TW_LEG_FUNDINGDefault off (off | test; anything else is off). Phase T leg money: the vault funds the owner's Hyperliquid account through a keeper fundLeg and a house-lent HL testnet mirror, and gets the money back. Off: no v2 create-call, no /money, /hl-leg/return-call (POST), /hl-leg/return (404 tw_feature_disabled), no mandate_money tool, the mover and reconcile do nothing, the keeper adds no in-flight term. Needs TW_HL_LEG_ENABLED
TW_GAS_DRIP_ENABLEDDefault off. The one switch for POST /torii/gas/drip and the torii_gas_drip MCP tool: a little Base Sepolia test ETH from the house funding wallet to the caller's own wallet, only for an account that owns a draft, signed or working mandate, or is the designated agent of a signed or working one (else not_eligible, reason no_mandate_yet). Off: the route answers status: disabled and the tool is not listed. On: tools/list shows it in the torii and full profiles. On Base mainnet it is always disabled; no variable overrides that (the chain id is a constant and is checked against the RPC). Needs the table: python -m svc.db.schema.apply_tw_gas_drip_schema
TW_GAS_DRIP_ETH (0.002), TW_GAS_DRIP_MIN_BALANCE_ETH (0.0005), TW_GAS_DRIP_DAILY_MAX (50), TW_GAS_DRIP_DAILY_ETH_MAX (0.1), TW_GAS_DRIP_ALERT_ETH (reserve + one drip + 0.0001 gas buffer, i.e. 0.0521), TW_GAS_DRIP_RESERVE_ETH (0.05), TW_GAS_DRIP_FUNDER_WALLET (the CDP admin account)Gas drip tuning. Amount per drip; the balance under which a wallet gets one; drips and ETH per UTC day for the whole deployment (over either: capped); the funding balance left after a drip under which (and on every funding_low cap), once a UTC day, an alert row is written (nw_tw_alert_log, kind gas_drip_funding_low), the ops Telegram channel gets a message (notify_ops_summary: needs NW_NOTIFY_OPS_CHAT_ID and NW_NOTIFY_TG_BOT_TOKEN) and a WARNING is logged; the reserve the funding account keeps (a drip that would leave less than it, after the drip and a gas margin, answers capped with reason funding_low, so drips never eat the keeper's gas when the funder is the admin account); the name of the CDP account that pays. Per address: one drip, one more at least 7 days later when the balance is under 0.0001 ETH, never a third; two addresses per account
TW_TEST_LEG_GATEWAYThe TestLegGateway on Base Sepolia. The create-call pins it as the vault's legGateway from this variable only, never from a request
TW_HL_TEST_POOL_ADDRESSThe house pool's PUBLIC address (a value, not a secret). Read by the API (the return-call destination), the reconcile worker and the mover; when the mover also holds the key it must be that key's address (pool_address_mismatch otherwise)
TW_HL_TEST_POOL_KEYSecret, never printed or logged. Raw 32-byte key of the house pool: signs the pool's usdSend on Hyperliquid TESTNET and tops up gains on Base Sepolia. Chuncheon only (/etc/nightwatch/tw-leg.env, mover unit). Refused when TW_HL_NETWORK=mainnet
TW_LEG_MIN_USDCDefault 6. Smallest first deposit of a v2 vault with a leg (tw_leg_min_deposit below it) and smallest first funding
TW_LEG_INFLIGHT_MAX_SECDefault 120 (Phase T). A fund or return row that has not moved for this long: the keeper withholds the mark (leg_transfer_stuck)
TW_LEG_RETURN_FALLBACK_SECDefault 86400. A closed mandate whose owner has not returned the leg money after this long is returned at the last good equity
TW_LEG_MOVER_DRY_RUN (1), TW_LEG_MOVER_SEC (30, loop mode), TW_LEG_MOVER_MAX (20 mandates a tick), TW_LEG_KEEPER_SIGNER (falls back to TW_KEEPER_MARK_SIGNER, cdp; dev only against a local node)The leg mover (nw-tw-leg-mover)
TW_LEG_RECONCILE_DRY_RUN (1), TW_LEG_RECONCILE_SEC (300, loop mode)The leg reconcile (nw-tw-leg-reconcile)
(clause 6 on hl-order)POST /torii/mandates/{id}/hl-order holds an open above the mandate's approval_above_usdc (HTTP 202, pending_approval) whenever TW_HL_LEG_ENABLED is on, with or without TW_LEG_FUNDING; it is switched off only by TW_APPROVAL_ENABLED=0
TW_HL_LEG_ENABLEDDefault off. The one switch for the HL leg (a mandate trades in the owner's own Hyperliquid account; spec 2.2). Off: the five hl-leg / hl-order routes answer 404 tw_feature_disabled, the MCP tool mandate_hl_order is not listed and a call is an unknown tool, the Trade door has no extra check, the keeper marks vault balance only, the stop line uses the house-relay adapter. On (1, true, yes, on): needs TW_HL_LEG_SEED and the schema (python -m svc.db.schema.apply_tw_hl_leg_schema). Set on the API host and on Chuncheon (keeper mark and stop line). Read on every request
TW_HL_LEG_SEEDSecret, never printed, never in a doc, chat or commit. Exactly 64 hex characters (32 random bytes; a leading 0x is allowed). Anything else is refused. The per-mandate Hyperliquid trade key is derived from it with HKDF-SHA256 and the mandate's public id and is never stored. With the flag on and no seed: the routes answer 503 not_configured, the stop-line worker refuses to start (exit 1) and a live close fails without guessing. Changing it orphans every bound leg (their approved keys no longer match): do not rotate it while a leg is live. Set on the API host and on Chuncheon (stop line)
TW_HL_NETWORKtestnet (default) or mainnet: which Hyperliquid the HL leg reads and signs for. The Trade door and the relay keep their own NW_TORII_MAINNET
NW_FORGE_SIGNAL_PUBLISH_ENABLEDDefault off. Forge's switch for operator signal publishing (POST /forge/vaults/{id}/signals, MCP forge_signal_publish). Off: every call answers 503 flag_off and writes nothing. Read on every request. It is a Forge flag, listed here because door 3 depends on it
NW_FORGE_SIGNAL_PUBLISH_RATE_PER_HOURDefault 60, minimum 1. The most signals one vault may publish in an hour; past it, 429 rate_limited with retry_at
TW_HOUSE_RUNNER_USER_IDThe user id of the house runner account. No default: unset, the runner worker exits 0 and does nothing, door 2 reports paper and the adopt draft names no agent. Set (with TW_HL_LEG_ENABLED on and TW_HL_NETWORK=testnet), door 2 reports testnet
TW_HOUSE_RUNNER_KEYSecret, never printed. The runner's agent key (scope tx); needed only for a live pass. Without it a signal is recorded skipped_no_runner_key
TW_HOUSE_FOLLOW_DRY_RUNDefault 1 (dry): the worker records dry_ verdicts and places nothing. 0 places orders through each mandate's HL leg. Timeouts and limits: TW_HOUSE_FOLLOW_STMT_TIMEOUT_MS (30000), TW_HOUSE_FOLLOW_LOCK_TIMEOUT_MS (5000), TW_HOUSE_FOLLOW_MAX_SIGNALS (20 per mandate per pass)
TW_KEEPER_HL_DEXES (,xyz), TW_KEEPER_HL_EXIT_BPS (15), TW_KEEPER_HL_MAX_AGE_SEC (120)HL leg worth: the perp dexs read (empty name = main), the exit allowance in basis points of open position value, and how old a Hyperliquid answer may be before the mark is withheld
TW_HL_LEG_MAX_OPEN_NOTIONAL_MULTDefault 2. The most open notional the HL leg may carry, as a multiple of the mandate's daily cap; checked on every opening hl-order against the positions on the owner's account. Positions carry over days, so the daily cap alone does not bound them
TW_STOP_LINE_TRANSIENT_PAGE_TICKS (10)A stop-line close that stays transient (Hyperliquid unreachable, partly filled) for this many ticks in a row makes the worker exit 1 so the unit pages; one tick is TW_STOP_LINE_SEC seconds (default 30), so ten ticks are about five minutes
TW_HL_SLIPPAGE_PCTAggressive-price band of the leg's IOC orders as a fraction, default 0.05; a value outside 0 to 0.5 is ignored
TW_HL_INFO_URL, TW_HL_EXCHANGE_URLTest and local-rig only: point the HL leg at a fake Hyperliquid server. Honoured only for a loopback host (127.0.0.1, localhost); any other value is ignored. Never set in production
TW_PUBLIC_WEB_BASE, TW_PUBLIC_API_BASEThe two public base URLs Torii puts in what it hands out, never the request Host. Web (TW_PUBLIC_WEB_BASE, else NW_PUBLIC_SITE_URL when set, default https://toriiwallet.com): the owner alert links. API (TW_PUBLIC_API_BASE, default https://api.nightwatchprotocol.com; NW_API_BASE is not read, it belongs to the workers and the record anchor): the URLs inside GET /torii/door and GET /torii/skill.md. docs in the door stays https://toriiwallet.com/llms.txt. Only https:// values are accepted; anything else is ignored and the default applies
TW_POLL_SECONDS, TW_CONFIRMATIONS (default 2; every chain-log ingest, the poller, page loads, POST /vault and POST /vault-tx, reads to head minus this; a failed head read ingests nothing), TW_CHUNK_BLOCKS (200, deposit-watch; the public node's eth_getLogs limit), TW_START_LOOKBACKChain reader pacing
TW_DEPOSIT_ADDR_BATCH (50), TW_DEPOSIT_BACKFILL_LOOKBACK (2000 blocks), TW_BLOCK_SECONDS (2), TW_REORG_WINDOW_SECONDS (3600), TW_REORG_RECHECK_MAX (50)Deposit watcher: addresses per eth_getLogs filter; how far back a late-registered address is scanned once; the block time used to estimate its registration block; how long a confirmed deposit is re-checked for a reorg, and how many per poll
TW_LINKS_FROM_NWNightWatch to wallet links
NW_PAYMASTER_URL, NW_PAYMASTER_KEY, NW_PAYMASTER_METHODGas sponsorship (Coinbase paymaster, Base only). Fail-closed when the URL is unset. Not on today

Analytics (frontend build)#

NameWhat it switches
NEXT_PUBLIC_TW_GA_IDThe Google Analytics 4 measurement ID (G-...) of the Torii Wallet web stream. A build-time variable: it is read when the frontend is built, so a change needs a new build and deploy. Empty or unset: the GA4 tag is not rendered. Used only under /tw, with no cross-domain linking to NightWatch
NEXT_PUBLIC_TW_CLARITY_IDThe Microsoft Clarity project ID of Torii Wallet. A build-time variable, same as above. Empty or unset: the Clarity tag is not rendered. Used only under /tw

Trading side (API host)#

NameWhat it switches
NW_TORII_LIVE_ENABLEDMaster switch for live orders. Default off
NW_TW_TRADE_LIVEMaster switch for live orders from the Trade door. Default off: every order is paper. Also needs NW_TORII_MAINNET on. A verified close of a live position works with it off
NW_TW_TRADE_ALLOWLISTComma list of 0x wallets or uid:<id> allowed to trade live. Required for live: with NW_TW_TRADE_LIVE on, an empty, blank or commas-only value means nobody trades live (everyone stays on paper). There is no wildcard; * matches nothing
NW_TW_TRADE_RATE_PER_MIN, NW_TW_TRADE_RATE_PER_HOURPer-wallet throttle on POST /tw/trade/order and /close together (default 10 a minute, 60 an hour, kept per API process). Past it: 429 tw_rate_limited with Retry-After
(no flag)Per-wallet caches on the Trade door, per API process: the account read behind /tw/trade/me and /positions (5 s, single-flight, dropped after an order or close) and the Hyperliquid account type (10 min). Request bodies on /tw/* writes above 64 KB get 413 tw_body_too_large
NW_TRUST_CF_HEADERSDefault off. When 1, the KR gate on live opening orders trusts the CF-IPCountry header (needs NW_TG_KR_GATE_ENABLED too, and a proxied Cloudflare edge with the origin locked to Cloudflare). Off: the gate does nothing for Trade and the log says so once
NW_TORII_CLIENT_SIGN_ENABLEDThe owner signs each order in the browser (sign_mode: client)
NW_TORII_BUILDER_ADDRESS, NW_TORII_BUILDER_FEE, NW_TORII_MAX_BUILDER_FEE_PCTThe builder address and fee (0.1%) and its ceiling
NW_TORII_BUILDER_APPROVAL_ENABLED, NW_TORII_AGENT_APPROVAL_ENABLEDOn-chain approvals for the builder fee and for the order agent key
NW_TORII_MAX_ORDER_USD, NW_TORII_MAX_DAILY_USD, NW_TORII_MAX_CLOSE_USDPer-order, per-day and close caps
NW_TORII_BEARER_DEFAULT_CAP_USD, NW_TORII_BEARER_MAX_DAILY_USD, NW_TORII_BEARER_MAX_CLOSE_USDCaps for the signed-in (bearer) path
NW_TORII_SLIPPAGE_PCTSlippage the Trade door sizes and paper-fills for, as a fraction: 0.05 (default) is 5 %. Allowed 0.0001 to 0.05; anything else (or a non-number, or 5 meant as percent) is ignored with a log line and 0.05 is used. Read on every request. The browser's own signed price band is the screen's constant, not this value
NW_TORII_REDUCE_ONLY_CAP_EXEMPTWhether reduce-only closes use the daily budget
NW_TORII_DEXThe perp dex the Trade door lists and accepts coins for (<dex>:<SYMBOL>). Default xyz; 1 to 16 lower-case letters or digits, anything else is ignored with a log line. Read on every request
NW_TORII_MAINNET, NW_TORII_ORDER_TYPES_V2, NW_TORII_DEX_TRANSFER_ENABLEDNetwork, order types, dex transfer
NW_TORII_OWN_MODE, NW_TORII_OWN_PAPER_WORKEROwn-account link and the paper worker
NW_TORII_ALLOWLIST, NW_TORII_ACCOUNT_ADDRESS, NW_TORII_AGENT_KEYAllowlist and account; the agent key is a secret held only on the server

Deploy script (the machine that deploys)#

NameNeeded for
CDP_API_KEY_ID, CDP_API_KEY_SECRET, CDP_WALLET_SECRETBroadcast deploys with the CDP server wallet

Frontend#

NameMeaning
NEXT_PUBLIC_API_BASE, NEXT_PUBLIC_API_URLThe API base the pages call
NEXT_PUBLIC_TW_HOSTSComma list of hosts that serve the wallet at the site root. Default toriiwallet.com,www.toriiwallet.com. On these hosts /x is rewritten to /tw/x, www. redirects to the apex (308), canonical URLs drop /tw. Build-time (baked into the middleware and pages), so a change needs a redeploy

Parked#

TW_TUSD_RESERVE_ADDRESS, TW_TUSD_TOKEN_ADDRESS, TW_TUSD_RPC_URL belong to the parked Torii dollar. They are not part of the product.