-
Notifications
You must be signed in to change notification settings - Fork 25
Expand file tree
/
Copy pathERC721EthscriptionsCollection.sol
More file actions
169 lines (140 loc) · 5.91 KB
/
Copy pathERC721EthscriptionsCollection.sol
File metadata and controls
169 lines (140 loc) · 5.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import "./ERC721EthscriptionsEnumerableUpgradeable.sol";
import "./Ethscriptions.sol";
import "./libraries/Predeploys.sol";
import {LibString} from "solady/utils/LibString.sol";
import {Base64} from "solady/utils/Base64.sol";
import "./ERC721EthscriptionsCollectionManager.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
/// @title ERC721EthscriptionsCollection
/// @notice Thin ERC-721 wrapper for Ethscription collections where the manager controls mint/burn
contract ERC721EthscriptionsCollection is ERC721EthscriptionsEnumerableUpgradeable, OwnableUpgradeable {
using LibString for *;
Ethscriptions public constant ethscriptions = Ethscriptions(Predeploys.ETHSCRIPTIONS);
/// @notice Factory (manager) that deployed this contract
address public factory;
// Events
event MemberAdded(bytes32 indexed ethscriptionId, uint256 indexed tokenId);
event MemberRemoved(bytes32 indexed ethscriptionId, uint256 indexed tokenId);
// Errors
error NotFactory();
error UnknownCollection();
error TransferNotAllowed();
modifier onlyFactory() {
if (msg.sender != factory) revert NotFactory();
_;
}
function initialize(
string memory name_,
string memory symbol_,
address initialOwner_
) external initializer {
__ERC721_init(name_, symbol_);
__Ownable_init(initialOwner_);
factory = msg.sender;
}
/// @notice Lookup collection id via the factory registry
function collectionId() public view returns (bytes32) {
ERC721EthscriptionsCollectionManager manager = ERC721EthscriptionsCollectionManager(factory);
bytes32 id = manager.collectionIdForAddress(address(this));
if (id == bytes32(0)) revert UnknownCollection();
return id;
}
function addMember(bytes32 ethscriptionId, uint256 tokenId) external onlyFactory {
address owner = ethscriptions.ownerOf(ethscriptionId);
_mint(owner, tokenId);
emit MemberAdded(ethscriptionId, tokenId);
}
function removeMember(bytes32 ethscriptionId, uint256 tokenId) external onlyFactory {
require(_tokenExists(tokenId), "Token does not exist");
address owner = ownerOf(tokenId);
// Mark token as non-existent (handles enumeration cleanup)
_setTokenExists(tokenId, false);
// Emit burn-style transfer for indexers
emit Transfer(owner, address(0), tokenId);
emit MemberRemoved(ethscriptionId, tokenId);
}
/// @notice Called by the manager to mirror Ethscription transfers
function forceTransfer(address from, address to, uint256 tokenId) external onlyFactory {
require(_ownerOf(tokenId) == from, "Unexpected owner");
_transfer(from, to, tokenId);
}
/// @notice Let the manager update Ownable owner to match inscription holder
function factoryTransferOwnership(address newOwner) external onlyFactory {
_transferOwnership(newOwner);
}
function tokenURI(uint256 tokenId)
public
view
override(ERC721EthscriptionsUpgradeable)
returns (string memory)
{
if (!_tokenExists(tokenId)) revert("Token does not exist");
ERC721EthscriptionsCollectionManager manager = ERC721EthscriptionsCollectionManager(factory);
ERC721EthscriptionsCollectionManager.CollectionItem memory item =
manager.getCollectionItem(collectionId(), tokenId);
if (item.ethscriptionId == bytes32(0)) revert("Token not in collection");
(string memory mediaType, string memory mediaUri) = ethscriptions.getMediaUri(item.ethscriptionId);
string memory jsonStart = string.concat('{"name":"', item.name.escapeJSON(), '"');
if (bytes(item.description).length > 0) {
jsonStart = string.concat(jsonStart, ',"description":"', item.description.escapeJSON(), '"');
}
string memory mediaField = string.concat(
',"',
mediaType,
'":"',
mediaUri.escapeJSON(),
'"'
);
string memory bgColor = "";
if (bytes(item.backgroundColor).length > 0) {
bgColor = string.concat(',"background_color":"', item.backgroundColor.escapeJSON(), '"');
}
string memory attributesJson = ',"attributes":[';
for (uint256 i = 0; i < item.attributes.length; i++) {
if (i > 0) attributesJson = string.concat(attributesJson, ',');
attributesJson = string.concat(
attributesJson,
'{"trait_type":"',
item.attributes[i].traitType.escapeJSON(),
'","value":"',
item.attributes[i].value.escapeJSON(),
'"}'
);
}
attributesJson = string.concat(attributesJson, ']');
string memory json = string.concat(jsonStart, mediaField, bgColor, attributesJson, '}');
return string.concat("data:application/json;base64,", Base64.encode(bytes(json)));
}
// --- Transfer/approvals blocked externally ---------------------------------
function transferFrom(address, address, uint256)
public
pure
override(ERC721EthscriptionsUpgradeable, IERC721)
{
revert TransferNotAllowed();
}
function safeTransferFrom(address, address, uint256, bytes memory)
public
pure
override(ERC721EthscriptionsUpgradeable, IERC721)
{
revert TransferNotAllowed();
}
function approve(address, uint256)
public
pure
override(ERC721EthscriptionsUpgradeable, IERC721)
{
revert TransferNotAllowed();
}
function setApprovalForAll(address, bool)
public
pure
override(ERC721EthscriptionsUpgradeable, IERC721)
{
revert TransferNotAllowed();
}
}