Skip to content

Field Governance Matrix

One-glance internal reference: for each governance-relevant fund / pool field — where it lives (DB / on-chain), who can change it, whether it is editable after deploy, and its timelock. This is a derived view that consolidates existing sources; it does not introduce new rules.

Sources of truth (do not restate values elsewhere): editability class → 08 §on-chain value sync · timelock durations → 08 §timelock · money-path immutability → 09a-custody §3 · column list → 11-db-schema.

Verified 2026-07-16, §5 extended 2026-08-17 with the repayment-plan columns (0189 / 0190, pending dev apply) · naming per v3-70 · classification corrected against contracts in v3-71 · treasury/fund-fee instant setters v3-69 · fee rates unified to bps (migration 0075) · lock-up decoupled from penalty_type; the on-chain NO_EARLYlockup_days=0 guard removed (v3-83, supersedes v3-66).

How to read this

  • StorageDB (Supabase), on-chain (contract state), or both (DB mirrors on-chain).
  • Class (08): A = on-chain setter exists (edit via dedicated endpoint); B = on-chain, no setter (change = redeploy new pool); C = off-chain only (plain PATCH); C-lock = off-chain but policy-locked.
  • Timelock — mirrored for convenience; canonical values live in 08 §timelock (if they ever differ, 08 wins).

1. Money-path & wallets

FieldStorageClassEditable byTimelockNotes
fund_walletbothAAdmin (governance)7dpartner-remainder destination; partner-controlled. proposeFundWalletChange also moves YIELD_DEPOSITOR_ROLE.
treasury_walletbothA*Admin (governance)noneAset fee destination. Instant setTreasuryWallet (v3-69, 09a) — no timelock: fees auto-distribute, so a hold would only strand fees at a stale address, never prevent a bad payout. Mirrors fund_fee_wallet.
fund_fee_walletbothA*Admin (governance)noneFM Pool-mgmt fee destination. Instant setFundFeeWallet (v3-69) — no timelock, same rationale as treasury_wallet. Must be a distinct address from fund_wallet.
reserveBalance (reserve_balance mirror)legacy onlyremovedno getter in new poolsIn the current external-reserve implementation, the reserve_bps share of each deposit goes to reserve_wallet and the remainder goes to fund_wallet. The in-pool reserve counter and getter are removed. The ratio specifies routing, not retention. An external balance does not automatically fund a pool payout.
Redemption payout (request.investor)on-chainimmutableThe holder-verified requester; payout fn has no destination param. No operator-settable payout address exists.
Yield claim recipient (msg.sender)on-chainimmutableNo recipient field exists to govern. settleYield's net leg names no one — it only increments the global per-share accumulator; each investor's claimYield always pays its own caller (msg.sender). Redirecting another holder's yield is structurally impossible.

* treasury_wallet / fund_fee_wallet are fee-path governance changes applied by instant admin setters (setTreasuryWallet / setFundFeeWallet, no timelock — v3-69); not in the Class A field table but behave as A. Unlike other governance fields they do not go through pool_governance_changes / the 7d propose→execute flow.

2. Pool config (governance-controlled)

