@bannynet/core-v6 0.0.43 → 0.0.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Banny Retail
2
2
 
3
- Banny Retail is an onchain avatar system for Juicebox 721 collections. A body NFT can wear outfit NFTs, use a background NFT, and resolve to a base64 JSON token URI whose image is an onchain SVG.
3
+ `@bannynet/core-v6` is the onchain avatar layer for Juicebox 721 collections. It defines how a body NFT wears outfit NFTs, uses a background NFT, and resolves to a base64 JSON token URI whose image is an onchain SVG.
4
4
 
5
5
 
6
6
  ## Documentation
@@ -13,7 +13,7 @@ Banny Retail is an onchain avatar system for Juicebox 721 collections. A body NF
13
13
  - [AUDIT_INSTRUCTIONS.md](./AUDIT_INSTRUCTIONS.md) — guidance for security reviewers
14
14
  - [SKILLS.md](./SKILLS.md) — repo-specific gotchas and integration notes
15
15
  - [STYLE_GUIDE.md](./STYLE_GUIDE.md) — coding and naming conventions
16
- - [CHANGELOG.md](./CHANGELOG.md) notable changes
16
+ - [CHANGELOG.md](./CHANGELOG.md) - V5 to V6 migration changelog
17
17
 
18
18
  ## Overview
19
19
 
@@ -28,13 +28,13 @@ The main user flows are:
28
28
 
29
29
  Use this repo when you need collection-specific, fully onchain metadata composition on top of Juicebox NFTs. Do not use it as a generic 721 hook. It is an app-layer resolver, not a protocol NFT primitive.
30
30
 
31
- ## Key Contract
31
+ ## Key contract
32
32
 
33
33
  | Contract | Role |
34
34
  | --- | --- |
35
35
  | `Banny721TokenUriResolver` | Resolves metadata, stores equipped accessories, enforces outfit locks, and renders layered SVG output for Banny collections. |
36
36
 
37
- ## Mental Model
37
+ ## Mental model
38
38
 
39
39
  This repo owns three things:
40
40
 
@@ -44,14 +44,14 @@ This repo owns three things:
44
44
 
45
45
  It does not own mint pricing, tier issuance, or treasury accounting.
46
46
 
47
- ## Read These Files First
47
+ ## Read these files first
48
48
 
49
49
  1. `src/Banny721TokenUriResolver.sol`
50
50
  2. `test/DecorateFlow.t.sol`
51
51
  3. `test/OutfitTransferLifecycle.t.sol`
52
52
  4. `nana-721-hook-v6/src/JB721TiersHook.sol` for upstream mint and tier behavior
53
53
 
54
- ## High-Signal Tests
54
+ ## High-signal tests
55
55
 
56
56
  1. `test/DecorateFlow.t.sol`
57
57
  2. `test/OutfitTransferLifecycle.t.sol`
@@ -59,7 +59,7 @@ It does not own mint pricing, tier issuance, or treasury accounting.
59
59
  4. `test/regression/TryTransferFromStrandsAssets.t.sol`
60
60
  5. `test/TestQALastMile.t.sol`
61
61
 
62
- ## Integration Traps
62
+ ## Integration traps
63
63
 
64
64
  - the resolver holds equipped assets, so transfer edge cases matter as much as rendering output
65
65
  - transferred bodies carry their equipped assets, so a new body holder can inherit control of them
@@ -67,7 +67,7 @@ It does not own mint pricing, tier issuance, or treasury accounting.
67
67
  - outfit locks survive body transfers until expiry
68
68
  - metadata quality depends on lazily uploaded asset payloads, not only token state
69
69
 
70
- ## Where State Lives
70
+ ## Where state lives
71
71
 
72
72
  - equipped outfit and background state live in `Banny721TokenUriResolver`
73
73
  - layer rendering and token URI generation live in the same resolver
@@ -96,11 +96,11 @@ Useful scripts:
96
96
  - `npm run deploy:mainnets:drop:1`
97
97
  - `npm run deploy:testnets:drop:1`
98
98
 
