@bannynet/core-v6 0.0.4 → 0.0.5

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/foundry.toml CHANGED
@@ -1,7 +1,7 @@
1
1
  [profile.default]
2
2
  solc = '0.8.26'
3
3
  evm_version = 'paris'
4
- optimizer_runs = 800
4
+ optimizer_runs = 200
5
5
  via_ir = true
6
6
  libs = ["node_modules", "lib"]
7
7
  fs_permissions = [{ access = "read-write", path = "./"}]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bannynet/core-v6",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "scripts": {
13
13
  "test": "forge test",
14
- "coverage:integration": "forge coverage --match-path \"./src/*.sol\" --report lcov --report summary",
14
+ "coverage": "forge coverage --match-path \"./src/*.sol\" --report lcov --report summary",
15
15
  "generate:migration": "node ./script/outfit_drop/generate-migration.js",
16
16
  "deploy:mainnets": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/Deploy.s.sol --networks mainnets",
17
17
  "deploy:testnets": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/Deploy.s.sol --networks testnets",
@@ -21,14 +21,14 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "@bananapus/721-hook-v6": "^0.0.9",
24
- "@bananapus/core-v6": "^0.0.9",
24
+ "@bananapus/core-v6": "^0.0.10",
25
25
  "@bananapus/router-terminal-v6": "^0.0.6",
26
- "@bananapus/suckers-v6": "^0.0.6",
26
+ "@bananapus/suckers-v6": "^0.0.7",
27
27
  "@openzeppelin/contracts": "5.2.0",
28
28
  "@rev-net/core-v6": "^0.0.6",
29
29
  "keccak": "^3.0.4"
30
30
  },
31
31
  "devDependencies": {
32
- "@sphinx-labs/plugins": "0.33.3"
32
+ "@sphinx-labs/plugins": "^0.33.2"
33
33
  }
34
34
  }
@@ -1,19 +1,19 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity 0.8.26;
3
3
 
4
- import {IERC721} from "@bananapus/721-hook-v6/src/abstract/ERC721.sol";
5
- import {IJB721TiersHook} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHook.sol";
6
- import {IJB721TiersHookStore} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookStore.sol";
7
- import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
8
- import {JB721Tier} from "@bananapus/721-hook-v6/src/structs/JB721Tier.sol";
9
- import {JBIpfsDecoder} from "@bananapus/721-hook-v6/src/libraries/JBIpfsDecoder.sol";
10
4
  import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
11
- import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
12
5
  import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
13
6
  import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
14
7
  import {Context} from "@openzeppelin/contracts/utils/Context.sol";
15
8
  import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
9
+ import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
16
10
  import {Base64} from "lib/base64/base64.sol";
11
+ import {IERC721} from "@bananapus/721-hook-v6/src/abstract/ERC721.sol";
12
+ import {IJB721TiersHook} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHook.sol";
13
+ import {IJB721TiersHookStore} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookStore.sol";
14
+ import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
15
+ import {JBIpfsDecoder} from "@bananapus/721-hook-v6/src/libraries/JBIpfsDecoder.sol";
16
+ import {JB721Tier} from "@bananapus/721-hook-v6/src/structs/JB721Tier.sol";
17
17
 
18
18
  import {IBanny721TokenUriResolver} from "./interfaces/IBanny721TokenUriResolver.sol";
19
19
 
