@bananapus/core-v6 0.0.65 → 0.0.66

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.
package/README.md CHANGED
@@ -21,7 +21,7 @@ This package provides:
21
21
  - token issuance, ruleset queueing, token setup, and splits through `JBController`
22
22
  - multi-token terminal accounting through `JBMultiTerminal` and `JBTerminalStore`
23
23
  - operator permissions through `JBPermissions`
24
- - on-chain price-feed routing through `JBPrices`
24
+ - on-chain price-feed routing and fallback feeds through `JBPrices`
25
25
 
26
26
  Use this repo when you need the protocol's canonical accounting and execution logic. Do not copy that logic into downstream repos unless the repo is explicitly meant to wrap or extend core.
27
27
 
@@ -72,6 +72,7 @@ The shortest reading path is:
72
72
  - Data hooks and cash-out hooks can change economics and side effects. They are part of the protocol surface.
73
73
  - Permission checks are not always against the project owner. Some flows are scoped to the token holder instead.
74
74
  - Preview and execution are intentionally close, but callers should still treat them as separate surfaces when hooks or routing can change behavior.
75
+ - Fee-bearing cash-out, payout, and allowance calls can carry a referral project ID. This credits fee volume; it does not redirect the fee itself.
75
76
 
76
77
  ## Where State Lives
77
78
 
@@ -79,6 +80,7 @@ The shortest reading path is:
79
80
  - controller and terminal routing: `JBDirectory`
80
81
  - ruleset history and activation: `JBRulesets`
81
82
  - balances, surplus, fees, and reclaim accounting: `JBTerminalStore`
83
+ - referral fee-volume accounting: `JBTerminalStore`
82
84
  - operator authority: `JBPermissions`
83
85
 
84
86
  When a flow is unclear, read the contract that owns the state before the contract that forwards into it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/core-v6",
3
- "version": "0.0.65",
3
+ "version": "0.0.66",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,9 +20,9 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
20
20
  | `JBTokens` | Dual-balance system: credits (internal) + ERC-20. Credits burned first on burn. |
21
21
  | `JBSplits` | Split configurations per project/ruleset/group. Packed storage for gas efficiency. |
22
22
  | `JBFundAccessLimits` | Payout limits and surplus allowances per project/ruleset/terminal/token. |
23
- | `JBPrices` | Price feed registry with project-specific and protocol-wide default feeds. Immutable once set. |
23
+ | `JBPrices` | Append-only price feed registry with project-specific feeds, protocol defaults, inverse lookup, and backup feeds. |
24
24
  | `JBERC20` | Cloneable ERC-20 with Votes + Permit + ERC-1271. Controlled by `JBTokens` via `onlyTokens`. Deployed via `Clones.clone()`. |
25
- | `JBFeelessAddresses` | Allowlist for fee-exempt addresses. |
25
+ | `JBFeelessAddresses` | Static and hook-driven fee-exemption registry. |
26
26
  | `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`). |
27
27
  | `JBChainlinkV3SequencerPriceFeed` | L2 sequencer-aware Chainlink feed (Optimism/Arbitrum) with grace period after restart. Treats any non-zero sequencer answer as down (`answer != 0`). |
28
28
  | `JBDeadline` | Approval hook: rejects rulesets queued within `DURATION` seconds of start. Ships as `JBDeadline3Hours`, `JBDeadline1Day`, `JBDeadline3Days`, `JBDeadline7Days`. |
@@ -61,9 +61,9 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
61
61
  | Function | What it does |
62
62
  |----------|--------------|
63
63
  | `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. |
64
- | `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. |
65
- | `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. |
66
- | `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. |
64
+ | `cashOutTokensOf(address holder, uint256 projectId, uint256 cashOutCount, address tokenToReclaim, uint256 minTokensReclaimed, address payable beneficiary, bytes metadata, uint256 referralProjectId)` | Burns project tokens and reclaims surplus terminal tokens via bonding curve. Optional referral credits protocol fee volume. |
65
+ | `sendPayoutsOf(uint256 projectId, address token, uint256 amount, uint256 currency, uint256 minTokensPaidOut, uint256 referralProjectId)` | Distributes payouts from the project's balance to its payout split group, up to the payout limit. Optional referral credits protocol fee volume. |
66
+ | `useAllowanceOf(uint256 projectId, address token, uint256 amount, uint256 currency, uint256 minTokensPaidOut, address payable beneficiary, address payable feeBeneficiary, string memo, uint256 referralProjectId)` | Withdraws from the project's surplus allowance to a beneficiary. The `feeBeneficiary` receives tokens minted by the fee payment. Optional referral credits protocol fee volume. |
67
67
  | `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. |
68
68
  | `migrateBalanceOf(uint256 projectId, address token, IJBTerminal to)` | Migrates a project's token balance to another terminal. Requires `allowTerminalMigration`. |
69
69
  | `processHeldFeesOf(uint256 projectId, address token, uint256 count)` | Processes up to `count` held fees for a project, sending them to the fee beneficiary project. |
@@ -74,6 +74,7 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
74
74
  | `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`. |
75
75
  | `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. Cash-out data hooks may alter pricing inputs, but not the caller-supplied burn count. Returns `(ruleset, reclaimAmount, cashOutTaxRate, hookSpecifications)`. Delegates to `STORE.previewCashOutFrom`. |