99
- ## Deployment Notes
99
+ ## Deployment notes
100
100
 
101
101
  Deployments are handled through Sphinx using the environments configured in `script/Deploy.s.sol`. The resolver is meant to be plugged into a Juicebox 721 hook as that hook's token URI resolver.
102
102
 
103
- ## Repository Layout
103
+ ## Repository layout
104
104
 
105
105
  ```text
106
106
  src/
@@ -115,7 +115,7 @@ script/
115
115
  helpers/
116
116
  ```
117
117
 
118
- ## Risks And Notes
118
+ ## Risks and notes
119
119
 
120
120
  - attached outfits and backgrounds are held by the resolver while equipped
121
121
  - outfit locks are fixed-duration and cannot be shortened once set
@@ -123,7 +123,7 @@ script/
123
123
  - plain `transferFrom` can still create asset-tracking surprises around resolver custody
124
124
  - rendering quality depends on the integrity of uploaded SVG assets
125
125
 
126
- ## For AI Agents
126
+ ## For AI agents
127
127
 
128
128
  - Treat this repo as an app-layer resolver, not as the NFT issuance primitive.
129
129
  - Start with `Banny721TokenUriResolver` and the lifecycle tests before summarizing attachment behavior.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bannynet/core-v6",
3
- "version": "0.0.43",
3
+ "version": "0.0.45",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,7 +19,6 @@
19
19
  "scripts": {
20
20
  "test": "forge test",
21
21
  "coverage": "forge coverage --match-path \"./src/*.sol\" --report lcov --report summary",
22
- "generate:migration": "node ./script/outfit_drop/generate-migration.js",
23
22
  "deploy:mainnets": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/Deploy.s.sol --networks mainnets",
24
23
  "deploy:testnets": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/Deploy.s.sol --networks testnets",
25
24
  "deploy:testnets:drop:1": "source ./.env && npx sphinx propose ./script/Drop1.s.sol --networks testnets",
@@ -27,18 +26,15 @@
27
26
  "artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'banny-core-v6'"
28
27
  },
29
28
  "dependencies": {
30
- "@bananapus/721-hook-v6": "^0.0.65",
31
- "@bananapus/buyback-hook-v6": "^0.0.66",
32
- "@bananapus/core-v6": "^0.0.78",
33
- "@bananapus/router-terminal-v6": "^0.0.60",
34
- "@bananapus/suckers-v6": "^0.0.67",
35
- "@croptop/core-v6": "^0.0.64",
29
+ "@bananapus/721-hook-v6": "^0.0.69",
30
+ "@bananapus/buyback-hook-v6": "^0.0.69",
31
+ "@bananapus/core-v6": "^0.0.82",
32
+ "@bananapus/suckers-v6": "^0.0.72",
36
33
  "@openzeppelin/contracts": "5.6.1",
37
- "@rev-net/core-v6": "^0.0.84",
38
- "keccak": "3.0.4"
34
+ "@rev-net/core-v6": "^0.0.89"
39
35
  },
40
36
  "devDependencies": {
41
- "@bananapus/address-registry-v6": "^0.0.32",
37
+ "@bananapus/address-registry-v6": "^0.0.34",
42
38
  "@sphinx-labs/plugins": "0.33.3"
43
39
  }
44
40
  }
@@ -1,25 +1,25 @@
1
1
  # Banny Operations
2
2
 
3
- ## Content and Deployment Surface
3
+ ## Content and deployment surface
4
4
 
5
5
  - [`script/Deploy.s.sol`](../script/Deploy.s.sol) is the main deployment entry point.
6
6
  - [`script/Drop1.s.sol`](../script/Drop1.s.sol) and [`script/Add.Denver.s.sol`](../script/Add.Denver.s.sol) are the first places to check when a problem is drop-specific rather than core resolver logic.
7
7
  - The resolver's metadata and SVG-content management functions live in [`src/Banny721TokenUriResolver.sol`](../src/Banny721TokenUriResolver.sol), not in separate admin helpers.
8
8
 