@@ -28,6 +28,10 @@ contract Banny721TokenUriResolver is
28
28
  {
29
29
  using Strings for uint256;
30
30
 
31
+ //*********************************************************************//
32
+ // --------------------------- custom errors ------------------------- //
33
+ //*********************************************************************//
34
+
31
35
  error Banny721TokenUriResolver_ArrayLengthMismatch();
32
36
  error Banny721TokenUriResolver_BannyBodyNotBodyCategory();
33
37
  error Banny721TokenUriResolver_CantAccelerateTheLock();
@@ -38,14 +42,14 @@ contract Banny721TokenUriResolver is
38
42
  error Banny721TokenUriResolver_HeadAlreadyAdded();
39
43
  error Banny721TokenUriResolver_OutfitChangesLocked();
40
44
  error Banny721TokenUriResolver_SuitAlreadyAdded();
45
+ error Banny721TokenUriResolver_UnauthorizedBackground();
41
46
  error Banny721TokenUriResolver_UnauthorizedBannyBody();
42
47
  error Banny721TokenUriResolver_UnauthorizedOutfit();
43
- error Banny721TokenUriResolver_UnauthorizedBackground();
48
+ error Banny721TokenUriResolver_UnauthorizedTransfer();
44
49
  error Banny721TokenUriResolver_UnorderedCategories();
45
- error Banny721TokenUriResolver_UnrecognizedCategory();
46
50
  error Banny721TokenUriResolver_UnrecognizedBackground();
51
+ error Banny721TokenUriResolver_UnrecognizedCategory();
47
52
  error Banny721TokenUriResolver_UnrecognizedProduct();
48
- error Banny721TokenUriResolver_UnauthorizedTransfer();
49
53
 
50
54
  //*********************************************************************//
51
55
  // ------------------------ private constants ------------------------ //
@@ -517,6 +521,39 @@ contract Banny721TokenUriResolver is
517
521
  // -------------------------- internal views ------------------------- //
518
522
  //*********************************************************************//
519
523
 
524
+ /// @notice The SVG contents for a banny body.
525
+ /// @param upc The ID of the token whose product's SVG is being returned.
526
+ /// @return contents The SVG contents of the banny body.
527
+ function _bannyBodySvgOf(uint256 upc) internal view returns (string memory) {
528
+ (
529
+ string memory b1,
530
+ string memory b2,
531
+ string memory b3,
532
+ string memory b4,
533
+ string memory a1,
534
+ string memory a2,
535
+ string memory a3
536
+ ) = _fillsFor(upc);
537
+ return string.concat(
538
+ "<style>.b1{fill:#",
539
+ b1,
540
+ ";}.b2{fill:#",
541
+ b2,
542
+ ";}.b3{fill:#",
543
+ b3,
544
+ ";}.b4{fill:#",
545
+ b4,
546
+ ";}.a1{fill:#",
547
+ a1,
548
+ ";}.a2{fill:#",
549
+ a2,
550
+ ";}.a3{fill:#",
551
+ a3,
552
+ ";}</style>",
553
+ BANNY_BODY
554
+ );
555
+ }
556
+
520
557
  /// @notice The name of each token's category.
521
558
  /// @param category The category of the token being named.
522
559
  /// @return name The token's category name.
@@ -561,11 +598,6 @@ contract Banny721TokenUriResolver is
561
598
  return "";
562
599
  }
563
600
 
564
- /// @dev ERC-2771 specifies the context as being a single address (20 bytes).
565
- function _contextSuffixLength() internal view virtual override(ERC2771Context, Context) returns (uint256) {
566
- return super._contextSuffixLength();
567
- }
568
-
569
601
  /// @notice Make sure the message sender own's the token.
570
602
  /// @param hook The 721 contract of the token having ownership checked.
571
603
  /// @param upc The product's UPC to check ownership of.
@@ -573,33 +605,9 @@ contract Banny721TokenUriResolver is
573
605
  if (IERC721(hook).ownerOf(upc) != _msgSender()) revert Banny721TokenUriResolver_UnauthorizedBannyBody();
574
606
  }
575
607
 
576
- /// @notice The fills for a product.
577
- /// @param upc The ID of the token whose product's fills are being returned.
578
- /// @return fills The fills for the product.
579
- function _fillsFor(uint256 upc)
580
- internal
581
- pure
582
- returns (
583
- string memory,
584
- string memory,
585
- string memory,
586
- string memory,
587
- string memory,
588
- string memory,
589
- string memory
590
- )
591
- {
592
- if (upc == ALIEN_UPC) {
593
- return ("67d757", "30a220", "217a15", "09490f", "e483ef", "dc2fef", "dc2fef");
594
- } else if (upc == PINK_UPC) {
595
- return ("ffd8c5", "ff96a9", "fe588b", "c92f45", "ffd8c5", "ff96a9", "fe588b");
596
- } else if (upc == ORANGE_UPC) {
597
- return ("f3a603", "ff7c02", "fd3600", "c32e0d", "f3a603", "ff7c02", "fd3600");
598
- } else if (upc == ORIGINAL_UPC) {
599
- return ("ffe900", "ffc700", "f3a603", "965a1a", "ffe900", "ffc700", "f3a603");
600
- }
601
-
602
- revert Banny721TokenUriResolver_UnrecognizedProduct();
608
+ /// @dev ERC-2771 specifies the context as being a single address (20 bytes).
609
+ function _contextSuffixLength() internal view virtual override(ERC2771Context, Context) returns (uint256) {
610
+ return super._contextSuffixLength();
603
611
  }
604
612
 
605
613
  /// @notice Encode the token URI JSON with base64.
@@ -647,6 +655,35 @@ contract Banny721TokenUriResolver is
647
655
  );
