@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 +1 -1
- package/package.json +5 -5
- package/src/Banny721TokenUriResolver.sol +160 -156
- package/src/interfaces/IBanny721TokenUriResolver.sol +88 -61
package/foundry.toml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bannynet/core-v6",
|
|
3
|
-
"version": "0.0.
|
|
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
|
|
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.
|
|
24
|
+
"@bananapus/core-v6": "^0.0.10",
|
|
25
25
|
"@bananapus/router-terminal-v6": "^0.0.6",
|
|
26
|
-
"@bananapus/suckers-v6": "^0.0.
|
|
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.
|
|
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
|
|
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
|
-
/// @
|
|
577
|
-
|
|
578
|
-
|
|
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
|
|
20
|
-
/// @param
|
|
21
|
-
/// @
|
|
22
|
-
|
|
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
|
|
25
|
-
/// @
|
|
26
|
-
|
|
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
|
|
29
|
-
/// @
|
|
30
|
-
|
|
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
|
|
33
|
-
/// @
|
|
34
|
-
|
|
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
|
|
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
|
|
39
|
-
/// @return The
|
|
40
|
-
|
|
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
|
|
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
|
|
65
|
-
/// @return
|
|
66
|
-
|
|
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
|
|
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
|
|
78
|
-
/// @return The
|
|
79
|
-
function
|
|
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
|
|
82
|
-
/// @
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
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
|
|
106
|
-
/// @return The
|
|
107
|
-
function
|
|
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
|
}
|