9
- ## Change Checklist
9
+ ## Change checklist
10
10
 
11
11
  - If you edit decoration behavior, verify both attachment and return-to-owner paths.
12
12
  - If you edit SVG rendering, re-check default layer injection and non-body preview rendering.
13
13
  - If you edit content upload assumptions, verify hash registration and one-time content storage still match.
14
14
  - If you edit metadata fields, check whether the issue belongs in resolver config or in the upstream hook that points to the resolver.
15
15
 
16
- ## Common Failure Modes
16
+ ## Common failure modes
17
17
 
18
18
  - Visible rendering issue is really stale or missing SVG content rather than code logic.
19
19
  - Resolver is blamed for minting or tier problems that actually live upstream in the 721 hook repo.
20
20
  - Attachment state looks inconsistent because a prior transfer or return failed and the resolver intentionally preserved safety over convenience.
21
21
 
22
- ## Useful Proof Points
22
+ ## Useful proof points
23
23
 
24
24
  - [`test/BannyAttacks.t.sol`](../test/BannyAttacks.t.sol) and [`test/TestRegressionGaps.sol`](../test/TestRegressionGaps.sol) for security-sensitive assumptions.
25
25
  - [`script/Drop1.s.sol`](../script/Drop1.s.sol) and [`script/Add.Denver.s.sol`](../script/Add.Denver.s.sol) when a deployment issue is really a script/config problem.
@@ -1,10 +1,10 @@
1
1
  # Banny Runtime
2
2
 
3
- ## Contract Role
3
+ ## Contract role
4
4
 
5
5
  - [`src/Banny721TokenUriResolver.sol`](../src/Banny721TokenUriResolver.sol) resolves token metadata, stores equipped outfits and backgrounds, enforces outfit locks, and composes layered SVG output for Banny collections.
6
6
 
7
- ## Runtime Path
7
+ ## Runtime path
8
8
 
9
9
  1. The hook calls the resolver for `tokenURI`-style metadata.
10
10
  2. The resolver reads tier and ownership context from the upstream 721 hook.
@@ -12,14 +12,14 @@
12
12
  4. If the token is an outfit or background, it renders a preview-style representation instead.
13
13
  5. During decoration flows, the resolver takes custody of attached items and updates wearer/background mappings.
14
14
 
15
- ## High-Risk Areas
15
+ ## High-risk areas
16
16
 
17
17
  - Attachment custody: equipped items are held by the resolver, so transfer and return behavior matters.
18
18
  - Outfit lock windows: lock duration is part of user-facing state and should not drift unexpectedly.
19
19
  - Rendering composition: layer ordering and default-item behavior affect visible output and must stay deterministic.
20
20
  - Stale attachment cleanup: views intentionally guard against inconsistent attachment state.
21
21
 
22
- ## Tests To Trust First
22
+ ## Tests to trust first
23
23
 
24
24
  - [`test/DecorateFlow.t.sol`](../test/DecorateFlow.t.sol) for the main equip/unequip lifecycle.
25
25
  - [`test/OutfitTransferLifecycle.t.sol`](../test/OutfitTransferLifecycle.t.sol) for custody and return behavior.
@@ -35,30 +35,67 @@ contract Banny721TokenUriResolver is
35
35
  // --------------------------- custom errors ------------------------- //
36
36
  //*********************************************************************//
37
37
 
38
+ /// @notice Thrown when two array arguments that must correspond have differing lengths.
38
39
  error Banny721TokenUriResolver_ArrayLengthMismatch(uint256 firstLength, uint256 secondLength);
40
+
41
+ /// @notice Thrown when the token being decorated is not a banny body tier.
39
42
  error Banny721TokenUriResolver_BannyBodyNotBodyCategory(address hook, uint256 bannyBodyId, uint256 category);
43
+
44
+ /// @notice Thrown when re-locking a banny body would shorten its existing outfit-change lock.
40
45
  error Banny721TokenUriResolver_CantAccelerateTheLock(
41
46
  address hook, uint256 bannyBodyId, uint256 currentLockedUntil, uint256 newLockUntil
42
47
  );
