@bananapus/core-v6 0.0.30 → 0.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/ADMINISTRATION.md +43 -13
  2. package/ARCHITECTURE.md +62 -137
  3. package/AUDIT_INSTRUCTIONS.md +149 -428
  4. package/CHANGELOG.md +73 -0
  5. package/README.md +90 -201
  6. package/RISKS.md +27 -12
  7. package/SKILLS.md +31 -441
  8. package/STYLE_GUIDE.md +52 -19
  9. package/USER_JOURNEYS.md +76 -627
  10. package/package.json +1 -2
  11. package/references/entrypoints.md +160 -0
  12. package/references/types-errors-events.md +297 -0
  13. package/script/Deploy.s.sol +7 -2
  14. package/script/DeployPeriphery.s.sol +51 -4
  15. package/src/JBController.sol +45 -17
  16. package/src/JBDirectory.sol +26 -13
  17. package/src/JBFundAccessLimits.sol +28 -7
  18. package/src/JBMultiTerminal.sol +180 -86
  19. package/src/JBPermissions.sol +17 -17
  20. package/src/JBRulesets.sol +82 -23
  21. package/src/JBSplits.sol +31 -12
  22. package/src/JBTerminalStore.sol +137 -53
  23. package/src/JBTokens.sol +5 -2
  24. package/src/abstract/JBControlled.sol +10 -3
  25. package/src/abstract/JBPermissioned.sol +1 -1
  26. package/src/interfaces/IJBRulesetDataHook.sol +5 -4
  27. package/src/libraries/JBCashOuts.sol +1 -1
  28. package/src/libraries/JBConstants.sol +1 -1
  29. package/src/libraries/JBCurrencyIds.sol +1 -1
  30. package/src/libraries/JBFees.sol +1 -1
  31. package/src/libraries/JBFixedPointNumber.sol +1 -1
  32. package/src/libraries/JBMetadataResolver.sol +5 -2
  33. package/src/libraries/JBPayoutSplitGroupLib.sol +7 -2
  34. package/src/libraries/JBRulesetMetadataResolver.sol +1 -1
  35. package/src/libraries/JBSplitGroupIds.sol +1 -1
  36. package/src/libraries/JBSurplus.sol +5 -2
  37. package/src/structs/JBSplit.sol +4 -1
  38. package/test/TestForwardedTokenConsumption.sol +419 -0
  39. package/test/audit/CrossTerminalSurplusSpoof.t.sol +140 -0
  40. package/test/audit/CycledSurplusAllowanceReset.t.sol +184 -0
  41. package/test/units/static/JBController/TestPreviewMintOf.sol +5 -4
  42. package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +15 -12
  43. package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +6 -0
  44. package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +3 -0
  45. package/test/units/static/JBMultiTerminal/TestMigrateBalanceOf.sol +3 -0
  46. package/test/units/static/JBMultiTerminal/TestPay.sol +7 -15
  47. package/test/units/static/JBMultiTerminal/TestSendPayoutsOf.sol +1 -1
  48. package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +1 -1
  49. package/CHANGE_LOG.md +0 -479
package/SKILLS.md CHANGED
@@ -1,454 +1,44 @@
1
1
  # Juicebox Core
2
2
 
