Skip to main content

Liquidity while a proposal is open

While any proposal is open (redemptionsLocked() == true, from propose to a terminal state), instant deposit / redeem are closed. Depositors use the withdrawal queue: requestRedeem (or requestDeposit) escrows in the VaultWithdrawalQueue, settlement stamps one frozen post-fee price for redeems, and anyone claims after unlock. The vault does not mark in-flight positions — totalAssets() is float minus queue reserve minus escrowed fees. See Deposits & Withdrawals for the full flow and claim mechanics.

The two-fee model

A fund charges depositors exactly two fees: a management fee and a performance fee. Everyone who is paid — the agent, the protocol, the guardian network, the fund owner — is paid out of those two numbers through governance-set splits. There are no deposit fees, no exit fees, no staking fees, and no referral fees anywhere in the protocol. Rates and splits are snapshotted at propose time, so a governance change mid-strategy never affects an in-flight proposal.
The live numbers for any fund are onchain: vault.managementFeeBps(), vault.agentFeeBps(), and the governor’s maxPerformanceFeeBps(). The protocol-wide ceilings are the constants SyndicateFactory.MAX_MANAGEMENT_FEE_BPS (300) and FeeConstants.MAX_PERFORMANCE_FEE_BPS (2,500).
The management fee is charged whether the fund gains value or loses it. It accrues on assets under management for the time a strategy is deployed, and it is settled on losing and flat strategies exactly as it is on profitable ones. Only the performance fee is contingent on profit.This is deliberate. The management fee is the leg that funds guardian review in flat and down markets: 20% of it goes to the guardian network, whose workload reviewing calldata does not fall when returns do. The performance fee pays nothing below the fund’s previous peak, so without the management leg the review layer would be unfunded exactly when scrutiny matters most.

Management fee

An annualized rate on assets under management, accrued over the time a strategy is actually deployed.
  • Base. The vault integrates asset-seconds: totalAssets() is stamped once at executeProposal, before the execute batch runs, and the clock runs until settlement, so idle capital between proposals accrues nothing. The base is the whole fund, not the deployed capital: a proposal that deploys a small fraction of the fund accrues the same fee as one that deploys all of it for the same time.
  • Formula. fee = assetSeconds × rateBps / (10_000 × 365 days).
  • Rate. vault.managementFeeBps(), stamped once at vault creation from the factory’s managementFeeBps (200 bps, 2%/yr at launch). The cap is SyndicateFactory.MAX_MANAGEMENT_FEE_BPS = 300 (3%/yr), enforced at factory initialize and setManagementFeeBps (ManagementFeeTooHigh).
  • Sticky per fund. There is no per-vault setter. The vault exposes only a getter, and changing the factory value reaches new funds only; a fund created under one rate keeps it for its whole life.
  • Base fallback. The stamp reads totalAssets() behind a try/catch; if pricing reverts, the base is the vault’s raw asset balance.

Performance fee

A share of new value created above the fund’s previous peak — a high-water mark, so depositors are not charged twice for the same gains.
  • High-water mark. The fee applies only to value above the highest price per share the fund has ever been charged at, and never to more than the proposal’s realized profit: the base is min(aboveHighWaterMark, max(pnl, 0)). The mark ratchets monotonically at settlement and is seeded at the fund’s first mint (instant or queued deposit). A loss leaves the mark in place, so recovering back to the old peak is free.
  • Rate. vault.agentFeeBps(), set by the fund owner with vault.setAgentFeeBps(bps) or the CLI sherwood vault set-agent-fee. A new vault starts at the protocol default, FeeConstants.DEFAULT_AGENT_FEE_BPS = 2000 (20%). An explicit 0% is legal and is stored distinguishably from unset.
  • Three stacked limits.
    1. FeeConstants.MAX_PERFORMANCE_FEE_BPS = 2500 (25%), the absolute protocol ceiling, checked at setAgentFeeBps (AgentFeeTooHigh).
    2. The per-vault governor cap maxPerformanceFeeBps, FeeConstants.DEFAULT_MAX_PERFORMANCE_FEE_BPS = 2000 (20%) on a new vault, settable by the fund owner between proposals up to the protocol ceiling.
    3. The vault’s own agentFeeBps, which starts equal to the per-vault cap by design, so a default fund charges its full allowance until the owner lowers one of the two. Charging above 20% therefore takes two changes, not one.
  • Double clamp. The rate is clamped against the governor cap at propose time and snapshotted onto the proposal, then re-clamped at settle against the live cap so a later cap reduction still bites. Clamping emits FeeClamped and continues — it never reverts.
  • No per-proposal fee. propose() takes no fee argument. To share the agent’s cut with collaborators, use co-proposers.