48
+
49
+ /// @notice Thrown when SVG contents have already been stored for the product code.
43
50
  error Banny721TokenUriResolver_ContentsAlreadyStored(uint256 upc);
51
+
52
+ /// @notice Thrown when the provided SVG contents do not match the product code's precommitted hash.
44
53
  error Banny721TokenUriResolver_ContentsMismatch(uint256 upc, bytes32 expectedHash, bytes32 actualHash);
54
+
55
+ /// @notice Thrown when two outfits in the merged set share the same category.
45
56
  error Banny721TokenUriResolver_DuplicateCategory(uint256 category);
57
+
58
+ /// @notice Thrown when an SVG hash has already been stored for the product code.
46
59
  error Banny721TokenUriResolver_HashAlreadyStored(uint256 upc, bytes32 existingHash);
60
+
61
+ /// @notice Thrown when no SVG hash has been stored for the product code.
47
62
  error Banny721TokenUriResolver_HashNotFound(uint256 upc);
63
+
64
+ /// @notice Thrown when a full head and individual head pieces are equipped together.
48
65
  error Banny721TokenUriResolver_HeadAlreadyAdded(uint256 category);
66
+
67
+ /// @notice Thrown when outfit changes are attempted on a banny body whose lock has not yet expired.
49
68
  error Banny721TokenUriResolver_OutfitChangesLocked(address hook, uint256 bannyBodyId, uint256 lockedUntil);
69
+
70
+ /// @notice Thrown when a full suit and individual suit pieces are equipped together.
50
71
  error Banny721TokenUriResolver_SuitAlreadyAdded(uint256 category);
72
+
73
+ /// @notice Thrown when the caller is not authorized to use the background for decoration.
51
74
  error Banny721TokenUriResolver_UnauthorizedBackground(
52
75
  address hook, uint256 backgroundId, address sender, address owner
53
76
  );
77
+
78
+ /// @notice Thrown when the caller does not own the banny body.
54
79
  error Banny721TokenUriResolver_UnauthorizedBannyBody(
55
80
  address hook, uint256 bannyBodyId, address sender, address owner
56
81
  );
82
+
83
+ /// @notice Thrown when the caller is not authorized to use the outfit for decoration.
57
84
  error Banny721TokenUriResolver_UnauthorizedOutfit(address hook, uint256 outfitId, address sender, address owner);
85
+
86
+ /// @notice Thrown when an ERC-721 token is received through an operator other than this contract.
58
87
  error Banny721TokenUriResolver_UnauthorizedTransfer(address operator, address expectedOperator);
88
+
89
+ /// @notice Thrown when outfit categories are not supplied in strictly ascending order.
59
90
  error Banny721TokenUriResolver_UnorderedCategories(uint256 previousCategory, uint256 nextCategory);
91
+
92
+ /// @notice Thrown when the supplied background does not exist or is not a background-category tier.
60
93
  error Banny721TokenUriResolver_UnrecognizedBackground(address hook, uint256 backgroundId, uint256 category);
94
+
95
+ /// @notice Thrown when an outfit's category falls outside the recognized category range.
61
96
  error Banny721TokenUriResolver_UnrecognizedCategory(uint256 category);
97
+
98
+ /// @notice Thrown when no fills are defined for the product code.
62
99
  error Banny721TokenUriResolver_UnrecognizedProduct(uint256 upc);
63
100
 
64
101
  //*********************************************************************//
@@ -72,28 +109,50 @@ contract Banny721TokenUriResolver is
72
109
  /// @notice The duration that banny bodies can be locked for.
73
110
  uint256 private constant _LOCK_DURATION = 7 days;
74
111
 
112
+ /// @notice The tier category for banny bodies, the carrier NFT that wears the other layers.
75
113
  uint8 private constant _BODY_CATEGORY = 0;
114
+ /// @notice The tier category for backgrounds rendered behind the banny body.
76
115
  uint8 private constant _BACKGROUND_CATEGORY = 1;