FieldStorageClassEditable byTimelockNotes
reserve_bpsDB (drives on-chain split)AAdmin (governance)7dDeposit routing ratio to the external reserve wallet. Integer bps (1000 = 10%), range 0..10000. Both increases and decreases use the 7-day governance timelock. This is distinct from redemption gating.
capacity (hardCap)bothAAdmin (setHardCap)none (raise-only)On-chain enforced (v3-71, moved from old §6). setHardCap rejects any decrease and rejects narrowing an unlimited (0) cap to finite; raising only admits more investors, so no timelock (v3-43). Enforced at deposit as the safety net; DB capacity mirrors it.
jurisdiction_whitelist + enforce_jurisdictionbothAAdmin (governance)7dRegion restriction (v3-10/18). enforce_jurisdiction (0064) is the master switch; the whitelist holds ISO alpha-3 country codes (renamed from kyc_jurisdiction_whitelist, migration 0070). Create-only direct setters; live changes go through the 7d timelock.
nav_per_tokenbothAAset ORACLE (/nav-changesupdateNAV)24h on decreaseIncrease capped at par (≤ 1.0). Bounded by deviation cap / circuit breaker / staleness (v3-32). ⚠️ Open (v3-71): confirm whether the 24h decrease delay is a legal requirement or an operational guard.
KYC contract implementationon-chain (UUPS proxy)Admin (proposeUpgrade)instant ⚠️The only upgradeable contract (v3-30); money-path stays immutable Clones. v3-71 adopted 2026-08-14: UPGRADE_TIMELOCK = 0. Propose/execute is still required (a direct upgradeToAndCall reverts), but there is no wait. ⚠️ This is the sole upgrade timelock on a security-critical contract, and every exit path reads it (canRedeem) — an implementation that answers false blocks all withdrawals.

No pool-level KYC/KYB level gate. kyc_level_required / requires_institutional were removed in migration 0063 — pools no longer gate by individual vs institution. Current pool gating is jurisdiction only (above). Qualified-investor gating is implemented backend-side (no on-chain change, migration 0072): pools.eligibility_mode (STATUS Status Gate / MIN_TICKET Ticket Gate) + users.investor_status (RETAIL/PROFESSIONAL) + qualification_*, enforced in checkKycGating — see the KYC/KYB & investor-tier spec (v3-74).

3. Fee config (rates)

FieldStorageClassEditable byTimelockNotes
net_yield_fee_config (JSONB)DB (JSONB)CAdmin (PATCH)noneComputed off-chain in Lambda; no on-chain twin (contract stores no rate). Fee split is built (v3-69, migration 0067): platform_yield_take_bps (→treasury) + spc_mgmt_bps (→treasury) + pool_mgmt_bps (→fund_fee_wallet), charged off-chain via a 3-arg withdrawFees. All rates are integer basis points (0075, unified from *_pct; 1% = 100 bps), validated 0..10000. perf_fee_bps / perf_hurdle_bps are deferred/dormant (kept as keys, never set by the admin UI → evaluate to 0), not removed.

4. Lifecycle & operational flags

FieldStorageClassEditable byTimelockNotes
is_pausedbothAAdmin (/pausepause())instantBlocks deposits only; redemption normal. Must be on-chain (DB-only would be bypassable).
is_emergency_frozenbothAPauser Safe (/freeze)instantHalt-only + auto-expiring: exit auto-allowed after ≤ 72h, whole freeze expires ≤ 7d.
lifecycle_statusIMPAIREDbothAAdmin (/lifecycle)7dPartner distress → pause deposits; withdrawals stay open (v3-12).
lifecycle_statusWIND_DOWNbothAAdmin (proposeWindDown)30dTerminal. navPerToken = distributable / totalSupply (v3-100), pro-rata exit via requestRedemption → claim only. The 30d is the on-chain WIND_DOWN_TIMELOCK; the "~60d partner-unresponsive" precondition before proposeWindDown is off-chain policy (not enforced on-chain).
lifecycle_status (other transitions)bothAAdmin (/lifecycle)instante.g. UPCOMINGACTIVECLOSEDMATURED.

5. Immutable after deploy (Class B — change = new pool)

These have on-chain state but no setter — editable only while DRAFT; PATCH is rejected once deployed.