76
76
  | `heldFeesOf(uint256 projectId, address token, uint256 count)` | Returns up to `count` held fees for a project/token. |
77
+ | `currentReferralProjectId()` | Returns the in-flight packed referral `(chainId << 48) | projectId`, or 0 outside a fee-bearing call. |
77
78
 
78
79
  ### JBTerminalStore
79
80
 
@@ -93,8 +94,11 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
93
94
  | `currentTotalReclaimableSurplusOf(uint256 projectId, uint256 cashOutCount, uint256 decimals, uint256 currency)` | Convenience view: reclaimable surplus across all terminals and all tokens. |
94
95
  | `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. |
95
96
  | `currentTotalSurplusOf(uint256 projectId, uint256 decimals, uint256 currency)` | Convenience view: total surplus across all terminals and all tokens. |
97
+ | `feeVolumeByReferralOf(address terminal, uint256 referralChainId, uint256 referralProjectId)` | Returns cumulative protocol fee volume credited to a referrer through a terminal. |
96
98
  | `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. |
97
99
  | `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, including any pricing-only adjustments they return. Returns ruleset, reclaim amount, tax rate, and hook specifications. |
100
+ | `recordFeeReferralCreditOf(uint256 referralProjectId, JBTokenAmount amount)` | Credits normalized fee volume to a packed referral project for the calling terminal. No-ops for zero referral, zero amount, or missing price feed. |
101
+ | `totalFeeVolumeOf(address terminal)` | Returns total referral-creditable fee volume recorded for a terminal. |
98
102
 
99
103
  ### JBRulesets
100
104
 
@@ -130,8 +134,11 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
130
134
 
131
135
  | Function | What it does |
132
136
  |----------|--------------|
133
- | `pricePerUnitOf(uint256 projectId, uint256 pricingCurrency, uint256 unitCurrency, uint256 decimals)` | Returns the price of 1 `unitCurrency` in `pricingCurrency`. Checks project-specific, inverse, then default feeds. |
134
- | `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. |
137
+ | `pricePerUnitOf(uint256 projectId, uint256 pricingCurrency, uint256 unitCurrency, uint256 decimals)` | Returns the price of 1 `unitCurrency` in `pricingCurrency`. Checks project direct, project inverse, default direct, then default inverse feeds; skips feeds that revert or return zero. |
138
+ | `addPriceFeedFor(uint256 projectId, uint256 pricingCurrency, uint256 unitCurrency, IJBPriceFeed feed)` | Appends a feed for an exact pair. Project ID 0 sets protocol-wide defaults (owner-only); nonzero project IDs are controller-only. |
139
+ | `priceFeedAt(uint256 projectId, uint256 pricingCurrency, uint256 unitCurrency, uint256 index)` | Returns the feed at an exact pair's index. |
140
+ | `priceFeedCountFor(uint256 projectId, uint256 pricingCurrency, uint256 unitCurrency)` | Returns how many feeds are configured for an exact pair. |
141
+ | `priceFeedFor(uint256 projectId, uint256 pricingCurrency, uint256 unitCurrency)` | Returns the primary feed for an exact pair, or zero if none is configured. |
135
142
 
136
143
  ### JBTokens
137
144
 
@@ -141,6 +148,8 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
141
148
  | `totalBalanceOf(address holder, uint256 projectId)` | Returns combined credit + ERC-20 balance. |
142
149
  | `creditBalanceOf(address holder, uint256 projectId)` | Returns the holder's credit balance. |
143
150
  | `tokenOf(uint256 projectId)` | Returns the ERC-20 token for a project (`IJBToken`). |
151
+ | `projectIdOf(IJBToken token)` | Returns the project ID associated with an ERC-20 token. |
152
+ | `totalCreditSupplyOf(uint256 projectId)` | Returns the internal credit supply for a project. |
144
153
  | `setTokenMetadataFor(uint256 projectId, string name, string symbol)` | Sets the name and symbol of a project's token. Controller-only. |
145
154
 
146
155
  ### JBSplits
@@ -157,6 +166,9 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
157
166
  |----------|--------------|
158
167
  | `setFeelessAddress(address addr, bool flag)` | Adds or removes an address from the global (all-project) fee exemption list. Owner-only. (`JBFeelessAddresses`) |
159
168
  | `setFeelessAddressFor(uint256 projectId, address addr, bool flag)` | Adds or removes an address from a project's fee exemption list. `projectId = 0` = global wildcard. Owner-only. (`JBFeelessAddresses`) |
160
- | `isFeelessFor(address addr, uint256 projectId)` | Returns whether an address is feeless for a project (checks both wildcard and project-specific). (`JBFeelessAddresses`) |
169
+ | `setFeelessHook(IJBFeelessHook hook)` | Sets or clears the optional hook consulted by `isFeelessFor`. Owner-only. (`JBFeelessAddresses`) |
170
+ | `isFeelessFor(address addr, uint256 projectId, address caller)` | Returns whether an address is feeless for a project, checking static grants and the optional caller-aware hook. (`JBFeelessAddresses`) |
171
+ | `creationFee()` / `creationFeeReceiver()` | Return the current project creation fee and receiver. (`JBProjects`) |
172
+ | `setCreationFee(uint256 fee, address payable receiver)` | Sets the native-token project creation fee. Owner-only. (`JBProjects`) |
161
173
  | `setControllerAllowed(uint256 projectId)` | Returns whether a project's controller can currently be set. (`IJBDirectoryAccessControl`) |