116
+ /// @notice The tier category for backside accessories rendered behind the body.
77
117
  uint8 private constant _BACKSIDE_CATEGORY = 2;
118
+ /// @notice The tier category for necklace accessories.
78
119
  uint8 private constant _NECKLACE_CATEGORY = 3;
120
+ /// @notice The tier category for full-head accessories that cover the face.
79
121
  uint8 private constant _HEAD_CATEGORY = 4;
122
+ /// @notice The tier category for eye accessories.
80
123
  uint8 private constant _EYES_CATEGORY = 5;
124
+ /// @notice The tier category for glasses accessories.
81
125
  uint8 private constant _GLASSES_CATEGORY = 6;
126
+ /// @notice The tier category for mouth accessories.
82
127
  uint8 private constant _MOUTH_CATEGORY = 7;
128
+ /// @notice The tier category for legs accessories.
83
129
  uint8 private constant _LEGS_CATEGORY = 8;
130
+ /// @notice The tier category for full-suit accessories that cover the body.
84
131
  uint8 private constant _SUIT_CATEGORY = 9;
132
+ /// @notice The tier category for suit-bottom accessories.
85
133
  uint8 private constant _SUIT_BOTTOM_CATEGORY = 10;
134
+ /// @notice The tier category for suit-top accessories.
86
135
  uint8 private constant _SUIT_TOP_CATEGORY = 11;
136
+ /// @notice The tier category for headtop accessories worn above the head.
87
137
  uint8 private constant _HEADTOP_CATEGORY = 12;
138
+ /// @notice The tier category for hand accessories.
88
139
  uint8 private constant _HAND_CATEGORY = 13;
140
+ /// @notice The tier category for special suit accessories.
89
141
  uint8 private constant _SPECIAL_SUIT_CATEGORY = 14;
142
+ /// @notice The tier category for special legs accessories.
90
143
  uint8 private constant _SPECIAL_LEGS_CATEGORY = 15;
144
+ /// @notice The tier category for special head accessories.
91
145
  uint8 private constant _SPECIAL_HEAD_CATEGORY = 16;
146
+ /// @notice The tier category for special banny bodies.
92
147
  uint8 private constant _SPECIAL_BODY_CATEGORY = 17;
93
148
 
149
+ /// @notice The universal product code of the alien banny body.
94
150
  uint8 private constant ALIEN_UPC = 1;
151
+ /// @notice The universal product code of the pink banny body.
95
152
  uint8 private constant PINK_UPC = 2;
153
+ /// @notice The universal product code of the orange banny body.
96
154
  uint8 private constant ORANGE_UPC = 3;
155
+ /// @notice The universal product code of the original banny body.
97
156
  uint8 private constant ORIGINAL_UPC = 4;
98
157
 
99
158
  //*********************************************************************//
@@ -141,12 +200,11 @@ contract Banny721TokenUriResolver is
141
200
  /// @dev Naked Banny bodies are shown only with outfits currently owned by the body owner.
142
201
  /// @dev NOTE: Equipped outfits travel with the banny body NFT on transfer. When a body is transferred,
143
202
  /// the new owner inherits all equipped outfits and can unequip them to receive the outfit NFTs.
144
- // The _attachedOutfitIdsOf array grows with each attachment. Gas cost for operations
145
- // iterating this array increases linearly. In practice, Bannys have a small, bounded number of outfit slots
146
- // (< 20), making gas cost manageable. No explicit cap is needed given the natural slot limit.
147
- // This array may contain stale entries (e.g. outfits transferred away externally). Stale entries are
148
- // filtered at read time via `outfitsOf` and `wearerOf`, which check current ownership/attachment status.
149
- // This lazy reconciliation avoids extra storage writes on every transfer.
203
+ /// @dev The array grows with each attachment, so operations iterating it cost gas linearly. Bannys have a small,
204
+ /// bounded number of outfit slots (< 20), so no explicit cap is needed given the natural slot limit.
205
+ /// @dev The array may contain stale entries (e.g. outfits transferred away externally). Stale entries are
206
+ /// filtered at read time via `outfitsOf` and `wearerOf`, which check current ownership/attachment status. This
207
+ /// lazy reconciliation avoids extra storage writes on every transfer.
150
208
  /// @custom:param hook The hook address of the collection.
