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-chainNO_EARLY⇔lockup_days=0guard removed (v3-83, supersedes v3-66).
How to read this
- Storage —
DB(Supabase),on-chain(contract state), orboth(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
| Field | Storage | Class | Editable by | Timelock | Notes |
|---|---|---|---|---|---|
fund_wallet | both | A | Admin (governance) | 7d | partner-remainder destination; partner-controlled. proposeFundWalletChange also moves YIELD_DEPOSITOR_ROLE. |
treasury_wallet | both | A* | Admin (governance) | none | Aset 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_wallet | both | A* | Admin (governance) | none | FM 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 only | removed | no getter in new pools | — | In 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-chain | — | immutable | — | The holder-verified requester; payout fn has no destination param. No operator-settable payout address exists. |
Yield claim recipient (msg.sender) | on-chain | — | immutable | — | No 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)
| Field | Storage | Class | Editable by | Timelock | Notes |
|---|---|---|---|---|---|
reserve_bps | DB (drives on-chain split) | A | Admin (governance) | 7d | Deposit 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) | both | A | Admin (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_jurisdiction | both | A | Admin (governance) | 7d | Region 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_token | both | A | Aset ORACLE (/nav-changes → updateNAV) | 24h on decrease | Increase 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 implementation | on-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)
| Field | Storage | Class | Editable by | Timelock | Notes |
|---|---|---|---|---|---|
net_yield_fee_config (JSONB) | DB (JSONB) | C | Admin (PATCH) | none | Computed 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
| Field | Storage | Class | Editable by | Timelock | Notes |
|---|---|---|---|---|---|
is_paused | both | A | Admin (/pause → pause()) | instant | Blocks deposits only; redemption normal. Must be on-chain (DB-only would be bypassable). |
is_emergency_frozen | both | A | Pauser Safe (/freeze) | instant | Halt-only + auto-expiring: exit auto-allowed after ≤ 72h, whole freeze expires ≤ 7d. |
lifecycle_status → IMPAIRED | both | A | Admin (/lifecycle) | 7d | Partner distress → pause deposits; withdrawals stay open (v3-12). |
lifecycle_status → WIND_DOWN | both | A | Admin (proposeWindDown) | 30d | Terminal. 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) | both | A | Admin (/lifecycle) | instant | e.g. UPCOMING→ACTIVE→CLOSED→MATURED. |
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.
| Field | Notes |
|---|---|
penalty_type / penalty_rate_bps / penalty_fee_amount | Early-redemption penalty terms. penalty_rate_bps is integer basis points (e.g. 5000 = 50%; 1% = 100 bps), not a 0–1 fraction (0068). |
lockup_days | 0 = 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_EARLY⇔lockup_days=0 coupling and its on-chain guard (setLockupDays → NoEarlyRequiresZeroLockup, v3-66) were removed; backend/FE validation no longer rejects the pairing. |
maturity_days | The 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_currencies | Per-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.
Related
- 08-smart-contracts — on-chain value sync (Class A/B/C) · timelock SoT
- 08a-contract-reference — per-role access control
- 09a-custody — money-path immutability & non-custody
- 11-db-schema — full column list
- 23-money-path — where money moves (fund & fee flow)
- 14-decisions — v3-71 (matrix corrections), v3-70 (naming), v3-69 (fee split), v3-43 (C-lock), v3-38 (epoch immutability)