@croptop/core-v6 0.0.27 → 0.0.29

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 CHANGED
@@ -2,6 +2,33 @@
2
2
 
3
3
  Admin privileges and their scope in croptop-core-v6.
4
4
 
5
+ ## At A Glance
6
+
7
+ | Item | Details |
8
+ |------|---------|
9
+ | Scope | Croptop project deployment, posting-criteria management, publisher permissions, and project burn-lock ownership flows. |
10
+ | Operators | Project owners, hook owners and delegates, the `CTDeployer`, `CTPublisher`, optional `CTProjectOwner`, and the configured sucker registry. |
11
+ | Highest-risk actions | Sending a project NFT to `CTProjectOwner`, misconfiguring posting criteria, or relying on the write-once `dataHookOf` mapping without validating the hook first. |
12
+ | Recovery posture | Ownership burn-lock mistakes are not recoverable in place. Operational fixes usually mean updating criteria if the project is still controlled, or deploying a new project/hook if it is not. |
13
+
14
+ ## Routine Operations
15
+
16
+ - Configure posting criteria before broad publisher access, because `mintFrom()` enforces those rules for every post.
17
+ - Use `claimCollectionOwnershipOf()` when the project should own its hook directly instead of leaving hook control with the deployer path.
18
+ - Treat sucker deployment as a project extension that still depends on the Croptop data-hook proxy remaining correct for the project.
19
+ - Avoid transferring project NFTs to `CTProjectOwner` unless the intention is to burn human control permanently.
20
+
21
+ ## One-Way Or High-Risk Actions
22
+
23
+ - `CTProjectOwner` permanently locks any JBProjects NFT it receives.
24
+ - `dataHookOf[projectId]` is set during deployment and has no later setter.
25
+ - Constructor-time wildcard permissions granted by `CTDeployer` are structural and cannot be revoked from within the deployer.
26
+
27
+ ## Recovery Notes
28
+
29
+ - If posting rules are wrong but the project still controls the hook, fix them through the hook-owner surface.
30
+ - If ownership was accidentally burned into `CTProjectOwner` or the wrong hook path was deployed, recovery generally means abandoning that control path and redeploying the project or hook composition.
31
+
5
32
  ## Roles
6
33
 
7
34
  ### 1. Project Owner
@@ -69,7 +96,7 @@ Admin privileges and their scope in croptop-core-v6.
69
96
 
70
97
  | Function | Required Role | Permission ID | Scope | What It Does |
71
98
  |----------|--------------|---------------|-------|-------------|
72
- | `onERC721Received()` | Anyone who transfers a JBProjects NFT | None | Per-project | On receiving a project NFT from `PROJECTS` (mint only, `from == address(0)` is NOT enforced here), grants `CTPublisher` the `ADJUST_721_TIERS` permission for that project. |
99
+ | `onERC721Received()` | Anyone who transfers a JBProjects NFT | None | Per-project | On receiving a project NFT from `PROJECTS`, grants `CTPublisher` the `ADJUST_721_TIERS` permission for that project. The contract does not restrict this to mint receipts; any transferred JBProjects NFT will be accepted and effectively burn human ownership. |
73
100
 
74
101
  ### Permissions Granted at CTDeployer Construction
75
102
 
@@ -134,7 +161,7 @@ What admins CANNOT do:
134
161
 
135
162
  4. **Project owners cannot disable Croptop posting entirely for a category.** `configurePostingCriteriaFor()` requires `minimumTotalSupply > 0`. The workaround is to set an astronomically high `minimumPrice` with `minimumTotalSupply = maximumTotalSupply = 1`. See finding NM-006.
136
163
 
