# Architecture ## Purpose `nana-core-v6` (npm: `@bananapus/core-v6`) is the root of the V6 stack. It owns project identity, rulesets, permissions, treasury balances, token issuance, fee behavior, payout limits, and the hook interfaces that extension repos use. If a change affects accounting, token supply, fees, terminal routing, or permission semantics, this repo is the source of truth. ## System overview `JBController`, `JBMultiTerminal`, and `JBTerminalStore` form the main execution and accounting path. `JBDirectory`, `JBRulesets`, `JBProjects`, `JBTokens`, `JBPermissions`, `JBSplits`, and related contracts provide routing, identity, and shared state for downstream repos. `JBTerminalStore` is terminal-scoped through `msg.sender`, so each terminal tracks its own balances and usage while sharing the same ruleset and price surfaces. Hooks can change economics or add side effects, but they should not create a second ledger. ## Design principles - Preview functions stay aligned with the state-changing functions they mirror. - Data hooks run before settlement and may change economics. Pay and cash-out hooks run after settlement. - Reserved tokens and other pending supply affect supply-sensitive math before distribution. - Terminal balances, fee accounting, reclaim math, and surplus calculations must agree. - Fee logic taxes value leaving the system, not every internal rebalance. - Rulesets are time-ordered and approval-aware, and downstream deployers depend on predictable ID progression. - Permission checks are protocol safety checks, not just UI hints. For the formal per-contract invariants and guarantees, see [INVARIANTS.md](./INVARIANTS.md). ## Modules | Module | Responsibility | Notes | | --- | --- | --- | | `JBMultiTerminal` | Payment, cash-out, payout, allowance, and fee entrypoints | Execution surface | | `JBTerminalStore` | Shared accounting and preview math | Economic source of truth | | `JBController` | Launch, queue rulesets, mint, burn, and update split groups | Supply and configuration | | `JBDirectory`, `JBRulesets` | Project routing and time-based ruleset lifecycle | Coordination layer | | `JBProjects`, `JBTokens`, `JBERC20` | Identity and token surfaces | Ownership and tokenization | | `JBPermissions`, `JBSplits`, `JBFundAccessLimits`, `JBPrices` | Shared authorization and configuration state | Cross-repo dependencies; price feeds are append-only with backups | ## Trust boundaries - This repo owns the canonical balance and supply transitions. - Hook repos may change inputs and post-settlement behavior, but they should not replace the core ledger. - External price feeds, Permit2, and ERC-20 behavior matter, but accounting truth still lives here. ## Critical flows ### Payment ```text terminal receives funds -> terminal store reads the active ruleset and optional data hooks -> before-pay data hook can change weight and return pay-hook specs -> terminal store records the payment in the terminal-scoped ledger -> controller mints beneficiary tokens and accrues reserved tokens -> pay hooks run after settlement ``` ### Cash out ```text holder requests redemption -> terminal store reads the current ruleset, balances, and supply inputs -> before-cash-out data hook can change reclaim inputs and hook specs -> terminal store records the cash out in the terminal-scoped ledger -> controller burns tokens -> terminal pays reclaim value and routes protocol fees -> cash-out hooks run after settlement ``` ### Launch and queue rulesets ```text owner, operator, or omnichain ruleset operator -> controller launches or queues rulesets -> launch also sets the controller in the directory and configures terminals -> rulesets become the source of truth for later pay, cash-out, and admin constraints ``` ### Payouts and allowances ```text authorized caller -> consumes payout limits or surplus allowances -> funds move to splits, projects, hooks, or direct recipients -> same-terminal project payouts stay inside terminal accounting and may add fee-free surplus ``` ## Accounting model This repo owns the canonical ledger for balances, fees, supply-sensitive reclaim math, payout limits, allowances, reserved tokens, and preview calculations. Other repos may wrap or influence these values, but they should not duplicate them. `JBTerminalStore` keeps terminal balances, payout-limit usage, and surplus-allowance usage. Those reset boundaries are not the same: - payout-limit usage is tracked by ruleset cycle number - surplus-allowance usage is tracked by `ruleset.id` If a duration-based ruleset auto-cycles without a new ruleset ID, payout-limit usage resets but allowance usage does not. ### Currency model: base currency vs accounting-context currency Two distinct currency systems coexist; conflating them is a common integration error. - `ruleset.baseCurrency` is a *standard* currency ID from `JBCurrencyIds` (`ETH = 1`, `USD = 2`). It is the unit the ruleset `weight` is denominated in — the denominator issuance is priced against. It is normally a standard unit, not a token address — but see the advanced exception below. - `JBAccountingContext.currency` is *token-keyed*: `uint32(uint160(tokenAddress))` (including the native-token sentinel). It identifies the actual asset a terminal accounts for, and also keys fund access limits and surplus allowances. Why the split exists — chain portability. The same ruleset must be deployable, byte-identical, on every chain: its encoded configuration is hashed into the cross-chain identity that keys deterministic CREATE addresses and lets suckers peer. But the concrete asset is chain-specific — USDC has a different address on each chain, and a chain's native/gas token need not be ETH. So a ruleset normally denominates its `weight` in a chain-independent *standard* unit (`USD`/`ETH`); each chain then binds its local token address in a per-chain accounting context and registers a local `JBPrices` feed that resolves that token to the ruleset's standard unit. Baking an *ordinary* token address into the ruleset would make the "same" revnet a different configuration on each chain, breaking deterministic addresses and sucker peering. Advanced exception — only if you know what you are doing: you may set `baseCurrency` to a token-keyed value (`uint32(uint160(token))`), and even match it to the accounting-context `currency` so that `amount.currency == baseCurrency` and no feed is read (`weightRatio = 10**decimals`). This is sound when the token resolves to the *same address on every chain* the revnet spans (a single-chain revnet, or a token with a guaranteed deterministic cross-chain address — e.g. another project's same-address token), or when you deliberately want a specific price-feed dynamic. Outside those cases, keep `baseCurrency` a standard unit. `JBPrices` feeds bridge the two. On every `pay`/cash-out, `JBTerminalStore` derives `weightRatio`: if `amount.currency == ruleset.baseCurrency` it is `10**decimals` (no feed read); otherwise it is `PRICES.pricePerUnitOf({pricingCurrency: amount.currency, unitCurrency: baseCurrency})`. So a USD-base revnet accepting USDC (`baseCurrency = USD(2)`, context `currency = uint32(uint160(usdc))`) prices issuance through the registered USDC↔USD feed, and an ETH-base revnet accepting the native token prices through the registered native↔ETH identity feed. Implications: - Any project whose accepted token's currency differs from its `baseCurrency` unit (every USD-denominated revnet, and native-token ETH revnets via the identity feed) reads a price feed on every pay/cash-out for that pair; that pair's feed must stay live for those operations to succeed. `JBPrices` wraps each feed in try/catch and falls through to project→default and their inverses, so register a backup feed for any pair you cannot afford to have a single point of failure on. - Do not set an accounting-context `currency` to a bare `JBCurrencyIds` value to "skip" the feed unless you genuinely want that asset accounted as the well-known unit at 1:1 — it changes accounting/limit keying and forgoes price-accurate issuance. A token-address `baseCurrency` is portable only under the advanced exception above (a same-address-everywhere token); otherwise an accidental mismatch between the accepted token's currency and `baseCurrency` makes the price-feed lookup miss and reverts the operation. See `JBCurrencyIds`, `JBAccountingContext`, and `JBRulesetMetadata.baseCurrency`. ## Security model - Review `JBMultiTerminal`, `JBTerminalStore`, and `JBController` as one pipeline. - `JBTerminalStore` uses shared logic with terminal-scoped state. Misreading that split leads to bad accounting assumptions. - Small changes in fee or surplus logic can affect every downstream repo. - Same-terminal project payouts, fee-free surplus capping, and migration cleanup are coupled. - `allowOwnerMinting` is not a universal mint kill switch. Other allowed paths can still mint. - Hook ordering and preview-execution alignment are ongoing maintenance requirements. ## Safe change guide - Trace both the preview path and the state-changing path for any nontrivial change. - Read downstream hook repos before changing hook metadata or interface expectations. - Keep fee logic, balance logic, reclaim math, and surplus math in sync. - If you change same-terminal payouts between projects, re-check self-pay reverts, fee-free surplus accumulation, and post-pay caps. - If you change ruleset rollover semantics, re-check which counters reset on cycle progression versus new ruleset IDs. - If permissions change, update shared docs and downstream assumptions at the same time. ## Canonical checks - fee-free surplus and same-terminal payout behavior: `test/TestFeeFreeCashOutBypass.sol` - migration and terminal-accounting continuity: `test/TestTerminalMigration.sol` - ruleset ordering and transition behavior: `test/RulesetTransitions.t.sol` ## Source map - `src/JBController.sol` - `src/JBMultiTerminal.sol` - `src/JBTerminalStore.sol` - `src/JBDirectory.sol` - `src/JBRulesets.sol` - `src/JBPermissions.sol` - `test/TestFeeFreeCashOutBypass.sol` - `test/TestTerminalMigration.sol` - `test/RulesetTransitions.t.sol`