648
656
  }
649
657
 
658
+ /// @notice The fills for a product.
659
+ /// @param upc The ID of the token whose product's fills are being returned.
660
+ /// @return fills The fills for the product.
661
+ function _fillsFor(uint256 upc)
662
+ internal
663
+ pure
664
+ returns (
665
+ string memory,
666
+ string memory,
667
+ string memory,
668
+ string memory,
669
+ string memory,
670
+ string memory,
671
+ string memory
672
+ )
673
+ {
674
+ if (upc == ALIEN_UPC) {
675
+ return ("67d757", "30a220", "217a15", "09490f", "e483ef", "dc2fef", "dc2fef");
676
+ } else if (upc == PINK_UPC) {
677
+ return ("ffd8c5", "ff96a9", "fe588b", "c92f45", "ffd8c5", "ff96a9", "fe588b");
678
+ } else if (upc == ORANGE_UPC) {
679
+ return ("f3a603", "ff7c02", "fd3600", "c32e0d", "f3a603", "ff7c02", "fd3600");
680
+ } else if (upc == ORIGINAL_UPC) {
681
+ return ("ffe900", "ffc700", "f3a603", "965a1a", "ffe900", "ffc700", "f3a603");
682
+ }
683
+
684
+ revert Banny721TokenUriResolver_UnrecognizedProduct();
685
+ }
686
+
650
687
  /// @notice The full name of each product, including category and inventory.
651
688
  /// @param tokenId The ID of the token being named.
652
689
  /// @param product The product of the token being named.
@@ -744,39 +781,6 @@ contract Banny721TokenUriResolver is
744
781
  return ERC2771Context._msgSender();
745
782
  }
746
783
 
747
- /// @notice The SVG contents for a banny body.
748
- /// @param upc The ID of the token whose product's SVG is being returned.
749
- /// @return contents The SVG contents of the banny body.
750
- function _bannyBodySvgOf(uint256 upc) internal view returns (string memory) {
751
- (
752
- string memory b1,
753
- string memory b2,
754
- string memory b3,
755
- string memory b4,
756
- string memory a1,
757
- string memory a2,
758
- string memory a3
759
- ) = _fillsFor(upc);
760
- return string.concat(
761
- "<style>.b1{fill:#",
762
- b1,
763
- ";}.b2{fill:#",
764
- b2,
765
- ";}.b3{fill:#",
766
- b3,
767
- ";}.b4{fill:#",
768
- b4,
769
- ";}.a1{fill:#",
770
- a1,
771
- ";}.a2{fill:#",
772
- a2,
773
- ";}.a3{fill:#",
774
- a3,
775
- ";}</style>",
776
- BANNY_BODY
777
- );
778
- }
779
-
780
784
  /// @notice The SVG contents for a list of outfit IDs.
781
785
  /// @param hook The 721 contract that the product belongs to.
782
786
  /// @param outfitIds The IDs of the outfits that'll be associated with the specified banny.
@@ -1041,22 +1045,6 @@ contract Banny721TokenUriResolver is
1041
1045
  return IERC721Receiver.onERC721Received.selector;
1042
1046
  }
1043
1047
 