137
- 5. **Project owners cannot bypass posting criteria to mint directly through CTPublisher.** They must use `mintFrom()` like anyone else, which enforces all configured rules. However, owners can adjust tiers directly on the hook (bypassing CTPublisher) if they have `ADJUST_721_TIERS` permission.
164
+ 5. **Project owners cannot bypass posting criteria through `CTPublisher`, but they may still bypass the publisher surface entirely.** `mintFrom()` enforces all configured rules. Separately, the initial owner/operator can hold direct hook-management permissions from `CTDeployer`, which lets them adjust tiers or mint without going through `CTPublisher` until ownership is claimed away or permissions are narrowed.
138
165
 
139
166
  6. **CTPublisher cannot mint without paying.** `mintFrom()` requires `msg.value >= totalPrice + fee`. There is no free-mint path through CTPublisher.
140
167
 
@@ -142,6 +169,6 @@ What admins CANNOT do:
142
169
 
143
170
  8. **No admin can modify existing tier prices.** Once a tier is created via `_setupPosts()`, the price is set in the `JB721TiersHookStore`. CTPublisher uses the stored price for fee calculation on subsequent mints (not `post.price`). See H-19 fix.
144
171
 
145
- 9. **No admin can drain CTPublisher funds.** CTPublisher has no `withdraw()` function and no `receive()` / `fallback()`. The only ETH that enters the contract is during `mintFrom()` and it is fully routed to the project terminal and fee terminal (or fallback recipients) within the same transaction. The fee terminal payment is wrapped in try-catch with fallback to `feeBeneficiary` then `msg.sender`, so ETH is never stranded by a fee terminal failure.
172
+ 9. **No admin can drain CTPublisher funds.** CTPublisher has no `withdraw()` function and no `receive()` / `fallback()`. The only ETH that enters the contract is during `mintFrom()` and it is fully routed to the project terminal and fee terminal (or refunded to the caller if the fee terminal reverts) within the same transaction. If that refund also fails, the mint reverts rather than trapping ETH in the publisher.
146
173
 
147
174
  10. **Sucker registry trust is irrevocable.** The `MAP_SUCKER_TOKEN` permission is granted at CTDeployer construction with `projectId: 0` (wildcard). There is no function to revoke this permission from within CTDeployer.
package/ARCHITECTURE.md CHANGED
@@ -1,179 +1,71 @@
1
- # croptop-core-v6 — Architecture
1
+ # Architecture
2
2
 
3
3
  ## Purpose
4
4
 
5
- NFT publishing platform built on Juicebox V6. Allows permissioned posting of NFT content to Juicebox projects. CTDeployer creates projects pre-configured for content publishing, and CTPublisher manages the posting workflow with configurable rules (price floors, supply limits, category restrictions).
5
+ `croptop-core-v6` turns a Juicebox project with a 721 tiers hook into a permissioned publishing surface. Project owners define what kinds of posts are allowed, and third parties can mint new content tiers only if their post matches those rules. A fixed fee is routed to a designated fee project on every publish.
6
6
 
7
- ## Contract Map
7
+ ## Boundaries
8
8
 