3
- ## Purpose
4
-
5
- The core Juicebox V6 protocol on EVM: a modular system for launching treasury-backed tokens with configurable rulesets that govern payments, payouts, cash outs, and token issuance.
6
-
7
- ## Contracts
8
-
9
- | Contract | Role |
10
- |----------|------|
11
- | `JBProjects` | ERC-721 project registry. Each NFT mint creates a new project ID. |
12
- | `JBPermissions` | Packed `uint256` bitmap permissions. Operators get specific permission IDs scoped to projects. |
13
- | `JBDirectory` | Maps project IDs to their controller (`IERC165`) and terminals (`IJBTerminal[]`). |
14
- | `JBController` | Orchestrates rulesets, tokens, splits, fund access limits. Entry point for project lifecycle. |
15
- | `JBMultiTerminal` | Handles ETH/ERC-20 payments, cash outs, payouts, surplus allowance, fees. Permit2 integration. |
16
- | `JBTerminalStore` | Bookkeeping: balances, payout limit tracking, surplus calculation, bonding curve reclaim math. |
17
- | `JBRulesets` | Stores/cycles rulesets with weight decay, approval hooks, and weight cache for gas-efficient long-running cycles. |
18
- | `JBTokens` | Dual-balance system: credits (internal) + ERC-20. Credits burned first on burn. |
19
- | `JBSplits` | Split configurations per project/ruleset/group. Packed storage for gas efficiency. |
20
- | `JBFundAccessLimits` | Payout limits and surplus allowances per project/ruleset/terminal/token. |
21
- | `JBPrices` | Price feed registry with project-specific and protocol-wide default feeds. Immutable once set. |
22
- | `JBERC20` | Cloneable ERC-20 with Votes + Permit. Owned by `JBTokens`. Deployed via `Clones.clone()`. |
23
- | `JBFeelessAddresses` | Allowlist for fee-exempt addresses. |
24
- | `JBChainlinkV3PriceFeed` | Chainlink AggregatorV3 price feed with staleness threshold. Rejects negative/zero prices, incomplete rounds (`updatedAt == 0`), and stale answers carried from previous rounds (`answeredInRound < roundId`). |
25
- | `JBChainlinkV3SequencerPriceFeed` | L2 sequencer-aware Chainlink feed (Optimism/Arbitrum) with grace period after restart. Treats any non-zero sequencer answer as down (`answer != 0`). |
26
- | `JBDeadline` | Approval hook: rejects rulesets queued within `DURATION` seconds of start. Ships as `JBDeadline3Hours`, `JBDeadline1Day`, `JBDeadline3Days`, `JBDeadline7Days`. |
27
- | `JBMatchingPriceFeed` | Always returns 1:1. For equivalent currencies (e.g. ETH/NATIVE_TOKEN). |
28
-
29
- ## Key Functions
30
-
31
- ### JBController
32
-
33
- | Function | What it does |
34
- |----------|--------------|
35
- | `launchProjectFor(address owner, string uri, JBRulesetConfig[] rulesetConfigs, JBTerminalConfig[] terminalConfigs, string memo)` | Creates a project, queues its first rulesets, and configures terminals. Returns `projectId`. |
36
- | `launchRulesetsFor(uint256 projectId, JBRulesetConfig[] rulesetConfigs, JBTerminalConfig[] terminalConfigs, string memo)` | Launches the first rulesets for an existing project that has none. |
37
- | `queueRulesetsOf(uint256 projectId, JBRulesetConfig[] rulesetConfigs, string memo)` | Queues new rulesets for a project. Takes effect after the current ruleset ends (or immediately if duration is 0). |
38
- | `mintTokensOf(uint256 projectId, uint256 tokenCount, address beneficiary, string memo, bool useReservedPercent)` | Mints project tokens. Requires `allowOwnerMinting` in the current ruleset or caller must be a terminal/hook with mint permission. |
39
- | `burnTokensOf(address holder, uint256 projectId, uint256 tokenCount, string memo)` | Burns tokens from a holder. Requires holder's permission (`BURN_TOKENS`). |
40
- | `sendReservedTokensToSplitsOf(uint256 projectId)` | Distributes accumulated reserved tokens to the reserved token split group. Returns token count sent. |
41
- | `deployERC20For(uint256 projectId, string name, string symbol, bytes32 salt)` | Deploys a cloneable `JBERC20` for the project. Credits become claimable. |
42
- | `claimTokensFor(address holder, uint256 projectId, uint256 count, address beneficiary)` | Redeems credits for ERC-20 tokens into beneficiary's wallet. |
43
- | `setSplitGroupsOf(uint256 projectId, uint256 rulesetId, JBSplitGroup[] splitGroups)` | Sets the split groups for a project's ruleset. |
44
- | `setTokenFor(uint256 projectId, IJBToken token)` | Sets an existing ERC-20 token for the project (requires `allowSetCustomToken` in ruleset). |
45
- | `setTokenMetadataOf(uint256 projectId, string name, string symbol)` | Sets the name and symbol of a project's ERC-20 token. Requires `SET_TOKEN_METADATA` permission. |
46
- | `setUriOf(uint256 projectId, string uri)` | Sets the project's metadata URI. |
47
- | `transferCreditsFrom(address holder, uint256 projectId, address recipient, uint256 creditCount)` | Transfers credits between addresses (reverts if `pauseCreditTransfers` is set in ruleset). |
48
- | `addPriceFeedFor(uint256 projectId, uint256 pricingCurrency, uint256 unitCurrency, IJBPriceFeed feed)` | Registers a price feed (requires `allowAddPriceFeed` in ruleset). |
49
- | `migrate(uint256 projectId, IERC165 to)` | Migrates the project to a new controller. Calls `beforeReceiveMigrationFrom`, `migrate`, updates directory, then `afterReceiveMigrationFrom`. |
50
- | `currentRulesetOf(uint256 projectId)` | Returns the current ruleset and unpacked metadata. |
51
- | `upcomingRulesetOf(uint256 projectId)` | Returns the upcoming ruleset and unpacked metadata. |
52
- | `allRulesetsOf(uint256 projectId, uint256 startingId, uint256 size)` | Returns an array of rulesets with metadata, paginated. |
53
- | `pendingReservedTokenBalanceOf(uint256 projectId)` | Returns accumulated reserved tokens not yet distributed. |
54
- | `totalTokenSupplyWithReservedTokensOf(uint256 projectId)` | Returns total supply including pending reserved tokens. |
55
- | `previewMintOf(uint256 projectId, uint256 tokenCount, bool useReservedPercent)` | Simulates a mint under the current ruleset. Returns `(beneficiaryTokenCount, reservedTokenCount)`. Reverts if `tokenCount` is 0. |
56
-
57
- ### JBMultiTerminal
58
-
59
- | Function | What it does |
60
- |----------|--------------|
61
- | `pay(uint256 projectId, address token, uint256 amount, address beneficiary, uint256 minReturnedTokens, string memo, bytes metadata)` | Pays a project. Mints project tokens to beneficiary based on ruleset weight. Returns token count. |
62
- | `cashOutTokensOf(address holder, uint256 projectId, uint256 cashOutCount, address tokenToReclaim, uint256 minTokensReclaimed, address payable beneficiary, bytes metadata)` | Burns project tokens and reclaims surplus terminal tokens via bonding curve. |
63
- | `sendPayoutsOf(uint256 projectId, address token, uint256 amount, uint256 currency, uint256 minTokensPaidOut)` | Distributes payouts from the project's balance to its payout split group, up to the payout limit. |
64
- | `useAllowanceOf(uint256 projectId, address token, uint256 amount, uint256 currency, uint256 minTokensPaidOut, address payable beneficiary, address payable feeBeneficiary, string memo)` | Withdraws from the project's surplus allowance to a beneficiary. The `feeBeneficiary` receives tokens minted by the fee payment. |
65
- | `addToBalanceOf(uint256 projectId, address token, uint256 amount, bool shouldReturnHeldFees, string memo, bytes metadata)` | Adds funds to a project's balance without minting tokens. Can unlock held fees. |
66
- | `migrateBalanceOf(uint256 projectId, address token, IJBTerminal to)` | Migrates a project's token balance to another terminal. Requires `allowTerminalMigration`. |
67
- | `processHeldFeesOf(uint256 projectId, address token, uint256 count)` | Processes up to `count` held fees for a project, sending them to the fee beneficiary project. |
68
- | `addAccountingContextsFor(uint256 projectId, JBAccountingContext[] accountingContexts)` | Adds new accounting contexts (token types) to a terminal for a project. |
69
- | `currentSurplusOf(uint256 projectId, address[] tokens, uint256 decimals, uint256 currency)` | Returns the project's current surplus in this terminal. Empty `tokens` = all tokens. |
70
- | `accountingContextForTokenOf(uint256 projectId, address token)` | Returns the accounting context for a specific token. |
71
- | `accountingContextsOf(uint256 projectId)` | Returns all accounting contexts for a project. |
72
- | `previewPayFor(uint256 projectId, address token, uint256 amount, address beneficiary, bytes metadata)` | Simulates a full payment including the reserved/beneficiary token split. Returns `(ruleset, beneficiaryTokenCount, reservedTokenCount, hookSpecifications)`. Composes `STORE.previewPayFrom` + `controller.previewMintOf`. |
73
- | `previewCashOutFrom(address holder, uint256 projectId, uint256 cashOutCount, address tokenToReclaim, address payable beneficiary, bytes metadata)` | Simulates a full cash out including bonding curve and data hook effects. Returns `(ruleset, reclaimAmount, cashOutTaxRate, hookSpecifications)`. Delegates to `STORE.previewCashOutFrom`. |
74
- | `heldFeesOf(uint256 projectId, address token, uint256 count)` | Returns up to `count` held fees for a project/token. |
75
-
76
- ### JBTerminalStore
77
-
78
- | Function | What it does |
79
- |----------|--------------|
80
- | `recordPaymentFrom(address payer, JBTokenAmount amount, uint256 projectId, address beneficiary, bytes metadata)` | Records a payment. Applies data hook if enabled. Returns ruleset, token count, hook specifications. |
81
- | `recordPayoutFor(uint256 projectId, address token, uint256 amount, uint256 currency)` | Records a payout. Enforces payout limits. Returns ruleset and amount paid out. |
82
- | `recordCashOutFor(address holder, uint256 projectId, uint256 cashOutCount, address tokenToReclaim, bool beneficiaryIsFeeless, bytes metadata)` | Records a cash out. Computes reclaim via bonding curve. Returns ruleset, reclaim amount, tax rate, and hook specifications. |
83
- | `recordUsedAllowanceOf(uint256 projectId, address token, uint256 amount, uint256 currency)` | Records surplus allowance usage. Enforces allowance limits. Returns ruleset and used amount. |
84
- | `recordAddedBalanceFor(uint256 projectId, address token, uint256 amount)` | Records funds added to a project's balance. |
85
- | `recordTerminalMigration(uint256 projectId, address token)` | Records a terminal migration, returning the full balance. |
86
- | `balanceOf(address terminal, uint256 projectId, address token)` | Returns the balance of a project at a terminal for a given token. |
87
- | `usedPayoutLimitOf(address terminal, uint256 projectId, address token, uint256 rulesetCycleNumber, uint256 currency)` | Returns the used payout limit for a project in a given cycle. |
88
- | `usedSurplusAllowanceOf(address terminal, uint256 projectId, address token, uint256 rulesetId, uint256 currency)` | Returns the used surplus allowance for a project in a given ruleset. |
89
- | `currentReclaimableSurplusOf(uint256 projectId, uint256 cashOutCount, uint256 totalSupply, uint256 surplus)` | Returns the reclaimable surplus given raw total supply and surplus values. Applies bonding curve from current ruleset. |
90
- | `currentReclaimableSurplusOf(uint256 projectId, uint256 cashOutCount, IJBTerminal[] terminals, address[] tokens, uint256 decimals, uint256 currency)` | Returns the reclaimable surplus across specified terminals and tokens. Empty arrays default to all. |
91
- | `currentTotalReclaimableSurplusOf(uint256 projectId, uint256 cashOutCount, uint256 decimals, uint256 currency)` | Convenience view: reclaimable surplus across all terminals and all tokens. |
92
- | `currentSurplusOf(uint256 projectId, IJBTerminal[] terminals, address[] tokens, uint256 decimals, uint256 currency)` | Returns the current surplus across specified terminals and tokens. Empty arrays default to all. |
93
- | `currentTotalSurplusOf(uint256 projectId, uint256 decimals, uint256 currency)` | Convenience view: total surplus across all terminals and all tokens. |
94
- | `previewPayFrom(address terminal, address payer, JBTokenAmount amount, uint256 projectId, address beneficiary, bytes metadata)` | Simulates a payment without modifying state. Uses the explicit `terminal` parameter for balance/surplus lookups. Invokes data hooks if configured. Returns ruleset, token count, and hook specifications. |
95
- | `previewCashOutFrom(address terminal, address holder, uint256 projectId, uint256 cashOutCount, address tokenToReclaim, bool beneficiaryIsFeeless, bytes metadata)` | Simulates a cash out without modifying state. Uses the explicit `terminal` parameter for balance/surplus lookups. Invokes data hooks if configured. Returns ruleset, reclaim amount, tax rate, and hook specifications. |
96
-
97
- ### JBRulesets
98
-
99
- | Function | What it does |
100
- |----------|--------------|
101
- | `currentOf(uint256 projectId)` | Returns the currently active ruleset with decayed weight and correct cycle number. |
102
- | `latestQueuedOf(uint256 projectId)` | Returns the latest queued ruleset and its approval status. |
103
- | `queueFor(uint256 projectId, uint256 duration, uint256 weight, uint256 weightCutPercent, IJBRulesetApprovalHook approvalHook, uint256 metadata, uint256 mustStartAtOrAfter)` | Queues a new ruleset. Only callable by the project's controller. |
104
- | `updateRulesetWeightCache(uint256 projectId, uint256 rulesetId)` | Updates the weight cache for long-running rulesets. Required when `weightCutMultiple > 20,000` to avoid gas limits. |
105
-
106
- ### JBPermissions
107
-
108
- | Function | What it does |
109
- |----------|--------------|
110
- | `setPermissionsFor(address account, JBPermissionsData permissionsData)` | Grants or revokes operator permissions. ROOT operators can set non-ROOT permissions for others. |
111
- | `hasPermission(address operator, address account, uint256 projectId, uint256 permissionId, bool includeRoot, bool includeWildcardProjectId)` | Checks if an operator has a specific permission. |
112
- | `hasPermissions(address operator, address account, uint256 projectId, uint256[] permissionIds, bool includeRoot, bool includeWildcardProjectId)` | Checks if an operator has all specified permissions. |
113
-
114
- ### JBDirectory
115
-
116
- | Function | What it does |
117
- |----------|--------------|
118
- | `controllerOf(uint256 projectId)` | Returns the project's controller as `IERC165`. |
119
- | `terminalsOf(uint256 projectId)` | Returns the project's terminals as `IJBTerminal[]`. |
120
- | `primaryTerminalOf(uint256 projectId, address token)` | Returns the project's primary terminal for a given token. |
121
- | `isTerminalOf(uint256 projectId, IJBTerminal terminal)` | Checks if a terminal belongs to a project. |
122
- | `setControllerOf(uint256 projectId, IERC165 controller)` | Sets the project's controller. |
123
- | `setTerminalsOf(uint256 projectId, IJBTerminal[] terminals)` | Sets the project's terminals. |
124
- | `setPrimaryTerminalOf(uint256 projectId, address token, IJBTerminal terminal)` | Sets the primary terminal for a token. Requires `ADD_TERMINALS` permission if the terminal is not already in the project's terminal list (implicit addition). |
125
- | `setIsAllowedToSetFirstController(address addr, bool flag)` | Allows/disallows an address to set a project's first controller. Owner-only. |
126
-
127
- ### JBPrices
128
-
129
- | Function | What it does |
130
- |----------|--------------|
131
- | `pricePerUnitOf(uint256 projectId, uint256 pricingCurrency, uint256 unitCurrency, uint256 decimals)` | Returns the price of 1 `unitCurrency` in `pricingCurrency`. Checks project-specific, inverse, then default feeds. |
132
- | `addPriceFeedFor(uint256 projectId, uint256 pricingCurrency, uint256 unitCurrency, IJBPriceFeed feed)` | Registers a price feed. Project ID 0 sets protocol-wide defaults (owner-only). Immutable once set. |
133
-
134
- ### JBTokens
135
-
136
- | Function | What it does |
137
- |----------|--------------|
138
- | `totalSupplyOf(uint256 projectId)` | Returns total supply: credits + ERC-20 tokens. |
139
- | `totalBalanceOf(address holder, uint256 projectId)` | Returns combined credit + ERC-20 balance. |
140
- | `creditBalanceOf(address holder, uint256 projectId)` | Returns the holder's credit balance. |
141
- | `tokenOf(uint256 projectId)` | Returns the ERC-20 token for a project (`IJBToken`). |
142
- | `setTokenMetadataFor(uint256 projectId, string name, string symbol)` | Sets the name and symbol of a project's token. Controller-only. |
143
-
144
- ### JBSplits
145
-
146
- | Function | What it does |
147
- |----------|--------------|
148
- | `splitsOf(uint256 projectId, uint256 rulesetId, uint256 groupId)` | Returns splits for a project/ruleset/group. Falls back to ruleset ID 0 if none set. |
149
-
150
- **Self-auth for `setSplitGroupsOf`**: A contract can set its own split groups without controller authorization if the lower 160 bits of the `groupId` match `msg.sender` AND the upper 96 bits are non-zero. GroupIds with zero upper 96 bits (bare addresses like `uint256(uint160(token))`) are protocol-reserved for terminal payout groups and always require controller auth.
151
-
152
- ### Other
153
-
154
- | Function | What it does |
155
- |----------|--------------|
156
- | `setFeelessAddress(address addr, bool flag)` | Adds or removes an address from the fee exemption list. Owner-only. (`JBFeelessAddresses`) |
157
- | `setControllerAllowed(uint256 projectId)` | Returns whether a project's controller can currently be set. (`IJBDirectoryAccessControl`) |
158
- | `setTerminalsAllowed(uint256 projectId)` | Returns whether a project's terminals can currently be set. (`IJBDirectoryAccessControl`) |
159
-
160
- ## Key Types
161
-
162
- | Struct/Enum | Key Fields | Used In |
163
- |-------------|------------|---------|
164
- | `JBRuleset` | `cycleNumber (uint48)`, `id (uint48)`, `basedOnId (uint48)`, `start (uint48)`, `duration (uint32)`, `weight (uint112)`, `weightCutPercent (uint32)`, `approvalHook`, `metadata (uint256)` | `currentOf()`, `recordPaymentFrom()`, `recordCashOutFor()` return values |
165
- | `JBRulesetConfig` | `mustStartAtOrAfter (uint48)`, `duration (uint32)`, `weight (uint112)`, `weightCutPercent (uint32)`, `approvalHook`, `metadata (JBRulesetMetadata)`, `splitGroups[]`, `fundAccessLimitGroups[]` | `launchProjectFor()`, `queueRulesetsOf()` input |
166
- | `JBRulesetMetadata` | `reservedPercent (uint16)`, `cashOutTaxRate (uint16)`, `baseCurrency (uint32)`, `pausePay`, `pauseCreditTransfers`, `allowOwnerMinting`, `allowSetCustomToken`, `allowTerminalMigration`, `allowSetTerminals`, `allowSetController`, `allowAddAccountingContext`, `allowAddPriceFeed`, `ownerMustSendPayouts`, `holdFees`, `useTotalSurplusForCashOuts`, `useDataHookForPay`, `useDataHookForCashOut`, `dataHook (address)`, `metadata (uint16)` | Packed into `JBRuleset.metadata` |
167
- | `JBSplit` | `percent (uint32)`, `projectId (uint64)`, `beneficiary (address payable)`, `preferAddToBalance`, `lockedUntil (uint48)`, `hook (IJBSplitHook)` | `splitsOf()`, `setSplitGroupsOf()` |
168
- | `JBSplitGroup` | `groupId (uint256)`, `splits (JBSplit[])` | `JBRulesetConfig.splitGroups`, `setSplitGroupsOf()` |
169
- | `JBAccountingContext` | `token (address)`, `decimals (uint8)`, `currency (uint32)` | Terminal token accounting, surplus/reclaim calculations |
170
- | `JBTokenAmount` | `token (address)`, `decimals (uint8)`, `currency (uint32)`, `value (uint256)` | `recordPaymentFrom()` input |
171
- | `JBTerminalConfig` | `terminal (IJBTerminal)`, `accountingContextsToAccept (JBAccountingContext[])` | `launchProjectFor()`, `launchRulesetsFor()` input |
172
- | `JBCurrencyAmount` | `amount (uint224)`, `currency (uint32)` | Payout limits and surplus allowances |
173
- | `JBFundAccessLimitGroup` | `terminal (address)`, `token (address)`, `payoutLimits (JBCurrencyAmount[])`, `surplusAllowances (JBCurrencyAmount[])` | `JBRulesetConfig.fundAccessLimitGroups` |
174
- | `JBPermissionsData` | `operator (address)`, `projectId (uint64)`, `permissionIds (uint8[])` | `setPermissionsFor()` input |
175
- | `JBFee` | `amount (uint256)`, `beneficiary (address)`, `unlockTimestamp (uint48)` | Held fees in `JBMultiTerminal` |
176
- | `JBSingleAllowance` | `sigDeadline (uint256)`, `amount (uint160)`, `expiration (uint48)`, `nonce (uint48)`, `signature (bytes)` | Permit2 allowance in terminal payments |
177
- | `JBRulesetWithMetadata` | `ruleset (JBRuleset)`, `metadata (JBRulesetMetadata)` | `allRulesetsOf()`, `currentRulesetOf()` return values |
178
- | `JBRulesetWeightCache` | `weight (uint112)`, `weightCutMultiple (uint168)` | Weight caching for long-running rulesets in `JBRulesets` |
179
- | `JBApprovalStatus` (enum) | `Empty`, `Upcoming`, `Active`, `ApprovalExpected`, `Approved`, `Failed` | Approval hook status for queued rulesets |
180
-
181
- ### Hook Structs
3
+ ## Use This File For
182
4
 
