@bananapus/721-hook-v6 0.0.36 → 0.0.38
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 +47 -47
- package/ARCHITECTURE.md +53 -46
- package/AUDIT_INSTRUCTIONS.md +35 -75
- package/CHANGELOG.md +4 -0
- package/README.md +19 -26
- package/RISKS.md +82 -89
- package/SKILLS.md +17 -22
- package/USER_JOURNEYS.md +61 -133
- package/package.json +1 -1
- package/src/JB721CheckpointsDeployer.sol +2 -0
- package/src/JB721TiersHookProjectDeployer.sol +3 -3
- package/src/interfaces/IJB721CheckpointsDeployer.sol +3 -0
- package/test/audit/CodexNemesisProjectDeployerAuth.t.sol +273 -0
- package/test/unit/JB721CheckpointsDeployer_AccessControl.t.sol +116 -0
package/RISKS.md
CHANGED
|
@@ -1,125 +1,118 @@
|
|
|
1
1
|
# Juicebox 721 Hook Risk Register
|
|
2
2
|
|
|
3
|
-
This file
|
|
3
|
+
This file covers the tiered-NFT accounting, reserve-mint, and cash-out risks in the shared 721 hook used across multiple higher-level products.
|
|
4
4
|
|
|
5
|
-
## How
|
|
5
|
+
## How To Use This File
|
|
6
6
|
|
|
7
|
-
- Read `Priority risks` first
|
|
8
|
-
- Use the
|
|
9
|
-
- Treat `Invariants to
|
|
7
|
+
- Read `Priority risks` first. They summarize the shared 721-hook risks with the widest blast radius.
|
|
8
|
+
- Use the later sections for reentrancy, gas, tier accounting, and integration reasoning.
|
|
9
|
+
- Treat `Invariants to verify` as required coverage for any hook or store change.
|
|
10
10
|
|
|
11
|
-
## Priority
|
|
11
|
+
## Priority Risks
|
|
12
12
|
|
|
13
13
|
| Priority | Risk | Why it matters | Primary controls |
|
|
14
14
|
|----------|------|----------------|------------------|
|
|
15
|
-
| P0 | Shared store corruption or accounting drift | `JB721TiersHookStore` is reused across products
|
|
16
|
-
| P1 | Gas and iteration ceilings around tier state | Tier operations can iterate over reserves, pricing state, and cash-out weights
|
|
17
|
-
| P1 | Cash-out and reserve math mismatch | Fair redemption depends on tier supply, pending reserves, and pricing state staying aligned. | Detailed invariants, fuzzing, and integration tests
|
|
18
|
-
|
|
15
|
+
| P0 | Shared store corruption or accounting drift | `JB721TiersHookStore` is reused across products. A tier-accounting bug can affect many repos at once. | Heavy store testing, invariants, and cautious deployment review. |
|
|
16
|
+
| P1 | Gas and iteration ceilings around tier state | Tier operations can iterate over reserves, pricing state, and cash-out weights. | Gas tests, tier-count limits, and DoS review. |
|
|
17
|
+
| P1 | Cash-out and reserve math mismatch | Fair redemption depends on tier supply, pending reserves, and pricing state staying aligned. | Detailed invariants, fuzzing, and integration tests. |
|
|
19
18
|
|
|
20
19
|
## 1. Trust Assumptions
|
|
21
20
|
|
|
22
|
-
- **
|
|
23
|
-
- **Tier configuration is
|
|
24
|
-
- **Category
|
|
25
|
-
- **`
|
|
26
|
-
- **Clone initialization is one-shot
|
|
27
|
-
-
|
|
28
|
-
- **`tokenURI` reverts for nonexistent tokens.** Calling `tokenURI` with a token ID that has never been minted reverts with `ERC721NonexistentToken(tokenId)`. The check is `_ownerOf(tokenId) == address(0)`.
|
|
29
|
-
- **JBDirectory is trusted for terminal authentication.** `afterPayRecordedWith` and `afterCashOutRecordedWith` check `DIRECTORY.isTerminalOf()`. If the directory is compromised, arbitrary addresses can invoke pay/cashout hooks.
|
|
30
|
-
- **JBPrices is trusted for cross-currency conversion.** A reverting price feed blocks all payments in non-matching currencies (DoS, not fund loss). If `address(prices) == address(0)`, cross-currency payments silently skip minting.
|
|
21
|
+
- **The store is trusted.** It keys state by `msg.sender`, so a hook can only affect its own namespace, but that namespace is fully trusted.
|
|
22
|
+
- **Tier configuration is partly immutable.** Price, supply, reserve frequency, category, voting units, and split percent are permanent after creation.
|
|
23
|
+
- **Category ordering matters.** The store's linked-list assumptions depend on correct sorted insertion.
|
|
24
|
+
- **`useReserveBeneficiaryAsDefault` has wide effects.** Setting it on a new tier can change the default reserve beneficiary for older tiers without their own explicit beneficiary.
|
|
25
|
+
- **Clone initialization is one-shot.** Clones are deployed and initialized atomically.
|
|
26
|
+
- **Directory and prices are trusted.** Terminal authentication and cross-currency behavior depend on core.
|
|
31
27
|
|
|
32
28
|
## 2. Economic Risks
|
|
33
29
|
|
|
34
|
-
- **Cash
|
|
35
|
-
- **Pending reserves inflate the
|
|
36
|
-
- **Pay credits accumulate without cap.**
|
|
37
|
-
- **Zero-price tiers are valid.**
|
|
38
|
-
- **Discount denominator
|
|
39
|
-
- **Currency mismatch silently
|
|
40
|
-
- **`splitPercent`
|
|
41
|
-
- **
|
|
42
|
-
- **Cross-reference: `PRICES == address(0)` behavior.** When `address(prices) == address(0)`, cross-currency payments skip minting silently (see Trust Assumptions section 1 and Accepted Behaviors section 8.3). This is the same address stored during `initialize()` — clones that omit the prices parameter get `address(0)` permanently.
|
|
30
|
+
- **Cash-out weight uses full undiscounted price.**
|
|
31
|
+
- **Pending reserves inflate the cash-out denominator before reserves are minted.**
|
|
32
|
+
- **Pay credits can accumulate without a cap.**
|
|
33
|
+
- **Zero-price tiers are valid.**
|
|
34
|
+
- **Discount math uses a denominator of 200, not 10,000.**
|
|
35
|
+
- **Currency mismatch can silently skip minting when no prices contract is configured.**
|
|
36
|
+
- **`splitPercent` can reduce fungible-token minting weight.**
|
|
37
|
+
- **Reserve minting is permissionless.**
|
|
43
38
|
|
|
44
39
|
## 3. Reentrancy Surface
|
|
45
40
|
|
|
46
|
-
- **Split hook callbacks
|
|
47
|
-
- **Split beneficiary ETH sends
|
|
48
|
-
- **Terminal
|
|
49
|
-
- **Split fallback can still strand value if the project terminal rejects leftovers.**
|
|
50
|
-
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
- **`
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
catalogs as an explicit gas-budgeting exercise, not as a default deployment shape.
|
|
61
|
-
- **`tiersOf` traverses removed tiers.** Removed tiers are skipped via bitmap but still traversed in the linked list. `cleanTiers()` must be called separately to compact. `cleanTiers()` is permissionless and idempotent.
|
|
62
|
-
- **Minting from many tiers in one payment.** `recordMint` loops per tier ID: storage read (stored tier + bitmap check) per iteration. 50 tiers in one payment ~5-7M gas (tested, fits in 30M block). 100+ tiers in a single mint is feasible but consumes most of the block.
|
|
63
|
-
- **`recordAddTiers` sort-insertion cost.** Adding a low-category tier to a hook with many existing higher-category tiers iterates the entire sorted list to find the insertion point. O(n) per added tier.
|
|
64
|
-
- **Reserve minting is unbounded per call.** `mintPendingReservesFor(tierId, count)` mints `count` NFTs in a loop. Large `count` could exceed block gas. Callers should batch.
|
|
65
|
-
- **Max tiers capped at `uint16.max` (65,535).** Store enforces this ceiling. Practical gas limits make 1,000+ tiers problematic for on-chain reads.
|
|
66
|
-
- **200+ tiers tested.** `TestAuditGaps_GasLimits` adds 200 tiers and verifies store correctness and gas within 30M block limit.
|
|
41
|
+
- **Split hook callbacks execute arbitrary code.**
|
|
42
|
+
- **Split beneficiary ETH sends can fail softly and reroute value.**
|
|
43
|
+
- **Terminal `pay` and `addToBalanceOf` calls during split distribution can reenter external systems.**
|
|
44
|
+
- **Split fallback can still strand value if the project terminal rejects leftovers.**
|
|
45
|
+
- **There is no `ReentrancyGuard`.** Safety depends on state ordering, terminal auth, and wrapped external calls.
|
|
46
|
+
|
|
47
|
+
## 4. Gas And DoS Vectors
|
|
48
|
+
|
|
49
|
+
- **`totalCashOutWeight` iterates all tier IDs.**
|
|
50
|
+
- **`balanceOf`, `votingUnitsOf`, and `totalSupplyOf` also iterate all tiers.**
|
|
51
|
+
- **Large tier catalogs are technically allowed but not the supported operating shape.**
|
|
52
|
+
- **`tiersOf` still traverses removed tiers until cleanup runs.**
|
|
53
|
+
- **Minting across many tiers in one payment can get expensive fast.**
|
|
54
|
+
- **Reserve minting is loop-based and should be batched when large.**
|
|
67
55
|
|
|
68
56
|
## 5. Access Control
|
|
69
57
|
|
|
70
|
-
- **`adjustTiers`
|
|
71
|
-
- **`mintFor`
|
|
72
|
-
- **`setDiscountPercentOf
|
|
73
|
-
- **`setMetadata
|
|
74
|
-
- **Transfer pause
|
|
75
|
-
- **`mintPendingReservesFor
|
|
76
|
-
- **`cleanTiers`:** Permissionless, idempotent. Compacts the sorted tier list by removing gaps from deleted tiers. No economic impact.
|
|
77
|
-
- **Store `recordFlags`:** No access control -- stores against `msg.sender`. Safe because the store keys by caller address, but a compromised hook can freely change its own flags.
|
|
58
|
+
- **`adjustTiers` is permissioned and respects append-only restrictions.**
|
|
59
|
+
- **`mintFor` is permissioned and still depends on per-tier owner-mint flags.**
|
|
60
|
+
- **`setDiscountPercentOf` is permissioned and can be one-way constrained.**
|
|
61
|
+
- **`setMetadata` is permissioned and changes name, symbol, URIs, resolver, and tier URIs.**
|
|
62
|
+
- **Transfer pause is tier-sensitive.**
|
|
63
|
+
- **`mintPendingReservesFor` and `cleanTiers` are permissionless by design.**
|
|
78
64
|
|
|
79
65
|
## 6. Integration Risks
|
|
80
66
|
|
|
81
|
-
- **
|
|
82
|
-
- **Metadata encoding is fragile.**
|
|
83
|
-
- **`beforeCashOutRecordedWith` rejects fungible
|
|
84
|
-
- **Split group
|
|
85
|
-
- **ERC-20 split distribution
|
|
86
|
-
- **Forwarded funds
|
|
87
|
-
- **
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
98
|
-
-
|
|
99
|
-
- **`maxTierIdOf` monotonically increases:** Tier removal marks a bitmap, does not decrement `maxTierIdOf`.
|
|
100
|
-
- **Balance consistency:** `sum(tierBalanceOf[hook][owner][tierId])` across all tiers equals `ERC721._balances[owner]` for each owner.
|
|
101
|
-
- **Cash out weight uses full price regardless of discount:** `cashOutWeightOf` for any token returns the tier's stored `price`, not the discounted purchase price.
|
|
102
|
-
- **Discount monotonicity when locked:** If `flags.cantIncreaseDiscountPercent` is set, `discountPercent` can only decrease or stay the same.
|
|
103
|
-
- **Flags are append-only restrictions:** `noNewTiersWithReserves`, `noNewTiersWithVotes`, `noNewTiersWithOwnerMinting` prevent future tiers from using those features but do not retroactively affect existing tiers.
|
|
67
|
+
- **Hook weight can override fungible-token minting.**
|
|
68
|
+
- **Metadata encoding is fragile.**
|
|
69
|
+
- **`beforeCashOutRecordedWith` rejects mixed fungible-token cash outs.**
|
|
70
|
+
- **Split group IDs are tightly coupled to the hook address.**
|
|
71
|
+
- **ERC-20 split distribution depends on terminal allowance behavior.**
|
|
72
|
+
- **Forwarded funds with empty hook metadata can skip distribution and remain in the hook.**
|
|
73
|
+
- **Token URI resolver calls can block metadata reads if the resolver reverts.**
|
|
74
|
+
|
|
75
|
+
## 7. Invariants To Verify
|
|
76
|
+
|
|
77
|
+
- per-tier supply conservation holds
|
|
78
|
+
- total cash-out weight stays consistent with outstanding NFTs and pending reserves
|
|
79
|
+
- reserve minting stays bounded by reserve frequency
|
|
80
|
+
- token IDs remain unique
|
|
81
|
+
- credits track leftovers correctly
|
|
82
|
+
- removed tiers stay excluded from active listings
|
|
83
|
+
- store balance views match ERC-721 balances
|
|
84
|
+
- discount monotonicity is enforced when locked
|
|
104
85
|
|
|
105
86
|
## 8. Accepted Behaviors
|
|
106
87
|
|
|
107
|
-
### 8.1 Pending reserves
|
|
88
|
+
### 8.1 Pending reserves dilute cash-out value before minting
|
|
108
89
|
|
|
109
|
-
|
|
90
|
+
This is intentional. Including pending reserves in the denominator prevents reserve front-running.
|
|
110
91
|
|
|
111
|
-
### 8.2 Cash-out weight uses full price regardless of discount
|
|
92
|
+
### 8.2 Cash-out weight uses full price regardless of discount
|
|
112
93
|
|
|
113
|
-
|
|
94
|
+
This is intentional. The cash-out weight represents treasury share, not purchase price.
|
|
114
95
|
|
|
115
|
-
### 8.3 Currency mismatch
|
|
96
|
+
### 8.3 Currency mismatch can skip minting when no prices surface exists
|
|
116
97
|
|
|
117
|
-
If
|
|
98
|
+
If currencies differ and `PRICES == address(0)`, payments can increase project balance without minting NFTs. That is an accepted degradation rather than a revert path.
|
|
118
99
|
|
|
119
|
-
### 8.4 Tiny split allocations can round down to zero
|
|
100
|
+
### 8.4 Tiny split allocations can round down to zero
|
|
120
101
|
|
|
121
|
-
|
|
102
|
+
Dust-sized split allocations can become economically lossy after rounding.
|
|
122
103
|
|
|
123
104
|
### 8.5 Failed split payouts only degrade cleanly if the fallback terminal path works
|
|
124
105
|
|
|
125
|
-
|
|
106
|
+
If both the primary split path and the fallback `addToBalanceOf` path fail, the hook can retain assets with no built-in rescue path.
|
|
107
|
+
|
|
108
|
+
### 8.6 Credit-funded tier purchases may underfund split obligations
|
|
109
|
+
|
|
110
|
+
Pay credits can be used to buy tiers that carry a `splitPercent`. When credits satisfy part of the tier price, the fresh ETH forwarded to splits may be less than the split obligation implied by the full tier price. Project owners who consider this a problem should enable the `preventBuyingTierWithCredits` flag on affected tiers. This is accepted behavior.
|
|
111
|
+
|
|
112
|
+
### 8.7 Changing the default reserve beneficiary redirects pending reserves
|
|
113
|
+
|
|
114
|
+
When the default reserve beneficiary is updated, any pending (unminted) reserves across all tiers that rely on the default will be distributed to the new beneficiary once minted. This is by design — the project owner controls reserve distribution targets and may intentionally redirect pending reserves by updating the default.
|
|
115
|
+
|
|
116
|
+
### 8.8 Discounted credit mints retain full cash-out weight
|
|
117
|
+
|
|
118
|
+
Tokens minted at a discounted price via credits still carry the full undiscounted tier price as their cash-out weight. This means a holder who purchased at a discount receives the same treasury share as a holder who paid full price. Project owners should factor this into discount percentage decisions, as aggressive discounts can create favorable cash-out economics for discounted buyers.
|
package/SKILLS.md
CHANGED
|
@@ -2,47 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
## Use This File For
|
|
4
4
|
|
|
5
|
-
- Use this file when the task involves tiered NFT
|
|
6
|
-
- Start here, then decide whether the
|
|
5
|
+
- Use this file when the task involves tiered NFT minting, reserve minting, tier adjustments, deployer wiring, or 721-aware cash-out behavior.
|
|
6
|
+
- Start here, then decide whether the issue is in hook execution, store accounting, deployer setup, or resolver behavior.
|
|
7
7
|
|
|
8
8
|
## Read This Next
|
|
9
9
|
|
|
10
10
|
| If you need... | Open this next |
|
|
11
11
|
|---|---|
|
|
12
|
-
| Repo overview and
|
|
13
|
-
| Runtime hook behavior | [`src/JB721TiersHook.sol`](./src/JB721TiersHook.sol)
|
|
14
|
-
| Tier
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
| Reentrancy, forks, and pinned edge cases | [`test/TestSafeTransferReentrancy.t.sol`](./test/TestSafeTransferReentrancy.t.sol), [`test/721HookAttacks.t.sol`](./test/721HookAttacks.t.sol), [`test/Fork.t.sol`](./test/Fork.t.sol), [`test/TestAuditGaps.sol`](./test/TestAuditGaps.sol) |
|
|
12
|
+
| Repo overview and architecture | [`README.md`](./README.md), [`ARCHITECTURE.md`](./ARCHITECTURE.md) |
|
|
13
|
+
| Runtime hook behavior | [`src/JB721TiersHook.sol`](./src/JB721TiersHook.sol) |
|
|
14
|
+
| Tier accounting | [`src/JB721TiersHookStore.sol`](./src/JB721TiersHookStore.sol) |
|
|
15
|
+
| Shared math and metadata helpers | [`src/libraries/`](./src/libraries/), [`src/interfaces/`](./src/interfaces/), [`src/structs/`](./src/structs/) |
|
|
16
|
+
| Deployer flows | [`src/JB721TiersHookDeployer.sol`](./src/JB721TiersHookDeployer.sol), [`src/JB721TiersHookProjectDeployer.sol`](./src/JB721TiersHookProjectDeployer.sol) |
|
|
17
|
+
| Lifecycle, invariant, and audit coverage | [`test/E2E/Pay_Mint_Redeem_E2E.t.sol`](./test/E2E/Pay_Mint_Redeem_E2E.t.sol), [`test/invariants/TierLifecycleInvariant.t.sol`](./test/invariants/TierLifecycleInvariant.t.sol), [`test/invariants/TieredHookStoreInvariant.t.sol`](./test/invariants/TieredHookStoreInvariant.t.sol), [`test/audit/CodexSplitCreditsMismatch.t.sol`](./test/audit/CodexSplitCreditsMismatch.t.sol) |
|
|
19
18
|
|
|
20
19
|
## Repo Map
|
|
21
20
|
|
|
22
21
|
| Area | Where to look |
|
|
23
22
|
|---|---|
|
|
24
23
|
| Main contracts | [`src/`](./src/) |
|
|
25
|
-
|
|
|
24
|
+
| Libraries, interfaces, and structs | [`src/libraries/`](./src/libraries/), [`src/interfaces/`](./src/interfaces/), [`src/structs/`](./src/structs/) |
|
|
26
25
|
| Scripts | [`script/`](./script/) |
|
|
27
26
|
| Tests | [`test/`](./test/) |
|
|
28
27
|
|
|
29
28
|
## Purpose
|
|
30
29
|
|
|
31
|
-
Tiered
|
|
30
|
+
Tiered NFT hook for Juicebox V6. This repo handles priced NFT tiers, reserve logic, tier-aware cash outs, and deployer flows that wire those hooks into projects.
|
|
32
31
|
|
|
33
32
|
## Reference Files
|
|
34
33
|
|
|
35
|
-
- Open [`references/runtime.md`](./references/runtime.md) when you need
|
|
36
|
-
- Open [`references/operations.md`](./references/operations.md) when you need
|
|
34
|
+
- Open [`references/runtime.md`](./references/runtime.md) when you need hook and store roles, cash-out weight behavior, or main invariants.
|
|
35
|
+
- Open [`references/operations.md`](./references/operations.md) when you need permission paths, tier-adjustment rules, test breadcrumbs, or common stale assumptions.
|
|
37
36
|
|
|
38
37
|
## Working Rules
|
|
39
38
|
|
|
40
|
-
- Start in [`src/
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
- Discounted mint price and cash-out weight are intentionally not the same thing. Free or discounted mints can still carry full tier cash-out weight by design.
|
|
46
|
-
- Changing the default reserve beneficiary is not cosmetic. It can change which tiers have pending reserves and therefore change redemption economics for existing mints.
|
|
47
|
-
- When a task mentions token metadata or rendering, confirm whether the behavior lives in this repo or in an external resolver. Do not over-edit the hook when the real change belongs downstream.
|
|
48
|
-
- When changing deployers or initialization, verify the hook, store, and project-launch path stay aligned. These flows are tightly coupled.
|
|
39
|
+
- Start in [`src/JB721TiersHookStore.sol`](./src/JB721TiersHookStore.sol) for tier accounting questions.
|
|
40
|
+
- Keep hook behavior, store behavior, and resolver behavior separate.
|
|
41
|
+
- Reserve logic, split routing, and cash-out weight calculations are part of the economic surface.
|
|
42
|
+
- Tier adjustments are high-risk because many tier properties are intentionally immutable after creation.
|
|
43
|
+
- If a problem looks like metadata only, verify it is not actually a hook or store issue first.
|
package/USER_JOURNEYS.md
CHANGED
|
@@ -2,192 +2,120 @@
|
|
|
2
2
|
|
|
3
3
|
## Repo Purpose
|
|
4
4
|
|
|
5
|
-
This repo
|
|
6
|
-
It owns tier issuance, reserve accounting, hook-aware mint and cash-out behavior, and deployer packaging for hook
|
|
7
|
-
clones or hook-shaped project launches. It does not own collection-specific rendering or app-layer policy built on top
|
|
8
|
-
of the hook.
|
|
5
|
+
This repo adds tiered NFT logic to Juicebox payment and cash-out flows. It owns tier pricing, reserves, and NFT lifecycle state. It does not own project-specific artwork or game logic.
|
|
9
6
|
|
|
10
7
|
## Primary Actors
|
|
11
8
|
|
|
12
|
-
-
|
|
13
|
-
- operators managing tier
|
|
14
|
-
-
|
|
15
|
-
-
|
|
9
|
+
- projects that want priced NFT tiers in their Juicebox flow
|
|
10
|
+
- operators managing tier configuration and hook permissions
|
|
11
|
+
- holders minting, transferring, and cashing out tiered NFTs
|
|
12
|
+
- auditors reviewing tier accounting, reserve behavior, and deployer wiring
|
|
16
13
|
|
|
17
14
|
## Key Surfaces
|
|
18
15
|
|
|
19
|
-
- `JB721TiersHook`:
|
|
20
|
-
- `JB721TiersHookStore`: tier
|
|
21
|
-
- `JB721TiersHookDeployer
|
|
22
|
-
-
|
|
16
|
+
- `JB721TiersHook`: runtime 721 hook behavior
|
|
17
|
+
- `JB721TiersHookStore`: tier accounting and supply state
|
|
18
|
+
- `JB721TiersHookDeployer` and `JB721TiersHookProjectDeployer`: wiring surfaces
|
|
19
|
+
- token URI resolver contracts in downstream repos: presentation layer
|
|
23
20
|
|
|
24
|
-
## Journey 1:
|
|
21
|
+
## Journey 1: Launch A Project With A Tiered NFT Hook
|
|
25
22
|
|
|
26
|
-
**Actor:** project
|
|
23
|
+
**Actor:** project operator or deployer.
|
|
27
24
|
|
|
28
|
-
**Intent:** attach tiered NFT
|
|
25
|
+
**Intent:** attach tiered NFT issuance to a project from the start.
|
|
29
26
|
|
|
30
27
|
**Preconditions**
|
|
31
|
-
- the project
|
|
32
|
-
- the
|
|
33
|
-
- the next ruleset metadata can be updated safely
|
|
28
|
+
- the project knows its tier structure and hook expectations
|
|
29
|
+
- the deployer path matches whether the project already exists
|
|
34
30
|
|
|
35
31
|
**Main Flow**
|
|
36
|
-
1.
|
|
37
|
-
2.
|
|
38
|
-
3.
|
|
39
|
-
4. Future payments can now mint tiers under the configured constraints.
|
|
32
|
+
1. Deploy a hook clone or launch a project with the hook already attached.
|
|
33
|
+
2. Configure tier data, hook metadata, and resolver expectations.
|
|
34
|
+
3. Transfer hook ownership into the intended project control surface.
|
|
40
35
|
|
|
41
36
|
**Failure Modes**
|
|
42
|
-
-
|
|
43
|
-
-
|
|
37
|
+
- wrong hook wiring at launch
|
|
38
|
+
- wrong resolver assumptions
|
|
39
|
+
- teams treat deployer convenience as proof that runtime economics are correct
|
|
44
40
|
|
|
45
41
|
**Postconditions**
|
|
46
|
-
- the project has
|
|
42
|
+
- the project has a tiered NFT hook wired into its Juicebox flow
|
|
47
43
|
|
|
48
|
-
## Journey 2:
|
|
44
|
+
## Journey 2: Pay And Mint Tiered NFTs
|
|
49
45
|
|
|
50
|
-
**Actor:**
|
|
46
|
+
**Actor:** payer or integration acting for a payer.
|
|
51
47
|
|
|
52
|
-
**Intent:**
|
|
48
|
+
**Intent:** mint NFTs from configured tiers while preserving the project's terminal flow.
|
|
53
49
|
|
|
54
50
|
**Preconditions**
|
|
55
|
-
- the
|
|
51
|
+
- the project has active tiers
|
|
52
|
+
- payment metadata correctly names the intended tiers
|
|
56
53
|
|
|
57
54
|
**Main Flow**
|
|
58
|
-
1.
|
|
59
|
-
2.
|
|
60
|
-
3.
|
|
61
|
-
4. Start life as a hook-backed project instead of converting later.
|
|
55
|
+
1. A payment reaches the hook through the terminal.
|
|
56
|
+
2. The hook decodes tier selection and records mint state in the store.
|
|
57
|
+
3. NFTs mint, reserve implications update, and any split routing is applied.
|
|
62
58
|
|
|
63
59
|
**Failure Modes**
|
|
64
|
-
-
|
|
65
|
-
-
|
|
60
|
+
- malformed metadata
|
|
61
|
+
- currency mismatch or missing pricing support
|
|
62
|
+
- splits or discounts behave differently than the integration expected
|
|
66
63
|
|
|
67
64
|
**Postconditions**
|
|
68
|
-
- the
|
|
65
|
+
- the payer or beneficiary receives the intended NFT tiers and tier state updates
|
|
69
66
|
|
|
70
|
-
## Journey 3: Mint
|
|
67
|
+
## Journey 3: Mint Or Release Reserve NFTs
|
|
71
68
|
|
|
72
|
-
**Actor:**
|
|
69
|
+
**Actor:** reserve beneficiary, operator, or any caller using the reserve path.
|
|
73
70
|
|
|
74
|
-
**Intent:**
|
|
71
|
+
**Intent:** realize pending reserves under the configured reserve rules.
|
|
75
72
|
|
|
76
73
|
**Preconditions**
|
|
77
|
-
- the
|
|
78
|
-
- the
|
|
74
|
+
- the relevant tiers have reserve logic enabled
|
|
75
|
+
- the ruleset does not pause pending reserve minting
|
|
79
76
|
|
|
80
77
|
**Main Flow**
|
|
81
|
-
1.
|
|
82
|
-
2.
|
|
83
|
-
3.
|
|
84
|
-
4. The hook mints the intended NFTs and the terminal completes treasury accounting.
|
|
78
|
+
1. Eligible reserve amounts accumulate as mint activity happens.
|
|
79
|
+
2. A caller triggers reserve minting for pending tiers.
|
|
80
|
+
3. The store moves reserve state forward and NFTs mint to the configured reserve beneficiary.
|
|
85
81
|
|
|
86
82
|
**Failure Modes**
|
|
87
|
-
-
|
|
88
|
-
-
|
|
89
|
-
- split-routing or hook behavior changes what part of the payment actually mints
|
|
83
|
+
- teams misunderstand that reserve minting timing is not owner-exclusive
|
|
84
|
+
- reserve assumptions drift from actual tier settings
|
|
90
85
|
|
|
91
86
|
**Postconditions**
|
|
92
|
-
-
|
|
87
|
+
- pending reserves mint according to tier configuration
|
|
93
88
|
|
|
94
|
-
## Journey 4:
|
|
89
|
+
## Journey 4: Cash Out Tiered NFTs
|
|
95
90
|
|
|
96
|
-
**Actor:**
|
|
91
|
+
**Actor:** NFT holder.
|
|
97
92
|
|
|
98
|
-
**Intent:**
|
|
93
|
+
**Intent:** redeem tiered NFT exposure through the terminal cash-out path.
|
|
99
94
|
|
|
100
95
|
**Preconditions**
|
|
101
|
-
- the
|
|
102
|
-
- the
|
|
96
|
+
- the holder owns valid NFTs
|
|
97
|
+
- the hook is active for the cash-out path
|
|
103
98
|
|
|
104
99
|
**Main Flow**
|
|
105
|
-
1.
|
|
106
|
-
2.
|
|
107
|
-
3.
|
|
108
|
-
4. Keep downstream products assuming stable tier semantics whenever possible.
|
|
100
|
+
1. The holder requests a cash out with NFT-specific metadata.
|
|
101
|
+
2. The hook burns the selected NFTs and records the burn in the store.
|
|
102
|
+
3. The terminal completes reclaim logic using the hook-aware cash-out surface.
|
|
109
103
|
|
|
110
104
|
**Failure Modes**
|
|
111
|
-
-
|
|
112
|
-
-
|
|
105
|
+
- integrations mix fungible-token and NFT cash-out assumptions
|
|
106
|
+
- pending reserves or discounts are misunderstood in value calculations
|
|
107
|
+
- token IDs are invalid or already burned
|
|
113
108
|
|
|
114
109
|
**Postconditions**
|
|
115
|
-
-
|
|
116
|
-
|
|
117
|
-
## Journey 5: Let Holders Cash Out Tier Positions
|
|
118
|
-
|
|
119
|
-
**Actor:** holder.
|
|
120
|
-
|
|
121
|
-
**Intent:** exit a tier position through the project's cash-out path.
|
|
122
|
-
|
|
123
|
-
**Preconditions**
|
|
124
|
-
- the holder owns one or more NFTs from the hook-enabled project
|
|
125
|
-
- the active ruleset allows a surplus-backed exit
|
|
126
|
-
|
|
127
|
-
**Main Flow**
|
|
128
|
-
1. Call the project's cash-out path on the terminal.
|
|
129
|
-
2. Let the hook participate in cash-out calculation so tier state is reflected.
|
|
130
|
-
3. Burn or consume the tier exposure as required by the exit path.
|
|
131
|
-
4. Receive the reclaim value through the terminal that holds the asset.
|
|
132
|
-
|
|
133
|
-
**Failure Modes**
|
|
134
|
-
- the project uses the hook for metadata only and the holder assumes an economic cash-out path exists
|
|
135
|
-
- terminal behavior or reserve drift changes reclaim expectations
|
|
136
|
-
|
|
137
|
-
**Postconditions**
|
|
138
|
-
- the holder exits the tier position through the hook-aware terminal path or learns that no such economic path is active
|
|
139
|
-
|
|
140
|
-
## Journey 6: Compose A Custom Product On Top Of The Standard Hook
|
|
141
|
-
|
|
142
|
-
**Actor:** integrator or downstream product team.
|
|
143
|
-
|
|
144
|
-
**Intent:** build collection-specific behavior without reimplementing hook economics.
|
|
145
|
-
|
|
146
|
-
**Preconditions**
|
|
147
|
-
- the team wants custom presentation or app-layer logic
|
|
148
|
-
- the team does not want to fork pricing, reserve, and treasury behavior
|
|
149
|
-
|
|
150
|
-
**Main Flow**
|
|
151
|
-
1. Plug a custom resolver or wrapper into the hook.
|
|
152
|
-
2. Keep collection-specific behavior outside this repo.
|
|
153
|
-
3. Audit hook-store interactions here first, then audit the downstream wrapper.
|
|
154
|
-
|
|
155
|
-
**Failure Modes**
|
|
156
|
-
- downstream products reimplement hook behavior and drift from canonical accounting
|
|
157
|
-
- teams blame the hook for bugs that actually live in the resolver or wrapper
|
|
158
|
-
|
|
159
|
-
**Postconditions**
|
|
160
|
-
- the custom product reuses canonical hook economics while isolating collection-specific behavior downstream
|
|
161
|
-
|
|
162
|
-
## Journey 7: Mint NFTs To The Correct Beneficiary During Cross-Chain Payments
|
|
163
|
-
|
|
164
|
-
**Actor:** cross-chain payer or integrator.
|
|
165
|
-
|
|
166
|
-
**Intent:** preserve the real remote beneficiary when a sucker relays a payment.
|
|
167
|
-
|
|
168
|
-
**Preconditions**
|
|
169
|
-
- a sucker or relay path pays on behalf of a remote user
|
|
170
|
-
- relay-beneficiary metadata is encoded correctly
|
|
171
|
-
|
|
172
|
-
**Main Flow**
|
|
173
|
-
1. The sucker calls `terminal.pay()` with relay-beneficiary metadata.
|
|
174
|
-
2. `_mintAndUpdateCredits` resolves the relay beneficiary when `payer == beneficiary`.
|
|
175
|
-
3. NFT minting and credit accounting use the resolved remote user.
|
|
176
|
-
|
|
177
|
-
**Failure Modes**
|
|
178
|
-
- relay metadata is missing or malformed
|
|
179
|
-
- downstream systems attribute NFTs or credits to the sucker instead of the user
|
|
180
|
-
|
|
181
|
-
**Postconditions**
|
|
182
|
-
- NFT minting and credit accounting attribute the remote payment to the correct beneficiary
|
|
110
|
+
- NFTs burn and reclaim value follows the intended tier model
|
|
183
111
|
|
|
184
112
|
## Trust Boundaries
|
|
185
113
|
|
|
186
|
-
-
|
|
187
|
-
-
|
|
188
|
-
-
|
|
114
|
+
- this repo trusts core terminals, directory checks, and pricing surfaces from `nana-core-v6`
|
|
115
|
+
- metadata resolvers are outside this repo but still affect user-visible trust
|
|
116
|
+
- the store is the main source of truth for tier lifecycle state
|
|
189
117
|
|
|
190
118
|
## Hand-Offs
|
|
191
119
|
|
|
192
|
-
- Use [nana-core-v6](../nana-core-v6/USER_JOURNEYS.md) for the
|
|
193
|
-
- Use
|
|
120
|
+
- Use [nana-core-v6](../nana-core-v6/USER_JOURNEYS.md) for the underlying terminal and accounting behavior.
|
|
121
|
+
- Use the downstream resolver repo when the question is about project-specific metadata or rendering.
|
package/package.json
CHANGED
|
@@ -37,6 +37,8 @@ contract JB721CheckpointsDeployer is IJB721CheckpointsDeployer {
|
|
|
37
37
|
/// @param store The store that holds tier data for the hook's NFTs.
|
|
38
38
|
/// @return module The newly deployed and initialized checkpoint module.
|
|
39
39
|
function deploy(address hook, IJB721TiersHookStore store) external override returns (IJB721Checkpoints module) {
|
|
40
|
+
if (msg.sender != hook) revert JB721CheckpointsDeployer_Unauthorized();
|
|
41
|
+
|
|
40
42
|
module = IJB721Checkpoints(
|
|
41
43
|
LibClone.cloneDeterministic({implementation: IMPLEMENTATION, salt: bytes32(uint256(uint160(hook)))})
|
|
42
44
|
);
|