Every proposal and governor command requires
--vault. Each vault has its own governor (a BeaconProxy deployed at creation), so the CLI resolves the target governor on the fly via factory.governorOf(vault). Proposal IDs are scoped per vault. The exceptions are sherwood governor set-mgmt-split and set-perf-split, which target the protocol-wide ProtocolConfig and take no --vault.Owner-only proposals. While the factory’s
ownerOnlyProposals launch flag is on (the private-beta setting), only the vault owner can propose, and collaborative proposals are refused. The owner must also be a registered agent on the vault. proposal create and strategy propose read the flag live and refuse any other proposer before pinning metadata or locking the bond: This vault is single-operator: … <proposer> is not the owner (<owner>); propose from the owner wallet. On chain the reverts are ProposerNotOwner and CollaborationDisabled. Keyless proposal create refuses only when given --proposer; without it, it warns and still prints the calldata.sherwood proposal create
The vault owner submits a strategy proposal with pre-committed execute + settle calls. Most proposals are built by sherwood strategy propose instead, which writes these files for you.
Proposing pulls the proposer bond in WOOD into
ProposerBondEscrow. The CLI quotes it from ExposureLedger.proposerBondWood, refuses with InsufficientProposerBondWood when the wallet does not hold it, and prints the WOOD approval to the governor’s bondEscrow() first when the allowance is short. See Proposer bond and proposal reclaim-bond.
No per-proposal fee.
proposal create does not take a fee flag — the proposal snapshots the vault’s agentFeeBps at propose time, which the vault owner sets via sherwood vault set-agent-fee. At settlement the governor uses that snapshot, clamped to its maxPerformanceFeeBps. The management fee is not a proposal parameter at all — it is stamped onto the vault at creation and accrues on assets × time deployed.Metadata pinning (IPFS via Pinata)
When--metadata-uri is not provided, the CLI builds a metadata JSON from --name and --description, pins it to IPFS via the Pinata API, and uses the resulting ipfs:// URI. This follows the same pattern as vault create.
- Pin:
POST https://api.pinata.cloud/pinning/pinJSONToIPFSwithPINATA_API_KEYfrom env - Resolve: Metadata displayed in the dashboard via
PINATA_GATEWAY(default:sherwood.mypinata.cloud) - Schema:
{ name, description, proposer, vault, strategyDuration, createdAt }
Call JSON format
Each calls file is an array ofCall objects:
Flow
- Validate the caller is a registered agent on the vault and, while
ownerOnlyProposalsis on, the vault owner - Parse and validate calls JSON
- If no
--metadata-uri: build metadata JSON, pin to IPFS via Pinata, getipfs://Qm...URI - Display proposal summary for review (name, duration, call count, metadata URI)
- Call
governor.propose(vault, strategy, metadataURI, strategyDuration, envelope, executeCalls, executeCallCaps, settlementCalls, settlementCallCaps, coProposers)—strategyis the clone that holds on-venue positions (passaddress(0)for a queue-only proposal);envelopeis theRiskEnvelope(maxCapital,maxDrawdownBps);executeCallCapsandsettlementCallCapsare parallel per-call gross-outflow caps in the vault asset (oneuint256per corresponding call; each batch’s sum must be<= envelope.maxCapital, checked independently);coProposersis the optional collaborative-proposal co-signer list. The agent fee is not a proposal argument — the governor snapshots the vault’sagentFeeBpsonto the proposal at propose time (clamped tomaxPerformanceFeeBpsat settlement) - Print proposalId and voting period end time
sherwood vault set-agent-fee
Set the vault’s agent performance fee. Vault owner only. This is a vault property — the governor snapshots it onto each proposal at propose time (immutable for that proposal), then clamps the snapshot to maxPerformanceFeeBps at settlement.
- A new vault starts at the protocol default (
FeeConstants.DEFAULT_AGENT_FEE_BPS, 2000 bps = 20%). An explicit0is legal. - Capped at the protocol ceiling (
FeeConstants.MAX_PERFORMANCE_FEE_BPS, 2500 bps = 25%) by the vault, and additionally clamped to the governor’smaxPerformanceFeeBps(2000 bps on a factory-created vault) at settlement. Going above 20% therefore takes two changes, not one. - Charged only on value above the fund’s previous peak price per share. This command does not affect the management fee, which has no per-vault setter.
- Calls
vault.setAgentFeeBps(bps). Applies to any proposal created after the change; proposals already created keep the fee snapshotted at their propose time.
sherwood proposal list
List proposals for a vault.
Queries subgraph for
Proposal entities filtered by vault/state. Falls back to on-chain iteration via governor.proposalCount() + governor.getProposal(id).
Output:
sherwood proposal show <id>
Full detail view of a single proposal.
- Proposal metadata (from IPFS if available)
- State, timestamps (created, vote end, review end, execution deadline, executed, settled)
- Guardian coverage (from
GuardianReviewon), read from theExposureLedger: each approving guardian’s WOOD lock and its USD value, total WOOD locked, required coverage in the vault asset (for example60 USDG) and its USD value, and a status:FULL,PARTIAL(with the share of capital execute would deploy),NONE(execute revertsInsufficientApproveCoverage) orUNKNOWN(feed unavailable; retry). The governor’seffectiveMaxCapitalis only written at execute, so do not use it to check coverage during review - Vote breakdown — voting is optimistic, so only AGAINST weight is tallied, against
votableSupply × vetoThresholdBps. There is novotesFor/votesAbstain - The strategy’s address and where it came from (provenance):
Cloned from the core protocol <name> template 0x… (sherwoodagent/sherwood-protocol).for Portfolio, Morpho Supply and Concentrated Liquidity;Cloned from the <name> template 0x… (sherwoodagent/sherwood-strategies).followed byThis strategy is unaudited: …for a community template;Not cloned from any template: this strategy is hand-written code …for a strategy registered directly on theStrategyFactory; orProvenance could not be read (<reason>).Signedproposal voteprints the same lines before it sends the vote - Decoded calls (show target names for known targets like the vault’s swap adapter and ERC-20 approvals)
- Capital snapshot (if executed)
- P&L and fees (if settled)
sherwood proposal vote
Cast a shareholder vote on a pending proposal. Voting is optimistic: only Against weight is tallied, and the proposal is rejected when it reaches votableSupply × vetoThresholdBps (20% by default). For and Abstain are recorded but carry no weight.
Flow
- Load proposal, verify state is Pending
- Wait for voting to open (see below), then check the caller has voting power (vault shares at the snapshot; queued shares carry none)
- Display the proposal summary, the strategy’s provenance lines and the vote weight
- Confirm with user
- Call
governor.vote(proposalId, support)— support is the VoteType:For(0),Against(1), orAbstain(2), set via--support for|against|abstain
Vote timing
Voting opens one second after the propose block. The governor snapshots at the propose block’s timestamp minus one and refuses votes whileblock.timestamp <= snapshotTimestamp + 1 (NotWithinVotingPeriod); getVoteWeight reads 0 until then. Signed proposal vote waits this out on the chain’s clock, showing Voting opens in <n> s — waiting..., then votes. If no later block has landed after the wait it stops with No block has been produced since this proposal …; rerun once the chain has produced a block.
With --calldata-only the CLI does not wait. Broadcast the vote only after a block whose timestamp is at least the propose block’s timestamp + 1. A vote sent earlier reverts NotWithinVotingPeriod; send it again.
Delegation is disabled on the vault (delegate reverts DelegationDisabled); every holder is self-delegated.
Casing differs between the CLI and the HTTP API. The CLI
--support choices are lowercase for | against | abstain. The HTTP API vote field is case-sensitive and takes the capitalized For | Against | Abstain — pass it exactly, or the request is rejected.sherwood proposal review-vote
Cast a guardian review vote on a proposal already in GuardianReview. Distinct from proposal vote, which is the shareholder For/Against/Abstain ballot.
Calls
GuardianRegistry.voteOnProposal(governor, proposalId, support, lockWood) — four arguments. There is deliberately no overload without the amount, so --lock-wood is required in both directions.
sherwood proposal execute
Execute an approved proposal (anyone can call).
--dry-run first checks guardian coverage on the ExposureLedger (the same section proposal show prints) and exits non-zero when it is short: NONE (execute would revert InsufficientApproveCoverage), PARTIAL (unless you pass --allow-partial-coverage) or UNKNOWN. It then simulates the execute calls.
Flow
- Verify proposal is Approved and within the execution window (24 hours by default; an unexecuted proposal becomes
Expired) - Verify no other strategy is active on the vault and the owner bond is live (
OwnerBondNotLiveotherwise) - Call
governor.executeProposal(proposalId). The governor re-checks tier, coverage and themaxCapitalBpsceiling, requires at least one covering guardian approval (InsufficientApproveCoverage), and scales the deployed amount down when coverage is partial - Print capital snapshot and redemption lock status
StalePrice during the weekend feed gap (about Saturday 18:40 UTC to Monday 00:00 UTC); see Strategy Commands.
sherwood proposal settle
Settle an executed proposal. Routes to the appropriate settlement path.
Routing logic
- If caller is the proposer:
settleProposal(proposalId)— allowed one hour after execution (StrategyDurationNotElapsedbefore that) - If strategy duration has elapsed:
settleProposal(proposalId)— permissionless, anyone can call - If caller is vault owner, duration elapsed and
--callsgiven:emergencySettleWithCalls(proposalId, calls)— opens a bonded, guardian-reviewed emergency settle; the calls run only atfinalizeEmergencySettle
sherwood proposal cancel
Cancel a proposal before execution.
- Proposer (default): can cancel from
Draft,Pending(while the voting window is open),GuardianReview, orApproved. A cancel duringGuardianReviewalso drivesregistry.cancelReviewso approvers can’t be slashed by a stale resolution. - Vault owner (
--emergency): canemergencyCancelonly fromDraftorPending. Once a proposal reachesGuardianRevieworApproved, only the proposer can cancel.
proposal reclaim-bond. It also starts the governor cooldown: the CLI prints the time the next proposal can be submitted, and strategy propose / proposal create refuse before then with CooldownNotElapsed and that time (with --calldata-only they warn instead). Reproposing the same strategy reuses the existing, never-executed clone, so no clone transaction is sent.
sherwood proposal reclaim-bond
Release a terminal proposal’s WOOD bond from ProposerBondEscrow back to the proposer. Permissionless: any wallet may broadcast it, and the WOOD always goes to the recorded proposer.
- The proposal must be
Rejected,Expired,CancelledorSettled. The CLI refuses withProposal is <state> — a bond is only reclaimable from Rejected / Expired / Cancelled / Settled.; on chain the revert isProposalNotTerminal. - A Rejected, Expired or Cancelled proposal releases immediately. A Settled proposal waits for the challenge window:
executedAt + strategyDuration + challengeWindow(14 days) must have passed and no challenge may have the coverage frozen, elseChallengeWindowOpen. This proposal has no proposer bond left to reclaim (never bonded, already reclaimed, or forfeited).means the bond is 0 (NoBondToReclaim). A bond is forfeited only by a challenge conviction; challenge filings are paused through the beta.
Veto (vault owner, no CLI command)
The vault owner can veto a proposal while it isPending by calling vetoProposal(proposalId) on the vault’s governor directly (sherwood governor info --vault <addr> prints the governor address; the HTTP API’s /prepare/veto encodes the call). The call reverts ProposalNotCancellable once the proposal enters GuardianReview. Shareholders do not veto: they cast Against votes, and the proposal is rejected when those reach the veto threshold. Blocking after the vote is the guardians’ block quorum.
Recovery: sherwood proposal unstick
Vault-owner-only recovery command for the rare case where an Executed proposal cannot settle because its pre-committed settlement calls revert (e.g. a downstream protocol changed interface or returned unexpected state). The vault stays locked and depositors cannot redeem until the proposal settles.
The command is hidden from --help because it should be a last resort — it’s listed here so owners can find it when they need it.
- Proposal state is
Executed(other states have their own recovery path — see below) - Strategy duration has elapsed (
unstickrequires it) - The proposal is currently the vault’s active proposal
- Caller is the vault owner
governor.unstick(id), which replays the voted settlement calls under the rescue price floor. If the strategy still reports executed() afterwards the governor refuses (StrategyNotSettled), and the only exit is the owner’s bonded, guardian-reviewed emergencySettleWithCalls → finalizeEmergencySettle path (sherwood proposal settle --calls <path>). On success the proposal transitions to Settled, the vault unlocks, and depositors can then vault redeem.
Recovery paths for other states:
sherwood governor info
Display a vault’s governor address and parameters. Resolves the governor via factory.governorOf(vault).
Output:
There is no “Registered Vaults” list any more — each governor serves exactly one vault. The fee splits are not shown here either; they live on the shared
ProtocolConfig, not the per-vault governor. See Economics.sherwood governor set-*
Vault-owner parameter setters, one governor per vault — each requires --vault and validates against protocol-wide bounds before submitting. They apply immediately (there is no timelock) but revert (ParamsFrozenDuringProposal) if the vault has an open proposal, Drafts included. All of them honour --calldata-only.
Governance timing (owner-set)
The vault owner sets the governance periods. Change them while no proposal is open;sherwood governor info --vault <addr> shows the current values.
The guardian review period after voting is protocol-set (
GuardianRegistry.reviewPeriod, 30 min on mainnet 4663), not owner-tunable. A proposal keeps the periods it was created with, so a change applies to the next proposal. For a fast test cycle, set the voting period to 30 min (1800) before the first proposal: vote ends 30 min after propose, review 30 min after that.
set-max-capital caps what one proposal may declare as --max-capital, as a share of totalAssets (checked at propose and again at execute). set-tier2-call-cap caps each uncertified (tier-2) call the same way. Both read as 100% on a fresh vault. The matching vault-side cap is sherwood vault set-min-buffer.
sherwood governor set-mgmt-split / set-perf-split (ProtocolConfig)
There is no standalone protocol fee in the two-number model, and ProtocolConfig exposes no setProtocolFeeBps. The protocol and guardian shares are legs of the two fees, changed with these two commands. Both target the protocol-wide config (factory.protocolConfig()), so they take no --vault, and both are protocol-owner only (the protocol multisig). Each set of legs must sum to 10000 bps.
These replaced
sherwood governor set-protocol-fee in CLI 0.87.0. The old command targeted a function the contract no longer has, so every call reverted. See Economics for what each leg pays for.Keeper commands: open-reviews and resolve-reviews
open-reviews opens every review whose shareholder vote has ended (window [voteEnd, reviewEnd)); a review nobody opens resolves not blocked. resolve-reviews resolves every review whose window has elapsed. --dry-run lists what would be sent.
UX Considerations
- Duration format: Accept human-readable durations (
7d,24h,1h) in addition to raw seconds - Call encoding: For common actions (swaps through the vault’s swap adapter, ERC-20 approvals), the CLI provides built-in call builders so agents don’t need to manually encode calldata
- Metadata via Pinata:
proposal createpins metadata to IPFS usingPINATA_API_KEY(same env var used byvault create). Dashboard resolves metadata viaPINATA_GATEWAY(sherwood.mypinata.cloud). If the agent provides--metadata-uridirectly, pinning is skipped - Vote weight display: Shows the user’s voting power before they vote, so they understand their influence
- Settlement routing: Auto-detects the correct settlement path based on caller identity and timing