1044
- /// @notice Allows the owner to set the product's name.
1045
- /// @param upcs The universal product codes of the products having their name stored.
1046
- /// @param names The names of the products.
1047
- function setProductNames(uint256[] memory upcs, string[] memory names) external override onlyOwner {
1048
- if (upcs.length != names.length) revert Banny721TokenUriResolver_ArrayLengthMismatch();
1049
-
1050
- for (uint256 i; i < upcs.length; i++) {
1051
- uint256 upc = upcs[i];
1052
- string memory name = names[i];
1053
-
1054
- _customProductNameOf[upc] = name;
1055
-
1056
- emit SetProductName({upc: upc, name: name, caller: _msgSender()});
1057
- }
1058
- }
1059
-
1060
1048
  /// @notice Allows the owner of this contract to set the token metadata description, external URL, and SVG base URI.
1061
1049
  /// @dev All fields are always written. Pass the current value for any field you do not want to change,
1062
1050
  /// or pass an empty string to clear a field.
@@ -1079,6 +1067,22 @@ contract Banny721TokenUriResolver is
1079
1067
  emit SetMetadata({description: description, externalUrl: url, baseUri: baseUri, caller: _msgSender()});
1080
1068
  }
1081
1069
 
1070
+ /// @notice Allows the owner to set the product's name.
1071
+ /// @param upcs The universal product codes of the products having their name stored.
1072
+ /// @param names The names of the products.
1073
+ function setProductNames(uint256[] memory upcs, string[] memory names) external override onlyOwner {
1074
+ if (upcs.length != names.length) revert Banny721TokenUriResolver_ArrayLengthMismatch();
1075
+
1076
+ for (uint256 i; i < upcs.length; i++) {
1077
+ uint256 upc = upcs[i];
1078
+ string memory name = names[i];
1079
+
1080
+ _customProductNameOf[upc] = name;
1081
+
1082
+ emit SetProductName({upc: upc, name: name, caller: _msgSender()});
1083
+ }
1084
+ }
1085
+
1082
1086
  /// @notice The owner of this contract can store SVG files for product IDs.
1083
1087
  /// @param upcs The universal product codes of the products having SVGs stored.
1084
1088
  /// @param svgContents The svg contents being stored, not including the parent <svg></svg> element.
@@ -1133,6 +1137,71 @@ contract Banny721TokenUriResolver is
1133
1137
  // ---------------------- internal transactions ---------------------- //
1134
1138
  //*********************************************************************//
1135
1139
 
1140
+ /// @notice Add a background to a banny body.
1141
+ /// @param hook The hook storing the assets.
1142
+ /// @param bannyBodyId The ID of the banny body being dressed.
1143
+ /// @param backgroundId The ID of the background that'll be associated with the specified banny.
1144
+ function _decorateBannyWithBackground(address hook, uint256 bannyBodyId, uint256 backgroundId) internal {
1145
+ // Keep a reference to the previous background attached.
1146
+ uint256 previousBackgroundId = _attachedBackgroundIdOf[hook][bannyBodyId];
1147
+
1148
+ // Keep a reference to the user of the previous background.
1149
+ uint256 userOfPreviousBackground = userOf({hook: hook, backgroundId: previousBackgroundId});
1150
+
1151
+ // If the background is changing, add the latest background and transfer the old one back to the owner.
1152
+ if (backgroundId != previousBackgroundId || userOfPreviousBackground != bannyBodyId) {
1153
+ // Add the background if needed.
1154
+ if (backgroundId != 0) {
1155
+ // Keep a reference to the background's owner.
1156
+ address owner = IERC721(hook).ownerOf(backgroundId);
1157
+
1158
+ // Check if the call is being made by the background's owner, or the owner of a banny body using it.
1159
+ if (_msgSender() != owner) {
1160
+ // Get the banny body currently using this background.
1161
+ uint256 userId = userOf({hook: hook, backgroundId: backgroundId});
1162
+
1163
+ // If the background is not currently used, only the background's owner can use it for decoration.
1164
+ if (userId == 0) revert Banny721TokenUriResolver_UnauthorizedBackground();
1165
+
1166
+ // If the background is used, the banny body's owner can also authorize its use.
1167
+ if (_msgSender() != IERC721(hook).ownerOf(userId)) {
1168
+ revert Banny721TokenUriResolver_UnauthorizedBackground();
1169
+ }
1170
+ }
1171
+
1172
+ // Get the background's product info.
1173
+ JB721Tier memory backgroundProduct = _productOfTokenId({hook: hook, tokenId: backgroundId});
1174
+
1175
+ // Background must exist and must be a background category.
1176
+ if (backgroundProduct.id == 0 || backgroundProduct.category != _BACKGROUND_CATEGORY) {
1177
+ revert Banny721TokenUriResolver_UnrecognizedBackground();
1178
+ }
1179
+
1180
+ // Effects: update all state before any external transfers (CEI pattern).
1181
+ _attachedBackgroundIdOf[hook][bannyBodyId] = backgroundId;
1182
+ _userOf[hook][backgroundId] = bannyBodyId;
1183
+
1184
+ // Interactions: try-transfer the previous background back (may have been burned).
1185
+ if (userOfPreviousBackground == bannyBodyId) {
1186
+ _tryTransferFrom({hook: hook, from: address(this), to: _msgSender(), assetId: previousBackgroundId});
1187
+ }
1188
+
1189
+ // Transfer the new background to this contract if it's not already owned by this contract.
1190
+ if (owner != address(this)) {
1191
+ _transferFrom({hook: hook, from: _msgSender(), to: address(this), assetId: backgroundId});
1192
+ }
1193
+ } else {
1194
+ // Effects: clear the background state before any external transfer.
1195
+ _attachedBackgroundIdOf[hook][bannyBodyId] = 0;
1196
+
1197
+ // Interactions: try-transfer the previous background back (may have been burned).
1198
+ if (userOfPreviousBackground == bannyBodyId) {
1199
+ _tryTransferFrom({hook: hook, from: address(this), to: _msgSender(), assetId: previousBackgroundId});
1200
+ }
1201
+ }
1202
+ }
1203
+ }
1204
+
1136
1205
  /// @notice Add outfits to a banny body.