183
- | Struct | Key Fields | Used In |
184
- |--------|------------|---------|
185
- | `JBBeforePayRecordedContext` | `terminal`, `payer`, `amount (JBTokenAmount)`, `projectId`, `rulesetId`, `beneficiary`, `weight`, `reservedPercent`, `metadata` | `IJBRulesetDataHook.beforePayRecordedWith()` input |
186
- | `JBBeforeCashOutRecordedContext` | `terminal`, `holder`, `projectId`, `rulesetId`, `cashOutCount`, `totalSupply`, `surplus (JBTokenAmount)`, `useTotalSurplus`, `cashOutTaxRate`, `beneficiaryIsFeeless`, `metadata` | `IJBRulesetDataHook.beforeCashOutRecordedWith()` input |
187
- | `JBAfterPayRecordedContext` | `payer`, `projectId`, `rulesetId`, `amount (JBTokenAmount)`, `forwardedAmount (JBTokenAmount)`, `weight`, `newlyIssuedTokenCount`, `beneficiary`, `hookMetadata`, `payerMetadata` | `IJBPayHook.afterPayRecordedWith()` input |
188
- | `JBAfterCashOutRecordedContext` | `holder`, `projectId`, `rulesetId`, `cashOutCount`, `reclaimedAmount (JBTokenAmount)`, `forwardedAmount (JBTokenAmount)`, `cashOutTaxRate`, `beneficiary`, `hookMetadata`, `cashOutMetadata` | `IJBCashOutHook.afterCashOutRecordedWith()` input |
189
- | `JBPayHookSpecification` | `hook (IJBPayHook)`, `noop (bool)`, `amount`, `metadata` | Returned by data hook; specifies which pay hooks to call and how much to forward. `noop = true` means informational-only (callback skipped, amount must be 0). |
190
- | `JBCashOutHookSpecification` | `hook (IJBCashOutHook)`, `noop (bool)`, `amount`, `metadata` | Returned by data hook; specifies which cash out hooks to call and how much to forward. `noop = true` means informational-only (callback skipped, amount must be 0). |
191
- | `JBSplitHookContext` | `token`, `amount`, `decimals`, `projectId`, `groupId`, `split (JBSplit)` | `IJBSplitHook.processSplitWith()` input |
5
+ - Use this file when the task touches protocol core behavior: payments, cash-outs, terminals, controller actions, rulesets, splits, tokens, permissions, or price feeds.
6
+ - Start here if you know the issue is in core, then open the specific contract that owns the state transition you are debugging.
192
7
 
