The exact numbers live in the Rulebook (ch.31) and at /rules/mandate-vault.
The Mandate Vault Whitepaper
What this chapter is
Chapter 27 explains the Mandate Vault the way a depositor, a maker, or a supply-auction supplier needs to read it. This chapter goes one level deeper โ the way a smart-contract auditor, an integrator, or an AI agent deciding whether to trust the vault with money would want it written: every contract in the system, every number that governs it, and exactly which of those numbers are enforced by code on-chain versus by NightWatch's own servers today. Every figure below is read from the actual contracts on branch m7-spot-vault, frozen at commit fd3a541d (the version covered by audit round 8 and the version this vault is deployed from) โ not from a plan or an earlier draft. Where NightWatch's plan for the next contract round differs from what's live now, that is marked "next version" and never blended into the current numbers.
Purpose
A Mandate Vault is a smart contract that pools deposits into one basket of real, on-chain tokens, run under a mandate a maker publishes and freezes before anyone deposits. It exists so a strategy can be followed with actual custody of funds โ not just a signal a follower has to execute themselves (chapter 27's "self-mirroring") โ while keeping every step a depositor, a maker, or an outside supplier can take, and every step they cannot, enforced by code instead of by trust in NightWatch or in the maker. Today one instance of it is live on BNB Smart Chain's test network and open to whitelisted, small real deposits; nothing here describes a plan, except where explicitly marked "next version".
Architecture
Four contracts, plus two shared libraries, make up the system:
| Contract | What it is |
|---|---|
SpotMandateVault | The vault itself. An ERC-4626-style share token over 18-decimal Binance-Peg USDT, holding its basket of tokens directly (never through a custodian or another contract). Deployed exactly once as an implementation โ its constructor immediately calls _disableInitializers(), so that implementation contract itself can never hold a real deposit. |
SpotMandateVaultFactory | Deploys a real vault by cloning that implementation (the EIP-1167 "minimal proxy" pattern, via OpenZeppelin's Clones.cloneDeterministic) and initializing it with one maker's mandate, allowed-asset list, fee rate, and cap. The factory is also the only holder of every admin power over a deployed vault: pause, unpause, retire, allowlist, and settler rotation. |
ReferenceOracle | One instance per chain, shared by every vault the factory deploys on it. Turns Chainlink feeds, an optional Pyth adapter, and the keeper's own signed price snapshot into a single fair price per asset, and into the buy/sell trading gate described below. |
BondedSupplyAuction | One instance per chain, also shared by every vault on it. Runs the bonded reverse-bid auction that fills a leg the open market can't, described in full below. |
SpotSettlementLib | A library holding the FIFO withdrawal-queue math and monthly fee crystallisation for the vault's own record types. It is a sibling of the older perp vault's SettlementLib, not a fork of it โ the logic is line-for-line identical where the two vaults' needs overlap, existing separately only because Solidity cannot bind one library's storage-pointer functions to two different struct types. |
MonthClock, and two pure functions of SettlementLib | Reused byte-for-byte from the perp vault's own libraries โ pure calendar and fee-cap math with no vault-specific state, so there was nothing to fork. |
Non-upgradeable, by construction. A deployed vault is a fixed clone of one implementation contract; nothing in the factory, the vault, or NightWatch's admin key can change that clone's logic after the fact. If NightWatch ships a new vault version, it deploys a new implementation and a new factory, and only vaults created after that point run the new code โ every vault already open keeps running exactly the bytecode it was created with, forever. This is a deliberate trade: it means a bug fix cannot reach an already-open vault except by a maker opening a new one, but it also means no admin key, compromised or not, can ever rewrite the rules a vault's depositors already agreed to.
How money moves in: deposit, pending, crossing, purchase, shares
A deposit does not buy shares on the spot. deposit(assets, receiver) moves your stablecoin into a PendingDeposit record โ outside the vault's net asset value entirely, earning nothing, owned by you, cancellable in full at any time before it is spent (cancelPending). There is no on-chain minimum โ deposit() accepts any positive amount โ but the deposit screen recommends $10 as a practical floor.
The keeper is expected to start deploying every pending deposit within 15 minutes (DEPLOY_SLA โ an operational target the keeper's own code enforces, not a number the contract itself times). Deployment happens through one settler call, deployPending(pendingId, data), which does two things in order:
- Crossing. If anyone is already queued to withdraw, your pending cash pays their claim first, at that exact moment's fair price โ you receive shares worth exactly that much at the vault's current net-asset-value-per-share, and the leaver receives cash. Nobody traded on an open market; the trade is entirely internal, and it costs only gas. This is recorded on-chain as
Crossed(pendingId, requestId, amount, navPerShare). - Purchase. Whatever cash is left buys the mandate's target holdings, through the same gate/rebalance path described below. Shares are minted only for what was actually acquired: the value of every token actually bought, at the price it was bought at, plus any cash left unspent (which mints 1:1, since idle cash has no acquisition cost to absorb). Any trading cost, slippage, or auction spread lands on this deposit alone โ it can never dilute value already held by earlier depositors. This is recorded as
Deployed(pendingId, sharesMinted, acquiredValue, unspentCash).
If a leg can't fill on the first attempt, the filled legs mint immediately and the unfilled remainder stays visibly pending, retried on the next keeper cycle. After 24 hours in that state, you may call resolveStalePending(id, choice) yourself: 0 refunds the stuck cash in full, 1 mints it as shares at that moment's NAV instead (both re-check the vault's deposit cap before minting, so a stale resolution can never push the vault over its own limit).
How money moves out: queue, cash, sale, dust, in-kind
requestWithdraw puts you in a strict first-in-first-out line. The settler's settleWithdrawals batch job pays each request, oldest first, out of whatever stablecoin the vault already holds; if that isn't enough, it sells a slice of the basket (through the same gate/rebalance path, or the auction if the gate won't clear) to raise the rest.
The dust rule. A pro-rata sale can round down by a few thousand wei. Rather than leave a request open forever chasing an unpayable fraction of a cent, any remaining shortfall of SETTLE_DUST (1e12 wei, i.e. 1e-6 USDT at 18 decimals) or less is written off as fully settled. That dust is never transferred anywhere โ it simply stops being counted as owed, and stays in the vault for everyone still in it.
If the settler goes dark. SETTLEMENT_EMERGENCY_TIMEOUT is 3 days. If the oldest unresolved request has been waiting that long, its own owner (nobody else) may call settleOwnRequest to price and pay it directly, bypassing the batch job. If there still isn't a live price to settle against cash, the same owner may instead call redeemInKind โ a pro-rata exit paid in the vault's actual basket tokens rather than cash.
redeemInKind's one rule. It works only when totalAssets() (live NAV) resolves successfully โ called directly, with no fallback path. If it reverts, the whole call reverts; there is deliberately no secondary formula for a price outage. An already-priced request redeems against its fixed USDT claim exactly (stillOwed / grossAssets); an unpriced one redeems shares / totalSupply() of net assets and burns those shares in the same call. Any asset excluded from NAV because it's currently impaired (below) is excluded from this distribution too โ it stays in the vault for everyone who remains. A request whose net value has fallen to zero reverts (NothingToRedeem) before touching any balance or share count, rather than burning shares for nothing. Each token transfer is attempted independently; one that fails is banked in claimableInKind (pullable later via claimInKind) instead of blocking every other transfer in the same call. An earlier design had a second, ratio-based fallback branch for when no live price existed at all; five independent ways that branch could be exploited were found in audit round 4, and NightWatch's decision was to delete the branch outright rather than patch it โ a price outage now delays this exit until the price recovers, and never blocks it permanently, because the impairment rule below always restores a live, readable NAV within 24 hours of an asset going unpriced.
NAV: fair price, the trading gate, and impairment
Fair price (ReferenceOracle.fairPrice) is the median of every currently valid source among: Chainlink (valid if its last update is within that asset's own configured maxAge โ on mainnet, twice the feed's own heartbeat), a Pyth adapter (valid within 60 seconds; the adapter exists in the code but has no address configured on mainnet, so in practice it never contributes there), and the keeper's own signed snapshot mid-price (valid within 30 seconds). Two valid sources more than 1% apart make that asset's price unavailable and emit ReferenceDisagreement.
One rule keeps a 30-second keeper gap from halting the vault. Normally at least two sources need to agree; but if Chainlink alone is valid within its own maxAge, that is sufficient on its own โ a single missed keeper snapshot cannot, by itself, freeze NAV or withdrawals for an asset Chainlink is still actively pricing.
Impairment is the answer to the opposite failure: an asset with no valid price for more than 24 hours. NightWatch's factory PAUSER role may then call markImpaired, which values that one asset at zero in NAV so the rest of the basket, and every withdrawal that doesn't touch it, keeps working normally. The instant a valid price returns โ whether or not anyone calls clearImpaired โ that asset is priced normally again; the impaired flag never overrides a live price, and clearImpaired itself changes no value, it only tidies the record.
The trading gate is a separate, narrower check that every buy or sell โ swap or auction โ must clear: buyGate = ask1 ร 1.01, sellGate = bid1 ร 0.99, where ask1/bid1 come from the keeper's own signed snapshot of the best available market quote. That snapshot is valid only if it is 30 seconds old or less and its own bid/ask/mid all agree within 1% of the independent anchor (Chainlink, plus Pyth where wired โ on mainnet that anchor is Chainlink alone). The same gate governs a swap quote and an auction bid identically, so there is no seam between the two paths for a price to slip through. A closed gate, or a router quote the gate rejects, never reverts the whole transaction โ it records lastGateFailure[token] and lets the off-chain keeper fall back to the supply auction for that one leg.
A retired vault's sell floor is the raw fair price rather than the gated bid1 ร 0.99 floor a live vault uses โ deliberately looser, since a retiring vault's whole purpose is to wind down โ but it still requires the trading gate's own freshness-and-agreement check (sellGate's ok flag) to be true before it opens at all; with no valid snapshot, a retired vault's sell refuses exactly like a live one's would. A retired vault's buy side stays blocked entirely, with no exception.
Rebalancing on the open market
rebalanceSwap(token, side, qty, venue, callerLimit, deadline), callable only by the settler, routes exclusively through PancakeSwap v3's exactInputSingle/exactOutputSingle โ the venue parameter is checked against the one router address the vault was configured with, kept as an explicit argument only so a future multi-venue version needs no interface change. The trade's price limit is derived from the gate above; a caller may only tighten that limit, never loosen it.
Per-asset pool fee. PancakeSwap v3 pools exist at more than one fee tier, and the deepest pool differs by asset. The factory sets a poolFeeOf tier per asset once, at vault creation (create()), with a fallback of 0.25% (POOL_FEE_DEFAULT) for any asset with no explicit entry. On mainnet, the tiers actually used, found by measuring real pool depth rather than assuming a single constant, are: BTCB and ETH at 0.05%, WBNB at 0.01%, and SOL and XRP at 0.25%. This value only ever changes which pool is queried โ it has no effect on the price limits the gate already enforces, so a wrong tier can make a trade route to a shallower pool or fail outright, but it can never let a trade clear outside the gate.
The supply auction, mechanically
BondedSupplyAuction opens a leg only when that vault's own lastGateFailure for the asset is less than 2 minutes old (GATE_FAILURE_MAX_AGE), and only the vault's own settler can open one (openAuction). One auction per asset, per vault, at a time.
Bond math. A bidder's notional on any single bid cannot exceed their own unused bond (bonded[trader] โ locked[trader]) โ as deployed today, that is 100% collateral against every bid, regardless of track record; the tiered bond ratios described in chapter 27's tier table (down to 25% collateral for the highest tier) are enforced by NightWatch's own bid-acceptance and pre-award filtering today, not yet inside award() itself โ see "next version" below for when that moves on-chain.
Bid window. 120 seconds (BID_WINDOW) after opening, plus a 30-second award grace (AWARD_GRACE), during which bidders sign off-chain EIP-712 bids โ paying no gas to bid at all. When the window closes, the settler submits the winning bid(s) in one award() call, which verifies each signature, the whitelist (today; SBT plus bond, next version), that the price sits inside the gate, that best-price bids are awarded first, the cumulative quantity against the vault's need, and the bidder's free bond โ then locks bond against the award.
Delivery. deliver(auctionId, awardIndex, qty) must be called within DELIVERY_WINDOW of the award โ 60 minutes, as deployed and audited today. Each call must move at least 20% of the awarded quantity (MIN_DELIVERY_BPS), except the final remainder, which may be any size. The winner's tokens move first; SpotMandateVault.auctionPay (a narrow function only the auction contract may call) pays the matching USDT in the same transaction โ the two legs are atomic, so there is no moment where one side has paid and the other hasn't. Bond unlocks by a cumulative formula (cum(d) = lockedAmt ร d / awardedQty, unlocking the delta on each call) that always telescopes to exactly the full locked amount released, with no rounding dust ever stuck. A two-argument deliver(auctionId, awardIndex) also exists, delivering the entire remaining amount in one call. Tokens sent straight to the vault's address, instead of through deliver, are never counted as a delivery and are never returned โ the contract has no way to know who sent them or why. If that token happens to be one of the vault's own basket assets, it still becomes part of the vault's holdings (a gift to whoever stays in the vault); only a token that is neither a basket asset nor the accounting stablecoin is a genuine stray, described below.
Expiry and the slash split. After the delivery window passes, anyone may call the permissionless expire(), which acts only on the undelivered remainder of the award (a partly-delivered award's completed portion always stands as a normal fill). The slash is min(diffNotional + slashFee, remainingLockedAmt), where diffNotional = max(0, gate price at expiry โ award price) ร remaining quantity (falling back to the auction's own price snapshot from award time, flagged usedFallbackGate, if the live gate happens to be closed at the exact moment of expiry) and slashFee = 0.5% ร remaining quantity ร price (SLASH_BPS = 50). The split sends toVault = min(diffNotional, slash) โ compensation for depositors, paid first and in full whenever the cap allows โ and toTreasury = slash โ toVault, the flat penalty, which can be partially or fully zeroed out if the locked-bond cap binds before it's reached. The treasury address can never be the zero address.
Fees
The only fee a Mandate Vault ever charges is a performance fee โ there is no deposit fee and no withdrawal fee anywhere in the contract. It applies only above a 2% monthly hurdle (HURDLE_BPS = 200) and only above each deposit lot's own high-water mark: a month where the vault returns under 2% earns no fee at all, and only the gain above that line, on top of any past peak, is fee-eligible. The rate itself โ a passive vault at 250bps (2.5%), an active one between 1,000 and 2,500bps (10%โ25%) โ is fixed permanently the moment the vault is created and cannot be changed afterward by the maker, NightWatch, or anyone else. Of every fee that crystallises, 20% (PLATFORM_SHARE_BPS = 2000) accrues to the NightWatch treasury and 80% to the maker, tracked separately as owedToTreasury and owedToOwner. Payment happens only through the permissionless claimFees() call, which pays each party's fixed, registered address โ it is junior to the withdrawal queue (it can never draw on reserved cash, pending deposits, or a depositor's own redemption) and cannot be called at all while the vault is paused.
Bonds
Maker bond. 25% of the vault's deposit cap, minimum $100 โ in this pilot's $1,000-cap vault, that is $250. It is collateral only: never a share of the vault, it earns nothing while posted, and it is refunded to the maker in full minus any penalty on top of the mandate/fee constraints already enforced on-chain โ a mandate breach itself is impossible post-creation, since the mandate is frozen at construction with no setter. Today the bond is posted in on-chain USDT, so the contract sees a real, slashable bond. A Cherry bond, priced at the published list rate, is planned for v1.1.
What counts as a maker violation, and who decides. Three things: plan falsification (what the maker publishes doesn't match what the vault actually does), a pilot-mandate breach (deviating from the approved pilot's own rules before a vault exists), and a retroactive edit of a published record (backtest, pilot performance, or plan text changed after publication). A keeper or NightWatch operational error โ a missed settlement SLA, for example โ is NightWatch's own responsibility, never the maker's, and is never grounds for a bond penalty. The NightWatch operator adjudicates every case publicly, with a written reason attached to the decision; restitution to affected depositors is paid first, before any remaining penalty is kept or forwarded to the treasury.
Supplier bond. The same on-chain-USDT mechanism, with a $100 minimum first bond. Every bid is backed 1:1 by unused bond as deployed today; the lower collateral ratios a higher tier earns (chapter 27) are a server-side rule applied before a bid is ever submitted to award(), not yet a number the auction contract itself reads per bid.
Roles and keys
| Role | Held by | Powers | Cannot |
|---|---|---|---|
Factory admin (DEFAULT_ADMIN_ROLE + PAUSER_ROLE + ALLOWLISTER_ROLE + SETTLER_ADMIN_ROLE) | NightWatch's CDP-managed admin wallet, two-step transfer | create, pause/unpause/retire, setAllowlist, setSettler, markImpaired/clearImpaired, sweepStray โ the entire surface of NightWatch's power over a deployed vault | Move a depositor's or the vault's funds to an arbitrary address; change fees or the mandate; upgrade a vault's logic |
Vault owner() | The maker's own account, Ownable2Step (renounce disabled) | Receives fee payouts; rotates its own address | Withdraw depositor funds; change fees after creation |
settler | A keeper key, rotatable only by SETTLER_ADMIN_ROLE | rebalanceSwap, settleWithdrawals, crystalliseFees; is also the only address BondedSupplyAuction accepts openAuction/award calls from, for that vault | Redirect funds anywhere except the fixed destinations those functions already pay |
snapshotSigner (on ReferenceOracle) | A separate server key, rotatable by the oracle's own admin, deliberately never the settler and never NightWatch's CDP wallet (which cannot produce this signature type at all) | Authorizes price snapshots via EIP-712 | Move any funds; change a vault's mandate or fees |
WHITELISTER_ROLE (on BondedSupplyAuction) | NightWatch ops | setWhitelisted โ today, the only gate on who may bid | โ |
auctionContract (immutable, on the vault) | The one BondedSupplyAuction deployed for that chain, shared by every vault on it | The only address allowed to call the vault's auctionPay | โ |
| Oracle owner | Must be a cold key | setAssetConfig, clearAssetConfig, setPythAdapter, setSnapshotSigner โ full pricing authority | โ (this key's authority over pricing is trusted by design; it is the one role the system asks you to trust rather than checks against another role) |
These five keys must all be distinct on mainnet. The design's entire separation-of-powers guarantee โ that no single key can both move prices and move money โ depends on the settler, the snapshot signer, the factory admin, the oracle owner, and the treasury address being five different keys. The mainnet deployment script enforces this: on BNB Smart Chain it refuses to run unless every role is set explicitly, refuses when the settler, snapshot signer and admin are not three distinct keys, and refuses a treasury address that equals any of them (added after audit round 8, finding F1). A verify-roles command re-reads the deployed contracts and reports every role so the separation can be checked again after the deploy.
Safety record: eight audit rounds
The contract has been through eight independent audit rounds (an Opus reviewer, each round working from the actual code, not a description of it), frozen at commit fd3a541d. In brief, in order:
- Rounds 1โ2 found and fixed a NAV rule that could freeze on a single missed keeper snapshot (replaced by the "Chainlink alone suffices" rule above), and a trading-gate design that compared a price snapshot against itself (fixed to compare only against the independent Chainlink/Pyth anchor). They also confirmed: the emergency-exit timer keys off the oldest unresolved request and can't be refreshed by a partial payment or a donation; an impaired asset's flag never overrides a live price; and one shared cash guard protects both a rebalance and an auction payout from ever spending money a depositor is owed.
- Round 3 proved the in-kind redemption formula exact for a live-NAV exit, and fixed a bug where one unpriceable request could stall the entire withdrawal queue behind it.
- Round 4 found five independent ways a price-outage fallback formula for in-kind redemption could be exploited โ from double-paying a partially-settled leaver to destroying a position for zero consideration. NightWatch's decision was to delete that fallback branch entirely rather than patch each hole: in-kind redemption today has exactly one path, requiring a live NAV, with no fallback (described above).
- Round 5 closed a gap where a stale-pending or stale-resolution mint could push a vault over its own deposit cap, and formalized what counts as a "readable" token balance.
- Round 6 replaced a typed try/catch (which could not detect a malformed, non-boolean token return) with an explicit low-level call-and-decode check across every in-kind transfer, and fixed a case where a retired vault's sell floor could briefly read a stale, un-gated price.
- Round 7 fixed a live testnet bug โ a request stuck open by a rounding shortfall of 39,850 wei, on a real testnet vault โ into the general dust rule described above, corrected the round-6 sell-floor fix's freshness check, and implemented the vault/treasury slash split NightWatch ordered for auction penalties.
- Round 8 (the frozen version this vault runs) verified partial auction delivery and the stray-token sweep, both fully implemented and correct; ran 522 Hardhat tests and 145 Python tests, all passing; and confirmed every deployable contract has at least 1,024 bytes of spare room under Ethereum's 24,576-byte contract-size limit (the vault itself is the tightest, at 23,454 bytes used, 1,122 free). Its verdict was ready after fixes, with zero contract-code blockers โ the remaining items were configuration and process (the key-separation checklist above, plus two documentation and one operational gap), never a change to the contract's bytecode.
No fund-loss path was found, at any round, that survives the key-separation rule above being followed.
The guardrail: how a vault is judged against its mandate
A mandate is only worth what is checked against it. Two layers do the checking, one running today and one in v1.1.
Today: the daily X-ray. The mandate a maker registers is hashed on-chain at vault creation and published in plain text on the maker's page. Every day the X-ray described in chapter 14 fetches the vault's actual holdings and flows and scores them against that text: instrument list, size limits, rebalance cadence, limit usage. The score, its inputs and the day's verdict are stored, shown on the maker's page, and included in the daily record anchor (chapter 14), so an adherence record can be checked by anyone later and cannot be quietly restated.
v1.1: Guardrail Live. The X-ray looks once a day. Guardrail Live is the same yardstick applied continuously: a house judge that reads every execution the moment it is recorded, compares it with the submitted operating intent, and publishes a verdict with a probability in the vault's Hive room and timeline. The judge is a decision model (jev) with a larger model writing commentary only on notable events; the rule is written first and the model is asked whether it agrees, never the other way round. Every verdict carries the label "judged by the house AI, calibration pending" until a month of published agreement records exists; an off-mandate verdict is confirmed by a person before it has any consequence, and is applied automatically only if no person has objected within 24 hours. Verdicts are appended, never edited, and anchored daily.
The guardrail binds every order, including the operator's own. A passive vault's orders come from the rule engine; an active vault's come from its operator, who holds the operator SBT. In both cases the order is an intent that must pass the same gate (mandate, liquidity, size, venue state, pairing, idempotency) before a house execution agent turns it into real orders and is responsible for them to completion. The operator SBT grants the right to state an intent, not the right to move money. Chapter 05 describes the product side of the same yardstick: the tradability rating of each listed index perp and the NightWatch sub-indices.
Limits of this version
What is actually deployed and audited differs from the long-run design in a few places, and every one of them is listed here rather than left to be discovered later:
- Whitelisted deposits only. Every depositor must be added to the vault's allowlist by NightWatch; there is no public deposit path yet.
- A $1,000 vault cap, a $200 maximum single rebalance leg, and a $100 per-depositor limit. The first two are enforced by the contract itself (
maxTotalAssets,maxLegAssets). The $100 per-depositor figure is enforced only by NightWatch's allowlist policy โ the contract has no per-depositor limit at all, and pending (not-yet-deployed) deposits aren't even counted against the vault-wide cap, so the allowlist is the real control during this pilot. - A 60-minute auction delivery window, as deployed and audited โ not the shorter, tiered window described as the plan in chapter 27's tier table, which is a server-side and next-contract-round design (below).
- Auction bidding is still gated by a plain NightWatch-approved whitelist on-chain, even though tiering already treats it, off-chain, as open to any account with an SBT and a bond.
- Only five of the vault's thirteen candidate assets can actually be listed on mainnet as shipped โ BTCB, ETH, SOL, XRP, and WBNB. The other eight have no Chainlink price feed configured in the mainnet deployment script, and the factory refuses to accept any asset without one, even though the vault contract itself has no such restriction.
- Pyth is wired in code but not configured on mainnet (
pythAdapter = address(0)), so the live price anchor is Chainlink alone, not Chainlink-plus-Pyth as the general design allows for. - The per-asset pool-fee tier has no on-chain validation โ any value can be set silently at creation โ and no on-chain visibility: the
PoolFeeSetevent is declared but never actually emitted, so today the only way to check which tier a vault uses is to read the rawcreate()transaction's input data. - The monthly fee step depends on the keeper. The keeper crystallises fees per depositor after each month boundary and then claims them; if the keeper is stopped, fees simply wait until it runs again โ not a fund-safety issue, just a fee-timing one.
Next version (R9 + R10)
The following are ordered and, for most items, already coded and tested on m7-spot-vault-r9 and m7-spot-vault-r10 on top of it โ audited (Opus, round R10, 8ac42f86: deployable as-is) but not yet deployed to mainnet. None of it changes anything described above as live; it is listed here so the difference between today's contract and the next one is never ambiguous:
- Per-award tier limits on-chain, trimmed rather than rejected.
award()itself takes and enforces a maximum share of the auction and a bond ratio for that specific award, rather than relying on the keeper alone to filter bids before submitting them. An over-sized or under-bonded bid is awarded the smaller amount those limits allow, not turned away outright. The trim itself never reverts โ but if it would trim a bid all the way down to nothing, the ENTIREawardcall reverts, taking every other winner already in that same batch down with it. The keeper's own pre-filter, which mirrors this same trim before ever sending the transaction, is what actually prevents that from happening in practice. - When you buy tokens from the vault, your bond pays first. On a sale where the vault is selling and you are the buyer, the bond your delivery releases is applied to your payment before your wallet is touched โ you need the purchase amount in total on hand, not your bond plus the purchase on top of it.
- A 30-minute delivery window. The fixed 60-minute
DELIVERY_WINDOWbecomes a per-chain constructor parameter, set to 1,800 seconds (30 minutes) on BNB Smart Chain, for every tier. Measured settled transfer times on that chain are about a minute and a half at the 90th percentile, so the window is a real margin for a supplier who already holds the inventory, not room to withdraw from an exchange after the award. - A probation tier for brand-new accounts. An account with no SBT or no prior delivery history would be capped at 1% of an auction's notional (floored at the exchange's minimum order size, capped at $100), post 100% bond, and be limited to a single, full delivery โ no partial delivery โ enforced by a new
allowPartialflag per award. - SBT-gated bidding, replacing today's plain whitelist flag with a check that the bidder holds a NightWatch SBT.
- A snapshot sanity check (
bid1 โค mid โค ask1), closing an edge case where a manipulated snapshot could let a retired vault's sell floor briefly read as lower than its live trading gate. - On-chain validation and visibility for the pool-fee tier โ rejecting an invalid tier at creation instead of accepting anything silently, and actually emitting
PoolFeeSetso the tier is readable without decoding a transaction. - A fallback flag on the auction's slash-split event, so a revenue indexer can always tell whether the treasury's share of a penalty actually landed in the vault instead (the rare case where the treasury's own transfer fails).
Glossary
- Mandate โ the frozen set of rules (instrument list, size limits, rebalance logic) a maker commits to before a vault opens; hashed and recorded on-chain.
- Pending deposit โ stablecoin sent in but not yet spent; sits outside NAV, cancellable in full, until the keeper deploys it.
- Crossing โ paying a queued withdrawal directly out of a fresh deposit's cash, at that moment's fair value, instead of trading on the open market.
- Fair price โ the NAV-grade price for one asset: the median of Chainlink, Pyth (where wired), and the keeper's snapshot, whichever are currently valid.
- The gate โ the narrower buy/sell price band (askร1.01 / bidร0.99) every trade, swap or auction, must clear, checked against the keeper's live snapshot and the independent price anchor together.
- Impairment โ marking a persistently unpriced asset at zero in NAV so the rest of the vault can keep functioning; self-clears the instant a real price returns.
- FIFO withdrawal queue โ the strict oldest-first order every withdrawal request is paid in.
- Dust rule โ writing off an unpayable remainder of 1e-6 USDT or less as fully settled, rather than leaving a request open forever.
- In-kind redemption โ an emergency exit paid in the vault's actual basket tokens, pro-rata, available only when a live NAV can be computed.
- Bonded supply auction โ the reverse-bid mechanism that fills a leg the open market can't, backed by a supplier's own posted collateral.
- Delivery window โ the time a winning auction bidder has to hand over what they bid, from the moment they're awarded.
- Slash โ the portion of a bidder's locked bond taken when they fail to deliver in time, split between compensating the vault and a flat penalty to the treasury.
- Tier โ a supplier's standing (chapter 27), based on lifetime delivered notional, setting how much of one auction they can win and how much bond that locks.
- Settler โ the keeper's own on-chain identity: the only key allowed to run a rebalance, batch-settle withdrawals, or submit an auction award.
- Snapshot signer โ the separate key that signs the price snapshots the gate depends on; never the same key as the settler.
- PAUSER โ the factory role that can pause, unpause, retire a vault, or mark/clear an asset as impaired.
- Sweep โ moving a token that is neither the vault's stablecoin nor part of its mandate, and therefore untracked and unrefundable, out to the NightWatch treasury.
- Lot / high-water mark โ a deposit's own tranche, tracked separately so its performance fee is charged only on gains above its own past peak.
- Hurdle โ the minimum monthly return (2%) a vault must clear before any performance fee applies at all.
- Crystallise โ the act of locking in an accrued performance fee as owed, at month-end or at withdrawal.
- EIP-1167 clone โ the minimal-proxy pattern used to deploy each vault cheaply from one shared implementation contract.
- Guardrail โ the house yardstick applied to a vault's execution: today the daily X-ray adherence score (chapter 14), in v1.1 the continuous Guardrail Live judge; both compare what was done with the written mandate, and both bind the operator's own orders.
- Non-upgradeable โ once deployed, a vault's logic can never be changed; a new version requires a new vault, never a patch to an existing one.