162
174
  | `setTerminalsAllowed(uint256 projectId)` | Returns whether a project's terminals can currently be set. (`IJBDirectoryAccessControl`) |
@@ -17,7 +17,7 @@ Use this file when you need deeper protocol reference material after the repo-lo
17
17
  | `JBCurrencyAmount` | `amount (uint224)`, `currency (uint32)` | Payout limits and surplus allowances |
18
18
  | `JBFundAccessLimitGroup` | `terminal (address)`, `token (address)`, `payoutLimits (JBCurrencyAmount[])`, `surplusAllowances (JBCurrencyAmount[])` | `JBRulesetConfig.fundAccessLimitGroups` |
19
19
  | `JBPermissionsData` | `operator (address)`, `projectId (uint64)`, `permissionIds (uint8[])` | `setPermissionsFor()` input |
20
- | `JBFee` | `amount (uint256)`, `beneficiary (address)`, `unlockTimestamp (uint48)` | Held fees in `JBMultiTerminal` |
20
+ | `JBFee` | `amount (uint224)`, `referralChainId (uint32)`, `beneficiary (address)`, `unlockTimestamp (uint48)`, `referralProjectId (uint48)` | Held fees in `JBMultiTerminal`; referral fields preserve fee attribution until processing |
21
21
  | `JBSingleAllowance` | `sigDeadline (uint256)`, `amount (uint160)`, `expiration (uint48)`, `nonce (uint48)`, `signature (bytes)` | Permit2 allowance in terminal payments |
22
22
  | `JBRulesetWithMetadata` | `ruleset (JBRuleset)`, `metadata (JBRulesetMetadata)` | `allRulesetsOf()`, `currentRulesetOf()` return values |
23
23
  | `JBRulesetWeightCache` | `weight (uint112)`, `weightCutMultiple (uint168)` | Weight caching for long-running rulesets in `JBRulesets` |
@@ -69,7 +69,7 @@ Use this file when you need deeper protocol reference material after the repo-lo
69
69
  | `projectId = 0` | `JBPermissionsData` | Wildcard: permission applies to ALL projects. Cannot be combined with ROOT (1). |
70
70
  | `permissionId = 1` | `JBPermissions` | ROOT: grants all permissions for the scoped project. |
71
71
  | `rulesetId = 0` | `JBSplits.splitsOf()` | Fallback split group used when no splits are set for a specific ruleset. |
72
- | `projectId = 0` | `JBPrices.addPriceFeedFor()` | Sets a protocol-wide default price feed (owner-only). |
72
+ | `projectId = 0` | `JBPrices.addPriceFeedFor()` | Appends a protocol-wide default price feed (owner-only). |
73
73
 
74
74
  ## Gotchas
75
75
 
@@ -92,8 +92,10 @@ Use this file when you need deeper protocol reference material after the repo-lo
92
92
  - **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.
93
93
  - `JBProjects` constructor optionally mints project #1 to `feeProjectOwner` -- if `address(0)`, no fee project is created
94
94
  - `JBMultiTerminal` derives `DIRECTORY` from the provided `store` in its constructor -- not passed directly
95
- - `JBPrices.pricePerUnitOf()` checks project-specific feed, then inverse, then falls back to `DEFAULT_PROJECT_ID = 0`
96
- - `useAllowanceOf()` takes 8 args including `address payable feeBeneficiary` -- do NOT omit it
95
+ - `JBPrices.pricePerUnitOf()` checks project direct feeds, project inverse feeds, default direct feeds, then default inverse feeds. It skips feeds that revert or return zero.
96
+ - `useAllowanceOf()` takes 9 args including `address payable feeBeneficiary` and `uint256 referralProjectId` -- do NOT omit them
97
+ - `sendPayoutsOf()` and `cashOutTokensOf()` also take `uint256 referralProjectId`; pass `0` for no referral credit
98
+ - `JBFeelessAddresses.isFeelessFor()` takes 3 args: `(addr, projectId, caller)`. The optional hook can use `caller` to scope dynamic grants.
97
99
  - 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.
98
100
  - `cashOutTaxRate` in `JBRulesetMetadata` is `uint16` (max 10,000 basis points), NOT 9-decimal precision
99
101
  - `reservedPercent` in `JBRulesetMetadata` is `uint16` (max 10,000 basis points), NOT 9-decimal precision
@@ -103,7 +105,7 @@ Use this file when you need deeper protocol reference material after the repo-lo
103
105
  - `JBController`, `JBMultiTerminal`, `JBProjects`, `JBPrices`, `JBPermissions` all support ERC-2771 meta-transactions
104
106
  - `JBRulesetMetadataResolver` bit layout: version (4 bits), reservedPercent (16), cashOutTaxRate (16), baseCurrency (32), 14 boolean flags (1 bit each), dataHook address (160), metadata (14)
105
107
  - `IJBDirectoryAccessControl` has `setControllerAllowed()` and `setTerminalsAllowed()` -- NOT `setControllerAllowedFor()`
106
- - Price feeds are immutable once set in `JBPrices` -- they cannot be replaced or removed
108
+ - Price feeds are append-only in `JBPrices`. Existing feeds cannot be replaced or removed; later feeds are fallbacks after the primary feed.
107
109
  - `JBFundAccessLimits` requires payout limits and surplus allowances to be in strictly increasing currency order to prevent duplicates
