@bananapus/721-hook-v6 0.0.29 → 0.0.31
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 +38 -11
- package/ARCHITECTURE.md +53 -99
- package/AUDIT_INSTRUCTIONS.md +84 -383
- package/CHANGELOG.md +71 -0
- package/README.md +80 -225
- package/RISKS.md +32 -11
- package/SKILLS.md +29 -296
- package/STYLE_GUIDE.md +57 -18
- package/USER_JOURNEYS.md +57 -501
- package/package.json +4 -4
- package/references/operations.md +28 -0
- package/references/runtime.md +32 -0
- package/script/Deploy.s.sol +5 -4
- package/src/JB721TiersHook.sol +4 -2
- package/src/JB721TiersHookDeployer.sol +1 -1
- package/src/JB721TiersHookProjectDeployer.sol +1 -1
- package/src/JB721TiersHookStore.sol +24 -25
- package/src/libraries/JB721Constants.sol +1 -1
- package/src/libraries/JB721TiersHookLib.sol +39 -12
- package/src/libraries/JB721TiersRulesetMetadataResolver.sol +1 -1
- package/src/libraries/JBBitmap.sol +1 -1
- package/src/libraries/JBIpfsDecoder.sol +1 -1
- package/src/structs/JB721Tier.sol +5 -11
- package/src/structs/JB721TierConfig.sol +5 -20
- package/src/structs/JB721TierConfigFlags.sol +26 -0
- package/src/structs/JB721TierFlags.sol +17 -0
- package/test/721HookAttacks.t.sol +22 -17
- package/test/E2E/Pay_Mint_Redeem_E2E.t.sol +19 -14
- package/test/Fork.t.sol +69 -54
- package/test/TestAuditGaps.sol +73 -56
- package/test/TestSafeTransferReentrancy.t.sol +4 -4
- package/test/TestVotingUnitsLifecycle.t.sol +11 -11
- package/test/audit/CodexPayCreditsBypassTierSplits.t.sol +10 -7
- package/test/audit/CodexSplitCreditsMismatch.t.sol +10 -7
- package/test/fork/ERC20CashOutFork.t.sol +37 -28
- package/test/fork/ERC20TierSplitFork.t.sol +28 -21
- package/test/fork/IssueTokensForSplitsFork.t.sol +10 -7
- package/test/invariants/handlers/TierLifecycleHandler.sol +10 -7
- package/test/invariants/handlers/TierStoreHandler.sol +10 -7
- package/test/regression/ProjectDeployerRulesets.t.sol +10 -7
- package/test/regression/ReserveBeneficiaryOverwrite.t.sol +6 -6
- package/test/unit/AuditFixes_Unit.t.sol +39 -30
- package/test/unit/adjustTier_Unit.t.sol +268 -202
- package/test/unit/getters_constructor_Unit.t.sol +20 -14
- package/test/unit/mintFor_mintReservesFor_Unit.t.sol +2 -2
- package/test/unit/pay_Unit.t.sol +1 -1
- package/CHANGE_LOG.md +0 -359
package/ADMINISTRATION.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
Admin privileges and their scope in nana-721-hook-v6.
|
|
4
4
|
|
|
5
|
+
## At A Glance
|
|
6
|
+
|
|
7
|
+
| Item | Details |
|
|
8
|
+
|------|---------|
|
|
9
|
+
| Scope | Per-project NFT administration for tier configuration, metadata, discounts, owner mints, and hook deployment. |
|
|
10
|
+
| Operators | The resolved hook owner via `JBOwnable`, project-scoped delegates through `JBPermissions`, terminals, and the deployer/store contracts. |
|
|
11
|
+
| Highest-risk actions | Adjusting tiers after launch, changing metadata or discounts that affect sale behavior, and deploying or initializing the wrong hook clone. |
|
|
12
|
+
| Recovery posture | Clone-level mistakes are usually fixed by deploying a new hook and moving future rulesets to it; immutable constructor references cannot be changed in place. |
|
|
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.
|
|
20
|
+
|
|
21
|
+
## One-Way Or High-Risk Actions
|
|
22
|
+
|
|
23
|
+
- Clone initialization is one-time, and immutable constructor references on the implementation cannot be changed afterward.
|
|
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.
|
|
26
|
+
|
|
27
|
+
## Recovery Notes
|
|
28
|
+
|
|
29
|
+
- If a hook is initialized with the wrong immutable dependencies or ownership model, deploy a new hook and migrate future project rulesets to it.
|
|
30
|
+
- If a project-specific configuration goes bad, prefer moving the project to a replacement hook over trying to retrofit behavior the hook was not designed to support.
|
|
31
|
+
|
|
5
32
|
## Roles
|
|
6
33
|
|
|
7
34
|
### Hook Owner (JBOwnable)
|
|
@@ -35,7 +62,7 @@ Admin privileges and their scope in nana-721-hook-v6.
|
|
|
35
62
|
| Function | Permission ID | Checked Against | What It Does |
|
|
36
63
|
|----------|--------------|-----------------|--------------|
|
|
37
64
|
| `adjustTiers()` | `ADJUST_721_TIERS` | `owner()` | Adds new tiers and/or soft-removes existing tiers. Sets tier split groups in JBSplits. |
|
|
38
|
-
| `mintFor()` | `MINT_721` | `owner()` | Manually mints NFTs from tiers that have `allowOwnerMint` enabled. Bypasses price checks (passes `type(uint256).max` as amount). |
|
|
65
|
+
| `mintFor()` | `MINT_721` | `owner()` | Manually mints NFTs from tiers that have `flags.allowOwnerMint` enabled. Bypasses price checks (passes `type(uint256).max` as amount). |
|
|
39
66
|
| `setDiscountPercentOf()` | `SET_721_DISCOUNT_PERCENT` | `owner()` | Sets the discount percentage for a single tier. |
|
|
40
67
|
| `setDiscountPercentsOf()` | `SET_721_DISCOUNT_PERCENT` | `owner()` | Batch-sets discount percentages for multiple tiers. |
|
|
41
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. |
|
|
@@ -46,8 +73,8 @@ Admin privileges and their scope in nana-721-hook-v6.
|
|
|
46
73
|
| Function | Permission ID | Checked Against | What It Does |
|
|
47
74
|
|----------|--------------|-----------------|--------------|
|
|
48
75
|
| `launchProjectFor()` | None | Anyone can call | Creates a new project with a 721 hook. Ownership goes to the specified `owner` address. |
|
|
49
|
-
| `launchRulesetsFor()` | `QUEUE_RULESETS` + `SET_TERMINALS` | Project NFT owner | Deploys a hook and launches rulesets for an existing project. |
|
|
50
|
-
| `queueRulesetsOf()` | `QUEUE_RULESETS` | Project NFT owner | Deploys a hook and queues rulesets for an existing project. |
|
|
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. |
|
|
51
78
|
|
|
52
79
|
### JB721TiersHookDeployer
|
|
53
80
|
|
|
@@ -67,14 +94,14 @@ Admin privileges and their scope in nana-721-hook-v6.
|
|
|
67
94
|
| Function | Caller | What It Does |
|
|
68
95
|
|----------|--------|--------------|
|
|
69
96
|
| `recordAddTiers()` | Hook contract | Adds tiers to the caller's namespace. Category sort order enforced. |
|
|
70
|
-
| `recordRemoveTierIds()` | Hook contract | Marks tiers as removed in bitmap. Respects `
|
|
97
|
+
| `recordRemoveTierIds()` | Hook contract | Marks tiers as removed in bitmap. Respects `flags.cantBeRemoved` flag. |
|
|
71
98
|
| `recordMint()` | Hook contract | Records mints, decrements supply, enforces price and reserve checks. |
|
|
72
99
|
| `recordMintReservesFor()` | Hook contract | Mints reserved NFTs from a tier. |
|
|
73
100
|
| `recordBurn()` | Hook contract | Increments burn counter for token IDs. |
|
|
74
101
|
| `recordFlags()` | Hook contract | Sets behavioral flags for the caller's hook. |
|
|
75
102
|
| `recordSetTokenUriResolver()` | Hook contract | Sets the token URI resolver. |
|
|
76
103
|
| `recordSetEncodedIPFSUriOf()` | Hook contract | Sets the encoded IPFS URI for a tier. |
|
|
77
|
-
| `recordSetDiscountPercentOf()` | Hook contract | Updates a tier's discount percent. Enforces bounds and `
|
|
104
|
+
| `recordSetDiscountPercentOf()` | Hook contract | Updates a tier's discount percent. Enforces bounds and `flags.cantIncreaseDiscountPercent`. |
|
|
78
105
|
| `recordTransferForTier()` | Hook contract | Updates per-tier balance tracking on transfer. |
|
|
79
106
|
| `cleanTiers()` | Anyone | Reorganizes the tier sorting linked list to skip removed tiers. Pure bookkeeping, no value at risk. |
|
|
80
107
|
|
|
@@ -116,8 +143,8 @@ The following are set at deploy/initialization time and **cannot be changed afte
|
|
|
116
143
|
| `PROJECT_ID` | `initialize()` | Which project this hook belongs to |
|
|
117
144
|
| Pricing context (currency, decimals) | `initialize()` | Packed into `_packedPricingContext` -- the token denomination for tier prices |
|
|
118
145
|
| `JB721TiersHookFlags` | `initialize()` | `noNewTiersWithReserves`, `noNewTiersWithVotes`, `noNewTiersWithOwnerMinting`, `preventOverspending`, `issueTokensForSplits` |
|
|
119
|
-
| Per-tier `
|
|
120
|
-
| Per-tier `
|
|
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 |
|
|
121
148
|
| Per-tier `reserveFrequency` | `recordAddTiers()` | How often reserve NFTs accrue |
|
|
122
149
|
| Per-tier `initialSupply` | `recordAddTiers()` | Maximum number of NFTs mintable from the tier |
|
|
123
150
|
| Per-tier `price` | `recordAddTiers()` | The base price (and cash-out weight) of NFTs in the tier |
|
|
@@ -139,7 +166,7 @@ Two behaviors are controlled by the project's current ruleset metadata (packed i
|
|
|
139
166
|
|
|
140
167
|
| Bit | Flag | Effect |
|
|
141
168
|
|-----|------|--------|
|
|
142
|
-
| 0 | `transfersPaused` | When set, NFT transfers are blocked for tiers that have `transfersPausable` enabled |
|
|
169
|
+
| 0 | `transfersPaused` | When set, NFT transfers are blocked for tiers that have `flags.transfersPausable` enabled |
|
|
143
170
|
| 1 | `mintPendingReservesPaused` | When set, `mintPendingReservesFor()` reverts |
|
|
144
171
|
|
|
145
172
|
These can change each ruleset cycle, giving the project owner temporary control over these behaviors without modifying the hook itself.
|
|
@@ -152,9 +179,9 @@ What the hook owner **cannot** do:
|
|
|
152
179
|
- **Cannot change tier prices after creation.** The `price` field in `JBStored721Tier` is set once in `recordAddTiers()` and never modified. Cash-out weight is always based on the original price.
|
|
153
180
|
- **Cannot change reserve frequency after creation.** The `reserveFrequency` is immutable per tier.
|
|
154
181
|
- **Cannot reduce a tier's initial supply.** Supply can only decrease through minting and burning.
|
|
155
|
-
- **Cannot remove a tier marked `
|
|
156
|
-
- **Cannot increase a tier's discount if `
|
|
157
|
-
- **Cannot mint from tiers without `allowOwnerMint`.** The `mintFor()` function passes `isOwnerMint: true` to the store, which checks the flag.
|
|
182
|
+
- **Cannot remove a tier marked `flags.cantBeRemoved`.** The store enforces this in `recordRemoveTierIds()`.
|
|
183
|
+
- **Cannot increase a tier's discount if `flags.cantIncreaseDiscountPercent` is set.** The store enforces this in `recordSetDiscountPercentOf()`.
|
|
184
|
+
- **Cannot mint from tiers without `flags.allowOwnerMint`.** The `mintFor()` function passes `isOwnerMint: true` to the store, which checks the flag.
|
|
158
185
|
- **Cannot re-initialize a hook.** The `initialize()` function reverts if `_initialized` is already true.
|
|
159
186
|
- **Cannot change the pricing currency, decimals, or prices contract.** `PRICES` is immutable (set in constructor), and the currency/decimals in `_packedPricingContext` are set once during initialization.
|
|
160
187
|
- **Cannot bypass the flag restrictions.** Once `noNewTiersWithReserves`, `noNewTiersWithVotes`, or `noNewTiersWithOwnerMinting` are set, all future tiers added via `adjustTiers()` must comply.
|
package/ARCHITECTURE.md
CHANGED
|
@@ -1,122 +1,76 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Architecture
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
NFT
|
|
5
|
+
`nana-721-hook-v6` adds tiered NFT behavior to Juicebox projects. It lets a project accept payments, mint NFTs from configured tiers, optionally accumulate NFT credits, lazily mint reserves, and, when enabled, let holders burn NFTs to cash out project surplus according to tier-defined economics.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
with `maxTierId`. In practice, this should be treated as a curated-catalog hook with an explicit operating envelope,
|
|
9
|
-
not as a guarantee that very large catalogs are comfortable to run on-chain.
|
|
7
|
+
## Boundaries
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
- `JB721TiersHook` owns tier-aware behavior.
|
|
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.
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
src/
|
|
15
|
-
├── JB721TiersHook.sol — Pay + cash-out hook that mints/burns tiered NFTs
|
|
16
|
-
├── JB721TiersHookStore.sol — Tier configuration storage and pricing logic
|
|
17
|
-
├── JB721TiersHookDeployer.sol — Deploys hook+store pairs (clone-based)
|
|
18
|
-
├── JB721TiersHookProjectDeployer.sol — Launches project + hook in one transaction
|
|
19
|
-
├── abstract/
|
|
20
|
-
│ ├── JB721Hook.sol — Base ERC-721 + pay/cashout hook integration
|
|
21
|
-
│ └── ERC721.sol — Minimal ERC-721 implementation
|
|
22
|
-
├── libraries/
|
|
23
|
-
│ ├── JB721Constants.sol — Global constants (DISCOUNT_DENOMINATOR)
|
|
24
|
-
│ ├── JB721TiersHookLib.sol — Split calculations, price normalization, weight math, fund distribution
|
|
25
|
-
│ ├── JB721TiersRulesetMetadataResolver.sol — Bit-packed 721 ruleset metadata (transfer/reserve pause flags)
|
|
26
|
-
│ ├── JBBitmap.sol — Bitmap utilities for tier removal tracking
|
|
27
|
-
│ └── JBIpfsDecoder.sol — IPFS CID encoding/decoding for token URIs
|
|
28
|
-
├── interfaces/ — All interfaces (IJB721TiersHook, etc.)
|
|
29
|
-
└── structs/ — Tier config, mint context, cash-out structs
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Key Data Flows
|
|
33
|
-
|
|
34
|
-
### NFT Minting (via Payment)
|
|
35
|
-
```
|
|
36
|
-
User → JBMultiTerminal.pay(metadata)
|
|
37
|
-
→ beforePayRecordedWith()
|
|
38
|
-
→ calculateSplitAmounts(): per-tier split amounts (in tier pricing denomination)
|
|
39
|
-
→ convertAndCapSplitAmounts(): convert to payment token denomination (if currencies differ)
|
|
40
|
-
→ calculateWeight(): adjust weight down by split fraction
|
|
41
|
-
→ JBTerminalStore records payment
|
|
42
|
-
→ afterPayRecordedWith() → _processPayment()
|
|
43
|
-
→ Normalize payment value to tier pricing currency
|
|
44
|
-
→ Decode tier IDs from metadata
|
|
45
|
-
→ For each tier:
|
|
46
|
-
→ Validate: not removed, not paused, supply available
|
|
47
|
-
→ Check price (with optional discount, normalized to tier pricing currency)
|
|
48
|
-
→ Mint NFT to beneficiary
|
|
49
|
-
→ Leftover amount stored as pay credits (revert if overspending not allowed)
|
|
50
|
-
→ Distribute split funds (priority: split.hook > split.projectId > split.beneficiary)
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
#### Split Amount and Price Normalization
|
|
54
|
-
|
|
55
|
-
Tiers can be priced in a different currency than the payment token (e.g. tiers priced in USD while payments arrive in ETH). The split/weight pipeline in `beforePayRecordedWith` resolves this in two steps:
|
|
56
|
-
|
|
57
|
-
1. **`calculateSplitAmounts`** — For each tier the payer wants to mint, looks up its `splitPercent` (the fraction of the tier price that should be routed to the tier's split group rather than into the project treasury). Computes `effectivePrice * splitPercent / SPLITS_TOTAL_PERCENT` for each tier, where `effectivePrice` accounts for any active discount. Returns the total and a per-tier breakdown, all denominated in the **tier pricing currency**.
|
|
58
|
-
|
|
59
|
-
2. **`convertAndCapSplitAmounts`** — If the payment currency differs from the tier pricing currency, converts every per-tier split amount (and the total) into the **payment token denomination** using `JBPrices`, and caps the total at the actual payment value. This is necessary because the terminal will subtract the split amount from the payment value, so both must be in the same unit.
|
|
14
|
+
## Main Components
|
|
60
15
|
|
|
61
|
-
|
|
16
|
+
| Component | Responsibility |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| `JB721TiersHook` | Data hook, pay hook, and cash-out hook for tiered NFTs |
|
|
19
|
+
| `JB721TiersHookStore` | Packed tier storage, mint accounting, reserves, and validation |
|
|
20
|
+
| `JB721Hook`, `ERC721` | Shared NFT machinery and token metadata plumbing |
|
|
21
|
+
| deployers | Clone and launch helpers for hook instances and projects |
|
|
22
|
+
| libraries and structs | Tier math, metadata decoding, flags, and config surfaces |
|
|
62
23
|
|
|
63
|
-
|
|
24
|
+
## Runtime Model
|
|
64
25
|
|
|
65
|
-
###
|
|
26
|
+
### Payment Path
|
|
66
27
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
### NFT Cash Out
|
|
75
|
-
```
|
|
76
|
-
Holder → JBMultiTerminal.cashOutTokensOf()
|
|
77
|
-
→ JB721TiersHook.afterCashOutRecordedWith()
|
|
78
|
-
→ Burn specified NFT token IDs
|
|
79
|
-
→ Each NFT's cash-out weight = tier.price (full price, ignoring discounts)
|
|
80
|
-
→ Total cash-out weight (denominator) = sum of (tier.price * (outstanding + pending)) across all tiers
|
|
81
|
-
→ where outstanding = minted - burned, pending = pending reserve mints
|
|
28
|
+
```text
|
|
29
|
+
terminal payment
|
|
30
|
+
-> data hook inspects metadata and requested tier IDs
|
|
31
|
+
-> hook computes which tiers can be minted and how much value is left over
|
|
32
|
+
-> terminal settles the payment into the project
|
|
33
|
+
-> pay hook mints NFTs and stores any remaining value as credits when allowed
|
|
82
34
|
```
|
|
83
35
|
|
|
84
|
-
###
|
|
85
|
-
```
|
|
86
|
-
Owner → JB721TiersHook.adjustTiers()
|
|
87
|
-
→ Add new tiers (must be sorted by category)
|
|
88
|
-
→ Remove existing tiers (flags, doesn't delete)
|
|
89
|
-
```
|
|
36
|
+
### Reserve Path
|
|
90
37
|
|
|
91
|
-
|
|
38
|
+
```text
|
|
39
|
+
tier purchases accumulate reserve entitlement
|
|
40
|
+
-> reserves are minted lazily via explicit reserve-mint calls
|
|
41
|
+
```
|
|
92
42
|
|
|
93
|
-
|
|
94
|
-
|-------|-----------|---------|
|
|
95
|
-
| Token URI resolver | `IJB721TokenUriResolver` | Custom metadata rendering |
|
|
96
|
-
| Pay hook | `IJBPayHook` | Called after payment recorded |
|
|
97
|
-
| Cash out hook | `IJBCashOutHook` | Called during cash out |
|
|
43
|
+
### Cash-Out Path
|
|
98
44
|
|
|
99
|
-
|
|
45
|
+
```text
|
|
46
|
+
holder burns NFT
|
|
47
|
+
-> data hook can override cash-out count, supply, and tax behavior
|
|
48
|
+
-> reclaim value is based on the tier's original configured price, not any discount used at mint time
|
|
49
|
+
```
|
|
100
50
|
|
|
101
|
-
|
|
51
|
+
## Critical Invariants
|
|
102
52
|
|
|
103
|
-
|
|
53
|
+
- Tiers are an ordered, compact data structure. Category ordering and tier IDs are part of storage semantics, not just metadata.
|
|
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.
|
|
104
58
|
|
|
105
|
-
|
|
59
|
+
## Where Complexity Lives
|
|
106
60
|
|
|
107
|
-
|
|
61
|
+
- The store is compact and efficient, which means seemingly small layout changes have wide effects.
|
|
62
|
+
- Preview behavior, pay behavior, reserve minting, and cash-out behavior all depend on the same tier semantics.
|
|
63
|
+
- Metadata decoding and credit handling create edge cases around partial spends and exact-tier selection.
|
|
108
64
|
|
|
109
|
-
|
|
65
|
+
## Dependencies
|
|
110
66
|
|
|
111
|
-
|
|
67
|
+
- `nana-core-v6` hooks, permissions, controller, and terminal surfaces
|
|
68
|
+
- Optional token URI resolvers such as `banny-retail-v6` and `defifa`
|
|
112
69
|
|
|
113
|
-
|
|
70
|
+
## Safe Change Guide
|
|
114
71
|
|
|
115
|
-
|
|
116
|
-
-
|
|
117
|
-
-
|
|
118
|
-
-
|
|
119
|
-
-
|
|
120
|
-
- `@openzeppelin/contracts` — ERC-721 utils, Ownable
|
|
121
|
-
- `@prb/math` — mulDiv
|
|
122
|
-
- `solady` — LibClone
|
|
72
|
+
- Treat store changes as consensus-level changes for every repo that composes this hook.
|
|
73
|
+
- Preview behavior and live behavior must stay aligned. If a preview lies, integrators misprice payments.
|
|
74
|
+
- Be careful when adding metadata fields; many sibling repos depend on stable encoding conventions.
|
|
75
|
+
- Keep hook-order assumptions explicit when this hook is composed with other data hooks through a wrapper deployer.
|
|
76
|
+
- If you touch reserve logic, also inspect supply math and cash-out denominators.
|