9
- ```
10
- src/
11
- ├── CTDeployer.sol — Deploys Croptop projects with 721 hooks and publishing rules
12
- ├── CTPublisher.sol — Manages permissioned NFT posting to projects
13
- ├── CTProjectOwner.sol — Proxy owner for Croptop-deployed projects
14
- ├── interfaces/
15
- │ ├── ICTDeployer.sol
16
- │ ├── ICTProjectOwner.sol
17
- │ └── ICTPublisher.sol
18
- └── structs/
19
- ├── CTAllowedPost.sol — Rules for what can be posted
20
- ├── CTDeployerAllowedPost.sol — Deployer-level post rules (no hook field)
21
- ├── CTPost.sol — A post submission
22
- ├── CTProjectConfig.sol — Project configuration
23
- └── CTSuckerDeploymentConfig.sol — Cross-chain config
24
- ```
25
-
26
- ## Key Data Flows
27
-
28
- ### Project Deployment
29
-
30
- ```
31
- Creator → CTDeployer.deployProjectFor(owner, projectConfig, suckerConfig, controller)
32
- 1. Deploy 721 hook via IJB721TiersHookDeployer (empty tiers, ETH currency)
33
- 2. Launch JB project with:
34
- → weight = 1,000,000 * 10^18
35
- → cashOutTaxRate = MAX (100%)
36
- → dataHook = CTDeployer itself (see "Data Hook Behavior" below)
37
- → useDataHookForPay = true, useDataHookForCashOut = true
38
- 3. Store dataHookOf[projectId] = the 721 hook (for pay forwarding)
39
- 4. Configure allowed post rules on CTPublisher
40
- 5. Deploy suckers for cross-chain support (if configured)
41
- 6. Transfer project NFT to the specified owner
42
- 7. Grant owner permissions: ADJUST_721_TIERS, SET_721_METADATA, MINT_721, SET_721_DISCOUNT_PERCENT
43
- ```
44
-
45
- ### Content Publishing (mintFrom)
46
-
47
- ```
48
- Publisher → CTPublisher.mintFrom(hook, posts[], nftBeneficiary, feeBeneficiary, ...)
49
- → _setupPosts: for each post:
50
- 1. Reject empty encodedIPFSUri
51
- 2. Reject duplicate encodedIPFSUri within the batch
52
- 3. If tier already exists for this encodedIPFSUri:
53
- → Use existing tier ID, add tier's price to totalPrice
54
- → (Prevents fee evasion by using actual on-chain price, not user-supplied)
55
- 4. If tier is new:
56
- → Load allowance for (hook, category) — see "Allowed Post Rules" below
57
- → Validate: category enabled, price >= minimum, supply in range,
58
- splitPercent <= maximum, caller in allowlist (if restricted)
59
- → Create JB721TierConfig with the post's price, supply, category,
60
- splitPercent, and splits array
61
- → Record tierIdForEncodedIPFSUriOf mapping
62
- → Add post.price to totalPrice
63
- → Calculate fee: totalPrice / FEE_DIVISOR (5% fee, FEE_DIVISOR = 20)
64
- Fee is skipped when projectId == FEE_PROJECT_ID
65
- → adjustTiers on the 721 hook to add new tiers
66
- → Pay the project terminal: payValue = msg.value - fee
67
- Metadata encodes tier IDs to mint, so the 721 hook mints one NFT per post
68
- → Pay pre-computed fee to FEE_PROJECT_ID terminal (try-catch; falls back to feeBeneficiary, then msg.sender)
69
- ```
70
-
71
- ### Allowed Post Rules
72
-
73
- Each category on each 721 hook has an allowance stored as bit-packed values in `_packedAllowanceFor[hook][category]`. The project owner configures these via `configurePostingCriteriaFor`.
74
-
75
- | Field | Type | Bits | Purpose |
76
- |-------|------|------|---------|
77
- | `minimumPrice` | `uint104` | 0-103 | Floor price per NFT. Posts below this revert. |
78
- | `minimumTotalSupply` | `uint32` | 104-135 | Minimum editions. Must be >= 1; a zero value means the category is disabled. |
79
- | `maximumTotalSupply` | `uint32` | 136-167 | Maximum editions. Must be >= minimumTotalSupply. |
80
- | `maximumSplitPercent` | `uint32` | 168-199 | Cap on the publisher's split (out of `SPLITS_TOTAL_PERCENT = 1,000,000,000`). 0 means no splits allowed. |
81
- | `allowedAddresses` | `address[]` | separate storage | If non-empty, only these addresses may post in this category. Empty means anyone can post. |
82
-
83
- Validation order in `_setupPosts`: category enabled (minimumTotalSupply != 0) -> price check -> supply range check -> split percent cap -> allowlist check.
9
+ - `CTPublisher` owns publishing policy and fee routing.
10
+ - `CTDeployer` owns one-shot project creation and optional sucker setup.
11
+ - The underlying 721 tier implementation remains in `nana-721-hook-v6`.
12
+ - The repo does not reimplement terminal accounting; payments still settle through Juicebox terminals.
84
13
 
