@bananapus/721-hook-v6 0.0.35 → 0.0.36
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 +62 -164
- package/ARCHITECTURE.md +59 -44
- package/AUDIT_INSTRUCTIONS.md +35 -32
- package/README.md +22 -3
- package/RISKS.md +7 -1
- package/SKILLS.md +8 -2
- package/USER_JOURNEYS.md +144 -49
- package/foundry.toml +2 -0
- package/package.json +1 -1
- package/references/operations.md +7 -3
- package/references/runtime.md +5 -4
- package/src/JB721TiersHook.sol +0 -2
- package/src/JB721TiersHookProjectDeployer.sol +0 -1
- package/src/JB721TiersHookStore.sol +1 -2
- package/src/abstract/JB721Hook.sol +0 -1
- package/src/interfaces/IJB721TiersHook.sol +0 -2
- package/src/interfaces/IJB721TiersHookStore.sol +1 -1
- package/src/libraries/JB721Constants.sol +0 -1
- package/src/structs/JB721InitTiersConfig.sol +0 -1
- package/src/structs/JB721Tier.sol +0 -2
- package/src/structs/JB721TierConfig.sol +0 -2
- package/src/structs/JB721TierConfigFlags.sol +0 -1
- package/src/structs/JB721TierFlags.sol +0 -1
- package/src/structs/JB721TiersHookFlags.sol +0 -1
- package/src/structs/JB721TiersMintReservesConfig.sol +0 -1
- package/src/structs/JB721TiersRulesetMetadata.sol +0 -1
- package/src/structs/JB721TiersSetDiscountPercentConfig.sol +0 -1
- package/src/structs/JBBitmapWord.sol +0 -1
- package/src/structs/JBDeploy721TiersHookConfig.sol +0 -1
- package/src/structs/JBLaunchProjectConfig.sol +0 -1
- package/src/structs/JBLaunchRulesetsConfig.sol +0 -1
- package/src/structs/JBPayDataHookRulesetConfig.sol +0 -1
- package/src/structs/JBPayDataHookRulesetMetadata.sol +0 -1
- package/src/structs/JBQueueRulesetsConfig.sol +0 -1
- package/src/structs/JBStored721Tier.sol +0 -1
package/ADMINISTRATION.md
CHANGED
|
@@ -1,189 +1,87 @@
|
|
|
1
1
|
# Administration
|
|
2
2
|
|
|
3
|
-
Admin privileges and their scope in nana-721-hook-v6.
|
|
4
|
-
|
|
5
3
|
## At A Glance
|
|
6
4
|
|
|
7
5
|
| Item | Details |
|
|
8
|
-
|
|
9
|
-
| Scope | Per-
|
|
10
|
-
|
|
|
11
|
-
| Highest-risk actions |
|
|
12
|
-
| Recovery posture |
|
|
13
|
-
|
|
14
|
-
## Routine Operations
|
|
15
|
-
|
|
16
|
-
- Grant only the specific 721 permission IDs a project operator needs instead of handing out broad owner-equivalent access.
|
|
17
|
-
- Use tier adjustments, metadata updates, owner mints, and discount changes with awareness of the project's current sale state and ruleset flags.
|
|
18
|
-
- Treat deployer and clone initialization steps as setup-only actions; verify ownership, pricing, and store references before publishing a hook address to users.
|
|
19
|
-
- When cash-out behavior or pay-hook composition changes, coordinate that with the project's ruleset configuration rather than assuming the hook can pause itself.
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| Scope | Per-hook ownership, delegated 721 administration, and hook deployment flows |
|
|
8
|
+
| Control posture | Per-instance owner or project-owner control with delegated `JBPermissions` |
|
|
9
|
+
| Highest-risk actions | Tier adjustments, owner minting, metadata changes, and misassigned hook ownership |
|
|
10
|
+
| Recovery posture | Project-specific config can sometimes be superseded with new rulesets, but bad clone wiring usually means replacement hooks |
|
|
20
11
|
|
|
21
|
-
##
|
|
12
|
+
## Purpose
|
|
22
13
|
|
|
23
|
-
-
|
|
24
|
-
- Hook ownership transfers change who can exercise every `onlyOwner` surface; accidental ownership moves are high-impact.
|
|
25
|
-
- Tier and pricing changes can have user-facing economic effects immediately even when they are technically reversible for future sales.
|
|
14
|
+
`nana-721-hook-v6` is administered per hook instance. The effective admin is the hook owner resolved through `JBOwnable`, plus any operators granted specific `JBPermissions`. The dangerous surfaces are tier adjustment, metadata changes, owner minting, discount changes, and hook deployment ownership.
|
|
26
15
|
|
|
27
|
-
##
|
|
16
|
+
## Control Model
|
|
28
17
|
|
|
29
|
-
-
|
|
30
|
-
-
|
|
18
|
+
- Each hook instance has its own owner.
|
|
19
|
+
- Ownership can follow an EOA or a Juicebox project NFT through `JBOwnable`.
|
|
20
|
+
- Fine-grained operator delegation runs through `JBPermissions`.
|
|
21
|
+
- Deployers are permissionless for new hooks, but existing-project launch and queue flows are permission-gated.
|
|
22
|
+
- The store has no owner role; it trusts `msg.sender`-keyed namespaces.
|
|
31
23
|
|
|
32
24
|
## Roles
|
|
33
25
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
- **Assigned by**: The hook owner grants permissions via the `JBPermissions` contract.
|
|
43
|
-
- **Scope**: Per-project. Operators can be granted specific permission IDs scoped to the hook's `PROJECT_ID`.
|
|
44
|
-
- **How it works**: Each privileged function calls `_requirePermissionFrom(account: owner(), projectId: PROJECT_ID, permissionId: ...)`. This passes if the caller IS the owner, OR if the caller has been granted the specified permission ID by the owner for the project.
|
|
45
|
-
|
|
46
|
-
### Terminal (Protocol-Level Caller)
|
|
47
|
-
|
|
48
|
-
- **Assigned by**: The project's `JBDirectory` configuration.
|
|
49
|
-
- **Scope**: Only a contract registered as a terminal for the hook's project in `JBDirectory` can call `afterPayRecordedWith()` and `afterCashOutRecordedWith()`.
|
|
50
|
-
- **Verification**: `DIRECTORY.isTerminalOf(projectId, IJBTerminal(msg.sender))` is checked in `JB721Hook.sol`.
|
|
51
|
-
|
|
52
|
-
### Store Callers (msg.sender Trust Model)
|
|
53
|
-
|
|
54
|
-
- **Assigned by**: Implicit. `JB721TiersHookStore` trusts `msg.sender` as the hook contract.
|
|
55
|
-
- **Scope**: All `record*` functions in the store use `msg.sender` as the hook address key. Any contract can call the store, but state changes are scoped to `msg.sender`'s own data namespace.
|
|
56
|
-
- **Why this is safe**: Each hook clone has its own address, and the store keys all data by `[msg.sender][tierId]`. A malicious contract calling the store can only modify its own namespace.
|
|
57
|
-
|
|
58
|
-
## Privileged Functions
|
|
59
|
-
|
|
60
|
-
### JB721TiersHook
|
|
61
|
-
|
|
62
|
-
| Function | Permission ID | Checked Against | What It Does |
|
|
63
|
-
|----------|--------------|-----------------|--------------|
|
|
64
|
-
| `adjustTiers()` | `ADJUST_721_TIERS` | `owner()` | Adds new tiers and/or soft-removes existing tiers. Sets tier split groups in JBSplits. |
|
|
65
|
-
| `mintFor()` | `MINT_721` | `owner()` | Manually mints NFTs from tiers that have `flags.allowOwnerMint` enabled. Bypasses price checks (passes `type(uint256).max` as amount). |
|
|
66
|
-
| `setDiscountPercentOf()` | `SET_721_DISCOUNT_PERCENT` | `owner()` | Sets the discount percentage for a single tier. |
|
|
67
|
-
| `setDiscountPercentsOf()` | `SET_721_DISCOUNT_PERCENT` | `owner()` | Batch-sets discount percentages for multiple tiers. |
|
|
68
|
-
| `setMetadata()` | `SET_721_METADATA` | `owner()` | Updates collection name, symbol, baseURI, contractURI, tokenUriResolver, and/or per-tier encoded IPFS URIs. Empty strings leave values unchanged. |
|
|
69
|
-
| `initialize()` | None (one-time) | `_initialized` flag check | Initializes a cloned hook. Can only be called once. Transfers ownership to caller on completion. |
|
|
70
|
-
|
|
71
|
-
### JB721TiersHookProjectDeployer
|
|
72
|
-
|
|
73
|
-
| Function | Permission ID | Checked Against | What It Does |
|
|
74
|
-
|----------|--------------|-----------------|--------------|
|
|
75
|
-
| `launchProjectFor()` | None | Anyone can call | Creates a new project with a 721 hook. Ownership goes to the specified `owner` address. |
|
|
76
|
-
| `launchRulesetsFor()` | `QUEUE_RULESETS` + `SET_TERMINALS` | Project NFT owner or delegate | Deploys a hook and launches rulesets for an existing project. |
|
|
77
|
-
| `queueRulesetsOf()` | `QUEUE_RULESETS` | Project NFT owner or delegate | Deploys a hook and queues rulesets for an existing project. |
|
|
78
|
-
|
|
79
|
-
### JB721TiersHookDeployer
|
|
80
|
-
|
|
81
|
-
| Function | Permission ID | Checked Against | What It Does |
|
|
82
|
-
|----------|--------------|-----------------|--------------|
|
|
83
|
-
| `deployHookFor()` | None | Anyone can call | Clones and initializes a new hook instance. Ownership starts with the deployer contract, then is transferred to `msg.sender`. |
|
|
84
|
-
|
|
85
|
-
### JB721Hook (Abstract Base)
|
|
86
|
-
|
|
87
|
-
| Function | Required Caller | What It Does |
|
|
88
|
-
|----------|----------------|--------------|
|
|
89
|
-
| `afterPayRecordedWith()` | Project terminal | Processes payment, mints NFTs. Verifies caller via `DIRECTORY.isTerminalOf()`. |
|
|
90
|
-
| `afterCashOutRecordedWith()` | Project terminal | Burns NFTs on cash out. Verifies caller via `DIRECTORY.isTerminalOf()` and that `msg.value == 0`. |
|
|
91
|
-
|
|
92
|
-
### JB721TiersHookStore (No Access Control -- msg.sender Keyed)
|
|
93
|
-
|
|
94
|
-
| Function | Caller | What It Does |
|
|
95
|
-
|----------|--------|--------------|
|
|
96
|
-
| `recordAddTiers()` | Hook contract | Adds tiers to the caller's namespace. Category sort order enforced. |
|
|
97
|
-
| `recordRemoveTierIds()` | Hook contract | Marks tiers as removed in bitmap. Respects `flags.cantBeRemoved` flag. |
|
|
98
|
-
| `recordMint()` | Hook contract | Records mints, decrements supply, enforces price and reserve checks. |
|
|
99
|
-
| `recordMintReservesFor()` | Hook contract | Mints reserved NFTs from a tier. |
|
|
100
|
-
| `recordBurn()` | Hook contract | Increments burn counter for token IDs. |
|
|
101
|
-
| `recordFlags()` | Hook contract | Sets behavioral flags for the caller's hook. |
|
|
102
|
-
| `recordSetTokenUriResolver()` | Hook contract | Sets the token URI resolver. |
|
|
103
|
-
| `recordSetEncodedIPFSUriOf()` | Hook contract | Sets the encoded IPFS URI for a tier. |
|
|
104
|
-
| `recordSetDiscountPercentOf()` | Hook contract | Updates a tier's discount percent. Enforces bounds and `flags.cantIncreaseDiscountPercent`. |
|
|
105
|
-
| `recordTransferForTier()` | Hook contract | Updates per-tier balance tracking on transfer. |
|
|
106
|
-
| `cleanTiers()` | Anyone | Reorganizes the tier sorting linked list to skip removed tiers. Pure bookkeeping, no value at risk. |
|
|
107
|
-
|
|
108
|
-
## Permission System
|
|
109
|
-
|
|
110
|
-
Permissions flow through two mechanisms:
|
|
111
|
-
|
|
112
|
-
1. **JBOwnable** (`JB721TiersHook` inherits from it): The hook has a single `owner()` that can be an EOA or a Juicebox project. When owned by a project, the holder of that project's ERC-721 NFT is the effective owner.
|
|
26
|
+
| Role | How Assigned | Scope | Notes |
|
|
27
|
+
| --- | --- | --- | --- |
|
|
28
|
+
| Hook owner | `JBOwnable.owner()` | Per hook | May resolve dynamically through a project NFT |
|
|
29
|
+
| Hook operator | Granted by `JBPermissions` | Per project | Usually `ADJUST_721_TIERS`, `MINT_721`, `SET_721_METADATA`, `SET_721_DISCOUNT_PERCENT` |
|
|
30
|
+
| Project owner | `JBProjects.ownerOf(projectId)` | Per project | Relevant for project-deployer flows |
|
|
31
|
+
| Terminal | `JBDirectory` routing | Per project | Can call pay and cash-out hook entrypoints |
|
|
32
|
+
| Deployer caller | Anyone | Per deployment | Can deploy new standalone hooks |
|
|
113
33
|
|
|
114
|
-
|
|
34
|
+
## Privileged Surfaces
|
|
115
35
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
36
|
+
| Contract | Function | Who Can Call | Effect |
|
|
37
|
+
| --- | --- | --- | --- |
|
|
38
|
+
| `JB721TiersHook` | `adjustTiers(...)` | Owner or `ADJUST_721_TIERS` operator | Adds or removes tiers and updates split groups |
|
|
39
|
+
| `JB721TiersHook` | `mintFor(...)` | Owner or `MINT_721` operator | Owner mint path, subject to tier flags |
|
|
40
|
+
| `JB721TiersHook` | `setMetadata(...)` | Owner or `SET_721_METADATA` operator | Updates collection-level metadata and resolver references |
|
|
41
|
+
| `JB721TiersHook` | `setDiscountPercentOf(...)`, `setDiscountPercentsOf(...)` | Owner or `SET_721_DISCOUNT_PERCENT` operator | Changes discount settings where allowed |
|
|
42
|
+
| `JB721TiersHook` | `initialize(...)` | Anyone once per clone | One-time hook initialization and ownership setup |
|
|
43
|
+
| `JB721TiersHookProjectDeployer` | `launchRulesetsFor(...)` | Project owner or relevant delegates | Launches hook-backed rulesets for an existing project |
|
|
44
|
+
| `JB721TiersHookProjectDeployer` | `queueRulesetsOf(...)` | Project owner or `QUEUE_RULESETS` delegate | Queues hook-backed rulesets for an existing project |
|
|
119
45
|
|
|
120
|
-
|
|
46
|
+
## Immutable And One-Way
|
|
121
47
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
| `JBPermissionIds.SET_721_DISCOUNT_PERCENT` | `SET_721_DISCOUNT_PERCENT` | `setDiscountPercentOf()`, `setDiscountPercentsOf()` |
|
|
127
|
-
| `JBPermissionIds.SET_721_METADATA` | `SET_721_METADATA` | `setMetadata()` |
|
|
128
|
-
| `JBPermissionIds.QUEUE_RULESETS` | `QUEUE_RULESETS` | `launchRulesetsFor()`, `queueRulesetsOf()` |
|
|
129
|
-
| `JBPermissionIds.SET_TERMINALS` | `SET_TERMINALS` | `launchRulesetsFor()` |
|
|
48
|
+
- Implementation constructor dependencies are immutable.
|
|
49
|
+
- Clone initialization is one-time.
|
|
50
|
+
- Per-tier price, reserve frequency, and several flags are effectively set-once semantics.
|
|
51
|
+
- Ownership transfers change every permission check because privileged functions check against `owner()`.
|
|
130
52
|
|
|
131
|
-
##
|
|
53
|
+
## Operational Notes
|
|
132
54
|
|
|
133
|
-
|
|
55
|
+
- Grant narrow per-project permissions instead of owner-equivalent access where possible.
|
|
56
|
+
- Treat `adjustTiers(...)` as an economic change, not just content management.
|
|
57
|
+
- Verify hook ownership and pricing context before publishing a clone address.
|
|
58
|
+
- When this hook is wrapped by deployers, review hook-order assumptions as part of administration.
|
|
134
59
|
|
|
135
|
-
|
|
136
|
-
|----------|--------|-------|
|
|
137
|
-
| `DIRECTORY` | Constructor | Which terminal/controller directory is trusted |
|
|
138
|
-
| `PRICES` | Constructor | Which prices contract is used for cross-currency conversions |
|
|
139
|
-
| `RULESETS` | Constructor | Which rulesets contract is consulted |
|
|
140
|
-
| `STORE` | Constructor | Which store manages tier data |
|
|
141
|
-
| `SPLITS` | Constructor | Which splits contract manages tier split groups |
|
|
142
|
-
| `METADATA_ID_TARGET` | Constructor | The address used for metadata ID derivation (original implementation address for clones) |
|
|
143
|
-
| `PROJECT_ID` | `initialize()` | Which project this hook belongs to |
|
|
144
|
-
| Pricing context (currency, decimals) | `initialize()` | Packed into `_packedPricingContext` -- the token denomination for tier prices |
|
|
145
|
-
| `JB721TiersHookFlags` | `initialize()` | `noNewTiersWithReserves`, `noNewTiersWithVotes`, `noNewTiersWithOwnerMinting`, `preventOverspending`, `issueTokensForSplits` |
|
|
146
|
-
| Per-tier `flags.cantBeRemoved` | `recordAddTiers()` | Whether a tier can be soft-removed |
|
|
147
|
-
| Per-tier `flags.cantIncreaseDiscountPercent` | `recordAddTiers()` | Whether a tier's discount can be increased |
|
|
148
|
-
| Per-tier `reserveFrequency` | `recordAddTiers()` | How often reserve NFTs accrue |
|
|
149
|
-
| Per-tier `initialSupply` | `recordAddTiers()` | Maximum number of NFTs mintable from the tier |
|
|
150
|
-
| Per-tier `price` | `recordAddTiers()` | The base price (and cash-out weight) of NFTs in the tier |
|
|
151
|
-
| Per-tier `category` | `recordAddTiers()` | The category grouping for sort order |
|
|
60
|
+
## Machine Notes
|
|
152
61
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
62
|
+
- Do not assume `owner()` is a static address; it may resolve through a project NFT.
|
|
63
|
+
- Treat `src/JB721TiersHook.sol` and `src/JB721TiersHookStore.sol` together as the control-plane source of truth.
|
|
64
|
+
- If a downstream deployer changes hook ownership or ruleset ordering, re-evaluate the admin model instead of reusing old assumptions.
|
|
156
65
|
|
|
157
|
-
|
|
158
|
-
- The implementation contract cannot be self-destructed or modified after deployment. Even if it could be, clones would break since they `delegatecall` to the implementation address.
|
|
159
|
-
- Each clone has its own storage (including `PROJECT_ID`, ownership, and tier data). The implementation's storage is unused.
|
|
160
|
-
- `METADATA_ID_TARGET` is set to the original implementation address, ensuring consistent metadata ID derivation across all clones.
|
|
161
|
-
- The `initialize()` function uses an `_initialized` bool flag to prevent re-initialization. The implementation contract's constructor sets `_initialized = true`, blocking direct initialization. Clones start with `_initialized = false` and set it to `true` during `initialize()`.
|
|
162
|
-
|
|
163
|
-
## Ruleset-Level Pauses
|
|
164
|
-
|
|
165
|
-
Two behaviors are controlled by the project's current ruleset metadata (packed into the 14-bit `metadata` field of `JBRulesetMetadata`), parsed by `JB721TiersRulesetMetadataResolver`:
|
|
166
|
-
|
|
167
|
-
| Bit | Flag | Effect |
|
|
168
|
-
|-----|------|--------|
|
|
169
|
-
| 0 | `transfersPaused` | When set, NFT transfers are blocked for tiers that have `flags.transfersPausable` enabled |
|
|
170
|
-
| 1 | `mintPendingReservesPaused` | When set, `mintPendingReservesFor()` reverts |
|
|
66
|
+
## Recovery
|
|
171
67
|
|
|
172
|
-
|
|
68
|
+
- If the wrong immutable dependencies or ownership model were deployed, use a new hook clone.
|
|
69
|
+
- If a project-specific configuration is bad, prefer migrating future rulesets to a replacement hook over trying to retrofit unsupported behavior.
|
|
173
70
|
|
|
174
71
|
## Admin Boundaries
|
|
175
72
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
-
|
|
179
|
-
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
-
|
|
184
|
-
-
|
|
185
|
-
-
|
|
186
|
-
-
|
|
187
|
-
-
|
|
188
|
-
-
|
|
189
|
-
-
|
|
73
|
+
- The owner cannot change the store's constructor immutables or the clone's one-time initialization.
|
|
74
|
+
- The owner cannot overwrite original tier price semantics used for cash-out weight.
|
|
75
|
+
- The owner cannot bypass store-level flags such as non-removable tiers or discount increase restrictions.
|
|
76
|
+
- There is no global admin who can rewrite every hook instance at once.
|
|
77
|
+
|
|
78
|
+
## Source Map
|
|
79
|
+
|
|
80
|
+
- `src/JB721TiersHook.sol`
|
|
81
|
+
- `src/JB721TiersHookProjectDeployer.sol`
|
|
82
|
+
- `src/JB721TiersHookDeployer.sol`
|
|
83
|
+
- `src/JB721TiersHookStore.sol`
|
|
84
|
+
- `script/Deploy.s.sol`
|
|
85
|
+
- `script/helpers/Hook721DeploymentLib.sol`
|
|
86
|
+
- `test/E2E/`
|
|
87
|
+
- `test/TestAuditGaps.sol`
|
package/ARCHITECTURE.md
CHANGED
|
@@ -2,75 +2,90 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
`nana-721-hook-v6`
|
|
5
|
+
`nana-721-hook-v6` is the canonical tiered NFT issuance layer for Juicebox V6. It lets a project mint NFTs on payment, manage tier pricing and supply, accumulate NFT credits, lazily mint reserves, and optionally use NFT-aware cash-out behavior.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## System Overview
|
|
8
8
|
|
|
9
|
-
- `
|
|
10
|
-
- `JB721TiersHookStore` owns compact tier storage and many validation rules.
|
|
11
|
-
- `JB721TiersHookDeployer` and `JB721TiersHookProjectDeployer` own deployment and project-launch convenience.
|
|
12
|
-
- The repo does not replace the core terminal, controller, or surplus logic; it plugs into them.
|
|
9
|
+
`JB721TiersHook` is the project-facing hook surface. Through the shared `JB721Hook` base, it installs itself as both the ruleset data hook and the post-settlement pay and cash-out hook for the project. `JB721TiersHookStore` is the compact storage and validation backend that defines most tier semantics. The deployers package that behavior for existing projects or one-shot launches. The repo composes `nana-core-v6` rather than replacing terminal, controller, or surplus accounting.
|
|
13
10
|
|
|
14
|
-
##
|
|
11
|
+
## Core Invariants
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
- Tier ordering, category ordering, and tier IDs are part of storage semantics.
|
|
14
|
+
- Original configured tier price drives cash-out weight; discounts affect mint price, not reclaim weight.
|
|
15
|
+
- Reserve frequency and owner-mint settings must not combine into duplicate mint authority.
|
|
16
|
+
- Pending reserves count in supply-sensitive logic before reserve tokens are lazily minted.
|
|
17
|
+
- Preview behavior must stay aligned with live mint and cash-out behavior.
|
|
18
|
+
- Tier splits can reduce the fungible-token mint weight before terminal settlement unless `issueTokensForSplits` is enabled.
|
|
19
|
+
- If `useDataHookForCashOut` is enabled, NFT cash-out semantics intentionally displace fungible-token cash-out behavior.
|
|
23
20
|
|
|
24
|
-
##
|
|
21
|
+
## Modules
|
|
25
22
|
|
|
26
|
-
|
|
23
|
+
| Module | Responsibility | Notes |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| `JB721TiersHook` | Data-hook, pay-hook, and optional cash-out-hook behavior | Project-facing entrypoint |
|
|
26
|
+
| `JB721TiersHookStore` | Tier storage, mint accounting, reserves, validation | Storage-critical |
|
|
27
|
+
| `JB721Hook`, `ERC721` | Shared NFT machinery and metadata plumbing | Base abstractions |
|
|
28
|
+
| `JB721TiersHookDeployer`, `JB721TiersHookProjectDeployer` | Clone and launch helpers | Deployment surface |
|
|
29
|
+
|
|
30
|
+
## Trust Boundaries
|
|
31
|
+
|
|
32
|
+
- Treasury accounting, controller semantics, and permissions live in `nana-core-v6`.
|
|
33
|
+
- Resolver contracts such as Banny are outside this repo and are part of the trusted metadata surface when configured.
|
|
34
|
+
- Hook composition order matters when this hook is wrapped by deployers such as `nana-omnichain-deployers-v6`.
|
|
35
|
+
|
|
36
|
+
## Critical Flows
|
|
37
|
+
|
|
38
|
+
### Payment
|
|
27
39
|
|
|
28
40
|
```text
|
|
29
41
|
terminal payment
|
|
30
|
-
-> data hook
|
|
31
|
-
-> hook computes
|
|
42
|
+
-> data hook decodes metadata and requested tiers
|
|
43
|
+
-> hook computes mintable tiers, split forwarding, beneficiary resolution, and leftover value
|
|
32
44
|
-> terminal settles the payment into the project
|
|
33
|
-
-> pay hook mints NFTs and
|
|
45
|
+
-> post-settlement pay hook mints NFTs and may store remaining value as credits
|
|
34
46
|
```
|
|
35
47
|
|
|
36
|
-
### Reserve
|
|
48
|
+
### Reserve Minting
|
|
37
49
|
|
|
38
50
|
```text
|
|
39
|
-
tier purchases
|
|
40
|
-
->
|
|
51
|
+
tier purchases
|
|
52
|
+
-> accumulate reserve entitlement
|
|
53
|
+
-> reserve-mint call later realizes those pending reserve tokens
|
|
41
54
|
```
|
|
42
55
|
|
|
43
|
-
### Cash
|
|
56
|
+
### Cash Out
|
|
44
57
|
|
|
45
58
|
```text
|
|
46
59
|
holder burns NFT
|
|
47
|
-
-> data hook
|
|
48
|
-
->
|
|
60
|
+
-> data hook overrides fungible-token cash-out inputs when enabled
|
|
61
|
+
-> terminal settles the cash out using NFT-derived weight
|
|
62
|
+
-> post-settlement cash-out hook burns the specified NFTs
|
|
63
|
+
-> reclaim value is derived from the tier's original configured price
|
|
49
64
|
```
|
|
50
65
|
|
|
51
|
-
##
|
|
66
|
+
## Accounting Model
|
|
52
67
|
|
|
53
|
-
|
|
54
|
-
- Original tier price drives cash-out weight. Discounts change purchase price, not reclaim weight.
|
|
55
|
-
- Reserve frequency and owner-mint settings interact; configurations that would double-count mint authority must stay invalid.
|
|
56
|
-
- Pending reserves belong in supply-sensitive calculations even before they are lazily minted.
|
|
57
|
-
- If `useDataHookForCashOut` is enabled, fungible-token cash outs are intentionally displaced by NFT cash-out semantics.
|
|
68
|
+
The repo owns tier accounting, reserve accounting, credit accounting, and NFT-specific cash-out inputs. It also owns the mapping from hook metadata to NFT mint and burn side effects after terminal settlement. It does not own the canonical treasury ledger, which remains in `nana-core-v6`.
|
|
58
69
|
|
|
59
|
-
##
|
|
70
|
+
## Security Model
|
|
60
71
|
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
72
|
+
- Store layout changes have repo-wide blast radius because many downstream packages assume stable tier semantics.
|
|
73
|
+
- Metadata decoding is part of economic correctness because it chooses tiers, credits, and cash-out behavior.
|
|
74
|
+
- Terminal authorization in the base hook is part of the trust model; arbitrary callers must not be able to trigger pay or cash-out hooks.
|
|
75
|
+
- Initialization is one-time and ends by transferring ownership to the initializer. Clone deployers and launch flows depend on that handoff being preserved.
|
|
76
|
+
- Reserve math and supply math must be reviewed together.
|
|
64
77
|
|
|
65
|
-
##
|
|
78
|
+
## Safe Change Guide
|
|
66
79
|
|
|
67
|
-
-
|
|
68
|
-
-
|
|
80
|
+
- Treat store changes as ecosystem-wide changes.
|
|
81
|
+
- Keep previews aligned with state-changing behavior.
|
|
82
|
+
- If you change split behavior, re-check both NFT mint side effects and the fungible-token weight returned to the terminal.
|
|
83
|
+
- When adding metadata fields or flags, update wrapper deployers and downstream integrations in the same change set.
|
|
84
|
+
- If reserve logic changes, re-check supply math, reserve minting, and cash-out denominators together.
|
|
69
85
|
|
|
70
|
-
##
|
|
86
|
+
## Source Map
|
|
71
87
|
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
- If you touch reserve logic, also inspect supply math and cash-out denominators.
|
|
88
|
+
- `src/JB721TiersHook.sol`
|
|
89
|
+
- `src/JB721TiersHookStore.sol`
|
|
90
|
+
- `src/JB721TiersHookDeployer.sol`
|
|
91
|
+
- `src/JB721TiersHookProjectDeployer.sol`
|
package/AUDIT_INSTRUCTIONS.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This repo is the tiered ERC-721 hook system for Juicebox payments and NFT cash-outs. Audit it as a shared primitive used by many other repos.
|
|
4
4
|
|
|
5
|
-
## Objective
|
|
5
|
+
## Audit Objective
|
|
6
6
|
|
|
7
7
|
Find issues that:
|
|
8
8
|
- let users mint tiers more cheaply than intended
|
|
@@ -26,7 +26,13 @@ In scope:
|
|
|
26
26
|
|
|
27
27
|
This repo is depended on by Defifa, Croptop, Banny, Revnets, and omnichain deployers. Bugs here often have ecosystem-wide blast radius.
|
|
28
28
|
|
|
29
|
-
##
|
|
29
|
+
## Start Here
|
|
30
|
+
|
|
31
|
+
1. `src/JB721TiersHookStore.sol`
|
|
32
|
+
2. `src/JB721TiersHook.sol`
|
|
33
|
+
3. `src/JB721TiersHookDeployer.sol` and `src/JB721TiersHookProjectDeployer.sol`
|
|
34
|
+
|
|
35
|
+
## Security Model
|
|
30
36
|
|
|
31
37
|
The hook can act as:
|
|
32
38
|
- a data hook for payment and cash-out accounting inputs
|
|
@@ -45,6 +51,22 @@ The most important design subtlety is that this repo affects both:
|
|
|
45
51
|
|
|
46
52
|
That combination is why small-looking mistakes here often become ecosystem-wide economic bugs.
|
|
47
53
|
|
|
54
|
+
## Roles And Privileges
|
|
55
|
+
|
|
56
|
+
| Role | Powers | How constrained |
|
|
57
|
+
|------|--------|-----------------|
|
|
58
|
+
| Project authority | Adjust tiers, discounts, and resolver setup | Must not break supply, ordering, or accounting assumptions |
|
|
59
|
+
| Hook instance | Mint, burn, and compute accounting inputs | Must stay isolated from other hook instances |
|
|
60
|
+
| Store contract | Hold shared tier state | Must not leak or corrupt cross-project data |
|
|
61
|
+
| Token URI resolver | Supply metadata only | Must not become a hidden control surface |
|
|
62
|
+
|
|
63
|
+
## Integration Assumptions
|
|
64
|
+
|
|
65
|
+
| Dependency | Assumption | What breaks if wrong |
|
|
66
|
+
|------------|------------|----------------------|
|
|
67
|
+
| `nana-core-v6` | Payment and cash-out semantics remain coherent | Downstream economic routing becomes unsafe |
|
|
68
|
+
| Split recipients and hooks | Failures are handled in bounded ways | Mint accounting and treasury routing desync |
|
|
69
|
+
|
|
48
70
|
## Critical Invariants
|
|
49
71
|
|
|
50
72
|
1. Supply caps hold
|
|
@@ -68,21 +90,7 @@ Unused payment value that becomes credits must not let a user later mint tiers,
|
|
|
68
90
|
7. Resolver trust stays read-only unless explicitly intended
|
|
69
91
|
Token URI resolvers must not become an implicit control plane for mint, burn, or accounting behavior.
|
|
70
92
|
|
|
71
|
-
##
|
|
72
|
-
|
|
73
|
-
Prioritize:
|
|
74
|
-
- overspending and leftover-credit edge cases
|
|
75
|
-
- cross-currency pricing with missing or stale feeds
|
|
76
|
-
- tier additions or adjustments with invalid sort order or percent bounds
|
|
77
|
-
- split hooks or terminal recipients that revert or partially fail
|
|
78
|
-
- data-hook and pay-hook interactions inside the same payment
|
|
79
|
-
|
|
80
|
-
Especially high-value attacker profiles:
|
|
81
|
-
- a payer crafting metadata and tier selections to desync credits, split routing, and token issuance
|
|
82
|
-
- a project owner adjusting tiers between preview and execution windows
|
|
83
|
-
- a downstream app assuming tier cash-out weight tracks discounted price when the primitive uses different economics
|
|
84
|
-
|
|
85
|
-
## Hotspots
|
|
93
|
+
## Attack Surfaces
|
|
86
94
|
|
|
87
95
|
- `beforePayRecordedWith`, `afterPayRecordedWith`, and cash-out hooks
|
|
88
96
|
- credit handling when `payer != beneficiary`
|
|
@@ -91,24 +99,19 @@ Especially high-value attacker profiles:
|
|
|
91
99
|
- `splitPercent` handling and hook distribution fallback behavior
|
|
92
100
|
- deployers that transfer ownership or queue rulesets around the hook
|
|
93
101
|
|
|
94
|
-
|
|
102
|
+
Replay these sequences:
|
|
103
|
+
1. cross-currency payment with missing, stale, or asymmetric prices
|
|
104
|
+
2. leftover credits across different payer and beneficiary arrangements
|
|
105
|
+
3. split-routed tier purchases when downstream hooks or terminals fail
|
|
106
|
+
4. reserve-heavy tiers followed by NFT cash-out before reserves are minted
|
|
107
|
+
5. tier adjustment or discount changes around active minting and cash-out windows
|
|
95
108
|
|
|
96
|
-
|
|
97
|
-
2. Payment with leftover value that becomes credits, then a second payment from a different payer/beneficiary arrangement.
|
|
98
|
-
3. Tier purchases with split routing enabled, especially when split hooks or downstream terminals fail.
|
|
99
|
-
4. Reserve-heavy tiers followed by NFT cash-out before pending reserves are minted.
|
|
100
|
-
5. Tier adjustment or discount updates around active minting and cash-out windows.
|
|
109
|
+
## Accepted Risks Or Behaviors
|
|
101
110
|
|
|
102
|
-
|
|
111
|
+
- Conservative behavior is preferable to optimistic behavior because downstream repos often treat these surfaces as economic truth.
|
|
112
|
+
|
|
113
|
+
## Verification
|
|
103
114
|
|
|
104
|
-
Standard workflow:
|
|
105
115
|
- `npm install`
|
|
106
116
|
- `forge build`
|
|
107
117
|
- `forge test`
|
|
108
|
-
|
|
109
|
-
Current tests emphasize:
|
|
110
|
-
- audit and regression fixes around split accounting and cross-currency behavior
|
|
111
|
-
- invariants on tier lifecycle and store state
|
|
112
|
-
- fork coverage for ERC-20 cash-out and tier split routes
|
|
113
|
-
|
|
114
|
-
High-value findings in this repo tend to become repeatable vulnerabilities in downstream repos, so favor proofs that show the primitive itself returning or recording the wrong value.
|
package/README.md
CHANGED
|
@@ -3,18 +3,23 @@
|
|
|
3
3
|
`@bananapus/721-hook-v6` is the tiered NFT issuance layer for Juicebox V6. It lets a project mint ERC-721s on payment, attach tier-specific pricing and supply rules, mint reserves, and integrate custom token URI resolvers.
|
|
4
4
|
|
|
5
5
|
Docs: <https://docs.juicebox.money>
|
|
6
|
-
Architecture: [ARCHITECTURE.md](./ARCHITECTURE.md)
|
|
6
|
+
Architecture: [ARCHITECTURE.md](./ARCHITECTURE.md)
|
|
7
|
+
User journeys: [USER_JOURNEYS.md](./USER_JOURNEYS.md)
|
|
8
|
+
Skills: [SKILLS.md](./SKILLS.md)
|
|
9
|
+
Risks: [RISKS.md](./RISKS.md)
|
|
10
|
+
Administration: [ADMINISTRATION.md](./ADMINISTRATION.md)
|
|
11
|
+
Audit instructions: [AUDIT_INSTRUCTIONS.md](./AUDIT_INSTRUCTIONS.md)
|
|
7
12
|
|
|
8
13
|
## Overview
|
|
9
14
|
|
|
10
|
-
This package is the
|
|
15
|
+
This package is the main shared tiered NFT hook used across the V6 ecosystem. Projects use it to:
|
|
11
16
|
|
|
12
17
|
- sell fixed-price NFT tiers through Juicebox payments
|
|
13
18
|
- apply tier supply, reserve frequency, voting unit, and discount rules
|
|
14
19
|
- cash out tiers through the Juicebox terminal surface
|
|
15
20
|
- compose custom metadata resolvers such as Banny or Defifa
|
|
16
21
|
|
|
17
|
-
The deployer
|
|
22
|
+
The deployer helps clone hooks for existing projects, and the project-deployer helps launch new projects with a hook already attached.
|
|
18
23
|
|
|
19
24
|
Use this repo when a project's NFT logic should be part of its payment and cash-out flow. Do not use it for collection-specific rendering or game logic; those belong in higher-level packages like Banny or Defifa.
|
|
20
25
|
|
|
@@ -70,6 +75,14 @@ The shortest useful reading order is:
|
|
|
70
75
|
|
|
71
76
|
That split is why UI bugs, economic bugs, and deployment bugs often land in different repos even though users describe them all as "721 hook issues."
|
|
72
77
|
|
|
78
|
+
## High-Signal Tests
|
|
79
|
+
|
|
80
|
+
1. `test/E2E/Pay_Mint_Redeem_E2E.t.sol`
|
|
81
|
+
2. `test/invariants/TierLifecycleInvariant.t.sol`
|
|
82
|
+
3. `test/invariants/TieredHookStoreInvariant.t.sol`
|
|
83
|
+
4. `test/audit/CodexSplitCreditsMismatch.t.sol`
|
|
84
|
+
5. `test/regression/ProjectDeployerRulesets.t.sol`
|
|
85
|
+
|
|
73
86
|
## Install
|
|
74
87
|
|
|
75
88
|
```bash
|
|
@@ -121,3 +134,9 @@ script/
|
|
|
121
134
|
- tier mutations after launch are powerful and should be permissioned carefully
|
|
122
135
|
|
|
123
136
|
When people say "the 721 hook," they often mean three different things: the hook contract, the store, and the metadata resolver plugged into it. Audits and integrations should separate those concerns.
|
|
137
|
+
|
|
138
|
+
## For AI Agents
|
|
139
|
+
|
|
140
|
+
- Separate hook behavior, store behavior, and resolver behavior in your explanation.
|
|
141
|
+
- Read the store invariants and end-to-end pay/mint/redeem tests before summarizing lifecycle guarantees.
|
|
142
|
+
- If metadata or rendering behavior is project-specific, move to the downstream resolver repo.
|
package/RISKS.md
CHANGED
|
@@ -44,8 +44,9 @@ This file focuses on the tiered-NFT accounting, reserve-mint, and cash-out risks
|
|
|
44
44
|
## 3. Reentrancy Surface
|
|
45
45
|
|
|
46
46
|
- **Split hook callbacks (`processSplitWith`).** During `afterPayRecordedWith` -> `_processPayment` -> `distributeAll`, the library calls `split.hook.processSplitWith{value}()` for each split with a hook. This executes arbitrary code. At callback time: NFTs already minted, `payCreditsOf` updated, `remainingSupply` decremented in the store. Reentering `afterPayRecordedWith` requires terminal authentication and processes as an independent payment. All split hook and terminal calls are wrapped in try-catch to prevent a single reverting recipient from bricking all payments to the project. For native token hooks, a revert returns false (ETH stays in the contract and routes to project balance). For ERC20 hooks, tokens are transferred before the callback; a revert still returns true because the tokens have already left the contract. Tested: `TestAuditGaps_Reentrancy` confirms reentrancy is blocked by terminal check.
|
|
47
|
-
- **Split beneficiary ETH sends.** `_sendPayoutToSplit` uses `beneficiary.call{value: amount}("")`. If the beneficiary reverts, the function returns `false` and the failed amount is accumulated separately, then routed
|
|
47
|
+
- **Split beneficiary ETH sends.** `_sendPayoutToSplit` uses `beneficiary.call{value: amount}("")`. If the beneficiary reverts, the function returns `false` and the failed amount is accumulated separately, then routed toward the project's balance after the distribution loop via `addToBalanceOf`. Later split recipients receive only their proportional share, not the failed recipient's share. This does not revert the entire payment unless the fallback `addToBalanceOf` path also reverts.
|
|
48
48
|
- **Terminal `.pay()` / `.addToBalanceOf()` during split distribution.** For project-targeted splits, the library calls the target project's primary terminal via try-catch. A reverting terminal returns false, routing the funds to the project's balance instead. For ERC20 terminal calls, approval is reset to zero on failure to prevent dangling approvals. The target terminal could call back into the hook, but the hook's state is fully settled (supply, credits, mint state). Reentrancy through this path cannot double-mint or corrupt state.
|
|
49
|
+
- **Split fallback can still strand value if the project terminal rejects leftovers.** `_distributeSingleSplit` tries to route failed split payouts into the source project's primary terminal with `addToBalanceOf`. If that fallback also reverts, the whole hook call reverts with `JB721TiersHookLib_SplitFallbackFailed` after the hook has already received the forwarded funds. For native ETH, that leaves the ETH stranded in the hook. For ERC-20s, approval is reset but the tokens remain in the hook. There is no built-in recovery path.
|
|
49
50
|
- **`afterCashOutRecordedWith` execution order.** Burns tokens via `_burn()` -> `_update()` -> `STORE.tierTransferInfoOfTokenId()` in a loop, then calls `STORE.recordBurn()`. ERC721 `_update` triggers the store's tier balance decrement. Burns go to `address(0)`, so no `onERC721Received` callback.
|
|
50
51
|
- **No `ReentrancyGuard`.** Protection relies on state ordering (all `STORE.record*` calls before external calls), terminal authentication checks, and try-catch wrapping of all external calls in `_sendPayoutToSplit`. `_mint()` uses the non-safe variant, avoiding `onERC721Received` callbacks during minting.
|
|
51
52
|
|
|
@@ -83,6 +84,7 @@ This file focuses on the tiered-NFT accounting, reserve-mint, and cash-out risks
|
|
|
83
84
|
- **Split group ID encoding.** Composite: `uint256(uint160(hookAddress)) | (tierId << 160)`. Tier IDs are capped at uint16, so no overflow. Splits are permanently coupled to a specific hook address -- migrating to a new hook requires re-creating all split groups.
|
|
84
85
|
- **ERC-20 split distribution pulls from terminal.** `distributeAll` calls `SafeERC20.safeTransferFrom(token, msg.sender, address(this), amount)` to pull ERC-20s from the terminal. Requires the terminal to have granted allowance via its `_beforeTransferTo` pattern. If the terminal's allowance mechanism changes, distribution fails.
|
|
85
86
|
- **Forwarded funds depend on non-empty split metadata.** `_processPayment` only calls `distributeAll` when both `context.forwardedAmount.value != 0` and `context.hookMetadata.length != 0`. If an integration ever forwards funds with empty hook metadata, distribution is skipped and the funds remain in the hook contract with no dedicated rescue path.
|
|
87
|
+
- **Split-fallback success depends on the source project's active terminal.** Failed split payouts are not simply burned or refunded. They are re-routed into the source project's current primary terminal for the forwarded token. If that terminal is unset or rejects `addToBalanceOf`, the call reverts and the hook can retain the funds.
|
|
86
88
|
- **Token URI resolver external calls.** `tokenURI()` and `tiersOf(..., includeResolvedUri=true)` call the resolver if set. A reverting resolver blocks all metadata reads (marketplace/frontend impact, no fund risk).
|
|
87
89
|
|
|
88
90
|
## 7. Invariants to Verify
|
|
@@ -117,3 +119,7 @@ If the payment currency differs from the tier pricing currency and `PRICES == ad
|
|
|
117
119
|
### 8.4 Tiny split allocations can round down to zero recipient amounts
|
|
118
120
|
|
|
119
121
|
Split metadata is expressed in whole token units after conversion and capping. For very small allocations, each rounded per-tier split amount can become zero even though the overall forwarded amount is still reduced by the capped split total. This is an accepted precision tradeoff for dust-sized payments: integrations should not rely on sub-precision split routing and should expect tiny split allocations to be economically lossy.
|
|
122
|
+
|
|
123
|
+
### 8.5 Failed split payouts only degrade cleanly if the fallback terminal path works
|
|
124
|
+
|
|
125
|
+
The hook treats a reverting split hook, beneficiary, or target terminal as a soft failure and attempts to re-route that amount into the source project's balance. That graceful degradation depends on the source project's current primary terminal accepting `addToBalanceOf` for the forwarded token. If that terminal is missing or rejects the call, the transaction reverts after funds have already reached the hook, and the hook can retain those assets without a built-in rescue path.
|
package/SKILLS.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
## Use This File For
|
|
4
4
|
|
|
5
5
|
- Use this file when the task involves tiered NFT issuance, reserve minting, voting units, tier splits, or token URI resolver integration for Juicebox projects.
|
|
6
|
-
- Start here, then
|
|
6
|
+
- Start here, then decide whether the bug is in hook runtime logic, store accounting, deployer initialization, or downstream token-URI resolution. This repo spans all four and they are easy to conflate.
|
|
7
7
|
|
|
8
8
|
## Read This Next
|
|
9
9
|
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
| Tier storage and accounting | [`src/JB721TiersHookStore.sol`](./src/JB721TiersHookStore.sol) |
|
|
15
15
|
| Deployment or project launch helpers | [`src/JB721TiersHookDeployer.sol`](./src/JB721TiersHookDeployer.sol), [`src/JB721TiersHookProjectDeployer.sol`](./src/JB721TiersHookProjectDeployer.sol), [`script/Deploy.s.sol`](./script/Deploy.s.sol) |
|
|
16
16
|
| Shared libraries, interfaces, and resolver surface | [`src/libraries/`](./src/libraries/), [`src/interfaces/`](./src/interfaces/), [`src/structs/`](./src/structs/) |
|
|
17
|
-
|
|
|
17
|
+
| Mint, pricing, voting, and checkpoint behavior | [`test/TestVotingUnitsLifecycle.t.sol`](./test/TestVotingUnitsLifecycle.t.sol), [`test/TestCheckpoints.t.sol`](./test/TestCheckpoints.t.sol) |
|
|
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) |
|
|
18
19
|
|
|
19
20
|
## Repo Map
|
|
20
21
|
|
|
@@ -37,6 +38,11 @@ Tiered ERC-721 NFT issuance and cash-out hook for Juicebox V6. This repo control
|
|
|
37
38
|
## Working Rules
|
|
38
39
|
|
|
39
40
|
- Start in [`src/JB721TiersHook.sol`](./src/JB721TiersHook.sol) for pay and cash-out behavior, but verify storage-side assumptions in [`src/JB721TiersHookStore.sol`](./src/JB721TiersHookStore.sol) before changing mint, burn, reserve, or supply logic.
|
|
41
|
+
- The store is the source of truth for supply, reserve, removal, and tier-order invariants. Do not “fix” those concepts only in the hook layer.
|
|
42
|
+
- Pending reserves are part of live economics, not deferred bookkeeping. Cash-out denominators and tier availability both depend on them before reserves are minted.
|
|
43
|
+
- Pay credits, overspending protection, and tier split forwarding are economically relevant. Treat them like accounting, not just UX.
|
|
40
44
|
- Treat tier splits, reserve minting, and discounted pricing as treasury-sensitive. Check both runtime code and regression coverage before assuming a change is local.
|
|
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.
|
|
41
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.
|
|
42
48
|
- When changing deployers or initialization, verify the hook, store, and project-launch path stay aligned. These flows are tightly coupled.
|
package/USER_JOURNEYS.md
CHANGED
|
@@ -1,96 +1,191 @@
|
|
|
1
1
|
# User Journeys
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Repo Purpose
|
|
4
|
+
|
|
5
|
+
This repo is the standard tiered NFT hook for V6 projects.
|
|
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.
|
|
9
|
+
|
|
10
|
+
## Primary Actors
|
|
4
11
|
|
|
5
12
|
- project owners selling or rewarding supporters with tiered NFTs
|
|
6
13
|
- operators managing tier supply, pricing, reserves, and ruleset-aware hook behavior
|
|
7
14
|
- supporters minting or cashing out tier positions through normal Juicebox flows
|
|
8
|
-
- integrators composing custom token URI resolvers on top of the
|
|
15
|
+
- integrators composing custom token URI resolvers or downstream products on top of the hook
|
|
16
|
+
|
|
17
|
+
## Key Surfaces
|
|
18
|
+
|
|
19
|
+
- `JB721TiersHook`: project-facing hook behavior for minting, reserves, metadata, and cash out
|
|
20
|
+
- `JB721TiersHookStore`: tier definitions and accounting backend
|
|
21
|
+
- `JB721TiersHookDeployer`: clone factory for existing projects
|
|
22
|
+
- `JB721TiersHookProjectDeployer`: project-launch packaging for new hook-backed projects
|
|
9
23
|
|
|
10
24
|
## Journey 1: Add A Tiered 721 Hook To An Existing Project
|
|
11
25
|
|
|
12
|
-
**
|
|
26
|
+
**Actor:** project owner or deployer.
|
|
27
|
+
|
|
28
|
+
**Intent:** attach tiered NFT behavior to an existing project without relaunching it.
|
|
29
|
+
|
|
30
|
+
**Preconditions**
|
|
31
|
+
- the project already exists in Juicebox
|
|
32
|
+
- the owner knows the tier config, reserve behavior, and resolver assumptions it wants
|
|
33
|
+
- the next ruleset metadata can be updated safely
|
|
34
|
+
|
|
35
|
+
**Main Flow**
|
|
36
|
+
1. Use `JB721TiersHookDeployer` to clone a hook for the project.
|
|
37
|
+
2. Define tier config, reserve behavior, resolver choice, and per-ruleset flags.
|
|
38
|
+
3. Queue or install ruleset metadata that points at the hook.
|
|
39
|
+
4. Future payments can now mint tiers under the configured constraints.
|
|
13
40
|
|
|
14
|
-
**
|
|
41
|
+
**Failure Modes**
|
|
42
|
+
- the hook is deployed correctly but the ruleset metadata does not actually activate it
|
|
43
|
+
- teams treat the resolver as cosmetic when it is part of the trusted surface
|
|
15
44
|
|
|
16
|
-
**
|
|
17
|
-
|
|
18
|
-
2. Define tier config, reserve behavior, token URI resolver, and per-ruleset flags.
|
|
19
|
-
3. Queue or install ruleset metadata that tells the project when the hook should participate in pay and cash-out flows.
|
|
20
|
-
4. Future payments into the project can now mint tiers under the configured constraints.
|
|
45
|
+
**Postconditions**
|
|
46
|
+
- the project has an attached hook and future rulesets can mint tiers under the configured constraints
|
|
21
47
|
|
|
22
48
|
## Journey 2: Launch A New Project With A 721 Hook Already Wired In
|
|
23
49
|
|
|
24
|
-
**
|
|
50
|
+
**Actor:** product team or deployer.
|
|
25
51
|
|
|
26
|
-
**
|
|
52
|
+
**Intent:** launch a project whose treasury and tiered NFT logic are aligned from the first ruleset.
|
|
27
53
|
|
|
28
|
-
**
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
54
|
+
**Preconditions**
|
|
55
|
+
- the team has launch config, terminal config, and initial tier config ready
|
|
56
|
+
|
|
57
|
+
**Main Flow**
|
|
58
|
+
1. Use `JB721TiersHookProjectDeployer` with launch and hook config.
|
|
59
|
+
2. Launch the project and deploy the hook in the same packaged flow.
|
|
60
|
+
3. Ensure the first ruleset already points at the created hook.
|
|
61
|
+
4. Start life as a hook-backed project instead of converting later.
|
|
62
|
+
|
|
63
|
+
**Failure Modes**
|
|
64
|
+
- deployers assume the package is purely convenience and miss the initial ruleset implications
|
|
65
|
+
- launch-time metadata drifts from the actual hook config
|
|
66
|
+
|
|
67
|
+
**Postconditions**
|
|
68
|
+
- the project launches with tiered NFT logic active from the first ruleset
|
|
33
69
|
|
|
34
70
|
## Journey 3: Mint Specific Tiers Through A Payment
|
|
35
71
|
|
|
36
|
-
**
|
|
72
|
+
**Actor:** payer.
|
|
37
73
|
|
|
38
|
-
**
|
|
74
|
+
**Intent:** mint one or more tiers through a normal payment flow.
|
|
39
75
|
|
|
40
|
-
**
|
|
41
|
-
|
|
42
|
-
|
|
76
|
+
**Preconditions**
|
|
77
|
+
- the project has live tiers
|
|
78
|
+
- the payer submits metadata encoding the intended tier selections
|
|
79
|
+
|
|
80
|
+
**Main Flow**
|
|
81
|
+
1. Submit a payment with tier-selection metadata.
|
|
82
|
+
2. `JB721TiersHook` validates availability, quantity rules, discounts, and ruleset flags.
|
|
43
83
|
3. `JB721TiersHookStore` updates supply and reserve accounting.
|
|
44
|
-
4. The hook mints the
|
|
84
|
+
4. The hook mints the intended NFTs and the terminal completes treasury accounting.
|
|
85
|
+
|
|
86
|
+
**Failure Modes**
|
|
87
|
+
- sold-out tiers, malformed metadata, or cross-currency pricing mistakes
|
|
88
|
+
- pay-hook participation flags do not match the user's assumptions
|
|
89
|
+
- split-routing or hook behavior changes what part of the payment actually mints
|
|
45
90
|
|
|
46
|
-
**
|
|
91
|
+
**Postconditions**
|
|
92
|
+
- the intended tiers are minted and store accounting reflects the updated supply and reserve state
|
|
47
93
|
|
|
48
94
|
## Journey 4: Mint Reserves And Operate Tier Inventory Over Time
|
|
49
95
|
|
|
50
|
-
**
|
|
96
|
+
**Actor:** owner or authorized operator.
|
|
97
|
+
|
|
98
|
+
**Intent:** manage tier inventory and reserve behavior after launch.
|
|
51
99
|
|
|
52
|
-
**
|
|
100
|
+
**Preconditions**
|
|
101
|
+
- the collection is live
|
|
102
|
+
- the operator has the required permission surfaces to mutate tiers or mint reserves
|
|
53
103
|
|
|
54
|
-
**Flow**
|
|
55
|
-
1.
|
|
56
|
-
2.
|
|
57
|
-
3.
|
|
58
|
-
4.
|
|
104
|
+
**Main Flow**
|
|
105
|
+
1. Use tier-management surfaces to add, remove, or adjust tiers.
|
|
106
|
+
2. Mint reserves through the configured reserve logic.
|
|
107
|
+
3. Let the store preserve historical tier state so old token IDs still resolve correctly.
|
|
108
|
+
4. Keep downstream products assuming stable tier semantics whenever possible.
|
|
109
|
+
|
|
110
|
+
**Failure Modes**
|
|
111
|
+
- tier mutations surprise downstream products or resolvers
|
|
112
|
+
- reserve accounting is misread as ordinary minting
|
|
113
|
+
|
|
114
|
+
**Postconditions**
|
|
115
|
+
- live tier inventory and reserve state match the operator's configured collection policy
|
|
59
116
|
|
|
60
117
|
## Journey 5: Let Holders Cash Out Tier Positions
|
|
61
118
|
|
|
62
|
-
**
|
|
119
|
+
**Actor:** holder.
|
|
120
|
+
|
|
121
|
+
**Intent:** exit a tier position through the project's cash-out path.
|
|
63
122
|
|
|
64
|
-
**
|
|
123
|
+
**Preconditions**
|
|
124
|
+
- the holder owns one or more NFTs from the hook-enabled project
|
|
125
|
+
- the active ruleset allows a surplus-backed exit
|
|
65
126
|
|
|
66
|
-
**Flow**
|
|
67
|
-
1.
|
|
68
|
-
2.
|
|
69
|
-
3.
|
|
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.
|
|
70
132
|
|
|
71
|
-
**
|
|
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
|
|
72
139
|
|
|
73
140
|
## Journey 6: Compose A Custom Product On Top Of The Standard Hook
|
|
74
141
|
|
|
75
|
-
**
|
|
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
|
|
76
149
|
|
|
77
|
-
**
|
|
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.
|
|
78
154
|
|
|
79
|
-
**
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
83
161
|
|
|
84
162
|
## Journey 7: Mint NFTs To The Correct Beneficiary During Cross-Chain Payments
|
|
85
163
|
|
|
86
|
-
**
|
|
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
|
|
87
183
|
|
|
88
|
-
|
|
184
|
+
## Trust Boundaries
|
|
89
185
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
3. All NFT minting and credit accounting uses the resolved relay beneficiary instead of the sucker address.
|
|
186
|
+
- `JB721TiersHookStore` is the accounting backend and should be treated as part of the same economic surface as the hook
|
|
187
|
+
- custom token URI resolvers are part of the trusted collection surface
|
|
188
|
+
- core terminals remain the source of treasury accounting truth around the hook
|
|
94
189
|
|
|
95
190
|
## Hand-Offs
|
|
96
191
|
|
package/foundry.toml
CHANGED
package/package.json
CHANGED
package/references/operations.md
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
## Change Checklist
|
|
10
10
|
|
|
11
11
|
- If you edit hook initialization, verify deployer config structs and project-launch helpers still encode the same assumptions.
|
|
12
|
-
- If you edit tier config or metadata behavior, inspect
|
|
12
|
+
- If you edit tier config or metadata behavior, inspect the corresponding structs and interfaces in `src/structs/` and `src/interfaces/`.
|
|
13
|
+
- If you edit reserve behavior, verify pending reserve counts, default reserve beneficiary semantics, and cash-out denominator effects together.
|
|
14
|
+
- If you edit discount behavior, verify mint price and cash-out weight separately. They are intentionally not the same quantity.
|
|
13
15
|
- If you touch permissions, verify the caller path and permission constants still line up with the downstream ecosystem package that defines them.
|
|
14
16
|
- If you touch URI behavior, confirm whether the issue belongs in this repo or in a downstream resolver contract that the hook calls.
|
|
15
17
|
|
|
@@ -24,5 +26,7 @@
|
|
|
24
26
|
|
|
25
27
|
- [`test/Fork.t.sol`](../test/Fork.t.sol) for live-integration assumptions.
|
|
26
28
|
- [`test/TestAuditGaps.sol`](../test/TestAuditGaps.sol) for known edge cases the repo authors considered worth pinning down.
|
|
27
|
-
- [`test/
|
|
28
|
-
- [`
|
|
29
|
+
- [`test/TestCheckpoints.t.sol`](../test/TestCheckpoints.t.sol) when you need a narrow function-level proof before editing a broad runtime path.
|
|
30
|
+
- [`test/invariants/TierLifecycleInvariant.t.sol`](../test/invariants/TierLifecycleInvariant.t.sol) and [`test/invariants/TieredHookStoreInvariant.t.sol`](../test/invariants/TieredHookStoreInvariant.t.sol) when a local patch may have broken store-level relationships.
|
|
31
|
+
- [`test/audit/CodexRetroactiveReserveBeneficiaryDilution.t.sol`](../test/audit/CodexRetroactiveReserveBeneficiaryDilution.t.sol) when reserve-beneficiary or pending-reserve behavior changes.
|
|
32
|
+
- [`script/Deploy.s.sol`](../script/Deploy.s.sol) when a deployment or launch question is really about config assembly rather than contract behavior.
|
package/references/runtime.md
CHANGED
|
@@ -22,11 +22,12 @@
|
|
|
22
22
|
- Discount behavior: price discounts affect mint eligibility but cash-out weight still tracks the original tier price. Do not conflate the two.
|
|
23
23
|
- Voting units: verify whether a tier uses explicit voting units or falls back to price-based voting power before changing governance-facing math.
|
|
24
24
|
- Tier removal and cleanup: removing tiers is not the same as cleaning the sorted tier list. Storage cleanup behavior matters.
|
|
25
|
+
- Default reserve beneficiary changes: they affect which tiers count pending reserves unless a tier-specific beneficiary overrides it. That is an economic change, not just an admin update.
|
|
25
26
|
|
|
26
27
|
## Tests To Trust First
|
|
27
28
|
|
|
28
|
-
- [`test/
|
|
29
|
-
- [`test/E2E/`](../test/E2E/) for launch and end-to-end payment flows.
|
|
30
|
-
- [`test/regression/`](../test/regression/) for previously broken edge cases.
|
|
29
|
+
- [`test/Fork.t.sol`](../test/Fork.t.sol) for launch and live integration flows.
|
|
31
30
|
- [`test/TestVotingUnitsLifecycle.t.sol`](../test/TestVotingUnitsLifecycle.t.sol) for voting-unit lifecycle behavior.
|
|
32
|
-
- [`test/
|
|
31
|
+
- [`test/TestCheckpoints.t.sol`](../test/TestCheckpoints.t.sol) for checkpoint/module behavior.
|
|
32
|
+
- [`test/invariants/TierLifecycleInvariant.t.sol`](../test/invariants/TierLifecycleInvariant.t.sol) and [`test/invariants/TieredHookStoreInvariant.t.sol`](../test/invariants/TieredHookStoreInvariant.t.sol) for store-level lifecycle invariants.
|
|
33
|
+
- [`test/TestSafeTransferReentrancy.t.sol`](../test/TestSafeTransferReentrancy.t.sol), [`test/721HookAttacks.t.sol`](../test/721HookAttacks.t.sol), [`test/audit/CodexRetroactiveReserveBeneficiaryDilution.t.sol`](../test/audit/CodexRetroactiveReserveBeneficiaryDilution.t.sol), and [`test/TestAuditGaps.sol`](../test/TestAuditGaps.sol) for reentrancy and attack-surface checks.
|
package/src/JB721TiersHook.sol
CHANGED
|
@@ -457,9 +457,7 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
457
457
|
string calldata baseUri,
|
|
458
458
|
string calldata contractUri,
|
|
459
459
|
IJB721TokenUriResolver tokenUriResolver,
|
|
460
|
-
// forge-lint: disable-next-line(mixed-case-variable)
|
|
461
460
|
uint256 encodedIPFSUriTierId,
|
|
462
|
-
// forge-lint: disable-next-line(mixed-case-variable)
|
|
463
461
|
bytes32 encodedIPFSUri
|
|
464
462
|
)
|
|
465
463
|
external
|
|
@@ -124,7 +124,6 @@ contract JB721TiersHookProjectDeployer is ERC2771Context, JBPermissioned, IJB721
|
|
|
124
124
|
returns (uint256 rulesetId, IJB721TiersHook hook)
|
|
125
125
|
{
|
|
126
126
|
// Get the project's projects contract.
|
|
127
|
-
// forge-lint: disable-next-line(mixed-case-variable)
|
|
128
127
|
IJBProjects PROJECTS = DIRECTORY.PROJECTS();
|
|
129
128
|
|
|
130
129
|
// Enforce permissions.
|
|
@@ -66,7 +66,6 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
66
66
|
/// @custom:param hook The 721 contract that the tier belongs to.
|
|
67
67
|
/// @custom:param tierId The ID of the tier to get the encoded IPFS URI of.
|
|
68
68
|
/// @custom:returns The encoded IPFS URI.
|
|
69
|
-
// forge-lint: disable-next-line(mixed-case-variable)
|
|
70
69
|
mapping(address hook => mapping(uint256 tierId => bytes32)) public override encodedIPFSUriOf;
|
|
71
70
|
|
|
72
71
|
/// @notice Returns the largest tier ID currently used on the provided 721 contract.
|
|
@@ -1344,7 +1343,7 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
1344
1343
|
/// @notice Record a new encoded IPFS URI for a tier.
|
|
1345
1344
|
/// @param tierId The ID of the tier to set the encoded IPFS URI of.
|
|
1346
1345
|
/// @param encodedIPFSUri The encoded IPFS URI to set for the tier.
|
|
1347
|
-
// forge-lint: disable-next-line(mixed-case-function
|
|
1346
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
1348
1347
|
function recordSetEncodedIPFSUriOf(uint256 tierId, bytes32 encodedIPFSUri) external override {
|
|
1349
1348
|
encodedIPFSUriOf[msg.sender][tierId] = encodedIPFSUri;
|
|
1350
1349
|
}
|
|
@@ -49,7 +49,6 @@ abstract contract JB721Hook is ERC721, IJB721Hook {
|
|
|
49
49
|
//*********************************************************************//
|
|
50
50
|
|
|
51
51
|
/// @notice The ID of the project that this contract is associated with.
|
|
52
|
-
// forge-lint: disable-next-line(mixed-case-variable)
|
|
53
52
|
uint256 public override PROJECT_ID;
|
|
54
53
|
|
|
55
54
|
//*********************************************************************//
|
|
@@ -234,9 +234,7 @@ interface IJB721TiersHook is IJB721Hook {
|
|
|
234
234
|
string calldata baseUri,
|
|
235
235
|
string calldata contractUri,
|
|
236
236
|
IJB721TokenUriResolver tokenUriResolver,
|
|
237
|
-
// forge-lint: disable-next-line(mixed-case-variable)
|
|
238
237
|
uint256 encodedIPFSUriTierId,
|
|
239
|
-
// forge-lint: disable-next-line(mixed-case-variable)
|
|
240
238
|
bytes32 encodedIPFSUri
|
|
241
239
|
)
|
|
242
240
|
external;
|
|
@@ -247,7 +247,7 @@ interface IJB721TiersHookStore {
|
|
|
247
247
|
/// @notice Record a new encoded IPFS URI for a tier.
|
|
248
248
|
/// @param tierId The ID of the tier to set the encoded IPFS URI of.
|
|
249
249
|
/// @param encodedIPFSUri The encoded IPFS URI to set for the tier.
|
|
250
|
-
// forge-lint: disable-next-line(mixed-case-function
|
|
250
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
251
251
|
function recordSetEncodedIPFSUriOf(uint256 tierId, bytes32 encodedIPFSUri) external;
|
|
252
252
|
|
|
253
253
|
/// @notice Record a newly set token URI resolver.
|
|
@@ -8,6 +8,5 @@ library JB721Constants {
|
|
|
8
8
|
/// @notice The metadata ID used to identify the 721 beneficiary entry in payment metadata.
|
|
9
9
|
/// @dev When a sucker pays on behalf of a remote user, the real user's address is embedded under this key
|
|
10
10
|
/// so NFTs mint to the correct recipient.
|
|
11
|
-
// forge-lint: disable-next-line(mixed-case-variable)
|
|
12
11
|
bytes4 public constant BENEFICIARY_METADATA_ID = bytes4(keccak256("JB_721_BENEFICIARY"));
|
|
13
12
|
}
|
|
@@ -8,7 +8,6 @@ import {JB721TierConfig} from "./JB721TierConfig.sol";
|
|
|
8
8
|
/// @custom:member tiers The tiers to initialize the hook with.
|
|
9
9
|
/// @custom:member currency The currency that the tier prices are denoted in. See `JBPrices`.
|
|
10
10
|
/// @custom:member decimals The number of decimals in the fixed point tier prices.
|
|
11
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
12
11
|
struct JB721InitTiersConfig {
|
|
13
12
|
JB721TierConfig[] tiers;
|
|
14
13
|
uint32 currency;
|
|
@@ -21,7 +21,6 @@ import {JB721TierFlags} from "./JB721TierFlags.sol";
|
|
|
21
21
|
/// an NFT from this tier is minted. Out of `JBConstants.SPLITS_TOTAL_PERCENT`.
|
|
22
22
|
/// @custom:member resolvedUri A resolved token URI for NFTs in this tier. Only available if the NFT this tier belongs
|
|
23
23
|
/// to has a resolver.
|
|
24
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
25
24
|
struct JB721Tier {
|
|
26
25
|
uint32 id;
|
|
27
26
|
uint104 price;
|
|
@@ -30,7 +29,6 @@ struct JB721Tier {
|
|
|
30
29
|
uint104 votingUnits;
|
|
31
30
|
uint16 reserveFrequency;
|
|
32
31
|
address reserveBeneficiary;
|
|
33
|
-
// forge-lint: disable-next-line(mixed-case-variable)
|
|
34
32
|
bytes32 encodedIPFSUri;
|
|
35
33
|
uint24 category;
|
|
36
34
|
uint8 discountPercent;
|
|
@@ -23,14 +23,12 @@ import {JB721TierConfigFlags} from "./JB721TierConfigFlags.sol";
|
|
|
23
23
|
/// an NFT from this tier is minted. Out of `JBConstants.SPLITS_TOTAL_PERCENT`.
|
|
24
24
|
/// @custom:member splits The splits to use for this tier's split group. These define where the split portion of the
|
|
25
25
|
/// tier's price gets routed when an NFT from this tier is minted.
|
|
26
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
27
26
|
struct JB721TierConfig {
|
|
28
27
|
uint104 price;
|
|
29
28
|
uint32 initialSupply;
|
|
30
29
|
uint32 votingUnits;
|
|
31
30
|
uint16 reserveFrequency;
|
|
32
31
|
address reserveBeneficiary;
|
|
33
|
-
// forge-lint: disable-next-line(mixed-case-variable)
|
|
34
32
|
bytes32 encodedIPFSUri;
|
|
35
33
|
uint24 category;
|
|
36
34
|
uint8 discountPercent;
|
|
@@ -14,7 +14,6 @@ pragma solidity ^0.8.0;
|
|
|
14
14
|
/// @custom:member cantIncreaseDiscountPercent If the tier cannot have its discount increased.
|
|
15
15
|
/// @custom:member cantBuyWithCredits If true, this tier cannot be purchased using accumulated pay credits. Only fresh
|
|
16
16
|
/// payment value counts toward this tier's price.
|
|
17
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
18
17
|
struct JB721TierConfigFlags {
|
|
19
18
|
bool allowOwnerMint;
|
|
20
19
|
bool useReserveBeneficiaryAsDefault;
|
|
@@ -7,7 +7,6 @@ pragma solidity ^0.8.0;
|
|
|
7
7
|
/// @custom:member cantBeRemoved A boolean indicating whether attempts to remove this tier will revert.
|
|
8
8
|
/// @custom:member cantIncreaseDiscountPercent If the tier cannot have its discount increased.
|
|
9
9
|
/// @custom:member cantBuyWithCredits If true, this tier cannot be purchased using accumulated pay credits.
|
|
10
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
11
10
|
struct JB721TierFlags {
|
|
12
11
|
bool allowOwnerMint;
|
|
13
12
|
bool transfersPausable;
|
|
@@ -11,7 +11,6 @@ pragma solidity ^0.8.0;
|
|
|
11
11
|
/// the NFTs being minted will revert.
|
|
12
12
|
/// @custom:member issueTokensForSplits A boolean indicating whether payers receive token credit for the portion of
|
|
13
13
|
/// their payment that is routed to tier splits. When false (default), weight is reduced proportionally.
|
|
14
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
15
14
|
struct JB721TiersHookFlags {
|
|
16
15
|
bool noNewTiersWithReserves;
|
|
17
16
|
bool noNewTiersWithVotes;
|
|
@@ -3,7 +3,6 @@ pragma solidity ^0.8.0;
|
|
|
3
3
|
|
|
4
4
|
/// @custom:member tierId The ID of the tier to mint from.
|
|
5
5
|
/// @custom:member count The number of NFTs to mint from that tier.
|
|
6
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
7
6
|
struct JB721TiersMintReservesConfig {
|
|
8
7
|
uint32 tierId;
|
|
9
8
|
uint16 count;
|
|
@@ -6,7 +6,6 @@ pragma solidity ^0.8.0;
|
|
|
6
6
|
/// @custom:member pauseTransfers A boolean indicating whether NFT transfers are paused during this ruleset.
|
|
7
7
|
/// @custom:member pauseMintPendingReserves A boolean indicating whether pending/outstanding NFT reserves can be minted
|
|
8
8
|
/// during this ruleset.
|
|
9
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
10
9
|
struct JB721TiersRulesetMetadata {
|
|
11
10
|
bool pauseTransfers;
|
|
12
11
|
bool pauseMintPendingReserves;
|
|
@@ -3,7 +3,6 @@ pragma solidity ^0.8.0;
|
|
|
3
3
|
|
|
4
4
|
/// @custom:member tierId The ID of the tier to set the discount percent for.
|
|
5
5
|
/// @custom:member discountPercent The discount percent to set for the tier.
|
|
6
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
7
6
|
struct JB721TiersSetDiscountPercentConfig {
|
|
8
7
|
uint32 tierId;
|
|
9
8
|
uint16 discountPercent;
|
|
@@ -5,7 +5,6 @@ pragma solidity ^0.8.0;
|
|
|
5
5
|
/// `JBBitmap` matrix is a "word".
|
|
6
6
|
/// @custom:member The information stored at the index.
|
|
7
7
|
/// @custom:member The index.
|
|
8
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
9
8
|
struct JBBitmapWord {
|
|
10
9
|
uint256 currentWord;
|
|
11
10
|
uint256 currentDepth;
|
|
@@ -12,7 +12,6 @@ import {IJB721TokenUriResolver} from "../interfaces/IJB721TokenUriResolver.sol";
|
|
|
12
12
|
/// @custom:member contractUri The URI where this contract's metadata can be found.
|
|
13
13
|
/// @custom:member tiersConfig The NFT tiers and pricing config to launch the hook with.
|
|
14
14
|
/// @custom:member flags A set of boolean options to configure the hook with.
|
|
15
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
16
15
|
struct JBDeploy721TiersHookConfig {
|
|
17
16
|
string name;
|
|
18
17
|
string symbol;
|
|
@@ -10,7 +10,6 @@ import {JBPayDataHookRulesetConfig} from "./JBPayDataHookRulesetConfig.sol";
|
|
|
10
10
|
/// @custom:member rulesetConfigurations The ruleset configurations to queue.
|
|
11
11
|
/// @custom:member terminalConfigurations The terminal configurations to add for the project.
|
|
12
12
|
/// @custom:member memo A memo to pass along to the emitted event.
|
|
13
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
14
13
|
struct JBLaunchProjectConfig {
|
|
15
14
|
string projectUri;
|
|
16
15
|
JBPayDataHookRulesetConfig[] rulesetConfigurations;
|
|
@@ -9,7 +9,6 @@ import {JBPayDataHookRulesetConfig} from "./JBPayDataHookRulesetConfig.sol";
|
|
|
9
9
|
/// @custom:member rulesetConfigurations The ruleset configurations to queue.
|
|
10
10
|
/// @custom:member terminalConfigurations The terminal configurations to add for the project.
|
|
11
11
|
/// @custom:member memo A memo to pass along to the emitted event.
|
|
12
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
13
12
|
struct JBLaunchRulesetsConfig {
|
|
14
13
|
uint56 projectId;
|
|
15
14
|
JBPayDataHookRulesetConfig[] rulesetConfigurations;
|
|
@@ -32,7 +32,6 @@ import {JBPayDataHookRulesetMetadata} from "./JBPayDataHookRulesetMetadata.sol";
|
|
|
32
32
|
/// its balance in each payment terminal while the ruleset is active. Amounts are fixed point numbers using the same
|
|
33
33
|
/// number of decimals as the corresponding terminal. The `payoutLimit` and `surplusAllowance` parameters must fit in
|
|
34
34
|
/// a `uint232`.
|
|
35
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
36
35
|
struct JBPayDataHookRulesetConfig {
|
|
37
36
|
uint48 mustStartAtOrAfter;
|
|
38
37
|
uint32 duration;
|
|
@@ -28,7 +28,6 @@ pragma solidity ^0.8.0;
|
|
|
28
28
|
/// during
|
|
29
29
|
/// this ruleset.
|
|
30
30
|
/// @custom:member metadata Metadata of the metadata, up to uint8 in size.
|
|
31
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
32
31
|
struct JBPayDataHookRulesetMetadata {
|
|
33
32
|
uint16 reservedPercent;
|
|
34
33
|
uint16 cashOutTaxRate;
|
|
@@ -6,7 +6,6 @@ import {JBPayDataHookRulesetConfig} from "./JBPayDataHookRulesetConfig.sol";
|
|
|
6
6
|
/// @custom:member projectId The ID of the project to queue rulesets for.
|
|
7
7
|
/// @custom:member rulesetConfigurations The ruleset configurations to queue.
|
|
8
8
|
/// @custom:member memo A memo to pass along to the emitted event.
|
|
9
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
10
9
|
struct JBQueueRulesetsConfig {
|
|
11
10
|
uint56 projectId;
|
|
12
11
|
JBPayDataHookRulesetConfig[] rulesetConfigurations;
|
|
@@ -13,7 +13,6 @@ pragma solidity ^0.8.0;
|
|
|
13
13
|
/// purchased.
|
|
14
14
|
/// @custom:member packedBools Packed boolean flags: allowOwnerMint, transfersPausable, useVotingUnits,
|
|
15
15
|
/// cantBeRemoved, cantIncreaseDiscountPercent, cantBuyWithCredits.
|
|
16
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
17
16
|
struct JBStored721Tier {
|
|
18
17
|
uint104 price;
|
|
19
18
|
uint32 remainingSupply;
|