151
209
  /// @custom:param bannyBodyId The ID of the banny body of the outfits.
152
210
  mapping(address hook => mapping(uint256 bannyBodyId => uint256[])) internal _attachedOutfitIdsOf;
@@ -450,9 +508,7 @@ contract Banny721TokenUriResolver is
450
508
  );
451
509
  }
452
510
 
453
- /// @notice Returns the SVG showing either a banny body with/without outfits and a background, or the stand alone
454
- /// outfit
455
- /// or background.
511
+ /// @notice Returns the SVG for a banny body, a standalone outfit, or a standalone background.
456
512
  /// @param hook The hook storing the assets.
457
513
  /// @param tokenId The ID of the token to show. If the ID belongs to a banny body, it will be shown with its
458
514
  /// current outfits in its current background if specified.
@@ -657,9 +713,9 @@ contract Banny721TokenUriResolver is
657
713
  }
658
714
 
659
715
  /// @notice Encode the token URI JSON with base64.
660
- // Metadata strings (name, description, external_url) are set by the contract owner, not by users.
661
- // No JSON escaping is applied the owner is trusted to provide valid values. On-chain JSON is consumed
662
- // by off-chain indexers and UIs, not rendered in a browser context where XSS would apply.
716
+ /// @dev Metadata strings (name, description, external_url) are set by the contract owner, not by users, and no
717
+ /// JSON escaping is applied because the owner is trusted to provide valid values. On-chain JSON is consumed by
718
+ /// off-chain indexers and UIs, not rendered in a browser context where XSS would apply.
663
719
  /// @param tokenId The ID of the token.
664
720
  /// @param product The tier product for the token.
665
721
  /// @param extraMetadata Additional JSON metadata fields to include.
@@ -1115,9 +1171,8 @@ contract Banny721TokenUriResolver is
1115
1171
  ///
1116
1172
  /// @dev WARNING: Equipped outfits and backgrounds are held by this contract on behalf of the banny body. When the
1117
1173
  /// banny body NFT is transferred to a new owner, all equipped assets remain associated with that body. The new
1118
- /// owner of the body effectively gains control of all equipped items they can unequip them (receiving the
1119
- /// outfit
1120
- /// NFTs) or re-equip different items. Sellers should unequip valuable outfits before transferring a banny body.
1174
+ /// owner of the body effectively gains control of all equipped items. They can unequip them, receiving the outfit
1175
+ /// NFTs, or re-equip different items. Sellers should unequip valuable outfits before transferring a banny body.
1121
1176
  ///
1122
1177
  /// @param hook The hook storing the assets.
1123
1178
  /// @param bannyBodyId The ID of the banny body to dress.
@@ -1198,9 +1253,9 @@ contract Banny721TokenUriResolver is
1198
1253
 
1199
1254
  /// @notice Handles the receipt of an ERC-721 token, only accepting transfers initiated by this contract.
1200
1255
  /// @dev Make sure tokens can be received if the transaction was initiated by this contract.
1201
- // NFTs sent via transferFrom (not safeTransferFrom) bypass onERC721Received and cannot be
1202
- // tracked or recovered. This is an inherent ERC-721 limitation — the contract cannot prevent non-safe
1203
- // transfers. Users and UIs should always use safeTransferFrom.
1256
+ /// @dev NFTs sent via transferFrom (not safeTransferFrom) bypass onERC721Received and cannot be tracked or
1257
+ /// recovered. This is an inherent ERC-721 limitation — the contract cannot prevent non-safe transfers. Users and
1258
+ /// UIs should always use safeTransferFrom.
1204
1259
  /// @param operator The address that initiated the transaction.
1205
1260
  /// @param from The address that initiated the transfer.
1206
1261
  /// @param tokenId The ID of the token to transfer.