1137
1206
  /// @dev The caller must own the banny body being dressed and all outfits being worn.
1138
1207
  /// @param hook The hook storing the assets.
@@ -1282,71 +1351,6 @@ contract Banny721TokenUriResolver is
1282
1351
  _attachedOutfitIdsOf[hook][bannyBodyId] = outfitIds;
1283
1352
  }
1284
1353
 
1285
- /// @notice Add a background to a banny body.
1286
- /// @param hook The hook storing the assets.
1287
- /// @param bannyBodyId The ID of the banny body being dressed.
1288
- /// @param backgroundId The ID of the background that'll be associated with the specified banny.
1289
- function _decorateBannyWithBackground(address hook, uint256 bannyBodyId, uint256 backgroundId) internal {
1290
- // Keep a reference to the previous background attached.
1291
- uint256 previousBackgroundId = _attachedBackgroundIdOf[hook][bannyBodyId];
1292
-
1293
- // Keep a reference to the user of the previous background.
1294
- uint256 userOfPreviousBackground = userOf({hook: hook, backgroundId: previousBackgroundId});
1295
-
1296
- // If the background is changing, add the latest background and transfer the old one back to the owner.
1297
- if (backgroundId != previousBackgroundId || userOfPreviousBackground != bannyBodyId) {
1298
- // Add the background if needed.
1299
- if (backgroundId != 0) {
1300
- // Keep a reference to the background's owner.
1301
- address owner = IERC721(hook).ownerOf(backgroundId);
1302
-
1303
- // Check if the call is being made by the background's owner, or the owner of a banny body using it.
1304
- if (_msgSender() != owner) {
1305
- // Get the banny body currently using this background.
1306
- uint256 userId = userOf({hook: hook, backgroundId: backgroundId});
1307
-
1308
- // If the background is not currently used, only the background's owner can use it for decoration.
1309
- if (userId == 0) revert Banny721TokenUriResolver_UnauthorizedBackground();
1310
-
1311
- // If the background is used, the banny body's owner can also authorize its use.
1312
- if (_msgSender() != IERC721(hook).ownerOf(userId)) {
1313
- revert Banny721TokenUriResolver_UnauthorizedBackground();
1314
- }
1315
- }
1316
-
1317
- // Get the background's product info.
1318
- JB721Tier memory backgroundProduct = _productOfTokenId({hook: hook, tokenId: backgroundId});
1319
-
1320
- // Background must exist and must be a background category.
1321
- if (backgroundProduct.id == 0 || backgroundProduct.category != _BACKGROUND_CATEGORY) {
1322
- revert Banny721TokenUriResolver_UnrecognizedBackground();
1323
- }
1324
-
1325
- // Effects: update all state before any external transfers (CEI pattern).
1326
- _attachedBackgroundIdOf[hook][bannyBodyId] = backgroundId;
1327
- _userOf[hook][backgroundId] = bannyBodyId;
1328
-
1329
- // Interactions: try-transfer the previous background back (may have been burned).
1330
- if (userOfPreviousBackground == bannyBodyId) {
1331
- _tryTransferFrom({hook: hook, from: address(this), to: _msgSender(), assetId: previousBackgroundId});
1332
- }
1333
-
1334
- // Transfer the new background to this contract if it's not already owned by this contract.
1335
- if (owner != address(this)) {
1336
- _transferFrom({hook: hook, from: _msgSender(), to: address(this), assetId: backgroundId});
1337
- }
1338
- } else {
1339
- // Effects: clear the background state before any external transfer.
1340
- _attachedBackgroundIdOf[hook][bannyBodyId] = 0;
1341
-
1342
- // Interactions: try-transfer the previous background back (may have been burned).
1343
- if (userOfPreviousBackground == bannyBodyId) {
1344
- _tryTransferFrom({hook: hook, from: address(this), to: _msgSender(), assetId: previousBackgroundId});
1345
- }
1346
- }
1347
- }
1348
- }
1349
-
1350
1354
  /// @notice Transfer a token from one address to another.
