On-chain contracts keep the
Syndicate* naming for audit continuity; the product concept is a vault, or fund.Architecture
Each fund is a vault. Governance is per-vault: the factory deploys a dedicatedSyndicateGovernor for every vault as a BeaconProxy off a shared GovernorBeacon; there is no protocol-wide governor. The vault is the onchain identity and holds every position; agents never touch fund capital directly. Strategy execution flows governor → vault.executeGovernorBatch → delegatecall into the stateless BatchExecutorLib, so every external call runs as the vault under the vault’s batch guards.
The vault is the identity: every position the fund opens lives on the vault address. Each vault gets its own governor at createSyndicate, which runs that one vault’s proposal lifecycle, voting and settlement. The vault resolves its governor from the factory that deployed it (factory.governorOf(vault)); there is no governor address stored on the vault and no rewire path, so a governor can never be swapped under a live vault. Upgrading every fund’s governor at once is a single GovernorBeacon.upgradeTo(newImpl) by the beacon owner.
Launch flags
Two factory flags shape the private beta. Both are factory-owner setters and are read live.
Creating a vault costs a
creationFee of 1,000,000 WOOD unless the factory owner has marked the creator creationSponsored, plus a prepared owner stake of minOwnerStake (10,000 WOOD) on StakedWood.
Core contracts
SyndicateVault
The ERC-4626 vault that is the fund. It holds every position and is the only address that moves fund capital. It extendsERC20Votes on a timestamp voting clock, so shares double as governance weight; every receiver is self-delegated and delegate reverts DelegationDisabled. Deposits are gated by pause state and the depositor whitelist (receiver-checked).
Strategy calldata can only enter through executeGovernorBatch(calls, callCaps, maxNetOutflow), which the vault accepts solely from its own governor and runs by delegatecall into BatchExecutorLib; the owner has no arbitrary-calldata path into the vault. The guards, in order:
- The executor’s codehash must equal the one pinned at
initialize(ExecutorCodehashMismatch). - Every target is the vault asset or a strategy registered on
StrategyFactory, resolved throughgovernor → tierRegistry → strategyFactoryand fail-closed (NotARegisteredStrategy). - A call on the asset is
transfer,transferFromfrom the vault, or an allowance call; the vault resets every named spender to zero after the batch (TransferFromNotVault,UnrecognizedAssetSelector,MalformedAssetCall). - Gross outflow per call ≤
callCaps[i](CallCapExceeded); net outflow ≤maxNetOutflow(MaxNetOutflowExceeded); float stays above the queue reserve (QueueReserveBreached) and theminBufferBpsfloor (BufferBreached).
StrategyFactory is the only reachability rule, and TierRegistry prices risk rather than permitting calls. Asset recovery is limited to rescueERC20 (non-asset tokens, only to a strategy clone of this vault), rescueERC721 and rescueEth, all owner-only and blocked while a proposal is open.
totalAssets() is vault float minus the queue reserve minus escrowed unpaid fees; there is no strategy NAV term. While any proposal is open (openProposalCount() != 0), instant deposit / redeem are closed and depositors use the withdrawal queue. The lock is derived, not stored.
The vault is a UUPS proxy. Factory.upgradeVault(vault, expectedImpl) is the only upgrade path: the creator calls it, only while upgradesEnabled and no proposal is open.
SyndicateGovernor
One governor per vault, deployed as aBeaconProxy so every fund shares an implementation but keeps isolated state. It runs the proposal lifecycle: Draft → Pending → GuardianReview → Approved → Executed → Settled, with terminal Rejected / Expired / Cancelled. One proposal is open per vault at a time.
Voting is optimistic: the electorate is recorded at the Pending transition with queued shares excluded, weight is the smaller of two ERC20Votes checkpoints around the snapshot, and the proposal passes after voting ends unless Against reaches vetoThresholdBps (20% at launch, bounds 20–80%). The vault owner can veto a Pending proposal or emergency-cancel a Draft / Pending one; once a proposal enters guardian review, only the guardian cohort and the execution window decide the outcome.
At propose the governor prices the calls into requiredCoverage through TierRegistry, enforces the covered-TVL cap and horizon on ExposureLedger, and locks the proposer bond in ProposerBondEscrow. At execute it scales capital to the coverage guardians booked. At settlement it charges the two fees using rates and splits snapshotted at propose, each divided once by ProtocolConfig; failed fee transfers escrow rather than bricking settlement. Parameter setters are owner-instant but frozen while any proposal is open, so the owner is expected to be a multisig; there is no separate onchain timelock. The governor also carries the owner-only emergency-settlement paths.
GovernorBeacon
TheUpgradeableBeacon holding the shared SyndicateGovernor implementation for every per-vault governor proxy. upgradeTo is a mass-upgrade primitive, moving all vault governors to a new implementation atomically, so its owner is the protocol multisig.
VaultWithdrawalQueue
The per-vault async substrate for deposits and withdrawals while a proposal is open, deployed by the factory at creation. The vault escrows shares (redeem) or assets (deposit) here; at settlement the vault stamps one frozen, post-fee price for the proposal and every redeem request in it claims at that single realized price, while deposit requests claim at the live price once nothing is open. A redeem can be cancelled only before its proposal is stamped; a deposit any time before it is claimed. See Deposits & Withdrawals.ProtocolConfig
Holds the protocol-level fee splits (management 60/20/20 across agent, protocol and guardians; performance 50/15/25/10 across agent, protocol, guardians and the fund owner), theprotocolFeeRecipient and guardiansFeeRecipient addresses, and maxStrategyDuration, shared by every per-vault governor. Each split must sum to exactly 10,000 bps. Values are read at propose time and snapshotted into the proposal, never read live at settle. Owned by the protocol multisig.
SyndicateFactory
Deploys a fund in one transaction: the vault proxy, itsVaultWithdrawalQueue, its governor (a BeaconProxy off GovernorBeacon, registered with GuardianRegistry and wired to the ledger and escrow), then binds the creator’s prepared owner stake on StakedWood. It checks the creator’s ERC-8004 identity (NotAgentOwner), pulls the creation fee unless sponsored, and records the vault ↔ fund mapping, subdomain and creator. The factory is UUPS-upgradeable so its config (creation fee, beacon, registries, launch flags, managementFeeBps) can change; pushWiring and pushExecutor propagate new wiring to existing governors and vaults. There is no ENS registrar: subdomains are a uniqueness check on the factory only.
StrategyFactory
Clones and initializes a strategy template in a single transaction (cloneAndInit, or cloneAndInitDeterministic with a CREATE2 salt), which closes the front-running window a separate clone-then-init sequence would open. Both are permissionless, but the clone binds to the vault and proposer the caller names and the proposer must be the sender (ProposerMustBeSender). Only owner-approved templates can be cloned (TemplateNotApproved); registerStrategy also admits a hand-written strategy that answers vault(), proposer() and executed(). isRegisteredStrategy is the vault’s batch-target rule, and it re-checks the registered codehash so a code change de-registers. Strategies are always deployed before a proposal; the governor never deploys one during execution.
BatchExecutorLib
A stateless library the vault delegatecalls to run a batch atomically. Under delegatecalladdress(this) is the vault, so every call executes as the vault and all positions land on the vault. It meters gross outflow per call against callCaps and has no state and no access control by design; the calling vault enforces the target and asset rules before delegatecalling, and if any call fails the whole batch reverts. The vault pins its codehash at initialize.
UniswapSwapAdapter
The swap venue for the Portfolio strategy on Robinhood Chain: a Uniswap-compatible route executor the strategy clone calls with a per-token encoded route. Allowlisted as a counterparty onTierRegistry.
Guardian layer
GuardianRegistry
The review layer between proposal approval and execution. It holds zero assets: it registers each proposal’s review window at propose, recordsApprove / Block votes with a WOOD lock on every approve, and on a blocked review calls StakedWood to slash the approvers. Review open and resolve are permissionless. It also runs the block-only review for an owner’s emergency settlement and holds a slash-appeal reserve the owner can refund from, capped at 20% of the reserve per epoch. Guardian rewards are paid off-chain via weekly Merkl airdrops; there is no onchain reward pool. See Guardian Review.
StakedWood (sWOOD)
The sole custodian of staked WOOD: guardian stake, vault-owner stakes, vote checkpoints and slashing all live here. sWOOD is non-transferable and there is no delegation. A guardian is active while staked with no pending unstake; unstaking has a 7-day cooldown that must be at least the review period and waits for the guardian’s coverage locks to expire. Slashed WOOD is burned, whether from a blocked review (slashGuardians), a challenge verdict (slashVerdict) or a blocked emergency settle (slashOwnerBond).
The in-repo
WoodToken.sol is a non-production test fixture. Mainnet WOOD is an external ERC-20 on Robinhood Chain, tradeable on Uniswap; its address is on Deployments.TierRegistry
Prices(target, selector) pairs for coverage (tier 0/1 with a certified bound, tier 2 at full notional by default) through a two-step, delayed certification, and carries the three allowlists strategy templates consult: counterparties, Morpho markets and per-token price sources. It also points the vault’s batch guard at StrategyFactory. See Tier policy.
ExposureLedger
The coverage book. Prices a proposal’srequiredCoverage in USD through Chainlink asset feeds, records the WOOD each approving guardian locks behind it in 7-day epoch buckets, quotes the proposer bond at the haircut WOOD price, enforces the per-proposal covered-TVL cap and the 60-day horizon, and reports the raised coverage at execute so the governor can scale capital. See Coverage.
ProposerBondEscrow
Holds the WOOD bond each proposal locks at propose.releaseBond pays the recorded proposer when the governor’s reclaimProposerBond runs; forfeitBond, callable only by the challenge game, pays the prosecutor fee to the challenger and burns the rest.
ChallengeGame and TokenCourt
The post-execution accountability tail. Anyone canfile a bonded challenge against an executed proposal within the 14-day window; silence for 7 days convicts, a filled counter-bond pool refers the case to TokenCourt, a single sWOOD-weighted vote that bars the accused, the challenger, the proposer and the counter-bond contributors. A conviction burns the approvers’ locks, forfeits the proposer bond and demotes the adapter pair. Filings are paused through the private beta.
WoodV3TwapFeed
The WOOD/USD oracle the ledger reads since 2026-10-13: the WOOD/WETH Uniswap V3 pool’s 1-hour TWAP (pool.observe), converted through Chainlink ETH/USD, with an in-range liquidity floor and an ETH/USD staleness bound. It is keeperless and stateless; when the pool’s observation ring cannot serve the window the read fails closed (NoWoodPrice), never with a wrong price. It replaced the run-1 WoodPoolFeed (the lower of two 24-hour pool TWAPs, rolled by a permissionless update()), which is retired.
Strategies
Strategies are the pluggable execute/settle logic a proposal points at. A strategy clone pulls vault capital, holds venue positions and returns the asset on settle. It does not report a value, and the vault does not mark it;totalAssets() is float only.
Trust boundaries
A few surfaces rely on governance discipline rather than a runtime check. Integrators and auditors should hold these in context:- The governor is trusted on the fee path.
vault.transferPerformanceFeehas no recipient cap on the vault side beyond the asset check and the reserve; the governor is trusted to transfer only what settlement math dictates, in the vault’s asset. The governor is upgradeable only through the multisig-controlledGovernorBeacon. - WOOD is assumed to be a fixed-behavior ERC-20. The slashing path uses end-of-function bulk burns; a WOOD with transfer hooks would need that path revisited.
- Certification trusts codehashes, not proxies.
tierOfand the counterparty allowlist re-checkEXTCODEHASH, which catches a bytecode swap at the same address but not a proxy implementation swap. Governance must not certify proxied adapters at tier 0/1. - The guardian cohort is operator-run at launch. Review is a security service over proposals submitted by vault owners, not a check on the operator; the challenge game, paused through the beta, is the ex-post backstop.
Building and testing
The contracts live in thesherwood-protocol repository.
chains/{chainId}.json (4663.json for Robinhood Chain), the source of truth for the addresses in Deployments.