193
- ### Constants (`JBConstants`)
8
+ ## Read This Next
194
9
 
195
- | Constant | Value | Meaning |
196
- |----------|-------|---------|
197
- | `NATIVE_TOKEN` | `0x000000000000000000000000000000000000EEEe` | Sentinel address for native ETH |
198
- | `MAX_RESERVED_PERCENT` | `10_000` | 100% reserved (basis points) |
199
- | `MAX_CASH_OUT_TAX_RATE` | `10_000` | 100% tax rate (basis points) |
200
- | `MAX_WEIGHT_CUT_PERCENT` | `1_000_000_000` | 100% weight cut (9-decimal precision) |
201
- | `SPLITS_TOTAL_PERCENT` | `1_000_000_000` | 100% split allocation (9-decimal precision) |
202
- | `MAX_FEE` | `1000` | 100% fee cap (the actual fee is 25 = 2.5%) |
10
+ | If you need... | Open this next |
11
+ |---|---|
12
+ | Repo overview and protocol framing | [`README.md`](./README.md) |
13
+ | Controller and project lifecycle behavior | [`src/JBController.sol`](./src/JBController.sol), [`src/JBProjects.sol`](./src/JBProjects.sol), [`src/JBTokens.sol`](./src/JBTokens.sol) |
14
+ | Payment, cash-out, surplus, and fee accounting | [`src/JBMultiTerminal.sol`](./src/JBMultiTerminal.sol), [`src/JBTerminalStore.sol`](./src/JBTerminalStore.sol), [`src/JBFundAccessLimits.sol`](./src/JBFundAccessLimits.sol) |
15
+ | Rulesets, permissions, directory, and prices | [`src/JBRulesets.sol`](./src/JBRulesets.sol), [`src/JBPermissions.sol`](./src/JBPermissions.sol), [`src/JBDirectory.sol`](./src/JBDirectory.sol), [`src/JBPrices.sol`](./src/JBPrices.sol) |
16
+ | Shared math, metadata parsing, and constants | [`src/libraries/`](./src/libraries/), [`src/structs/`](./src/structs/), [`src/enums/`](./src/enums/) |
17
+ | Periphery helpers and deployment | [`src/periphery/`](./src/periphery/), [`script/Deploy.s.sol`](./script/Deploy.s.sol), [`script/DeployPeriphery.s.sol`](./script/DeployPeriphery.s.sol) |
18
+ | Invariants, fork tests, and security/economic regressions | [`test/formal/`](./test/formal/), [`test/fork/`](./test/fork/), [`test/audit/`](./test/audit/), [`test/helpers/`](./test/helpers/) |
203
19
 
204
- ### Currency IDs (`JBCurrencyIds`)
20
+ ## Repo Map
205
21
 