Where the fees go

Each fee is divided once, by a split held on the global ProtocolConfig behind the protocol multisig — not the per-vault governor. Splits are read at propose time and snapshotted onto the proposal, so settlement uses the split that was in force when depositors voted. It is one division of one base, not a sequential waterfall of compounding haircuts. Legs must sum to exactly 10,000 bps. Management split Performance split The fund-owner leg exists only on the performance split, not on the management split: the owner is paid on the profit side rather than on assets under management.
An unset (zero-address) protocol or guardian recipient folds that leg into the agent’s remainder rather than stranding it. ProtocolConfig seeds the splits in its constructor but leaves both recipients zero, so a deployment that forgets setGuardiansFeeRecipient pays the guardians’ share to the proposer instead. Deploy scripts seat both recipients inside the broadcast and assert both afterwards.

The guardian share

The guardian slice of both fees is paid onchain, in the fund’s asset, to guardiansFeeRecipient. Distribution to individual guardians happens off-chain, weekly, via Merkl, which swaps the collected asset to $WOOD and airdrops it to approvers. Attribution comes from the GuardianFeeAccrued event, which is emitted only on actual delivery — never on escrow. There is no onchain guardian reward pool and there are no staking emissions. See Guardian Review.

Settlement ordering

_finalizeSettlement charges in a fixed order so no fee is charged on assets another fee already took:
  1. Management fee — lowers fund assets, and therefore lowers price per share.
  2. Performance fee — reads the high-water mark after the management fee has been taken.
  3. High-water mark ratchet — against the post-fee price.
  4. Queue settle price stamped — queued redeemers and depositors settle at post-fee NAV.

Failed transfers escrow

Every fee transfer is wrapped so settlement never bricks. If a transfer reverts — a recipient a token has blacklisted, a paused token, a contract recipient with a failing receive — the governor credits the owed amount to an onchain escrow keyed by (vault, recipient, token), emits FeeTransferFailed, and continues. The escrowed amount is capped at what the vault can spare (FeeEscrowCapped). The recipient later pulls it with claimUnclaimedFees(vault, token): the escrow is keyed to msg.sender, nobody else can push it, and the pull reverts VaultProposalActive while that vault has an active proposal. Depositor capital is never held hostage by a bad fee recipient, and no fee is lost. The vault also refuses to pay fees out of float reserved for stamped queue redemptions or already escrowed to other recipients (transferPerformanceFee subtracts both), so a fee can never eat into an already-priced exit. totalAssets() subtracts the outstanding escrow too.

Other charges

These are not depositor fees, but they are the only other amounts the protocol moves:

Consolidated bounds

One proposal per vault

Only one proposal is open per vault at a time (VaultHasOpenProposal), and so only one strategy is ever live. This keeps capital accounting simple, eliminates cross-strategy risk, and makes the liquidity-lock and cooldown model clean. The governor tracks a single active proposal; executeProposal reverts StrategyAlreadyActive if a strategy is already live, and the next propose reverts CooldownNotElapsed until the cooldown stamped by the previous terminal transition has run.

When a strategy loses money

  • No performance fee is charged. It applies only to value above the fund’s previous peak price per share, and a loss leaves that mark untouched.
  • The management fee is still charged, on the assets and the time the strategy was deployed. See the disclosure above.
  • The loss is socialized across all depositors, as in any fund.
  • The loss is recorded onchain via the ProposalSettled(proposalId, vault, pnl, performanceFee, duration) event, which indexers aggregate into per-agent track records. There is no agent-slashing mechanism for losses; guardian and owner-bond slashing (see Guardian Review) is a separate layer for malicious calldata, not for honest losses.

Proposals are immutable

Once submitted, a proposal’s parameters are fixed. An agent who wants different terms cancels and creates a new proposal. This keeps voting clean — depositors always know exactly what they are voting on.