@bananapus/distributor-v6 0.0.37 → 0.0.42

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
@@ -6,13 +6,13 @@
6
6
 
7
7
  - [ARCHITECTURE.md](./ARCHITECTURE.md) — system overview, modules, trust boundaries, and core invariants.
8
8
  - [USER_JOURNEYS.md](./USER_JOURNEYS.md) — end-to-end flows for funders and claimants across token and 721 variants.
9
- - [INVARIANTS.md](./INVARIANTS.md) — per-section invariants for snapshot fairness, vesting math, claim authority, loans, and recycling.
9
+ - [INVARIANTS.md](./INVARIANTS.md) — per-section invariants for snapshot fairness, vesting math, beneficiary routing, loans, and recycling.
10
10
  - [RISKS.md](./RISKS.md) — risk register with priority risks and the minimum invariants to verify.
11
11
  - [ADMINISTRATION.md](./ADMINISTRATION.md) — deployment parameters, control posture, and recovery guidance.
12
12
  - [SKILLS.md](./SKILLS.md) — quick index for routing tasks into the right sub-document.
13
13
  - [STYLE_GUIDE.md](./STYLE_GUIDE.md) — Solidity and repo conventions used across the Juicebox V6 ecosystem.
14
14
  - [AUDIT_INSTRUCTIONS.md](./AUDIT_INSTRUCTIONS.md) — audit framing, targets, and suggested hunting grounds.
15
- - [CHANGELOG.md](./CHANGELOG.md) release notes and dependency bumps.
15
+ - [CHANGELOG.md](./CHANGELOG.md) - V5 to V6 migration changelog.
16
16
 
17
17
  ## Overview
18
18
 
@@ -22,7 +22,7 @@ The package separates distribution mechanics by asset type:
22
22
 
23
23
  - `JBDistributor` coordinates shared round and vesting logic
24
24
  - `JBTokenDistributor` distributes ERC-20 balances using `IVotes` checkpointed voting power
25
- - `JB721Distributor` distributes value to 721 holders using checkpointed voting power, ensuring only holders at the funded round's snapshot block are eligible
25
+ - `JB721Distributor` distributes value to active 721 holders using the hook's checkpointed owner and active-vote data, ensuring only holders at the funded round's snapshot block are eligible
26
26
 
27
27
  Both concrete distributors implement `IJBSplitHook`, which makes them usable directly from Juicebox payout splits.
28
28
 
@@ -30,44 +30,52 @@ Use this repo when the problem is "how do we distribute already-owned assets ove
30
30
 
31
31
  If the issue is "where did the project's value come from?" start in `nana-core-v6`, `nana-721-hook-v6`, or the upstream repo that minted or received the assets first.
32
32
 
33
- ## Key Contracts
33
+ ## Key contracts
34
34
 
35
35
  | Contract | Role |
36
36
  | --- | --- |
37
37
  | `JBDistributor` | Shared round-based vesting, claiming, and accounting logic. |
38
38
  | `JBTokenDistributor` | ERC-20 distributor keyed to `IVotes` checkpointed voting power. |
39
- | `JB721Distributor` | NFT-aware distributor keyed to checkpointed voting power from the hook's `CHECKPOINTS()` module. Only NFTs held at the funded round's snapshot block are eligible. |
39
+ | `JB721Distributor` | NFT-aware distributor keyed to checkpointed voting power from the hook's checkpoint module. Only NFTs held at the funded round's snapshot block are eligible. |
40
40
 
41
- ## Mental Model
41
+ ## Mental model
42
42
 
43
43
  1. a project funds the distributor, often through a payout split
44
44
  2. accepted funding is assigned to the current reward round for the chosen token or 721 stake source
45
- 3. the distributor's immutable claim duration decides whether funded reward rounds expire
46
- 4. the encoded token staker or current NFT owner later claims completed past reward rounds into a fresh vesting entry
47
- 5. anyone can recycle expired unclaimed reward rounds after their deadline
48
- 6. recipients collect their vested share as the configured vesting schedule unlocks
45
+ 3. each funded reward round records an active-vote snapshot denominator for its token, collection, or tier group
46
+ 4. anyone can start vesting completed past reward rounds for an encoded token staker or current NFT owner
47
+ 5. anyone can recycle expired reward-round inventory that has not started vesting after the claim deadline
48
+ 6. recipients collect their vested share as the configured vesting schedule unlocks; helpers can collect only to the
49
+ canonical holder
49
50
  7. eligible claimants can borrow against vesting revnet rewards without bypassing the vesting schedule
