@bannynet/core-v6 0.0.43 → 0.0.44
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 +11 -11
- package/package.json +1 -1
- package/references/operations.md +4 -4
- package/references/runtime.md +4 -4
- package/src/Banny721TokenUriResolver.sol +70 -12
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Banny Retail
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
@@ -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
|
|
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
|
|
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
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
package/references/operations.md
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
# Banny Operations
|
|
2
2
|
|
|
3
|
-
## Content and
|
|
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
|
|
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
|
|
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
|
|
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.
|
package/references/runtime.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Banny Runtime
|
|
2
2
|
|
|
3
|
-
## Contract
|
|
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
|
|
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-
|
|
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
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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;
|
|
@@ -657,9 +715,9 @@ contract Banny721TokenUriResolver is
|
|
|
657
715
|
}
|
|
658
716
|
|
|
659
717
|
/// @notice Encode the token URI JSON with base64.
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
718
|
+
/// @dev Metadata strings (name, description, external_url) are set by the contract owner, not by users, and no
|
|
719
|
+
/// JSON escaping is applied because the owner is trusted to provide valid values. On-chain JSON is consumed by
|
|
720
|
+
/// off-chain indexers and UIs, not rendered in a browser context where XSS would apply.
|
|
663
721
|
/// @param tokenId The ID of the token.
|
|
664
722
|
/// @param product The tier product for the token.
|
|
665
723
|
/// @param extraMetadata Additional JSON metadata fields to include.
|
|
@@ -1198,9 +1256,9 @@ contract Banny721TokenUriResolver is
|
|
|
1198
1256
|
|
|
1199
1257
|
/// @notice Handles the receipt of an ERC-721 token, only accepting transfers initiated by this contract.
|
|
1200
1258
|
/// @dev Make sure tokens can be received if the transaction was initiated by this contract.
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1259
|
+
/// @dev NFTs sent via transferFrom (not safeTransferFrom) bypass onERC721Received and cannot be tracked or
|
|
1260
|
+
/// recovered. This is an inherent ERC-721 limitation — the contract cannot prevent non-safe transfers. Users and
|
|
1261
|
+
/// UIs should always use safeTransferFrom.
|
|
1204
1262
|
/// @param operator The address that initiated the transaction.
|
|
1205
1263
|
/// @param from The address that initiated the transfer.
|
|
1206
1264
|
/// @param tokenId The ID of the token to transfer.
|