1+ // SPDX-License-Identifier: MIT
2+ pragma solidity >= 0.8.30 ;
3+
4+
5+ /// @notice Interface for contracts that want to support safeTransfers.
6+ interface IERC721Receiver {
7+ function onERC721Received (address _operator , address _from , uint256 _tokenId , bytes calldata _data ) external returns (bytes4 );
8+ }
9+
10+
11+ /// @title ERC-721 token (zero-dependency implementation)
12+ /// @notice A complete, dependency-free ERC-721 implementation using the project's storage pattern.
13+ /// @dev This contract provides metadata, ownership, approvals, safe transfers (with local IERC721Receiver check),
14+ /// minting, burning, and helpers. It intentionally avoids external imports.
15+ contract ERC721Facet {
16+
17+ // ERC-6093: Custom errors for ERC-721
18+ error ERC721InvalidOwner (address _owner );
19+ error ERC721NonexistentToken (uint256 _tokenId );
20+ error ERC721IncorrectOwner (address _sender , uint256 _tokenId , address _owner );
21+ error ERC721InvalidSender (address _sender );
22+ error ERC721InvalidReceiver (address _receiver );
23+ error ERC721InsufficientApproval (address _operator , uint256 _tokenId );
24+ error ERC721InvalidApprover (address _approver );
25+ error ERC721InvalidOperator (address _operator );
26+
27+ event Transfer (address indexed _from , address indexed _to , uint256 indexed _tokenId );
28+ event Approval (address indexed _owner , address indexed _approved , uint256 indexed _tokenId );
29+ event ApprovalForAll (address indexed _owner , address indexed _operator , bool _approved );
30+
31+ // Struct storage position defined by keccak256 hash
32+ // of diamond storage identifier
33+ bytes32 constant STORAGE_POSITION = keccak256 ("compose.erc721 " );
34+
35+ // Storage defined using the ERC-8042 standard
36+ // @custom:storage-location erc8042:compose.erc721
37+ struct ERC721Storage {
38+ string name;
39+ string symbol;
40+ mapping (uint256 tokenId = > string tokenURI ) tokenURIOf;
41+ mapping (uint256 tokenId = > address owner ) ownerOf;
42+ mapping (address owner = > uint256 balance ) balanceOf;
43+ mapping (uint256 tokenId = > address approved ) approved;
44+ mapping (address owner = > mapping (address operator = > bool approved )) isApprovedForAll;
45+ }
46+
47+ function getStorage () internal pure returns (ERC721Storage storage s ) {
48+ bytes32 position = STORAGE_POSITION;
49+ assembly {
50+ s.slot := position
51+ }
52+ }
53+
54+ function name () external view returns (string memory ) {
55+ return getStorage ().name;
56+ }
57+
58+ function symbol () external view returns (string memory ) {
59+ return getStorage ().symbol;
60+ }
61+
62+ function balanceOf (address _owner ) external view returns (uint256 ) {
63+ if (_owner == address (0 )) {
64+ revert ERC721InvalidOwner (_owner);
65+ }
66+ return getStorage ().balanceOf[_owner];
67+ }
68+
69+ function ownerOf (uint256 _tokenId ) public view returns (address ) {
70+ address owner = getStorage ().ownerOf[_tokenId];
71+ if (owner == address (0 )) {
72+ revert ERC721NonexistentToken (_tokenId);
73+ }
74+ return owner;
75+ }
76+
77+ function getApproved (uint256 _tokenId ) external view returns (address ) {
78+ address owner = getStorage ().ownerOf[_tokenId];
79+ if (owner == address (0 )) {
80+ revert ERC721NonexistentToken (_tokenId);
81+ }
82+ return getStorage ().approved[_tokenId];
83+ }
84+
85+ function isApprovedForAll (address _owner , address _operator ) external view returns (bool ) {
86+ return getStorage ().isApprovedForAll[_owner][_operator];
87+ }
88+
89+ function approve (address _approved , uint256 _tokenId ) external {
90+ ERC721Storage storage s = getStorage ();
91+ address owner = s.ownerOf[_tokenId];
92+ if (owner == address (0 )) {
93+ revert ERC721NonexistentToken (_tokenId);
94+ }
95+ if (msg .sender != owner && ! s.isApprovedForAll[owner][msg .sender ]) {
96+ revert ERC721InvalidApprover (_approved);
97+ }
98+ s.approved[_tokenId] = _approved;
99+ emit Approval (owner, _approved, _tokenId);
100+ }
101+
102+ function setApprovalForAll (address _operator , bool _approved ) external {
103+ if (_operator == address (0 )) {
104+ revert ERC721InvalidOperator (_operator);
105+ }
106+ getStorage ().isApprovedForAll[msg .sender ][_operator] = _approved;
107+ emit ApprovalForAll (msg .sender , _operator, _approved);
108+ }
109+
110+ function internalTransferFrom (address _from , address _to , uint256 _tokenId ) internal {
111+ ERC721Storage storage s = getStorage ();
112+ if (_to == address (0 )) {
113+ revert ERC721InvalidReceiver (address (0 ));
114+ }
115+ address owner = s.ownerOf[_tokenId];
116+ if (owner == address (0 )) {
117+ revert ERC721NonexistentToken (_tokenId);
118+ }
119+ if (owner != _from) {
120+ revert ERC721IncorrectOwner (_from, _tokenId, owner);
121+ }
122+ if (msg .sender != _from) {
123+ if (! s.isApprovedForAll[_from][msg .sender ] && msg .sender != s.approved[_tokenId]) {
124+ revert ERC721InsufficientApproval (msg .sender , _tokenId);
125+ }
126+ }
127+ delete s.approved[_tokenId];
128+ unchecked {
129+ s.balanceOf[_from]-- ;
130+ s.balanceOf[_to]++ ;
131+ }
132+ s.ownerOf[_tokenId] = _to;
133+ emit Transfer (_from, _to, _tokenId);
134+ }
135+
136+ function transferFrom (address _from , address _to , uint256 _tokenId ) external {
137+ internalTransferFrom (_from, _to, _tokenId);
138+ }
139+
140+ function safeTransferFrom (address _from , address _to , uint256 _tokenId ) external {
141+ internalTransferFrom (_from, _to, _tokenId);
142+
143+ // If _to is a contract, check for IERC721Receiver implementation
144+ if (_to.code.length > 0 ) {
145+ try IERC721Receiver (_to).onERC721Received (msg .sender , _from, _tokenId, "" ) returns (bytes4 retval ) {
146+ if (retval != IERC721Receiver .onERC721Received.selector ) {
147+ revert ERC721InvalidReceiver (_to);
148+ }
149+ } catch (bytes memory reason ) {
150+ if (reason.length == 0 ) {
151+ // non-IERC721Receiver implementer
152+ revert ERC721InvalidReceiver (_to);
153+ } else {
154+ // Return the revert reason
155+ // "memory-safe" means we used memory safely so Solidity does not disable optimizations
156+ assembly ("memory-safe" ) {
157+ revert (add (reason, 0x20 ), mload (reason))
158+ }
159+ }
160+ }
161+ }
162+ }
163+
164+ function safeTransferFrom (address _from , address _to , uint256 _tokenId , bytes calldata _data ) external {
165+ internalTransferFrom (_from, _to, _tokenId);
166+ // If _to is a contract, check for IERC721Receiver implementation
167+ if (_to.code.length > 0 ) {
168+ try IERC721Receiver (_to).onERC721Received (msg .sender , _from, _tokenId, _data) returns (bytes4 retval ) {
169+ if (retval != IERC721Receiver .onERC721Received.selector ) {
170+ revert ERC721InvalidReceiver (_to);
171+ }
172+ } catch (bytes memory reason ) {
173+ if (reason.length == 0 ) {
174+ // non-IERC721Receiver implementer
175+ revert ERC721InvalidReceiver (_to);
176+ } else {
177+ // Return the revert reason
178+ // "memory-safe" means we used memory safely so Solidity does not disable optimizations
179+ assembly ("memory-safe" ) {
180+ revert (add (reason, 0x20 ), mload (reason))
181+ }
182+ }
183+ }
184+ }
185+ }
186+ }
0 commit comments