50
- 8. some unclaimable value can be recycled through explicit cleanup paths, depending on the distributor type
51
+ 8. burned 721 rewards can be materialized and recycled through explicit cleanup paths as they vest
51
52
 
52
53
  This repo does not explain why an allocation exists. It only defines how funded inventory is handed out.
53
54
 
54
- ## Read These Files First
55
+ ## Read these files first
55
56
 
56
57
  1. `src/interfaces/IJBDistributor.sol`
57
58
  2. `src/JBDistributor.sol`
58
59
  3. `src/JBTokenDistributor.sol`
59
60
  4. `src/JB721Distributor.sol`
60
61
 
61
- ## Integration Traps
62
+ ## Integration traps
62
63
 
63
64
  - distribution correctness depends on the distributor actually holding the assets it is expected to vest
64
- - ERC-20 and ERC-721 distributions share historical reward-round accounting, but claim authority differs:
65
- token rewards are claimed by the encoded staker address, while 721 rewards are claimed by the current NFT owner
66
- - `CLAIM_DURATION` is fixed at deployment; `0` means reward rounds do not expire, otherwise all funding paths use the
67
- same deadline measured from when the funded round first becomes claimable
68
- - `burnExpiredRewards` is permissionless and only recycles the unclaimed remainder; already-materialized vesting entries
69
- remain claimable on their normal vesting curve
70
- - expired and forfeited rewards stay in distributor inventory and are recycled into the current reward round
65
+ - ERC-20 and ERC-721 distributions share historical reward-round accounting, but their canonical beneficiaries differ:
66
+ token rewards belong to the encoded staker address, while 721 rewards belong to the current NFT owner
67
+ - `beginVesting` is permissionless because no value leaves the distributor; `collectVestedRewards` is permissionless
68
+ only when paid to the canonical beneficiary, while an authorized holder can still choose any beneficiary
69
+ - `CLAIM_DURATION` is fixed at deployment; `0` means reward rounds do not expire, while nonzero values set the window
70
+ after which unmaterialized reward inventory can be recycled
71
+ - token distributors record `IJBActiveVotes.getPastTotalActiveVotes` at the funded round's snapshot block; only
72
+ addresses with `getPastVotes` at that block share the pot
73
+ - 721 distributors record the hook checkpoint module's active total for all-tiers rewards, or the summed active totals
74
+ for a tier-scoped reward group; both modes cap each NFT claim by the snapshot owner's remaining active units for that
75
+ NFT's tier
76
+ - `recycleExpiredRewards` is permissionless; it recycles the expired round's unmaterialized remainder while preserving
77
+ amounts that already started vesting
78
+ - eligible expired and forfeited rewards stay in distributor inventory and are recycled into the current reward round
71
79
  - revnet loan-backed vesting is opt-in at deployment; the reward token must be a REVOwner-owned revnet token, the
72
80
  distributor keeps the loan NFT, and repayment restores the original vesting schedule instead of releasing all
73
81
  collateral immediately
@@ -75,26 +83,28 @@ This repo does not explain why an allocation exists. It only defines how funded
75
83
  stale collection lock and forfeit only the vesting rewards that were collateralized by that loan
76
84
  - distributors deployed with `VESTING_ROUNDS == 0` disable revnet vesting loans because rewards are immediately
77
85
  collectible instead of locked in a vesting position
78
- - `releaseForfeitedRewards` matters for 721 distributions; token-vote distributions do not have the same burned-token
79
- forfeiture path
86
+ - `releaseForfeitedRewards` matters for 721 distributions; it first materializes any unclaimed historical shares for
87
+ burned NFTs, then recycles only the amount unlocked by the vesting schedule. Token-vote distributions do not have the
88
+ same burned-token forfeiture path
80
89
  - reward, vesting, and loan accounting carries a `groupId`: `0` is the all-tiers group (the default pool), a non-zero