85
- Categories cannot be fully removed after creation. This is by design -- once a category exists, removing posting would break expectations for existing posters. Projects can set restrictive allowance configurations to effectively disable new posts.
14
+ ## Main Components
86
15
 
87
- ## Data Hook Behavior
16
+ | Component | Responsibility |
17
+ | --- | --- |
18
+ | `CTPublisher` | Validates posts, creates or reuses tiers, mints the first copy, and routes publish fees |
19
+ | `CTDeployer` | Launches a Juicebox project plus hook configuration in one transaction and can proxy hook callbacks |
20
+ | `CTProjectOwner` | Burn-lock helper that permanently delegates tier adjustment authority to Croptop |
21
+ | `CTAllowedPost`, `CTPost`, related structs | Encode project-level publishing policy and publish requests |
88
22
 
89
- CTDeployer registers itself as the ruleset's `dataHook` so it can intercept both payments and cash-outs. It acts as a transparent proxy that adds sucker-awareness:
23
+ ## Runtime Model
90
24
 
91
- **`beforePayRecordedWith`**: Passthrough with null check. If `dataHookOf[projectId]` is `address(0)`, returns the context weight and empty hook specifications (defaults). Otherwise, forwards the call to the stored data hook (the project's 721 hook). The 721 hook returns the weight and pay hook specifications that handle NFT minting. CTDeployer does not modify pay behavior.
25
+ ### Publishing
92
26
 
93
- **`beforeCashOutRecordedWith`**: Checks if the `holder` is a sucker for the project (via `SUCKER_REGISTRY.isSuckerOf`). If yes, returns `cashOutTaxRate = 0` with no hook specifications -- suckers cash out without any tax. If no, forwards to `dataHookOf[projectId]` for standard cash-out behavior.
94
-
95
- **`hasMintPermissionFor`**: Returns `true` only for addresses that are suckers for the project. This allows suckers to mint tokens on-demand during cross-chain bridging.
96
-
97
- ```
98
- Payment path: CTDeployer.beforePayRecordedWith 721 hook (passthrough)
99
- Cash-out (sucker): CTDeployer.beforeCashOutRecordedWith return 0% tax
100
- Cash-out (normal): CTDeployer.beforeCashOutRecordedWith 721 hook (forward)
101
- Mint permission: Only suckers get on-demand mint permission
27
+ ```text
28
+ poster
29
+ -> mintFrom(hook, posts, ...)
30
+ -> publisher validates each post against project-defined criteria
31
+ -> publisher calls the 721 hook to create or reuse tiers
32
+ -> project terminal receives the publish payment
33
+ -> fee project receives the fixed fee slice, or `_msgSender()` is refunded that fee if the fee terminal rejects it
34
+ -> first copy of each created tier is minted to the poster
102
35
  ```
103
36
 
104
- ## Ownership Model
105
-
106
- ### Why a Proxy Owner?
107
-
108
- When CTDeployer creates a project, it initially owns the project NFT (because `launchProjectFor` mints to `msg.sender`). CTDeployer needs to be the initial owner so it can:
109
- 1. Configure posting criteria on CTPublisher (requires `ADJUST_721_TIERS` permission from the hook's owner, which is CTDeployer).
110
- 2. Deploy suckers (requires project ownership).
111
- 3. Grant the final owner NFT management permissions.
112
-
113
- After setup, CTDeployer transfers the project NFT to the specified `owner`. The 721 hook's ownership stays with CTDeployer, which has granted `ADJUST_721_TIERS` permission to CTPublisher with `projectId = 0` (wildcard). This means CTPublisher can add tiers to any Croptop-deployed project without further permission grants.
114
-
115
- ### CTProjectOwner: The Immutable-Rules Pattern
116
-
117
- `CTProjectOwner` is a separate contract that serves as a "lockbox" for projects that want immutable posting rules. When the project owner transfers their project NFT to a CTProjectOwner instance:
118
-
119
- 1. `onERC721Received` fires and grants CTPublisher the `ADJUST_721_TIERS` permission for that project.
120
- 2. CTProjectOwner exposes no function to reconfigure posting criteria, queue new rulesets, or transfer the project further.
121
- 3. The posting rules become permanently frozen -- content can still be published under the existing rules, but the rules themselves cannot change.
122
-
123
- This enables a trust model: creators can prove to publishers that the rules (price floors, supply caps, split percentages) will never change, providing a credible commitment that the economic terms are permanent.
124
-
125
- ### Claiming Collection Ownership
126
-
127
- `CTDeployer.claimCollectionOwnershipOf(hook)` allows the project NFT holder to take direct ownership of the 721 hook by calling `JBOwnable.transferOwnershipToProject(projectId)`. After this, the hook's owner resolves to whoever holds the project NFT. The caller must then independently grant CTPublisher the `ADJUST_721_TIERS` permission, or subsequent posts will revert.
128
-
129
- ## Publisher Fee and Split Mechanics
130
-
131
- ### Fee Structure
132
-
133
- CTPublisher charges a 5% fee (`FEE_DIVISOR = 20`) on the total price of all posts in a `mintFrom` call. The fee is skipped when the target project is the fee project itself (`FEE_PROJECT_ID`).
37
+ ### Project Launch
134
38
 
39
+ ```text
40
+ creator
41
+ -> CTDeployer launches the project, hook, posting criteria, and optional suckers
42
+ -> deployer can also stand in as a ruleset data-hook wrapper when needed
135
43
  ```
136
- msg.value breakdown:
137
- totalPrice → paid to the project's terminal (mints NFTs + project tokens)
138
- totalPrice/20 → paid to FEE_PROJECT_ID's terminal (Croptop platform fee)
139
- remainder → reverts if insufficient, excess stays as overpayment
140
- ```
141
-
142
- ### Publisher Split Mechanism
143
-
144
- When a publisher creates a new post, they can set a `splitPercent` and a `splits` array on their `CTPost`. These are stored directly on the 721 tier via `JB721TierConfig.splitPercent` and `JB721TierConfig.splits`.
145
-
146
- The split mechanics work at the 721 hook level: whenever someone mints (buys) an NFT from that tier, the 721 hook routes `splitPercent` of the tier's price to the addresses in the `splits` array. The publisher typically sets themselves as a split recipient so they earn a share of every future mint from the tier they created.
147
-
148
- The project owner controls the maximum split a publisher can claim via `maximumSplitPercent` in the allowed post rules. If `maximumSplitPercent` is 0, publishers cannot set any splits. This gives project owners control over how much revenue publishers can capture versus how much flows to the project treasury.
149
44
 
150
- ## Design Decisions
45
+ ## Critical Invariants
151
46
 
152
- **Permissioned publishing over open posting.** Posts are validated against per-category allowance rules rather than allowing anyone to post anything. This prevents spam, ensures minimum economic commitment (price floors), and lets project owners curate their collection's quality by restricting who can post and at what terms.
47
+ - A post is valid only if it satisfies the configured category, price, supply, split, and allowlist constraints.
48
+ - Fee routing must be computed from the payment value, not transient contract balance, so forced ETH cannot distort the fee.
49
+ - Fee routing must not strand ETH in `CTPublisher`. If the fee terminal rejects payment, the fee is refunded to `_msgSender()`; if that refund fails, the mint reverts.
50
+ - `CTProjectOwner` only makes sense as a lock, not a flexible admin layer. Once a project is burn-locked, Croptop becomes the only intended tier-adjustment path.
51
+ - Publishing should not bypass the 721 hook's own invariants around tier creation and minting.
153
52
 
154
- **Publisher gets a split, not a direct payment.** Rather than paying publishers upfront, Croptop uses the 721 hook's split mechanism. The publisher earns a percentage of every future mint from their tier. This aligns incentives: publishers profit when their content is popular enough that others want to mint copies, not just from the act of posting.
53
+ ## Where Complexity Lives
155
54
 
156
- **CTDeployer as data hook proxy.** Instead of requiring projects to use a custom data hook, CTDeployer inserts itself as a transparent proxy. This lets it add sucker-awareness (tax-free cross-chain cash-outs) without requiring the underlying 721 hook to know about suckers. The 721 hook handles NFT minting logic; CTDeployer handles cross-chain policy.
55
+ - Post validation is spread across category rules, split limits, supply bounds, and optional allowlists.
56
+ - `CTDeployer` is subtle because it is both a launch helper and, in some flows, a runtime hook proxy.
57
+ - Fee routing is intentionally liveness-first: it prefers refunding `_msgSender()` over blocking the mint when the fee terminal is down, but still reverts if the refund itself cannot be delivered.
157
58
 
158
- **Immutable rules via CTProjectOwner.** Rather than building immutability into CTPublisher (which would add complexity for all projects), immutability is opt-in: transfer the project to CTProjectOwner and the rules freeze. Projects that want governance flexibility simply keep the project NFT in a wallet or multisig.
159
-
160
- **Category-based organization.** Posts are organized by `category` (uint24), with each category having independent allowance rules. This lets a single project support multiple content types (e.g., category 1 for images at 0.01 ETH, category 2 for music at 0.1 ETH) with different price floors, supply limits, and access controls.
161
-
162
- **Duplicate prevention via encodedIPFSUri mapping.** `tierIdForEncodedIPFSUriOf[hook][encodedIPFSUri]` ensures each piece of content can only create one tier. If a tier already exists, the mint uses the existing tier (at its on-chain price, not the caller-supplied price). This prevents duplicate tiers and fee evasion.
163
-
164
- ## Extension Points
59
+ ## Dependencies
165
60
 
166
- | Point | Interface | Purpose |
167
- |-------|-----------|---------|
168
- | Data hook | `IJBRulesetDataHook` | CTDeployer proxies pay/cash-out hooks, adds sucker-awareness |
169
- | 721 hook | `IJB721TiersHook` | NFT tier management, split routing on mints |
170
- | Publisher | `ICTPublisher` | Content posting workflow and allowance configuration |
61
+ - `nana-721-hook-v6` for tier storage and minting
62
+ - `nana-suckers-v6` and `nana-omnichain-deployers-v6` patterns when omnichain deployment is enabled
63
+ - `nana-core-v6` terminals, permissions, and project ownership
171
64
 
172
- ## Dependencies
65
+ ## Safe Change Guide
173
66
 
174
- - `@bananapus/core-v6` -- Core protocol (terminals, rulesets, permissions, directory)
175
- - `@bananapus/721-hook-v6` -- NFT tier system (tiers, minting, splits)
176
- - `@bananapus/ownable-v6` -- JB-aware ownership (ownership-to-project transfer)
177
- - `@bananapus/permission-ids-v6` -- Permission constants
178
- - `@bananapus/suckers-v6` -- Cross-chain support (sucker registry)
179
- - `@openzeppelin/contracts` -- ERC2771 (meta-transactions), ERC721Receiver
67
+ - Keep publishing policy separate from 721 implementation details. If a change is generally useful for all tiered collections, it belongs downstream.
68
+ - When modifying fee logic, reason through terminal payment ordering and failure fallback paths together.
69
+ - Any change to `CTDeployer` should be reviewed as both a deployer and a live hook wrapper, because it participates in runtime flows after launch.
70
+ - Changes to burn-lock semantics should be treated as governance changes, not UI conveniences.
71
+ - Be wary of adding product-specific tier semantics here that really belong in the generic 721 hook.