206
- | ID | Currency |
207
- |----|----------|
208
- | `1` | ETH |
209
- | `2` | USD |
22
+ | Area | Where to look |
23
+ |---|---|
24
+ | Main contracts | [`src/`](./src/) |
25
+ | Libraries, types, and enums | [`src/libraries/`](./src/libraries/), [`src/structs/`](./src/structs/), [`src/interfaces/`](./src/interfaces/), [`src/enums/`](./src/enums/) |
26
+ | Periphery | [`src/periphery/`](./src/periphery/) |
27
+ | Tests | [`test/`](./test/) |
210
28
 
211
- ### Split Group IDs (`JBSplitGroupIds`)
212
-
213
- | ID | Group |
214
- |----|-------|
215
- | `1` | `RESERVED_TOKENS` -- reserved token distribution |
216
-
217
- ### Special Values
218
-
219
- | Value | Context | Meaning |
220
- |-------|---------|---------|
221
- | `weight = 0` | `JBRuleset` / `JBRulesetConfig` | No token issuance for payments. |
222
- | `weight = 1` | `JBRuleset` / `JBRulesetConfig` | Inherit decayed weight from previous ruleset (sentinel). |
223
- | `duration = 0` | `JBRuleset` / `JBRulesetConfig` | Ruleset never expires; must be explicitly replaced by a new queued ruleset (takes effect immediately). |
224
- | `projectId = 0` | `JBPermissionsData` | Wildcard: permission applies to ALL projects. Cannot be combined with ROOT (1). |
225
- | `permissionId = 1` | `JBPermissions` | ROOT: grants all permissions for the scoped project. |
226
- | `rulesetId = 0` | `JBSplits.splitsOf()` | Fallback split group used when no splits are set for a specific ruleset. |
227
- | `projectId = 0` | `JBPrices.addPriceFeedFor()` | Sets a protocol-wide default price feed (owner-only). |
228
-
229
- ## Gotchas
230
-
231
- - `IJBDirectory.controllerOf()` returns `IERC165`, NOT `address` -- must wrap: `address(directory.controllerOf(projectId))`
232
- - `IJBDirectory.primaryTerminalOf()` returns `IJBTerminal`, NOT `address` -- must wrap: `address(directory.primaryTerminalOf(projectId, token))`
233
- - `IJBDirectory.terminalsOf()` returns `IJBTerminal[]`, NOT `address[]`
234
- - `pricePerUnitOf()` is on `IJBPrices`, NOT `IJBController` -- access via `IJBController(ctrl).PRICES().pricePerUnitOf(...)`
235
- - `JBRulesetConfig` fields need explicit casts: `uint48 mustStartAtOrAfter`, `uint32 duration`, `uint112 weight`, `uint32 weightCutPercent`
236
- - Zero-amount `pay{value:0}()` and zero-count `cashOutTokensOf(count=0)` are valid no-ops (mint/return 0)
237
- - `sendPayoutsOf()` reverts when `amount > payout limit` -- does NOT auto-cap
238
- - `IJBTokens.claimTokensFor()` takes 4 args: `(holder, projectId, count, beneficiary)` -- NOT 3
239
- - `JBFeelessAddresses.setFeelessAddress()` NOT `setIsFeelessAddress()` -- the function name omits "Is"
240
- - Named returns auto-return (no explicit `return` statement needed in Solidity)
241
- - `bool` defaults to `false` (correct security default for metadata flags)
242
- - Credits are burned before ERC-20 tokens in `JBTokens.burnFrom()`
243
- - `JBRuleset.weight` is `uint112` with 18 decimals; `JBRuleset.metadata` is packed -- use `JBRulesetMetadataResolver` to unpack
244
- - `JBERC20` is cloned via `Clones.clone()` -- its constructor sets invalid name/symbol; real values set in `initialize()`
245
- - Fee is 2.5% (`FEE = 25` out of `MAX_FEE = 1000`)
246
- - Project #1 is the fee beneficiary project (receives all protocol fees)
247
- - **Fee-free cashout exemption is scoped to fee-free intra-terminal payout amounts.** `_feeFreeSurplusOf[projectId][token]` accumulates the value of fee-free payouts. After any outflow (payouts, `useAllowanceOf`, non-zero-tax or feeless cashouts), the counter is capped at the remaining balance — non-fee-free funds leave first, preserving the fee-free counter. During cashout with `cashOutTaxRate=0`, the 2.5% fee applies only up to this surplus, then depletes. Once consumed, subsequent cashouts are fee-free again. Cleared on terminal migration. This prevents a round-trip fee bypass (intra-terminal payout → zero-tax cashout) while scoping fees precisely to the fee-free inflow.
248
- - `JBProjects` constructor optionally mints project #1 to `feeProjectOwner` -- if `address(0)`, no fee project is created
249
- - `JBMultiTerminal` derives `DIRECTORY` from the provided `store` in its constructor -- not passed directly
250
- - `JBPrices.pricePerUnitOf()` checks project-specific feed, then inverse, then falls back to `DEFAULT_PROJECT_ID = 0`
251
- - `useAllowanceOf()` takes 8 args including `address payable feeBeneficiary` -- do NOT omit it
252
- - Cash out tax rate of 0% = proportional (1:1) redemption; 100% = nothing reclaimable (all surplus locked). Do NOT confuse with a "cash out rate" where 100% means full redemption.
253
- - `cashOutTaxRate` in `JBRulesetMetadata` is `uint16` (max 10,000 basis points), NOT 9-decimal precision
254
- - `reservedPercent` in `JBRulesetMetadata` is `uint16` (max 10,000 basis points), NOT 9-decimal precision
255
- - `weight` in `JBRuleset` is `uint112`, but `weight` in `JBRulesetConfig` is also `uint112` -- both use 18 decimals
256
- - `JBSplits.splitsOf()` falls back to ruleset ID 0 if no splits are set for the given rulesetId
257
- - Held fees are held for 28 days (`_FEE_HOLDING_SECONDS = 2,419,200`) before they can be processed
258
- - `JBController`, `JBMultiTerminal`, `JBProjects`, `JBPrices`, `JBPermissions` all support ERC-2771 meta-transactions
259
- - `JBRulesetMetadataResolver` bit layout: version (4 bits), reservedPercent (16), cashOutTaxRate (16), baseCurrency (32), 14 boolean flags (1 bit each), dataHook address (160), metadata (14)
260
- - `IJBDirectoryAccessControl` has `setControllerAllowed()` and `setTerminalsAllowed()` -- NOT `setControllerAllowedFor()`
261
- - Price feeds are immutable once set in `JBPrices` -- they cannot be replaced or removed
262
- - `JBFundAccessLimits` requires payout limits and surplus allowances to be in strictly increasing currency order to prevent duplicates
263
- - **Empty `fundAccessLimitGroups` = zero payouts, NOT unlimited.** If a ruleset's `fundAccessLimitGroups` array is empty (or has no entry for the terminal/token), `payoutLimitsOf()` returns an empty array → cumulative limit is 0 → `sendPayoutsOf()` reverts on any amount. To allow unlimited payouts, explicitly set a payout limit with `amount: type(uint224).max`.
264
- - **`groupId` (uint256) vs `currency` (uint32) are different types for the same address.** `JBSplitGroup.groupId` is `uint256(uint160(tokenAddress))` while `JBAccountingContext.currency` is `uint32(uint160(tokenAddress))`. These truncate differently — only `NATIVE_TOKEN` (0x000000000000000000000000000000000000EEEe) matches by coincidence. Don't confuse them.
265
- - **`JBAccountingContext.currency` is NOT `baseCurrency` — by design.** `baseCurrency` in ruleset metadata uses abstract real-world values (1 = ETH, 2 = USD) so rulesets are portable across chains — `baseCurrency=2` means "issue X tokens per USD" whether on Ethereum, Base, or Arbitrum. `JBAccountingContext.currency` uses token-derived values (`uint32(uint160(tokenAddress))`) because terminals track specific tokens at specific addresses — e.g. NATIVE_TOKEN = 61166, USDC on Ethereum = 909516616, USDC on Base = 3169378579. `JBPrices` mediates between the two: it converts token-derived currencies to/from abstract currencies (e.g. USDC token → USD concept, NATIVE_TOKEN → ETH concept) so that payout limits denominated in USD work correctly regardless of which token the terminal holds. The separation is what makes cross-chain consistency possible: same ruleset, different terminal accounting per chain.
266
- - **Don't queue multiple identical rulesets.** A ruleset with a `duration` automatically cycles — no need to queue copies. Queue multiple rulesets only when configuration actually changes between periods (e.g. different weight, splits, or limits).
267
- - **`NATIVE_TOKEN` represents a different token on each chain.** `NATIVE_TOKEN` (`0x000000000000000000000000000000000000EEEe`) is the token received via `msg.value` — ETH on Ethereum/Base/Optimism/Arbitrum, CELO on Celo, etc. Its currency is `uint32(uint160(NATIVE_TOKEN))` = 61166. A `JBMatchingPriceFeed` (returns 1:1) is deployed for `ETH:NATIVE_TOKEN` on ETH-native chains so that `baseCurrency=ETH` resolves correctly to the native token. On non-ETH-native chains, a different price feed would be needed.
268
- - **Noop hook specifications are informational-only.** `noop = true` + `amount != 0` reverts with `JBTerminalStore_NoopHookSpecHasAmount`. Data hooks use noop specs to return diagnostics to preview clients without triggering a hook callback. The `noop` flag only suppresses the callback — parameter overrides (weight, tax rate, supply) from the data hook still apply.
269
-
270
- ## Permission IDs
271
-
272
- Quick-reference for the most common `JBPermissionIds` values (from `@bananapus/permission-ids-v6`). Pass these to `JBPermissions.setPermissionsFor()`.
273
-
274
- | ID | Name | Gates |
275
- |----|------|-------|
276
- | `1` | `ROOT` | All permissions for the scoped project. Cannot be combined with `projectId = 0`. |
277
- | `2` | `QUEUE_RULESETS` | `JBController.queueRulesetsOf` |
278
- | `3` | `LAUNCH_RULESETS` | `JBController.launchRulesetsFor` |
279
- | `4` | `CASH_OUT_TOKENS` | `JBMultiTerminal.cashOutTokensOf` |
280
- | `5` | `SEND_PAYOUTS` | `JBMultiTerminal.sendPayoutsOf` |
281
- | `6` | `MIGRATE_TERMINAL` | `JBMultiTerminal.migrateBalanceOf` |
282
- | `7` | `SET_PROJECT_URI` | `JBController.setUriOf` |
283
- | `8` | `DEPLOY_ERC20` | `JBController.deployERC20For` |
284
- | `9` | `SET_TOKEN` | `JBController.setTokenFor` |
285
- | `10` | `MINT_TOKENS` | `JBController.mintTokensOf` |
286
- | `11` | `BURN_TOKENS` | `JBController.burnTokensOf` |
287
- | `12` | `CLAIM_TOKENS` | `JBController.claimTokensFor` |
288
- | `13` | `TRANSFER_CREDITS` | `JBController.transferCreditsFrom` |
289
- | `14` | `SET_CONTROLLER` | `JBDirectory.setControllerOf` |
290
- | `15` | `SET_TERMINALS` | `JBDirectory.setTerminalsOf` (can remove primary terminal) |
291
- | `16` | `SET_PRIMARY_TERMINAL` | `JBDirectory.setPrimaryTerminalOf` (also requires `ADD_TERMINALS` if the terminal is not already in the project's list) |
292
- | `17` | `USE_ALLOWANCE` | `JBMultiTerminal.useAllowanceOf` |
293
- | `18` | `SET_SPLIT_GROUPS` | `JBController.setSplitGroupsOf` |
294
- | `19` | `ADD_PRICE_FEED` | `JBController.addPriceFeedFor` |
295
- | `20` | `ADD_ACCOUNTING_CONTEXTS` | `JBMultiTerminal.addAccountingContextsFor` |
296
- | `21` | `SET_TOKEN_METADATA` | `JBController.setTokenMetadataOf` |
297
-
298
- IDs 22-33 are used by extension contracts (721 hook, buyback hook, router terminal, suckers).
299
-
300
- ## Common Errors
301
-
302
- Errors an agent is most likely to encounter. All are custom errors (revert with selector).
303
-
304
- | Error | Contract | When |
305
- |-------|----------|------|
306
- | `JBPermissioned_Unauthorized` | `JBPermissioned` | Caller lacks the required permission ID for the project. |
307
- | `JBController_RulesetsArrayEmpty` | `JBController` | `launchProjectFor` / `queueRulesetsOf` called with empty rulesets array. |
308
- | `JBController_RulesetsAlreadyLaunched` | `JBController` | `launchRulesetsFor` called on a project that already has rulesets. |
309
- | `JBController_MintNotAllowedAndNotTerminalOrHook` | `JBController` | `mintTokensOf` called but `allowOwnerMinting` is false and caller is not a terminal/hook. |
310
- | `JBController_ZeroTokensToMint` | `JBController` | `mintTokensOf` called with `tokenCount = 0`. |
311
- | `JBController_ZeroTokensToBurn` | `JBController` | `burnTokensOf` called with `tokenCount = 0`. |
312
- | `JBController_NoReservedTokens` | `JBController` | `sendReservedTokensToSplitsOf` called but no pending reserved tokens. |
313
- | `JBController_CreditTransfersPaused` | `JBController` | `transferCreditsFrom` called but `pauseCreditTransfers` is set in ruleset. |
314
- | `JBController_RulesetSetTokenNotAllowed` | `JBController` | `setTokenFor` called but `allowSetCustomToken` is false in ruleset. |
315
- | `JBController_AddingPriceFeedNotAllowed` | `JBController` | `addPriceFeedFor` called but `allowAddPriceFeed` is false in ruleset. |
316
- | `JBController_InvalidReservedPercent` | `JBController` | `reservedPercent` exceeds `MAX_RESERVED_PERCENT` (10,000). |
317
- | `JBController_InvalidCashOutTaxRate` | `JBController` | `cashOutTaxRate` exceeds `MAX_CASH_OUT_TAX_RATE` (10,000). |
318
- | `JBMultiTerminal_UnderMinReturnedTokens` | `JBMultiTerminal` | Payment minted fewer tokens than `minReturnedTokens`. |
319
- | `JBMultiTerminal_UnderMinTokensReclaimed` | `JBMultiTerminal` | Cash out reclaimed less than `minTokensReclaimed`. |
320
- | `JBMultiTerminal_UnderMinTokensPaidOut` | `JBMultiTerminal` | Payout distributed less than `minTokensPaidOut`. |
321
- | `JBMultiTerminal_TokenNotAccepted` | `JBMultiTerminal` | Token has no accounting context for the project in this terminal. |
322
- | `JBMultiTerminal_NoMsgValueAllowed` | `JBMultiTerminal` | `msg.value > 0` sent with an ERC-20 payment (not `NATIVE_TOKEN`). |
323
- | `JBMultiTerminal_PermitAllowanceNotEnough` | `JBMultiTerminal` | Permit2 allowance insufficient for the payment amount. |
324
- | `JBTerminalStore_RulesetPaymentPaused` | `JBTerminalStore` | `pausePay` is set in the current ruleset. |
325
- | `JBTerminalStore_RulesetNotFound` | `JBTerminalStore` | No ruleset exists for the project (not launched). |
326
- | `JBTerminalStore_InadequateControllerPayoutLimit` | `JBTerminalStore` | `sendPayoutsOf` amount exceeds the payout limit for this cycle. |
327
- | `JBTerminalStore_InadequateControllerAllowance` | `JBTerminalStore` | `useAllowanceOf` amount exceeds the surplus allowance. |
328
- | `JBTerminalStore_InadequateTerminalStoreBalance` | `JBTerminalStore` | Withdrawal exceeds the terminal's recorded balance. |
329
- | `JBTerminalStore_InsufficientTokens` | `JBTerminalStore` | Cash out count exceeds the holder's token balance. |
330
- | `JBTerminalStore_TerminalMigrationNotAllowed` | `JBTerminalStore` | `migrateBalanceOf` called but `allowTerminalMigration` is false. |
331
- | `JBTerminalStore_NoopHookSpecHasAmount` | `JBTerminalStore` | Data hook returned a noop spec with `amount != 0`. |
332
- | `JBTerminalStore_AccountingContextAlreadySet` | `JBTerminalStore` | Accounting context already exists for that token. |
333
- | `JBDirectory_SetControllerNotAllowed` | `JBDirectory` | Controller change not allowed by the current ruleset. |
334
- | `JBDirectory_SetTerminalsNotAllowed` | `JBDirectory` | Terminal change not allowed by the current ruleset. |
335
- | `JBDirectory_DuplicateTerminals` | `JBDirectory` | Duplicate terminal in the terminals array. |
336
- | `JBTokens_ProjectAlreadyHasToken` | `JBTokens` | `deployERC20For` / `setTokenFor` called but project already has an ERC-20. |
337
- | `JBTokens_InsufficientCredits` | `JBTokens` | `claimTokensFor` count exceeds credit balance. |
338
- | `JBTokens_TokensMustHave18Decimals` | `JBTokens` | Custom token does not use 18 decimals. |
339
- | `JBSplits_TotalPercentExceeds100` | `JBSplits` | Split percentages sum exceeds `SPLITS_TOTAL_PERCENT`. |
340
- | `JBPrices_PriceFeedAlreadyExists` | `JBPrices` | Feed already set for that currency pair (immutable). |
341
- | `JBPrices_PriceFeedNotFound` | `JBPrices` | No feed found for the requested currency pair. |
342
- | `JBPermissions_CantSetRootPermissionForWildcardProject` | `JBPermissions` | Tried to grant ROOT with `projectId = 0` (wildcard). |
343
- | `JBRulesets_InvalidWeight` | `JBRulesets` | Weight exceeds `uint112.max`. |
344
- | `JBRulesets_InvalidWeightCutPercent` | `JBRulesets` | `weightCutPercent` exceeds `MAX_WEIGHT_CUT_PERCENT`. |
345
- | `JBFundAccessLimits_InvalidPayoutLimitCurrencyOrdering` | `JBFundAccessLimits` | Payout limit currencies not in strictly increasing order. |
346
-
347
- ## Key Events
348
-
349
- The most important events for indexing and off-chain monitoring. Indexed params marked with `*`.
350
-
351
- | Event | Interface | Key Params |
352
- |-------|-----------|------------|
353
- | `Pay` | `IJBTerminal` | `rulesetId*`, `rulesetCycleNumber*`, `projectId*`, `payer`, `beneficiary`, `amount`, `newlyIssuedTokenCount` |
354
- | `CashOutTokens` | `IJBCashOutTerminal` | `rulesetId*`, `rulesetCycleNumber*`, `projectId*`, `holder`, `beneficiary`, `cashOutCount`, `cashOutTaxRate`, `reclaimAmount` |
355
- | `SendPayouts` | `IJBPayoutTerminal` | `rulesetId*`, `rulesetCycleNumber*`, `projectId*`, `projectOwner`, `amount`, `amountPaidOut`, `fee`, `netLeftoverPayoutAmount` |
356
- | `SendPayoutToSplit` | `IJBPayoutTerminal` | `projectId*`, `rulesetId*`, `group*`, `split`, `amount`, `netAmount` |
357
- | `UseAllowance` | `IJBPayoutTerminal` | `rulesetId*`, `rulesetCycleNumber*`, `projectId*`, `beneficiary`, `amount`, `netAmountPaidOut` |
358
- | `MintTokens` | `IJBController` | `beneficiary*`, `projectId*`, `tokenCount`, `beneficiaryTokenCount`, `reservedPercent` |
359
- | `BurnTokens` | `IJBController` | `holder*`, `projectId*`, `tokenCount` |
360
- | `SendReservedTokensToSplits` | `IJBController` | `rulesetId*`, `rulesetCycleNumber*`, `projectId*`, `owner`, `tokenCount`, `leftoverAmount` |
361
- | `SendReservedTokensToSplit` | `IJBController` | `projectId*`, `rulesetId*`, `groupId*`, `split`, `tokenCount` |
362
- | `SplitHookReverted` | `IJBController` | `projectId*`, `hook`, `reason` |
363
- | `LaunchProject` | `IJBController` | `rulesetId`, `projectId`, `projectUri` |
364
- | `QueueRulesets` | `IJBController` | `rulesetId`, `projectId` |
365
- | `DeployERC20` | `IJBController` | `projectId*`, `deployer*`, `salt`, `saltHash`, `caller` |
366
- | `SetUri` | `IJBController` | `projectId*`, `uri` |
367
- | `AddToBalance` | `IJBTerminal` | `projectId*`, `amount`, `returnedFees` |
368
- | `MigrateTerminal` | `IJBTerminal` | `projectId*`, `token*`, `to*`, `amount` |
369
- | `HoldFee` | `IJBFeeTerminal` | `projectId*`, `token*`, `amount*`, `fee`, `beneficiary` |
370
- | `ProcessFee` | `IJBFeeTerminal` | `projectId*`, `token*`, `amount*`, `wasHeld`, `beneficiary` |
371
- | `ReturnHeldFees` | `IJBFeeTerminal` | `projectId*`, `token*`, `amount*`, `returnedFees`, `leftoverAmount` |
372
- | `Create` | `IJBProjects` | `projectId*`, `owner*` |
373
- | `OperatorPermissionsSet` | `IJBPermissions` | (operator, account, projectId, permissionIds, packed, caller) |
374
- | `RulesetQueued` | `IJBRulesets` | (rulesetId, projectId, duration, weight, weightCutPercent, approvalHook, metadata, mustStartAtOrAfter, caller) |
375
- | `SetSplit` | `IJBSplits` | (projectId, rulesetId, groupId, split, caller) |
376
- | `AddPriceFeed` | `IJBPrices` | (projectId, pricingCurrency, unitCurrency, feed, caller) |
377
-
378
- ## Hook Interface Return Types
379
-
380
- ### `IJBRulesetDataHook.beforePayRecordedWith()`
381
-
382
- ```solidity
383
- function beforePayRecordedWith(JBBeforePayRecordedContext calldata context)
384
- external view
385
- returns (
386
- uint256 weight, // Overrides the ruleset's weight for token issuance
387
- JBPayHookSpecification[] memory hookSpecifications // Pay hooks to call + amounts to forward
388
- );
389
- ```
390
-
391
- The data hook can override `weight` to change how many tokens the payer receives. Return `hookSpecifications` to redirect funds to pay hooks (each spec has `hook`, `amount`, `metadata`, and `noop`). An empty array means all funds stay in the terminal balance.
392
-
393
- ### `IJBRulesetDataHook.beforeCashOutRecordedWith()`
394
-
395
- ```solidity
396
- function beforeCashOutRecordedWith(JBBeforeCashOutRecordedContext calldata context)
397
- external view
398
- returns (
399
- uint256 cashOutTaxRate, // Overrides the ruleset's cash out tax rate
400
- uint256 cashOutCount, // Overrides the number of tokens being cashed out
401
- uint256 totalSupply, // Overrides total supply for bonding curve calc
402
- JBCashOutHookSpecification[] memory hookSpecifications // Cash out hooks to call + amounts to forward
403
- );
404
- ```
405
-
406
- The data hook can override `cashOutTaxRate` (0 = proportional, 10000 = nothing reclaimable), `cashOutCount` and `totalSupply` (to shift the bonding curve), and return `hookSpecifications` to redirect reclaimed funds to cash out hooks.
407
-
408
- ### `IJBRulesetDataHook.hasMintPermissionFor()`
409
-
410
- ```solidity
411
- function hasMintPermissionFor(uint256 projectId, JBRuleset memory ruleset, address addr)
412
- external view returns (bool flag);
413
- ```
414
-
415
- Returns whether `addr` is allowed to mint tokens for the project. Called by `JBController.mintTokensOf` when the caller is not the owner and `allowOwnerMinting` is false -- the data hook can grant mint permission to specific addresses (e.g. suckers for omnichain bridging).
416
-
417
- ## Example Integration
418
-
419
- ```solidity
420
- // SPDX-License-Identifier: MIT
421
- pragma solidity 0.8.28;
29
+ ## Purpose
422
30
 
423
- import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
424
- import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
425
- import {IJBMultiTerminal} from "@bananapus/core-v6/src/interfaces/IJBMultiTerminal.sol";
426
- import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
427
- import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
31
+ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-backed tokens with configurable rulesets that govern payments, payouts, cash outs, and token issuance.
428
32
 
429
- contract PayProject {
430
- IJBDirectory public immutable DIRECTORY;
33
+ ## Reference Files
431
34
 
432
- constructor(IJBDirectory directory) {
433
- DIRECTORY = directory;
434
- }
35
+ | If you need... | Open this next |
36
+ |---|---|
37
+ | Contract map and callable entrypoints | [`references/entrypoints.md`](./references/entrypoints.md) |
38
+ | Types, constants, gotchas, permissions, common errors, events, and hook return shapes | [`references/types-errors-events.md`](./references/types-errors-events.md) |
435
39
 
436
- /// @notice Pay a project with native ETH and receive project tokens.
437
- function payProject(uint256 projectId) external payable returns (uint256 tokenCount) {
438
- // Look up the project's primary terminal for native ETH.
439
- IJBTerminal terminal = DIRECTORY.primaryTerminalOf(projectId, JBConstants.NATIVE_TOKEN);
440
- require(address(terminal) != address(0), "No terminal");
40
+ ## Working Rules
441
41
 
442
- // Pay the project. The msg.sender receives the minted tokens.
443
- tokenCount = IJBMultiTerminal(address(terminal)).pay{value: msg.value}({
444
- projectId: projectId,
445
- token: JBConstants.NATIVE_TOKEN,
446
- amount: msg.value,
447
- beneficiary: msg.sender,
448
- minReturnedTokens: 0,
449
- memo: "Paid via PayProject",
450
- metadata: ""
451
- });
452
- }
453
- }
454
- ```
42
+ - Open source before relying on any summary here. This skill is a router, not the ground truth.
43
+ - For runtime bugs, start from the terminal/controller/store contract that owns the state transition.
44
+ - For config or weird value-shape issues, open `references/types-errors-events.md` before changing structs or metadata packing.
package/STYLE_GUIDE.md CHANGED
@@ -26,8 +26,8 @@ pragma solidity 0.8.28;
26
26
  // Interfaces, structs, enums — caret for forward compatibility
27
27
  pragma solidity ^0.8.0;
28
28
 
29
- // Libraries — caret, may use newer features
30
- pragma solidity ^0.8.17;
29
+ // Libraries — pin to exact version like contracts
30
+ pragma solidity 0.8.28;
31
31
  ```
32
32
 
33
33
  ## Imports
@@ -86,12 +86,20 @@ contract JBExample is JBPermissioned, IJBExample {
86
86
 
87
87
  uint256 internal constant _FEE_BENEFICIARY_PROJECT_ID = 1;
88
88
 
89
+ //*********************************************************************//
90
+ // ------------------------ private constants ------------------------ //
91
+ //*********************************************************************//
92
+
89
93
  //*********************************************************************//
90
94
  // --------------- public immutable stored properties ---------------- //
91
95
  //*********************************************************************//
92
96
 
93
97
  IJBDirectory public immutable override DIRECTORY;
94
98
 
99
+ //*********************************************************************//
100
+ // -------------- internal immutable stored properties -------------- //
101
+ //*********************************************************************//
102
+
95
103
  //*********************************************************************//
96
104
  // --------------------- public stored properties -------------------- //
97
105
  //*********************************************************************//
@@ -100,10 +108,26 @@ contract JBExample is JBPermissioned, IJBExample {
100
108
  // -------------------- internal stored properties ------------------- //
101
109
  //*********************************************************************//
102
110
 
111
+ //*********************************************************************//
112
+ // -------------------- private stored properties -------------------- //
113
+ //*********************************************************************//
114
+
115
+ //*********************************************************************//
116
+ // ------------------- transient stored properties ------------------- //
117
+ //*********************************************************************//
118
+
103
119
  //*********************************************************************//
104
120
  // -------------------------- constructor ---------------------------- //
105
121
  //*********************************************************************//
106
122
 
123
+ //*********************************************************************//
124
+ // ---------------------------- modifiers ---------------------------- //
125
+ //*********************************************************************//
126
+
127
+ //*********************************************************************//
128
+ // ------------------------- receive / fallback ---------------------- //
129
+ //*********************************************************************//
130
+
107
131
  //*********************************************************************//
108
132
  // ---------------------- external transactions ---------------------- //
109
133
  //*********************************************************************//
@@ -120,6 +144,10 @@ contract JBExample is JBPermissioned, IJBExample {
120
144
  // ----------------------- public transactions ----------------------- //
121
145
  //*********************************************************************//
122
146
 
147
+ //*********************************************************************//
148
+ // ---------------------- internal transactions ---------------------- //
149
+ //*********************************************************************//
150
+
123
151
  //*********************************************************************//
124
152
  // ----------------------- internal helpers -------------------------- //
125
153
  //*********************************************************************//
@@ -138,18 +166,28 @@ contract JBExample is JBPermissioned, IJBExample {
138
166
  1. Custom errors
139
167
  2. Public constants
140
168
  3. Internal constants
141
- 4. Public immutable stored properties
142
- 5. Internal immutable stored properties
143
- 6. Public stored properties
144
- 7. Internal stored properties
145
- 8. Constructor
146
- 9. External transactions
147
- 10. External views
148
- 11. Public views
149
- 12. Public transactions
150
- 13. Internal helpers
151
- 14. Internal views
152
- 15. Private helpers
169
+ 4. Private constants
170
+ 5. Public immutable stored properties
171
+ 6. Internal immutable stored properties
172
+ 7. Public stored properties
173
+ 8. Internal stored properties
174
+ 9. Private stored properties
175
+ 10. Transient stored properties
176
+ 11. Constructor
177
+ 12. Modifiers
178
+ 13. Receive / fallback
179
+ 14. External transactions
180
+ 15. External views
181
+ 16. Public views
182
+ 17. Public transactions
183
+ 18. Internal transactions
184
+ 19. Internal helpers
185
+ 20. Internal views
186
+ 21. Private helpers
187
+
188
+ Use these additional section labels where they better match the contents of the block:
189
+ - `internal functions` is accepted as equivalent to `internal helpers`
190
+ - `events` and `structs` are acceptable in specialized contracts that define them explicitly
153
191
 
154
192
  Functions are alphabetized within each section.
155
193
 
@@ -570,8 +608,3 @@ CI checks formatting via `forge fmt --check`.
570
608
  ### Contract Size Checks
571
609
 
572
610
  CI runs `forge build --sizes` to catch contracts approaching the 24KB limit. When the repo's default `optimizer_runs` differs from what you want for size checking, use `FOUNDRY_PROFILE=ci_sizes forge build --sizes` with a `[profile.ci_sizes]` section in `foundry.toml`.
573
-
574
-
575
- ## Repo-Specific Deviations
576
-
577
- None. This repo follows the standard configuration exactly.