81
90
  group is `keccak256(abi.encode(tierIds))`. The tier overloads live on `JB721Distributor`; the base is tier-agnostic.
82
91
  Split funding via `processSplitWith` always lands in group 0 — a split cannot carry a tier set; tier-scoped pots
83
92
  require the explicit `fund(hook, tierIds, token, amount)` overload, and claims/collections must pass the same
84
93
  `tierIds` to hit that group
85
94
  - tier-scoped 721 pots weigh each eligible NFT by its tier's `votingUnits` against a summed
86
- `getPastTierVotingUnits` denominator, which requires `@bananapus/721-hook-v6 >= 0.0.63` for that checkpoints API
95
+ `getPastTotalTierActiveVotes` denominator, then cap each numerator with `getPastAccountTierActiveVotes`; this
96
+ requires `@bananapus/721-hook-v6 >= 0.0.73` for the active-vote checkpoints API
87
97
  - snapshot timing is part of the trusted surface
88
98
  - this repo settles distributions, but it does not prove the upstream entitlement math was correct
89
99
 
90
- ## Where State Lives
100
+ ## Where state lives
91
101
 
92
102
  - round and vesting state: `JBDistributor`
93
103
  - historical reward-round inputs: `JBRewardRoundData`
94
104
  - vesting schedule state: `JBVestingData`
95
105
  - asset-specific claim behavior: the concrete distributor
96
106
 
97
- ## High-Signal Tests
107
+ ## High-signal tests
98
108
 
99
109
  1. `test/JBTokenDistributor.t.sol`
100
110
  2. `test/JB721Distributor.t.sol`
@@ -121,7 +131,7 @@ Useful scripts:
121
131
  - `npm run deploy:mainnets`
122
132
  - `npm run deploy:testnets`
123
133
 
124
- ## Repository Layout
134
+ ## Repository layout
125
135
 
126
136
  ```text
127
137
  src/
@@ -136,15 +146,15 @@ script/
136
146
  Deploy.s.sol
137
147
  ```
138
148
 
139
- ## Risks And Notes
149
+ ## Risks and notes
140
150
 
141
151
  - distributors are only as trustworthy as the vesting parameters and funding they receive
142
152
  - operational mistakes often come from funding the wrong asset or underfunding the distributor
143
153
  - teams should review claim timing and snapshot assumptions with the same care they review the payout source
144
- - deployers that set a nonzero claim duration should choose a window long enough for expected claimants, because
145
- expired unclaimed rewards can be recycled by anyone
154
+ - token distributors require hooks that expose `IJBActiveVotes`; expiring token rounds recycle any unmaterialized
155
+ remainder after the deadline
146
156
 
147
- ## For AI Agents
157
+ ## For AI agents
148
158
 
149
159
  - Treat this repo as distribution plumbing, not as the source of upstream entitlement math.
150
160
  - Read both the ERC-20 and ERC-721 tests before claiming the flows are equivalent.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/distributor-v6",
3
- "version": "0.0.37",
3
+ "version": "0.0.42",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -24,12 +24,12 @@
24
24
  "deploy:testnets": "source ./.env && npx sphinx propose ./script/Deploy.s.sol --networks testnets"
25
25
  },