FieldNotes
penalty_type / penalty_rate_bps / penalty_fee_amountEarly-redemption penalty terms. penalty_rate_bps is integer basis points (e.g. 5000 = 50%; 1% = 100 bps), not a 0–1 fraction (0068).
lockup_days0 = no lockup. v3-83: independent of penalty_type — a NO_EARLY pool may still have lockup_days > 0 (LOCKED during the lock-up, then penalty-free exit). The old NO_EARLYlockup_days=0 coupling and its on-chain guard (setLockupDaysNoEarlyRequiresZeroLockup, v3-66) were removed; backend/FE validation no longer rejects the pairing.
maturity_daysThe term length. What the contract holds is the absolute maturityDate (subscription_end_date + maturity_days since v3-145; deploy_time + maturity_days before it, mirrored to pools.maturity_date since migration 0161) — a property of the pool, not of a holding. 🔴 A FIXED_TERM pool cannot be published without an offering close, because the term is measured from it. 📋 Anchoring maturity per investor is decided and not built (v3-131 item 4); it needs a contract change, since storage has no maturityDays. Note that v3-145 removes the reason it was wanted — no holder is invested for less than the term any more.
redemption_type (redemptionType)ON_DEMAND = exits allowed per the 3-state model · FIXED_MATURITY = exits refused until maturityDate. LIQUIDITY_WINDOWS was dropped (migration 0106). Orthogonal to epoch_duration_days on-chain; the admin wizard used to couple them (v3-88 D7) and v3-131 / v3-132 lifted the coupling. ⚠️ "No schema change" is stale — that was true of the guard, not of making the dates correct: it has since taken 0189 and 0190. No contract change; the funding-date ordering guard added with it sits in the endpoint, not in GovernanceLib (v3-139).
min_investment (minInvestment)On-chain, no setter (v3-71, moved from old §6). Enforced at deposit (BelowMinimumInvestment); immutable post-deploy — a published investor term.
accepted_currenciesPer-pool stablecoins. Not truly immutable, and the two setters differ: addStablecoin (admin) works only while the pool has no LP (totalSupply() == 0) — it freezes at the first deposit (ConfigImmutableAfterDeposit). removeStablecoin has no such deposit gate; instead it is guarded so it can never strand value — it rejects removing the pool's last stablecoin once LP exists, and rejects removing any coin the pool still holds a balance of. So a zero-balance, non-last coin can be de-registered even after deposits, but an in-use coin cannot. Go-forward policy: add-only (v3-71).
epoch_duration_days (epochDurationDays)0 = instant, >0 = epoch pro-rata. Immutable once totalLPSupply > 0; bounded MAX_EPOCH_DURATION_DAYS = 90 (v3-38). One value serves both the EARLY window and the post-maturity period — see 07 → segment × mode for what that makes unrepresentable.
redemption_term_epochs🔨 Built (0189, pending dev apply) — shipped under this name; older text calls it redemption_window_epochs. How many post-maturity cycles the pool repays over; NULL = unbounded, which is every epoch pool today. DB only, no contract field — the chain holds the dates, not the count. Create-only, and no longer optional: the deploy's write list, the reminder and the per-cycle endpoint all read it (v3-132 · v3-133). 🔴 NULL means not a plan and must never be collapsed with no cycles recorded yet.
epoch_date_basis · epoch_roll_day🔨 Built (0189, pending dev apply). The create-time rule (CALENDAR / FIXED_DAYS; roll day 1–28) kept so a human can reproduce the same list. DB only. Create-only — they have to keep describing the list that was actually written. 🔴 Governance rule of their own (v3-136): a date these produce may be shown to a person; it may not be written on-chain and may not decide a schedule. Reading them is allowed and three surfaces do.
redemption_epochs.funding_date🔨 Built (0190, pending dev apply). Not a governance field and not editable by anyone: an indexer mirror of EpochFundingDateSet, observed facts only. Listed here because it is the only thing that distinguishes a funding date the chain was given from one it derived, and a write path that filled it with an intended date would silently destroy that (v3-133). The governed act is the transaction, through POST /pools/{id}/epoch-schedule (ORACLE).

6. Off-chain, policy-locked (Class C-lock)

No fields currently in this class. min_investment and capacity — previously listed here (v3-43) — are actually on-chain enforced, not off-chain policy: their "locked / raise-only" behavior comes from the contract itself (minInvestment has no setter; capacity/hardCap uses raise-only setHardCap), not from a backend policy. Moved to §5 and §2 respectively (v3-71). The class definition is retained for any future off-chain-only policy lock.