@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.
- package/ADMINISTRATION.md +43 -13
- package/ARCHITECTURE.md +62 -137
- package/AUDIT_INSTRUCTIONS.md +149 -428
- package/CHANGELOG.md +73 -0
- package/README.md +90 -201
- package/RISKS.md +27 -12
- package/SKILLS.md +31 -441
- package/STYLE_GUIDE.md +52 -19
- package/USER_JOURNEYS.md +76 -627
- package/package.json +1 -2
- package/references/entrypoints.md +160 -0
- package/references/types-errors-events.md +297 -0
- package/script/Deploy.s.sol +7 -2
- package/script/DeployPeriphery.s.sol +51 -4
- package/src/JBController.sol +45 -17
- package/src/JBDirectory.sol +26 -13
- package/src/JBFundAccessLimits.sol +28 -7
- package/src/JBMultiTerminal.sol +180 -86
- package/src/JBPermissions.sol +17 -17
- package/src/JBRulesets.sol +82 -23
- package/src/JBSplits.sol +31 -12
- package/src/JBTerminalStore.sol +137 -53
- package/src/JBTokens.sol +5 -2
- package/src/abstract/JBControlled.sol +10 -3
- package/src/abstract/JBPermissioned.sol +1 -1
- package/src/interfaces/IJBRulesetDataHook.sol +5 -4
- package/src/libraries/JBCashOuts.sol +1 -1
- package/src/libraries/JBConstants.sol +1 -1
- package/src/libraries/JBCurrencyIds.sol +1 -1
- package/src/libraries/JBFees.sol +1 -1
- package/src/libraries/JBFixedPointNumber.sol +1 -1
- package/src/libraries/JBMetadataResolver.sol +5 -2
- package/src/libraries/JBPayoutSplitGroupLib.sol +7 -2
- package/src/libraries/JBRulesetMetadataResolver.sol +1 -1
- package/src/libraries/JBSplitGroupIds.sol +1 -1
- package/src/libraries/JBSurplus.sol +5 -2
- package/src/structs/JBSplit.sol +4 -1
- package/test/TestForwardedTokenConsumption.sol +419 -0
- package/test/audit/CrossTerminalSurplusSpoof.t.sol +140 -0
- package/test/audit/CycledSurplusAllowanceReset.t.sol +184 -0
- package/test/units/static/JBController/TestPreviewMintOf.sol +5 -4
- package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +15 -12
- package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +6 -0
- package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +3 -0
- package/test/units/static/JBMultiTerminal/TestMigrateBalanceOf.sol +3 -0
- package/test/units/static/JBMultiTerminal/TestPay.sol +7 -15
- package/test/units/static/JBMultiTerminal/TestSendPayoutsOf.sol +1 -1
- package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +1 -1
- package/CHANGE_LOG.md +0 -479
package/USER_JOURNEYS.md
CHANGED
|
@@ -1,668 +1,117 @@
|
|
|
1
|
-
#
|
|
1
|
+
# User Journeys
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Who This Repo Serves
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- founders launching and evolving Juicebox projects
|
|
6
|
+
- supporters paying projects in the asset a terminal accepts
|
|
7
|
+
- token holders cashing out against project surplus
|
|
8
|
+
- operators managing permissions, splits, fund access limits, and rulesets
|
|
9
|
+
- integrators wiring hooks, terminals, price feeds, and migrations into the canonical protocol surface
|
|
6
10
|
|
|
7
|
-
## 1
|
|
11
|
+
## Journey 1: Launch A Project With The Right Initial Shape
|
|
8
12
|
|
|
9
|
-
**
|
|
13
|
+
**Starting state:** you know who should own the project, which terminals it should use, and what the first ruleset should allow.
|
|
10
14
|
|
|
11
|
-
**
|
|
15
|
+
**Success:** the project NFT exists, the initial ruleset is active, accepted terminals are installed, and downstream hooks or splits can begin working immediately.
|
|
12
16
|
|
|
13
|
-
**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
- `memo` -- Arbitrary string emitted in the event
|
|
17
|
+
**Flow**
|
|
18
|
+
1. Call `JBController.launchProjectFor(...)` with the owner, URI, ruleset config, terminal configs, and any split or hook metadata.
|
|
19
|
+
2. `JBProjects` mints the project NFT, `JBDirectory` records controller and terminal routing, and `JBRulesets` stores the first ruleset.
|
|
20
|
+
3. If the project wants ERC-20 tokens, reserved-rate behavior, or hook-driven behavior, that configuration is committed at launch instead of being inferred later.
|
|
21
|
+
4. The project can now accept payments and queue future rulesets without changing project identity.
|
|
19
22
|
|
|
20
|
-
**
|
|
21
|
-
1. `JBProjects.createFor(owner)` -- Mints ERC-721, increments project count, returns `projectId`
|
|
22
|
-
2. `uriOf[projectId] = projectUri` -- Stores metadata URI (if non-empty)
|
|
23
|
-
3. `JBDirectory.setControllerOf(projectId, controller)` -- Sets this controller as the project's controller
|
|
24
|
-
4. For each terminal config: `terminal.addAccountingContextsFor(projectId, contexts)` -- Registers accepted tokens
|
|
25
|
-
5. `JBDirectory.setTerminalsOf(projectId, terminals)` -- Registers terminals
|
|
26
|
-
6. For each ruleset config:
|
|
27
|
-
- `JBRulesets.queueFor(...)` -- Creates ruleset with packed intrinsic/user properties and metadata
|
|
28
|
-
- `JBSplits.setSplitGroupsOf(...)` -- Stores split groups for the ruleset
|
|
29
|
-
- `JBFundAccessLimits.setFundAccessLimitsFor(...)` -- Stores payout limits and surplus allowances
|
|
23
|
+
**Failure cases that matter:** mismatched accounting contexts, wrong terminals for the target asset, invalid hook metadata, and launching with permissions or ownership assumptions that cannot be repaired cleanly later.
|
|
30
24
|
|
|
31
|
-
|
|
25
|
+
## Journey 2: Accept A Payment And Issue The Right Token Exposure
|
|
32
26
|
|
|
33
|
-
**
|
|
34
|
-
- Empty `rulesetConfigurations` array is valid -- project launches with no rulesets (cannot receive payments until rulesets are launched via `launchRulesetsFor`)
|
|
35
|
-
- Multiple rulesets can be queued in a single launch -- they form a linked list
|
|
36
|
-
- If `block.timestamp` collision occurs for rulesetId, the ID is incremented by 1
|
|
27
|
+
**Starting state:** the project has an active ruleset and a terminal that accepts the payer's asset.
|
|
37
28
|
|
|
38
|
-
|
|
29
|
+
**Success:** treasury balances increase, hooks run in the right order, and the beneficiary receives credits or ERC-20 tokens consistent with the ruleset.
|
|
39
30
|
|
|
40
|
-
|
|
31
|
+
**Flow**
|
|
32
|
+
1. A payer calls `pay(...)` on `JBMultiTerminal`.
|
|
33
|
+
2. The terminal validates the accounting context, records funds, and asks `JBTerminalStore` to derive issuance from the active ruleset.
|
|
34
|
+
3. `JBController` and `JBTokens` decide whether the beneficiary gets project token credits, ERC-20s, or no issuance because weight is zero.
|
|
35
|
+
4. Any configured pay hooks or data hooks run around the accounting path.
|
|
41
36
|
|
|
42
|
-
**
|
|
37
|
+
**Edge conditions that change user experience:** paused payments, custom hook side effects, fee-on-transfer tokens, unsupported price feeds, zero-weight rulesets, and permit-based flows.
|
|
43
38
|
|
|
44
|
-
|
|
39
|
+
## Journey 3: Turn Credits Into ERC-20 Tokens Once A Project Wants A Transferable Token
|
|
45
40
|
|
|
46
|
-
**
|
|
47
|
-
- `projectId` -- The project to pay
|
|
48
|
-
- `token` -- Token address (`JBConstants.NATIVE_TOKEN` for ETH)
|
|
49
|
-
- `amount` -- Amount of tokens (ignored for native token; uses `msg.value`)
|
|
50
|
-
- `beneficiary` -- Address to receive minted project tokens
|
|
51
|
-
- `minReturnedTokens` -- Slippage protection; reverts if fewer tokens minted
|
|
52
|
-
- `memo` -- Arbitrary string
|
|
53
|
-
- `metadata` -- Bytes; may contain Permit2 data (keyed by `"permit2"` ID) and/or hook-specific data
|
|
41
|
+
**Starting state:** users already have project token credits and the project now wants an ERC-20 representation.
|
|
54
42
|
|
|
55
|
-
**
|
|
56
|
-
1. Tokens transferred to terminal (or `msg.value` accepted)
|
|
57
|
-
2. `JBTerminalStore.balanceOf[terminal][projectId][token]` incremented (minus any hook-diverted amounts)
|
|
58
|
-
3. `JBTokens` mints project tokens to beneficiary (credits or ERC-20)
|
|
59
|
-
4. `JBController.pendingReservedTokenBalanceOf[projectId]` incremented by reserved portion
|
|
60
|
-
5. Pay hooks execute (if data hook returns specifications)
|
|
43
|
+
**Success:** the project deploys or installs its ERC-20 token and holders can claim credits into transferable balances.
|
|
61
44
|
|
|
62
|
-
**
|
|
45
|
+
**Flow**
|
|
46
|
+
1. Deploy or set the project's ERC-20 token through `JBController`.
|
|
47
|
+
2. Holders or operators call `claimTokensFor(...)` to convert credits into ERC-20 balances for a beneficiary.
|
|
48
|
+
3. Future issuance can continue using the same project identity while users now interact with a standard token surface.
|
|
63
49
|
|
|
64
|
-
|
|
65
|
-
- `amount = 0` is valid -- records a zero payment, mints 0 tokens
|
|
66
|
-
- `beneficiary = address(0)` -- tokens minted to zero address (effectively burned on mint)
|
|
67
|
-
- If `pausePay` is set in ruleset metadata, `recordPaymentFrom` reverts
|
|
68
|
-
- Token count = `mulDiv(amount.value, weight, weightRatio)` -- if weight is 0, no tokens minted
|
|
69
|
-
- Data hook can return empty weight (0) to suppress minting while still recording payment
|
|
70
|
-
- Fee-on-transfer tokens: actual amount received is `_balanceOf(token) - balanceBefore` (measured via balance diff)
|
|
50
|
+
## Journey 4: Distribute Treasury Funds Through Governed Paths
|
|
71
51
|
|
|
72
|
-
**
|
|
52
|
+
**Starting state:** the project has terminal balances and the owner wants payouts or allowance-based withdrawals.
|
|
73
53
|
|
|
74
|
-
|
|
54
|
+
**Success:** treasury value leaves only through configured limits, recipients, and fee logic instead of arbitrary admin withdrawals.
|
|
75
55
|
|
|
76
|
-
|
|
56
|
+
**Flow**
|
|
57
|
+
1. Authorized actors call payout or allowance surfaces on the terminal.
|
|
58
|
+
2. `JBFundAccessLimits` bounds how much may leave for the current ruleset cycle.
|
|
59
|
+
3. `JBSplits` fans value out to beneficiaries, projects, hooks, or fee recipients as configured.
|
|
60
|
+
4. `JBTerminalStore` updates balances and fee accounting so later previews and cash outs remain consistent.
|
|
77
61
|
|
|
78
|
-
**
|
|
62
|
+
**Failure cases that matter:** stale split expectations, exceeding access limits, downstream hook failures, and assuming allowance withdrawals behave like payouts when fee treatment differs.
|
|
79
63
|
|
|
80
|
-
|
|
64
|
+
## Journey 5: Let Holders Cash Out Against Surplus
|
|
81
65
|
|
|
82
|
-
**
|
|
83
|
-
- `holder` -- Address whose tokens are being cashed out
|
|
84
|
-
- `projectId` -- The project to cash out from
|
|
85
|
-
- `cashOutCount` -- Number of project tokens to burn (18 decimals)
|
|
86
|
-
- `tokenToReclaim` -- Terminal token to receive back
|
|
87
|
-
- `minTokensReclaimed` -- Slippage protection
|
|
88
|
-
- `beneficiary` -- Address to receive reclaimed tokens
|
|
89
|
-
- `metadata` -- Hook-specific data
|
|
66
|
+
**Starting state:** a holder owns project token exposure and the project has reclaimable surplus in some terminal.
|
|
90
67
|
|
|
91
|
-
**
|
|
92
|
-
1. `STORE.recordCashOutFor()` -- computes the reclaim amount via bonding curve (using current `totalSupply` which still includes the tokens being cashed out), then decrements `JBTerminalStore.balanceOf[terminal][projectId][token]` by `reclaimAmount + hookSpec amounts`
|
|
93
|
-
2. Project tokens burned via `JBController.burnTokensOf()` (credits first, then ERC-20) -- happens AFTER the reclaim calculation, so the bonding curve sees the pre-burn supply
|
|
94
|
-
3. Reclaimed tokens transferred to beneficiary
|
|
95
|
-
4. Cash out hooks execute (if data hook returns specifications)
|
|
96
|
-
5. Fee taken (2.5%) on total amount eligible for fees, unless beneficiary is feeless. When `cashOutTaxRate == 0`, the fee applies only up to the project's unconsumed fee-free surplus (`_feeFreeSurplusOf`) from intra-terminal payouts -- once depleted, cashouts are fee-free.
|
|
68
|
+
**Success:** the holder burns the intended amount of token exposure and receives the correct reclaim amount under the current ruleset.
|
|
97
69
|
|
|
98
|
-
**
|
|
70
|
+
**Flow**
|
|
71
|
+
1. The holder calls `cashOutTokensOf(...)` on the relevant terminal.
|
|
72
|
+
2. `JBTerminalStore` calculates reclaim value using surplus, outstanding token supply, cash-out tax rate, and any pending reserved token effects.
|
|
73
|
+
3. Cash-out hooks can modify behavior or side effects, but the core accounting remains anchored in the terminal store.
|
|
74
|
+
4. Tokens burn and value exits the treasury through the terminal that actually held the asset.
|
|
99
75
|
|
|
100
|
-
**
|
|
76
|
+
**Edge conditions that matter:** fee-free addresses, custom cash-out hooks, preview-versus-execution drift under volatile routing, and multi-terminal surplus that users may misread as single-pool liquidity.
|
|
101
77
|
|
|
102
|
-
|
|
103
|
-
- `cashOutCount = 0` with `totalSupply = 0` -- returns entire surplus (C-5 known bug)
|
|
104
|
-
- `cashOutTaxRate = MAX (10,000)` -- returns 0 (all surplus locked)
|
|
105
|
-
- `cashOutTaxRate = 0` -- proportional (1:1 against supply) with no discount
|
|
106
|
-
- `cashOutCount >= totalSupply` -- returns entire surplus regardless of tax rate
|
|
107
|
-
- Data hook can override `cashOutTaxRate`, `cashOutCount`, `totalSupply` to arbitrary values
|
|
108
|
-
- Fee is NOT taken when `cashOutTaxRate == 0` UNLESS the project has unconsumed fee-free surplus from intra-terminal payouts (`_feeFreeSurplusOf`). The fee applies only up to the surplus amount and depletes it — preventing round-trip fee bypass while scoping fees precisely to the fee-free inflow.
|
|
109
|
-
- Pending reserved tokens inflate `totalSupply`, reducing individual cash out value (H-4)
|
|
78
|
+
## Journey 6: Queue New Rulesets Without Migrating The Project
|
|
110
79
|
|
|
111
|
-
|
|
80
|
+
**Starting state:** the project is live and future economics need to change.
|
|
112
81
|
|
|
113
|
-
|
|
82
|
+
**Success:** the next ruleset activates on schedule while the project keeps the same identity, treasury, and downstream integrations.
|
|
114
83
|
|
|
115
|
-
**
|
|
84
|
+
**Flow**
|
|
85
|
+
1. The owner or an operator with the right permission queues one or more new rulesets through `JBController`.
|
|
86
|
+
2. `JBRulesets` stores the proposed future configuration and any approval hook requirements.
|
|
87
|
+
3. When the active duration elapses, the next approved ruleset becomes live and future pays, payouts, and cash outs follow the new terms.
|
|
88
|
+
4. Existing balances and token history remain intact because only future behavior changed.
|
|
116
89
|
|
|
117
|
-
**
|
|
90
|
+
**Failure cases that matter:** forgetting approval hooks, queueing incompatible metadata for installed hooks, and assuming a ruleset change can retroactively repair prior accounting.
|
|
118
91
|
|
|
119
|
-
|
|
120
|
-
- `projectId` -- The project distributing payouts
|
|
121
|
-
- `token` -- Token being distributed
|
|
122
|
-
- `amount` -- Amount to distribute (in terms of `currency`)
|
|
123
|
-
- `currency` -- Currency denomination of the amount; must match a configured payout limit's currency
|
|
124
|
-
- `minTokensPaidOut` -- Slippage protection on the actual token amount paid out
|
|
92
|
+
## Journey 7: Migrate A Project To New Terminal Or Controller Surfaces Deliberately
|
|
125
93
|
|
|
126
|
-
**
|
|
127
|
-
1. `JBTerminalStore.balanceOf` decremented by `amountPaidOut` (currency-converted)
|
|
128
|
-
2. `JBTerminalStore.usedPayoutLimitOf` incremented
|
|
129
|
-
3. For each split: funds transferred (split hooks, project terminals, or addresses)
|
|
130
|
-
4. Failed splits: amount returned to project balance via `recordAddedBalanceFor`
|
|
131
|
-
5. Leftover (if splits < 100%): sent to project owner
|
|
132
|
-
6. Fee taken on all non-feeless payouts
|
|
94
|
+
**Starting state:** the project needs to move to a new terminal or controller path without pretending historical balances and routing do not exist.
|
|
133
95
|
|
|
134
|
-
**
|
|
96
|
+
**Success:** migration uses the protocol's explicit surfaces so balances, permissions, and future routing stay coherent.
|
|
135
97
|
|
|
136
|
-
**
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
- Payout limit resets each ruleset cycle (`cycleNumber`)
|
|
141
|
-
- `ownerMustSendPayouts` flag gates who can trigger payouts
|
|
142
|
-
- Individual split failures are caught by try-catch; the payout continues to remaining splits
|
|
143
|
-
- Split percentage uses `mulDiv(amount, split.percent, leftoverPercentage)` -- each split gets its proportion of the remaining amount, not of the original total
|
|
98
|
+
**Flow**
|
|
99
|
+
1. Confirm the active ruleset permits migration and the destination surface is the intended successor.
|
|
100
|
+
2. Use `JBController.migrate(...)` and the terminal-store migration paths instead of manually repointing addresses.
|
|
101
|
+
3. Recheck directory routing and accepted accounting contexts after migration completes.
|
|
144
102
|
|
|
145
|
-
|
|
103
|
+
## Journey 8: Hand Off Authority Without Handing Out Root Access
|
|
146
104
|
|
|
147
|
-
|
|
105
|
+
**Starting state:** the project owner wants operators, delegates, or automation to manage only specific surfaces.
|
|
148
106
|
|
|
149
|
-
**
|
|
107
|
+
**Success:** permissions are narrow, auditable, and scoped to the actions the operator actually needs.
|
|
150
108
|
|
|
151
|
-
**
|
|
109
|
+
**Flow**
|
|
110
|
+
1. The owner configures operator permissions in `JBPermissions`.
|
|
111
|
+
2. Downstream calls check those packed permission bits instead of assuming project ownership.
|
|
112
|
+
3. Integrations such as ownable wrappers, hook deployers, and router registries can now respect project-scoped delegation without custom ACL logic.
|
|
152
113
|
|
|
153
|
-
|
|
154
|
-
- `projectId`, `token`, `amount`, `currency` -- What to withdraw and in what denomination
|
|
155
|
-
- `minTokensPaidOut` -- Slippage protection on net amount after fees
|
|
156
|
-
- `beneficiary` -- Receives the withdrawn surplus
|
|
157
|
-
- `feeBeneficiary` -- Receives project #1 tokens minted from the fee payment
|
|
158
|
-
- `memo` -- Arbitrary string
|
|
114
|
+
## Hand-Offs
|
|
159
115
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
2. `JBTerminalStore.usedSurplusAllowanceOf` incremented
|
|
163
|
-
3. Fee taken (unless owner or beneficiary is feeless)
|
|
164
|
-
4. Net amount transferred to beneficiary
|
|
165
|
-
|
|
166
|
-
**Events**: `UseAllowance(rulesetId, rulesetCycleNumber, projectId, beneficiary, feeBeneficiary, amount, amountPaidOut, netAmountPaidOut, memo, caller)`
|
|
167
|
-
|
|
168
|
-
**Edge cases**:
|
|
169
|
-
- `usedAmount > surplus` -- reverts (`InadequateTerminalStoreBalance`)
|
|
170
|
-
- Surplus allowance resets each ruleset (keyed by `rulesetId`, not `cycleNumber`)
|
|
171
|
-
- Amount validated against surplus BEFORE checking allowance limit
|
|
172
|
-
- If either the owner or the beneficiary is feeless, no fee is taken
|
|
173
|
-
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
## 6. Mint Tokens (Owner)
|
|
177
|
-
|
|
178
|
-
**Entry point**: `JBController.mintTokensOf(uint256 projectId, uint256 tokenCount, address beneficiary, string memo, bool useReservedPercent)`
|
|
179
|
-
|
|
180
|
-
**Who can call**: Project owner, address with `MINT_TOKENS` permission, a project terminal, the data hook, or an address with `hasMintPermissionFor` from the data hook.
|
|
181
|
-
|
|
182
|
-
**Parameters**:
|
|
183
|
-
- `projectId` -- Target project
|
|
184
|
-
- `tokenCount` -- Total tokens to mint (including reserved portion)
|
|
185
|
-
- `beneficiary` -- Receives the non-reserved tokens
|
|
186
|
-
- `memo` -- Arbitrary string
|
|
187
|
-
- `useReservedPercent` -- If true, applies the ruleset's `reservedPercent`; if false, all tokens go to beneficiary
|
|
188
|
-
|
|
189
|
-
**State changes**:
|
|
190
|
-
1. `JBTokens.mintFor(beneficiary, beneficiaryTokenCount)` -- Mints non-reserved portion
|
|
191
|
-
2. `pendingReservedTokenBalanceOf[projectId]` incremented by reserved portion
|
|
192
|
-
|
|
193
|
-
**Events**: `MintTokens(beneficiary, projectId, tokenCount, beneficiaryTokenCount, memo, reservedPercent, caller)`
|
|
194
|
-
|
|
195
|
-
**Preview**: Call `JBController.previewMintOf(projectId, tokenCount, useReservedPercent)` to see how a mint would split between the beneficiary and reserved token pools under the current ruleset. Returns `(beneficiaryTokenCount, reservedTokenCount)`. This is a `view` function that does not modify state.
|
|
196
|
-
|
|
197
|
-
**Edge cases**:
|
|
198
|
-
- `tokenCount = 0` -- reverts (`ZeroTokensToMint`)
|
|
199
|
-
- If `allowOwnerMinting` is false in ruleset, only terminals and data hooks can mint
|
|
200
|
-
- If `reservedPercent = 10,000` (100%), all tokens go to pending reserved balance, `beneficiaryTokenCount = 0`
|
|
201
|
-
- Terminal calls this with `useReservedPercent = true` during payments
|
|
202
|
-
|
|
203
|
-
---
|
|
204
|
-
|
|
205
|
-
## 7. Burn Tokens
|
|
206
|
-
|
|
207
|
-
**Entry point**: `JBController.burnTokensOf(address holder, uint256 projectId, uint256 tokenCount, string memo)`
|
|
208
|
-
|
|
209
|
-
**Who can call**: The token holder, an address with the holder's `BURN_TOKENS` permission, or a project terminal.
|
|
210
|
-
|
|
211
|
-
**Parameters**:
|
|
212
|
-
- `holder` -- Address whose tokens to burn
|
|
213
|
-
- `projectId` -- Project whose tokens are being burned
|
|
214
|
-
- `tokenCount` -- Number of tokens to burn
|
|
215
|
-
- `memo` -- Arbitrary string
|
|
216
|
-
|
|
217
|
-
**State changes**:
|
|
218
|
-
1. Credits burned first (up to credit balance)
|
|
219
|
-
2. Remaining amount burned from ERC-20 balance (if any)
|
|
220
|
-
3. `JBTokens` reduces credit and/or ERC-20 supply
|
|
221
|
-
|
|
222
|
-
**Events**: `BurnTokens(holder, projectId, tokenCount, memo, caller)`
|
|
223
|
-
|
|
224
|
-
**Edge cases**:
|
|
225
|
-
- `tokenCount = 0` -- reverts (`ZeroTokensToBurn`)
|
|
226
|
-
- Credits are always burned first. If holder has 100 credits and 50 ERC-20, burning 120 burns all 100 credits + 20 ERC-20.
|
|
227
|
-
- Terminal calls this during cash outs
|
|
228
|
-
|
|
229
|
-
---
|
|
230
|
-
|
|
231
|
-
## 8. Queue New Ruleset
|
|
232
|
-
|
|
233
|
-
**Entry point**: `JBController.queueRulesetsOf(uint256 projectId, JBRulesetConfig[] rulesetConfigurations, string memo)`
|
|
234
|
-
|
|
235
|
-
**Who can call**: Project owner, address with `QUEUE_RULESETS` permission, or the `OMNICHAIN_RULESET_OPERATOR`.
|
|
236
|
-
|
|
237
|
-
**Parameters**:
|
|
238
|
-
- `projectId` -- Target project
|
|
239
|
-
- `rulesetConfigurations` -- Array of ruleset configs to queue
|
|
240
|
-
- `memo` -- Arbitrary string
|
|
241
|
-
|
|
242
|
-
**State changes**:
|
|
243
|
-
1. For each config:
|
|
244
|
-
- `JBRulesets.queueFor(...)` -- Creates new ruleset in linked list
|
|
245
|
-
- `JBSplits.setSplitGroupsOf(...)` -- Sets splits for the new ruleset
|
|
246
|
-
- `JBFundAccessLimits.setFundAccessLimitsFor(...)` -- Sets limits for the new ruleset
|
|
247
|
-
2. `latestRulesetIdOf[projectId]` updated
|
|
248
|
-
|
|
249
|
-
**Events**: `QueueRulesets(rulesetId, projectId, memo, caller)`, `RulesetQueued(rulesetId, projectId, ...)`
|
|
250
|
-
|
|
251
|
-
**Edge cases**:
|
|
252
|
-
- Empty array reverts (`RulesetsArrayEmpty`)
|
|
253
|
-
- `reservedPercent > 10,000` reverts
|
|
254
|
-
- `cashOutTaxRate > 10,000` reverts
|
|
255
|
-
- `weight > type(uint112).max` reverts
|
|
256
|
-
- `duration > type(uint32).max` reverts
|
|
257
|
-
- `mustStartAtOrAfter + duration > type(uint48).max` reverts
|
|
258
|
-
- If `mustStartAtOrAfter = 0`, it defaults to `block.timestamp`
|
|
259
|
-
- If `rulesetId` collides with current timestamp, it is incremented by 1
|
|
260
|
-
- Approval hook address is validated: must have code, must support `IJBRulesetApprovalHook` interface
|
|
261
|
-
- Queued rulesets take effect after the current ruleset expires (or immediately for `duration = 0`)
|
|
262
|
-
|
|
263
|
-
---
|
|
264
|
-
|
|
265
|
-
## 9. Set Splits
|
|
266
|
-
|
|
267
|
-
**Entry point**: `JBController.setSplitGroupsOf(uint256 projectId, uint256 rulesetId, JBSplitGroup[] splitGroups)`
|
|
268
|
-
|
|
269
|
-
**Who can call**: Project owner or address with `SET_SPLIT_GROUPS` permission. Alternatively, a contract whose address matches the lower 160 bits of a `groupId` can set that group directly -- but only if the upper 96 bits of the `groupId` are non-zero (bare-address groupIds are protocol-reserved for terminal payout groups).
|
|
270
|
-
|
|
271
|
-
**Parameters**:
|
|
272
|
-
- `projectId` -- Target project
|
|
273
|
-
- `rulesetId` -- The ruleset ID the splits apply to. Use `0` for default/fallback splits.
|
|
274
|
-
- `splitGroups` -- Array of `JBSplitGroup` structs, each containing a `groupId` and `JBSplit[]`
|
|
275
|
-
|
|
276
|
-
**State changes**:
|
|
277
|
-
1. `JBSplits` stores the new split groups for the project/ruleset/group combination
|
|
278
|
-
2. Locked splits from existing configuration must be preserved (validated by `JBSplits`)
|
|
279
|
-
|
|
280
|
-
**Events**: `SetSplit(projectId, rulesetId, groupId, split, caller)` per split (emitted by `JBSplits`, not `JBController`)
|
|
281
|
-
|
|
282
|
-
**Edge cases**:
|
|
283
|
-
- Locked splits (`lockedUntil > block.timestamp`) cannot be removed or modified
|
|
284
|
-
- If no splits set for a rulesetId, `splitsOf()` falls back to `rulesetId = 0` (default splits)
|
|
285
|
-
- If no default splits either, all payouts/reserved tokens go to project owner
|
|
286
|
-
- Split `percent` values are out of `SPLITS_TOTAL_PERCENT` (1,000,000,000)
|
|
287
|
-
- Payout splits use `groupId = uint256(uint160(token))`, reserved token splits use `groupId = 1` (`JBSplitGroupIds.RESERVED_TOKENS`)
|
|
288
|
-
|
|
289
|
-
---
|
|
290
|
-
|
|
291
|
-
## 10. Migrate Terminal
|
|
292
|
-
|
|
293
|
-
**Entry point**: `JBMultiTerminal.migrateBalanceOf(uint256 projectId, address token, IJBTerminal to)`
|
|
294
|
-
|
|
295
|
-
**Who can call**: Project owner or address with `MIGRATE_TERMINAL` permission.
|
|
296
|
-
|
|
297
|
-
**Parameters**:
|
|
298
|
-
- `projectId` -- Project being migrated
|
|
299
|
-
- `token` -- Token balance to migrate
|
|
300
|
-
- `to` -- Destination terminal
|
|
301
|
-
|
|
302
|
-
**State changes**:
|
|
303
|
-
1. `_feeFreeSurplusOf[projectId][token]` cleared
|
|
304
|
-
2. `JBTerminalStore.balanceOf[oldTerminal][projectId][token]` set to 0
|
|
305
|
-
3. If destination is non-feeless: 2.5% protocol fee deducted from balance via `_takeFeeFrom`
|
|
306
|
-
4. Remaining funds transferred to destination terminal via `to.addToBalanceOf()`
|
|
307
|
-
5. Destination terminal records the added balance
|
|
308
|
-
|
|
309
|
-
**Events**: `MigrateTerminal(projectId, token, to, amount, caller)`
|
|
310
|
-
|
|
311
|
-
**Edge cases**:
|
|
312
|
-
- Requires `allowTerminalMigration` in current ruleset
|
|
313
|
-
- Destination terminal must have accounting context for the token (validated via `accountingContextForTokenOf`)
|
|
314
|
-
- **Standard 2.5% protocol fee** is charged when migrating to a non-feeless terminal, consistent with all other fund egress. This also settles any `_feeFreeSurplusOf` liability.
|
|
315
|
-
- **Held fees are NOT transferred** -- they remain in the old terminal. Held fees belong to the fee beneficiary (project #1), not the migrating project.
|
|
316
|
-
- If balance is 0, no transfer occurs
|
|
317
|
-
- This only migrates one token's balance. Must be called once per token.
|
|
318
|
-
|
|
319
|
-
---
|
|
320
|
-
|
|
321
|
-
## 11. Migrate Controller
|
|
322
|
-
|
|
323
|
-
**Entry point**: `JBDirectory.setControllerOf(uint256 projectId, IERC165 controller)`
|
|
324
|
-
|
|
325
|
-
**Who can call**: Project owner, address with `SET_CONTROLLER` permission, or an address in `isAllowedToSetFirstController` (for first controller only). The current controller's ruleset must have `allowSetController` enabled.
|
|
326
|
-
|
|
327
|
-
**Parameters**:
|
|
328
|
-
- `projectId` -- The project being migrated
|
|
329
|
-
- `controller` -- The new controller (must support `IERC165`)
|
|
330
|
-
|
|
331
|
-
**Flow**:
|
|
332
|
-
1. `JBDirectory.setControllerOf(projectId, newController)` is called
|
|
333
|
-
2. If old controller exists AND new controller supports `IJBMigratable`: directory calls `newController.beforeReceiveMigrationFrom(oldController, projectId)`:
|
|
334
|
-
- Copies metadata URI from old controller (if it supports `IJBProjectUriRegistry`)
|
|
335
|
-
- Distributes pending reserved tokens from old controller (if it supports `IJBController` and has pending tokens)
|
|
336
|
-
3. If old controller exists AND old controller supports `IJBMigratable`: directory calls `oldController.migrate(projectId, newController)`:
|
|
337
|
-
- Reverts if pending reserved tokens > 0 (must distribute first)
|
|
338
|
-
4. Directory updates `controllerOf[projectId] = newController`
|
|
339
|
-
5. If old controller exists AND new controller supports `IJBMigratable`: directory calls `newController.afterReceiveMigrationFrom(oldController, projectId)` (currently a no-op; verifies caller is the directory)
|
|
340
|
-
|
|
341
|
-
**Events**: `Migrate(projectId, to, caller)` from old controller; `SetController(projectId, controller, caller)` from directory
|
|
342
|
-
|
|
343
|
-
**Edge cases**:
|
|
344
|
-
- `pendingReservedTokenBalanceOf[projectId] != 0` causes revert in `migrate()` -- reserved tokens must be distributed first
|
|
345
|
-
- `beforeReceiveMigrationFrom` automatically distributes pending reserved tokens from the old controller
|
|
346
|
-
- The old controller's `migrate()` runs while the directory still points to it (prevents reentrancy window)
|
|
347
|
-
- First controller can be set by addresses in `isAllowedToSetFirstController` without owner permission
|
|
348
|
-
|
|
349
|
-
---
|
|
350
|
-
|
|
351
|
-
## 12. Deploy ERC-20 for Project Tokens
|
|
352
|
-
|
|
353
|
-
**Entry point**: `JBController.deployERC20For(uint256 projectId, string name, string symbol, bytes32 salt)`
|
|
354
|
-
|
|
355
|
-
**Who can call**: Project owner or address with `DEPLOY_ERC20` permission.
|
|
356
|
-
|
|
357
|
-
**Parameters**:
|
|
358
|
-
- `projectId` -- Target project
|
|
359
|
-
- `name` -- ERC-20 token name
|
|
360
|
-
- `symbol` -- ERC-20 token symbol
|
|
361
|
-
- `salt` -- For deterministic deployment (CREATE2). Pass `bytes32(0)` for non-deterministic.
|
|
362
|
-
|
|
363
|
-
**State changes**:
|
|
364
|
-
1. `JBTokens.deployERC20For()` clones `JBERC20` implementation via `Clones.clone()` (or `Clones.cloneDeterministic()` if salt provided)
|
|
365
|
-
2. Clone's `initialize(name, symbol, owner=JBTokens)` is called
|
|
366
|
-
3. `JBTokens.tokenOf[projectId]` set to the new token
|
|
367
|
-
|
|
368
|
-
**Events**: `DeployERC20(projectId, deployer, salt, saltHash, caller)` from `JBController`, `DeployERC20(projectId, token, name, symbol, salt, caller)` from `JBTokens`
|
|
369
|
-
|
|
370
|
-
**Edge cases**:
|
|
371
|
-
- Can only be called once per project. If a token is already set, `JBTokens` reverts.
|
|
372
|
-
- `salt` is hashed with `_msgSender()` to prevent front-running deterministic deployments
|
|
373
|
-
- The clone's constructor sets invalid name/symbol; real values come from `initialize()`
|
|
374
|
-
|
|
375
|
-
---
|
|
376
|
-
|
|
377
|
-
## 13. Claim Credits as ERC-20
|
|
378
|
-
|
|
379
|
-
**Entry point**: `JBController.claimTokensFor(address holder, uint256 projectId, uint256 tokenCount, address beneficiary)`
|
|
380
|
-
|
|
381
|
-
**Who can call**: The credit holder or address with `CLAIM_TOKENS` permission.
|
|
382
|
-
|
|
383
|
-
**Parameters**:
|
|
384
|
-
- `holder` -- Address whose credits to convert
|
|
385
|
-
- `projectId` -- Target project
|
|
386
|
-
- `tokenCount` -- Number of credits to convert to ERC-20
|
|
387
|
-
- `beneficiary` -- Address to receive the ERC-20 tokens
|
|
388
|
-
|
|
389
|
-
**State changes**:
|
|
390
|
-
1. `JBTokens.creditBalanceOf[holder][projectId]` decreased by `tokenCount`
|
|
391
|
-
2. ERC-20 tokens minted to `beneficiary` for `tokenCount`
|
|
392
|
-
|
|
393
|
-
**Events**: `ClaimTokens(holder, projectId, creditBalance, count, beneficiary, caller)` (emitted by `JBTokens`)
|
|
394
|
-
|
|
395
|
-
**Edge cases**:
|
|
396
|
-
- Requires an ERC-20 token to be deployed for the project (reverts otherwise)
|
|
397
|
-
- Credits and ERC-20 tokens are fungible -- this is a one-way conversion from internal credits to on-chain ERC-20
|
|
398
|
-
- Does not require any ruleset flag (always allowed if ERC-20 exists)
|
|
399
|
-
|
|
400
|
-
---
|
|
401
|
-
|
|
402
|
-
## 14. Process Held Fees
|
|
403
|
-
|
|
404
|
-
**Entry point**: `JBMultiTerminal.processHeldFeesOf(uint256 projectId, address token, uint256 count)`
|
|
405
|
-
|
|
406
|
-
**Who can call**: Anyone.
|
|
407
|
-
|
|
408
|
-
**Parameters**:
|
|
409
|
-
- `projectId` -- Project whose held fees to process
|
|
410
|
-
- `token` -- Token the fees are denominated in
|
|
411
|
-
- `count` -- Maximum number of held fees to process
|
|
412
|
-
|
|
413
|
-
**State changes**:
|
|
414
|
-
1. For each processable fee (unlocked, i.e., `unlockTimestamp <= block.timestamp`):
|
|
415
|
-
- Fee entry deleted from `_heldFeesOf` array
|
|
416
|
-
- `_nextHeldFeeIndexOf` incremented
|
|
417
|
-
- Fee amount sent to project #1's terminal via `_processFee` (try-catch)
|
|
418
|
-
- On failure: fee amount returned to project's balance
|
|
419
|
-
2. If all fees processed: array and index are reset to 0
|
|
420
|
-
|
|
421
|
-
**Events**: `ProcessFee(projectId, token, amount, wasHeld, beneficiary, caller)` per fee, or `FeeReverted(...)` on failure
|
|
422
|
-
|
|
423
|
-
**Edge cases**:
|
|
424
|
-
- Fees unlock after 28 days from when they were held
|
|
425
|
-
- Processing stops at the first locked fee (fees are sequential)
|
|
426
|
-
- Re-reads storage index each iteration (reentrancy-safe)
|
|
427
|
-
- If fee terminal for project #1 does not exist for the token, `_processFee` reverts (caught by try-catch, returns to project balance)
|
|
428
|
-
|
|
429
|
-
---
|
|
430
|
-
|
|
431
|
-
## 15. Add Price Feeds
|
|
432
|
-
|
|
433
|
-
**Entry point**: `JBController.addPriceFeedFor(uint256 projectId, uint256 pricingCurrency, uint256 unitCurrency, IJBPriceFeed feed)`
|
|
434
|
-
|
|
435
|
-
**Who can call**: Project owner or address with `ADD_PRICE_FEED` permission.
|
|
436
|
-
|
|
437
|
-
**Parameters**:
|
|
438
|
-
- `projectId` -- Project the feed applies to (0 for protocol-wide default, owner-only)
|
|
439
|
-
- `pricingCurrency` -- Currency the feed's output is in
|
|
440
|
-
- `unitCurrency` -- Currency being priced
|
|
441
|
-
- `feed` -- The price feed contract address
|
|
442
|
-
|
|
443
|
-
**State changes**:
|
|
444
|
-
1. `JBPrices` stores the feed for the `(projectId, pricingCurrency, unitCurrency)` triple
|
|
445
|
-
2. Feed is **immutable** once set -- cannot be replaced or removed
|
|
446
|
-
|
|
447
|
-
**Events**: `AddPriceFeed(projectId, pricingCurrency, unitCurrency, feed, caller)` (emitted by `JBPrices`)
|
|
448
|
-
|
|
449
|
-
**Edge cases**:
|
|
450
|
-
- Requires `allowAddPriceFeed` in current ruleset
|
|
451
|
-
- Protocol-wide defaults (`projectId = 0`) can only be set by the `JBPrices` owner
|
|
452
|
-
- `JBPrices` auto-calculates inverse: if A->B exists, B->A is derived. If both explicit and inverse exist, explicit takes priority.
|
|
453
|
-
- Lookup order: project-specific -> project-specific inverse -> default (projectId=0) -> default inverse
|
|
454
|
-
|
|
455
|
-
---
|
|
456
|
-
|
|
457
|
-
## 16. Set Permissions
|
|
458
|
-
|
|
459
|
-
**Entry point**: `JBPermissions.setPermissionsFor(address account, JBPermissionsData permissionsData)`
|
|
460
|
-
|
|
461
|
-
**Who can call**: The `account` itself, or a ROOT operator for the project (with restrictions).
|
|
462
|
-
|
|
463
|
-
**Parameters**:
|
|
464
|
-
- `account` -- The account granting permissions
|
|
465
|
-
- `permissionsData.operator` -- Address receiving the permissions
|
|
466
|
-
- `permissionsData.projectId` -- Project scope (0 = wildcard, all projects)
|
|
467
|
-
- `permissionsData.permissionIds` -- Array of `uint8` permission IDs to grant
|
|
468
|
-
|
|
469
|
-
**State changes**:
|
|
470
|
-
1. `permissionsOf[operator][account][projectId]` set to packed `uint256` bitmap
|
|
471
|
-
|
|
472
|
-
**Events**: `OperatorPermissionsSet(operator, account, projectId, permissionIds, packed, caller)`
|
|
473
|
-
|
|
474
|
-
**Edge cases**:
|
|
475
|
-
- Permission ID 0 cannot be set (reserved, always `NoZeroPermission` revert)
|
|
476
|
-
- ROOT (ID 1) cannot be set for wildcard `projectId = 0` (`CantSetRootPermissionForWildcardProject`)
|
|
477
|
-
- ROOT operators can set non-ROOT permissions for others on their scoped project
|
|
478
|
-
- ROOT operators CANNOT grant ROOT to other addresses
|
|
479
|
-
- ROOT operators CANNOT set permissions for wildcard `projectId = 0`
|
|
480
|
-
- Setting permissions replaces the entire bitmap (not additive) -- passing an empty array clears all permissions
|
|
481
|
-
|
|
482
|
-
---
|
|
483
|
-
|
|
484
|
-
## 17. Transfer Project Ownership
|
|
485
|
-
|
|
486
|
-
**Entry point**: `JBProjects.transferFrom(address from, address to, uint256 tokenId)` (standard ERC-721)
|
|
487
|
-
|
|
488
|
-
**Who can call**: The current owner, an approved address, or an operator approved for all.
|
|
489
|
-
|
|
490
|
-
**Parameters**:
|
|
491
|
-
- `from` -- Current owner
|
|
492
|
-
- `to` -- New owner
|
|
493
|
-
- `tokenId` -- The project ID (same as the ERC-721 token ID)
|
|
494
|
-
|
|
495
|
-
**State changes**:
|
|
496
|
-
1. ERC-721 ownership transferred
|
|
497
|
-
2. All `PROJECTS.ownerOf(projectId)` calls now return the new owner
|
|
498
|
-
3. All permission checks that reference the owner now apply to the new owner
|
|
499
|
-
|
|
500
|
-
**Events**: `Transfer(from, to, tokenId)` (standard ERC-721 event)
|
|
501
|
-
|
|
502
|
-
**Edge cases**:
|
|
503
|
-
- This is a standard ERC-721 transfer. All ERC-721 rules apply (approval, operator, etc.)
|
|
504
|
-
- **Permissions are NOT transferred**. Existing operators retain their permissions scoped to the account that granted them (the old owner). The new owner must grant their own permissions.
|
|
505
|
-
- The new owner immediately gets full control: queue rulesets, set terminals, set splits, etc.
|
|
506
|
-
- Transferring to `address(0)` is prevented by OpenZeppelin's ERC-721 implementation
|
|
507
|
-
|
|
508
|
-
---
|
|
509
|
-
|
|
510
|
-
## 18. Add to Balance (Without Minting)
|
|
511
|
-
|
|
512
|
-
**Entry point**: `JBMultiTerminal.addToBalanceOf(uint256 projectId, address token, uint256 amount, bool shouldReturnHeldFees, string memo, bytes metadata)`
|
|
513
|
-
|
|
514
|
-
**Who can call**: Anyone.
|
|
515
|
-
|
|
516
|
-
**Parameters**:
|
|
517
|
-
- `projectId` -- Project to add funds to
|
|
518
|
-
- `token` -- Token to add
|
|
519
|
-
- `amount` -- Amount to add (uses `msg.value` for native token)
|
|
520
|
-
- `shouldReturnHeldFees` -- If true, uses the added amount to return held fees (reducing the fee burden)
|
|
521
|
-
- `memo`, `metadata` -- Arbitrary data
|
|
522
|
-
|
|
523
|
-
**State changes**:
|
|
524
|
-
1. Tokens transferred to terminal
|
|
525
|
-
2. If `shouldReturnHeldFees`: iterates held fees, returns fees proportional to amount added
|
|
526
|
-
3. `JBTerminalStore.balanceOf[terminal][projectId][token]` incremented by `amount + returnedFees`
|
|
527
|
-
|
|
528
|
-
**Events**: `AddToBalance(projectId, amount, returnedFees, memo, metadata, caller)`
|
|
529
|
-
|
|
530
|
-
**Edge cases**:
|
|
531
|
-
- Does NOT mint tokens -- purely adds to balance. This increases surplus, which increases cash out value for existing holders.
|
|
532
|
-
- `shouldReturnHeldFees = true` partially or fully returns held fees. The returned fee amount is added to the project balance on top of the deposited amount.
|
|
533
|
-
- Used by terminal migration (`migrateBalanceOf`) and split payouts (when `preferAddToBalance = true`)
|
|
534
|
-
|
|
535
|
-
---
|
|
536
|
-
|
|
537
|
-
## 19. Transfer Credits
|
|
538
|
-
|
|
539
|
-
**Entry point**: `JBController.transferCreditsFrom(address holder, uint256 projectId, address recipient, uint256 creditCount)`
|
|
540
|
-
|
|
541
|
-
**Who can call**: The credit holder or address with `TRANSFER_CREDITS` permission.
|
|
542
|
-
|
|
543
|
-
**Parameters**:
|
|
544
|
-
- `holder` -- Address transferring credits
|
|
545
|
-
- `projectId` -- Project whose credits are being transferred
|
|
546
|
-
- `recipient` -- Address to receive credits
|
|
547
|
-
- `creditCount` -- Number of credits to transfer
|
|
548
|
-
|
|
549
|
-
**State changes**:
|
|
550
|
-
1. `JBTokens.creditBalanceOf[holder][projectId]` decreased
|
|
551
|
-
2. `JBTokens.creditBalanceOf[recipient][projectId]` increased
|
|
552
|
-
|
|
553
|
-
**Events**: `TransferCredits(holder, projectId, recipient, count, caller)` (emitted by `JBTokens`)
|
|
554
|
-
|
|
555
|
-
**Edge cases**:
|
|
556
|
-
- Reverts if `pauseCreditTransfers` is set in current ruleset
|
|
557
|
-
- Credits are internal -- they are NOT ERC-20 tokens. Use `claimTokensFor` to convert credits to ERC-20.
|
|
558
|
-
- This only transfers credits, not ERC-20 tokens. ERC-20 tokens are transferred via standard ERC-20 `transfer`/`transferFrom`.
|
|
559
|
-
|
|
560
|
-
---
|
|
561
|
-
|
|
562
|
-
## 20. Set Project URI
|
|
563
|
-
|
|
564
|
-
**Entry point**: `JBController.setUriOf(uint256 projectId, string uri)`
|
|
565
|
-
|
|
566
|
-
**Who can call**: Project owner or address with `SET_PROJECT_URI` permission.
|
|
567
|
-
|
|
568
|
-
**Parameters**:
|
|
569
|
-
- `projectId` -- Target project
|
|
570
|
-
- `uri` -- Metadata URI (typically an IPFS hash)
|
|
571
|
-
|
|
572
|
-
**State changes**: `uriOf[projectId] = uri`
|
|
573
|
-
|
|
574
|
-
**Events**: `SetUri(projectId, uri, caller)`
|
|
575
|
-
|
|
576
|
-
**Edge cases**:
|
|
577
|
-
- Empty string is valid -- clears the metadata URI
|
|
578
|
-
- No ruleset flag required (always allowed with permission)
|
|
579
|
-
|
|
580
|
-
---
|
|
581
|
-
|
|
582
|
-
## 21. Set Custom Token
|
|
583
|
-
|
|
584
|
-
**Entry point**: `JBController.setTokenFor(uint256 projectId, IJBToken token)`
|
|
585
|
-
|
|
586
|
-
**Who can call**: Project owner or address with `SET_TOKEN` permission.
|
|
587
|
-
|
|
588
|
-
**Parameters**:
|
|
589
|
-
- `projectId` -- Target project
|
|
590
|
-
- `token` -- The custom token contract (must implement `IJBToken`)
|
|
591
|
-
|
|
592
|
-
**State changes**: `JBTokens.tokenOf[projectId] = token`
|
|
593
|
-
|
|
594
|
-
**Events**: `SetToken(projectId, token, caller)` (emitted by `JBTokens`)
|
|
595
|
-
|
|
596
|
-
**Edge cases**:
|
|
597
|
-
- Requires `allowSetCustomToken` in current or upcoming ruleset
|
|
598
|
-
- Can only be called if no token is currently set for the project
|
|
599
|
-
- The token must conform to `IJBToken` interface (18 decimals required)
|
|
600
|
-
|
|
601
|
-
---
|
|
602
|
-
|
|
603
|
-
## 22. Set Token Metadata
|
|
604
|
-
|
|
605
|
-
**Entry point**: `JBController.setTokenMetadataOf(uint256 projectId, string name, string symbol)`
|
|
606
|
-
|
|
607
|
-
**Who can call**: Project owner or address with `SET_TOKEN_METADATA` permission.
|
|
608
|
-
|
|
609
|
-
**Parameters**:
|
|
610
|
-
- `projectId` -- Target project
|
|
611
|
-
- `name` -- New ERC-20 token name
|
|
612
|
-
- `symbol` -- New ERC-20 token symbol
|
|
613
|
-
|
|
614
|
-
**State changes**: Updates the ERC-20 token's name and symbol via `JBERC20.setMetadata()`
|
|
615
|
-
|
|
616
|
-
**Events**: `SetTokenMetadata(projectId, name, symbol, caller)` (emitted by `JBTokens`)
|
|
617
|
-
|
|
618
|
-
**Edge cases**:
|
|
619
|
-
- Requires an ERC-20 token to be deployed for the project (reverts if no token set)
|
|
620
|
-
- Only works with `JBERC20` clones (custom tokens that implement `IJBToken` may not support `setMetadata`)
|
|
621
|
-
|
|
622
|
-
---
|
|
623
|
-
|
|
624
|
-
## 23. Update Ruleset Weight Cache
|
|
625
|
-
|
|
626
|
-
**Entry point**: `JBRulesets.updateRulesetWeightCache(uint256 projectId, uint256 rulesetId)`
|
|
627
|
-
|
|
628
|
-
**Who can call**: Anyone.
|
|
629
|
-
|
|
630
|
-
**Parameters**:
|
|
631
|
-
- `projectId` -- Target project
|
|
632
|
-
- `rulesetId` -- The specific ruleset to update cache for
|
|
633
|
-
|
|
634
|
-
**State changes**:
|
|
635
|
-
1. `_weightCacheOf[projectId][rulesetId].weight` updated to current decayed weight
|
|
636
|
-
2. `_weightCacheOf[projectId][rulesetId].weightCutMultiple` updated
|
|
637
|
-
|
|
638
|
-
**Events**: `WeightCacheUpdated(projectId, weight, weightCutMultiple, caller)`
|
|
639
|
-
|
|
640
|
-
**Edge cases**:
|
|
641
|
-
- Required for projects with >20,000 cycles (otherwise `currentOf()` reverts with `WeightCacheRequired`)
|
|
642
|
-
- Advances the cache by at most `_WEIGHT_CUT_MULTIPLE_CACHE_LOOKUP_THRESHOLD` (20,000) cycles per call
|
|
643
|
-
- Multiple calls needed to fully catch up for very large cycle gaps
|
|
644
|
-
- No-op if `duration == 0` or `weightCutPercent == 0`
|
|
645
|
-
|
|
646
|
-
---
|
|
647
|
-
|
|
648
|
-
## 24. Add Accounting Contexts
|
|
649
|
-
|
|
650
|
-
**Entry point**: `JBMultiTerminal.addAccountingContextsFor(uint256 projectId, JBAccountingContext[] accountingContexts)`
|
|
651
|
-
|
|
652
|
-
**Who can call**: Project owner, address with `ADD_ACCOUNTING_CONTEXTS` permission, or the project's controller.
|
|
653
|
-
|
|
654
|
-
**Parameters**:
|
|
655
|
-
- `projectId` -- Target project
|
|
656
|
-
- `accountingContexts` -- Array of `JBAccountingContext` structs, each specifying a token address, decimals, and currency
|
|
657
|
-
|
|
658
|
-
**State changes**:
|
|
659
|
-
1. For each context: validates token decimals, stores `_accountingContextForTokenOf[projectId][token]`
|
|
660
|
-
2. Appends to `_accountingContextsOf[projectId]` array
|
|
661
|
-
|
|
662
|
-
**Events**: `SetAccountingContext(projectId, context, caller)` per token
|
|
663
|
-
|
|
664
|
-
**Edge cases**:
|
|
665
|
-
- Requires `allowAddAccountingContext` in current ruleset (if ruleset exists)
|
|
666
|
-
- Token cannot be added twice (`AccountingContextAlreadySet`)
|
|
667
|
-
- Currency must be non-zero (`ZeroAccountingContextCurrency`)
|
|
668
|
-
- For non-native tokens: decimals are validated against the token's `decimals()` function. Tokens that revert on `decimals()` bypass validation (caller responsible).
|
|
116
|
+
- Use [nana-permission-ids-v6](../nana-permission-ids-v6/USER_JOURNEYS.md) for the shared permission vocabulary that downstream repos import.
|
|
117
|
+
- Use [nana-721-hook-v6](../nana-721-hook-v6/USER_JOURNEYS.md), [nana-router-terminal-v6](../nana-router-terminal-v6/USER_JOURNEYS.md), and [nana-buyback-hook-v6](../nana-buyback-hook-v6/USER_JOURNEYS.md) for opinionated layers on top of the core terminal and ruleset surfaces.
|