1351
1355
  /// @param hook The 721 contract of the token being transferred.
1352
1356
  /// @param from The address to transfer the token from.
@@ -4,6 +4,12 @@ pragma solidity ^0.8.0;
4
4
  /// @notice Manages Banny NFT assets -- bodies, backgrounds, and outfits -- and resolves on-chain SVG token URIs for
5
5
  /// dressed Banny compositions.
6
6
  interface IBanny721TokenUriResolver {
7
+ /// @notice Emitted when a banny body is decorated with a background and outfits.
8
+ /// @param hook The hook address of the collection.
9
+ /// @param bannyBodyId The ID of the banny body that was decorated.
10
+ /// @param backgroundId The ID of the background attached.
11
+ /// @param outfitIds The IDs of the outfits attached.
12
+ /// @param caller The address that decorated the banny.
7
13
  event DecorateBanny(
8
14
  address indexed hook,
9
15
  uint256 indexed bannyBodyId,
@@ -11,33 +17,48 @@ interface IBanny721TokenUriResolver {
11
17
  uint256[] outfitIds,
12
18
  address caller
13
19
  );
14
- event SetMetadata(string description, string externalUrl, string baseUri, address caller);
15
- event SetProductName(uint256 indexed upc, string name, address caller);
16
- event SetSvgContent(uint256 indexed upc, string svgContent, address caller);
17
- event SetSvgHash(uint256 indexed upc, bytes32 indexed svgHash, address caller);
18
20
 
19
- /// @notice The stored SVG content hash for a given product.
20
- /// @param upc The universal product code to look up.
21
- /// @return The SVG content hash.
22
- function svgHashOf(uint256 upc) external view returns (bytes32);
21
+ /// @notice Emitted when metadata (description, external URL, base URI) is updated.
22
+ /// @param description The new description.
23
+ /// @param externalUrl The new external URL.
24
+ /// @param baseUri The new base URI.
25
+ /// @param caller The address that updated the metadata.
26
+ event SetMetadata(string description, string externalUrl, string baseUri, address caller);
23
27
 
24
- /// @notice The base URI used to lazily resolve SVG content from IPFS.
25
- /// @return The base URI string.
26
- function svgBaseUri() external view returns (string memory);
28
+ /// @notice Emitted when a product name is set.
29
+ /// @param upc The universal product code.
30
+ /// @param name The name assigned to the product.
31
+ /// @param caller The address that set the name.
32
+ event SetProductName(uint256 indexed upc, string name, address caller);
27
33
 
28
- /// @notice The description used in token metadata.
29
- /// @return The description string.
30
- function svgDescription() external view returns (string memory);
34
+ /// @notice Emitted when SVG content is stored for a product.
35
+ /// @param upc The universal product code.
36
+ /// @param svgContent The SVG content that was stored.
37
+ /// @param caller The address that stored the content.
38
+ event SetSvgContent(uint256 indexed upc, string svgContent, address caller);
31
39
 
32
- /// @notice The external URL used in token metadata.
33
- /// @return The external URL string.
34
- function svgExternalUrl() external view returns (string memory);
40
+ /// @notice Emitted when an SVG content hash is stored for a product.
41
+ /// @param upc The universal product code.
42
+ /// @param svgHash The SVG content hash that was stored.
43
+ /// @param caller The address that stored the hash.
44
+ event SetSvgHash(uint256 indexed upc, bytes32 indexed svgHash, address caller);
35
45
 
36
- /// @notice The timestamp until which a banny body's outfit is locked and cannot be changed.
46
+ /// @notice The background and outfit IDs currently attached to a banny body.
37
47
  /// @param hook The hook address of the collection.
38
- /// @param upc The ID of the banny body.
39
- /// @return The lock expiration timestamp, or 0 if not locked.
40
- function outfitLockedUntil(address hook, uint256 upc) external view returns (uint256);
48
+ /// @param bannyBodyId The ID of the banny body.
49
+ /// @return backgroundId The ID of the attached background.
50
+ /// @return outfitIds The IDs of the attached outfits.
51
+ function assetIdsOf(
52
+ address hook,
53
+ uint256 bannyBodyId
54
+ )
55
+ external
56
+ view
57
+ returns (uint256 backgroundId, uint256[] memory outfitIds);
58
+
59
+ /// @notice The base SVG content for a banny body.
60
+ /// @return The SVG string.
61
+ function BANNY_BODY() external view returns (string memory);
41
62
 
42
63
  /// @notice The default SVG content for alien banny eyes.
43
64
  /// @return The SVG string.
@@ -55,34 +76,34 @@ interface IBanny721TokenUriResolver {
55
76
  /// @return The SVG string.
56
77
  function DEFAULT_STANDARD_EYES() external view returns (string memory);
57
78
 
58
- /// @notice The base SVG content for a banny body.
59
- /// @return The SVG string.
60
- function BANNY_BODY() external view returns (string memory);
61
-
62
- /// @notice The background and outfit IDs currently attached to a banny body.
79
+ /// @notice Get the names associated with a token (product name, category name, and display name).
63
80
  /// @param hook The hook address of the collection.
64
- /// @param bannyBodyId The ID of the banny body.
65
- /// @return backgroundId The ID of the attached background.
66
- /// @return outfitIds The IDs of the attached outfits.
67
- function assetIdsOf(
68
- address hook,
69
- uint256 bannyBodyId
70
- )
71
- external
72
- view
73
- returns (uint256 backgroundId, uint256[] memory outfitIds);
81
+ /// @param tokenId The token ID to look up.
82
+ /// @return The product name, the category name, and the display name.
83
+ function namesOf(address hook, uint256 tokenId) external view returns (string memory, string memory, string memory);
74
84
 
75
- /// @notice The banny body ID that is currently using a given background.
85
+ /// @notice The timestamp until which a banny body's outfit is locked and cannot be changed.
76
86
  /// @param hook The hook address of the collection.
77
- /// @param backgroundId The ID of the background.
78
- /// @return The banny body ID using the background, or 0 if none.
79
- function userOf(address hook, uint256 backgroundId) external view returns (uint256);
87
+ /// @param upc The ID of the banny body.
88
+ /// @return The lock expiration timestamp, or 0 if not locked.
89
+ function outfitLockedUntil(address hook, uint256 upc) external view returns (uint256);
80
90
 
81
- /// @notice The banny body ID that is currently wearing a given outfit.
82
- /// @param hook The hook address of the collection.
83
- /// @param outfitId The ID of the outfit.
84
- /// @return The banny body ID wearing the outfit, or 0 if none.
85
- function wearerOf(address hook, uint256 outfitId) external view returns (uint256);
91
+ /// @notice The base URI used to lazily resolve SVG content from IPFS.
92
+ /// @return The base URI string.
93
+ function svgBaseUri() external view returns (string memory);
94
+
95
+ /// @notice The description used in token metadata.
96
+ /// @return The description string.
97
+ function svgDescription() external view returns (string memory);
98
+
99
+ /// @notice The external URL used in token metadata.
100
+ /// @return The external URL string.
101
+ function svgExternalUrl() external view returns (string memory);
102
+
103
+ /// @notice The stored SVG content hash for a given product.
104
+ /// @param upc The universal product code to look up.
105
+ /// @return The SVG content hash.
106
+ function svgHashOf(uint256 upc) external view returns (bytes32);
86
107
 
87
108
  /// @notice Get the composed SVG for a token, optionally dressed and with a background.
88
109
  /// @param hook The hook address of the collection.
@@ -100,11 +121,17 @@ interface IBanny721TokenUriResolver {
100
121
  view
101
122
  returns (string memory);
102
123
 
103
- /// @notice Get the names associated with a token (product name, category name, and display name).
124
+ /// @notice The banny body ID that is currently using a given background.
104
125
  /// @param hook The hook address of the collection.
105
- /// @param tokenId The token ID to look up.
106
- /// @return The product name, the category name, and the display name.
107
- function namesOf(address hook, uint256 tokenId) external view returns (string memory, string memory, string memory);
126
+ /// @param backgroundId The ID of the background.
127
+ /// @return The banny body ID using the background, or 0 if none.
128
+ function userOf(address hook, uint256 backgroundId) external view returns (uint256);
129
+
130
+ /// @notice The banny body ID that is currently wearing a given outfit.
131
+ /// @param hook The hook address of the collection.
132
+ /// @param outfitId The ID of the outfit.
133
+ /// @return The banny body ID wearing the outfit, or 0 if none.
134
+ function wearerOf(address hook, uint256 outfitId) external view returns (uint256);
108
135
 
109
136
  /// @notice Dress a banny body with a background and outfits.
110
137
  /// @param hook The hook address of the collection.
@@ -124,6 +151,18 @@ interface IBanny721TokenUriResolver {
124
151
  /// @param bannyBodyId The ID of the banny body to lock.
125
152
  function lockOutfitChangesFor(address hook, uint256 bannyBodyId) external;
126
153
 
154
+ /// @notice Set the token metadata description, external URL, and SVG base URI. Only the contract owner can call
155
+ /// this.
156
+ /// @param description The new description.
157
+ /// @param url The new external URL.
158
+ /// @param baseUri The new base URI.
159
+ function setMetadata(string calldata description, string calldata url, string calldata baseUri) external;
160
+
161
+ /// @notice Set custom display names for products. Only the contract owner can call this.
162
+ /// @param upcs The universal product codes to set names for.
163
+ /// @param names The names to assign to each product.
164
+ function setProductNames(uint256[] memory upcs, string[] memory names) external;
165
+
127
166
  /// @notice Store SVG contents for products, validated against previously stored hashes.
128
167
  /// @param upcs The universal product codes to store SVG contents for.
129
168
  /// @param svgContents The SVG contents to store (must match stored hashes).
@@ -133,16 +172,4 @@ interface IBanny721TokenUriResolver {
133
172
  /// @param upcs The universal product codes to store SVG hashes for.
134
173
  /// @param svgHashes The SVG content hashes to store.
135
174
  function setSvgHashesOf(uint256[] memory upcs, bytes32[] memory svgHashes) external;
136
-
137
- /// @notice Set custom display names for products. Only the contract owner can call this.
138
- /// @param upcs The universal product codes to set names for.
139
- /// @param names The names to assign to each product.
140
- function setProductNames(uint256[] memory upcs, string[] memory names) external;
141
-
142
- /// @notice Set the token metadata description, external URL, and SVG base URI. Only the contract owner can call
143
- /// this.
144
- /// @param description The new description.
145
- /// @param url The new external URL.
146
- /// @param baseUri The new base URI.
147
- function setMetadata(string calldata description, string calldata url, string calldata baseUri) external;
148
175
  }