108
110
  - **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()` caps to 0 and returns 0. To allow unlimited payouts, explicitly set a payout limit with `amount: type(uint224).max`.
109
111
  - **`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.
@@ -180,7 +182,7 @@ Errors an agent is most likely to encounter. All are custom errors (revert with
180
182
  | `JBTokens_InsufficientCredits` | `JBTokens` | `claimTokensFor` count exceeds credit balance. |
181
183
  | `JBTokens_TokensMustHave18Decimals` | `JBTokens` | Custom token does not use 18 decimals. |
182
184
  | `JBSplits_TotalPercentExceeds100` | `JBSplits` | Split percentages sum exceeds `SPLITS_TOTAL_PERCENT`. |
183
- | `JBPrices_PriceFeedAlreadyExists` | `JBPrices` | Feed already set for that currency pair (immutable). |
185
+ | `JBPrices_PriceFeedAlreadyAdded` | `JBPrices` | The same feed address is already configured for that exact pair. Other backup feeds can still be appended. |
184
186
  | `JBPrices_PriceFeedNotFound` | `JBPrices` | No feed found for the requested currency pair. |
185
187
  | `JBRulesets_InvalidWeight` | `JBRulesets` | Weight exceeds `uint112.max`. |
186
188
  | `JBRulesets_InvalidWeightCutPercent` | `JBRulesets` | `weightCutPercent` exceeds `MAX_WEIGHT_CUT_PERCENT`. |
@@ -211,7 +213,10 @@ The most important events for indexing and off-chain monitoring. Indexed params
211
213
  | `HoldFee` | `IJBFeeTerminal` | `projectId*`, `token*`, `amount*`, `fee`, `beneficiary` |
212
214
  | `ProcessFee` | `IJBFeeTerminal` | `projectId*`, `token*`, `amount*`, `wasHeld`, `beneficiary` |
213
215
  | `ReturnHeldFees` | `IJBFeeTerminal` | `projectId*`, `token*`, `amount*`, `returnedFees`, `leftoverAmount` |
216
+ | `FeeReverted` | `IJBFeeTerminal` | `projectId*`, `token*`, `feeProjectId*`, `amount`, `reason` |
217
+ | `ReferralCredit` | `IJBTerminalStore` | `terminal*`, `referralChainId*`, `referralProjectId*`, `amount`, `newTotal` |
214
218
  | `Create` | `IJBProjects` | `projectId*`, `owner*` |
219
+ | `SetTokenMetadata` | `IJBTokens` | `projectId*`, `name`, `symbol` |
215
220
  | `OperatorPermissionsSet` | `IJBPermissions` | (operator, account, projectId, permissionIds, packed, caller) |
216
221
  | `RulesetQueued` | `IJBRulesets` | (rulesetId, projectId, duration, weight, weightCutPercent, approvalHook, metadata, mustStartAtOrAfter, caller) |
217
222
  | `SetSplit` | `IJBSplits` | (projectId, rulesetId, groupId, split, caller) |
@@ -241,11 +246,12 @@ function beforeCashOutRecordedWith(JBBeforeCashOutRecordedContext calldata conte
241
246
  uint256 cashOutTaxRate, // Overrides the ruleset's cash out tax rate
242
247
  uint256 effectiveCashOutCount, // Overrides the token count used for pricing only
243
248
  uint256 effectiveTotalSupply, // Overrides total supply used for bonding curve calc
249
+ uint256 effectiveSurplusValue, // Overrides surplus used for bonding curve calc
244
250
  JBCashOutHookSpecification[] memory hookSpecifications // Cash out hooks to call + amounts to forward
245
251
  );
246
252
  ```
247
253
 
248
- The data hook can override `cashOutTaxRate` (0 = proportional, 10000 = nothing reclaimable), `effectiveCashOutCount`, and `effectiveTotalSupply` to shift cash-out pricing, and return `hookSpecifications` to redirect reclaimed funds to cash out hooks. The terminal still burns the caller-supplied `cashOutCount`.
254
+ The data hook can override `cashOutTaxRate` (0 = proportional, 10000 = nothing reclaimable), `effectiveCashOutCount`, `effectiveTotalSupply`, and `effectiveSurplusValue` to shift cash-out pricing, and return `hookSpecifications` to redirect reclaimed funds to cash out hooks. The terminal still burns the caller-supplied `cashOutCount` and caps the reclaim at locally available funds.
249
255
 
250
256
  ### `IJBRulesetDataHook.hasMintPermissionFor()`
251
257
 
@@ -25,11 +25,11 @@ contract Deploy is Script, Sphinx {
25
25
  /// @notice The universal PERMIT2 address.
26
26
  IPermit2 private constant _PERMIT2 = IPermit2(0x000000000022D473030F116dDEE9F6B43aC78BA3);
27
27
 
28
- /// @notice The address that is allowed to forward calls to the terminal and controller on a users behalf.
28
+ /// @notice The address that is allowed to forward calls to the terminal and controller on behalf of users.
29
29
  string private constant _TRUSTED_FORWARDER_NAME = "Juicebox";
30
30
  address private trustedForwarder;
31
31
 
32
- /// @notice The address that will manage the few privileged functions of the protocol.
32
+ /// @notice The address that will manage privileged protocol functions.
33
33
  address private manager;
34
34
 
35
35
  /// @notice The address that will own the fee-project.
@@ -69,11 +69,10 @@ contract DeployPeriphery is Script, Sphinx {
69
69
  IJBPriceFeed matchingPriceFeed;
70
70
  matchingPriceFeed = new JBMatchingPriceFeed();
71
71
 
72
- // Same as the chainlink example grace period.
72
+ // Same grace period used in Chainlink sequencer-feed examples.
73
73
  uint256 l2GracePeriod = 3600 seconds;
74
74
 
75
- // NOTE: Feeds come from this url `https://data.chain.link/feeds/ethereum/mainnet/eth-usd`.
76
- // Sequencer feeds come from this url `https://docs.chain.link/data-feeds/l2-sequencer-feeds`.
75
+ // Feed addresses come from Chainlink price-feed and L2 sequencer-feed docs.
77
76
 
78
77
  // Perform the deploy for L1(s).
79
78
  if (block.chainid == 1) {
@@ -162,8 +161,7 @@ contract DeployPeriphery is Script, Sphinx {
162
161
  "Unexpected USD/native price feed"
163
162
  );
164
163
 
165
- // WARN: We are using the same price feed as the native token for the USD price feed. Which is only valid on
166
- // chains where Ether is the native asset. We *NEED* to update this when we deploy to a non-ether chain!
164
+ // This feed also prices USD/ETH, which is valid only on ETH-native chains.
167
165
  try core.prices
168
166
  .addPriceFeedFor({
169
167
  projectId: 0, pricingCurrency: JBCurrencyIds.USD, unitCurrency: JBCurrencyIds.ETH, feed: feed
@@ -179,9 +177,7 @@ contract DeployPeriphery is Script, Sphinx {
179
177
  "Unexpected USD/ETH price feed"
180
178
  );
181
179
 
182
- // If the native asset for this chain is ether, then the conversion from native asset to ether is 1:1.
183
- // NOTE: We need to refactor this the moment we add a chain where its native token is *NOT* ether.
184
- // As otherwise prices for the `NATIVE_TOKEN` will be incorrect!
180
+ // ETH/native is 1:1 only on ETH-native chains. Add a separate feed before deploying elsewhere.
185
181
  try core.prices
186
182
  .addPriceFeedFor({
187
183
  projectId: 0,
@@ -42,9 +42,8 @@ import {JBSplitHookContext} from "./structs/JBSplitHookContext.sol";
42
42
  import {JBTerminalConfig} from "./structs/JBTerminalConfig.sol";
43
43
 
44
44
  /// @notice The orchestrator for every Juicebox project's lifecycle. Use the controller to launch a project, queue new
45
- /// rulesets (funding cycles), mint or burn tokens, deploy an ERC-20, distribute reserved tokens, and manage
46
- /// permissions. The controller coordinates between the terminal (money), rulesets (rules), tokens (issuance), and
47
- /// splits (distribution).
45
+ /// rulesets, mint or burn tokens, deploy an ERC-20, distribute reserved tokens, and manage permissions. The controller
46
+ /// coordinates between the terminal (money), rulesets (rules), tokens (issuance), and splits (distribution).
48
47
  /// @dev Supports ERC-2771 meta-transactions. Implements `IJBMigratable` for controller-to-controller migration.
49
48
  /// An omnichain deployer address is trusted to launch and queue rulesets on behalf of any project for cross-chain
50
49
  /// coordination.
@@ -7,9 +7,9 @@ import {IJBFundAccessLimits} from "./interfaces/IJBFundAccessLimits.sol";
7
7
  import {JBCurrencyAmount} from "./structs/JBCurrencyAmount.sol";
8
8
  import {JBFundAccessLimitGroup} from "./structs/JBFundAccessLimitGroup.sol";
9
9
 
10
- /// @notice Controls how much a project can withdraw from its terminals each funding cycle. Two types of limits:
11
- /// **Payout limits** cap how much can be distributed to splits and the project owner. **Surplus allowances** cap how
12
- /// much the project owner can pull from the surplus (funds above payout limits). Both reset each ruleset cycle.
10
+ /// @notice Controls how much a project can withdraw from its terminals during each ruleset. Two types of limits:
11
+ /// **Payout limits** cap distributions to splits and the project owner. **Surplus allowances** cap how much the
12
+ /// project owner can pull from surplus (funds above payout limits).
13
13
  /// @dev Limits are denominated in a currency (which may differ from the held token) and resolved at withdrawal time
14
14
  /// via `JBPrices`. An empty `fundAccessLimitGroups` array means zero access (not unlimited) — use `type(uint224).max`
15
15
  /// for unlimited.
@@ -74,7 +74,7 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
74
74
 
75
75
  /// @notice Configure how much a project can withdraw from each of its terminals during a ruleset. Payout limits
76
76
  /// cap how much can be distributed to splits/owner; surplus allowances cap how much extra the owner can pull from
77
- /// surplus. Both reset each funding cycle.
77
+ /// surplus. Payout usage resets by ruleset cycle number; surplus-allowance usage resets by ruleset ID.
78
78
  /// @dev Only a project's controller can set fund access limits (called during `queueRulesetsOf`).
79
79
  /// @dev Limits within each group must be sorted by currency in strictly increasing order to prevent duplicates.
80
80
  /// @param projectId The ID of the project to set fund access limits for.
@@ -115,13 +115,13 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
115
115
  //*********************************************************************//
116
116
 
117
117
  /// @notice The cumulative amount of fee-free intra-terminal payouts a project has received for a given token.
118
- /// @dev Incremented each time a fee-free payout lands (same terminal, no fee charged). During cashout with
118
+ /// @dev Incremented each time a fee-free payout lands (same terminal, no fee charged). During cash out with
119
119
  /// `cashOutTaxRate == 0`, fees are applied only up to this amount, then decremented. This prevents a round-trip
120
- /// fee bypass (intra-terminal payout zero-tax cashout) while scoping the fee precisely to the fee-free inflow
121
- /// — legitimate cashouts beyond this amount remain fee-free.
120
+ /// fee bypass (intra-terminal payout -> zero-tax cash out) while scoping the fee precisely to the fee-free inflow
121
+ /// — legitimate cash outs beyond this amount remain fee-free.
122
122
  /// @dev Lifecycle: incremented on fee-free intra-terminal payouts. After any outflow (payouts, useAllowanceOf,
123
- /// non-zero-tax or feeless cashouts), capped at remaining balance — non-fee-free funds are considered to leave
124
- /// first, preserving the fee-free counter. Consumed during zero-tax cashouts. Cleared on terminal migration.
123
+ /// non-zero-tax or feeless cash outs), capped at remaining balance — non-fee-free funds are considered to leave
124
+ /// first, preserving the fee-free counter. Consumed during zero-tax cash outs. Cleared on terminal migration.
125
125
  /// @dev Persists across rulesets — projects switching from zero-tax to non-zero-tax carry forward any
126
126
  /// unconsumed balance. There is no admin function to reset it.
127
127
  /// @custom:param projectId The ID of the project that received the payout.
@@ -155,8 +155,8 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
155
155
  /// their own chain ID.
156
156
  /// @dev Set by the three entry points via the `_setReferralProjectId` save-restore wrapper. Read inside `_pay`
157
157
  /// to credit `feeVolumeByReferralOf` when the fee project's pay call is recorded locally.
158
- /// @dev Public so pay/cashout/split hooks can introspect which referral originated the in-flight call (e.g. to
159
- /// apply referral-specific logic). Reads `0` outside any fee-paying call.
158
+ /// @dev Public so pay, cash out, and split hooks can introspect which referral originated the in-flight call (e.g.
159
+ /// to apply referral-specific logic). Reads `0` outside any fee-paying call.
160
160
  uint256 public transient override currentReferralProjectId;
161
161
 
162
162
  //*********************************************************************//
@@ -291,8 +291,8 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
291
291
  /// call to succeed, as a fixed point number with the same number of decimals as the terminal token's accounting
292
292
  /// context. If fewer terminal tokens would be reclaimed, the cash out is reverted.
293
293
  /// @param beneficiary The address to send the reclaimed terminal tokens to, and to pass along to the ruleset's
294
- /// data hook and cash out hook if applicable.
295
- /// @param metadata Bytes to send along to the emitted event, as well as the data hook and cash out hook if
294
+ /// data hook and cash out hooks if applicable.
295
+ /// @param metadata Bytes to send along to the emitted event, as well as the data hook and cash out hooks if
296
296
  /// applicable.
297
297
  /// @param referralProjectId Optional referrer reference to credit with the protocol fee volume taken by this
298
298
  /// call, encoded as `(referralChainId << 48) | referralProjectId` (chain ID in the upper 32 bits, project ID
@@ -1338,13 +1338,8 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1338
1338
  });
1339
1339
  }
1340
1340
 
1341
- // Cap fee-free surplus at remaining balance.
1342
- // Why: this single call replaces per-branch calls so that EVERY cashout path (non-zero tax,
1343
- // zero tax, and feeless beneficiary) gets the cap. Without it, the zero-tax path would use
1344
- // a stale _feeFreeSurplusOf that may exceed STORE.balanceOf (e.g. if pay hooks reduced the
1345
- // balance during a prior inbound payout), overcharging fees on round-trip prevention.
1346
- // Placed after hook fulfillment so any further balance reductions from cashout hooks are
1347
- // also accounted for.
1341
+ // Cap fee-free surplus after every cash-out path so stale `_feeFreeSurplusOf` cannot survive after
1342
+ // associated surplus leaves. Do this after hook fulfillment so hook-driven balance reductions are included.
1348
1343
  _capFeeFreeSurplus({projectId: projectId, token: tokenToReclaim});
1349
1344
 
1350
1345
  // Take the fee from all outbound reclaimings.
@@ -289,8 +289,8 @@ contract JBTerminalStore is IJBTerminalStore {
289
289
  /// @param beneficiaryIsFeeless Whether the cash out's beneficiary is a feeless address. Passed through to data
290
290
  /// hooks so they can skip their own fees when value stays in the protocol (e.g. project-to-project routing).
291
291
  /// @param metadata Bytes to send to the data hook, if the project's current ruleset specifies one.
292
- /// @return ruleset The ruleset during the cash out was made during, as a `JBRuleset` struct. This ruleset will
293
- /// have a cash out tax rate provided by the cash out hook if applicable.
292
+ /// @return ruleset The ruleset during the cash out was made during, as a `JBRuleset` struct. Its cash out tax
293
+ /// rate may be overridden by the data hook.
294
294
  /// @return reclaimAmount The amount of tokens reclaimed from the terminal, as a fixed point number with 18
295
295
  /// decimals.
296
296
  /// @return cashOutTaxRate The cash out tax rate influencing the reclaim amount.
@@ -574,6 +574,11 @@ contract JBTerminalStore is IJBTerminalStore {
574
574
  })
575
575
  });
576
576
 
577
+ // If cross-currency conversion rounded to zero, return without consuming any surplus allowance.
578
+ if (usedAmount == 0) {
579
+ return (ruleset, 0);
580
+ }
581
+
577
582
  // Set the token being used as the only one to look for surplus within.
578
583
  JBAccountingContext[] memory accountingContexts = new JBAccountingContext[](1);
579
584
  accountingContexts[0] = accountingContext;
@@ -1388,7 +1393,7 @@ contract JBTerminalStore is IJBTerminalStore {
1388
1393
  {
1389
1394
  // Saturating subtraction: if a new ruleset activates with a lower payout limit than
1390
1395
  // what was already used under the previous limit, `used` can exceed `amount`. Clamping
1391
- // to zero prevents an underflow revert that would DOS cashouts and surplus views.
1396
+ // to zero prevents an underflow revert that would DoS cash outs and surplus views.
1392
1397
  uint256 used = usedPayoutLimitOf[
1393
1398
  terminal
1394
1399
  ][projectId][accountingContext.token][ruleset.cycleNumber][payoutLimit.currency];
@@ -85,7 +85,7 @@ interface IJBCashOutTerminal is IJBTerminal {
85
85
  /// @param beneficiary The address to send the reclaimed terminal tokens to.
86
86
  /// @param metadata Extra data to send to the data hook and cash out hooks.
87
87
  /// @param referralProjectId Optional project to credit with the protocol fee volume taken by this call. Pass `0`
88
- /// to credit the project being cashed out.
88
+ /// for no referral credit.
89
89
  /// @return reclaimAmount The number of terminal tokens reclaimed from the project's surplus.
90
90
  function cashOutTokensOf(
91
91
  address holder,
@@ -38,9 +38,9 @@ interface IJBMultiTerminal is IJBTerminal, IJBFeeTerminal, IJBCashOutTerminal, I
38
38
  /// resolved to the current execution chain via `block.chainid` at the entry point, so storage and indexers
39
39
  /// always see a fully-resolved `(chainId, projectId)` pair.
40
40
  /// @dev Backed by transient storage. Set by `cashOutTokensOf`, `sendPayoutsOf`, and `useAllowanceOf` (save-
41
- /// restore wrapper) so hooks invoked during that call (pay hooks, cashout hooks, split hooks) can introspect
41
+ /// restore wrapper) so hooks invoked during that call (pay hooks, cash out hooks, split hooks) can introspect
42
42
  /// which referrer originated the activity. Reads `0` outside any fee-paying call.
43
43
  /// @dev Per-referrer cumulative fee payment amounts credited via this terminal are stored in
44
- /// `JBTerminalStore.feeVolumeByReferralOf(address terminal, uint256 referralProjectId)`.
44
+ /// `JBTerminalStore.feeVolumeByReferralOf(address terminal, uint256 referralChainId, uint256 referralProjectId)`.
45
45
  function currentReferralProjectId() external view returns (uint256);
46
46
  }
@@ -103,7 +103,7 @@ interface IJBPayoutTerminal is IJBTerminal {
103
103
  /// @param currency The currency the amount is denominated in.
104
104
  /// @param minTokensPaidOut The minimum number of terminal tokens expected to be paid out.
105
105
  /// @param referralProjectId Optional project to credit with the protocol fee volume taken by this call. Pass `0`
106
- /// to credit the project sending payouts.
106
+ /// for no referral credit.
107
107
  /// @return amountPaidOut The total amount paid out.
108
108
  function sendPayoutsOf(
109
109
  uint256 projectId,
@@ -126,7 +126,7 @@ interface IJBPayoutTerminal is IJBTerminal {
126
126
  /// @param feeBeneficiary The address that will receive any project tokens minted from fees.
127
127
  /// @param memo A memo to pass along to the emitted event.
128
128
  /// @param referralProjectId Optional project to credit with the protocol fee volume taken by this call. Pass `0`
129
- /// to credit the project whose surplus allowance is being used.
129
+ /// for no referral credit.
130
130
  /// @return netAmountPaidOut The net amount paid out to the beneficiary after fees.
131
131
  function useAllowanceOf(
132
132
  uint256 projectId,
@@ -17,9 +17,8 @@ import {JBTokenAmount} from "../structs/JBTokenAmount.sol";
17
17
  interface IJBTerminalStore {
18
18
  /// @notice Emitted when a referrer is credited with a fee payment amount.
19
19
  /// @dev `referralChainId` and `referralProjectId` are emitted as separate indexed topics so off-chain consumers
20
- /// can filter directly on either dimension. The `feeVolumeByReferralOf` storage key is the packed
21
- /// `(referralChainId << 48) | referralProjectId` form — indexers re-pack the two fields when looking up the
22
- /// cumulative balance for a referrer.
20
+ /// can filter directly on either dimension. The public `feeVolumeByReferralOf` getter exposes the same unpacked
21
+ /// `(terminal, referralChainId, referralProjectId)` tuple.
23
22
  /// @param terminal The terminal that originated the fee-paying call (`msg.sender` on `recordFeeReferralCreditOf`).
24
23
  /// @param referralChainId The EIP-155 chain ID of the referrer's home chain.
25
24
  /// @param referralProjectId The referrer's bare project ID on `referralChainId` (no chain bits).
@@ -90,7 +90,7 @@ library JBRulesetMetadataResolver {
90
90
  return uint16(ruleset.metadata >> 242);
91
91
  }
92
92
 
93
- /// @notice Pack the funding cycle metadata.
93
+ /// @notice Pack ruleset metadata.
94
94
  /// @param rulesetMetadata The ruleset metadata to validate and pack.
95
95
  /// @return packed The packed uint256 of all metadata params. The first 4 bits (bits 0-3) specify the version;
96
96
  /// supported values are 0-15. A future protocol version that needs more than 16 distinct metadata layouts must
@@ -131,18 +131,18 @@ library JBRulesetMetadataResolver {
131
131
  if (rulesetMetadata.holdFees) packed |= 1 << 78;
132
132
  // scopeCashOutsToLocalBalances in bit 79.
133
133
  if (rulesetMetadata.scopeCashOutsToLocalBalances) packed |= 1 << 79;
134
- // use pay data source in bit 80.
134
+ // use pay data hook in bit 80.
135
135
  if (rulesetMetadata.useDataHookForPay) packed |= 1 << 80;
136
- // use cash out data source in bit 81.
136
+ // use cash out data hook in bit 81.
137
137
  if (rulesetMetadata.useDataHookForCashOut) packed |= 1 << 81;
138
- // data source address in bits 82-241.
138
+ // data hook address in bits 82-241.
139
139
  packed |= uint256(uint160(address(rulesetMetadata.dataHook))) << 82;
140
140
  // metadata in bits 242-255 (14 bits).
141
141
  packed |= (uint256(rulesetMetadata.metadata) & 0x3FFF) << 242;
142
142
  }
143
143
 
144
- /// @notice Expand the funding cycle metadata.
145
- /// @param ruleset The funding cycle having its metadata expanded.
144
+ /// @notice Expand ruleset metadata.
145
+ /// @param ruleset The ruleset whose metadata is expanded.
146
146
  /// @return rulesetMetadata The ruleset's metadata object.
147
147
  function expandMetadata(JBRuleset memory ruleset) internal pure returns (JBRulesetMetadata memory) {
148
148
  return JBRulesetMetadata({
@@ -11,9 +11,9 @@ pragma solidity ^0.8.0;
11
11
  /// @custom:member beneficiary The address that will receive the tokens that are minted as a result of the fee payment.
12
12
  /// @custom:member unlockTimestamp The timestamp at which the fee is unlocked and can be processed.
13
13
  /// @custom:member referralProjectId The referrer's project ID on `referralChainId`. Captured from the lower bits of
14
- /// `currentReferralProjectId` at fee-take time so held-fee attribution survives the 28-day hold window. The on-chain
15
- /// transient slot, function argument, and `feeVolumeByReferralOf` mapping key all use the packed `uint256` form
16
- /// `(referralChainId << 48) | referralProjectId`; this struct stores the two halves separately for cheap storage.
14
+ /// `currentReferralProjectId` at fee-take time so held-fee attribution survives the 28-day hold window. The transient
15
+ /// slot and terminal entry-point arguments use the packed `(referralChainId << 48) | referralProjectId` form; this
16
+ /// struct stores the two halves separately for cheap storage.
17
17
  struct JBFee {
18
18
  uint224 amount;
19
19
  uint32 referralChainId;
@@ -3,17 +3,17 @@ pragma solidity ^0.8.0;
3
3
 
4
4
  import {JBCurrencyAmount} from "./JBCurrencyAmount.sol";
5
5
 
6
- /// @notice Defines how much a project can withdraw from a specific terminal and token each funding cycle.
6
+ /// @notice Defines how much a project can withdraw from a specific terminal and token during a ruleset.
7
7
  /// @dev A ruleset configuration should include at most one group for each `(terminal, token)` pair.
8
8
  /// @dev Example — payout limit of 5 USD in an ETH terminal: the project can distribute up to 5 USD worth of ETH to
9
- /// its splits per cycle. Example — surplus allowance of 5 USD: the project owner can pull up to 5 USD worth of ETH
10
- /// from the surplus (balance above payout limits).
11
- /// @dev Multiple limits in different currencies are additive each can be used independently within one cycle.
9
+ /// its splits per ruleset cycle. Example — surplus allowance of 5 USD: the project owner can pull up to 5 USD
10
+ /// worth of ETH from the surplus (balance above payout limits) during the ruleset.
11
+ /// @dev Multiple limits in different currencies are additive within their respective reset windows.
12
12
  /// @dev Amounts use the same decimal precision as the terminal token (e.g. 18 for ETH, 6 for USDC).
13
13
  /// @custom:member terminal The terminal address these limits apply to.
14
14
  /// @custom:member token The token address within that terminal these limits apply to.
15
- /// @custom:member payoutLimits Maximum amounts distributable to splits per cycle, each in a specific currency.
16
- /// @custom:member surplusAllowances Maximum amounts withdrawable from surplus per cycle, each in a specific currency.
15
+ /// @custom:member payoutLimits Maximum amounts distributable to splits per ruleset cycle, each in a specific currency.
16
+ /// @custom:member surplusAllowances Maximum amounts withdrawable from surplus per ruleset, each in a specific currency.
17
17
  struct JBFundAccessLimitGroup {
18
18
  address terminal;
19
19
  address token;