26
26
  "dependencies": {
27
- "@bananapus/721-hook-v6": "^0.0.65",
28
- "@bananapus/core-v6": "^0.0.78",
29
- "@bananapus/permission-ids-v6": "^0.0.28",
27
+ "@bananapus/721-hook-v6": "^0.0.73",
28
+ "@bananapus/core-v6": "^0.0.86",
29
+ "@bananapus/permission-ids-v6": "^0.0.30",
30
30
  "@openzeppelin/contracts": "5.6.1",
31
- "@prb/math": "4.1.1",
32
- "@rev-net/core-v6": "^0.0.84"
31
+ "@prb/math": "4.1.2",
32
+ "@rev-net/core-v6": "^0.0.89"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@sphinx-labs/plugins": "0.33.3"
@@ -5,10 +5,10 @@
5
5
  | If you're editing... | Verify... |
6
6
  |---|---|
7
7
  | `JBDistributor` vesting math | Claim totals, `totalVestingAmountOf`, and pool balances still reconcile across rounds |
8
- | `JBTokenDistributor` checkpoint logic | `getPastVotes` and `getPastTotalSupply` are read at the intended round-start block |
8
+ | `JBTokenDistributor` checkpoint logic | Non-expiring rounds read `getPastVotes` and `getPastTotalSupply` at the intended snapshot block; active-voter rounds read `getPastVotes` and `getPastTotalActiveVotes` at the intended snapshot block |
9
9
  | `JB721Distributor` stake math | Minted, remaining, and burned supply still produce the intended tier-weighted total stake |
10
10
  | `processSplitWith` | Allowance-based `transferFrom` flow preserves actual received balances; split funding still records under `groupId == 0` only (a split cannot carry a tier set) |
11
- | `groupId` threading | The `groupId` dimension stays consistent across the reward (`rewardRoundOf`), vesting (`vestingDataOf`, `latestVestedIndexOf`, `nextClaimRoundOf`), and loan (`activeVestingLoanIdOf`, `JBVestingLoan`) maps; the `tierIds` overloads (`fund`/`beginVesting`/`collectVestedRewards`/`borrowAgainstVesting`/`burnExpiredRewards`/`releaseForfeitedRewards`) live on `JB721Distributor` and derive the group ID via `_groupIdFor`; the base is tier-agnostic and group 0 is the default all-tiers pool |
11
+ | `groupId` threading | The `groupId` dimension stays consistent across the reward (`rewardRoundOf`), vesting (`vestingDataOf`, `latestVestedIndexOf`, `nextClaimRoundOf`), and loan (`activeVestingLoanIdOf`, `JBVestingLoan`) maps; the `tierIds` overloads (`fund`/`beginVesting`/`collectVestedRewards`/`borrowAgainstVesting`/`recycleExpiredRewards`/`releaseForfeitedRewards`) live on `JB721Distributor` and derive the group ID via `_groupIdFor`; the base is tier-agnostic and group 0 is the default all-tiers pool |
12
12
  | Deployment inputs | `DIRECTORY_ADDRESS`, `ROUND_DURATION`, and `VESTING_ROUNDS` match the intended chain and operator plan |
13
13
 
14
14
  ## Common failure modes
@@ -16,11 +16,11 @@
16
16
  | Symptom | Likely cause |
17
17
  |---|---|
18
18
  | A holder gets no rewards in the token distributor | They never delegated, so `getPastVotes` returned zero |
19
- | Rewards appear stuck in the distributor | Supply was undelegated, vesting never began for the target token IDs, or the round boundary assumption is wrong |
19
+ | Rewards appear stuck in the distributor | Supply was undelegated in a non-expiring token round, an active-voter token round had zero active votes and has not been recycled, vesting never began for the target token IDs, or the round boundary assumption is wrong |
20
20
  | 721 reward shares look diluted | Burned supply was not excluded correctly or token-to-tier mapping is wrong |
21
21
  | Split-hook funding credits the wrong amount | The caller did not grant a sufficient ERC-20 allowance before calling `processSplitWith` |
22
22
 
23
- ## Read Next
23
+ ## Read next
24
24
 
25
25
  - [`script/Deploy.s.sol`](../script/Deploy.s.sol) when the failure might be deployment config rather than distributor math.
26
26
  - [`test/invariant/JB721DistributorInvariant.t.sol`](../test/invariant/JB721DistributorInvariant.t.sol) when a local patch looks safe but may have broken a longer-lived accounting invariant.
@@ -12,7 +12,7 @@
12
12
 
13
13
  ### Round and checkpoint semantics
14
14
 
15
- The token distributor depends on checkpointed voting power at the round start block. Holders must delegate for `getPastVotes` to count them, and undelegated supply can leave rewards stranded in the pool for later rounds.
15
+ The token distributor depends on checkpointed voting power at the reward round's snapshot block. Holders must delegate for `getPastVotes` to count them. Deployments with `CLAIM_DURATION == 0` use `getPastTotalSupply`, so undelegated supply can dilute claims; deployments with nonzero claim duration use `IJBActiveVotes.getPastTotalActiveVotes`, so undelegated balances such as AMM custody with no delegate do not share the pot.
16
16
 
17
17
  ### Funding path
18
18