@bannynet/core-v6 0.0.23 → 0.0.24
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 +14 -14
- package/ARCHITECTURE.md +10 -10
- package/AUDIT_INSTRUCTIONS.md +7 -4
- package/README.md +22 -25
- package/RISKS.md +39 -48
- package/SKILLS.md +6 -6
- package/USER_JOURNEYS.md +44 -23
- package/package.json +8 -8
package/ADMINISTRATION.md
CHANGED
|
@@ -11,21 +11,21 @@
|
|
|
11
11
|
|
|
12
12
|
## Purpose
|
|
13
13
|
|
|
14
|
-
`banny-retail-v6` has a small but real control plane. The resolver owner controls collection-wide metadata and SVG commitments. Body owners control decoration and outfit locks. No admin can rescue equipped NFTs
|
|
14
|
+
`banny-retail-v6` has a small but real control plane. The resolver owner controls collection-wide metadata and SVG commitments. Body owners control decoration and outfit locks. No admin can rescue equipped NFTs if resolver logic fails.
|
|
15
15
|
|
|
16
16
|
## Control Model
|
|
17
17
|
|
|
18
18
|
- `Banny721TokenUriResolver` is `Ownable`.
|
|
19
19
|
- Global admin power is limited to metadata, product naming, and SVG hash commitments.
|
|
20
|
-
- Actual SVG
|
|
20
|
+
- Actual SVG upload is permissionless once the hash is committed.
|
|
21
21
|
- Body owners control decoration and locking for their own bodies.
|
|
22
|
-
- Equipped accessories are held
|
|
22
|
+
- Equipped accessories are held by the resolver while attached.
|
|
23
23
|
|
|
24
24
|
## Roles
|
|
25
25
|
|
|
26
26
|
| Role | How Assigned | Scope | Notes |
|
|
27
27
|
| --- | --- | --- | --- |
|
|
28
|
-
| Resolver owner | `Ownable(owner)` at construction | Global | Can transfer ownership with
|
|
28
|
+
| Resolver owner | `Ownable(owner)` at construction | Global | Can transfer ownership with `transferOwnership()` |
|
|
29
29
|
| Body owner | `IERC721(hook).ownerOf(bannyBodyId)` | Per body | Can decorate and lock that body |
|
|
30
30
|
| Anyone | No assignment | Global | Can upload SVG bytes only if they match a committed hash |
|
|
31
31
|
|
|
@@ -44,35 +44,35 @@
|
|
|
44
44
|
|
|
45
45
|
- SVG hash commitments are write-once.
|
|
46
46
|
- SVG contents are write-once once uploaded.
|
|
47
|
-
- `lockOutfitChangesFor(...)` only extends the active lock
|
|
48
|
-
- The lock duration is fixed by
|
|
47
|
+
- `lockOutfitChangesFor(...)` only extends the active lock.
|
|
48
|
+
- The lock duration is fixed by `_LOCK_DURATION`.
|
|
49
49
|
- Default art fragments, category semantics, and the trusted forwarder are constructor or code immutables.
|
|
50
50
|
|
|
51
51
|
## Operational Notes
|
|
52
52
|
|
|
53
|
-
- Treat `setSvgHashesOf(...)` like a release gate. A wrong hash usually means new resolver or new UPC strategy, not
|
|
53
|
+
- Treat `setSvgHashesOf(...)` like a release gate. A wrong hash usually means a new resolver or new UPC strategy, not a small edit.
|
|
54
54
|
- Treat `setMetadata(...)` and `setProductNames(...)` as collection-wide display changes.
|
|
55
55
|
- Remind users that equipped assets are in resolver custody while attached.
|
|
56
|
-
- Only lock outfits when temporary non-editability is the intended
|
|
57
|
-
- Use safe ERC-721 transfer flows when assets
|
|
56
|
+
- Only lock outfits when temporary non-editability is the intended experience.
|
|
57
|
+
- Use safe ERC-721 transfer flows when assets enter the resolver path. Plain `transferFrom` can strand NFTs without a recovery path.
|
|
58
58
|
|
|
59
59
|
## Machine Notes
|
|
60
60
|
|
|
61
|
-
- Do not
|
|
61
|
+
- Do not assume there is a rescue path for equipped assets. There is none.
|
|
62
62
|
- Treat `src/Banny721TokenUriResolver.sol` as the source of truth for lock extension and write-once SVG behavior.
|
|
63
|
-
- If a committed hash and intended asset bytes differ, stop
|
|
63
|
+
- If a committed hash and intended asset bytes differ, stop. The contract does not support overwrite repair.
|
|
64
64
|
- If an asset arrived through non-safe ERC-721 transfer semantics, do not assume the resolver can detect or recover it.
|
|
65
65
|
|
|
66
66
|
## Recovery
|
|
67
67
|
|
|
68
68
|
- Bad metadata can be changed by the owner.
|
|
69
69
|
- Bad SVG commitments or uploaded content cannot be corrected in place.
|
|
70
|
-
- If equipped assets become stuck because of resolver logic, there is no owner rescue path
|
|
71
|
-
- If NFTs are stranded through non-safe transfer semantics, this contract does not provide
|
|
70
|
+
- If equipped assets become stuck because of resolver logic, there is no owner rescue path.
|
|
71
|
+
- If NFTs are stranded through non-safe transfer semantics, this contract does not provide recovery.
|
|
72
72
|
|
|
73
73
|
## Admin Boundaries
|
|
74
74
|
|
|
75
|
-
- The owner cannot withdraw equipped user NFTs
|
|
75
|
+
- The owner cannot arbitrarily withdraw equipped user NFTs.
|
|
76
76
|
- The owner cannot overwrite committed hashes or uploaded SVG contents.
|
|
77
77
|
- The owner cannot bypass body-owner checks on decoration or locking.
|
|
78
78
|
- Nobody can shorten an active outfit lock.
|
package/ARCHITECTURE.md
CHANGED
|
@@ -2,33 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
`banny-retail-v6` is the Banny-specific metadata and attachment layer for Juicebox 721 collections. It does not mint
|
|
5
|
+
`banny-retail-v6` is the Banny-specific metadata and attachment layer for Juicebox 721 collections. It does not mint NFTs or own treasury logic. It owns attachment custody, outfit-lock rules, and final token rendering.
|
|
6
6
|
|
|
7
7
|
## System Overview
|
|
8
8
|
|
|
9
|
-
The repo
|
|
9
|
+
The repo centers on `Banny721TokenUriResolver`. A 721 hook from `nana-721-hook-v6` points to this resolver for `tokenURI(...)`. Bodies, outfits, and backgrounds remain separate NFTs at the collection layer. The resolver escrows equipped accessories, records which assets are attached to each body, and composes the final SVG and JSON metadata on demand.
|
|
10
10
|
|
|
11
11
|
## Core Invariants
|
|
12
12
|
|
|
13
13
|
- A body can only reference accessories that are currently escrowed by the resolver.
|
|
14
14
|
- Replacing an equipped item must atomically return the old item and escrow the new item.
|
|
15
15
|
- Outfit locks must block both explicit removal and implicit replacement until the lock expires.
|
|
16
|
-
- Equipped assets
|
|
17
|
-
- Registered SVG payloads must match their
|
|
16
|
+
- Equipped assets move with the body NFT on transfer until the new owner unequips them.
|
|
17
|
+
- Registered SVG payloads must match their committed content hash before they can render.
|
|
18
18
|
- Rendering must stay deterministic for the same stored body state.
|
|
19
19
|
|
|
20
20
|
## Modules
|
|
21
21
|
|
|
22
22
|
| Module | Responsibility | Notes |
|
|
23
23
|
| --- | --- | --- |
|
|
24
|
-
| `Banny721TokenUriResolver` | Escrow, attachment state, lock windows, and metadata rendering | Main contract
|
|
24
|
+
| `Banny721TokenUriResolver` | Escrow, attachment state, lock windows, and metadata rendering | Main contract |
|
|
25
25
|
| `IBanny721TokenUriResolver` | External integration surface | Used by hooks and offchain tooling |
|
|
26
26
|
|
|
27
27
|
## Trust Boundaries
|
|
28
28
|
|
|
29
29
|
- Minting, ownership transfer, and collection-level ERC-721 semantics live in `nana-721-hook-v6`.
|
|
30
30
|
- This repo is trusted for rendering correctness and custody of equipped assets.
|
|
31
|
-
- Asset
|
|
31
|
+
- Asset-content upload is controlled by the registered content owner, but the contract verifies uploaded bytes against the stored hash.
|
|
32
32
|
|
|
33
33
|
## Critical Flows
|
|
34
34
|
|
|
@@ -66,20 +66,20 @@ body owner
|
|
|
66
66
|
|
|
67
67
|
This repo does not own treasury accounting. Its critical state is custody accounting: which NFTs are escrowed, which body they belong to, and when a body is locked against changes.
|
|
68
68
|
|
|
69
|
-
That custody model uses lazy reconciliation for some stale attachment records. Read paths filter against current ownership and attachment state instead of
|
|
69
|
+
That custody model uses lazy reconciliation for some stale attachment records. Read paths filter against current ownership and attachment state instead of rewriting storage on every outside transfer.
|
|
70
70
|
|
|
71
71
|
## Security Model
|
|
72
72
|
|
|
73
73
|
- The main failure mode is custody drift between slot state and actual escrowed NFTs.
|
|
74
|
-
- Rendering order is part of
|
|
75
|
-
- Lazy reconciliation is intentional. Changes that assume
|
|
74
|
+
- Rendering order is part of app semantics, not cosmetic output.
|
|
75
|
+
- Lazy reconciliation is intentional. Changes that assume storage is always perfectly clean can strand assets or mis-render bodies.
|
|
76
76
|
- Any new asset category adds both a rendering concern and a custody concern.
|
|
77
77
|
|
|
78
78
|
## Safe Change Guide
|
|
79
79
|
|
|
80
80
|
- Keep generic ERC-721 behavior in `nana-721-hook-v6`, not here.
|
|
81
81
|
- Review escrow writes and transfer behavior together whenever changing attachment logic.
|
|
82
|
-
- If transfer or cleanup behavior changes, re-check lazy reconciliation
|
|
82
|
+
- If transfer or cleanup behavior changes, re-check lazy reconciliation alongside body-transfer inheritance.
|
|
83
83
|
- If `tokenURI(...)` changes, test stable output for unchanged state and replacement behavior for changed state.
|
|
84
84
|
- If adding slots or asset classes, update rendering order, slot replacement, and lock enforcement in one change.
|
|
85
85
|
|
package/AUDIT_INSTRUCTIONS.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# Audit Instructions
|
|
2
2
|
|
|
3
|
-
This repo is the Banny avatar composition layer. It does not mint the base NFTs, but it does
|
|
3
|
+
This repo is the Banny avatar composition layer. It does not mint the base NFTs, but it does hold equipped accessories and define the metadata users see.
|
|
4
4
|
|
|
5
5
|
## Audit Objective
|
|
6
6
|
|
|
7
7
|
Find issues that:
|
|
8
|
+
|
|
8
9
|
- strand outfits or backgrounds in resolver custody
|
|
9
10
|
- let the wrong actor equip, unequip, overwrite, or recover accessories
|
|
10
11
|
- break outfit-lock timing or freeze a body longer than intended
|
|
@@ -14,9 +15,10 @@ Find issues that:
|
|
|
14
15
|
## Scope
|
|
15
16
|
|
|
16
17
|
In scope:
|
|
18
|
+
|
|
17
19
|
- `src/Banny721TokenUriResolver.sol`
|
|
18
20
|
- `src/interfaces/IBanny721TokenUriResolver.sol`
|
|
19
|
-
-
|
|
21
|
+
- deployment helpers in `script/`
|
|
20
22
|
|
|
21
23
|
## Start Here
|
|
22
24
|
|
|
@@ -27,6 +29,7 @@ In scope:
|
|
|
27
29
|
## Security Model
|
|
28
30
|
|
|
29
31
|
The resolver is an attachment and rendering layer around a `JB721TiersHook` collection.
|
|
32
|
+
|
|
30
33
|
- the underlying 721 hook remains the token contract and source of body ownership
|
|
31
34
|
- the resolver temporarily holds accessory NFTs while they are equipped
|
|
32
35
|
- body ownership should be the only authority that changes equipped state
|
|
@@ -53,7 +56,7 @@ The resolver is an attachment and rendering layer around a `JB721TiersHook` coll
|
|
|
53
56
|
2. Only the current body owner or an intended delegate can change that body's equipped state.
|
|
54
57
|
3. Conflicting categories cannot be equipped together, including through replacement or invalidation edge paths.
|
|
55
58
|
4. Outfit-lock state only affects the intended body for the intended duration.
|
|
56
|
-
5. Metadata and SVG generation reflect current state and do not
|
|
59
|
+
5. Metadata and SVG generation reflect current state and do not show impossible combinations.
|
|
57
60
|
|
|
58
61
|
## Attack Surfaces
|
|
59
62
|
|
|
@@ -61,7 +64,7 @@ The resolver is an attachment and rendering layer around a `JB721TiersHook` coll
|
|
|
61
64
|
- ERC-721 receipt hooks and any path that accepts custody
|
|
62
65
|
- release paths after redecorating, burning, or invalid token state
|
|
63
66
|
- category validation and conflict checks
|
|
64
|
-
- metadata assembly that assumes
|
|
67
|
+
- metadata assembly that assumes onchain assets or tier data remain available
|
|
65
68
|
|
|
66
69
|
## Accepted Risks Or Behaviors
|
|
67
70
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Banny Retail
|
|
2
2
|
|
|
3
|
-
Banny Retail is an
|
|
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.
|
|
4
4
|
|
|
5
5
|
Docs: <https://docs.juicebox.money>
|
|
6
6
|
Architecture: [ARCHITECTURE.md](./ARCHITECTURE.md)
|
|
@@ -12,34 +12,32 @@ Audit instructions: [AUDIT_INSTRUCTIONS.md](./AUDIT_INSTRUCTIONS.md)
|
|
|
12
12
|
|
|
13
13
|
## Overview
|
|
14
14
|
|
|
15
|
-
This is a resolver-centric
|
|
15
|
+
This is a resolver-centric app built on top of [`@bananapus/721-hook-v6`](https://www.npmjs.com/package/@bananapus/721-hook-v6). The resolver holds attached outfit and background NFTs while a body is decorated, then composes the active layers into a single token URI response.
|
|
16
16
|
|
|
17
17
|
The main user flows are:
|
|
18
18
|
|
|
19
19
|
- mint body, outfit, and background NFTs through a Juicebox 721 hook
|
|
20
20
|
- attach accessories to a body with `decorateBannyWith`
|
|
21
|
-
- optionally freeze the
|
|
22
|
-
- upload SVG payloads
|
|
21
|
+
- optionally freeze the look for seven days with `lockOutfitChangesFor`
|
|
22
|
+
- upload SVG payloads after an owner registers the content hashes
|
|
23
23
|
|
|
24
|
-
Use this repo when you need collection-specific, fully
|
|
25
|
-
|
|
26
|
-
If a bug changes tier pricing, mint eligibility, or treasury flow, it is probably not here first. Start in the 721 hook repo and only come here once the issue is clearly in attachment, custody, or rendering behavior.
|
|
24
|
+
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.
|
|
27
25
|
|
|
28
26
|
## Key Contract
|
|
29
27
|
|
|
30
28
|
| Contract | Role |
|
|
31
29
|
| --- | --- |
|
|
32
|
-
| `Banny721TokenUriResolver` | Resolves
|
|
30
|
+
| `Banny721TokenUriResolver` | Resolves metadata, stores equipped accessories, enforces outfit locks, and renders layered SVG output for Banny collections. |
|
|
33
31
|
|
|
34
32
|
## Mental Model
|
|
35
33
|
|
|
36
34
|
This repo owns three things:
|
|
37
35
|
|
|
38
|
-
1. custody of
|
|
39
|
-
2. rules
|
|
40
|
-
3. rendering of the final
|
|
36
|
+
1. custody of outfit and background NFTs while they are equipped
|
|
37
|
+
2. rules for what a body can wear and when that can change
|
|
38
|
+
3. rendering of the final metadata payload
|
|
41
39
|
|
|
42
|
-
It does not own mint pricing, tier issuance, or
|
|
40
|
+
It does not own mint pricing, tier issuance, or treasury accounting.
|
|
43
41
|
|
|
44
42
|
## Read These Files First
|
|
45
43
|
|
|
@@ -58,12 +56,11 @@ It does not own mint pricing, tier issuance, or project accounting.
|
|
|
58
56
|
|
|
59
57
|
## Integration Traps
|
|
60
58
|
|
|
61
|
-
- the resolver
|
|
62
|
-
- transferred bodies carry their equipped assets, so a new body holder can inherit control of
|
|
63
|
-
- burned bodies and non-safe transfer patterns can strand expectations around resolver-held assets
|
|
64
|
-
- outfit locks
|
|
65
|
-
- metadata quality depends on lazily uploaded asset payloads, not only
|
|
66
|
-
- collection logic here assumes a Juicebox 721 hook upstream and should not be read as a generic NFT renderer
|
|
59
|
+
- the resolver holds equipped assets, so transfer edge cases matter as much as rendering output
|
|
60
|
+
- transferred bodies carry their equipped assets, so a new body holder can inherit control of them
|
|
61
|
+
- burned bodies and non-safe transfer patterns can strand expectations around resolver-held assets
|
|
62
|
+
- outfit locks survive body transfers until expiry
|
|
63
|
+
- metadata quality depends on lazily uploaded asset payloads, not only token state
|
|
67
64
|
|
|
68
65
|
## Where State Lives
|
|
69
66
|
|
|
@@ -96,7 +93,7 @@ Useful scripts:
|
|
|
96
93
|
|
|
97
94
|
## Deployment Notes
|
|
98
95
|
|
|
99
|
-
Deployments are handled through Sphinx using the environments configured in `script/Deploy.s.sol`. The resolver is
|
|
96
|
+
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.
|
|
100
97
|
|
|
101
98
|
## Repository Layout
|
|
102
99
|
|
|
@@ -115,14 +112,14 @@ script/
|
|
|
115
112
|
|
|
116
113
|
## Risks And Notes
|
|
117
114
|
|
|
118
|
-
- attached outfits and backgrounds are
|
|
115
|
+
- attached outfits and backgrounds are held by the resolver while equipped
|
|
119
116
|
- outfit locks are fixed-duration and cannot be shortened once set
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
- rendering quality
|
|
117
|
+
- onchain SVG content is immutable once uploaded for a committed hash
|
|
118
|
+
- plain `transferFrom` can still create asset-tracking surprises around resolver custody
|
|
119
|
+
- rendering quality depends on the integrity of uploaded SVG assets
|
|
123
120
|
|
|
124
121
|
## For AI Agents
|
|
125
122
|
|
|
126
|
-
- Treat this repo as an
|
|
123
|
+
- Treat this repo as an app-layer resolver, not as the NFT issuance primitive.
|
|
127
124
|
- Start with `Banny721TokenUriResolver` and the lifecycle tests before summarizing attachment behavior.
|
|
128
|
-
- If the question is about mint economics or tier availability, inspect `nana-721-hook-v6` instead
|
|
125
|
+
- If the question is about mint economics or tier availability, inspect `nana-721-hook-v6` instead.
|
package/RISKS.md
CHANGED
|
@@ -1,89 +1,80 @@
|
|
|
1
1
|
# Banny Retail Risk Register
|
|
2
2
|
|
|
3
|
-
This file focuses on failure modes that can break NFT custody,
|
|
3
|
+
This file focuses on the failure modes that can break NFT custody, bypass hook assumptions, or leave a rendered Banny in a state that does not match the assets users think they own.
|
|
4
4
|
|
|
5
5
|
## How to use this file
|
|
6
6
|
|
|
7
|
-
- Read `Priority risks` first
|
|
8
|
-
- Use `Accepted Behaviors` to separate intentional tradeoffs from
|
|
7
|
+
- Read `Priority risks` first.
|
|
8
|
+
- Use `Accepted Behaviors` to separate intentional tradeoffs from bugs.
|
|
9
9
|
- Treat `Invariants to Verify` as required test and audit targets.
|
|
10
10
|
|
|
11
11
|
## Priority risks
|
|
12
12
|
|
|
13
13
|
| Priority | Risk | Why it matters | Primary controls |
|
|
14
14
|
|----------|------|----------------|------------------|
|
|
15
|
-
| P0 | Untrusted `hook` or store integration | The
|
|
16
|
-
| P1 | Silent transfer
|
|
17
|
-
| P1 | Sale-time outfit
|
|
18
|
-
|
|
15
|
+
| P0 | Untrusted `hook` or store integration | The resolver trusts the supplied hook for ownership checks, tier metadata, and transfers. A bad hook can fake authority or trap assets. | Restrict supported hooks, scrutinize the hook boundary, and test hostile hook behavior. |
|
|
16
|
+
| P1 | Silent transfer-failure retention | Failed returns intentionally keep attachment records to avoid stranding NFTs, but this can leave phantom render state if the asset is gone forever. | Explicit accepted-behavior rules, retained-item handling, and custody/state invariants. |
|
|
17
|
+
| P1 | Sale-time outfit-lock griefing | A seller can transfer a locked body and force the buyer to wait up to 7 days before changing outfits. | Fixed-duration lock, marketplace disclosure, and user education. |
|
|
19
18
|
|
|
20
19
|
## 1. Trust Assumptions
|
|
21
20
|
|
|
22
|
-
- **Trusted forwarder.** ERC-2771 `_msgSender()` is trusted for
|
|
23
|
-
- **Hook contract.** The `hook` parameter is caller-supplied and not validated against
|
|
24
|
-
- **Owner
|
|
25
|
-
- **721 hook store.** `_storeOf(hook)`
|
|
21
|
+
- **Trusted forwarder.** ERC-2771 `_msgSender()` is trusted for ownership checks and admin functions.
|
|
22
|
+
- **Hook contract.** The `hook` parameter is caller-supplied and not validated against a registry.
|
|
23
|
+
- **Owner.** The contract owner controls SVG hashes, product names, and metadata URIs.
|
|
24
|
+
- **721 hook store.** `_storeOf(hook)` trusts the hook to return a legitimate store.
|
|
26
25
|
|
|
27
|
-
## 2. Economic
|
|
26
|
+
## 2. Economic And Manipulation Risks
|
|
28
27
|
|
|
29
|
-
- **Outfit theft via
|
|
30
|
-
- **
|
|
31
|
-
- **Lock griefing.** `lockOutfitChangesFor` extends the lock to `block.timestamp + 7 days`. Locking just before
|
|
28
|
+
- **Outfit theft via body transfer.** Equipped outfits and backgrounds move with the body NFT. If a body is sold while wearing valuable items, the buyer gains control of them.
|
|
29
|
+
- **Try-catch silent failures with retention.** Failed transfer-outs preserve attachment records instead of clearing state. This avoids stranding but can create phantom render entries for burned or removed assets.
|
|
30
|
+
- **Lock griefing.** `lockOutfitChangesFor` extends the lock to `block.timestamp + 7 days`. Locking just before a sale can block the buyer from changing the look for up to 7 days.
|
|
32
31
|
|
|
33
32
|
## 3. Access Control
|
|
34
33
|
|
|
35
|
-
- **No hook validation
|
|
36
|
-
- **SVG content upload is permissionless
|
|
37
|
-
-
|
|
34
|
+
- **No hook validation.** Any address can be passed as `hook`. A malicious hook can fake `ownerOf`, execute arbitrary code during transfers, or return manipulated tier data.
|
|
35
|
+
- **SVG content upload is permissionless once the hash is committed.** This is safe only if the committed hash is correct.
|
|
36
|
+
- **`onERC721Received` restriction.** The contract only accepts NFTs when `operator == address(this)`. Plain `transferFrom` bypasses that and can permanently lock NFTs.
|
|
38
37
|
|
|
39
38
|
## 4. DoS Vectors
|
|
40
39
|
|
|
41
|
-
- **External call iteration scales with outfit count.** `
|
|
42
|
-
- **External hook calls in view functions.** `tokenUriOf` and `svgOf` call into the hook's store multiple times
|
|
40
|
+
- **External call iteration scales with outfit count.** `decorateBannyWith` iterates both old and new outfit arrays, so gas cost scales with the number of outfits changed in one call.
|
|
41
|
+
- **External hook calls in view functions.** `tokenUriOf` and `svgOf` call into the hook's store multiple times. A malicious or gas-heavy hook can make metadata unreadable.
|
|
43
42
|
|
|
44
43
|
## 5. Integration Risks
|
|
45
44
|
|
|
46
|
-
- **Cross-contract NFT custody.** Outfits are held by
|
|
47
|
-
- **Tier removal desync.** If a tier is removed
|
|
48
|
-
- **Non-safe transfer loss.** Outfits sent directly
|
|
49
|
-
- **
|
|
45
|
+
- **Cross-contract NFT custody.** Outfits are held by the resolver via `safeTransferFrom`. If approval is revoked on the hook contract, equipping fails.
|
|
46
|
+
- **Tier removal desync.** If a tier is removed while an outfit from that tier is equipped, the outfit may remain attached but render empty.
|
|
47
|
+
- **Non-safe transfer loss.** Outfits sent directly via `transferFrom` can be permanently stuck because there is no rescue function.
|
|
48
|
+
- **Reentrancy assumptions.** `decorateBannyWith` uses `nonReentrant`, but other functions rely on ordering and limited state impact instead.
|
|
50
49
|
|
|
51
50
|
## 6. Invariants to Verify
|
|
52
51
|
|
|
53
|
-
- Every outfit held by
|
|
54
|
-
- Every background held by
|
|
55
|
-
- `outfitLockedUntil
|
|
56
|
-
- After `decorateBannyWith`,
|
|
57
|
-
-
|
|
58
|
-
- SVG content integrity
|
|
59
|
-
-
|
|
52
|
+
- Every outfit held by the contract has a corresponding wearer mapping to a valid body.
|
|
53
|
+
- Every background held by the contract has a corresponding user mapping to a valid body.
|
|
54
|
+
- `outfitLockedUntil` is monotonically non-decreasing per body.
|
|
55
|
+
- After `decorateBannyWith`, old outfits not in the new set are either returned or explicitly retained because transfer-out failed.
|
|
56
|
+
- Category exclusivity holds on the merged set of retained and newly supplied outfits.
|
|
57
|
+
- SVG content integrity holds for all populated entries.
|
|
58
|
+
- The number of held outfit NFTs should match the number of outfits still tracked as equipped for that hook.
|
|
60
59
|
|
|
61
60
|
## 7. Accepted Behaviors
|
|
62
61
|
|
|
63
|
-
### 7.1 Failed transfers retain attachment records
|
|
64
|
-
|
|
65
|
-
`_tryTransferFrom` catches all transfer failures and returns `false`. When returning a previously equipped item fails, the resolver preserves the attachment record rather than clearing state:
|
|
66
|
-
|
|
67
|
-
- **Backgrounds**: If returning the old background fails, the entire background change is aborted (`return` in `_decorateBannyWithBackground`). The old background stays attached and the new one is not equipped.
|
|
68
|
-
- **Background removal**: If returning the background fails during removal (backgroundId=0), `_attachedBackgroundIdOf` is not cleared. The background stays attached.
|
|
69
|
-
- **Outfits**: Failed-to-return outfits remain non-zero in the `previousOutfitIds` array. `_storeOutfitsWithRetained` appends them to the new outfit list, preserving their attachment record. After merging, the resolver verifies no two outfits share the same category (reverts with `DuplicateCategory` if a retained outfit conflicts with a newly supplied one).
|
|
70
|
-
|
|
71
|
-
This prevents NFT stranding — assets held by the resolver stay tracked and recoverable. Once the transfer issue is resolved (e.g., the owner contract implements `IERC721Receiver`), a subsequent `decorateBannyWith` call will successfully return the retained items.
|
|
62
|
+
### 7.1 Failed transfers retain attachment records
|
|
72
63
|
|
|
73
|
-
|
|
64
|
+
If returning a previously equipped item fails, the resolver keeps the attachment record instead of dropping it. This avoids stranding NFTs held by the resolver, but it can leave cosmetic phantom state for permanently unrecoverable assets.
|
|
74
65
|
|
|
75
|
-
### 7.2 Lock griefing
|
|
66
|
+
### 7.2 Lock griefing is bounded at 7 days
|
|
76
67
|
|
|
77
|
-
`lockOutfitChangesFor`
|
|
68
|
+
`lockOutfitChangesFor` can force a buyer to wait, but the window is fixed and cannot be extended arbitrarily beyond the current maximum.
|
|
78
69
|
|
|
79
|
-
### 7.3
|
|
70
|
+
### 7.3 Onchain SVG rendering gas is acceptable
|
|
80
71
|
|
|
81
|
-
`tokenUriOf`
|
|
72
|
+
Full `tokenUriOf` rendering is expensive but still within practical RPC limits for the supported outfit counts.
|
|
82
73
|
|
|
83
74
|
### 7.4 Outfits burn alongside the body
|
|
84
75
|
|
|
85
|
-
|
|
76
|
+
If a body NFT is burned while outfits are equipped, those outfits are intentionally unrecoverable. Users who want to keep them must unequip first.
|
|
86
77
|
|
|
87
|
-
### 7.5 Reentrancy in non-guarded functions is harmless
|
|
78
|
+
### 7.5 Reentrancy in non-guarded functions is treated as harmless under the current model
|
|
88
79
|
|
|
89
|
-
`lockOutfitChangesFor`
|
|
80
|
+
`lockOutfitChangesFor` only extends a timestamp, and view functions do not write storage. That keeps the remaining reentrancy surface narrow.
|
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 Banny outfit attachment, layered SVG rendering, token URI composition, or asset custody and lock behavior.
|
|
6
|
-
- Start here, then decide whether the issue is custody state, lock
|
|
6
|
+
- Start here, then decide whether the issue is custody state, lock timing, stored SVG content, or final token-URI composition.
|
|
7
7
|
|
|
8
8
|
## Read This Next
|
|
9
9
|
|
|
@@ -26,17 +26,17 @@
|
|
|
26
26
|
|
|
27
27
|
## Purpose
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
App-layer token URI resolver for Juicebox 721 collections. It lets Banny body NFTs equip outfit and background NFTs, holds them while equipped, and renders fully onchain layered SVG metadata.
|
|
30
30
|
|
|
31
31
|
## Reference Files
|
|
32
32
|
|
|
33
|
-
- Open [`references/runtime.md`](./references/runtime.md)
|
|
34
|
-
- Open [`references/operations.md`](./references/operations.md)
|
|
33
|
+
- Open [`references/runtime.md`](./references/runtime.md) for attachment and custody behavior, rendering order, and the main invariants that protect equipped assets.
|
|
34
|
+
- Open [`references/operations.md`](./references/operations.md) for upload and metadata-management behavior, deployment breadcrumbs, and common stale-data traps around SVG content.
|
|
35
35
|
|
|
36
36
|
## Working Rules
|
|
37
37
|
|
|
38
|
-
- Start in [`src/Banny721TokenUriResolver.sol`](./src/Banny721TokenUriResolver.sol) for both rendering and attachment behavior.
|
|
38
|
+
- Start in [`src/Banny721TokenUriResolver.sol`](./src/Banny721TokenUriResolver.sol) for both rendering and attachment behavior.
|
|
39
39
|
- Treat custody, stale attachment cleanup, and lock timing as high-risk. Rendering bugs are visible, but custody bugs are worse.
|
|
40
|
-
- Equipped outfits and backgrounds travel with the body NFT. Treat that inheritance
|
|
40
|
+
- Equipped outfits and backgrounds travel with the body NFT. Treat that inheritance as intentional before calling it a bug.
|
|
41
41
|
- When a task mentions minting, pricing, or terminal accounting, verify that the problem is not actually in the upstream 721 hook repo.
|
|
42
42
|
- If you touch SVG or metadata behavior, check whether the issue is in stored content, rendering composition, or the hook-to-resolver integration point before patching.
|
package/USER_JOURNEYS.md
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Repo Purpose
|
|
4
4
|
|
|
5
|
-
This repo is the Banny-specific composition and metadata layer on top of a Juicebox 721 collection.
|
|
6
|
-
It owns attachment custody, compatibility rules, outfit locks, and rendered token metadata. It does not own tier
|
|
7
|
-
pricing, treasury accounting, or mint eligibility outside the resolver-specific checks.
|
|
5
|
+
This repo is the Banny-specific composition and metadata layer on top of a Juicebox 721 collection. It owns attachment custody, compatibility rules, outfit locks, and rendered token metadata. It does not own tier pricing, treasury accounting, or mint eligibility outside resolver-specific checks.
|
|
8
6
|
|
|
9
7
|
## Primary Actors
|
|
10
8
|
|
|
@@ -15,7 +13,7 @@ pricing, treasury accounting, or mint eligibility outside the resolver-specific
|
|
|
15
13
|
## Key Surfaces
|
|
16
14
|
|
|
17
15
|
- `Banny721TokenUriResolver`: custody, compatibility, locks, and rendered SVG metadata
|
|
18
|
-
- `decorateBannyWith(...)`: equips outfits and a background to a body and returns
|
|
16
|
+
- `decorateBannyWith(...)`: equips outfits and a background to a body and returns old items when possible
|
|
19
17
|
- `lockOutfitChangesFor(...)`: freezes appearance changes for the fixed lock window
|
|
20
18
|
- `setSvgHashesOf(...)` / `setSvgContentsOf(...)`: publish or repair art payloads
|
|
21
19
|
- `setMetadata(...)` / `setProductNames(...)`: update collection metadata and UPC naming
|
|
@@ -27,21 +25,24 @@ pricing, treasury accounting, or mint eligibility outside the resolver-specific
|
|
|
27
25
|
**Intent:** acquire the pieces needed to build a composed Banny.
|
|
28
26
|
|
|
29
27
|
**Preconditions**
|
|
28
|
+
|
|
30
29
|
- the Banny collection is live through the 721 hook
|
|
31
30
|
- the required body, outfit, and background tiers exist
|
|
32
31
|
|
|
33
32
|
**Main Flow**
|
|
33
|
+
|
|
34
34
|
1. Mint the body, outfit, and background NFTs through the underlying 721 project.
|
|
35
35
|
2. Keep mint pricing and issuance assumptions anchored in the 721 hook, not this repo.
|
|
36
|
-
3. Move to this resolver only once the user
|
|
36
|
+
3. Move to this resolver only once the user owns compatible pieces.
|
|
37
37
|
|
|
38
38
|
**Failure Modes**
|
|
39
|
-
|
|
39
|
+
|
|
40
|
+
- the wrong tiers are minted or the pieces are incompatible
|
|
40
41
|
- teams misread this repo as the minting or accounting surface
|
|
41
42
|
|
|
42
43
|
**Postconditions**
|
|
44
|
+
|
|
43
45
|
- the user holds the components needed for later composition
|
|
44
|
-
- mint pricing, reserves, and treasury effects still belong to the underlying 721 project rather than this resolver
|
|
45
46
|
|
|
46
47
|
## Journey 2: Dress A Banny And Put Accessories Into Resolver Custody
|
|
47
48
|
|
|
@@ -50,24 +51,28 @@ pricing, treasury accounting, or mint eligibility outside the resolver-specific
|
|
|
50
51
|
**Intent:** equip a body with a background and outfits so the resolver serves the composed avatar.
|
|
51
52
|
|
|
52
53
|
**Preconditions**
|
|
54
|
+
|
|
53
55
|
- the caller controls the body and the accessories being equipped
|
|
54
56
|
- no active outfit lock blocks the change
|
|
55
57
|
- the selected pieces are compatible by category and collection rules
|
|
56
58
|
|
|
57
59
|
**Main Flow**
|
|
60
|
+
|
|
58
61
|
1. Call `decorateBannyWith(...)` for the target body.
|
|
59
62
|
2. The resolver checks compatibility and diffs old versus new attachments.
|
|
60
63
|
3. Equipped accessories move into resolver custody while attached.
|
|
61
64
|
4. The token URI for the body now reflects the combined SVG and metadata.
|
|
62
65
|
|
|
63
66
|
**Failure Modes**
|
|
67
|
+
|
|
64
68
|
- duplicate outfit categories or incompatible combinations are provided
|
|
65
|
-
-
|
|
66
|
-
- reviewers forget that the resolver, not the
|
|
69
|
+
- transfer-back of previously attached items fails, leaving retained custody state for later recovery
|
|
70
|
+
- reviewers forget that the resolver, not the wallet, holds equipped accessories while active
|
|
67
71
|
|
|
68
72
|
**Postconditions**
|
|
69
|
-
|
|
70
|
-
-
|
|
73
|
+
|
|
74
|
+
- the body renders with the new composition
|
|
75
|
+
- attached accessories stay in resolver custody until replaced or cleared
|
|
71
76
|
|
|
72
77
|
## Journey 3: Lock A Banny's Appearance For A Period
|
|
73
78
|
|
|
@@ -76,43 +81,51 @@ pricing, treasury accounting, or mint eligibility outside the resolver-specific
|
|
|
76
81
|
**Intent:** freeze the current appearance for the fixed lock window.
|
|
77
82
|
|
|
78
83
|
**Preconditions**
|
|
84
|
+
|
|
79
85
|
- the body already has a state worth freezing
|
|
80
|
-
- the caller understands the lock is
|
|
86
|
+
- the caller understands the lock is fixed-duration
|
|
81
87
|
|
|
82
88
|
**Main Flow**
|
|
89
|
+
|
|
83
90
|
1. Call `lockOutfitChangesFor(...)`.
|
|
84
91
|
2. The resolver extends the lock for that body.
|
|
85
92
|
3. Future decoration or removal attempts must wait until the lock expires.
|
|
86
93
|
|
|
87
94
|
**Failure Modes**
|
|
88
|
-
|
|
89
|
-
-
|
|
95
|
+
|
|
96
|
+
- a seller locks just before transfer and the buyer cannot restyle immediately
|
|
97
|
+
- integrations fail to show lock state before listing or sale
|
|
90
98
|
|
|
91
99
|
**Postconditions**
|
|
100
|
+
|
|
92
101
|
- appearance changes are blocked until the lock expires
|
|
93
102
|
|
|
94
|
-
## Journey 4: Publish Or Repair
|
|
103
|
+
## Journey 4: Publish Or Repair Onchain Art Assets
|
|
95
104
|
|
|
96
105
|
**Actor:** collection operator or art publisher.
|
|
97
106
|
|
|
98
107
|
**Intent:** make token URIs render complete onchain art.
|
|
99
108
|
|
|
100
109
|
**Preconditions**
|
|
110
|
+
|
|
101
111
|
- the relevant UPCs and content hashes are known
|
|
102
|
-
- the operator understands
|
|
112
|
+
- the operator understands that the hash is the commitment and SVG content must match it exactly
|
|
103
113
|
|
|
104
114
|
**Main Flow**
|
|
115
|
+
|
|
105
116
|
1. Register hashes with `setSvgHashesOf(...)`.
|
|
106
117
|
2. Upload matching payloads with `setSvgContentsOf(...)`.
|
|
107
118
|
3. Re-check token URI output after publication or repair.
|
|
108
119
|
|
|
109
120
|
**Failure Modes**
|
|
110
|
-
|
|
121
|
+
|
|
122
|
+
- uploaded SVG does not match the committed hash
|
|
111
123
|
- product names are missing or stale
|
|
112
124
|
- teams assume the 721 hook owns rendered output when this repo does
|
|
113
125
|
|
|
114
126
|
**Postconditions**
|
|
115
|
-
|
|
127
|
+
|
|
128
|
+
- token URIs can render the intended onchain art payloads
|
|
116
129
|
|
|
117
130
|
## Journey 5: Update Collection Metadata And Product Catalog Entries
|
|
118
131
|
|
|
@@ -121,19 +134,23 @@ pricing, treasury accounting, or mint eligibility outside the resolver-specific
|
|
|
121
134
|
**Intent:** change collection-level metadata and human-readable product labels.
|
|
122
135
|
|
|
123
136
|
**Preconditions**
|
|
137
|
+
|
|
124
138
|
- the operator has authority over the resolver metadata surface
|
|
125
139
|
|
|
126
140
|
**Main Flow**
|
|
141
|
+
|
|
127
142
|
1. Update collection metadata with `setMetadata(...)`.
|
|
128
143
|
2. Set or repair UPC names with `setProductNames(...)`.
|
|
129
144
|
3. Re-check a representative token URI so labels and art agree.
|
|
130
145
|
|
|
131
146
|
**Failure Modes**
|
|
147
|
+
|
|
132
148
|
- metadata and SVG state drift apart
|
|
133
149
|
- operators update catalog labels without checking already-minted assets
|
|
134
150
|
|
|
135
151
|
**Postconditions**
|
|
136
|
-
|
|
152
|
+
|
|
153
|
+
- collection-level metadata and UPC names line up with the published art set
|
|
137
154
|
|
|
138
155
|
## Journey 6: Unequip And Recover Custodied Accessories
|
|
139
156
|
|
|
@@ -142,20 +159,24 @@ pricing, treasury accounting, or mint eligibility outside the resolver-specific
|
|
|
142
159
|
**Intent:** recover attached accessories from resolver custody.
|
|
143
160
|
|
|
144
161
|
**Preconditions**
|
|
162
|
+
|
|
145
163
|
- the current lock window, if any, has expired
|
|
146
|
-
- the owner understands old pieces may only be returned as part of a later decoration update
|
|
164
|
+
- the owner understands that old pieces may only be returned as part of a later decoration update
|
|
147
165
|
|
|
148
166
|
**Main Flow**
|
|
167
|
+
|
|
149
168
|
1. Replace or clear the equipped items through `decorateBannyWith(...)`.
|
|
150
169
|
2. The resolver attempts to return no-longer-equipped accessories.
|
|
151
|
-
3. Once returned, those NFTs can be transferred or
|
|
170
|
+
3. Once returned, those NFTs can be transferred or reused independently.
|
|
152
171
|
|
|
153
172
|
**Failure Modes**
|
|
173
|
+
|
|
154
174
|
- previously equipped pieces remain retained because transfer-back failed
|
|
155
175
|
- burned or otherwise unrecoverable pieces leave cosmetic phantom state until corrected
|
|
156
176
|
|
|
157
177
|
**Postconditions**
|
|
158
|
-
|
|
178
|
+
|
|
179
|
+
- no-longer-equipped accessories are either returned or remain explicitly retained pending recovery
|
|
159
180
|
|
|
160
181
|
## Trust Boundaries
|
|
161
182
|
|
|
@@ -166,4 +187,4 @@ pricing, treasury accounting, or mint eligibility outside the resolver-specific
|
|
|
166
187
|
## Hand-Offs
|
|
167
188
|
|
|
168
189
|
- Use [nana-721-hook-v6](../nana-721-hook-v6/USER_JOURNEYS.md) for mint pricing, tier issuance, reserves, and treasury behavior.
|
|
169
|
-
- Use this repo
|
|
190
|
+
- Use this repo once the question is about custody, compatibility, outfit locks, or SVG composition.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bannynet/core-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.24",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,14 +20,14 @@
|
|
|
20
20
|
"artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'banny-core-v6'"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@bananapus/721-hook-v6": "^0.0.
|
|
24
|
-
"@bananapus/core-v6": "^0.0.
|
|
25
|
-
"@bananapus/permission-ids-v6": "^0.0.
|
|
26
|
-
"@bananapus/router-terminal-v6": "^0.0.
|
|
27
|
-
"@bananapus/suckers-v6": "^0.0.
|
|
28
|
-
"@croptop/core-v6": "^0.0.
|
|
23
|
+
"@bananapus/721-hook-v6": "^0.0.38",
|
|
24
|
+
"@bananapus/core-v6": "^0.0.36",
|
|
25
|
+
"@bananapus/permission-ids-v6": "^0.0.19",
|
|
26
|
+
"@bananapus/router-terminal-v6": "^0.0.30",
|
|
27
|
+
"@bananapus/suckers-v6": "^0.0.28",
|
|
28
|
+
"@croptop/core-v6": "^0.0.36",
|
|
29
29
|
"@openzeppelin/contracts": "^5.6.1",
|
|
30
|
-
"@rev-net/core-v6": "^0.0.
|
|
30
|
+
"@rev-net/core-v6": "^0.0.35",
|
|
31
31
|
"keccak": "^3.0.4"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|